From b8bdfccebe1adee3cf4270ef5066b302ad493990 Mon Sep 17 00:00:00 2001 From: Matt Feddersen Date: Thu, 28 Mar 2024 19:21:54 -0700 Subject: [PATCH] Source release 19.1.0 --- CHANGELOG.md | 33 + README.md | 34 +- cdm/cdm_reboot_tests.gyp | 7 - cdm/cdm_unittests.gyp | 9 +- cdm/include/cdm.h | 1 + cdm/include/cdm_version.h | 4 +- cdm/install_keybox_tool.gyp | 90 + cdm/src/cdm.cpp | 4 +- cdm/src/log.cpp | 4 +- cdm/src/properties_ce.cpp | 3 + cdm/test/cdm_test.cpp | 8 +- core/include/cdm_engine.h | 6 - core/include/cdm_session.h | 7 +- core/include/certificate_provisioning.h | 1 + core/include/content_key_session.h | 7 - core/include/crypto_session.h | 33 +- core/include/key_session.h | 3 - core/include/license.h | 24 +- core/include/oemcrypto_adapter.h | 7 +- core/include/policy_engine.h | 7 - core/include/privacy_crypto.h | 1 + core/include/properties.h | 2 + core/include/wv_cdm_types.h | 2 - core/src/cdm_engine.cpp | 33 +- core/src/cdm_session.cpp | 100 +- core/src/certificate_provisioning.cpp | 40 +- core/src/client_identification.cpp | 26 +- core/src/content_key_session.cpp | 53 - core/src/crypto_session.cpp | 186 +- core/src/entitlement_key_session.cpp | 2 +- core/src/license.cpp | 102 +- core/src/license_protocol.proto | 3 + core/src/oemcrypto_adapter_static.cpp | 18 +- core/src/policy_engine.cpp | 7 +- core/src/privacy_crypto_apple.cpp | 6 + core/src/privacy_crypto_boringssl.cpp | 7 + core/src/privacy_crypto_dummy.cpp | 8 + core/src/wv_cdm_types.cpp | 4 - .../cdm_engine_metrics_decorator_unittest.cpp | 4 +- core/test/cdm_usage_table_unittest.cpp | 16 +- .../certificate_provisioning_unittest.cpp | 4 +- core/test/core_integration_test.cpp | 2 +- core/test/device_files_unittest.cpp | 16 +- core/test/duration_use_case_test.cpp | 4 - core/test/fake_provisioning_server.cpp | 12 +- core/test/http_socket.cpp | 4 + core/test/license_holder.cpp | 65 +- core/test/license_holder.h | 14 +- core/test/license_unittest.cpp | 87 +- core/test/message_dumper.cpp | 7 +- core/test/parallel_operations_test.cpp | 2 +- core/test/policy_engine_unittest.cpp | 17 +- core/test/policy_integration_test.cpp | 110 +- core/test/provisioning_holder.cpp | 1 + core/test/test_base.cpp | 132 +- core/test/test_base.h | 36 +- core/test/test_printers.cpp | 17 + core/test/test_printers.h | 6 + core/test/url_request.cpp | 2 +- factory_upload_tool/ce/log.cpp | 4 +- oemcrypto/include/OEMCryptoCENC.h | 882 ++++++--- oemcrypto/odk/Android.bp | 5 +- oemcrypto/odk/include/OEMCryptoCENCCommon.h | 2 +- .../odk/include/core_message_deserialize.h | 12 +- oemcrypto/odk/include/core_message_features.h | 8 +- .../odk/include/core_message_serialize.h | 21 +- .../include/core_message_serialize_proto.h | 2 +- oemcrypto/odk/include/core_message_types.h | 13 +- oemcrypto/odk/include/odk.h | 169 +- oemcrypto/odk/include/odk_attributes.h | 2 +- oemcrypto/odk/include/odk_message.h | 2 +- oemcrypto/odk/include/odk_structs.h | 22 +- oemcrypto/odk/include/odk_target.h | 2 +- .../odk/src/core_message_deserialize.cpp | 20 +- oemcrypto/odk/src/core_message_features.cpp | 7 +- oemcrypto/odk/src/core_message_serialize.cpp | 20 +- .../odk/src/core_message_serialize_proto.cpp | 17 +- oemcrypto/odk/src/kdo.gypi | 2 +- oemcrypto/odk/src/odk.c | 135 +- oemcrypto/odk/src/odk.gyp | 2 +- oemcrypto/odk/src/odk.gypi | 2 +- oemcrypto/odk/src/odk_assert.h | 2 +- oemcrypto/odk/src/odk_endian.h | 2 +- oemcrypto/odk/src/odk_message.c | 2 +- oemcrypto/odk/src/odk_message_priv.h | 2 +- oemcrypto/odk/src/odk_overflow.c | 2 +- oemcrypto/odk/src/odk_overflow.h | 2 +- oemcrypto/odk/src/odk_serialize.c | 25 +- oemcrypto/odk/src/odk_serialize.h | 8 +- oemcrypto/odk/src/odk_structs_priv.h | 15 +- oemcrypto/odk/src/odk_timer.c | 7 +- oemcrypto/odk/src/odk_util.c | 2 +- oemcrypto/odk/src/odk_util.h | 2 +- oemcrypto/odk/src/serialization_base.c | 2 +- oemcrypto/odk/src/serialization_base.h | 2 +- oemcrypto/odk/test/fuzzing/Android.bp | 2 +- .../test/fuzzing/corpus_generator/Android.bp | 2 +- .../corpus_generator/odk_corpus_generator.c | 2 +- .../odk_corpus_generator_helper.c | 2 +- .../odk_corpus_generator_helper.h | 2 +- .../odk_fuzz_corpus_generator.gyp | 2 +- oemcrypto/odk/test/fuzzing/odk_fuzz.gyp | 2 +- .../odk/test/fuzzing/odk_fuzz_helper.cpp | 2 +- oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h | 2 +- oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h | 2 +- .../test/fuzzing/odk_license_request_fuzz.cpp | 2 +- .../fuzzing/odk_license_response_fuzz.cpp | 2 +- ...odk_license_response_fuzz_with_mutator.cpp | 2 +- .../fuzzing/odk_provisioning_request_fuzz.cpp | 2 +- .../odk_provisioning_response_fuzz.cpp | 2 +- ...rovisioning_response_fuzz_with_mutator.cpp | 2 +- .../test/fuzzing/odk_renewal_request_fuzz.cpp | 2 +- .../fuzzing/odk_renewal_response_fuzz.cpp | 2 +- ...odk_renewal_response_fuzz_with_mutator.cpp | 2 +- oemcrypto/odk/test/odk_core_message_test.cpp | 5 +- oemcrypto/odk/test/odk_golden_v16.cpp | 4 +- oemcrypto/odk/test/odk_golden_v17.cpp | 4 +- oemcrypto/odk/test/odk_golden_v18.cpp | 4 +- oemcrypto/odk/test/odk_test.cpp | 199 +- oemcrypto/odk/test/odk_test.gypi | 2 +- oemcrypto/odk/test/odk_test_helper.cpp | 48 +- oemcrypto/odk/test/odk_test_helper.h | 17 +- oemcrypto/odk/test/odk_timer_test.cpp | 2 +- oemcrypto/test/GEN_api_lock_file.c | 58 +- oemcrypto/test/fuzz_tests/README.md | 327 ++-- .../test/fuzz_tests/clusterfuzz_setup.md | 171 ++ .../fuzz_tests/oemcrypto_decrypt_hash_fuzz.cc | 5 +- .../test/fuzz_tests/oemcrypto_fuzztests.gypi | 2 +- .../fuzz_tests/oemcrypto_opk_fuzztests.gypi | 2 +- .../partner_oemcrypto_fuzztests.gypi | 2 +- oemcrypto/test/install_keybox_tool.cpp | 233 +++ oemcrypto/test/oec_device_features.cpp | 22 - oemcrypto/test/oec_device_features.h | 7 +- oemcrypto/test/oec_key_deriver.cpp | 34 +- oemcrypto/test/oec_key_deriver.h | 6 +- oemcrypto/test/oec_session_util.cpp | 330 ++-- oemcrypto/test/oec_session_util.h | 91 +- oemcrypto/test/oemcrypto_basic_test.cpp | 52 +- oemcrypto/test/oemcrypto_cast_test.cpp | 26 +- oemcrypto/test/oemcrypto_cast_test.h | 23 - oemcrypto/test/oemcrypto_decrypt_test.cpp | 23 +- oemcrypto/test/oemcrypto_decrypt_test.h | 8 +- .../test/oemcrypto_generic_crypto_test.cpp | 55 +- oemcrypto/test/oemcrypto_license_test.cpp | 13 +- oemcrypto/test/oemcrypto_license_test.h | 13 +- .../test/oemcrypto_provisioning_test.cpp | 243 +-- oemcrypto/test/oemcrypto_security_test.cpp | 9 +- oemcrypto/test/oemcrypto_test.cpp | 516 ++--- oemcrypto/test/oemcrypto_test_android.cpp | 106 +- oemcrypto/test/oemcrypto_test_main.cpp | 10 - oemcrypto/test/oemcrypto_usage_table_test.cpp | 53 + oemcrypto/test/ota_keybox_test.cpp | 35 +- oemcrypto/util/include/bcc_validator.h | 81 + oemcrypto/util/include/cbor_validator.h | 86 + .../util/include/device_info_validator.h | 54 + oemcrypto/util/include/oemcrypto_ecc_key.h | 23 + .../include/signed_csr_payload_validator.h | 44 + oemcrypto/util/oec_ref_util.gypi | 8 + oemcrypto/util/oec_ref_util_unittests.gypi | 6 + oemcrypto/util/src/bcc_validator.cpp | 564 ++++++ oemcrypto/util/src/cbor_validator.cpp | 140 ++ oemcrypto/util/src/device_info_validator.cpp | 227 +++ oemcrypto/util/src/oemcrypto_ecc_key.cpp | 163 +- .../util/src/signed_csr_payload_validator.cpp | 393 ++++ .../util/test/bcc_validator_unittest.cpp | 147 ++ .../test/device_info_validator_unittest.cpp | 204 ++ .../signed_csr_payload_validator_unittest.cpp | 294 +++ .../example-runtime-client-info/settings.gypi | 4 +- platforms/example/no_oemcrypto.cpp | 91 +- platforms/example/settings.gypi | 4 +- third_party/libcppbor.gyp | 22 + third_party/libcppbor/Android.bp | 95 + third_party/libcppbor/LICENSE | 202 ++ third_party/libcppbor/METADATA | 3 + third_party/libcppbor/README.md | 226 +++ third_party/libcppbor/include/cppbor/cppbor.h | 1127 +++++++++++ .../libcppbor/include/cppbor/cppbor_parse.h | 150 ++ third_party/libcppbor/src/cppbor.cpp | 567 ++++++ third_party/libcppbor/src/cppbor_parse.cpp | 380 ++++ third_party/libcppbor/tests/cppbor_test.cpp | 1689 +++++++++++++++++ util/include/wv_attributes.h | 8 + util/test/test_sleep.cpp | 9 +- 182 files changed, 10645 insertions(+), 2040 deletions(-) create mode 100644 cdm/install_keybox_tool.gyp create mode 100644 oemcrypto/test/fuzz_tests/clusterfuzz_setup.md create mode 100644 oemcrypto/test/install_keybox_tool.cpp create mode 100644 oemcrypto/util/include/bcc_validator.h create mode 100644 oemcrypto/util/include/cbor_validator.h create mode 100644 oemcrypto/util/include/device_info_validator.h create mode 100644 oemcrypto/util/include/signed_csr_payload_validator.h create mode 100644 oemcrypto/util/src/bcc_validator.cpp create mode 100644 oemcrypto/util/src/cbor_validator.cpp create mode 100644 oemcrypto/util/src/device_info_validator.cpp create mode 100644 oemcrypto/util/src/signed_csr_payload_validator.cpp create mode 100644 oemcrypto/util/test/bcc_validator_unittest.cpp create mode 100644 oemcrypto/util/test/device_info_validator_unittest.cpp create mode 100644 oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp create mode 100644 third_party/libcppbor.gyp create mode 100644 third_party/libcppbor/Android.bp create mode 100644 third_party/libcppbor/LICENSE create mode 100644 third_party/libcppbor/METADATA create mode 100644 third_party/libcppbor/README.md create mode 100644 third_party/libcppbor/include/cppbor/cppbor.h create mode 100644 third_party/libcppbor/include/cppbor/cppbor_parse.h create mode 100644 third_party/libcppbor/src/cppbor.cpp create mode 100644 third_party/libcppbor/src/cppbor_parse.cpp create mode 100644 third_party/libcppbor/tests/cppbor_test.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 92894eaa..88003ede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,39 @@ [TOC] +## 19.1.0 (2024-03-28) + +Note: Release v19.0 was skipped so that this release's version number matches +the OEMCrypto version. + +CE CDM v19.1.0 includes all changes from CE CDM v18.5.0. + +## Features + + - Supports up to OEMCrypto v19.1, including new OEMCrypto tests introduced + since OEMCrypto v18. + - Added support for License Protocol v2.2, which uses SHA256-signed license + requests. + - Raised the minimum required version of C++ to C++17. + - Added a new error code, `kDeviceRevoked`, which requires special handling. + - `kDeviceRevoked` may be returned from any method that processes a service + response. It indicates that the device's root of trust has been revoked + and is no longer trusted. + - All service interactions will fail after this, even after a device reboot + or factory reset. Apps may want to handle this error by using a different + DRM system, if supported. + - Devices that support renewal can escape this condition by renewing their + root of trust. Root of trust renewal is outside the scope of CE CDM. + You'll need to work directly with your Widevine contact. + - Improved code performance slightly by reducing copy operations. + - Added additional tests to check for problems when moving usage entries. + +## Bug Fixes + + - Fixed a potential out-of-bounds read in the logging code for certain + invalid severity level values. + - Fixed many minor issues identified via static analysis. + ## 18.5.0 (2024-03-28) Note: Releases v18.2-18.4 were skipped so that this release's version number diff --git a/README.md b/README.md index 60c17bfe..3e84ca46 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Widevine CE CDM 18.5.0 +# Widevine CE CDM 19.1.0 Released 2024-03-28 @@ -10,19 +10,28 @@ 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 v18.5.0 +## New in v19.1.0 -Note: Releases v18.2-18.4 were skipped so that this release's version number -matches the OEMCrypto version. - -**It is strongly recommended** for partners to update from v18.1 to v18.5 to -address two major bugs in the CE CDM code which could result in lost offline -licenses or app crashes. See _Bug Fixes_ for details. +Note: Release v19.0 was skipped so that this release's version number matches +the OEMCrypto version. ## Features - - Supports up to OEMCrypto v18.5, including new OEMCrypto tests introduced - since OEMCrypto v18.1. + - Supports up to OEMCrypto v19.1, including new OEMCrypto tests introduced + since OEMCrypto v18. + - Added support for License Protocol v2.2, which uses SHA256-signed license + requests. + - Raised the minimum required version of C++ to C++17. + - Added a new error code, `kDeviceRevoked`, which requires special handling. + - `kDeviceRevoked` may be returned from any method that processes a service + response. It indicates that the device's root of trust has been revoked + and is no longer trusted. + - All service interactions will fail after this, even after a device reboot + or factory reset. Apps may want to handle this error by using a different + DRM system, if supported. + - Devices that support renewal can escape this condition by renewing their + root of trust. Root of trust renewal is outside the scope of CE CDM. + You'll need to work directly with your Widevine contact. - Added additional logging when license request fails, to help diagnose what went wrong. - Improved support for HDCP v1.0-1.4 version. @@ -56,6 +65,8 @@ licenses or app crashes. See _Bug Fixes_ for details. - Affected partners should check the L3 OEMCrypto documentation for details. - Added new tests for CE CDM and OEMCrypto v18.5 features. + - Improved code performance slightly by reducing copy operations. + - Added additional tests to check for problems when moving usage entries. ### Bug Fixes @@ -89,6 +100,9 @@ licenses or app crashes. See _Bug Fixes_ for details. expected behavior. - These warning did not cause the tests to fail, but created a lot of noise when trying to diagnose other failures + - Fixed a potential out-of-bounds read in the logging code for certain + invalid severity level values. + - Fixed many minor issues identified via static analysis. [CHANGELOG.md](./CHANGELOG.md) lists the major changes for each past release. diff --git a/cdm/cdm_reboot_tests.gyp b/cdm/cdm_reboot_tests.gyp index d3abb4da..842171bc 100644 --- a/cdm/cdm_reboot_tests.gyp +++ b/cdm/cdm_reboot_tests.gyp @@ -98,13 +98,6 @@ }, { 'type': 'executable', }], - # TODO(b/139814713): For testing and internal use only. - ['oemcrypto_adapter_type=="static_v15"', { - 'defines': [ - # This is used by the unit tests to use some v15 functions. - 'TEST_OEMCRYPTO_V15', - ], - }], ['oemcrypto_lib=="level3"', { 'sources': [ # The test impl of OEMCrypto_Level3FileSystem and its factory. diff --git a/cdm/cdm_unittests.gyp b/cdm/cdm_unittests.gyp index 3ee5e1a3..1cc1c58a 100644 --- a/cdm/cdm_unittests.gyp +++ b/cdm/cdm_unittests.gyp @@ -14,6 +14,7 @@ 'oemcrypto_dir': '../oemcrypto', # Directory where widevine utilities live. 'util_dir': '../util', + 'third_party_path%': '../third_party', 'metrics_target': 'cdm.gyp:metrics_proto', 'device_files_target': 'cdm.gyp:device_files', # The path to the prebuilt CDM to test against. (iOS only) @@ -82,13 +83,6 @@ }, { 'type': 'executable', }], - # TODO(b/139814713): For testing and internal use only. - ['oemcrypto_adapter_type=="static_v15"', { - 'defines': [ - # This is used by the unit tests to use some v15 functions. - 'TEST_OEMCRYPTO_V15', - ], - }], ['oemcrypto_lib=="level3"', { 'sources': [ # The test impl of OEMCrypto_Level3FileSystem and its factory. @@ -146,6 +140,7 @@ 'dependencies': [ '../third_party/googletest.gyp:gmock', '../third_party/googletest.gyp:gtest', + '<(device_files_target)', ], 'conditions': [ ['OS=="linux"', { diff --git a/cdm/include/cdm.h b/cdm/include/cdm.h index 6c321940..13283952 100644 --- a/cdm/include/cdm.h +++ b/cdm/include/cdm.h @@ -100,6 +100,7 @@ class CDM_EXPORT Cdm : public ITimerClient { kSystemStateLost = 109, // Recoverable kOutputTooLarge = 110, // Recoverable kNeedsServiceCertificate = 111, // Recoverable + kDeviceRevoked = 112, // This covers errors that we do not expect (see logs for details): kUnexpectedError = 99999, diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index 0eba3f09..3ebed157 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -7,10 +7,10 @@ // Widevine CE CDM Version #ifndef CDM_VERSION_MAJOR -# define CDM_VERSION_MAJOR 18 +# define CDM_VERSION_MAJOR 19 #endif #ifndef CDM_VERSION_MINOR -# define CDM_VERSION_MINOR 5 +# define CDM_VERSION_MINOR 1 #endif #ifndef CDM_VERSION_PATCH # define CDM_VERSION_PATCH 0 diff --git a/cdm/install_keybox_tool.gyp b/cdm/install_keybox_tool.gyp new file mode 100644 index 00000000..adf3e8d1 --- /dev/null +++ b/cdm/install_keybox_tool.gyp @@ -0,0 +1,90 @@ +# Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. +# +# Any top-level targets in this file (and their dependencies) will be built by +# the CE CDM's ./build.py build system. Refer to the distribution package's +# README for details. +{ + 'includes': [ + 'platform_properties.gypi', + ], + 'variables': { + # Directory where OEMCrypto header, test, and reference files lives. + 'oemcrypto_dir': '../oemcrypto', + # Directory where widevine utilities live. + 'util_dir': '../util', + 'metrics_target': 'cdm.gyp:metrics_proto', + 'device_files_target': 'cdm.gyp:device_files', + }, + 'targets': [{ + 'toolsets' : [ 'target' ], + 'target_name': 'install_keybox_tool', + 'sources': [ + '../oemcrypto/test/install_keybox_tool.cpp', + ], + 'includes': [ + '../oemcrypto/odk/src/kdo.gypi', + '../util/libssl_dependency.gypi', + ], + 'include_dirs': [ + '../cdm/include', + '../core/include', + '../core/test', + '../metrics/include', + '../oemcrypto/test', + '../oemcrypto/include', + '../util/include', + '../util/test', + ], + 'dependencies': [ + 'cdm.gyp:widevine_ce_cdm_static', + '../oemcrypto/odk/src/odk.gyp:odk', + '../third_party/googletest.gyp:gmock', + '../third_party/googletest.gyp:gtest', + '<(metrics_target)', + '<(device_files_target)', + ], + 'defines': [ + 'UNIT_TEST', + 'CDM_TESTS', + ], + 'msvs_settings': { + 'VCLinkerTool': { + # Additionally, since they are loaded locally, suppress these + # warnings. + 'AdditionalOptions': [ + '/IGNORE:4049', + '/IGNORE:4217', + ], + }, + }, + 'conditions': [ + ['OS=="ios"', { + 'type': 'loadable_module', + 'mac_xctest_bundle': '1', + 'defines': [ + 'GTEST_FILTER="<(gtest_filter)"', + ], + 'dependencies': [ + 'cdm_unittests.gyp:dummy_app', + ], + 'sources': [ + 'test/gtest_xctest_wrapper.mm', + ], + 'xcode_settings': { + 'BUNDLE_LOADER': '$(TEST_HOST)', + 'TEST_HOST': '<(PRODUCT_DIR)/dummy_app.app/dummy_app', + 'WRAPPER_EXTENSION': 'xctest', + }, + }, { + 'type': 'executable', + }], + ['oemcrypto_lib=="target"', { + 'dependencies': [ + '<(oemcrypto_gyp_path)', + ], + }], + ], + }], +} diff --git a/cdm/src/cdm.cpp b/cdm/src/cdm.cpp index e02ef2a1..f86b4ffa 100644 --- a/cdm/src/cdm.cpp +++ b/cdm/src/cdm.cpp @@ -1499,7 +1499,9 @@ Cdm::Status CdmImpl::ConvertStatusCode( case PRIVACY_MODE_ERROR_3: LOGE("No service certificate installed; %s", message.c_str()); return kNeedsServiceCertificate; - + case DEVICE_REVOKED: + LOGE("Device has been revoked; %s", message.c_str()); + return kDeviceRevoked; default: LOGE("Unknown error; %s", message.c_str()); return kUnexpectedError; diff --git a/cdm/src/log.cpp b/cdm/src/log.cpp index c0848998..8d8564bc 100644 --- a/cdm/src/log.cpp +++ b/cdm/src/log.cpp @@ -40,8 +40,8 @@ void Log(const char* file, const char* function, int line, LogPriority level, } const char* severities[] = {"ERROR", "WARN", "INFO", "DEBUG", "VERBOSE"}; - if (level >= - static_cast(sizeof(severities) / sizeof(*severities))) { + if (level < 0 || level >= static_cast(sizeof(severities) / + sizeof(severities[0]))) { std::string fatal_message(kFallbackLogMessage); FormatString(&fatal_message, "[FATAL:%s(%d):%s] Invalid log priority level: %d", file, line, diff --git a/cdm/src/properties_ce.cpp b/cdm/src/properties_ce.cpp index 60a36de4..19ab9718 100644 --- a/cdm/src/properties_ce.cpp +++ b/cdm/src/properties_ce.cpp @@ -278,4 +278,7 @@ bool Properties::AlwaysUseKeySetIds() { return true; } // static bool Properties::UseProviderIdInProvisioningRequest() { return true; } +// static +bool Properties::ForceL3() { return false; } + } // namespace wvcdm diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index b0c12f9e..30172d26 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -305,9 +305,9 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { std::string reply_body; LicenseRequest lic_request; lic_request.GetDrmMessage(http_response, reply_body); - *response = reply_body; + *response = std::move(reply_body); } else { - *response = http_response; + *response = std::move(http_response); } LOGV("Reply body(hex): \n%s\n", b2a_hex(*response).c_str()); LOGV("Reply body(b64): \n%s\n", Base64SafeEncode(*response).c_str()); @@ -350,7 +350,7 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { std::map fields; if (UrlRequest::GetDebugHeaderFields(*response, &fields)) { LOGD("Unexpected status code: code = %d", status_code); - for (auto field : fields) { + for (const auto& field : fields) { LOGD("- %s: %s", field.first.c_str(), field.second.c_str()); } } @@ -1195,7 +1195,7 @@ TEST_F(CdmTest, Update_RevokedDrmCertError) { signed_message.set_msg(error_msg); signed_message.SerializeToString(&error_response); Cdm::Status status = updateWithRetry(session_id, error_response); - EXPECT_EQ(Cdm::kUnexpectedError, status); + EXPECT_EQ(Cdm::kDeviceRevoked, status); } TEST_F(CdmTest, Update_UnexpectedUpdate) { diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 57f306fc..90a74b8c 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -332,12 +332,6 @@ class CdmEngine { return CryptoSession::SetDebugIgnoreKeyboxCount(count); } - // This tells the OEMCrypto adapter to allow the device to continue with a - // test keybox. Otherwise, the keybox is reported as invalid. - static CdmResponseType SetAllowTestKeybox(bool allow) { - return CryptoSession::SetAllowTestKeybox(allow); - } - static CdmResponseType ParseDecryptHashString(const std::string& hash_string, CdmSessionId* id, uint32_t* frame_number, diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index 2557d0d3..2c6018b0 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -265,8 +265,6 @@ class CdmSession { bool HasRootOfTrustBeenRenewed(); - CdmResponseType ResetCryptoSession(); - // These setters are for testing only. Takes ownership of the pointers. void set_license_parser(CdmLicense* license_parser); void set_crypto_session(CryptoSession* crypto_session); @@ -342,9 +340,8 @@ class CdmSession { bool has_license_been_loaded_ = false; bool has_license_been_restored_ = false; - bool mock_crypto_session_in_use_ = false; - bool mock_license_parser_in_use_ = false; - bool mock_policy_engine_in_use_ = false; + bool mock_license_parser_in_use_; + bool mock_policy_engine_in_use_; CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession); }; diff --git a/core/include/certificate_provisioning.h b/core/include/certificate_provisioning.h index 5ca9e317..52d35fd6 100644 --- a/core/include/certificate_provisioning.h +++ b/core/include/certificate_provisioning.h @@ -125,6 +125,7 @@ class CertificateProvisioning { std::unique_ptr crypto_session_; CdmCertificateType cert_type_; std::unique_ptr service_certificate_; + std::string request_; // The wrapped private key in provisioning 4 generated by calling // GenerateCertificateKeyPair. It will be saved to file system if a valid // response is received. diff --git a/core/include/content_key_session.h b/core/include/content_key_session.h index 48e645a2..833cb5ce 100644 --- a/core/include/content_key_session.h +++ b/core/include/content_key_session.h @@ -24,13 +24,6 @@ class ContentKeySession : public KeySession { KeySessionType Type() override { return kDefault; } - // Generate Derived Keys for ContentKeySession - OEMCryptoResult GenerateDerivedKeys(const std::string& message) override; - - // Generate Derived Keys (from session key) for ContentKeySession - OEMCryptoResult GenerateDerivedKeys(const std::string& message, - const std::string& session_key) override; - // Load Keys for ContentKeySession OEMCryptoResult LoadKeys(const std::string& message, const std::string& signature, diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index d0fa7b8a..a68d6bb7 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -91,6 +91,13 @@ class CryptoSession { virtual CdmResponseType GetProvisioningToken(std::string* token, std::string* additional_token); + virtual CdmResponseType GetProvisioning40TokenType( + RequestedSecurityLevel requested_security_level, + OEMCrypto_BCCType* bcc_type); + // Must be called after session is open. + virtual CdmResponseType GetProvisioning40TokenType( + OEMCrypto_BCCType* bcc_type); + virtual CdmClientTokenType GetPreProvisionTokenType() { return pre_provision_token_type_; } @@ -162,7 +169,9 @@ class CryptoSession { OEMCrypto_SignatureHashAlgorithm& algorithm); virtual CdmResponseType UseSecondaryKey(bool dual_key); // V16 licenses. - virtual CdmResponseType LoadLicense(const std::string& signed_message, + virtual CdmResponseType LoadLicense(const std::string& context, + const std::string& session_key, + const std::string& signed_message, const std::string& core_message, const std::string& signature, CdmLicenseKeyType key_type); @@ -181,17 +190,19 @@ class CryptoSession { const std::vector& key_array); // Provisioning request/responses - virtual CdmResponseType GenerateDerivedKeys(const std::string& message); - virtual CdmResponseType GenerateDerivedKeys(const std::string& message, - const std::string& session_key); virtual CdmResponseType PrepareAndSignProvisioningRequest( const std::string& message, std::string* core_message, std::string* signature, bool& should_specify_algorithm, OEMCrypto_SignatureHashAlgorithm& algorithm); - virtual CdmResponseType LoadProvisioning(const std::string& signed_message, + virtual CdmResponseType LoadProvisioning(const std::string& request, + const std::string& signed_message, const std::string& core_message, const std::string& signature, std::string* wrapped_private_key); + virtual CdmResponseType LoadProvisioningCast( + const std::string& derivation_key, const std::string& request, + const std::string& signed_message, const std::string& core_message, + const std::string& signature, std::string* wrapped_private_key); virtual CdmResponseType LoadCertificatePrivateKey( const CryptoWrappedKey& private_key); virtual CdmResponseType GetBootCertificateChain( @@ -341,10 +352,6 @@ class CryptoSession { // report that it needs provisioning instead. static CdmResponseType SetDebugIgnoreKeyboxCount(uint32_t count); - // This tells the OEMCrypto adapter to allow the device to continue with a - // test keybox. Otherwise, the keybox is reported as invalid. - static CdmResponseType SetAllowTestKeybox(bool allow); - // Returns a system-wide singleton instance of SystemFallbackPolicy // to be used for communicating OTA keybox provisioning state between // apps. Returns a null pointer if OTA provisioning is not supported, @@ -484,12 +491,12 @@ class CryptoSession { // otherwise, such as making two calls into OEMCrypto immediately after each // other. template - static auto WithStaticFieldWriteLock(const char* tag, Func body) - -> decltype(body()); + static auto WithStaticFieldWriteLock(const char* tag, + Func body) -> decltype(body()); template - static auto WithStaticFieldReadLock(const char* tag, Func body) - -> decltype(body()); + static auto WithStaticFieldReadLock(const char* tag, + Func body) -> decltype(body()); template static auto WithOecWriteLock(const char* tag, Func body) -> decltype(body()); diff --git a/core/include/key_session.h b/core/include/key_session.h index 0b0174e6..81197f19 100644 --- a/core/include/key_session.h +++ b/core/include/key_session.h @@ -23,9 +23,6 @@ class KeySession { typedef enum { kDefault, kEntitlement } KeySessionType; virtual ~KeySession() {} virtual KeySessionType Type() = 0; - virtual OEMCryptoResult GenerateDerivedKeys(const std::string& message) = 0; - virtual OEMCryptoResult GenerateDerivedKeys( - const std::string& message, const std::string& session_key) = 0; virtual OEMCryptoResult LoadKeys(const std::string& message, const std::string& signature, const std::string& mac_key_iv, diff --git a/core/include/license.h b/core/include/license.h index 3e317f91..0f1f2f50 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -90,15 +90,6 @@ class CdmLicense { const CdmKeyResponse& license_response, std::string* provider_session_token); - // Testing only. Caller retains ownership of pointers. - void set_crypto_session(CryptoSession* crypto_session) { - crypto_session_ = crypto_session; - } - - void set_policy_engine(PolicyEngine* policy_engine) { - policy_engine_ = policy_engine; - } - private: CdmResponseType HandleKeyErrorResponse( const video_widevine::SignedMessage& signed_message); @@ -114,16 +105,18 @@ class CdmLicense { video_widevine::LicenseRequest* license_request); CdmResponseType HandleContentKeyResponse( - bool is_restore, const std::string& msg, const std::string& core_message, - const std::string& signature, const std::vector& key_array, + 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& msg, const std::string& core_message, - const std::string& signature, const std::vector& key_array, + 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 @@ -138,8 +131,8 @@ class CdmLicense { bool SetTypeAndId(CdmLicenseType license_type, const std::string& request_id, T* content_id); - CryptoSession* crypto_session_ = nullptr; - PolicyEngine* policy_engine_ = nullptr; + CryptoSession* crypto_session_; + PolicyEngine* policy_engine_; std::string server_url_; std::string client_token_; const CdmSessionId session_id_; @@ -147,6 +140,7 @@ class CdmLicense { bool initialized_; std::set loaded_keys_; std::string provider_session_token_; + video_widevine::ProtocolVersion protocol_version_; bool renew_with_client_id_; bool is_offline_; diff --git a/core/include/oemcrypto_adapter.h b/core/include/oemcrypto_adapter.h index afcf32bd..a38aa6f7 100644 --- a/core/include/oemcrypto_adapter.h +++ b/core/include/oemcrypto_adapter.h @@ -20,10 +20,6 @@ OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( // report that it needs provisioning instead. OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count); -// This tells the OEMCrypto adapter to allow the device to continue with a -// test keybox. Otherwise, the keybox is reported as invalid. -OEMCryptoResult OEMCrypto_SetAllowTestKeybox(bool allow); - // This attempts to open a session at the desired security level. // If one level is not available, the other will be used instead. OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, @@ -98,7 +94,8 @@ OEMCryptoResult OEMCrypto_Generic_Verify( size_t key_handle_length, const OEMCrypto_SharedMemory* buffer, size_t buffer_length, OEMCrypto_Algorithm algorithm, const OEMCrypto_SharedMemory* signature, size_t signature_length); - +OEMCryptoResult OEMCrypto_GetBCCType(RequestedSecurityLevel level, + OEMCrypto_BCCType* bcc_type); } // namespace wvcdm #endif // WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ diff --git a/core/include/policy_engine.h b/core/include/policy_engine.h index 506b7e91..3819cc34 100644 --- a/core/include/policy_engine.h +++ b/core/include/policy_engine.h @@ -106,8 +106,6 @@ class PolicyEngine { virtual const LicenseIdentification& license_id() { return license_id_; } - WvCdmEventListener* event_listener() { return event_listener_; } - bool GetSecondsSinceStarted(int64_t* seconds_since_started); bool GetSecondsSinceLastPlayed(int64_t* seconds_since_started); @@ -133,11 +131,6 @@ class PolicyEngine { return license_keys_->MeetsConstraints(key_id); } - // Testing only. Caller retains ownership. - void set_crypto_session(CryptoSession* crypto_session) { - crypto_session_ = crypto_session; - } - private: friend class PolicyEngineTest; friend class PolicyEngineConstraintsTest; diff --git a/core/include/privacy_crypto.h b/core/include/privacy_crypto.h index 9904a518..5ac0bf30 100644 --- a/core/include/privacy_crypto.h +++ b/core/include/privacy_crypto.h @@ -83,6 +83,7 @@ bool ExtractExtensionValueFromCertificate(const std::string& cert, std::string Md5Hash(const std::string& data); std::string Sha256Hash(const std::string& data); +std::string Sha512Hash(const std::string& data); } // namespace wvcdm diff --git a/core/include/properties.h b/core/include/properties.h index 3ff6f055..f411f4d3 100644 --- a/core/include/properties.h +++ b/core/include/properties.h @@ -82,6 +82,8 @@ class Properties { static bool GetSandboxId(std::string* sandbox_id); static bool AlwaysUseKeySetIds(); static bool UseProviderIdInProvisioningRequest(); + // Cdm only loads L3 library when this returns true + static bool ForceL3(); static bool GetSecurityLevelDirectories(std::vector* dirs); static bool GetApplicationId(const CdmSessionId& session_id, diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 5a5a12db..f002331e 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -948,8 +948,6 @@ const char* IdToString(const std::string& id); // provided as string pointers. const char* IdPtrToString(const std::string* id); -const char* BoolToString(bool value); - // Logging utilities for OEMCrypto types. const char* OemCryptoResultToString(OEMCryptoResult result); } // namespace wvcdm diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index 623b9e0c..d0e26d61 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -95,7 +95,9 @@ class UsagePropertySet : public CdmClientPropertySet { CdmEngine::CdmEngine(wvutil::FileSystem* file_system, std::shared_ptr metrics) - : metrics_(metrics), file_system_(file_system), spoid_(EMPTY_SPOID) { + : metrics_(std::move(metrics)), + file_system_(file_system), + spoid_(EMPTY_SPOID) { assert(file_system); Properties::Init(); } @@ -712,7 +714,7 @@ CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level, LOGW("GetWVCdmVersion failed"); return CdmResponseType(UNKNOWN_ERROR); } - *query_response = cdm_version; + *query_response = std::move(cdm_version); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_RESOURCE_RATING_TIER) { @@ -934,7 +936,7 @@ CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level, metrics_->GetCryptoMetrics() ->crypto_session_get_device_unique_id_.Increment(status); if (status != NO_ERROR) return status; - *query_response = device_id; + *query_response = std::move(device_id); return CdmResponseType(NO_ERROR); } if (query_token == QUERY_KEY_PROVISIONING_ID) { @@ -944,7 +946,7 @@ CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level, LOGW("GetProvisioningId failed: status = %d", static_cast(status)); return status; } - *query_response = provisioning_id; + *query_response = std::move(provisioning_id); return CdmResponseType(NO_ERROR); } LOGW("Unknown status requested: query_token = %s", IdToString(query_token)); @@ -1255,11 +1257,24 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( const CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse( file_system_, response, cert, wrapped_key); - // Release resources only on success. It is possible that a provisioning - // attempt was made after this one was requested but before the response was - // received, which will cause this attempt to fail. Not releasing will - // allow for the possibility that the later attempt succeeds. - if (NO_ERROR == ret) cert_provisioning_.reset(); + if (NO_ERROR == ret) { + // Release resources on success. + cert_provisioning_.reset(); + } else if (DEVICE_REVOKED == ret) { + // If a device is revoked, future attempts will likely fail. + // Caller may attempt changing security level to recover. + LOGE("Device has been revoked, cannot provision: status = %s", + ret.ToString().c_str()); + cert_provisioning_.reset(); + } else { + // It is possible that a provisioning attempt was made after this one was + // requested but before the response was received, which will cause this + // attempt to fail. Not releasing will allow for the possibility that the + // later attempt succeeds. + LOGW("Provisioning failed, app may try again: status = %s", + ret.ToString().c_str()); + } + return ret; } diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index dfe6f7df..69141aac 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -70,7 +70,7 @@ int DrmKeyTypeToMetricValue(CryptoWrappedKey::Type type) { CdmSession::CdmSession(wvutil::FileSystem* file_system, std::shared_ptr metrics) - : metrics_(metrics), + : metrics_(std::move(metrics)), initialized_(false), closed_(true), file_handle_(new DeviceFiles(file_system)), @@ -81,7 +81,9 @@ CdmSession::CdmSession(wvutil::FileSystem* file_system, security_level_(kSecurityLevelUninitialized), requested_security_level_(kLevelDefault), is_initial_usage_update_(true), - is_usage_update_needed_(false) { + is_usage_update_needed_(false), + mock_license_parser_in_use_(false), + mock_policy_engine_in_use_(false) { assert(metrics_); // metrics_ must not be null. crypto_metrics_ = metrics_->GetCryptoMetrics(); crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_)); @@ -864,11 +866,19 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_index) { // The usage entry cannot be deleted if it has a crypto session handling // it, so close and reopen session. UpdateUsageEntryInformation(); - + CdmResponseType sts; crypto_session_->Close(); - CdmResponseType sts = ResetCryptoSession(); + crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_)); + M_TIME(sts = crypto_session_->Open(requested_security_level_), + crypto_metrics_, crypto_session_open_, sts, requested_security_level_); if (sts != NO_ERROR) return sts; + usage_table_ = nullptr; + bool has_support = false; + if (crypto_session_->HasUsageTableSupport(&has_support) && has_support) { + usage_table_ = crypto_session_->GetUsageTable(); + } + if (usage_table_ == nullptr) { LOGE("Usage table header unavailable"); return CdmResponseType(INCORRECT_USAGE_SUPPORT_TYPE_1); @@ -1002,8 +1012,14 @@ bool CdmSession::StoreLicense(CdmOfflineLicenseState state, int* error_detail) { } CdmResponseType CdmSession::RemoveKeys() { - crypto_session_->Close(); - return ResetCryptoSession(); + CdmResponseType sts; + crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_)); + // Ignore errors + M_TIME(sts = crypto_session_->Open(requested_security_level_), + crypto_metrics_, crypto_session_open_, sts, requested_security_level_); + policy_engine_.reset( + new PolicyEngine(session_id_, nullptr, crypto_session_.get())); + return CdmResponseType(NO_ERROR); } CdmResponseType CdmSession::RemoveLicense() { @@ -1297,77 +1313,6 @@ CdmResponseType CdmSession::GenerateRsaSignature(const std::string& message, return crypto_session_->GenerateRsaSignature(message, signature, scheme); } -CdmResponseType CdmSession::ResetCryptoSession() { - LOGD("Resetting crypto session: session_id = %s, ksid = %s", - IdToString(session_id_), IdToString(key_set_id_)); - if (mock_crypto_session_in_use_) { - // If the crypto session is not reset, then there is nothing to do. - return CdmResponseType(NO_ERROR); - } - CdmResponseType sts; - crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_)); - usage_table_ = nullptr; - M_TIME(sts = crypto_session_->Open(requested_security_level_), - crypto_metrics_, crypto_session_open_, sts, requested_security_level_); - - CdmResponseType final_sts(NO_ERROR); - if (sts != NO_ERROR) { - // Challenging case, still need to reset other components. - LOGE("Failed to open crypto session: sts = %s", sts.ToString().c_str()); - final_sts = sts; - } else { - // Reset all component dependent on the crypto session. - security_level_ = crypto_session_->GetSecurityLevel(); - crypto_metrics_->crypto_session_security_level_.Record(security_level_); - - if (!file_handle_->Init(security_level_)) { - LOGE("Unable to initialize file handle"); - final_sts = CdmResponseType(SESSION_FILE_HANDLE_INIT_ERROR); - } - - if (!file_handle_->HasCertificate(atsc_mode_enabled_)) { - LOGE("Missing certificate: atsc_mode_enabled = %s", - BoolToString(atsc_mode_enabled_)); - final_sts = CdmResponseType(NEED_PROVISIONING); - } - - bool has_support = false; - if (crypto_session_->HasUsageTableSupport(&has_support) && has_support) { - usage_table_ = crypto_session_->GetUsageTable(); - } - } - - // Even if the session is not open, other members need new session pointer. - if (mock_policy_engine_in_use_) { - // Simply pass the new pointer. - policy_engine_->set_crypto_session(crypto_session_.get()); - } else { - // Attempt to maintain event listener. - WvCdmEventListener* event_listener = - policy_engine_ ? policy_engine_->event_listener() : nullptr; - policy_engine_.reset( - new PolicyEngine(session_id_, event_listener, crypto_session_.get())); - } - - if (mock_license_parser_in_use_) { - license_parser_->set_crypto_session(crypto_session_.get()); - license_parser_->set_policy_engine(policy_engine_.get()); - } else { - license_parser_.reset(new CdmLicense(session_id_)); - std::string service_certificate; - if (!Properties::GetServiceCertificate(session_id_, &service_certificate)) - service_certificate.clear(); - if (!license_parser_->Init(Properties::UsePrivacyMode(session_id_), - service_certificate, crypto_session_.get(), - policy_engine_.get())) { - LOGE("Failed to initialize license parser"); - final_sts = CdmResponseType(LICENSE_PARSER_INIT_ERROR); - } - } - - return final_sts; -} - // For testing only - takes ownership of pointers void CdmSession::set_license_parser(CdmLicense* license_parser) { @@ -1377,7 +1322,6 @@ void CdmSession::set_license_parser(CdmLicense* license_parser) { void CdmSession::set_crypto_session(CryptoSession* crypto_session) { crypto_session_.reset(crypto_session); - mock_crypto_session_in_use_ = true; } void CdmSession::set_policy_engine(PolicyEngine* policy_engine) { diff --git a/core/src/certificate_provisioning.cpp b/core/src/certificate_provisioning.cpp index f7b3b99b..b40c3825 100644 --- a/core/src/certificate_provisioning.cpp +++ b/core/src/certificate_provisioning.cpp @@ -261,6 +261,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal( status = crypto_session_->PrepareAndSignProvisioningRequest( serialized_message, &core_message, &request_signature, should_specify_algorithm, oec_algorithm); + request_ = serialized_message; if (status != NO_ERROR) { LOGE("Failed to prepare provisioning request: status = %d", @@ -488,6 +489,7 @@ CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal( } else { *request = std::move(serialized_request); } + request_ = std::move(serialized_message); return CdmResponseType(NO_ERROR); } @@ -574,20 +576,14 @@ CdmResponseType CertificateProvisioning::HandleProvisioning40Response( return status; } - status = crypto_session_->GenerateDerivedKeys( - provisioning_request_message_, signed_response.session_key()); - if (status != NO_ERROR) { - LOGE("Failed to generate derived keys."); - return status; - } - // Get wrapped private key for cast cert CryptoWrappedKey cast_cert_private_key; const std::string& signature = signed_response.signature(); const std::string& core_message = signed_response.oemcrypto_core_message(); - status = crypto_session_->LoadProvisioning(response_message, core_message, - signature, - &cast_cert_private_key.key()); + status = crypto_session_->LoadProvisioningCast( + signed_response.session_key(), provisioning_request_message_, + response_message, core_message, signature, + &cast_cert_private_key.key()); if (status != NO_ERROR) { LOGE("Failed to generate wrapped key for cast cert."); return status; @@ -727,14 +723,26 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( } CryptoWrappedKey private_key; - const CdmResponseType status = crypto_session_->LoadProvisioning( - signed_message, core_message, signature, &private_key.key()); + // TODO(b/316053127): clean this up a bit. + if (cert_type_ == kCertificateX509) { + const std::string dummy_key; + const CdmResponseType status = crypto_session_->LoadProvisioningCast( + dummy_key, request_, signed_message, core_message, signature, + &private_key.key()); - if (status != NO_ERROR) { - LOGE("LoadProvisioning failed: status = %d", static_cast(status)); - return status; + if (status != NO_ERROR) { + LOGE("LoadProvisioning failed: status = %d", static_cast(status)); + return status; + } + } else { + const CdmResponseType status = crypto_session_->LoadProvisioning( + request_, signed_message, core_message, signature, &private_key.key()); + + if (status != NO_ERROR) { + LOGE("LoadProvisioning failed: status = %d", static_cast(status)); + return status; + } } - const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); CloseSession(); diff --git a/core/src/client_identification.cpp b/core/src/client_identification.cpp index 83fac392..94bb8720 100644 --- a/core/src/client_identification.cpp +++ b/core/src/client_identification.cpp @@ -380,10 +380,30 @@ bool ClientIdentification::GetProvisioningTokenType( *token_type = video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE; return true; - case kClientTokenBootCertChain: - *token_type = - video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN; + case kClientTokenBootCertChain: { + OEMCrypto_BCCType bcc_type; + const CdmResponseType result = + crypto_session_->GetProvisioning40TokenType(&bcc_type); + if (result == NOT_IMPLEMENTED_ERROR) { + // Default to CBOR BCC for OEMCrypto that doesn't support GetBCCType(). + *token_type = + video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN; + return true; + } + if (result != NO_ERROR) return false; + if (bcc_type == OEMCrypto_CBOR) { + *token_type = + video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN; + } else if (bcc_type == OEMCrypto_X509) { + *token_type = + video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN_X509; + } else { + // shouldn't happen + LOGE("Unexpected BCC type: %d", static_cast(bcc_type)); + return false; + } return true; + } case kClientTokenDrmCertificateReprovisioning: *token_type = video_widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE; diff --git a/core/src/content_key_session.cpp b/core/src/content_key_session.cpp index 60e52514..7926b1d1 100644 --- a/core/src/content_key_session.cpp +++ b/core/src/content_key_session.cpp @@ -11,59 +11,6 @@ namespace wvcdm { -// Generate Derived Keys for ContentKeySession -OEMCryptoResult ContentKeySession::GenerateDerivedKeys( - const std::string& message) { - std::string mac_deriv_message; - std::string enc_deriv_message; - GenerateMacContext(message, &mac_deriv_message); - GenerateEncryptContext(message, &enc_deriv_message); - - LOGV("Generating derived keys: id = %u", oec_session_id_); - OEMCryptoResult sts; - M_TIME(sts = OEMCrypto_GenerateDerivedKeys( - oec_session_id_, - reinterpret_cast(mac_deriv_message.data()), - mac_deriv_message.size(), - reinterpret_cast(enc_deriv_message.data()), - enc_deriv_message.size()), - metrics_, oemcrypto_generate_derived_keys_, sts); - if (OEMCrypto_SUCCESS != sts) { - LOGE("OEMCrypto_GenerateDerivedKeys failed: status = %d", - static_cast(sts)); - } - - return sts; -} - -// Generate Derived Keys (from session key) for ContentKeySession -OEMCryptoResult ContentKeySession::GenerateDerivedKeys( - const std::string& message, const std::string& session_key) { - std::string mac_deriv_message; - std::string enc_deriv_message; - GenerateMacContext(message, &mac_deriv_message); - GenerateEncryptContext(message, &enc_deriv_message); - - LOGV("Generating derived keys from session key: id = %u", oec_session_id_); - OEMCryptoResult sts; - M_TIME( - sts = OEMCrypto_DeriveKeysFromSessionKey( - oec_session_id_, reinterpret_cast(session_key.data()), - session_key.size(), - reinterpret_cast(mac_deriv_message.data()), - mac_deriv_message.size(), - reinterpret_cast(enc_deriv_message.data()), - enc_deriv_message.size()), - metrics_, oemcrypto_derive_keys_from_session_key_, sts); - - if (OEMCrypto_SUCCESS != sts) { - LOGE("OEMCrypto_DeriveKeysFromSessionKey failed: status = %d", - static_cast(sts)); - } - - return sts; -} - // Load Keys for ContentKeySession OEMCryptoResult ContentKeySession::LoadKeys( const std::string& message, const std::string& signature, diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index fb002b0c..816ed220 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -248,38 +248,6 @@ OEMCrypto_Substring GetSubstring(const std::string& message, return substring; } -void GenerateMacContext(const std::string& input_context, - std::string* deriv_context) { - if (!deriv_context) { - LOGE("Output parameter |deriv_context| not provided"); - return; - } - - const std::string kSigningKeyLabel = "AUTHENTICATION"; - const size_t kSigningKeySizeBits = wvcdm::MAC_KEY_SIZE * 8; - - deriv_context->assign(kSigningKeyLabel); - deriv_context->append(1, '\0'); - deriv_context->append(input_context); - deriv_context->append(wvutil::EncodeUint32(kSigningKeySizeBits * 2)); -} - -void GenerateEncryptContext(const std::string& input_context, - std::string* deriv_context) { - if (!deriv_context) { - LOGE("Output parameter |deriv_context| not provided"); - return; - } - - const std::string kEncryptionKeyLabel = "ENCRYPTION"; - const size_t kEncryptionKeySizeBits = wvcdm::CONTENT_KEY_SIZE * 8; - - deriv_context->assign(kEncryptionKeyLabel); - deriv_context->append(1, '\0'); - deriv_context->append(input_context); - deriv_context->append(wvutil::EncodeUint32(kEncryptionKeySizeBits)); -} - OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) { return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CENC : OEMCrypto_CipherMode_CBCS; @@ -696,6 +664,24 @@ CdmResponseType CryptoSession::GetProvisioningToken( return status; } +CdmResponseType CryptoSession::GetProvisioning40TokenType( + OEMCrypto_BCCType* bcc_type) { + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); + return GetProvisioning40TokenType(requested_security_level_, bcc_type); +} + +CdmResponseType CryptoSession::GetProvisioning40TokenType( + RequestedSecurityLevel requested_security_level, + OEMCrypto_BCCType* bcc_type) { + RETURN_IF_NULL(bcc_type, PARAMETER_NULL); + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + OEMCryptoResult sts = WithOecReadLock("GetBCCType", [&] { + return OEMCrypto_GetBCCType(requested_security_level, bcc_type); + }); + return MapOEMCryptoResult(sts, UNKNOWN_CLIENT_TOKEN_TYPE, + "GetProvisioning40TokenType"); +} + CdmSecurityLevel CryptoSession::GetSecurityLevel() { LOGV("Getting security level"); RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized); @@ -1104,7 +1090,6 @@ CdmResponseType CryptoSession::PrepareAndSignLicenseRequest( "PrepareAndSignLicenseRequest"); } -#ifdef HAS_DUAL_KEY CdmResponseType CryptoSession::UseSecondaryKey(bool dual_key) { OEMCryptoResult sts; WithOecSessionLock("UseSecondaryKey", [&] { @@ -1113,13 +1098,10 @@ CdmResponseType CryptoSession::UseSecondaryKey(bool dual_key) { return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "UseSecondaryKey"); } -#else -CdmResponseType CryptoSession::UseSecondaryKey(bool /* dual_key */) { - return CdmResponseType(NO_ERROR); -} -#endif -CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message, +CdmResponseType CryptoSession::LoadLicense(const std::string& context, + const std::string& session_key, + const std::string& signed_message, const std::string& core_message, const std::string& signature, CdmLicenseKeyType key_type) { @@ -1135,6 +1117,9 @@ CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message, M_TIME(sts = OEMCrypto_LoadLicense( oec_session_id_, + reinterpret_cast(context.data()), context.size(), + reinterpret_cast(session_key.data()), + session_key.size(), reinterpret_cast(combined_message.data()), combined_message.size(), core_message.size(), reinterpret_cast(signature.data()), @@ -1262,8 +1247,6 @@ CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest( // TODO: b/305093063 - Refactor to a switch statement to improve readability if (pre_provision_token_type_ == kClientTokenKeybox) { should_specify_algorithm = false; - const CdmResponseType status = GenerateDerivedKeys(message); - if (status != NO_ERROR) return status; } else if (pre_provision_token_type_ == kClientTokenOemCert) { should_specify_algorithm = true; WithOecSessionLock("LoadOEMPrivateKey", [&] { @@ -1764,26 +1747,6 @@ CdmResponseType CryptoSession::SelectKey(const std::string& key_id, } } -CdmResponseType CryptoSession::GenerateDerivedKeys(const std::string& message) { - OEMCryptoResult sts; - WithOecSessionLock("GenerateDerivedKeys without session_key", - [&] { sts = key_session_->GenerateDerivedKeys(message); }); - - return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR_2, - "GenerateDerivedKeys"); -} - -CdmResponseType CryptoSession::GenerateDerivedKeys( - const std::string& message, const std::string& session_key) { - OEMCryptoResult sts; - WithOecSessionLock("GenerateDerivedKeys with session_key", [&] { - sts = key_session_->GenerateDerivedKeys(message, session_key); - }); - - return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR, - "GenerateDerivedKeys"); -} - CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message, std::string* signature, RSA_Padding_Scheme scheme) { @@ -2265,8 +2228,9 @@ bool CryptoSession::SetDestinationBufferType() { } CdmResponseType CryptoSession::LoadProvisioning( - const std::string& signed_message, const std::string& core_message, - const std::string& signature, std::string* wrapped_private_key) { + const std::string& request, const std::string& signed_message, + const std::string& core_message, const std::string& signature, + std::string* wrapped_private_key) { LOGV("Loading provisioning certificate: id = %u", oec_session_id_); if (wrapped_private_key == nullptr) { LOGE("Missing wrapped |wrapped_private_key|"); @@ -2280,6 +2244,7 @@ CdmResponseType CryptoSession::LoadProvisioning( WithOecSessionLock("LoadProvisioning Attempt 1", [&] { M_TIME(status = OEMCrypto_LoadProvisioning( oec_session_id_, + reinterpret_cast(request.data()), request.size(), reinterpret_cast(combined_message.data()), combined_message.size(), core_message.size(), reinterpret_cast(signature.data()), @@ -2297,6 +2262,7 @@ CdmResponseType CryptoSession::LoadProvisioning( WithOecSessionLock("LoadProvisioning Attempt 2", [&] { M_TIME(status = OEMCrypto_LoadProvisioning( oec_session_id_, + reinterpret_cast(request.data()), request.size(), reinterpret_cast(combined_message.data()), combined_message.size(), core_message.size(), reinterpret_cast(signature.data()), @@ -2315,6 +2281,64 @@ CdmResponseType CryptoSession::LoadProvisioning( "LoadProvisioning"); } +CdmResponseType CryptoSession::LoadProvisioningCast( + const std::string& derivation_key, const std::string& request, + const std::string& signed_message, const std::string& core_message, + const std::string& signature, std::string* wrapped_private_key) { + LOGV("Loading provisioning certificate: id = %u", oec_session_id_); + if (wrapped_private_key == nullptr) { + LOGE("Missing wrapped |wrapped_private_key|"); + return CdmResponseType(PARAMETER_NULL); + } + + const std::string combined_message = core_message + signed_message; + // Round 1, get the size of the wrapped private key buffer. + size_t wrapped_private_key_length = 0; + OEMCryptoResult status; + WithOecSessionLock("LoadProvisioningCast Attempt 1", [&] { + M_TIME(status = OEMCrypto_LoadProvisioningCast( + oec_session_id_, + reinterpret_cast(derivation_key.data()), + derivation_key.size(), + reinterpret_cast(request.data()), request.size(), + reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), + signature.size(), nullptr, &wrapped_private_key_length), + metrics_, oemcrypto_load_provisioning_, status); + }); + + if (status != OEMCrypto_ERROR_SHORT_BUFFER) { + return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR, + "LoadProvisioningCast"); + } + + wrapped_private_key->resize(wrapped_private_key_length); + + WithOecSessionLock("LoadProvisioningCast Attempt 2", [&] { + M_TIME(status = OEMCrypto_LoadProvisioningCast( + oec_session_id_, + reinterpret_cast(derivation_key.data()), + derivation_key.size(), + reinterpret_cast(request.data()), request.size(), + reinterpret_cast(combined_message.data()), + combined_message.size(), core_message.size(), + reinterpret_cast(signature.data()), + signature.size(), + reinterpret_cast(&wrapped_private_key->front()), + &wrapped_private_key_length), + metrics_, oemcrypto_load_provisioning_, status); + }); + + if (status == OEMCrypto_SUCCESS) { + wrapped_private_key->resize(wrapped_private_key_length); + return CdmResponseType(NO_ERROR); + } + wrapped_private_key->clear(); + return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR, + "LoadProvisioningCast"); +} + CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current, HdcpCapability* max) { LOGV("Getting HDCP capabilities: id = %u", oec_session_id_); @@ -2625,10 +2649,15 @@ CdmResponseType CryptoSession::SetDecryptHash(uint32_t frame_number, const std::string& hash) { LOGV("Setting decrypt hash"); OEMCryptoResult sts; + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); + if (hash.size() != sizeof(uint32_t)) { + LOGE("Unsupported hash size: hash_size = %zu, expected = %zu", hash.size(), + sizeof(uint32_t)); + return CdmResponseType(UNKNOWN_ERROR); + } WithOecSessionLock("SetDecryptHash", [&] { - sts = OEMCrypto_SetDecryptHash( - oec_session_id_, frame_number, - reinterpret_cast(hash.data()), hash.size()); + const uint32_t crc32 = *reinterpret_cast(hash.data()); + sts = OEMCrypto_SetDecryptHash(oec_session_id_, frame_number, crc32); metrics_->oemcrypto_set_decrypt_hash_.Increment(sts); }); @@ -3442,11 +3471,6 @@ CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) { return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount"); } -CdmResponseType CryptoSession::SetAllowTestKeybox(bool allow) { - OEMCryptoResult status = OEMCrypto_SetAllowTestKeybox(allow); - return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetAllowTestKeybox"); -} - okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() { const auto getter = [&]() -> okp::SystemFallbackPolicy* { // If not set, then OTA keybox provisioning is not supported or @@ -3505,40 +3529,40 @@ CdmResponseType CryptoSession::LoadOtaProvisioning( } template -auto CryptoSession::WithStaticFieldWriteLock(const char* tag, Func body) - -> decltype(body()) { +auto CryptoSession::WithStaticFieldWriteLock(const char* tag, + Func body) -> decltype(body()) { LOGV("Static field write lock: %s", tag); std::unique_lock auto_lock(static_field_mutex_); return body(); } template -auto CryptoSession::WithStaticFieldReadLock(const char* tag, Func body) - -> decltype(body()) { +auto CryptoSession::WithStaticFieldReadLock(const char* tag, + Func body) -> decltype(body()) { LOGV("Static field read lock: %s", tag); wvutil::shared_lock auto_lock(static_field_mutex_); return body(); } template -auto CryptoSession::WithOecWriteLock(const char* tag, Func body) - -> decltype(body()) { +auto CryptoSession::WithOecWriteLock(const char* tag, + Func body) -> decltype(body()) { LOGV("OEMCrypto write lock: %s", tag); std::unique_lock auto_lock(oem_crypto_mutex_); return body(); } template -auto CryptoSession::WithOecReadLock(const char* tag, Func body) - -> decltype(body()) { +auto CryptoSession::WithOecReadLock(const char* tag, + Func body) -> decltype(body()) { LOGV("OEMCrypto read lock: %s", tag); wvutil::shared_lock auto_lock(oem_crypto_mutex_); return body(); } template -auto CryptoSession::WithOecSessionLock(const char* tag, Func body) - -> decltype(body()) { +auto CryptoSession::WithOecSessionLock(const char* tag, + Func body) -> decltype(body()) { LOGV("OEMCrypto session lock: %s", tag); wvutil::shared_lock oec_auto_lock(oem_crypto_mutex_); std::unique_lock session_auto_lock(oem_crypto_session_mutex_); diff --git a/core/src/entitlement_key_session.cpp b/core/src/entitlement_key_session.cpp index b52ee22a..63a17e34 100644 --- a/core/src/entitlement_key_session.cpp +++ b/core/src/entitlement_key_session.cpp @@ -115,7 +115,7 @@ OEMCryptoResult EntitlementKeySession::SelectKey(const std::string& key_id, OEMCrypto_EntitledContentKeyObject EntitlementKeySession::MakeOecEntitledKey( const CryptoKey& input_key, std::string& message) { - OEMCrypto_EntitledContentKeyObject output_key; + OEMCrypto_EntitledContentKeyObject output_key = {}; message.clear(); const std::string& entitlement_key_id = input_key.entitlement_key_id(); diff --git a/core/src/license.cpp b/core/src/license.cpp index a7b5f297..79eeec8c 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -113,7 +113,8 @@ std::vector ExtractEntitlementKeys(const License& license) { return key_array; } -std::vector ExtractContentKeys(const License& license) { +std::vector ExtractContentKeys( + const License& license, video_widevine::ProtocolVersion version) { std::vector key_array; // Extract content key(s) @@ -130,21 +131,20 @@ std::vector ExtractContentKeys(const License& license) { // TODO(b/232464183): When we switch to License Protocol 2.2, there will // no longer be padding on these keys, so this // removal code must be removed at the same time. - if (license.key(i).key().size() != - CONTENT_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING && - license.key(i).key().size() != - MAC_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING) { + const auto padding = version <= video_widevine::VERSION_2_1 + ? LICENSE_PROTOCOL_2_1_PADDING + : 0; + if (license.key(i).key().size() != CONTENT_KEY_SIZE + padding && + license.key(i).key().size() != MAC_KEY_SIZE + padding) { LOGE( "Skipping key %s because it is an unexpected size. Expected: %zu " "or %zu, Actual: %zu", - license.key(i).id().c_str(), - CONTENT_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING, - MAC_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING, - license.key(i).key().size()); + license.key(i).id().c_str(), CONTENT_KEY_SIZE + padding, + MAC_KEY_SIZE + padding, license.key(i).key().size()); continue; } const size_t length = - license.key(i).key().size() - LICENSE_PROTOCOL_2_1_PADDING; + license.key(i).key().size() - padding; key.set_key_data(license.key(i).key().substr(0, length)); key.set_key_data_iv(license.key(i).iv()); if (license.key(i).has_key_control()) { @@ -195,8 +195,11 @@ std::vector ExtractContentKeys(const License& license) { } // namespace CdmLicense::CdmLicense(const CdmSessionId& session_id) - : session_id_(session_id), + : crypto_session_(nullptr), + policy_engine_(nullptr), + 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), @@ -204,17 +207,16 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id) license_key_type_(kLicenseKeyTypeContent) {} CdmLicense::CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock) - : session_id_(session_id), + : crypto_session_(nullptr), + policy_engine_(nullptr), + 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) { - if (!clock_) { - LOGW("Input |clock| is null, using default"); - clock_.reset(new wvutil::Clock()); - } + clock_.reset(clock); } CdmLicense::~CdmLicense() {} @@ -244,6 +246,13 @@ bool CdmLicense::Init(bool use_privacy_mode, return false; } + uint32_t api_version; + if (!session->GetApiVersion(&api_version)) { + api_version = 16; + } + protocol_version_ = api_version >= 19 ? video_widevine::VERSION_2_2 + : video_widevine::VERSION_2_1; + crypto_session_ = session; policy_engine_ = policy_engine; use_privacy_mode_ = use_privacy_mode; @@ -338,7 +347,7 @@ CdmResponseType CdmLicense::PrepareKeyRequest( return CdmResponseType(LICENSE_REQUEST_NONCE_GENERATION_ERROR); } license_request.set_key_control_nonce(license_nonce_); - license_request.set_protocol_version(video_widevine::VERSION_2_1); + license_request.set_protocol_version(protocol_version_); // License request is complete. Serialize it. std::string serialized_license_req; @@ -494,7 +503,7 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( current_license->set_seconds_since_last_played(seconds_since_last_played); } - license_request.set_protocol_version(video_widevine::VERSION_2_1); + license_request.set_protocol_version(protocol_version_); // License request is complete. Serialize it. std::string serialized_license_req; @@ -585,10 +594,6 @@ CdmResponseType CdmLicense::HandleKeyResponse( LOGE("Signed response has no session keys present"); return CdmResponseType(SESSION_KEYS_NOT_FOUND); } - CdmResponseType status = crypto_session_->GenerateDerivedKeys( - key_request_, signed_response.session_key()); - - if (status != NO_ERROR) return status; // Extract mac key std::string mac_key_iv; @@ -623,7 +628,7 @@ CdmResponseType CdmLicense::HandleKeyResponse( CdmLicenseKeyType key_type = kLicenseKeyTypeEntitlement; std::vector key_array = ExtractEntitlementKeys(license); if (key_array.empty()) { - key_array = ExtractContentKeys(license); + key_array = ExtractContentKeys(license, protocol_version_); key_type = kLicenseKeyTypeContent; } if (key_array.empty()) { @@ -655,18 +660,19 @@ CdmResponseType CdmLicense::HandleKeyResponse( } // If the field is not set, it will default to false. - status = + CdmResponseType status = crypto_session_->UseSecondaryKey(signed_response.using_secondary_key()); if (status != NO_ERROR) return status; CdmResponseType resp(NO_CONTENT_KEY); if (kLicenseKeyTypeEntitlement == key_type) { - resp = - HandleEntitlementKeyResponse(is_restore, signed_message, core_message, - signature, key_array, license); + resp = HandleEntitlementKeyResponse( + is_restore, signed_response.session_key(), signed_message, core_message, + signature, key_array, license); } else if (kLicenseKeyTypeContent == key_type) { - resp = HandleContentKeyResponse(is_restore, signed_message, core_message, - signature, key_array, license); + resp = HandleContentKeyResponse(is_restore, signed_response.session_key(), + signed_message, core_message, signature, + key_array, license); } return resp; } @@ -797,6 +803,7 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( 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); @@ -1026,13 +1033,20 @@ CdmResponseType CdmLicense::PrepareClientId( license_request->mutable_encrypted_client_id(); status = service_certificate_.EncryptClientId(crypto_session_, client_id, encrypted_client_id); - if (NO_ERROR == status) { - license_request->clear_client_id(); - } else { + if (status != NO_ERROR) { + LOGE("Failed to encrypt client ID: status = %s", + status.ToString().c_str()); license_request->clear_encrypted_client_id(); + return status; } - return status; + license_request->clear_client_id(); } + + std::string client_version; + if (Properties::GetWVCdmVersion(&client_version)) { + license_request->set_client_version(std::move(client_version)); + } + return CdmResponseType(NO_ERROR); } @@ -1079,15 +1093,19 @@ CdmResponseType CdmLicense::PrepareContentId( } CdmResponseType CdmLicense::HandleContentKeyResponse( - bool is_restore, const std::string& msg, const std::string& core_message, - const std::string& signature, const std::vector& key_array, + 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) { if (key_array.empty()) { LOGE("No content keys provided"); return CdmResponseType(NO_CONTENT_KEY); } const CdmResponseType resp = crypto_session_->LoadLicense( - msg, core_message, signature, kLicenseKeyTypeContent); + protocol_version_ <= video_widevine::VERSION_2_1 + ? key_request_ + : Sha512Hash(key_request_), + session_key, msg, core_message, signature, kLicenseKeyTypeContent); if (KEY_ADDED == resp) { loaded_keys_.clear(); for (const CryptoKey& key : key_array) { @@ -1099,15 +1117,19 @@ CdmResponseType CdmLicense::HandleContentKeyResponse( } CdmResponseType CdmLicense::HandleEntitlementKeyResponse( - bool is_restore, const std::string& msg, const std::string& core_message, - const std::string& signature, const std::vector& key_array, + 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) { if (key_array.empty()) { LOGE("No entitlement keys provided"); return CdmResponseType(NO_CONTENT_KEY); } const CdmResponseType resp = crypto_session_->LoadLicense( - msg, core_message, signature, kLicenseKeyTypeEntitlement); + protocol_version_ <= video_widevine::VERSION_2_1 + ? key_request_ + : Sha512Hash(key_request_), + session_key, msg, core_message, signature, kLicenseKeyTypeEntitlement); if (KEY_ADDED != resp) { return resp; diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto index 55777b69..7e814b54 100644 --- a/core/src/license_protocol.proto +++ b/core/src/license_protocol.proto @@ -1097,7 +1097,10 @@ message ClientIdentification { DRM_DEVICE_CERTIFICATE = 1; REMOTE_ATTESTATION_CERTIFICATE = 2; OEM_DEVICE_CERTIFICATE = 3; + // Boot certificate chain in CBOR format. BOOT_CERTIFICATE_CHAIN = 4; + // Boot certificate chain in X509 format. + BOOT_CERTIFICATE_CHAIN_X509 = 5; } message NameValue { diff --git a/core/src/oemcrypto_adapter_static.cpp b/core/src/oemcrypto_adapter_static.cpp index b0bd14ba..e4f0090e 100644 --- a/core/src/oemcrypto_adapter_static.cpp +++ b/core/src/oemcrypto_adapter_static.cpp @@ -10,6 +10,7 @@ #include "log.h" #include "odk_structs.h" #include "oemcrypto_adapter.h" +#include "wv_attributes.h" namespace wvcdm { OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( @@ -45,11 +46,6 @@ OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } -OEMCryptoResult OEMCrypto_SetAllowTestKeybox(bool allow) { - (void)allow; - return OEMCrypto_SUCCESS; -} - OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, RequestedSecurityLevel) { return ::OEMCrypto_OpenSession(session); @@ -223,4 +219,16 @@ OEMCryptoResult OEMCrypto_Generic_Verify( signature_length); } +OEMCryptoResult OEMCrypto_GetBCCType(RequestedSecurityLevel level, + OEMCrypto_BCCType* bcc_type) { + (void)level; + return ::OEMCrypto_GetBCCType(bcc_type); +} } // namespace wvcdm + +// Provide default implementation of L3-only functions. WEAK allows them to be +// replaced by the L3 if available. + +WEAK OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION, bool) { + return OEMCrypto_SUCCESS; +} diff --git a/core/src/policy_engine.cpp b/core/src/policy_engine.cpp index 08d85524..f736d2b7 100644 --- a/core/src/policy_engine.cpp +++ b/core/src/policy_engine.cpp @@ -30,7 +30,7 @@ namespace wvcdm { PolicyEngine::PolicyEngine(CdmSessionId session_id, WvCdmEventListener* event_listener, CryptoSession* crypto_session) - : session_id_(session_id), + : session_id_(std::move(session_id)), event_listener_(event_listener), license_keys_(new LicenseKeys(crypto_session->GetSecurityLevel())), clock_(new wvutil::Clock()) { @@ -39,12 +39,9 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id, if (version >= kMinOemCryptoApiVersionSupportsRenewalDelayBase) { policy_timers_.reset(new PolicyTimersV18()); } - } else { - LOGW("Failed to get API version: session_id = %s", IdToString(session_id)); } - if (!policy_timers_) { - // Use V16 policy timers if getting version failed. + if (policy_timers_ == nullptr) { policy_timers_.reset(new PolicyTimersV16()); } InitDevice(crypto_session); diff --git a/core/src/privacy_crypto_apple.cpp b/core/src/privacy_crypto_apple.cpp index b9b1076d..d3661284 100644 --- a/core/src/privacy_crypto_apple.cpp +++ b/core/src/privacy_crypto_apple.cpp @@ -391,4 +391,10 @@ std::string Sha256Hash(const std::string& data) { return hash; } +std::string Sha512Hash(const std::string& data) { + std::string hash(CC_SHA512_DIGEST_LENGTH, '\0'); + CC_SHA512(data.data(), data.size(), reinterpret_cast(&hash[0])); + return hash; +} + } // namespace wvcdm diff --git a/core/src/privacy_crypto_boringssl.cpp b/core/src/privacy_crypto_boringssl.cpp index 6377642b..40f7bb1b 100644 --- a/core/src/privacy_crypto_boringssl.cpp +++ b/core/src/privacy_crypto_boringssl.cpp @@ -417,4 +417,11 @@ std::string Sha256Hash(const std::string& data) { return hash; } +std::string Sha512Hash(const std::string& data) { + std::string hash(SHA512_DIGEST_LENGTH, '\0'); + SHA512(reinterpret_cast(data.data()), data.size(), + reinterpret_cast(&hash[0])); + return hash; +} + } // namespace wvcdm diff --git a/core/src/privacy_crypto_dummy.cpp b/core/src/privacy_crypto_dummy.cpp index c016b478..26ef4d77 100644 --- a/core/src/privacy_crypto_dummy.cpp +++ b/core/src/privacy_crypto_dummy.cpp @@ -13,6 +13,8 @@ # include # define SHA256 CC_SHA256 # define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH +# define SHA512 CC_SHA512 +# define SHA512_DIGEST_LENGTH CC_SHA512_DIGEST_LENGTH # define MD5 CC_MD5 # define MD5_DIGEST_LENGTH CC_MD5_DIGEST_LENGTH #else @@ -69,4 +71,10 @@ std::string Sha256Hash(const std::string& data) { return hash; } +std::string Sha512Hash(const std::string& data) { + std::string hash(SHA512_DIGEST_LENGTH, '\0'); + SHA512(data.data(), data.size(), reinterpret_cast(&hash[0])); + return hash; +} + } // namespace wvcdm diff --git a/core/src/wv_cdm_types.cpp b/core/src/wv_cdm_types.cpp index edc2edf2..2094129b 100644 --- a/core/src/wv_cdm_types.cpp +++ b/core/src/wv_cdm_types.cpp @@ -12,8 +12,6 @@ namespace wvcdm { namespace { const char kEmptyIdRep[] = ""; const char kNullIdRep[] = ""; -const char kFalseRep[] = "false"; -const char kTrueRep[] = "true"; // Thread local buffer used by UnknownEnumValueToString() to represent // unknown enum values. @@ -898,8 +896,6 @@ const char* IdPtrToString(const std::string* id) { return id->empty() ? kEmptyIdRep : id->c_str(); } -const char* BoolToString(bool value) { return value ? kTrueRep : kFalseRep; } - const char* OemCryptoResultToString(OEMCryptoResult result) { switch (result) { /* OEMCrypto return values */ diff --git a/core/test/cdm_engine_metrics_decorator_unittest.cpp b/core/test/cdm_engine_metrics_decorator_unittest.cpp index b325578d..f4fd4f38 100644 --- a/core/test/cdm_engine_metrics_decorator_unittest.cpp +++ b/core/test/cdm_engine_metrics_decorator_unittest.cpp @@ -56,7 +56,7 @@ class MockCdmEngineImpl : public CdmEngine { public: MockCdmEngineImpl(wvutil::FileSystem* file_system, std::shared_ptr metrics) - : CdmEngine(file_system, metrics) {} + : CdmEngine(file_system, std::move(metrics)) {} MOCK_METHOD(CdmResponseType, OpenSession, (const CdmKeySystem&, CdmClientPropertySet*, const CdmSessionId&, WvCdmEventListener*), @@ -119,7 +119,7 @@ class WvCdmEngineMetricsImplTest : public ::testing::Test { std::shared_ptr engine_metrics(new EngineMetrics); test_cdm_metrics_engine_.reset( new CdmEngineMetricsImpl>( - file_system_.get(), engine_metrics)); + file_system_.get(), std::move(engine_metrics))); } protected: diff --git a/core/test/cdm_usage_table_unittest.cpp b/core/test/cdm_usage_table_unittest.cpp index 5d602796..ef5d0bf0 100644 --- a/core/test/cdm_usage_table_unittest.cpp +++ b/core/test/cdm_usage_table_unittest.cpp @@ -301,10 +301,6 @@ void InitVectorConstants() { case 3: kOverFullUsageEntryInfoVector.push_back(kUsageEntryInfoSecureStop2); break; - default: - kOverFullUsageEntryInfoVector.push_back( - kUsageEntryInfoStorageTypeUnknown); - break; } } @@ -794,8 +790,8 @@ TEST_P(CdmUsageTableInitializationTest, RestoreUsageTable_AtCapacity) { EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(usage_entries), SetArgPointee<2>(false), - Return(true))); + SetArgPointee<1>(std::move(usage_entries)), + SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(security_level, kUsageTableHeader)) .WillOnce(Return(CdmResponseType(NO_ERROR))); @@ -822,8 +818,8 @@ TEST_P(CdmUsageTableInitializationTest, EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(usage_entries), SetArgPointee<2>(false), - Return(true))); + SetArgPointee<1>(std::move(usage_entries)), + SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(security_level, kUsageTableHeader)) .WillOnce(Return(CdmResponseType(NO_ERROR))); @@ -3588,7 +3584,7 @@ TEST_F(CdmUsageTableTest, StaleHeader) { EXPECT_CALL(*device_files_, RetrieveUsageTableInfo(NotNull(), NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kUsageTableHeader), - SetArgPointee<1>(entry_info_list), + SetArgPointee<1>(std::move(entry_info_list)), SetArgPointee<2>(false), Return(true))); EXPECT_CALL(*crypto_session_, LoadUsageTableHeader(kLevelDefault, kUsageTableHeader)) @@ -4136,6 +4132,7 @@ TEST_F(CdmUsageTableTest, DetermineLicenseToRemove_BasicPriorities) { CdmUsageEntryInfo streaming_entry_info; streaming_entry_info.storage_type = kStorageUsageInfo; streaming_entry_info.last_use_time = kLruBaseTime; + streaming_entry_info.offline_license_expiry_time = 0; usage_entry_info_list.push_back(streaming_entry_info); constexpr UsageEntryIndex streaming_entry_index = 2; @@ -4144,6 +4141,7 @@ TEST_F(CdmUsageTableTest, DetermineLicenseToRemove_BasicPriorities) { unknown_entry_info.storage_type = kStorageTypeUnknown; // Should be chosen regardless of |last_use_time|. unknown_entry_info.last_use_time = kCurrentTime; + unknown_entry_info.offline_license_expiry_time = 0; usage_entry_info_list.push_back(unknown_entry_info); constexpr UsageEntryIndex unknown_entry_index = 3; diff --git a/core/test/certificate_provisioning_unittest.cpp b/core/test/certificate_provisioning_unittest.cpp index 944c7af2..01328861 100644 --- a/core/test/certificate_provisioning_unittest.cpp +++ b/core/test/certificate_provisioning_unittest.cpp @@ -201,7 +201,7 @@ class MockCryptoSession : public TestCryptoSession { MOCK_METHOD(CdmSecurityLevel, GetSecurityLevel, (), (override)); MOCK_METHOD(CdmResponseType, LoadProvisioning, (const std::string&, const std::string&, const std::string&, - std::string*), + const std::string&, std::string*), (override)); }; @@ -458,7 +458,7 @@ TEST_P(CertificateProvisioningTest, ProvisioningResponseSuccess) { .WillByDefault(Return(kSecurityLevelL3)); EXPECT_CALL(*crypto_session, LoadProvisioning) .Times(1) - .WillOnce(DoAll(SetArgPointee<3>(kWrappedPrivateKey), + .WillOnce(DoAll(SetArgPointee<4>(kWrappedPrivateKey), Return(CdmResponseType(NO_ERROR)))); MockFile* file = new MockFile(); diff --git a/core/test/core_integration_test.cpp b/core/test/core_integration_test.cpp index ef3657f2..9e7a7186 100644 --- a/core/test/core_integration_test.cpp +++ b/core/test/core_integration_test.cpp @@ -85,7 +85,7 @@ class CoreIntegrationTest : public WvCdmTestBaseWithEngine { const std::string origin, std::string* spoid, std::string* drm_certificate_serial_number) { std::string name = app_package_name + origin; - return Provision(name, spoid, drm_certificate_serial_number); + return Provision(std::move(name), spoid, drm_certificate_serial_number); } private: diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp index 1e0f811a..575cf041 100644 --- a/core/test/device_files_unittest.cpp +++ b/core/test/device_files_unittest.cpp @@ -5206,7 +5206,7 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) - .With(AllArgs(StrAndLenContains(expected_substrings))) + .With(AllArgs(StrAndLenContains(std::move(expected_substrings)))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); @@ -5226,7 +5226,7 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { kLicenseTestData[license_num].playback_start_time, kLicenseTestData[license_num].last_playback_time, kLicenseTestData[license_num].grace_period_end_time, - app_parameters, + std::move(app_parameters), kLicenseTestData[license_num].usage_entry, kLicenseTestData[license_num].usage_entry_index, kLicenseTestData[license_num].drm_certificate, @@ -5271,7 +5271,7 @@ TEST_F(DeviceFilesTest, StoreLicenses) { .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) - .With(AllArgs(StrAndLenContains(expected_substrings))) + .With(AllArgs(StrAndLenContains(std::move(expected_substrings)))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); } @@ -5295,7 +5295,7 @@ TEST_F(DeviceFilesTest, StoreLicenses) { kLicenseTestData[i].playback_start_time, kLicenseTestData[i].last_playback_time, kLicenseTestData[i].grace_period_end_time, - app_parameters, + std::move(app_parameters), kLicenseTestData[i].usage_entry, kLicenseTestData[i].usage_entry_index, kLicenseTestData[i].drm_certificate, @@ -5980,7 +5980,7 @@ TEST_P(DeviceFilesUsageInfoListTest, UsageInfoList) { } EXPECT_CALL(file_system, List(StrEq(device_base_path_), NotNull())) - .WillOnce(DoAll(SetArgPointee<1>(file_list), Return(true))); + .WillOnce(DoAll(SetArgPointee<1>(std::move(file_list)), Return(true))); DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); @@ -6030,7 +6030,7 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { EXPECT_CALL(file_system, Open(StrEq(path), _)) .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) - .With(AllArgs(StrAndLenContains(usage_data_fields))) + .With(AllArgs(StrAndLenContains(std::move(usage_data_fields)))) .WillOnce(ReturnArg<1>()); DeviceFiles device_files(&file_system); @@ -6336,7 +6336,7 @@ TEST_P(DeviceFilesUsageInfoTest, UpdateUsageInfo) { .WillOnce(Return(ByMove(std::unique_ptr(next_file)))); EXPECT_CALL(*next_file, Write(_, _)) - .With(AllArgs(StrAndLenContains(usage_data_fields))) + .With(AllArgs(StrAndLenContains(std::move(usage_data_fields)))) .WillOnce(ReturnArg<1>()); } @@ -6451,7 +6451,7 @@ TEST_P(DeviceFilesUsageTableTest, Store) { EXPECT_CALL(file_system, Open(StrEq(path), _)) .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) - .With(AllArgs(StrAndLenContains(entry_data))) + .With(AllArgs(StrAndLenContains(std::move(entry_data)))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); diff --git a/core/test/duration_use_case_test.cpp b/core/test/duration_use_case_test.cpp index b36e814f..e9c003ef 100644 --- a/core/test/duration_use_case_test.cpp +++ b/core/test/duration_use_case_test.cpp @@ -1062,7 +1062,6 @@ class CdmUseCase_LicenseWithRenewal : public RenewalTest { void SetUp() override { RenewalTest::SetUp(); - if(Test::IsSkipped()) return; const uint64_t next_renewal = start_of_playback_ + initial_policy_.renewal_delay; // Allow playback within the initial renewal window. @@ -1268,7 +1267,6 @@ class CdmUseCase_LicenseWithRenewalPlayback : public RenewalTest { void SetUp() override { RenewalTest::SetUp(); - if(Test::IsSkipped()) return; uint64_t next_renewal = start_of_playback_ + initial_policy_.renewal_delay; // Allow playback within the initial renewal window. SleepUntil(start_of_playback_); @@ -1586,7 +1584,6 @@ class CdmUseCase_RenewOnLicenseLoad : public RenewalTest { void SetUp() override { RenewalTest::SetUp(); - if(Test::IsSkipped()) return; // The Renew on License Load feature is only supported on v18+ servers. if (config_.ServerOlderThan(18) || wvoec::global_features.api_version < 18) { @@ -1734,7 +1731,6 @@ class CdmUseCase_Heartbeat : public RenewalTest { void SetUp() override { RenewalTest::SetUp(); - if(Test::IsSkipped()) return; const uint64_t next_renewal = start_of_playback_ + initial_policy_.renewal_delay; // Allow playback within the initial renewal window. diff --git a/core/test/fake_provisioning_server.cpp b/core/test/fake_provisioning_server.cpp index 359307e3..defe8229 100644 --- a/core/test/fake_provisioning_server.cpp +++ b/core/test/fake_provisioning_server.cpp @@ -275,18 +275,14 @@ bool FakeProvisioningServer::MakeResponse( // Next, we derive the keys from the keybox device key. This is Provisioning // 2.0 specific. // TODO(b/141438127): Add support for provisioing 3.0. - std::string mac_context; - GenerateMacContext(serialized_message, &mac_context); - std::vector mac_context_v(mac_context.begin(), mac_context.end()); - std::string enc_context; - GenerateEncryptContext(serialized_message, &enc_context); - std::vector enc_context_v(enc_context.begin(), enc_context.end()); wvoec::KeyDeriver key_deriver; + std::vector serialized_message_v(serialized_message.begin(), + serialized_message.end()); // Not only is this Prov 2.0 specific, it assumes the device is using the // standard test keybox. key_deriver.DeriveKeys(wvoec::kTestKeybox.device_key_, - sizeof(wvoec::kTestKeybox.device_key_), mac_context_v, - enc_context_v); + sizeof(wvoec::kTestKeybox.device_key_), + serialized_message_v); // Create a structure to hold the RSA private key. This is used by the key // deriver to encrypt the key. diff --git a/core/test/http_socket.cpp b/core/test/http_socket.cpp index f1a8649e..4504ce58 100644 --- a/core/test/http_socket.cpp +++ b/core/test/http_socket.cpp @@ -309,6 +309,7 @@ bool HttpSocket::Connect(int timeout_in_ms) { if (socket_fd_ < 0) { LOGE("Cannot open socket %s (port %s): errno = %d", domain_name_.c_str(), port_.c_str(), GetError()); + freeaddrinfo(addr_info); return false; } @@ -318,6 +319,7 @@ bool HttpSocket::Connect(int timeout_in_ms) { if (ioctlsocket(socket_fd_, FIONBIO, &mode) != 0) { LOGE("ioctlsocket error %s (port %s), wsa error = %d", domain_name_.c_str(), port_.c_str(), WSAGetLastError()); + freeaddrinfo(addr_info); CloseSocket(); return false; } @@ -326,12 +328,14 @@ bool HttpSocket::Connect(int timeout_in_ms) { if (original_flags == -1) { LOGE("fcntl error %s (port %s), errno = %d", domain_name_.c_str(), port_.c_str(), errno); + freeaddrinfo(addr_info); CloseSocket(); return false; } if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) { LOGE("fcntl error %s (port %s), errno = %d", domain_name_.c_str(), port_.c_str(), errno); + freeaddrinfo(addr_info); CloseSocket(); return false; } diff --git a/core/test/license_holder.cpp b/core/test/license_holder.cpp index f5bec604..0e9837b7 100644 --- a/core/test/license_holder.cpp +++ b/core/test/license_holder.cpp @@ -75,6 +75,13 @@ void LicenseHolder::ReloadLicense() { << "Failed to reload license for " << content_id(); } +void LicenseHolder::FailReloadLicense() { + const CdmResponseType status = + cdm_engine_->RestoreKey(session_id_, key_set_id_); + ASSERT_NE(KEY_ADDED, status) + << "Unexpected success loading license for " << content_id(); +} + void LicenseHolder::GenerateAndPostRenewalRequest( const std::string& policy_id) { event_listener_.set_renewal_needed(false); @@ -86,28 +93,70 @@ void LicenseHolder::GenerateAndPostRenewalRequest( MessageDumper::DumpRenewalRequest(request); } const std::string url = MakeUrl(config_.renewal_server(), policy_id); - renewal_in_flight_.reset(new UrlRequest(url)); - ASSERT_TRUE(renewal_in_flight_->is_connected()) + request_in_flight_.reset(new UrlRequest(url)); + ASSERT_TRUE(request_in_flight_->is_connected()) << "Failed for " << content_id(); - renewal_in_flight_->PostRequest(request.message); + request_in_flight_->PostRequest(request.message); } + void LicenseHolder::FetchRenewal() { - ASSERT_NE(renewal_in_flight_, nullptr) << "Failed for " << content_id(); + ASSERT_NE(request_in_flight_, nullptr) << "Failed for " << content_id(); ASSERT_NO_FATAL_FAILURE( - renewal_in_flight_->AssertOkResponse(&renewal_response_)) + request_in_flight_->AssertOkResponse(&request_response_)) << "Renewal failed for " << content_id(); } void LicenseHolder::LoadRenewal() { LicenseRequest license_request; - license_request.GetDrmMessage(renewal_response_, renewal_message_); + license_request.GetDrmMessage(request_response_, request_message_); if (config_.dump_golden_data()) { - MessageDumper::DumpRenewal(renewal_message_); + MessageDumper::DumpRenewal(request_message_); } - EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, renewal_message_)) + EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, request_message_)) << "Failed for " << content_id(); } +void LicenseHolder::GenerateAndPostReleaseRequest( + const std::string& policy_id) { + event_listener_.set_renewal_needed(false); + CdmKeyRequest request; + CdmAppParameterMap empty_app_parameters; + video_widevine::WidevinePsshData pssh; + pssh.set_content_id(content_id_); + const std::string init_data_string = MakePSSH(pssh); + const InitializationData init_data(kCencMimeType, init_data_string); + init_data.DumpToLogs(); + const CdmResponseType result = cdm_engine_->GenerateKeyRequest( + session_id_, key_set_id_, init_data, kLicenseTypeRelease, + empty_app_parameters, &request); + ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id(); + if (config_.dump_golden_data()) { + // TODO (b/295956275) vickymin: write DumpReleaseRequest function + // MessageDumper::DumpReleaseRequest(request); + } + const std::string url = MakeUrl(config_.renewal_server(), policy_id); + request_in_flight_.reset(new UrlRequest(url)); + ASSERT_TRUE(request_in_flight_->is_connected()) + << "Failed for " << content_id(); + request_in_flight_->PostRequest(request.message); +} + +void LicenseHolder::FetchRelease() { + ASSERT_NE(request_in_flight_, nullptr) << "Failed for " << content_id(); + ASSERT_NO_FATAL_FAILURE( + request_in_flight_->AssertOkResponse(&request_response_)) + << "Renewal failed for " << content_id(); +} + +void LicenseHolder::LoadRelease() { + LicenseRequest license_request; + license_request.GetDrmMessage(request_response_, request_message_); + if (config_.dump_golden_data()) { + // TODO (b/295956275) vickymin: write DumpRelease function + // MessageDumper::DumpRelease(request_message_); + } +} + void LicenseHolder::RemoveLicense() { EXPECT_EQ(NO_ERROR, cdm_engine_->RemoveLicense(session_id_)) << "Failed for " << content_id(); diff --git a/core/test/license_holder.h b/core/test/license_holder.h index 6c5174dc..405af278 100644 --- a/core/test/license_holder.h +++ b/core/test/license_holder.h @@ -66,12 +66,20 @@ class LicenseHolder { // ReloadLicense(). Also, the key_set_id must have been set previously. The // key_set_id is set by calling LoadLicense(), or by calling set_key_set_id(). void ReloadLicense(); + // Attempt to reload a license, but expect a failure. + void FailReloadLicense(); // Generate the renewal request, and send it to the server. void GenerateAndPostRenewalRequest(const std::string& policy_id); // Fetch the renewal response. This can add a few seconds of latency. void FetchRenewal(); // Load the renewal response that was fetched in FetchRenewal(). void LoadRenewal(); + // Generate the release request, and send it to the server. + void GenerateAndPostReleaseRequest(const std::string& policy_id); + // Fetch the release response. This can add a few seconds of latency. + void FetchRelease(); + // Load the release response that was fetched in FetchRelease(). + void LoadRelease(); // Releases the license and frees up entry in usage table. void RemoveLicense(); @@ -118,9 +126,9 @@ class LicenseHolder { CdmEngine* cdm_engine_ = nullptr; const ConfigTestEnv& config_; SimpleEventListener event_listener_; - std::unique_ptr renewal_in_flight_; - std::string renewal_message_; - std::string renewal_response_; + std::unique_ptr request_in_flight_; + std::string request_message_; + std::string request_response_; // Generate the license request. void GenerateKeyRequest(const InitializationData& init_data, diff --git a/core/test/license_unittest.cpp b/core/test/license_unittest.cpp index c57ca87e..81627d7c 100644 --- a/core/test/license_unittest.cpp +++ b/core/test/license_unittest.cpp @@ -136,7 +136,6 @@ const std::string kFakeKeyTooLong = const std::string kFakeKeyTooShort = a2bs_hex("06e247e7f924208011"); const std::string kFakeIv = a2bs_hex("3d515a3ee0be1687080ac59da9e0d69a"); const std::string kFakeBuildInfo = "Mock Crypto Session - License Test"; -const uint32_t kDefaultOemCryptoVersion = 18; class MockCryptoSession : public TestCryptoSession { public: @@ -207,7 +206,7 @@ class CdmLicenseTestPeer : public CdmLicense { using CdmLicense::HandleNewEntitledKeys; - void set_entitlement_keys(License license) { + void set_entitlement_keys(const License& license) { entitlement_keys_.CopyFrom(license.key()); } }; @@ -216,85 +215,84 @@ class CdmLicenseTest : public WvCdmTestBase { protected: CdmLicenseTest(const std::string& pssh = (kCencInitDataHdr + kCencPssh)) : pssh_(pssh) {} - void SetUp() override { WvCdmTestBase::SetUp(); - crypto_session_.reset(new MockCryptoSession(&crypto_metrics_)); + clock_ = new MockClock(); + crypto_session_ = new MockCryptoSession(&crypto_metrics_); + init_data_ = new InitializationData(CENC_INIT_DATA_FORMAT, pssh_); + policy_engine_ = new MockPolicyEngine(crypto_session_); + ON_CALL(*crypto_session_, GetSupportedCertificateTypes(NotNull())) .WillByDefault( DoAll(SetArgPointee<0>(kDefaultSupportedCertTypes), Return(true))); - // PolicyEngine will call GetApiVersion() on creation. - EXPECT_CALL(*crypto_session_, GetApiVersion(NotNull())) - .WillRepeatedly( - DoAll(SetArgPointee<0>(kDefaultOemCryptoVersion), Return(true))); - - policy_engine_.reset(new MockPolicyEngine(crypto_session_.get())); - - init_data_ = InitializationData(CENC_INIT_DATA_FORMAT, pssh_); - - clock_ = new MockClock(); - cdm_license_.reset(new CdmLicenseTestPeer(kCdmSessionId, clock_)); } void TearDown() override { - // Nullify pointers for objects owned by CdmLicense. - clock_ = nullptr; - - cdm_license_.reset(); - - // Release mock objects used by the CdmLicense. - // Order is important. - policy_engine_.reset(); - crypto_session_.reset(); + delete cdm_license_; + delete policy_engine_; + delete init_data_; + delete crypto_session_; + delete clock_; } - metrics::CryptoMetrics crypto_metrics_; - MockClock* clock_ = nullptr; // Owned by |cdm_license_|. - std::unique_ptr cdm_license_; - std::unique_ptr policy_engine_; - std::unique_ptr crypto_session_; + virtual void CreateCdmLicense() { + cdm_license_ = new CdmLicenseTestPeer(kCdmSessionId, clock_); + clock_ = nullptr; + } - InitializationData init_data_; + CdmLicenseTestPeer* cdm_license_ = nullptr; + MockClock* clock_ = nullptr; + metrics::CryptoMetrics crypto_metrics_; + MockCryptoSession* crypto_session_ = nullptr; + InitializationData* init_data_ = nullptr; + MockPolicyEngine* policy_engine_ = nullptr; std::string pssh_; }; TEST_F(CdmLicenseTest, InitSuccess) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); + + CreateCdmLicense(); EXPECT_TRUE(cdm_license_->Init(false, kEmptyServiceCertificate, - crypto_session_.get(), policy_engine_.get())); + crypto_session_, policy_engine_)); } TEST_F(CdmLicenseTest, InitFail_CryptoSessionNull) { + CreateCdmLicense(); EXPECT_FALSE(cdm_license_->Init(false, kEmptyServiceCertificate, nullptr, - policy_engine_.get())); + policy_engine_)); } TEST_F(CdmLicenseTest, InitFail_PolicyEngineNull) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); + CreateCdmLicense(); EXPECT_FALSE(cdm_license_->Init(false, kEmptyServiceCertificate, - crypto_session_.get(), nullptr)); + crypto_session_, nullptr)); } TEST_F(CdmLicenseTest, InitWithEmptyServiceCert) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); + CreateCdmLicense(); EXPECT_TRUE(cdm_license_->Init(true, kEmptyServiceCertificate, - crypto_session_.get(), policy_engine_.get())); + crypto_session_, policy_engine_)); } TEST_F(CdmLicenseTest, InitWithInvalidServiceCert) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); + CreateCdmLicense(); EXPECT_FALSE(cdm_license_->Init(true, kInvalidServiceCertificate, - crypto_session_.get(), policy_engine_.get())); + crypto_session_, policy_engine_)); } TEST_F(CdmLicenseTest, InitWithServiceCert) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); + CreateCdmLicense(); EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, - crypto_session_.get(), policy_engine_.get())); + crypto_session_, policy_engine_)); } TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { @@ -337,14 +335,15 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { .WillOnce( DoAll(SetArgPointee<0>(kWatermarkingConfigurable), Return(true))); + CreateCdmLicense(); EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, - crypto_session_.get(), policy_engine_.get())); + crypto_session_, policy_engine_)); CdmAppParameterMap app_parameters; CdmKeyMessage signed_request; std::string server_url; EXPECT_EQ(cdm_license_->PrepareKeyRequest( - init_data_, kToken, kLicenseTypeStreaming, app_parameters, + *init_data_, kToken, kLicenseTypeStreaming, app_parameters, &signed_request, &server_url), KEY_MESSAGE); @@ -428,6 +427,7 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { 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()); + EXPECT_FALSE(license_request.client_version().empty()); } TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { @@ -470,14 +470,15 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { .WillOnce( DoAll(SetArgPointee<0>(kWatermarkingNotSupported), Return(true))); + CreateCdmLicense(); EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, - crypto_session_.get(), policy_engine_.get())); + crypto_session_, policy_engine_)); CdmAppParameterMap app_parameters; CdmKeyMessage signed_request; std::string server_url; EXPECT_EQ(cdm_license_->PrepareKeyRequest( - init_data_, kToken, kLicenseTypeStreaming, app_parameters, + *init_data_, kToken, kLicenseTypeStreaming, app_parameters, &signed_request, &server_url), KEY_MESSAGE); @@ -561,6 +562,7 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { 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()); + EXPECT_FALSE(license_request.client_version().empty()); } struct EntitledKeyVariant { @@ -614,8 +616,9 @@ TEST_P(CdmLicenseEntitledKeyTest, LoadsEntitledKeys) { } // Set up the CdmLicense with the mocks and fake entitlement key - EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, - crypto_session_.get(), policy_engine_.get())); + CreateCdmLicense(); + ASSERT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, + crypto_session_, policy_engine_)); cdm_license_->set_entitlement_keys(entitlement_license); // Call the function under test and check its return value diff --git a/core/test/message_dumper.cpp b/core/test/message_dumper.cpp index ee3fa8ef..48ab9bde 100644 --- a/core/test/message_dumper.cpp +++ b/core/test/message_dumper.cpp @@ -34,9 +34,10 @@ void DumpHeader(std::ofstream* out, const std::string& type) { void DumpHex(std::ofstream* out, const std::string& name, const std::string& value) { + const auto out_flags = out->flags(); *out << "const uint8_t " << name << "_raw[] = {\n"; *out << " "; - for (unsigned int i = 0; i < value.length(); i++) { + for (size_t i = 0; i < value.length(); i++) { if ((i > 0) && (i % 10 == 0)) *out << "\n "; uint8_t c = value[i]; *out << "0x" << std::hex << std::setw(2) << std::setfill('0') << int(c) @@ -46,7 +47,7 @@ void DumpHex(std::ofstream* out, const std::string& name, *out << name << "_ = std::string (\n" << " reinterpret_cast(" << name << "_raw), \n" << " sizeof(" << name << "_raw));\n"; - *out << std::dec; // Turn off hex when we're done. + out->flags(out_flags); // Restore flags when we're done. } void LogTimer(const char* field, bool set, int64_t time) { @@ -251,7 +252,7 @@ void MessageDumper::DumpProvisioningRequest( 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 v17 core message."); + LOGD("Provisioning 4.0 does not have a core message."); } else { SignedProvisioningMessage signed_response; if (!signed_response.ParseFromString(response)) { diff --git a/core/test/parallel_operations_test.cpp b/core/test/parallel_operations_test.cpp index bf47fa1e..d8a93e4a 100644 --- a/core/test/parallel_operations_test.cpp +++ b/core/test/parallel_operations_test.cpp @@ -135,7 +135,7 @@ class ParallelCdmTest : public WvCdmTestBaseWithEngine, url.c_str()); HttpHeaderFields fields; if (url_request.GetDebugHeaderFields(http_response, &fields)) { - for (auto field : fields) { + for (const auto& field : fields) { LOGD(" %s: %s", field.first.c_str(), field.second.c_str()); } } diff --git a/core/test/policy_engine_unittest.cpp b/core/test/policy_engine_unittest.cpp index f9c17dc7..d54990d4 100644 --- a/core/test/policy_engine_unittest.cpp +++ b/core/test/policy_engine_unittest.cpp @@ -210,11 +210,12 @@ class PolicyEngineTest : public WvCdmTestBase { void ExpectSessionKeysChange(CdmKeyStatus expected_key_status, bool expected_has_new_usable_key, KeyId expected_keyid) { - EXPECT_CALL(mock_event_listener_, - OnSessionKeysChange(kSessionId, - UnorderedElementsAre(Pair( - expected_keyid, expected_key_status)), - expected_has_new_usable_key)); + EXPECT_CALL( + mock_event_listener_, + OnSessionKeysChange(kSessionId, + UnorderedElementsAre(Pair(std::move(expected_keyid), + expected_key_status)), + expected_has_new_usable_key)); } void ExpectSessionKeysChange(CdmKeyStatus expected_key1_status, @@ -266,7 +267,7 @@ class PolicyEngineTestV18 : public PolicyEngineTest { } }; -TEST_F(PolicyEngineTestV16, NoLicense) { +TEST_F(PolicyEngineTest, NoLicense) { EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); } @@ -2825,10 +2826,6 @@ TEST_F(PolicyEngineTestV16, PlaybackOk_RestoreWithoutPlaybackTimes) { EXPECT_TRUE(policy_engine_->CanDecryptContent(kKeyId)); } -TEST_F(PolicyEngineTestV18, NoLicense) { - EXPECT_FALSE(policy_engine_->CanDecryptContent(kKeyId)); -} - // These tests exercise license policy when OEMCrypto supports v18. // The following scenarios are from the duration-and-renewal doc. // Verifies correct reporting of events, OnSessionRenewalNeeded, diff --git a/core/test/policy_integration_test.cpp b/core/test/policy_integration_test.cpp index 258df3e8..f53e16c0 100644 --- a/core/test/policy_integration_test.cpp +++ b/core/test/policy_integration_test.cpp @@ -22,6 +22,7 @@ #include "provisioning_holder.h" #include "test_base.h" #include "test_printers.h" +#include "test_sleep.h" #include "wv_cdm_types.h" namespace wvcdm { @@ -76,10 +77,67 @@ TEST_F(CorePIGTest, OfflineWithPST) { ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); } -// This test verifies that the system can download and install license with a -// key that requires secure buffers. It also verifies that we cannot decrypt to -// a non-secure buffer using this key, but that we can decrypt to a secure -// buffer, if the test harness supports secure buffers. +TEST_F(CorePIGTest, OfflineMultipleLicensesWithDefrag) { + const KeyId key_id = "0000000000000000"; + + // 1. Open a session, load license, close session + LicenseHolder holder1("CDM_OfflineWithPST", &cdm_engine_, config_); + holder1.set_can_persist(true); + ASSERT_NO_FATAL_FAILURE(holder1.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder1.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder1.LoadLicense()); + ASSERT_NO_FATAL_FAILURE(holder1.CloseSession()); + + // 2. Open a session, load license, keep session open + LicenseHolder holder2("CDM_OfflineWithPST", &cdm_engine_, config_); + holder2.set_can_persist(true); + ASSERT_NO_FATAL_FAILURE(holder2.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder2.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder2.LoadLicense()); + + // 3. Remove first license + ASSERT_NO_FATAL_FAILURE(holder1.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder1.ReloadLicense()); + ASSERT_NO_FATAL_FAILURE(holder1.RemoveLicense()); + ASSERT_NO_FATAL_FAILURE(holder1.CloseSession()); + + // 4. Open a session, load license + LicenseHolder holder3("CDM_OfflineWithPST", &cdm_engine_, config_); + holder3.set_can_persist(true); + ASSERT_NO_FATAL_FAILURE(holder3.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder3.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder3.LoadLicense()); + EXPECT_EQ(NO_ERROR, holder3.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder3.CloseSession()); + + ASSERT_NO_FATAL_FAILURE(holder2.CloseSession()); + + // Ensure first offline license can no longer be used + ASSERT_NO_FATAL_FAILURE(holder1.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder1.FailReloadLicense()); + EXPECT_NE(NO_ERROR, holder1.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder1.CloseSession()); + + // Ensure second and third offline licenses can be used + ASSERT_NO_FATAL_FAILURE(holder2.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder2.ReloadLicense()); + EXPECT_EQ(NO_ERROR, holder2.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder2.RemoveLicense()); + ASSERT_NO_FATAL_FAILURE(holder2.CloseSession()); + + ASSERT_NO_FATAL_FAILURE(holder3.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder3.ReloadLicense()); + EXPECT_EQ(NO_ERROR, holder3.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder3.RemoveLicense()); + ASSERT_NO_FATAL_FAILURE(holder3.CloseSession()); +} + +/** + * This test verifies that the system can download and install license with a + * key that requires secure buffers. It also verifies that we cannot decrypt to + * a non-secure buffer using this key, but that we can decrypt to a secure + * buffer, if the test harness supports secure buffers. + */ TEST_F(CorePIGTest, OfflineHWSecureRequired) { LicenseHolder holder("CDM_OfflineHWSecureRequired", &cdm_engine_, config_); holder.set_can_persist(true); @@ -118,6 +176,50 @@ TEST_F(CorePIGTest, OfflineHWSecureRequired) { ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); } +/** + * Should be able to request license, perform playback, generate a license + * release, and receive the release response. + */ +TEST_F(CorePIGTest, LicenseRelease1) { + LicenseHolder holder("CDM_UnlimitedStreaming_can_persist", &cdm_engine_, + config_); + holder.set_can_persist(true); + const KeyId key_id = "0000000000000000"; + + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); + EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder.GenerateAndPostReleaseRequest( + "CDM_UnlimitedStreaming_can_persist")); + EXPECT_NE(NO_ERROR, holder.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder.FetchRelease()); + ASSERT_NO_FATAL_FAILURE(holder.LoadRelease()); + EXPECT_NE(NO_ERROR, holder.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); +} + +/** + * Should be able to request license, wait some time, generate a license + * release, and receive the release response. + */ +TEST_F(CorePIGTest, LicenseRelease2) { + LicenseHolder holder("CDM_UnlimitedStreaming_can_persist", &cdm_engine_, + config_); + holder.set_can_persist(true); + const KeyId key_id = "0000000000000000"; + + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); + wvutil::TestSleep::Sleep(10); + ASSERT_NO_FATAL_FAILURE(holder.GenerateAndPostReleaseRequest( + "CDM_UnlimitedStreaming_can_persist")); + ASSERT_NO_FATAL_FAILURE(holder.FetchRelease()); + ASSERT_NO_FATAL_FAILURE(holder.LoadRelease()); + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); +} + TEST_F(CorePIGTest, CastReceiverProvisioningUsingCdm) { const std::string digest_hex_str = // digest info header diff --git a/core/test/provisioning_holder.cpp b/core/test/provisioning_holder.cpp index 954b2eff..697beb97 100644 --- a/core/test/provisioning_holder.cpp +++ b/core/test/provisioning_holder.cpp @@ -185,6 +185,7 @@ std::string ProvisioningHolder::DumpProvAttempt(const std::string& request, if (result != OEMCrypto_SUCCESS) { info << "--- ERROR GETTING BCC. result=" << result; } else { + bcc.resize(bcc_length); info << "BCC = (len=" << bcc_length << ") " << wvutil::unlimited_b2a_hex(bcc) << "\n" << "Additional Sig = (len=" << signature_length << ") " diff --git a/core/test/test_base.cpp b/core/test/test_base.cpp index 26d44167..a72c92d8 100644 --- a/core/test/test_base.cpp +++ b/core/test/test_base.cpp @@ -145,14 +145,60 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) { std::cout << " --dump_golden_data" << std::endl; std::cout << " Dump the license request and response from the server." + << std::endl + << std::endl; + + std::cout << " --skip-slow-tests" << std::endl; + std::cout << " Skips tests that are known to be slow." << std::endl; + std::cout << " --skip-sleepy-tests" << std::endl; + std::cout << " Skips tests that sleep a lot." << std::endl; + std::cout << " --run-sleepy-tests" << std::endl; + std::cout << " Allow tests that sleep a lot (used with --skip-slow-tests)" + << std::endl; + std::cout << " --skip-decryption-stress-tests" << std::endl; + std::cout << " Skips decryption stress tests." << std::endl; + std::cout << " --run-decryption-stress-tests" << std::endl; + std::cout << " Allow decryption stress tests (used with " + << "--skip-slow-tests)" << std::endl; + std::cout << " --skip-request-flood-tests" << std::endl; + std::cout << " Skips tests that generate a lot of requests." << std::endl; + std::cout << " --run-request-flood-tests" << std::endl; + std::cout << " Allow tests that generate a lot of requests (used with " + << "--skip-slow-tests)" << std::endl; + std::cout << " --skip-multi-thread-stress-tests" << std::endl; + std::cout << " Skips tests that stress thread protections." << std::endl; + std::cout << " --run-multi-thread-stress-tests" << std::endl; + std::cout << " Allow tests that stress thread protections (used with " + << "--skip-slow-tests)" << std::endl; + std::cout << " --skip-usage-table-stress-tests" << std::endl; + std::cout << " Skips tests that stess the usage table." << std::endl; + std::cout << " --run-usage-table-stress-tests" << std::endl; + std::cout << " Allow tests that stess the usage table (used with " + << "--skip-slow-tests)" << std::endl << std::endl; std::cout << extra_help_text << std::endl; } + +enum OptionalBool { + kBoolUnset, + kBoolFalse, + kBoolTrue, +}; + +bool UnwrapOptionalBool(OptionalBool value, bool default_value) { + return (value == kBoolUnset) ? default_value : (value == kBoolTrue); +} } // namespace +// Static WvCdmTestBase variables. std::unique_ptr WvCdmTestBase::default_config_; bool WvCdmTestBase::use_qa_test_keybox_ = false; +bool WvCdmTestBase::skip_sleepy_tests_ = false; +bool WvCdmTestBase::skip_decryption_stress_tests_ = false; +bool WvCdmTestBase::skip_request_flood_tests_ = false; +bool WvCdmTestBase::skip_multi_thread_stress_tests_ = false; +bool WvCdmTestBase::skip_usage_table_stress_tests_ = false; void WvCdmTestBase::StripeBuffer(std::vector* buffer, size_t size, uint8_t init) { @@ -208,7 +254,6 @@ TestCryptoSession::TestCryptoSession(metrics::CryptoMetrics* crypto_metrics, void TestCryptoSession::MaybeInstallTestKeybox() { if (IsTestKeyboxNeeded()) { - CryptoSession::SetAllowTestKeybox(true); ReinitializeForTest(); WvCdmTestBase::InstallTestRootOfTrust(); } @@ -312,8 +357,7 @@ void WvCdmTestBase::InstallTestRootOfTrust() { } } -WvCdmTestBase::WvCdmTestBase() - : config_(*default_config_), binary_provisioning_(false) { +WvCdmTestBase::WvCdmTestBase() : config_(*default_config_) { const ::testing::TestInfo* const test_info = ::testing::UnitTest::GetInstance()->current_test_info(); @@ -374,53 +418,29 @@ void WvCdmTestBase::EnsureProvisioned() { ASSERT_EQ(NO_ERROR, cdm_engine.CloseSession(session_id)); } -bool WvCdmTestBase::ExtractSignedMessage(const std::string& response, - std::string* result) { - static const std::string kMessageStart = "\"signedResponse\": \""; - static const std::string kMessageEnd = "\""; - std::string response_string; - size_t start = response.find(kMessageStart); - - if (start == response.npos) { - // Assume serialized protobuf message. - result->assign(response); - } else { - // Assume JSON-wrapped protobuf. - size_t end = response.find(kMessageEnd, start + kMessageStart.length()); - if (end == response.npos) { - LOGE("ExtractSignedMessage cannot locate end substring"); - result->clear(); - return false; - } - size_t result_string_size = end - start - kMessageStart.length(); - result->assign(response, start + kMessageStart.length(), - result_string_size); - } - - if (result->empty()) { - LOGE("ExtractSignedMessage: Response message is empty"); - return false; - } - return true; -} - bool WvCdmTestBase::Initialize(int argc, const char* const argv[], const std::string& extra_help_text) { Properties::Init(); bool is_cast_receiver = false; - bool filter_tests = true; bool show_usage = false; int verbosity = 0; + bool skip_slow_tests = false; + OptionalBool skip_sleepy_tests = kBoolUnset; + OptionalBool skip_decryption_stress_tests = kBoolUnset; + OptionalBool skip_request_flood_tests = kBoolUnset; + OptionalBool skip_multi_thread_stress_tests = kBoolUnset; + OptionalBool skip_usage_table_stress_tests = kBoolUnset; + default_config_.reset(new ConfigTestEnv(kContentProtectionUatServer)); // Skip the first element, which is the program name. const std::vector args(argv + 1, argv + argc); for (const std::string& arg : args) { - if (arg == "--verbose" || arg == "-v") { + if (arg == "--help" || arg == "-h") { + show_usage = true; + } else if (arg == "--verbose" || arg == "-v") { ++verbosity; - } else if (arg == "--no_filter") { - filter_tests = false; } else if (arg == "--cast") { is_cast_receiver = true; } else if (arg == "--fake_sleep") { @@ -435,6 +455,28 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], } else if (arg == "--dump_golden_data") { default_config_->set_dump_golden_data(true); testing::AddGlobalTestEnvironment(new MessageDumper); + } else if (arg == "--skip-slow-tests") { + skip_slow_tests = true; + } else if (arg == "--skip-sleepy-tests") { + skip_sleepy_tests = kBoolTrue; + } else if (arg == "--run-sleepy-tests") { + skip_sleepy_tests = kBoolFalse; + } else if (arg == "--skip-decryption-stress-tests") { + skip_decryption_stress_tests = kBoolTrue; + } else if (arg == "--run-decryption-stress-tests") { + skip_decryption_stress_tests = kBoolFalse; + } else if (arg == "--skip-request-flood-tests") { + skip_request_flood_tests = kBoolTrue; + } else if (arg == "--run-request-flood-tests") { + skip_request_flood_tests = kBoolFalse; + } else if (arg == "--skip-multi-thread-stress-tests") { + skip_multi_thread_stress_tests = kBoolTrue; + } else if (arg == "--run-multi-thread-stress-tests") { + skip_multi_thread_stress_tests = kBoolFalse; + } else if (arg == "--skip-usage-table-stress-tests") { + skip_usage_table_stress_tests = kBoolTrue; + } else if (arg == "--run-usage-table-stress-tests") { + skip_usage_table_stress_tests = kBoolFalse; } else { const auto index = arg.find('='); if (index == std::string::npos) { @@ -529,12 +571,16 @@ bool WvCdmTestBase::Initialize(int argc, const char* const argv[], // support being a cast receiver. wvoec::global_features.set_cast_receiver(is_cast_receiver); } - // If the user requests --no_filter, we don't change the filter, otherwise, we - // filter out features that are not supported. - if (filter_tests) { - ::testing::GTEST_FLAG(filter) = - wvoec::global_features.RestrictFilter(::testing::GTEST_FLAG(filter)); - } + + skip_sleepy_tests_ = UnwrapOptionalBool(skip_sleepy_tests, skip_slow_tests); + skip_decryption_stress_tests_ = + UnwrapOptionalBool(skip_decryption_stress_tests, skip_slow_tests); + skip_request_flood_tests_ = + UnwrapOptionalBool(skip_request_flood_tests, skip_slow_tests); + skip_multi_thread_stress_tests_ = + UnwrapOptionalBool(skip_multi_thread_stress_tests, skip_slow_tests); + skip_usage_table_stress_tests_ = + UnwrapOptionalBool(skip_usage_table_stress_tests, skip_slow_tests); return true; } diff --git a/core/test/test_base.h b/core/test/test_base.h index 98c3dd4f..d95cef40 100644 --- a/core/test/test_base.h +++ b/core/test/test_base.h @@ -48,16 +48,22 @@ class WvCdmTestBase : public ::testing::Test { // Calls Provision() if not already provisioned. virtual void EnsureProvisioned(); - // Locate the portion of the server's provisioning response message that is - // between the strings jason_start_substr and json_end_substr. Returns the - // string through *result. If the start substring match fails, assume the - // entire string represents a serialized protobuf mesaage and return true with - // the entire string. If the end_substring match fails, return false with an - // empty *result. - bool ExtractSignedMessage(const std::string& response, std::string* result); + virtual bool skip_sleepy_tests() const { return skip_sleepy_tests_; } + virtual bool skip_decryption_stress_tests() const { + return skip_decryption_stress_tests_; + } + virtual bool skip_request_flood_tests() const { + return skip_request_flood_tests_; + } + virtual bool skip_multi_thread_stress_tests() const { + return skip_multi_thread_stress_tests_; + } + virtual bool skip_usage_table_stress_tests() const { + return skip_usage_table_stress_tests_; + } - // Fill a buffer with some nonconstant data of the given size. The first - // byte will be set to to help you find the buffer when debugging. + // Fill a buffer with some nonconstant data of the given size. The first byte + // will be set to to help you find the buffer when debugging. static void StripeBuffer(std::vector* buffer, size_t size, uint8_t init); @@ -86,7 +92,15 @@ class WvCdmTestBase : public ::testing::Test { // This should be set by test subclasses BEFORE calling SetUp -- i.e. in the // tests's constructor. - bool binary_provisioning_; + bool binary_provisioning_ = false; + + private: + // Skip flags for long running tests. + static bool skip_sleepy_tests_; + static bool skip_decryption_stress_tests_; + static bool skip_request_flood_tests_; + static bool skip_multi_thread_stress_tests_; + static bool skip_usage_table_stress_tests_; }; // This just makes the constructor public so that we can create one with dummy @@ -95,7 +109,7 @@ class TestCdmEngine : public CdmEngine { public: TestCdmEngine(wvutil::FileSystem* file_system, std::shared_ptr metrics) - : CdmEngine(file_system, metrics) {} + : CdmEngine(file_system, std::move(metrics)) {} const CdmSession* GetCdmSession(std::string sessionId) const; }; diff --git a/core/test/test_printers.cpp b/core/test/test_printers.cpp index b39b4bc4..f5747f52 100644 --- a/core/test/test_printers.cpp +++ b/core/test/test_printers.cpp @@ -79,3 +79,20 @@ void PrintTo(const SystemState& state, std::ostream* os) { } } // namespace okp } // namespace wvcdm + +namespace std { +void PrintTo(future_status status, ostream* os) { + switch (status) { + case future_status::ready: + *os << "future_status::ready"; + return; + case future_status::timeout: + *os << "future_status::timeout"; + return; + case future_status::deferred: + *os << "future_status::deferred"; + return; + } + *os << "(status) << ")>"; +} +} // namespace std diff --git a/core/test/test_printers.h b/core/test/test_printers.h index 174872c7..2a3b72b4 100644 --- a/core/test/test_printers.h +++ b/core/test/test_printers.h @@ -7,6 +7,7 @@ #ifndef CDM_TEST_PRINTERS_H_ #define CDM_TEST_PRINTERS_H_ +#include #include #include "OEMCryptoCENC.h" @@ -24,4 +25,9 @@ namespace okp { void PrintTo(const SystemState& state, std::ostream* os); } // namespace okp } // namespace wvcdm + +namespace std { +void PrintTo(future_status status, ostream* os); +} // namespace std + #endif // CDM_TEST_PRINTERS_H_ diff --git a/core/test/url_request.cpp b/core/test/url_request.cpp index 302816b6..b7f4a7c1 100644 --- a/core/test/url_request.cpp +++ b/core/test/url_request.cpp @@ -120,7 +120,7 @@ bool UrlRequest::GetResponse(std::string* message) { } } - ConcatenateChunkedResponse(response, message); + ConcatenateChunkedResponse(std::move(response), message); LOGV("HTTP response from %s: (%zu): %s", socket_.url().c_str(), message->size(), message->c_str()); return true; diff --git a/factory_upload_tool/ce/log.cpp b/factory_upload_tool/ce/log.cpp index dabb509b..f7197430 100644 --- a/factory_upload_tool/ce/log.cpp +++ b/factory_upload_tool/ce/log.cpp @@ -20,8 +20,8 @@ void InitLogging() {} void Log(const char* file, const char* function, int line, LogPriority level, const char* fmt, ...) { const char* severities[] = {"ERROR", "WARN", "INFO", "DEBUG", "VERBOSE"}; - if (level >= - static_cast(sizeof(severities) / sizeof(*severities))) { + if (level < 0 || level >= static_cast(sizeof(severities) / + sizeof(severities[0]))) { fprintf(stderr, "[FATAL:%s(%d)] Invalid log priority level: %d\n", file, line, level); return; diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h index e349200a..0131355b 100644 --- a/oemcrypto/include/OEMCryptoCENC.h +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -3,7 +3,7 @@ // License Agreement. /** - * @mainpage OEMCrypto API v18.5 + * @mainpage OEMCrypto API v19.1 * * 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 @@ -103,8 +103,8 @@ * provisioning procedure and implemented in the library, but are not called * from the Widevine DRM Plugin during normal operation. * - * @defgroup prov40 OEM Certificate and Provisioning 4.0 API - * Functions that are needed process a boot chain certificate. + * @defgroup prov40 BCC and Provisioning 4.0 API + * Functions that are needed process a boot certificate chain (BCC). * * The OEMCrypto API allows for a device to be initially provisioned with a * keybox or with an OEM certificate in the factory, or to use a boot chain @@ -143,6 +143,11 @@ * This can be used to implement content key rotation without requiring new * licenses, or access to multiple pieces of content with a single license. * + * For OEMCrypto supporting CE CDM and Android MediaDrm, each license session + * must support at least one entitled key session. For OEMcrypto supporting + * MediaCas, each license session must support at least six entitled key + * sessions. + * * @defgroup test_verify Test and Verification API * Functions that are designed to help test OEMCrypto and the device. They are * not used during normal operation. Some functions, like those that test the @@ -300,9 +305,9 @@ typedef struct { * region. * * @param[in] num_bytes_clear: The number of unprotected bytes in this - * subsample. The clear bytes come before the encrypted bytes. + * subsample. The unprotected bytes come before the protected bytes. * @param[in] num_bytes_encrypted: The number of protected bytes in this - * subsample. The protected bytes come after the clear bytes. + * subsample. The protected bytes come after the unprotected bytes. * @param[in] subsample_flags: bitwise flags indicating if this is the first, * middle, or last subsample in a sample. 1 = first subsample, 2 = last * subsample, 3 = both first and last subsample, 0 = neither first nor last @@ -319,10 +324,10 @@ typedef struct { * This struct changed in API version 16. */ typedef struct { - size_t num_bytes_clear; - size_t num_bytes_encrypted; - uint8_t subsample_flags; // is this the first/last subsample in a sample? - size_t block_offset; // used for CTR "cenc" mode only. + size_t num_bytes_clear; // Number of bytes in the unprotected region. + size_t num_bytes_encrypted; // Number of bytes in the protected region. + uint8_t subsample_flags; // is this the first/last subsample in a sample? + size_t block_offset; // used for CTR "cenc" mode only. } OEMCrypto_SubSampleDescription; #define OEMCrypto_FirstSubsample 1 @@ -462,9 +467,11 @@ typedef enum OEMCrypto_Clock_Security_Level { } OEMCrypto_Clock_Security_Level; typedef uint8_t RSA_Padding_Scheme; -// RSASSA-PSS with SHA1. +// RSASSA-PSS with SHA1. Scheme used for DRM certificates for signing a license +// request. #define kSign_RSASSA_PSS ((RSA_Padding_Scheme)0x1) -// PKCS1 with block type 1 padding (only). +// PKCS1 with block type 1 padding (only). Keys used with x509 Cast Receiver +// certificates. #define kSign_PKCS1_Block1 ((RSA_Padding_Scheme)0x2) /// @} @@ -518,6 +525,16 @@ typedef enum OEMCrypto_ProvisioningMethod { OEMCrypto_DrmReprovisioning = 5 } OEMCrypto_ProvisioningMethod; +/** + Return value for OEMCrypto_GetBCCType(). + */ +typedef enum OEMCrypto_BCCType { + // Boot certificate chain in CBOR format. + OEMCrypto_CBOR = 0, + // Boot certificate chain in X509 format. + OEMCrypto_X509 = 1, +} OEMCrypto_BCCType; + /** Return value for OEMCrypto_GetWatermarkingSupport(). */ @@ -607,7 +624,7 @@ typedef enum OEMCrypto_SignatureHashAlgorithm { #define OEMCrypto_RewrapDeviceRSAKey _oecc18 #define OEMCrypto_LoadDeviceRSAKey _oecc19 #define OEMCrypto_GenerateRSASignature_V8 _oecc20 -#define OEMCrypto_DeriveKeysFromSessionKey _oecc21 +#define OEMCrypto_DeriveKeysFromSessionKey_V18 _oecc21 #define OEMCrypto_APIVersion _oecc22 #define OEMCrypto_SecurityLevel_V16 _oecc23 #define OEMCrypto_Generic_Encrypt_V17 _oecc24 @@ -664,20 +681,20 @@ typedef enum OEMCrypto_SignatureHashAlgorithm { #define OEMCrypto_ResourceRatingTier _oecc85 #define OEMCrypto_SupportsDecryptHash _oecc86 #define OEMCrypto_InitializeDecryptHash _oecc87 -#define OEMCrypto_SetDecryptHash _oecc88 +#define OEMCrypto_SetDecryptHash_V18 _oecc88 #define OEMCrypto_GetHashErrorCode _oecc89 #define OEMCrypto_BuildInformation_V16 _oecc90 #define OEMCrypto_RefreshKeys _oecc91 #define OEMCrypto_LoadEntitledContentKeys_V16 _oecc92 #define OEMCrypto_CopyBuffer _oecc93 #define OEMCrypto_MaximumUsageTableHeaderSize _oecc94 -#define OEMCrypto_GenerateDerivedKeys _oecc95 +#define OEMCrypto_GenerateDerivedKeys_V18 _oecc95 #define OEMCrypto_PrepAndSignLicenseRequest _oecc96 #define OEMCrypto_PrepAndSignRenewalRequest _oecc97 #define OEMCrypto_PrepAndSignProvisioningRequest _oecc98 -#define OEMCrypto_LoadLicense _oecc99 +#define OEMCrypto_LoadLicense_V18 _oecc99 #define OEMCrypto_LoadRenewal _oecc101 -#define OEMCrypto_LoadProvisioning _oecc102 +#define OEMCrypto_LoadProvisioning_V18 _oecc102 #define OEMCrypto_LoadOEMPrivateKey _oecc103 #define OEMCrypto_GetOEMPublicCertificate _oecc104 #define OEMCrypto_DecryptCENC_V17 _oecc105 @@ -717,8 +734,16 @@ typedef enum OEMCrypto_SignatureHashAlgorithm { #define OEMCrypto_EnterTestMode _oecc140 #define OEMCrypto_GetDeviceSignedCsrPayload _oecc141 #define OEMCrypto_FactoryInstallBCCSignature _oecc142 -#define OEMCrypto_GetEmbeddedDrmCertificate _oecc143 -#define OEMCrypto_UseSecondaryKey _oecc144 +#define OEMCrypto_SetDecryptHash _oecc143 +#define OEMCrypto_LoadLicense _oecc144 +#define OEMCrypto_LoadProvisioning _oecc145 +#define OEMCrypto_LoadProvisioningCast _oecc146 +#define OEMCrypto_PrepAndSignReleaseRequest _oecc147 +#define OEMCrypto_GetUsageEntryInfo _oecc148 +#define OEMCrypto_GetBCCType _oecc149 +#define OEMCrypto_LoadRelease _oecc150 +#define OEMCrypto_GetEmbeddedDrmCertificate _oecc151 +#define OEMCrypto_UseSecondaryKey _oecc152 // clang-format on /// @addtogroup initcontrol @@ -956,159 +981,6 @@ OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session); */ OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session); -/** - * Generates three secondary keys, mac_key[server], mac_key[client], and - * encrypt_key, for handling signing and content key decryption under the - * license server protocol for CENC. - * - * Refer to the Key Derivation section above for more details. This function - * computes the AES-128-CMAC of the enc_key_context and stores it in secure - * memory as the encrypt_key. It then computes four cycles of AES-128-CMAC of - * the mac_key_context and stores it in the mac_keys -- the first two cycles - * generate the mac_key[server] and the second two cycles generate the - * mac_key[client]. These two keys will be stored until the next call to - * OEMCrypto_LoadLicense(). The device key from the keybox is used as the key - * for the AES-128-CMAC. - * - * @param[in] session: handle for the session to be used. - * @param[in] mac_key_context: pointer to memory containing context data for - * computing the HMAC generation key. - * @param[in] mac_key_context_length: length of the HMAC key context data, in - * bytes. - * @param[in] enc_key_context: pointer to memory containing context data for - * computing the encryption key. - * @param[in] enc_key_context_length: length of the encryption key context data, - * in bytes. - * - * Results: - * mac_key[server]: the 256 bit mac key is generated and stored in secure - * memory. - * mac_key[client]: the 256 bit mac key is generated and stored in secure - * memory. - * enc_key: the 128 bit encryption key is generated and stored in secure - * memory. - * - * @retval OEMCrypto_SUCCESS success - * @retval OEMCrypto_ERROR_NO_DEVICE_KEY - * @retval OEMCrypto_ERROR_INVALID_SESSION - * @retval OEMCrypto_ERROR_INVALID_CONTEXT - * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES - * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE - * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE - * @retval OEMCrypto_ERROR_SESSION_LOST_STATE - * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED - * - * @buffer_size - * OEMCrypto shall support mac_key_context and enc_key_context sizes as - * described in the section OEMCrypto_ResourceRatingTier() for messages. The - * key derivation context is about 25 bytes prepended to the request message. - * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffers are - * too large. - * - * @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 will not be called simultaneously with initialization - * or usage table functions. It is as if the CDM holds a write lock for this - * session, and a read lock on the OEMCrypto system. - * - * @version - * This method changed in API version 12. - */ -OEMCryptoResult OEMCrypto_GenerateDerivedKeys( - OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* mac_key_context, - size_t mac_key_context_length, - const OEMCrypto_SharedMemory* enc_key_context, - size_t enc_key_context_length); - -/** - * Generates three secondary keys, mac_key[server], mac_key[client] and - * encrypt_key, for handling signing and content key decryption under the - * license server protocol for CENC. - * - * This function is similar to OEMCrypto_GenerateDerivedKeys(), except that it - * uses a session key to generate the secondary keys instead of the Widevine - * Keybox device key. These three keys will be stored in secure memory until - * the next call to LoadLicense or LoadProvisioning. - * - * If the session's private key is an RSA key, then the session key is passed - * in encrypted by the device RSA public key as the derivation_key, and must - * be decrypted with the RSA private key before use. - * - * If the sesion's private key is an ECC key, then the session key is the - * SHA256 of the shared secret key calculated by ECDH between the device's - * ECC private key and the derivation_key. See the document "OEMCrypto - * Elliptic Curve Support" for details. - * - * Once the enc_key and mac_keys have been generated, all calls to - * OEMCrypto_LoadLicense() proceed in the same manner for license requests using - * RSA or using a Widevine keybox token. - * - * This function is also used to derive keys before processing a Cast - * Certificate provisioning response in OEMCrypto_LoadProvisioning(). - * See [Cast Receiver](../../cast) for more details. - * - * @verification - * If the RSA key's allowed_schemes is not kSign_RSASSA_PSS, then no keys are - * derived and the error OEMCrypto_ERROR_INVALID_KEY is returned. An RSA - * key cannot be used for both deriving session keys and also for PKCS1 - * signatures. - * - * @param[in] session: handle for the session to be used. - * @param[in] derivation_key: session key, encrypted with the public RSA key - * (from the DRM certifcate) using RSA-OAEP. - * @param[in] derivation_key_length: length of derivation_key, in bytes. - * @param[in] mac_key_context: pointer to memory containing context data for - * computing the HMAC generation key. - * @param[in] mac_key_context_length: length of the HMAC key context data, in - * bytes. - * @param[in] enc_key_context: pointer to memory containing context data for - * computing the encryption key. - * @param[in] enc_key_context_length: length of the encryption key context data, - * in bytes. - * - * Results: - * mac_key[server]: the 256 bit mac key is generated and stored in secure - * memory. - * mac_key[client]: the 256 bit mac key is generated and stored in secure - * memory. - * enc_key: the 128 bit encryption key is generated and stored in secure - * memory. - * - * @retval OEMCrypto_SUCCESS success - * @retval OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED - * @retval OEMCrypto_ERROR_INVALID_SESSION - * @retval OEMCrypto_ERROR_INVALID_CONTEXT - * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES - * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE - * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE - * @retval OEMCrypto_ERROR_SESSION_LOST_STATE - * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED - * - * @buffer_size - * OEMCrypto shall support mac_key_context and enc_key_context sizes as - * described in the section OEMCrypto_ResourceRatingTier() for messages. The - * key derivation context is about 25 bytes prepended to the request message. - * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffers are - * too large. - * - * @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 will not be called simultaneously with initialization - * or usage table functions. It is as if the CDM holds a write lock for this - * session, and a read lock on the OEMCrypto system. - * - * @version - * This method changed in API version 16. - */ -OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( - OEMCrypto_SESSION session, const uint8_t* derivation_key, - size_t derivation_key_length, const OEMCrypto_SharedMemory* mac_key_context, - size_t mac_key_context_length, - const OEMCrypto_SharedMemory* enc_key_context, - size_t enc_key_context_length); - /** * Generates a 32-bit nonce to detect possible replay attack on the key * control block. The nonce is stored in secure memory and will be used in @@ -1165,9 +1037,7 @@ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, * Message Serialization". * * The message body is the buffer starting at message + core_message_size, - * and with length message_length - core_message_size. The reason OEMCrypto - * only signs the message body and not the entire message is to allow a v16 - * device to request a license from a v15 license server. + * and with length message_length - core_message_size. * * If the session's private RSA key has an "allowed_schemes" bit field, then * it must be 0x1 (RSASSA-PSS with SHA1). If not, then an error of @@ -1180,6 +1050,9 @@ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, * Refer to the Signing Messages Sent to a Server section above for more * details about the signature algorithm. * + * Starting in OEMCrypto v19, the |message| buffer must be hashed using SHA512 + * before signing. + * * NOTE: if signature pointer is null and/or input signature_length is zero, * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output * signature_length to the size needed to receive the output signature. @@ -1222,12 +1095,86 @@ OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session, * session, and a read lock on the OEMCrypto system. * * @version - * This method changed in API version 16. + * This method changed in API version 17. */ OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( OEMCrypto_SESSION session, uint8_t* message, size_t message_length, size_t* core_message_size, uint8_t* signature, size_t* signature_length); +/** + * This function replaces both OEMCrypto_DeactivateUsageEntry and + * OEMCrypto_ReportUsage. As with other OEMCrypto_PrepAndSign* functions, it + * first verifies that the core_message_size is the right size. If not, it + * returns OEMCrypto_ERROR_SHORT_BUFFER. + + * OEMCrypto will change the status of the usage entry to InactiveUsed if it was + * Active, or InactiveUnused if it was Unused. This also increments the entry's + * generation number by 2, and the header's master generation number. The + * corresponding generation number in the usage table header is also incremented + * so that it matches the one in the entry. + * + * OEMCrypto will use ODK_PrepareCoreLicenseRelease to prepare the core + * message. If it returns OEMCrypto_SUCCESS, then OEMCrypto shall sign the + * message body using the DRM certificate's private key. If it returns an + * error, the error should be returned by OEMCrypto to the CDM layer. + * ODK_PrepareCoreLicenseRelease is described in the document "Widevine Core + * Message Serialization". + * + * This function generates a HMAC-SHA256 signature using the mac_key[client] + * for license release signing under the license server protocol for CENC. + * + * The key used for signing should be the mac_key[client] that was generated + * for this session or loaded for this session by + * OEMCrypto_LoadLicense() or OEMCrypto_LoadUsageEntry(). + * + * NOTE: if signature pointer is null and/or input signature_length is zero, + * this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * signature_length to the size needed to receive the output signature. + * + * @param[in] session: handle for the session to be used. + * @param[in,out] message: Pointer to memory for the entire message. Modified by + * OEMCrypto via the ODK library. + * @param[in] message_length: length of the entire message buffer. + * @param[in,out] core_message_size: length of the core message at the beginning + * of the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * @param[out] signature: pointer to memory to receive the computed signature. + * @param[in,out] signature_length: (in) length of the signature buffer, in + * bytes. (out) actual length of the signature, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough + * to hold the signature. + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED since OEMCrypto does not implement + * license release before v19 + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @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 will not be called simultaneously with initialization + * or usage table functions. 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_PrepAndSignReleaseRequest( + OEMCrypto_SESSION session, uint8_t* message, size_t message_length, + size_t* core_message_size, uint8_t* signature, size_t* signature_length); + /** * OEMCrypto will use ODK_PrepareCoreRenewalRequest, as described in the * document "Widevine Core Message Serialization", to prepare the core @@ -1313,7 +1260,11 @@ OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( /** * Install a set of keys for performing decryption in the current session. * - * First, OEMCrypto shall verify the signature of the message using + * First, OEMCrypto should generate three secondary keys, mac_key[server], + * mac_key[client], and encryption_key, for handling signing and content key + * derivation under the license server protocol for CENC. + * + * Then OEMCrypto shall verify the signature of the message using * HMAC-SHA256 with the derived mac_key[server]. The signature verification * shall use a constant-time algorithm (a signature mismatch will always take * the same time as a successful comparison). The signature is over the @@ -1322,9 +1273,6 @@ OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( * OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add the keys to the session * context. * - * NOTE: The calling software must have previously established the mac_keys - * and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey(). - * * Refer to the Verification of Messages from a Server section above for more * details. * @@ -1522,13 +1470,16 @@ OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( * OEMCrypto_ERROR_INSUFFICIENT_HDCP at that time. * * @param[in] session: crypto session identifier. + * @param[in] context: pointer to memory containing context data. + * @param[in] context_length: length of the context, in bytes. + * @param[in] derivation_key: pointer to memory containing derivation key. + * @param[in] derivation_key_length: length of the derivation_key, in bytes. * @param[in] message: pointer to memory containing data. * @param[in] message_length: length of the message, in bytes. * @param[in] core_message_length: length of the core submessage, in bytes. * @param[in] signature: pointer to memory containing the signature. * @param[in] signature_length: length of the signature, in bytes. * - * @retval OEMCrypto_SUCCESS success OEMCrypto_ERROR_NO_DEVICE_KEY * @retval OEMCrypto_SUCCESS success * @retval OEMCrypto_ERROR_NO_DEVICE_KEY * @retval OEMCrypto_ERROR_INVALID_SESSION @@ -1543,6 +1494,8 @@ OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED * @retval OEMCrypto_ERROR_LICENSE_RELOAD * @retval OEMCrypto_ERROR_KEY_EXPIRED + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED * * @buffer_size * OEMCrypto shall support message sizes as described in the section @@ -1560,12 +1513,11 @@ OEMCryptoResult OEMCrypto_PrepAndSignRenewalRequest( * @version * This method changed in API version 16. */ -OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session, - const uint8_t* message, - size_t message_length, - size_t core_message_length, - const uint8_t* signature, - size_t signature_length); +OEMCryptoResult OEMCrypto_LoadLicense( + OEMCrypto_SESSION session, const uint8_t* context, size_t context_length, + const uint8_t* derivation_key, size_t derivation_key_length, + const uint8_t* message, size_t message_length, size_t core_message_length, + const uint8_t* signature, size_t signature_length); /** * Updates the clock values and resets the renewal timer for the current @@ -1642,6 +1594,53 @@ OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, const uint8_t* signature, size_t signature_length); +/** + * If the signature passes, OEMCrypto shall use the function + * ODK_ParseRelease, as described in the document "Widevine Core Message + * Serialization" to parse and verify the message. If ODK_ParseRelease + * returns an error, OEMCrypto returns the error to the CDM layer. + * + * NOTE: OEMCrypto_LoadLicense() must be called first to load the keys into + * the session. + * + * @verification + * The signature of the message shall be computed using mac_key[server], and + * the API shall verify the computed signature matches the signature passed + * in. If not, return OEMCrypto_ERROR_SIGNATURE_FAILURE. The signature + * verification shall use a constant-time algorithm (a signature mismatch + * will always take the same time as a successful comparison). + * + * @param[in] session: handle for the session to be used. + * @param[in] message: pointer to memory containing message to be verified. + * @param[in] message_length: length of the message, in bytes. + * @param[in] core_message_length: length of the core submessage, in bytes. + * @param[in] signature: pointer to memory containing the signature. + * @param[in] signature_length: length of the signature, in bytes. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * + * @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 will not be called simultaneously with initialization + * or usage table functions. 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_LoadRelease(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + /** * Returns the decrypted key control block for the given content_key_id. This * function is for application developers to debug license server and key @@ -1675,7 +1674,7 @@ OEMCryptoResult OEMCrypto_LoadRenewal(OEMCrypto_SESSION session, * @param[in,out] key_control_block_length. The length of key_control_block * buffer. * - * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_SUCCESS success * @retval OEMCrypto_ERROR_INVALID_CONTEXT * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE @@ -1710,6 +1709,9 @@ OEMCryptoResult OEMCrypto_QueryKeyControl(OEMCrypto_SESSION session, * license. For CAS support, we also require that OEMCrypto support at least * six entitled key sessions per license. * + * If a call to this function would create too many entitled key sessions for + * oec_session, then the function returns OEMCrypto_ERROR_TOO_MANY_SESSIONS. + * * @param[in] oec_session: handle for the OEMCrypto session to be associated * with the created entitled key session. * @param[out] key_session: id of the created entitled key session. @@ -2092,17 +2094,19 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * buffers in its input_data and output fields, respectively. * * Each sample contains an array of subsample descriptions in its subsamples - * field. Each subsample is defined as a number of clear bytes followed by a - * number of encrypted bytes. Subsamples are consecutive inside the sample; - * the clear bytes of the second subsample begin immediately after the - * encrypted bytes of the first subsample. This follows the definition in the - * ISO-CENC standard. + * field. Each subsample is defined as a number of unprotected bytes followed by + * a number of protected bytes. Subsamples are consecutive inside the sample; + * the unprotected bytes of the second subsample begin immediately after the + * protected bytes of the first subsample. This follows the definition in the + * ISO-CENC standard. The protected bytes are encrypted, and the unprotected + * bytes are not encrypted. * * Decryption mode is AES-128-CTR or AES-128-CBC depending on the value of * cipher_mode previously passed in to OEMCrypto_GetKeyHandle(). For the * encrypted portion of subsamples, the content key associated with the handle * is latched in the active hardware key ladder and is used for the decryption - * operation. For the clear portion of subsamples, the data is simply copied. + * operation. For the unprotected portion of subsamples, the data is simply + * copied. * * After decryption, all the input_data bytes are copied to the location * described by the output field. The output field is an @@ -2163,10 +2167,10 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * data will not be used until after the subsample with the flag * OEMCrypto_LastSubsample has been sent to OEMCrypto. This can be relied on by * OEMCrypto for optimization by not doing decrypt until the last subsample has - * been received. However, a device that can do decrypt of more than one - * subsample at a time will always have better performance if it can receive - * those subsamples in one OEMCrypto_DecryptCENC() call rather than as - * individual subsamples. + * been received. However, a device that can decrypt of more than one subsample + * at a time will always have better performance if it can receive those + * subsamples in one OEMCrypto_DecryptCENC() call rather than as individual + * subsamples. * * Although the exact way that the CDM code breaks up the samples array when * it receives OEMCrypto_ERROR_BUFFER_TOO_LARGE is not guaranteed by this @@ -2206,15 +2210,15 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * 'cenc' SCHEME: * * The 'cenc' scheme is OEMCrypto_CipherMode_CENC without an encryption - * pattern. All the bytes in the encrypted portion of each subsample are + * pattern. All the bytes in the protected portion of each subsample are * encrypted. In the pattern parameter, both the encrypt and skip fields will * be zero. * - * The length of a crypto block in AES-128 is 16 bytes. In the 'cenc' scheme, - * if an encrypted subsample has a length that is not a multiple of 16 bytes, - * then all the bytes of the encrypted subsample must be decrypted, but the - * next encrypted subsample will begin by completing the incomplete crypto - * block from the previous encrypted subsample. The following diagram + * The length of a crypto block in AES-128 is 16 bytes. In the 'cenc' scheme, if + * the protected region of a subsample has a length that is not a multiple of 16 + * bytes, then all the bytes of the protected region must be decrypted, but the + * protected region of next subsample will begin by completing the incomplete + * crypto block from the previous protected region. The following diagram * provides an example: * * ![Drawing of block offset](fig5.svg) @@ -2229,18 +2233,18 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * 'cbcs' SCHEME: * * The 'cbcs' scheme is OEMCrypto_CipherMode_CBCS with an encryption pattern. - * Only some of the bytes in the encrypted portion of each subsample are + * Only some of the bytes in the protected portion of each subsample are * encrypted. In the pattern parameter, the encrypt and skip fields will * 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 encrypted part of the subsample are encrypted. It is not valid for + * in the protected part of the subsample are encrypted. It is not valid for * the encrypt field to be zero. * * The length of a crypto block in AES-128 is 16 bytes. In the 'cbcs' scheme, - * if the encrypted part of a subsample has a length that is not a multiple + * if the protected part of a subsample has a length that is not a multiple * of 16 bytes, then the final bytes that do not make up a full crypto block * are clear and should never be decrypted. The following diagram provides an * example: @@ -2253,9 +2257,10 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * the pattern says to decrypt every protected block, these bytes are clear * and should not be decrypted. * - * Of course, if the encrypted subsample has a length that is a multiple of - * 16 bytes, all the bytes in it are protected, and they may need to be - * decrypted following the pattern. The following diagram provides an example: + * Of course, if the protected region of a subsample has a length that is a + * multiple of 16 bytes, all the bytes in it are protected, and they may need to + * be decrypted following the pattern. The following diagram provides an + * example: * * ![CBCS Scheme - final partial block](fig7.svg) * @@ -2280,7 +2285,7 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * subsample should start with the IV that was passed into * OEMCrypto_DecryptCENC(). * - * To phrase it another way: In 'cenc', the encrypted portions of the + * To phrase it another way: In 'cenc', the protected regions of the * subsamples can be concatenated to form one continuous ciphertext. In * 'cbcs', each encrypted portion of a subsample is a separate ciphertext. * Each separate ciphertext begins with the IV specified in the iv field of @@ -2316,18 +2321,18 @@ OEMCryptoResult OEMCrypto_GetKeyHandle(OEMCrypto_SESSION session, * OEMCrypto cannot assume that the buffers of consecutive samples are * consecutive in memory. * - * A subsample may consist entirely of encrypted bytes or clear bytes. In - * this case, the clear or the encrypted part of the subsample will be zero, - * indicating that no bytes of that kind appear in the subsample. + * A subsample may consist entirely of protected bytes or unprotected bytes. In + * this case, the unprotected or the encrypted part of the subsample will be + * zero, indicating that no bytes of that kind appear in the subsample. * * The ISO-CENC spec implicitly limits both the skip and encrypt values to be * 4 bits, so they are at most 15. * * ![CTR Mode - no skip pattern](fig8.svg) * - * If OEMCrypto assembles all of the encrypted subsample portions into a - * single buffer and then decrypts it in one pass, it can assume that the - * block offset is 0. + * If OEMCrypto assembles the protected regions of all the subsamples into a + * single buffer and then decrypts it in one pass, it can assume that the block + * offset is 0. * * ![CTR Mode - with skip pattern](fig9.svg) * @@ -3009,10 +3014,12 @@ OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert(const uint8_t* keybox_or_cert, * Install a factory generated signature for the BCC. This is for devices that * use Provisioning 4.0, with the signing option in the factory. With the * signing option, the BCC is extracted from the device in the factory. Instead - * of being uploaded to the Widevine server, the BCC is signed by a certificate - * that the manufacturer shares with Widevine. The signature is then installed - * on the device is a secure location. The signature must not be erased during - * factory reset. + * of being uploaded to the Widevine server, the BCC is signed by either a + * certificate that the manufacturer shares with Widevine, or the keybox on the + * device. The signature is then installed on the device in a secure location. + * The signature must not be erased during factory reset. Please work with your + * Widevine Partner Engineer before implementing this function to make sure the + * installed signature is in the expected format. * * This signature should be returned as `addition_signature` in a call to the * function `OEMCrypto_GetBootCertificateChain()`. @@ -3067,6 +3074,37 @@ OEMCryptoResult OEMCrypto_FactoryInstallBCCSignature(const uint8_t* signature, */ OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(void); +/** + * This function is for OEMCrypto to tell the layer above what type of Boot + * Certificate Chain it uses. This is for devices that use Provisioning 4.0. + * + * Valid values for OEMCrypto_BCCType are: + * + * OEMCrypto_CBOR means the device has a boot certificate chain of CBOR type, + * which shall follow the IETF CBOR Web Token (CWT) specification, and the CBOR + * Object Signing and Encryption (COSE) specification. Android requires CBOR. + * OEMCrypto_X509 means the device has a boot certificate chain of X509 type. + * Please work with your Widevine Partner Engineer if you plan to support X509. + * + * @param[out] bcc_type: the type of the boot certificate chain. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT if any pointer is NULL. + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED: this function is for + * Provisioning 4.0 only. + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE any other failure. + * + * @threading + * This is a "Property Function" and may be called simultaneously with any + * other property function or session function, but not any initialization or + * usage table function, as if the CDM holds a read lock on the OEMCrypto + * system. + * + * @version + * This method is new API version 19. + */ +OEMCryptoResult OEMCrypto_GetBCCType(OEMCrypto_BCCType* bcc_type); + /** * If the device has a keybox, this validates the Widevine Keybox loaded into * the security processor device. This method verifies two fields in the @@ -3201,7 +3239,7 @@ OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* key_data, * This is an "Initialization and Termination Function" and will not be * called simultaneously with any other function, as if the CDM holds a write * lock on the OEMCrypto system. It is called after OEMCrypto_Initialize() and - * after OEMCrypto_GetProvisioningMethod() and only if the provisoining method + * after OEMCrypto_GetProvisioningMethod() and only if the provisioning method * is OEMCrypto_Keybox, * * @version @@ -3789,9 +3827,7 @@ uint32_t OEMCrypto_GetAnalogOutputFlags(void); * The message size limit applies to all functions that sign or verify a * message: OEMCrypto_PrepAndSignLicenseRequest(), * OEMCrypto_PrepAndSignRenewalRequest(), - * OEMCrypto_PrepAndSignProvisioningRequest(), and OEMCrypto_LoadLicense(). A - * request message is also used as the context buffer in - * OEMCrypto_DeriveKeysFromSessionKey() and OEMCrypto_GenerateDerivedKeys(). + * OEMCrypto_PrepAndSignProvisioningRequest(), and OEMCrypto_LoadLicense(). * * * @return @@ -3924,24 +3960,19 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( /** * Load and parse a provisioning response, and then rewrap the private key - * for storage on the filesystem. We recommend that the OEM use an encryption - * key and signing key generated using an algorithm at least as strong as - * that in GenerateDerivedKeys. + * for storage on the filesystem. We recommend that the OEM use a strong + * encryption key and signing key algorithm. * * First, OEMCrypto shall verify the signature of the message using the correct * algorithm depending on if the device supports Provisioning 2.0, 3.0 or 4.0. * - * For Provisioning 2.0, OEMCrypto shall verify the signature of the message - * using HMAC-SHA256 with the derived mac_key[server]. The signature - * verification shall use a constant-time algorithm (a signature mismatch will - * always take the same time as a successful comparison). The signature is over - * the entire message buffer starting at message with length message_length. If - * the signature verification fails, ignore all other arguments and return - * OEMCrypto_ERROR_SIGNATURE_FAILURE. - * - * NOTE: The calling software must have previously established the mac_keys - * and encrypt_key with a call to OEMCrypto_DeriveKeysFromSessionKey() or - * OEMCrypto_GenerateDerivedKeys(). + * For Provisioning 2.0, OEMCrypto shall use the provisioning request to derive + * mac_key[server] and verify the signature of the message using HMAC-SHA256. + * The signature verification shall use a constant-time algorithm (a signature + * mismatch will always take the same time as a successful comparison). The + * signature is over the entire message buffer starting at message with length + * message_length. If the signature verification fails, ignore all other + * arguments and return OEMCrypto_ERROR_SIGNATURE_FAILURE. * * For Provisioning 3.0 and 4.0, the signature is not verified. * @@ -3954,22 +3985,10 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( * Below, all fields are found in the struct ODK_ParsedLicense parsed_license * returned by ODK_ParsedProvisioning. * - * After decrypting `parsed_response->enc_private_key`, If the first four bytes - * of the buffer are the string "SIGN", then the actual RSA key begins on the - * 9th byte of the buffer. The second four bytes of the buffer is the 32 bit - * field "allowed_schemes" of type RSA_Padding_Scheme, which is used in - * OEMCrypto_GenerateRSASignature(). The value of allowed_schemes must also be - * wrapped with RSA key. We recommend storing the magic string "SIGN" with - * the key to distinguish keys that have a value for allowed_schemes from - * those that should use the default allowed_schemes. Devices that do not - * support the alternative signing algorithms may refuse to load these keys - * and return an error of OEMCrypto_ERROR_NOT_IMPLEMENTED. The main use case - * for these alternative signing algorithms is to support devices that use - * X509 certificates for authentication when acting as a ChromeCast receiver. - * This is not needed for devices that wish to send data to a ChromeCast. - * - * If the first four bytes of the buffer `enc_private_key` are not the string - * "SIGN", then this key may not be used with OEMCrypto_GenerateRSASignature(). + * After decrypting `parsed_response->enc_private_key`, OEMCrypto shall verify + * that the first four bytes of the buffer are *not* the string "SIGN". Keys + * loaded with this function are DRM certificate private keys and may not be + * used with OEMCrypto_GenerateRSASignature(). * * Verification and Algorithm: * The following checks should be performed. If any check fails, an error is @@ -3979,44 +3998,32 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( * 2. Verify that (in) wrapped_private_key_length is large enough to hold * the rewrapped key, returning OEMCrypto_ERROR_SHORT_BUFFER otherwise. * 3. Verify the message signature, using the derived signing key - * (mac_key[server]) from a previous call to - * OEMCrypto_GenerateDerivedKeys() or OEMCrypto_DeriveKeysFromSessionKey(). + * (mac_key[server]). * 4. The function ODK_ParseProvisioning is called to parse the message. * 5. Decrypt enc_private_key in the buffer private_key using the session's * derived encryption key (enc_key). Use enc_private_key_iv as the initial * vector for AES_128-CBC mode, with PKCS#5 padding. The private_key should * be kept in secure memory and protected from the user. - * 6. If the first four bytes of the buffer private_key are the string "SIGN", - * then the actual RSA key begins on the 9th byte of the buffer. The - * second four bytes of the buffer is the 32 bit field - * "allowed_schemes", of type RSA_Padding_Scheme, which is used in - * OEMCrypto_GenerateRSASignature(). The value of allowed_schemes must - * also be wrapped with RSA key. We recommend storing the magic string - * "SIGN" with the key to distinguish keys that have a value for - * allowed_schemes from those that should use the default - * allowed_schemes. Devices that do not support the alternative signing - * algorithms may refuse to load these keys and return an error of - * OEMCrypto_ERROR_NOT_IMPLEMENTED. The main use case for these - * alternative signing algorithms is to support devices that use X.509 - * certificates for authentication when acting as a ChromeCast receiver. - * This is not needed for devices that wish to send data to a ChromeCast. - * 7. If the first four bytes of the buffer private_key are not the string - * "SIGN", this key may not be used with OEMCrypto_GenerateRSASignature(). - * 8. After possibly skipping past the first 8 bytes signifying the allowed + * 6. Verify that the first four bytes of the buffer private_key are not the + * string "SIGN". This key may not be used with + * OEMCrypto_GenerateRSASignature(). + * 7. After possibly skipping past the first 8 bytes signifying the allowed * signing algorithm, the rest of the buffer private_key contains an ECC * private key or an RSA private key in PKCS#8 binary DER encoded * format. The OEMCrypto library shall verify that this private key is * valid. - * 9. Re-encrypt the device private key with an internal key (such as one + * 8. Re-encrypt the device private key with an internal key (such as one * derived by the OEM key or Widevine Keybox key) and the generated IV * using AES-128-CBC with PKCS#5 padding. The data should also be * signed. This algorithm is just a suggestion. The implementer may use any * suitable encrypting and validation algorithm with a key that ties it to * the device. - * 10. Copy the rewrapped key to the buffer specified by wrapped_private_key + * 9. Copy the rewrapped key to the buffer specified by wrapped_private_key * and the size of the wrapped key to wrapped_private_key_length. * * @param[in] session: crypto session identifier. + * @param[in] provision_request: the initial provisioning request. + * @param[in] provision_request_length: length of provision_request, in bytes. * @param[in] message: pointer to memory containing data. * @param[in] message_length: length of the message, in bytes. * @param[in] core_message_length: length of the core submessage, in bytes. @@ -4058,15 +4065,169 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( * This method changed in API version 16. */ OEMCryptoResult OEMCrypto_LoadProvisioning( - OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, - size_t core_message_length, const uint8_t* signature, + OEMCrypto_SESSION session, const uint8_t* provision_request, + size_t provision_request_length, const uint8_t* message, + size_t message_length, size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); + +/** + * Load and parse a provisioning response, and then rewrap the private key. We + * recommend that the OEM use a strong encryption key and signing key algorithm. + * + * This is the same as OEMCrypto_LoadProvisioning except it is for CAST devices. + * Devices that do not support the alternative signing algorithms used by Cast + * Receiver certificates may refuse to load these keys and return an error of + * OEMCrypto_ERROR_NOT_IMPLEMENTED. The main use case for these alternative + * signing algorithms is to support devices that use X509 certificates for + * authentication when acting as a ChromeCast receiver. This is not needed for + * devices that wish to send data to a ChromeCast. Keys loaded from this + * function may not be used with OEMCrypto_PrepAndSignLicenseRequest(). + * + * First, OEMCrypto should generate three secondary keys, mac_key[server], + * mac_key[client], and encryption_key, for handling signing and content key + * derivation under the license server protocol for CENC. This is the same + * process as used in `OEMCrypto_LoadLicense()`. For devices that support + * Provisioning 2.0, the derivation_key is ignored and the device key from the + * keybox is used to derive the three keys. For devices that support + * Provisioning 4.0, the derivation_key is used to derive the three keys, using + * the same algorithm as in OEMCrypto_LoadLicense(). + * + * Then OEMCrypto shall verify the signature of the message using + * HMAC-SHA256 with the derived mac_key[server]. The signature verification + * shall use a constant-time algorithm (a signature mismatch will always take + * the same time as a successful comparison). The signature is over the + * entire message buffer starting at message with length message_length. If + * the signature verification fails, ignore all other arguments and return + * OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add the keys to the session + * context. + * + * Refer to the Verification of Messages from a Server section above for more + * details. + * + * After the signature is verified, + * the function ODK_ParseProvisioning is called to parse the message. If it + * returns an error, OEMCrypto shall return that error to the CDM layer. The + * function ODK_ParseProvisioning is described in the document "Widevine Core + * Message Serialization". + * + * Below, all fields are found in the struct ODK_ParsedLicense parsed_license + * returned by ODK_ParsedProvisioning. + * + * After decrypting `parsed_response->enc_private_key`, OEMCryto should verify + * that the first four bytes of the buffer are the string "SIGN". The actual RSA + * key begins on the 9th byte of the buffer. The second four bytes of the buffer + * is the 32 bit field "allowed_schemes" of type RSA_Padding_Scheme, which is + * used in OEMCrypto_GenerateRSASignature(). The only supported scheme for keys + * loaded with this function is kSign_PKCS1_Block1. The value of allowed_schemes + * must also be wrapped with RSA key. We recommend storing the magic string + * "SIGN" with the key to distinguish keys that have a value for allowed_schemes + * from those that should use the default allowed_schemes. + * + * If the first four bytes of the buffer `enc_private_key` are not the string + * "SIGN", then this key may not be loaded. The error + * OEMCrypto_ERROR_INVALID_KEY is returned. + * + * Verification and Algorithm: + * The following checks should be performed. If any check fails, an error is + * returned, and the key is not loaded. + * 1. Check that all the pointer values passed into it are within the + * buffer specified by message and message_length. + * 2. Verify that (in) wrapped_private_key_length is large enough to hold + * the rewrapped key, returning OEMCrypto_ERROR_SHORT_BUFFER otherwise. + * 3. Verify the message signature, using the derived signing key + * (mac_key[server]). + * 4. The function ODK_ParseProvisioning is called to parse the message. + * 5. Decrypt enc_private_key in the buffer private_key using the session's + * derived encryption key (enc_key). Use enc_private_key_iv as the initial + * vector for AES_128-CBC mode, with PKCS#5 padding. The private_key should + * be kept in secure memory and protected from the user. + * 6. Verify that the first four bytes of the buffer private_key are the + * string "SIGN". The actual RSA key begins on the 9th byte of the + * buffer. The second four bytes of the buffer is the 32 bit field + * "allowed_schemes", of type RSA_Padding_Scheme, which is used in + * OEMCrypto_GenerateRSASignature(). The value of allowed_schemes must + * also be wrapped with RSA key. We recommend storing the magic string + * "SIGN" with the key to distinguish keys that have a value for + * allowed_schemes from those that should use the default + * allowed_schemes. Devices that do not support the alternative signing + * algorithms may refuse to load these keys and return an error of + * OEMCrypto_ERROR_NOT_IMPLEMENTED. The main use case for these + * alternative signing algorithms is to support devices that use X.509 + * certificates for authentication when acting as a ChromeCast receiver. + * This is not needed for devices that wish to send data to a ChromeCast. + * 7. After possibly skipping past the first 8 bytes signifying the allowed + * signing algorithm, the rest of the buffer private_key contains an ECC + * private key or an RSA private key in PKCS#8 binary DER encoded + * format. The OEMCrypto library shall verify that this private key is + * valid. + * 8. Re-encrypt the device private key with an internal key (such as one + * derived by the OEM key or Widevine Keybox key) and the generated IV + * using AES-128-CBC with PKCS#5 padding. The data should also be + * signed. This algorithm is just a suggestion. The implementer may use any + * suitable encrypting and validation algorithm with a key that ties it to + * the device. + * 9. Copy the rewrapped key to the buffer specified by wrapped_private_key + * and the size of the wrapped key to wrapped_private_key_length. + * + * @param[in] session: crypto session identifier. + * @param[in] derivation_key: pointer to memory containing derivation key. + * @param[in] derivation_key_length: length of the derivation_key, in bytes. + * @param[in] provision_request: the initial provisioning request. + * @param[in] provision_request_length: length of provision_request, in bytes. + * @param[in] message: pointer to memory containing data. + * @param[in] message_length: length of the message, in bytes. + * @param[in] core_message_length: length of the core submessage, in bytes. + * @param[in] signature: pointer to memory containing the signature. + * @param[in] signature_length: length of the signature, in bytes. + * @param[out] wrapped_private_key: pointer to buffer in which encrypted RSA or + * ECC private key should be stored. May be null on the first call in order + * to find required buffer size. + * @param[in,out] wrapped_private_key_length: (in) length of the encrypted + * private key, in bytes. (out) actual length of the encrypted private key + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_NO_DEVICE_KEY + * @retval OEMCrypto_ERROR_INVALID_SESSION + * @retval OEMCrypto_ERROR_INVALID_KEY + * @retval OEMCrypto_ERROR_SIGNATURE_FAILURE + * @retval OEMCrypto_ERROR_INVALID_NONCE + * @retval OEMCrypto_ERROR_SHORT_BUFFER + * @retval OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval OEMCrypto_ERROR_BUFFER_TOO_LARGE + * @retval OEMCrypto_ERROR_SESSION_LOST_STATE + * @retval OEMCrypto_ERROR_SYSTEM_INVALIDATED + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * + * @buffer_size + * OEMCrypto shall support message sizes as described in the section + * OEMCrypto_ResourceRatingTier(). + * OEMCrypto shall return OEMCrypto_ERROR_BUFFER_TOO_LARGE if the buffer is + * larger than the supported size. + * + * @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 will not be called simultaneously with initialization + * or usage table functions. It is as if the CDM holds a write lock for this + * session, and a read lock on the OEMCrypto system. + * + * @version + * This method was added in API version 19. + */ +OEMCryptoResult OEMCrypto_LoadProvisioningCast( + OEMCrypto_SESSION session, const uint8_t* derivation_key, + size_t derivation_key_length, const uint8_t* provision_request, + size_t provision_request_length, const uint8_t* message, + size_t message_length, size_t core_message_length, const uint8_t* signature, size_t signature_length, uint8_t* wrapped_private_key, size_t* wrapped_private_key_length); /** * Loads a wrapped RSA or ECC private key to secure memory for use by this * session in future calls to OEMCrypto_PrepAndSignLicenseRequest() or - * OEMCrypto_DeriveKeysFromSessionKey(). The wrapped private key will be the + * OEMCrypto_LoadLicense(). The wrapped private key will be the * one verified and wrapped by OEMCrypto_LoadProvisioning(). The private key * should be stored in secure memory. * @@ -4074,7 +4235,7 @@ OEMCryptoResult OEMCrypto_LoadProvisioning( * value will be loaded and stored with the RSA key, and the key may be used * with calls to OEMCrypto_GenerateRSASignature(). If there was not a bit field * wrapped with the RSA key, the key will be used for - * OEMCrypto_PrepAndSignLicenseRequest() or OEMCrypto_DeriveKeysFromSessionKey() + * OEMCrypto_PrepAndSignLicenseRequest() or OEMCrypto_LoadLicense() * * @verification * The following checks should be performed. If any check fails, an error is @@ -4234,15 +4395,14 @@ OEMCryptoResult OEMCrypto_GenerateRSASignature( * message with length message_length. * * For a device that has a keybox, i.e. Provisioning 2.0, OEMCrypto will sign - * the request with the session's derived client mac key from the previous - * call to OEMCrypto_GenerateDerivedKeys(). + * the request with the session's derived client mac key using the message. * * For Provisioning 3.0, i.e. a device that has a baked in OEM Certificate, * OEMCrypto will sign the request with the private key associated with the OEM * Certificate. The key shall have been loaded by a previous call to * OEMCrypto_LoadDRMPrivateKey(). * - * For Provisioning 4.0, i.e. a device that uses a Boot Chain Certificate to + * For Provisioning 4.0, i.e. a device that uses a Boot Certificate Chain to * request and OEM cert, a request for an OEM cert is signed by the OEM private * key. A request for a DRM cert is signed by the DRM private key. The DRM cert * that was generated on the device in OEMCrypto_GenerateCertificateKeyPair() is @@ -4602,6 +4762,9 @@ OEMCryptoResult OEMCrypto_UpdateUsageEntry( * already InactiveUsed or InactiveUnused, then this function does not change * the entry or its state. * + * This API is deprecated in OEMCrypto v19, but will not be removed until + * OEMCrypto v22 to maintain backwards compatibility. + * * @param[in] session: handle for the session to be used. * @param[in] pst: pointer to memory containing Provider Session Token. * @param[in] pst_length: length of the pst, in bytes. @@ -4697,6 +4860,9 @@ OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session, * Devices that do not implement a Session Usage Table may return * OEMCrypto_ERROR_NOT_IMPLEMENTED. * + * This API is deprecated in OEMCrypto v19, but will not be removed until + * OEMCrypto v22 to maintain backwards compatibility. + * * @param[in] session: handle for the session to be used. * @param[in] pst: pointer to memory containing Provider Session Token. * @param[in] pst_length: length of the pst, in bytes. @@ -4737,6 +4903,36 @@ OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session, const uint8_t* pst, size_t pst_length, uint8_t* buffer, size_t* buffer_length); +/** + * Fetches information from a license release without performing any signatures + * or deactivating the license. + * + * @param[in] session: handle for the session to be used. + * @param[out] status: the enumeration of OEMCrypto_Usage_Entry_Status. + * @param[out] seconds_since_license_received: the time since the license being + * requested in seconds. + * @param[out] seconds_since_first_decrypt: the time since playback has + * started in seconds. + * + * @retval OEMCrypto_SUCCESS success + * @retval OEMCrypto_ERROR_INVALID_SESSION no open session with that id. + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED + * @retval OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * @threading + * This is a "Usage Table Function" and will not be called simultaneously + * with any other function, as if the CDM holds a write lock on the OEMCrypto + * system. + * + * @version + * This method is new in API version 19. + */ +OEMCryptoResult OEMCrypto_GetUsageEntryInfo( + OEMCrypto_SESSION session, OEMCrypto_Usage_Entry_Status* status, + int64_t* seconds_since_license_received, + int64_t* seconds_since_first_decrypt); + /** * Moves the entry associated with the current session from one location in * the usage table header to another. This function is used by the CDM layer @@ -4845,8 +5041,10 @@ OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(uint32_t new_entry_count, * output, the number of bytes written into the buffer. * @param[out] additional_signature: pointer to the buffer that receives * additional device key signature (certificate chain). This field is only - * used by the signing model where a vendor certificate is available on the - * device. + * used by the signing model where either a vendor certificate or a keybox is + * available on the device. Please work with your Widevine Partner Engineer + * before implementing this field to make sure the generated signature is in the + * expected format. * @param[in,out] additional_signature_length - on input, size of the caller's * additional_signature buffer. On output, the number of bytes written into * the buffer. @@ -4983,16 +5181,20 @@ OEMCryptoResult OEMCrypto_GenerateCertificateKeyPair( OEMCrypto_PrivateKeyType* key_type); /** - * Get the serialized device information in CBOR map format. This is for devices - * that use Provisioning 4.0, with the device key uploading option in the - * factory. + * Get the serialized device information in CBOR map format. The CBOR map must + * be canonicalized before serialization according to the specification in RFC + * 7049: + * 1) If two keys have different lengths, the shorter one sorts earlier. + * 2) If two keys have the same length, the one with the lower value in + * (byte-wise) lexical order sorts earlier. + * This is for devices that use Provisioning 4.0, with the device key uploading + * option in the factory. * - * The device - * information may contain, for example, device make and model, "fused" status, - * and other properties, which is intended to be 1) uploaded during device - * manufacture in the factory, 2) checked by the server to verify that the - * provisioning request is coming from the expected device in the fields, based - * on the values previously uploaded and registered. + * The device information may contain, for example, device make and model, + * "fused" status, and other properties, which is intended to be 1) uploaded + * during device manufacture in the factory, 2) checked by the server to verify + * that the provisioning request is coming from the expected device in the + * fields, based on the values previously uploaded and registered. * * Devices that do not support Provisioning 4.0, or do not support * Provisioning 4.0 Uploading Option should return @@ -5050,7 +5252,8 @@ OEMCryptoResult OEMCrypto_GetDeviceInformation(uint8_t* device_info, * the input |encoded_device_info|. DeviceInfo must be canonicalized according * to the specification in RFC 7049. The required fields from DeviceInfo.aidl * are: brand, manufacturer, product, model, device, vb_state, bootloader_state, - * vbmeta_digest, security_level. + * vbmeta_digest, system_patch_level, boot_patch_level, vendor_patch_level, + * security_level, fused. * * Once CsrPayload is prepared, together with |challenge| it is signed by the * leaf cert of BCC, in the format of: @@ -5173,7 +5376,7 @@ OEMCryptoResult OEMCrypto_InstallOemPrivateKey( /** * Enter Test Mode. This enables OEMCrypto test functionality. Without a call to - * this function, none of the test functions](./test-verify) shall be + * this function, none of the [test functions](./test-verify) shall be * enabled. After this function has been called, OEMCrypto will not use the * production keybox. Once OEMCrypto has entered Test Mode, it will not leave * Test Mode until the next reboot. @@ -5248,8 +5451,13 @@ uint32_t OEMCrypto_SupportsDecryptHash(void); * output is not supported, then this will return * OEMCrypto_ERROR_NOT_IMPLEMENTED. If the hash is ill formed or there are * other error conditions, this returns OEMCrypto_ERROR_UNKNOWN_FAILURE. The - * length of the hash will be at most 128 bytes, and will be 4 bytes (32 - * bits) for the default CRC32 hash. + * length of the hash will be 4 bytes (32 bits) for the default CRC32 hash. + * + * Since version 16, the function OEMCrypto_DecryptCENC has allowed several + * samples to be decrypted in one function call using an array of + * samples. OEMCrypto may assume that the hash passed into this function is of + * the last sample in that array. In practice the test app only uses an array of + * length 1 for each call. * * This may be called before the first call to OEMCrypto_GetKeyHandle. In that * case, this function cannot verify that the key control block allows hash @@ -5270,8 +5478,7 @@ uint32_t OEMCrypto_SupportsDecryptHash(void); * * @param[in] session: session id for current decrypt operation * @param[in] frame_number: frame number for the recent DecryptCENC sample. - * @param[in] hash: hash or CRC of previously decrypted frame. - * @param[in] hash_length: length of hash, in bytes. + * @param[in] crc32: CRC of previously decrypted frame. * * @retval OEMCrypto_SUCCESS if the hash was set * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED function not implemented @@ -5295,9 +5502,7 @@ uint32_t OEMCrypto_SupportsDecryptHash(void); * This method is new in API version 15. */ OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session, - uint32_t frame_number, - const uint8_t* hash, - size_t hash_length); + uint32_t frame_number, uint32_t crc32); /** * If the hash set in OEMCrypto_SetDecryptHash() did not match the computed @@ -5427,16 +5632,16 @@ OEMCryptoResult OEMCrypto_FreeSecureBuffer( * same. Differences in only the |minor| fields indicates that the protocols * are different but are still compatible. * - * @param[in,out] ree_major: pointer to memory to recieve the REE's |major| + * @param[in,out] ree_major: pointer to memory to receive the REE's |major| * version. On input, *ree_major may be zero to request the serialization * version of the REE. If *ree_major is non-zero, this function will test the * TEE's compatibility using the specified REE major version. - * @param[in,out] ree_minor: pointer to memory to recieve the REE's |minor| + * @param[in,out] ree_minor: pointer to memory to receive the REE's |minor| * version. On input, *ree_minor may be zero to request the serialization * version of the REE. If *ree_minor is non-zero, this function will test the * TEE's compatibility using the specified REE minor version. - * @param[out] tee_major: pointer to memory to recieve the TEE's |major| version - * @param[out] tee_minor: pointer to memory to recieve the TEE's |minor| version + * @param[out] tee_major: pointer to memory to receive the TEE's |major| version + * @param[out] tee_minor: pointer to memory to receive the TEE's |minor| version * * @retval OEMCrypto_SUCCESS success * @retval OPK_ERROR_INCOMPATIBLE_VERSION @@ -5536,6 +5741,17 @@ OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session, * backwards compatibility. */ +/* + * OEMCrypto_SetDecryptHash + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_SetDecryptHash_V18(OEMCrypto_SESSION session, + uint32_t frame_number, + const uint8_t* hash, + size_t hash_length); + /* * OEMCrypto_GenerateSignature * @deprecated @@ -5844,6 +6060,56 @@ OEMCryptoResult OEMCrypto_Generic_Verify_V17( size_t buffer_length, OEMCrypto_Algorithm algorithm, const OEMCrypto_SharedMemory* signature, size_t signature_length); +/** + * OEMCrypto_GenerateDerivedKeys_V18 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_GenerateDerivedKeys_V18( + OEMCrypto_SESSION session, const OEMCrypto_SharedMemory* mac_key_context, + size_t mac_key_context_length, + const OEMCrypto_SharedMemory* enc_key_context, + size_t enc_key_context_length); + +/** + * OEMCrypto_DeriveKeysFromSessionKey_V18 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey_V18( + OEMCrypto_SESSION session, const uint8_t* derivation_key, + size_t derivation_key_length, const OEMCrypto_SharedMemory* mac_key_context, + size_t mac_key_context_length, + const OEMCrypto_SharedMemory* enc_key_context, + size_t enc_key_context_length); + +/** + * OEMCrypto_LoadLicense_V18 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_LoadLicense_V18(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + size_t core_message_length, + const uint8_t* signature, + size_t signature_length); + +/** + * OEMCrypto_LoadProvisioning_V18 + * @deprecated + * Not required for the current version of OEMCrypto. Declared here to + * help with backward compatibility. + */ +OEMCryptoResult OEMCrypto_LoadProvisioning_V18( + OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); + /****************************************************************************/ /****************************************************************************/ /* The following functions are used by internal L3 CDMs and are not required by @@ -5864,14 +6130,14 @@ OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate(uint8_t* public_cert, size_t* public_cert_length); /** - * Marks the given session as using a secondary key. + * Marks the given session as using a secondary key. This should return success + * when not implemented. * * @param[in] session_id: handle for the session to be used. * @param[in] dual_key: whether this session uses a secondary key. * * @ignore * @retval OEMCrypto_SUCCESS on success - * @retval OEMCrypto_ERROR_NOT_IMPLEMENTED */ OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session_id, bool dual_key); diff --git a/oemcrypto/odk/Android.bp b/oemcrypto/odk/Android.bp index f0e918e6..90c4a6d4 100644 --- a/oemcrypto/odk/Android.bp +++ b/oemcrypto/odk/Android.bp @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -110,6 +110,9 @@ cc_test { ], srcs: [ + "test/odk_golden_v16.cpp", + "test/odk_golden_v17.cpp", + "test/odk_golden_v18.cpp", "test/odk_test.cpp", "test/odk_test_helper.cpp", "test/odk_timer_test.cpp", diff --git a/oemcrypto/odk/include/OEMCryptoCENCCommon.h b/oemcrypto/odk/include/OEMCryptoCENCCommon.h index 7da8b45a..f5317a5e 100644 --- a/oemcrypto/odk/include/OEMCryptoCENCCommon.h +++ b/oemcrypto/odk/include/OEMCryptoCENCCommon.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/include/core_message_deserialize.h b/oemcrypto/odk/include/core_message_deserialize.h index a52c5fd8..fb80dc5e 100644 --- a/oemcrypto/odk/include/core_message_deserialize.h +++ b/oemcrypto/odk/include/core_message_deserialize.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -34,6 +34,16 @@ namespace deserialize { bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, ODK_LicenseRequest* core_license_request); +/** + * Counterpart (deserializer) of ODK_PrepareCoreReleaseRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_license_request + */ +bool CoreReleaseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_ReleaseRequest* core_release_request); + /** * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) * diff --git a/oemcrypto/odk/include/core_message_features.h b/oemcrypto/odk/include/core_message_features.h index 06396d58..b2dda6a9 100644 --- a/oemcrypto/odk/include/core_message_features.h +++ b/oemcrypto/odk/include/core_message_features.h @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC. All rights reserved. This file and proprietary +// Copyright 2021 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -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 18.5. - uint32_t maximum_major_version = 18; - uint32_t maximum_minor_version = 5; + // number. The default is 19.1. + uint32_t maximum_major_version = 19; + uint32_t maximum_minor_version = 1; bool operator==(const CoreMessageFeatures &other) const; bool operator!=(const CoreMessageFeatures &other) const { diff --git a/oemcrypto/odk/include/core_message_serialize.h b/oemcrypto/odk/include/core_message_serialize.h index a76b79fd..64e9427a 100644 --- a/oemcrypto/odk/include/core_message_serialize.h +++ b/oemcrypto/odk/include/core_message_serialize.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -25,7 +25,7 @@ namespace oemcrypto_core_message { namespace serialize { -using oemcrypto_core_message::features::CoreMessageFeatures; +using ::oemcrypto_core_message::features::CoreMessageFeatures; /** * Counterpart (serializer) of ODK_ParseLicense (deserializer) @@ -44,6 +44,23 @@ bool CreateCoreLicenseResponse(const CoreMessageFeatures& features, const std::string& core_request_sha256, std::string* oemcrypto_core_message); +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * struct-input variant + * + * Parameters: + * [in] features feature support for response message. + * [in] core_request + * [in] seconds_since_license_requested + * [in] seconds_since_first_decrypt + * [out] oemcrypto_core_message + */ +bool CreateCoreReleaseResponse(const CoreMessageFeatures& features, + const ODK_ReleaseRequest& core_request, + int64_t seconds_since_license_requested, + int64_t seconds_since_first_decrypt, + std::string* oemcrypto_core_message); + /** * Counterpart (serializer) of ODK_ParseRenewal (deserializer) * diff --git a/oemcrypto/odk/include/core_message_serialize_proto.h b/oemcrypto/odk/include/core_message_serialize_proto.h index 73d7b738..5d69e1df 100644 --- a/oemcrypto/odk/include/core_message_serialize_proto.h +++ b/oemcrypto/odk/include/core_message_serialize_proto.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/include/core_message_types.h b/oemcrypto/odk/include/core_message_types.h index d268aeff..05f66c13 100644 --- a/oemcrypto/odk/include/core_message_types.h +++ b/oemcrypto/odk/include/core_message_types.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -101,6 +101,17 @@ struct ODK_LicenseRequest { ODK_MessageCounter counter_info; }; +/** + * Output structure for CoreReleaseRequestFromMessage + * Input structure for CreateCoreReleaseResponse + */ +struct ODK_ReleaseRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +}; + /** * Output structure for CoreRenewalRequestFromMessage * Input structure for CreateCoreRenewalResponse diff --git a/oemcrypto/odk/include/odk.h b/oemcrypto/odk/include/odk.h index 16af94af..679d4e00 100644 --- a/oemcrypto/odk/include/odk.h +++ b/oemcrypto/odk/include/odk.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -55,6 +55,9 @@ * * @defgroup common_types Common Types * Enumerations and structures that are used by several OEMCrypto and ODK + * + * @defgroup odk_derivation Key Derivation Utils + * Utilities and constants relating to key derivation. * functions. *********************************************************************/ @@ -274,6 +277,49 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( const ODK_NonceValues* nonce_values, const ODK_MessageCounterInfo* counter_info); +/** + * Modifies the message to include a core license release at the beginning of + * the message buffer. The values in nonce_values are used to populate the + * message. + * + * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignLicenseRelease. + * + * NOTE: if the message pointer is null and/or input core_message_size is + * zero, this function returns OEMCrypto_ERROR_SHORT_BUFFER and sets output + * core_message_size to the size needed. + * + * @param[in,out] message: Pointer to memory for the entire message. Modified by + * the ODK library. + * @param[in] message_length: length of the entire message buffer. + * @param[in,out] core_message_size: length of the core message at the beginning + * of the message. (in) size of buffer reserved for the core message, in + * bytes. (out) actual length of the core message, in bytes. + * @param[in] nonce_values: pointer to the session's nonce data. + * @param[in] status: the enumeration of OEMCrypto_Usage_Entry_Status + * @param[in] clock_security_level: the enumeration of + * OEMCryto_Clock_Security_Level + * @param[in] seconds_since_license_requested: the time between the license + * being requested and the release being generated in seconds + * @param[in] seconds_since_first_decrypt: The time since playback has started + * in seconds + * @param[in,out] clock_values: the session's clock values. + * @param[in] system_time_seconds: the current time on OEMCrypto's clock, in + * seconds. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_SHORT_BUFFER: core_message_size is too small + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 19 of the API. + */ +OEMCryptoResult ODK_PrepareCoreReleaseRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + ODK_NonceValues* nonce_values, uint32_t status, + uint32_t clock_security_level, int64_t seconds_since_license_requested, + int64_t seconds_since_first_decrypt, ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + /** * Modifies the message to include a core renewal request at the beginning of * the message buffer. The values in nonce_values, clock_values and @@ -340,7 +386,7 @@ OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, * of the message. (in) size of buffer reserved for the core message, in * bytes. (out) actual length of the core message, in bytes. * @param[in] nonce_values: pointer to the session's nonce data. - * @param[in] message_count_info: information used for server-side anomaly + * @param[in] counter_info: information used for server-side anomaly * detection * * @retval OEMCrypto_SUCCESS @@ -559,14 +605,17 @@ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, * @param[in,out] nonce_values: The session's nonce values. These will be * updated. * @param[out] parsed_license: the destination for the data. - * @param[out] timer_value: set if playback timer should be started. + * @param[out] timer_value: set to the new timer value. Only used if the return + * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a + * hardware timer. * * @retval OEMCrypto_SUCCESS * @retval ODK_ERROR_CORE_MESSAGE: if the message did not parse correctly, or * there were other incorrect values. An error should be returned to the * CDM layer. * @retval ODK_UNSUPPORTED_API - * @retval ODK_SET_TIMER: if the playback timer has been started successfully + * @retval ODK_SET_TIMER: Success. The timer should be reset to the specified + * timer value. * @retval ODK_DISABLE_TIMER: if the playtime timer has been started * successfully then is disabled. * @retval ODK_TIMER_EXPIRED: if the license is attempted to be loaded after the @@ -574,7 +623,7 @@ OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, * @retval OEMCrypto_ERROR_INVALÃ¥ID_NONCE * * @version - * This method is new in version 16 of the API. + * This method changed in version 18 of the API. */ OEMCryptoResult ODK_ParseLicense( const uint8_t* message, size_t message_length, size_t core_message_length, @@ -618,6 +667,7 @@ OEMCryptoResult ODK_ParseLicense( * value is ODK_SET_TIMER. This must be non-null if OEMCrypto uses a * hardware timer. * + * @retval OEMCrypto_SUCCESS * @retval ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there * were other incorrect values. An error should be returned to the CDM * layer. @@ -632,7 +682,7 @@ OEMCryptoResult ODK_ParseLicense( * @retval OEMCrypto_ERROR_INVALID_NONCE * * @version - * This method is new in version 16 of the API. + * This method changed in version 18 of the API. */ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, size_t core_message_length, @@ -642,6 +692,33 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, ODK_ClockValues* clock_values, uint64_t* timer_value); +/** + * The function ODK_ParseRelease will parse the message and verify its + * header contents. If the message does not parse correctly, an error of + * ODK_ERROR_CORE_MESSAGE is returned. This function is mostly a placeholder + * function since there is no information needed in the release response. + * + * @param[in] message: pointer to the message buffer. + * @param[in] message_length: length of the entire message buffer. + * @param[in] core_message_size: length of the core message, at the beginning of + * the message buffer. + * @param[in,out] nonce_values: pointer to the session's nonce data. These might + * be updated if the server returns a lower API version. + * + * @retval OEMCrypto_SUCCESS + * @retval ODK_ERROR_CORE_MESSAGE: the message did not parse correctly, or there + * were other incorrect values. An error should be returned to the CDM + * layer. + * @retval ODK_UNSUPPORTED_API + * @retval OEMCrypto_ERROR_INVALID_NONCE + * + * @version + * This method is new in version 19 of the API. + */ +OEMCryptoResult ODK_ParseRelease(const uint8_t* message, size_t message_length, + size_t core_message_length, + ODK_NonceValues* nonce_values); + /** * The function ODK_ParseProvisioning will parse the message and verify the * nonce values match those in the license. @@ -670,7 +747,6 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * @param[in] device_id: a pointer to a buffer containing the device ID of the * device. The ODK function will verify it matches that in the message. * @param[in] device_id_length: the length of the device ID. - * @param[out] counter_info: destination for counter portion of parse data. * @param[out] parsed_response: destination for response portion of parse data. * * @retval OEMCrypto_SUCCESS @@ -681,7 +757,7 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, * @retval OEMCrypto_ERROR_INVALID_NONCE * * @version - * This method is new in version 16 of the API. + * This method changed in version 18 of the API. */ OEMCryptoResult ODK_ParseProvisioning( const uint8_t* message, size_t message_length, size_t core_message_length, @@ -740,6 +816,83 @@ bool CheckApiVersionAtMost(const ODK_NonceValues* nonce_values, /// @} +/// @addtogroup odk_derivation +/// @{ + +/** + * Contains the key label for the Mac key derivation. This contains + * |ODK_MacKeyLabelWithZeroLength| number of bytes. + */ +extern const uint8_t ODK_MacKeyLabelWithZero[]; + +/** Contains the number of bytes in |ODK_MacKeyLabelWithZero|. */ +extern const size_t ODK_MacKeyLabelWithZeroLength; + +/** + * Contains the key label for the Encryption key derivation. This contains + * |ODK_EncKeyLabelWithZeroLength| number of bytes. + */ +extern const uint8_t ODK_EncKeyLabelWithZero[]; + +/** Contains the number of bytes in |ODK_EncKeyLabelWithZero|. */ +extern const size_t ODK_EncKeyLabelWithZeroLength; + +/** + * Contains the suffix bytes (NIST 800-108 key length) for Mac key derivation. + * This value is appended after the context string. This contains + * ODK_MacKeySuffixLength number of bytes. + */ +extern const uint8_t ODK_MacKeySuffix[]; + +/** Contains the number of bytes in |ODK_MacKeySuffix|. */ +extern const size_t ODK_MacKeySuffixLength; + +/** + * Contains the suffix bytes (NIST 800-108 key length) for Encryption key + * derivation. This value is appended after the context string. This contains + * ODK_EncKeySuffixLength number of bytes. + */ +extern const uint8_t ODK_EncKeySuffix[]; + +/** Contains the number of bytes in |ODK_EncKeySuffix|. */ +extern const size_t ODK_EncKeySuffixLength; + +/** + * Generates the key-derivation contexts for the license exchange based on the + * given context value. + * + * NOTE: if the mac_key_context/enc_key_context pointer are null and/or input + * mac_key_context_length/enc_key_context_length is zero, this function returns + * OEMCrypto_ERROR_SHORT_BUFFER and sets output + * mac_key_context_length/enc_key_context_length to the size needed. + * + * @param[in] context: pointer to the context buffer. + * @param[in] context_length: the length of the context buffer. + * @param[out] mac_key_context: an output buffer to contain the MAC key context. + * @param[in,out] mac_key_context_length: on input, contains the number of bytes + * in |mac_key_context|; on return, will contain the context length. + * @param[out] enc_key_context: an output buffer to contain the encryption key + * context. + * @param[in,out] enc_key_context_length: on input, contains the number of bytes + * in |enc_key_context|; on return, will contain the context length. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_SHORT_BUFFER: mac_key_context_length or + * enc_key_context_length is too small + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 19 of the API. + */ +OEMCryptoResult ODK_GenerateKeyContexts(const uint8_t* context, + size_t context_length, + uint8_t* mac_key_context, + size_t* mac_key_context_length, + uint8_t* enc_key_context, + size_t* enc_key_context_length); + +/// @} + #ifdef __cplusplus } #endif diff --git a/oemcrypto/odk/include/odk_attributes.h b/oemcrypto/odk/include/odk_attributes.h index 72321b14..3fe09bf6 100644 --- a/oemcrypto/odk/include/odk_attributes.h +++ b/oemcrypto/odk/include/odk_attributes.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/include/odk_message.h b/oemcrypto/odk/include/odk_message.h index cdbf26b4..aedd31a6 100644 --- a/oemcrypto/odk/include/odk_message.h +++ b/oemcrypto/odk/include/odk_message.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h index b7d8ca02..3335f95d 100644 --- a/oemcrypto/odk/include/odk_structs.h +++ b/oemcrypto/odk/include/odk_structs.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -15,11 +15,11 @@ extern "C" { #include "odk_target.h" /* The version of this library. */ -#define ODK_MAJOR_VERSION 18 -#define ODK_MINOR_VERSION 5 +#define ODK_MAJOR_VERSION 19 +#define ODK_MINOR_VERSION 1 /* ODK Version string. Date changed automatically on each release. */ -#define ODK_RELEASE_DATE "ODK v18.5 2024-03-21" +#define ODK_RELEASE_DATE "ODK v19.1 2024-03-25" /* The lowest version number for an ODK message. */ #define ODK_FIRST_VERSION 16 @@ -223,9 +223,10 @@ typedef struct { * entitlement keys. * @param nonce_required: indicates if the license requires a nonce. * @param timer_limits: time limits of the for the license. - * @param watermarking: specifies if device supports watermarking. - * @param dtcp2_required: specifies if device supports DTCP. - * @param renewal_delay_base: what time the timer starting is based off of. + * @param watermarking: indicates watermarking requirements of the license. + * @param dtcp2_required: indicates dtcp2 requirements of the license. + * @param renewal_delay_base: indicates which time is used for the renewal timer + * and playback timer starting point. * @param key_array_length: number of keys present. * @param key_array: set of keys to be installed. * @@ -262,9 +263,10 @@ typedef struct { * entitlement keys. * @param nonce_required: indicates if the license requires a nonce. * @param timer_limits: time limits of the for the license. - * @param watermarking: specifies if device supports watermarking. - * @param dtcp2_required: specifies if device supports DTCP. - * @param renewal_delay_base: what time the timer starting is based off of. + * @param watermarking: indicates watermarking requirements of the license. + * @param dtcp2_required: indicates dtcp2 requirements of the license. + * @param renewal_delay_base: indicates which time is used for the renewal timer + * and playback timer starting point. * @param key_array_length: number of keys present. * @param key_array: set of keys to be installed. This is a pointer to an array * to allow packing a number of keys greater than |ODK_MAX_NUM_KEYS|. diff --git a/oemcrypto/odk/include/odk_target.h b/oemcrypto/odk/include/odk_target.h index d1c0652d..825a263a 100644 --- a/oemcrypto/odk/include/odk_target.h +++ b/oemcrypto/odk/include/odk_target.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file is distributed +// Copyright 2019 Google LLC. This file is distributed // under the Widevine License Agreement. // Partners are expected to edit this file to support target specific code diff --git a/oemcrypto/odk/src/core_message_deserialize.cpp b/oemcrypto/odk/src/core_message_deserialize.cpp index 30e68c4e..c9901014 100644 --- a/oemcrypto/odk/src/core_message_deserialize.cpp +++ b/oemcrypto/odk/src/core_message_deserialize.cpp @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -11,6 +11,7 @@ #include #include "OEMCryptoCENCCommon.h" +#include "core_message_types.h" #include "odk_message.h" #include "odk_serialize.h" #include "odk_structs.h" @@ -147,6 +148,14 @@ bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, return true; } +bool CoreReleaseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_ReleaseRequest* core_release_request) { + ODK_PreparedReleaseRequest prepared_release = {}; + return ParseRequest(ODK_Release_Request_Type, oemcrypto_core_message, + core_release_request, &prepared_release, + Unpack_ODK_PreparedReleaseRequest); +} + bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, ODK_RenewalRequest* core_renewal_request) { const auto unpacker = Unpack_ODK_PreparedRenewalRequest; @@ -167,11 +176,12 @@ bool CoreProvisioningRequestFromMessage( ODK_NonceValues nonce; if (!GetNonceFromMessage(oemcrypto_core_message, &nonce)) return false; - if (nonce.api_major_version == 18) { + if (nonce.api_major_version >= 18) { // Use special case unpacker for v18.0 - const auto unpacker = nonce.api_minor_version == 0 - ? Unpack_ODK_PreparedProvisioningRequestV180 - : Unpack_ODK_PreparedProvisioningRequest; + const auto unpacker = + nonce.api_minor_version == 0 && nonce.api_major_version == 18 + ? Unpack_ODK_PreparedProvisioningRequestV180 + : Unpack_ODK_PreparedProvisioningRequest; ODK_PreparedProvisioningRequest prepared_provision = {}; if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, diff --git a/oemcrypto/odk/src/core_message_features.cpp b/oemcrypto/odk/src/core_message_features.cpp index 94a3d953..4143d140 100644 --- a/oemcrypto/odk/src/core_message_features.cpp +++ b/oemcrypto/odk/src/core_message_features.cpp @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC. All rights reserved. This file and proprietary +// Copyright 2021 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -30,7 +30,10 @@ CoreMessageFeatures CoreMessageFeatures::DefaultFeatures( features.maximum_minor_version = 2; // 17.2 break; case 18: - features.maximum_minor_version = 5; // 18.5 + features.maximum_minor_version = 4; // 18.4 + break; + case 19: + features.maximum_minor_version = 1; // 19.1 break; default: features.maximum_minor_version = 0; diff --git a/oemcrypto/odk/src/core_message_serialize.cpp b/oemcrypto/odk/src/core_message_serialize.cpp index 5edd3525..888b957a 100644 --- a/oemcrypto/odk/src/core_message_serialize.cpp +++ b/oemcrypto/odk/src/core_message_serialize.cpp @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -10,6 +10,7 @@ #include #include +#include "core_message_types.h" #include "odk_serialize.h" #include "odk_structs.h" #include "odk_structs_priv.h" @@ -137,6 +138,23 @@ bool CreateCoreLicenseResponse(const CoreMessageFeatures& features, Pack_ODK_LicenseResponse); } +bool CreateCoreReleaseResponse(const CoreMessageFeatures& features, + const ODK_ReleaseRequest& core_request, + int64_t seconds_since_license_requested, + int64_t seconds_since_first_decrypt, + std::string* oemcrypto_core_message) { + (void)seconds_since_license_requested; + (void)seconds_since_first_decrypt; + ODK_ReleaseResponse release_response{}; + if (!CreateResponseHeader(features, ODK_Release_Response_Type, + &release_response.core_message, core_request)) { + return false; + } + return CreateResponse(ODK_Release_Response_Type, oemcrypto_core_message, + &release_response.core_message, release_response, + Pack_ODK_ReleaseResponse); +} + bool CreateCoreRenewalResponse(const CoreMessageFeatures& features, const ODK_RenewalRequest& core_request, uint64_t renewal_duration_seconds, diff --git a/oemcrypto/odk/src/core_message_serialize_proto.cpp b/oemcrypto/odk/src/core_message_serialize_proto.cpp index 61c50312..2559012f 100644 --- a/oemcrypto/odk/src/core_message_serialize_proto.cpp +++ b/oemcrypto/odk/src/core_message_serialize_proto.cpp @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -9,8 +9,10 @@ #include #include #include +#include #include +#include "OEMCryptoCENCCommon.h" #include "core_message_serialize.h" #include "license_protocol.pb.h" #include "odk_serialize.h" @@ -32,8 +34,8 @@ using oemcrypto_core_message::features::CoreMessageFeatures; * message: serialized license protobuf * field: substring value */ -OEMCrypto_Substring GetOecSubstring(const std::string& message, - const std::string& field) { +OEMCrypto_Substring GetOecSubstring(std::string_view message, + std::string_view field) { OEMCrypto_Substring substring = {}; size_t pos = message.find(field); if (pos != std::string::npos) { @@ -167,9 +169,14 @@ bool CreateCoreLicenseResponseFromProto(const CoreMessageFeatures& features, timer_limits.rental_duration_seconds = policy.rental_duration_seconds(); timer_limits.total_playback_duration_seconds = policy.playback_duration_seconds(); + // On devices these seconds are tracking time so should not be negative. + if (policy.renewal_delay_seconds() < 0 || + policy.renewal_recovery_duration_seconds() < 0) { + return false; + } timer_limits.initial_renewal_duration_seconds = - policy.renewal_delay_seconds() + - policy.renewal_recovery_duration_seconds(); + static_cast(policy.renewal_delay_seconds()) + + static_cast(policy.renewal_recovery_duration_seconds()); parsed_lic.key_array = key_array.data(); parsed_lic.key_array_length = static_cast(key_array.size()); diff --git a/oemcrypto/odk/src/kdo.gypi b/oemcrypto/odk/src/kdo.gypi index 6e77b456..cd8717a9 100644 --- a/oemcrypto/odk/src/kdo.gypi +++ b/oemcrypto/odk/src/kdo.gypi @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# Copyright 2019 Google LLC. This file and proprietary # source code may only be used and distributed under the Widevine License # Agreement. diff --git a/oemcrypto/odk/src/odk.c b/oemcrypto/odk/src/odk.c index 3ade4a76..3f4090f2 100644 --- a/oemcrypto/odk/src/odk.c +++ b/oemcrypto/odk/src/odk.c @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -9,6 +9,7 @@ #include #include +#include "OEMCryptoCENCCommon.h" #include "odk_message.h" #include "odk_overflow.h" #include "odk_serialize.h" @@ -65,6 +66,15 @@ static OEMCryptoResult ODK_PrepareRequest( } break; } + case ODK_Release_Request_Type: { + core_message->message_length = ODK_RELEASE_REQUEST_SIZE; + if (sizeof(ODK_PreparedReleaseRequest) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedReleaseRequest( + &msg, (ODK_PreparedReleaseRequest*)prepared_request_buffer); + break; + } case ODK_Renewal_Request_Type: { core_message->message_length = ODK_RENEWAL_REQUEST_SIZE; if (sizeof(ODK_PreparedRenewalRequest) > prepared_request_buffer_length) { @@ -223,6 +233,34 @@ OEMCryptoResult ODK_PrepareCoreLicenseRequest( } } +OEMCryptoResult ODK_PrepareCoreReleaseRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + ODK_NonceValues* nonce_values, uint32_t status, + uint32_t clock_security_level, int64_t seconds_since_license_requested, + int64_t seconds_since_first_decrypt, ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + (void)status; + (void)clock_security_level; + (void)seconds_since_license_requested; + (void)seconds_since_first_decrypt; + if (core_message_size == NULL || nonce_values == NULL || + clock_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + if (nonce_values->api_major_version >= 19) { + ODK_PreparedReleaseRequest release_request = {0}; + return ODK_PrepareRequest( + message, message_length, core_message_size, ODK_Release_Request_Type, + nonce_values, &release_request, sizeof(ODK_PreparedReleaseRequest)); + } else { + // If the version is pre 19 when license release isn't supported, create a + // license request. + return ODK_PrepareCoreRenewalRequest(message, message_length, + core_message_size, nonce_values, + clock_values, system_time_seconds); + } +} + OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, size_t message_length, size_t* core_message_size, @@ -423,7 +461,7 @@ OEMCryptoResult ODK_ParseLicense( *timer_limits = parsed_license->timer_limits; /* And update the clock values state. */ clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; - if (nonce_values->api_major_version == 18 && license_load) { + if (nonce_values->api_major_version >= 18 && license_load) { err = ODK_AttemptFirstPlayback(system_time_seconds, timer_limits, clock_values, timer_value); return err; @@ -488,6 +526,33 @@ OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, timer_value); } +OEMCryptoResult ODK_ParseRelease(const uint8_t* message, size_t message_length, + size_t core_message_length, + ODK_NonceValues* nonce_values) { + if (message == NULL || nonce_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + const OEMCryptoResult err = + ODK_ParseCoreHeader(message, message_length, core_message_length, + ODK_Release_Response_Type, nonce_values); + if (err != OEMCrypto_SUCCESS) { + return err; + } + + ODK_ReleaseResponse release_response = {0}; + ODK_Message msg = ODK_Message_Create((uint8_t*)message, message_length); + ODK_Message_SetSize(&msg, core_message_length); + Unpack_ODK_ReleaseResponse(&msg, &release_response); + + if (ODK_Message_GetStatus(&msg) != MESSAGE_STATUS_OK || + ODK_Message_GetOffset(&msg) != core_message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + return OEMCrypto_SUCCESS; +} + OEMCryptoResult ODK_ParseProvisioning( const uint8_t* message, size_t message_length, size_t core_message_length, ODK_NonceValues* nonce_values, const uint8_t* device_id, @@ -589,3 +654,69 @@ bool CheckApiVersionAtMost(const ODK_NonceValues* nonce_values, (nonce_values->api_major_version == major_version && nonce_values->api_minor_version <= minor_version); } + +const uint8_t ODK_MacKeyLabelWithZero[] = "AUTHENTICATION"; +const size_t ODK_MacKeyLabelWithZeroLength = sizeof(ODK_MacKeyLabelWithZero); +// This is the key size (512) in network byte order. +const uint8_t ODK_MacKeySuffix[] = {0x00, 0x00, 0x02, 0x00}; +const size_t ODK_MacKeySuffixLength = sizeof(ODK_MacKeySuffix); + +const uint8_t ODK_EncKeyLabelWithZero[] = "ENCRYPTION"; +const size_t ODK_EncKeyLabelWithZeroLength = sizeof(ODK_EncKeyLabelWithZero); +// This is the key size (128) in network byte order. +const uint8_t ODK_EncKeySuffix[] = {0x00, 0x00, 0x00, 0x80}; +const size_t ODK_EncKeySuffixLength = sizeof(ODK_EncKeySuffix); + +OEMCryptoResult ODK_GenerateKeyContexts(const uint8_t* context, + size_t context_length, + uint8_t* mac_key_context, + size_t* mac_key_context_length, + uint8_t* enc_key_context, + size_t* enc_key_context_length) { + size_t real_mac_length; + size_t real_enc_length; + if (odk_add_overflow_ux( + context_length, + ODK_MacKeyLabelWithZeroLength + ODK_MacKeySuffixLength, + &real_mac_length) || + real_mac_length > 0xffffffff || + odk_add_overflow_ux( + context_length, + ODK_EncKeyLabelWithZeroLength + ODK_EncKeySuffixLength, + &real_enc_length) || + real_enc_length > 0xffffffff) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + bool short_buffer = false; + if (mac_key_context_length) { + short_buffer = real_mac_length > *mac_key_context_length; + *mac_key_context_length = real_mac_length; + } + if (enc_key_context_length) { + short_buffer = short_buffer || real_enc_length > *enc_key_context_length; + *enc_key_context_length = real_enc_length; + } + if (short_buffer || !mac_key_context || !enc_key_context) { + return OEMCrypto_ERROR_SHORT_BUFFER; + } + + if (!context || !mac_key_context_length || !enc_key_context_length) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + + memcpy(mac_key_context, ODK_MacKeyLabelWithZero, + ODK_MacKeyLabelWithZeroLength); + memcpy(mac_key_context + ODK_MacKeyLabelWithZeroLength, context, + context_length); + memcpy(mac_key_context + ODK_MacKeyLabelWithZeroLength + context_length, + ODK_MacKeySuffix, ODK_MacKeySuffixLength); + + memcpy(enc_key_context, ODK_EncKeyLabelWithZero, + ODK_EncKeyLabelWithZeroLength); + memcpy(enc_key_context + ODK_EncKeyLabelWithZeroLength, context, + context_length); + memcpy(enc_key_context + ODK_EncKeyLabelWithZeroLength + context_length, + ODK_EncKeySuffix, ODK_EncKeySuffixLength); + + return OEMCrypto_SUCCESS; +} diff --git a/oemcrypto/odk/src/odk.gyp b/oemcrypto/odk/src/odk.gyp index 4aa79a4b..d026db19 100644 --- a/oemcrypto/odk/src/odk.gyp +++ b/oemcrypto/odk/src/odk.gyp @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# Copyright 2019 Google LLC. This file and proprietary # source code may only be used and distributed under the Widevine License # Agreement. diff --git a/oemcrypto/odk/src/odk.gypi b/oemcrypto/odk/src/odk.gypi index 1867605a..d37f1340 100644 --- a/oemcrypto/odk/src/odk.gypi +++ b/oemcrypto/odk/src/odk.gypi @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# Copyright 2019 Google LLC. This file and proprietary # source code may only be used and distributed under the Widevine License # Agreement. diff --git a/oemcrypto/odk/src/odk_assert.h b/oemcrypto/odk/src/odk_assert.h index e0de19df..6322a5b9 100644 --- a/oemcrypto/odk/src/odk_assert.h +++ b/oemcrypto/odk/src/odk_assert.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/odk_endian.h b/oemcrypto/odk/src/odk_endian.h index 58a2fd70..bdd79d17 100644 --- a/oemcrypto/odk/src/odk_endian.h +++ b/oemcrypto/odk/src/odk_endian.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/odk_message.c b/oemcrypto/odk/src/odk_message.c index 7cc05c05..a2487ff2 100644 --- a/oemcrypto/odk/src/odk_message.c +++ b/oemcrypto/odk/src/odk_message.c @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/odk_message_priv.h b/oemcrypto/odk/src/odk_message_priv.h index 8ad5f030..f8e9bcc6 100644 --- a/oemcrypto/odk/src/odk_message_priv.h +++ b/oemcrypto/odk/src/odk_message_priv.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/odk_overflow.c b/oemcrypto/odk/src/odk_overflow.c index 37c3bb9b..ba19962f 100644 --- a/oemcrypto/odk/src/odk_overflow.c +++ b/oemcrypto/odk/src/odk_overflow.c @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/odk_overflow.h b/oemcrypto/odk/src/odk_overflow.h index e7257051..7b50552b 100644 --- a/oemcrypto/odk/src/odk_overflow.h +++ b/oemcrypto/odk/src/odk_overflow.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/odk_serialize.c b/oemcrypto/odk/src/odk_serialize.c index eec02663..04982fa4 100644 --- a/oemcrypto/odk/src/odk_serialize.c +++ b/oemcrypto/odk/src/odk_serialize.c @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -50,10 +50,6 @@ static void Pack_ODK_ParsedLicense(ODK_Message* msg, ODK_Packing_ParsedLicense const* obj, const ODK_NonceValues* nonce_values) { /* hand-coded */ - if (obj->key_array_length > ODK_MAX_NUM_KEYS) { - ODK_Message_SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); - return; - } Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); Pack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); Pack_OEMCrypto_Substring(msg, &obj->pst); @@ -128,6 +124,11 @@ void Pack_ODK_PreparedLicenseRequestV17( Pack_ODK_CoreMessage(msg, &obj->core_message); } +void Pack_ODK_PreparedReleaseRequest(ODK_Message* msg, + const ODK_PreparedReleaseRequest* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); +} + void Pack_ODK_PreparedRenewalRequest(ODK_Message* msg, ODK_PreparedRenewalRequest const* obj) { Pack_ODK_CoreMessage(msg, &obj->core_message); @@ -182,6 +183,11 @@ void Pack_ODK_LicenseResponse(ODK_Message* msg, } } +void Pack_ODK_ReleaseResponse(ODK_Message* msg, + ODK_ReleaseResponse const* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); +} + void Pack_ODK_RenewalResponse(ODK_Message* msg, ODK_RenewalResponse const* obj) { Pack_ODK_PreparedRenewalRequest(msg, &obj->request); @@ -322,6 +328,11 @@ void Unpack_ODK_PreparedLicenseRequestV17(ODK_Message* msg, Unpack_ODK_CoreMessage(msg, &obj->core_message); } +void Unpack_ODK_PreparedReleaseRequest(ODK_Message* msg, + ODK_PreparedReleaseRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); +} + void Unpack_ODK_PreparedRenewalRequest(ODK_Message* msg, ODK_PreparedRenewalRequest* obj) { Unpack_ODK_CoreMessage(msg, &obj->core_message); @@ -384,6 +395,10 @@ void Unpack_ODK_LicenseResponse(ODK_Message* msg, ODK_LicenseResponse* obj) { } } +void Unpack_ODK_ReleaseResponse(ODK_Message* msg, ODK_ReleaseResponse* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); +} + void Unpack_ODK_RenewalResponse(ODK_Message* msg, ODK_RenewalResponse* obj) { Unpack_ODK_PreparedRenewalRequest(msg, &obj->request); Unpack_uint64_t(msg, &obj->renewal_duration_seconds); diff --git a/oemcrypto/odk/src/odk_serialize.h b/oemcrypto/odk/src/odk_serialize.h index 1ec74b6c..a7aa2208 100644 --- a/oemcrypto/odk/src/odk_serialize.h +++ b/oemcrypto/odk/src/odk_serialize.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -20,6 +20,8 @@ void Pack_ODK_PreparedLicenseRequest(ODK_Message* msg, const ODK_PreparedLicenseRequest* obj); void Pack_ODK_PreparedLicenseRequestV17( ODK_Message* msg, const ODK_PreparedLicenseRequestV17* obj); +void Pack_ODK_PreparedReleaseRequest(ODK_Message* msg, + const ODK_PreparedReleaseRequest* obj); void Pack_ODK_PreparedRenewalRequest(ODK_Message* msg, const ODK_PreparedRenewalRequest* obj); void Pack_ODK_PreparedProvisioningRequest( @@ -34,6 +36,7 @@ void Pack_ODK_PreparedRenewedProvisioningRequest( /* odk unpack */ void Unpack_ODK_CoreMessage(ODK_Message* msg, ODK_CoreMessage* obj); void Unpack_ODK_LicenseResponse(ODK_Message* msg, ODK_LicenseResponse* obj); +void Unpack_ODK_ReleaseResponse(ODK_Message* msg, ODK_ReleaseResponse* obj); void Unpack_ODK_RenewalResponse(ODK_Message* msg, ODK_RenewalResponse* obj); void Unpack_ODK_ProvisioningResponse(ODK_Message* msg, ODK_ProvisioningResponse* obj); @@ -45,6 +48,7 @@ void Unpack_ODK_Provisioning40Response(ODK_Message* msg, /* kdo pack */ void Pack_ODK_LicenseResponse(ODK_Message* msg, const ODK_Packing_LicenseResponse* obj); +void Pack_ODK_ReleaseResponse(ODK_Message* msg, const ODK_ReleaseResponse* obj); void Pack_ODK_RenewalResponse(ODK_Message* msg, const ODK_RenewalResponse* obj); void Pack_ODK_ProvisioningResponse(ODK_Message* msg, const ODK_ProvisioningResponse* obj); @@ -58,6 +62,8 @@ void Unpack_ODK_PreparedLicenseRequest(ODK_Message* msg, ODK_PreparedLicenseRequest* obj); void Unpack_ODK_PreparedLicenseRequestV17(ODK_Message* msg, ODK_PreparedLicenseRequestV17* obj); +void Unpack_ODK_PreparedReleaseRequest(ODK_Message* msg, + ODK_PreparedReleaseRequest* obj); void Unpack_ODK_PreparedRenewalRequest(ODK_Message* msg, ODK_PreparedRenewalRequest* obj); void Unpack_ODK_PreparedProvisioningRequest( diff --git a/oemcrypto/odk/src/odk_structs_priv.h b/oemcrypto/odk/src/odk_structs_priv.h index 5306a4b6..208ce2c4 100644 --- a/oemcrypto/odk/src/odk_structs_priv.h +++ b/oemcrypto/odk/src/odk_structs_priv.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -24,13 +24,13 @@ typedef uint32_t ODK_MessageType; #define ODK_Renewal_Response_Type ((ODK_MessageType)4u) #define ODK_Provisioning_Request_Type ((ODK_MessageType)5u) #define ODK_Provisioning_Response_Type ((ODK_MessageType)6u) +#define ODK_Release_Request_Type ((ODK_MessageType)7u) +#define ODK_Release_Response_Type ((ODK_MessageType)8u) #define ODK_Renewed_Provisioning_Request_Type ((ODK_MessageType)11u) #define ODK_Provisioning40_Request_Type ((ODK_MessageType)12u) // TODO(b/244580447): Reserve future message types to support // forward compatibility. -#define ODK_Release_Request_Type ((ODK_MessageType)7u) -#define ODK_Release_Response_Type ((ODK_MessageType)8u) #define ODK_Common_Request_Type ((ODK_MessageType)9u) #define ODK_Common_Response_Type ((ODK_MessageType)10u) @@ -49,6 +49,10 @@ typedef struct { ODK_CoreMessage core_message; } ODK_PreparedLicenseRequestV17; +typedef struct { + ODK_CoreMessage core_message; +} ODK_PreparedReleaseRequest; + typedef struct { ODK_CoreMessage core_message; uint64_t playback_time; @@ -97,6 +101,10 @@ typedef struct { uint8_t request_hash[ODK_SHA256_HASH_SIZE]; } ODK_Packing_LicenseResponse; +typedef struct { + ODK_CoreMessage core_message; +} ODK_ReleaseResponse; + typedef struct { ODK_PreparedRenewalRequest request; uint64_t renewal_duration_seconds; @@ -124,6 +132,7 @@ typedef struct { #define ODK_CORE_MESSAGE_SIZE 20u #define ODK_LICENSE_REQUEST_SIZE 90u #define ODK_LICENSE_REQUEST_SIZE_V17 20u +#define ODK_RELEASE_REQUEST_SIZE 20u #define ODK_RENEWAL_REQUEST_SIZE 28u #define ODK_PROVISIONING_REQUEST_SIZE 94u #define ODK_PROVISIONING_REQUEST_SIZE_V17 88u diff --git a/oemcrypto/odk/src/odk_timer.c b/oemcrypto/odk/src/odk_timer.c index 0b503f5e..c76fc5ab 100644 --- a/oemcrypto/odk/src/odk_timer.c +++ b/oemcrypto/odk/src/odk_timer.c @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -274,7 +274,10 @@ OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, nonce_values->api_minor_version = 2; break; case 18: - nonce_values->api_minor_version = 5; + nonce_values->api_minor_version = 4; + break; + case 19: + nonce_values->api_minor_version = 1; break; default: nonce_values->api_minor_version = 0; diff --git a/oemcrypto/odk/src/odk_util.c b/oemcrypto/odk/src/odk_util.c index a6669a4c..12c93e4c 100644 --- a/oemcrypto/odk/src/odk_util.c +++ b/oemcrypto/odk/src/odk_util.c @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/odk_util.h b/oemcrypto/odk/src/odk_util.h index ab932dd0..723fb594 100644 --- a/oemcrypto/odk/src/odk_util.h +++ b/oemcrypto/odk/src/odk_util.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/serialization_base.c b/oemcrypto/odk/src/serialization_base.c index b84385bd..4527b2af 100644 --- a/oemcrypto/odk/src/serialization_base.c +++ b/oemcrypto/odk/src/serialization_base.c @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/src/serialization_base.h b/oemcrypto/odk/src/serialization_base.h index 299e0479..e4b0ef00 100644 --- a/oemcrypto/odk/src/serialization_base.h +++ b/oemcrypto/odk/src/serialization_base.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/test/fuzzing/Android.bp b/oemcrypto/odk/test/fuzzing/Android.bp index f3512177..6ce3f457 100644 --- a/oemcrypto/odk/test/fuzzing/Android.bp +++ b/oemcrypto/odk/test/fuzzing/Android.bp @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp b/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp index 0a0c0ae7..614d6e75 100644 --- a/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c index 655bd06d..3ebaaa94 100644 --- a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c index 534b2457..8a65bb23 100644 --- a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. #include "fuzzing/corpus_generator/odk_corpus_generator_helper.h" diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h index d6c1e99a..9f495b78 100644 --- a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. #ifndef WIDEVINE_ODK_TEST_FUZZING_CORPUS_GENERATOR_ODK_CORPUS_GENERATOR_HELPER_H_ diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_fuzz_corpus_generator.gyp b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_fuzz_corpus_generator.gyp index 83299841..9f46e3ad 100644 --- a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_fuzz_corpus_generator.gyp +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_fuzz_corpus_generator.gyp @@ -1,4 +1,4 @@ -# Copyright 2020 Google LLC. All rights reserved. This file and proprietary +# Copyright 2020 Google LLC. This file and proprietary # source code may only be used and distributed under the Widevine License # Agreement. diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz.gyp b/oemcrypto/odk/test/fuzzing/odk_fuzz.gyp index 694e3acc..11ec27b7 100644 --- a/oemcrypto/odk/test/fuzzing/odk_fuzz.gyp +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz.gyp @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# Copyright 2019 Google LLC. This file and proprietary # source code may only be used and distributed under the Widevine License # Agreement. diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp index 04905f1d..fbc00dc2 100644 --- a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. #include "fuzzing/odk_fuzz_helper.h" diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h index 0f454671..309c2bed 100644 --- a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. #ifndef WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_HELPER_H_ diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h b/oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h index b35c56a0..1d45557b 100644 --- a/oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. #ifndef WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_STRUCTS_H_ diff --git a/oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp index d089c4ad..043f965b 100644 --- a/oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp index f3655248..c674cdb0 100644 --- a/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp index 880e1d88..735ce7a2 100644 --- a/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp index deac024a..b17f5e07 100644 --- a/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp index 3a0457d4..ecb96e81 100644 --- a/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp index 4ad8ca40..7e6c94e7 100644 --- a/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp index d715eeb6..befa8a43 100644 --- a/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp index c0903758..416c7b0d 100644 --- a/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp index 2502ab80..7763f133 100644 --- a/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2020 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/odk/test/odk_core_message_test.cpp b/oemcrypto/odk/test/odk_core_message_test.cpp index db476ad0..bea78914 100644 --- a/oemcrypto/odk/test/odk_core_message_test.cpp +++ b/oemcrypto/odk/test/odk_core_message_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// Copyright 2020 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -13,7 +13,10 @@ #include "core_message_serialize_proto.h" #include "core_message_types.h" #include "gtest/gtest.h" +#include "license_protocol.pb.h" #include "odk.h" +#include "odk_structs.h" +#include "third_party/absl/log/log.h" #include "third_party/absl/strings/escaping.h" namespace wvodk_test { diff --git a/oemcrypto/odk/test/odk_golden_v16.cpp b/oemcrypto/odk/test/odk_golden_v16.cpp index 2e80b0a9..3dcf1f79 100644 --- a/oemcrypto/odk/test/odk_golden_v16.cpp +++ b/oemcrypto/odk/test/odk_golden_v16.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC. All rights reserved. This file and proprietary +// Copyright 2023 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -6,7 +6,9 @@ #include #include +#include "OEMCryptoCENCCommon.h" #include "core_message_deserialize.h" +#include "core_message_features.h" #include "core_message_serialize.h" #include "core_message_serialize_proto.h" #include "core_message_types.h" diff --git a/oemcrypto/odk/test/odk_golden_v17.cpp b/oemcrypto/odk/test/odk_golden_v17.cpp index 786c6783..e1e832ce 100644 --- a/oemcrypto/odk/test/odk_golden_v17.cpp +++ b/oemcrypto/odk/test/odk_golden_v17.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC. All rights reserved. This file and proprietary +// Copyright 2023 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -6,7 +6,9 @@ #include #include +#include "OEMCryptoCENCCommon.h" #include "core_message_deserialize.h" +#include "core_message_features.h" #include "core_message_serialize.h" #include "core_message_serialize_proto.h" #include "core_message_types.h" diff --git a/oemcrypto/odk/test/odk_golden_v18.cpp b/oemcrypto/odk/test/odk_golden_v18.cpp index c1adfb1e..acbd8dc1 100644 --- a/oemcrypto/odk/test/odk_golden_v18.cpp +++ b/oemcrypto/odk/test/odk_golden_v18.cpp @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC. All rights reserved. This file and proprietary +// Copyright 2023 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -6,7 +6,9 @@ #include #include +#include "OEMCryptoCENCCommon.h" #include "core_message_deserialize.h" +#include "core_message_features.h" #include "core_message_serialize.h" #include "core_message_serialize_proto.h" #include "core_message_types.h" diff --git a/oemcrypto/odk/test/odk_test.cpp b/oemcrypto/odk/test/odk_test.cpp index c6937c3d..5201ff80 100644 --- a/oemcrypto/odk/test/odk_test.cpp +++ b/oemcrypto/odk/test/odk_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -18,9 +18,11 @@ #include "core_message_serialize_proto.h" #include "core_message_types.h" #include "gtest/gtest.h" +#include "license_protocol.pb.h" #include "odk_overflow.h" #include "odk_structs.h" #include "odk_structs_priv.h" +#include "odk_target.h" #include "odk_test_helper.h" namespace wvodk_test { @@ -32,12 +34,14 @@ using oemcrypto_core_message::ODK_LicenseRequest; using oemcrypto_core_message::ODK_MessageCounter; using oemcrypto_core_message::ODK_Provisioning40Request; using oemcrypto_core_message::ODK_ProvisioningRequest; +using oemcrypto_core_message::ODK_ReleaseRequest; using oemcrypto_core_message::ODK_RenewalRequest; using oemcrypto_core_message::deserialize::CoreCommonRequestFromMessage; using oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage; using oemcrypto_core_message::deserialize::CoreProvisioning40RequestFromMessage; using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreReleaseRequestFromMessage; using oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage; using oemcrypto_core_message::deserialize:: CoreRenewedProvisioningRequestFromMessage; @@ -49,6 +53,7 @@ using oemcrypto_core_message::serialize::CreateCoreProvisioning40Response; using oemcrypto_core_message::serialize::CreateCoreProvisioningResponse; using oemcrypto_core_message::serialize:: CreateCoreProvisioningResponseFromProto; +using oemcrypto_core_message::serialize::CreateCoreReleaseResponse; using oemcrypto_core_message::serialize::CreateCoreRenewalResponse; constexpr uint32_t kExtraPayloadSize = 128u; @@ -255,7 +260,7 @@ TEST(OdkTest, SerializeFieldsStress) { size_t total_size = 0; for (int i = 0; i < n; i++) { fields[i].type = static_cast( - std::rand() % static_cast(ODK_LAST_STRESSABLE_TYPE)); + std::rand() % (static_cast(ODK_LAST_STRESSABLE_TYPE) + 1)); fields[i].value = malloc(ODK_AllocSize(fields[i].type)); fields[i].name = "stress"; total_size += ODK_FieldLength(fields[i].type); @@ -686,6 +691,35 @@ TEST(OdkTest, RenewalRequestRoundtrip) { odk_prepare_func, kdo_parse_func); } +TEST(OdkTest, ReleaseRequestRoundTrip) { + const uint32_t clock_security_level = 1; + const uint32_t status = 1; + constexpr uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; + uint64_t playback_time = 0xCAFE00000000; + const int64_t seconds_since_license_requested = 1; + const int64_t seconds_since_first_decrypt = + static_cast(system_time_seconds - playback_time); + ODK_ClockValues clock_values; + memset(&clock_values, 0, sizeof(clock_values)); + clock_values.time_of_first_decrypt = seconds_since_first_decrypt; + std::vector extra_fields = {}; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreReleaseRequest( + buf, SIZE_MAX, size, nonce_values, status, clock_security_level, + seconds_since_license_requested, seconds_since_first_decrypt, + &clock_values, system_time_seconds); + }; + auto kdo_parse_func = [&](const std::string& oemcrypto_core_message, + ODK_ReleaseRequest* core_release_request) { + bool ok = CoreReleaseRequestFromMessage(oemcrypto_core_message, + core_release_request); + return ok; + }; + ValidateRequest(ODK_Release_Request_Type, extra_fields, + odk_prepare_func, kdo_parse_func); +} + TEST(OdkTest, ProvisionRequestRoundtrip) { ODK_MessageCounterInfo counter_info; counter_info.master_generation_number = 0x12345678abcdffff; @@ -1095,7 +1129,7 @@ TEST_P(OdkVersionTest, LicenseResponseRoundtripMoreThanMaxKeys) { bool result = CreateCoreLicenseResponse(features_, parsed_license, core_request, request_hash_string, &oemcrypto_core_message); - EXPECT_FALSE(result); + EXPECT_TRUE(result); delete[] buf; delete[] zero; @@ -1131,6 +1165,30 @@ TEST_P(OdkVersionTest, RenewalResponseRoundtrip) { kdo_prepare_func); } +TEST_P(OdkVersionTest, ReleaseResponseRoundtrip) { + ODK_ReleaseResponseParams params; + ODK_SetDefaultReleaseResponseParams(¶ms); + SetRequestVersion(¶ms); + const int64_t seconds_since_license_requested = + params.seconds_since_license_requested; + const int64_t seconds_since_first_decrypt = + params.seconds_since_first_decrypt; + auto odk_parse_func = [&](const uint8_t* buf, size_t size) { + OEMCryptoResult err = + ODK_ParseRelease(buf, size, size, &(params.core_message.nonce_values)); + return err; + }; + auto kdo_prepare_func = [&](ODK_ReleaseRequest& core_request, + std::string* oemcrypto_core_message) { + return CreateCoreReleaseResponse( + features_, core_request, seconds_since_license_requested, + seconds_since_first_decrypt, oemcrypto_core_message); + }; + ValidateResponse(GetParam(), &(params.core_message), + params.extra_fields, odk_parse_func, + kdo_prepare_func); +} + TEST_P(OdkVersionTest, ProvisionResponseRoundtrip) { ODK_ProvisioningResponseParams params; ODK_SetDefaultProvisioningResponseParams(¶ms, @@ -1216,7 +1274,8 @@ std::vector TestCases() { // number. {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, 5}, + {18, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 18, 4}, + {19, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 19, 1}, // 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}, @@ -1226,15 +1285,17 @@ std::vector TestCases() { {ODK_MAJOR_VERSION, 18, 1, 18, 1}, {ODK_MAJOR_VERSION, 18, 2, 18, 2}, {ODK_MAJOR_VERSION, 18, 3, 18, 3}, - {ODK_MAJOR_VERSION, 18, 4, 18, 4}, - {ODK_MAJOR_VERSION, 18, 5, 18, 5}, + {ODK_MAJOR_VERSION, 19, 0, 19, 0}, + {ODK_MAJOR_VERSION, 19, 1, 19, 1}, {0, 16, 3, 16, 3}, {0, 16, 4, 16, 4}, {0, 16, 5, 16, 5}, {0, 17, 1, 17, 1}, {0, 17, 2, 17, 2}, + {0, 18, 3, 18, 3}, {0, 18, 4, 18, 4}, - {0, 18, 5, 18, 5}, + {0, 19, 0, 19, 0}, + {0, 19, 1, 19, 1}, }; return test_cases; } @@ -1376,6 +1437,130 @@ TEST(OdkOverflowTest, MultiplyUX) { EXPECT_TRUE(odk_mul_overflow_ux(4, SIZE_MAX >> 1, &result)); } +TEST(OdkTest, GenerateKeyContexts_Success) { + const uint8_t kContext[] = { + 0x0a, 0x4c, 0x08, 0x00, 0x12, 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, + 0x10, 0x19, 0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, 0xc1, 0x22, 0x67, + 0x80, 0x53, 0x36, 0x21, 0x36, 0xbd, 0xf8, 0x40, 0x8f, 0x82, 0x76, 0xe4, + 0xc2, 0xd8, 0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, 0x64, 0x6e, 0x58, + 0x73, 0x49, 0x30, 0xac, 0xeb, 0xe8, 0x99, 0xb3, 0xe4, 0x64, 0x18, 0x9a, + 0x14, 0xa8, 0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, 0x64, 0x0b, 0xd2, + 0x2e, 0xf4, 0x4b, 0x2d, 0x7e, 0x39, 0x12, 0x25, 0x0a, 0x23, 0x0a, 0x14, + 0x08, 0x01, 0x12, 0x10, 0x09, 0x15, 0x00, 0x7c, 0xaa, 0x9b, 0x59, 0x31, + 0xb7, 0x6a, 0x3a, 0x85, 0xf0, 0x46, 0x52, 0x3e, 0x10, 0x01, 0x1a, 0x09, + 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x18, 0x01, 0x20, + 0x00, 0x2a, 0x0c, 0x31, 0x38, 0x38, 0x36, 0x37, 0x38, 0x37, 0x34, 0x30, + 0x35, 0x00, 0x00, + }; + const uint8_t kMacKey[] = { + 0x41, 0x55, 0x54, 0x48, 0x45, 0x4e, 0x54, 0x49, 0x43, 0x41, 0x54, 0x49, + 0x4f, 0x4e, 0x00, 0x0a, 0x4c, 0x08, 0x00, 0x12, 0x48, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x10, 0x19, 0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, + 0xc1, 0x22, 0x67, 0x80, 0x53, 0x36, 0x21, 0x36, 0xbd, 0xf8, 0x40, 0x8f, + 0x82, 0x76, 0xe4, 0xc2, 0xd8, 0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, + 0x64, 0x6e, 0x58, 0x73, 0x49, 0x30, 0xac, 0xeb, 0xe8, 0x99, 0xb3, 0xe4, + 0x64, 0x18, 0x9a, 0x14, 0xa8, 0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, + 0x64, 0x0b, 0xd2, 0x2e, 0xf4, 0x4b, 0x2d, 0x7e, 0x39, 0x12, 0x25, 0x0a, + 0x23, 0x0a, 0x14, 0x08, 0x01, 0x12, 0x10, 0x09, 0x15, 0x00, 0x7c, 0xaa, + 0x9b, 0x59, 0x31, 0xb7, 0x6a, 0x3a, 0x85, 0xf0, 0x46, 0x52, 0x3e, 0x10, + 0x01, 0x1a, 0x09, 0x39, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, + 0x18, 0x01, 0x20, 0x00, 0x2a, 0x0c, 0x31, 0x38, 0x38, 0x36, 0x37, 0x38, + 0x37, 0x34, 0x30, 0x35, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, + }; + const uint8_t kEncKey[] = { + 0x45, 0x4e, 0x43, 0x52, 0x59, 0x50, 0x54, 0x49, 0x4f, 0x4e, 0x00, 0x0a, + 0x4c, 0x08, 0x00, 0x12, 0x48, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, + 0x19, 0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, 0xc1, 0x22, 0x67, 0x80, + 0x53, 0x36, 0x21, 0x36, 0xbd, 0xf8, 0x40, 0x8f, 0x82, 0x76, 0xe4, 0xc2, + 0xd8, 0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, 0x64, 0x6e, 0x58, 0x73, + 0x49, 0x30, 0xac, 0xeb, 0xe8, 0x99, 0xb3, 0xe4, 0x64, 0x18, 0x9a, 0x14, + 0xa8, 0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, 0x64, 0x0b, 0xd2, 0x2e, + 0xf4, 0x4b, 0x2d, 0x7e, 0x39, 0x12, 0x25, 0x0a, 0x23, 0x0a, 0x14, 0x08, + 0x01, 0x12, 0x10, 0x09, 0x15, 0x00, 0x7c, 0xaa, 0x9b, 0x59, 0x31, 0xb7, + 0x6a, 0x3a, 0x85, 0xf0, 0x46, 0x52, 0x3e, 0x10, 0x01, 0x1a, 0x09, 0x39, + 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x18, 0x01, 0x20, 0x00, + 0x2a, 0x0c, 0x31, 0x38, 0x38, 0x36, 0x37, 0x38, 0x37, 0x34, 0x30, 0x35, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, + }; + + std::vector mac_key(sizeof(kMacKey) + 24); + std::vector enc_key(sizeof(kEncKey) + 24); + size_t mac_key_size = mac_key.size(); + size_t enc_key_size = enc_key.size(); + ASSERT_EQ(OEMCrypto_SUCCESS, + ODK_GenerateKeyContexts(kContext, sizeof(kContext), &mac_key[0], + &mac_key_size, &enc_key[0], &enc_key_size)); + ASSERT_EQ(mac_key_size, sizeof(kMacKey)); + ASSERT_EQ(enc_key_size, sizeof(kEncKey)); + mac_key.resize(mac_key_size); + enc_key.resize(enc_key_size); + + EXPECT_EQ(std::vector(kMacKey, kMacKey + sizeof(kMacKey)), mac_key); + EXPECT_EQ(std::vector(kEncKey, kEncKey + sizeof(kEncKey)), enc_key); +} + +TEST(OdkTest, GenerateKeyContexts_ShortBuffer) { + const uint8_t kContext[] = {1, 2, 3}; + size_t mac_key_size = 0; + size_t enc_key_size = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_GenerateKeyContexts(kContext, sizeof(kContext), nullptr, + &mac_key_size, nullptr, &enc_key_size)); + EXPECT_EQ(mac_key_size, sizeof(kContext) + 19); + EXPECT_EQ(enc_key_size, sizeof(kContext) + 15); +} + +TEST(OdkTest, GenerateKeyContexts_ShortBufferMacOnly) { + const uint8_t kContext[] = {1, 2, 3}; + uint8_t buffer[128]; + size_t mac_key_size = 0; + size_t enc_key_size = sizeof(buffer); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_GenerateKeyContexts(kContext, sizeof(kContext), nullptr, + &mac_key_size, buffer, &enc_key_size)); + EXPECT_EQ(mac_key_size, sizeof(kContext) + 19); + EXPECT_EQ(enc_key_size, sizeof(kContext) + 15); +} + +TEST(OdkTest, GenerateKeyContexts_ShortBufferEncOnly) { + const uint8_t kContext[] = {1, 2, 3}; + uint8_t buffer[128]; + size_t mac_key_size = sizeof(buffer); + size_t enc_key_size = 0; + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_GenerateKeyContexts(kContext, sizeof(kContext), buffer, + &mac_key_size, buffer, &enc_key_size)); + EXPECT_EQ(mac_key_size, sizeof(kContext) + 19); + EXPECT_EQ(enc_key_size, sizeof(kContext) + 15); +} + +TEST(OdkTest, GenerateKeyContexts_NullArgs) { + const uint8_t kContext[] = {1, 2, 3}; + uint8_t buffer[24]; + size_t buffer_size = sizeof(buffer); + size_t buffer_size2 = sizeof(buffer); + + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, + ODK_GenerateKeyContexts(nullptr, sizeof(kContext), buffer, + &buffer_size, buffer, &buffer_size2)); + buffer_size = buffer_size2 = sizeof(buffer); // Update to avoid short buffer. + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_GenerateKeyContexts(kContext, sizeof(kContext), nullptr, + &buffer_size, buffer, &buffer_size2)); + buffer_size = buffer_size2 = sizeof(buffer); // Update to avoid short buffer. + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, + ODK_GenerateKeyContexts(kContext, sizeof(kContext), buffer, nullptr, + buffer, &buffer_size2)); + buffer_size = buffer_size2 = sizeof(buffer); // Update to avoid short buffer. + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_GenerateKeyContexts(kContext, sizeof(kContext), buffer, + &buffer_size, nullptr, &buffer_size2)); + buffer_size = buffer_size2 = sizeof(buffer); // Update to avoid short buffer. + EXPECT_EQ(OEMCrypto_ERROR_INVALID_CONTEXT, + ODK_GenerateKeyContexts(kContext, sizeof(kContext), buffer, + &buffer_size, buffer, nullptr)); +} + } // namespace } // namespace wvodk_test diff --git a/oemcrypto/odk/test/odk_test.gypi b/oemcrypto/odk/test/odk_test.gypi index 1b5c5e7f..9cc33f6f 100644 --- a/oemcrypto/odk/test/odk_test.gypi +++ b/oemcrypto/odk/test/odk_test.gypi @@ -1,4 +1,4 @@ -# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# Copyright 2019 Google LLC. This file and proprietary # source code may only be used and distributed under the Widevine License # Agreement. diff --git a/oemcrypto/odk/test/odk_test_helper.cpp b/oemcrypto/odk/test/odk_test_helper.cpp index db9b2f2c..184515df 100644 --- a/oemcrypto/odk/test/odk_test_helper.cpp +++ b/oemcrypto/odk/test/odk_test_helper.cpp @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -266,6 +266,14 @@ void ODK_SetDefaultLicenseResponseParams(ODK_LicenseResponseParams* params, } } +void ODK_SetDefaultReleaseResponseParams(ODK_ReleaseResponseParams* params) { + ODK_SetDefaultCoreFields(&(params->core_message), ODK_Release_Response_Type); + params->status = kActive; + params->clock_security_level = 0; + params->seconds_since_license_requested = 0; + params->seconds_since_first_decrypt = 0; +} + void ODK_SetDefaultRenewalResponseParams(ODK_RenewalResponseParams* params) { ODK_SetDefaultCoreFields(&(params->core_message), ODK_Renewal_Response_Type); params->system_time = 0xfaceb00c; @@ -350,22 +358,22 @@ size_t ODK_FieldLength(ODK_FieldType type) { return sizeof(uint32_t); case ODK_UINT64: return sizeof(uint64_t); - case ODK_BOOL: // Booleans are stored in the message as 32 bit ints. - return sizeof(uint32_t); + case ODK_INT64: + return sizeof(uint64_t); case ODK_SUBSTRING: return sizeof(uint32_t) + sizeof(uint32_t); case ODK_DEVICEID: return ODK_DEVICE_ID_LEN_MAX; - case ODK_MESSAGECOUNTER: - return ODK_MESSAGECOUNTERINFO_SIZE; case ODK_DEVICEINFO: return ODK_DEVICE_INFO_LEN_MAX; + case ODK_MESSAGECOUNTER: + return ODK_MESSAGECOUNTERINFO_SIZE; case ODK_RENEWALDATA: return ODK_KEYBOX_RENEWAL_DATA_SIZE; case ODK_HASH: return ODK_SHA256_HASH_SIZE; - default: - return SIZE_MAX; + case ODK_BOOL: // Booleans are stored in the message as 32 bit ints. + return sizeof(uint32_t); } } @@ -406,6 +414,12 @@ OEMCryptoResult ODK_WriteSingleField(uint8_t* buf, const ODK_Field* field) { memcpy(buf, &u64, sizeof(u64)); break; } + case ODK_INT64: { + const int64_t i64 = + oemcrypto_htobe64(*static_cast(field->value)); + memcpy(buf, &i64, sizeof(i64)); + break; + } case ODK_BOOL: { const bool value = *static_cast(field->value); const uint32_t u32 = oemcrypto_htobe32(value ? 1 : 0); @@ -485,6 +499,12 @@ OEMCryptoResult ODK_ReadSingleField(const uint8_t* buf, *u64p = oemcrypto_be64toh(*u64p); break; } + case ODK_INT64: { + memcpy(field->value, buf, sizeof(int64_t)); + int64_t* i64p = static_cast(field->value); + *i64p = oemcrypto_be64toh(*i64p); + break; + } case ODK_BOOL: { uint32_t value; memcpy(&value, buf, sizeof(uint32_t)); @@ -604,6 +624,14 @@ OEMCryptoResult ODK_DumpSingleField(const uint8_t* buf, << "\n"; break; } + case ODK_INT64: { + int64_t val; + memcpy(&val, buf, sizeof(int64_t)); + val = oemcrypto_be64toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } case ODK_SUBSTRING: { uint32_t off = 0; uint32_t len = 0; @@ -708,15 +736,15 @@ void ODK_ExpectEqualBuf(const void* s1, const void* s2, size_t n, if (temp_fd >= 0) { close(temp_fd); } else { - std::cerr << "Failed to open temp file." << std::endl; + std::cerr << "Failed to open temp file." << '\n'; break; } std::string tmp(_tmp); std::fstream out(tmp, std::ios::out | std::ios::binary); out.write(static_cast(buffers[i]), n); out.close(); - std::cerr << std::endl - << "Message buffer " << i << " dumped to " << tmp << std::endl; + std::cerr << '\n' + << "Message buffer " << i << " dumped to " << tmp << '\n'; size_t bytes_written; uint8_t* buf = const_cast(reinterpret_cast(buffers[i])); diff --git a/oemcrypto/odk/test/odk_test_helper.h b/oemcrypto/odk/test/odk_test_helper.h index b0042332..5005b94f 100644 --- a/oemcrypto/odk/test/odk_test_helper.h +++ b/oemcrypto/odk/test/odk_test_helper.h @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// Copyright 2019 Google LLC. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. @@ -9,6 +9,7 @@ #include #include +#include "OEMCryptoCENCCommon.h" #include "odk_structs.h" #include "odk_structs_priv.h" @@ -19,6 +20,7 @@ enum ODK_FieldType { ODK_UINT16, ODK_UINT32, ODK_UINT64, + ODK_INT64, ODK_SUBSTRING, ODK_DEVICEID, ODK_DEVICEINFO, @@ -27,7 +29,7 @@ enum ODK_FieldType { ODK_HASH, // The "stressable" types are the ones we can put in a stress test that packs // and unpacks random data and can expect to get back the same thing. - ODK_LAST_STRESSABLE_TYPE, + ODK_LAST_STRESSABLE_TYPE = ODK_HASH, // Put boolean after ODK_LAST_STRESSABLE_TYPE, so that we skip boolean type in // SerializeFieldsStress because we unpack any nonzero to 'true'. ODK_BOOL, @@ -58,6 +60,15 @@ struct ODK_LicenseResponseParams { std::vector extra_fields; }; +struct ODK_ReleaseResponseParams { + ODK_CoreMessage core_message; + uint32_t status; + uint32_t clock_security_level; + int64_t seconds_since_license_requested; + int64_t seconds_since_first_decrypt; + std::vector extra_fields; +}; + struct ODK_RenewalResponseParams { ODK_CoreMessage core_message; uint64_t system_time; @@ -73,7 +84,6 @@ struct ODK_ProvisioningResponseParams { ODK_CoreMessage core_message; uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; uint32_t device_id_length; - uint32_t padding_u32; ODK_MessageCounterInfo counter_info; ODK_ParsedProvisioning parsed_provisioning; std::vector extra_fields; @@ -89,6 +99,7 @@ void ODK_SetDefaultCoreFields(ODK_CoreMessage* core_message, ODK_MessageType message_type); void ODK_SetDefaultLicenseResponseParams(ODK_LicenseResponseParams* params, uint32_t odk_major_version); +void ODK_SetDefaultReleaseResponseParams(ODK_ReleaseResponseParams* params); void ODK_SetDefaultRenewalResponseParams(ODK_RenewalResponseParams* params); void ODK_SetDefaultProvisioningResponseParams( ODK_ProvisioningResponseParams* params, uint32_t odk_major_version); diff --git a/oemcrypto/odk/test/odk_timer_test.cpp b/oemcrypto/odk/test/odk_timer_test.cpp index b595d980..1190cf00 100644 --- a/oemcrypto/odk/test/odk_timer_test.cpp +++ b/oemcrypto/odk/test/odk_timer_test.cpp @@ -1,4 +1,4 @@ -/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary +/* Copyright 2019 Google LLC. This file and proprietary * source code may only be used and distributed under the Widevine * License Agreement. */ diff --git a/oemcrypto/test/GEN_api_lock_file.c b/oemcrypto/test/GEN_api_lock_file.c index a60a6cfb..1822df9d 100644 --- a/oemcrypto/test/GEN_api_lock_file.c +++ b/oemcrypto/test/GEN_api_lock_file.c @@ -370,8 +370,58 @@ OEMCryptoResult _oecc140(void); // OEMCrypto_FactoryInstallBCCSignature defined in v18.3 OEMCryptoResult _oecc142(const uint8_t* signature, size_t signature_length); -// OEMCrypto_GetEmbeddedDrmCertificate defined in v18.5 -OEMCryptoResult _oecc143(uint8_t* public_cert, size_t* public_cert_length); +// OEMCrypto_PrepAndSignReleaseRequest defined in v19.0 +OEMCryptoResult _oecc147(OEMCrypto_SESSION session, uint8_t* message, + size_t message_length, size_t* core_message_size, + uint8_t* signature, size_t* signature_length); -// OEMCrypto_UseSecondaryKey defined in v18.5 -OEMCryptoResult _oecc144(OEMCrypto_SESSION session_id, bool dual_key); +// OEMCrypto_LoadLicense defined in v19.0 +OEMCryptoResult _oecc144(OEMCrypto_SESSION session, const uint8_t* context, + size_t context_length, const uint8_t* derivation_key, + size_t derivation_key_length, const uint8_t* message, + size_t message_length, size_t core_message_length, + const uint8_t* signature, size_t signature_length); + +// OEMCrypto_LoadRelease defined in v19.0 +OEMCryptoResult _oecc150(OEMCrypto_SESSION session, const uint8_t* message, + size_t message_length, size_t core_message_length, + const uint8_t* signature, size_t signature_length); + +// OEMCrypto_GetBCCType defined in v19.0 +OEMCryptoResult _oecc149(OEMCrypto_BCCType* bcc_type); + +// OEMCrypto_LoadProvisioning defined in v19.0 +OEMCryptoResult _oecc145(OEMCrypto_SESSION session, + const uint8_t* provision_request, + size_t provision_request_length, + const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); + +// OEMCrypto_LoadProvisioningCast defined in v19.0 +OEMCryptoResult _oecc146(OEMCrypto_SESSION session, + const uint8_t* derivation_key, + size_t derivation_key_length, + const uint8_t* provision_request, + size_t provision_request_length, + const uint8_t* message, size_t message_length, + size_t core_message_length, const uint8_t* signature, + size_t signature_length, uint8_t* wrapped_private_key, + size_t* wrapped_private_key_length); + +// OEMCrypto_GetUsageEntryInfo defined in v19.0 +OEMCryptoResult _oecc148(OEMCrypto_SESSION session, + OEMCrypto_Usage_Entry_Status* status, + int64_t* seconds_since_license_received, + int64_t* seconds_since_first_decrypt); + +// OEMCrypto_SetDecryptHash defined in v19.0 +OEMCryptoResult _oecc143(OEMCrypto_SESSION session, uint32_t frame_number, + uint32_t crc32); + +// OEMCrypto_GetEmbeddedDrmCertificate defined in v19.1 +OEMCryptoResult _oecc151(uint8_t* public_cert, size_t* public_cert_length); + +// OEMCrypto_UseSecondaryKey defined in v19.1 +OEMCryptoResult _oecc152(OEMCrypto_SESSION session_id, bool dual_key); diff --git a/oemcrypto/test/fuzz_tests/README.md b/oemcrypto/test/fuzz_tests/README.md index e1b7b9ca..249fe2f7 100644 --- a/oemcrypto/test/fuzz_tests/README.md +++ b/oemcrypto/test/fuzz_tests/README.md @@ -1,229 +1,140 @@ -# OEMCRYPTO Fuzzing +# OEMCrypto fuzzing -Refer to [Setting up Clusterfuzz](build_clusterfuzz.md) if you are interested -in setting up a local instance of cluster fuzz to run fuzzing on your own -OEMCrypto implementations on linux. +ClusterFuzz and Google Cloud infrastructure continuously runs OEMCrypto fuzz +tests and reports crashes. To create a new automated fuzzing setup, refer to +[*ClusterFuzz setup*][1]. -## Objective +## Run fuzz tests locally -* Run fuzzing on OEMCrypto public APIs on linux using google supported - clusterfuzz infrastructure to find security vulnerabilities. - - Design Document - https://docs.google.com/document/d/1mdSV2irJZz5Y9uYb5DmSIddBjrAIZU9q8G5Q_BGpA4I/edit?usp=sharing - - Fuzzing at google - - [go/fuzzing](https://g3doc.corp.google.com/security/fuzzing/g3doc/fuzzing_resources.md?cl=head) -## Monitoring -### Cluster fuzz statistics - -* Performance of OEMCrypto fuzz binaries running continuously using cluster - fuzz infrastructure can be monitored - [here](https://clusterfuzz.corp.google.com/fuzzer-stats). - - The options to select are `Job type: libfuzzer_asan_oemcrypto` and `Fuzzer: - fuzzer name you are looking for` - - Example: [load_license_fuzz](https://clusterfuzz.corp.google.com/fuzzer-stats?group_by=by-day&date_start=2022-07-11&date_end=2022-07-17&fuzzer=libFuzzer_oemcrypto_load_license_fuzz&job=libfuzzer_asan_oemcrypto) - -### Issues filed by clusterfuzz - Fixing those issues - -* Any issues found with the fuzz target under test are reported by clusterfuzz - [here](https://b.corp.google.com/hotlists/2442954). - -* The bug will have a link to the test case that generated the bug. Download - the test case and follow the steps from - [testing fuzzer locally](#testing-fuzzer-locally) section to run the fuzzer - locally using the test case that caused the crash. - -* Once the issue is fixed, consider adding the test case that caused the crash - to the seed corpus zip file. Details about seed corpus and their location - are mentioned in - [this section](#build-oemcrypto-unit-tests-to-generate-corpus). - -## Corpus - -* Once the fuzzer scripts are ready and running continuously using clusterfuzz - or android infrastructure, we can measure the efficiency of fuzzers by - looking at code coverage and number of new features that have been - discovered by fuzzer scripts here Fuzz script statistics. - - A fuzzer which tries to start from random inputs and figure out intelligent - inputs to crash the libraries can be time consuming and not effective. A way - to make fuzzers more effective is by providing a set of valid and invalid - inputs of the library so that fuzzer can use those as a starting point. - These sets of valid and invalid inputs are called corpus. - - The idea is to run OEMCrypto unit tests and read required data into binary - corpus files before calling into respective OEMCrypto APIs under test. - Writing corpus data to binary files is controlled by --generate_corpus flag. - -### Build OEMCrypto unit tests to generate corpus - -* Install Pre-requisites +1. Build the fuzz tests: ```shell - $ sudo apt-get install gyp ninja-build + $ cd + $ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests ``` -* download cdm source code (including ODK & OEMCrypto unit tests): +2. Run the fuzz test: ```shell - $ git clone sso://widevine-internal/cdm + $ out/Default/ [] ``` -* Build OEMCrypto unit tests and run with --generate_corpus flag to generate - corpus files: + The corpus directory is optional and can either be a seed corpus from the + `corpus` subdirectory or be an empty directory. The corpus will be extended + with new inputs while the fuzz test is running. + +## Triage crashes + +To reproduce a crash locally for debugging: + +1. Download the minimized testcase from the ClusterFuzz report. + +2. Build the fuzz tests: ```shell - $ cd /path/to/cdm/repo - $ export CDM_DIR=/path/to/cdm/repo + $ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests + ``` + +3. Debug the crash: + + ```shell + $ gdb --args -timeout=0 + ``` + + Example after substituting fuzz test and test case paths: + + ```shell + $ gdb --args out/Default/oemcrypto_opk_decrypt_cenc_fuzz -timeout=0 \ + clusterfuzz-testcase-minimized-oemcrypto_v17_opk_decrypt_cenc_fuzz-6727459932078080 + ``` + +4. If reproducing the crash is unsuccessful, download the unminimized testcase + from the ClusterFuzz report and try again. If still unsuccessful, this may + indicate there is a persistent state issue with the fuzz test. + +Once the root cause of the crash is identified, its severity and complexity +should be assessed. The [*SEI CERT C Coding Standard*][2] is a good resource for +risk assessment. The ClusterFuzz report will also provide input in the Security +field. For complex fixes with a longer timeline, ClusterFuzz may report +duplicate crashes with the same root cause. + +## Write fuzz tests + +While fuzzing has random elements, input data mutations are heavily influenced +by coverage feedback. Since discovering new control flow edges is a time +consuming process, input bytes should map to control flow edges in a simple, +predictable way. [`FuzzedDataProvider`][3], a class supplied with LLVM’s +libFuzzer, can be used to easily split input data: + +```cpp +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + FuzzedDataProvider fuzzed_data(data, size); + + // One bit of input data maps to this control flow edge: + if (fuzzed_data.ConsumeBool()) { + // ... + } + + // ... +} +``` + +Fuzzing API methods with complex, structured input, may benefit from a seed +corpus containing a representative set of starting inputs. Unfortunately, +`FuzzedDataProvider` is not suitable for fuzz tests utilizing a seed corpus +since there is no equivalent serialization functionality for generating the +corpus. OEMCrypto fuzz tests have previously used struct-based serialization, +but this is no longer recommended due to portability issues. Protocol Buffers or +another portable serialization format should be considered instead. + +Fuzz tests must be deterministic to reproduce and debug a crash. A common +pitfall is not resetting the OEMCrypto API state between calls to +`LLVMFuzzerTestOneInput`. Fully terminating OEMCrypto between inputs is +preferred, but in some cases, it may be necessary to implement careful +optimizations to achieve acceptable performance. Candidates for optimization +typically have less than 1000 executions per second (exec/s). +`LLVMFuzzerInitialize` can be used for global initialization, but there is no +corresponding termination method. + +A good starting example is [`oemcrypto_install_oem_private_key_fuzz.cc`][4]. +Targets should be added to `oemcrypto_opk_fuzztests.gyp` and, if the fuzz test +applies to partner OEMCrypto implementations, `partner_oemcrypto_fuzztests.gyp`. +The infrastructure expects that the target name starts with *oemcrypto* and ends +with *fuzz*. + +For additional information about writing fuzz tests, see +[*What makes a good fuzz target*][5]. + +## Generate corpus with OEMCrypto unit tests + +1. Build the unit tests: + + ```shell + $ cd + $ export CDM_DIR=${PWD} $ export PATH_TO_CDM_DIR=.. - $ gyp --format=ninja --depth=$(pwd) oemcrypto/oemcrypto_unittests.gyp - $ ninja -C out/Default/ - $ mkdir oemcrypto/test/fuzz_tests/corpus/_seed_corpus - # Generate corpus by excluding buffer overflow tests. - $ ./out/Default/oemcrypto_unittests --generate_corpus \ - --gtest_filter=-"*Huge*" + $ gyp --format=ninja --depth=${PWD} oemcrypto/oemcrypto_unittests.gyp + $ ninja -C out/Default ``` -* There can be lot of duplicate corpus files that are generated from unit - tests. We can minimize the corpus files to only a subset of files that - cover unique paths within the API when run using fuzzer. Run following - command to minimize corpus. +2. Run the unit tests with the `--generate_corpus` flag: ```shell - $ cd /path/to/cdm/repo - # build fuzzer binaries - $ ./oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests + $ mkdir oemcrypto/test/fuzz_tests/corpus/_seed_corpus + $ out/Default/oemcrypto_unittests --generate_corpus --gtest_filter='-*Huge*' + ``` + +3. The unit tests can generate many duplicate corpus files. To minimize the + corpus to only the subset of inputs that cover unique paths within the API: + + ```shell + $ oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests $ mkdir /tmp/minimized_corpus - # minimize corpus - $ ./out/Default/ -merge=1 /tmp/minimized_corpus \ - + $ out/Default/ -merge=1 /tmp/minimized_corpus ``` -* To avoid uploading huge binary files to git repository, the minimized corpus - files will be saved in fuzzername_seed_corpus.zip format in blockbuster - project's oemcrypto_fuzzing_corpus GCS bucket using gsutil. If you need - permissions for blockbuster project, contact widevine-engprod@google.com. - - ```shell - $ gsutil cp gs://oemcrypto_fuzzing_corpus/ \ - - ``` - -## Testing fuzzer locally - -* Corpus needed to run fuzz tests locally are available in blockbuster - project's oemcrypto_fuzzing_corpus GCS bucket. If you need permissions for - this project, contact widevine-engprod@google.com. Download corpus. - - ```shell - $ gsutil cp gs://oemcrypto_fuzzing_corpus/ \ - - ``` - -* Add flags to generate additional debugging information. Add '-g3' flag to - oemcrypto_fuzztests.gypi cflags_cc in order to generate additional debug - information locally. - -* Build and test fuzz scripts locally using following commands. The build - script builds fuzz binaries for opk implementation. - - ```shell - $ cd PATH_TO_CDM_DIR - $ ./oemcrypto/test/fuzz_tests/build_oemcrypto_fuzztests - $ mkdir /tmp/new_interesting_corpus - $ ./out/Default/fuzzer_binary /tmp/new_interesting_corpus \ - /path/to/fuzz/seed/corpus/folder - ``` - -* In order to run fuzz script against a crash input, follow the above steps - and run the fuzz binary against crash input rather than seed corpus. - - ```shell - $ ./out/Default/fuzzer_binary crash_input_file - ``` -## Adding a new OEMCrypto fuzz script -* In order to fuzz a new OEMCrypto API in future, a fuzz script can be added - to oemcrypto/test/fuzz_tests folder which starts with oemcrypto and ends - with fuzz.cc(GCB build script for oemcrypto fuzzers expects the format). - -* In the program, define the function LLVMFuzzerTestOneInput with the following signature: - ``` - extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - - return 0; - } - ``` - *Note*: Make sure LLVMFuzzerTestOneInput calls the function you want to fuzz. - -* Add a new target to oemcrypto_fuzztests.gyp file and follow instructions in - [testing fuzzer locally](#testing-fuzzer-locally) to build and test locally. - -## Building OEMCrypto fuzz scripts and uploading them to Google Cloud Storage: - -* We are using Google Cloud Buid (GCB) in order to setup continuous - integration which uploads OEMCrypto fuzz binaries to Google Cloud Storage. - GCB expects build script in form of a docker image that is uploaded to - Google Container Registry(GCR). - - The cloud build scripts (docker images) for widevine projects are - [here](https://widevine-internal.googlesource.com/cloud/+/refs/heads/master/docker/README.md) - - Refer to README of the project to setup a new docker image and uploading - the image to GCR. - -* Git on borg repository needs to be integrated with GCB and a git trigger - needs to be set up in order to achieve continuous integration. Git trigger - will mention which docker image the GCB needs to use in order to build fuzz - binaries. GCB searches for docker images from GCR. - - Design document lists the steps to create a git trigger. - -### Adding a new fuzz script to the build script: - -* As long as a new fuzz script is added which starts with oemcrypto and ends - with fuzz, the build command can be added to build_oemcrypto_fuzztests. - GCB script uses build_oemcrypto_fuzztests script to build fuzz binaries - and make them available for clusterfuzz to run continuously. - -* If the new fuzzer cannot follow the naming convention OR GCB script needs - to be updated for any other reason, refer to [this section](https://docs.google.com/document/d/1mdSV2irJZz5Y9uYb5DmSIddBjrAIZU9q8G5Q_BGpA4I/edit#heading=h.bu9yfftdonkg) - section. - -## Generate code coverage reports locally - -* Code coverage is a means of measuring fuzzer performance. We want to make - sure that our fuzzer covers all the paths in our code and make any tweeks to - fuzzer logic so we can maximize coverage to get better results. - - Coverage reports for git on borg project is not automated and needs to be - generated manually. Future plan is to build a dashboard for git on borg - coverage reports. - -### Generate code coverage reports using script from Google cloud build -* A docker image with script to generate code coverage reports for oemcrypto - fuzz scripts is linked with a GCB trigger - `oemcrypto-fuzzing-code-coverage-git-trigger`. More information about clang - source based coverage can be found - [here](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html). - -* This trigger when invoked compiles oemcrypto fuzz scripts with clang source - based code coverage enabled, downloads latest corpus from cluster fuzz - for the respective fuzzer, generates and uploads code coverage html reports - to [GCS](https://pantheon.corp.google.com/storage/browser/oemcrypto_fuzzing_code_coverage_reports;tab=objects?forceOnBucketsSortingFiltering=false&project=google.com:blockbuster-1154&prefix=). - -* The trigger can be invoked manually using cloud scheduler - `oemcrypto_fuzzing_code_coverage_reports`. - -* In order to generate latest code coverage reports from master branch, - go to pantheon->cloud scheduler->oemcrypto_fuzzing_code_coverage_reports and - click on `RUN NOW` button. - -* The above step should invoke a google cloud build. Go to cloud build console - and find latest build job with Trigger Name - `oemcrypto-fuzzing-code-coverage-git-trigger`. - -* Once the build job is successful, latest code coverage reports can be - downloaded from [GCS](https://pantheon.corp.google.com/storage/browser/oemcrypto_fuzzing_code_coverage_reports;tab=objects?forceOnBucketsSortingFiltering=false&project=google.com:blockbuster-1154&prefix=). - The coverage report folder uploaded to GCS is appended with timestamp. +[1]: clusterfuzz_setup.md +[2]: https://wiki.sei.cmu.edu/confluence/display/c/SEI+CERT+C+Coding+Standard +[3]: https://github.com/llvm/llvm-project/blob/main/compiler-rt/include/fuzzer/FuzzedDataProvider.h +[4]: oemcrypto_install_oem_private_key_fuzz.cc +[5]: https://github.com/google/fuzzing/blob/master/docs/good-fuzz-target.md diff --git a/oemcrypto/test/fuzz_tests/clusterfuzz_setup.md b/oemcrypto/test/fuzz_tests/clusterfuzz_setup.md new file mode 100644 index 00000000..4a455a98 --- /dev/null +++ b/oemcrypto/test/fuzz_tests/clusterfuzz_setup.md @@ -0,0 +1,171 @@ +# ClusterFuzz setup + +[ClusterFuzz][1] + +## Objective + +* Run fuzzing on OEMCrypto public APIs on Linux by building open sourced + ClusterFuzz source code in order to find security vulnerabilities. + +* Partners who implement OEMCrypto can follow these instructions to build + ClusterFuzz, the fuzzing framework and run fuzzing using fuzzer scripts + provided by the Widevine team at Google. + +## Glossary + +* Fuzzing - Fuzzing is a methodology where random, interesting, unexpected + inputs are fed to APIs in order to crash those, thereby catching any + security vulnerabilities with the code. + +* Fuzzing engines - [libFuzzer][4], AFL, Honggfuzz, etc. are the actual + fuzzing engines that get the coverage information from API, use that to + generate more interesting inputs which can be passed to fuzzer. + +* Seed corpus - Fuzzing engine trying to generate interesting inputs from an + empty file is not efficient. Seed corpus is the initial input that a fuzzer + can accept and call the API with that. Fuzzing engine can then mutate this + seed corpus to generate more inputs to fuzzer. + +* ClusterFuzz - ClusterFuzz is a scalable fuzzing infrastructure that finds + security and stability issues in software. Google uses ClusterFuzz to fuzz + all Google products. ClusterFuzz provides us with the capability, tools to + upload fuzz binaries and make use of the fuzzing engines to run fuzzing, + find crashes and organizes the information. ClusterFuzz framework is open + sourced, the source code can be downloaded and framework can be built + locally or by using Google Cloud. + +* Fuzzing output - Fuzzing is used to pass random inputs to API in order to + ensure that API is crash resistant. We are not testing functionality via + fuzzing. Fuzz scripts run continuously until they find a crash with the API + under test. + +## Build fuzz scripts + +This section outlines the steps to build fuzz binaries that can be run +continuously using ClusterFuzz. + +> **Note:** All the directories mentioned below are relative to cdm repository +> root directory. + +1. Fuzz scripts for OEMCrypto APIs are provided by the Widevine team at Google + located under `oemcrypto/test/fuzz_tests` directory. + + > **Note:** Prerequisites to run the following step are [here][10]. We also + > need to install Ninja. + +2. Build a static library of your OEMCrypto implementation. + * Compile and link your OEMCrypto implementation source with + `-fsanitize=address,fuzzer` flag as per these [instructions][9] when + building a static library. + + * Run `./oemcrypto/test/fuzz_tests/build_partner_oemcrypto_fuzztests + ` script from cdm repository root + directory. + + * This will generate fuzz binaries under the `out/Default` directory. + + > **Note:** Alternatively, you can use your own build systems, for which you + > will need to define your own build files with the OEMCrypto fuzz source + > files included. You can find the the fuzz source files in + > `oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gyp` and + > `oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi`. + +3. Seed corpus for each fuzz script can be found under + `oemcrypto/test/fuzz_tests/corpus` directory. Some fuzzers are simple and do + not have seed corpus associated with them. + +4. Create a zip file `oemcrypto_fuzzers_yyyymmddhhmmss.zip` with fuzz binaries + and respective seed corpus zip files. Structure of a sample zip file with + fuzzer binaries and seed corpus would look like this: + + ``` + * fuzzerA + * fuzzerA_seed_corpus.zip + * fuzzerB + * fuzzerB_seed_corpus.zip + * fuzzerC (fuzzerC doesn't have seed corpus associated with it) + ``` + +## Build ClusterFuzz + +OEMCrypto implementation can be fuzzed by building ClusterFuzz code, which is +open source, and using it to run fuzzing. Use a Linux VM to build ClusterFuzz. + +> **Note:** You may see some issues with Python modules missing. Please install +> those modules if you see errors. If you have multiple versions of Python on +> the VM, then use `python -m pipenv shell` when you are at [this][3] +> step. + +Follow these [instructions][2] in order to download the ClusterFuzz repository, +build it locally or create a continuous fuzz infrastructure setup using Google +Cloud. + +## Run fuzzers on local ClusterFuzz instance + +If you prefer to run fuzzing on a local machine instead of having a production +setup using Google Cloud, then follow these [instructions][5] to add a job to +the local ClusterFuzz instance. + +> **Note:** Job name should have a fuzzing engine and sanitizer as part of it. A +> libFuzzer and AddressSanitizer job should have libfuzzer_asan in the job name. + +* Create a job e:g:`libfuzzer_asan_oemcrypto` and upload previously created + `oemcrypto_fuzzers_yyyymmddhhmmss.zip` as a custom build. Future uploads of + zip file should have a name greater than current name. Following the above + naming standard will ensure zip file names are always in ascending order. + +* Once the job is added and ClusterFuzz bot is running, fuzzing should be up + and running. Results can be monitored as mentioned [here][6]. + +* On a local ClusterFuzz instance, only one fuzzer is being fuzzed at a time. + +> **Note:** Fuzzing is time consuming. Finding issues as well as ClusterFuzz +> regressing and fixing the issues can take time. We need fuzzing to run at +> least for a couple of weeks to have good coverage. + +## Find fuzz crashes + +* Once the ClusterFuzz finds an issue, it logs crash information such as the + build, test case and stack trace for the crash. + +* Test cases tab should show the fuzz crash and test case that caused the + crash. Run `./fuzz_binary ` in order to debug the crash locally. + +More information about different types of logs is below: + +* [Bot logs][7] will show information related to fuzzing, number of crashes + that a particular fuzzer finds, number of new crashes, number of known + crashes etc. + +* [Local GCS][8] in your ClusterFuzz checkout folder will store the fuzz + binaries that are being fuzzed, seed corpus etc. + +* `local_gcs/test-fuzz-logs-bucket` will store information related to fuzz + crashes if any were found by the fuzzing engine. It will store crash + information categorized by fuzzer and by each day. It will also store test + case that caused the crash. + +* `/path/to/my-bot/clusterfuzz/log.txt` will have any log information from + fuzzer script and OEMCrypto implementation. + +## Fix issues + +1. Once you are able to debug using the crash test case, apply fix to the + implementation, create `oemcrypto_fuzzers_yyyymmddhhmmss.zip` with latest + fuzz binaries. + +2. Upload the latest fuzz binary to the fuzz job that was created earlier. + Fuzzer will recognize the fix and mark the crash as fixed in test cases tab + once the regression finishes. You do not need to update crashes as fixed, + ClusterFuzz will do that. + +[1]: https://google.github.io/clusterfuzz/ +[2]: https://google.github.io/clusterfuzz/getting-started/ +[3]: https://google.github.io/clusterfuzz/getting-started/prerequisites/#loading-pipenv +[4]: https://llvm.org/docs/LibFuzzer.html +[5]: https://google.github.io/clusterfuzz/setting-up-fuzzing/libfuzzer-and-afl/ +[6]: https://google.github.io/clusterfuzz/setting-up-fuzzing/libfuzzer-and-afl/#checking-results +[7]: https://google.github.io/clusterfuzz/getting-started/local-instance/#viewing-logs +[8]: https://google.github.io/clusterfuzz/getting-started/local-instance/#local-google-cloud-storage +[9]: https://google.github.io/clusterfuzz/setting-up-fuzzing/libfuzzer-and-afl/#libfuzzer +[10]: https://google.github.io/clusterfuzz/setting-up-fuzzing/libfuzzer-and-afl/#prerequisites diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_decrypt_hash_fuzz.cc b/oemcrypto/test/fuzz_tests/oemcrypto_decrypt_hash_fuzz.cc index e894884d..8bc2ae3f 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_decrypt_hash_fuzz.cc +++ b/oemcrypto/test/fuzz_tests/oemcrypto_decrypt_hash_fuzz.cc @@ -55,15 +55,14 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { uint32_t* const failed_frame_number = fuzzed_data.ConsumeBool() ? &failed_frame_number_data : nullptr; - const std::vector hash = - fuzzed_data.ConsumeRemainingBytes(); + const uint32_t crc32 = fuzzed_data.ConsumeIntegral(); license_api_fuzz.LoadLicense(); std::vector key_handle; wvoec::GetKeyHandleIntoVector(session_id, content_key_id.data(), content_key_id.size(), OEMCrypto_CipherMode_CENC, key_handle); - OEMCrypto_SetDecryptHash(session_id, frame_number, hash.data(), hash.size()); + OEMCrypto_SetDecryptHash(session_id, frame_number, crc32); OEMCrypto_DecryptCENC(key_handle.data(), key_handle.size(), &sample, 1, &pattern); OEMCrypto_GetHashErrorCode(session_id, failed_frame_number); diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi b/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi index d4db9501..187d5031 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi @@ -63,7 +63,7 @@ '-D_POSIX_C_SOURCE=200809L', ], 'cflags_cc': [ - '-std=c++14', + '-std=c++17', ], 'ldflags': [ '-fPIC', diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_opk_fuzztests.gypi b/oemcrypto/test/fuzz_tests/oemcrypto_opk_fuzztests.gypi index 4e7efc74..a2470137 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_opk_fuzztests.gypi +++ b/oemcrypto/test/fuzz_tests/oemcrypto_opk_fuzztests.gypi @@ -32,7 +32,7 @@ ], 'cflags_cc' : [ '-frtti', - '-std=c++14', + '-std=c++17', ], 'ldflags': [ '-fPIC', diff --git a/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi b/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi index 0a9e92f1..7252e2f5 100644 --- a/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi +++ b/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi @@ -69,7 +69,7 @@ '-D_POSIX_C_SOURCE=200809L', ], 'cflags_cc': [ - '-std=c++14', + '-std=c++17', '-frtti', ], 'ldflags': [ diff --git a/oemcrypto/test/install_keybox_tool.cpp b/oemcrypto/test/install_keybox_tool.cpp new file mode 100644 index 00000000..1d521ecf --- /dev/null +++ b/oemcrypto/test/install_keybox_tool.cpp @@ -0,0 +1,233 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "string_conversions.h" + +namespace { +// Size of a valid keybox. +constexpr size_t kKeyboxSize = 128; +// Size of a valid keybox token section. +constexpr size_t kKeyDataSize = 72; +// Offset of the system ID in key data. +constexpr size_t kSystemIdOffset = 4; +// Offset of the keybox version in key data. +constexpr size_t kVersionOffset = 0; + +// == Utils == + +bool IsRegularFile(const std::string& path) { + struct stat st; + if (stat(path.c_str(), &st) != 0) { + if (errno == ENOENT || errno == ENOTDIR) { + std::cerr << "File does not exist: path = " << path << std::endl; + return false; + } + std::cerr << "Failed to call stat: path = " << path; + std::cerr << ", errno = " << errno << std::endl; + return false; + } + if (!S_ISREG(st.st_mode)) { + std::cerr << "Not a regular file: path = " << path << ", mode = "; + std::cerr << std::setfill('0') << std::setw(7) << std::oct << st.st_mode; + std::cerr << std::endl; + return false; + } + return true; +} + +void PrintDeviceId(const uint8_t* device_id_u8, size_t device_id_length) { + std::string device_id(reinterpret_cast(device_id_u8), + device_id_length); + // Trim null bytes. + while (!device_id.empty() && device_id.back() == '\0') device_id.pop_back(); + // Check if empty. + if (device_id.empty()) { + std::cerr << "Device ID was all null bytes: "; + std::cerr << "length = " << device_id_length << std::endl; + std::cout << "device_id = " << std::endl; + return; + } + // Check if printable. + if (!std::all_of(device_id.begin(), device_id.end(), ::isprint)) { + device_id = wvutil::b2a_hex(device_id); + } + std::cout << "device_id = " << device_id << std::endl; +} + +void PrintSystemId(const uint8_t* key_data) { + // Assumes that |key_data| length as already been verified. + const uint32_t* system_id_ptr = + reinterpret_cast(&key_data[kSystemIdOffset]); + const uint32_t system_id = ntohl(*system_id_ptr); + std::cout << "system_id = " << system_id << std::endl; + std::cout << "hex(system_id) = 0x"; + std::cout << std::setfill('0') << std::setw(8) << std::hex << system_id; + std::cout << std::endl; +} + +void PrintKeyboxVersion(const uint8_t* key_data) { + // Assumes that |key_data| length as already been verified. + const uint32_t* version_ptr = + reinterpret_cast(&key_data[kVersionOffset]); + const uint32_t version = ntohl(*version_ptr); + std::cout << "version = " << version << std::endl; +} + +// == Primary == + +bool RetrieveKeybox(const std::string& path, std::vector* keybox) { + using StreamIter = std::istreambuf_iterator; + using PosType = std::iostream::pos_type; + std::ifstream fin(path, std::ios::in | std::ios::binary); + if (!fin) { + std::cerr << "Failed to open input file: path = " << path << std::endl; + return false; + } + + // Verify size. + fin.seekg(0, std::ios::end); + if (!fin) { + std::cerr << "Failed to seek to end of file: path = " << path << std::endl; + return false; + } + const PosType keybox_size_pt = fin.tellg(); + if (keybox_size_pt == PosType(-1)) { + std::cerr << "Failed to obtain the file size: path = " << path << std::endl; + return false; + } + const size_t keybox_size = static_cast(keybox_size_pt); + fin.seekg(0, std::ios::beg); + if (!fin) { + std::cerr << "Failed to seek to beginning of file: path = "; + std::cerr << path << std::endl; + return false; + } + if (keybox_size != kKeyboxSize) { + std::cerr << "Unexpected keybox size: "; + std::cerr << "size = " << keybox_size; + std::cerr << ", expected = " << kKeyboxSize; + std::cerr << ", path = " << path << std::endl; + return false; + } + + // Read keybox data. + keybox->clear(); + keybox->reserve(kKeyboxSize); + keybox->assign(StreamIter(fin), StreamIter()); + + if (keybox->size() != kKeyboxSize) { + std::cerr << "Failed to read entire keybox: "; + std::cerr << "read = " << keybox->size(); + std::cerr << ", expected = " << kKeyboxSize; + std::cerr << ", path = " << path << std::endl; + keybox->clear(); + return false; + } + return true; +} + +bool InstallKeyboxAndPrintInfo(const std::vector& keybox) { + std::cout << "Install keybox: " << wvutil::b2a_hex(keybox) << std::endl; + + // Step 1: Initialize. + OEMCryptoResult result = OEMCrypto_Initialize(); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to initialize: result = " << result << std::endl; + return false; + } + std::cout << "OEMCrypto initialized" << std::endl; + + // Step 2: Install keybox. + const OEMCrypto_ProvisioningMethod method = OEMCrypto_GetProvisioningMethod(); + if (method != OEMCrypto_Keybox) { + std::cerr << "OEMCrypto is not keybox type: method = "; + std::cerr << method << std::endl; + OEMCrypto_Terminate(); + return false; + } + result = OEMCrypto_InstallKeyboxOrOEMCert(keybox.data(), keybox.size()); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to install keybox: result = " << result << std::endl; + OEMCrypto_Terminate(); + return false; + } + std::cout << "OEMCrypto keybox installed" << std::endl; + + // Step 3: Verify device ID. + uint8_t buffer[128] = {}; + memset(buffer, 0, sizeof(buffer)); + size_t buffer_length = sizeof(buffer); + result = OEMCrypto_GetDeviceID(buffer, &buffer_length); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to get device ID: result = " << result << std::endl; + OEMCrypto_Terminate(); + return false; + } + PrintDeviceId(buffer, buffer_length); + + // Step 4: Verify system ID and keybox version. + memset(buffer, 0, sizeof(buffer)); + buffer_length = sizeof(buffer); + + result = OEMCrypto_GetKeyData(buffer, &buffer_length); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to get key data: result = " << result << std::endl; + OEMCrypto_Terminate(); + return false; + } + if (buffer_length != kKeyDataSize) { + std::cerr << "Unexpected key data size: "; + std::cerr << "size = " << buffer_length; + std::cerr << ", expected = " << kKeyDataSize << std::endl; + OEMCrypto_Terminate(); + return false; + } + PrintSystemId(buffer); + PrintKeyboxVersion(buffer); + + // Step 5: Cleanup. + result = OEMCrypto_Terminate(); + if (result != OEMCrypto_SUCCESS) { + std::cerr << "Failed to terminate: result = " << result << std::endl; + return false; + } + std::cout << "OEMCrypto terminated" << std::endl; + return true; +} +} // namespace + +int main(int argc, char** argv) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " " << std::endl; + return 1; + } + const std::string keybox_path = argv[1]; + if (!IsRegularFile(keybox_path)) { + std::cerr << "Bad keybox path: " << keybox_path << std::endl; + return 1; + } + std::vector keybox; + if (!RetrieveKeybox(keybox_path, &keybox)) { + std::cerr << "Failed to retrieve keybox" << std::endl; + return 1; + } + if (!InstallKeyboxAndPrintInfo(keybox)) { + std::cerr << "Failed to install keybox" << std::endl; + return 1; + } + return 0; +} diff --git a/oemcrypto/test/oec_device_features.cpp b/oemcrypto/test/oec_device_features.cpp index 77538c7f..0bd759f6 100644 --- a/oemcrypto/test/oec_device_features.cpp +++ b/oemcrypto/test/oec_device_features.cpp @@ -145,28 +145,6 @@ void DeviceFeatures::Initialize() { initialized_ = true; } -std::string DeviceFeatures::RestrictFilter(const std::string& initial_filter) { - std::string filter = initial_filter; - // clang-format off - if (api_version < 17) FilterOut(&filter, "*API17*"); - if (api_version < 18) FilterOut(&filter, "*API18*"); - // clang-format on - // Some tests may require root access. If user is not root, filter these tests - // out. - if (!wvutil::TestSleep::CanChangeSystemTime()) { - printf("Filtering out TimeRollbackPrevention.\n"); - FilterOut(&filter, "*TimeRollbackPrevention*"); - } else { - printf("Can change time. I will run TimeRollbackPrevention.\n"); - } - // Performance tests take a long time. Filter them out if they are not - // specifically requested. - if (filter.find("Performance") == std::string::npos) { - FilterOut(&filter, "*Performance*"); - } - return filter; -} - void DeviceFeatures::PickDerivedKey() { switch (provisioning_method) { case OEMCrypto_OEMCertificate: diff --git a/oemcrypto/test/oec_device_features.h b/oemcrypto/test/oec_device_features.h index ec0a8fc1..78d1cf69 100644 --- a/oemcrypto/test/oec_device_features.h +++ b/oemcrypto/test/oec_device_features.h @@ -10,7 +10,7 @@ namespace wvoec { // These tests are designed to work for this version: -constexpr unsigned int kCurrentAPI = 18; +constexpr unsigned int kCurrentAPI = 19; // The API version when Core Messages were introduced. constexpr unsigned int kCoreMessagesAPI = 16; // The API version when we stopped encrypting key control blocks. @@ -61,11 +61,6 @@ class DeviceFeatures { void set_cast_receiver(bool is_cast_receiver) { cast_receiver = is_cast_receiver; } - // Generate a GTest filter of tests that should not be run. This should be - // called after Initialize. Tests are filtered out based on which features - // are not supported. For example, a device that uses Provisioning 3.0 will - // have all keybox tests filtered out. - std::string RestrictFilter(const std::string& initial_filter); // Get a list of output types that should be tested. const std::vector& GetOutputTypes(); diff --git a/oemcrypto/test/oec_key_deriver.cpp b/oemcrypto/test/oec_key_deriver.cpp index 21d50781..fcca9449 100644 --- a/oemcrypto/test/oec_key_deriver.cpp +++ b/oemcrypto/test/oec_key_deriver.cpp @@ -37,6 +37,23 @@ using namespace std; namespace wvoec { +namespace { + +std::vector CreateContext(const char* prefix, + const std::vector& context, + uint32_t suffix) { + std::vector ret; + // +1 to include the null-terminator + ret.insert(ret.end(), prefix, prefix + strlen(prefix) + 1); + ret.insert(ret.end(), context.begin(), context.end()); + const uint32_t suffix_net = htonl(suffix); + auto* ptr = reinterpret_cast(&suffix_net); + ret.insert(ret.end(), ptr, ptr + sizeof(suffix_net)); + return ret; +} + +} // namespace + void Encryptor::set_enc_key(const std::vector& enc_key) { enc_key_ = enc_key; } @@ -119,8 +136,21 @@ void KeyDeriver::DeriveKey(const uint8_t* key, size_t master_key_size, // this function, then there is something wrong with the test program and its // dependency on BoringSSL. void KeyDeriver::DeriveKeys(const uint8_t* master_key, size_t master_key_size, - const vector& mac_key_context, - const vector& enc_key_context) { + const vector& context) { + // TODO: Use ODK constants instead + DeriveKeys(master_key, master_key_size, context, "AUTHENTICATION", + "ENCRYPTION"); +} + +void KeyDeriver::DeriveKeys(const uint8_t* master_key, size_t master_key_size, + const vector& context, + const char* mac_label, const char* enc_label) { + // TODO: Use ODK constants instead + const std::vector mac_key_context = + CreateContext(mac_label, context, 0x200); + const std::vector enc_key_context = + CreateContext(enc_label, context, 0x80); + // Generate derived key for mac key std::vector mac_key_part2; DeriveKey(master_key, master_key_size, mac_key_context, 1, &mac_key_server_); diff --git a/oemcrypto/test/oec_key_deriver.h b/oemcrypto/test/oec_key_deriver.h index 57d8fc1d..9da7e635 100644 --- a/oemcrypto/test/oec_key_deriver.h +++ b/oemcrypto/test/oec_key_deriver.h @@ -73,8 +73,10 @@ class KeyDeriver : public Encryptor { // Generate mac and enc keys give the master key. void DeriveKeys(const uint8_t* master_key, size_t master_key_size, - const std::vector& mac_key_context, - const std::vector& enc_key_context); + const std::vector& context); + void DeriveKeys(const uint8_t* master_key, size_t master_key_size, + const std::vector& context, const char* mac_label, + const char* enc_label); // Sign the buffer with server's mac key. void ServerSignBuffer(const uint8_t* data, size_t data_length, std::vector* signature) const; diff --git a/oemcrypto/test/oec_session_util.cpp b/oemcrypto/test/oec_session_util.cpp index 783caa1d..519ae373 100644 --- a/oemcrypto/test/oec_session_util.cpp +++ b/oemcrypto/test/oec_session_util.cpp @@ -218,9 +218,12 @@ class boringssl_ptr { Test_PST_Report::Test_PST_Report(const std::string& pst_in, OEMCrypto_Usage_Entry_Status status_in) - : status(status_in), pst(pst_in) { - time_created = wvutil::Clock().GetCurrentTime(); -} + : status(status_in), + seconds_since_license_received(0), + seconds_since_first_decrypt(0), + seconds_since_last_decrypt(0), + pst(pst_in), + time_created(wvutil::Clock().GetCurrentTime()) {} template @@ -231,7 +234,8 @@ RoundTrip:: // verified by the server. This simulates that. size_t gen_signature_length = 0; size_t core_message_length = 0; - constexpr size_t small_size = 42; // arbitrary. + const vector context = session()->GetDefaultContext(); + const size_t small_size = context.size(); // arbitrary. if (RequestHasNonce()) { session()->GenerateNonce(); } @@ -249,7 +253,10 @@ RoundTrip:: size_t message_size = std::max(required_message_size_, core_message_length + small_size); vector data(message_size); - for (size_t i = 0; i < data.size(); i++) data[i] = i & 0xFF; + memcpy(&data[core_message_length], context.data(), context.size()); + for (size_t i = context.size() + core_message_length; i < data.size(); i++) { + data[i] = i & 0xFF; + } if (ShouldGenerateCorpus()) { WriteRequestApiCorpus(gen_signature_length, core_message_length, data); @@ -345,29 +352,37 @@ void ProvisioningRoundTrip::PrepareSession( const wvoec::WidevineKeybox& keybox) { ASSERT_NO_FATAL_FAILURE(session_->open()); if (global_features.provisioning_method == OEMCrypto_Keybox) { - session_->GenerateDerivedKeysFromKeybox(keybox); - encryptor_ = session_->key_deriver(); + keybox_ = &keybox; } else if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { // TODO(chelu): change this to CSR provisioning. session_->LoadOEMCert(true); - session_->GenerateRsaSessionKey(&message_key_, &encrypted_message_key_); - encryptor_.set_enc_key(message_key_); + session_->GenerateRsaSessionKey(); + encryptor_.set_enc_key(session_->session_key()); } else { EXPECT_EQ(global_features.provisioning_method, OEMCrypto_OEMCertificate); session_->LoadOEMCert(true); - session_->GenerateRsaSessionKey(&message_key_, &encrypted_message_key_); - encryptor_.set_enc_key(message_key_); + session_->GenerateRsaSessionKey(); + encryptor_.set_enc_key(session_->session_key()); } } void ProvisioningRoundTrip::VerifyRequestSignature( const vector& data, const vector& generated_signature, - size_t /* core_message_length */) { - if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { + size_t core_message_length) { + if (keybox_ == nullptr) { session()->VerifyRsaSignature(data, generated_signature.data(), generated_signature.size(), kSign_RSASSA_PSS); } else { + // Setup the derived keys using the proto message (ignoring the core + // message). + ASSERT_LE(core_message_length, data.size()); + const std::vector base_message(data.begin() + core_message_length, + data.end()); + session()->GenerateDerivedKeysFromKeybox(*keybox_, base_message); + encryptor_ = session()->key_deriver(); + request_ = base_message; + EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size()); std::vector expected_signature; @@ -400,11 +415,11 @@ void ProvisioningRoundTrip::CreateDefaultResponse() { response_data_.rsa_key_length = encoded_rsa_key_.size(); } response_data_.nonce = session_->nonce(); - if (encrypted_message_key_.size() > 0) { - ASSERT_LE(encrypted_message_key_.size(), kMaxTestRSAKeyLength); - memcpy(response_data_.enc_message_key, encrypted_message_key_.data(), - encrypted_message_key_.size()); - response_data_.enc_message_key_length = encrypted_message_key_.size(); + if (session_->enc_session_key().size() > 0) { + ASSERT_LE(session_->enc_session_key().size(), kMaxTestRSAKeyLength); + memcpy(response_data_.enc_message_key, session_->enc_session_key().data(), + session_->enc_session_key().size()); + response_data_.enc_message_key_length = session_->enc_session_key().size(); } else { response_data_.enc_message_key_length = 0; } @@ -460,9 +475,6 @@ void ProvisioningRoundTrip::SignResponse() { memcpy(encrypted_response_.data() + serialized_core_message_.size(), reinterpret_cast(&encrypted_response_data_), sizeof(encrypted_response_data_)); - if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { - session()->GenerateDerivedKeysFromSessionKey(); - } session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), encrypted_response_.size(), &response_signature_); @@ -528,11 +540,24 @@ OEMCryptoResult ProvisioningRoundTrip::LoadResponseNoRetry( Session* session, size_t* wrapped_key_length) { EXPECT_NE(session, nullptr); VerifyEncryptAndSignResponseLengths(); - return OEMCrypto_LoadProvisioning( - session->session_id(), encrypted_response_.data(), - encrypted_response_.size(), serialized_core_message_.size(), - response_signature_.data(), response_signature_.size(), - wrapped_rsa_key_.data(), wrapped_key_length); + if (allowed_schemes_ == kSign_RSASSA_PSS) { + return OEMCrypto_LoadProvisioning( + session->session_id(), request_.data(), request_.size(), + encrypted_response_.data(), encrypted_response_.size(), + serialized_core_message_.size(), response_signature_.data(), + response_signature_.size(), wrapped_rsa_key_.data(), + wrapped_key_length); + } else { + // TODO(b/316053127): Clean this up a lot. + const uint8_t* derivation_key = nullptr; + const size_t derivation_key_length = 0; + return OEMCrypto_LoadProvisioningCast( + session->session_id(), derivation_key, derivation_key_length, + request_.data(), request_.size(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size(), + wrapped_rsa_key_.data(), wrapped_key_length); + } } void ProvisioningRoundTrip::VerifyLoadFailed() { @@ -560,12 +585,12 @@ void Provisioning40RoundTrip::PrepareSession(bool is_oem_key) { public_key.resize(public_key_size); if (is_oem_key) { - wrapped_oem_key_ = wrapped_private_key; - oem_public_key_ = public_key; + wrapped_oem_key_ = std::move(wrapped_private_key); + oem_public_key_ = std::move(public_key); oem_key_type_ = key_type; } else { - wrapped_drm_key_ = wrapped_private_key; - drm_public_key_ = public_key; + wrapped_drm_key_ = std::move(wrapped_private_key); + drm_public_key_ = std::move(public_key); drm_key_type_ = key_type; } } @@ -621,8 +646,8 @@ void Provisioning40CastRoundTrip::PrepareSession() { wrapped_private_key.resize(wrapped_private_key_size); public_key.resize(public_key_size); - wrapped_drm_key_ = wrapped_private_key; - drm_public_key_ = public_key; + wrapped_drm_key_ = std::move(wrapped_private_key); + drm_public_key_ = std::move(public_key); drm_key_type_ = key_type; } @@ -751,11 +776,13 @@ OEMCryptoResult Provisioning40CastRoundTrip::LoadResponseNoRetry( Session* session, size_t* wrapped_key_length) { EXPECT_NE(session, nullptr); VerifyEncryptAndSignResponseLengths(); - return OEMCrypto_LoadProvisioning( - session->session_id(), encrypted_response_.data(), - encrypted_response_.size(), serialized_core_message_.size(), - response_signature_.data(), response_signature_.size(), - wrapped_rsa_key_.data(), wrapped_key_length); + const std::vector context = session->GetDefaultContext(); + return OEMCrypto_LoadProvisioningCast( + session->session_id(), session->enc_session_key().data(), + session->enc_session_key().size(), context.data(), context.size(), + encrypted_response_.data(), encrypted_response_.size(), + serialized_core_message_.size(), response_signature_.data(), + response_signature_.size(), wrapped_rsa_key_.data(), wrapped_key_length); } void LicenseRoundTrip::VerifyRequestSignature( @@ -768,17 +795,18 @@ void LicenseRoundTrip::VerifyRequestSignature( if (api_version_ > global_features.api_version) api_version_ = global_features.api_version; + vector sign_source; if (global_features.api_version < 17) { - const std::vector subdata(data.begin() + core_message_length, - data.end()); - session()->VerifyRsaSignature(subdata, generated_signature.data(), - generated_signature.size(), kSign_RSASSA_PSS); - SHA256(data.data(), core_message_length, request_hash_); + sign_source.assign(data.begin() + core_message_length, data.end()); + } else if (global_features.api_version < 19) { + sign_source = data; } else { - session()->VerifySignature(data, generated_signature.data(), - generated_signature.size(), kSign_RSASSA_PSS); - SHA256(data.data(), core_message_length, request_hash_); + sign_source.resize(SHA512_DIGEST_LENGTH); + SHA512(data.data(), data.size(), sign_source.data()); } + session()->VerifySignature(sign_source, generated_signature.data(), + generated_signature.size(), kSign_RSASSA_PSS); + SHA256(data.data(), core_message_length, request_hash_); } void LicenseRoundTrip::FillAndVerifyCoreRequest( @@ -984,7 +1012,8 @@ void LicenseRoundTrip::FillCoreResponseSubstrings() { } void LicenseRoundTrip::EncryptResponse(bool force_clear_kcb) { - ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey()); + const auto context = session_->GetDefaultContext(!skip_request_hash_); + ASSERT_NO_FATAL_FAILURE(session_->GenerateDerivedKeysFromSessionKey(context)); encrypted_response_data_ = response_data_; uint8_t iv_buffer[KEY_IV_SIZE]; memcpy(iv_buffer, &response_data_.mac_key_iv[0], KEY_IV_SIZE); @@ -1115,6 +1144,9 @@ OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session, core_response_.key_array_length * sizeof(*core_response_.key_array)); } + const vector context = + session->GetDefaultContext(!skip_request_hash_); + // Some tests adjust the offset to be beyond the length of the message. Here, // we create a duplicate of the main message buffer so that these offsets do // not point to garbage data. The goal is to make sure OEMCrypto is verifying @@ -1131,7 +1163,9 @@ OEMCryptoResult LicenseRoundTrip::LoadResponse(Session* session, reinterpret_cast(&encrypted_response_data_) + sizeof(encrypted_response_data_)); OEMCryptoResult result = OEMCrypto_LoadLicense( - session->session_id(), double_message.data(), encrypted_response_.size(), + session->session_id(), context.data(), context.size(), + session->enc_session_key().data(), session->enc_session_key().size(), + double_message.data(), encrypted_response_.size(), serialized_core_message_.size(), response_signature_.data(), response_signature_.size()); if (verify_keys && result == OEMCrypto_SUCCESS) { @@ -1495,27 +1529,9 @@ void RenewalRoundTrip::FillAndVerifyCoreRequest( } } -void RenewalRoundTrip::CreateDefaultResponse() { - if (is_release_) { - uint32_t control = 0; - uint32_t nonce = 0; - // A single key object with no key id should update all keys. - constexpr size_t index = 0; - response_data_.keys[index].key_id_length = 0; - response_data_.keys[index].key_id[0] = '\0'; - const uint32_t renewal_api = - std::max(core_request_.api_major_version, 15u); - std::string kcVersion = "kc" + std::to_string(renewal_api); - memcpy(response_data_.keys[index].control.verification, kcVersion.c_str(), - 4); - const uint32_t duration = static_cast( - license_messages_->core_response() - .timer_limits.initial_renewal_duration_seconds); - response_data_.keys[index].control.duration = htonl(duration); - response_data_.keys[index].control.nonce = htonl(nonce); - response_data_.keys[index].control.control_bits = htonl(control); - } -} +// Nothing is needed for this function but it needs a definition since it's +// declared as a virtual function in the RoundTrip class. +void RenewalRoundTrip::CreateDefaultResponse() {} void RenewalRoundTrip::EncryptAndSignResponse() { // Renewal messages are not encrypted. @@ -1593,7 +1609,7 @@ OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) { GetFileName("oemcrypto_load_renewal_fuzz_seed_corpus"); // Corpus for renewal response fuzzer should be in the format: // OEMCrypto_Renewal_Response_Fuzz + license_renewal_response. - OEMCrypto_Renewal_Response_Fuzz renewal_response_fuzz; + OEMCrypto_Renewal_Response_Fuzz renewal_response_fuzz = {}; renewal_response_fuzz.core_request = core_request_; renewal_response_fuzz.renewal_duration_seconds = renewal_duration_seconds_; AppendToFile(file_name, @@ -1610,6 +1626,81 @@ OEMCryptoResult RenewalRoundTrip::LoadResponse(Session* session) { response_signature_.data(), response_signature_.size()); } +void ReleaseRoundTrip::VerifyRequestSignature( + const vector& data, const vector& generated_signature, + size_t core_message_length) { + (void)core_message_length; + ASSERT_EQ(HMAC_SHA256_SIGNATURE_SIZE, generated_signature.size()); + std::vector expected_signature; + session()->key_deriver().ClientSignBuffer(data, &expected_signature); + ASSERT_EQ(expected_signature, generated_signature); +} + +void ReleaseRoundTrip::FillAndVerifyCoreRequest( + const std::string& core_message_string) { + EXPECT_TRUE( + oemcrypto_core_message::deserialize::CoreReleaseRequestFromMessage( + core_message_string, &core_request_)); + EXPECT_EQ(license_messages_->api_version(), core_request_.api_major_version); + EXPECT_EQ(license_messages_->core_request().nonce, core_request_.nonce); + EXPECT_EQ(license_messages_->core_request().session_id, + core_request_.session_id); +} + +// Nothing is needed for this function but it needs a definition since it's +// declared as a virtual function in the RoundTrip class. +void ReleaseRoundTrip::CreateDefaultResponse() {} + +void ReleaseRoundTrip::EncryptAndSignResponse() { + // Release messages are not encrypted. + encrypted_response_data_ = response_data_; + // Create a core response for a call to LoadRelease. + // TODO(b/191724203): Test release server has different version from license + // server. + ASSERT_NE(license_messages_, nullptr); + CoreMessageFeatures features = + CoreMessageFeatures::DefaultFeatures(license_messages_->api_version()); + ASSERT_TRUE(oemcrypto_core_message::serialize::CreateCoreReleaseResponse( + features, core_request_, seconds_since_license_received_, + seconds_since_first_decrypt_, &serialized_core_message_)); + // Resize serialize core message to be just big enough or required core + // message size, whichever is larger. + serialized_core_message_.resize( + std::max(required_core_message_size_, serialized_core_message_.size())); + // Make the message buffer a just big enough, or the + // required size, whichever is larger. + const size_t message_size = + std::max(required_message_size_, serialized_core_message_.size() + + sizeof(encrypted_response_data_)); + // Stripe the encrypted message. + encrypted_response_.resize(message_size); + for (size_t i = 0; i < encrypted_response_.size(); i++) { + encrypted_response_[i] = i % 0x100; + } + // Concatenate the core message and the response. + ASSERT_GE(encrypted_response_.size(), serialized_core_message_.size()); + memcpy(encrypted_response_.data(), serialized_core_message_.data(), + serialized_core_message_.size()); + ASSERT_GE(encrypted_response_.size(), + serialized_core_message_.size() + sizeof(encrypted_response_data_)); + memcpy(encrypted_response_.data() + serialized_core_message_.size(), + reinterpret_cast(&encrypted_response_data_), + sizeof(encrypted_response_data_)); + session()->key_deriver().ServerSignBuffer(encrypted_response_.data(), + encrypted_response_.size(), + &response_signature_); + SetEncryptAndSignResponseLengths(); +} + +OEMCryptoResult ReleaseRoundTrip::LoadResponse(Session* session) { + // TODO(vickymin): Write corpus for oemcrypto_load_release_fuzz. + VerifyEncryptAndSignResponseLengths(); + return OEMCrypto_LoadRelease( + session->session_id(), encrypted_response_.data(), + encrypted_response_.size(), serialized_core_message_.size(), + response_signature_.data(), response_signature_.size()); +} + std::unordered_map, std::hash> Session::server_ephemeral_keys_; @@ -1659,63 +1750,48 @@ void Session::GenerateNonce(int* error_counter) { } } -void Session::FillDefaultContext(vector* mac_context, - vector* enc_context) { - /* Context strings - * These context strings are normally created by the CDM layer +vector Session::GetDefaultContext(bool do_hash) { + /* Context string + * This context string is normally created by the CDM layer * from a license request message. * They are used to test MAC and ENC key generation. */ - *mac_context = wvutil::a2b_hex( - "41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff" - "de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873" - "4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a" - "230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635" - "34333231180120002a0c31383836373837343035000000000200"); - *enc_context = wvutil::a2b_hex( - "454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95" - "c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb" - "e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408" - "0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231" - "180120002a0c31383836373837343035000000000080"); + auto ret = wvutil::a2b_hex( + "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" + "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" + "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" + "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" + "38373430350000"); + if (do_hash) { + uint8_t hash[SHA512_DIGEST_LENGTH]; + SHA512(ret.data(), ret.size(), hash); + ret.assign(hash, hash + sizeof(hash)); + } + return ret; } // This should only be called if the device uses Provisioning 2.0. A failure in // this function is probably caused by a bad keybox. void Session::GenerateDerivedKeysFromKeybox( const wvoec::WidevineKeybox& keybox) { - vector mac_context; - vector enc_context; - FillDefaultContext(&mac_context, &enc_context); - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_GenerateDerivedKeys( - session_id(), mac_context.data(), mac_context.size(), - enc_context.data(), enc_context.size())); + return GenerateDerivedKeysFromKeybox(keybox, GetDefaultContext()); +} + +void Session::GenerateDerivedKeysFromKeybox( + const wvoec::WidevineKeybox& keybox, const std::vector& context) { key_deriver_.DeriveKeys(keybox.device_key_, sizeof(keybox.device_key_), - mac_context, enc_context); + context); } void Session::GenerateDerivedKeysFromSessionKey() { - // Uses test certificate. - vector session_key; - vector enc_session_key; - ASSERT_TRUE(public_rsa_ || public_ec_) - << "No public RSA/ECC key loaded in test code"; - // A failure here probably indicates that there is something wrong with the - // test program and its dependency on BoringSSL. - ASSERT_TRUE(GenerateSessionKey(&session_key, &enc_session_key)); - vector mac_context; - vector enc_context; - FillDefaultContext(&mac_context, &enc_context); - // A failure here is probably caused by having the wrong RSA key loaded. - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DeriveKeysFromSessionKey( - session_id(), enc_session_key.data(), enc_session_key.size(), - mac_context.data(), mac_context.size(), enc_context.data(), - enc_context.size())); + GenerateDerivedKeysFromSessionKey(GetDefaultContext()); +} - key_deriver_.DeriveKeys(session_key.data(), session_key.size(), mac_context, - enc_context); +void Session::GenerateDerivedKeysFromSessionKey( + const std::vector& context) { + // Uses test certificate. + ASSERT_TRUE(GenerateSessionKey()); + key_deriver_.DeriveKeys(session_key_.data(), session_key_.size(), context); } void Session::TestDecryptCTR(bool get_fresh_key_handle_first, @@ -1872,7 +1948,6 @@ void Session::LoadOEMCert(bool verify_cert) { util::RsaPublicKey::FromSslHandle(EVP_PKEY_get0_RSA(pubkey.get())); ASSERT_TRUE(public_rsa_) << "Failed to extract public RSA key from OEM certificate"; - return; } if (verify_cert) { vector buffer(80); @@ -2017,19 +2092,17 @@ void Session::VerifySignature(const vector& message, FAIL() << "No public RSA or ECC key loaded in test code"; } -bool Session::GenerateRsaSessionKey(vector* session_key, - vector* enc_session_key) { +bool Session::GenerateRsaSessionKey() { if (!public_rsa_) { cerr << "No public RSA key loaded in test code\n"; return false; } - *session_key = wvutil::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1"); - *enc_session_key = public_rsa_->EncryptSessionKey(*session_key); - return !enc_session_key->empty(); + session_key_ = wvutil::a2b_hex("6fa479c731d2770b6a61a5d1420bb9d1"); + enc_session_key_ = public_rsa_->EncryptSessionKey(session_key_); + return !enc_session_key_.empty(); } -bool Session::GenerateEccSessionKey(vector* session_key, - vector* ecdh_public_key_data) { +bool Session::GenerateEccSessionKey() { if (!public_ec_) { cerr << "No public ECC key loaded in test code\n"; return false; @@ -2044,24 +2117,23 @@ bool Session::GenerateEccSessionKey(vector* session_key, << util::EccCurveToString(curve) << std::endl; return false; } - *session_key = server_ephemeral_keys_[curve]->DeriveSessionKey(*public_ec_); - if (session_key->empty()) { + session_key_ = server_ephemeral_keys_[curve]->DeriveSessionKey(*public_ec_); + if (session_key_.empty()) { return false; } - *ecdh_public_key_data = server_ephemeral_keys_[curve]->SerializeAsPublicKey(); - if (ecdh_public_key_data->empty()) { - session_key->clear(); + enc_session_key_ = server_ephemeral_keys_[curve]->SerializeAsPublicKey(); + if (enc_session_key_.empty()) { + session_key_.clear(); return false; } return true; } -bool Session::GenerateSessionKey(vector* session_key, - vector* key_material) { +bool Session::GenerateSessionKey() { if (public_rsa_ != nullptr) { - return GenerateRsaSessionKey(session_key, key_material); + return GenerateRsaSessionKey(); } else if (public_ec_ != nullptr) { - return GenerateEccSessionKey(session_key, key_material); + return GenerateEccSessionKey(); } cerr << "No public RSA or ECC key loaded in test code\n"; return false; diff --git a/oemcrypto/test/oec_session_util.h b/oemcrypto/test/oec_session_util.h index ec11dcef..75204d5c 100644 --- a/oemcrypto/test/oec_session_util.h +++ b/oemcrypto/test/oec_session_util.h @@ -276,7 +276,7 @@ class ProvisioningRoundTrip const std::vector& encoded_rsa_key) : RoundTrip(session), allowed_schemes_(kSign_RSASSA_PSS), - encryptor_(), + keybox_(nullptr), encoded_rsa_key_(encoded_rsa_key) {} // Prepare the session for signing the request. virtual void PrepareSession(const wvoec::WidevineKeybox& keybox); @@ -317,9 +317,9 @@ class ProvisioningRoundTrip uint32_t allowed_schemes_; Encryptor encryptor_; + std::vector request_; + const wvoec::WidevineKeybox* keybox_; // The message key used for Prov 3.0. - std::vector message_key_; - std::vector encrypted_message_key_; std::vector encoded_rsa_key_; std::vector wrapped_rsa_key_; }; @@ -337,8 +337,8 @@ class Provisioning40RoundTrip // Not used. Use Load*CertResponse() below to load OEM/DRM response // respectively. - void CreateDefaultResponse() override{}; - void EncryptAndSignResponse() override{}; + void CreateDefaultResponse() override {}; + void EncryptAndSignResponse() override {}; OEMCryptoResult LoadResponse(Session* session) override { (void)session; return OEMCrypto_ERROR_NOT_IMPLEMENTED; @@ -383,8 +383,10 @@ class Provisioning40CastRoundTrip /* ResponseData */ RSAPrivateKeyMessage> { public: Provisioning40CastRoundTrip(Session* session, - const std::vector& encoded_rsa_key) - : RoundTrip(session), encryptor_(), + const std::vector& encoded_rsa_key) + : RoundTrip(session), + allowed_schemes_(kSign_RSASSA_PSS), + encryptor_(), encoded_rsa_key_(encoded_rsa_key) {} void PrepareSession(); @@ -394,7 +396,8 @@ class Provisioning40CastRoundTrip void EncryptAndSignResponse() override; OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } OEMCryptoResult LoadResponse(Session* session) override; - OEMCryptoResult LoadResponseNoRetry(Session* session, size_t* wrapped_key_length) ; + OEMCryptoResult LoadResponseNoRetry(Session* session, + size_t* wrapped_key_length); // Returned const std::vector& wrapped_drm_key() { return wrapped_drm_key_; } @@ -442,6 +445,7 @@ class LicenseRoundTrip update_mac_keys_(true), api_version_(kCurrentAPI), expect_request_has_correct_nonce_(true), + skip_request_hash_(global_features.api_version < 19), license_type_(OEMCrypto_ContentLicense), request_hash_() {} void CreateDefaultResponse() override; @@ -516,6 +520,8 @@ class LicenseRoundTrip } // Skip the nonce check when verifying the license request. void skip_nonce_check() { expect_request_has_correct_nonce_ = false; } + // Skip hashing license request before signing/KDF. + void skip_request_hash() { skip_request_hash_ = true; } // This sets the key id of the specified key to the specified string. // This is used to test with different key id lengths. void SetKeyId(size_t index, const string& key_id); @@ -547,6 +553,9 @@ class LicenseRoundTrip // session. This is usually true, but when we are testing how OEMCrypto // handles a bad nonce, we don't want to. bool expect_request_has_correct_nonce_; + // Whether to skip hashing the request before signing and KDF; this is used + // for license protocol 2.2. + bool skip_request_hash_; // Whether this is a content license or an entitlement license. Used in // CreateDefaultResponse. OEMCrypto_LicenseType license_type_; @@ -601,6 +610,48 @@ class RenewalRoundTrip bool is_release_; // If this is a license release, and not a real renewal. }; +class ReleaseRoundTrip + : public RoundTrip< + /* CoreRequest */ oemcrypto_core_message::ODK_ReleaseRequest, + OEMCrypto_PrepAndSignReleaseRequest, + // Release response info is same as request: + /* CoreResponse */ oemcrypto_core_message::ODK_ReleaseRequest, + /* ResponseData */ MessageData> { + public: + ReleaseRoundTrip(LicenseRoundTrip* license_messages) + : RoundTrip(license_messages->session()), + license_messages_(license_messages) {} + void CreateDefaultResponse() override; + void EncryptAndSignResponse() override; + OEMCryptoResult LoadResponse() override { return LoadResponse(session_); } + OEMCryptoResult LoadResponse(Session* session) override; + int64_t seconds_since_license_received() const { + return seconds_since_license_received_; + } + void set_seconds_since_license_received( + int64_t seconds_since_license_received) { + seconds_since_license_received_ = seconds_since_license_received; + } + int64_t seconds_since_first_decrypt() const { + return seconds_since_first_decrypt_; + } + void set_seconds_since_first_decrypt(int64_t seconds_since_first_decrypt) { + seconds_since_first_decrypt_ = seconds_since_first_decrypt; + } + + protected: + bool RequestHasNonce() override { return false; } + void VerifyRequestSignature(const vector& data, + const vector& generated_signature, + size_t core_message_length) override; + // Verify the values of the core response. + virtual void FillAndVerifyCoreRequest( + const std::string& core_message_string) override; + LicenseRoundTrip* license_messages_; + int64_t seconds_since_license_received_; + int64_t seconds_since_first_decrypt_; +}; + class EntitledMessage { public: EntitledMessage(LicenseRoundTrip* license_messages) @@ -671,15 +722,17 @@ class Session { // and try again if a nonce flood has been detected. If error_counter is // not null, it will be incremented when a nonce flood is detected. void GenerateNonce(int* error_counter = nullptr); - // Fill the vectors with test context which generate known mac and enc keys. - void FillDefaultContext(vector* mac_context, - vector* enc_context); + // Fill the vector with test context which generate known mac and enc keys. + std::vector GetDefaultContext(bool do_hash = false); // Generate known mac and enc keys using OEMCrypto_GenerateDerivedKeys and // also fill out enc_key_, mac_key_server_, and mac_key_client_. void GenerateDerivedKeysFromKeybox(const wvoec::WidevineKeybox& keybox); + void GenerateDerivedKeysFromKeybox(const wvoec::WidevineKeybox& keybox, + const std::vector& context); // Generate known mac and enc keys using OEMCrypto_DeriveKeysFromSessionKey // and also fill out enc_key_, mac_key_server_, and mac_key_client_. void GenerateDerivedKeysFromSessionKey(); + void GenerateDerivedKeysFromSessionKey(const std::vector& context); // Encrypt some data and pass to OEMCrypto_DecryptCENC to verify decryption. void TestDecryptCTR(bool get_fresh_key_handle_first = true, OEMCryptoResult expected_result = OEMCrypto_SUCCESS, @@ -745,17 +798,14 @@ class Session { // Encrypts a known session key with public_rsa_ for use in future calls to // OEMCrypto_DeriveKeysFromSessionKey or OEMCrypto_RewrapDeviceRSAKey30. // The unencrypted session key is stored in session_key. - bool GenerateRsaSessionKey(vector* session_key, - vector* enc_session_key); + bool GenerateRsaSessionKey(); // Derives a session key with public_ec_ and a ephemeral "server" ECC key // for use in future calls to OEMCrypto_DeriveKeysFromSessionKey. // The unencrypted session key is stored in session_key. - bool GenerateEccSessionKey(vector* session_key, - vector* ecdh_public_key_data); + bool GenerateEccSessionKey(); // Based on the key type installed, call GenerateRsaSessionKey or // GenerateEccSessionKey. - bool GenerateSessionKey(vector* session_key, - vector* key_material); + bool GenerateSessionKey(); // Calls OEMCrypto_RewrapDeviceRSAKey30 with the given provisioning response // message. If force is true, we assert that the key loads successfully. @@ -838,6 +888,11 @@ class Session { // functions. vector& key_handle() { return key_handle_; } + const std::vector& session_key() const { return session_key_; } + const std::vector& enc_session_key() const { + return enc_session_key_; + } + const KeyDeriver& key_deriver() const { return key_deriver_; } void set_mac_keys(const uint8_t* mac_keys) { key_deriver_.set_mac_keys(mac_keys); @@ -880,6 +935,8 @@ class Session { vector pst_report_buffer_; MessageData license_ = {}; vector key_handle_; + std::vector session_key_; + std::vector enc_session_key_; vector encrypted_usage_entry_; uint32_t usage_entry_number_ = 0; diff --git a/oemcrypto/test/oemcrypto_basic_test.cpp b/oemcrypto/test/oemcrypto_basic_test.cpp index 015a14bd..0d669262 100644 --- a/oemcrypto/test/oemcrypto_basic_test.cpp +++ b/oemcrypto/test/oemcrypto_basic_test.cpp @@ -120,22 +120,46 @@ const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value) { // Return a printable string from data. If all the characters are printable, // then just use the string. Otherwise, convert to hex. std::string MaybeHex(const uint8_t* data, size_t length) { - for (size_t i = 0; i < length; i++) { + // Check for a early null termination. This is common for the device + // id in a keybox, which is padded with 0s. + const size_t c_len = strnlen(reinterpret_cast(data), length); + // If there is any nonzero after the first zero, then just use hex. + for (size_t i = c_len; i < length; i++) { + if (data[i] != 0) return "0x" + wvutil::HexEncode(data, length); + } + for (size_t i = 0; i < c_len; i++) { if (!isprint(data[i])) return "0x" + wvutil::HexEncode(data, length); } - return std::string(reinterpret_cast(data), length); + return std::string(reinterpret_cast(data), c_len); } + std::string MaybeHex(const std::vector& data) { return MaybeHex(data.data(), data.size()); } +// Get the Device's ID and return it in a printable format. +std::string GetDeviceId() { + OEMCryptoResult sts; + std::vector dev_id(128, 0); + size_t dev_id_len = dev_id.size(); + sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len); + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + if (dev_id_len <= 0) return "NO DEVICE ID"; + dev_id.resize(dev_id_len); + sts = OEMCrypto_GetDeviceID(dev_id.data(), &dev_id_len); + } + if (sts != OEMCrypto_SUCCESS) return "NO DEVICE ID"; + dev_id.resize(dev_id_len); + return MaybeHex(dev_id); +} + /// @addtogroup basic /// @{ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) { Session s; s.open(); - OEMCrypto_DestBufferDesc output_descriptor; + OEMCrypto_DestBufferDesc output_descriptor = {}; int secure_fd = kHugeRandomNumber; ASSERT_NE(OEMCrypto_SUCCESS, OEMCrypto_FreeSecureBuffer(s.session_id(), &output_descriptor, @@ -156,7 +180,7 @@ TEST_F(OEMCryptoClientTest, FreeUnallocatedSecureBufferNoFailure) { */ TEST_F(OEMCryptoClientTest, VersionNumber) { const std::string log_message = - "OEMCrypto unit tests for API 18.5. Tests last updated 2024-03-21"; + "OEMCrypto unit tests for API 19.1. Tests last updated 2024-03-25"; cout << " " << log_message << "\n"; cout << " " << "These tests are part of Android U." @@ -164,8 +188,8 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { 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, 18); - EXPECT_EQ(ODK_MINOR_VERSION, 5); + EXPECT_EQ(ODK_MAJOR_VERSION, 19); + EXPECT_EQ(ODK_MINOR_VERSION, 1); EXPECT_EQ(kCurrentAPI, static_cast(ODK_MAJOR_VERSION)); OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel(); EXPECT_GT(level, OEMCrypto_Level_Unknown); @@ -175,6 +199,9 @@ TEST_F(OEMCryptoClientTest, VersionNumber) { uint32_t minor_version = OEMCrypto_MinorAPIVersion(); cout << " OEMCrypto API version is " << version << "." << minor_version << endl; + cout << " OEMCrypto Device ID is '" << GetDeviceId() << "'" + << endl; + if (OEMCrypto_SupportsUsageTable()) { cout << " OEMCrypto supports usage tables" << endl; } else { @@ -261,6 +288,9 @@ TEST_F(OEMCryptoClientTest, CheckSRMCapabilityV13) { } TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } OEMCryptoResult sts; std::string build_info; sts = OEMCrypto_BuildInformation(&build_info[0], nullptr); @@ -287,6 +317,9 @@ TEST_F(OEMCryptoClientTest, CheckNullBuildInformationAPI17) { } 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); @@ -332,7 +365,7 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { // check for existence in map // check if value matches expectation // remove from map - for (int i = 0; i < jsmn_result; i++) { + 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()) { @@ -345,7 +378,7 @@ TEST_F(OEMCryptoClientTest, CheckJsonBuildInformationAPI18) { // if map is not empty, return false if (expected.size() > 0) { std::string missing; - for (auto e : expected) { + for (const auto& e : expected) { missing.append(e.first); missing.append(" "); } @@ -389,6 +422,9 @@ TEST_F(OEMCryptoClientTest, NormalInitTermination) { } TEST_F(OEMCryptoClientTest, CheckDTCP2CapabilityAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } OEMCryptoResult sts; OEMCrypto_DTCP2_Capability capability; sts = OEMCrypto_GetDTCP2Capability(&capability); diff --git a/oemcrypto/test/oemcrypto_cast_test.cpp b/oemcrypto/test/oemcrypto_cast_test.cpp index 91730253..2a6e0197 100644 --- a/oemcrypto/test/oemcrypto_cast_test.cpp +++ b/oemcrypto/test/oemcrypto_cast_test.cpp @@ -11,6 +11,9 @@ using ::testing::Range; namespace wvoec { +/// @addtogroup cast +/// @{ + /** If a device can load a private key with the alternate padding schemes, it * should support signing with the alternate scheme. */ TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) { @@ -20,22 +23,21 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, TestSignaturePKCS1) { // If the device is a cast receiver, then this scheme is required. if (global_features.cast_receiver) { ASSERT_TRUE(key_loaded_); + // A signature with a valid size should succeed. + TestSignature(kSign_PKCS1_Block1, 83); + TestSignature(kSign_PKCS1_Block1, 50); } // If the key loaded with no error, then we will verify that it is not used - // for forbidden padding schemes. + // for forbidden padding schemes. This should be tested for both devices that + // are cast receivers and devices that are not. if (key_loaded_) { - if (global_features.cast_receiver) { - // A signature with a valid size should succeed. - TestSignature(kSign_PKCS1_Block1, 83); - TestSignature(kSign_PKCS1_Block1, 50); - } // A signature with padding that is too big should fail. DisallowForbiddenPaddingDRMKey(kSign_PKCS1_Block1, 84); // too big. } } /** The alternate padding is only required for cast receivers, but if a device - * does load an alternate certificate, it should NOT be used as a DRM cert + * does load an alternate certificate, it should NOT be used to as a DRM cert * key. */ TEST_F(OEMCryptoLoadsCertificateAlternates, ForbidUseAsDRMCert) { // Try to load an RSA key with alternative padding schemes. This signing @@ -50,7 +52,6 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, ForbidUseAsDRMCert) { if (key_loaded_) { // The other padding scheme should fail. DisallowForbiddenPaddingDRMKey(kSign_RSASSA_PSS, 83); - DisallowDeriveKeys(); } } @@ -81,10 +82,7 @@ TEST_F(OEMCryptoLoadsCertificateAlternates, ForbidPrepAndSign) { OEMCryptoResult result = OEMCrypto_PrepAndSignLicenseRequest( s.session_id(), message.data(), message.size(), &core_message_length, signature.data(), &signature_length); - // TODO: remove OEMCrypto_ERROR_INVALID_RSA_KEY once OEMCrypto v16 is not - // supported anymore. This error code has been deprecated since v17. - ASSERT_TRUE(result == OEMCrypto_ERROR_INVALID_KEY || - result == OEMCrypto_ERROR_INVALID_RSA_KEY); + ASSERT_EQ(OEMCrypto_ERROR_INVALID_KEY, result); const vector zero(signature.size(), 0); ASSERT_EQ(signature, zero); // Signature should not have been computed. } @@ -1010,6 +1008,9 @@ TEST_F(OEMCryptoCastReceiverTest, TestSignaturePKCS1_15_20) { } TEST_P(OEMCryptoSessionTestLoadCasKeysWithHDCP, CasOnlyLoadCasKeysAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } if (!global_features.supports_cas) { GTEST_SKIP() << "OEMCrypto does not support CAS"; } @@ -1018,4 +1019,5 @@ TEST_P(OEMCryptoSessionTestLoadCasKeysWithHDCP, CasOnlyLoadCasKeysAPI17) { } INSTANTIATE_TEST_SUITE_P(TestHDCP, OEMCryptoSessionTestLoadCasKeysWithHDCP, Range(1, 6)); +/// @} } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_cast_test.h b/oemcrypto/test/oemcrypto_cast_test.h index 283df69b..3317fb7b 100644 --- a/oemcrypto/test/oemcrypto_cast_test.h +++ b/oemcrypto/test/oemcrypto_cast_test.h @@ -19,9 +19,6 @@ namespace wvoec { const char* HDCPCapabilityAsString(OEMCrypto_HDCP_Capability value); -std::string MaybeHex(const uint8_t* data, size_t length); -std::string MaybeHex(const std::vector& data); - // This test attempts to use alternate algorithms for loaded device certs. class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { protected: @@ -54,26 +51,6 @@ class OEMCryptoLoadsCertificateAlternates : public OEMCryptoLoadsCertificate { licenseRequest, signature.data(), signature_length, scheme)); } - void DisallowDeriveKeys() { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); - s.GenerateNonce(); - vector session_key; - vector enc_session_key; - ASSERT_NO_FATAL_FAILURE(s.SetRsaPublicKeyFromPrivateKeyInfo( - encoded_rsa_key_.data(), encoded_rsa_key_.size())); - ASSERT_TRUE(s.GenerateRsaSessionKey(&session_key, &enc_session_key)); - vector mac_context; - vector enc_context; - s.FillDefaultContext(&mac_context, &enc_context); - ASSERT_NE(OEMCrypto_SUCCESS, - OEMCrypto_DeriveKeysFromSessionKey( - s.session_id(), enc_session_key.data(), - enc_session_key.size(), mac_context.data(), - mac_context.size(), enc_context.data(), enc_context.size())); - } - // If force is true, we assert that the key loads successfully. void LoadCastCertificateKey(bool force) { if (!wvoec::global_features.cast_receiver) { diff --git a/oemcrypto/test/oemcrypto_decrypt_test.cpp b/oemcrypto/test/oemcrypto_decrypt_test.cpp index a61c8933..6e7a49a9 100644 --- a/oemcrypto/test/oemcrypto_decrypt_test.cpp +++ b/oemcrypto/test/oemcrypto_decrypt_test.cpp @@ -242,12 +242,10 @@ TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) { ASSERT_EQ(OEMCrypto_SUCCESS, license_messages_.LoadResponse()); uint32_t frame_number = 1; - uint32_t hash = 42; + const uint32_t crc32 = 42; // It is OK to set the hash before loading the keys - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_SetDecryptHash(session_.session_id(), frame_number, - reinterpret_cast(&hash), - sizeof(hash))); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_SetDecryptHash(session_.session_id(), + frame_number, crc32)); // It is OK to select the key and decrypt. ASSERT_NO_FATAL_FAILURE(session_.TestDecryptCTR()); // But the error code should be bad. @@ -257,11 +255,10 @@ TEST_P(OEMCryptoLicenseTest, HashForbiddenAPI15) { // This test verifies OEMCrypto_SetDecryptHash for out of range frame number. TEST_P(OEMCryptoLicenseTest, DecryptHashForOutOfRangeFrameNumber) { - uint32_t frame_number = kHugeRandomNumber; - uint32_t hash = 42; - ASSERT_NO_FATAL_FAILURE(OEMCrypto_SetDecryptHash( - session_.session_id(), frame_number, - reinterpret_cast(&hash), sizeof(hash))); + const uint32_t frame_number = kHugeRandomNumber; + const uint32_t crc32 = 42; + ASSERT_NO_FATAL_FAILURE( + OEMCrypto_SetDecryptHash(session_.session_id(), frame_number, crc32)); } // @@ -463,7 +460,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, DecryptMaxSampleAPI16) { subsample_sizes.push_back({clear_size, encrypted_size}); bytes_remaining -= this_subsample_size; } - ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes(subsample_sizes)); + ASSERT_NO_FATAL_FAILURE(SetSubsampleSizes(std::move(subsample_sizes))); ASSERT_NO_FATAL_FAILURE(LoadLicense()); ASSERT_NO_FATAL_FAILURE(MakeBuffers()); ASSERT_NO_FATAL_FAILURE(EncryptData()); @@ -477,7 +474,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, while (number_of_subsamples-- > 0) { subsample_sizes.push_back({100, 100}); } - SetSubsampleSizes(subsample_sizes); + SetSubsampleSizes(std::move(subsample_sizes)); LoadLicense(); MakeBuffers(); EncryptData(); @@ -501,7 +498,7 @@ TEST_P(OEMCryptoSessionTestsDecryptTests, OEMCryptoMemoryCheckDecryptCENCStatusForHugeSubSample) { std::vector subsample_sizes; subsample_sizes.push_back({100000, 100000}); - SetSubsampleSizes(subsample_sizes); + SetSubsampleSizes(std::move(subsample_sizes)); LoadLicense(); MakeBuffers(); EncryptData(); diff --git a/oemcrypto/test/oemcrypto_decrypt_test.h b/oemcrypto/test/oemcrypto_decrypt_test.h index 36a249c8..9c0e135e 100644 --- a/oemcrypto/test/oemcrypto_decrypt_test.h +++ b/oemcrypto/test/oemcrypto_decrypt_test.h @@ -120,7 +120,7 @@ class OEMCryptoSessionTestsDecryptTests void SetSubsampleSizes(std::vector subsample_sizes) { // This is just sugar for having one sample with the given subsamples in it. - SetSampleSizes({subsample_sizes}); + SetSampleSizes({std::move(subsample_sizes)}); } void SetSampleSizes(std::vector> sample_sizes) { @@ -386,11 +386,9 @@ class OEMCryptoSessionTestsDecryptTests if (verify_crc_) { const TestSample& sample = samples_[0]; - uint32_t hash = + uint32_t crc32 = util::wvcrc32(sample.truth_buffer.data(), sample.truth_buffer.size()); - OEMCrypto_SetDecryptHash(session_.session_id(), 1, - reinterpret_cast(&hash), - sizeof(hash)); + OEMCrypto_SetDecryptHash(session_.session_id(), 1, crc32); } // Build an array of just the sample descriptions. diff --git a/oemcrypto/test/oemcrypto_generic_crypto_test.cpp b/oemcrypto/test/oemcrypto_generic_crypto_test.cpp index 867b4f8e..241c6dcd 100644 --- a/oemcrypto/test/oemcrypto_generic_crypto_test.cpp +++ b/oemcrypto/test/oemcrypto_generic_crypto_test.cpp @@ -11,9 +11,12 @@ using ::testing::Range; namespace wvoec { +/// @addtogroup generic +/// @{ + TEST_P(OEMCryptoGenericCryptoTest, GenericKeyLoad) { EncryptAndLoadKeys(); } -// Test that the Generic_Encrypt function works correctly. +/** Test that the Generic_Encrypt function works correctly. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncrypt) { EncryptAndLoadKeys(); unsigned int key_index = 0; @@ -34,7 +37,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncrypt) { ASSERT_EQ(expected_encrypted, encrypted); } -// Test that the Generic_Encrypt function fails when not allowed. +/** Test that the Generic_Encrypt function fails when not allowed. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadEncrypt) { EncryptAndLoadKeys(); BadEncrypt(0, OEMCrypto_HMAC_SHA256, buffer_size_); @@ -45,8 +48,8 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadEncrypt) { BadEncrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, buffer_size_); } -// Test that the Generic_Encrypt works if the input and output buffers are the -// same. +/** Test that the Generic_Encrypt works if the input and output buffers are the + * same. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptSameBufferAPI12) { EncryptAndLoadKeys(); unsigned int key_index = 0; @@ -87,7 +90,7 @@ TEST_P( OEMCrypto_AES_CBC_128_NO_PADDING, buffer.data())); } -// Test Generic_Decrypt works correctly. +/** Test Generic_Decrypt works correctly. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecrypt) { EncryptAndLoadKeys(); unsigned int key_index = 1; @@ -108,8 +111,8 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecrypt) { ASSERT_EQ(clear_buffer_, resultant); } -// Test that Generic_Decrypt works correctly when the input and output buffers -// are the same. +/** Test that Generic_Decrypt works correctly when the input and output buffers + * are the same. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptSameBufferAPI12) { EncryptAndLoadKeys(); unsigned int key_index = 1; @@ -122,16 +125,16 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptSameBufferAPI12) { session_.license().keys[key_index].key_id, session_.license().keys[key_index].key_id_length, OEMCrypto_CipherMode_CENC, key_handle)); - vector buffer = encrypted; + vector resultant(encrypted.size()); ASSERT_EQ(OEMCrypto_SUCCESS, - GenericDecrypt(key_handle.data(), key_handle.size(), buffer.data(), - buffer.size(), iv_, OEMCrypto_AES_CBC_128_NO_PADDING, - buffer.data())); - ASSERT_EQ(clear_buffer_, buffer); + GenericDecrypt(key_handle.data(), key_handle.size(), + encrypted.data(), encrypted.size(), iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant.data())); + ASSERT_EQ(clear_buffer_, resultant); } -// Test that Generic_Decrypt fails to decrypt to an insecure buffer if the key -// requires a secure data path. +/** Test that Generic_Decrypt fails to decrypt to an insecure buffer if the key + * requires a secure data path. */ TEST_P(OEMCryptoGenericCryptoTest, GenericSecureToClear) { license_messages_.set_control(wvoec::kControlObserveDataPath | wvoec::kControlDataPathSecure); @@ -155,7 +158,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericSecureToClear) { ASSERT_NE(clear_buffer_, resultant); } -// Test that the Generic_Decrypt function fails when not allowed. +/** Test that the Generic_Decrypt function fails when not allowed. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadDecrypt) { EncryptAndLoadKeys(); BadDecrypt(1, OEMCrypto_HMAC_SHA256, buffer_size_); @@ -194,7 +197,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeySign) { ASSERT_EQ(expected_signature, signature); } -// Test that the Generic_Sign function fails when not allowed. +/** Test that the Generic_Sign function fails when not allowed. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadSign) { EncryptAndLoadKeys(); BadSign(0, OEMCrypto_HMAC_SHA256); // Can't sign with encrypt key. @@ -223,7 +226,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerify) { signature.data(), signature.size())); } -// Test that the Generic_Verify function fails when not allowed. +/** Test that the Generic_Verify function fails when not allowed. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadVerify) { EncryptAndLoadKeys(); BadVerify(0, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); @@ -235,7 +238,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyBadVerify) { BadVerify(3, OEMCrypto_AES_CBC_128_NO_PADDING, SHA256_DIGEST_LENGTH, false); } -// Test Generic_Encrypt with the maximum buffer size. +/** Test Generic_Encrypt with the maximum buffer size. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) { ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); EncryptAndLoadKeys(); @@ -257,7 +260,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyEncryptLargeBuffer) { ASSERT_EQ(expected_encrypted, encrypted); } -// Test Generic_Decrypt with the maximum buffer size. +/** Test Generic_Decrypt with the maximum buffer size. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) { // Some applications are known to pass in a block that is almost 400k. ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); @@ -280,7 +283,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyDecryptLargeBuffer) { ASSERT_EQ(clear_buffer_, resultant); } -// Test Generic_Sign with the maximum buffer size. +/** Test Generic_Sign with the maximum buffer size. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) { ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); EncryptAndLoadKeys(); @@ -310,7 +313,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeySignLargeBuffer) { ASSERT_EQ(expected_signature, signature); } -// Test Generic_Verify with the maximum buffer size. +/** Test Generic_Verify with the maximum buffer size. */ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) { ResizeBuffer(GetResourceValue(kMaxGenericBuffer)); EncryptAndLoadKeys(); @@ -332,7 +335,7 @@ TEST_P(OEMCryptoGenericCryptoTest, GenericKeyVerifyLargeBuffer) { signature.data(), signature.size())); } -// Test Generic_Encrypt when the key duration has expired. +/** Test Generic_Encrypt when the key duration has expired. */ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) { license_messages_.core_response() .timer_limits.total_playback_duration_seconds = kDuration; @@ -368,7 +371,7 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationEncrypt) { ASSERT_NO_FATAL_FAILURE(session_.TestGetKeyHandleExpired(key_index)); } -// Test Generic_Decrypt when the key duration has expired. +/** Test Generic_Decrypt when the key duration has expired. */ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) { license_messages_.core_response() .timer_limits.total_playback_duration_seconds = kDuration; @@ -403,7 +406,7 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationDecrypt) { ASSERT_NO_FATAL_FAILURE(session_.TestGetKeyHandleExpired(key_index)); } -// Test Generic_Sign when the key duration has expired. +/** Test Generic_Sign when the key duration has expired. */ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) { license_messages_.core_response() .timer_limits.total_playback_duration_seconds = kDuration; @@ -442,7 +445,7 @@ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationSign) { ASSERT_NO_FATAL_FAILURE(session_.TestGetKeyHandleExpired(key_index)); } -// Test Generic_Verify when the key duration has expired. +/** Test Generic_Verify when the key duration has expired. */ TEST_P(OEMCryptoGenericCryptoTest, KeyDurationVerify) { license_messages_.core_response() .timer_limits.total_playback_duration_seconds = kDuration; @@ -574,4 +577,4 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoGenericCryptoKeyIdLengthTest, Range(kCoreMessagesAPI, kCurrentAPI + 1)); /// @} -} // namespace wvoec \ No newline at end of file +} // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_license_test.cpp b/oemcrypto/test/oemcrypto_license_test.cpp index 93065b22..f4dbf798 100644 --- a/oemcrypto/test/oemcrypto_license_test.cpp +++ b/oemcrypto/test/oemcrypto_license_test.cpp @@ -12,6 +12,9 @@ using ::testing::Range; namespace wvoec { +/// @addtogroup license +/// @{ + // Function to test APIs that expect a buffer length as input // by passing huge buffer lengths up to end_buffer_length and test that the API // doesn't crash. @@ -457,6 +460,8 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyWithBadVerification) { // This test verifies that LoadKeys still works when the message is not aligned // in memory on a word (2 or 4 byte) boundary. TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) { + license_messages_.skip_request_hash(); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); @@ -472,9 +477,12 @@ TEST_P(OEMCryptoLicenseTest, LoadKeyUnalignedMessageAPI16) { license_messages_.encrypted_response_buffer().end()); // Thus, buffer[offset] is NOT word aligned. const uint8_t* unaligned_message = &buffer[offset]; + const std::vector context = session_.GetDefaultContext(); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_LoadLicense( - session_.session_id(), unaligned_message, + session_.session_id(), context.data(), context.size(), + session_.enc_session_key().data(), + session_.enc_session_key().size(), unaligned_message, license_messages_.encrypted_response_buffer().size(), license_messages_.serialized_core_message().size(), license_messages_.response_signature().data(), @@ -674,6 +682,9 @@ TEST_P(OEMCryptoLicenseTest, QueryKeyControl) { // implementation should be able to handle the clear KCB in the 16.4.x response // and load the license correctly. TEST_F(OEMCryptoSessionTests, ClearKcbAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } Session s; ASSERT_NO_FATAL_FAILURE(s.open()); ASSERT_NO_FATAL_FAILURE(InstallTestDrmKey(&s)); diff --git a/oemcrypto/test/oemcrypto_license_test.h b/oemcrypto/test/oemcrypto_license_test.h index 8d7339b2..e80c22cb 100644 --- a/oemcrypto/test/oemcrypto_license_test.h +++ b/oemcrypto/test/oemcrypto_license_test.h @@ -329,7 +329,7 @@ class LicenseWithUsageEntry { ASSERT_NO_FATAL_FAILURE(session_.GenerateReport(pst())); Test_PST_Report expected(pst(), status); ASSERT_NO_FATAL_FAILURE( - session_.VerifyReport(expected, time_license_received_, + session_.VerifyReport(std::move(expected), time_license_received_, time_first_decrypt_, time_last_decrypt_)); // The PST report was signed above. Below we verify that the entire message // that is sent to the server will be signed by the right mac keys. @@ -400,6 +400,17 @@ class OEMCryptoRefreshTest : public OEMCryptoLicenseTest { ASSERT_EQ(expected_result, renewal_messages->LoadResponse()); } + void MakeReleaseRequest(ReleaseRoundTrip* release_messages) { + ASSERT_NO_FATAL_FAILURE(release_messages->SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(release_messages->CreateDefaultResponse()); + } + + void LoadRelease(ReleaseRoundTrip* release_messages, + OEMCryptoResult expected_result) { + ASSERT_NO_FATAL_FAILURE(release_messages->EncryptAndSignResponse()); + ASSERT_EQ(expected_result, release_messages->LoadResponse()); + } + ODK_TimerLimits timer_limits_; }; diff --git a/oemcrypto/test/oemcrypto_provisioning_test.cpp b/oemcrypto/test/oemcrypto_provisioning_test.cpp index 47d7228d..fbb7a83c 100644 --- a/oemcrypto/test/oemcrypto_provisioning_test.cpp +++ b/oemcrypto/test/oemcrypto_provisioning_test.cpp @@ -5,8 +5,11 @@ #include "oemcrypto_provisioning_test.h" +#include "bcc_validator.h" +#include "device_info_validator.h" #include "log.h" #include "platform.h" +#include "signed_csr_payload_validator.h" #include "test_sleep.h" namespace wvoec { @@ -67,24 +70,6 @@ TEST_F(OEMCryptoKeyboxTest, ProductionKeyboxValid) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); } -// This tests GenerateDerivedKeys with an 8k context. -TEST_F(OEMCryptoKeyboxTest, GenerateDerivedKeysFromKeyboxLargeBuffer) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - const size_t max_size = GetResourceValue(kLargeMessageSize); - vector mac_context(max_size); - vector enc_context(max_size); - // Stripe the data so the two vectors are not identical, and not all zeroes. - for (size_t i = 0; i < max_size; i++) { - mac_context[i] = i % 0x100; - enc_context[i] = (3 * i) % 0x100; - } - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_GenerateDerivedKeys( - s.session_id(), mac_context.data(), mac_context.size(), - enc_context.data(), enc_context.size())); -} - // This verifies that the device really does claim to have a certificate. // It should be filtered out for devices that have a keybox. TEST_F(OEMCryptoProv30Test, DeviceClaimsOEMCertificate) { @@ -164,7 +149,6 @@ TEST_F(OEMCryptoProv30Test, GetCertOnlyAPI16) { // Derive keys from the session key -- this should use the DRM Cert's key. // It should NOT use the OEM Private key because that key should not have // been loaded. - ASSERT_NO_FATAL_FAILURE(s.GenerateDerivedKeysFromSessionKey()); // Now fill a message and try to load it. LicenseRoundTrip license_messages(&s); license_messages.set_control(0); @@ -251,6 +235,9 @@ TEST_F(OEMCryptoProv40Test, GetBootCertificateChainSuccess) { additional_signature.data(), &additional_signature_size), OEMCrypto_SUCCESS); + util::BccValidator validator; + EXPECT_EQ(util::CborMessageStatus::kCborParseOk, validator.Parse(bcc)); + EXPECT_EQ(util::CborMessageStatus::kCborValidateOk, validator.Validate()); } // Verifies that short buffer error returns when the buffer is short. @@ -363,6 +350,9 @@ TEST_F(OEMCryptoProv40Test, GenerateCertificateKeyPairsAreDifferent) { } TEST_F(OEMCryptoProv40Test, GetDeviceInformationAPI18) { + if (wvoec::global_features.api_version < 18) { + GTEST_SKIP() << "Test for versions 18 and up only."; + } std::vector device_info; size_t device_info_length = 0; OEMCryptoResult sts = @@ -374,24 +364,26 @@ TEST_F(OEMCryptoProv40Test, GetDeviceInformationAPI18) { OEMCrypto_GetDeviceInformation(device_info.data(), &device_info_length), OEMCrypto_SUCCESS); EXPECT_NE(device_info_length, 0uL); + device_info.resize(device_info_length); + constexpr int kDeviceVersion = 3; + util::DeviceInfoValidator validator(kDeviceVersion); + EXPECT_EQ(util::CborMessageStatus::kCborParseOk, + validator.Parse(device_info)); + validator.Validate(); + EXPECT_EQ(util::CborMessageStatus::kCborValidateOk, validator.Validate()); } TEST_F(OEMCryptoProv40Test, GetDeviceSignedCsrPayloadAPI18) { - std::vector challenge(64, 0xaa); - // TODO: add cppbor support for oemcrypto tests for all targets. Before that, - // use hex values which are equivalent of the commented cppbor statement. - // std::vector device_info = cppbor::Map() - // .add("manufacturer", "google") - // .add("fused", 0) - // .add("other", "ignored") - // .canonicalize() - // .encode(); - // - std::vector device_info = { - 0xa3, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x0, 0x65, 0x6f, 0x74, - 0x68, 0x65, 0x72, 0x67, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, - 0x6c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x72, 0x66, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65}; + if (wvoec::global_features.api_version < 18) { + GTEST_SKIP() << "Test for versions 18 and up only."; + } + const std::vector challenge(64, 0xaa); + const std::vector device_info = cppbor::Map() + .add("manufacturer", "google") + .add("fused", 0) + .add("other", "ignored") + .canonicalize() + .encode(); std::vector signed_csr_payload; size_t signed_csr_payload_length = 0; OEMCryptoResult sts = OEMCrypto_GetDeviceSignedCsrPayload( @@ -407,17 +399,22 @@ TEST_F(OEMCryptoProv40Test, GetDeviceSignedCsrPayloadAPI18) { &signed_csr_payload_length), OEMCrypto_SUCCESS); EXPECT_NE(signed_csr_payload_length, 0uL); + util::SignedCsrPayloadValidator validator; + EXPECT_EQ(util::CborMessageStatus::kCborParseOk, + validator.Parse(signed_csr_payload)); + EXPECT_EQ(util::CborMessageStatus::kCborValidateOk, validator.Validate()); } TEST_F(OEMCryptoProv40Test, GetDeviceSignedCsrPayloadInvalid) { std::vector signed_csr_payload; size_t signed_csr_payload_length = 0; - std::vector challenge(64, 0xaa); - std::vector device_info = { - 0xa3, 0x65, 0x66, 0x75, 0x73, 0x65, 0x64, 0x0, 0x65, 0x6f, 0x74, - 0x68, 0x65, 0x72, 0x67, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x64, - 0x6c, 0x6d, 0x61, 0x6e, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, - 0x65, 0x72, 0x66, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65}; + const std::vector challenge(64, 0xaa); + const std::vector device_info = cppbor::Map() + .add("manufacturer", "google") + .add("fused", 0) + .add("other", "ignored") + .canonicalize() + .encode(); std::vector challenge_empty; OEMCryptoResult sts = OEMCrypto_GetDeviceSignedCsrPayload( challenge_empty.data(), challenge_empty.size(), device_info.data(), @@ -427,7 +424,7 @@ TEST_F(OEMCryptoProv40Test, GetDeviceSignedCsrPayloadInvalid) { ASSERT_EQ(sts, OEMCrypto_ERROR_INVALID_CONTEXT); // Oversized challenge - std::vector challenge_long(65, 0xaa); + const std::vector challenge_long(65, 0xaa); sts = OEMCrypto_GetDeviceSignedCsrPayload( challenge_long.data(), challenge_long.size(), device_info.data(), device_info.size(), signed_csr_payload.data(), @@ -693,6 +690,9 @@ TEST_F(OEMCryptoLoadsCertificate, ForbidRSASignatureForDRMKey2) { } TEST_F(OEMCryptoLoadsCertificate, PrepAndSignLicenseRequestCounterAPI18) { + if (wvoec::global_features.api_version < 18) { + GTEST_SKIP() << "Test for versions 18 and up only."; + } // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for // provisioning 4. Disabled here temporarily. if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { @@ -736,14 +736,8 @@ TEST_F(OEMCryptoLoadsCertificate, SignProvisioningRequest) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { - s.LoadOEMCert(true); - } else { - EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); - s.GenerateDerivedKeysFromKeybox(keybox_); - } ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(keybox_)); ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); } @@ -755,16 +749,10 @@ TEST_F(OEMCryptoLoadsCertificate, SignLargeProvisioningRequestAPI16) { GTEST_SKIP() << "Test for non Prov 4.0 devices only."; } Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - if (global_features.provisioning_method == OEMCrypto_OEMCertificate) { - s.LoadOEMCert(true); - } else { - EXPECT_EQ(global_features.provisioning_method, OEMCrypto_Keybox); - s.GenerateDerivedKeysFromKeybox(keybox_); - } ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); const size_t max_size = GetResourceValue(kLargeMessageSize); provisioning_messages.set_message_size(max_size); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(keybox_)); ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); } @@ -779,7 +767,7 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvision) { } Session s; ProvisioningRoundTrip provisioning_messages(&s, encoded_rsa_key_); - provisioning_messages.PrepareSession(keybox_); + ASSERT_NO_FATAL_FAILURE(provisioning_messages.PrepareSession(keybox_)); ASSERT_NO_FATAL_FAILURE(provisioning_messages.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(provisioning_messages.CreateDefaultResponse()); ASSERT_NO_FATAL_FAILURE(provisioning_messages.EncryptAndSignResponse()); @@ -913,6 +901,9 @@ TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadRange5Prov30_API16) { // TODO(b/144186970): This test should also run on Prov 3.0 devices. TEST_F(OEMCryptoLoadsCertificate, CertificateProvisionBadSignatureKeyboxTestAPI16) { + if (global_features.provisioning_method != OEMCrypto_Keybox) { + GTEST_SKIP() << "Test for Prov 2.0 devices only."; + } // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for // provisioning 4. Disabled here temporarily. if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { @@ -977,6 +968,9 @@ TEST_F(OEMCryptoLoadsCertificate, if (global_features.provisioning_method != OEMCrypto_Keybox) { GTEST_SKIP() << "Test for Prov 2.0 devices only."; } + if (global_features.provisioning_method != OEMCrypto_Keybox) { + GTEST_SKIP() << "Test for Prov 2.0 devices only."; + } // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for // provisioning 4. Disabled here temporarily. if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { @@ -1243,141 +1237,4 @@ TEST_F(OEMCryptoLoadsCertificate, SupportsCertificatesAPI13) { << "Supported certificates is only " << OEMCrypto_SupportedCertificates(); } -// This test is not run by default, because it takes a long time and -// is used to measure RSA performance, not test functionality. -TEST_F(OEMCryptoLoadsCertificate, RSAPerformance) { - // TODO(b/197141970): Need to revisit OEMCryptoLoadsCert* tests for - // provisioning 4. Disabled here temporarily. - if (global_features.provisioning_method == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } - const std::chrono::milliseconds kTestDuration(5000); - OEMCryptoResult sts; - std::chrono::steady_clock clock; - wvutil::TestSleep::Sleep(kShortSleep); // Make sure we are not nonce limited. - - auto start_time = clock.now(); - int count = 15; - for (int i = 0; i < count; i++) { // Only 20 nonce available. - ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); - } - auto delta_time = clock.now() - start_time; - const double provision_time = - delta_time / std::chrono::milliseconds(1) / count; - - Session session; - ASSERT_NO_FATAL_FAILURE(CreateWrappedDRMKey()); - start_time = clock.now(); - count = 0; - while (clock.now() - start_time < kTestDuration) { - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); - const size_t size = 50; - vector licenseRequest(size); - GetRandBytes(licenseRequest.data(), licenseRequest.size()); - size_t signature_length = 0; - sts = OEMCrypto_GenerateRSASignature(s.session_id(), licenseRequest.data(), - licenseRequest.size(), nullptr, - &signature_length, kSign_RSASSA_PSS); - ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); - ASSERT_NE(static_cast(0), signature_length); - - if (ShouldGenerateCorpus()) { - const std::string file_name = - GetFileName("oemcrypto_generate_rsa_signature_fuzz_seed_corpus"); - OEMCrypto_Generate_RSA_Signature_Fuzz fuzzed_structure; - fuzzed_structure.padding_scheme = kSign_RSASSA_PSS; - fuzzed_structure.signature_length = signature_length; - // Cipher mode and algorithm. - AppendToFile(file_name, reinterpret_cast(&fuzzed_structure), - sizeof(fuzzed_structure)); - AppendToFile(file_name, - reinterpret_cast(licenseRequest.data()), - licenseRequest.size()); - } - - std::vector signature(signature_length, 0); - sts = OEMCrypto_GenerateRSASignature( - s.session_id(), licenseRequest.data(), licenseRequest.size(), - signature.data(), &signature_length, kSign_RSASSA_PSS); - ASSERT_EQ(OEMCrypto_SUCCESS, sts); - count++; - } - delta_time = clock.now() - start_time; - const double license_request_time = - delta_time / std::chrono::milliseconds(1) / count; - - Session s; - ASSERT_NO_FATAL_FAILURE(s.open()); - ASSERT_NO_FATAL_FAILURE(s.LoadWrappedRsaDrmKey(wrapped_drm_key_)); - vector session_key; - vector enc_session_key; - ASSERT_NO_FATAL_FAILURE(s.SetRsaPublicKeyFromPrivateKeyInfo( - encoded_rsa_key_.data(), encoded_rsa_key_.size())); - ASSERT_TRUE(s.GenerateRsaSessionKey(&session_key, &enc_session_key)); - vector mac_context; - vector enc_context; - s.FillDefaultContext(&mac_context, &enc_context); - - enc_session_key = wvutil::a2b_hex( - "7789c619aa3b9fa3c0a53f57a4abc6" - "02157c8aa57e3c6fb450b0bea22667fb" - "0c3200f9d9d618e397837c720dc2dadf" - "486f33590744b2a4e54ca134ae7dbf74" - "434c2fcf6b525f3e132262f05ea3b3c1" - "198595c0e52b573335b2e8a3debd0d0d" - "d0306f8fcdde4e76476be71342957251" - "e1688c9ca6c1c34ed056d3b989394160" - "cf6937e5ce4d39cc73d11a2e93da21a2" - "fa019d246c852fe960095b32f120c3c2" - "7085f7b64aac344a68d607c0768676ce" - "d4c5b2d057f7601921b453a451e1dea0" - "843ebfef628d9af2784d68e86b730476" - "e136dfe19989de4be30a4e7878efcde5" - "ad2b1254f80c0c5dd3cf111b56572217" - "b9f58fc1dacbf74b59d354a1e62cfa0e" - "bf"); - start_time = clock.now(); - while (clock.now() - start_time < kTestDuration) { - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DeriveKeysFromSessionKey( - s.session_id(), enc_session_key.data(), - enc_session_key.size(), mac_context.data(), - mac_context.size(), enc_context.data(), enc_context.size())); - count++; - } - delta_time = clock.now() - start_time; - const double derive_keys_time = - delta_time / std::chrono::milliseconds(1) / count; - - OEMCrypto_Security_Level level = OEMCrypto_SecurityLevel(); - printf( - "PERF:head, security, provision (ms), lic req(ms), derive " - "keys(ms)\n"); - printf("PERF:stat, %u, %8.3f, %8.3f, %8.3f\n", - static_cast(level), provision_time, license_request_time, - derive_keys_time); -} - -// Test DeriveKeysFromSessionKey using the maximum size for the HMAC context. -TEST_F(OEMCryptoUsesCertificate, GenerateDerivedKeysLargeBuffer) { - vector session_key; - vector enc_session_key; - ASSERT_TRUE(session_.GenerateSessionKey(&session_key, &enc_session_key)); - const size_t max_size = GetResourceValue(kLargeMessageSize); - vector mac_context(max_size); - vector enc_context(max_size); - // Stripe the data so the two vectors are not identical, and not all zeroes. - for (size_t i = 0; i < max_size; i++) { - mac_context[i] = i % 0x100; - enc_context[i] = (3 * i) % 0x100; - } - ASSERT_EQ(OEMCrypto_SUCCESS, - OEMCrypto_DeriveKeysFromSessionKey( - session_.session_id(), enc_session_key.data(), - enc_session_key.size(), mac_context.data(), mac_context.size(), - enc_context.data(), enc_context.size())); -} - } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_security_test.cpp b/oemcrypto/test/oemcrypto_security_test.cpp index 72390eef..ca853d40 100644 --- a/oemcrypto/test/oemcrypto_security_test.cpp +++ b/oemcrypto/test/oemcrypto_security_test.cpp @@ -599,11 +599,10 @@ TEST_F(OEMCryptoSessionTests, TEST_F(OEMCryptoMemoryLicenseTest, OEMCryptoMemoryDecryptHashForHugeHashBuffer) { uint32_t session_id = session_.session_id(); - auto f = [session_id](size_t hash_length) { - uint32_t frame_number = 1; - vector hash_buffer(hash_length); - return OEMCrypto_SetDecryptHash(session_id, frame_number, - hash_buffer.data(), hash_buffer.size()); + auto f = [session_id]() { + const uint32_t frame_number = 1; + const uint32_t crc32 = 0; + return OEMCrypto_SetDecryptHash(session_id, frame_number, crc32); }; TestHugeLengthDoesNotCrashAPI(f, kCheckStatus); } diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp index 1b6d5b48..051fb054 100644 --- a/oemcrypto/test/oemcrypto_test.cpp +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -281,6 +281,9 @@ class OEMCryptoEntitlementLicenseTest : public OEMCryptoLicenseTest { /** This verifies that entitlement keys and entitled content keys can be loaded. */ TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } LoadEntitlementLicense(); uint32_t key_session_id = 0; ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( @@ -295,40 +298,15 @@ TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysAPI17) { ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(true)); } -TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysAPI17) { - if (!global_features.supports_cas) { - GTEST_SKIP() << "OEMCrypto does not support CAS"; - } - LoadEntitlementLicense(); - uint32_t key_session_id = 0; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id)); - - EntitledMessage entitled_message_1(&license_messages_); - entitled_message_1.FillKeyArray(); - entitled_message_1.SetEntitledKeySession(key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( - /*load_even=*/true, /*load_odd=*/true, OEMCrypto_SUCCESS)); - EntitledMessage entitled_message_2(&license_messages_); - entitled_message_2.FillKeyArray(); - entitled_message_2.SetEntitledKeySession(key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadCasKeys( - /*load_even=*/true, /*load_odd=*/false, OEMCrypto_SUCCESS)); - EntitledMessage entitled_message_3(&license_messages_); - entitled_message_3.FillKeyArray(); - entitled_message_3.SetEntitledKeySession(key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys( - /*load_even=*/false, /*load_odd=*/true, OEMCrypto_SUCCESS)); - ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys( - /*load_even=*/false, /*load_odd=*/false, OEMCrypto_SUCCESS)); -} - /** * This verifies that entitled content keys cannot be loaded if we have not yet * loaded the entitlement keys. */ TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysNoEntitlementKeysAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -343,53 +321,14 @@ TEST_P(OEMCryptoEntitlementLicenseTest, ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false)); } -/** - * This verifies that entitled content keys cannot be loaded if we have loaded - * the wrong entitlement keys. - */ -TEST_P(OEMCryptoEntitlementLicenseTest, - CasOnlyLoadCasKeysNoEntitlementKeysAPI17) { - if (!global_features.supports_cas) { - GTEST_SKIP() << "OEMCrypto does not support CAS"; - } - license_messages_.set_license_type(OEMCrypto_EntitlementLicense); - ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); - ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); - ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); - uint32_t key_session_id = 0; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id)); - - EntitledMessage entitled_message_1(&license_messages_); - entitled_message_1.FillKeyArray(); - entitled_message_1.SetEntitledKeySession(key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( - /*load_even=*/true, /*load_odd=*/true, OEMCrypto_ERROR_INVALID_CONTEXT)); -} - /** * This verifies that entitled content keys cannot be loaded if we have loaded * the wrong entitlement keys. */ TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysWrongEntitlementKeysAPI17) { - LoadEntitlementLicense(); - uint32_t key_session_id = 0; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id)); - - EntitledMessage entitled_message_1(&license_messages_); - entitled_message_1.FillKeyArray(); - const std::string key_id = "no_key"; - entitled_message_1.SetEntitlementKeyId(0, key_id); - entitled_message_1.SetEntitledKeySession(key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false)); -} - -TEST_P(OEMCryptoEntitlementLicenseTest, - CasOnlyLoadCasKeysWrongEntitlementKeysAPI17) { - if (!global_features.supports_cas) { - GTEST_SKIP() << "OEMCrypto does not support CAS"; + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; } LoadEntitlementLicense(); uint32_t key_session_id = 0; @@ -401,8 +340,7 @@ TEST_P(OEMCryptoEntitlementLicenseTest, const std::string key_id = "no_key"; entitled_message_1.SetEntitlementKeyId(0, key_id); entitled_message_1.SetEntitledKeySession(key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( - /*load_even=*/true, /*load_odd=*/true, OEMCrypto_KEY_NOT_ENTITLED)); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false)); } /** @@ -411,22 +349,8 @@ TEST_P(OEMCryptoEntitlementLicenseTest, */ TEST_P(OEMCryptoEntitlementLicenseTest, LoadEntitlementKeysWrongEntitledKeySessionAPI17) { - LoadEntitlementLicense(); - uint32_t key_session_id = 0; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id)); - - EntitledMessage entitled_message_1(&license_messages_); - entitled_message_1.FillKeyArray(); - const uint32_t wrong_key_session_id = key_session_id == 0 ? 1 : 0; - entitled_message_1.SetEntitledKeySession(wrong_key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false)); -} - -TEST_P(OEMCryptoEntitlementLicenseTest, - CasOnlyLoadCasKeysWrongEntitledKeySessionAPI17) { - if (!global_features.supports_cas) { - GTEST_SKIP() << "OEMCrypto does not support CAS"; + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; } LoadEntitlementLicense(); uint32_t key_session_id = 0; @@ -437,54 +361,9 @@ TEST_P(OEMCryptoEntitlementLicenseTest, entitled_message_1.FillKeyArray(); const uint32_t wrong_key_session_id = key_session_id == 0 ? 1 : 0; entitled_message_1.SetEntitledKeySession(wrong_key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( - /*load_even=*/true, /*load_odd=*/true, - OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION)); -} - -/** - * This verifies that entitled content keys cannot be loaded if we specify an - * entitled key session that is actually an oemcrypto session. - */ -TEST_P(OEMCryptoEntitlementLicenseTest, - LoadEntitlementKeysOemcryptoSessionAPI17) { - if (!global_features.supports_cas) { - GTEST_SKIP() << "OEMCrypto does not support CAS"; - } - LoadEntitlementLicense(); - uint32_t key_session_id = 0; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id)); - - EntitledMessage entitled_message_1(&license_messages_); - entitled_message_1.FillKeyArray(); - if (session_.session_id() == key_session_id) { - GTEST_SKIP() - << "Skipping test because entitled and entitlement sessions are both " - << key_session_id << "."; - } - entitled_message_1.SetEntitledKeySession(session_.session_id()); ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false)); } -TEST_P(OEMCryptoEntitlementLicenseTest, - CasOnlyLoadCasKeysOemcryptoSessionAPI17) { - if (!global_features.supports_cas) { - GTEST_SKIP() << "OEMCrypto does not support CAS"; - } - LoadEntitlementLicense(); - uint32_t key_session_id = 0; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id)); - - EntitledMessage entitled_message_1(&license_messages_); - entitled_message_1.FillKeyArray(); - entitled_message_1.SetEntitledKeySession(session_.session_id()); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( - /*load_even=*/true, /*load_odd=*/true, - OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION)); -} - INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoEntitlementLicenseTest, Range(kCoreMessagesAPI, kCurrentAPI + 1)); @@ -697,6 +576,9 @@ TEST_F(OEMCryptoMemoryLicenseTest, /// @{ TEST_P(OEMCryptoLicenseTest, GetKeyHandleEntitledKeyAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -724,6 +606,9 @@ TEST_P(OEMCryptoLicenseTest, GetKeyHandleEntitledKeyAPI17) { // SelectEntitledKey should fail if we attempt to select a key that has not been // loaded. Also, the error should be NO_CONTENT_KEY. TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -745,41 +630,11 @@ TEST_P(OEMCryptoLicenseTest, SelectKeyEntitledKeyNotThereAPI17) { strlen(content_key_id))); } -/** - * Select key with entitlement license fails if the key id is entitlement key - * id. - */ -TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) { - if (!global_features.supports_cas) { - GTEST_SKIP() << "OEMCrypto does not support CAS"; - } - license_messages_.set_license_type(OEMCrypto_EntitlementLicense); - 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()); - - uint32_t key_session_id; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id)); - EntitledMessage entitled_message_1(&license_messages_); - entitled_message_1.FillKeyArray(); - entitled_message_1.SetEntitledKeySession(key_session_id); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true)); - - if (session_.session_id() == key_session_id) { - GTEST_SKIP() - << "Skipping test because entitled and entitlement sessions are both " - << key_session_id << "."; - } - ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( - OEMCrypto_ERROR_INVALID_CONTEXT, session_.session_id(), - session_.license().keys[0].key_id, - session_.license().keys[0].key_id_length)); -} - // This verifies that entitled key sessions can be created and removed. TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -806,6 +661,9 @@ TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsAPI17) { TEST_P(OEMCryptoLicenseTest, EntitledKeySessionsCloseWithOEMCryptoSessionAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -825,76 +683,13 @@ TEST_P(OEMCryptoLicenseTest, session_.open(); } -// This verifies that multiple entitled key sessions can be created. They can -// load and select keys independently. -TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) { - if (!global_features.supports_cas) { - GTEST_SKIP() << "OEMCrypto does not support CAS"; - } - license_messages_.set_license_type(OEMCrypto_EntitlementLicense); - 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()); - - uint32_t key_session_id_1; - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id_1)); - EntitledMessage entitled_message_1(&license_messages_); - entitled_message_1.FillKeyArray(); - entitled_message_1.SetEntitledKeySession(key_session_id_1); - const char* content_key_id_1 = "content_key_id_1"; - entitled_message_1.SetContentKeyId(0, content_key_id_1); - ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true)); - // We can select content key 1 in entitled key session 1. - ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( - OEMCrypto_SUCCESS, key_session_id_1, - reinterpret_cast(content_key_id_1), - strlen(content_key_id_1))); - - // Create another entitled key session. - uint32_t key_session_id_2; - OEMCryptoResult status = OEMCrypto_CreateEntitledKeySession( - session_.session_id(), &key_session_id_2); - // For DRM, but not for CAS, we allow there to be only a single entitled - // session. - if (status == OEMCrypto_ERROR_TOO_MANY_SESSIONS) { - GTEST_SKIP() - << "Skipping test because multiple entitled sessions not supported."; - } - ASSERT_EQ(OEMCrypto_SUCCESS, status); - // Entitled key sessions should have unique ids. - ASSERT_NE(key_session_id_1, key_session_id_2); - - EntitledMessage entitled_message_2(&license_messages_); - entitled_message_2.FillKeyArray(); - entitled_message_2.SetEntitledKeySession(key_session_id_2); - const char* content_key_id_2 = "content_key_id_2"; - entitled_message_2.SetContentKeyId(0, content_key_id_2); - ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(true)); - // We can select content key 2 in entitled key session 2. - ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( - OEMCrypto_SUCCESS, key_session_id_2, - reinterpret_cast(content_key_id_2), - strlen(content_key_id_2))); - - // Content key id 1 is not in entitled key session 2. - ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( - OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_2, - reinterpret_cast(content_key_id_1), - strlen(content_key_id_1))); - - // Content key id 2 is not in entitled key session 1. - ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( - OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_1, - reinterpret_cast(content_key_id_2), - strlen(content_key_id_2))); -} - // This verifies that within an entitled key session, each entitlement key can // corresponds to only one content key at most. TEST_P(OEMCryptoLicenseTest, EntitledKeySessionOneContentKeyPerEntitlementAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -940,6 +735,9 @@ TEST_P(OEMCryptoLicenseTest, // instead). TEST_P(OEMCryptoLicenseTest, RejectOecSessionDecryptWithEntitlementLicenseAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -979,6 +777,9 @@ TEST_P(OEMCryptoLicenseTest, // This verifies that an entitled key session can be reassociated to an // OEMCrypto session. TEST_P(OEMCryptoEntitlementLicenseTest, ReassociateEntitledKeySessionAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } license_messages_.set_license_type(OEMCrypto_EntitlementLicense); ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); @@ -1480,7 +1281,260 @@ INSTANTIATE_TEST_SUITE_P(TestAll, OEMCryptoLicenseOverflowTest, /// @addtogroup cas /// @{ +TEST_P(OEMCryptoEntitlementLicenseTest, CasOnlyLoadCasKeysAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } + LoadEntitlementLicense(); + uint32_t key_session_id = 0; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id)); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( + /*load_even=*/true, /*load_odd=*/true, OEMCrypto_SUCCESS)); + EntitledMessage entitled_message_2(&license_messages_); + entitled_message_2.FillKeyArray(); + entitled_message_2.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadCasKeys( + /*load_even=*/true, /*load_odd=*/false, OEMCrypto_SUCCESS)); + EntitledMessage entitled_message_3(&license_messages_); + entitled_message_3.FillKeyArray(); + entitled_message_3.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys( + /*load_even=*/false, /*load_odd=*/true, OEMCrypto_SUCCESS)); + ASSERT_NO_FATAL_FAILURE(entitled_message_3.LoadCasKeys( + /*load_even=*/false, /*load_odd=*/false, OEMCrypto_SUCCESS)); +} + +/** + * This verifies that entitled content keys cannot be loaded if we have loaded + * the wrong entitlement keys. + */ +TEST_P(OEMCryptoEntitlementLicenseTest, + CasOnlyLoadCasKeysNoEntitlementKeysAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + ASSERT_NO_FATAL_FAILURE(license_messages_.SignAndVerifyRequest()); + ASSERT_NO_FATAL_FAILURE(license_messages_.CreateDefaultResponse()); + ASSERT_NO_FATAL_FAILURE(license_messages_.EncryptAndSignResponse()); + uint32_t key_session_id = 0; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id)); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( + /*load_even=*/true, /*load_odd=*/true, OEMCrypto_ERROR_INVALID_CONTEXT)); +} + +TEST_P(OEMCryptoEntitlementLicenseTest, + CasOnlyLoadCasKeysWrongEntitlementKeysAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } + LoadEntitlementLicense(); + uint32_t key_session_id = 0; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id)); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + const std::string key_id = "no_key"; + entitled_message_1.SetEntitlementKeyId(0, key_id); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( + /*load_even=*/true, /*load_odd=*/true, OEMCrypto_KEY_NOT_ENTITLED)); +} + +TEST_P(OEMCryptoEntitlementLicenseTest, + CasOnlyLoadCasKeysWrongEntitledKeySessionAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } + LoadEntitlementLicense(); + uint32_t key_session_id = 0; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id)); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + const uint32_t wrong_key_session_id = key_session_id == 0 ? 1 : 0; + entitled_message_1.SetEntitledKeySession(wrong_key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( + /*load_even=*/true, /*load_odd=*/true, + OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION)); +} + +/** + * This verifies that entitled content keys cannot be loaded if we specify an + * entitled key session that is actually an oemcrypto session. + */ +TEST_P(OEMCryptoEntitlementLicenseTest, + LoadEntitlementKeysOemcryptoSessionAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + LoadEntitlementLicense(); + uint32_t key_session_id = 0; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id)); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + if (session_.session_id() == key_session_id) { + GTEST_SKIP() + << "Skipping test because entitled and entitlement sessions are both " + << key_session_id << "."; + } + entitled_message_1.SetEntitledKeySession(session_.session_id()); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(false)); +} + +TEST_P(OEMCryptoEntitlementLicenseTest, + CasOnlyLoadCasKeysOemcryptoSessionAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } + LoadEntitlementLicense(); + uint32_t key_session_id = 0; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id)); + + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(session_.session_id()); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadCasKeys( + /*load_even=*/true, /*load_odd=*/true, + OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION)); +} + +/** + * Select key with entitlement license fails if the key id is entitlement key + * id. + */ +TEST_P(OEMCryptoLicenseTest, SelectKeyEntitlementKeyAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + 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()); + + uint32_t key_session_id; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id)); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true)); + + if (session_.session_id() == key_session_id) { + GTEST_SKIP() + << "Skipping test because entitled and entitlement sessions are both " + << key_session_id << "."; + } + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( + OEMCrypto_ERROR_INVALID_CONTEXT, session_.session_id(), + session_.license().keys[0].key_id, + session_.license().keys[0].key_id_length)); +} + +// This verifies that multiple entitled key sessions can be created. They can +// load and select keys independently. +TEST_P(OEMCryptoLicenseTest, EntitledKeySessionMultipleKeySessionsAPI17) { + if (!global_features.supports_cas) { + GTEST_SKIP() << "OEMCrypto does not support CAS"; + } + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } + license_messages_.set_license_type(OEMCrypto_EntitlementLicense); + 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()); + + uint32_t key_session_id_1; + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id_1)); + EntitledMessage entitled_message_1(&license_messages_); + entitled_message_1.FillKeyArray(); + entitled_message_1.SetEntitledKeySession(key_session_id_1); + const char* content_key_id_1 = "content_key_id_1"; + entitled_message_1.SetContentKeyId(0, content_key_id_1); + ASSERT_NO_FATAL_FAILURE(entitled_message_1.LoadKeys(true)); + // We can select content key 1 in entitled key session 1. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( + OEMCrypto_SUCCESS, key_session_id_1, + reinterpret_cast(content_key_id_1), + strlen(content_key_id_1))); + + // Create another entitled key session. + uint32_t key_session_id_2; + OEMCryptoResult status = OEMCrypto_CreateEntitledKeySession( + session_.session_id(), &key_session_id_2); + // For DRM, but not for CAS, we allow there to be only a single entitled + // session. + if (!global_features.supports_cas && + (key_session_id_2 == key_session_id_1 || + status == OEMCrypto_ERROR_TOO_MANY_SESSIONS)) { + GTEST_SKIP() + << "Skipping test because multiple entitled sessions not supported."; + } + ASSERT_EQ(OEMCrypto_SUCCESS, status); + // Entitled key sessions should have unique ids. + ASSERT_NE(key_session_id_1, key_session_id_2); + + EntitledMessage entitled_message_2(&license_messages_); + entitled_message_2.FillKeyArray(); + entitled_message_2.SetEntitledKeySession(key_session_id_2); + const char* content_key_id_2 = "content_key_id_2"; + entitled_message_2.SetContentKeyId(0, content_key_id_2); + ASSERT_NO_FATAL_FAILURE(entitled_message_2.LoadKeys(true)); + // We can select content key 2 in entitled key session 2. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( + OEMCrypto_SUCCESS, key_session_id_2, + reinterpret_cast(content_key_id_2), + strlen(content_key_id_2))); + + // Content key id 1 is not in entitled key session 2. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( + OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_2, + reinterpret_cast(content_key_id_1), + strlen(content_key_id_1))); + + // Content key id 2 is not in entitled key session 1. + ASSERT_NO_FATAL_FAILURE(session_.TestDecryptEntitled( + OEMCrypto_ERROR_NO_CONTENT_KEY, key_session_id_1, + reinterpret_cast(content_key_id_2), + strlen(content_key_id_2))); +} /// @} /// @addtogroup security diff --git a/oemcrypto/test/oemcrypto_test_android.cpp b/oemcrypto/test/oemcrypto_test_android.cpp index 4337f756..9f1e8910 100644 --- a/oemcrypto/test/oemcrypto_test_android.cpp +++ b/oemcrypto/test/oemcrypto_test_android.cpp @@ -21,15 +21,12 @@ namespace wvoec { -// These tests are required for LollyPop Android devices. +/** These tests are required for LollyPop Android devices.*/ class OEMCryptoAndroidLMPTest : public ::testing::Test { protected: void SetUp() override { OEMCrypto_SetSandbox(kTestSandbox, sizeof(kTestSandbox)); ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); - if (OEMCrypto_GetProvisioningMethod() == OEMCrypto_BootCertificateChain) { - GTEST_SKIP() << "Test for non Prov 4.0 devices only."; - } OEMCrypto_SetMaxAPIVersion(kCurrentAPI); OEMCrypto_EnterTestMode(); } @@ -37,34 +34,7 @@ class OEMCryptoAndroidLMPTest : public ::testing::Test { void TearDown() override { OEMCrypto_Terminate(); } }; -// Android devices must have a keybox, or use provisioning 3.0. -TEST_F(OEMCryptoAndroidLMPTest, GetKeyDataImplemented) { - if (global_features.provisioning_method != OEMCrypto_Keybox && - global_features.provisioning_method != OEMCrypto_OEMCertificate) { - GTEST_SKIP() << "Test for Prov 2.0 and 3.0 devices only."; - } - uint8_t key_data[256]; - size_t key_data_len = sizeof(key_data); - if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) { - ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_GetKeyData(key_data, &key_data_len)); - } else { - ASSERT_EQ(OEMCrypto_OEMCertificate, OEMCrypto_GetProvisioningMethod()); - } -} - -// Android devices must have a valid keybox. -TEST_F(OEMCryptoAndroidLMPTest, ValidKeybox) { - if (OEMCrypto_GetProvisioningMethod() == OEMCrypto_Keybox) { - ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); - } -} - -TEST_F(OEMCryptoAndroidLMPTest, MinVersionNumber9) { - uint32_t version = OEMCrypto_APIVersion(); - ASSERT_LE(9u, version); -} - +/** Android devices that use Provisioning 2.0 must have a valid keybox. */ TEST_F(OEMCryptoAndroidLMPTest, ValidKeyboxTest) { if (global_features.provisioning_method != OEMCrypto_Keybox) { GTEST_SKIP() << "Test for Prov 2.0 devices only."; @@ -72,13 +42,15 @@ TEST_F(OEMCryptoAndroidLMPTest, ValidKeyboxTest) { ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); } +/** Android devices must support remote provisioning. Either Provisioning 2, 3 + * or 4. */ TEST_F(OEMCryptoAndroidLMPTest, RewrapDeviceRSAKeyImplemented) { ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, - OEMCrypto_LoadProvisioning(0, nullptr, 0, 0, nullptr, 0, nullptr, - nullptr)); + OEMCrypto_LoadProvisioning(0, nullptr, 0, nullptr, 0, 0, nullptr, 0, + nullptr, 0)); } -// The Generic Crypto API functions are required for Android. +/** The Generic Crypto API functions are required for Android. */ TEST_F(OEMCryptoAndroidLMPTest, GenericCryptoImplemented) { ASSERT_NE( OEMCrypto_ERROR_NOT_IMPLEMENTED, @@ -96,13 +68,15 @@ TEST_F(OEMCryptoAndroidLMPTest, GenericCryptoImplemented) { OEMCrypto_HMAC_SHA256, nullptr, 0)); } -// Android requires support of usage table. The usage table is used for Secure -// Stops and for offline licenses. +/** Android requires support of usage table. The usage table is used for + * offline licenses. */ TEST_F(OEMCryptoAndroidLMPTest, SupportsUsageTable) { ASSERT_TRUE(OEMCrypto_SupportsUsageTable()); } -// Android devices require L1 OEMCrypto. +/** Most Android GMS devices require L1 OEMCrypto. This is not a hard + * requirement for all devices, but is a source of common errors, so we test for + * it here. */ TEST_F(OEMCryptoAndroidLMPTest, Level1Required) { OEMCrypto_Security_Level security_level = OEMCrypto_SecurityLevel(); EXPECT_EQ(OEMCrypto_Level1, security_level) @@ -111,32 +85,24 @@ TEST_F(OEMCryptoAndroidLMPTest, Level1Required) { << "repeat the tests with the flag --gtest_filter=\"*-*Level1Required\""; } -// These tests are required for M Android devices. +/** These tests are required for M Android devices. */ class OEMCryptoAndroidMNCTest : public OEMCryptoAndroidLMPTest {}; -TEST_F(OEMCryptoAndroidMNCTest, MinVersionNumber10) { - uint32_t version = OEMCrypto_APIVersion(); - ASSERT_GE(version, 10u); -} - -// Android devices using Provisioning 2.0 must be able to load a test keybox. -// If they are not using Provisioning 2.0, then they must use Provisioning 3.0. +/** Android devices using Provisioning 2.0 must be able to load a test keybox. + * If they are not using Provisioning 2.0, then they must use Provisioning 3 or + * 4. */ TEST_F(OEMCryptoAndroidMNCTest, LoadsTestKeyboxImplemented) { if (global_features.provisioning_method != OEMCrypto_Keybox) { GTEST_SKIP() << "Test for Prov 2.0 devices only."; } - if (OEMCrypto_Keybox == OEMCrypto_GetProvisioningMethod()) { - ASSERT_EQ( - OEMCrypto_SUCCESS, - OEMCrypto_LoadTestKeybox(reinterpret_cast(&kTestKeybox), - sizeof(kTestKeybox))); - } else { - // Android should use keybox or provisioning 3.0. - ASSERT_EQ(OEMCrypto_OEMCertificate, OEMCrypto_GetProvisioningMethod()); - } + ASSERT_EQ( + OEMCrypto_SUCCESS, + OEMCrypto_LoadTestKeybox(reinterpret_cast(&kTestKeybox), + sizeof(kTestKeybox))); } -// Android requires implementation of these functions. +/** Android requires implementation of functions that report how many open + * sesions are available. */ TEST_F(OEMCryptoAndroidMNCTest, NumberOfSessionsImplemented) { ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, OEMCrypto_GetNumberOfOpenSessions(nullptr)); @@ -144,34 +110,20 @@ TEST_F(OEMCryptoAndroidMNCTest, NumberOfSessionsImplemented) { OEMCrypto_GetMaxNumberOfSessions(nullptr)); } -// Android requires implementation of these functions. +/** Android requires implementation of `OEMCrypto_QueryKeyControl`. */ TEST_F(OEMCryptoAndroidMNCTest, QueryKeyControlImplemented) { ASSERT_NE(OEMCrypto_ERROR_NOT_IMPLEMENTED, OEMCrypto_QueryKeyControl(0, nullptr, 0, nullptr, nullptr)); } -// These tests are required for N Android devices. -class OEMCryptoAndroidNYCTest : public OEMCryptoAndroidMNCTest {}; +/** These tests are required for R Android devices. */ +class OEMCryptoAndroidRVCTest : public OEMCryptoAndroidMNCTest {}; -TEST_F(OEMCryptoAndroidNYCTest, MinVersionNumber11) { +/** Minimum OEMCrypto version 16 is required for all Android R and later + * releases. */ +TEST_F(OEMCryptoAndroidRVCTest, MinVersionNumber16) { uint32_t version = OEMCrypto_APIVersion(); - ASSERT_GE(version, 11u); -} - -// These tests are required for O MR1 Android devices. -class OEMCryptoAndroidOCTest : public OEMCryptoAndroidNYCTest {}; - -TEST_F(OEMCryptoAndroidOCTest, MinVersionNumber13) { - uint32_t version = OEMCrypto_APIVersion(); - ASSERT_GE(version, 13u); -} - -// These tests are required for Q Android devices. -class OEMCryptoAndroidQTest : public OEMCryptoAndroidOCTest {}; - -TEST_F(OEMCryptoAndroidQTest, MinVersionNumber14) { - uint32_t version = OEMCrypto_APIVersion(); - ASSERT_GE(version, 15u); + ASSERT_GE(version, 16u); } } // namespace wvoec diff --git a/oemcrypto/test/oemcrypto_test_main.cpp b/oemcrypto/test/oemcrypto_test_main.cpp index 0779b11c..c4cf9355 100644 --- a/oemcrypto/test/oemcrypto_test_main.cpp +++ b/oemcrypto/test/oemcrypto_test_main.cpp @@ -19,7 +19,6 @@ static void acknowledge_cast() { // Also, the test filter is updated based on the feature list. int main(int argc, char** argv) { bool is_cast_receiver = false; - bool filter_tests = true; int verbosity = 0; // Skip the first element, which is the program name. const std::vector args(argv + 1, argv + argc); @@ -37,9 +36,6 @@ int main(int argc, char** argv) { std::cerr << "The argument --force_load_test_keybox is obsolete.\n"; return 1; } - if (arg == "--no_filter") { - filter_tests = false; - } if (arg == "--fake_sleep") { wvutil::TestSleep::set_real_sleep(false); } @@ -55,11 +51,5 @@ int main(int argc, char** argv) { } // Init GTest after device properties has been initialized. ::testing::InitGoogleTest(&argc, argv); - // If the user requests --no_filter, we don't change the filter, otherwise, we - // filter out features that are not supported. - if (filter_tests) { - ::testing::GTEST_FLAG(filter) = - wvoec::global_features.RestrictFilter(::testing::GTEST_FLAG(filter)); - } return RUN_ALL_TESTS(); } diff --git a/oemcrypto/test/oemcrypto_usage_table_test.cpp b/oemcrypto/test/oemcrypto_usage_table_test.cpp index 40491b42..fc6bd176 100644 --- a/oemcrypto/test/oemcrypto_usage_table_test.cpp +++ b/oemcrypto/test/oemcrypto_usage_table_test.cpp @@ -13,6 +13,9 @@ namespace wvoec { // Test that successive calls to PrepAndSignProvisioningRequest only increase // the provisioning count in the ODK message TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) { + if (wvoec::global_features.api_version < 18) { + GTEST_SKIP() << "Test for versions 18 and up only."; + } // local struct to hold count values from core message typedef struct counts { uint32_t prov; @@ -90,6 +93,9 @@ TEST_F(OEMCryptoSessionTests, Provisioning_IncrementCounterAPI18) { // Test that successive calls to PrepAndSignLicenseRequest only increase // the license count in the ODK message TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) { + if (wvoec::global_features.api_version < 18) { + GTEST_SKIP() << "Test for versions 18 and up only."; + } Session s; s.open(); LicenseRoundTrip license_messages(&s); @@ -128,6 +134,9 @@ TEST_F(OEMCryptoSessionTests, License_IncrementCounterAPI18) { // it is incremented correctly after usage table modification (save offline // license) and decrypt. Also test that decrypt count increments. TEST_F(OEMCryptoSessionTests, MasterGeneration_IncrementCounterAPI18) { + if (wvoec::global_features.api_version < 18) { + GTEST_SKIP() << "Test for versions 18 and up only."; + } if (!OEMCrypto_SupportsUsageTable()) { GTEST_SKIP() << "Usage table not supported, so master generation number " "does not need to be checked."; @@ -642,6 +651,28 @@ TEST_P(OEMCryptoUsageTableTest, OfflineLicenseRefresh) { ASSERT_NO_FATAL_FAILURE(entry.GenerateVerifyReport(kActive)); } +// Test that an offline license can be loaded and that the license can be +// released +TEST_P(OEMCryptoUsageTableTest, OfflineLicenseReleaseAPI19) { + // License release is new in OEMCrypto v19. + if (wvoec::global_features.api_version < 19 || license_api_version_ < 19) { + GTEST_SKIP() << "Test for versions 19 and up only."; + } + LicenseWithUsageEntry entry; + entry.license_messages().set_api_version(license_api_version_); + entry.MakeAndLoad(this, wvoec::kControlNonceOrEntry); + Session& s = entry.session(); + + ASSERT_NO_FATAL_FAILURE(entry.TestDecryptCTR()); + // License release message is signed by client and verified by the server. + ReleaseRoundTrip release_messages(&entry.license_messages()); + MakeReleaseRequest(&release_messages); + LoadRelease(&release_messages, OEMCrypto_SUCCESS); + ASSERT_NO_FATAL_FAILURE(s.UpdateUsageEntry(&encrypted_usage_header_)); + ASSERT_NO_FATAL_FAILURE( + entry.TestDecryptCTR(true, OEMCrypto_ERROR_UNKNOWN_FAILURE)); +} + // Test that an offline license can be reloaded in a new session. TEST_P(OEMCryptoUsageTableTest, ReloadOfflineLicense) { LicenseWithUsageEntry entry; @@ -1235,6 +1266,9 @@ TEST_P(OEMCryptoUsageTableDefragTest, ManyUsageEntries) { // Verify that usage entries can be created in the position of existing entry // indexes. TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); LicenseWithUsageEntry entry1; @@ -1252,6 +1286,9 @@ TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryAPI17) { // Verify that usage entries cannot replace an entry that is currently in // use by a session. TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryIndexInUseAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); LicenseWithUsageEntry entry1; @@ -1268,6 +1305,9 @@ TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryIndexInUseAPI17) { // Verify that usage entries cannot be created if the usage entry index is // too large. TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryWithInvalidIndexAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } LicenseWithUsageEntry entry0; entry0.set_pst("pst 0"); LicenseWithUsageEntry entry1; @@ -1287,6 +1327,9 @@ TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntryWithInvalidIndexAPI17) { // entry. TEST_P(OEMCryptoUsageTableDefragTest, ReuseUsageEntrySessionAlreadyHasEntryAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } LicenseWithUsageEntry entry; entry.set_pst("pst 0"); @@ -1620,6 +1663,13 @@ class OEMCryptoUsageTableTestWallClock : public OEMCryptoUsageTableTest { // clang-format on TEST_P(OEMCryptoUsageTableTestWallClock, TimeRollbackPrevention) { + // This test may require root access. If user is not root, filter this test + // out. + if (!wvutil::TestSleep::CanChangeSystemTime()) { + GTEST_SKIP() << "Filtering out TimeRollbackPrevention."; + } else { + printf("Can change time. I will run TimeRollbackPrevention.\n"); + } cout << "This test temporarily rolls back the system time in order to " "verify " << "that the usage report accounts for the change. After the test, it " @@ -1729,6 +1779,9 @@ TEST_P(OEMCryptoUsageTableTest, UsageEntryWithInvalidSession) { // Verify that a usage entry with an invalid session cannot be used. TEST_P(OEMCryptoUsageTableTest, ReuseUsageEntryWithInvalidSessionAPI17) { + if (wvoec::global_features.api_version < 17) { + GTEST_SKIP() << "Test for versions 17 and up only."; + } std::string pst("pst"); LicenseWithUsageEntry entry; entry.license_messages().set_pst(pst); diff --git a/oemcrypto/test/ota_keybox_test.cpp b/oemcrypto/test/ota_keybox_test.cpp index d1a94641..464e30d0 100644 --- a/oemcrypto/test/ota_keybox_test.cpp +++ b/oemcrypto/test/ota_keybox_test.cpp @@ -173,15 +173,11 @@ class OTAKeyboxProvisioningTest : public ::testing::Test, public SessionUtil { TEST_F(OTAKeyboxProvisioningTest, BasicTest) { OEMCryptoResult result = OEMCrypto_IsKeyboxValid(); if (result == OEMCrypto_SUCCESS) { - cout << " " - << "Keybox valid after initialization. Skipping rest of test." << endl; - return; + GTEST_SKIP() << "Keybox valid after initialization. Skipping rest of test."; } if (result != OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING) { - cout << " " - << "OTA Keybox functions not supported. Skipping rest of test." - << endl; - return; + GTEST_SKIP() + << "OTA Keybox functions not supported. Skipping rest of test."; } cout << " " << "OTA Keybox functions supported. Device needs provisioning." << endl; @@ -235,28 +231,11 @@ TEST_F(OTAKeyboxProvisioningTest, BasicTest) { const std::vector model_key = GetModelKey(device_id); #endif // The server should derive the same set of keys as the client. - const std::string mac_label = "WV_SIGN"; - std::vector mac_context(mac_label.begin(), mac_label.end()); - mac_context.push_back(0); - std::copy(cert.begin(), cert.end(), std::back_inserter(mac_context)); - std::copy(device_id.begin(), device_id.end(), - std::back_inserter(mac_context)); - uint32_t bit_size = MAC_KEY_SIZE * 8 * 2; - std::string bit_size_string = wvutil::EncodeUint32(bit_size); - std::copy(bit_size_string.begin(), bit_size_string.end(), - std::back_inserter(mac_context)); - std::string enc_label = "WV_ENCRYPT"; - std::vector enc_context(enc_label.begin(), enc_label.end()); - enc_context.push_back(0); - std::copy(cert.begin(), cert.end(), std::back_inserter(enc_context)); - std::copy(device_id.begin(), device_id.end(), - std::back_inserter(enc_context)); - bit_size = KEY_SIZE * 8; - bit_size_string = wvutil::EncodeUint32(bit_size); - std::copy(bit_size_string.begin(), bit_size_string.end(), - std::back_inserter(enc_context)); KeyDeriver keys; - keys.DeriveKeys(model_key.data(), model_key.size(), mac_context, enc_context); + std::vector context = cert; + context.insert(context.end(), device_id.begin(), device_id.end()); + keys.DeriveKeys(model_key.data(), model_key.size(), context, "WV_SIGN", + "WV_ENCRYPT"); const std::vector message( request.data(), request.data() + request.size() - HMAC_SHA256_SIGNATURE_SIZE); diff --git a/oemcrypto/util/include/bcc_validator.h b/oemcrypto/util/include/bcc_validator.h new file mode 100644 index 00000000..94f07862 --- /dev/null +++ b/oemcrypto/util/include/bcc_validator.h @@ -0,0 +1,81 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#ifndef WVOEC_UTIL_BCC_VALIDATOR_H_ +#define WVOEC_UTIL_BCC_VALIDATOR_H_ + +#include +#include +#include + +#include "cbor_validator.h" +#include "cppbor.h" + +namespace wvoec { +namespace util { +// Enums and struct to hold EC public key info +enum BccSignatureAlgorithm { + kBccDefaultSignature = 0, + kBccEdDsa = 1, + kBccEcdsaSha256 = 2, + kBccEcdsaSha384 = 3 +}; + +enum BccCurve { + kBccDefaultCurve = 0, + kBccEd25519 = 1, + kBccP256 = 2, + kBccP384 = 3 +}; + +struct BccPublicKeyInfo { + BccSignatureAlgorithm signature_algorithm; + BccCurve curve; + // Raw EC key bytes extracted from BCC + std::vector key_bytes; +}; + +// BccValidator processes a Provisioning 4.0 device root of trust. It extracts +// and validates relevant pieces of information of BCC. +// Relevant documents: +// Android definition: go/remote-provisioning-hal#bcc. +// Google Dice Profile: go/dice-profile +class BccValidator : public CborValidator { + public: + explicit BccValidator() {} + virtual ~BccValidator() override = default; + BccValidator(const BccValidator&) = delete; + BccValidator& operator=(const BccValidator&) = delete; + // 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 + // Widevine device. + virtual CborMessageStatus Validate() override; + // Outputs BCC in YAML. + virtual std::string GetFormattedMessage() const override; + + private: + // Processes CoseKey PubKeyEd25519 / PubKeyECDSA256, prints into |fmt_msgs|, + // and extracts the PubKey to *|public_key_info|. + CborMessageStatus ProcessSubjectPublicKeyInfo( + const cppbor::Map& public_key_info_map, + std::vector& fmt_msgs, BccPublicKeyInfo* public_key_info); + // Processes DiceChainEntryPayload, which contains subject public key, prints + // into |fmt_msgs|, and extracts the PubKey to *|public_key_info|. + CborMessageStatus ProcessDiceChainEntryPayload( + const std::vector& payload, std::vector& fmt_msgs, + BccPublicKeyInfo* public_key_info); + // Verifies the raw EC signature |signature| with the public key + // |signing_key|. |signature| extracted from BCC is not ASN.1 DER encoded. + bool VerifySignature(const BccPublicKeyInfo& signing_key, + const std::vector& message, + const std::vector& signature); + // Used to generate formatted message. + std::stringstream msg_ss_; +}; +} // 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 new file mode 100644 index 00000000..7d4c621b --- /dev/null +++ b/oemcrypto/util/include/cbor_validator.h @@ -0,0 +1,86 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#ifndef WVOEC_UTIL_CBOR_VALIDATOR_H_ +#define WVOEC_UTIL_CBOR_VALIDATOR_H_ + +#include +#include + +#include "cppbor.h" +#include "cppbor_parse.h" + +namespace wvoec { +namespace util { +// CborMessageStatus values are ranked in level of severity. +// kCborUninitialized being the lowest severity, and +// kCborValidateFatal being the highest. +enum CborMessageStatus { + kCborUninitialized = 0, + kCborParseOk = 1, + kCborParseError = 2, + kCborValidateOk = 3, + kCborValidateWarning = 4, + kCborValidateError = 5, + kCborValidateFatal = 6 +}; + +std::string CppborMajorTypeToString(cppbor::MajorType type); +std::string CborMessageStatusToString(CborMessageStatus status); + +class CborValidator { + public: + explicit 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); + const cppbor::ParseResult* GetParseResult() const; + // Returns pretty-printed CBOR for |parse_result_|. Returns empty string if + // |parse_result_| is not valid. + std::string GetRawMessage() const; + + // Verifies the fields in |parse_result_| to have expected types and values. + // Requires that Parse() is called first and |parse_result_| contains a valid + // CBOR message. + virtual CborMessageStatus Validate(); + // Returns all validation messages from Validate(). + const std::vector>& + GetValidateMessages() const { + return validate_messages_; + } + // Prints |parse_result_| in readable format. Requires that Parse() is called + // first and |parse_result_| contains a valid CBOR message. + virtual std::string GetFormattedMessage() const; + const cppbor::ParseResult& parse_result() const { return parse_result_; } + const std::vector>& + validate_messages() { + return validate_messages_; + } + + protected: + void Reset(); + // Writes validation output |msg| to |validate_messages_|, and updates + // |message_status_| if the |status| is more severe than the current value. + void AddValidationMessage(CborMessageStatus status, const std::string& msg); + static const cppbor::Item* GetMapEntry(const cppbor::Map& map, + const std::string& entry_name); + // Checks whether an entry with |entry_name| and |major_type| exists in |map|. + static std::string CheckMapEntry(const cppbor::Map& map, + cppbor::MajorType major_type, + const std::string& entry_name); + CborMessageStatus message_status_ = kCborUninitialized; + + private: + // Internal status of parsing and validating. + cppbor::ParseResult parse_result_ = {}; + std::vector> validate_messages_; +}; +} // namespace util +} // namespace wvoec +#endif // WVOEC_UTIL_CBOR_VALIDATOR_H_ diff --git a/oemcrypto/util/include/device_info_validator.h b/oemcrypto/util/include/device_info_validator.h new file mode 100644 index 00000000..d0d298de --- /dev/null +++ b/oemcrypto/util/include/device_info_validator.h @@ -0,0 +1,54 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#ifndef WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ +#define WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ + +#include +#include +#include + +#include "cbor_validator.h" +#include "cppbor.h" + +namespace wvoec { +namespace util { +// DeviceInfoValidator parses and validates a Cbor struct of DeviceInfo used by +// Provisioning 4.0. DeviceInfo definition: +// 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: + 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( + const std::vector& device_info) override; + // Verifies the Cbor struct of a client generated device info. + virtual CborMessageStatus Validate() override; + // Outputs DeviceInfo in YAML. + virtual std::string GetFormattedMessage() const override; + + private: + // Checks whether a device info entry with |entry_name| and |major_type| + // exists in |device_info| map. + void CheckDeviceInfoMapEntry(const cppbor::Map& device_info, + cppbor::MajorType major_type, + const std::string& entry_name); + // Used to generate formatted message. + std::stringstream msg_ss_; + // Device info version. Validations are done based on the version number. + int version_number_; + // Saved Cbor-encoded device info. + std::vector device_info_bytes_; +}; +} // namespace util +} // namespace wvoec +#endif // WVOEC_UTIL_DEVICE_INFO_VALIDATOR_H_ diff --git a/oemcrypto/util/include/oemcrypto_ecc_key.h b/oemcrypto/util/include/oemcrypto_ecc_key.h index 62df66da..079da6ec 100644 --- a/oemcrypto/util/include/oemcrypto_ecc_key.h +++ b/oemcrypto/util/include/oemcrypto_ecc_key.h @@ -60,6 +60,13 @@ class EccPublicKey { size_t length); static std::unique_ptr Load(const std::string& buffer); static std::unique_ptr Load(const std::vector& buffer); + // Loads EC public key from the |curve| and |buffer|. + // The provided |buffer| must contain an EC point serialized from raw X9.62 + // format. For uncompressed form, it is a 1-byte prefix plus two 32-byte + // integers representing X, Y coordinates. + static std::unique_ptr LoadKeyPoint(EccCurve curve, + const uint8_t* buffer, + size_t length); // Loads a serialized ECC private key, but only converting the public key. static std::unique_ptr LoadPrivateKeyInfo(const uint8_t* buffer, @@ -107,6 +114,15 @@ class EccPublicKey { const std::string& signature) const; OEMCryptoResult VerifySignature(const std::vector& message, const std::vector& signature) const; + // Verifies the raw |signature| matches the provided |message| by the + // private equivalent of this public key. + // A raw ECDSA signature consists of a pair of integers (r,s). The |signature| + // is a concatenation of two octet strings resulting from the integer-to-octet + // encoding of the values of r and s, in the order of (r||s). + OEMCryptoResult VerifyRawSignature(const uint8_t* message, + size_t message_length, + const uint8_t* signature, + size_t signature_length) const; ~EccPublicKey(); @@ -125,6 +141,13 @@ class EccPublicKey { bool InitFromPrivateKeyInfo(const uint8_t* buffer, size_t length); // Initializes the public key object from a private. bool InitFromPrivateKey(const EccPrivateKey& private_key); + // Initializes the public key object from the provided curve and key point + // |buffer|. + bool InitFromKeyPoint(EccCurve curve, const uint8_t* buffer, size_t length); + // Digests the |message| and verifies signature against the provided signature + // point. + OEMCryptoResult DigestAndVerify(const uint8_t* message, size_t message_length, + const ECDSA_SIG* sig_point) const; // OpenSSL/BoringSSL implementation of an ECC key. // As a public key, this will only have key point initialized. diff --git a/oemcrypto/util/include/signed_csr_payload_validator.h b/oemcrypto/util/include/signed_csr_payload_validator.h new file mode 100644 index 00000000..a2d8127f --- /dev/null +++ b/oemcrypto/util/include/signed_csr_payload_validator.h @@ -0,0 +1,44 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#ifndef WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_ +#define WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_ + +#include +#include + +#include "cbor_validator.h" +#include "cppbor.h" + +namespace wvoec { +namespace util { +// SignedCsrPayloadValidator parses and validates a Cbor struct of +// SignedData. The definition of SignedData and CsrPayload can be +// found at: +// https://source.corp.google.com/h/googleplex-android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/generateCertificateRequestV2.cddl +class SignedCsrPayloadValidator : public CborValidator { + public: + explicit SignedCsrPayloadValidator() {} + virtual ~SignedCsrPayloadValidator() override = default; + SignedCsrPayloadValidator(const SignedCsrPayloadValidator&) = delete; + SignedCsrPayloadValidator& operator=(const SignedCsrPayloadValidator&) = + delete; + + // Verifies the Cbor struct of a client generated SignedData. + virtual CborMessageStatus Validate() override; + // Outputs SignedData in YAML. + virtual std::string GetFormattedMessage() const override; + + private: + CborMessageStatus ValidateProtectedParams( + const cppbor::Bstr* protected_params); + CborMessageStatus ValidateDataToBeSigned(const cppbor::Bstr* data); + // Used to generate formatted message. + std::stringstream msg_ss_; +}; +} // namespace util +} // namespace wvoec +#endif // WVOEC_UTIL_SIGNED_CSR_PAYLOAD_VALIDATOR_H_ diff --git a/oemcrypto/util/oec_ref_util.gypi b/oemcrypto/util/oec_ref_util.gypi index 9c1e1e97..1196f5a6 100644 --- a/oemcrypto/util/oec_ref_util.gypi +++ b/oemcrypto/util/oec_ref_util.gypi @@ -5,6 +5,7 @@ 'variables': { 'privacy_crypto_impl%': 'boringssl', 'boringssl_libcrypto_path%': '../../third_party/boringssl/boringssl.gyp:crypto', + 'libcppbor_path%': '../../third_party/libcppbor.gyp:cppbor', }, 'include_dirs': [ '<(oemcrypto_dir)/include', @@ -18,15 +19,22 @@ ], }, 'sources': [ + '<(oemcrypto_dir)/util/src/bcc_validator.cpp', + '<(oemcrypto_dir)/util/src/cbor_validator.cpp', '<(oemcrypto_dir)/util/src/cmac.cpp', + '<(oemcrypto_dir)/util/src/device_info_validator.cpp', '<(oemcrypto_dir)/util/src/hmac.cpp', '<(oemcrypto_dir)/util/src/oemcrypto_drm_key.cpp', '<(oemcrypto_dir)/util/src/oemcrypto_ecc_key.cpp', '<(oemcrypto_dir)/util/src/oemcrypto_key_deriver.cpp', '<(oemcrypto_dir)/util/src/oemcrypto_oem_cert.cpp', '<(oemcrypto_dir)/util/src/oemcrypto_rsa_key.cpp', + '<(oemcrypto_dir)/util/src/signed_csr_payload_validator.cpp', '<(oemcrypto_dir)/util/src/wvcrc.cpp', ], + 'dependencies': [ + '<(libcppbor_path)', + ], 'includes': [ '../../util/libcrypto_dependency.gypi', ], diff --git a/oemcrypto/util/oec_ref_util_unittests.gypi b/oemcrypto/util/oec_ref_util_unittests.gypi index 095f9015..eea92f72 100644 --- a/oemcrypto/util/oec_ref_util_unittests.gypi +++ b/oemcrypto/util/oec_ref_util_unittests.gypi @@ -15,7 +15,9 @@ ], }, 'sources': [ + '<(oemcrypto_dir)/util/test/bcc_validator_unittest.cpp', '<(oemcrypto_dir)/util/test/cmac_unittest.cpp', + '<(oemcrypto_dir)/util/test/device_info_validator_unittest.cpp', '<(oemcrypto_dir)/util/test/hmac_unittest.cpp', '<(oemcrypto_dir)/util/test/oem_cert_test.cpp', '<(oemcrypto_dir)/util/test/oemcrypto_ecc_key_unittest.cpp', @@ -23,5 +25,9 @@ '<(oemcrypto_dir)/util/test/oemcrypto_ref_test_utils.cpp', '<(oemcrypto_dir)/util/test/oemcrypto_rsa_key_unittest.cpp', '<(oemcrypto_dir)/util/test/oemcrypto_wvcrc32_unittest.cpp', + '<(oemcrypto_dir)/util/test/signed_csr_payload_validator_unittest.cpp', + ], + 'dependencies': [ + '<(third_party_path)/libcppbor.gyp:cppbor', ], } diff --git a/oemcrypto/util/src/bcc_validator.cpp b/oemcrypto/util/src/bcc_validator.cpp new file mode 100644 index 00000000..e74354f8 --- /dev/null +++ b/oemcrypto/util/src/bcc_validator.cpp @@ -0,0 +1,564 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include "bcc_validator.h" + +#include + +#include +#include +#include +#include +#include + +#include + +#include "oemcrypto_ecc_key.h" +#include "string_conversions.h" + +namespace wvoec { +namespace util { +namespace { +// The BCC is encoded using RFC 8949- Concise Binary Object Representation +// (CBOR). + +// The full definition of the following enums can be found here: +// go/remote-provisioning-hal#bcc. + +// The device key is encoded in a cbor map. The key values are a mix of +// positive and negative integer values. +enum { + MAP_KEY_DEVICE_KEY_TYPE = 1, + MAP_KEY_DEVICE_KEY_ALGORITHM = 3, + MAP_KEY_DEVICE_KEY_OPS = 4, + MAP_KEY_DEVICE_KEY_CURVE = -1, + MAP_KEY_DEVICE_KEY_BYTES_0 = -2, + MAP_KEY_DEVICE_KEY_BYTES_1 = -3, +}; + +// The device key may be encoded in the BCC as either X,Y elliptic curve +// coordinates, or as raw bytes. The value is identified using +// MAP_KEY_DEVICE_KEY_TYPE. +enum { + DEVICE_KEY_ENCODING_UNKNOWN = 0, + DEVICE_KEY_BYTE_STRING = 1, + DEVICE_KEY_OCTET_PAIR = 2, +}; + +// Android/Widevine Dice Attestation allows two signing models. This is +// identified using MAP_KEY_DEVICE_KEY_ALGORITHM. +enum { + DEVICE_KEY_ALGORITHM_ES256 = -7, // EC key with SHA-256 + DEVICE_KEY_ALGORITHM_EDDSA = -8, // Pure ED25519. + DEVICE_KEY_ALGORITHM_ES384 = -35, // EC key with SHA-384 +}; + +// The curve used to generate the device public key is identified using the +// MAP_KEY_DEVICE_KEY_CURVE. +enum { + DEVICE_KEY_CURVE_P256 = 1, + DEVICE_KEY_CURVE_P384 = 2, + DEVICE_KEY_CURVE_ED25519 = 6, +}; + +// Sized to hold a component of a P256 public key uncompressed point compatible +// with X9.62. The key is formatted in an Z/X/Y format in which Z == 0x04 and X +// and Y are the public key coordinates. X and Y are each 32 bytes. +constexpr int kP256KeyComponentSize = 256 / 8; +// Sized to hold a P384 public key uncompressed point compatible with X9.62. +// The key is formatted in an Z/X/Y format in which Z == 0x04 and X and Y are +// the public key coordinates. X and Y are each 48 bytes. +constexpr int kP384KeyComponentSize = 384 / 8; +constexpr int kMarshaledP256KeySize = kP256KeyComponentSize * 2 + 1; +constexpr int kMarshaledP384KeySize = kP384KeyComponentSize * 2 + 1; +constexpr char kMarshaledECKeyZValue = 0x04; +constexpr int kED25519KeyDataItemSize = 32; +// The Issuer field key in BccEntryPayload. +constexpr int64_t kIssuer = 1; +// The Subject field key in BccEntryPayload. +constexpr int64_t kSubject = 2; +// The SubjectPublicKey field key in BccEntryPayload. +constexpr int64_t kSubjectPublicKey = -4670552; +// This signature context is defined by COSE SIGN1. +constexpr char kSignatureContextString[] = "Signature1"; + +struct IssuerSubject { + std::string issuer; + std::string subject; + bool IsValid() const { return !issuer.empty() && !subject.empty(); } + void PrintTo(std::vector& fmt_msgs) const { + fmt_msgs.push_back("Issuer: "); + fmt_msgs.back().append(issuer.empty() ? "" : issuer); + fmt_msgs.push_back("Subject: "); + fmt_msgs.back().append(subject.empty() ? "" : subject); + } +}; + +IssuerSubject GetIssuerSubjectFromBccEntryPayload( + const cppbor::Map* bcc_entry_payload) { + IssuerSubject ret; + for (size_t i = 0; i < bcc_entry_payload->size(); ++i) { + const auto& entry = (*bcc_entry_payload)[i]; + if (entry.first == nullptr || entry.first->asInt() == nullptr || + entry.second == nullptr || entry.second->asTstr() == nullptr) { + continue; + } + const auto& value = entry.second->asTstr()->value(); + if (entry.first->asInt()->value() == kIssuer) { + ret.issuer = value.empty() ? "" : value; + } else if (entry.first->asInt()->value() == kSubject) { + ret.subject = value.empty() ? "" : value; + } + } + return ret; +} + +const cppbor::Bstr* GetSubjectPublicKeyFromBccEntryPayload( + const cppbor::Map* bcc_entry_payload) { + for (size_t i = 0; i < bcc_entry_payload->size(); ++i) { + const auto& entry = (*bcc_entry_payload)[i]; + if (entry.first == nullptr || entry.first->asInt() == nullptr || + entry.second == nullptr) { + continue; + } + if (entry.first->asInt()->value() == kSubjectPublicKey) { + return entry.second->asBstr(); + } + } + return nullptr; +} + +void AddMessages(std::stringstream& ss, + const std::vector& fmt_msgs, int indent) { + const std::string spaces = std::string(indent * 2, ' '); + for (auto& msg : fmt_msgs) { + ss << spaces << msg << "\n"; + } +} +} // namespace + +bool BccValidator::VerifySignature(const BccPublicKeyInfo& signing_key, + const std::vector& message, + const std::vector& signature) { + if (signing_key.signature_algorithm == kBccEdDsa) { + constexpr size_t kEd25519SignatureLength = 64; + // ED25519 incorporates SHA512 into the signing algorithm. + if (signature.size() != kEd25519SignatureLength) { + AddValidationMessage( + kCborValidateError, + "Signature has unexpected size: " + std::to_string(signature.size())); + return false; + } + EVP_PKEY* pkey = nullptr; + if ((pkey = EVP_PKEY_new_raw_public_key( + EVP_PKEY_ED25519, nullptr, + reinterpret_cast(signing_key.key_bytes.data()), + signing_key.key_bytes.size())) == nullptr) { + AddValidationMessage( + kCborValidateError, + "Can not create EVP_PKEY_ED25519 from the public key info."); + return false; + } + EVP_MD_CTX* md_ctx = EVP_MD_CTX_new(); + const bool res = + EVP_DigestVerifyInit(md_ctx, nullptr, nullptr, nullptr, pkey) && + EVP_DigestVerify(md_ctx, signature.data(), signature.size(), + message.data(), message.size()) == 1; + EVP_MD_CTX_free(md_ctx); + EVP_PKEY_free(pkey); + return res; + } + if (signing_key.signature_algorithm == kBccEcdsaSha256 || + signing_key.signature_algorithm == kBccEcdsaSha384) { + const EccCurve curve = (signing_key.signature_algorithm == kBccEcdsaSha256) + ? EccCurve::kEccSecp256r1 + : EccCurve::kEccSecp384r1; + std::unique_ptr key = EccPublicKey::LoadKeyPoint( + curve, reinterpret_cast(signing_key.key_bytes.data()), + signing_key.key_bytes.size()); + if (!key) { + AddValidationMessage(kCborValidateError, + "Can not create ECPublicKey from raw EC KeyPoint."); + return false; + } + const OEMCryptoResult res = key->VerifyRawSignature( + message.data(), message.size(), signature.data(), signature.size()); + return (res == OEMCrypto_SUCCESS); + } + AddValidationMessage(kCborValidateError, + "Unknown signature algorithm: " + + std::to_string(signing_key.signature_algorithm)); + return false; +} + +CborMessageStatus BccValidator::Validate() { + if (message_status_ != kCborParseOk) return message_status_; + const cppbor::Item* parsed_bcc = std::get<0>(parse_result()).get(); + if (parsed_bcc == nullptr) { + AddValidationMessage(kCborValidateFatal, "BCC is empty."); + return message_status_; + } + if (parsed_bcc->asArray() == nullptr) { + AddValidationMessage(kCborValidateFatal, + "BCC is not a CBOR array. Actual type: " + + CppborMajorTypeToString(parsed_bcc->type())); + return message_status_; + } + const cppbor::Array* bcc_array = parsed_bcc->asArray(); + if (bcc_array->size() < 2) { + AddValidationMessage(kCborValidateFatal, + "BCC should contain at least two elements. Actual: " + + std::to_string(bcc_array->size())); + return message_status_; + } + + // Writes YAML-formatted output to |msg_ss_| during validation. + msg_ss_.str(std::string()); + msg_ss_ << "---" + << "\n"; + msg_ss_ << "DEVICE PUBLIC KEY:\n"; + + // The first element in the array contains the root device public key + // definition. + const cppbor::Map* device_public_key_map = (*bcc_array)[0]->asMap(); + if (device_public_key_map == nullptr) { + AddValidationMessage( + kCborValidateFatal, + "Device public key info is not a CBOR map. Actual type: " + + CppborMajorTypeToString((*bcc_array)[0]->type())); + return message_status_; + } + BccPublicKeyInfo root_pub_key; + std::vector key_value_texts; // for pretty print + CborMessageStatus status = ProcessSubjectPublicKeyInfo( + *device_public_key_map, key_value_texts, &root_pub_key); + AddMessages(msg_ss_, key_value_texts, 1); + if (status == kCborValidateFatal) return status; + + BccPublicKeyInfo leaf_pub_key = root_pub_key; + msg_ss_ << "BCC ENTRY:\n"; + // Parse and verify each certificate in the chain. The structure of thr + // entries are COSE_Sign1 (untagged). leaf_pub_key is being updated while we + // process the chain. + for (size_t i = 1; i < bcc_array->size(); ++i) { + msg_ss_ << "- CDI PUBLIC KEY INDEX: " << i << "\n"; + const cppbor::Array* bcc_entry = (*bcc_array)[i]->asArray(); + if (bcc_entry == nullptr) { + AddValidationMessage(kCborValidateFatal, + "BCC entry is empty at index " + std::to_string(i)); + return message_status_; + } + if (bcc_entry->size() != 4) { + AddValidationMessage(kCborValidateFatal, + "BCC entry should contain 4 items. Actual: " + + std::to_string(bcc_entry->size())); + return message_status_; + } + // Skip CoseSign1 signature verification here, only extract pub keys + if ((*bcc_entry)[0]->type() != cppbor::BSTR || + (*bcc_entry)[1]->type() != cppbor::MAP || + (*bcc_entry)[2]->type() != cppbor::BSTR || + (*bcc_entry)[3]->type() != cppbor::BSTR) { + AddValidationMessage(kCborValidateFatal, "Invalid BCC entry type."); + return message_status_; + } + + // Signature verification Step 1: construct and encode signature input + const std::vector& protected_bytes = + (*bcc_entry)[0]->asBstr()->value(); + // Index 1 is unprotected parameters, which is ignored. + const std::vector& payload = (*bcc_entry)[2]->asBstr()->value(); + const std::vector& actual_signature = + (*bcc_entry)[3]->asBstr()->value(); + + const std::vector signature_input = + cppbor::Array() + .add(kSignatureContextString) + .add(protected_bytes) + .add(/* AAD */ std::vector()) + .add(payload) + .encode(); + + // Signature verification Step 2: verify + if (!VerifySignature(leaf_pub_key, signature_input, actual_signature)) { + AddValidationMessage( + kCborValidateError, + "Failed to verify the signature for BCC entry index: " + + std::to_string(i)); + } + + key_value_texts.clear(); + BccPublicKeyInfo entry_pub_key; + status = + ProcessDiceChainEntryPayload(payload, key_value_texts, &entry_pub_key); + AddMessages(msg_ss_, key_value_texts, 1); + if (status == kCborValidateFatal) return status; + leaf_pub_key = std::move(entry_pub_key); + } + // If the size of the BCC array (including device pub key) is 2, then it + // must be a de-generated BCC, which means the second element in the array + // is a self-signed entry. The entry's public key should be identical to the + // device's public key. + if (bcc_array->size() == 2) { + // self-signed BCC entry + if (leaf_pub_key.key_bytes != root_pub_key.key_bytes) { + AddValidationMessage(kCborValidateError, + "The public key of a self-signed entry should be " + "identical to its device public key."); + } + } + msg_ss_ << "...\n"; + if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk; + return message_status_; +} + +CborMessageStatus BccValidator::ProcessSubjectPublicKeyInfo( + const cppbor::Map& public_key_info_map, std::vector& fmt_msgs, + BccPublicKeyInfo* public_key_info) { + int key_encoding_format = DEVICE_KEY_ENCODING_UNKNOWN; + std::vector device_key_bytes_0; + std::vector device_key_bytes_1; + std::unordered_set key_set; + for (size_t index = 0; index < public_key_info_map.size(); ++index) { + std::pair&, + const std::unique_ptr&> + entry = public_key_info_map[index]; + if (entry.first->type() != cppbor::NINT && + entry.first->type() != cppbor::UINT) { + AddValidationMessage(kCborValidateFatal, + "Invalid key type in public key info map: " + + CppborMajorTypeToString(entry.first->type())); + return kCborValidateFatal; + } + const int64_t map_key = entry.first->asInt()->value(); + switch (map_key) { + case MAP_KEY_DEVICE_KEY_TYPE: { + if (entry.second->type() != cppbor::UINT) { + AddValidationMessage( + kCborValidateFatal, + "Invalid value type in public key info map for " + "key MAP_KEY_DEVICE_KEY_TYPE: " + + CppborMajorTypeToString(entry.second->type())); + return kCborValidateFatal; + } + std::string kv = "key encoding format: "; + const int64_t value = entry.second->asUint()->value(); + if (value == DEVICE_KEY_OCTET_PAIR) { + key_encoding_format = DEVICE_KEY_OCTET_PAIR; + kv += "DEVICE_KEY_OCTET_PAIR"; + } else if (value == DEVICE_KEY_BYTE_STRING) { + key_encoding_format = DEVICE_KEY_BYTE_STRING; + kv += "DEVICE_KEY_BYTE_STRING"; + } else { + AddValidationMessage(kCborValidateError, + "Invalid value in public key info map for key " + "MAP_KEY_DEVICE_KEY_TYPE: " + + std::to_string(value)); + } + fmt_msgs.push_back(kv); + } break; + case MAP_KEY_DEVICE_KEY_ALGORITHM: { + if (entry.second->type() != cppbor::NINT) { + AddValidationMessage( + kCborValidateFatal, + "Invalid value type in public key info map for " + "key MAP_KEY_DEVICE_KEY_ALGORITHM: " + + CppborMajorTypeToString(entry.second->type())); + return kCborValidateFatal; + } + std::string kv = "key algorithm type: "; + const int64_t value = entry.second->asNint()->value(); + if (value == DEVICE_KEY_ALGORITHM_ES256) { + kv += "ECDSA_SHA256"; + public_key_info->signature_algorithm = kBccEcdsaSha256; + } else if (value == DEVICE_KEY_ALGORITHM_ES384) { + kv += "ECDSA_SHA384"; + public_key_info->signature_algorithm = kBccEcdsaSha384; + } else if (value == DEVICE_KEY_ALGORITHM_EDDSA) { + kv += "EDDSA"; + public_key_info->signature_algorithm = kBccEdDsa; + } else { + AddValidationMessage(kCborValidateError, + "Invalid value in public key info map for key " + "MAP_KEY_DEVICE_KEY_ALGORITHM: " + + std::to_string(value)); + } + fmt_msgs.push_back(kv); + } break; + case MAP_KEY_DEVICE_KEY_OPS: + // The OPS is an array. Ignored for now. + break; + case MAP_KEY_DEVICE_KEY_CURVE: { + if (entry.second->type() != cppbor::UINT) { + AddValidationMessage( + kCborValidateFatal, + "Invalid value type in public key info map for " + "key MAP_KEY_DEVICE_KEY_CURVE: " + + CppborMajorTypeToString(entry.second->type())); + return kCborValidateFatal; + } + std::string kv = "curve: "; + const int64_t value = entry.second->asUint()->value(); + if (value == DEVICE_KEY_CURVE_P256) { + public_key_info->curve = kBccP256; + kv += "P256"; + } else if (value == DEVICE_KEY_CURVE_P384) { + public_key_info->curve = kBccP384; + kv += "P384"; + } else if (value == DEVICE_KEY_CURVE_ED25519) { + public_key_info->curve = kBccEd25519; + kv += "ED25519"; + } else { + AddValidationMessage(kCborValidateError, + "Invalid value in public key info map for key " + "MAP_KEY_DEVICE_KEY_CURVE: " + + std::to_string(value)); + } + fmt_msgs.push_back(kv); + } break; + case MAP_KEY_DEVICE_KEY_BYTES_0: + case MAP_KEY_DEVICE_KEY_BYTES_1: + // BCC encodes keys as either two X, Y octet strings or a single + // octet string. The format used depends on the key type. + if (entry.second->type() != cppbor::BSTR) { + AddValidationMessage( + kCborValidateFatal, + "Invalid value type in public key info map for " + "key MAP_KEY_DEVICE_KEY_BYTES_0/1: " + + CppborMajorTypeToString(entry.second->type())); + return kCborValidateFatal; + } + const std::vector& key_bytes = entry.second->asBstr()->value(); + // Key byte length depends upon the key type. + if (key_bytes.size() != kED25519KeyDataItemSize && + key_bytes.size() != kP256KeyComponentSize && + key_bytes.size() != kP384KeyComponentSize) { + AddValidationMessage(kCborValidateFatal, + "Malformed public key data size of: " + + std::to_string(key_bytes.size())); + return kCborValidateFatal; + } + if (map_key == MAP_KEY_DEVICE_KEY_BYTES_0) { + device_key_bytes_0 = key_bytes; + } else { + device_key_bytes_1 = key_bytes; + } + } + key_set.insert(map_key); + } + if (key_set.find(MAP_KEY_DEVICE_KEY_TYPE) == key_set.end()) { + AddValidationMessage(kCborValidateError, + "Missing MAP_KEY_DEVICE_KEY_TYPE."); + } + if (key_set.find(MAP_KEY_DEVICE_KEY_ALGORITHM) == key_set.end()) { + AddValidationMessage(kCborValidateError, + "Missing MAP_KEY_DEVICE_KEY_ALGORITHM."); + } + if (key_set.find(MAP_KEY_DEVICE_KEY_CURVE) == key_set.end()) { + AddValidationMessage(kCborValidateError, + "Missing MAP_KEY_DEVICE_KEY_CURVE."); + } + if (device_key_bytes_0.empty() || + (key_encoding_format == DEVICE_KEY_OCTET_PAIR && + device_key_bytes_1.empty())) { + AddValidationMessage( + kCborValidateFatal, + "Malformed public key definition. Missing device public key bytes."); + return kCborValidateFatal; + } + std::vector device_key_bytes; + if (key_encoding_format == DEVICE_KEY_OCTET_PAIR) { + // Key is an ECDSA elliptic key. We need to return the ANSI X9.62 + // marshaled public key. Generate the marshaled key if needed. The + // marshaled key is needed to create an ECPublicKey object. + device_key_bytes.push_back(kMarshaledECKeyZValue); + device_key_bytes.insert(device_key_bytes.end(), device_key_bytes_0.begin(), + device_key_bytes_0.end()); + device_key_bytes.insert(device_key_bytes.end(), device_key_bytes_1.begin(), + device_key_bytes_1.end()); + if (device_key_bytes.size() != kMarshaledP384KeySize && + device_key_bytes.size() != kMarshaledP256KeySize) { + AddValidationMessage(kCborValidateFatal, + "Invalid ECDSA public key size: " + + std::to_string(device_key_bytes.size())); + return kCborValidateFatal; + } + } else { + device_key_bytes = std::move(device_key_bytes_0); + } + fmt_msgs.push_back("public key bytes: " + wvutil::b2a_hex(device_key_bytes)); + public_key_info->key_bytes = std::move(device_key_bytes); + return message_status_; +} + +CborMessageStatus BccValidator::ProcessDiceChainEntryPayload( + const std::vector& payload, std::vector& fmt_msgs, + BccPublicKeyInfo* entry_public_key_info) { + if (payload.empty()) { + AddValidationMessage(kCborValidateFatal, "Empty bcc entry payload."); + return kCborValidateFatal; + } + auto parse_result = cppbor::parse(payload); + std::unique_ptr item = std::move(std::get<0>(parse_result)); + std::string error_message = std::move(std::get<2>(parse_result)); + if (item == nullptr || !error_message.empty()) { + AddValidationMessage(kCborValidateFatal, + "Unable to parse bcc entry payload: " + error_message); + return kCborValidateFatal; + } + if (item->type() != cppbor::MAP) { + AddValidationMessage(kCborValidateFatal, + "Unexpected bcc entry payload type: " + + CppborMajorTypeToString(item->type())); + return kCborValidateFatal; + } + const IssuerSubject issuer_subject = + GetIssuerSubjectFromBccEntryPayload(item->asMap()); + if (!issuer_subject.IsValid()) { + AddValidationMessage(kCborValidateError, "Missing Issuer or Subject."); + } + issuer_subject.PrintTo(fmt_msgs); + const cppbor::Bstr* subject_public_key = + GetSubjectPublicKeyFromBccEntryPayload(item->asMap()); + if (subject_public_key == nullptr) { + AddValidationMessage(kCborValidateFatal, + "Bcc entry payload has no subject public key."); + return kCborValidateFatal; + } + + // Now parse the serialized subject public key. + parse_result = cppbor::parse(subject_public_key->value()); + item = std::move(std::get<0>(parse_result)); + error_message = std::move(std::get<2>(parse_result)); + if (item == nullptr || !error_message.empty()) { + AddValidationMessage( + kCborValidateFatal, + "Unable to parse serialized subject public key: " + error_message); + return kCborValidateFatal; + } + const cppbor::Map* subject_public_key_info = item->asMap(); + if (subject_public_key_info == nullptr) { + AddValidationMessage(kCborValidateFatal, + "Invalid subject public key type. Expected Map."); + return kCborValidateFatal; + } + return ProcessSubjectPublicKeyInfo(*subject_public_key_info, fmt_msgs, + entry_public_key_info); +} + +std::string BccValidator::GetFormattedMessage() const { + if (message_status_ == kCborUninitialized || + message_status_ == kCborParseError) { + return std::string(); + } + const cppbor::Item* parsed_item = std::get<0>(parse_result()).get(); + if (parsed_item == nullptr) { + return ""; + } + return msg_ss_.str(); +} +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/src/cbor_validator.cpp b/oemcrypto/util/src/cbor_validator.cpp new file mode 100644 index 00000000..dce72a82 --- /dev/null +++ b/oemcrypto/util/src/cbor_validator.cpp @@ -0,0 +1,140 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include "cbor_validator.h" + +#include + +#include +#include +#include +#include +#include + +namespace wvoec { +namespace util { +std::string CppborMajorTypeToString(cppbor::MajorType type) { + switch (type) { + case cppbor::UINT: + return "UINT"; + case cppbor::NINT: + return "NINT"; + case cppbor::BSTR: + return "BSTR"; + case cppbor::TSTR: + return "TSTR"; + case cppbor::ARRAY: + return "ARRAY"; + case cppbor::MAP: + return "MAP"; + case cppbor::SEMANTIC: + return "SEMANTIC"; + case cppbor::SIMPLE: + return "SIMPLE"; + } + return "undefined type"; +} + +std::string CborMessageStatusToString(CborMessageStatus status) { + switch (status) { + case kCborUninitialized: + return "Uninitialized"; + case kCborParseOk: + return "ParseOk"; + case kCborParseError: + return "ParseError"; + case kCborValidateOk: + return "ValidateOk"; + case kCborValidateWarning: + return "ValidateWarning"; + case kCborValidateError: + return "ValidateError"; + case kCborValidateFatal: + return "ValidateFatal"; + } + return "undefined status"; +} + +void CborValidator::Reset() { + message_status_ = kCborUninitialized; + parse_result_ = {nullptr, nullptr, ""}; + validate_messages_.clear(); +} + +CborMessageStatus CborValidator::Parse(const std::vector& cbor) { + Reset(); + parse_result_ = cppbor::parse(cbor); + message_status_ = + (std::get<0>(parse_result_) && std::get<2>(parse_result_).empty()) + ? kCborParseOk + : kCborParseError; + return message_status_; +} + +const cppbor::ParseResult* CborValidator::GetParseResult() const { + if (message_status_ == kCborUninitialized) { + return nullptr; + } + return &parse_result_; +} + +std::string CborValidator::GetRawMessage() const { + if (message_status_ == kCborUninitialized || + message_status_ == kCborParseError) { + return std::string(); + } + const cppbor::Item* parsed_item = std::get<0>(parse_result_).get(); + if (parsed_item == nullptr) { + return ""; + } + return cppbor::prettyPrint(parsed_item); +} + +CborMessageStatus CborValidator::Validate() { + if (message_status_ != kCborParseOk) return message_status_; + // No other validations to be done than Parse() being successful. + AddValidationMessage(kCborValidateOk, "No validations are done."); + return message_status_; +} + +std::string CborValidator::GetFormattedMessage() const { + return GetRawMessage(); +} + +void CborValidator::AddValidationMessage(CborMessageStatus status, + const std::string& msg) { + validate_messages_.push_back({status, msg}); + if (status > message_status_) message_status_ = status; +} + +// TODO(b/314141962): Replace this with the map lookup function in cppbor +// library +const cppbor::Item* CborValidator::GetMapEntry(const cppbor::Map& map, + const std::string& entry_name) { + for (auto const& entry : map) { + if (!entry.first->asTstr()) continue; + const std::string& name = entry.first->asTstr()->value(); + if (name == entry_name) return entry.second.get(); + } + return nullptr; +} + +std::string CborValidator::CheckMapEntry(const cppbor::Map& map, + cppbor::MajorType major_type, + const std::string& entry_name) { + const cppbor::Item* value = GetMapEntry(map, entry_name); + if (!value) { + return entry_name + " is missing."; + } + if (value->type() != major_type) { + return entry_name + " has the wrong type. Expect: " + + CppborMajorTypeToString(major_type) + + ", actual: " + CppborMajorTypeToString(value->type()); + } + return ""; +} +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/src/device_info_validator.cpp b/oemcrypto/util/src/device_info_validator.cpp new file mode 100644 index 00000000..9877f43b --- /dev/null +++ b/oemcrypto/util/src/device_info_validator.cpp @@ -0,0 +1,227 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include "device_info_validator.h" + +#include + +#include "string_conversions.h" + +namespace wvoec { +namespace util { +namespace { +// Number of required device info properties returned from TEE for DeviceInfo +// version v3. +constexpr uint32_t kNumTeeDeviceInfoEntriesV3 = 14; +const std::vector kDeviceInfoKeysV3 = {"brand", + "manufacturer", + "product", + "model", + "device", + "vb_state", + "bootloader_state", + "vbmeta_digest", + "os_version", + "system_patch_level", + "boot_patch_level", + "vendor_patch_level", + "security_level", + "fused"}; + +struct AttestationIdEntry { + const char* id; + bool alwaysValidate; +}; + +// Attestation Id and whether it is required. +constexpr AttestationIdEntry kAttestationIdEntrySet[] = {{"brand", false}, + {"manufacturer", true}, + {"product", true}, + {"model", true}, + {"device", false}}; +} // namespace + +CborMessageStatus DeviceInfoValidator::Parse( + const std::vector& device_info) { + message_status_ = CborValidator::Parse(device_info); + device_info_bytes_ = device_info; + return message_status_; +} + +CborMessageStatus DeviceInfoValidator::Validate() { + if (message_status_ != kCborParseOk) return message_status_; + std::unique_ptr parsed_device_info = + std::get<0>(parse_result())->clone(); + if (!parsed_device_info) { + AddValidationMessage(kCborValidateFatal, "Device info is empty."); + return message_status_; + } + cppbor::Map* device_info_map = parsed_device_info->asMap(); + if (!device_info_map) { + AddValidationMessage( + kCborValidateFatal, + "Device info is not a CBOR map. Actual type: " + + CppborMajorTypeToString(parsed_device_info->type())); + return message_status_; + } + if (device_info_map->canonicalize().encode() != device_info_bytes_) { + AddValidationMessage(kCborValidateError, + "Device info ordering is non-canonical."); + } + const cppbor::Item* security_level = + GetMapEntry(*device_info_map, "security_level"); + const bool is_tee_device_info = security_level && security_level->asTstr() && + security_level->asTstr()->value() == "tee"; + std::set previous_keys; + switch (version_number_) { + case 3: + if (is_tee_device_info && + device_info_map->size() != kNumTeeDeviceInfoEntriesV3) { + AddValidationMessage( + kCborValidateError, + "Incorrect number of TEE device info entries. Expected " + + std::to_string(kNumTeeDeviceInfoEntriesV3) + " but got " + + std::to_string(device_info_map->size())); + } + // TEE IRPC instances require all entries to be present in device info. + // Non-TEE instances may omit `os_version`. + if (!is_tee_device_info && + (device_info_map->size() != kNumTeeDeviceInfoEntriesV3 && + device_info_map->size() != kNumTeeDeviceInfoEntriesV3 - 1)) { + AddValidationMessage( + kCborValidateError, + "Incorrect number of non-TEE device info entries. Expected " + + std::to_string(kNumTeeDeviceInfoEntriesV3 - 1) + " but got " + + std::to_string(device_info_map->size())); + } + for (auto const& entry : *device_info_map) { + if (!entry.first->asTstr()) { + AddValidationMessage( + kCborValidateError, + "Unexpected entry key type. Expected TSTR, but got " + + CppborMajorTypeToString(entry.first->type())); + continue; + } + const std::string& key = entry.first->asTstr()->value(); + if (previous_keys.find(key) != previous_keys.end()) { + AddValidationMessage(kCborValidateError, + "Duplicate device info entry: " + key); + } + previous_keys.insert(key); + if (std::find(kDeviceInfoKeysV3.begin(), kDeviceInfoKeysV3.end(), + key) == kDeviceInfoKeysV3.end()) { + AddValidationMessage(kCborValidateError, + "Unrecognized device info entry: " + key); + } + } + // Checks for the required fields that only apply to v3. + CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, + "system_patch_level"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, + "boot_patch_level"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, + "vendor_patch_level"); + // Fall through + CORE_UTIL_FALLTHROUGH; + case 2: + for (const auto& entry : kAttestationIdEntrySet) { + if (entry.alwaysValidate) { + CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, entry.id); + } + } + CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "vb_state"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, + "bootloader_state"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::BSTR, "vbmeta_digest"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, + "system_patch_level"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, + "boot_patch_level"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, + "vendor_patch_level"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::UINT, "fused"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "security_level"); + if (is_tee_device_info) { + CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "os_version"); + } + break; + case 1: + CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "security_level"); + CheckDeviceInfoMapEntry(*device_info_map, cppbor::TSTR, "att_id_state"); + break; + default: + AddValidationMessage( + kCborValidateFatal, + "Unrecognized version: " + std::to_string(version_number_)); + return message_status_; + } + + if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk; + return message_status_; +} + +void DeviceInfoValidator::CheckDeviceInfoMapEntry( + const cppbor::Map& device_info, cppbor::MajorType major_type, + const std::string& entry_name) { + const std::string error = CheckMapEntry(device_info, major_type, entry_name); + if (!error.empty()) { + AddValidationMessage(kCborValidateError, error); + } +} + +std::string DeviceInfoValidator::GetFormattedMessage() const { + if (message_status_ == kCborUninitialized || + message_status_ == kCborParseError || + message_status_ == kCborValidateFatal) { + return std::string(); + } + const cppbor::Item* parsed_item = std::get<0>(parse_result()).get(); + if (parsed_item == nullptr) { + return ""; + } + // Writes YAML-formatted output to |msg_ss_|. + std::stringstream msg_ss; + msg_ss << "---\n"; + msg_ss << "DEVICE INFO MAP:\n"; + + for (auto const& entry : *(parsed_item->asMap())) { + auto const& entry_value = entry.second; + // Device info map only allows TSTR key type. + if (!entry.first->asTstr()) continue; + const std::string& name = entry.first->asTstr()->value(); + msg_ss << " " << name << ": "; + switch (entry_value->type()) { + case cppbor::TSTR: { + const std::string val = entry_value->asTstr()->value().empty() + ? "" + : entry_value->asTstr()->value(); + msg_ss << val << "\n"; + break; + } + case cppbor::UINT: + msg_ss << std::to_string(entry_value->asUint()->value()) << "\n"; + break; + case cppbor::NINT: + msg_ss << std::to_string(entry_value->asNint()->value()) << "\n"; + break; + case cppbor::BSTR: { + const std::vector& bytes = entry_value->asBstr()->value(); + const std::string val = + bytes.empty() ? "" : wvutil::b2a_hex(bytes); + msg_ss << val << "\n"; + break; + } + default: + msg_ss << "Unsupported type (" + << CppborMajorTypeToString(entry_value->type()) << ")\n"; + break; + } + } + msg_ss << "...\n"; + return msg_ss.str(); +} +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/src/oemcrypto_ecc_key.cpp b/oemcrypto/util/src/oemcrypto_ecc_key.cpp index e226ed3a..47a4655d 100644 --- a/oemcrypto/util/src/oemcrypto_ecc_key.cpp +++ b/oemcrypto/util/src/oemcrypto_ecc_key.cpp @@ -44,6 +44,7 @@ using ScopedEvpPkey = ScopedObject; using ScopedPrivateKeyInfo = ScopedObject; using ScopedSigPoint = ScopedObject; +using ScopedEcPoint = ScopedObject; const EC_GROUP* GetEcGroup(EccCurve curve) { // Creating a named EC_GROUP is an expensive operation, and they @@ -140,6 +141,45 @@ EccCurve GetCurveFromKeyGroup(const EC_KEY* key) { return kEccCurveUnknown; } +// Creates EC public key from |curve| and |key_point|, and sets the result in +// *|public_key|. +bool GetPublicKeyFromKeyPoint(EccCurve curve, const uint8_t* key_point, + size_t key_point_length, EC_KEY** public_key) { + if (key_point == nullptr || key_point_length == 0) { + return false; + } + const EC_GROUP* group = GetEcGroup(curve); + if (!group) { + LOGE("Failed to get ECC group for curve %d", curve); + return false; + } + ScopedEcPoint point(EC_POINT_new(group)); + if (!point) { + LOGE("Failed to new EC_POINT"); + return false; + } + if (!EC_POINT_oct2point(group, point.get(), key_point, key_point_length, + nullptr)) { + LOGE("Failed to convert the serialized point to EC_POINT"); + return false; + } + ScopedEcKey key(EC_KEY_new()); + if (!key) { + LOGE("Failed to allocate key"); + return false; + } + if (!EC_KEY_set_group(key.get(), group)) { + LOGE("Failed to set group"); + return false; + } + if (EC_KEY_set_public_key(key.get(), point.get()) == 0) { + LOGE("Failed to convert the EC_POINT to EC_KEY"); + return false; + } + *public_key = key.release(); + return true; +} + // Compares the public EC points of both keys to see if they are the // equal. // Both |public_key| and |private_key| must be of the same group. @@ -457,6 +497,26 @@ std::unique_ptr EccPublicKey::LoadPrivateKeyInfo( return LoadPrivateKeyInfo(buffer.data(), buffer.size()); } +// static +std::unique_ptr EccPublicKey::LoadKeyPoint(EccCurve curve, + const uint8_t* buffer, + size_t length) { + if (buffer == nullptr) { + LOGE("Provided key point buffer is null"); + return nullptr; + } + if (length == 0) { + LOGE("Provided key point buffer is zero length"); + return nullptr; + } + std::unique_ptr key(new EccPublicKey()); + if (!key->InitFromKeyPoint(curve, buffer, length)) { + LOGE("Failed to initialize public key from KeyPoint"); + key.reset(); + } + return key; +} + bool EccPublicKey::IsMatchingPrivateKey( const EccPrivateKey& private_key) const { if (private_key.curve() != curve_) { @@ -486,7 +546,7 @@ OEMCryptoResult EccPublicKey::VerifySignature(const uint8_t* message, LOGE("Bad message data"); return OEMCrypto_ERROR_INVALID_CONTEXT; } - // Step 1: Parse signature. + // Parse signature. const uint8_t* tp = signature; ScopedSigPoint sig_point(d2i_ECDSA_SIG(nullptr, &tp, signature_length)); if (!sig_point) { @@ -494,24 +554,7 @@ OEMCryptoResult EccPublicKey::VerifySignature(const uint8_t* message, // Most likely an invalid signature than an OpenSSL error. return OEMCrypto_ERROR_SIGNATURE_FAILURE; } - // Step 2: Hash message - std::vector digest; - if (!DigestMessage(curve_, message, message_length, &digest)) { - LOGE("Failed to digest message"); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - // Step 3: Verify signature - const int res = ECDSA_do_verify( - digest.data(), static_cast(digest.size()), sig_point.get(), key_); - if (res == -1) { - LOGE("Error occurred checking signature"); - return OEMCrypto_ERROR_UNKNOWN_FAILURE; - } - if (res == 0) { - LOGD("Signature did not match"); - return OEMCrypto_ERROR_SIGNATURE_FAILURE; - } - return OEMCrypto_SUCCESS; + return DigestAndVerify(message, message_length, sig_point.get()); } OEMCryptoResult EccPublicKey::VerifySignature( @@ -536,6 +579,37 @@ OEMCryptoResult EccPublicKey::VerifySignature( signature.size()); } +OEMCryptoResult EccPublicKey::VerifyRawSignature( + const uint8_t* message, size_t message_length, const uint8_t* signature, + size_t signature_length) const { + if (signature == nullptr || signature_length == 0) { + LOGE("Signature is missing"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (message == nullptr && message_length > 0) { + LOGE("Bad message data"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (signature_length % 2 == 1) { + LOGE("Bad signature size: %zu", signature_length); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Parse signature. + ScopedSigPoint sig_point(ECDSA_SIG_new()); + if (!sig_point) { + LOGE("Error occurred in ECDSA_SIG_new()"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + const int r_s_size = static_cast(signature_length) / 2; + if (!ECDSA_SIG_set0(sig_point.get(), BN_bin2bn(signature, r_s_size, nullptr), + BN_bin2bn(signature + r_s_size, r_s_size, nullptr))) { + LOGE("Failed to parse signature"); + // Most likely an invalid signature than an OpenSSL error. + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + return DigestAndVerify(message, message_length, sig_point.get()); +} + EccPublicKey::~EccPublicKey() { if (key_ != nullptr) { EC_KEY_free(key_); @@ -608,6 +682,57 @@ bool EccPublicKey::InitFromPrivateKey(const EccPrivateKey& private_key) { return true; } +bool EccPublicKey::InitFromKeyPoint(EccCurve curve, const uint8_t* buffer, + size_t length) { + if (buffer == nullptr || length == 0) { + LOGE("Provided key point buffer is empty"); + return false; + } + EC_KEY* ec_key; + if (!GetPublicKeyFromKeyPoint(curve, buffer, length, &ec_key)) { + return false; + } + ScopedEcKey key(ec_key); + if (EC_KEY_check_key(key.get()) != 1) { + LOGE("Invalid public EC key"); + return false; + } + + // Required flags for IETF compliance. + EC_KEY_set_asn1_flag(key.get(), OPENSSL_EC_NAMED_CURVE); + EC_KEY_set_conv_form(key.get(), POINT_CONVERSION_UNCOMPRESSED); + key_ = key.release(); + curve_ = curve; + return true; +} + +OEMCryptoResult EccPublicKey::DigestAndVerify( + const uint8_t* message, size_t message_length, + const ECDSA_SIG* sig_point) const { + if (message == nullptr && message_length > 0) { + LOGE("Bad message data"); + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + // Hash message. + std::vector digest; + if (!DigestMessage(curve_, message, message_length, &digest)) { + LOGE("Failed to digest message"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + // Verify signature. + const int res = ECDSA_do_verify( + digest.data(), static_cast(digest.size()), sig_point, key_); + if (res == -1) { + LOGE("Error occurred checking signature"); + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (res == 0) { + LOGD("Signature did not match"); + return OEMCrypto_ERROR_SIGNATURE_FAILURE; + } + return OEMCrypto_SUCCESS; +} + // static std::unique_ptr EccPrivateKey::New(EccCurve curve) { std::unique_ptr key(new EccPrivateKey()); diff --git a/oemcrypto/util/src/signed_csr_payload_validator.cpp b/oemcrypto/util/src/signed_csr_payload_validator.cpp new file mode 100644 index 00000000..c3923d5a --- /dev/null +++ b/oemcrypto/util/src/signed_csr_payload_validator.cpp @@ -0,0 +1,393 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include "signed_csr_payload_validator.h" + +#include + +#include "string_conversions.h" + +namespace wvoec { +namespace util { +namespace { +enum CoseKeyAlgorithm : int { + AES_GCM_256 = 3, + HMAC_256 = 5, + ES256 = -7, // ECDSA with SHA-256 + EDDSA = -8, + ECDH_ES_HKDF_256 = -25, + ES384 = -35, // ECDSA with SHA-384 +}; + +void AddMessages(std::stringstream& ss, + const std::vector& fmt_msgs, int indent) { + const std::string spaces = std::string(indent * 2, ' '); + for (auto& msg : fmt_msgs) { + ss << spaces << msg << "\n"; + } +} +} // namespace + +// clang-format off +// Signed CSR payload CBOR data to be verified: +// SignedData = [ +// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / +// AlgorithmES384 }, +// unprotected: {}, +// payload: bstr .cbor DataToBeSigned / nil, +// signature: bstr ; PureEd25519(priv_key, Sig_structure) / +// ; ECDSA(priv_key, Sig_structure) +// ] +// +// DataToBeSigned = [ +// challenge: bstr .size (0..64), +// bstr .cbor CsrPayload, +// ] +// +// CsrPayload = [ ; CBOR Array defining the payload for CSR. +// version: 3, ; The CsrPayload CDDL Schema version. +// CertificateType: "widevine" ; The type of certificate being requested. +// DeviceInfo, ; Defined in Android DeviceInfo.aidl +// KeysToSign: [] ; Empty list +// ] +// +// Sig_structure = [ +// context: "Signature1", +// protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / AlgorithmES384 }, +// external_aad: bstr .size 0, +// payload: bstr .cbor DataToBeSigned / nil, +// ] +// clang-format on + +// Caller ensures the pointer is not NULL. +CborMessageStatus SignedCsrPayloadValidator::ValidateProtectedParams( + const cppbor::Bstr* protected_params) { + auto parse_result = cppbor::parse(protected_params); + std::unique_ptr parsed_protected_params = + std::move(std::get<0>(parse_result)); + const std::string error_message = std::move(std::get<2>(parse_result)); + if (!parsed_protected_params || !error_message.empty()) { + AddValidationMessage(kCborValidateFatal, + "Unable to parse protectedParams: " + error_message); + return message_status_; + } + // |parsed_protected_params| is a CBOR map of 1 entry + const cppbor::Map* protected_params_map = parsed_protected_params->asMap(); + if (!protected_params_map) { + AddValidationMessage( + kCborValidateFatal, + "ProtectedParams must be a CBOR map. Actual type: " + + CppborMajorTypeToString(parsed_protected_params->type())); + return message_status_; + } + if (protected_params_map->size() == 0) { + AddValidationMessage(kCborValidateFatal, "ProtectedParams is empty."); + return message_status_; + } + if (protected_params_map->size() > 1) { + AddValidationMessage(kCborValidateWarning, + "ProtectedParams expects 1 entry, but got " + + std::to_string(protected_params_map->size())); + } + // TODO(b/314141962): Replace this with the map lookup function in cppbor + // library + bool algo_found = false; + for (auto const& entry : *protected_params_map) { + if (!entry.first->asInt()) { + AddValidationMessage(kCborValidateWarning, + "Unsupported key type: " + + CppborMajorTypeToString(entry.first->type())); + } else if (entry.first->asInt()->value() != 1) { + AddValidationMessage(kCborValidateWarning, + "Unsupported key value: " + + std::to_string(entry.first->asInt()->value())); + } else { + auto& algorithm = entry.second; + if (!algorithm) { + AddValidationMessage(kCborValidateFatal, "Algorithm value is missing."); + return message_status_; + } + if (!algorithm->asInt()) { + AddValidationMessage(kCborValidateFatal, + "Unsupported signature algorithm value type: " + + CppborMajorTypeToString(algorithm->type())); + return message_status_; + } + std::string kv = "1: "; + const int64_t algo = algorithm->asInt()->value(); + if (algo == EDDSA) + kv += "EDDSA"; + else if (algo == ES256) + kv += "ES256"; + else if (algo == ES384) + kv += "ES384"; + else { + kv += std::to_string(algo); + AddValidationMessage( + kCborValidateFatal, + "Unsupported signature algorithm value: " + std::to_string(algo)); + return message_status_; + } + AddMessages(msg_ss_, {std::move(kv)}, 1); + algo_found = true; + } + } + if (!algo_found) { + AddValidationMessage(kCborValidateFatal, + "ProtectedParams has no signature algorithm."); + } + return message_status_; +} + +// Caller ensures the pointer is not NULL. +CborMessageStatus SignedCsrPayloadValidator::ValidateDataToBeSigned( + const cppbor::Bstr* data) { + if (data->value().empty()) { + AddValidationMessage(kCborValidateFatal, "Payload to be signed is empty."); + return message_status_; + } + auto parse_result = cppbor::parse(data); + std::unique_ptr parsed_payload_to_be_signed = + std::move(std::get<0>(parse_result)); + std::string error_message = std::move(std::get<2>(parse_result)); + if (!parsed_payload_to_be_signed || !error_message.empty()) { + AddValidationMessage( + kCborValidateFatal, + "Unable to parse the payload to be signed: " + error_message); + return message_status_; + } + // Verify that |parsed_payload_to_be_signed| is a valid array. + const cppbor::Array* payload_to_be_signed_array = + parsed_payload_to_be_signed->asArray(); + if (!payload_to_be_signed_array) { + AddValidationMessage( + kCborValidateFatal, + "Payload to be signed must be a CBOR array. Actual type: " + + CppborMajorTypeToString(parsed_payload_to_be_signed->type())); + return message_status_; + } + if (payload_to_be_signed_array->size() != 2U) { + AddValidationMessage( + kCborValidateFatal, + "Payload to be signed must contain the challenge and " + "payload. Actual size: " + + std::to_string(payload_to_be_signed_array->size())); + return message_status_; + } + + // Element 0: challenge. + std::string msg = "- Challenge: "; + const cppbor::Bstr* challenge = payload_to_be_signed_array->get(0)->asBstr(); + if (!challenge) { + AddValidationMessage(kCborValidateFatal, + "Challenge must be Bstr. Actual type: " + + CppborMajorTypeToString( + payload_to_be_signed_array->get(0)->type())); + return message_status_; + } + if (challenge->value().size() > 64) { + AddValidationMessage( + kCborValidateError, + "Challenge size must be between 0 and 64 bytes inclusive. " + "However, challenge is " + + std::to_string(challenge->value().size()) + " bytes long."); + } + msg += wvutil::b2a_hex(challenge->value()); + AddMessages(msg_ss_, {std::move(msg)}, 1); + + // Element 1: CsrPayload. + AddMessages(msg_ss_, {"- CsrPayload:"}, 1); + const cppbor::Bstr* csr_payload = + payload_to_be_signed_array->get(1)->asBstr(); + if (!csr_payload) { + AddValidationMessage(kCborValidateFatal, "CSR payload is missing."); + return message_status_; + } + parse_result = cppbor::parse(csr_payload); + std::unique_ptr parsed_csr_payload = + std::move(std::get<0>(parse_result)); + error_message = std::move(std::get<2>(parse_result)); + if (!parsed_csr_payload) { + AddValidationMessage(kCborValidateFatal, + "Unable to parse CSR payload: " + error_message); + return message_status_; + } + if (!parsed_csr_payload->asArray()) { + AddValidationMessage( + kCborValidateFatal, + "CSR payload must be an array. Actual: " + + CppborMajorTypeToString(parsed_csr_payload->type())); + return message_status_; + } + if (parsed_csr_payload->asArray()->size() != 4U) { + AddValidationMessage( + kCborValidateFatal, + "CSR payload must contain 4 elements. Actual size: " + + std::to_string(parsed_csr_payload->asArray()->size())); + return message_status_; + } + // |parsed_csr_payload| is an array of 4 elements. + const cppbor::Uint* version = parsed_csr_payload->asArray()->get(0)->asUint(); + if (!version) { + AddValidationMessage(kCborValidateFatal, + "CSR payload version must be an unsigned integer."); + return message_status_; + } + AddMessages(msg_ss_, {"- version: " + std::to_string(version->value())}, 2); + if (version->value() != 3U) { + AddValidationMessage(kCborValidateError, + "CSR payload version must be must be equal to 3."); + } + + const cppbor::Tstr* certificate_type = + parsed_csr_payload->asArray()->get(1)->asTstr(); + if (!certificate_type) { + // Certificate type is allowed to be extended by vendor, i.e. we can't + // enforce its value. + AddValidationMessage( + kCborValidateFatal, + "Certificate type must be Tstr. Actual type: " + + CppborMajorTypeToString( + parsed_csr_payload->asArray()->get(1)->type())); + return message_status_; + } + AddMessages(msg_ss_, {"- certificate_type: " + certificate_type->value()}, 2); + + const cppbor::Map* device_info = + parsed_csr_payload->asArray()->get(2)->asMap(); + if (!device_info) { + AddValidationMessage( + kCborValidateFatal, + "Device info must be a CBOR map. Actual type: " + + CppborMajorTypeToString( + parsed_csr_payload->asArray()->get(2)->type())); + return message_status_; + } + AddMessages(msg_ss_, {"- device_info: " + cppbor::prettyPrint(device_info)}, + 2); + + const cppbor::Array* keys = parsed_csr_payload->asArray()->get(3)->asArray(); + if (!keys) { + AddValidationMessage( + kCborValidateFatal, + "Keys must be a CBOR array. Actual type: " + + CppborMajorTypeToString( + parsed_csr_payload->asArray()->get(3)->type())); + return message_status_; + } + AddMessages(msg_ss_, {"- keys_to_sign: " + cppbor::prettyPrint(keys)}, 2); + return message_status_; +} + +CborMessageStatus SignedCsrPayloadValidator::Validate() { + if (message_status_ != kCborParseOk) return message_status_; + const cppbor::Item* parsed_signed_csr_payload = + std::get<0>(parse_result()).get(); + if (!parsed_signed_csr_payload) { + AddValidationMessage(kCborValidateFatal, "Signed CSR payload is empty."); + return message_status_; + } + + // Verify that |parsed_signed_csr_payload| is a valid array. + if (!parsed_signed_csr_payload->asArray()) { + AddValidationMessage( + kCborValidateFatal, + "Signed CSR payload must be a CBOR array. Actual type: " + + CppborMajorTypeToString(parsed_signed_csr_payload->type())); + return message_status_; + } + const cppbor::Array* signed_csr_payload_array = + parsed_signed_csr_payload->asArray(); + if (signed_csr_payload_array->size() != 4U) { + AddValidationMessage(kCborValidateFatal, + "Invalid CoseSign1 size. Actual: " + + std::to_string(signed_csr_payload_array->size())); + return message_status_; + } + + // Writes YAML-formatted output to |msg_ss_| during validation. + msg_ss_.str(std::string()); + msg_ss_ << "---" + << "\n"; + + // Element 0: protected params. + msg_ss_ << "- Protected params:\n"; + const cppbor::Bstr* protected_params = + signed_csr_payload_array->get(0)->asBstr(); + CborMessageStatus status = ValidateProtectedParams(protected_params); + if (status == kCborValidateFatal) return kCborValidateFatal; + + // Element 1: unprotected params map. + std::string msg = "- Unprotected params: "; + const cppbor::Map* unprotected_params = + signed_csr_payload_array->get(1)->asMap(); + if (!unprotected_params) { + AddValidationMessage( + kCborValidateFatal, + "UnprotectedParams must be a CBOR map. Actual type: " + + CppborMajorTypeToString(signed_csr_payload_array->get(1)->type())); + return message_status_; + } + if (unprotected_params->size() == 0) { + msg += ""; + } else { + msg += "non-null map"; + AddValidationMessage(kCborValidateWarning, + "UnprotectedParams is expected to be empty."); + } + msg_ss_ << msg << "\n"; + + // Element 2: payload (DataToBeSigned). + msg_ss_ << "- Payload:\n"; + const cppbor::Bstr* payload_to_be_signed = + signed_csr_payload_array->get(2)->asBstr(); + if (!payload_to_be_signed) { + AddValidationMessage( + kCborValidateFatal, + "Payload to be signed must be Bstr. Actual type: " + + CppborMajorTypeToString(signed_csr_payload_array->get(2)->type())); + return message_status_; + } + status = ValidateDataToBeSigned(payload_to_be_signed); + if (status == kCborValidateFatal) return kCborValidateFatal; + + // Element 3: signature. + std::string sig_msg = "- Signature: "; + const cppbor::Bstr* signature = signed_csr_payload_array->get(3)->asBstr(); + if (!signature) { + AddValidationMessage( + kCborValidateFatal, + "CoseSign1 signature must be Bstr. Actual type: " + + CppborMajorTypeToString(signed_csr_payload_array->get(3)->type())); + return message_status_; + } + // Skip CoseSign1 signature verification since the validator doesn't have + // verifying keys. + if (signature->value().empty()) { + sig_msg += ""; + AddValidationMessage(kCborValidateError, "CoseSign1 signature is missing."); + } else { + sig_msg += wvutil::b2a_hex(signature->value()); + } + msg_ss_ << sig_msg << "\n"; + msg_ss_ << "...\n"; + if (message_status_ == kCborParseOk) message_status_ = kCborValidateOk; + return message_status_; +} + +std::string SignedCsrPayloadValidator::GetFormattedMessage() const { + if (message_status_ == kCborUninitialized || + message_status_ == kCborParseError) { + return std::string(); + } + const cppbor::Item* parsed_item = std::get<0>(parse_result()).get(); + if (parsed_item == nullptr) { + return ""; + } + return msg_ss_.str(); +} +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/test/bcc_validator_unittest.cpp b/oemcrypto/util/test/bcc_validator_unittest.cpp new file mode 100644 index 00000000..ca92e6d6 --- /dev/null +++ b/oemcrypto/util/test/bcc_validator_unittest.cpp @@ -0,0 +1,147 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include +#include + +#include "bcc_validator.h" + +using ::testing::AllOf; +using ::testing::Ge; +using ::testing::HasSubstr; +using ::testing::Le; + +namespace wvoec { +namespace util { +namespace { +// Self-signed phase 1 BCC generated by OPK reference implementation. +const std::vector kBcc = { + 0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff, + 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee, + 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43, + 0xa1, 0x01, 0x27, 0xa0, 0x58, 0x40, 0xa4, 0x01, 0x60, 0x02, 0x60, 0x3a, + 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, + 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, + 0x41, 0xf1, 0x0b, 0x40, 0xff, 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, + 0x5a, 0x97, 0xa6, 0x80, 0xee, 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, + 0xe5, 0xfb, 0x10, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, + 0x73, 0x02, 0x36, 0xaa, 0x6d, 0x52, 0x50, 0x67, 0x43, 0xc4, 0x0b, 0xf8, + 0x3f, 0x35, 0x2a, 0xd8, 0x44, 0x09, 0xf4, 0x1d, 0xca, 0x91, 0x12, 0x27, + 0x01, 0xdf, 0x73, 0xb7, 0x9b, 0x31, 0x28, 0x8e, 0xae, 0x9b, 0xc6, 0x7a, + 0xdc, 0x07, 0xab, 0x69, 0xd2, 0x85, 0x9a, 0x15, 0x8b, 0xe3, 0x5b, 0xf2, + 0x94, 0x95, 0xee, 0x49, 0x74, 0xc5, 0x85, 0x62, 0x3d, 0x46, 0x4c, 0xeb, + 0x11, 0x89, 0x68, 0x02}; +const std::vector kBccWrongEntryKey = { + 0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff, + 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee, + 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43, + 0xa1, 0x01, 0x27, 0xa0, 0x58, 0x40, 0xa4, 0x01, 0x60, 0x02, 0x60, 0x3a, + 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, + 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, + 0x89, 0x1d, 0xff, 0xb3, 0x3b, 0xe2, 0xdc, 0xc6, 0xbc, 0xbd, 0xc7, 0xcd, + 0x3f, 0x9c, 0x43, 0xf6, 0xdd, 0xea, 0x58, 0x53, 0x45, 0x8f, 0x87, 0x17, + 0x0a, 0xe4, 0x06, 0xf2, 0xbe, 0x14, 0x69, 0x13, 0x3d, 0x1d, 0xd0, 0x52, + 0x8f, 0x56, 0x4b, 0x0f, 0xad, 0x2e, 0xf0, 0xbf, 0xbb, 0xd1, 0x35, 0x9c, + 0x5a, 0xe8, 0x67, 0xbe, 0xec, 0xff, 0x9d, 0xfe, 0xac, 0x8d, 0x47, 0x4e, + 0x6d, 0xd1, 0xd3, 0x02}; +const std::vector kBccMissingIssuer = { + 0x82, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, + 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, 0x0b, 0x40, 0xff, + 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, 0xa6, 0x80, 0xee, + 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, 0x10, 0x84, 0x43, + 0xa1, 0x01, 0x27, 0xa0, 0x58, 0x3e, 0xa3, 0x02, 0x60, 0x3a, 0x00, 0x47, + 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, + 0x20, 0x06, 0x21, 0x58, 0x20, 0x02, 0xab, 0xbe, 0x80, 0x02, 0x41, 0xf1, + 0x0b, 0x40, 0xff, 0xd5, 0xf0, 0xd0, 0x26, 0x65, 0x70, 0xdc, 0x5a, 0x97, + 0xa6, 0x80, 0xee, 0x15, 0x95, 0xec, 0x26, 0x36, 0x70, 0x77, 0xe5, 0xfb, + 0x10, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20, 0x58, 0x40, 0xf9, 0x46, + 0x36, 0xbd, 0x95, 0x75, 0xc2, 0x3d, 0xf9, 0xa2, 0xbe, 0x60, 0x8e, 0xbf, + 0x64, 0x89, 0xdf, 0xb9, 0x9c, 0x3c, 0x17, 0x36, 0x23, 0x9a, 0x68, 0x1a, + 0x34, 0x36, 0x51, 0x89, 0x59, 0xf2, 0x54, 0x62, 0xd3, 0x8f, 0xeb, 0x9b, + 0x75, 0x3e, 0xe9, 0xfc, 0xe3, 0xc2, 0x8f, 0x84, 0xb1, 0x71, 0xcd, 0x29, + 0x12, 0x65, 0xeb, 0xab, 0x28, 0x4b, 0xe2, 0x3e, 0x1b, 0xd8, 0x17, 0xdb, + 0x97, 0x0f}; +} // namespace + +TEST(OEMCryptoBccValidatorTest, BccParseError) { + const std::vector bcc_bad(kBcc.begin(), kBcc.end() - 1); + BccValidator validator; + CborMessageStatus result = validator.Parse(bcc_bad); + EXPECT_EQ(kCborParseError, result); + result = validator.Validate(); + EXPECT_EQ(kCborParseError, result); + EXPECT_EQ("", validator.GetRawMessage()); + EXPECT_EQ("", validator.GetFormattedMessage()); +} + +TEST(OEMCryptoBccValidatorTest, Bcc) { + BccValidator validator; + CborMessageStatus result = validator.Parse(kBcc); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); + const std::string out = validator.GetFormattedMessage(); + EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING")); + EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA")); +} + +TEST(OEMCryptoBccValidatorTest, BccWrongEntryKey) { + BccValidator validator; + CborMessageStatus result = validator.Parse(kBccWrongEntryKey); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_EQ(result, kCborValidateError); + // Non-fatal validation error should be able to return formatted output. + const std::string out = validator.GetFormattedMessage(); + EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING")); + EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA")); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateError, msgs[0].first); +} + +TEST(OEMCryptoBccValidatorTest, BccParseThreeTimes) { + BccValidator validator; + const std::vector bcc_bad(kBcc.begin(), kBcc.end() - 2); + CborMessageStatus result = validator.Parse(bcc_bad); + EXPECT_EQ(kCborParseError, result); + result = validator.Validate(); + EXPECT_EQ(kCborParseError, result); + + result = validator.Parse(kBccWrongEntryKey); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_EQ(result, kCborValidateError); + + result = validator.Parse(kBcc); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_EQ(result, kCborValidateOk); +} + +TEST(OEMCryptoBccValidatorTest, BccMissingIssuer) { + BccValidator validator; + CborMessageStatus result = validator.Parse(kBccMissingIssuer); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_EQ(result, kCborValidateError); + const std::string out = validator.GetFormattedMessage(); + EXPECT_THAT(out, HasSubstr("key encoding format: DEVICE_KEY_BYTE_STRING")); + EXPECT_THAT(out, HasSubstr("key algorithm type: EDDSA")); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateError, msgs[0].first); + EXPECT_THAT(msgs[0].second, HasSubstr("Missing Issuer")); +} +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/test/device_info_validator_unittest.cpp b/oemcrypto/util/test/device_info_validator_unittest.cpp new file mode 100644 index 00000000..2e4e022c --- /dev/null +++ b/oemcrypto/util/test/device_info_validator_unittest.cpp @@ -0,0 +1,204 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include +#include + +#include "device_info_validator.h" + +using ::testing::AllOf; +using ::testing::Ge; +using ::testing::HasSubstr; +using ::testing::Le; + +namespace wvoec { +namespace util { +namespace { +constexpr int kDeviceVersion1 = 1; +constexpr int kDeviceVersion2 = 2; +constexpr int kDeviceVersion3 = 3; + +cppbor::Map BuildDeviceInfoMap(int version) { + cppbor::Map device_info = + cppbor::Map() + .add("brand", "brand") + .add("manufacturer", "manufacturer") + .add("product", "product") + .add("model", "model") + .add("vb_state", "green") + .add("bootloader_state", "unlocked") + .add("vbmeta_digest", cppbor::Bstr(std::vector())) + .add("os_version", "os_version") + .add("system_patch_level", 202312) + .add("boot_patch_level", 20231201) + .add("vendor_patch_level", 20231201) + .add("security_level", "tee"); + switch (version) { + case kDeviceVersion1: + device_info.add("board", "board"); + device_info.add("version", 1); + device_info.add("att_id_state", "open"); + break; + case kDeviceVersion2: + device_info.add("device", "device"); + device_info.add("version", 2); + device_info.add("fused", 0); + break; + case kDeviceVersion3: + device_info.add("device", "device"); + device_info.add("fused", 0); + break; + } + return device_info; +} + +std::vector BuildDeviceInfo(int version) { + auto map = BuildDeviceInfoMap(version); + return map.canonicalize().encode(); +} +} // namespace + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoParseError) { + const std::vector device_info = BuildDeviceInfo(kDeviceVersion3); + const std::vector device_info_bad(device_info.begin(), + device_info.end() - 1); + DeviceInfoValidator validator(kDeviceVersion3); + CborMessageStatus result = validator.Parse(device_info_bad); + EXPECT_EQ(kCborParseError, result); + result = validator.Validate(); + EXPECT_EQ(kCborParseError, result); + EXPECT_EQ("", validator.GetRawMessage()); + EXPECT_EQ("", validator.GetFormattedMessage()); +} + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoNotMap) { + cppbor::Array array = cppbor::Array().add("make").add(123).add("model"); + const std::vector device_info_bad = array.encode(); + DeviceInfoValidator validator(kDeviceVersion3); + CborMessageStatus result = validator.Parse(device_info_bad); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateFatal); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateFatal, msgs[0].first); + EXPECT_THAT(msgs[0].second, HasSubstr("Device info is not a CBOR map")); +} + +TEST(OEMCryptoDeviceInfoValidatorTest, + DeviceInfoV3WrongKeyValueTypeAndMissingField) { + const std::vector device_info_bad = + cppbor::Map() + .add("brand", "brand") + .add("manufacturer", "manufacturer") + .add(123, 456) // Non-Tstr key type + .add("system_patch_level", "not a uint") // Non-uint value type + .canonicalize() + .encode(); + DeviceInfoValidator validator(kDeviceVersion3); + CborMessageStatus result = validator.Parse(device_info_bad); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_EQ(kCborValidateError, result); + const std::vector> msgs = + validator.GetValidateMessages(); + const bool unexpected_key_type_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("Unexpected entry key type") != std::string::npos; + }); + EXPECT_EQ(true, unexpected_key_type_found); + const bool unexpected_value_type_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("system_patch_level has the wrong type") != + std::string::npos; + }); + EXPECT_EQ(true, unexpected_value_type_found); + const bool missing_model_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("model is missing") != std::string::npos; + }); + EXPECT_EQ(true, missing_model_found); +} + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV3NonCanonical) { + const cppbor::Map map = BuildDeviceInfoMap(kDeviceVersion3); + const std::vector device_info = map.encode(); + DeviceInfoValidator validator(kDeviceVersion3); + CborMessageStatus result = validator.Parse(device_info); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateError); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateError, msgs[0].first); + EXPECT_THAT(msgs[0].second, + HasSubstr("Device info ordering is non-canonical")); +} + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV3) { + const std::vector device_info = BuildDeviceInfo(kDeviceVersion3); + DeviceInfoValidator validator(kDeviceVersion3); + CborMessageStatus result = validator.Parse(device_info); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); + const std::string out = validator.GetFormattedMessage(); + EXPECT_THAT(out, HasSubstr("manufacturer: manufacturer")); + EXPECT_THAT(out, HasSubstr("model: model")); + EXPECT_THAT(out, HasSubstr("fused: 0")); +} + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV2) { + const std::vector device_info = BuildDeviceInfo(kDeviceVersion2); + DeviceInfoValidator validator(kDeviceVersion2); + CborMessageStatus result = validator.Parse(device_info); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); + const std::string out = validator.GetFormattedMessage(); + EXPECT_THAT(out, HasSubstr("manufacturer: manufacturer")); + EXPECT_THAT(out, HasSubstr("model: model")); + EXPECT_THAT(out, HasSubstr("fused: 0")); + EXPECT_THAT(out, HasSubstr("version: 2")); +} + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV1MissingField) { + const std::vector device_info = cppbor::Map() + .add("brand", "brand") + .add("security_level", "tee") + .add("version", 1) + .canonicalize() + .encode(); + DeviceInfoValidator validator(kDeviceVersion1); + CborMessageStatus result = validator.Parse(device_info); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateError); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateError, msgs[0].first); + EXPECT_THAT(msgs[0].second, HasSubstr("att_id_state is missing")); +} + +TEST(OEMCryptoDeviceInfoValidatorTest, DeviceInfoV1) { + DeviceInfoValidator validator(kDeviceVersion1); + const std::vector device_info = BuildDeviceInfo(kDeviceVersion1); + CborMessageStatus result = validator.Parse(device_info); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); + const std::string out = validator.GetFormattedMessage(); + EXPECT_THAT(out, HasSubstr("board: board")); + EXPECT_THAT(out, HasSubstr("version: 1")); +} +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp b/oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp new file mode 100644 index 00000000..f5c4c7f8 --- /dev/null +++ b/oemcrypto/util/test/signed_csr_payload_validator_unittest.cpp @@ -0,0 +1,294 @@ +// Copyright 2023 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Reference implementation utilities of OEMCrypto APIs +// +#include +#include + +#include "signed_csr_payload_validator.h" + +using ::testing::AllOf; +using ::testing::Ge; +using ::testing::HasSubstr; +using ::testing::Le; + +namespace wvoec { +namespace util { +namespace { +std::vector GetDefaultProtectedData() { + return cppbor::Map().add(1, /*ES256=*/-7).encode(); +} + +cppbor::Map GetDefaultDeviceInfoMap() { + return cppbor::Map() + .add("brand", "brand") + .add("manufacturer", "manufacturer") + .add("product", "product") + .add("model", "model") + .add("vb_state", "green") + .add("bootloader_state", "unlocked") + .add("vbmeta_digest", cppbor::Bstr(std::vector())) + .add("os_version", "os_version") + .add("system_patch_level", 202312) + .add("boot_patch_level", 20231201) + .add("vendor_patch_level", 20231201) + .add("security_level", "tee") + .add("device", "device") + .add("fused", 0); +} + +std::vector GetDefaultCsrPayload() { + // CsrPayload = [ ; CBOR Array defining the payload for CSR. + // version: 3, ; The CsrPayload CDDL Schema version. + // CertificateType: "widevine" ; The type of certificate being requested. + // DeviceInfo, ; Defined in Android DeviceInfo.aidl + // KeysToSign: [] ; Empty list + // ] + return cppbor::Array() + .add(3) + .add("widevine") + .add(GetDefaultDeviceInfoMap()) + .add(/*KeysToSign*/ cppbor::Array()) + .encode(); +} + +std::vector GetDefaultPayloadData() { + // payload: bstr .cbor DataToBeSigned / nil + // DataToBeSigned = [ + // challenge: bstr .size (0..64), + // bstr .cbor CsrPayload, + // ] + const std::vector challenge(64, 0xC0); + return cppbor::Array() + .add(cppbor::Bstr(challenge)) + .add(cppbor::Bstr(GetDefaultCsrPayload())) + .encode(); +} + +std::vector GetDefaultSignature() { + return std::vector(64, 0xA0); +} + +std::vector GetDefaultSignedCsrPayload() { + // SignedData = [ + // protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 / + // AlgorithmES384 }, + // unprotected: {}, + // payload: bstr .cbor DataToBeSigned / nil, + // signature: bstr + // ] + return cppbor::Array() + .add(cppbor::Bstr(GetDefaultProtectedData())) + .add(/*unprotected*/ cppbor::Map()) + .add(cppbor::Bstr(GetDefaultPayloadData())) + .add(cppbor::Bstr(GetDefaultSignature())) + .encode(); +} +} // namespace + +TEST(OEMCryptoSignedCsrPayloadValidatorTest, SignedCsrPayloadParseError) { + const std::vector signed_csr_payload = GetDefaultSignedCsrPayload(); + const std::vector signed_csr_payload_bad( + signed_csr_payload.begin(), signed_csr_payload.end() - 1); + SignedCsrPayloadValidator validator; + CborMessageStatus result = validator.Parse(signed_csr_payload_bad); + EXPECT_EQ(kCborParseError, result); + result = validator.Validate(); + EXPECT_EQ(kCborParseError, result); + EXPECT_EQ("", validator.GetRawMessage()); + EXPECT_EQ("", validator.GetFormattedMessage()); +} + +TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataNotMap) { + cppbor::Array protected_data_array = cppbor::Array().add(1).add(-7); + std::vector signed_csr_payload = + cppbor::Array() + .add(cppbor::Bstr(protected_data_array.encode())) + .add(/*unprotected*/ cppbor::Map()) + .add(cppbor::Bstr(GetDefaultPayloadData())) + .add(cppbor::Bstr(GetDefaultSignature())) + .encode(); + SignedCsrPayloadValidator validator; + CborMessageStatus result = validator.Parse(signed_csr_payload); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateFatal); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateFatal, msgs[0].first); + EXPECT_THAT(msgs[0].second, HasSubstr("ProtectedParams must be a CBOR map")); +} + +TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapMissingKey) { + cppbor::Map protected_data = cppbor::Map(); + std::vector signed_csr_payload = + cppbor::Array() + .add(cppbor::Bstr(protected_data.encode())) + .add(/*unprotected*/ cppbor::Map()) + .add(cppbor::Bstr(GetDefaultPayloadData())) + .add(cppbor::Bstr(GetDefaultSignature())) + .encode(); + SignedCsrPayloadValidator validator; + CborMessageStatus result = validator.Parse(signed_csr_payload); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateFatal); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateFatal, msgs[0].first); + EXPECT_THAT(msgs[0].second, HasSubstr("ProtectedParams is empty")); +} + +TEST(OEMCryptoSignedCsrPayloadValidatorTest, ProtectedDataMapKeyWarnings) { + cppbor::Map protected_data = + cppbor::Map().add(1, -7).add("1", -7).add(2, "abc"); + std::vector signed_csr_payload = + cppbor::Array() + .add(cppbor::Bstr(protected_data.encode())) + .add(/*unprotected*/ cppbor::Map()) + .add(cppbor::Bstr(GetDefaultPayloadData())) + .add(cppbor::Bstr(GetDefaultSignature())) + .encode(); + SignedCsrPayloadValidator validator; + CborMessageStatus result = validator.Parse(signed_csr_payload); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateWarning); + const std::vector> msgs = + validator.GetValidateMessages(); + const bool unexpected_key_type_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("Unsupported key type") != std::string::npos; + }); + EXPECT_EQ(true, unexpected_key_type_found); + const bool unexpected_key_value_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("Unsupported key value") != std::string::npos; + }); + EXPECT_EQ(true, unexpected_key_value_found); + const bool unexpected_entry_found = + std::any_of(msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("ProtectedParams expects 1 entry") != + std::string::npos; + }); + EXPECT_EQ(true, unexpected_entry_found); +} + +TEST(OEMCryptoSignedCsrPayloadValidatorTest, InvalidChallengeAndPayload) { + const std::vector challenge(65, 0xC0); // bad length + const std::vector csr_payload = + cppbor::Array() + .add(2) // wrong version + .add("widevine") + .add(GetDefaultDeviceInfoMap()) + .add(/*KeysToSign*/ cppbor::Array()) + .encode(); + const std::vector payload = cppbor::Array() + .add(cppbor::Bstr(challenge)) + .add(cppbor::Bstr(csr_payload)) + .encode(); + std::vector signed_csr_payload = + cppbor::Array() + .add(cppbor::Bstr(GetDefaultProtectedData())) + .add(/*unprotected*/ cppbor::Map()) + .add(cppbor::Bstr(payload)) + .add(cppbor::Bstr(GetDefaultSignature())) + .encode(); + SignedCsrPayloadValidator validator; + CborMessageStatus result = validator.Parse(signed_csr_payload); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateError); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(2u, msgs.size()); + const bool challenge_error_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find("Challenge size must be between 0 and 64 bytes") != + std::string::npos; + }); + EXPECT_EQ(true, challenge_error_found); + const bool csr_payload_error_found = std::any_of( + msgs.begin(), msgs.end(), + [](const std::pair& p) { + return p.second.find( + "CSR payload version must be must be equal to 3") != + std::string::npos; + }); + EXPECT_EQ(true, csr_payload_error_found); +} + +TEST(OEMCryptoSignedCsrPayloadValidatorTest, KeysToSignEmptyList) { + const std::vector challenge(64, 0xC0); + const std::vector csr_payload = + cppbor::Array() + .add(3) + .add("widevine") + .add(GetDefaultDeviceInfoMap()) + .add(/*KeysToSign*/ cppbor::Bstr()) // wrong type, expect list + .encode(); + const std::vector payload = cppbor::Array() + .add(cppbor::Bstr(challenge)) + .add(cppbor::Bstr(csr_payload)) + .encode(); + std::vector signed_csr_payload = + cppbor::Array() + .add(cppbor::Bstr(GetDefaultProtectedData())) + .add(/*unprotected*/ cppbor::Map()) + .add(cppbor::Bstr(payload)) + .add(cppbor::Bstr(GetDefaultSignature())) + .encode(); + SignedCsrPayloadValidator validator; + CborMessageStatus result = validator.Parse(signed_csr_payload); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateFatal); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateFatal, msgs[0].first); + EXPECT_THAT(msgs[0].second, HasSubstr("Keys must be a CBOR array")); +} + +TEST(OEMCryptoSignedCsrPayloadValidatorTest, SignatureMissing) { + std::vector signed_csr_payload = + cppbor::Array() + .add(cppbor::Bstr(GetDefaultProtectedData())) + .add(/*unprotected*/ cppbor::Map()) + .add(cppbor::Bstr(GetDefaultPayloadData())) + .add(cppbor::Bstr(std::vector())) // empty signature + .encode(); + SignedCsrPayloadValidator validator; + CborMessageStatus result = validator.Parse(signed_csr_payload); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, kCborValidateError); + const std::vector> msgs = + validator.GetValidateMessages(); + EXPECT_EQ(1u, msgs.size()); + EXPECT_EQ(kCborValidateError, msgs[0].first); + EXPECT_THAT(msgs[0].second, HasSubstr("CoseSign1 signature is missing")); +} + +TEST(OEMCryptoSignedCsrPayloadValidatorTest, ValidateOk) { + SignedCsrPayloadValidator validator; + CborMessageStatus result = validator.Parse(GetDefaultSignedCsrPayload()); + EXPECT_EQ(kCborParseOk, result); + result = validator.Validate(); + EXPECT_THAT(result, AllOf(Ge(kCborValidateOk), Le(kCborValidateWarning))); + const std::string out = validator.GetFormattedMessage(); + EXPECT_THAT(out, HasSubstr("1: ES256")); + EXPECT_THAT(out, HasSubstr("version: 3")); + EXPECT_THAT(out, HasSubstr("certificate_type: widevine")); + EXPECT_THAT(out, HasSubstr("keys_to_sign: []")); +} +} // namespace util +} // namespace wvoec diff --git a/platforms/example-runtime-client-info/settings.gypi b/platforms/example-runtime-client-info/settings.gypi index 2c8ad053..bf9213a4 100644 --- a/platforms/example-runtime-client-info/settings.gypi +++ b/platforms/example-runtime-client-info/settings.gypi @@ -35,8 +35,8 @@ ], # These are flags passed to the compiler for C++ only. 'cflags_cc': [ - # Compile using the C++14 standard. - '-std=c++14', + # Compile using the C++17 standard. + '-std=c++17', # CE CDM does not use exceptions or RTTI. '-fno-exceptions', '-fno-rtti', diff --git a/platforms/example/no_oemcrypto.cpp b/platforms/example/no_oemcrypto.cpp index 3659f566..196f8dda 100644 --- a/platforms/example/no_oemcrypto.cpp +++ b/platforms/example/no_oemcrypto.cpp @@ -6,7 +6,7 @@ #include "wv_attributes.h" #pragma message( \ - "Warning: The Widevine CE CDM does not include an implementation of \ + "Warning: The Widevine CE CDM does not include an implementation of \ OEMCrypto. You must provide your own implementation. If you have access to the \ OEMCrypto repository, you can use an implementation from there. Otherwise, you \ will need to acquire an implementation from your SoC vendor. This build should \ @@ -101,12 +101,12 @@ OEMCryptoResult OEMCrypto_LoadKeys( return OEMCrypto_ERROR_NOT_IMPLEMENTED; } -OEMCryptoResult OEMCrypto_LoadLicense(OEMCrypto_SESSION session UNUSED, - const uint8_t* message UNUSED, - size_t message_length UNUSED, - size_t core_message_length UNUSED, - const uint8_t* signature UNUSED, - size_t signature_length UNUSED) { +OEMCryptoResult OEMCrypto_LoadLicense_V18(OEMCrypto_SESSION session UNUSED, + const uint8_t* message UNUSED, + size_t message_length UNUSED, + size_t core_message_length UNUSED, + const uint8_t* signature UNUSED, + size_t signature_length UNUSED) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } @@ -316,7 +316,7 @@ OEMCryptoResult OEMCrypto_GetSignatureHashAlgorithm( return OEMCrypto_ERROR_NOT_IMPLEMENTED; } -OEMCryptoResult OEMCrypto_LoadProvisioning( +OEMCryptoResult OEMCrypto_LoadProvisioning_V18( OEMCrypto_SESSION session UNUSED, const uint8_t* message UNUSED, size_t message_length UNUSED, size_t core_message_length UNUSED, const uint8_t* signature UNUSED, size_t signature_length UNUSED, @@ -523,3 +523,78 @@ OEMCryptoResult OEMCrypto_SetMaxAPIVersion(uint32_t max_version UNUSED) { OEMCryptoResult OEMCrypto_EnterTestMode(void) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } + +OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session UNUSED, + uint32_t frame_number UNUSED, + uint32_t crc32 UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadLicense( + OEMCrypto_SESSION session UNUSED, const uint8_t* context UNUSED, + size_t context_length UNUSED, const uint8_t* derivation_key UNUSED, + size_t derivation_key_length UNUSED, const uint8_t* message UNUSED, + size_t message_length UNUSED, size_t core_message_length UNUSED, + const uint8_t* signature UNUSED, size_t signature_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadProvisioning( + OEMCrypto_SESSION session UNUSED, const uint8_t* provision_request UNUSED, + size_t provision_request_length UNUSED, const uint8_t* message UNUSED, + size_t message_length UNUSED, size_t core_message_length UNUSED, + const uint8_t* signature UNUSED, size_t signature_length UNUSED, + uint8_t* wrapped_private_key UNUSED, + size_t* wrapped_private_key_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadProvisioningCast( + OEMCrypto_SESSION session UNUSED, const uint8_t* derivation_key UNUSED, + size_t derivation_key_length UNUSED, + const uint8_t* provision_request UNUSED, + size_t provision_request_length UNUSED, const uint8_t* message UNUSED, + size_t message_length UNUSED, size_t core_message_length UNUSED, + const uint8_t* signature UNUSED, size_t signature_length UNUSED, + uint8_t* wrapped_private_key UNUSED, + size_t* wrapped_private_key_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_PrepAndSignReleaseRequest( + OEMCrypto_SESSION session UNUSED, uint8_t* message UNUSED, + size_t message_length UNUSED, size_t* core_message_size UNUSED, + uint8_t* signature UNUSED, size_t* signature_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetUsageEntryInfo( + OEMCrypto_SESSION session UNUSED, + OEMCrypto_Usage_Entry_Status* status UNUSED, + int64_t* seconds_since_license_received UNUSED, + int64_t* seconds_since_first_decrypt UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetBCCType(OEMCrypto_BCCType* bcc_type UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadRelease(OEMCrypto_SESSION session UNUSED, + const uint8_t* message UNUSED, + size_t message_length UNUSED, + size_t core_message_length UNUSED, + const uint8_t* signature UNUSED, + size_t signature_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetEmbeddedDrmCertificate( + uint8_t* public_cert UNUSED, size_t* public_cert_length) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session_id UNUSED, + bool dual_key UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} diff --git a/platforms/example/settings.gypi b/platforms/example/settings.gypi index 509f8e60..033d15c5 100644 --- a/platforms/example/settings.gypi +++ b/platforms/example/settings.gypi @@ -42,8 +42,8 @@ ], # These are flags passed to the compiler for C++ only. 'cflags_cc': [ - # Compile using the C++14 standard. - '-std=c++14', + # Compile using the C++17 standard. + '-std=c++17', # CE CDM does not use exceptions or RTTI. '-fno-exceptions', '-fno-rtti', diff --git a/third_party/libcppbor.gyp b/third_party/libcppbor.gyp new file mode 100644 index 00000000..c045e2de --- /dev/null +++ b/third_party/libcppbor.gyp @@ -0,0 +1,22 @@ +{ + 'targets': [ + { + 'target_name': 'cppbor', + 'type': 'static_library', + 'sources': [ + 'libcppbor/src/cppbor.cpp', + 'libcppbor/src/cppbor_parse.cpp', + ], + 'include_dirs': [ + 'libcppbor/include', + 'libcppbor/include/cppbor', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + 'libcppbor/include', + 'libcppbor/include/cppbor', + ], + }, + }, + ], +} diff --git a/third_party/libcppbor/Android.bp b/third_party/libcppbor/Android.bp new file mode 100644 index 00000000..8f2b64a5 --- /dev/null +++ b/third_party/libcppbor/Android.bp @@ -0,0 +1,95 @@ +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package { + default_applicable_licenses: ["external_libcppbor_license"], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "external_libcppbor_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE", + ], +} + +cc_defaults { + name: "libcppbor_defaults", + cflags: [ + "-Wall", + "-Wextra", + "-Werror", + ], +} + +cc_library { + name: "libcppbor_external", + defaults: [ + "libcppbor_defaults", + ], + vendor_available: true, + host_supported: true, + srcs: [ + "src/cppbor.cpp", + "src/cppbor_parse.cpp", + ], + export_include_dirs: [ + "include/cppbor", + ], + shared_libs: [ + "libbase", + "libcrypto", + ] +} + +cc_test { + name: "cppbor_test_external", + defaults: [ + "libcppbor_defaults", + ], + srcs: [ + "tests/cppbor_test.cpp" + ], + shared_libs: [ + "libcppbor_external", + "libbase", + ], + static_libs: [ + "libgmock", + ], + test_suites: ["general-tests"], +} + +cc_test_host { + name: "cppbor_host_test_external", + defaults: [ + "libcppbor_defaults", + ], + srcs: [ + "tests/cppbor_test.cpp" + ], + shared_libs: [ + "libcppbor_external", + "libbase", + ], + static_libs: [ + "libgmock", + ], + test_suites: ["general-tests"], +} diff --git a/third_party/libcppbor/LICENSE b/third_party/libcppbor/LICENSE new file mode 100644 index 00000000..d6456956 --- /dev/null +++ b/third_party/libcppbor/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/libcppbor/METADATA b/third_party/libcppbor/METADATA new file mode 100644 index 00000000..d97975ca --- /dev/null +++ b/third_party/libcppbor/METADATA @@ -0,0 +1,3 @@ +third_party { + license_type: NOTICE +} diff --git a/third_party/libcppbor/README.md b/third_party/libcppbor/README.md new file mode 100644 index 00000000..0fbbcf3e --- /dev/null +++ b/third_party/libcppbor/README.md @@ -0,0 +1,226 @@ +LibCppBor: A Modern C++ CBOR Parser and Generator +============================================== + +TODO(b/254108623): +This is a modified version of LibCppBor and is C++-14 compliant. The released +version can be found at +https://android.googlesource.com/platform/external/libcppbor, which requires +C++-17. This is a reminder of refreshing the library with the latest source +above once we officially move to C++-17. + +LibCppBor provides a natural and easy-to-use syntax for constructing and +parsing CBOR messages. It does not (yet) support all features of +CBOR, nor (yet) support validation against CDDL schemata, though both +are planned. CBOR features that aren't supported include: + +* Indefinite length values +* Semantic tagging +* Floating point + +LibCppBor requires C++-17. + +## CBOR representation + +LibCppBor represents CBOR data items as instances of the `Item` class or, +more precisely, as instances of subclasses of `Item`, since `Item` is a +pure interface. The subclasses of `Item` correspond almost one-to-one +with CBOR major types, and are named to match the CDDL names to which +they correspond. They are: + +* `Uint` corresponds to major type 0, and can hold unsigned integers + up through (2^64 - 1). +* `Nint` corresponds to major type 1. It can only hold values from -1 + to -(2^63 - 1), since it's internal representation is an int64_t. + This can be fixed, but it seems unlikely that applications will need + the omitted range from -(2^63) to (2^64 - 1), since it's + inconvenient to represent them in many programming languages. +* `Int` is an abstract base of `Uint` and `Nint` that facilitates + working with all signed integers representable with int64_t. +* `Bstr` corresponds to major type 2, a byte string. +* `Tstr` corresponds to major type 3, a text string. +* `Array` corresponds to major type 4, an Array. It holds a + variable-length array of `Item`s. +* `Map` corresponds to major type 5, a Map. It holds a + variable-length array of pairs of `Item`s. +* `Simple` corresponds to major type 7. It's an abstract class since + items require more specific type. +* `Bool` is the only currently-implemented subclass of `Simple`. + +Note that major type 6, semantic tag, is not yet implemented. + +In practice, users of LibCppBor will rarely use most of these classes +when generating CBOR encodings. This is because LibCppBor provides +straightforward conversions from the obvious normal C++ types. +Specifically, the following conversions are provided in appropriate +contexts: + +* Signed and unsigned integers convert to `Uint` or `Nint`, as + appropriate. +* `std::string`, `std::string_view`, `const char*` and + `std::pair` convert to `Tstr`. +* `std::vector`, `std::pair` and `std::pair` convert to `Bstr`. +* `bool` converts to `Bool`. + +## CBOR generation + +### Complete tree generation + +The set of `encode` methods in `Item` provide the interface for +producing encoded CBOR. The basic process for "complete tree" +generation (as opposed to "incremental" generation, which is discussed +below) is to construct an `Item` which models the data to be encoded, +and then call one of the `encode` methods, whichever is convenient for +the encoding destination. A trivial example: + +``` +cppbor::Uint val(0); +std::vector encoding = val.encode(); +``` + + It's relatively rare that single values are encoded as above. More often, the + "root" data item will be an `Array` or `Map` which contains a more complex structure.For example + : + +``` using cppbor::Map; +using cppbor::Array; + +std::vector vec = // ... + Map val("key1", Array(Map("key_a", 99 "key_b", vec), "foo"), "key2", true); +std::vector encoding = val.encode(); +``` + +This creates a map with two entries, with `Tstr` keys "Outer1" and +"Outer2", respectively. The "Outer1" entry has as its value an +`Array` containing a `Map` and a `Tstr`. The "Outer2" entry has a +`Bool` value. + +This example demonstrates how automatic conversion of C++ types to +LibCppBor `Item` subclass instances is done. Where the caller provides a +C++ or C string, a `Tstr` entry is added. Where the caller provides +an integer literal or variable, a `Uint` or `Nint` is added, depending +on whether the value is positive or negative. + +As an alternative, a more fluent-style API is provided for building up +structures. For example: + +``` +using cppbor::Map; +using cppbor::Array; + +std::vector vec = // ... + Map val(); +val.add("key1", Array().add(Map().add("key_a", 99).add("key_b", vec)).add("foo")).add("key2", true); +std::vector encoding = val.encode(); +``` + + An advantage of this interface over the constructor - + based creation approach above is that it need not be done all at once. + The `add` methods return a reference to the object added to to allow calls to be chained, + but chaining is not necessary; calls can be made + sequentially, as the data to add is available. + +#### `encode` methods + +There are several variations of `Item::encode`, all of which +accomplish the same task but output the encoded data in different +ways, and with somewhat different performance characteristics. The +provided options are: + +* `bool encode(uint8\_t** pos, const uint8\_t* end)` encodes into the + buffer referenced by the range [`*pos`, end). `*pos` is moved. If + the encoding runs out of buffer space before finishing, the method + returns false. This is the most efficient way to encode, into an + already-allocated buffer. +* `void encode(EncodeCallback encodeCallback)` calls `encodeCallback` + for each encoded byte. It's the responsibility of the implementor + of the callback to behave safely in the event that the output buffer + (if applicable) is exhausted. This is less efficient than the prior + method because it imposes an additional function call for each byte. +* `template void encode(OutputIterator i)` + encodes into the provided iterator. SFINAE ensures that the + template doesn't match for non-iterators. The implementation + actually uses the callback-based method, plus has whatever overhead + the iterator adds. +* `std::vector encode()` creates a new std::vector, reserves + sufficient capacity to hold the encoding, and inserts the encoded + bytes with a std::pushback_iterator and the previous method. +* `std::string toString()` does the same as the previous method, but + returns a string instead of a vector. + +### Incremental generation + +Incremental generation requires deeper understanding of CBOR, because +the library can't do as much to ensure that the output is valid. The +basic tool for intcremental generation is the `encodeHeader` +function. There are two variations, one which writes into a buffer, +and one which uses a callback. Both simply write out the bytes of a +header. To construct the same map as in the above examples, +incrementally, one might write: + +``` +using namespace cppbor; // For example brevity + +std::vector encoding; +auto iter = std::back_inserter(result); +encodeHeader(MAP, 2 /* # of map entries */, iter); +std::string s = "key1"; +encodeHeader(TSTR, s.size(), iter); +std::copy(s.begin(), s.end(), iter); +encodeHeader(ARRAY, 2 /* # of array entries */, iter); +Map().add("key_a", 99).add("key_b", vec).encode(iter) +s = "foo"; +encodeHeader(TSTR, foo.size(), iter); +std::copy(s.begin(), s.end(), iter); +s = "key2"; +encodeHeader(TSTR, foo.size(), iter); +std::copy(s.begin(), s.end(), iter); +encodeHeader(SIMPLE, TRUE, iter); +``` + +As the above example demonstrates, the styles can be mixed -- Note the +creation and encoding of the inner Map using the fluent style. + +## Parsing + +LibCppBor also supports parsing of encoded CBOR data, with the same +feature set as encoding. There are two basic approaches to parsing, +"full" and "stream" + +### Full parsing + +Full parsing means completely parsing a (possibly-compound) data +item from a byte buffer. The `parse` functions that do not take a +`ParseClient` pointer do this. They return a `ParseResult` which is a +tuple of three values: + +* std::unique_ptr that points to the parsed item, or is nullptr + if there was a parse error. +* const uint8_t* that points to the byte after the end of the decoded + item, or to the first unparseable byte in the event of an error. +* std::string that is empty on success or contains an error message if + a parse error occurred. + +Assuming a successful parse, you can then use `Item::type()` to +discover the type of the parsed item (e.g. MAP), and then use the +appropriate `Item::as*()` method (e.g. `Item::asMap()`) to get a +pointer to an interface which allows you to retrieve specific values. + +### Stream parsing + +Stream parsing is more complex, but more flexible. To use +StreamParsing, you must create your own subclass of `ParseClient` and +call one of the `parse` functions that accepts it. See the +`ParseClient` methods docstrings for details. + +One unusual feature of stream parsing is that the `ParseClient` +callback methods not only provide the parsed Item, but also pointers +to the portion of the buffer that encode that Item. This is useful +if, for example, you want to find an element inside of a structure, +and then copy the encoding of that sub-structure, without bothering to +parse the rest. + +The full parser is implemented with the stream parser. + +### Disclaimer +This is not an officially supported Google product diff --git a/third_party/libcppbor/include/cppbor/cppbor.h b/third_party/libcppbor/include/cppbor/cppbor.h new file mode 100644 index 00000000..75e577a0 --- /dev/null +++ b/third_party/libcppbor/include/cppbor/cppbor.h @@ -0,0 +1,1127 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus >= 201402L || \ + (defined __cpp_lib_make_unique && __cpp_lib_make_unique >= 201304L) || \ + (defined(_MSC_VER) && _MSC_VER >= 1900) +using std::make_unique; +#else +template +std::unique_ptr make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + +template +using enable_if_t = typename std::enable_if::type; + +template +using remove_cv_t = typename std::remove_cv::type; + +template +using remove_reference_t = typename std::remove_reference::type; + +template +using remove_pointer_t = typename std::remove_pointer::type; + +template +using decay_t = typename std::decay::type; + +template +struct is_null_pointer : std::is_same> {}; + +namespace cppbor { + +enum MajorType : uint8_t { + UINT = 0 << 5, + NINT = 1 << 5, + BSTR = 2 << 5, + TSTR = 3 << 5, + ARRAY = 4 << 5, + MAP = 5 << 5, + SEMANTIC = 6 << 5, + SIMPLE = 7 << 5, +}; + +enum SimpleType { + BOOLEAN, + NULL_T, // Only two supported, as yet. +}; + +enum SpecialAddlInfoValues : uint8_t { + FALSE = 20, + TRUE = 21, + NULL_V = 22, + ONE_BYTE_LENGTH = 24, + TWO_BYTE_LENGTH = 25, + FOUR_BYTE_LENGTH = 26, + EIGHT_BYTE_LENGTH = 27, +}; + +class Item; +class Uint; +class Nint; +class Int; +class Tstr; +class Bstr; +class Simple; +class Bool; +class Array; +class Map; +class Null; +class SemanticTag; +class EncodedItem; + +/** + * Returns the size of a CBOR header that contains the additional info value + * addlInfo. + */ +size_t headerSize(uint64_t addlInfo); + +/** + * Encodes a CBOR header with the specified type and additional info into the + * range [pos, end). Returns a pointer to one past the last byte written, or + * nullptr if there isn't sufficient space to write the header. + */ +uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, + const uint8_t* end); + +using EncodeCallback = std::function; + +/** + * Encodes a CBOR header with the specified type and additional info, passing + * each byte in turn to encodeCallback. + */ +void encodeHeader(MajorType type, uint64_t addlInfo, + EncodeCallback encodeCallback); + +/** + * Encodes a CBOR header with the specified type and additional info, writing + * each byte to the provided OutputIterator. + */ +template ::iterator_category>::value>> +void encodeHeader(MajorType type, uint64_t addlInfo, OutputIterator iter) { + return encodeHeader(type, addlInfo, [&](uint8_t v) { *iter++ = v; }); +} + +/** + * Item represents a CBOR-encodeable data item. Item is an abstract interface + * with a set of virtual methods that allow encoding of the item or conversion + * to the appropriate derived type. + */ +class Item { + public: + virtual ~Item() {} + + /** + * Returns the CBOR type of the item. + */ + virtual MajorType type() const = 0; + + // These methods safely downcast an Item to the appropriate subclass. + virtual Int* asInt() { return nullptr; } + const Int* asInt() const { return const_cast(this)->asInt(); } + virtual Uint* asUint() { return nullptr; } + const Uint* asUint() const { return const_cast(this)->asUint(); } + virtual Nint* asNint() { return nullptr; } + const Nint* asNint() const { return const_cast(this)->asNint(); } + virtual Tstr* asTstr() { return nullptr; } + const Tstr* asTstr() const { return const_cast(this)->asTstr(); } + virtual Bstr* asBstr() { return nullptr; } + const Bstr* asBstr() const { return const_cast(this)->asBstr(); } + virtual Simple* asSimple() { return nullptr; } + const Simple* asSimple() const { return const_cast(this)->asSimple(); } + virtual Map* asMap() { return nullptr; } + const Map* asMap() const { return const_cast(this)->asMap(); } + virtual Array* asArray() { return nullptr; } + const Array* asArray() const { return const_cast(this)->asArray(); } + + // Like those above, these methods safely downcast an Item when it's actually + // a SemanticTag. However, if you think you want to use these methods, you + // probably don't. Typically, the way you should handle tagged Items is by + // calling the appropriate method above (e.g. asInt()) which will return a + // pointer to the tagged Item, rather than the tag itself. If you want to + // find out if the Item* you're holding is to something with one or more tags + // applied, see semanticTagCount() and semanticTag() below. + virtual SemanticTag* asSemanticTag() { return nullptr; } + const SemanticTag* asSemanticTag() const { + return const_cast(this)->asSemanticTag(); + } + + /** + * Returns the number of semantic tags prefixed to this Item. + */ + virtual size_t semanticTagCount() const { return 0; } + + /** + * Returns the semantic tag at the specified nesting level `nesting`, iff + * `nesting` is less than the value returned by semanticTagCount(). + * + * CBOR tags are "nested" by applying them in sequence. The "rightmost" tag + * is the "inner" tag. That is, given: + * + * 4(5(6("AES"))) which encodes as C1 C2 C3 63 414553 + * + * The tstr "AES" is tagged with 6. The combined entity ("AES" tagged with 6) + * is tagged with 5, etc. So in this example, semanticTagCount() would return + * 3, and semanticTag(0) would return 5 semanticTag(1) would return 5 and + * semanticTag(2) would return 4. For values of n > 2, semanticTag(n) will + * return 0, but this is a meaningless value. + * + * If this layering is confusing, you probably don't have to worry about it. + * Nested tagging does not appear to be common, so semanticTag(0) is the only + * one you'll use. + */ + virtual uint64_t semanticTag(size_t /* nesting */ = 0) const { return 0; } + + /** + * Returns true if this is a "compound" item, i.e. one that contains one or + * more other items. + */ + virtual bool isCompound() const { return false; } + + bool operator==(const Item& other) const&; + bool operator!=(const Item& other) const& { return !(*this == other); } + + /** + * Returns the number of bytes required to encode this Item into CBOR. Note + * that if this is a complex Item, calling this method will require walking + * the whole tree. + */ + virtual size_t encodedSize() const = 0; + + /** + * Encodes the Item into buffer referenced by range [*pos, end). Returns a + * pointer to one past the last position written. Returns nullptr if there + * isn't enough space to encode. + */ + virtual uint8_t* encode(uint8_t* pos, const uint8_t* end) const = 0; + + /** + * Encodes the Item by passing each encoded byte to encodeCallback. + */ + virtual void encode(EncodeCallback encodeCallback) const = 0; + + /** + * Clones the Item + */ + virtual std::unique_ptr clone() const = 0; + + /** + * Encodes the Item into the provided OutputIterator. + */ + template ::iterator_category> + void encode(OutputIterator i) const { + return encode([&](uint8_t v) { *i++ = v; }); + } + + /** + * Encodes the Item into a new std::vector. + */ + std::vector encode() const { + std::vector retval; + retval.reserve(encodedSize()); + encode(std::back_inserter(retval)); + return retval; + } + + /** + * Encodes the Item into a new std::string. + */ + std::string toString() const { + std::string retval; + retval.reserve(encodedSize()); + encode([&](uint8_t v) { retval.push_back(v); }); + return retval; + } + + /** + * Encodes only the header of the Item. + */ + inline uint8_t* encodeHeader(uint64_t addlInfo, uint8_t* pos, + const uint8_t* end) const { + return ::cppbor::encodeHeader(type(), addlInfo, pos, end); + } + + /** + * Encodes only the header of the Item. + */ + inline void encodeHeader(uint64_t addlInfo, + EncodeCallback encodeCallback) const { + ::cppbor::encodeHeader(type(), addlInfo, std::move(encodeCallback)); + } +}; + +/** + * EncodedItem represents a bit of already-encoded CBOR. Caveat emptor: It does + * no checking to ensure that the provided data is a valid encoding, cannot be + * meaninfully-compared with other kinds of items and you cannot use the as*() + * methods to find out what's inside it. + */ +class EncodedItem : public Item { + public: + explicit EncodedItem(std::vector value) : mValue(std::move(value)) {} + + bool operator==(const EncodedItem& other) const& { + return mValue == other.mValue; + } + + // Type can't be meaningfully-obtained. We could extract the type from the + // first byte and return it, but you can't do any of the normal things with an + // EncodedItem so there's no point. + MajorType type() const override { + assert(false); + return static_cast(-1); + } + size_t encodedSize() const override { return mValue.size(); } + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + if (end - pos < static_cast(mValue.size())) return nullptr; + return std::copy(mValue.begin(), mValue.end(), pos); + } + void encode(EncodeCallback encodeCallback) const override { + std::for_each(mValue.begin(), mValue.end(), std::move(encodeCallback)); + } + std::unique_ptr clone() const override { + return make_unique(mValue); + } + + private: + std::vector mValue; +}; + +/** + * Int is an abstraction that allows Uint and Nint objects to be manipulated + * without caring about the sign. + */ +class Int : public Item { + public: + bool operator==(const Int& other) const& { return value() == other.value(); } + + virtual int64_t value() const = 0; + using Item::asInt; + Int* asInt() override { return this; } +}; + +/** + * Uint is a concrete Item that implements CBOR major type 0. + */ +class Uint : public Int { + public: + static constexpr MajorType kMajorType = UINT; + + explicit Uint(uint64_t v) : mValue(v) {} + + bool operator==(const Uint& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + using Item::asUint; + Uint* asUint() override { return this; } + + size_t encodedSize() const override { return headerSize(mValue); } + + int64_t value() const override { return mValue; } + uint64_t unsignedValue() const { return mValue; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(mValue, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue, std::move(encodeCallback)); + } + + std::unique_ptr clone() const override { + return make_unique(mValue); + } + + private: + uint64_t mValue; +}; + +/** + * Nint is a concrete Item that implements CBOR major type 1. + + * Note that it is incapable of expressing the full range of major type 1 + values, becaue it can only + * express values that fall into the range [std::numeric_limits::min(), + -1]. It cannot + * express values in the range [std::numeric_limits::min() - 1, + * -std::numeric_limits::max()]. + */ +class Nint : public Int { + public: + static constexpr MajorType kMajorType = NINT; + + explicit Nint(int64_t v); + + bool operator==(const Nint& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + using Item::asNint; + Nint* asNint() override { return this; } + size_t encodedSize() const override { return headerSize(addlInfo()); } + + int64_t value() const override { return mValue; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(addlInfo(), pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(addlInfo(), std::move(encodeCallback)); + } + + std::unique_ptr clone() const override { + return make_unique(mValue); + } + + private: + uint64_t addlInfo() const { return -1ll - mValue; } + + int64_t mValue; +}; + +/** + * Bstr is a concrete Item that implements major type 2. + */ +class Bstr : public Item { + public: + static constexpr MajorType kMajorType = BSTR; + + // Construct an empty Bstr + explicit Bstr() {} + + // Construct from a vector + explicit Bstr(std::vector v) : mValue(std::move(v)) {} + + // Construct from a string + explicit Bstr(const std::string& v) + : mValue(reinterpret_cast(v.data()), + reinterpret_cast(v.data()) + v.size()) {} + + // Construct from a pointer/size pair + explicit Bstr(const std::pair& buf) + : mValue(buf.first, buf.first + buf.second) {} + + // Construct from a pair of iterators + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + explicit Bstr(const std::pair& pair) + : mValue(pair.first, pair.second) {} + + // Construct from an iterator range. + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + Bstr(I1 begin, I2 end) : mValue(begin, end) {} + + bool operator==(const Bstr& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + using Item::asBstr; + Bstr* asBstr() override { return this; } + size_t encodedSize() const override { + return headerSize(mValue.size()) + mValue.size(); + } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue.size(), encodeCallback); + encodeValue(std::move(encodeCallback)); + } + + const std::vector& value() const { return mValue; } + std::vector&& moveValue() { return std::move(mValue); } + + std::unique_ptr clone() const override { + return make_unique(mValue); + } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::vector mValue; +}; + +/** + * Tstr is a concrete Item that implements major type 3. + */ +class Tstr : public Item { + public: + static constexpr MajorType kMajorType = TSTR; + + // Construct from a string + explicit Tstr(std::string v) : mValue(std::move(v)) {} + + // Construct from a C string + explicit Tstr(const char* v) : mValue(std::string(v)) {} + + // Construct from a pair of iterators + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + explicit Tstr(const std::pair& pair) + : mValue(pair.first, pair.second) {} + + // Construct from an iterator range + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + Tstr(I1 begin, I2 end) : mValue(begin, end) {} + + bool operator==(const Tstr& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + using Item::asTstr; + Tstr* asTstr() override { return this; } + size_t encodedSize() const override { + return headerSize(mValue.size()) + mValue.size(); + } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue.size(), encodeCallback); + encodeValue(std::move(encodeCallback)); + } + + const std::string& value() const { return mValue; } + std::string&& moveValue() { return std::move(mValue); } + + std::unique_ptr clone() const override { + return make_unique(mValue); + } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::string mValue; +}; + +/* + * Array is a concrete Item that implements CBOR major type 4. + * + * Note that Arrays are not copyable. This is because copying them is expensive + * and making them move-only ensures that they're never copied accidentally. If + * you actually want to copy an Array, use the clone() method. + */ +class Array : public Item { + public: + static constexpr MajorType kMajorType = ARRAY; + + Array() = default; + Array(const Array& other) = delete; + Array(Array&&) = default; + Array& operator=(const Array&) = delete; + Array& operator=(Array&&) = default; + + bool operator==(const Array& other) const&; + + /** + * Construct an Array from a variable number of arguments of different types. + * See details::makeItem below for details on what types may be provided. In + * general, this accepts all of the types you'd expect and doest the things + * you'd expect (integral values are addes as Uint or Nint, std::string and + * char* are added as Tstr, bools are added as Bool, etc.). + */ + // template + // Array(Args&&... args); + + /** + * Append a single element to the Array, of any compatible type. + */ + template + Array& add(T&& v) &; + template + Array&& add(T&& v) &&; + + bool isCompound() const override { return true; } + + virtual size_t size() const { return mEntries.size(); } + + size_t encodedSize() const override { + return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(size()), + [](size_t sum, const std::unique_ptr& entry) { + return sum + entry->encodedSize(); + }); + } + + using Item::encode; // Make base versions visible. + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override; + + const std::unique_ptr& operator[](size_t index) const { + return get(index); + } + std::unique_ptr& operator[](size_t index) { return get(index); } + + const std::unique_ptr& get(size_t index) const { + return mEntries[index]; + } + std::unique_ptr& get(size_t index) { return mEntries[index]; } + + MajorType type() const override { return kMajorType; } + using Item::asArray; + Array* asArray() override { return this; } + + std::unique_ptr clone() const override; + + std::vector>::iterator begin() { + return mEntries.begin(); + } + std::vector>::const_iterator begin() const { + return mEntries.begin(); + } + std::vector>::iterator end() { return mEntries.end(); } + std::vector>::const_iterator end() const { + return mEntries.end(); + } + + protected: + std::vector> mEntries; +}; + +/* + * Map is a concrete Item that implements CBOR major type 5. + * + * Note that Maps are not copyable. This is because copying them is expensive + * and making them move-only ensures that they're never copied accidentally. If + * you actually want to copy a Map, use the clone() method. + */ +class Map : public Item { + public: + static constexpr MajorType kMajorType = MAP; + + using entry_type = std::pair, std::unique_ptr>; + + Map() = default; + Map(const Map& other) = delete; + Map(Map&&) = default; + Map& operator=(const Map& other) = delete; + Map& operator=(Map&&) = default; + + bool operator==(const Map& other) const&; + + /** + * Construct a Map from a variable number of arguments of different types. An + * even number of arguments must be provided (this is verified statically). + * See details::makeItem below for details on what types may be provided. In + * general, this accepts all of the types you'd expect and doest the things + * you'd expect (integral values are addes as Uint or Nint, std::string and + * char* are added as Tstr, bools are added as Bool, etc.). + */ + template + Map(Args&&... args); + + /** + * Append a key/value pair to the Map, of any compatible types. + */ + template + Map& add(Key&& key, Value&& value) &; + template + Map&& add(Key&& key, Value&& value) &&; + + bool isCompound() const override { return true; } + + virtual size_t size() const { return mEntries.size(); } + + size_t encodedSize() const override { + return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(size()), + [](size_t sum, const entry_type& entry) { + return sum + entry.first->encodedSize() + + entry.second->encodedSize(); + }); + } + + using Item::encode; // Make base versions visible. + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override; + + /** + * Find and return the value associated with `key`, if any. + * + * If the searched-for `key` is not present, returns `nullptr`. + * + * Note that if the map is canonicalized (sorted), Map::get() peforms a binary + * search. If your map is large and you're searching in it many times, it may + * be worthwhile to canonicalize it to make Map::get() faster. Any use of a + * method that might modify the map disables the speedup. + */ + template + const std::unique_ptr& get(Key key) const; + + // Note that use of non-const operator[] marks the map as not canonicalized. + entry_type& operator[](size_t index) { + mCanonicalized = false; + return mEntries[index]; + } + const entry_type& operator[](size_t index) const { return mEntries[index]; } + + MajorType type() const override { return kMajorType; } + using Item::asMap; + Map* asMap() override { return this; } + + /** + * Sorts the map in canonical order, as defined in RFC 7049. Use this before + * encoding if you want canonicalization; cppbor does not canonicalize by + * default, though the integer encodings are always canonical and cppbor does + * not support indefinite-length encodings, so map order canonicalization is + * the only thing that needs to be done. + * + * @param recurse If set to true, canonicalize() will also walk the contents + * of the map and canonicalize any contained maps as well. + */ + Map& canonicalize(bool recurse = false) &; + Map&& canonicalize(bool recurse = false) && { + canonicalize(recurse); + return std::move(*this); + } + + bool isCanonical() { return mCanonicalized; } + + std::unique_ptr clone() const override; + + std::vector::iterator begin() { + mCanonicalized = false; + return mEntries.begin(); + } + std::vector::const_iterator begin() const { + return mEntries.begin(); + } + std::vector::iterator end() { + mCanonicalized = false; + return mEntries.end(); + } + std::vector::const_iterator end() const { return mEntries.end(); } + + // Returns true if a < b, per CBOR map key canonicalization rules. + static bool keyLess(const Item* a, const Item* b); + + protected: + std::vector mEntries; + + private: + bool mCanonicalized = false; +}; + +class SemanticTag : public Item { + public: + static constexpr MajorType kMajorType = SEMANTIC; + + template + SemanticTag(uint64_t tagValue, T&& taggedItem); + SemanticTag(const SemanticTag& other) = delete; + SemanticTag(SemanticTag&&) = default; + SemanticTag& operator=(const SemanticTag& other) = delete; + SemanticTag& operator=(SemanticTag&&) = default; + + bool operator==(const SemanticTag& other) const& { + return mValue == other.mValue && *mTaggedItem == *other.mTaggedItem; + } + + bool isCompound() const override { return true; } + + virtual size_t size() const { return 1; } + + // Encoding returns the tag + enclosed Item. + size_t encodedSize() const override { + return headerSize(mValue) + mTaggedItem->encodedSize(); + } + + using Item::encode; // Make base versions visible. + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override; + + // type() is a bit special. In normal usage it should return the wrapped + // type, but during parsing when we haven't yet parsed the tagged item, it + // needs to return SEMANTIC. + MajorType type() const override { + return mTaggedItem ? mTaggedItem->type() : SEMANTIC; + } + using Item::asSemanticTag; + SemanticTag* asSemanticTag() override { return this; } + + // Type information reflects the enclosed Item. Note that if the + // immediately-enclosed Item is another tag, these methods will recurse down + // to the non-tag Item. + using Item::asInt; + Int* asInt() override { return mTaggedItem->asInt(); } + using Item::asUint; + Uint* asUint() override { return mTaggedItem->asUint(); } + using Item::asNint; + Nint* asNint() override { return mTaggedItem->asNint(); } + using Item::asTstr; + Tstr* asTstr() override { return mTaggedItem->asTstr(); } + using Item::asBstr; + Bstr* asBstr() override { return mTaggedItem->asBstr(); } + using Item::asSimple; + Simple* asSimple() override { return mTaggedItem->asSimple(); } + using Item::asMap; + Map* asMap() override { return mTaggedItem->asMap(); } + using Item::asArray; + Array* asArray() override { return mTaggedItem->asArray(); } + + std::unique_ptr clone() const override; + + size_t semanticTagCount() const override; + uint64_t semanticTag(size_t nesting = 0) const override; + + protected: + SemanticTag() = default; + SemanticTag(uint64_t value) : mValue(value) {} + uint64_t mValue; + std::unique_ptr mTaggedItem; +}; + +/** + * Simple is abstract Item that implements CBOR major type 7. It is intended to + * be subclassed to create concrete Simple types. At present only Bool is + * provided. + */ +class Simple : public Item { + public: + static constexpr MajorType kMajorType = SIMPLE; + + bool operator==(const Simple& other) const&; + + virtual SimpleType simpleType() const = 0; + MajorType type() const override { return kMajorType; } + + Simple* asSimple() override { return this; } + + virtual const Bool* asBool() const { return nullptr; }; + virtual const Null* asNull() const { return nullptr; }; +}; + +/** + * Bool is a concrete type that implements CBOR major type 7, with additional + * item values for TRUE and FALSE. + */ +class Bool : public Simple { + public: + static constexpr SimpleType kSimpleType = BOOLEAN; + + explicit Bool(bool v) : mValue(v) {} + + bool operator==(const Bool& other) const& { return mValue == other.mValue; } + + SimpleType simpleType() const override { return kSimpleType; } + const Bool* asBool() const override { return this; } + + size_t encodedSize() const override { return 1; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(mValue ? TRUE : FALSE, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue ? TRUE : FALSE, std::move(encodeCallback)); + } + + bool value() const { return mValue; } + + std::unique_ptr clone() const override { + return make_unique(mValue); + } + + private: + bool mValue; +}; + +/** + * Null is a concrete type that implements CBOR major type 7, with additional + * item value for NULL + */ +class Null : public Simple { + public: + static constexpr SimpleType kSimpleType = NULL_T; + + explicit Null() {} + + SimpleType simpleType() const override { return kSimpleType; } + const Null* asNull() const override { return this; } + + size_t encodedSize() const override { return 1; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(NULL_V, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(NULL_V, std::move(encodeCallback)); + } + + std::unique_ptr clone() const override { return make_unique(); } +}; + +/** + * Returns pretty-printed CBOR for |item| + * + * If a byte-string is larger than |maxBStrSize| its contents will not be + * printed, instead the value of the form "" will be printed. Pass zero + * for |maxBStrSize| to disable this. + * + * The |mapKeysToNotPrint| parameter specifies the name of map values to not + * print. This is useful for unit tests. + */ +std::string prettyPrint(const Item* item, size_t maxBStrSize = 32, + const std::vector& mapKeysNotToPrint = {}); + +/** + * Details. Mostly you shouldn't have to look below, except perhaps at the + * docstring for makeItem. + */ +namespace details { + +template +struct is_iterator_pair_over : public std::false_type {}; + +template +struct is_iterator_pair_over< + std::pair, V, + typename std::enable_if::value_type>::value>::type> + : public std::true_type {}; + +template +struct is_unique_ptr_of_subclass_of_v : public std::false_type {}; + +template +struct is_unique_ptr_of_subclass_of_v< + T, std::unique_ptr

, + typename std::enable_if::value>::type> + : public std::true_type {}; + +/* check if type is one of std::string (1), std::string_view (2), + * null-terminated char* (3) or pair of iterators (4)*/ +template +struct is_text_type_v : public std::false_type {}; + +template +struct is_text_type_v< + T, typename std::enable_if< + /* case 1 */ // + std::is_same>, std::string>::value + /* case 2 */ // + // || is_same_v>, std::string_view> + /* case 3 */ // + || std::is_same>, char*>::value // + || std::is_same>, const char*>::value + /* case 4 */ + || details::is_iterator_pair_over::value>::type> + : public std::true_type {}; + +/** + * Construct a unique_ptr from many argument types. Accepts: + * + * (a) booleans; + * (b) integers, all sizes and signs; + * (c) text strings, as defined by is_text_type_v above; + * (d) byte strings, as std::vector(d1), pair of iterators (d2) or + * pair (d3); and (e) Item subclass instances, including Array + * and Map. Items may be provided by naked pointer (e1), unique_ptr (e2), + * reference (e3) or value (e3). If provided by reference or value, will be + * moved if possible. If provided by pointer, ownership is taken. (f) null + * pointer; (g) enums, using the underlying integer value. + */ +template ::value>::type* = nullptr> +std::unique_ptr makeItem(T v) { + Item* p = new Bool(v); + return std::unique_ptr(p); +} + +template ::value>::type* = nullptr> +std::unique_ptr makeItem(T v) { + Item* p = nullptr; + if (v < 0) { + p = new Nint(v); + } else { + p = new Uint(static_cast(v)); + } + return std::unique_ptr(p); +} + +template ::value>::type* = nullptr> +std::unique_ptr makeItem(T v) { + Item* p = new Tstr(v); + return std::unique_ptr(p); +} + +template < + typename T, + typename std::enable_if< + /* case d1 */ std::is_same>, + std::vector>::value + /* case d2 */ // + || details::is_iterator_pair_over::value + /* case d3 */ // + || std::is_same>, + std::pair>::value>::type* = nullptr> +std::unique_ptr makeItem(T v) { + Item* p = new Bstr(std::move(v)); + return std::unique_ptr(p); +} + +template < + typename T, + typename std::enable_if< + /* case e1 */ std::is_pointer::value && + std::is_base_of>::value>::type* = nullptr> +std::unique_ptr makeItem(T v) { + Item* p = v; + return std::unique_ptr(p); +} + +template ::value>::type* = nullptr> +std::unique_ptr makeItem(T v) { + Item* p = v.release(); + return std::unique_ptr(p); +} + +template ::value>::type* = nullptr> +std::unique_ptr makeItem(T v) { + Item* p = new T(std::move(v)); + return std::unique_ptr(p); +} + +template ::value>::type* = nullptr> +std::unique_ptr makeItem(T) { + Item* p = new Null(); + return std::unique_ptr(p); +} + +template ::value>::type* = nullptr> +std::unique_ptr makeItem(T v) { + Item* p = makeItem(static_cast::type>(v)); + return std::unique_ptr(p); +} + +inline void map_helper(Map& /* map */) {} + +template +inline void map_helper(Map& map, Key&& key, Value&& value, Rest&&... rest) { + map.add(std::forward(key), std::forward(value)); + map_helper(map, std::forward(rest)...); +} + +} // namespace details + +// template >>::value || ...)>> +// Array::Array(Args&&... args) { +// mEntries.reserve(sizeof...(args)); +// (mEntries.push_back(details::makeItem(std::forward(args))), ...); +// } + +template +Array& Array::add(T&& v) & { + mEntries.push_back(details::makeItem(std::forward(v))); + return *this; +} + +template +Array&& Array::add(T&& v) && { + mEntries.push_back(details::makeItem(std::forward(v))); + return std::move(*this); +} + +template > +Map::Map(Args&&... args) { + static_assert((sizeof...(Args)) % 2 == 0, + "Map must have an even number of entries"); + mEntries.reserve(sizeof...(args) / 2); + details::map_helper(*this, std::forward(args)...); +} + +template +Map& Map::add(Key&& key, Value&& value) & { + mEntries.push_back({details::makeItem(std::forward(key)), + details::makeItem(std::forward(value))}); + mCanonicalized = false; + return *this; +} + +template +Map&& Map::add(Key&& key, Value&& value) && { + this->add(std::forward(key), std::forward(value)); + return std::move(*this); +} + +static const std::unique_ptr kEmptyItemPtr; + +template ::value || + std::is_enum::value || + details::is_text_type_v::value>> +const std::unique_ptr& Map::get(Key key) const { + auto keyItem = details::makeItem(key); + + if (mCanonicalized) { + // It's sorted, so binary-search it. + auto found = + std::lower_bound(begin(), end(), keyItem.get(), + [](const entry_type& entry, const Item* itemKey) { + return keyLess(entry.first.get(), itemKey); + }); + return (found == end() || *found->first != *keyItem) ? kEmptyItemPtr + : found->second; + } else { + // Unsorted, do a linear search. + auto found = std::find_if(begin(), end(), [&](const entry_type& entry) { + return *entry.first == *keyItem; + }); + return found == end() ? kEmptyItemPtr : found->second; + } +} + +template +SemanticTag::SemanticTag(uint64_t value, T&& taggedItem) + : mValue(value), + mTaggedItem(details::makeItem(std::forward(taggedItem))) {} + +} // namespace cppbor \ No newline at end of file diff --git a/third_party/libcppbor/include/cppbor/cppbor_parse.h b/third_party/libcppbor/include/cppbor/cppbor_parse.h new file mode 100644 index 00000000..f1b36472 --- /dev/null +++ b/third_party/libcppbor/include/cppbor/cppbor_parse.h @@ -0,0 +1,150 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "cppbor.h" + +namespace cppbor { + +using ParseResult = std::tuple /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */>; + +/** + * Parse the first CBOR data item (possibly compound) from the range [begin, end). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +ParseResult parse(const uint8_t* begin, const uint8_t* end); + +/** + * Parse the first CBOR data item (possibly compound) from the byte vector. + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +inline ParseResult parse(const std::vector& encoding) { + return parse(encoding.data(), encoding.data() + encoding.size()); +} + +/** + * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +inline ParseResult parse(const uint8_t* begin, size_t size) { + return parse(begin, begin + size); +} + +/** + * Parse the first CBOR data item (possibly compound) from the value contained in a Bstr. + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +inline ParseResult parse(const Bstr* bstr) { + if (!bstr) + return ParseResult(nullptr, nullptr, "Null Bstr pointer"); + return parse(bstr->value()); +} + +class ParseClient; + +/** + * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the + * provided ParseClient when elements are found. + */ +void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient); + +/** + * Parse the CBOR data in the vector in streaming fashion, calling methods on the + * provided ParseClient when elements are found. + */ +inline void parse(const std::vector& encoding, ParseClient* parseClient) { + return parse(encoding.data(), encoding.data() + encoding.size(), parseClient); +} + +/** + * A pure interface that callers of the streaming parse functions must implement. + */ +class ParseClient { + public: + virtual ~ParseClient() {} + + /** + * Called when an item is found. The Item pointer points to the found item; use type() and + * the appropriate as*() method to examine the value. hdrBegin points to the first byte of the + * header, valueBegin points to the first byte of the value and end points one past the end of + * the item. In the case of header-only items, such as integers, and compound items (ARRAY, + * MAP or SEMANTIC) whose end has not yet been found, valueBegin and end are equal and point to + * the byte past the header. + * + * Note that for compound types (ARRAY, MAP, and SEMANTIC), the Item will have no content. For + * Map and Array items, the size() method will return a correct value, but the index operators + * are unsafe, and the object cannot be safely compared with another Array/Map. + * + * The method returns a ParseClient*. In most cases "return this;" will be the right answer, + * but a different ParseClient may be returned, which the parser will begin using. If the method + * returns nullptr, parsing will be aborted immediately. + */ + virtual ParseClient* item(std::unique_ptr& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end) = 0; + + /** + * Called when the end of a compound item (MAP or ARRAY) is found. The item argument will be + * the same one passed to the item() call -- and may be empty if item() moved its value out. + * hdrBegin, valueBegin and end point to the beginning of the item header, the beginning of the + * first contained value, and one past the end of the last contained value, respectively. + * + * Note that the Item will have no content. + * + * As with item(), itemEnd() can change the ParseClient by returning a different one, or end the + * parsing by returning nullptr; + */ + virtual ParseClient* itemEnd(std::unique_ptr& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end) = 0; + + /** + * Called when parsing encounters an error. position is set to the first unparsed byte (one + * past the last successfully-parsed byte) and errorMessage contains an message explaining what + * sort of error occurred. + */ + virtual void error(const uint8_t* position, const std::string& errorMessage) = 0; +}; + +} // namespace cppbor diff --git a/third_party/libcppbor/src/cppbor.cpp b/third_party/libcppbor/src/cppbor.cpp new file mode 100644 index 00000000..254f2041 --- /dev/null +++ b/third_party/libcppbor/src/cppbor.cpp @@ -0,0 +1,567 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cppbor.h" + +#include + +#include + +using std::string; +using std::vector; + +#define CHECK(x) (void)(x) + +namespace cppbor { + +namespace { + +template ::value>> +Iterator writeBigEndian(T value, Iterator pos) { + for (unsigned i = 0; i < sizeof(value); ++i) { + *pos++ = static_cast(value >> (8 * (sizeof(value) - 1))); + value = static_cast(value << 8); + } + return pos; +} + +template ::value>> +void writeBigEndian(T value, std::function& cb) { + for (unsigned i = 0; i < sizeof(value); ++i) { + cb(static_cast(value >> (8 * (sizeof(value) - 1)))); + value = static_cast(value << 8); + } +} + +bool cborAreAllElementsNonCompound(const Item* compoundItem) { + if (compoundItem->type() == ARRAY) { + const Array* array = compoundItem->asArray(); + for (size_t n = 0; n < array->size(); n++) { + const Item* entry = (*array)[n].get(); + switch (entry->type()) { + case ARRAY: + case MAP: + return false; + default: + break; + } + } + } else { + const Map* map = compoundItem->asMap(); + for (auto& entry : *map) { + auto& keyEntry = entry.first; + auto& valueEntry = entry.second; + + switch (keyEntry->type()) { + case ARRAY: + case MAP: + return false; + default: + break; + } + switch (valueEntry->type()) { + case ARRAY: + case MAP: + return false; + default: + break; + } + } + } + return true; +} + +bool prettyPrintInternal(const Item* item, string& out, size_t indent, + size_t maxBStrSize, + const vector& mapKeysToNotPrint) { + if (!item) { + out.append(""); + return false; + } + + char buf[80]; + + string indentString(indent, ' '); + + size_t tagCount = item->semanticTagCount(); + while (tagCount > 0) { + --tagCount; + snprintf(buf, sizeof(buf), "tag %" PRIu64 " ", item->semanticTag(tagCount)); + out.append(buf); + } + + switch (item->type()) { + case SEMANTIC: + // Handled above. + break; + + case UINT: + snprintf(buf, sizeof(buf), "%" PRIu64, item->asUint()->unsignedValue()); + out.append(buf); + break; + + case NINT: + snprintf(buf, sizeof(buf), "%" PRId64, item->asNint()->value()); + out.append(buf); + break; + + case BSTR: { + const uint8_t* valueData; + size_t valueSize; + const Bstr* bstr = item->asBstr(); + if (bstr == nullptr) { + return false; + } + const vector& value = bstr->value(); + valueData = value.data(); + valueSize = value.size(); + + out.append("{"); + for (size_t n = 0; n < valueSize; n++) { + if (n > 0) { + out.append(", "); + } + snprintf(buf, sizeof(buf), "0x%02x", valueData[n]); + out.append(buf); + } + out.append("}"); + } break; + + case TSTR: + out.append("'"); + { + // TODO: escape "'" characters + if (item->asTstr() != nullptr) { + out.append(item->asTstr()->value().c_str()); + } else { + } + } + out.append("'"); + break; + + case ARRAY: { + const Array* array = item->asArray(); + if (array->size() == 0) { + out.append("[]"); + } else if (cborAreAllElementsNonCompound(array)) { + out.append("["); + for (size_t n = 0; n < array->size(); n++) { + if (!prettyPrintInternal((*array)[n].get(), out, indent + 2, + maxBStrSize, mapKeysToNotPrint)) { + return false; + } + out.append(", "); + } + out.append("]"); + } else { + out.append("[\n" + indentString); + for (size_t n = 0; n < array->size(); n++) { + out.append(" "); + if (!prettyPrintInternal((*array)[n].get(), out, indent + 2, + maxBStrSize, mapKeysToNotPrint)) { + return false; + } + out.append(",\n" + indentString); + } + out.append("]"); + } + } break; + + case MAP: { + const Map* map = item->asMap(); + + if (map->size() == 0) { + out.append("{}"); + } else { + out.append("{\n" + indentString); + for (auto& entry : *map) { + auto& map_key = entry.first; + auto& map_value = entry.second; + + out.append(" "); + + if (!prettyPrintInternal(map_key.get(), out, indent + 2, maxBStrSize, + mapKeysToNotPrint)) { + return false; + } + out.append(" : "); + if (map_key->type() == TSTR && + std::find(mapKeysToNotPrint.begin(), mapKeysToNotPrint.end(), + map_key->asTstr()->value()) != + mapKeysToNotPrint.end()) { + out.append(""); + } else { + if (!prettyPrintInternal(map_value.get(), out, indent + 2, + maxBStrSize, mapKeysToNotPrint)) { + return false; + } + } + out.append(",\n" + indentString); + } + out.append("}"); + } + } break; + + case SIMPLE: + const Bool* asBool = item->asSimple()->asBool(); + const Null* asNull = item->asSimple()->asNull(); + if (asBool != nullptr) { + out.append(asBool->value() ? "true" : "false"); + } else if (asNull != nullptr) { + out.append("null"); + } else { + return false; + } + break; + } + + return true; +} + +} // namespace + +size_t headerSize(uint64_t addlInfo) { + if (addlInfo < ONE_BYTE_LENGTH) return 1; + if (addlInfo <= std::numeric_limits::max()) return 2; + if (addlInfo <= std::numeric_limits::max()) return 3; + if (addlInfo <= std::numeric_limits::max()) return 5; + return 9; +} + +uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, + const uint8_t* end) { + size_t sz = headerSize(addlInfo); + if (end - pos < static_cast(sz)) return nullptr; + switch (sz) { + case 1: + *pos++ = type | static_cast(addlInfo); + return pos; + case 2: + *pos++ = type | ONE_BYTE_LENGTH; + *pos++ = static_cast(addlInfo); + return pos; + case 3: + *pos++ = type | TWO_BYTE_LENGTH; + return writeBigEndian(static_cast(addlInfo), pos); + case 5: + *pos++ = type | FOUR_BYTE_LENGTH; + return writeBigEndian(static_cast(addlInfo), pos); + case 9: + *pos++ = type | EIGHT_BYTE_LENGTH; + return writeBigEndian(addlInfo, pos); + default: + CHECK(false); // Impossible to get here. + return nullptr; + } +} + +void encodeHeader(MajorType type, uint64_t addlInfo, + EncodeCallback encodeCallback) { + size_t sz = headerSize(addlInfo); + switch (sz) { + case 1: + encodeCallback(type | static_cast(addlInfo)); + break; + case 2: + encodeCallback(type | ONE_BYTE_LENGTH); + encodeCallback(static_cast(addlInfo)); + break; + case 3: + encodeCallback(type | TWO_BYTE_LENGTH); + writeBigEndian(static_cast(addlInfo), encodeCallback); + break; + case 5: + encodeCallback(type | FOUR_BYTE_LENGTH); + writeBigEndian(static_cast(addlInfo), encodeCallback); + break; + case 9: + encodeCallback(type | EIGHT_BYTE_LENGTH); + writeBigEndian(addlInfo, encodeCallback); + break; + default: + CHECK(false); // Impossible to get here. + } +} + +bool Item::operator==(const Item& other) const& { + if (type() != other.type()) return false; + switch (type()) { + case UINT: + return *asUint() == *(other.asUint()); + case NINT: + return *asNint() == *(other.asNint()); + case BSTR: + if (asBstr() != nullptr && other.asBstr() != nullptr) { + return *asBstr() == *(other.asBstr()); + } + // Interesting corner case: comparing a Bstr and ViewBstr with + // identical contents. The function currently returns false for + // this case. + // TODO: if it should return true, this needs a deep comparison + return false; + case TSTR: + if (asTstr() != nullptr && other.asTstr() != nullptr) { + return *asTstr() == *(other.asTstr()); + } + // Same corner case as Bstr + return false; + case ARRAY: + return *asArray() == *(other.asArray()); + case MAP: + return *asMap() == *(other.asMap()); + case SIMPLE: + return *asSimple() == *(other.asSimple()); + case SEMANTIC: + return *asSemanticTag() == *(other.asSemanticTag()); + default: + CHECK(false); // Impossible to get here. + return false; + } +} + +Nint::Nint(int64_t v) : mValue(v) { CHECK(v < 0); } + +bool Simple::operator==(const Simple& other) const& { + if (simpleType() != other.simpleType()) return false; + + switch (simpleType()) { + case BOOLEAN: + return *asBool() == *(other.asBool()); + case NULL_T: + return true; + default: + CHECK(false); // Impossible to get here. + return false; + } +} + +uint8_t* Bstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mValue.size(), pos, end); + if (!pos || end - pos < static_cast(mValue.size())) return nullptr; + return std::copy(mValue.begin(), mValue.end(), pos); +} + +void Bstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mValue) { + encodeCallback(c); + } +} + +uint8_t* Tstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mValue.size(), pos, end); + if (!pos || end - pos < static_cast(mValue.size())) return nullptr; + return std::copy(mValue.begin(), mValue.end(), pos); +} + +void Tstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mValue) { + encodeCallback(static_cast(c)); + } +} + +bool Array::operator==(const Array& other) const& { + return size() == other.size() + // Can't use vector::operator== because the contents are pointers. + // std::equal lets us provide a predicate that does the dereferencing. + && std::equal(mEntries.begin(), mEntries.end(), other.mEntries.begin(), + [](const std::unique_ptr& a, + const std::unique_ptr& b) -> bool { + return *a == *b; + }); +} + +uint8_t* Array::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(size(), pos, end); + if (!pos) return nullptr; + for (auto& entry : mEntries) { + pos = entry->encode(pos, end); + if (!pos) return nullptr; + } + return pos; +} + +void Array::encode(EncodeCallback encodeCallback) const { + encodeHeader(size(), encodeCallback); + for (auto& entry : mEntries) { + entry->encode(encodeCallback); + } +} + +std::unique_ptr Array::clone() const { + auto res = make_unique(); + for (size_t i = 0; i < mEntries.size(); i++) { + res->add(mEntries[i]->clone()); + } + return res; +} + +bool Map::operator==(const Map& other) const& { + return size() == other.size() + // Can't use vector::operator== because the contents are pairs of + // pointers. std::equal lets us provide a predicate that does the + // dereferencing. + && std::equal(begin(), end(), other.begin(), + [](const entry_type& a, const entry_type& b) { + return *a.first == *b.first && *a.second == *b.second; + }); +} + +uint8_t* Map::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(size(), pos, end); + if (!pos) return nullptr; + for (auto& entry : mEntries) { + pos = entry.first->encode(pos, end); + if (!pos) return nullptr; + pos = entry.second->encode(pos, end); + if (!pos) return nullptr; + } + return pos; +} + +void Map::encode(EncodeCallback encodeCallback) const { + encodeHeader(size(), encodeCallback); + for (auto& entry : mEntries) { + entry.first->encode(encodeCallback); + entry.second->encode(encodeCallback); + } +} + +bool Map::keyLess(const Item* a, const Item* b) { + // CBOR map canonicalization rules are: + + // 1. If two keys have different lengths, the shorter one sorts earlier. + if (a->encodedSize() < b->encodedSize()) return true; + if (a->encodedSize() > b->encodedSize()) return false; + + // 2. If two keys have the same length, the one with the lower value in + // (byte-wise) lexical order sorts earlier. This requires encoding both + // items. + auto encodedA = a->encode(); + auto encodedB = b->encode(); + + return std::lexicographical_compare(encodedA.begin(), encodedA.end(), // + encodedB.begin(), encodedB.end()); +} + +void recursivelyCanonicalize(std::unique_ptr& item) { + switch (item->type()) { + case UINT: + case NINT: + case BSTR: + case TSTR: + case SIMPLE: + return; + + case ARRAY: + std::for_each(item->asArray()->begin(), item->asArray()->end(), + recursivelyCanonicalize); + return; + + case MAP: + item->asMap()->canonicalize(true /* recurse */); + return; + + case SEMANTIC: + // This can't happen. SemanticTags delegate their type() method to the + // contained Item's type. + assert(false); + return; + } +} + +Map& Map::canonicalize(bool recurse) & { + if (recurse) { + for (auto& entry : mEntries) { + recursivelyCanonicalize(entry.first); + recursivelyCanonicalize(entry.second); + } + } + + if (size() < 2 || mCanonicalized) { + // Trivially or already canonical; do nothing. + return *this; + } + + std::sort(begin(), end(), [](const entry_type& a, const entry_type& b) { + return keyLess(a.first.get(), b.first.get()); + }); + mCanonicalized = true; + return *this; +} + +std::unique_ptr Map::clone() const { + auto res = make_unique(); + for (auto& entry : *this) { + auto& key = entry.first; + auto& value = entry.second; + res->add(key->clone(), value->clone()); + } + res->mCanonicalized = mCanonicalized; + return res; +} + +std::unique_ptr SemanticTag::clone() const { + return make_unique(mValue, mTaggedItem->clone()); +} + +uint8_t* SemanticTag::encode(uint8_t* pos, const uint8_t* end) const { + // Can't use the encodeHeader() method that calls type() to get the major + // type, since that will return the tagged Item's type. + pos = ::cppbor::encodeHeader(kMajorType, mValue, pos, end); + if (!pos) return nullptr; + return mTaggedItem->encode(pos, end); +} + +void SemanticTag::encode(EncodeCallback encodeCallback) const { + // Can't use the encodeHeader() method that calls type() to get the major + // type, since that will return the tagged Item's type. + ::cppbor::encodeHeader(kMajorType, mValue, encodeCallback); + mTaggedItem->encode(std::move(encodeCallback)); +} + +size_t SemanticTag::semanticTagCount() const { + size_t levelCount = 1; // Count this level. + const SemanticTag* cur = this; + while (cur->mTaggedItem && + (cur = cur->mTaggedItem->asSemanticTag()) != nullptr) + ++levelCount; + return levelCount; +} + +uint64_t SemanticTag::semanticTag(size_t nesting) const { + // Getting the value of a specific nested tag is a bit tricky, because we + // start with the outer tag and don't know how many are inside. We count the + // number of nesting levels to find out how many there are in total, then to + // get the one we want we have to walk down levelCount - nesting steps. + size_t levelCount = semanticTagCount(); + if (nesting >= levelCount) return 0; + + levelCount -= nesting; + const SemanticTag* cur = this; + while (--levelCount > 0) cur = cur->mTaggedItem->asSemanticTag(); + + return cur->mValue; +} + +string prettyPrint(const Item* item, size_t maxBStrSize, + const vector& mapKeysToNotPrint) { + string out; + prettyPrintInternal(item, out, 0, maxBStrSize, mapKeysToNotPrint); + return out; +} + +} // namespace cppbor diff --git a/third_party/libcppbor/src/cppbor_parse.cpp b/third_party/libcppbor/src/cppbor_parse.cpp new file mode 100644 index 00000000..4388b7bc --- /dev/null +++ b/third_party/libcppbor/src/cppbor_parse.cpp @@ -0,0 +1,380 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "cppbor_parse.h" + +#include +#include +#include + +#ifndef __has_feature + #define __has_feature(x) 0 +#endif + +namespace cppbor { + +namespace { + +std::string insufficientLengthString(size_t bytesNeeded, size_t bytesAvail, + const std::string& type) { + char buf[1024]; + snprintf(buf, sizeof(buf), "Need %zu byte(s) for %s, have %zu.", bytesNeeded, type.c_str(), + bytesAvail); + return std::string(buf); +} + +template ::value>> +std::tuple parseLength(const uint8_t* pos, const uint8_t* end, + ParseClient* parseClient) { + if (pos + sizeof(T) > end) { + parseClient->error(pos - 1, insufficientLengthString(sizeof(T), end - pos, "length field")); + return {false, 0, pos}; + } + + const uint8_t* intEnd = pos + sizeof(T); + T result = 0; + do { + result = static_cast((result << 8) | *pos++); + } while (pos < intEnd); + return {true, result, pos}; +} + +std::tuple parseRecursively(const uint8_t* begin, const uint8_t* end, + bool emitViews, ParseClient* parseClient); + +std::tuple handleUint(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr item = std::make_unique(value); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple handleNint(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + if (value > std::numeric_limits::max()) { + parseClient->error(hdrBegin, "NINT values that don't fit in int64_t are not supported."); + return {hdrBegin, nullptr /* end parsing */}; + } + std::unique_ptr item = std::make_unique(-1 - static_cast(value)); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple handleBool(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr item = std::make_unique(value == TRUE); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple handleNull(const uint8_t* hdrBegin, const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr item = std::make_unique(); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +template +std::tuple handleString(uint64_t length, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end, + const std::string& errLabel, + ParseClient* parseClient) { + ssize_t signed_length = static_cast(length); + if (end - valueBegin < signed_length || signed_length < 0) { + parseClient->error(hdrBegin, insufficientLengthString(length, end - valueBegin, errLabel)); + return {hdrBegin, nullptr /* end parsing */}; + } + + std::unique_ptr item = std::make_unique(valueBegin, valueBegin + length); + return {valueBegin + length, + parseClient->item(item, hdrBegin, valueBegin, valueBegin + length)}; +} + +class IncompleteItem { + public: + virtual ~IncompleteItem() {} + virtual void add(std::unique_ptr item) = 0; +}; + +class IncompleteArray : public Array, public IncompleteItem { + public: + explicit IncompleteArray(size_t size) : mSize(size) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return mSize; } + + void add(std::unique_ptr item) override { + mEntries.reserve(mSize); + mEntries.push_back(std::move(item)); + } + + private: + size_t mSize; +}; + +class IncompleteMap : public Map, public IncompleteItem { + public: + explicit IncompleteMap(size_t size) : mSize(size) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return mSize; } + + void add(std::unique_ptr item) override { + if (mKeyHeldForAdding) { + mEntries.reserve(mSize); + mEntries.push_back({std::move(mKeyHeldForAdding), std::move(item)}); + } else { + mKeyHeldForAdding = std::move(item); + } + } + + private: + std::unique_ptr mKeyHeldForAdding; + size_t mSize; +}; + +class IncompleteSemanticTag : public SemanticTag, public IncompleteItem { + public: + explicit IncompleteSemanticTag(uint64_t value) : SemanticTag(value) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return 1; } + + void add(std::unique_ptr item) override { mTaggedItem = std::move(item); } +}; + +std::tuple handleEntries(size_t entryCount, const uint8_t* hdrBegin, + const uint8_t* pos, const uint8_t* end, + const std::string& typeName, + bool emitViews, + ParseClient* parseClient) { + while (entryCount > 0) { + --entryCount; + if (pos == end) { + parseClient->error(hdrBegin, "Not enough entries for " + typeName + "."); + return {hdrBegin, nullptr /* end parsing */}; + } + std::tie(pos, parseClient) = parseRecursively(pos, end, emitViews, parseClient); + if (!parseClient) return {hdrBegin, nullptr}; + } + return {pos, parseClient}; +} + +std::tuple handleCompound( + std::unique_ptr item, uint64_t entryCount, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end, const std::string& typeName, + bool emitViews, ParseClient* parseClient) { + parseClient = + parseClient->item(item, hdrBegin, valueBegin, valueBegin /* don't know the end yet */); + if (!parseClient) return {hdrBegin, nullptr}; + + const uint8_t* pos; + std::tie(pos, parseClient) = + handleEntries(entryCount, hdrBegin, valueBegin, end, typeName, emitViews, parseClient); + if (!parseClient) return {hdrBegin, nullptr}; + + return {pos, parseClient->itemEnd(item, hdrBegin, valueBegin, pos)}; +} + +std::tuple parseRecursively(const uint8_t* begin, const uint8_t* end, + bool emitViews, ParseClient* parseClient) { + if (begin == end) { + parseClient->error( + begin, + "Input buffer is empty. Begin and end cannot point to the same location."); + return {begin, nullptr}; + } + + const uint8_t* pos = begin; + + MajorType type = static_cast(*pos & 0xE0); + uint8_t tagInt = *pos & 0x1F; + ++pos; + + bool success = true; + uint64_t addlData; + if (tagInt < ONE_BYTE_LENGTH) { + addlData = tagInt; + } else if (tagInt > EIGHT_BYTE_LENGTH) { + parseClient->error( + begin, + "Reserved additional information value or unsupported indefinite length item."); + return {begin, nullptr}; + } else { + switch (tagInt) { + case ONE_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength(pos, end, parseClient); + break; + + case TWO_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength(pos, end, parseClient); + break; + + case FOUR_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength(pos, end, parseClient); + break; + + case EIGHT_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength(pos, end, parseClient); + break; + + default: + // It's impossible to get here + parseClient->error(begin, "Invalid tag."); + return {}; + } + } + + if (!success) return {begin, nullptr}; + + switch (type) { + case UINT: + return handleUint(addlData, begin, pos, parseClient); + + case NINT: + return handleNint(addlData, begin, pos, parseClient); + + case BSTR: + return handleString(addlData, begin, pos, end, "byte string", parseClient); + + case TSTR: + return handleString(addlData, begin, pos, end, "text string", parseClient); + + case ARRAY: + return handleCompound(std::make_unique(addlData), addlData, begin, pos, + end, "array", emitViews, parseClient); + + case MAP: + return handleCompound(std::make_unique(addlData), addlData * 2, begin, + pos, end, "map", emitViews, parseClient); + + case SEMANTIC: + return handleCompound(std::make_unique(addlData), 1, begin, pos, + end, "semantic", emitViews, parseClient); + + case SIMPLE: + switch (addlData) { + case TRUE: + case FALSE: + return handleBool(addlData, begin, pos, parseClient); + case NULL_V: + return handleNull(begin, pos, parseClient); + default: + parseClient->error(begin, "Unsupported floating-point or simple value."); + return {begin, nullptr}; + } + } + // Impossible to get here. + parseClient->error(begin, "Invalid type."); + return {}; +} + +class FullParseClient : public ParseClient { + public: + virtual ParseClient* item(std::unique_ptr& item, const uint8_t*, const uint8_t*, + const uint8_t* end) override { + if (mParentStack.empty() && !item->isCompound()) { + // This is the first and only item. + mTheItem = std::move(item); + mPosition = end; + return nullptr; // We're done. + } + + if (item->isCompound()) { + // Starting a new compound data item, i.e. a new parent. Save it on the parent stack. + // It's safe to save a raw pointer because the unique_ptr is guaranteed to stay in + // existence until the corresponding itemEnd() call. + mParentStack.push(item.get()); + return this; + } else { + appendToLastParent(std::move(item)); + return this; + } + } + + virtual ParseClient* itemEnd(std::unique_ptr& item, const uint8_t*, const uint8_t*, + const uint8_t* end) override { + if (!item->isCompound() || item.get() != mParentStack.top()) { + return nullptr; + } + mParentStack.pop(); + + if (mParentStack.empty()) { + mTheItem = std::move(item); + mPosition = end; + return nullptr; // We're done + } else { + appendToLastParent(std::move(item)); + return this; + } + } + + virtual void error(const uint8_t* position, const std::string& errorMessage) override { + mPosition = position; + mErrorMessage = errorMessage; + } + + std::tuple /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */> + parseResult() { + std::unique_ptr p = std::move(mTheItem); + return {std::move(p), mPosition, std::move(mErrorMessage)}; + } + + private: + void appendToLastParent(std::unique_ptr item) { + auto parent = mParentStack.top(); +#if __has_feature(cxx_rtti) + assert(dynamic_cast(parent)); +#endif + + IncompleteItem* parentItem{}; + if (parent->type() == ARRAY) { + parentItem = static_cast(parent); + } else if (parent->type() == MAP) { + parentItem = static_cast(parent); + } else if (parent->asSemanticTag()) { + parentItem = static_cast(parent); + } else { + // Impossible to get here. + } + parentItem->add(std::move(item)); + } + + std::unique_ptr mTheItem; + std::stack mParentStack; + const uint8_t* mPosition = nullptr; + std::string mErrorMessage; +}; + +} // anonymous namespace + +void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient) { + parseRecursively(begin, end, false, parseClient); +} + +std::tuple /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */> +parse(const uint8_t* begin, const uint8_t* end) { + FullParseClient parseClient; + parse(begin, end, &parseClient); + return parseClient.parseResult(); +} + +} // namespace cppbor diff --git a/third_party/libcppbor/tests/cppbor_test.cpp b/third_party/libcppbor/tests/cppbor_test.cpp new file mode 100644 index 00000000..d40de092 --- /dev/null +++ b/third_party/libcppbor/tests/cppbor_test.cpp @@ -0,0 +1,1689 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include "cppbor.h" +#include "cppbor_parse.h" + +using namespace cppbor; +using namespace std; + +using ::testing::_; +using ::testing::ByRef; +using ::testing::InSequence; +using ::testing::IsNull; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::Unused; + +string hexDump(const string& str) { + stringstream s; + for (auto c : str) { + s << setfill('0') << setw(2) << hex << (static_cast(c) & 0xff); + } + return s.str(); +} + +TEST(SimpleValueTest, UnsignedValueSizes) { + // Check that unsigned integers encode to correct lengths, and that encodedSize() is correct. + vector> testCases{ + {0, 1}, + {1, 1}, + {23, 1}, + {24, 2}, + {255, 2}, + {256, 3}, + {65535, 3}, + {65536, 5}, + {4294967295, 5}, + {4294967296, 9}, + {std::numeric_limits::max(), 9}, + }; + for (auto& testCase : testCases) { + Uint val(testCase.first); + EXPECT_EQ(testCase.second, val.encodedSize()) << "Wrong size for value " << testCase.first; + EXPECT_EQ(val.encodedSize(), val.toString().size()) + << "encodedSize and encoding disagree for value " << testCase.first; + } +} + +TEST(SimpleValueTest, UnsignedValueEncodings) { + EXPECT_EQ("\x00"s, Uint(0u).toString()); + EXPECT_EQ("\x01"s, Uint(1u).toString()); + EXPECT_EQ("\x0a"s, Uint(10u).toString()); + EXPECT_EQ("\x17"s, Uint(23u).toString()); + EXPECT_EQ("\x18\x18"s, Uint(24u).toString()); + EXPECT_EQ("\x18\x19"s, Uint(25u).toString()); + EXPECT_EQ("\x18\x64"s, Uint(100u).toString()); + EXPECT_EQ("\x19\x03\xe8"s, Uint(1000u).toString()); + EXPECT_EQ("\x1a\x00\x0f\x42\x40"s, Uint(1000000u).toString()); + EXPECT_EQ("\x1b\x00\x00\x00\xe8\xd4\xa5\x10\x00"s, Uint(1000000000000u).toString()); + EXPECT_EQ("\x1B\x7f\xff\xff\xff\xff\xff\xff\xff"s, + Uint(std::numeric_limits::max()).toString()); +} + +TEST(SimpleValueTest, NegativeValueEncodings) { + EXPECT_EQ("\x20"s, Nint(-1).toString()); + EXPECT_EQ("\x28"s, Nint(-9).toString()); + EXPECT_EQ("\x29"s, Nint(-10).toString()); + EXPECT_EQ("\x36"s, Nint(-23).toString()); + EXPECT_EQ("\x37"s, Nint(-24).toString()); + EXPECT_EQ("\x38\x18"s, Nint(-25).toString()); + EXPECT_EQ("\x38\x62"s, Nint(-99).toString()); + EXPECT_EQ("\x38\x63"s, Nint(-100).toString()); + EXPECT_EQ("\x39\x03\xe6"s, Nint(-999).toString()); + EXPECT_EQ("\x39\x03\xe7"s, Nint(-1000).toString()); + EXPECT_EQ("\x3a\x00\x0f\x42\x3F"s, Nint(-1000000).toString()); + EXPECT_EQ("\x3b\x00\x00\x00\xe8\xd4\xa5\x0f\xff"s, Nint(-1000000000000).toString()); + EXPECT_EQ("\x3B\x7f\xff\xff\xff\xff\xff\xff\xff"s, + Nint(std::numeric_limits::min()).toString()); +} + +TEST(SimpleValueDeathTest, NegativeValueEncodings) { + EXPECT_DEATH(Nint(0), ""); + EXPECT_DEATH(Nint(1), ""); +} + +TEST(SimpleValueTest, BooleanEncodings) { + EXPECT_EQ("\xf4"s, Bool(false).toString()); + EXPECT_EQ("\xf5"s, Bool(true).toString()); +} + +TEST(SimpleValueTest, NullEncodings) { + EXPECT_EQ("\xf6"s, Null().toString()); +} + +TEST(SimpleValueTest, ByteStringEncodings) { + EXPECT_EQ("\x40", Bstr("").toString()); + EXPECT_EQ("\x41\x61", Bstr("a").toString()); + EXPECT_EQ("\x41\x41", Bstr("A").toString()); + EXPECT_EQ("\x44\x49\x45\x54\x46", Bstr("IETF").toString()); + EXPECT_EQ("\x42\x22\x5c", Bstr("\"\\").toString()); + EXPECT_EQ("\x42\xc3\xbc", Bstr("\xc3\xbc").toString()); + EXPECT_EQ("\x43\xe6\xb0\xb4", Bstr("\xe6\xb0\xb4").toString()); + EXPECT_EQ("\x44\xf0\x90\x85\x91", Bstr("\xf0\x90\x85\x91").toString()); + EXPECT_EQ("\x44\x01\x02\x03\x04", Bstr("\x01\x02\x03\x04").toString()); + EXPECT_EQ("\x44\x40\x40\x40\x40", Bstr("@@@@").toString()); +} + +TEST(SimpleValueTest, TextStringEncodings) { + EXPECT_EQ("\x60"s, Tstr("").toString()); + EXPECT_EQ("\x61\x61"s, Tstr("a").toString()); + EXPECT_EQ("\x61\x41"s, Tstr("A").toString()); + EXPECT_EQ("\x64\x49\x45\x54\x46"s, Tstr("IETF").toString()); + EXPECT_EQ("\x62\x22\x5c"s, Tstr("\"\\").toString()); + EXPECT_EQ("\x62\xc3\xbc"s, Tstr("\xc3\xbc").toString()); + EXPECT_EQ("\x63\xe6\xb0\xb4"s, Tstr("\xe6\xb0\xb4").toString()); + EXPECT_EQ("\x64\xf0\x90\x85\x91"s, Tstr("\xf0\x90\x85\x91").toString()); + EXPECT_EQ("\x64\x01\x02\x03\x04"s, Tstr("\x01\x02\x03\x04").toString()); +} + +TEST(SimpleValueTest, SemanticTagEncoding) { + EXPECT_EQ("\xDB\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x63\x41\x45\x53"s, + SemanticTag(std::numeric_limits::max(), "AES").toString()); +} + +TEST(SimpleValueTest, NestedSemanticTagEncoding) { + auto tripleTagged = + SemanticTag(254, + SemanticTag(1, // + SemanticTag(std::numeric_limits::max(), // + "AES"))); + EXPECT_EQ("\xD8\xFE\xC1\xDB\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x63\x41\x45\x53"s, + tripleTagged.toString()); +} + +TEST(IsIteratorPairOverTest, All) { + EXPECT_TRUE(( + details::is_iterator_pair_over, char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over, + char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over, + char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over, char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over, char>::value)); + EXPECT_TRUE((details::is_iterator_pair_over, char>::value)); + EXPECT_FALSE((details::is_iterator_pair_over, + uint8_t>::value)); + EXPECT_FALSE((details::is_iterator_pair_over, uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over< + pair::iterator, vector::iterator>, uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over< + pair::const_iterator, vector::iterator>, + uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over< + pair::iterator, vector::const_iterator>, + uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over, uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over, uint8_t>::value)); + EXPECT_TRUE((details::is_iterator_pair_over, uint8_t>::value)); + EXPECT_FALSE((details::is_iterator_pair_over< + pair::iterator, vector::iterator>, char>::value)); + EXPECT_FALSE((details::is_iterator_pair_over, char>::value)); +} + +TEST(IsUniquePtrSubclassOf, All) { + EXPECT_TRUE((details::is_unique_ptr_of_subclass_of_v>::value)); + EXPECT_TRUE((details::is_unique_ptr_of_subclass_of_v>::value)); + EXPECT_TRUE((details::is_unique_ptr_of_subclass_of_v>::value)); + EXPECT_TRUE( + (details::is_unique_ptr_of_subclass_of_v>::value)); + EXPECT_FALSE( + (details::is_unique_ptr_of_subclass_of_v>::value)); + EXPECT_FALSE(( + details::is_unique_ptr_of_subclass_of_v>::value)); +} + +TEST(MakeEntryTest, Boolean) { + EXPECT_EQ("\xf4"s, details::makeItem(false)->toString()); +} + +TEST(MakeEntryTest, Null) { + EXPECT_EQ("\xf6"s, details::makeItem(nullptr)->toString()); +} + +TEST(MakeEntryTest, Integers) { + EXPECT_EQ("\x00"s, details::makeItem(static_cast(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast(0))->toString()); + EXPECT_EQ("\x00"s, details::makeItem(static_cast(0))->toString()); + EXPECT_EQ("\x20"s, details::makeItem(static_cast(-1))->toString()); + EXPECT_EQ("\x20"s, details::makeItem(static_cast(-1))->toString()); + EXPECT_EQ("\x20"s, details::makeItem(static_cast(-1))->toString()); + EXPECT_EQ("\x20"s, details::makeItem(static_cast(-1))->toString()); + + EXPECT_EQ("\x1b\xff\xff\xff\xff\xff\xff\xff\xff"s, + details::makeItem(static_cast(std::numeric_limits::max())) + ->toString()); +} + +TEST(MakeEntryTest, StdStrings) { + string s1("hello"); + const string s2("hello"); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString()); // copy of string + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, + details::makeItem(s2)->toString()); // copy of const string + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, + details::makeItem(std::move(s1))->toString()); // move string +} + +TEST(MakeEntryTest, CStrings) { + char s1[] = "hello"; + const char s2[] = "hello"; + const char* s3 = "hello"; + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s1)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s2)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(s3)->toString()); +} + +TEST(MakeEntryTest, StringIteratorPairs) { + // Use iterators from string to prove that "real" iterators work + string s1 = "hello"s; + pair p1 = make_pair(s1.begin(), s1.end()); + + const pair p2 = p1; + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p1)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p2)->toString()); + + // Use char*s as iterators + const char* s2 = "hello"; + pair p3 = make_pair(s2, s2 + 5); + const pair p4 = p3; + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p3)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(p4)->toString()); +} + +TEST(MakeEntryTest, ByteStrings) { + vector v1 = {0x00, 0x01, 0x02}; + const vector v2 = {0x00, 0x01, 0x02}; + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(v1)->toString()); // copy of vector + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(v2)->toString()); // copy of const vector + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(std::move(v1))->toString()); // move vector + EXPECT_EQ(0U, v1.size()); // Prove vector was moved, not copied. +} + +TEST(MakeEntryTest, ByteStringIteratorPairs) { + using vec = vector; + using iter = vec::iterator; + vec v1 = {0x00, 0x01, 0x02}; + pair p1 = make_pair(v1.begin(), v1.end()); + const pair p2 = make_pair(v1.begin(), v1.end()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p1)->toString()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p2)->toString()); + + // Use uint8_t*s as iterators + uint8_t v2[] = {0x00, 0x01, 0x02}; + uint8_t* v3 = v2; + pair p3 = make_pair(v2, v2 + 3); + const pair p4 = make_pair(v2, v2 + 3); + pair p5 = make_pair(v3, v3 + 3); + const pair p6 = make_pair(v3, v3 + 3); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p3)->toString()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p4)->toString()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p5)->toString()); + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(p6)->toString()); +} + +TEST(MakeEntryTest, ByteStringBuffers) { + uint8_t v1[] = {0x00, 0x01, 0x02}; + EXPECT_EQ("\x43\x00\x01\x02"s, details::makeItem(make_pair(v1, 3))->toString()); +} + +TEST(MakeEntryTest, ItemPointer) { + Uint* p1 = new Uint(0); + EXPECT_EQ("\x00"s, details::makeItem(p1)->toString()); + EXPECT_EQ("\x60"s, details::makeItem(new Tstr(string()))->toString()); +} + +TEST(MakeEntryTest, ItemReference) { + Tstr str("hello"s); + Tstr& strRef = str; + const Tstr& strConstRef = str; + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(str)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(strRef)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(strConstRef)->toString()); + EXPECT_EQ("\x65\x68\x65\x6c\x6c\x6f"s, details::makeItem(std::move(str))->toString()); + EXPECT_EQ("\x60"s, details::makeItem(str)->toString()); // Prove that it moved + + EXPECT_EQ("\x00"s, details::makeItem(Uint(0))->toString()); + + EXPECT_EQ("\x43\x00\x01\x02"s, + details::makeItem(Bstr(vector{0x00, 0x01, 0x02}))->toString()); + + EXPECT_EQ("\x80"s, details::makeItem(Array())->toString()); + EXPECT_EQ("\xa0"s, details::makeItem(Map())->toString()); +} + +TEST(CompoundValueTest, ArrayOfInts) { + EXPECT_EQ("\x80"s, Array().toString()); + Array(Uint(0)).toString(); + + EXPECT_EQ("\x81\x00"s, Array(Uint(0U)).toString()); + EXPECT_EQ("\x82\x00\x01"s, Array(Uint(0), Uint(1)).toString()); + EXPECT_EQ("\x83\x00\x01\x38\x62"s, Array(Uint(0), Uint(1), Nint(-99)).toString()); + + EXPECT_EQ("\x81\x00"s, Array(0).toString()); + EXPECT_EQ("\x82\x00\x01"s, Array(0, 1).toString()); + EXPECT_EQ("\x83\x00\x01\x38\x62"s, Array(0, 1, -99).toString()); +} + +TEST(CompoundValueTest, MapOfInts) { + EXPECT_EQ("\xA0"s, Map().toString()); + EXPECT_EQ("\xA1\x00\x01"s, Map(Uint(0), Uint(1)).toString()); + // Maps with an odd number of arguments will fail to compile. Uncomment the next lines to test. + // EXPECT_EQ("\xA1\x00"s, Map(Int(0)).toString()); + // EXPECT_EQ("\xA1\x00\x01\x02"s, Map(Int(0), Int(1), Int(2)).toString()); +} + +TEST(CompoundValueTest, MixedArray) { + vector vec = {3, 2, 1}; + EXPECT_EQ("\x84\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s, + Array(Uint(1), Nint(-1), Bstr(vec), Tstr("hello")).toString()); + + EXPECT_EQ("\x84\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s, + Array(1, -1, vec, "hello").toString()); +} + +TEST(CompoundValueTest, MixedMap) { + vector vec = {3, 2, 1}; + EXPECT_EQ("\xA2\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s, + Map(Uint(1), Nint(-1), Bstr(vec), Tstr("hello")).toString()); + + EXPECT_EQ("\xA2\x01\x20\x43\x03\x02\x01\x65\x68\x65\x6C\x6C\x6F"s, + Map(1, -1, vec, "hello").toString()); +} + +TEST(CompoundValueTest, NestedStructures) { + vector vec = {3, 2, 1}; + + string expectedEncoding = + "\xA2\x66\x4F\x75\x74\x65\x72\x31\x82\xA2\x66\x49\x6E\x6E\x65\x72\x31\x18\x63\x66\x49" + "\x6E" + "\x6E\x65\x72\x32\x43\x03\x02\x01\x63\x66\x6F\x6F\x66\x4F\x75\x74\x65\x72\x32\x0A"s; + + // Do it with explicity-created Items + EXPECT_EQ(expectedEncoding, + Map(Tstr("Outer1"), + Array( // + Map(Tstr("Inner1"), Uint(99), Tstr("Inner2"), Bstr(vec)), Tstr("foo")), + Tstr("Outer2"), // + Uint(10)) + .toString()); + EXPECT_EQ(3U, vec.size()); + + // Now just use convertible types + EXPECT_EQ(expectedEncoding, Map("Outer1", + Array(Map("Inner1", 99, // + "Inner2", vec), + "foo"), + "Outer2", 10) + .toString()); + EXPECT_EQ(3U, vec.size()); + + // Finally, do it with the .add() method. This is slightly less efficient, but has the + // advantage you can build a structure up incrementally, or somewhat fluently if you like. + // First, fluently. + EXPECT_EQ(expectedEncoding, Map().add("Outer1", Array().add(Map() // + .add("Inner1", 99) + .add("Inner2", vec)) + .add("foo")) + .add("Outer2", 10) + .toString()); + EXPECT_EQ(3U, vec.size()); + + // Next, more incrementally + Array arr; + arr.add(Map() // + .add("Inner1", 99) + .add("Inner2", vec)) + .add("foo"); + EXPECT_EQ(3U, vec.size()); + + Map m; + m.add("Outer1", std::move(arr)); // Moving is necessary; Map and Array cannot be copied. + m.add("Outer2", 10); + auto s = m.toString(); + EXPECT_EQ(expectedEncoding, s); +} + +TEST(EncodingMethodsTest, AllVariants) { + Map map; + map.add("key1", Array().add(Map() // + .add("key_a", 9999999) + .add("key_b", std::vector{0x01, 0x02, 0x03}) + .add("key_c", std::numeric_limits::max()) + .add("key_d", std::numeric_limits::min())) + .add("foo")) + .add("key2", true) + .add("key3", SemanticTag(1, SemanticTag(987654321, "Zhai gana test"))); + + std::vector buf; + buf.resize(map.encodedSize()); + + EXPECT_EQ(buf.data() + buf.size(), map.encode(buf.data(), buf.data() + buf.size())); + + EXPECT_EQ(buf, map.encode()); + + std::vector buf2; + map.encode(std::back_inserter(buf2)); + EXPECT_EQ(buf, buf2); + + auto iter = buf.begin(); + map.encode([&](uint8_t c) { EXPECT_EQ(c, *iter++); }); +} + +TEST(EncodingMethodsTest, UintWithTooShortBuf) { + Uint val(100000); + vector buf(val.encodedSize() - 1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, TstrWithTooShortBuf) { + Tstr val("01234567890123456789012345"s); + vector buf(1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); + + buf.resize(val.encodedSize() - 1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, BstrWithTooShortBuf) { + Bstr val("01234567890123456789012345"s); + vector buf(1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); + + buf.resize(val.encodedSize() - 1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, ArrayWithTooShortBuf) { + Array val("a", 5, -100); + + std::vector buf(val.encodedSize() - 1); + EXPECT_EQ(nullptr, val.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, MapWithTooShortBuf) { + Map map; + map.add("key1", Array().add(Map() // + .add("key_a", 99) + .add("key_b", std::vector{0x01, 0x02, 0x03})) + .add("foo")) + .add("key2", true); + + std::vector buf(map.encodedSize() - 1); + EXPECT_EQ(nullptr, map.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EncodingMethodsTest, SemanticTagWithTooShortBuf) { + SemanticTag tag(4321, Array().add(Array().add("Qaiyrly kesh!").add("Kesh zharyq!").add("431")) + .add(Map().add("kilt_1", 777).add("kilt_2", 999))); + std::vector buf(tag.encodedSize() - 1); + EXPECT_EQ(nullptr, tag.encode(buf.data(), buf.data() + buf.size())); +} + +TEST(EqualityTest, Uint) { + Uint val(99); + EXPECT_EQ(val, Uint(99)); + + EXPECT_NE(val, Uint(98)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("99")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Nint) { + Nint val(-1); + EXPECT_EQ(val, Nint(-1)); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("99")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Tstr) { + Tstr val("99"); + EXPECT_EQ(val, Tstr("99")); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("98")); + EXPECT_NE(val, Bstr("99")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Bstr) { + Bstr val("99"); + EXPECT_EQ(val, Bstr("99")); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(false)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Bool) { + Bool val(false); + EXPECT_EQ(val, Bool(false)); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Array) { + Array val(99, 1); + EXPECT_EQ(val, Array(99, 1)); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 2)); + EXPECT_NE(val, Array(98, 1)); + EXPECT_NE(val, Array(99, 1, 2)); + EXPECT_NE(val, Map(99, 1)); +} + +TEST(EqualityTest, Map) { + Map val(99, 1); + EXPECT_EQ(val, Map(99, 1)); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 2)); + EXPECT_NE(val, Map(99, 1, 99, 2)); +} + +TEST(EqualityTest, Null) { + Null val; + EXPECT_EQ(val, Null()); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 2)); + EXPECT_NE(val, Map(99, 1, 99, 2)); +} + +TEST(EqualityTest, SemanticTag) { + SemanticTag val(215, Bstr("asd")); + EXPECT_EQ(val, SemanticTag(215, Bstr("asd"))); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 2)); + EXPECT_NE(val, Null()); +} + +TEST(EqualityTest, NestedSemanticTag) { + SemanticTag val(238238, SemanticTag(215, Bstr("asd"))); + EXPECT_EQ(val, SemanticTag(238238, SemanticTag(215, Bstr("asd")))); + + EXPECT_NE(val, Uint(99)); + EXPECT_NE(val, Nint(-1)); + EXPECT_NE(val, Nint(-4)); + EXPECT_NE(val, Tstr("99")); + EXPECT_NE(val, Bstr("98")); + EXPECT_NE(val, Bool(true)); + EXPECT_NE(val, Array(99, 1)); + EXPECT_NE(val, Map(99, 2)); + EXPECT_NE(val, Null()); +} + +TEST(ConvertTest, Uint) { + unique_ptr item = details::makeItem(10); + + EXPECT_EQ(cppbor::UINT, item->type()); + EXPECT_NE(nullptr, item->asInt()); + EXPECT_NE(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(10, item->asInt()->value()); + EXPECT_EQ(10, item->asUint()->value()); +} + +TEST(ConvertTest, Nint) { + unique_ptr item = details::makeItem(-10); + + EXPECT_EQ(NINT, item->type()); + EXPECT_NE(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_NE(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(-10, item->asInt()->value()); + EXPECT_EQ(-10, item->asNint()->value()); +} + +TEST(ConvertTest, Tstr) { + unique_ptr item = details::makeItem("hello"); + + EXPECT_EQ(TSTR, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_NE(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ("hello"s, item->asTstr()->value()); +} + +TEST(ConvertTest, Bstr) { + vector vec{0x23, 0x24, 0x22}; + unique_ptr item = details::makeItem(vec); + + EXPECT_EQ(BSTR, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_NE(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(vec, item->asBstr()->value()); +} + +TEST(ConvertTest, Bool) { + unique_ptr item = details::makeItem(false); + + EXPECT_EQ(SIMPLE, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_NE(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(cppbor::BOOLEAN, item->asSimple()->simpleType()); + EXPECT_NE(nullptr, item->asSimple()->asBool()); + EXPECT_EQ(nullptr, item->asSimple()->asNull()); + + EXPECT_FALSE(item->asSimple()->asBool()->value()); +} + +TEST(ConvertTest, Map) { + unique_ptr item(new Map); + + EXPECT_EQ(MAP, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_NE(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(0U, item->asMap()->size()); +} + +TEST(ConvertTest, Array) { + unique_ptr item(new Array); + + EXPECT_EQ(ARRAY, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_NE(nullptr, item->asArray()); + + EXPECT_EQ(0U, item->asArray()->size()); +} + +TEST(ConvertTest, SemanticTag) { + unique_ptr item(new SemanticTag(10, "DSA")); + + EXPECT_EQ(TSTR, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + // Both asTstr() (the contained type) and asSemanticTag() return non-null. + EXPECT_NE(nullptr, item->asTstr()); + EXPECT_NE(nullptr, item->asSemanticTag()); + + // asTtr() and asSemanticTag() actually return different objects. + EXPECT_NE(static_cast(item->asTstr()), static_cast(item->asSemanticTag())); + + EXPECT_EQ(1U, item->asSemanticTag()->size()); + EXPECT_EQ("DSA", item->asTstr()->value()); + + EXPECT_EQ(1U, item->semanticTagCount()); + EXPECT_EQ(10U, item->semanticTag()); +} + +TEST(ConvertTest, NestedSemanticTag) { + unique_ptr item(new SemanticTag(40, new SemanticTag(10, "DSA"))); + + EXPECT_EQ(TSTR, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_EQ(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + // Both asTstr() (the contained type) and asSemanticTag() return non-null. + EXPECT_NE(nullptr, item->asTstr()); + EXPECT_NE(nullptr, item->asSemanticTag()); + + // asTtr() and asSemanticTag() actually return different objects. Note that there's no way to + // get a pointer to the "inner" SemanticTag object. There shouldn't be any need to. + EXPECT_NE(static_cast(item->asTstr()), static_cast(item->asSemanticTag())); + + EXPECT_EQ(1U, item->asSemanticTag()->size()); + EXPECT_EQ("DSA", item->asTstr()->value()); + + EXPECT_EQ(2U, item->semanticTagCount()); + EXPECT_EQ(10U, item->semanticTag(0)); + EXPECT_EQ(40U, item->semanticTag(1)); +} + +TEST(ConvertTest, Null) { + unique_ptr item(new Null); + + EXPECT_EQ(SIMPLE, item->type()); + EXPECT_EQ(nullptr, item->asInt()); + EXPECT_EQ(nullptr, item->asUint()); + EXPECT_EQ(nullptr, item->asNint()); + EXPECT_EQ(nullptr, item->asTstr()); + EXPECT_EQ(nullptr, item->asBstr()); + EXPECT_NE(nullptr, item->asSimple()); + EXPECT_EQ(nullptr, item->asMap()); + EXPECT_EQ(nullptr, item->asArray()); + + EXPECT_EQ(NULL_T, item->asSimple()->simpleType()); + EXPECT_EQ(nullptr, item->asSimple()->asBool()); + EXPECT_NE(nullptr, item->asSimple()->asNull()); +} + +TEST(CloningTest, Uint) { + Uint item(10); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), cppbor::UINT); + EXPECT_NE(clone->asUint(), nullptr); + EXPECT_EQ(item, *clone->asUint()); + EXPECT_EQ(*clone->asUint(), Uint(10)); +} + +TEST(CloningTest, Nint) { + Nint item(-1000000); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), NINT); + EXPECT_NE(clone->asNint(), nullptr); + EXPECT_EQ(item, *clone->asNint()); + EXPECT_EQ(*clone->asNint(), Nint(-1000000)); +} + +TEST(CloningTest, Tstr) { + Tstr item("qwertyasdfgh"); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), TSTR); + EXPECT_NE(clone->asTstr(), nullptr); + EXPECT_EQ(item, *clone->asTstr()); + EXPECT_EQ(*clone->asTstr(), Tstr("qwertyasdfgh")); +} + +TEST(CloningTest, Bstr) { + Bstr item(std::vector{1, 2, 3, 255, 0}); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), BSTR); + EXPECT_NE(clone->asBstr(), nullptr); + EXPECT_EQ(item, *clone->asBstr()); + EXPECT_EQ(*clone->asBstr(), Bstr(std::vector{1, 2, 3, 255, 0})); +} + +TEST(CloningTest, Array) { + Array item(-1000000, 22222222, "item", Map(1, 2, 4, Array(1, "das", true, nullptr)), + SemanticTag(16, "DATA")), + copy(-1000000, 22222222, "item", Map(1, 2, 4, Array(1, "das", true, nullptr)), + SemanticTag(16, "DATA")); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), ARRAY); + EXPECT_NE(clone->asArray(), nullptr); + EXPECT_EQ(item, *clone->asArray()); + EXPECT_EQ(*clone->asArray(), copy); +} + +TEST(CloningTest, Map) { + Map item("key", Array("value1", "value2", 3), 15, Null(), -5, 45), + copy("key", Array("value1", "value2", 3), 15, Null(), -5, 45); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), MAP); + EXPECT_NE(clone->asMap(), nullptr); + EXPECT_EQ(item, *clone->asMap()); + EXPECT_EQ(*clone->asMap(), copy); +} + +TEST(CloningTest, Bool) { + Bool item(true); + auto clone = item.clone(); + EXPECT_EQ(clone->type(), SIMPLE); + EXPECT_NE(clone->asSimple(), nullptr); + EXPECT_EQ(clone->asSimple()->simpleType(), cppbor::BOOLEAN); + EXPECT_NE(clone->asSimple()->asBool(), nullptr); + EXPECT_EQ(item, *clone->asSimple()->asBool()); + EXPECT_EQ(*clone->asSimple()->asBool(), Bool(true)); +} + +TEST(CloningTest, Null) { + Null item; + auto clone = item.clone(); + EXPECT_EQ(clone->type(), SIMPLE); + EXPECT_NE(clone->asSimple(), nullptr); + EXPECT_EQ(clone->asSimple()->simpleType(), NULL_T); + EXPECT_NE(clone->asSimple()->asNull(), nullptr); + EXPECT_EQ(item, *clone->asSimple()->asNull()); + EXPECT_EQ(*clone->asSimple()->asNull(), Null()); +} + +TEST(CloningTest, SemanticTag) { + SemanticTag item(96, Array(1, 2, 3, "entry", Map("key", "value"))); + SemanticTag copy(96, Array(1, 2, 3, "entry", Map("key", "value"))); + + auto clone = item.clone(); + EXPECT_EQ(clone->type(), ARRAY); + EXPECT_NE(clone->asSemanticTag(), nullptr); + EXPECT_EQ(item, *clone->asSemanticTag()); + EXPECT_EQ(*clone->asSemanticTag(), copy); +} + +TEST(CloningTest, NestedSemanticTag) { + SemanticTag item(20, // + SemanticTag(30, // + SemanticTag(96, // + Array(1, 2, 3, "entry", Map("key", "value"))))); + SemanticTag copy(20, // + SemanticTag(30, // + SemanticTag(96, // + Array(1, 2, 3, "entry", Map("key", "value"))))); + + auto clone = item.clone(); + EXPECT_EQ(clone->type(), ARRAY); + EXPECT_NE(clone->asSemanticTag(), nullptr); + EXPECT_EQ(item, *clone->asSemanticTag()); + EXPECT_EQ(*clone->asSemanticTag(), copy); +} + +TEST(PrettyPrintingTest, NestedSemanticTag) { + SemanticTag item(20, // + SemanticTag(30, // + SemanticTag(96, // + Array(1, 2, 3, "entry", Map("key", "value"))))); + EXPECT_EQ(prettyPrint(&item), + "tag 20 tag 30 tag 96 [\n" + " 1,\n" + " 2,\n" + " 3,\n" + " 'entry',\n" + " {\n" + " 'key' : 'value',\n" + " },\n" + "]"); +} + +TEST(MapCanonicalizationTest, CanonicalizationTest) { + Map map; + map.add("hello", 1) + .add("h", 1) + .add(1, 1) + .add(-4, 1) + .add(-5, 1) + .add(2, 1) + .add("hellp", 1) + .add(254, 1) + .add(27, 1); + + EXPECT_EQ(prettyPrint(&map), + "{\n" + " 'hello' : 1,\n" + " 'h' : 1,\n" + " 1 : 1,\n" + " -4 : 1,\n" + " -5 : 1,\n" + " 2 : 1,\n" + " 'hellp' : 1,\n" + " 254 : 1,\n" + " 27 : 1,\n" + "}"); + + map.canonicalize(); + + // Canonically ordered by key encoding. + EXPECT_EQ(prettyPrint(&map), + "{\n" + " 1 : 1,\n" + " 2 : 1,\n" + " -4 : 1,\n" + " -5 : 1,\n" + " 27 : 1,\n" + " 254 : 1,\n" + " 'h' : 1,\n" + " 'hello' : 1,\n" + " 'hellp' : 1,\n" + "}"); +} + +TEST(MapCanonicalizationTest, DecanonicalizationTest) { + Map map; + map.add("hello", 1) + .add("h", 1) + .add(1, 1) + .add(-4, 1) + .add(-5, 1) + .add(2, 1) + .add("hellp", 1) + .add(254, 1) + .add(27, 1); + + EXPECT_FALSE(map.isCanonical()); + map.canonicalize(); + EXPECT_TRUE(map.isCanonical()); + + /* + * Any operation that could potentially mutate the contents of the map should mark it as + * non-canonical. This includes getting non-const iterators or using the non-const [] operator. + */ + + map.begin(); + EXPECT_FALSE(map.isCanonical()); + + map.canonicalize(); + EXPECT_TRUE(map.isCanonical()); + + map.end(); // Non-const map.end() invalidates canonicalization. + EXPECT_FALSE(map.isCanonical()); + + map.canonicalize(); + EXPECT_TRUE(map.isCanonical()); + + map[0]; // Non-const map.operator[]() invalidates canonicalization. + EXPECT_FALSE(map.isCanonical()); +} + +TEST(MapCanonicalizationTest, RecursiveTest) { + auto map = Map() // + .add("hello", 1) + .add("h", 1) + .add(1, 1) + .add(-4, Array( // + 2, 1, + Map() // + .add("b", 1) + .add(Map() // + .add("hello", "goodbye") + .add(1, 9) + .add(0, 3), + Map() // + .add("b", 1) + .add("a", 2)))) + .add(-5, 1) + .add(2, 1) + .add("hellp", 1) + .add(254, 1) + .add(27, 1); + + EXPECT_EQ(prettyPrint(&map), + "{\n" + " 'hello' : 1,\n" + " 'h' : 1,\n" + " 1 : 1,\n" + " -4 : [\n" + " 2,\n" + " 1,\n" + " {\n" + " 'b' : 1,\n" + " {\n" + " 'hello' : 'goodbye',\n" + " 1 : 9,\n" + " 0 : 3,\n" + " } : {\n" + " 'b' : 1,\n" + " 'a' : 2,\n" + " },\n" + " },\n" + " ],\n" + " -5 : 1,\n" + " 2 : 1,\n" + " 'hellp' : 1,\n" + " 254 : 1,\n" + " 27 : 1,\n" + "}"); + + map.canonicalize(true /* recurse */); + + EXPECT_EQ(prettyPrint(&map), + "{\n" + " 1 : 1,\n" + " 2 : 1,\n" + " -4 : [\n" + " 2,\n" + " 1,\n" + " {\n" + " 'b' : 1,\n" + " {\n" + " 0 : 3,\n" + " 1 : 9,\n" + " 'hello' : 'goodbye',\n" + " } : {\n" + " 'a' : 2,\n" + " 'b' : 1,\n" + " },\n" + " },\n" + " ],\n" + " -5 : 1,\n" + " 27 : 1,\n" + " 254 : 1,\n" + " 'h' : 1,\n" + " 'hello' : 1,\n" + " 'hellp' : 1,\n" + "}"); +} + +class MockParseClient : public ParseClient { + public: + MOCK_METHOD4(item, ParseClient*(std::unique_ptr& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end)); + MOCK_METHOD4(itemEnd, ParseClient*(std::unique_ptr& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end)); + MOCK_METHOD2(error, void(const uint8_t* position, const std::string& errorMessage)); +}; + +MATCHER_P(IsType, value, std::string("Type ") + (negation ? "doesn't match" : "matches")) { + return arg->type() == value; +} + +MATCHER_P(MatchesItem, value, "") { + return arg && *arg == value; +} + +MATCHER_P(IsArrayOfSize, value, "") { + return arg->type() == ARRAY && arg->asArray()->size() == value; +} + +MATCHER_P(IsSemanticTagOfValue, value, "") { + return arg->semanticTagCount() == 1 && arg->semanticTag() == value; +} + +MATCHER_P(IsMapOfSize, value, "") { + return arg->type() == MAP && arg->asMap()->size() == value; +} + +TEST(StreamParseTest, Uint) { + MockParseClient mpc; + + Uint val(100); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Nint) { + MockParseClient mpc; + + Nint val(-10); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc)); + + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Bool) { + MockParseClient mpc; + + Bool val(true); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Null) { + MockParseClient mpc; + + Null val; + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encEnd, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Tstr) { + MockParseClient mpc; + + Tstr val("Hello"); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Bstr) { + MockParseClient mpc; + + Bstr val("Hello"); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + EXPECT_CALL(mpc, item(MatchesItem(val), encBegin, encBegin + 1, encEnd)).WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(_, _, _, _)).Times(0); + EXPECT_CALL(mpc, error(_, _)).Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Array) { + MockParseClient mpc; + + Array val("Hello", 4, Array(-9, "Goodbye"), std::numeric_limits::max()); + ASSERT_NE(val[2]->asArray(), nullptr); + const Array& interior = *(val[2]->asArray()); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + { + InSequence s; + const uint8_t* pos = encBegin; + EXPECT_CALL(mpc, item(IsArrayOfSize(val.size()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0])), pos, pos + 1, pos + 6)) + .WillOnce(Return(&mpc)); + pos += 6; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[1])), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + const uint8_t* innerArrayBegin = pos; + EXPECT_CALL(mpc, item(IsArrayOfSize(interior.size()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[0])), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[1])), pos, pos + 1, pos + 8)) + .WillOnce(Return(&mpc)); + pos += 8; + EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(interior.size()), innerArrayBegin, + innerArrayBegin + 1, pos)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[3])), pos, pos + 9, pos + 9)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(val.size()), encBegin, encBegin + 1, encEnd)) + .WillOnce(Return(&mpc)); + } + + EXPECT_CALL(mpc, error(_, _)) // + .Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, SemanticTag) { + MockParseClient mpc; + SemanticTag val(15, Array(-5, "Hi")); + auto encoded = val.encode(); + ASSERT_NE(val.asArray(), nullptr); + const Array& array = *(val.asArray()); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + { + InSequence s; + const uint8_t* pos = encBegin; + EXPECT_CALL(mpc, item(IsSemanticTagOfValue(val.semanticTag()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + const uint8_t* innerArrayBegin = pos; + EXPECT_CALL(mpc, item(IsArrayOfSize(array.size()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*array[0])), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*array[1])), pos, pos + 1, pos + 3)) + .WillOnce(Return(&mpc)); + pos += 3; + EXPECT_CALL(mpc, + itemEnd(IsArrayOfSize(array.size()), innerArrayBegin, innerArrayBegin + 1, pos)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(IsSemanticTagOfValue(val.semanticTag()), encBegin, encBegin + 1, + encEnd)) + .WillOnce(Return(&mpc)); + } + + EXPECT_CALL(mpc, error(_, _)) // + .Times(0); + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(StreamParseTest, Map) { + MockParseClient mpc; + + Map val("Hello", 4, Array(-9, "Goodbye"), std::numeric_limits::max()); + ASSERT_NE(val[1].first->asArray(), nullptr); + const Array& interior = *(val[1].first->asArray()); + auto encoded = val.encode(); + uint8_t* encBegin = encoded.data(); + uint8_t* encEnd = encoded.data() + encoded.size(); + + { + InSequence s; + const uint8_t* pos = encBegin; + EXPECT_CALL(mpc, item(_, pos, pos + 1, pos + 1)).WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0].first)), pos, pos + 1, pos + 6)) + .WillOnce(Return(&mpc)); + pos += 6; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[0].second)), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + const uint8_t* innerArrayBegin = pos; + EXPECT_CALL(mpc, item(IsArrayOfSize(interior.size()), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[0])), pos, pos + 1, pos + 1)) + .WillOnce(Return(&mpc)); + ++pos; + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*interior[1])), pos, pos + 1, pos + 8)) + .WillOnce(Return(&mpc)); + pos += 8; + EXPECT_CALL(mpc, itemEnd(IsArrayOfSize(interior.size()), innerArrayBegin, + innerArrayBegin + 1, pos)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, item(MatchesItem(ByRef(*val[1].second)), pos, pos + 9, pos + 9)) + .WillOnce(Return(&mpc)); + EXPECT_CALL(mpc, itemEnd(IsMapOfSize(val.size()), encBegin, encBegin + 1, encEnd)) + .WillOnce(Return(&mpc)); + } + + EXPECT_CALL(mpc, error(_, _)) // + .Times(0); + + parse(encoded.data(), encoded.data() + encoded.size(), &mpc); +} + +TEST(FullParserTest, Uint) { + Uint val(10); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, Null) { + Null val; + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, Nint) { + Nint val(-10); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); + + vector minNint = {0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + std::tie(item, pos, message) = parse(minNint); + EXPECT_THAT(item, NotNull()); + EXPECT_EQ(item->asNint()->value(), std::numeric_limits::min()); +} + +TEST(FullParserTest, NintOutOfRange) { + vector outOfRangeNint = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + + auto [item, pos, message] = parse(outOfRangeNint); + EXPECT_THAT(item, IsNull()); + EXPECT_EQ(pos, outOfRangeNint.data()); + EXPECT_EQ(message, "NINT values that don't fit in int64_t are not supported."); +} + +TEST(FullParserTest, Tstr) { + Tstr val("Hello"); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, Bstr) { + Bstr val("\x00\x01\0x02"s); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(val)); +} + +TEST(FullParserTest, Array) { + Array val("hello", -4, 3); + + auto encoded = val.encode(); + auto [item, pos, message] = parse(encoded); + EXPECT_THAT(item, MatchesItem(ByRef(val))); + EXPECT_EQ(pos, encoded.data() + encoded.size()); + EXPECT_EQ("", message); + + // We've already checked it all, but walk it just for fun. + ASSERT_NE(nullptr, item->asArray()); + const Array& arr = *(item->asArray()); + ASSERT_EQ(arr[0]->type(), TSTR); + EXPECT_EQ(arr[0]->asTstr()->value(), "hello"); +} + +TEST(FullParserTest, Map) { + Map val("hello", -4, 3, Bstr("hi")); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(ByRef(val))); +} + +TEST(FullParserTest, SemanticTag) { + SemanticTag val(99, "Salem"); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(ByRef(val))); +} + +TEST(FullParserTest, NestedSemanticTag) { + SemanticTag val(10, SemanticTag(99, "Salem")); + + auto [item, pos, message] = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(ByRef(val))); +} + +TEST(FullParserTest, Complex) { + vector vec = {0x01, 0x02, 0x08, 0x03}; + Map val("Outer1", + Array(Map("Inner1", 99, // + "Inner2", vec), + "foo"), + "Outer2", 10); + + std::unique_ptr item; + const uint8_t* pos; + std::string message; + std::tie(item, pos, message) = parse(val.encode()); + EXPECT_THAT(item, MatchesItem(ByRef(val))); +} + +TEST(FullParserTest, IncompleteUint) { + Uint val(1000); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data(), pos); + EXPECT_EQ("Need 2 byte(s) for length field, have 1.", message); +} + +TEST(FullParserTest, IncompleteString) { + Tstr val("hello"); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 2); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data(), pos); + EXPECT_EQ("Need 5 byte(s) for text string, have 3.", message); +} + +TEST(FullParserTest, ArrayWithInsufficientEntries) { + Array val(1, 2, 3, 4); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data(), pos); + EXPECT_EQ("Not enough entries for array.", message); +} + +TEST(FullParserTest, ArrayWithTruncatedEntry) { + Array val(1, 2, 3, 400000); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 1); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data() + encoding.size() - 5, pos); + EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message); +} + +TEST(FullParserTest, MapWithTruncatedEntry) { + Map val(1, 2, 300000, 4); + + auto encoding = val.encode(); + auto [item, pos, message] = parse(encoding.data(), encoding.size() - 2); + EXPECT_EQ(nullptr, item.get()); + EXPECT_EQ(encoding.data() + 3, pos); + EXPECT_EQ("Need 4 byte(s) for length field, have 3.", message); +} + +TEST(FullParserTest, ReservedAdditionalInformation) { + vector reservedVal = {0x1D}; + + auto [item, pos, message] = parse(reservedVal); + EXPECT_THAT(item, IsNull()); + EXPECT_EQ(pos, reservedVal.data()); + EXPECT_EQ("Reserved additional information value or unsupported indefinite length item.", + message); +} + +TEST(FullParserTest, IndefiniteArray) { + vector indefiniteArray = {0x7F}; + + auto [item, pos, message] = parse(indefiniteArray); + EXPECT_THAT(item, IsNull()); + EXPECT_EQ(pos, indefiniteArray.data()); + EXPECT_EQ("Reserved additional information value or unsupported indefinite length item.", + message); +} + +TEST(FullParserTest, UnassignedSimpleValue) { + vector unassignedSimpleValue = {0xE5}; + + auto [item, pos, message] = parse(unassignedSimpleValue); + EXPECT_THAT(item, IsNull()); + EXPECT_EQ(pos, unassignedSimpleValue.data()); + EXPECT_EQ("Unsupported floating-point or simple value.", message); +} + +TEST(FullParserTest, FloatingPointValue) { + vector floatingPointValue = {0xFA, 0x12, 0x75, 0x34, 0x37}; + + auto [item, pos, message] = parse(floatingPointValue); + EXPECT_THAT(item, IsNull()); + EXPECT_EQ(pos, floatingPointValue.data()); + EXPECT_EQ("Unsupported floating-point or simple value.", message); +} + +TEST(MapGetValueByKeyTest, Map) { + Array compoundItem(1, 2, 3, 4, 5, Map(4, 5, "a", "b")); + auto clone = compoundItem.clone(); + Map item(1, 2, "key", "value", "item", std::move(compoundItem)); + auto& value1 = item.get(1); + EXPECT_NE(value1.get(), nullptr); + EXPECT_EQ(*value1, Uint(2)); + auto& value2 = item.get("key"); + EXPECT_NE(value2.get(), nullptr); + EXPECT_EQ(*value2, Tstr("value")); + auto& value3 = item.get("item"); + EXPECT_NE(value3.get(), nullptr); + EXPECT_EQ(*value3, *clone); + auto& value4 = item.get("wrong"); + EXPECT_EQ(value4.get(), nullptr); +} + +TEST(EmptyBstrTest, Bstr) { + Bstr bstr(std::vector{}); + auto encoding = bstr.encode(); + auto [obj, pos, message] = parse(encoding.data(), encoding.size()); + EXPECT_NE(obj.get(), nullptr); + EXPECT_EQ(*obj, bstr); +} + +TEST(ArrayIterationTest, EmptyArray) { + Array array; + EXPECT_EQ(array.begin(), array.end()); + + const Array& const_array = array; + EXPECT_EQ(const_array.begin(), const_array.end()); +} + +TEST(ArrayIterationTest, ForwardTest) { + Array array(1, 2, 3, "hello", -4); + + auto iter = array.begin(); + ASSERT_NE(iter, array.end()); + EXPECT_EQ(**iter, Uint(1)); + + ASSERT_NE(++iter, array.end()); + EXPECT_EQ(**iter++, Uint(2)); + + ASSERT_NE(iter, array.end()); + EXPECT_EQ(**iter, Uint(3)); + + ASSERT_NE(++iter, array.end()); + EXPECT_EQ(**iter++, Tstr("hello")); + + ASSERT_NE(iter, array.end()); + EXPECT_EQ(**iter, Nint(-4)); + + EXPECT_EQ(++iter, array.end()); +} + +TEST(ArrayIterationTest, BidirectionalTest) { + Array array(1, 2, 3, "hello", -4); + + auto iter = array.begin(); + ASSERT_NE(iter, array.end()); + EXPECT_EQ(**iter, Uint(1)); + + ASSERT_NE(++iter, array.end()); + EXPECT_EQ(**iter, Uint(2)); + + ASSERT_NE(--iter, array.end()); + ASSERT_EQ(iter, array.begin()); + EXPECT_EQ(**iter, Uint(1)); + + ASSERT_NE(++iter, array.end()); + EXPECT_EQ(**iter, Uint(2)); + + ASSERT_NE(++iter, array.end()); + EXPECT_EQ(**iter--, Uint(3)); + + ASSERT_NE(++iter, array.end()); + EXPECT_EQ(**iter, Uint(3)); + + ASSERT_NE(++iter, array.end()); + EXPECT_EQ(**iter, Tstr("hello")); + + ASSERT_NE(++iter, array.end()); + EXPECT_EQ(**iter, Nint(-4)); + + EXPECT_EQ(++iter, array.end()); +} + +TEST(MapIterationTest, EmptyMap) { + Map map; + + EXPECT_EQ(map.begin(), map.end()); +} + +TEST(MapIterationTest, ForwardTest) { + Map map(1, 2, 3, "hello", -4, 5); + + auto iter = map.begin(); + ASSERT_NE(iter, map.end()); + EXPECT_EQ(*iter->first, Uint(1)); + EXPECT_EQ(*iter->second, Uint(2)); + + ASSERT_NE(++iter, map.end()); + EXPECT_EQ(*iter->first, Uint(3)); + EXPECT_EQ(*(iter++)->second, Tstr("hello")); + + ASSERT_NE(iter, map.end()); + EXPECT_EQ(*iter->first, Nint(-4)); + EXPECT_EQ(*(iter++)->second, Uint(5)); + + EXPECT_EQ(iter, map.end()); +} + +TEST(MapIterationTest, BidirectionalTest) { + Map map(1, 2, 3, "hello", -4, 5); + + auto iter = map.begin(); + ASSERT_NE(iter, map.end()); + EXPECT_EQ(*iter->first, Uint(1)); + EXPECT_EQ(*iter->second, Uint(2)); + + ASSERT_NE(++iter, map.end()); + EXPECT_EQ(*iter->first, Uint(3)); + EXPECT_EQ(*(iter--)->second, Tstr("hello")); + + ASSERT_NE(iter, map.end()); + EXPECT_EQ(*iter->first, Uint(1)); + EXPECT_EQ(*(iter++)->second, Uint(2)); + + ASSERT_NE(iter, map.end()); + EXPECT_EQ(*iter->first, Uint(3)); + EXPECT_EQ(*iter->second, Tstr("hello")); + + ASSERT_NE(++iter, map.end()); + EXPECT_EQ(*iter->first, Nint(-4)); + EXPECT_EQ(*(iter++)->second, Uint(5)); + + EXPECT_EQ(iter, map.end()); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/util/include/wv_attributes.h b/util/include/wv_attributes.h index c817f1c0..c890c933 100644 --- a/util/include/wv_attributes.h +++ b/util/include/wv_attributes.h @@ -13,4 +13,12 @@ # endif #endif +#ifndef WEAK +# if defined(__GNUC__) || defined(__clang__) +# define WEAK __attribute__((weak)) +# else +# define WEAK +# endif +#endif + #endif // WVCDM_UTIL_WV_ATTRIBUTES_H_ diff --git a/util/test/test_sleep.cpp b/util/test/test_sleep.cpp index 439a46d5..f52e5715 100644 --- a/util/test/test_sleep.cpp +++ b/util/test/test_sleep.cpp @@ -30,7 +30,7 @@ TestSleep::CallBack* TestSleep::callback_ = nullptr; int TestSleep::total_clock_rollback_seconds_ = 0; void TestSleep::Sleep(unsigned int seconds) { - int64_t milliseconds = 1000 * seconds; + int64_t milliseconds = 1000 * static_cast(seconds); if (real_sleep_) { // This next bit of logic is to avoid slow drift apart of the real clock and // the fake clock. We compute how far from the real clock has advanced in @@ -40,7 +40,8 @@ void TestSleep::Sleep(unsigned int seconds) { static const auto start_real = std::chrono::system_clock().now(); sleep(seconds); const auto now_real = std::chrono::system_clock().now(); - const int64_t rollback_adjustment = 1000 * total_clock_rollback_seconds_; + const int64_t rollback_adjustment = + 1000 * static_cast(total_clock_rollback_seconds_); const int64_t total_real = (now_real - start_real) / std::chrono::milliseconds(1) + rollback_adjustment; @@ -129,7 +130,9 @@ bool TestSleep::RollbackSystemTime(int seconds) { // For both real and fake sleep we still update the callback and we still keep // track of the total amount of time slept. total_clock_rollback_seconds_ += seconds; - if (callback_ != nullptr) callback_->ElapseTime(-1000 * seconds); + if (callback_ != nullptr) { + callback_->ElapseTime(-1000 * static_cast(seconds)); + } return true; }