From f11df1e14461ed336b6dd1a3ac5a0d5062da5ac8 Mon Sep 17 00:00:00 2001 From: "John \"Juce\" Bruce" Date: Tue, 29 Nov 2022 12:54:04 -0800 Subject: [PATCH] Source release 17.1.1 --- CHANGELOG.md | 32 + README.md | 4 +- cdm/cdm.gyp | 100 +- cdm/cdm_reboot_tests.gyp | 15 +- cdm/cdm_unittests.gyp | 42 +- cdm/create_cert_cc.py | 6 +- cdm/include/cdm.h | 34 +- cdm/include/cdm_version.h | 2 +- cdm/include/compiler_detection.h | 2 +- cdm/include/properties_ce.h | 1 + cdm/include/read_client_info.h | 32 + cdm/platform_properties.gypi | 104 +- cdm/src/cdm.cpp | 96 +- cdm/src/properties_ce.cpp | 118 +- cdm/test/cdm_reboot_test_main.cpp | 1 - cdm/test/cdm_test.cpp | 17 +- cdm/test/cdm_test_main.cpp | 1 - cdm/test/cdm_test_runner.cpp | 1 - cdm/test/device_cert.cpp | 228 --- cdm/test/device_cert.h | 14 - cdm/test/perf_test_xctest.mm | 3 - cdm/test/test_host.cpp | 29 +- cdm/util.gypi | 12 +- cdm/util_unittests.gypi | 1 + factory_upload_tool/ce/README | 10 + factory_upload_tool/ce/example_main.cpp | 33 + factory_upload_tool/ce/log.cpp | 42 + factory_upload_tool/ce/properties_ce.cpp | 87 ++ factory_upload_tool/ce/properties_ce.h | 19 + .../ce/wv_factory_extractor.cpp | 83 ++ factory_upload_tool/ce/wv_factory_extractor.h | 70 + factory_upload_tool/ce/wv_upload_tool.py | 347 +++++ factory_upload_tool/common/README | 1 + .../include/WidevineOemcryptoInterface.h | 52 + .../common/include/properties.h | 34 + .../common/src/WidevineOemcryptoInterface.cpp | 143 ++ oemcrypto/include/OEMCryptoCENCCommon.h | 246 +--- oemcrypto/odk/Android.bp | 102 ++ oemcrypto/odk/README | 8 + oemcrypto/odk/include/OEMCryptoCENCCommon.h | 245 ++++ .../odk/include/core_message_deserialize.h | 83 ++ oemcrypto/odk/include/core_message_features.h | 44 + .../odk/include/core_message_serialize.h | 78 ++ .../include/core_message_serialize_proto.h | 67 + oemcrypto/odk/include/core_message_types.h | 115 ++ oemcrypto/odk/include/odk.h | 664 +++++++++ oemcrypto/odk/include/odk_attributes.h | 14 + oemcrypto/odk/include/odk_message.h | 141 ++ oemcrypto/odk/include/odk_structs.h | 228 +++ oemcrypto/odk/include/odk_target.h | 13 + .../odk/src/core_message_deserialize.cpp | 181 +++ oemcrypto/odk/src/core_message_features.cpp | 41 + oemcrypto/odk/src/core_message_serialize.cpp | 204 +++ .../odk/src/core_message_serialize_proto.cpp | 198 +++ oemcrypto/odk/src/kdo.gypi | 19 + oemcrypto/odk/src/odk.c | 511 +++++++ oemcrypto/odk/src/odk.gyp | 41 + oemcrypto/odk/src/odk.gypi | 18 + oemcrypto/odk/src/odk_assert.h | 24 + oemcrypto/odk/src/odk_endian.h | 41 + oemcrypto/odk/src/odk_message.c | 170 +++ oemcrypto/odk/src/odk_message_priv.h | 41 + oemcrypto/odk/src/odk_overflow.c | 46 + oemcrypto/odk/src/odk_overflow.h | 24 + oemcrypto/odk/src/odk_serialize.c | 352 +++++ oemcrypto/odk/src/odk_serialize.h | 61 + oemcrypto/odk/src/odk_structs_priv.h | 139 ++ oemcrypto/odk/src/odk_timer.c | 503 +++++++ oemcrypto/odk/src/odk_util.c | 33 + oemcrypto/odk/src/odk_util.h | 29 + oemcrypto/odk/src/serialization_base.c | 200 +++ oemcrypto/odk/src/serialization_base.h | 42 + oemcrypto/odk/test/fuzzing/Android.bp | 180 +++ oemcrypto/odk/test/fuzzing/README.md | 19 + .../602c63d2f3d13ca3206cdf204cde24e7d8f4266c | Bin 0 -> 20 bytes .../8cebdcc0161125a10e19c45f055051712873de25 | Bin 0 -> 20 bytes .../4e578d6c9628e832c099623b44f56d95aa37f94b | Bin 0 -> 272 bytes .../5b693511ef850e42c5ffded171794dbeb9460cc0 | Bin 0 -> 282200 bytes .../b6b865b095697164ad032c2f695ed828f5754749 | 1 + .../dba39b6cf6524e996397ddc1e08b928b5c92bb5d | Bin 0 -> 352 bytes .../dd1bc1827a331b7aed2a6fb6740da032123aa0a8 | Bin 0 -> 432 bytes .../53c26407b39c997143146a0dce8ff0ac11f565e1 | Bin 0 -> 88 bytes .../fab3c99d604bab7b7bf5c54c5bd995fc98d4d96f | Bin 0 -> 88 bytes .../91e10d030fbdd3374e57a2720f09488f2b03ce69 | Bin 0 -> 204 bytes .../12a72efb395e731ec4470b5f5b6768d6806e9131 | Bin 0 -> 76 bytes .../21de033b9baf2a0e82ae3b4185b22aa0acf69bbc | Bin 0 -> 48 bytes .../97bf96be666434bfa93dbfb36b81baeefed14170 | Bin 0 -> 76 bytes .../a7b0e7dca597331d7f051204096c9d01ba6d468e | Bin 0 -> 76 bytes .../38df40a320f60e955006aaa294b74d45a316e50f | Bin 0 -> 148 bytes .../9962997b5ea87005276319cbfff67884846485cf | Bin 0 -> 148 bytes .../c84663115c890873dd585987c1223193d29aef16 | Bin 0 -> 148 bytes .../test/fuzzing/corpus_generator/Android.bp | 39 + .../test/fuzzing/corpus_generator/README.md | 79 ++ .../corpus_generator/odk_corpus_generator.c | 158 +++ .../odk_corpus_generator_helper.c | 22 + .../odk_corpus_generator_helper.h | 18 + .../odk_fuzz_corpus_generator.gyp | 33 + oemcrypto/odk/test/fuzzing/odk_fuzz.gyp | 44 + .../odk/test/fuzzing/odk_fuzz_helper.cpp | 163 +++ oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h | 206 +++ oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h | 28 + .../test/fuzzing/odk_license_request_fuzz.cpp | 22 + .../fuzzing/odk_license_response_fuzz.cpp | 20 + ...odk_license_response_fuzz_with_mutator.cpp | 36 + .../fuzzing/odk_provisioning_request_fuzz.cpp | 22 + .../odk_provisioning_response_fuzz.cpp | 21 + ...rovisioning_response_fuzz_with_mutator.cpp | 40 + .../test/fuzzing/odk_renewal_request_fuzz.cpp | 22 + .../fuzzing/odk_renewal_response_fuzz.cpp | 20 + ...odk_renewal_response_fuzz_with_mutator.cpp | 38 + oemcrypto/odk/test/odk_core_message_test.cpp | 39 + oemcrypto/odk/test/odk_test.cpp | 975 +++++++++++++ oemcrypto/odk/test/odk_test.gypi | 13 + oemcrypto/odk/test/odk_test_helper.cpp | 656 +++++++++ oemcrypto/odk/test/odk_test_helper.h | 109 ++ oemcrypto/odk/test/odk_timer_test.cpp | 1239 +++++++++++++++++ .../test/fuzz_tests/oemcrypto_fuzztests.gypi | 3 +- .../partner_oemcrypto_fuzztests.gypi | 1 + oemcrypto/util/test/oem_cert_test.cpp | 8 +- oemcrypto/util/test/oem_cert_test.h | 27 + .../util/test/oemcrypto_oem_cert_unittest.cpp | 6 +- .../environment.py | 53 + .../read_client_info.cpp | 28 + .../read_client_info.gyp | 13 + .../example-runtime-client-info/settings.gypi | 122 ++ platforms/example/no_oemcrypto.cpp | 495 +++++++ platforms/example/oemcrypto.gyp | 20 + platforms/example/settings.gypi | 3 + third_party/gyp/input.py | 2 +- third_party/protoc.gypi | 8 +- util/include/file_store.h | 4 +- util/include/log.h | 13 +- util/include/platform.h | 2 +- util/include/rw_lock.h | 2 +- util/include/string_conversions.h | 49 +- util/include/string_format.h | 18 + util/include/util_common.h | 12 - util/src/string_format.cpp | 40 + util/test/string_format_unittest.cpp | 64 + 139 files changed, 11266 insertions(+), 771 deletions(-) create mode 100644 cdm/include/read_client_info.h delete mode 100644 cdm/test/device_cert.cpp delete mode 100644 cdm/test/device_cert.h create mode 100644 factory_upload_tool/ce/README create mode 100644 factory_upload_tool/ce/example_main.cpp create mode 100644 factory_upload_tool/ce/log.cpp create mode 100644 factory_upload_tool/ce/properties_ce.cpp create mode 100644 factory_upload_tool/ce/properties_ce.h create mode 100644 factory_upload_tool/ce/wv_factory_extractor.cpp create mode 100644 factory_upload_tool/ce/wv_factory_extractor.h create mode 100644 factory_upload_tool/ce/wv_upload_tool.py create mode 100644 factory_upload_tool/common/README create mode 100644 factory_upload_tool/common/include/WidevineOemcryptoInterface.h create mode 100644 factory_upload_tool/common/include/properties.h create mode 100644 factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp create mode 100644 oemcrypto/odk/Android.bp create mode 100644 oemcrypto/odk/README create mode 100644 oemcrypto/odk/include/OEMCryptoCENCCommon.h create mode 100644 oemcrypto/odk/include/core_message_deserialize.h create mode 100644 oemcrypto/odk/include/core_message_features.h create mode 100644 oemcrypto/odk/include/core_message_serialize.h create mode 100644 oemcrypto/odk/include/core_message_serialize_proto.h create mode 100644 oemcrypto/odk/include/core_message_types.h create mode 100644 oemcrypto/odk/include/odk.h create mode 100644 oemcrypto/odk/include/odk_attributes.h create mode 100644 oemcrypto/odk/include/odk_message.h create mode 100644 oemcrypto/odk/include/odk_structs.h create mode 100644 oemcrypto/odk/include/odk_target.h create mode 100644 oemcrypto/odk/src/core_message_deserialize.cpp create mode 100644 oemcrypto/odk/src/core_message_features.cpp create mode 100644 oemcrypto/odk/src/core_message_serialize.cpp create mode 100644 oemcrypto/odk/src/core_message_serialize_proto.cpp create mode 100644 oemcrypto/odk/src/kdo.gypi create mode 100644 oemcrypto/odk/src/odk.c create mode 100644 oemcrypto/odk/src/odk.gyp create mode 100644 oemcrypto/odk/src/odk.gypi create mode 100644 oemcrypto/odk/src/odk_assert.h create mode 100644 oemcrypto/odk/src/odk_endian.h create mode 100644 oemcrypto/odk/src/odk_message.c create mode 100644 oemcrypto/odk/src/odk_message_priv.h create mode 100644 oemcrypto/odk/src/odk_overflow.c create mode 100644 oemcrypto/odk/src/odk_overflow.h create mode 100644 oemcrypto/odk/src/odk_serialize.c create mode 100644 oemcrypto/odk/src/odk_serialize.h create mode 100644 oemcrypto/odk/src/odk_structs_priv.h create mode 100644 oemcrypto/odk/src/odk_timer.c create mode 100644 oemcrypto/odk/src/odk_util.c create mode 100644 oemcrypto/odk/src/odk_util.h create mode 100644 oemcrypto/odk/src/serialization_base.c create mode 100644 oemcrypto/odk/src/serialization_base.h create mode 100644 oemcrypto/odk/test/fuzzing/Android.bp create mode 100644 oemcrypto/odk/test/fuzzing/README.md create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/602c63d2f3d13ca3206cdf204cde24e7d8f4266c create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/8cebdcc0161125a10e19c45f055051712873de25 create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/4e578d6c9628e832c099623b44f56d95aa37f94b create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/5b693511ef850e42c5ffded171794dbeb9460cc0 create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/b6b865b095697164ad032c2f695ed828f5754749 create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dba39b6cf6524e996397ddc1e08b928b5c92bb5d create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dd1bc1827a331b7aed2a6fb6740da032123aa0a8 create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/53c26407b39c997143146a0dce8ff0ac11f565e1 create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/fab3c99d604bab7b7bf5c54c5bd995fc98d4d96f create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_response_corpus/91e10d030fbdd3374e57a2720f09488f2b03ce69 create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/12a72efb395e731ec4470b5f5b6768d6806e9131 create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/21de033b9baf2a0e82ae3b4185b22aa0acf69bbc create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/97bf96be666434bfa93dbfb36b81baeefed14170 create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/a7b0e7dca597331d7f051204096c9d01ba6d468e create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/38df40a320f60e955006aaa294b74d45a316e50f create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/9962997b5ea87005276319cbfff67884846485cf create mode 100644 oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/c84663115c890873dd585987c1223193d29aef16 create mode 100644 oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp create mode 100644 oemcrypto/odk/test/fuzzing/corpus_generator/README.md create mode 100644 oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c create mode 100644 oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c create mode 100644 oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h create mode 100644 oemcrypto/odk/test/fuzzing/corpus_generator/odk_fuzz_corpus_generator.gyp create mode 100644 oemcrypto/odk/test/fuzzing/odk_fuzz.gyp create mode 100644 oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h create mode 100644 oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h create mode 100644 oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp create mode 100644 oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp create mode 100644 oemcrypto/odk/test/odk_core_message_test.cpp create mode 100644 oemcrypto/odk/test/odk_test.cpp create mode 100644 oemcrypto/odk/test/odk_test.gypi create mode 100644 oemcrypto/odk/test/odk_test_helper.cpp create mode 100644 oemcrypto/odk/test/odk_test_helper.h create mode 100644 oemcrypto/odk/test/odk_timer_test.cpp create mode 100644 oemcrypto/util/test/oem_cert_test.h create mode 100644 platforms/example-runtime-client-info/environment.py create mode 100644 platforms/example-runtime-client-info/read_client_info.cpp create mode 100644 platforms/example-runtime-client-info/read_client_info.gyp create mode 100644 platforms/example-runtime-client-info/settings.gypi create mode 100644 platforms/example/no_oemcrypto.cpp create mode 100644 platforms/example/oemcrypto.gyp create mode 100644 util/include/string_format.h create mode 100644 util/src/string_format.cpp create mode 100644 util/test/string_format_unittest.cpp diff --git a/CHANGELOG.md b/CHANGELOG.md index 80bccdea..8b9ecc6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,38 @@ [TOC] +## 17.1.1 (2022-11-28) + +### Features: + + - For platforms that _cannot_ support compile-time client info, an interface + has been added that enables runtime client info support on CE CDM 17. + Widevine still recommends using compile-time client info if possible. + - To enable runtime client info, you must change your platform's + `client_info_source` property to `runtime` and then set the + `read_client_info_path` variable to point to a GYP target that implements + `read_client_info.h`. You are responsible for providing an implementation + of `read_client_info.h` that reads your platform's runtime client info. + - An example of how to use runtime client info is provided in + `platforms/example-runtime-client-info/`. + - Since the OEMCrypto Ref is no longer distributed by Widevine, the lines + offering it in `platform_properties.gypi` have been removed. It is no longer + the default OEMCrypto target. + - The example platform has been updated with its own stubbed-out + implementation of OEMCrypto. This will allow the example platform to build + without the OEMCrypto Ref but will not allow it to pass unit tests. + - The Provisioning 4.0 factory upload tool is now released alongside the CE + CDM. + +### Bugfixes: + + - The files `oem_cert.h` and `oem_cert.cpp` were omitted from 17.1.0 by + mistake and are now included. + - The ODK is now distributed with the CE CDM again in order to facilitate the + OEMCrypto unit tests. + - Fixed an issue where `CdmIndividualizationTest.RemoveProvisioning` would + fail for Provisioning 4.0 devices. + ## 17.1.0 (2022-06-29) **Note:** CE CDM 17.1.0 is the first release of the CE CDM 17 series. It is diff --git a/README.md b/README.md index 3357038e..a51ad5f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Widevine CE CDM 17.1.0 +# Widevine CE CDM 17.1.1 -Released 2022-06-29 +Released 2022-11-28 ## Getting Started diff --git a/cdm/cdm.gyp b/cdm/cdm.gyp index c8841f98..6c58fb7b 100644 --- a/cdm/cdm.gyp +++ b/cdm/cdm.gyp @@ -2,10 +2,9 @@ # source code may only be used and distributed under the Widevine License # Agreement. # -# Refer to the distribution package's integration guide -# (Widevine_CE_CDM_IntegrationGuide_x.x.x.pdf) for information about -# setting up your system, performing the build, and using/testing -# the build targets. +# Refer to the distribution package's integration guide for information about +# setting up your system, performing the build, and using/testing the build +# targets. { 'includes': [ @@ -15,6 +14,7 @@ ], # Get list of core source files. 'variables': { 'has_dual_key%': 'false', + 'embedded_cert%': '', }, 'targets': [ { @@ -86,9 +86,6 @@ '../third_party/jsmn', '../util/include', ], - 'defines': [ - 'CORE_UTIL_IMPLEMENTATION', - ], 'direct_dependent_settings': { 'include_dirs': [ '../core/include', @@ -104,6 +101,40 @@ ], 'includes': [ '../util/libcrypto_dependency.gypi' ], 'conditions': [ + ['client_info_source=="compiled"', { + 'conditions': [ + ['client_company_name=="" or " | " in client_company_name or "%" in client_company_name', { + 'sources': [ 'invalid_client_company_name.c' ], + }], + ['client_model_name=="" or " | " in client_model_name or "%" in client_model_name', { + 'sources': [ 'invalid_client_model_name.c' ], + }], + # year may be a number, so use __str__ to convert to string. Can't use + # str() since we can't use global functions. + ['client_model_year=="" or " | " in client_model_year.__str__() or "%" in client_model_year.__str__()', { + 'sources': [ 'invalid_client_model_year.c' ], + }], + ['client_product_name=="" or " | " in client_product_name or "%" in client_product_name', { + 'sources': [ 'invalid_client_product_name.c' ], + }], + ['client_device_name=="" or " | " in client_device_name or "%" in client_device_name', { + 'sources': [ 'invalid_client_device_name.c' ], + }], + ['client_arch_name=="" or " | " in client_arch_name or "%" in client_arch_name', { + 'sources': [ 'invalid_client_arch_name.c' ], + }], + ['client_platform=="" or " | " in client_platform or "%" in client_platform', { + 'sources': [ 'invalid_client_platform.c' ], + }], + ['client_form_factor=="" or " | " in client_form_factor or "%" in client_form_factor', { + 'sources': [ 'invalid_client_form_factor.c' ], + }], + ['client_version=="" or " | " in client_version or "%" in client_version', { + 'sources': [ 'invalid_client_version.c' ], + }], + ], + }], + ['privacy_crypto_impl=="boringssl" or privacy_crypto_impl=="openssl"', { 'sources': [ '../core/src/privacy_crypto_boringssl.cpp', @@ -173,7 +204,6 @@ ], 'defines': [ 'CDM_IMPLEMENTATION', - 'CORE_UTIL_IMPLEMENTATION', ], 'include_dirs': [ 'include', @@ -191,6 +221,60 @@ 'src/log.cpp', 'src/properties_ce.cpp', ], + 'conditions': [ + ['embedded_cert!=""', { + 'actions': [{ + 'action_name': 'generate_cert', + 'msvs_cygwin_shell': 0, + 'message': 'Generating cert.cc', + 'inputs': [ + 'create_cert_cc.py', + '<(embedded_cert)', + ], + 'outputs': [ + '<(INTERMEDIATE_DIR)/cert.cc', + ], + 'action': [ + 'python3', + '>@(_inputs)', + '>@(_outputs)', + ], + }], + 'sources': [ + '<(INTERMEDIATE_DIR)/cert.cc', + ], + 'defines': ['HAS_EMBEDDED_CERT'], + }], # embedded_cert!="" + + # This block defines preprocessor values that match the client + # information variables in platform properties.gypi. If you are writing + # your own build files to build CE CDM insted of using these GYP files + # and you are using compile-time client info, make sure to define these + # values, following the rules in the client info section in + # platform_properties.gypi. + ['client_info_source=="compiled"', { + 'defines': [ + 'CLIENT_COMPANY_NAME="<(client_company_name)"', + 'CLIENT_MODEL_NAME="<(client_model_name)"', + 'CLIENT_MODEL_YEAR="<(client_model_year)"', + 'CLIENT_PRODUCT_NAME="<(client_product_name)"', + 'CLIENT_DEVICE_NAME="<(client_device_name)"', + 'CLIENT_ARCH_NAME="<(client_arch_name)"', + 'CLIENT_PLATFORM="<(client_platform)"', + 'CLIENT_FORM_FACTOR="<(client_form_factor)"', + 'CLIENT_VERSION="<(client_version)"', + ], + }], # client_info_source=="compiled" + + ['client_info_source=="runtime"', { + 'defines': [ + 'RUNTIME_CLIENT_INFO', + ], + 'dependencies': [ + '<(read_client_info_path)', + ], + }], # client_info_source=="runtime" + ], # conditions }, # widevine_cdm_static target { 'toolsets' : [ 'target' ], diff --git a/cdm/cdm_reboot_tests.gyp b/cdm/cdm_reboot_tests.gyp index 6d980cc6..62dff0cb 100644 --- a/cdm/cdm_reboot_tests.gyp +++ b/cdm/cdm_reboot_tests.gyp @@ -16,8 +16,6 @@ 'util_dir': '../util', 'metrics_target': 'cdm.gyp:metrics_proto', 'device_files_target': 'cdm.gyp:device_files', - # The path to the test device cert source file. - 'device_cert_path%': 'test/device_cert.cpp', }, 'targets': [{ 'toolsets' : [ 'target' ], @@ -26,8 +24,6 @@ 'test/cdm_reboot_test_main.cpp', 'test/cdm_test_runner.cpp', 'test/create_test_file_system.cpp', - '<(device_cert_path)', - 'test/device_cert.h', 'test/test_host.cpp', 'test/test_host.h', '../core/test/config_test_env.cpp', @@ -66,10 +62,6 @@ '<(device_files_target)', ], 'defines': [ - # The methods in util/ are marked as dllimport but in this case are - # being loaded in the static library. So define this so Windows - # doesn't look in a DLL for the implementations. - 'CORE_UTIL_IMPLEMENTATION', 'UNIT_TEST', 'CDM_TESTS', ], @@ -104,11 +96,6 @@ }, { 'type': 'executable', }], - ['oemcrypto_lib=="ref"', { - 'dependencies': [ - 'oemcrypto_reference_implementation.gyp:oec_ref', - ], - }], # TODO(b/139814713): For testing and internal use only. ['oemcrypto_adapter_type=="static_v15"', { 'defines': [ @@ -137,7 +124,7 @@ }], ['oemcrypto_lib=="target"', { 'dependencies': [ - '<(oemcrypto_gyp_target)', + '<(oemcrypto_gyp_path)', ], }], ], diff --git a/cdm/cdm_unittests.gyp b/cdm/cdm_unittests.gyp index 2d3e453c..ac42c836 100644 --- a/cdm/cdm_unittests.gyp +++ b/cdm/cdm_unittests.gyp @@ -16,11 +16,8 @@ 'util_dir': '../util', 'metrics_target': 'cdm.gyp:metrics_proto', 'device_files_target': 'cdm.gyp:device_files', - # The path to the test device cert source file. - 'device_cert_path%': 'test/device_cert.cpp', # The path to the prebuilt CDM to test against. (iOS only) 'prebuilt_cdm_path%': '', - 'prebuilt_cdm_cert%': '', 'supports_dynamic_perf_test%': 0, }, 'targets': [{ @@ -30,8 +27,6 @@ 'test/cdm_test_main.cpp', 'test/cdm_test_runner.cpp', 'test/create_test_file_system.cpp', - '<(device_cert_path)', - 'test/device_cert.h', # The test host, which is required for all test suites on CE. 'test/test_host.cpp', 'test/test_host.h', @@ -52,12 +47,6 @@ '../third_party/googletest.gyp:gmock', '../third_party/googletest.gyp:gtest', ], - 'defines': [ - # The methods in util/ are marked as dllimport but in this case are - # being loaded in the static library. So define this so Windows - # doesn't look in a DLL for the implementations. - 'CORE_UTIL_IMPLEMENTATION', - ], 'msvs_settings': { 'VCLinkerTool': { # Additionally, since they are loaded locally, suppress these @@ -91,11 +80,6 @@ }, { 'type': 'executable', }], - ['oemcrypto_lib=="ref"', { - 'dependencies': [ - 'oemcrypto_reference_implementation.gyp:oec_ref', - ], - }], # TODO(b/139814713): For testing and internal use only. ['oemcrypto_adapter_type=="static_v15"', { 'defines': [ @@ -124,7 +108,7 @@ }], ['oemcrypto_lib=="target"', { 'dependencies': [ - '<(oemcrypto_gyp_target)', + '<(oemcrypto_gyp_path)', ], }], ], @@ -135,7 +119,6 @@ 'target_name': 'widevine_perf_test_dynamic', 'type': 'executable', 'sources': [ - '<(device_cert_path)', '../core/test/config_test_env.cpp', '../core/test/http_socket.cpp', '../core/test/license_request.cpp', @@ -199,27 +182,6 @@ 'conditions': [ ['supports_dynamic_perf_test', { 'targets': [{ - 'target_name': 'create_cert_cc', - 'type': 'none', - 'actions': [{ - 'action_name': 'gen_cert', - 'message': 'Generating device_cert.cc', - 'msvs_cygwin_shell': 0, - 'inputs': [ - 'create_cert_cc.py', - '<(prebuilt_cdm_cert)', - ], - 'outputs': [ - '<(INTERMEDIATE_DIR)/cert.cc', - ], - 'action': [ - 'python', - 'create_cert_cc.py', - '<(prebuilt_cdm_cert)', - '<(INTERMEDIATE_DIR)/cert.cc', - ], - }], - }, { 'target_name': 'widevine_perf_test_xctest', 'type': 'loadable_module', 'mac_xctest_bundle': '1', @@ -242,7 +204,6 @@ '<(prebuilt_cdm_path)', ], 'sources': [ - '<(INTERMEDIATE_DIR)/cert.cc', '../core/test/config_test_env.cpp', '../core/test/http_socket.cpp', '../core/test/license_request.cpp', @@ -266,7 +227,6 @@ 'dependencies': [ '../third_party/googletest.gyp:gmock', '../third_party/googletest.gyp:gtest', - 'create_cert_cc', 'dummy_app', ], }], diff --git a/cdm/create_cert_cc.py b/cdm/create_cert_cc.py index e587b60c..78e05987 100755 --- a/cdm/create_cert_cc.py +++ b/cdm/create_cert_cc.py @@ -23,8 +23,12 @@ if __name__ == '__main__': #include #include +namespace widevine {{ + extern const uint8_t k{options.variable}[]; const uint8_t k{options.variable}[] = {{ {dat} }}; extern const size_t k{options.variable}Size; -const size_t k{options.variable}Size = sizeof(k{options.variable});""") +const size_t k{options.variable}Size = sizeof(k{options.variable}); + +}} // namespace widevine""") diff --git a/cdm/include/cdm.h b/cdm/include/cdm.h index aa513d5b..fd8359ba 100644 --- a/cdm/include/cdm.h +++ b/cdm/include/cdm.h @@ -12,20 +12,28 @@ #include #include +// Uncomment this line when making static CDM builds. This will be edited by +// release scripts. +//#define CDM_STATIC 1 + // Define CDM_EXPORT to export functionality across shared library boundaries. -#if defined(_WIN32) -# if defined(CDM_IMPLEMENTATION) -# define CDM_EXPORT __declspec(dllexport) -# else -# define CDM_EXPORT __declspec(dllimport) -# endif // defined(CDM_IMPLEMENTATION) -#else // defined(_WIN32) -# if defined(CDM_IMPLEMENTATION) -# define CDM_EXPORT __attribute__((visibility("default"))) -# else -# define CDM_EXPORT -# endif -#endif // defined(_WIN32) +#if defined(CDM_STATIC) +# define CDM_EXPORT +#else +# if defined(_WIN32) +# if defined(CDM_IMPLEMENTATION) +# define CDM_EXPORT __declspec(dllexport) +# else +# define CDM_EXPORT __declspec(dllimport) +# endif // defined(CDM_IMPLEMENTATION) +# else // defined(_WIN32) +# if defined(CDM_IMPLEMENTATION) +# define CDM_EXPORT __attribute__((visibility("default"))) +# else +# define CDM_EXPORT +# endif +# endif // defined(_WIN32) +#endif // defined(CDM_STATIC) namespace widevine { diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index 5a8d8d58..ae89af8b 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -13,7 +13,7 @@ # define CDM_VERSION_MINOR 1 #endif #ifndef CDM_VERSION_PATCH -# define CDM_VERSION_PATCH 0 +# define CDM_VERSION_PATCH 1 #endif #ifndef CDM_VERSION_TAG # define CDM_VERSION_TAG "" diff --git a/cdm/include/compiler_detection.h b/cdm/include/compiler_detection.h index 43bcebb2..5ff3bb8e 100644 --- a/cdm/include/compiler_detection.h +++ b/cdm/include/compiler_detection.h @@ -5,7 +5,7 @@ #define WVCDM_CDM_COMPILER_DETECTION_H_ // This file makes some best-effort attempts to detect details about the -// compilation environment used by CacheBuildInfo. +// compilation environment used by SetClientInfo. #if defined(CDM_DISABLE_LOGGING) # define LOGGING_MESSAGE "Logging Disabled" diff --git a/cdm/include/properties_ce.h b/cdm/include/properties_ce.h index 83812f7a..bef101c9 100644 --- a/cdm/include/properties_ce.h +++ b/cdm/include/properties_ce.h @@ -19,6 +19,7 @@ class PropertiesCE { // If this is set to an empty string, (which is the default) then PropertiesCE // will report that there is no Sandbox ID in use. static void SetSandboxId(const std::string& sandbox_id); + static bool ClientInfoIsValid(); private: static void SetSecureOutputType(Cdm::SecureOutputType secure_output_type); diff --git a/cdm/include/read_client_info.h b/cdm/include/read_client_info.h new file mode 100644 index 00000000..169fef9f --- /dev/null +++ b/cdm/include/read_client_info.h @@ -0,0 +1,32 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CDM_READ_CLIENT_INFO_H_ +#define WVCDM_CDM_READ_CLIENT_INFO_H_ + +#include + +namespace widevine { + +// This function must be implemented by partners who wish to use run-time client +// information. (i.e. Those that set 'client_info_source' to 'runtime' in their +// platform's properties.) These partners must set 'read_client_info_path' to +// a GYP target that contains an implementation of this function. This function +// will be called by the CE CDM during Cdm::initialize() to retrieve the client +// information at runtime. +// +// This function should fill in each of the strings with the relevant piece of +// client information and return true. If for any reason the platform cannot +// fill in all the strings or an error occurs, the function should return false. +// +// For a definition of each string's meaning, see the client info property +// documentation in platform_properties.gypi. +bool ReadClientInformation(std::string* company_name, std::string* model_name, + std::string* model_year, std::string* product_name, + std::string* device_name, std::string* arch_name, + std::string* platform, std::string* form_factor, + std::string* version); + +} // namespace widevine + +#endif // WVCDM_CDM_READ_CLIENT_INFO_H_ diff --git a/cdm/platform_properties.gypi b/cdm/platform_properties.gypi index daa61ff0..3153f0ee 100644 --- a/cdm/platform_properties.gypi +++ b/cdm/platform_properties.gypi @@ -16,42 +16,71 @@ # achieve. { 'variables': { + # Choose whether the client information is compiled into the binary at + # compile-time or whether it is gathered by the CDM at runtime. The client + # information ends up as client identification in license requests. All + # values may be used by a license server proxy to drive business logic. + # Valid values are: + # + # 'compiled' - The client information is read from the platform properties + # and compiled into the CE CDM binary. The platform properties + # that define client info must be defined. + # + # 'runtime' - The client information is read at runtime using + # read_client_info.h. The property 'read_client_info_path' must + # also be defined, pointing to a GYP target that provides an + # implementation of read_client_info.h. + 'client_info_source%': 'compiled', + # The following variables define the client info that is baked into the CDM - # binary at build-time. These parameters end up as client identification in - # license requests. All values may be used by a license server proxy to - # drive business logic. You *must* set meaningful values for all of these - # variables as part of defining your platform. Values may not contain - # double quotes, percent signs, or the substring " | ". (a vertical pipe - # surrounded by spaces) + # binary at build-time. If 'client_info_source' is 'compiled'. You *must* + # set meaningful values for all of these variables as part of defining your + # platform. Values may not contain double quotes, percent signs, or the + # substring " | ". (a vertical pipe surrounded by spaces) If + # 'client_info_source' is 'runtime', these variables are ignored. # The name of the company who makes the client, e.g. "KubrickTech". # This should match the "Make" field in the Widevine Integration Console. - 'client_company_name%': '', + #'client_company_name%': '', + # The client's model name, e.g. "HAL 9000". # This should match the "Model" field in the Widevine Integration Console. - 'client_model_name%': '', + #'client_model_name%': '', + # The client's model year, e.g. "2001". # Can be used to distinguish different devices with the same model name. # This should match the "Year" field in the Widevine Integration Console. - 'client_model_year%': '', + #'client_model_year%': '', + # The name or codename of the product or application, e.g. "clarke". # This may be the same as "client_model_name". - 'client_product_name%': '', + #'client_product_name%': '', + # The name or codename of the client, e.g. "HAL". # This may be the same as "client_model_name" or "client_product_name". - 'client_device_name%': '', + #'client_device_name%': '', + # The CPU architecture of the client, e.g. "ARMv7". - 'client_arch_name%': '', + #'client_arch_name%': '', # The OS platform of the client, e.g. "Linux". # This should match the "Platform" field in the Widevine Integration # Console. - 'client_platform%': '', + #'client_platform%': '', + # The form factor of the client, e.g. "TV". # This should match the "Type" field in the Widevine Integration Console. - 'client_form_factor%': '', + #'client_form_factor%': '', + # The version number of the client that the CE CDM is integrated into. This # is the operating system or app version, not the CDM version. - 'client_version%': '', + #'client_version%': '', + + # If 'client_info_source' is 'runtime', this variable must be set to the + # path to a GYP target that provides an implementation of + # read_client_info.h. This implementation will be compiled into the CDM and + # used at runtime to get the client info. If 'client_info_source' is + # 'compiled', this variable is ignored. + 'read_client_info_path%': '', # Choose type of OEMCrypto library to compile in. Valid values are: @@ -60,11 +89,6 @@ # they are providing their own OEMCrypto library and the Widevine # CE CDM build system is not responsible for building anything. # - # 'ref' - The default value of 'ref' builds the OEMCrypto reference code, in - # order to allow the CE CDM to build without a vendor integration. - # This setting should *NEVER* be used in production systems and is - # for testing purposes only. - # # 'level3' - Partners who have received a Widevine-generated Level 3 library # should use 'level3' to indicate that the Widevine CE CDM build # system should include it in the build. @@ -72,13 +96,15 @@ # 'target' - If your vendor OEMCrypto is built using GYP and you would like # the Widevine CE CDM build system to build it as part of the CE # CDM build process, you can use 'target' and also set the - # variable 'oemcrypto_gyp_target' below. Most vendors will want - # to use 'vendor', above, instead. This setting exists primarily - # for Google's internal testing. - 'oemcrypto_lib%': 'ref', + # variable 'oemcrypto_gyp_path' below. Most vendors will want to + # use 'vendor', above, instead. This setting exists primarily for + # Google's internal testing. + #'oemcrypto_lib%': '', + # You only need to set this value if you set 'oemcrypto_lib' to 'target' # above. - 'oemcrypto_gyp_target%': '', + #'oemcrypto_gyp_path%': '', + # Choose the oemcrypto adapter type. Valid values are: # # 'static' - (default). Statically link oemcrypto into the tests. @@ -152,34 +178,16 @@ # # 2) protobuf_config == 'target' # Use an existing protobuf gyp target from your project. - # Specify the protobuf gyp file and target in protobuf_lib_target. - # Specify the protoc gyp file and target in protoc_host_target. + # Specify the protobuf gyp file and target in protobuf_lib_path. + # Specify the protoc gyp file and target in protoc_host_path. # Specify the path to protoc in protoc_bin. # # 3) protobuf_config == 'source' (default) # Build protobuf and protoc from the copy in third_party/protobuf. 'protobuf_config%': 'source', + 'protobuf_lib%': '', 'protoc_bin%': '', - 'protoc_lib%': '', - 'protoc_lib_target%': '', - 'protoc_host_target%': '', + 'protobuf_lib_path%': '', + 'protoc_host_path%': '', }, # variables - - # This block defines preprocessor values that match the client information - # variables above. If you are writing your own build files to build CE CDM - # insted of using these GYP files, make sure to define these values, following - # the rules in the client info section above. - 'target_defaults': { - 'defines': [ - 'CLIENT_COMPANY_NAME="<(client_company_name)"', - 'CLIENT_MODEL_NAME="<(client_model_name)"', - 'CLIENT_MODEL_YEAR="<(client_model_year)"', - 'CLIENT_PRODUCT_NAME="<(client_product_name)"', - 'CLIENT_DEVICE_NAME="<(client_device_name)"', - 'CLIENT_ARCH_NAME="<(client_arch_name)"', - 'CLIENT_PLATFORM="<(client_platform)"', - 'CLIENT_FORM_FACTOR="<(client_form_factor)"', - 'CLIENT_VERSION="<(client_version)"', - ], - }, } diff --git a/cdm/src/cdm.cpp b/cdm/src/cdm.cpp index 5bc19c13..3d27a0d6 100644 --- a/cdm/src/cdm.cpp +++ b/cdm/src/cdm.cpp @@ -36,6 +36,11 @@ namespace widevine { +#ifdef HAS_EMBEDDED_CERT +extern const uint8_t kDeviceCert[]; +extern const size_t kDeviceCertSize; +#endif + using namespace wvcdm; using namespace wvutil; @@ -45,42 +50,6 @@ constexpr char kNoSandboxId[] = ""; const int64_t kPolicyTimerDurationMilliseconds = 5000; void* const kPolicyTimerContext = nullptr; -#define STRINGIFY(PARAM) #PARAM - -bool isClientInfoValid(const char* tag, const char* value) { - constexpr char kForbiddenSeparator[] = " | "; - constexpr char kForbiddenPercent[] = "%"; - if (value[0] == '\0') { - LOGE("%s may not be empty, but it is.", tag); - return false; - } - if (strstr(value, kForbiddenSeparator) != nullptr) { - LOGE("%s may not contain \"%s\", but it's \"%s\"", tag, kForbiddenSeparator, - value); - return false; - } - if (strstr(value, kForbiddenPercent) != nullptr) { - LOGE("%s may not contain \"%s\", but it's \"%s\"", tag, kForbiddenPercent, - value); - return false; - } - return true; -} - -bool clientInfoIsValid() { - return isClientInfoValid(STRINGIFY(CLIENT_COMPANY_NAME), - CLIENT_COMPANY_NAME) && - isClientInfoValid(STRINGIFY(CLIENT_MODEL_NAME), CLIENT_MODEL_NAME) && - isClientInfoValid(STRINGIFY(CLIENT_MODEL_YEAR), CLIENT_MODEL_YEAR) && - isClientInfoValid(STRINGIFY(CLIENT_PRODUCT_NAME), - CLIENT_PRODUCT_NAME) && - isClientInfoValid(STRINGIFY(CLIENT_DEVICE_NAME), CLIENT_DEVICE_NAME) && - isClientInfoValid(STRINGIFY(CLIENT_ARCH_NAME), CLIENT_ARCH_NAME) && - isClientInfoValid(STRINGIFY(CLIENT_PLATFORM), CLIENT_PLATFORM) && - isClientInfoValid(STRINGIFY(CLIENT_FORM_FACTOR), CLIENT_FORM_FACTOR) && - isClientInfoValid(STRINGIFY(CLIENT_VERSION), CLIENT_VERSION); -} - struct HostType { Cdm::IStorage* storage; Cdm::IClock* clock; @@ -1752,8 +1721,6 @@ Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, return kTypeError; } - if (!clientInfoIsValid()) return kUnexpectedError; - if (!storage || !clock || !timer) { LOGE("All interfaces are required!"); return kTypeError; @@ -1762,6 +1729,8 @@ Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, PropertiesCE::SetSecureOutputType(secure_output_type); if (sandbox_id != kNoSandboxId) PropertiesCE::SetSandboxId(sandbox_id); Properties::Init(); + if (!PropertiesCE::ClientInfoIsValid()) return kUnexpectedError; + host.storage = storage; host.clock = clock; host.timer = timer; @@ -1779,7 +1748,7 @@ Cdm::Status Cdm::getClientInfo(ClientInfo* client_info) { return kTypeError; } - if (!clientInfoIsValid()) return kUnexpectedError; + if (!PropertiesCE::ClientInfoIsValid()) return kUnexpectedError; if (!Properties::GetCompanyName(&client_info->company_name)) { LOGE("Unable to get Company Name."); @@ -1912,6 +1881,27 @@ class FileImpl final : public File { const bool truncate_; }; +#ifdef HAS_EMBEDDED_CERT +class FileCertImpl final : public File { + public: + // This Read method only gives correct results for the first call. + ssize_t Read(char* buffer, size_t bytes) override { + if (!buffer) { + LOGW("File::Read: buffer is empty"); + return -1; + } + size_t to_copy = std::min(bytes, kDeviceCertSize); + memcpy(buffer, kDeviceCert, to_copy); + return to_copy; + } + + ssize_t Write(const char* buffer, size_t bytes) override { + LOGW("File::Write: device cert is read-only."); + return -1; + } +}; +#endif + class FileSystem::Impl { public: Impl(widevine::Cdm::IStorage* storage) : storage_(storage) { @@ -1931,6 +1921,12 @@ FileSystem::~FileSystem() {} std::unique_ptr FileSystem::Open(const std::string& file_path, int flags) { +#ifdef HAS_EMBEDDED_CERT + if (file_path == kLegacyCertificateFileName) { + return std::unique_ptr(new FileCertImpl); + } +#endif + if (!(flags & kCreate) && !impl_->storage_->exists(file_path)) { return nullptr; } @@ -1939,20 +1935,40 @@ std::unique_ptr FileSystem::Open(const std::string& file_path, } bool FileSystem::Exists(const std::string& file_path) { +#ifdef HAS_EMBEDDED_CERT + if (file_path == kLegacyCertificateFileName) return true; +#endif return !file_path.empty() && impl_->storage_->exists(file_path); } bool FileSystem::Remove(const std::string& file_path) { +#ifdef HAS_EMBEDDED_CERT + if (file_path == kLegacyCertificateFileName) { + LOGE("Cannot remove device cert when embedded."); + return false; + } +#endif return impl_->storage_->remove(file_path); } ssize_t FileSystem::FileSize(const std::string& file_path) { +#ifdef HAS_EMBEDDED_CERT + if (file_path == kLegacyCertificateFileName) { + return kDeviceCertSize; + } +#endif return impl_->storage_->size(file_path); } bool FileSystem::List(const std::string&, std::vector* file_names) { - return file_names && impl_->storage_->list(file_names); + const bool ret = file_names && impl_->storage_->list(file_names); +#ifdef HAS_EMBEDDED_CERT + if (ret) { + file_names->emplace_back(kLegacyCertificateFileName); + } +#endif + return ret; } } // namespace wvutil diff --git a/cdm/src/properties_ce.cpp b/cdm/src/properties_ce.cpp index 8a22b721..3c8f6f79 100644 --- a/cdm/src/properties_ce.cpp +++ b/cdm/src/properties_ce.cpp @@ -4,6 +4,7 @@ #include "properties_ce.h" #include +#include #include #include @@ -12,6 +13,8 @@ #include "compiler_detection.h" #include "log.h" #include "properties.h" +#include "read_client_info.h" +#include "string_format.h" // This anonymous namespace is shared between both the widevine namespace and // wvcdm namespace objects below. @@ -26,6 +29,98 @@ std::string sandbox_id_; widevine::Cdm::SecureOutputType secure_output_type_ = widevine::Cdm::kNoSecureOutput; +bool client_info_is_valid_; +std::string company_name_; +std::string model_name_; +std::string model_year_; +std::string product_name_; +std::string device_name_; +std::string arch_name_; +std::string build_info_; + +bool isClientInfoFieldValid(const char* tag, const char* value) { + constexpr char kForbiddenSeparator[] = " | "; + constexpr char kForbiddenPercent[] = "%"; + if (value[0] == '\0') { + LOGE("%s may not be empty, but it is.", tag); + return false; + } + if (strstr(value, kForbiddenSeparator) != nullptr) { + LOGE("%s may not contain \"%s\", but it's \"%s\"", tag, kForbiddenSeparator, + value); + return false; + } + if (strstr(value, kForbiddenPercent) != nullptr) { + LOGE("%s may not contain \"%s\", but it's \"%s\"", tag, kForbiddenPercent, + value); + return false; + } + return true; +} + +void SetClientInfo() { + std::string platform; + std::string form_factor; + std::string version; + +#if defined(RUNTIME_CLIENT_INFO) + if (!widevine::ReadClientInformation( + &company_name_, &model_name_, &model_year_, &product_name_, + &device_name_, &arch_name_, &platform, &form_factor, &version)) { + LOGE("ReadClientInformation failed."); + client_info_is_valid_ = false; + return; + } + + if (!(isClientInfoFieldValid("company_name", company_name_.c_str()) && + isClientInfoFieldValid("model_name", model_name_.c_str()) && + isClientInfoFieldValid("model_year", model_year_.c_str()) && + isClientInfoFieldValid("product_name", product_name_.c_str()) && + isClientInfoFieldValid("device_name", device_name_.c_str()) && + isClientInfoFieldValid("arch_name", arch_name_.c_str()) && + isClientInfoFieldValid("platform", platform.c_str()) && + isClientInfoFieldValid("form_factor", form_factor.c_str()) && + isClientInfoFieldValid("version", version.c_str()))) { + client_info_is_valid_ = false; + return; + } +#else + if (!(isClientInfoFieldValid("CLIENT_COMPANY_NAME", CLIENT_COMPANY_NAME) && + isClientInfoFieldValid("CLIENT_MODEL_NAME", CLIENT_MODEL_NAME) && + isClientInfoFieldValid("CLIENT_MODEL_YEAR", CLIENT_MODEL_YEAR) && + isClientInfoFieldValid("CLIENT_PRODUCT_NAME", CLIENT_PRODUCT_NAME) && + isClientInfoFieldValid("CLIENT_DEVICE_NAME", CLIENT_DEVICE_NAME) && + isClientInfoFieldValid("CLIENT_ARCH_NAME", CLIENT_ARCH_NAME) && + isClientInfoFieldValid("CLIENT_PLATFORM", CLIENT_PLATFORM) && + isClientInfoFieldValid("CLIENT_FORM_FACTOR", CLIENT_FORM_FACTOR) && + isClientInfoFieldValid("CLIENT_VERSION", CLIENT_VERSION))) { + client_info_is_valid_ = false; + return; + } + + company_name_ = CLIENT_COMPANY_NAME; + model_name_ = CLIENT_MODEL_NAME; + model_year_ = CLIENT_MODEL_YEAR; + product_name_ = CLIENT_PRODUCT_NAME; + device_name_ = CLIENT_DEVICE_NAME; + arch_name_ = CLIENT_ARCH_NAME; + platform = CLIENT_PLATFORM; + form_factor = CLIENT_FORM_FACTOR; + version = CLIENT_VERSION; +#endif + + if (!wvutil::FormatString( + &build_info_, "%s | %s | %s | %s | CE CDM %s | %s | %s | %s", + version.c_str(), platform.c_str(), form_factor.c_str(), + arch_name_.c_str(), CDM_VERSION, CPU_ARCH_MESSAGE, LOGGING_MESSAGE, + BUILD_FLAVOR_MESSAGE)) { + client_info_is_valid_ = false; + LOGE("Formatting the build info failed."); + } else { + client_info_is_valid_ = true; + } +} + bool GetValue(const char* source, std::string* output) { if (!source || !output) { return false; @@ -34,11 +129,6 @@ bool GetValue(const char* source, std::string* output) { return source[0] != '\0'; } -constexpr char kBuildInfo[] = CLIENT_VERSION - " | " CLIENT_PLATFORM " | " CLIENT_FORM_FACTOR " | " CLIENT_ARCH_NAME - " | CE CDM " CDM_VERSION " | " CPU_ARCH_MESSAGE " | " LOGGING_MESSAGE - " | " BUILD_FLAVOR_MESSAGE; - } // namespace namespace widevine { @@ -83,6 +173,9 @@ void PropertiesCE::SetSandboxId(const std::string& sandbox_id) { sandbox_id_ = sandbox_id; } +// static +bool PropertiesCE::ClientInfoIsValid() { return client_info_is_valid_; } + } // namespace widevine namespace wvcdm { @@ -97,42 +190,43 @@ void Properties::InitOnce() { device_files_is_a_real_filesystem_ = false; allow_restore_of_offline_licenses_with_release_ = true; delay_oem_crypto_termination_ = false; + SetClientInfo(); session_property_set_.reset(new CdmClientPropertySetMap()); } // static bool Properties::GetCompanyName(std::string* company_name) { - return GetValue(CLIENT_COMPANY_NAME, company_name); + return GetValue(company_name_.c_str(), company_name); } // static bool Properties::GetModelName(std::string* model_name) { - return GetValue(CLIENT_MODEL_NAME, model_name); + return GetValue(model_name_.c_str(), model_name); } // static bool Properties::GetModelYear(std::string* model_year) { - return GetValue(CLIENT_MODEL_YEAR, model_year); + return GetValue(model_year_.c_str(), model_year); } // static bool Properties::GetArchitectureName(std::string* arch_name) { - return GetValue(CLIENT_ARCH_NAME, arch_name); + return GetValue(arch_name_.c_str(), arch_name); } // static bool Properties::GetDeviceName(std::string* device_name) { - return GetValue(CLIENT_DEVICE_NAME, device_name); + return GetValue(device_name_.c_str(), device_name); } // static bool Properties::GetProductName(std::string* product_name) { - return GetValue(CLIENT_PRODUCT_NAME, product_name); + return GetValue(product_name_.c_str(), product_name); } // static bool Properties::GetBuildInfo(std::string* build_info) { - return GetValue(kBuildInfo, build_info); + return GetValue(build_info_.c_str(), build_info); } // static diff --git a/cdm/test/cdm_reboot_test_main.cpp b/cdm/test/cdm_reboot_test_main.cpp index de569e06..694d1204 100644 --- a/cdm/test/cdm_reboot_test_main.cpp +++ b/cdm/test/cdm_reboot_test_main.cpp @@ -19,7 +19,6 @@ #include "cdm.h" #include "cdm_test_runner.h" #include "clock.h" -#include "device_cert.h" #include "log.h" #include "reboot_test.h" #include "test_base.h" diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index 12be3626..ec0b9635 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -1306,6 +1306,12 @@ TEST_F(CdmTest, PerOriginLoadPersistent) { // Create another host to use its storage. This will simulate another // origin. TestHost other_host; + // EnsureProvisioned uses the global host, so set that temporarily to + // provision the new host (if needed). + TestHost* cur_host = g_host; + g_host = &other_host; + EnsureProvisioned(); + g_host = cur_host; // Create a new CDM that uses the new host and new storage. std::unique_ptr other_cdm(Cdm::create( @@ -2626,7 +2632,9 @@ TEST_F(CdmIndividualizationTest, RemoveProvisioning) { if (!CheckProvisioningSupport()) return; // Clear any existing certificates. - EXPECT_EQ(Cdm::kSuccess, cdm_->removeProvisioning()); + g_host->global_storage().remove(kOemCertificateFileName); + g_host->per_origin_storage().remove(kCertificateFileName); + g_host->per_origin_storage().remove(kLegacyCertificateFileName); if (wvoec::global_features.provisioning_method == OEMCrypto_BootCertificateChain) { @@ -2641,12 +2649,7 @@ TEST_F(CdmIndividualizationTest, RemoveProvisioning) { EXPECT_EQ(Cdm::kSuccess, cdm_->removeProvisioning()); - if (wvoec::global_features.provisioning_method == - OEMCrypto_BootCertificateChain) { - EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsOemCertProvisioning); - } else { - EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); - } + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); } TEST_F(CdmIndividualizationTest, NoCreateSessionWithoutProvisioning) { diff --git a/cdm/test/cdm_test_main.cpp b/cdm/test/cdm_test_main.cpp index 155dba18..b573b41a 100644 --- a/cdm/test/cdm_test_main.cpp +++ b/cdm/test/cdm_test_main.cpp @@ -12,7 +12,6 @@ #include "cdm.h" #include "cdm_test_runner.h" -#include "device_cert.h" #include "log.h" #include "test_base.h" #include "test_host.h" diff --git a/cdm/test/cdm_test_runner.cpp b/cdm/test/cdm_test_runner.cpp index a1b0c3bc..089f5dbc 100644 --- a/cdm/test/cdm_test_runner.cpp +++ b/cdm/test/cdm_test_runner.cpp @@ -11,7 +11,6 @@ #include #include "cdm.h" -#include "device_cert.h" #include "log.h" #include "test_base.h" #include "test_host.h" diff --git a/cdm/test/device_cert.cpp b/cdm/test/device_cert.cpp deleted file mode 100644 index f0bfce28..00000000 --- a/cdm/test/device_cert.cpp +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine License -// Agreement. - -#include "device_cert.h" - -const uint8_t kDeviceCert[] = { - 0x0A, 0x9A, 0x14, 0x08, 0x01, 0x10, 0x01, 0x1A, 0x93, 0x14, 0x0A, 0xED, - 0x09, 0x0A, 0xAE, 0x02, 0x08, 0x02, 0x12, 0x10, 0x7F, 0x6F, 0x31, 0x50, - 0xB3, 0x73, 0x26, 0x2C, 0xBD, 0xC3, 0xB2, 0xDA, 0xDE, 0x07, 0x5D, 0xBB, - 0x18, 0xF3, 0xFA, 0xB0, 0xA9, 0x05, 0x22, 0x8E, 0x02, 0x30, 0x82, 0x01, - 0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xBF, 0xAF, 0x84, 0xAC, 0x9C, 0x2D, - 0x57, 0x4C, 0xDE, 0x10, 0xB9, 0x94, 0xF0, 0x51, 0x4F, 0xBE, 0xA0, 0x0D, - 0xDC, 0x7E, 0x44, 0x38, 0x76, 0xD7, 0xB7, 0xBF, 0x0B, 0x7C, 0x1A, 0x7A, - 0x15, 0x9B, 0x66, 0xD1, 0xE4, 0x67, 0x83, 0xA8, 0xE5, 0xFA, 0x11, 0xE3, - 0xEA, 0xB8, 0x84, 0xD8, 0x32, 0x9C, 0x8B, 0xA7, 0xCC, 0x60, 0xBE, 0x68, - 0x7C, 0x13, 0x29, 0xBD, 0xF5, 0x8A, 0x6E, 0xFC, 0x7B, 0xAA, 0x4A, 0x72, - 0xB8, 0xB2, 0x52, 0xD8, 0xEA, 0x03, 0x87, 0x75, 0xE4, 0xE4, 0x52, 0xD9, - 0x83, 0x72, 0x7A, 0xA5, 0x9E, 0x0B, 0x27, 0xD8, 0xA1, 0x6A, 0x23, 0xF4, - 0x59, 0xAB, 0xE8, 0xD5, 0x5B, 0xDD, 0x64, 0xC8, 0xFD, 0x30, 0x28, 0x5B, - 0x0D, 0x0E, 0xCE, 0x7F, 0x1E, 0x4C, 0xC0, 0x36, 0x17, 0xC5, 0xC1, 0xD0, - 0x19, 0x0C, 0x5A, 0x71, 0xCA, 0x3B, 0xCA, 0x41, 0xD9, 0x67, 0x36, 0x0A, - 0x23, 0xA3, 0xDC, 0x9B, 0x86, 0x53, 0xF6, 0xAC, 0x1E, 0xD9, 0x41, 0x34, - 0x73, 0x04, 0xE1, 0x68, 0x1C, 0x38, 0xA8, 0x2A, 0xE8, 0x0D, 0x88, 0xF9, - 0xA9, 0xD4, 0x64, 0x4E, 0xFF, 0xF8, 0x94, 0x56, 0x99, 0xDB, 0xA2, 0x6C, - 0x15, 0xF2, 0xA1, 0x34, 0xF6, 0x51, 0xC0, 0xAB, 0x2A, 0x99, 0xB9, 0xF3, - 0x3D, 0x2D, 0x13, 0xCD, 0x8B, 0x6B, 0xAD, 0x82, 0x10, 0x73, 0x4E, 0x42, - 0x61, 0x78, 0x69, 0x2F, 0x19, 0x09, 0x06, 0x65, 0x0F, 0x7E, 0xA9, 0xB1, - 0xB3, 0xC2, 0x30, 0x86, 0xE9, 0x0B, 0x29, 0xC0, 0xB6, 0x08, 0x73, 0x7C, - 0x3B, 0xFA, 0x39, 0x5A, 0x5A, 0xB1, 0xD5, 0x6E, 0xFF, 0x85, 0x41, 0xF3, - 0xBF, 0xE9, 0xD9, 0x45, 0xE6, 0xDD, 0xB1, 0x35, 0x39, 0x56, 0xA5, 0xCE, - 0xBD, 0x6C, 0xC3, 0xFD, 0xEA, 0x47, 0xE9, 0x65, 0x58, 0x21, 0x62, 0xFF, - 0x00, 0x9A, 0xFE, 0xD3, 0x5B, 0x15, 0xC2, 0xC9, 0x64, 0x3F, 0x02, 0x03, - 0x01, 0x00, 0x01, 0x28, 0x99, 0x20, 0x12, 0x80, 0x02, 0x72, 0xAA, 0xE9, - 0xCD, 0xE2, 0xF8, 0xFC, 0x8B, 0x85, 0x52, 0xFE, 0x30, 0xAD, 0x49, 0x94, - 0x3F, 0x56, 0x5A, 0x60, 0xFD, 0xDD, 0x2D, 0x79, 0xDA, 0xB4, 0x5F, 0x3A, - 0x87, 0x10, 0x82, 0x5F, 0x0B, 0xBD, 0x20, 0x17, 0xF1, 0x59, 0xB0, 0x3F, - 0xDE, 0x24, 0x3A, 0x13, 0x43, 0xBC, 0xC6, 0xBC, 0xC0, 0xFD, 0xEF, 0xCF, - 0x3B, 0x19, 0xBF, 0x07, 0xD7, 0xD9, 0xF0, 0x1E, 0x1B, 0xDF, 0xF9, 0x35, - 0x82, 0x69, 0x3F, 0x3F, 0xF3, 0x71, 0x65, 0xB5, 0x86, 0xF8, 0xCB, 0x44, - 0x2E, 0xEE, 0x80, 0xE3, 0x53, 0x6D, 0xA8, 0x50, 0xDB, 0x87, 0x1A, 0x3B, - 0x49, 0x78, 0x6E, 0x61, 0x3D, 0x60, 0x61, 0xFD, 0xE8, 0xBE, 0x05, 0x77, - 0xFF, 0x2B, 0x0F, 0x0D, 0xE4, 0x53, 0x29, 0xB8, 0x55, 0x52, 0x2E, 0x7F, - 0xA3, 0x54, 0x0F, 0x66, 0x93, 0x4B, 0x17, 0xFB, 0xE1, 0x82, 0x33, 0x59, - 0x21, 0x9E, 0x44, 0x02, 0x27, 0x4E, 0xEC, 0x86, 0xC4, 0x6C, 0x5E, 0xF9, - 0xD0, 0x31, 0xDD, 0xFA, 0xB8, 0x5D, 0x05, 0x2E, 0xBC, 0xE9, 0x48, 0xB8, - 0xC2, 0xF4, 0x15, 0x2C, 0xDF, 0x51, 0x6B, 0x75, 0x62, 0x7D, 0x99, 0x67, - 0x25, 0x6F, 0x0D, 0xA9, 0x5A, 0x96, 0x01, 0x3D, 0x8F, 0x14, 0x13, 0x36, - 0x9D, 0x8D, 0x34, 0xF1, 0xEC, 0xFC, 0xFE, 0xEA, 0xBA, 0xAD, 0xFC, 0x7C, - 0xCE, 0xA3, 0x36, 0x80, 0xFF, 0xB8, 0x9D, 0x60, 0x70, 0x78, 0xE1, 0x42, - 0x7F, 0xD7, 0x99, 0x61, 0xBB, 0x51, 0x70, 0xC7, 0xE5, 0xA3, 0x74, 0xFA, - 0x18, 0x02, 0x4B, 0x2A, 0x5D, 0xC4, 0x40, 0x1E, 0x6A, 0x22, 0xA9, 0xC5, - 0x37, 0xF3, 0xD9, 0x7F, 0x8B, 0x1A, 0xD4, 0x8B, 0x3B, 0x00, 0x78, 0xB3, - 0xAA, 0x7C, 0x04, 0xBB, 0x96, 0x31, 0x33, 0x65, 0xFA, 0xF2, 0xEB, 0x87, - 0x2D, 0xF1, 0x8A, 0xFA, 0xEE, 0x7B, 0x1F, 0xD4, 0x67, 0xBD, 0x3F, 0x0F, - 0x61, 0x1A, 0xB6, 0x05, 0x0A, 0xB0, 0x02, 0x08, 0x01, 0x12, 0x10, 0x08, - 0x4D, 0x60, 0xBC, 0x65, 0x93, 0x7D, 0xB6, 0xAC, 0x7F, 0x99, 0x2B, 0x41, - 0xEC, 0xF5, 0xF2, 0x18, 0xA2, 0xD3, 0x8A, 0x8C, 0x05, 0x22, 0x8E, 0x02, - 0x30, 0x82, 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0x99, 0x1B, 0x35, - 0x7B, 0xB7, 0x14, 0xDE, 0xCA, 0xC1, 0xE7, 0x0E, 0xAA, 0x36, 0xD1, 0xD0, - 0x83, 0x4D, 0xF2, 0x4A, 0x3D, 0x10, 0x36, 0x9F, 0x90, 0xA2, 0x27, 0x34, - 0xA3, 0xAD, 0x4A, 0xEB, 0xCC, 0xFC, 0x24, 0x32, 0xBD, 0xD6, 0xDC, 0xD1, - 0xFF, 0xD7, 0x56, 0x99, 0xBA, 0x56, 0x08, 0xC4, 0x45, 0x79, 0x72, 0xD2, - 0x7C, 0x47, 0xAB, 0xCF, 0xF8, 0xC8, 0x8C, 0xD9, 0x90, 0x09, 0x38, 0x26, - 0x0B, 0x8C, 0x85, 0x79, 0x53, 0x27, 0xB2, 0xAE, 0xF3, 0xA1, 0xDB, 0x27, - 0xE8, 0xB9, 0x62, 0x03, 0x92, 0x84, 0xD6, 0x02, 0xC6, 0xA7, 0x4C, 0x6A, - 0x14, 0x3A, 0x67, 0xBE, 0x64, 0xA1, 0x6A, 0x1C, 0x8F, 0x44, 0xFA, 0xFA, - 0xDA, 0xC2, 0x42, 0xCE, 0xB6, 0xD8, 0xAD, 0xE9, 0xC4, 0xD4, 0x54, 0x2E, - 0xBD, 0x3E, 0xE9, 0xD2, 0xA0, 0xA7, 0x1E, 0xB7, 0xA4, 0xCC, 0xF9, 0x16, - 0xE0, 0x2F, 0x26, 0x79, 0x3B, 0x0C, 0x13, 0xA4, 0x92, 0x1C, 0x3E, 0xED, - 0x49, 0xC5, 0xC9, 0x6C, 0xD4, 0x55, 0x59, 0x8E, 0xDE, 0x04, 0x0C, 0xE4, - 0x72, 0xB0, 0x15, 0xA9, 0xAF, 0x3C, 0xDA, 0x82, 0xC4, 0x97, 0x92, 0xAA, - 0x7F, 0x6B, 0xBF, 0xBF, 0x46, 0x67, 0x29, 0x3B, 0x44, 0xEC, 0x74, 0x29, - 0xC3, 0x9A, 0x03, 0x89, 0x11, 0xCE, 0x1F, 0xE4, 0x62, 0xC3, 0x5E, 0x95, - 0xC2, 0x64, 0xD3, 0x63, 0x34, 0x3C, 0x3A, 0xDB, 0x94, 0x4F, 0xA2, 0xAA, - 0xBD, 0xBA, 0x0F, 0xB6, 0x1C, 0x40, 0x27, 0x97, 0x58, 0x5F, 0x3D, 0xFB, - 0x79, 0x6F, 0x6C, 0xE1, 0xDC, 0xF3, 0xB7, 0x9C, 0x98, 0x87, 0xBE, 0x98, - 0x14, 0x70, 0xEC, 0xBC, 0xB0, 0x48, 0xE5, 0x9E, 0x56, 0x4F, 0xC3, 0xAD, - 0x2C, 0x4B, 0x86, 0x0F, 0x70, 0xAA, 0x46, 0x8F, 0xB6, 0x6C, 0x4A, 0x28, - 0x47, 0xEE, 0xC7, 0x55, 0xAD, 0xE8, 0x9B, 0xD6, 0x22, 0x57, 0x40, 0xC9, - 0xA5, 0x02, 0x03, 0x01, 0x00, 0x01, 0x28, 0x99, 0x20, 0x30, 0x01, 0x12, - 0x80, 0x03, 0x21, 0x2D, 0xE1, 0x41, 0xC1, 0x30, 0x57, 0xC1, 0x98, 0x72, - 0x18, 0xDD, 0x7F, 0xBC, 0x2D, 0xC9, 0xF8, 0x4C, 0x82, 0xF9, 0x1F, 0x5B, - 0x16, 0x3D, 0x03, 0x70, 0xC7, 0x06, 0x93, 0x87, 0xC7, 0x0C, 0x90, 0xC3, - 0x06, 0x0F, 0x05, 0x04, 0x57, 0x3C, 0x6E, 0x0B, 0xD3, 0xAC, 0xB9, 0x84, - 0xFC, 0x3A, 0xB7, 0xB2, 0x21, 0xA7, 0x7D, 0x37, 0x33, 0x32, 0x4D, 0x67, - 0x0A, 0x9A, 0x5E, 0xEA, 0x64, 0x69, 0x1D, 0x56, 0x66, 0xA9, 0xB6, 0x7D, - 0xD3, 0x67, 0x80, 0x3C, 0xD5, 0x75, 0x3B, 0xD7, 0x5E, 0xA7, 0x23, 0x9B, - 0xEC, 0x0C, 0x00, 0x82, 0x97, 0x69, 0x57, 0x71, 0xCE, 0x20, 0x3C, 0x60, - 0x89, 0x8D, 0x4F, 0x26, 0x4A, 0xD5, 0x5D, 0x3D, 0x01, 0xC9, 0x6F, 0x66, - 0xD6, 0xFB, 0x6C, 0x53, 0xD1, 0x92, 0x8A, 0x60, 0xAD, 0x04, 0x0D, 0x7D, - 0x48, 0xE9, 0xC8, 0x3D, 0x68, 0x49, 0xC2, 0x48, 0xE4, 0x5C, 0x0F, 0x95, - 0xBB, 0xB8, 0xCD, 0x0B, 0x1F, 0xEE, 0xE4, 0x45, 0x8C, 0x5C, 0x60, 0xAB, - 0x0D, 0x52, 0xE1, 0xFB, 0x0F, 0x8E, 0xE9, 0x87, 0x66, 0xAF, 0x9C, 0xF9, - 0x33, 0x07, 0x57, 0x86, 0x57, 0xE8, 0x00, 0xF1, 0xA4, 0xED, 0x3F, 0x6A, - 0x00, 0xE8, 0x95, 0x1B, 0x21, 0x58, 0xF4, 0x5C, 0xFC, 0xC2, 0xF7, 0xE5, - 0xE5, 0xCC, 0x3D, 0x7F, 0x04, 0xEA, 0x2B, 0xE3, 0x4A, 0xA8, 0xDD, 0x49, - 0xEB, 0xD7, 0x8C, 0x61, 0x8C, 0x14, 0x04, 0xE3, 0x08, 0x25, 0x7E, 0x50, - 0x2A, 0xAC, 0x8D, 0xF0, 0x0D, 0x52, 0xA8, 0x98, 0x53, 0x01, 0xBF, 0xE5, - 0xE6, 0xE7, 0x84, 0xD7, 0x7B, 0x0B, 0xC5, 0x2C, 0x93, 0x5A, 0x8B, 0x18, - 0x3F, 0xB5, 0xAF, 0xED, 0x25, 0xA5, 0x85, 0x5A, 0x77, 0xFC, 0xF2, 0x71, - 0x5C, 0x90, 0x76, 0x11, 0x62, 0x4A, 0x58, 0xB0, 0x37, 0xD8, 0x19, 0x85, - 0x7B, 0xAB, 0x92, 0x6B, 0xEE, 0x4D, 0x56, 0x02, 0x77, 0x08, 0x9E, 0xFB, - 0x07, 0x0F, 0xB3, 0xF5, 0x38, 0x5D, 0x1D, 0xDE, 0x1E, 0x48, 0x6D, 0x8F, - 0x32, 0xD2, 0x53, 0xDE, 0x86, 0x92, 0xE3, 0x09, 0x8E, 0x35, 0xB3, 0x19, - 0x26, 0x47, 0xE7, 0x95, 0x44, 0xCF, 0x0F, 0xB2, 0x8B, 0x36, 0x21, 0x33, - 0xE9, 0x42, 0x97, 0x78, 0x1C, 0xF3, 0xF6, 0x89, 0x60, 0x53, 0x16, 0x67, - 0xEE, 0x74, 0xE7, 0x02, 0x6B, 0xD7, 0x21, 0x37, 0xEB, 0x1D, 0xDD, 0x05, - 0x78, 0x34, 0xCF, 0x1D, 0xAB, 0xE8, 0xD7, 0x06, 0x9F, 0x88, 0xAC, 0xB4, - 0xB7, 0xA4, 0x02, 0x47, 0x0F, 0x28, 0x01, 0x26, 0x4B, 0xB4, 0x9C, 0x3B, - 0xFE, 0xE2, 0x4F, 0xC6, 0x40, 0xA4, 0x63, 0x74, 0xD6, 0xC1, 0x0B, 0xC7, - 0xD2, 0xB9, 0x60, 0x79, 0x2D, 0x11, 0xDA, 0x50, 0x86, 0xAB, 0x8C, 0xCF, - 0xF4, 0x61, 0x80, 0xC2, 0x43, 0xC3, 0x95, 0xD2, 0x2F, 0x1A, 0x9E, 0x4F, - 0x6B, 0x02, 0x12, 0xA0, 0x0A, 0x92, 0x1C, 0xDD, 0x85, 0x4C, 0xEB, 0x5E, - 0x8E, 0xF0, 0x48, 0x26, 0x1F, 0xB8, 0x9B, 0xE1, 0x79, 0x96, 0xBF, 0x44, - 0x0D, 0x88, 0x67, 0xF6, 0x48, 0x65, 0xD8, 0xE5, 0x33, 0x93, 0x05, 0x07, - 0xAC, 0x0C, 0x0B, 0x60, 0xE5, 0xDB, 0x60, 0xD0, 0x3F, 0x42, 0x58, 0x8E, - 0x73, 0x9D, 0x91, 0x6F, 0xD7, 0xD3, 0x14, 0x97, 0x75, 0xCA, 0xDC, 0xF2, - 0xBA, 0x8C, 0x20, 0x13, 0xD8, 0xD2, 0x07, 0x74, 0xAA, 0xC2, 0x07, 0x89, - 0xFF, 0x95, 0x27, 0x52, 0x63, 0x94, 0xA5, 0xF1, 0xA9, 0x20, 0x61, 0xEE, - 0x56, 0x81, 0xA0, 0xDD, 0xF7, 0xF0, 0xAF, 0xD6, 0xF3, 0x83, 0x88, 0x9C, - 0xD2, 0x50, 0xE5, 0x43, 0xB8, 0xD6, 0xF0, 0x93, 0x7D, 0x11, 0x52, 0x82, - 0xFD, 0xD8, 0xCA, 0xF5, 0xC4, 0xD1, 0xE7, 0x78, 0x1C, 0xEC, 0x5C, 0x34, - 0x82, 0x8D, 0x82, 0x88, 0xB3, 0x84, 0xBF, 0xCD, 0x82, 0xA5, 0x8D, 0xE7, - 0xCA, 0xAD, 0x39, 0x9F, 0x98, 0x03, 0xD1, 0x05, 0xE0, 0xA3, 0x0A, 0x88, - 0xA6, 0x5E, 0xBA, 0x8D, 0xC3, 0xF7, 0xB9, 0x07, 0x75, 0x14, 0x54, 0xE8, - 0x3A, 0x96, 0x02, 0x94, 0xB5, 0x21, 0x7B, 0xDA, 0x30, 0xE8, 0xC6, 0x25, - 0x1F, 0xE2, 0x53, 0x1D, 0xDA, 0xE5, 0xE1, 0x2A, 0xDD, 0x74, 0xDE, 0x03, - 0xDA, 0x73, 0xCB, 0x1D, 0x1B, 0x1F, 0xCA, 0xFE, 0x23, 0x42, 0xFA, 0x47, - 0x1E, 0xB3, 0x1E, 0x43, 0x55, 0xB4, 0x1B, 0x67, 0x51, 0x3C, 0x20, 0x42, - 0x36, 0x57, 0xCA, 0x83, 0x5F, 0xD5, 0x1F, 0x7E, 0xC5, 0x80, 0x1E, 0x04, - 0xCE, 0xD0, 0xDA, 0x72, 0xAF, 0xEA, 0x91, 0x60, 0xC2, 0xC5, 0x4E, 0xA1, - 0x99, 0x55, 0x93, 0x09, 0xA8, 0x6F, 0xCE, 0x78, 0x71, 0xE1, 0x56, 0x01, - 0x17, 0x76, 0xA1, 0x37, 0x20, 0x21, 0x42, 0xE9, 0xF5, 0xB7, 0x77, 0xC9, - 0xF7, 0x18, 0x2A, 0xB4, 0x12, 0xD0, 0x0A, 0x60, 0x14, 0x02, 0x6A, 0xE5, - 0x63, 0x39, 0x5B, 0xD4, 0xB9, 0xBE, 0xA9, 0x4D, 0x28, 0x86, 0xA3, 0x87, - 0xB6, 0x91, 0xCF, 0x61, 0xEB, 0x83, 0x0F, 0xE7, 0x4E, 0x42, 0xC2, 0xDC, - 0xDC, 0xF3, 0x2F, 0x31, 0x78, 0xC7, 0x73, 0x5A, 0xB2, 0x5C, 0x69, 0xDC, - 0x89, 0x82, 0xDF, 0x2A, 0xEF, 0xC5, 0x36, 0x89, 0x8F, 0xF1, 0x1F, 0x2B, - 0x8B, 0x56, 0x28, 0x2A, 0x68, 0x34, 0xBE, 0x69, 0x58, 0x89, 0x09, 0x19, - 0x3D, 0xDE, 0xBC, 0xD3, 0x8F, 0x73, 0x30, 0xAA, 0xA5, 0x5F, 0xDF, 0x65, - 0x91, 0xC2, 0xC3, 0x70, 0xE5, 0xF8, 0x9D, 0x26, 0x47, 0xBA, 0xAB, 0x90, - 0x41, 0xD6, 0xE5, 0x84, 0xF7, 0xF8, 0x10, 0x0E, 0x09, 0x75, 0x4D, 0xCC, - 0x45, 0x8B, 0x9A, 0x58, 0xB3, 0xD4, 0x58, 0xC1, 0x7C, 0x8E, 0x78, 0x11, - 0xF9, 0x4A, 0xAA, 0xB5, 0xB5, 0x32, 0x04, 0x2E, 0x63, 0x78, 0x5A, 0x0C, - 0xD0, 0xB2, 0xE7, 0x1F, 0x16, 0xD0, 0xDB, 0x21, 0x3A, 0xC5, 0xEE, 0xBF, - 0x8F, 0xE8, 0x50, 0xF0, 0x93, 0x9B, 0xAF, 0x7C, 0x95, 0x5A, 0x9F, 0x61, - 0xB0, 0xFC, 0x98, 0xE8, 0x9D, 0x99, 0xB8, 0xEA, 0x86, 0xDD, 0x4B, 0x84, - 0x79, 0xD8, 0xEC, 0xCE, 0xE9, 0x66, 0xDC, 0x4F, 0xF8, 0x4E, 0x3D, 0x64, - 0x28, 0xCB, 0xE0, 0x3E, 0x28, 0x81, 0x11, 0xB6, 0x4D, 0xFE, 0x20, 0x2C, - 0xF5, 0xC4, 0xD0, 0x5E, 0x12, 0x05, 0x25, 0xEE, 0x6D, 0x81, 0xDE, 0x87, - 0x5F, 0x45, 0xC8, 0x1B, 0x68, 0x33, 0xE2, 0xC8, 0xF9, 0x13, 0x0F, 0xF4, - 0x6B, 0xFF, 0x75, 0x02, 0xF7, 0xAC, 0x25, 0xC3, 0xB7, 0x24, 0xC3, 0x88, - 0xEF, 0xD1, 0xDC, 0xAC, 0xF6, 0x57, 0x5B, 0x4E, 0xC4, 0x74, 0x21, 0x89, - 0x04, 0x44, 0xCA, 0x11, 0x74, 0xA7, 0xB2, 0x29, 0x43, 0x45, 0x2D, 0xBD, - 0x2E, 0xB0, 0xA7, 0x81, 0x70, 0x78, 0xD4, 0x7C, 0xE9, 0x22, 0x30, 0x48, - 0xF2, 0xF0, 0x4B, 0xD4, 0x9F, 0x49, 0x18, 0xF2, 0x82, 0xC9, 0x2C, 0xF0, - 0xCD, 0xB6, 0x94, 0x86, 0x24, 0x14, 0xBC, 0x3F, 0x07, 0x9A, 0x54, 0x76, - 0xDE, 0x53, 0x10, 0x2B, 0xAD, 0x54, 0x06, 0xD8, 0xFA, 0xE6, 0x27, 0x49, - 0x5E, 0xB7, 0x9C, 0x68, 0xAE, 0x3C, 0x1E, 0x66, 0x7E, 0x88, 0xAD, 0xF2, - 0x2E, 0xAE, 0x5E, 0xDE, 0xAC, 0x89, 0xCE, 0xED, 0x7D, 0x8C, 0x7D, 0x7E, - 0x68, 0x49, 0x7F, 0x41, 0x79, 0xF4, 0x34, 0x28, 0x1E, 0xCC, 0x83, 0xE1, - 0xE5, 0xCB, 0x29, 0xCA, 0x41, 0x36, 0xCD, 0x35, 0x84, 0x34, 0x63, 0x15, - 0xFF, 0x7E, 0x3E, 0xEE, 0x9A, 0x3B, 0x52, 0x16, 0x3B, 0xAF, 0x4B, 0xE7, - 0x84, 0xF2, 0x5B, 0x0A, 0x9B, 0x9F, 0x35, 0x4D, 0xCE, 0xA6, 0xBE, 0xB4, - 0xF5, 0xAE, 0xBF, 0xA8, 0x9E, 0xEF, 0xA8, 0x2F, 0x58, 0x22, 0x74, 0xC1, - 0x02, 0xB4, 0x34, 0x96, 0x57, 0xC4, 0x96, 0x88, 0x24, 0xA1, 0x81, 0xB5, - 0x33, 0xA6, 0x9E, 0x18, 0x43, 0x3F, 0x35, 0xD3, 0x66, 0x03, 0x53, 0x47, - 0xF7, 0x4B, 0x10, 0x61, 0x58, 0x06, 0xEE, 0x3B, 0xB1, 0x0F, 0x29, 0x4F, - 0xFD, 0x62, 0x5C, 0x4B, 0x23, 0x52, 0x4E, 0x16, 0x5A, 0x2E, 0x06, 0x4E, - 0xCB, 0x00, 0xCA, 0xD6, 0xF1, 0x06, 0x9D, 0x6D, 0x93, 0x72, 0x1D, 0x1D, - 0x2F, 0x09, 0xBC, 0x66, 0xC6, 0x87, 0xC6, 0xD4, 0xF3, 0xB7, 0xD6, 0xFA, - 0xFA, 0x67, 0xFD, 0xFF, 0x9A, 0x20, 0x1D, 0x24, 0xF5, 0xCD, 0xC3, 0xD6, - 0xEC, 0x28, 0xB9, 0x9A, 0x93, 0x67, 0xFF, 0x9E, 0x19, 0x96, 0x70, 0xB9, - 0x61, 0x26, 0x75, 0x58, 0x29, 0x52, 0x52, 0xDB, 0x75, 0xCC, 0x2E, 0xB2, - 0x2F, 0xEB, 0x34, 0x7D, 0x83, 0x82, 0x23, 0xA2, 0x61, 0x4F, 0xED, 0x19, - 0x64, 0xDC, 0xCD, 0x1C, 0x0E, 0xB8, 0x32, 0xF6, 0x5C, 0x68, 0x94, 0xA9, - 0xDF, 0xBC, 0x23, 0xB4, 0x95, 0x3D, 0x75, 0x18, 0x14, 0xBE, 0xE1, 0x67, - 0x44, 0xDD, 0x39, 0x7E, 0x10, 0x00, 0xFE, 0x90, 0xF4, 0x56, 0x15, 0x40, - 0xCF, 0x8E, 0xBC, 0x95, 0xF9, 0x53, 0xA9, 0xD3, 0xFA, 0xB3, 0xCD, 0xF2, - 0x82, 0xEC, 0xC5, 0xA3, 0xE2, 0xDD, 0xDD, 0xCD, 0xA3, 0xC7, 0x1D, 0x55, - 0xEA, 0x7B, 0x4E, 0x93, 0xE9, 0xBE, 0x15, 0x34, 0xF2, 0x77, 0xA8, 0x44, - 0x12, 0x22, 0x81, 0xF0, 0x82, 0xE0, 0x89, 0x71, 0xBE, 0xD3, 0x12, 0x04, - 0x27, 0xFC, 0xB1, 0xC6, 0x16, 0x7C, 0x3C, 0x6D, 0xA4, 0xD5, 0x6C, 0xBA, - 0x6B, 0x78, 0x03, 0xA9, 0x77, 0xCB, 0xD6, 0x69, 0xB4, 0x8F, 0xB6, 0xEA, - 0xE9, 0xAA, 0x54, 0xA6, 0x8A, 0x2B, 0x75, 0x8A, 0x54, 0x3C, 0xE0, 0x24, - 0xBD, 0x2D, 0xF4, 0xA7, 0x88, 0x84, 0x72, 0x2E, 0x3D, 0x49, 0x01, 0x6E, - 0xBB, 0x01, 0x4C, 0x0D, 0x17, 0xDB, 0x7F, 0xF0, 0xF7, 0x67, 0x0F, 0xA3, - 0x2F, 0x75, 0x39, 0x5D, 0x59, 0xEE, 0xD4, 0x0D, 0x12, 0x5A, 0xCA, 0x7A, - 0x2B, 0xD7, 0xBF, 0x5D, 0xB7, 0xF4, 0xE0, 0x6F, 0x18, 0x29, 0x75, 0x13, - 0xC7, 0x97, 0x2E, 0xEE, 0xF3, 0x28, 0x3B, 0x13, 0xC1, 0x34, 0x70, 0xA1, - 0x94, 0x00, 0x5C, 0xA5, 0x5C, 0x41, 0x81, 0xB5, 0xC9, 0x6D, 0xD4, 0xE7, - 0x1C, 0xC0, 0xC0, 0x4C, 0x23, 0x44, 0x9B, 0xDC, 0x24, 0xCB, 0x72, 0x9D, - 0xE7, 0xDD, 0x80, 0x34, 0xFD, 0x55, 0xE7, 0xA6, 0xD0, 0x78, 0x3C, 0x0D, - 0x00, 0x96, 0xC6, 0xBB, 0x33, 0x95, 0x35, 0xD8, 0x8D, 0x78, 0x8B, 0x64, - 0x76, 0x55, 0x2B, 0xE0, 0x0E, 0xD7, 0x59, 0xC0, 0x21, 0x41, 0xF5, 0x66, - 0x83, 0x56, 0xF4, 0x56, 0xB2, 0x07, 0x70, 0xE7, 0x71, 0x49, 0x42, 0xF5, - 0xED, 0x09, 0x30, 0x88, 0x6F, 0xC3, 0x0D, 0x21, 0xD1, 0xF8, 0xC3, 0xD7, - 0x2C, 0xCE, 0x40, 0x96, 0x5B, 0x30, 0xB3, 0x79, 0xF9, 0x0F, 0x0F, 0x07, - 0xA7, 0x73, 0xC1, 0x96, 0x0E, 0xD8, 0x3E, 0x2D, 0xF6, 0x04, 0xF4, 0xDE, - 0x50, 0x50, 0xC8, 0x4F, 0x8A, 0xE2, 0xDC, 0xED, 0x89, 0x5D, 0x2A, 0x07, - 0xFE, 0xD9, 0x6E, 0xDE, 0xED, 0x2F, 0xDE, 0xC5, 0xE9, 0x18, 0x48, 0xE1, - 0xAD, 0xC0, 0x27, 0xE7, 0xC5, 0xB2, 0xE8, 0xC8, 0x58, 0x2C, 0x52, 0x23, - 0x78, 0x1B, 0x9D, 0xC4, 0xA6, 0x01, 0x07, 0xE6, 0xBE, 0xE2, 0x26, 0x17, - 0x81, 0x5F, 0x49, 0x3A, 0xE6, 0xDB, 0x51, 0xC6, 0x06, 0xD7, 0x2C, 0x36, - 0x13, 0xB0, 0x2B, 0x04, 0xE4, 0x29, 0xDD, 0xFE, 0x6F, 0x9B, 0xA3, 0xFD, - 0x68, 0x79, 0x56, 0x24, 0x8A, 0x28, 0x6D, 0x69, 0xD6, 0x54, 0x5C, 0xD7, - 0xF3, 0x28, 0x45, 0x77, 0x78, 0x27, 0x23, 0x35, 0xCA, 0x68, 0xBB, 0x1A, - 0x47, 0x41, 0x51, 0x8E, 0x9A, 0x01, 0x49, 0xBD, 0xE8, 0xB6, 0x3A, 0x26, - 0xFE, 0x4A, 0x43, 0xE5, 0xF7, 0xCB, 0x3B, 0x21, 0xBC, 0xF3, 0xD4, 0xEF, - 0x38, 0x89, 0x06, 0x0A, 0xA2, 0x3D, 0xA1, 0xF6, 0xEC, 0x84, 0x0E, 0xF4, - 0xAA, 0x1E, 0xD3, 0x4B, 0x5E, 0x91, 0x6C, 0xD9, 0x83, 0x5F, 0xB3, 0x0B, - 0x47, 0x3E, 0x85, 0x18, 0xE9, 0x2D, 0x8A, 0x33, 0xAE, 0x34, 0xF6, 0x58, - 0x54, 0x11, 0xDC, 0xD8, 0xC4, 0x6D, 0x7C, 0xAA, 0x15, 0xCD, 0xCC, 0x17, - 0x7A, 0xF2, 0x77, 0x57, 0x5F, 0x40, 0xF8, 0x58, 0xF4, 0x96, 0xDF, 0x6E, - 0xFC, 0xB9, 0x70, 0x17, 0x08, 0x5B, 0x43, 0x29, 0x4D, 0xD4, 0xA7, 0x6C, - 0xDD, 0x8E, 0xC7, 0xFD, 0x8D, 0xE9, 0xE1, 0xBA, 0x7E, 0xFF, 0x39, 0xE5, - 0x74, 0x79, 0x5E, 0x9A, 0x29, 0x2F, 0xC3, 0x0D, 0xDD, 0x65, 0x1C, 0x2F, - 0xBB, 0x3C, 0x50, 0x8F, 0x5B, 0x47, 0x9A, 0x91, 0xEF, 0xC4, 0x0F, 0x7F, - 0xCD, 0x85, 0xBC, 0x7C, 0x41, 0x9C, 0x96, 0x59, 0x17, 0x9B, 0x95, 0x3E, - 0x26, 0x41, 0xDD, 0xE5, 0xD1, 0x72, 0x85, 0x10, 0x05, 0x9C, 0xBF, 0x88, - 0x26, 0x29, 0xBE, 0x96, 0x6C, 0x0C, 0x36, 0x76, 0xE8, 0x06, 0xC5, 0x04, - 0xD9, 0x06, 0xA8, 0x79, 0xF8, 0xEA, 0xF0, 0x60, 0xAF, 0x12, 0x20, 0xA8, - 0x98, 0xAD, 0x74, 0xB9, 0x6F, 0x94, 0xCA, 0xCB, 0xDD, 0x9C, 0x4E, 0x62, - 0xDF, 0xD1, 0x59, 0x3E, 0x58, 0xEE, 0x82, 0xB9, 0xAD, 0x9E, 0xB7, 0x45, - 0x2C, 0x1F, 0x8C, 0x15, 0x77, 0xCC, 0xD2}; - -const size_t kDeviceCertSize = sizeof(kDeviceCert); diff --git a/cdm/test/device_cert.h b/cdm/test/device_cert.h deleted file mode 100644 index f0b8c4ea..00000000 --- a/cdm/test/device_cert.h +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine License -// Agreement. - -#ifndef WVCDM_CDM_TEST_DEVICE_CERT_H_ -#define WVCDM_CDM_TEST_DEVICE_CERT_H_ - -#include -#include - -extern const uint8_t kDeviceCert[]; -extern const size_t kDeviceCertSize; - -#endif // WVCDM_CDM_TEST_DEVICE_CERT_H_ diff --git a/cdm/test/perf_test_xctest.mm b/cdm/test/perf_test_xctest.mm index 616d9626..333bd855 100644 --- a/cdm/test/perf_test_xctest.mm +++ b/cdm/test/perf_test_xctest.mm @@ -6,7 +6,6 @@ #import #include "cdm.h" -#include "device_cert.h" #include "perf_test.h" @interface GtestTests : XCTestCase @@ -21,8 +20,6 @@ int argc = 1; testing::InitGoogleTest(&argc, argv); - std::string cert{kDeviceCert, kDeviceCert + kDeviceCertSize}; - printf("Size 1: %zu, 2: %zu\n", kDeviceCertSize, cert.size()); XCTAssertEqual(widevine::PerfTestMain(&widevine::Cdm::initialize, &widevine::Cdm::create, cert), 0); } diff --git a/cdm/test/test_host.cpp b/cdm/test/test_host.cpp index 054b89ab..51ff0b62 100644 --- a/cdm/test/test_host.cpp +++ b/cdm/test/test_host.cpp @@ -8,7 +8,6 @@ #include #include "cdm_version.h" -#include "device_cert.h" #include "file_store.h" #include "log.h" @@ -16,20 +15,30 @@ using namespace widevine; namespace { -constexpr char kCertificateFilename[] = "cert.bin"; - // Some files are expected to go in global storage. All other files are expected // to go in per-origin storage. To help us enforce this in tests, this set // tracks the filenames that belong in global storage. TestHost::Storage will // reject attempts to access these files via per-origin storage or to access // files not in this list via global storage. const std::unordered_set kGlobalFilenames = { - "usgtable.bin", - "StoredUsageTime.dat", - "GenerationNumber.dat", - "persistent.dat", + "usgtable.bin", // CDM usage table data + "StoredUsageTime.dat", // Reference OEMCrypto usage table data + "GenerationNumber.dat", // Reference OEMCrypto master generation number + "persistent.dat", // Persistent data storage for certain TEE + // implementations + "keybox.dat", // Legacy file for storing keybox in non-secure storage. + // CDM data for OTA keybox renewal. "okp.bin", - "keybox.dat", + "debug_ignore_keybox_count.txt", + "debug_allow_test_keybox.txt", + // Widevine L3 data files. + "ay64.dat", + "ay64.dat2", + "ay64.dat3", + "ay64.dat4", + "ay64.dat5", + "ay64.dat6", + "l3_failure_file", wvutil::kOemCertificateFileName, }; @@ -109,10 +118,6 @@ TestHost::Storage::Storage(bool is_global) : is_global_(is_global) { Reset(); } void TestHost::Storage::Reset() { files_.clear(); - if (!is_global_) { - files_[kCertificateFilename] = - std::string((const char*)kDeviceCert, kDeviceCertSize); - } } bool TestHost::Storage::read(const std::string& name, std::string* data) { diff --git a/cdm/util.gypi b/cdm/util.gypi index 60350f0b..32c26e6e 100644 --- a/cdm/util.gypi +++ b/cdm/util.gypi @@ -4,21 +4,11 @@ { 'variables': { 'wvutil_sources': [ - '../util/include/advance_iv_ctr.h', - '../util/include/arraysize.h', - '../util/include/cdm_random.h', - '../util/include/clock.h', - '../util/include/disallow_copy_and_assign.h', - '../util/include/file_store.h', - '../util/include/log.h', - '../util/include/platform.h', - '../util/include/rw_lock.h', - '../util/include/string_conversions.h', - '../util/include/util_common.h', '../util/src/cdm_random.cpp', '../util/src/platform.cpp', '../util/src/rw_lock.cpp', '../util/src/string_conversions.cpp', + '../util/src/string_format.cpp', ], }, } diff --git a/cdm/util_unittests.gypi b/cdm/util_unittests.gypi index 45c4b8ac..b91559fc 100644 --- a/cdm/util_unittests.gypi +++ b/cdm/util_unittests.gypi @@ -7,6 +7,7 @@ '../util/test/cdm_random_unittest.cpp', # TODO(b/167126046): Needs test vectors # '../util/test/file_store_unittest.cpp', + '../util/test/string_format_unittest.cpp', ], 'include_dirs': [ '../util/include' diff --git a/factory_upload_tool/ce/README b/factory_upload_tool/ce/README new file mode 100644 index 00000000..68986f88 --- /dev/null +++ b/factory_upload_tool/ce/README @@ -0,0 +1,10 @@ +Instructions: +1. Write the main function or modify the example main function in example_main.cpp +2. To compile the example_main: + clang++ *.cpp ../common/src/*.cpp ../../util/src/string_conversions.cpp -ldl -rdynamic -I../../util/include -I../../oemcrypto/include -I../common/include -o example_main.out +3. Specify the location of liboemcrypto.so: + export LIBOEMCRYPTO_PATH=/path/to/liboemcrypto.so +4. Run the main program to extract the BCC and save it to a file. + ./example_main.out > csr.bin +5. Upload the csr.bin with the following command. cred.json is the OAuth 2.0 client credentials obtained via Google cloud platform. + python3 upload.py --credentials=cred.json --org-name={your organization name} --json-csr=csr.bin \ No newline at end of file diff --git a/factory_upload_tool/ce/example_main.cpp b/factory_upload_tool/ce/example_main.cpp new file mode 100644 index 00000000..62334f91 --- /dev/null +++ b/factory_upload_tool/ce/example_main.cpp @@ -0,0 +1,33 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include + +#include "log.h" +#include "wv_factory_extractor.h" + +int main() { + widevine::ClientInfo client_info; + client_info.company_name = ""; + client_info.arch_name = ""; + client_info.device_name = ""; + client_info.model_name = ""; + client_info.product_name = ""; + client_info.build_info = ""; + + auto extractor = widevine::WidevineFactoryExtractor::Create(client_info); + if (extractor == nullptr) { + LOGE("Failed to create WidevineFactoryExtractor"); + return 1; + } + + std::string request; + widevine::Status status = extractor->GenerateUploadRequest(request); + if (status != widevine::Status::kSuccess) { + LOGE("Fail to generate upload request: %d", status); + return 2; + } + std::cout << request << std::endl; + return 0; +} \ No newline at end of file diff --git a/factory_upload_tool/ce/log.cpp b/factory_upload_tool/ce/log.cpp new file mode 100644 index 00000000..dabb509b --- /dev/null +++ b/factory_upload_tool/ce/log.cpp @@ -0,0 +1,42 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// Log - implemented using stderr. + +#include "log.h" + +#include +#include +#include +#include + +namespace wvutil { + +LogPriority g_cutoff = CDM_LOG_INFO; + +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))) { + fprintf(stderr, "[FATAL:%s(%d)] Invalid log priority level: %d\n", file, + line, level); + return; + } + if (level > g_cutoff) return; + + fprintf(stderr, "[%s:%s(%d):%s] ", severities[level], file, line, function); + + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + putc('\n', stderr); + fflush(stderr); +} + +} // namespace wvutil diff --git a/factory_upload_tool/ce/properties_ce.cpp b/factory_upload_tool/ce/properties_ce.cpp new file mode 100644 index 00000000..c922485c --- /dev/null +++ b/factory_upload_tool/ce/properties_ce.cpp @@ -0,0 +1,87 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include "properties_ce.h" + +#include "log.h" +#include "properties.h" +#include "wv_factory_extractor.h" + +// This anonymous namespace is shared between both the widevine namespace and +// wvcdm namespace objects below. +namespace { +widevine::ClientInfo client_info_; + +bool GetValue(const std::string& source, std::string* output) { + if (!output) { + LOGE("Null output"); + return false; + } + *output = source; + return source.size() != 0; +} + +} // namespace + +namespace widevine { +// static +bool PropertiesCE::SetClientInfo(const ClientInfo& client_info) { + if (client_info.product_name.empty() || client_info.company_name.empty() || + client_info.device_name.empty() || client_info.model_name.empty() || + client_info.arch_name.empty() || client_info.build_info.empty()) { + return false; + } + client_info_ = client_info; + return true; +} + +// static +ClientInfo PropertiesCE::GetClientInfo() { return client_info_; } + +} // namespace widevine + +namespace wvcdm { +// static +bool Properties::GetCompanyName(std::string* company_name) { + return GetValue(client_info_.company_name, company_name); +} + +// static +bool Properties::GetModelName(std::string* model_name) { + return GetValue(client_info_.model_name, model_name); +} + +// static +bool Properties::GetArchitectureName(std::string* arch_name) { + return GetValue(client_info_.arch_name, arch_name); +} + +// static +bool Properties::GetDeviceName(std::string* device_name) { + return GetValue(client_info_.device_name, device_name); +} + +// static +bool Properties::GetProductName(std::string* product_name) { + return GetValue(client_info_.product_name, product_name); +} + +// static +bool Properties::GetBuildInfo(std::string* build_info) { + return GetValue(client_info_.build_info, build_info); +} + +// static +bool Properties::GetOEMCryptoPath(std::string* path) { + if (path == nullptr) return false; + // Using an environment variable is useful for testing. + const char* env_path = getenv("LIBOEMCRYPTO_PATH"); + if (env_path) { + *path = std::string(env_path); + } else { + *path = "liboemcrypto.so"; + } + return true; +} + +} // namespace wvcdm diff --git a/factory_upload_tool/ce/properties_ce.h b/factory_upload_tool/ce/properties_ce.h new file mode 100644 index 00000000..1ad29f4a --- /dev/null +++ b/factory_upload_tool/ce/properties_ce.h @@ -0,0 +1,19 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CDM_PROPERTIES_CE_H_ +#define WVCDM_CDM_PROPERTIES_CE_H_ + +#include "wv_factory_extractor.h" + +namespace widevine { + +class PropertiesCE { + public: + static bool SetClientInfo(const ClientInfo& client_info); + static ClientInfo GetClientInfo(); +}; + +} // namespace widevine + +#endif // WVCDM_CDM_PROPERTIES_CE_H_ diff --git a/factory_upload_tool/ce/wv_factory_extractor.cpp b/factory_upload_tool/ce/wv_factory_extractor.cpp new file mode 100644 index 00000000..c306cb0c --- /dev/null +++ b/factory_upload_tool/ce/wv_factory_extractor.cpp @@ -0,0 +1,83 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "wv_factory_extractor.h" + +#include "log.h" +#include "properties.h" +#include "properties_ce.h" +#include "string_conversions.h" + +namespace widevine { +namespace { +std::string StringMapToJson( + const std::map& string_map) { + std::string json = "{"; + for (const auto& value_pair : string_map) { + json.append("\"" + value_pair.first + "\": " + "\"" + value_pair.second + + "\","); + } + json.resize(json.size() - 1); // Remove the last comma. + json.append("}"); + return json; +} +} // namespace + +std::unique_ptr WidevineFactoryExtractor::Create( + const ClientInfo& client_info) { + if (!PropertiesCE::SetClientInfo(client_info)) { + LOGE("Invalid client info."); + return nullptr; + } + return std::unique_ptr( + new WidevineFactoryExtractor()); +} + +Status WidevineFactoryExtractor::GenerateUploadRequest(std::string& request) { + if (crypto_interface_ == nullptr) { + std::string oemcrypto_path; + if (!wvcdm::Properties::GetOEMCryptoPath(&oemcrypto_path)) { + LOGE("Failed to get OEMCrypto path."); + return kOEMCryptoError; + } + LOGI("OEMCrypto path is %s", oemcrypto_path.c_str()); + + crypto_interface_ = std::make_unique(); + if (!crypto_interface_->Init(oemcrypto_path)) { + LOGE("Failed to initialize OEMCrypto interface."); + crypto_interface_.reset(nullptr); + return kOEMCryptoError; + } + } + + std::vector bcc; + OEMCryptoResult result = crypto_interface_->GetBcc(bcc); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to get BCC."); + return kOEMCryptoError; + } + + std::string oemcrypto_build_info; + result = crypto_interface_->GetOEMCryptoBuildInfo(oemcrypto_build_info); + if (result != OEMCrypto_SUCCESS) { + LOGE("Failed to get oemcrypto build info."); + return kOEMCryptoError; + } + + std::map request_map; + request_map["company"] = PropertiesCE::GetClientInfo().company_name; + request_map["architecture"] = PropertiesCE::GetClientInfo().arch_name; + request_map["name"] = PropertiesCE::GetClientInfo().device_name; + request_map["model"] = PropertiesCE::GetClientInfo().model_name; + request_map["product"] = PropertiesCE::GetClientInfo().product_name; + request_map["build_info"] = PropertiesCE::GetClientInfo().build_info; + request_map["oemcrypto_build_info"] = oemcrypto_build_info; + request_map["bcc"] = wvutil::Base64Encode(bcc); + std::string request_json = StringMapToJson(request_map); + + request.assign(request_json.begin(), request_json.end()); + return kSuccess; +} + +} // namespace widevine diff --git a/factory_upload_tool/ce/wv_factory_extractor.h b/factory_upload_tool/ce/wv_factory_extractor.h new file mode 100644 index 00000000..8a43bad0 --- /dev/null +++ b/factory_upload_tool/ce/wv_factory_extractor.h @@ -0,0 +1,70 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WV_FACTORY_UPLOADER_H_ +#define WV_FACTORY_UPLOADER_H_ + +#include +#include +#include + +#include "WidevineOemcryptoInterface.h" + +namespace widevine { + +enum Status : int32_t { + kSuccess = 0, + kUnknownError = 1, + kOEMCryptoError = 2, +}; + +// Client information, provided by the application, independent of CDM +// instances. +// See Cdm::initialize(). +// These parameters end up as client identification in license requests. +// All fields may be used by a license server proxy to drive business logic. +// Some fields are required (indicated below), but please fill out as many +// as make sense for your application. +// No user-identifying information may be put in these fields! +struct ClientInfo { + // The name of the product or application, e.g. "TurtleTube" + // Required. + std::string product_name; + + // The name of the company who makes the device, e.g. "Kubrick, Inc." + // Required. + std::string company_name; + + // The name of the device, e.g. "HAL" + std::string device_name; + + // The device model, e.g. "HAL 9000" + // Required. + std::string model_name; + + // The architecture of the device, e.g. "x86-64" + std::string arch_name; + + // Information about the build of the browser, application, or platform into + // which the CDM is integrated, e.g. "v2.71828, 2038-01-19-03:14:07" + std::string build_info; +}; + +class WidevineFactoryExtractor { + public: + ~WidevineFactoryExtractor() = default; + + static std::unique_ptr Create( + const ClientInfo& client_info); + + Status GenerateUploadRequest(std::string& request); + + private: + WidevineFactoryExtractor() = default; + std::unique_ptr crypto_interface_; +}; + +} // namespace widevine + +#endif // WV_FACTORY_UPLOADER_H_ \ No newline at end of file diff --git a/factory_upload_tool/ce/wv_upload_tool.py b/factory_upload_tool/ce/wv_upload_tool.py new file mode 100644 index 00000000..021e8a8e --- /dev/null +++ b/factory_upload_tool/ce/wv_upload_tool.py @@ -0,0 +1,347 @@ +#!/usr/bin/env python3 +# Copyright 2022 Google LLC. All rights reserved. +"""Uploader tool for sending device keys to Widevine remote provisioning server. + +This tool consumes an input file containing device info, which includes the +device's public key, and batch uploads it to Google. Once uploaded, the device +may use Widevine provisioning 4 to request OEM certificates from Google. + +This tool is designed to be used with the widevine factory extraction tool. +Therefore, the JSON output from widevine factory extraction tool is the expected +input, with one JSON string per line of input. +""" + +import argparse +import http.server +import json +import os +import sys +import urllib.parse +import urllib.request +import uuid +import webbrowser + +DEFAULT_BASE = 'https://widevine.googleapis.com/v1/orgs/' +UPLOAD_PATH = '/uniqueDeviceInfo:batchUpload' +TOKEN_CACHE_FILE = os.path.join( + os.path.expanduser('~'), '.device_info_uploader.token') + +OAUTH_SERVICE_BASE = 'https://accounts.google.com/o/oauth2' +OAUTH_AUTHN_URL = OAUTH_SERVICE_BASE + '/auth' +OAUTH_TOKEN_URL = OAUTH_SERVICE_BASE + '/token' + + +class OAuthHTTPRequestHandler(http.server.BaseHTTPRequestHandler): + """HTTP Handler used to accept the oauth response when the user logs in.""" + + def do_GET(self): # pylint: disable=invalid-name + """Handles GET, extracting the authorization code in the query params.""" + print(f'GET path: {self.path}') + parsed_path = urllib.parse.urlparse(self.path) + params = dict(urllib.parse.parse_qsl(parsed_path.query)) + if 'error' in params: + error = params['error'] + self.respond(400, error, + f'Error received from the OAuth server: {error}.') + sys.exit(-1) + elif 'code' not in params: + self.respond(400, 'ERROR', + ('Response from OAuth server is missing the authorization ' + f'code. Full response: "{self.path}"')) + sys.exit(-1) + else: + self.respond(200, 'Success!', + 'Success! You may close this browser window.') + self.server.code = params['code'] + + def do_POST(self): # pylint: disable=invalid-name + print(f'POST path: {self.path}') + + def respond(self, code, title, message): + """Send a response to the HTTP client. + + Args: + code: The HTTP status code to send + title: The page title to display + message: The message to display to the user on the page + """ + if code != 200: + eprint(message) + self.send_response(code) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(('' + f' {title}' + f' ' + f'

{message}

' + f' ' + '').encode('utf-8')) + + +class LocalOAuthReceiver(http.server.HTTPServer): + """HTTP server that will wait for an OAuth authorization code.""" + + def __init__(self): + super(LocalOAuthReceiver, self).__init__(('127.0.0.1', 0), + OAuthHTTPRequestHandler) + self.code = None + + def port(self): + return self.socket.getsockname()[1] + + def wait_for_code(self): + print('Waiting for a response from the Google OAuth service.') + print('If you receive an error in your browser, interrupt this script.') + self.handle_request() + return self.code + + +def eprint(message): + print(message, file=sys.stderr) + + +def die(message): + eprint(message) + sys.exit(-1) + + +def parse_args(): + """Parse and return the command line args. + + Returns: + An argparse.Namespace object populated with the arguments. + """ + parser = argparse.ArgumentParser(description='Upload device info') + parser.add_argument( + '--json-csr', + nargs='+', + required=True, + help='list of files containing JSON output from rkp_factory_extraction_tool' + ) + parser.add_argument( + '--credentials', required=True, help='JSON credentials file') + + parser.add_argument( + '--endpoint', default=DEFAULT_BASE, help='destination server URL') + + parser.add_argument('--org-name', required=True, help='orgnization name') + + parser.add_argument( + '--cache-token', + action='store_true', + help='Use a locally cached a refresh token') + return parser.parse_args() + + +def parse_json_csrs(filename, batches): + """Parse the given file and insert it into batches. + + If the input is not a valid JSON CSR blob, exit the program. + + Args: + filename: The file that contains a JSON-formatted build and CSR + batches: Output dict containing a mapping from json dumped device metadata + to BCCs. + """ + line_count = 0 + for line in open(filename): + line_count = line_count + 1 + try: + obj = json.loads(line) + except json.JSONDecodeError as e: + die(f'{e.msg} {filename}:{line_count}, char {e.pos}') + + try: + bcc = {'boot_certificate_chain': obj['bcc']} + device_metadata = json.dumps({ + 'company': obj['company'], + 'architecture': obj['architecture'], + 'name': obj['name'], + 'model': obj['model'], + 'product': obj['product'], + 'build_info': obj['build_info'] + }) + except KeyError as e: + die(f'Invalid object at {filename}:{line_count}, missing {e}') + + if device_metadata not in batches: + batches[device_metadata] = [] + batches[device_metadata].append(bcc) + + +def format_request_body(args, device_metadata, bccs): + """Generate a formatted request buffer for the given build and CSRs.""" + request = { + 'parent': 'orgs/' + args.org_name, + 'request_id': uuid.uuid4().hex, + 'metadata': json.loads(device_metadata), + 'device_info': bccs, + } + return json.dumps(request).encode('utf-8') + + +def load_refresh_token(): + if not os.path.exists(TOKEN_CACHE_FILE): + return None + with open(TOKEN_CACHE_FILE) as f: + return f.readline() + + +def store_refresh_token(refresh_token): + with open(TOKEN_CACHE_FILE, 'w') as f: + f.write(refresh_token) + + +def fetch_access_token(creds, cache_token=False, code=None, redirect_uri=None): + """Fetch an oauth2 access token. + + If a code is passed, then it is used to get the token. If code + is None, then look for a persisted refresh token and use that to + get the access token instead. + + Args: + creds: The OAuth client credentials, including client secret and id. + cache_token: If True, then the refresh token is cached on disk so that the + user does not have to reauthenticate when the script is used again. + code: The OAuth authorization code, returned by Google's OAuth service. + redirect_uri: If an authorization code is supplied, then the redirect_uri + used to fetch the code must be passed here. + + Returns: + A base64-encode OAuth access token, suitable for including in a request. + """ + request = urllib.request.Request(OAUTH_TOKEN_URL) + request.add_header('Content-Type', 'application/x-www-form-urlencoded') + body = 'client_id=' + creds['client_id'] + body += '&client_secret=' + creds['client_secret'] + + if code is not None: + if redirect_uri is None: + raise ValueError('"code" was supplied, but "redirect_uri" is None') + body += '&grant_type=authorization_code' + body += '&code=' + code + body += '&redirect_uri=' + redirect_uri + else: + refresh_token = load_refresh_token() + if refresh_token is None: + return None + body += '&grant_type=refresh_token' + body += '&refresh_token=' + refresh_token + + try: + response = urllib.request.urlopen(request, body.encode('utf-8')) + parsed_response = json.load(response) + if cache_token: + store_refresh_token(parsed_response['refresh_token']) + return parsed_response['access_token'] + except urllib.error.HTTPError as e: + # Catch bogus/expired refresh tokens, but bubble up errors when + # an authorization code is used. + if code is None: + return None + die(f'Failed to receive access token: {e.code} {e.reason}') + + +def load_and_validate_creds(credfile): + """Loads the credentials from the given file and validates them. + + Args: + credfile: the name of the file containing the client credentials + + Returns: + A map containing the credentials for connecting to the APE backend. + """ + credmap = json.load(open(credfile)) + + not_local_app_creds_error = ( + 'ERROR: Invalid credential file.\n' + ' The given credentials do not appear to be for a locally installed\n' + ' application. Please navigate to the credentials dashboard and\n' + ' ensure that the "Type" of your client is "Desktop":\n' + ' https://console.cloud.google.com/apis/credentials') + + if 'installed' not in credmap: + die(not_local_app_creds_error) + + creds = credmap['installed'] + + expected_keys = set(['client_id', 'client_secret', 'redirect_uris']) + if not expected_keys.issubset(creds.keys()): + die(('ERROR: Invalid credential file.\n' + ' The given credentials do not appear to be valid. Please\n' + ' re-download the client credentials file from the dashboard:\n' + ' https://console.cloud.google.com/apis/credentials')) + + if 'http://localhost' not in creds['redirect_uris']: + die(not_local_app_creds_error) + + return creds + + +def authenticate_and_fetch_token(args): + """Authenticate the user and fetch an OAUTH2 access token.""" + creds = load_and_validate_creds(args.credentials) + + access_type = 'online' + if args.cache_token: + token = fetch_access_token(creds) + if token is not None: + return token + access_type = 'offline' + + httpd = LocalOAuthReceiver() + redirect_uri = f'http://127.0.0.1:{httpd.port()}' + url = ( + OAUTH_AUTHN_URL + '?response_type=code' + '&client_id=' + + creds['client_id'] + '&redirect_uri=' + redirect_uri + + '&scope=https://www.googleapis.com/auth/widevine/frontend' + + '&access_type=' + access_type + '&prompt=select_account') + print('Opening your web browser to authenticate...') + if not webbrowser.open(url, new=1, autoraise=True): + print('Error opening the browser. Please open this link in a browser') + print(f'that is running on this same system:\n {url}\n') + code = httpd.wait_for_code() + return fetch_access_token(creds, args.cache_token, code, redirect_uri) + + +def upload_batch(args, device_metadata, bccs): + """Batch upload all the CSRs associated build device_metadata. + + Args: + args: The parsed command-line arguments + device_metadata: The build for which we're uploading CSRs + bccs: a list of BCCs to be uploaded for the given build + """ + print("Uploading {} bcc(s) for build '{}'".format(len(bccs), device_metadata)) + body = format_request_body(args, device_metadata, bccs) + print(body) + print(args.endpoint + args.org_name + UPLOAD_PATH) + request = urllib.request.Request(args.endpoint + args.org_name + UPLOAD_PATH) + request.add_header('Content-Type', 'application/json') + request.add_header('X-GFE-SSL', 'yes') + request.add_header('Authorization', + 'Bearer ' + authenticate_and_fetch_token(args)) + try: + response = urllib.request.urlopen(request, body) + except urllib.error.HTTPError as e: + eprint(f'Error uploading bccs. {e}') + for line in e: + eprint(line.decode('utf-8').rstrip()) + sys.exit(1) + + while chunk := response.read(1024): + print(chunk.decode('utf-8')) + + +def main(): + args = parse_args() + batches = {} + for filename in args.json_csr: + parse_json_csrs(filename, batches) + + for device_metadata, bccs in batches.items(): + upload_batch(args, device_metadata, bccs) + + +if __name__ == '__main__': + main() diff --git a/factory_upload_tool/common/README b/factory_upload_tool/common/README new file mode 100644 index 00000000..9cb9e1ed --- /dev/null +++ b/factory_upload_tool/common/README @@ -0,0 +1 @@ +This folder contains files neededfor both ../android and ../ce. diff --git a/factory_upload_tool/common/include/WidevineOemcryptoInterface.h b/factory_upload_tool/common/include/WidevineOemcryptoInterface.h new file mode 100644 index 00000000..be355532 --- /dev/null +++ b/factory_upload_tool/common/include/WidevineOemcryptoInterface.h @@ -0,0 +1,52 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WIDEVINE_OEMCRYPTO_INTERFACE_H_ +#define WIDEVINE_OEMCRYPTO_INTERFACE_H_ + +#include +#include +#include + +#include "OEMCryptoCENC.h" + +namespace widevine { + +class OEMCryptoInterface { + public: + OEMCryptoInterface() = default; + OEMCryptoInterface(const OEMCryptoInterface&) = delete; + OEMCryptoInterface& operator=(const OEMCryptoInterface&) = delete; + virtual ~OEMCryptoInterface(); + + // Initializes this interface by providing path to the OEMCrypto library. + bool Init(const std::string& oemcrypto_path); + + // Retrieves the boot certificate chain from OEMCrypto implementation. + OEMCryptoResult GetBcc(std::vector& bcc); + + // Retrieves the build information of the OEMCrypto library from OEMCrypto + // implementation. + OEMCryptoResult GetOEMCryptoBuildInfo(std::string& build_info); + + private: + typedef OEMCryptoResult (*Initialize_t)(); + typedef OEMCryptoResult (*Terminate_t)(); + typedef OEMCryptoResult (*GetBootCertificateChain_t)( + uint8_t* bcc, size_t* bcc_size, uint8_t* additional_signature, + size_t* additional_signature_size); + typedef OEMCryptoResult (*BuildInformation_t)(char* buffer, + size_t* buffer_length); + + Initialize_t Initialize = nullptr; + Terminate_t Terminate = nullptr; + GetBootCertificateChain_t GetBootCertificateChain = nullptr; + BuildInformation_t BuildInformation = nullptr; + + void* handle_ = nullptr; +}; + +} // namespace widevine + +#endif // WIDEVINE_OEMCRYPTO_INTERFACE_H_ \ No newline at end of file diff --git a/factory_upload_tool/common/include/properties.h b/factory_upload_tool/common/include/properties.h new file mode 100644 index 00000000..d61394f8 --- /dev/null +++ b/factory_upload_tool/common/include/properties.h @@ -0,0 +1,34 @@ +// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_CORE_PROPERTIES_H_ +#define WVCDM_CORE_PROPERTIES_H_ + +#include +#include +#include +#include + +#include "disallow_copy_and_assign.h" + +namespace wvcdm { + +// This class gives device information/meta data. +class Properties { + public: + static bool GetCompanyName(std::string* company_name); + static bool GetModelName(std::string* model_name); + static bool GetArchitectureName(std::string* arch_name); + static bool GetDeviceName(std::string* device_name); + static bool GetProductName(std::string* product_name); + static bool GetBuildInfo(std::string* build_info); + static bool GetOEMCryptoPath(std::string* library_name); + + private: + CORE_DISALLOW_COPY_AND_ASSIGN(Properties); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_PROPERTIES_H_ diff --git a/factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp b/factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp new file mode 100644 index 00000000..dee299c6 --- /dev/null +++ b/factory_upload_tool/common/src/WidevineOemcryptoInterface.cpp @@ -0,0 +1,143 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "WidevineOemcryptoInterface.h" + +#include + +#include "OEMCryptoCENC.h" +#include "clock.h" +#include "file_store.h" +#include "log.h" + +// These macros lookup the obfuscated name used for OEMCrypto. +#define QUOTE_DEFINE(A) #A +#define QUOTE(A) QUOTE_DEFINE(A) +#define LOOKUP(handle, name) dlsym(handle, QUOTE(name)) +#define LOAD_SYM(name) \ + name = reinterpret_cast(LOOKUP(handle_, OEMCrypto_##name)); \ + if (name == nullptr) { \ + LOGE("%s", dlerror()); \ + return false; \ + } + +// These are implementations required by OEMCrypto Reference Implementation +// and/or the Testbed, but not needed in this package. +namespace wvutil { +int64_t Clock::GetCurrentTime() { return 0; } + +class FileImpl final : public File { + public: + FileImpl() {} + ssize_t Read(char*, size_t) override { return 0; } + ssize_t Write(const char*, size_t) override { return 0; } +}; + +class FileSystem::Impl { + public: + Impl() {} +}; + +FileSystem::FileSystem() {} +FileSystem::FileSystem(const std::string&, void*) {} +FileSystem::~FileSystem() {} +std::unique_ptr FileSystem::Open(const std::string&, int) { + return std::unique_ptr(new FileImpl()); +} +bool FileSystem::Exists(const std::string&) { return false; } +bool FileSystem::Remove(const std::string&) { return false; } +ssize_t FileSystem::FileSize(const std::string&) { return false; } +bool FileSystem::List(const std::string&, std::vector*) { + return false; +} + +} // namespace wvutil + +namespace widevine { + +OEMCryptoInterface::~OEMCryptoInterface() { + if (Terminate != nullptr) { + Terminate(); + } + if (handle_ != nullptr) { + dlclose(handle_); + } +} + +bool OEMCryptoInterface::Init(const std::string& oemcrypto_path) { + dlerror(); + handle_ = dlopen(oemcrypto_path.c_str(), RTLD_LAZY | RTLD_GLOBAL); + if (handle_ == nullptr) { + LOGE("Can't open OEMCrypto library: %s", dlerror()); + return false; + } + LOGI("OEMCrypto library opened."); + + LOAD_SYM(Initialize); + LOAD_SYM(Terminate); + LOAD_SYM(GetBootCertificateChain); + LOAD_SYM(BuildInformation); + + OEMCryptoResult status = Initialize(); + if (status != OEMCrypto_SUCCESS) { + LOGE("OEMCrypto Initialize failed: %d", status); + return false; + } + return true; +} + +OEMCryptoResult OEMCryptoInterface::GetBcc(std::vector& bcc) { + if (handle_ == nullptr) { + return OEMCrypto_ERROR_INIT_FAILED; + } + + bcc.resize(0); + size_t bcc_size = 0; + + std::vector additional_signature; // It should be empty. + size_t additional_signature_size = 0; + OEMCryptoResult result = GetBootCertificateChain(bcc.data(), &bcc_size, + additional_signature.data(), + &additional_signature_size); + LOGI("GetBootCertificateChain first attempt result %d", result); + if (additional_signature_size != 0) { + LOGW( + "The additional_signature_size required by OEMCrypto is %zu, while it " + "is expected to be zero.", + additional_signature_size); + } + + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + bcc.resize(bcc_size); + additional_signature.resize(additional_signature_size); + result = GetBootCertificateChain(bcc.data(), &bcc_size, + additional_signature.data(), + &additional_signature_size); + LOGI("GetBootCertificateChain second attempt result %d", result); + } + + return result; +} + +OEMCryptoResult OEMCryptoInterface::GetOEMCryptoBuildInfo( + std::string& build_info) { + if (handle_ == nullptr) { + return OEMCrypto_ERROR_INIT_FAILED; + } + + build_info.resize(0); + size_t build_info_size = 0; + + OEMCryptoResult result = BuildInformation(&build_info[0], &build_info_size); + LOGI("BuildInformation first attempt result %d", result); + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + build_info.resize(build_info_size); + result = BuildInformation(&build_info[0], &build_info_size); + LOGI("BuildInformation second attempt result %d", result); + } + + return result; +} + +} // namespace widevine diff --git a/oemcrypto/include/OEMCryptoCENCCommon.h b/oemcrypto/include/OEMCryptoCENCCommon.h index ce51b8d0..da011694 100644 --- a/oemcrypto/include/OEMCryptoCENCCommon.h +++ b/oemcrypto/include/OEMCryptoCENCCommon.h @@ -1,245 +1 @@ -// Copyright 2019 Google LLC. All rights reserved. This file and proprietary -// source code may only be used and distributed under the Widevine -// License Agreement. - -/********************************************************************* - * OEMCryptoCENCCommon.h - * - * Common structures and error codes between WV servers and OEMCrypto. - * - *********************************************************************/ - -#ifndef WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ -#define WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/// @addtogroup common_types -/// @{ - -/* clang-format off */ -/** Error and result codes returned by OEMCrypto functions. */ -typedef enum OEMCryptoResult { - OEMCrypto_SUCCESS = 0, - OEMCrypto_ERROR_INIT_FAILED = 1, - OEMCrypto_ERROR_TERMINATE_FAILED = 2, - OEMCrypto_ERROR_OPEN_FAILURE = 3, - OEMCrypto_ERROR_CLOSE_FAILURE = 4, - OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, /* deprecated */ - OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, /* deprecated */ - OEMCrypto_ERROR_SHORT_BUFFER = 7, - OEMCrypto_ERROR_NO_DEVICE_KEY = 8, /* no keybox device key. */ - OEMCrypto_ERROR_NO_ASSET_KEY = 9, - OEMCrypto_ERROR_KEYBOX_INVALID = 10, - OEMCrypto_ERROR_NO_KEYDATA = 11, - OEMCrypto_ERROR_NO_CW = 12, - OEMCrypto_ERROR_DECRYPT_FAILED = 13, - OEMCrypto_ERROR_WRITE_KEYBOX = 14, - OEMCrypto_ERROR_WRAP_KEYBOX = 15, - OEMCrypto_ERROR_BAD_MAGIC = 16, - OEMCrypto_ERROR_BAD_CRC = 17, - OEMCrypto_ERROR_NO_DEVICEID = 18, - OEMCrypto_ERROR_RNG_FAILED = 19, - OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20, - OEMCrypto_ERROR_SETUP = 21, - OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22, - OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23, - OEMCrypto_ERROR_INVALID_SESSION = 24, - OEMCrypto_ERROR_NOT_IMPLEMENTED = 25, - OEMCrypto_ERROR_NO_CONTENT_KEY = 26, - OEMCrypto_ERROR_CONTROL_INVALID = 27, - OEMCrypto_ERROR_UNKNOWN_FAILURE = 28, - OEMCrypto_ERROR_INVALID_CONTEXT = 29, - OEMCrypto_ERROR_SIGNATURE_FAILURE = 30, - OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31, - OEMCrypto_ERROR_INVALID_NONCE = 32, - OEMCrypto_ERROR_TOO_MANY_KEYS = 33, - OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34, - OEMCrypto_ERROR_INVALID_RSA_KEY = 35, /* deprecated */ - OEMCrypto_ERROR_KEY_EXPIRED = 36, - OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37, - OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38, - OEMCrypto_ERROR_BUFFER_TOO_LARGE = 39, - OEMCrypto_WARNING_GENERATION_SKEW = 40, /* Warning, not error. */ - OEMCrypto_ERROR_GENERATION_SKEW = 41, - OEMCrypto_LOCAL_DISPLAY_ONLY = 42, /* Info, not an error. */ - OEMCrypto_ERROR_ANALOG_OUTPUT = 43, - OEMCrypto_ERROR_WRONG_PST = 44, - OEMCrypto_ERROR_WRONG_KEYS = 45, - OEMCrypto_ERROR_MISSING_MASTER = 46, - OEMCrypto_ERROR_LICENSE_INACTIVE = 47, - OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48, - OEMCrypto_ERROR_ENTRY_IN_USE = 49, - OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, /* Obsolete. Don't use. */ - /* Use OEMCrypto_ERROR_NO_CONTENT_KEY instead of KEY_NOT_LOADED. */ - OEMCrypto_KEY_NOT_LOADED = 51, /* Obsolete. */ - OEMCrypto_KEY_NOT_ENTITLED = 52, - OEMCrypto_ERROR_BAD_HASH = 53, - OEMCrypto_ERROR_OUTPUT_TOO_LARGE = 54, - OEMCrypto_ERROR_SESSION_LOST_STATE = 55, - OEMCrypto_ERROR_SYSTEM_INVALIDATED = 56, - OEMCrypto_ERROR_LICENSE_RELOAD = 57, - OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES = 58, - OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION = 59, - OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION = 60, - OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING = 61, - OEMCrypto_ERROR_UNSUPPORTED_CIPHER = 62, - OEMCrypto_ERROR_DVR_FORBIDDEN = 63, - OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE = 64, - OEMCrypto_ERROR_INVALID_KEY = 65, - /* ODK return values */ - ODK_ERROR_BASE = 1000, - ODK_ERROR_CORE_MESSAGE = ODK_ERROR_BASE, - ODK_SET_TIMER = ODK_ERROR_BASE + 1, - ODK_DISABLE_TIMER = ODK_ERROR_BASE + 2, - ODK_TIMER_EXPIRED = ODK_ERROR_BASE + 3, - ODK_UNSUPPORTED_API = ODK_ERROR_BASE + 4, - ODK_STALE_RENEWAL = ODK_ERROR_BASE + 5, - /* OPK return values */ - OPK_ERROR_BASE = 2000, - OPK_ERROR_REMOTE_CALL = OPK_ERROR_BASE, - OPK_ERROR_INCOMPATIBLE_VERSION = OPK_ERROR_BASE + 1, - OPK_ERROR_NO_PERSISTENT_DATA = OPK_ERROR_BASE + 2, -} OEMCryptoResult; -/* clang-format on */ - -/** - * Valid values for status in the usage table. - */ -typedef enum OEMCrypto_Usage_Entry_Status { - kUnused = 0, - kActive = 1, - kInactive = 2, /* Deprecated. Use kInactiveUsed or kInactiveUnused. */ - kInactiveUsed = 3, - kInactiveUnused = 4, -} OEMCrypto_Usage_Entry_Status; - -typedef enum OEMCrypto_ProvisioningRenewalType { - OEMCrypto_NoRenewal = 0, - OEMCrypto_RenewalACert = 1, -} OEMCrypto_ProvisioningRenewalType; - -/** - * OEMCrypto_LicenseType is used in the license message to indicate if the key - * objects are for content keys, or for entitlement keys. - */ -typedef enum OEMCrypto_LicenseType { - OEMCrypto_ContentLicense = 0, - OEMCrypto_EntitlementLicense = 1, - OEMCrypto_LicenseType_MaxValue = OEMCrypto_EntitlementLicense, -} OEMCrypto_LicenseType; - -/* Private key type used in the provisioning response. */ -typedef enum OEMCrypto_PrivateKeyType { - OEMCrypto_RSA_Private_Key = 0, - OEMCrypto_ECC_Private_Key = 1, -} OEMCrypto_PrivateKeyType; - -/** - * Used to indicate a substring of a signed message in OEMCrypto_LoadKeys and - * other functions which must verify that a parameter is contained within a - * signed message. - */ -typedef struct { - size_t offset; - size_t length; -} OEMCrypto_Substring; - -/** - * Used to specify information about CMI Descriptor 0. - * @param id: ID value of CMI Descriptor assigned by DTLA. - * @param length: byte length of the usage rules field. - * @param data: usage rules data. - */ -typedef struct { - uint8_t id; // 0x00 - uint8_t extension; // 0x00 - uint16_t length; // 0x01 - uint8_t data; -} OEMCrypto_DTCP2_CMI_Descriptor_0; - -/** - * Used to specify information about CMI Descriptor 1. - * @param id: ID value of CMI Descriptor assigned by DTLA. - * @param extension: specified by the CMI descriptor - * @param length: byte length of the usage rules field. - * @param data: usage rules data. - */ -typedef struct { - uint8_t id; // 0x01 - uint8_t extension; // 0x00 - uint16_t length; // 0x03 - uint8_t data[3]; -} OEMCrypto_DTCP2_CMI_Descriptor_1; - -/** - * Used to specify information about CMI Descriptor 2. - * @param id: ID value of CMI Descriptor assigned by DTLA. - * @param extension: specified by the CMI descriptor - * @param length: byte length of the usage rules field. - * @param data: usage rules data. - */ -typedef struct { - uint8_t id; // 0x02 - uint8_t extension; // 0x00 - uint16_t length; // 0x03 - uint8_t data[3]; -} OEMCrypto_DTCP2_CMI_Descriptor_2; - -/** - * Used to specify the required DTCP2 level. If dtcp2_required is 0, there are - * no requirements on any of the keys. If dtcp2_required is 1, any key with the - * kControlHDCPRequired bit set requires DTCP2 in its output. - * @param dtcp2_required: specifies whether dtcp2 is required. 0 = not required, - * 1 = DTCP2 required. - * @param cmi_descriptor_1: three bytes of CMI descriptor 1 - */ -typedef struct { - uint8_t dtcp2_required; // 0 = not required. 1 = DTCP2 v1 required. - OEMCrypto_DTCP2_CMI_Descriptor_0 cmi_descriptor_0; - OEMCrypto_DTCP2_CMI_Descriptor_1 cmi_descriptor_1; - OEMCrypto_DTCP2_CMI_Descriptor_2 cmi_descriptor_2; -} OEMCrypto_DTCP2_CMI_Packet; - -/** - * Points to the relevant fields for a content key. The fields are extracted - * from the License Response message offered to OEMCrypto_LoadKeys(). Each - * field points to one of the components of the key. Key data, key control, - * and both IV fields are 128 bits (16 bytes): - * @param key_id: the unique id of this key. - * @param key_id_length: the size of key_id. OEMCrypto may assume this is at - * most 16. However, OEMCrypto shall correctly handle key id lengths - * from 1 to 16 bytes. - * @param key_data_iv: the IV for performing AES-128-CBC decryption of the - * key_data field. - * @param key_data - the key data. It is encrypted (AES-128-CBC) with the - * session's derived encrypt key and the key_data_iv. - * @param key_control_iv: the IV for performing AES-128-CBC decryption of the - * key_control field. - * @param key_control: the key control block. It is encrypted (AES-128-CBC) with - * the content key from the key_data field. - * - * The memory for the OEMCrypto_KeyObject fields is allocated and freed - * by the caller of OEMCrypto_LoadKeys(). - */ -typedef struct { - OEMCrypto_Substring key_id; - OEMCrypto_Substring key_data_iv; - OEMCrypto_Substring key_data; - OEMCrypto_Substring key_control_iv; - OEMCrypto_Substring key_control; -} OEMCrypto_KeyObject; - -/// @} - -#ifdef __cplusplus -} -#endif - -#endif // WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ +#include "../../oemcrypto/odk/include/OEMCryptoCENCCommon.h" diff --git a/oemcrypto/odk/Android.bp b/oemcrypto/odk/Android.bp new file mode 100644 index 00000000..3b7d807a --- /dev/null +++ b/oemcrypto/odk/Android.bp @@ -0,0 +1,102 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +// ---------------------------------------------------------------- +// Builds libwv_odk.a, The ODK Library (libwv_odk) is used by +// the CDM and by oemcrypto implementations. +// *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE +// CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE +// DEPENDING ON IT IN YOUR PROJECT. *** +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "vendor_widevine_license" + // to get the below license kinds: + // legacy_by_exception_only (by exception only) + default_applicable_licenses: ["vendor_widevine_license"], +} + +cc_library_static { + name: "libwv_odk", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/src", + ], + + srcs: [ + "src/odk.c", + "src/odk_message.c", + "src/odk_overflow.c", + "src/odk_serialize.c", + "src/odk_timer.c", + "src/odk_util.c", + "src/serialization_base.c", + ], + proprietary: true, + + owner: "widevine", +} + +// ---------------------------------------------------------------- +// Builds libwv_kdo.a, The ODK Library companion (libwv_kdo) is used by +// the CDM and by oemcrypto tests, but not by oemcrypto implementations. +cc_library_static { + name: "libwv_kdo", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/src", + ], + + srcs: [ + "src/core_message_deserialize.cpp", + "src/core_message_features.cpp", + "src/core_message_serialize.cpp", + "src/core_message_serialize_proto.cpp", + ], + + static_libs: [ + "libcdm_protos", + "libwv_odk", + ], + + proprietary: true, + + owner: "widevine", +} + +// ---------------------------------------------------------------- +// Builds odk_test executable, which tests the ODK library. +cc_test { + name: "odk_test", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/src", + ], + + // WARNING: Module tags are not supported in Soong. + // For native test binaries, use the "cc_test" module type. Some differences: + // - If you don't use gtest, set "gtest: false" + // - Binaries will be installed into /data/nativetest[64]// + // - Both 32 & 64 bit versions will be built (as appropriate) + + owner: "widevine", + proprietary: true, + + static_libs: [ + "libcdm_protos", + "libcdm", + "libwv_odk", + "libwv_kdo", + ], + + srcs: [ + "test/odk_test.cpp", + "test/odk_test_helper.cpp", + "test/odk_timer_test.cpp", + ], + +} diff --git a/oemcrypto/odk/README b/oemcrypto/odk/README new file mode 100644 index 00000000..408fc448 --- /dev/null +++ b/oemcrypto/odk/README @@ -0,0 +1,8 @@ +This ODK Library is used to generate and parse core OEMCrypto messages for +OEMCrypto v16 and above. This library is used by both OEMCrypto on a device +and by Widevine license and provisioning servers. + +The source of truth for these files is in the server code base on piper. Do not +edit these files in the Android directory tree or in the Widevine Git +repository. If you need to edit these files and are not sure how to procede, +please ask for help from an engineer on the Widevine server or device teams. diff --git a/oemcrypto/odk/include/OEMCryptoCENCCommon.h b/oemcrypto/odk/include/OEMCryptoCENCCommon.h new file mode 100644 index 00000000..ce51b8d0 --- /dev/null +++ b/oemcrypto/odk/include/OEMCryptoCENCCommon.h @@ -0,0 +1,245 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/********************************************************************* + * OEMCryptoCENCCommon.h + * + * Common structures and error codes between WV servers and OEMCrypto. + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ +#define WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/// @addtogroup common_types +/// @{ + +/* clang-format off */ +/** Error and result codes returned by OEMCrypto functions. */ +typedef enum OEMCryptoResult { + OEMCrypto_SUCCESS = 0, + OEMCrypto_ERROR_INIT_FAILED = 1, + OEMCrypto_ERROR_TERMINATE_FAILED = 2, + OEMCrypto_ERROR_OPEN_FAILURE = 3, + OEMCrypto_ERROR_CLOSE_FAILURE = 4, + OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, /* deprecated */ + OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, /* deprecated */ + OEMCrypto_ERROR_SHORT_BUFFER = 7, + OEMCrypto_ERROR_NO_DEVICE_KEY = 8, /* no keybox device key. */ + OEMCrypto_ERROR_NO_ASSET_KEY = 9, + OEMCrypto_ERROR_KEYBOX_INVALID = 10, + OEMCrypto_ERROR_NO_KEYDATA = 11, + OEMCrypto_ERROR_NO_CW = 12, + OEMCrypto_ERROR_DECRYPT_FAILED = 13, + OEMCrypto_ERROR_WRITE_KEYBOX = 14, + OEMCrypto_ERROR_WRAP_KEYBOX = 15, + OEMCrypto_ERROR_BAD_MAGIC = 16, + OEMCrypto_ERROR_BAD_CRC = 17, + OEMCrypto_ERROR_NO_DEVICEID = 18, + OEMCrypto_ERROR_RNG_FAILED = 19, + OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20, + OEMCrypto_ERROR_SETUP = 21, + OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22, + OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23, + OEMCrypto_ERROR_INVALID_SESSION = 24, + OEMCrypto_ERROR_NOT_IMPLEMENTED = 25, + OEMCrypto_ERROR_NO_CONTENT_KEY = 26, + OEMCrypto_ERROR_CONTROL_INVALID = 27, + OEMCrypto_ERROR_UNKNOWN_FAILURE = 28, + OEMCrypto_ERROR_INVALID_CONTEXT = 29, + OEMCrypto_ERROR_SIGNATURE_FAILURE = 30, + OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31, + OEMCrypto_ERROR_INVALID_NONCE = 32, + OEMCrypto_ERROR_TOO_MANY_KEYS = 33, + OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34, + OEMCrypto_ERROR_INVALID_RSA_KEY = 35, /* deprecated */ + OEMCrypto_ERROR_KEY_EXPIRED = 36, + OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37, + OEMCrypto_ERROR_INSUFFICIENT_HDCP = 38, + OEMCrypto_ERROR_BUFFER_TOO_LARGE = 39, + OEMCrypto_WARNING_GENERATION_SKEW = 40, /* Warning, not error. */ + OEMCrypto_ERROR_GENERATION_SKEW = 41, + OEMCrypto_LOCAL_DISPLAY_ONLY = 42, /* Info, not an error. */ + OEMCrypto_ERROR_ANALOG_OUTPUT = 43, + OEMCrypto_ERROR_WRONG_PST = 44, + OEMCrypto_ERROR_WRONG_KEYS = 45, + OEMCrypto_ERROR_MISSING_MASTER = 46, + OEMCrypto_ERROR_LICENSE_INACTIVE = 47, + OEMCrypto_ERROR_ENTRY_NEEDS_UPDATE = 48, + OEMCrypto_ERROR_ENTRY_IN_USE = 49, + OEMCrypto_ERROR_USAGE_TABLE_UNRECOVERABLE = 50, /* Obsolete. Don't use. */ + /* Use OEMCrypto_ERROR_NO_CONTENT_KEY instead of KEY_NOT_LOADED. */ + OEMCrypto_KEY_NOT_LOADED = 51, /* Obsolete. */ + OEMCrypto_KEY_NOT_ENTITLED = 52, + OEMCrypto_ERROR_BAD_HASH = 53, + OEMCrypto_ERROR_OUTPUT_TOO_LARGE = 54, + OEMCrypto_ERROR_SESSION_LOST_STATE = 55, + OEMCrypto_ERROR_SYSTEM_INVALIDATED = 56, + OEMCrypto_ERROR_LICENSE_RELOAD = 57, + OEMCrypto_ERROR_MULTIPLE_USAGE_ENTRIES = 58, + OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION = 59, + OEMCrypto_ERROR_INVALID_ENTITLED_KEY_SESSION = 60, + OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING = 61, + OEMCrypto_ERROR_UNSUPPORTED_CIPHER = 62, + OEMCrypto_ERROR_DVR_FORBIDDEN = 63, + OEMCrypto_ERROR_INSUFFICIENT_PRIVILEGE = 64, + OEMCrypto_ERROR_INVALID_KEY = 65, + /* ODK return values */ + ODK_ERROR_BASE = 1000, + ODK_ERROR_CORE_MESSAGE = ODK_ERROR_BASE, + ODK_SET_TIMER = ODK_ERROR_BASE + 1, + ODK_DISABLE_TIMER = ODK_ERROR_BASE + 2, + ODK_TIMER_EXPIRED = ODK_ERROR_BASE + 3, + ODK_UNSUPPORTED_API = ODK_ERROR_BASE + 4, + ODK_STALE_RENEWAL = ODK_ERROR_BASE + 5, + /* OPK return values */ + OPK_ERROR_BASE = 2000, + OPK_ERROR_REMOTE_CALL = OPK_ERROR_BASE, + OPK_ERROR_INCOMPATIBLE_VERSION = OPK_ERROR_BASE + 1, + OPK_ERROR_NO_PERSISTENT_DATA = OPK_ERROR_BASE + 2, +} OEMCryptoResult; +/* clang-format on */ + +/** + * Valid values for status in the usage table. + */ +typedef enum OEMCrypto_Usage_Entry_Status { + kUnused = 0, + kActive = 1, + kInactive = 2, /* Deprecated. Use kInactiveUsed or kInactiveUnused. */ + kInactiveUsed = 3, + kInactiveUnused = 4, +} OEMCrypto_Usage_Entry_Status; + +typedef enum OEMCrypto_ProvisioningRenewalType { + OEMCrypto_NoRenewal = 0, + OEMCrypto_RenewalACert = 1, +} OEMCrypto_ProvisioningRenewalType; + +/** + * OEMCrypto_LicenseType is used in the license message to indicate if the key + * objects are for content keys, or for entitlement keys. + */ +typedef enum OEMCrypto_LicenseType { + OEMCrypto_ContentLicense = 0, + OEMCrypto_EntitlementLicense = 1, + OEMCrypto_LicenseType_MaxValue = OEMCrypto_EntitlementLicense, +} OEMCrypto_LicenseType; + +/* Private key type used in the provisioning response. */ +typedef enum OEMCrypto_PrivateKeyType { + OEMCrypto_RSA_Private_Key = 0, + OEMCrypto_ECC_Private_Key = 1, +} OEMCrypto_PrivateKeyType; + +/** + * Used to indicate a substring of a signed message in OEMCrypto_LoadKeys and + * other functions which must verify that a parameter is contained within a + * signed message. + */ +typedef struct { + size_t offset; + size_t length; +} OEMCrypto_Substring; + +/** + * Used to specify information about CMI Descriptor 0. + * @param id: ID value of CMI Descriptor assigned by DTLA. + * @param length: byte length of the usage rules field. + * @param data: usage rules data. + */ +typedef struct { + uint8_t id; // 0x00 + uint8_t extension; // 0x00 + uint16_t length; // 0x01 + uint8_t data; +} OEMCrypto_DTCP2_CMI_Descriptor_0; + +/** + * Used to specify information about CMI Descriptor 1. + * @param id: ID value of CMI Descriptor assigned by DTLA. + * @param extension: specified by the CMI descriptor + * @param length: byte length of the usage rules field. + * @param data: usage rules data. + */ +typedef struct { + uint8_t id; // 0x01 + uint8_t extension; // 0x00 + uint16_t length; // 0x03 + uint8_t data[3]; +} OEMCrypto_DTCP2_CMI_Descriptor_1; + +/** + * Used to specify information about CMI Descriptor 2. + * @param id: ID value of CMI Descriptor assigned by DTLA. + * @param extension: specified by the CMI descriptor + * @param length: byte length of the usage rules field. + * @param data: usage rules data. + */ +typedef struct { + uint8_t id; // 0x02 + uint8_t extension; // 0x00 + uint16_t length; // 0x03 + uint8_t data[3]; +} OEMCrypto_DTCP2_CMI_Descriptor_2; + +/** + * Used to specify the required DTCP2 level. If dtcp2_required is 0, there are + * no requirements on any of the keys. If dtcp2_required is 1, any key with the + * kControlHDCPRequired bit set requires DTCP2 in its output. + * @param dtcp2_required: specifies whether dtcp2 is required. 0 = not required, + * 1 = DTCP2 required. + * @param cmi_descriptor_1: three bytes of CMI descriptor 1 + */ +typedef struct { + uint8_t dtcp2_required; // 0 = not required. 1 = DTCP2 v1 required. + OEMCrypto_DTCP2_CMI_Descriptor_0 cmi_descriptor_0; + OEMCrypto_DTCP2_CMI_Descriptor_1 cmi_descriptor_1; + OEMCrypto_DTCP2_CMI_Descriptor_2 cmi_descriptor_2; +} OEMCrypto_DTCP2_CMI_Packet; + +/** + * Points to the relevant fields for a content key. The fields are extracted + * from the License Response message offered to OEMCrypto_LoadKeys(). Each + * field points to one of the components of the key. Key data, key control, + * and both IV fields are 128 bits (16 bytes): + * @param key_id: the unique id of this key. + * @param key_id_length: the size of key_id. OEMCrypto may assume this is at + * most 16. However, OEMCrypto shall correctly handle key id lengths + * from 1 to 16 bytes. + * @param key_data_iv: the IV for performing AES-128-CBC decryption of the + * key_data field. + * @param key_data - the key data. It is encrypted (AES-128-CBC) with the + * session's derived encrypt key and the key_data_iv. + * @param key_control_iv: the IV for performing AES-128-CBC decryption of the + * key_control field. + * @param key_control: the key control block. It is encrypted (AES-128-CBC) with + * the content key from the key_data field. + * + * The memory for the OEMCrypto_KeyObject fields is allocated and freed + * by the caller of OEMCrypto_LoadKeys(). + */ +typedef struct { + OEMCrypto_Substring key_id; + OEMCrypto_Substring key_data_iv; + OEMCrypto_Substring key_data; + OEMCrypto_Substring key_control_iv; + OEMCrypto_Substring key_control; +} OEMCrypto_KeyObject; + +/// @} + +#ifdef __cplusplus +} +#endif + +#endif // WIDEVINE_ODK_INCLUDE_OEMCRYPTOCENCCOMMON_H_ diff --git a/oemcrypto/odk/include/core_message_deserialize.h b/oemcrypto/odk/include/core_message_deserialize.h new file mode 100644 index 00000000..545a8062 --- /dev/null +++ b/oemcrypto/odk/include/core_message_deserialize.h @@ -0,0 +1,83 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/********************************************************************* + * core_message_deserialize.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * This file declares functions to deserialize request messages prepared by + * Widevine clients (OEMCrypto/ODK). + * + * Please refer to core_message_types.h for details. + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ + +#include + +#include "core_message_types.h" + +namespace oemcrypto_core_message { +namespace deserialize { + +/** + * Counterpart (deserializer) of ODK_PrepareCoreLicenseRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_license_request + */ +bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreRenewalRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_renewal_request + */ +bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreProvisioningRequest (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_provisioning_request + */ +bool CoreProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request); + +/** + * Counterpart (deserializer) of ODK_PrepareCoreRenewedProvisioningRequest + * (serializer) + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_provisioning_request + */ +bool CoreRenewedProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request); + +/** + * Serializer counterpart is not used and is therefore not implemented. + * + * Parameters: + * [in] oemcrypto_core_message + * [out] core_common_request + */ +bool CoreCommonRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_CommonRequest* core_common_request); + +} // namespace deserialize +} // namespace oemcrypto_core_message + +#endif // WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_DESERIALIZE_H_ diff --git a/oemcrypto/odk/include/core_message_features.h b/oemcrypto/odk/include/core_message_features.h new file mode 100644 index 00000000..16289c6b --- /dev/null +++ b/oemcrypto/odk/include/core_message_features.h @@ -0,0 +1,44 @@ +// Copyright 2021 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_FEATURES_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_FEATURES_H_ + +#include + +#include +#include + +namespace oemcrypto_core_message { +namespace features { + +// Features that may be supported by core messages. By restricting values in +// this structure, we can turn off features at runtime. This is plain data, and +// is essentially a version number. +struct CoreMessageFeatures { + // A default set of features. + static const CoreMessageFeatures kDefaultFeatures; + + // Create the default feature set for the given major version number. + static CoreMessageFeatures DefaultFeatures(uint32_t maximum_major_version); + + // 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 16.5, the last version used by Chrome. This will + // change to 17.0 when v17 has been released. + uint32_t maximum_major_version = 17; + uint32_t maximum_minor_version = 0; + + bool operator==(const CoreMessageFeatures &other) const; + bool operator!=(const CoreMessageFeatures &other) const { + return !(*this == other); + } +}; + +std::ostream &operator<<(std::ostream &os, const CoreMessageFeatures &features); + +} // namespace features +} // namespace oemcrypto_core_message + +#endif // WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_FEATURES_H_ diff --git a/oemcrypto/odk/include/core_message_serialize.h b/oemcrypto/odk/include/core_message_serialize.h new file mode 100644 index 00000000..bd6d6354 --- /dev/null +++ b/oemcrypto/odk/include/core_message_serialize.h @@ -0,0 +1,78 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/********************************************************************* + * core_message_serialize.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * This file declares functions to serialize response messages that will be + * parsed by Widevine clients (OEMCrypto/ODK). + * + * Please refer to core_message_types.h for details. + * + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ + +#include + +#include "core_message_features.h" +#include "core_message_types.h" +#include "odk_structs.h" + +namespace oemcrypto_core_message { +namespace serialize { +using oemcrypto_core_message::features::CoreMessageFeatures; + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * struct-input variant + * + * Parameters: + * [in] features feature support for response message. + * [in] parsed_lic + * [in] core_request + * [in] core_request_sha256 + * [out] oemcrypto_core_message + */ +bool CreateCoreLicenseResponse(const CoreMessageFeatures& features, + const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseRenewal (deserializer) + * + * Parameters: + * [in] features feature support for response message. + * [in] core_request + * [in] renewal_duration_seconds + * [out] oemcrypto_core_message + */ +bool CreateCoreRenewalResponse(const CoreMessageFeatures& features, + const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, + std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * struct-input variant + * + * Parameters: + * [in] features feature support for response message. + * [in] parsed_prov + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponse(const CoreMessageFeatures& features, + const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message); +} // namespace serialize +} // namespace oemcrypto_core_message + +#endif // WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_H_ diff --git a/oemcrypto/odk/include/core_message_serialize_proto.h b/oemcrypto/odk/include/core_message_serialize_proto.h new file mode 100644 index 00000000..de753620 --- /dev/null +++ b/oemcrypto/odk/include/core_message_serialize_proto.h @@ -0,0 +1,67 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/********************************************************************* + * core_message_serialize_proto.h + * + * These functions are an extension of those found in + * core_message_serialize.h. The difference is that these use the + * license and provisioning messages in protobuf format to create the core + * message. + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ + +#include +#include + +#include "core_message_features.h" +#include "core_message_types.h" +#include "license_protocol.pb.h" + +namespace oemcrypto_core_message { +namespace serialize { +// @ public create response (serializer) functions accepting proto input + +/** + * Counterpart (serializer) of ODK_ParseLicense (deserializer) + * + * Parameters: + * [in] features feature support for response message. + * [in] serialized_license + serialized video_widevine::License + * [in] core_request oemcrypto core message from request. + * [in] core_request_sha256 - hash of serialized core request. + * [in] nonce_required - if the device should require a nonce match. + * [in] uses_padding - if the keys use padding. + * [out] oemcrypto_core_message - the serialized oemcrypto core response. + */ +bool CreateCoreLicenseResponseFromProto( + const oemcrypto_core_message::features::CoreMessageFeatures& features, + const std::string& serialized_license, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, const bool nonce_required, + const bool uses_padding, std::string* oemcrypto_core_message); + +/** + * Counterpart (serializer) of ODK_ParseProvisioning (deserializer) + * + * Parameters: + * [in] features feature support for response message. + * [in] serialized_provisioning_response + * serialized video_widevine::ProvisioningResponse + * [in] core_request + * [out] oemcrypto_core_message + */ +bool CreateCoreProvisioningResponseFromProto( + const oemcrypto_core_message::features::CoreMessageFeatures& features, + const std::string& serialized_provisioning_response, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message); + +} // namespace serialize +} // namespace oemcrypto_core_message + +#endif // WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_SERIALIZE_PROTO_H_ diff --git a/oemcrypto/odk/include/core_message_types.h b/oemcrypto/odk/include/core_message_types.h new file mode 100644 index 00000000..5315913e --- /dev/null +++ b/oemcrypto/odk/include/core_message_types.h @@ -0,0 +1,115 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +// clang-format off +/********************************************************************* + * core_message_types.h + * + * OEMCrypto v16 Core Message Serialization library counterpart (a.k.a. KDO) + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The KDO library is the counterpart of ODK used in the CDM & Widevine + * servers. For each message type generated by the ODK, KDO provides a + * corresponding parser. For each message type to be parsed by the ODK, + * KDO provides a corresponding writer. + * + * Table: ODK vs KDO (s: serialize; d: deserialize) + * +----------------------------------------+---------------------------------------+ + * | ODK | KDO | + * +---+------------------------------------+---+-----------------------------------+ + * | s | ODK_PrepareCoreLicenseRequest | d | CoreLicenseRequestFromMessage | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCoreRenewalRequest | | CoreRenewalRequestFromMessage | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCoreProvisioningRequest | | CoreProvisioningRequestFromMessage| + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_PrepareCommonRequest | | CoreCommonRequestFromMessage | + * +---+------------------------------------+---+-----------------------------------+ + * | d | ODK_ParseLicense | s | CreateCoreLicenseResponse | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_ParseRenewal | | CreateCoreRenewalResponse | + * | +------------------------------------+ +-----------------------------------+ + * | | ODK_ParseProvisioning | | CreateCoreProvisioningResponse | + * +---+------------------------------------+---+-----------------------------------+ + * + *********************************************************************/ +// clang-format on + +#ifndef WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ +#define WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ + +#include +#include + +namespace oemcrypto_core_message { + +// @ input/output structs + +/** + * Output structure for CommonRequestFromMessage + * Input structure for CreateCommonResponse + */ +struct ODK_CommonRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +}; + +/** + * Output structure for CoreLicenseRequestFromMessage + * Input structure for CreateCoreLicenseResponse + */ +struct ODK_LicenseRequest { + 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 + */ +struct ODK_RenewalRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; + uint64_t playback_time_seconds; +}; + +/** + * Output structure for CoreProvisioningRequestFromMessage and + * CoreRenewedProvisioningRequestFromMessage + * Input structure for CreateCoreProvisioningResponse + */ +struct ODK_ProvisioningRequest { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; + std::string device_id; + uint16_t renewal_type; + std::string renewal_data; +}; + +} // namespace oemcrypto_core_message + +#endif // WIDEVINE_ODK_INCLUDE_CORE_MESSAGE_TYPES_H_ diff --git a/oemcrypto/odk/include/odk.h b/oemcrypto/odk/include/odk.h new file mode 100644 index 00000000..e3499da2 --- /dev/null +++ b/oemcrypto/odk/include/odk.h @@ -0,0 +1,664 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/** + * @mainpage OEMCrypto v16 Core Message Serialization library + * + * For Widevine Modular DRM, there are six message types between a server and + * a client device: license request and response, provisioning request and + * response, and renewal request and response. + * + * In OEMCrypto v15 and earlier, messages from the server were parsed by the + * CDM layer above OEMCrypto; the CDM in turn gave OEMCrypto a collection of + * pointers to protected data within the message. However, the pointers + * themselves were not signed by the server. + * + * Starting from OEMCrypto v16, all fields used by OEMCrypto in each of these + * messages have been identified in the document "Widevine Core Message + * Serialization". These fields are called the core of the message. Core + * message fields are (de)serialized using the ODK, a C library provided by + * Widevine. OEMCrypto will parse and verify the core of the message with + * help from the ODK. + * + * The ODK functions that parse code will fill out structs that have similar + * formats to the function parameters of the OEMCrypto v15 functions being + * replaced. The ODK will be provided in source code and it is Widevine's + * intention that partners can build and link ODK with their implementation + * of OEMCrypto with no or few code changes. + * + * OEMCrypto implementers shall build the ODK library as part of the Trusted + * Application (TA) running in the TEE. All memory and buffers used by the + * ODK library shall be sanitized by the OEMCrypto implementer to prevent + * modification by any process running the REE. + * + * See the documents + * Widevine Core Message Serialization + * and + * License Duration and Renewal + * for a detailed description of the ODK API. You can + * find these documents in the widevine repository as + * docs/Widevine_Core_Message_Serialization.pdf and + * docs/License_Duration_and_Renewal.pdf + * + * @defgroup odk_parser Core Message Parsing and Verification + * Functions that parse core messages and verify they are valid. + * TODO(fredgc): add documentation for parsing functions. + * + * @defgroup odk_packer Core Message Creation + * Functions that create core messages. + * TODO(fredgc): add documentation for packing functions. + * + * @defgroup odk_timer Timer and Clock Functions + * Functions related to enforcing timer and duration restrictions. + * TODO(fredgc): add documentation for timers and clocks. + * + * @defgroup common_types Common Types + * Enumerations and structures that are used by several OEMCrypto and ODK + * functions. + *********************************************************************/ + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_H_ + +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/// @addtogroup odk_timer +/// @{ + +/** + * This function initializes the session's data structures. It shall be + * called from OEMCrypto_OpenSession. + * + * @param[out] timer_limits: the session's timer limits. + * @param[out] clock_values: the session's clock values. + * @param[out] nonce_values: the session's ODK nonce values. + * @param[in] api_major_version: the API version of OEMCrypto. + * @param[in] session_id: the session id of the newly created session. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t api_major_version, + uint32_t session_id); + +/** + * This function sets the nonce value in the session's nonce structure. It + * shall be called from OEMCrypto_GenerateNonce. + * + * @param[in,out] nonce_values: the session's nonce data. + * @param[in] nonce: the new nonce that was just generated. + * + * @retval true on success + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, + uint32_t nonce); + +/** + * This function initializes the clock values in the session clock_values + * structure. It shall be called from OEMCrypto_PrepAndSignLicenseRequest. + * + * Parameters: + * @param[in,out] clock_values: the session's clock data. + * @param[in] system_time_seconds: the current time on OEMCrypto's monotonic + * clock. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + +/** + * This function sets the values in the clock_values structure. It shall be + * called from OEMCrypto_LoadUsageEntry. When a usage entry from a v15 or + * earlier license is loaded, the value time_of_license_loaded shall be used + * in place of time_of_license_request_signed. + * + * @param[in,out] clock_values: the session's clock data. + * @param[in] time_of_license_request_signed: the value time_license_received + * from the loaded usage entry. + * @param[in] time_of_first_decrypt: the value time_of_first_decrypt from the + * loaded usage entry. + * @param[in] time_of_last_decrypt: the value time_of_last_decrypt from the + * loaded usage entry. + * @param[in] status: the value status from the loaded usage entry. + * @param[in] system_time_seconds: the current time on OEMCrypto's monotonic + * clock. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, + uint64_t time_of_license_request_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds); + +/** + * This updates the clock values, and determines if playback may start based + * on the given system time. It uses the values in clock_values to determine + * if this is the first playback for the license or the first playback for + * just this session. + * + * This shall be called from the first call in a session to any of + * OEMCrypto_DecryptCENC or any of the OEMCrypto_Generic* functions. + * + * If OEMCrypto uses a hardware timer, and this function returns + * ODK_SET_TIMER, then the timer should be set to the value pointed to by + * timer_value. + * + * @param[in] system_time_seconds: the current time on OEMCrypto's monotonic + * clock, in seconds. + * @param[in] timer_limits: timer limits specified in the license. + * @param[in,out] clock_values: the sessions clock values. + * @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 ODK_SET_TIMER: Success. The timer should be reset to the specified + * value and playback is allowed. + * @retval ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * @retval ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value); + +/** + * Vendors that do not implement their own timer should call + * ODK_UpdateLastPlaybackTime regularly during playback. This updates the + * clock values, and determines if playback may continue based on the given + * system time. This shall be called from any of OEMCrypto_DecryptCENC or any + * of the OEMCrypto_Generic* functions. + * + * All Vendors (i.e. those that do or do not implement their own timer) shall + * call ODK_UpdateLastPlaybackTime from the function + * OEMCrypto_UpdateUsageEntry before updating the usage entry so that the + * clock values are accurate. + * + * @param[in] system_time_seconds: the current time on OEMCrypto's monotonic + * clock, in seconds. + * @param[in] timer_limits: timer limits specified in the license. + * @param[in,out] clock_values: the sessions clock values. + * + * @retval OEMCrypto_SUCCESS: Success. Playback is allowed. + * @retval ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values); + +/** + * This function modifies the session's clock values to indicate that the + * license has been deactivated. It shall be called from + * OEMCrypto_DeactivateUsageEntry + * + * Parameters: + * @param[in,out] clock_values: the sessions clock values. + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values); + +/// @} + +/// @addtogroup odk_packer +/// @{ + +/** + * Modifies the message to include a core license request 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_PrepAndSignLicenseRequest. + * + * 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. + * + * @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 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_size, + const ODK_NonceValues* nonce_values); + +/** + * Modifies the message to include a core renewal request at the beginning of + * the message buffer. The values in nonce_values, clock_values and + * system_time_seconds are used to populate the message. The nonce_values + * should match those from the license. + * + * This shall be called by OEMCrypto from OEMCrypto_PrepAndSignRenewalRequest. + * + * If status in clock_values indicates that a license has not been loaded, + * then this is a license release. The ODK library will change the value of + * nonce_values.api_major_version to 15. This will make + * OEMCrypto_PrepAndSignRenewalRequest sign just the message body, as it does + * for all legacy licenses. + * + * 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,out] nonce_values: pointer to the session's nonce data. + * @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 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds); + +/** + * Modifies the message to include a core provisioning request 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_PrepAndSignProvisioningRequest. + * + * The buffer device_id shall be the same string returned by + * OEMCrypto_GetDeviceID. The device ID shall be unique to the device, and + * stable across reboots and factory resets for an L1 device. + * + * NOTE: if the message pointer is null and/or input core_message_length 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] device_id: For devices with a keybox, this is the device ID from + * the keybox. For devices with an OEM Certificate, this is a device + * unique id string. + * @param[in] device_id_length: length of device_id. The device ID can be at + * most 64 bytes. + * + * @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 16 of the API. + */ +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length); + +/** + * Modifies the message to include a core renewal provisioning request 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_PrepAndSignProvisioningRequest. + * + * The buffer device_id shall be the same string returned by + * OEMCrypto_GetDeviceID. The device ID shall be unique to the device, and + * stable across reboots and factory resets for an L1 device. + * + * NOTE: if the message pointer is null and/or input core_message_length 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] device_id: For devices with a keybox, this is the device ID from + * the keybox. For devices with an OEM Certificate, this is a device + * unique id string. + * @param[in] device_id_length: length of device_id. The device ID can be at + * most 64 bytes. + * @param[in] renewal_type: type of renewal used + * @param[in] renewal_data: renewal data used. For renewal_type = 1, + * renewal_data is the Android attestation batch certificate. + * @param[in] renewal_data_length: length of renewal_data + * + * @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 17 of the API. + */ +OEMCryptoResult ODK_PrepareCoreRenewedProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, uint16_t renewal_type, const uint8_t* renewal_data, + size_t renewal_data_length); + +/// @} + +/// @addtogroup odk_timer +/// @{ + +/** + * This function sets all limits in the timer_limits struct to the + * key_duration and initializes the other values. The field + * nonce_values.api_major_version will be set to 15. It shall be called from + * OEMCrypto_LoadKeys when loading a legacy license. + * + * @param[out] timer_limits: The session's timer limits. + * @param[in,out] clock_values: The session's clock values. + * @param[in,out] nonce_values: The session's ODK nonce values. + * @param[in] key_duration: The duration from the first key's key control + * block. In practice, the key duration is the same for all keys and is + * the same as the license duration. + * @param[in] system_time_seconds: The current time on the system clock, as + * described in the document "License Duration and Renewal". + * + * @retval OEMCrypto_SUCCESS + * @retval OEMCrypto_ERROR_INVALID_CONTEXT + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t key_duration, + uint64_t system_time_seconds); + +/** + * This function updates the clock_values as needed if a v15 renewal is + * accepted. The field nonce_values.api_major_version is verified to be 15. + * + * This is called from OEMCrypto_RefreshKeys for a valid license renewal. + * OEMCrypto shall pass in the current system time, and the key duration from + * the first object in the OEMCrypto_KeyRefreshObject. + * + * @param[in] timer_limits: The session's timer limits. + * @param[in,out] clock_values: The session's clock values. + * @param[in] nonce_values: The session's ODK nonce values. + * @param[in] system_time_seconds: The current time on the system clock, as + * described in the document "License Duration and Renewal". + * @param[in] new_key_duration: The duration from the first + * OEMCrypto_KeyRefreshObject in key_array. + * @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 OEMCrypto_ERROR_UNKNOWN_FAILURE + * @retval ODK_SET_TIMER: Success. The timer should be reset to the specified + * value and playback is allowed. + * @retval ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * @retval ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + uint32_t new_key_duration, + uint64_t* timer_value); + +/// @} + +/// @addtogroup odk_parser +/// @{ + +/** + * The function ODK_ParseLicense will parse the message and verify fields in + * the message. + * + * If the message does not parse correctly, ODK_VerifyAndParseLicense will + * return ODK_ERROR_CORE_MESSAGE that OEMCrypto should return to the CDM + * layer above. + * + * If the API in the message is not 16, then ODK_UNSUPPORTED_API is returned. + * + * If initial_license_load is true, and nonce_required in the license is + * true, then the ODK library shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. If + * verification fails, then it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * If initial_license_load is false, and nonce_required is true, then + * ODK_ParseLicense will set the values in nonce_values from those in the + * message. + * + * The function ODK_ParseLicense will verify that each substring points to a + * location in the message body. The message body is the buffer starting at + * message + core_message_length with size message_length - + * core_message_length. + * + * If initial_license_load is true, then ODK_ParseLicense shall verify that + * the parameter request_hash matches request_hash in the parsed license. If + * verification fails, then it shall return ODK_ERROR_CORE_MESSAGE. This was + * computed by OEMCrypto when the license was requested. + * + * If usage_entry_present is true, then ODK_ParseLicense shall verify that + * the pst in the license has a nonzero length. + * + * @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] initial_license_load: true when called for OEMCrypto_LoadLicense + * and false when called for OEMCrypto_ReloadLicense. + * @param[in] usage_entry_present: true if the session has a new usage entry + * associated with it created via OEMCrypto_CreateNewUsageEntry. + * @param[in,out] timer_limits: The session's timer limits. These will be + * updated. + * @param[in,out] clock_values: The session's clock values. These will be + * updated. + * @param[in,out] nonce_values: The session's nonce values. These will be + * updated. + * @param[out] parsed_license: the destination for the data. + * + * @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 OEMCrypto_ERROR_INVALID_NONCE + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, ODK_ParsedLicense* parsed_license); + +/** + * The function ODK_ParseRenewal will parse the message and verify its + * contents. If the message does not parse correctly, an error of + * ODK_ERROR_CORE_MESSAGE is returned. + * + * ODK_ParseRenewal shall verify that all fields in nonce_values match those + * in the license. Otherwise it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * After parsing the message, this function updates the clock_values based on + * the timer_limits and the current system time. If playback may not + * continue, then ODK_TIMER_EXPIRED is returned. + * + * If playback may continue, a return value of ODK_SET_TIMER or + * ODK_TIMER_EXPIRED is returned. If the return value is ODK_SET_TIMER, then + * playback may continue until the timer expires. If the return value is + * ODK_DISABLE_TIMER, then playback time is not limited. + * + * If OEMCrypto uses a hardware timer, and this function returns + * ODK_SET_TIMER, then OEMCrypto shall set the timer to the value pointed to + * by timer_value. + * + * @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] nonce_values: pointer to the session's nonce data. + * @param[in] system_time_seconds: the current time on OEMCrypto's clock, in + * seconds. + * @param[in] timer_limits: timer limits specified in the license. + * @param[in,out] clock_values: the sessions clock values. + * @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 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_SET_TIMER: Success. The timer should be reset to the specified + * timer value. + * @retval ODK_DISABLE_TIMER: Success, but disable timer. Unlimited playback is + * allowed. + * @retval ODK_TIMER_EXPIRED: Set timer as disabled. Playback is not allowed. + * @retval ODK_UNSUPPORTED_API + * @retval ODK_STALE_RENEWAL: This renewal is not the most recently signed. It + * is rejected. + * @retval OEMCrypto_ERROR_INVALID_NONCE + * + * @version + * This method is new in version 16 of the API. + */ +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value); + +/** + * The function ODK_ParseProvisioning will parse the message and verify the + * nonce values match those in the license. + * + * If the message does not parse correctly, ODK_ParseProvisioning will return + * an error that OEMCrypto should return to the CDM layer above. + * + * If the API in the message is larger than 16, then ODK_UNSUPPORTED_API is + * returned. + * + * ODK_ParseProvisioning shall verify that nonce_values->nonce and + * nonce_values->session_id are the same as those in the message. Otherwise + * it shall return OEMCrypto_ERROR_INVALID_NONCE. + * + * The function ODK_ParseProvisioning will verify that each substring points + * to a location in the message body. The message body is the buffer starting + * at message + core_message_length with size message_length - + * core_message_length. + * + * @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] nonce_values: pointer to the session's nonce data. + * @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] parsed_response: destination for the parse data. + * + * @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 16 of the API. + */ +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response); + +/** + * The function ODK_ParseProvisioning will parse the message and verify the + * API version is at most the version passed in. + * + * @param[in] nonce_values: pointer to the session's nonce data. + * @param[in] major_versioh: current API major version. + * @param[in] minor_version: current API minor version. + * + * @version + * This method is new in version 17 of the API. + */ +bool CheckApiVersionAtMost(const ODK_NonceValues* nonce_values, + uint16_t major_version, uint16_t minor_version); + +/// @} + +#ifdef __cplusplus +} +#endif + +#endif // WIDEVINE_ODK_INCLUDE_ODK_H_ diff --git a/oemcrypto/odk/include/odk_attributes.h b/oemcrypto/odk/include/odk_attributes.h new file mode 100644 index 00000000..72321b14 --- /dev/null +++ b/oemcrypto/odk/include/odk_attributes.h @@ -0,0 +1,14 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_ATTRIBUTES_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_ATTRIBUTES_H_ + +#if defined(__GNUC__) || defined(__clang__) +#define UNUSED __attribute__((__unused__)) +#else +#define UNUSED +#endif + +#endif // WIDEVINE_ODK_INCLUDE_ODK_ATTRIBUTES_H_ diff --git a/oemcrypto/odk/include/odk_message.h b/oemcrypto/odk/include/odk_message.h new file mode 100644 index 00000000..075f28cc --- /dev/null +++ b/oemcrypto/odk/include/odk_message.h @@ -0,0 +1,141 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_MESSAGE_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_MESSAGE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +/* + * ODK_Message is the structure that defines the serialized messages passed + * between the REE and TEE. ODK_Message is an abstract data type that represents + * the concept of a message without disclosing the implementation details. By + * hiding the internal structure, modification of the message fields by code + * that is not privy to the message definition can be prevented. If the message + * definition was exposed, there could be serious yet subtle errors in message + * manipulation anywhere in the code base. By restricting message modification + * it is possible to enforce validity and integrity with a small set of + * primitives that can be carefully reviewed. Checks can be added to verify that + * a message's fields are internally consistent before every operation. As an + * example, it can be guaranteed that the message status will be checked prior + * to accessing any field so parsing will be stopped when the message status is + * set after any parse error is detected. This also makes development easier + * since any access to the message structure can be tracked through a single + * point so, for example, it becomes possible to add trace statements globally + * to all message operations by only changing the field accessors. Finally it + * simplifies maintenance by localizing changes to the message structure to a + * few files. + */ + +#if defined(__GNUC__) || defined(__clang__) +#define ALIGNED __attribute__((aligned)) +#else +#define ALIGNED +#error ODK_Message must be aligned to the maximum useful alignment of the \ + machine you are compiling for. Define the ALIGNED macro accordingly. +#endif + +typedef struct { +#define SIZE_OF_ODK_MESSAGE_IMPL 64 + uint8_t opaque_data[SIZE_OF_ODK_MESSAGE_IMPL]; +} ALIGNED ODK_Message; + +typedef enum { + MESSAGE_STATUS_OK = 0x7937fcf7, + MESSAGE_STATUS_UNKNOWN_ERROR = 0x706c1190, + MESSAGE_STATUS_OVERFLOW_ERROR = 0x543ae4bc, + MESSAGE_STATUS_UNDERFLOW_ERROR = 0x7123cd0b, + MESSAGE_STATUS_PARSE_ERROR = 0x0b9f6189, + MESSAGE_STATUS_NULL_POINTER_ERROR = 0x2d66837a, + MESSAGE_STATUS_API_VALUE_ERROR = 0x6ba34f47, + MESSAGE_STATUS_END_OF_MESSAGE_ERROR = 0x798db72a, + MESSAGE_STATUS_INVALID_ENUM_VALUE = 0x7db88197, + MESSAGE_STATUS_INVALID_TAG_ERROR = 0x14dce06a, + MESSAGE_STATUS_NOT_INITIALIZED = 0x2990b6c6, + MESSAGE_STATUS_OUT_OF_MEMORY = 0x7c5c64cc, + MESSAGE_STATUS_MAP_SHARED_MEMORY_FAILED = 0x7afecacf, + MESSAGE_STATUS_SECURE_BUFFER_ERROR = 0x78f0e873 +} ODK_MessageStatus; + +/* + * Create a message structure that references a separate data buffer. An + * initialized message is returned. The caller is responsible for ensuring that + * the buffer remains allocated for the lifetime of the message. If |buffer| + * is NULL or |capacity| is zero, the message is invalid and the status + * will be set to MESSAGE_STATUS_NOT_INITIALIZED. + */ +ODK_Message ODK_Message_Create(uint8_t* buffer, size_t capacity); + +/* + * Erase the contents of the message, set it to an empty state by setting the + * message size and read offset to 0, effectively erasing the contents of the + * message. The message data buffer pointer remains unchanged, i.e. the message + * retains ownership of the buffer. The message status is reset to + * MESSAGE_STATUS_OK. + */ +void ODK_Message_Clear(ODK_Message* message); + +/* + * Reset read pointer to the beginning of the message and clear status + * so that parsing of the message will restart at the beginning of the + * message. The message status is reset to MESSAGE_STATUS_OK. + */ +void ODK_Message_Reset(ODK_Message* message); + +/* + * Return a pointer to the message data buffer, i.e. the message payload. + * This is the buffer address that was passed into ODK_Message_Create. + */ +uint8_t* ODK_Message_GetBase(ODK_Message* message); + +/* + * Get the maximum number of bytes the message can hold. + */ +size_t ODK_Message_GetCapacity(ODK_Message* message); + +/* + * Get the number of bytes currently in the message + */ +size_t ODK_Message_GetSize(ODK_Message* message); + +/* + * Get the offset of where the next bytes will be read from the message data + * buffer. + */ +size_t ODK_Message_GetOffset(ODK_Message* message); + +/* + * Return the status of the message + */ +ODK_MessageStatus ODK_Message_GetStatus(ODK_Message* message); + +/* + * Set the message status to a specific value + */ +void ODK_Message_SetStatus(ODK_Message* message, ODK_MessageStatus status); + +/* + * Set the size of the message to a value. This may be needed after writing data + * into the message data buffer. + */ +void ODK_Message_SetSize(ODK_Message* message, size_t size); + +/* + * Test if the integrity of a message. This means that the status must be + * MESSAGE_STATUS_OK and that the internal fields of the message are + * within the range of valid values. + */ +bool ODK_Message_IsValid(ODK_Message* message); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WIDEVINE_ODK_INCLUDE_ODK_MESSAGE_H_ diff --git a/oemcrypto/odk/include/odk_structs.h b/oemcrypto/odk/include/odk_structs.h new file mode 100644 index 00000000..fba3c3aa --- /dev/null +++ b/oemcrypto/odk/include/odk_structs.h @@ -0,0 +1,228 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_target.h" + +/* The version of this library. */ +#define ODK_MAJOR_VERSION 17 +#define ODK_MINOR_VERSION 1 + +/* ODK Version string. Date changed automatically on each release. */ +#define ODK_RELEASE_DATE "ODK v17.1 2022-06-17" + +/* The lowest version number for an ODK message. */ +#define ODK_FIRST_VERSION 16 + +/* Some useful constants. */ +#define ODK_DEVICE_ID_LEN_MAX 64 +#define ODK_SHA256_HASH_SIZE 32 +#define ODK_KEYBOX_RENEWAL_DATA_SIZE 1600 + +/// @addtogroup odk_timer +/// @{ + +/** + * Timer limits are specified in a license and are used to determine when + * playback is allowed. See the document "License Duration and Renewal" for a + * discussion on the time restrictions that may be placed on a license. The + * fields in this structure are directly related to the fields in the core + * license message. The fields are set when OEMCrypto calls the function + * ODK_ParseLicense or ODK_InitializeV15Values. + * + * @param soft_enforce_rental_duration: A boolean controlling the soft or hard + * enforcement of rental duration. + * @param soft_enforce_playback_duration: A boolean controlling the soft or hard + * enforcement of playback duration. + * @param earliest_playback_start_seconds: The earliest time that the first + * playback is allowed. Measured in seconds since the license request was + * signed. For most use cases, this is zero. + * @param rental_duration_seconds: Window of time for the allowed first + * playback. Measured in seconds since the earliest playback start. If + * soft_enforce_rental_duration is true, this applies only to the first + * playback. If soft_enforce_rental_duration is false, then this + * restricts any playback. A value of zero means no limit. + * @param total_playback_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. If + * soft_enforce_playback_duration is true, this applies only to the start + * of playback for any session. If soft_enforce_playback_duration is + * false, then this restricts any playback. A value of zero means no + * limit. + * @param initial_renewal_duration_seconds: Window of time for allowed playback. + * Measured in seconds since the first playback start. This value is only + * used to start the renewal timer. After a renewal message is loaded, + * the timer will be reset. A value of zero means no limit. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + bool soft_enforce_rental_duration; + bool soft_enforce_playback_duration; + uint64_t earliest_playback_start_seconds; + uint64_t rental_duration_seconds; + uint64_t total_playback_duration_seconds; + uint64_t initial_renewal_duration_seconds; +} ODK_TimerLimits; + +/** + * Clock values are modified when decryption occurs or when a renewal is + * processed. They are used to track the current status of the license -- + * i.e. has playback started? When does the timer expire? See the section + * "Complete ODK API" of the document "Widevine Core Message Serialization" + * for a complete list of all fields in this structure. Most of these values + * shall be saved with the usage entry. + * + * All times are in seconds. Most of the fields in this structure are saved + * in the usage entry. This structure should be initialized when a usage + * entry is created or loaded, and should be used to save a usage entry. It + * is updated using the ODK functions listed below. The time values are based + * on OEMCrypto's system clock, as described in the document "License + * Duration and Renewal". + * + * @param time_of_license_request_signed: Time that the license request was + * signed, based on OEMCrypto's system clock. This value shall be stored + * and reloaded with usage entry as time_of_license_received. + * @param time_of_first_decrypt: Time of the first decrypt or call select key, + * based on OEMCrypto's system clock. This is 0 if the license has not + * been used to decrypt any data. This value shall be stored and reloaded + * with usage entry. + * @param time_of_last_decrypt: Time of the most recent decrypt call, based on + * OEMCrypto's system clock. This value shall be stored and reloaded with + * usage entry. + * @param time_of_renewal_request: Time of the most recent renewal request, + * based on OEMCrypto's system clock. This is used to verify that a + * renewal is not stale. + * @param time_when_timer_expires: Time that the current timer expires, based on + * OEMCrypto's system clock. If the timer is active, this is used by the + * ODK library to determine if it has expired. + * @param timer_status: Used internally by the ODK library to indicate the + * current timer status. + * @param status: The license or usage entry status. This value shall be stored + * and reloaded with usage entry. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + uint64_t time_of_license_request_signed; + uint64_t time_of_first_decrypt; + uint64_t time_of_last_decrypt; + uint64_t time_of_renewal_request; + uint64_t time_when_timer_expires; + uint32_t timer_status; + enum OEMCrypto_Usage_Entry_Status status; +} ODK_ClockValues; + +/** + * Nonce values are used to match a license or provisioning request to a + * license or provisioning response. They are also used to match a renewal + * request and response to a license. For this reason, the api_version might + * be lower than that supported by OEMCrypto. The api_version matches the + * version of the license. Similarly the nonce and session_id match the + * session that generated the license request. For an offline license, these + * might not match the session that is loading the license. We use the nonce + * to prevent a license from being replayed. By also including a session_id + * in the license request and license response, we prevent an attack using + * the birthday paradox to generate nonce collisions on a single device. + * + * @param api_major_version: the API version of the license. This is initialized + * to the API version of the ODK library, but may be lower. + * @param api_minor_version: the minor version of the ODK library. This is used + * by the server to verify that device is not using an obsolete version + * of the ODK library. + * @param nonce: a randomly generated number used to prevent replay attacks. + * @param session_id: the session id of the session which signed the license or + * provisioning request. It is used to prevent replay attacks from one + * session to another. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + uint16_t api_minor_version; + uint16_t api_major_version; + uint32_t nonce; + uint32_t session_id; +} ODK_NonceValues; + +/// @} + +/// @addtogroup odk_parser +/// @{ + +/** + * The parsed license structure contains information from the license + * message. The function ODK_ParseLicense will fill in the fields of this + * message. All substrings are contained within the message body. + * + * @param enc_mac_keys_iv: IV for decrypting new mac_key. Size is 128 bits. + * @param enc_mac_keys: encrypted mac_keys for generating new mac_keys. Size is + * 512 bits. + * @param pst: the Provider Session Token. + * @param srm_restriction_data: optional data specifying the minimum SRM + * version. + * @param license_type: specifies if the license contains content keys or + * 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 key_array_length: number of keys present. + * @param key_array: set of keys to be installed. + * + * @version + * This struct changed in API version 17. + */ +typedef struct { + OEMCrypto_Substring enc_mac_keys_iv; + OEMCrypto_Substring enc_mac_keys; + OEMCrypto_Substring pst; + OEMCrypto_Substring srm_restriction_data; + OEMCrypto_LicenseType license_type; + bool nonce_required; + ODK_TimerLimits timer_limits; + uint32_t watermarking; + OEMCrypto_DTCP2_CMI_Packet dtcp2_required; + uint32_t key_array_length; + OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; +} ODK_ParsedLicense; + +/** + * The parsed provisioning structure contains information from the license + * message. The function ODK_ParseProvisioning will fill in the fields of + * this message. All substrings are contained within the message body. + * + * @param key_type: indicates if this key is an RSA or ECC private key. + * @param enc_private_key: encrypted private key for the DRM certificate. + * @param enc_private_key_iv: IV for decrypting new private key. Size is 128 + * bits. + * @param encrypted_message_key: used for provisioning 3.0 to derive keys. + * + * @version + * This struct changed in API version 16.2. + */ +typedef struct { + OEMCrypto_PrivateKeyType key_type; + OEMCrypto_Substring enc_private_key; + OEMCrypto_Substring enc_private_key_iv; + OEMCrypto_Substring encrypted_message_key; /* Used for Prov 3.0 */ +} ODK_ParsedProvisioning; + +/// @} + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WIDEVINE_ODK_INCLUDE_ODK_STRUCTS_H_ diff --git a/oemcrypto/odk/include/odk_target.h b/oemcrypto/odk/include/odk_target.h new file mode 100644 index 00000000..d1c0652d --- /dev/null +++ b/oemcrypto/odk/include/odk_target.h @@ -0,0 +1,13 @@ +// Copyright 2019 Google LLC. All rights reserved. This file is distributed +// under the Widevine License Agreement. + +// Partners are expected to edit this file to support target specific code +// and limits. + +#ifndef WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ +#define WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ + +// Maximum number of keys can be modified to suit target's resource tier. +#define ODK_MAX_NUM_KEYS 32 + +#endif // WIDEVINE_ODK_INCLUDE_ODK_TARGET_H_ diff --git a/oemcrypto/odk/src/core_message_deserialize.cpp b/oemcrypto/odk/src/core_message_deserialize.cpp new file mode 100644 index 00000000..2e69641d --- /dev/null +++ b/oemcrypto/odk/src/core_message_deserialize.cpp @@ -0,0 +1,181 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "core_message_deserialize.h" + +#include +#include +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace deserialize { +namespace { + +/** + * Template for parsing requests + * + * Template arguments: + * S: kdo output struct + * T: struct serialized by odk + * U: auto-generated deserializing function for |T| + */ +template +bool ParseRequest(uint32_t message_type, + const std::string& oemcrypto_core_message, S* core_request, + T* prepared, const U unpacker) { + if (core_request == nullptr || prepared == nullptr) { + return false; + } + + const uint8_t* buf = + reinterpret_cast(oemcrypto_core_message.c_str()); + const size_t buf_length = oemcrypto_core_message.size(); + + ODK_Message msg = ODK_Message_Create(const_cast(buf), buf_length); + ODK_Message_SetSize(&msg, buf_length); + + unpacker(&msg, prepared); + if (!ODK_Message_IsValid(&msg)) { + return false; + } + + const auto& core_message = prepared->core_message; + core_request->api_major_version = core_message.nonce_values.api_major_version; + core_request->api_minor_version = core_message.nonce_values.api_minor_version; + core_request->nonce = core_message.nonce_values.nonce; + core_request->session_id = core_message.nonce_values.session_id; + + // Verify that the minor version matches the released version for the given + // major version. + if (core_request->api_major_version < ODK_FIRST_VERSION) { + // Non existing versions are not supported. + return false; + } else if (core_request->api_major_version == 16) { + // For version 16, we demand a minor version of at least 2. + // We accept 16.2, 16.3, or higher. + if (core_request->api_minor_version < 2) return false; + } else { + // Other versions do not (yet) have a restriction on minor number. + // In particular, future versions are accepted for forward compatibility. + } + // For v16, a release and a renewal use the same message structure. + // However, for future API versions, the release might be a separate + // message. Otherwise, we expect an exact match of message types. + // A provisioning request may contain a renewed provisioning message. + if (message_type != ODK_Common_Request_Type && + core_message.message_type != message_type && + !(message_type == ODK_Renewal_Request_Type && + core_message.message_type == ODK_Release_Request_Type) && + !(message_type == ODK_Provisioning_Request_Type && + core_message.message_type == ODK_Renewed_Provisioning_Request_Type)) { + return false; + } + // Verify that the amount of buffer we read, which is GetOffset, is not more + // than the total message size. We allow the total message size to be larger + // for forward compatibility because future messages might have extra fields + // that we can ignore. + if (core_message.message_length < ODK_Message_GetOffset(&msg)) return false; + return true; +} + +} // namespace + +bool CoreLicenseRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_LicenseRequest* core_license_request) { + const auto unpacker = Unpack_ODK_PreparedLicenseRequest; + ODK_PreparedLicenseRequest prepared_license = {}; + return ParseRequest(ODK_License_Request_Type, oemcrypto_core_message, + core_license_request, &prepared_license, unpacker); +} + +bool CoreRenewalRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request) { + const auto unpacker = Unpack_ODK_PreparedRenewalRequest; + ODK_PreparedRenewalRequest prepared_renewal = {}; + if (!ParseRequest(ODK_Renewal_Request_Type, oemcrypto_core_message, + core_renewal_request, &prepared_renewal, unpacker)) { + return false; + } + core_renewal_request->playback_time_seconds = prepared_renewal.playback_time; + return true; +} + +bool CoreProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + const auto unpacker = Unpack_ODK_PreparedProvisioningRequest; + ODK_PreparedProvisioningRequest prepared_provision = {}; + if (!ParseRequest(ODK_Provisioning_Request_Type, oemcrypto_core_message, + core_provisioning_request, &prepared_provision, unpacker)) { + return false; + } + const uint8_t* device_id = prepared_provision.device_id; + const uint32_t device_id_length = prepared_provision.device_id_length; + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return false; + } + uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {}; + if (memcmp(zero, device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + return false; + } + core_provisioning_request->device_id.assign( + reinterpret_cast(device_id), device_id_length); + core_provisioning_request->renewal_type = OEMCrypto_NoRenewal; + core_provisioning_request->renewal_data.clear(); + return true; +} + +bool CoreRenewedProvisioningRequestFromMessage( + const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + const auto unpacker = Unpack_ODK_PreparedRenewedProvisioningRequest; + ODK_PreparedRenewedProvisioningRequest prepared_provision = {}; + if (!ParseRequest(ODK_Renewed_Provisioning_Request_Type, + oemcrypto_core_message, core_provisioning_request, + &prepared_provision, unpacker)) { + return false; + } + const uint8_t* device_id = prepared_provision.device_id; + const uint32_t device_id_length = prepared_provision.device_id_length; + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return false; + } + uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {}; + if (memcmp(zero, device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length)) { + return false; + } + core_provisioning_request->device_id.assign( + reinterpret_cast(device_id), device_id_length); + + if (prepared_provision.renewal_data_length > + sizeof(prepared_provision.renewal_data)) { + return false; + } + core_provisioning_request->renewal_type = OEMCrypto_RenewalACert; + core_provisioning_request->renewal_data.assign( + reinterpret_cast(prepared_provision.renewal_data), + prepared_provision.renewal_data_length); + return true; +} + +bool CoreCommonRequestFromMessage(const std::string& oemcrypto_core_message, + ODK_CommonRequest* common_request) { + const auto unpacker = Unpack_ODK_PreparedCommonRequest; + ODK_PreparedCommonRequest prepared_common = {}; + return ParseRequest(ODK_Common_Request_Type, oemcrypto_core_message, + common_request, &prepared_common, unpacker); +} + +} // namespace deserialize +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/src/core_message_features.cpp b/oemcrypto/odk/src/core_message_features.cpp new file mode 100644 index 00000000..615e4779 --- /dev/null +++ b/oemcrypto/odk/src/core_message_features.cpp @@ -0,0 +1,41 @@ +// Copyright 2021 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "core_message_features.h" + +namespace oemcrypto_core_message { +namespace features { +const CoreMessageFeatures CoreMessageFeatures::kDefaultFeatures; + +bool CoreMessageFeatures::operator==(const CoreMessageFeatures &other) const { + return maximum_major_version == other.maximum_major_version && + maximum_minor_version == other.maximum_minor_version; +} + +CoreMessageFeatures CoreMessageFeatures::DefaultFeatures( + uint32_t maximum_major_version) { + CoreMessageFeatures features; + features.maximum_major_version = maximum_major_version; + // The default minor version is the highest for each major version. + switch (maximum_major_version) { + case 16: + features.maximum_minor_version = 5; // 16.5 + break; + case 17: + features.maximum_minor_version = 1; // 17.1 + break; + default: + features.maximum_minor_version = 0; + } + return features; +} + +std::ostream &operator<<(std::ostream &os, + const CoreMessageFeatures &features) { + return os << "v" << features.maximum_major_version << "." + << features.maximum_minor_version; +} + +} // namespace features +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/src/core_message_serialize.cpp b/oemcrypto/odk/src/core_message_serialize.cpp new file mode 100644 index 00000000..3c3590ef --- /dev/null +++ b/oemcrypto/odk/src/core_message_serialize.cpp @@ -0,0 +1,204 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "core_message_serialize.h" + +#include +#include +#include +#include +#include + +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "odk_target.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace serialize { +namespace { + +/** + * Template for copying nonce values from request to response, and also + * computing the API version of the response. + * + * Template arguments: + * T: struct to be deserialized by odk + * S: kdo input struct + */ +template +bool CreateResponseHeader(const CoreMessageFeatures& features, + ODK_MessageType message_type, const S& core_request, + T& response) { + // Bad major version. + if ((features.maximum_major_version > ODK_MAJOR_VERSION) || + (features.maximum_major_version == ODK_MAJOR_VERSION && + features.maximum_minor_version > ODK_MINOR_VERSION)) { + // TODO(b/147513335): this should be logged. + return false; + } + + auto* header = &response.request.core_message; + header->message_type = message_type; + header->nonce_values.api_major_version = core_request.api_major_version; + header->nonce_values.api_minor_version = core_request.api_minor_version; + header->nonce_values.nonce = core_request.nonce; + header->nonce_values.session_id = core_request.session_id; + // The message API version for the response is the minimum of our version and + // the request's version. + if (core_request.api_major_version > features.maximum_major_version) { + header->nonce_values.api_major_version = features.maximum_major_version; + header->nonce_values.api_minor_version = features.maximum_minor_version; + } else if (core_request.api_major_version == features.maximum_major_version && + core_request.api_minor_version > features.maximum_minor_version) { + header->nonce_values.api_minor_version = features.maximum_minor_version; + } + return true; +} + +/** + * Template for parsing requests and packing response + * + * Template arguments: + * T: struct to be deserialized by odk + * S: kdo input struct + * P: auto-generated serializing function for |T| + */ +template +bool CreateResponse(ODK_MessageType message_type, const S& core_request, + std::string* oemcrypto_core_message, T& response, + const P& packer) { + if (!oemcrypto_core_message) { + return false; + } + auto* header = &response.request.core_message; + if (header->message_type != message_type || + header->nonce_values.api_major_version < ODK_FIRST_VERSION) { + // This indicates CreateResponseHeader was not called. + return false; + } + + static constexpr size_t BUF_CAPACITY = 2048; + std::vector buf(BUF_CAPACITY, 0); + ODK_Message msg = ODK_Message_Create(buf.data(), buf.capacity()); + packer(&msg, &response); + if (!ODK_Message_IsValid(&msg)) { + return false; + } + + uint32_t message_length = static_cast(ODK_Message_GetSize(&msg)); + msg = ODK_Message_Create(buf.data() + sizeof(header->message_type), + sizeof(header->message_length)); + Pack_uint32_t(&msg, &message_length); + oemcrypto_core_message->assign(reinterpret_cast(buf.data()), + message_length); + return true; +} + +bool CopyDeviceId(const ODK_ProvisioningRequest& src, + ODK_ProvisioningResponse* dest) { + auto& request = dest->request; + const std::string& device_id = src.device_id; + if (request.device_id_length > sizeof(request.device_id)) { + return false; + } + request.device_id_length = static_cast(device_id.size()); + memset(request.device_id, 0, sizeof(request.device_id)); + memcpy(request.device_id, device_id.data(), request.device_id_length); + return true; +} + +} // namespace + +bool CreateCoreLicenseResponse(const CoreMessageFeatures& features, + const ODK_ParsedLicense& parsed_lic, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + std::string* oemcrypto_core_message) { + ODK_LicenseResponse license_response{ + {}, const_cast(&parsed_lic)}; + if (!CreateResponseHeader(features, ODK_License_Response_Type, core_request, + license_response)) { + return false; + } + if (ODK_MAX_NUM_KEYS < license_response.parsed_license->key_array_length) { + return false; + } + if (license_response.request.core_message.nonce_values.api_major_version == + 16) { + ODK_LicenseResponseV16 license_response_v16; + license_response_v16.request = license_response.request; + license_response_v16.parsed_license.enc_mac_keys_iv = + license_response.parsed_license->enc_mac_keys_iv; + license_response_v16.parsed_license.enc_mac_keys = + license_response.parsed_license->enc_mac_keys; + license_response_v16.parsed_license.pst = + license_response.parsed_license->pst; + license_response_v16.parsed_license.srm_restriction_data = + license_response.parsed_license->srm_restriction_data; + license_response_v16.parsed_license.license_type = + license_response.parsed_license->license_type; + license_response_v16.parsed_license.nonce_required = + license_response.parsed_license->nonce_required; + license_response_v16.parsed_license.timer_limits = + license_response.parsed_license->timer_limits; + license_response_v16.parsed_license.key_array_length = + license_response.parsed_license->key_array_length; + uint32_t i; + for (i = 0; i < license_response_v16.parsed_license.key_array_length && + i < license_response.parsed_license->key_array_length; + i++) { + license_response_v16.parsed_license.key_array[i] = + license_response.parsed_license->key_array[i]; + } + if (core_request_sha256.size() != sizeof(license_response_v16.request_hash)) + return false; + memcpy(license_response_v16.request_hash, core_request_sha256.data(), + sizeof(license_response_v16.request_hash)); + return CreateResponse(ODK_License_Response_Type, core_request, + oemcrypto_core_message, license_response_v16, + Pack_ODK_LicenseResponseV16); + } + return CreateResponse(ODK_License_Response_Type, core_request, + oemcrypto_core_message, license_response, + Pack_ODK_LicenseResponse); +} + +bool CreateCoreRenewalResponse(const CoreMessageFeatures& features, + const ODK_RenewalRequest& core_request, + uint64_t renewal_duration_seconds, + std::string* oemcrypto_core_message) { + ODK_RenewalResponse renewal_response{{}, core_request.playback_time_seconds}; + renewal_response.request.playback_time = core_request.playback_time_seconds; + renewal_response.renewal_duration_seconds = renewal_duration_seconds; + if (!CreateResponseHeader(features, ODK_Renewal_Response_Type, core_request, + renewal_response)) { + return false; + } + return CreateResponse(ODK_Renewal_Response_Type, core_request, + oemcrypto_core_message, renewal_response, + Pack_ODK_RenewalResponse); +} + +bool CreateCoreProvisioningResponse(const CoreMessageFeatures& features, + const ODK_ParsedProvisioning& parsed_prov, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + ODK_ProvisioningResponse prov_response{ + {}, const_cast(&parsed_prov)}; + if (!CopyDeviceId(core_request, &prov_response)) { + return false; + } + if (!CreateResponseHeader(features, ODK_Provisioning_Response_Type, + core_request, prov_response)) { + return false; + } + return CreateResponse(ODK_Provisioning_Response_Type, core_request, + oemcrypto_core_message, prov_response, + Pack_ODK_ProvisioningResponse); +} + +} // namespace serialize +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/src/core_message_serialize_proto.cpp b/oemcrypto/odk/src/core_message_serialize_proto.cpp new file mode 100644 index 00000000..5132bda2 --- /dev/null +++ b/oemcrypto/odk/src/core_message_serialize_proto.cpp @@ -0,0 +1,198 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "core_message_serialize_proto.h" + +#include +#include +#include +#include +#include + +#include "core_message_serialize.h" +#include "license_protocol.pb.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "serialization_base.h" + +namespace oemcrypto_core_message { +namespace serialize { +namespace { +using oemcrypto_core_message::features::CoreMessageFeatures; + +/* @ private functions */ + +/** + * Extract OEMCrypto_Substring (offset, length) from serialized protobuf + * + * Parameters: + * message: serialized license protobuf + * field: substring value + */ +OEMCrypto_Substring GetOecSubstring(const std::string& message, + const std::string& field) { + OEMCrypto_Substring substring = {}; + size_t pos = message.find(field); + if (pos != std::string::npos) { + substring = OEMCrypto_Substring{pos, field.length()}; + } + return substring; +} + +OEMCrypto_KeyObject KeyContainerToOecKey( + const std::string& proto, const video_widevine::License::KeyContainer& k, + const bool uses_padding) { + OEMCrypto_KeyObject obj = {}; + obj.key_id = GetOecSubstring(proto, k.id()); + obj.key_data_iv = GetOecSubstring(proto, k.iv()); + + OEMCrypto_Substring key_data = GetOecSubstring(proto, k.key()); + + // Strip off PKCS#5 padding. A key can either be 16 of 32 bytes, but that + // makes it hard to know if a key (when 32 bytes) is a 16 byte key with + // padding or a 32 byte key without padding. + if (uses_padding) { + const size_t PKCS5_PADDING_SIZE = 16; + key_data.length -= PKCS5_PADDING_SIZE; + } + obj.key_data = key_data; + + if (k.has_key_control()) { + const auto& key_control = k.key_control(); + obj.key_control_iv = GetOecSubstring(proto, key_control.iv()); + obj.key_control = GetOecSubstring(proto, key_control.key_control_block()); + } + return obj; +} + +} // namespace + +// @ public create response functions + +bool CreateCoreLicenseResponseFromProto(const CoreMessageFeatures& features, + const std::string& serialized_license, + const ODK_LicenseRequest& core_request, + const std::string& core_request_sha256, + const bool nonce_required, + const bool uses_padding, + std::string* oemcrypto_core_message) { + video_widevine::License lic; + if (!lic.ParseFromString(serialized_license)) { + return false; + } + + ODK_ParsedLicense parsed_lic{}; + bool any_content = false; + bool any_entitlement = false; + + for (int i = 0; i < lic.key_size(); ++i) { + const auto& k = lic.key(i); + switch (k.type()) { + case video_widevine::License_KeyContainer::SIGNING: { + if (!k.has_key()) { + continue; + } + parsed_lic.enc_mac_keys_iv = + GetOecSubstring(serialized_license, k.iv()); + parsed_lic.enc_mac_keys = GetOecSubstring(serialized_license, k.key()); + break; + } + case video_widevine::License_KeyContainer::CONTENT: + case video_widevine::License_KeyContainer::OPERATOR_SESSION: + case video_widevine::License_KeyContainer::OEM_CONTENT: + case video_widevine::License_KeyContainer::OEM_ENTITLEMENT: + case video_widevine::License_KeyContainer::ENTITLEMENT: { + if (k.type() == video_widevine::License_KeyContainer::ENTITLEMENT || + k.type() == video_widevine::License_KeyContainer::OEM_ENTITLEMENT) { + any_entitlement = true; + } else { + any_content = true; + } + if (parsed_lic.key_array_length >= ODK_MAX_NUM_KEYS) { + return false; + } + uint32_t& n = parsed_lic.key_array_length; + parsed_lic.key_array[n++] = + KeyContainerToOecKey(serialized_license, k, uses_padding); + break; + } + default: { + continue; + } + } + } + if (any_content && any_entitlement) { + // TODO(b/147513335): this should be logged -- both type of keys. + return false; + } + if (!any_content && !any_entitlement) { + // TODO(b/147513335): this should be logged -- no keys? + return false; + } + parsed_lic.license_type = + any_content ? OEMCrypto_ContentLicense : OEMCrypto_EntitlementLicense; + const auto& lid = lic.id(); + if (lid.has_provider_session_token()) { + parsed_lic.pst = + GetOecSubstring(serialized_license, lid.provider_session_token()); + } + if (lic.has_srm_requirement()) { + parsed_lic.srm_restriction_data = + GetOecSubstring(serialized_license, lic.srm_requirement()); + } + if (lic.policy().has_watermarking_control()) { + parsed_lic.watermarking = lic.policy().watermarking_control(); + } + parsed_lic.nonce_required = nonce_required; + const auto& policy = lic.policy(); + ODK_TimerLimits& timer_limits = parsed_lic.timer_limits; + timer_limits.soft_enforce_rental_duration = + policy.soft_enforce_rental_duration(); + timer_limits.soft_enforce_playback_duration = + policy.soft_enforce_playback_duration(); + timer_limits.earliest_playback_start_seconds = 0; + timer_limits.rental_duration_seconds = policy.rental_duration_seconds(); + timer_limits.total_playback_duration_seconds = + policy.playback_duration_seconds(); + timer_limits.initial_renewal_duration_seconds = + policy.renewal_delay_seconds() + + policy.renewal_recovery_duration_seconds(); + + return CreateCoreLicenseResponse(features, parsed_lic, core_request, + core_request_sha256, oemcrypto_core_message); +} + +bool CreateCoreProvisioningResponseFromProto( + const CoreMessageFeatures& features, + const std::string& serialized_provisioning_resp, + const ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + ODK_ParsedProvisioning parsed_prov{}; + video_widevine::ProvisioningResponse prov; + if (!prov.ParseFromString(serialized_provisioning_resp)) { + return false; + } + + parsed_prov.key_type = + OEMCrypto_RSA_Private_Key; // TODO(b/148404408): ECC or RSA + if (prov.has_device_rsa_key()) { + parsed_prov.enc_private_key = + GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key()); + } + if (prov.has_device_rsa_key_iv()) { + parsed_prov.enc_private_key_iv = + GetOecSubstring(serialized_provisioning_resp, prov.device_rsa_key_iv()); + } + if (prov.has_wrapping_key()) { + parsed_prov.encrypted_message_key = + GetOecSubstring(serialized_provisioning_resp, prov.wrapping_key()); + } + + return CreateCoreProvisioningResponse(features, parsed_prov, core_request, + oemcrypto_core_message); +} + +} // namespace serialize +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/src/kdo.gypi b/oemcrypto/odk/src/kdo.gypi new file mode 100644 index 00000000..6e77b456 --- /dev/null +++ b/oemcrypto/odk/src/kdo.gypi @@ -0,0 +1,19 @@ +# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. + +# These files are used by the server and by some ODK test code. These files are +# not built into the ODK library on the device. +{ + 'sources': [ + 'core_message_deserialize.cpp', + 'core_message_features.cpp', + 'core_message_serialize.cpp', + 'core_message_serialize_proto.cpp', + ], + 'include_dirs': [ + 'src', + '../include', + ], +} + diff --git a/oemcrypto/odk/src/odk.c b/oemcrypto/odk/src/odk.c new file mode 100644 index 00000000..4f283898 --- /dev/null +++ b/oemcrypto/odk/src/odk.c @@ -0,0 +1,511 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "odk.h" + +#include +#include +#include + +#include "odk_overflow.h" +#include "odk_serialize.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" +#include "odk_util.h" +#include "serialization_base.h" + +/* @ private odk functions */ + +static OEMCryptoResult ODK_PrepareRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + ODK_MessageType message_type, const ODK_NonceValues* nonce_values, + void* prepared_request_buffer, size_t prepared_request_buffer_length) { + if (nonce_values == NULL || core_message_length == NULL || + prepared_request_buffer == NULL || + *core_message_length > message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_Message msg = ODK_Message_Create(message, *core_message_length); + + /* The core message should be at the beginning of the buffer, and with a + * shorter length. */ + if (sizeof(ODK_CoreMessage) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_CoreMessage* core_message = (ODK_CoreMessage*)prepared_request_buffer; + *core_message = (ODK_CoreMessage){ + message_type, + 0, + *nonce_values, + }; + + /* Set core message length, and pack prepared request into message if the + * message buffer has been correctly initialized by the caller. */ + switch (message_type) { + case ODK_License_Request_Type: { + core_message->message_length = ODK_LICENSE_REQUEST_SIZE; + if (sizeof(ODK_PreparedLicenseRequest) > prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedLicenseRequest( + &msg, (ODK_PreparedLicenseRequest*)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) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedRenewalRequest( + &msg, (ODK_PreparedRenewalRequest*)prepared_request_buffer); + break; + } + case ODK_Provisioning_Request_Type: { + core_message->message_length = ODK_PROVISIONING_REQUEST_SIZE; + if (sizeof(ODK_PreparedProvisioningRequest) > + prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedProvisioningRequest( + &msg, (ODK_PreparedProvisioningRequest*)prepared_request_buffer); + break; + } + case ODK_Renewed_Provisioning_Request_Type: { + core_message->message_length = ODK_RENEWED_PROVISIONING_REQUEST_SIZE; + if (sizeof(ODK_PreparedRenewedProvisioningRequest) > + prepared_request_buffer_length) { + return ODK_ERROR_CORE_MESSAGE; + } + Pack_ODK_PreparedRenewedProvisioningRequest( + &msg, + (ODK_PreparedRenewedProvisioningRequest*)prepared_request_buffer); + break; + } + default: { + return ODK_ERROR_CORE_MESSAGE; + } + } + + *core_message_length = core_message->message_length; + if (ODK_Message_GetStatus(&msg) != MESSAGE_STATUS_OK) { + /* This is to indicate the caller that the core_message_length has been + * appropriately set, but the message buffer is either empty or too small, + * which needs to be initialized and filled in the subsequent call. */ + return OEMCrypto_ERROR_SHORT_BUFFER; + } + if (ODK_Message_GetSize(&msg) != *core_message_length) { + /* This should not happen. Something is wrong. */ + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +/* Parse the core message and verify that it has the right type. The nonce + * values are updated to hold the response's API version. + */ +static OEMCryptoResult ODK_ParseCoreHeader(const uint8_t* message, + size_t message_length, + size_t core_message_length, + ODK_MessageType message_type, + ODK_NonceValues* nonce_values) { + // The core_message_length is the length of the core message, which is a + // substring of the complete message. + if (message == NULL || core_message_length > message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_CoreMessage core_message; + ODK_Message msg = ODK_Message_Create((uint8_t*)message, message_length); + + /* The core message should be at the beginning of the buffer. The core message + * is the part we are parsing. */ + ODK_Message_SetSize(&msg, core_message_length); + Unpack_ODK_CoreMessage(&msg, &core_message); + + if (ODK_Message_GetStatus(&msg) != MESSAGE_STATUS_OK || + message_type != core_message.message_type) { + return ODK_ERROR_CORE_MESSAGE; + } + // The current offset should be the end of the header, which is the message + // type, message length, api version, and nonce fields. The header can't be + // larger than the whole core message. Also, the core message specifies its + // length, which should be exactly the length of the core message buffer. + if (ODK_Message_GetOffset(&msg) > core_message.message_length || + core_message.message_length != core_message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + /* We do not support future API version. Also, this function should not be + * used for legacy licenses without a core message. */ + if (core_message.nonce_values.api_major_version > ODK_MAJOR_VERSION || + core_message.nonce_values.api_major_version < ODK_FIRST_VERSION) { + return ODK_UNSUPPORTED_API; + } + if (nonce_values) { + /* If the server sent us an older format, record the message's API version. + */ + if (nonce_values->api_major_version > + core_message.nonce_values.api_major_version) { + // If the major version is smaller, use both values from the server. + nonce_values->api_major_version = + core_message.nonce_values.api_major_version; + nonce_values->api_minor_version = + core_message.nonce_values.api_minor_version; + } else if (nonce_values->api_major_version == + core_message.nonce_values.api_major_version && + nonce_values->api_minor_version > + core_message.nonce_values.api_minor_version) { + // Otherwise, if the major versions are equal, but the minor is smaller, + // then we should lower the minor version. + nonce_values->api_minor_version = + core_message.nonce_values.api_minor_version; + } + } + return OEMCrypto_SUCCESS; +} + +/* @ public odk functions */ + +/* @@ prepare request functions */ + +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values) { + if (core_message_length == NULL || nonce_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_PreparedLicenseRequest license_request = {0}; + return ODK_PrepareRequest( + message, message_length, core_message_length, ODK_License_Request_Type, + nonce_values, &license_request, sizeof(ODK_PreparedLicenseRequest)); +} + +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (core_message_size == NULL || nonce_values == NULL || + clock_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + /* If the license has not been loaded, then this is release instead of a + * renewal. All releases use v15. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE) { + nonce_values->api_major_version = ODK_FIRST_VERSION - 1; + } + if (nonce_values->api_major_version < ODK_FIRST_VERSION) { + *core_message_size = 0; + return OEMCrypto_SUCCESS; + } + + ODK_PreparedRenewalRequest renewal_request = {0}; + /* First, we compute the time this request was made relative to the playback + * clock. */ + if (clock_values->time_of_first_decrypt == 0) { + /* It is OK to preemptively request a renewal before playback starts. + * We'll treat this as asking for a renewal at playback time 0. */ + renewal_request.playback_time = 0; + } else { + /* Otherwise, playback_time is relative to the first decrypt. */ + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_first_decrypt, + &renewal_request.playback_time)) { + return ODK_ERROR_CORE_MESSAGE; + } + } + + /* Save time for this request so that we can verify the response. This makes + * all earlier requests invalid. If preparing this request fails, then all + * requests will be bad. */ + clock_values->time_of_renewal_request = renewal_request.playback_time; + + return ODK_PrepareRequest( + message, message_length, core_message_size, ODK_Renewal_Request_Type, + nonce_values, &renewal_request, sizeof(ODK_PreparedRenewalRequest)); +} + +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length) { + if (core_message_length == NULL || nonce_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_PreparedProvisioningRequest provisioning_request = {0}; + if (device_id_length > sizeof(provisioning_request.device_id)) { + return ODK_ERROR_CORE_MESSAGE; + } + provisioning_request.device_id_length = (uint32_t)device_id_length; + if (device_id) { + memcpy(provisioning_request.device_id, device_id, device_id_length); + } + return ODK_PrepareRequest(message, message_length, core_message_length, + ODK_Provisioning_Request_Type, nonce_values, + &provisioning_request, + sizeof(ODK_PreparedProvisioningRequest)); +} + +OEMCryptoResult ODK_PrepareCoreRenewedProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, uint16_t renewal_type, const uint8_t* renewal_data, + size_t renewal_data_length) { + if (core_message_length == NULL || nonce_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + ODK_PreparedRenewedProvisioningRequest provisioning_request = {0}; + if (device_id_length > sizeof(provisioning_request.device_id)) { + return ODK_ERROR_CORE_MESSAGE; + } + provisioning_request.device_id_length = (uint32_t)device_id_length; + if (device_id) { + memcpy(provisioning_request.device_id, device_id, device_id_length); + } + if (renewal_data_length > sizeof(provisioning_request.renewal_data)) { + return ODK_ERROR_CORE_MESSAGE; + } + provisioning_request.renewal_type = renewal_type; + provisioning_request.renewal_data_length = (uint32_t)renewal_data_length; + if (renewal_data) { + memcpy(provisioning_request.renewal_data, renewal_data, + renewal_data_length); + } + return ODK_PrepareRequest(message, message_length, core_message_length, + ODK_Renewed_Provisioning_Request_Type, nonce_values, + &provisioning_request, + sizeof(provisioning_request)); +} + +/* @@ parse response functions */ + +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, ODK_ParsedLicense* parsed_license) { + if (message == NULL || timer_limits == NULL || clock_values == NULL || + nonce_values == NULL || parsed_license == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + const OEMCryptoResult err = + ODK_ParseCoreHeader(message, message_length, core_message_length, + ODK_License_Response_Type, nonce_values); + if (err != OEMCrypto_SUCCESS) { + return err; + } + + ODK_LicenseResponse license_response = {0}; + license_response.parsed_license = parsed_license; + + ODK_Message msg = ODK_Message_Create((uint8_t*)message, message_length); + ODK_Message_SetSize(&msg, core_message_length); + if (nonce_values->api_major_version == 16) { + ODK_LicenseResponseV16 license_response_v16 = {0}; + Unpack_ODK_LicenseResponseV16(&msg, &license_response_v16); + + if (ODK_Message_GetStatus(&msg) != MESSAGE_STATUS_OK || + ODK_Message_GetOffset(&msg) != core_message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + + // Need to manually set parsed_license fields to + // license_response_v16.parsed_license field values since + // license_response_v16 is no longer a pointer so parsed_license doesn't get + // updated during the unpacking. + parsed_license->enc_mac_keys_iv = + license_response_v16.parsed_license.enc_mac_keys_iv; + parsed_license->enc_mac_keys = + license_response_v16.parsed_license.enc_mac_keys; + parsed_license->pst = license_response_v16.parsed_license.pst; + parsed_license->srm_restriction_data = + license_response_v16.parsed_license.srm_restriction_data; + parsed_license->license_type = + license_response_v16.parsed_license.license_type; + parsed_license->nonce_required = + license_response_v16.parsed_license.nonce_required; + parsed_license->timer_limits = + license_response_v16.parsed_license.timer_limits; + parsed_license->key_array_length = + license_response_v16.parsed_license.key_array_length; + uint32_t i; + for (i = 0; i < parsed_license->key_array_length; i++) { + parsed_license->key_array[i] = + license_response_v16.parsed_license.key_array[i]; + } + // Set fields not used in V16 to default values. + parsed_license->watermarking = 0; + // Set fields not used in V16 to default values. + parsed_license->dtcp2_required.dtcp2_required = 0; + parsed_license->dtcp2_required.cmi_descriptor_0.id = 0; + parsed_license->dtcp2_required.cmi_descriptor_0.extension = 0; + parsed_license->dtcp2_required.cmi_descriptor_0.length = 1; + parsed_license->dtcp2_required.cmi_descriptor_0.data = 0; + parsed_license->dtcp2_required.cmi_descriptor_1.id = 1; + parsed_license->dtcp2_required.cmi_descriptor_1.extension = 0; + parsed_license->dtcp2_required.cmi_descriptor_1.length = 3; + parsed_license->dtcp2_required.cmi_descriptor_1.data[0] = 0; + parsed_license->dtcp2_required.cmi_descriptor_1.data[1] = 0; + parsed_license->dtcp2_required.cmi_descriptor_1.data[2] = 0; + parsed_license->dtcp2_required.cmi_descriptor_2.id = 2; + parsed_license->dtcp2_required.cmi_descriptor_2.extension = 0; + parsed_license->dtcp2_required.cmi_descriptor_2.length = 3; + parsed_license->dtcp2_required.cmi_descriptor_2.data[0] = 0; + parsed_license->dtcp2_required.cmi_descriptor_2.data[1] = 0; + parsed_license->dtcp2_required.cmi_descriptor_2.data[2] = 0; + license_response.request = license_response_v16.request; + } else { + Unpack_ODK_LicenseResponse(&msg, &license_response); + + if (ODK_Message_GetStatus(&msg) != MESSAGE_STATUS_OK || + ODK_Message_GetOffset(&msg) != core_message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + } + + /* If the license has a provider session token (pst), then OEMCrypto should + * have a usage entry loaded. The opposite is also an error. */ + if ((usage_entry_present && parsed_license->pst.length == 0) || + (!usage_entry_present && parsed_license->pst.length > 0)) { + return ODK_ERROR_CORE_MESSAGE; + } + + /* If this is the first time we load this license, then we verify that the + * nonce values are the correct, otherwise we copy the nonce values. If the + * nonce values are not required to be correct, then we don't know if this is + * an initial load or not. In that case, we also copy the values so that we + * can use the nonce values later for a renewal. + */ + if (parsed_license->nonce_required && initial_license_load) { + if (nonce_values->nonce != + license_response.request.core_message.nonce_values.nonce || + nonce_values->session_id != + license_response.request.core_message.nonce_values.session_id) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + } else { /* !initial_license_load, or can't tell if initial. */ + nonce_values->nonce = + license_response.request.core_message.nonce_values.nonce; + nonce_values->session_id = + license_response.request.core_message.nonce_values.session_id; + } + *timer_limits = parsed_license->timer_limits; + /* And update the clock values state. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + if (message == NULL || nonce_values == NULL || timer_limits == NULL || + clock_values == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + + const OEMCryptoResult err = + ODK_ParseCoreHeader(message, message_length, core_message_length, + ODK_Renewal_Response_Type, NULL); + if (err != OEMCrypto_SUCCESS) { + return err; + } + ODK_RenewalResponse renewal_response = {0}; + ODK_Message msg = ODK_Message_Create((uint8_t*)message, message_length); + ODK_Message_SetSize(&msg, core_message_length); + Unpack_ODK_RenewalResponse(&msg, &renewal_response); + + if (ODK_Message_GetStatus(&msg) != MESSAGE_STATUS_OK || + ODK_Message_GetOffset(&msg) != core_message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + /* always verify nonce_values for Renewal and Provisioning responses */ + if (!ODK_NonceValuesEqualExcludingVersion( + nonce_values, + &(renewal_response.request.core_message.nonce_values))) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + + /* Reference: + * Doc: License Duration and Renewal (Changes for OEMCrypto v16) + * Section: Renewal Message + */ + /* If a renewal request is lost in transit, we should throw it out and create + * a new one. We use the timestamp to make sure we have the latest request. + * We only do this if playback has already started. This allows us to reload + * an offline license and also reload a renewal before starting playback. + */ + if (clock_values->timer_status != ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED && + clock_values->time_of_renewal_request < + renewal_response.request.playback_time) { + return ODK_STALE_RENEWAL; + } + return ODK_ComputeRenewalDuration(timer_limits, clock_values, system_time, + renewal_response.renewal_duration_seconds, + timer_value); +} + +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { + if (message == NULL || nonce_values == NULL || device_id == NULL || + parsed_response == NULL) { + return ODK_ERROR_CORE_MESSAGE; + } + const OEMCryptoResult err = + ODK_ParseCoreHeader(message, message_length, core_message_length, + ODK_Provisioning_Response_Type, NULL); + if (err != OEMCrypto_SUCCESS) { + return err; + } + ODK_ProvisioningResponse provisioning_response = {0}; + provisioning_response.parsed_provisioning = parsed_response; + + if (device_id_length > ODK_DEVICE_ID_LEN_MAX) { + return ODK_ERROR_CORE_MESSAGE; + } + + ODK_Message msg = ODK_Message_Create((uint8_t*)message, message_length); + ODK_Message_SetSize(&msg, core_message_length); + Unpack_ODK_ProvisioningResponse(&msg, &provisioning_response); + if (ODK_Message_GetStatus(&msg) != MESSAGE_STATUS_OK || + ODK_Message_GetOffset(&msg) != core_message_length) { + return ODK_ERROR_CORE_MESSAGE; + } + /* always verify nonce_values for Renewal and Provisioning responses */ + if (!ODK_NonceValuesEqualExcludingVersion( + nonce_values, + &(provisioning_response.request.core_message.nonce_values))) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + + if (crypto_memcmp(device_id, provisioning_response.request.device_id, + device_id_length) != 0) { + return ODK_ERROR_CORE_MESSAGE; + } + + const uint8_t zero[ODK_DEVICE_ID_LEN_MAX] = {0}; + /* check bytes beyond device_id_length are 0 */ + if (crypto_memcmp(zero, + provisioning_response.request.device_id + device_id_length, + ODK_DEVICE_ID_LEN_MAX - device_id_length) != 0) { + return ODK_ERROR_CORE_MESSAGE; + } + + return OEMCrypto_SUCCESS; +} + +bool CheckApiVersionAtMost(const ODK_NonceValues* nonce_values, + uint16_t major_version, uint16_t minor_version) { + return nonce_values->api_major_version < major_version || + (nonce_values->api_major_version == major_version && + nonce_values->api_minor_version <= minor_version); +} diff --git a/oemcrypto/odk/src/odk.gyp b/oemcrypto/odk/src/odk.gyp new file mode 100644 index 00000000..4aa79a4b --- /dev/null +++ b/oemcrypto/odk/src/odk.gyp @@ -0,0 +1,41 @@ +# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. + +{ + 'targets': [ + { + 'toolsets' : [ 'target' ], + 'target_name': 'odk', + 'type': 'static_library', + 'standalone_static_library' : 1, + 'hard_dependency': 1, + 'include_dirs': [ + '../include', + '../../include', + ], + 'includes' : [ + 'odk.gypi', + ], + 'cflags': [ + # TODO(b/172518513): Remove this + '-Wno-error=cast-qual', + ], + 'defines': [ + # Needed for to work. + '_DEFAULT_SOURCE', + ], + 'direct_dependent_settings': { + 'defines': [ + # Needed for to work. + '_DEFAULT_SOURCE', + ], + 'include_dirs': [ + '.', + '../include', + '../../include', + ], + } + }, + ], +} diff --git a/oemcrypto/odk/src/odk.gypi b/oemcrypto/odk/src/odk.gypi new file mode 100644 index 00000000..1867605a --- /dev/null +++ b/oemcrypto/odk/src/odk.gypi @@ -0,0 +1,18 @@ +# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. + +# These files are built into the ODK library on the device. They are also used +# by the server and by test cocde. These files should compile on C99 compilers. +{ + 'sources': [ + 'odk.c', + 'odk_message.c', + 'odk_overflow.c', + 'odk_serialize.c', + 'odk_timer.c', + 'odk_util.c', + 'serialization_base.c', + ], +} + diff --git a/oemcrypto/odk/src/odk_assert.h b/oemcrypto/odk/src/odk_assert.h new file mode 100644 index 00000000..0517818b --- /dev/null +++ b/oemcrypto/odk/src/odk_assert.h @@ -0,0 +1,24 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_SRC_ODK_ASSERT_H_ +#define WIDEVINE_ODK_SRC_ODK_ASSERT_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if (__STDC_VERSION__ >= 201112L) +#include +#define odk_static_assert static_assert +#else +#define odk_static_assert(msg, e) \ + enum { odk_static_assert = 1 / (!!((msg) && (e))) }; +#endif + +#ifdef __cplusplus +} +#endif + +#endif // WIDEVINE_ODK_SRC_ODK_ASSERT_H_ diff --git a/oemcrypto/odk/src/odk_endian.h b/oemcrypto/odk/src/odk_endian.h new file mode 100644 index 00000000..1e9f50d2 --- /dev/null +++ b/oemcrypto/odk/src/odk_endian.h @@ -0,0 +1,41 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ +#define WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__linux__) || defined(__ANDROID__) +#include +#define oemcrypto_htobe16 htobe16 +#define oemcrypto_be16toh be16toh +#define oemcrypto_htobe32 htobe32 +#define oemcrypto_be32toh be32toh +#define oemcrypto_htobe64 htobe64 +#define oemcrypto_be64toh be64toh +#elif defined(__APPLE__) +#include +#define oemcrypto_htobe16 OSSwapHostToBigInt16 +#define oemcrypto_be16toh OSSwapBigToHostInt16 +#define oemcrypto_htobe32 OSSwapHostToBigInt32 +#define oemcrypto_be32toh OSSwapBigToHostInt32 +#define oemcrypto_htobe64 OSSwapHostToBigInt64 +#define oemcrypto_be64toh OSSwapBigToHostInt64 +#else /* defined(__linux__) || defined(__ANDROID__) */ +uint32_t oemcrypto_htobe16(uint16_t u16); +uint32_t oemcrypto_be16toh(uint16_t u16); +uint32_t oemcrypto_htobe32(uint32_t u32); +uint32_t oemcrypto_be32toh(uint32_t u32); +uint64_t oemcrypto_htobe64(uint64_t u64); +uint64_t oemcrypto_be64toh(uint64_t u64); +#endif /* defined(__linux__) || defined(__ANDROID__) */ + +#ifdef __cplusplus +} +#endif + +#endif // WIDEVINE_ODK_SRC_ODK_ENDIAN_H_ diff --git a/oemcrypto/odk/src/odk_message.c b/oemcrypto/odk/src/odk_message.c new file mode 100644 index 00000000..df29d231 --- /dev/null +++ b/oemcrypto/odk/src/odk_message.c @@ -0,0 +1,170 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "odk_message.h" + +#include +#include +#include + +#include "odk_message_priv.h" + +/* + * C11 defines static_assert in assert.h. If it is available, force a compile + * time error if the abstract ODK_Message struct size does not match its + * implementation. If static_assert is not available, the runtime assert in + * InitMessage will catch the mismatch at the time a message is initialized. + */ +#ifdef static_assert +static_assert( + sizeof(ODK_Message) >= sizeof(ODK_Message_Impl), + "sizeof(ODK_Message) is too small. You can increase " + "SIZE_OF_ODK_MESSAGE_IMPL in odk_message.h to make it large enough."); +#endif + +/* + * Create a message structure that references a separate data buffer. An + * initialized message is returned. The caller is responsible for ensuring that + * the buffer remains allocated for the lifetime of the message. |buffer| may be + * NULL. Serialization into a message with a NULL buffer will cause the message + * size to be incremented, but no data will be written into the message + * buffer. This is useful for calculating the amount of space a message will + * need, prior to doing the actual serialization. The buffer contents are + * unchanged by this function. + */ +ODK_Message ODK_Message_Create(uint8_t* buffer, size_t capacity) { + assert(sizeof(ODK_Message) >= sizeof(ODK_Message_Impl)); + ODK_Message message; + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)&message; + message_impl->base = buffer; + message_impl->capacity = capacity; + message_impl->size = 0; + message_impl->read_offset = 0; + message_impl->status = MESSAGE_STATUS_OK; + return message; +} + +/* + * Erase the contents of the message, set it to an empty state by setting the + * message size and read offset to 0, effectively erasing the contents of the + * message. The message data buffer pointer remains unchanged, i.e. the message + * retains ownership of the buffer. The message buffer is zero-filled. The + * message status is reset to MESSAGE_STATUS_OK. + */ +void ODK_Message_Clear(ODK_Message* message) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + message_impl->read_offset = 0; + message_impl->size = 0; + message_impl->status = MESSAGE_STATUS_OK; + if (message_impl->base) { + memset(message_impl->base, 0, message_impl->capacity); + } +} + +/* + * Reset read pointer to the beginning of the message and clear status + * so that parsing of the message will restart at the beginning of the + * message. The message status is reset to MESSAGE_STATUS_OK. + */ +void ODK_Message_Reset(ODK_Message* message) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + message_impl->read_offset = 0; + message_impl->status = MESSAGE_STATUS_OK; +} + +/* + * Return a pointer to the message data buffer, i.e. the message payload. + * This is the buffer address that was passed into ODK_Message_Create. + */ +uint8_t* ODK_Message_GetBase(ODK_Message* message) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + return message_impl->base; +} + +/* + * Get the maximum number of bytes the message can hold. + */ +size_t ODK_Message_GetCapacity(ODK_Message* message) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + return message_impl->capacity; +} + +/* + * Get the number of bytes currently in the message + */ +size_t ODK_Message_GetSize(ODK_Message* message) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + return message_impl->size; +} + +/* + * Get the offset of where the next bytes will be read from the message data + * buffer. + */ +size_t ODK_Message_GetOffset(ODK_Message* message) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + return message_impl->read_offset; +} + +/* + * Return the status of the message + */ +ODK_MessageStatus ODK_Message_GetStatus(ODK_Message* message) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + return message_impl->status; +} + +/* + * Set the message status to a specific value + */ +void ODK_Message_SetStatus(ODK_Message* message, ODK_MessageStatus status) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + /* preserve the first error */ + if (message_impl->status == MESSAGE_STATUS_OK) { + message_impl->status = status; + } +} + +/* + * Set the size of the message to a value. This may be needed after writing data + * into the message data buffer. + */ +void ODK_Message_SetSize(ODK_Message* message, size_t size) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + assert(message_impl != NULL); + assert(size <= message_impl->capacity); + message_impl->size = size; +} + +/* + * Test if the integrity of a message. This means that the status must be + * MESSAGE_STATUS_OK and that the base, read_offset, size and capacity of the + * message are within the range of valid values. The message's base pointer + * may be NULL if the buffer has not been assigned yet, that is not invalid. + */ +bool ODK_Message_IsValid(ODK_Message* message) { + assert(message); + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + if (message_impl == NULL) { + return false; + } + if (message_impl->status != MESSAGE_STATUS_OK) { + return false; + } + if (message_impl->read_offset > message_impl->capacity || + message_impl->size > message_impl->capacity || + message_impl->read_offset > message_impl->size) { + message_impl->status = MESSAGE_STATUS_OVERFLOW_ERROR; + return false; + } + return true; +} diff --git a/oemcrypto/odk/src/odk_message_priv.h b/oemcrypto/odk/src/odk_message_priv.h new file mode 100644 index 00000000..8ad5f030 --- /dev/null +++ b/oemcrypto/odk/src/odk_message_priv.h @@ -0,0 +1,41 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_SRC_ODK_MESSAGE_PRIV_H_ +#define WIDEVINE_ODK_SRC_ODK_MESSAGE_PRIV_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This file must only be included by odk_message.c and serialization_base.c. + */ + +#include +#include + +#include "odk_message.h" + +/* + * This is the implementation of a message. This structure is private, i.e. it + * should only be included by files that are allowed to modify the internals of + * a message, that being odk_message.c and serialization_base.c. To ensure + * proper alignment and message size, an ODK_Message_Impl should never be + * allocated directly, instead allocate ODK_Message and cast to ODK_Message_Impl + * because ODK_Message_Impl may be smaller than ODK_Message. + */ +typedef struct { + uint8_t* base; + size_t capacity; + size_t size; + size_t read_offset; + ODK_MessageStatus status; +} ODK_Message_Impl; + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WIDEVINE_ODK_SRC_ODK_MESSAGE_PRIV_H_ diff --git a/oemcrypto/odk/src/odk_overflow.c b/oemcrypto/odk/src/odk_overflow.c new file mode 100644 index 00000000..0ebc0846 --- /dev/null +++ b/oemcrypto/odk/src/odk_overflow.c @@ -0,0 +1,46 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include +#include + +int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c) { + if (a >= b) { + if (c) { + *c = a - b; + } + return 0; + } + return 1; +} + +int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c) { + if (UINT64_MAX - a >= b) { + if (c) { + *c = a + b; + } + return 0; + } + return 1; +} + +int odk_add_overflow_ux(size_t a, size_t b, size_t* c) { + if (SIZE_MAX - a >= b) { + if (c) { + *c = a + b; + } + return 0; + } + return 1; +} + +int odk_mul_overflow_ux(size_t a, size_t b, size_t* c) { + if (b > 0 && a > SIZE_MAX / b) { + return 1; + } + if (c) { + *c = a * b; + } + return 0; +} diff --git a/oemcrypto/odk/src/odk_overflow.h b/oemcrypto/odk/src/odk_overflow.h new file mode 100644 index 00000000..e7257051 --- /dev/null +++ b/oemcrypto/odk/src/odk_overflow.h @@ -0,0 +1,24 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ +#define WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int odk_sub_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); +int odk_add_overflow_u64(uint64_t a, uint64_t b, uint64_t* c); +int odk_add_overflow_ux(size_t a, size_t b, size_t* c); +int odk_mul_overflow_ux(size_t a, size_t b, size_t* c); + +#ifdef __cplusplus +} +#endif + +#endif // WIDEVINE_ODK_SRC_ODK_OVERFLOW_H_ diff --git a/oemcrypto/odk/src/odk_serialize.c b/oemcrypto/odk/src/odk_serialize.c new file mode 100644 index 00000000..5c582000 --- /dev/null +++ b/oemcrypto/odk/src/odk_serialize.c @@ -0,0 +1,352 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/* + * This code is auto-generated, do not edit + */ + +#include "odk_structs_priv.h" +#include "serialization_base.h" + +/* @ serialize */ + +/* @@ private serialize */ + +static void Pack_ODK_NonceValues(ODK_Message* msg, ODK_NonceValues const* obj) { + Pack_uint16_t(msg, &obj->api_minor_version); + Pack_uint16_t(msg, &obj->api_major_version); + Pack_uint32_t(msg, &obj->nonce); + Pack_uint32_t(msg, &obj->session_id); +} + +static void Pack_ODK_CoreMessage(ODK_Message* msg, ODK_CoreMessage const* obj) { + Pack_uint32_t(msg, &obj->message_type); + Pack_uint32_t(msg, &obj->message_length); + Pack_ODK_NonceValues(msg, &obj->nonce_values); +} + +static void Pack_OEMCrypto_KeyObject(ODK_Message* msg, + OEMCrypto_KeyObject const* obj) { + Pack_OEMCrypto_Substring(msg, &obj->key_id); + Pack_OEMCrypto_Substring(msg, &obj->key_data_iv); + Pack_OEMCrypto_Substring(msg, &obj->key_data); + Pack_OEMCrypto_Substring(msg, &obj->key_control_iv); + Pack_OEMCrypto_Substring(msg, &obj->key_control); +} + +static void Pack_ODK_TimerLimits(ODK_Message* msg, ODK_TimerLimits const* obj) { + Pack_bool(msg, &obj->soft_enforce_rental_duration); + Pack_bool(msg, &obj->soft_enforce_playback_duration); + Pack_uint64_t(msg, &obj->earliest_playback_start_seconds); + Pack_uint64_t(msg, &obj->rental_duration_seconds); + Pack_uint64_t(msg, &obj->total_playback_duration_seconds); + Pack_uint64_t(msg, &obj->initial_renewal_duration_seconds); +} + +static void Pack_ODK_ParsedLicense(ODK_Message* msg, + ODK_ParsedLicense const* obj) { + /* 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); + Pack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + Pack_enum(msg, obj->license_type); + Pack_bool(msg, &obj->nonce_required); + Pack_ODK_TimerLimits(msg, &obj->timer_limits); + Pack_uint32_t(msg, &obj->watermarking); + Pack_uint8_t(msg, &obj->dtcp2_required.dtcp2_required); + if (obj->dtcp2_required.dtcp2_required) { + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_0.id); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_0.extension); + Pack_uint16_t(msg, &obj->dtcp2_required.cmi_descriptor_0.length); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_0.data); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.id); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.extension); + Pack_uint16_t(msg, &obj->dtcp2_required.cmi_descriptor_1.length); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.data[0]); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.data[1]); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.data[2]); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.id); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.extension); + Pack_uint16_t(msg, &obj->dtcp2_required.cmi_descriptor_2.length); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.data[0]); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.data[1]); + Pack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.data[2]); + } + Pack_uint32_t(msg, &obj->key_array_length); + size_t i; + for (i = 0; i < (size_t)obj->key_array_length; i++) { + Pack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Pack_ODK_ParsedLicenseV16(ODK_Message* msg, + ODK_ParsedLicenseV16 const* obj) { + /* 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); + Pack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + Pack_enum(msg, obj->license_type); + Pack_bool(msg, &obj->nonce_required); + Pack_ODK_TimerLimits(msg, &obj->timer_limits); + Pack_uint32_t(msg, &obj->key_array_length); + size_t i; + for (i = 0; i < (size_t)obj->key_array_length; i++) { + Pack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Pack_ODK_ParsedProvisioning(ODK_Message* msg, + ODK_ParsedProvisioning const* obj) { + Pack_enum(msg, obj->key_type); + Pack_OEMCrypto_Substring(msg, &obj->enc_private_key); + Pack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); + Pack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); +} + +/* @@ odk serialize */ + +void Pack_ODK_PreparedLicenseRequest(ODK_Message* msg, + ODK_PreparedLicenseRequest const* 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); + Pack_uint64_t(msg, &obj->playback_time); +} + +void Pack_ODK_PreparedProvisioningRequest( + ODK_Message* msg, const ODK_PreparedProvisioningRequest* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_uint32_t(msg, &obj->device_id_length); + PackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); +} + +void Pack_ODK_PreparedRenewedProvisioningRequest( + ODK_Message* msg, const ODK_PreparedRenewedProvisioningRequest* obj) { + Pack_ODK_CoreMessage(msg, &obj->core_message); + Pack_uint32_t(msg, &obj->device_id_length); + PackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); + Pack_uint16_t(msg, &obj->renewal_type); + Pack_uint32_t(msg, &obj->renewal_data_length); + PackArray(msg, &obj->renewal_data[0], sizeof(obj->renewal_data)); +} + +/* @@ kdo serialize */ + +void Pack_ODK_LicenseResponse(ODK_Message* msg, + ODK_LicenseResponse const* obj) { + Pack_ODK_PreparedLicenseRequest(msg, &obj->request); + Pack_ODK_ParsedLicense(msg, (const ODK_ParsedLicense*)obj->parsed_license); +} + +void Pack_ODK_LicenseResponseV16(ODK_Message* msg, + ODK_LicenseResponseV16 const* obj) { + Pack_ODK_PreparedLicenseRequest(msg, &obj->request); + Pack_ODK_ParsedLicenseV16(msg, &obj->parsed_license); + PackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Pack_ODK_RenewalResponse(ODK_Message* msg, + ODK_RenewalResponse const* obj) { + Pack_ODK_PreparedRenewalRequest(msg, &obj->request); + Pack_uint64_t(msg, &obj->renewal_duration_seconds); +} + +void Pack_ODK_ProvisioningResponse(ODK_Message* msg, + const ODK_ProvisioningResponse* obj) { + Pack_ODK_PreparedProvisioningRequest(msg, &obj->request); + Pack_ODK_ParsedProvisioning( + msg, (const ODK_ParsedProvisioning*)obj->parsed_provisioning); +} + +/* @ deserialize */ + +/* @@ private deserialize */ + +static void Unpack_ODK_NonceValues(ODK_Message* msg, ODK_NonceValues* obj) { + Unpack_uint16_t(msg, &obj->api_minor_version); + Unpack_uint16_t(msg, &obj->api_major_version); + Unpack_uint32_t(msg, &obj->nonce); + Unpack_uint32_t(msg, &obj->session_id); +} + +void Unpack_ODK_CoreMessage(ODK_Message* msg, ODK_CoreMessage* obj) { + Unpack_uint32_t(msg, &obj->message_type); + Unpack_uint32_t(msg, &obj->message_length); + Unpack_ODK_NonceValues(msg, &obj->nonce_values); +} + +static void Unpack_OEMCrypto_KeyObject(ODK_Message* msg, + OEMCrypto_KeyObject* obj) { + Unpack_OEMCrypto_Substring(msg, &obj->key_id); + Unpack_OEMCrypto_Substring(msg, &obj->key_data_iv); + Unpack_OEMCrypto_Substring(msg, &obj->key_data); + Unpack_OEMCrypto_Substring(msg, &obj->key_control_iv); + Unpack_OEMCrypto_Substring(msg, &obj->key_control); +} + +static void Unpack_ODK_TimerLimits(ODK_Message* msg, ODK_TimerLimits* obj) { + Unpack_bool(msg, &obj->soft_enforce_rental_duration); + Unpack_bool(msg, &obj->soft_enforce_playback_duration); + Unpack_uint64_t(msg, &obj->earliest_playback_start_seconds); + Unpack_uint64_t(msg, &obj->rental_duration_seconds); + Unpack_uint64_t(msg, &obj->total_playback_duration_seconds); + Unpack_uint64_t(msg, &obj->initial_renewal_duration_seconds); +} + +static void Unpack_ODK_ParsedLicense(ODK_Message* msg, ODK_ParsedLicense* obj) { + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); + Unpack_OEMCrypto_Substring(msg, &obj->pst); + Unpack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + obj->license_type = (OEMCrypto_LicenseType)Unpack_enum(msg); + Unpack_bool(msg, &obj->nonce_required); + Unpack_ODK_TimerLimits(msg, &obj->timer_limits); + Unpack_uint32_t(msg, &obj->watermarking); + Unpack_uint8_t(msg, &obj->dtcp2_required.dtcp2_required); + if (obj->dtcp2_required.dtcp2_required) { + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_0.id); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_0.extension); + Unpack_uint16_t(msg, &obj->dtcp2_required.cmi_descriptor_0.length); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_0.data); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.id); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.extension); + Unpack_uint16_t(msg, &obj->dtcp2_required.cmi_descriptor_1.length); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.data[0]); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.data[1]); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_1.data[2]); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.id); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.extension); + Unpack_uint16_t(msg, &obj->dtcp2_required.cmi_descriptor_2.length); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.data[0]); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.data[1]); + Unpack_uint8_t(msg, &obj->dtcp2_required.cmi_descriptor_2.data[2]); + } else { + obj->dtcp2_required.dtcp2_required = 0; + obj->dtcp2_required.cmi_descriptor_0.id = 0; + obj->dtcp2_required.cmi_descriptor_0.extension = 0; + obj->dtcp2_required.cmi_descriptor_0.length = 0; + obj->dtcp2_required.cmi_descriptor_0.data = 0; + obj->dtcp2_required.cmi_descriptor_1.id = 0; + obj->dtcp2_required.cmi_descriptor_1.extension = 0; + obj->dtcp2_required.cmi_descriptor_1.length = 0; + obj->dtcp2_required.cmi_descriptor_1.data[0] = 0; + obj->dtcp2_required.cmi_descriptor_1.data[1] = 0; + obj->dtcp2_required.cmi_descriptor_1.data[2] = 0; + obj->dtcp2_required.cmi_descriptor_2.id = 0; + obj->dtcp2_required.cmi_descriptor_2.extension = 0; + obj->dtcp2_required.cmi_descriptor_2.length = 0; + obj->dtcp2_required.cmi_descriptor_2.data[0] = 0; + obj->dtcp2_required.cmi_descriptor_2.data[1] = 0; + obj->dtcp2_required.cmi_descriptor_2.data[2] = 0; + } + Unpack_uint32_t(msg, &obj->key_array_length); + if (obj->key_array_length > ODK_MAX_NUM_KEYS) { + ODK_Message_SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + uint32_t i; + for (i = 0; i < obj->key_array_length; i++) { + Unpack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Unpack_ODK_ParsedLicenseV16(ODK_Message* msg, + ODK_ParsedLicenseV16* obj) { + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys_iv); + Unpack_OEMCrypto_Substring(msg, &obj->enc_mac_keys); + Unpack_OEMCrypto_Substring(msg, &obj->pst); + Unpack_OEMCrypto_Substring(msg, &obj->srm_restriction_data); + obj->license_type = (OEMCrypto_LicenseType)Unpack_enum(msg); + Unpack_bool(msg, &obj->nonce_required); + Unpack_ODK_TimerLimits(msg, &obj->timer_limits); + Unpack_uint32_t(msg, &obj->key_array_length); + if (obj->key_array_length > ODK_MAX_NUM_KEYS) { + ODK_Message_SetStatus(msg, MESSAGE_STATUS_OVERFLOW_ERROR); + return; + } + uint32_t i; + for (i = 0; i < obj->key_array_length; i++) { + Unpack_OEMCrypto_KeyObject(msg, &obj->key_array[i]); + } +} + +static void Unpack_ODK_ParsedProvisioning(ODK_Message* msg, + ODK_ParsedProvisioning* obj) { + obj->key_type = (OEMCrypto_PrivateKeyType)Unpack_enum(msg); + Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key); + Unpack_OEMCrypto_Substring(msg, &obj->enc_private_key_iv); + Unpack_OEMCrypto_Substring(msg, &obj->encrypted_message_key); +} + +/* @ kdo deserialize */ + +void Unpack_ODK_PreparedLicenseRequest(ODK_Message* msg, + ODK_PreparedLicenseRequest* 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); + Unpack_uint64_t(msg, &obj->playback_time); +} + +void Unpack_ODK_PreparedProvisioningRequest( + ODK_Message* msg, ODK_PreparedProvisioningRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint32_t(msg, &obj->device_id_length); + UnpackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); +} + +void Unpack_ODK_PreparedRenewedProvisioningRequest( + ODK_Message* msg, ODK_PreparedRenewedProvisioningRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); + Unpack_uint32_t(msg, &obj->device_id_length); + UnpackArray(msg, &obj->device_id[0], sizeof(obj->device_id)); + Unpack_uint16_t(msg, &obj->renewal_type); + Unpack_uint32_t(msg, &obj->renewal_data_length); + UnpackArray(msg, &obj->renewal_data[0], obj->renewal_data_length); +} + +void Unpack_ODK_PreparedCommonRequest(ODK_Message* msg, + ODK_PreparedCommonRequest* obj) { + Unpack_ODK_CoreMessage(msg, &obj->core_message); +} +/* @@ odk deserialize */ + +void Unpack_ODK_LicenseResponse(ODK_Message* msg, ODK_LicenseResponse* obj) { + Unpack_ODK_PreparedLicenseRequest(msg, &obj->request); + Unpack_ODK_ParsedLicense(msg, obj->parsed_license); +} + +void Unpack_ODK_LicenseResponseV16(ODK_Message* msg, + ODK_LicenseResponseV16* obj) { + Unpack_ODK_PreparedLicenseRequest(msg, &obj->request); + Unpack_ODK_ParsedLicenseV16(msg, &obj->parsed_license); + UnpackArray(msg, &obj->request_hash[0], sizeof(obj->request_hash)); +} + +void Unpack_ODK_RenewalResponse(ODK_Message* msg, ODK_RenewalResponse* obj) { + Unpack_ODK_PreparedRenewalRequest(msg, &obj->request); + Unpack_uint64_t(msg, &obj->renewal_duration_seconds); +} + +void Unpack_ODK_ProvisioningResponse(ODK_Message* msg, + ODK_ProvisioningResponse* obj) { + Unpack_ODK_PreparedProvisioningRequest(msg, &obj->request); + Unpack_ODK_ParsedProvisioning(msg, obj->parsed_provisioning); +} diff --git a/oemcrypto/odk/src/odk_serialize.h b/oemcrypto/odk/src/odk_serialize.h new file mode 100644 index 00000000..0904700c --- /dev/null +++ b/oemcrypto/odk/src/odk_serialize.h @@ -0,0 +1,61 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +/* + * This code is auto-generated, do not edit + */ +#ifndef WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ +#define WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ + +#include "odk_structs_priv.h" +#include "serialization_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* odk pack */ +void Pack_ODK_PreparedLicenseRequest(ODK_Message* msg, + const ODK_PreparedLicenseRequest* obj); +void Pack_ODK_PreparedRenewalRequest(ODK_Message* msg, + const ODK_PreparedRenewalRequest* obj); +void Pack_ODK_PreparedProvisioningRequest( + ODK_Message* msg, const ODK_PreparedProvisioningRequest* obj); +void Pack_ODK_PreparedRenewedProvisioningRequest( + ODK_Message* msg, const ODK_PreparedRenewedProvisioningRequest* obj); + +/* 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_LicenseResponseV16(ODK_Message* msg, + ODK_LicenseResponseV16* obj); +void Unpack_ODK_RenewalResponse(ODK_Message* msg, ODK_RenewalResponse* obj); +void Unpack_ODK_ProvisioningResponse(ODK_Message* msg, + ODK_ProvisioningResponse* obj); + +/* kdo pack */ +void Pack_ODK_LicenseResponse(ODK_Message* msg, const ODK_LicenseResponse* obj); +void Pack_ODK_LicenseResponseV16(ODK_Message* msg, + const ODK_LicenseResponseV16* obj); +void Pack_ODK_RenewalResponse(ODK_Message* msg, const ODK_RenewalResponse* obj); +void Pack_ODK_ProvisioningResponse(ODK_Message* msg, + const ODK_ProvisioningResponse* obj); + +/* kdo unpack */ +void Unpack_ODK_PreparedLicenseRequest(ODK_Message* msg, + ODK_PreparedLicenseRequest* obj); +void Unpack_ODK_PreparedRenewalRequest(ODK_Message* msg, + ODK_PreparedRenewalRequest* obj); +void Unpack_ODK_PreparedProvisioningRequest( + ODK_Message* msg, ODK_PreparedProvisioningRequest* obj); +void Unpack_ODK_PreparedRenewedProvisioningRequest( + ODK_Message* msg, ODK_PreparedRenewedProvisioningRequest* obj); + +void Unpack_ODK_PreparedCommonRequest(ODK_Message* msg, + ODK_PreparedCommonRequest* obj); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // WIDEVINE_ODK_SRC_ODK_SERIALIZE_H_ diff --git a/oemcrypto/odk/src/odk_structs_priv.h b/oemcrypto/odk/src/odk_structs_priv.h new file mode 100644 index 00000000..3fe73eed --- /dev/null +++ b/oemcrypto/odk/src/odk_structs_priv.h @@ -0,0 +1,139 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ +#define WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ + +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// We use a typedef here so `ODK_CoreMessage` will contain "simple types" which +// should work better with the auto code generator. +typedef uint32_t ODK_MessageType; + +#define ODK_License_Request_Type ((ODK_MessageType)1u) +#define ODK_License_Response_Type ((ODK_MessageType)2u) +#define ODK_Renewal_Request_Type ((ODK_MessageType)3u) +#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_Renewed_Provisioning_Request_Type ((ODK_MessageType)11u) + +// 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) + +typedef struct { + ODK_MessageType message_type; // Type of core message (defined above) + uint32_t message_length; // Length of core message. + ODK_NonceValues nonce_values; +} ODK_CoreMessage; + +typedef struct { + ODK_CoreMessage core_message; +} ODK_PreparedLicenseRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint64_t playback_time; +} ODK_PreparedRenewalRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint32_t device_id_length; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; +} ODK_PreparedProvisioningRequest; + +typedef struct { + ODK_CoreMessage core_message; + uint32_t device_id_length; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; + uint16_t renewal_type; + uint32_t renewal_data_length; + uint8_t renewal_data[ODK_KEYBOX_RENEWAL_DATA_SIZE]; +} ODK_PreparedRenewedProvisioningRequest; + +typedef struct { + ODK_CoreMessage core_message; +} ODK_PreparedCommonRequest; + +typedef struct { + OEMCrypto_Substring enc_mac_keys_iv; + OEMCrypto_Substring enc_mac_keys; + OEMCrypto_Substring pst; + OEMCrypto_Substring srm_restriction_data; + OEMCrypto_LicenseType license_type; + bool nonce_required; + ODK_TimerLimits timer_limits; + uint32_t key_array_length; + OEMCrypto_KeyObject key_array[ODK_MAX_NUM_KEYS]; +} ODK_ParsedLicenseV16; + +typedef struct { + ODK_PreparedLicenseRequest request; + ODK_ParsedLicense* parsed_license; +} ODK_LicenseResponse; + +typedef struct { + ODK_PreparedLicenseRequest request; + ODK_ParsedLicenseV16 parsed_license; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; +} ODK_LicenseResponseV16; + +typedef struct { + ODK_PreparedRenewalRequest request; + uint64_t renewal_duration_seconds; +} ODK_RenewalResponse; + +typedef struct { + ODK_PreparedProvisioningRequest request; + ODK_ParsedProvisioning* parsed_provisioning; +} ODK_ProvisioningResponse; + +// These are the sum of sizeof of each individual member of the request structs +// without any padding added by the compiler. Make sure they get updated when +// request structs change. Refer to test suite OdkSizeTest in +// ../test/odk_test.cpp for validations of each of the defined request sizes. +#define ODK_LICENSE_REQUEST_SIZE 20u +#define ODK_RENEWAL_REQUEST_SIZE 28u +#define ODK_PROVISIONING_REQUEST_SIZE 88u +#define ODK_RENEWED_PROVISIONING_REQUEST_SIZE 1694u + +// These are the possible timer status values. +#define ODK_CLOCK_TIMER_STATUS_UNDEFINED 0u // Should not happen. +// When the structure has been initialized, but no license is loaded. +#define ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED 1u +// After the license is loaded, before a successful decrypt. +#define ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED 2u +// After the license is loaded, if a renewal has also been loaded. +#define ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED 3u +// The first decrypt has occurred and the timer is active. +#define ODK_CLOCK_TIMER_STATUS_ACTIVE 4u +// The first decrypt has occurred and the timer is unlimited. +#define ODK_CLOCK_TIMER_STATUS_UNLIMITED 5u +// The timer has transitioned from active to expired. +#define ODK_CLOCK_TIMER_STATUS_EXPIRED 6u +// The license has been marked as inactive. +#define ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE 7u + +// A helper function for computing timer limits when a renewal is loaded. +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value); + +#ifdef __cplusplus +} +#endif + +#endif // WIDEVINE_ODK_SRC_ODK_STRUCTS_PRIV_H_ diff --git a/oemcrypto/odk/src/odk_timer.c b/oemcrypto/odk/src/odk_timer.c new file mode 100644 index 00000000..1b09551e --- /dev/null +++ b/oemcrypto/odk/src/odk_timer.c @@ -0,0 +1,503 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include +#include + +#include "odk.h" +#include "odk_attributes.h" +#include "odk_overflow.h" +#include "odk_structs_priv.h" + +/* Private function. Checks to see if the license is active. Returns + * ODK_TIMER_EXPIRED if the license is valid but inactive. Returns + * OEMCrypto_SUCCESS if the license is active. Returns + * OEMCrypto_ERROR_UNKNOWN_FAILURE on other errors. */ +static OEMCryptoResult ODK_LicenseActive(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + /* Check some basic errors. */ + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if the license has not been loaded yet. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_UNDEFINED || + clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status > kActive) { + return ODK_TIMER_EXPIRED; + } + return OEMCrypto_SUCCESS; +} + +/* Private function. Sets the timer_value to be the min(timer_value, new_value), + * with the convention that 0 means infinite. The convention that 0 means + * infinite is used for all Widevine license and duration values. */ +static void ComputeMinimum(uint64_t* timer_value, uint64_t new_value) { + if (timer_value == NULL) return; + if (new_value > 0) { + if (*timer_value == 0 || *timer_value > new_value) { + *timer_value = new_value; + } + } +} + +/* Private function. Check to see if the rental window restricts playback. If + * the rental enforcement is hard, or if this is the first playback, then we + * verify that system_time_seconds is within the rental window. If the + * enforcement is soft and we have already started playback, then there is no + * restriction. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_TIMER_ACTIVE if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no there should be no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckRentalWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* If playback has already started, and rental duration enforcement is soft, + * then there is no restriction. */ + if (clock_values->time_of_first_decrypt > 0 && + timer_limits->soft_enforce_rental_duration) { + return ODK_DISABLE_TIMER; + } + + /* rental_clock = time since license signed. */ + uint64_t rental_clock = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_request_signed, + &rental_clock)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check if it is before license is valid. This is an unusual case. First + * playback may still work if it occurs after the rental window opens. */ + if (rental_clock < timer_limits->earliest_playback_start_seconds) { + return ODK_TIMER_EXPIRED; + } + /* If the rental duration is 0, there is no limit. */ + if (timer_limits->rental_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + /* End of rental window, based on rental clock (not system time). */ + uint64_t end_of_rental_window = 0; + if (odk_add_overflow_u64(timer_limits->earliest_playback_start_seconds, + timer_limits->rental_duration_seconds, + &end_of_rental_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_rental_window <= rental_clock) { + return ODK_TIMER_EXPIRED; + } + /* At this point system_time is within the rental window. */ + if (timer_limits->soft_enforce_rental_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_rental_window, rental_clock, &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Private function. Check to see if the playback window restricts + * playback. This should only be called if playback has started, so that + * clock_values->time_of_first_decrypt is nonzero. + * Return ODK_TIMER_EXPIRED if out of the window. + * Return ODK_SET_TIMER if within the window, and there is a hard limit. + * Return ODK_DISABLE_TIMER if no limit. + * Return other error on error. + * Also, if this function does compute a limit, the timer_value is reduced to + * obey that limit. If the limit is less restrictive than the current + * timer_value, then timer_value is not changed. */ +static OEMCryptoResult ODK_CheckPlaybackWindow( + const ODK_TimerLimits* timer_limits, ODK_ClockValues* clock_values, + uint64_t system_time_seconds, uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL || timer_value == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* if the playback duration is 0, there is no limit. */ + if (timer_limits->total_playback_duration_seconds == 0) { + return ODK_DISABLE_TIMER; + } + uint64_t end_of_playback_window = 0; + if (odk_add_overflow_u64(timer_limits->total_playback_duration_seconds, + clock_values->time_of_first_decrypt, + &end_of_playback_window)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (end_of_playback_window <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + /* At this point, system_time is within the total playback window. */ + if (timer_limits->soft_enforce_playback_duration) { + /* For soft enforcement, we allow playback, and do not adjust the timer. */ + return ODK_DISABLE_TIMER; + } + uint64_t time_left = 0; + if (odk_sub_overflow_u64(end_of_playback_window, system_time_seconds, + &time_left)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + ComputeMinimum(timer_value, time_left); + return ODK_SET_TIMER; +} + +/* Update the timer status. If playback has already started, we use the given + * status. However, if playback has not yet started, then we expect a call to + * ODK_AttemptFirstPlayback in the future, and we need to signal to it that we + * have already computed the timer limit. */ +static void ODK_UpdateTimerStatusForRenewal(ODK_ClockValues* clock_values, + uint32_t new_status) { + if (clock_values == NULL) { + return; /* should not happen. */ + } + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED) { + /* Signal that the timer is already set. */ + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED; + } else { + clock_values->timer_status = new_status; + } +} + +/* Private function, but accessed from odk.c so cannot be static. This checks to + * see if a renewal message should restart the playback timer and sets the value + * appropriately. */ +OEMCryptoResult ODK_ComputeRenewalDuration(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds, + uint64_t new_renewal_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; /* should not happen. */ + } + /* If this is before the license was signed, something is odd. Return an + * error. */ + if (system_time_seconds < clock_values->time_of_license_request_signed) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + + const OEMCryptoResult license_status = + ODK_LicenseActive(timer_limits, clock_values); + /* If the license is not active, then we cannot renew the license. */ + if (license_status != OEMCrypto_SUCCESS) { + return license_status; + } + + /* We start with the new renewal duration as the new timer limit. */ + uint64_t new_timer_value = new_renewal_duration; + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + const OEMCryptoResult rental_status = ODK_CheckRentalWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + + /* If the rental status forbids playback, then we're done. */ + if ((rental_status != ODK_DISABLE_TIMER) && + (rental_status != ODK_SET_TIMER)) { + return rental_status; + } + + /* If playback has already started and it has hard enforcement, then check + * total playback window. */ + if (clock_values->time_of_first_decrypt > 0 && + !timer_limits->soft_enforce_playback_duration) { + /* This might decrease new_timer_value. */ + const OEMCryptoResult playback_status = ODK_CheckPlaybackWindow( + timer_limits, clock_values, system_time_seconds, &new_timer_value); + /* If the timer limits forbid playback in the playback window, then we're + * done. */ + if ((playback_status != ODK_DISABLE_TIMER) && + (playback_status != ODK_SET_TIMER)) { + return playback_status; + } + } + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + ODK_UpdateTimerStatusForRenewal(clock_values, + ODK_CLOCK_TIMER_STATUS_UNLIMITED); + return ODK_DISABLE_TIMER; + } + + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value != NULL) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + ODK_UpdateTimerStatusForRenewal(clock_values, ODK_CLOCK_TIMER_STATUS_ACTIVE); + return ODK_SET_TIMER; +} + +/************************************************************************/ +/************************************************************************/ +/* Public functions, declared in odk.h. */ + +/* This is called when OEMCrypto opens a new session. */ +OEMCryptoResult ODK_InitializeSessionValues(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t api_major_version, + uint32_t session_id) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Check that the API version passed in from OEMCrypto matches the version of + * this ODK library. */ + if (api_major_version != ODK_MAJOR_VERSION) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_rental_duration = false; + timer_limits->soft_enforce_playback_duration = false; + timer_limits->earliest_playback_start_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = 0; + + ODK_InitializeClockValues(clock_values, 0); + + nonce_values->api_major_version = ODK_MAJOR_VERSION; + nonce_values->api_minor_version = ODK_MINOR_VERSION; + nonce_values->nonce = 0; + nonce_values->session_id = session_id; + + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto generates a new nonce in + * OEMCrypto_GenerateNonce. */ +OEMCryptoResult ODK_SetNonceValues(ODK_NonceValues* nonce_values, + uint32_t nonce) { + if (nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + /* Setting the nonce should only happen once per session. */ + if (nonce_values->nonce != 0) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + nonce_values->nonce = nonce; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto signs a license. */ +OEMCryptoResult ODK_InitializeClockValues(ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + clock_values->time_of_license_request_signed = system_time_seconds; + clock_values->time_of_first_decrypt = 0; + clock_values->time_of_last_decrypt = 0; + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; + clock_values->status = kUnused; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto reloads a usage entry. */ +OEMCryptoResult ODK_ReloadClockValues(ODK_ClockValues* clock_values, + uint64_t time_of_license_request_signed, + uint64_t time_of_first_decrypt, + uint64_t time_of_last_decrypt, + enum OEMCrypto_Usage_Entry_Status status, + uint64_t system_time_seconds UNUSED) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + clock_values->time_of_license_request_signed = time_of_license_request_signed; + clock_values->time_of_first_decrypt = time_of_first_decrypt; + clock_values->time_of_last_decrypt = time_of_last_decrypt; + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED; + clock_values->status = status; + return OEMCrypto_SUCCESS; +} + +/* This is called on the first playback for a session. */ +OEMCryptoResult ODK_AttemptFirstPlayback(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + if (clock_values == NULL || timer_limits == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + /* All times are relative to when the license was signed. */ + uint64_t rental_time = 0; + if (odk_sub_overflow_u64(system_time_seconds, + clock_values->time_of_license_request_signed, + &rental_time)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (rental_time < timer_limits->earliest_playback_start_seconds) { + clock_values->timer_status = ODK_TIMER_EXPIRED; + return ODK_TIMER_EXPIRED; + } + /* If the license is inactive or not loaded, then playback is not allowed. */ + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) { + return status; + } + + /* We start with the initial renewal duration as the timer limit. */ + uint64_t new_timer_value = timer_limits->initial_renewal_duration_seconds; + /* However, if a renewal was loaded before this first playback, use the + * previously computed limit. */ + if (clock_values->timer_status == ODK_CLOCK_TIMER_STATUS_RENEWAL_LOADED) { + if (clock_values->time_when_timer_expires <= system_time_seconds) { + return ODK_TIMER_EXPIRED; + } + if (odk_sub_overflow_u64(clock_values->time_when_timer_expires, + system_time_seconds, &new_timer_value)) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + } + + /* Then we factor in the rental window restrictions. This might decrease + * new_timer_value. */ + status = ODK_CheckRentalWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) { + return status; + } + + /* If playback has not already started, then this is the first playback. */ + if (clock_values->time_of_first_decrypt == 0) { + clock_values->time_of_first_decrypt = system_time_seconds; + clock_values->status = kActive; + } + + /* Similar to the rental window, we check the playback window + * restrictions. This might decrease new_timer_value. */ + status = ODK_CheckPlaybackWindow(timer_limits, clock_values, + system_time_seconds, &new_timer_value); + if ((status != ODK_DISABLE_TIMER) && (status != ODK_SET_TIMER)) { + return status; + } + + /* We know we are allowed to decrypt. The rest computes the timer duration. */ + clock_values->time_of_last_decrypt = system_time_seconds; + + /* If new_timer_value is infinite (represented by 0), then there are no + * limits, so we can return now. */ + if (new_timer_value == 0) { + clock_values->time_when_timer_expires = 0; + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_UNLIMITED; + return ODK_DISABLE_TIMER; + } + /* If the caller gave us a pointer to store the new timer value. Fill it. */ + if (timer_value) { + *timer_value = new_timer_value; + } + if (odk_add_overflow_u64(system_time_seconds, new_timer_value, + &clock_values->time_when_timer_expires)) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE; + return ODK_SET_TIMER; +} + +/* This is called regularly during playback if OEMCrypto does not implement its + * own timer. */ +OEMCryptoResult ODK_UpdateLastPlaybackTime(uint64_t system_time_seconds, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values) { + OEMCryptoResult status = ODK_LicenseActive(timer_limits, clock_values); + if (status != OEMCrypto_SUCCESS) { + return status; + } + switch (clock_values->timer_status) { + case ODK_CLOCK_TIMER_STATUS_UNLIMITED: + break; + case ODK_CLOCK_TIMER_STATUS_ACTIVE: + /* Note: we allow playback at the time when the timer expires, but not + * after. This is not important for business cases, but it makes it + * easier to write tests. */ + if (clock_values->time_when_timer_expires > 0 && + system_time_seconds > clock_values->time_when_timer_expires) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_EXPIRED; + return ODK_TIMER_EXPIRED; + } + break; + default: /* Expired, error state, or never started. */ + return ODK_TIMER_EXPIRED; + } + clock_values->time_of_last_decrypt = system_time_seconds; + return OEMCrypto_SUCCESS; +} + +/* This is called from OEMCrypto_DeactivateUsageEntry. */ +OEMCryptoResult ODK_DeactivateUsageEntry(ODK_ClockValues* clock_values) { + if (clock_values == NULL) { + return OEMCrypto_ERROR_UNKNOWN_FAILURE; + } + if (clock_values->status == kUnused) { + clock_values->status = kInactiveUnused; + } else if (clock_values->status == kActive) { + clock_values->status = kInactiveUsed; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto loads a legacy v15 license, from + * OEMCrypto_LoadKeys. */ +OEMCryptoResult ODK_InitializeV15Values(ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + ODK_NonceValues* nonce_values, + uint32_t key_duration, + uint64_t system_time_seconds) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + timer_limits->soft_enforce_playback_duration = false; + timer_limits->soft_enforce_rental_duration = false; + timer_limits->earliest_playback_start_seconds = 0; + timer_limits->rental_duration_seconds = 0; + timer_limits->total_playback_duration_seconds = 0; + timer_limits->initial_renewal_duration_seconds = key_duration; + + nonce_values->api_major_version = 15; + nonce_values->api_minor_version = 0; + if (key_duration > 0) { + clock_values->time_when_timer_expires = system_time_seconds + key_duration; + } else { + clock_values->time_when_timer_expires = 0; + } + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + return OEMCrypto_SUCCESS; +} + +/* This is called when OEMCrypto loads a legacy license renewal in + * OEMCrypto_RefreshKeys. */ +OEMCryptoResult ODK_RefreshV15Values(const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + const ODK_NonceValues* nonce_values, + uint64_t system_time_seconds, + uint32_t new_key_duration, + uint64_t* timer_value) { + if (timer_limits == NULL || clock_values == NULL || nonce_values == NULL) { + return OEMCrypto_ERROR_INVALID_CONTEXT; + } + if (nonce_values->api_major_version != 15) { + return OEMCrypto_ERROR_INVALID_NONCE; + } + if (clock_values->status > kActive) { + clock_values->timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + return ODK_TIMER_EXPIRED; + } + return ODK_ComputeRenewalDuration(timer_limits, clock_values, + system_time_seconds, new_key_duration, + timer_value); +} diff --git a/oemcrypto/odk/src/odk_util.c b/oemcrypto/odk/src/odk_util.c new file mode 100644 index 00000000..a6669a4c --- /dev/null +++ b/oemcrypto/odk/src/odk_util.c @@ -0,0 +1,33 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "odk_util.h" + +int crypto_memcmp(const void* in_a, const void* in_b, size_t len) { + if (len == 0) { + return 0; + } + + /* Only valid pointers are allowed. */ + if (in_a == NULL || in_b == NULL) { + return -1; + } + + const uint8_t* a = (const uint8_t*)in_a; + const uint8_t* b = (const uint8_t*)in_b; + uint8_t x = 0; + + for (size_t i = 0; i < len; i++) { + x |= a[i] ^ b[i]; + } + return x; +} + +bool ODK_NonceValuesEqualExcludingVersion(const ODK_NonceValues* a, + const ODK_NonceValues* b) { + if (a == NULL || b == NULL) { + return (a == b); + } + return (a->nonce == b->nonce && a->session_id == b->session_id); +} diff --git a/oemcrypto/odk/src/odk_util.h b/oemcrypto/odk/src/odk_util.h new file mode 100644 index 00000000..ab932dd0 --- /dev/null +++ b/oemcrypto/odk/src/odk_util.h @@ -0,0 +1,29 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_SRC_ODK_UTIL_H_ +#define WIDEVINE_ODK_SRC_ODK_UTIL_H_ + +#include +#include + +#include "odk_structs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* crypto_memcmp returns zero iff the |len| bytes at |a| and |b| are equal. It + * takes an amount of time dependent on |len|, but independent of the contents + * of |a| and |b|. Unlike memcmp, it cannot be used to order elements as the + * return value when a != b is undefined, other than being non-zero. */ +int crypto_memcmp(const void* a, const void* b, size_t len); + +bool ODK_NonceValuesEqualExcludingVersion(const ODK_NonceValues* a, + const ODK_NonceValues* b); + +#ifdef __cplusplus +} // extern "C" +#endif +#endif // WIDEVINE_ODK_SRC_ODK_UTIL_H_ diff --git a/oemcrypto/odk/src/serialization_base.c b/oemcrypto/odk/src/serialization_base.c new file mode 100644 index 00000000..30af34cf --- /dev/null +++ b/oemcrypto/odk/src/serialization_base.c @@ -0,0 +1,200 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "serialization_base.h" + +#include +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_message.h" +#include "odk_message_priv.h" +#include "odk_overflow.h" + +/* + * An ODK_Message_Impl pointer must only be obtained by calling GetMessageImpl. + * This forces any message to pass the validity check before being operated on, + * which means that no function can modify or access the internals of a message + * without having it be validated first. + */ +static ODK_Message_Impl* GetMessageImpl(ODK_Message* message) { + if (!ODK_Message_IsValid(message)) return NULL; + return (ODK_Message_Impl*)message; +} + +static void PackBytes(ODK_Message* message, const uint8_t* ptr, size_t count) { + ODK_Message_Impl* message_impl = GetMessageImpl(message); + if (!message_impl) return; + if (count <= message_impl->capacity - message_impl->size) { + memcpy((void*)(message_impl->base + message_impl->size), (const void*)ptr, + count); + message_impl->size += count; + } else { + message_impl->status = MESSAGE_STATUS_OVERFLOW_ERROR; + } +} + +void Pack_enum(ODK_Message* message, int value) { + uint32_t v32 = (uint32_t)value; + Pack_uint32_t(message, &v32); +} + +void Pack_bool(ODK_Message* message, const bool* value) { + assert(value); + uint8_t data[4] = {0}; + data[3] = *value ? 1 : 0; + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint8_t(ODK_Message* message, const uint8_t* value) { + assert(value); + uint8_t data[1] = {0}; + data[0] = (uint8_t)(*value >> 0); + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint16_t(ODK_Message* message, const uint16_t* value) { + assert(value); + uint8_t data[2] = {0}; + data[0] = (uint8_t)(*value >> 8); + data[1] = (uint8_t)(*value >> 0); + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint32_t(ODK_Message* message, const uint32_t* value) { + assert(value); + uint8_t data[4] = {0}; + data[0] = (uint8_t)(*value >> 24); + data[1] = (uint8_t)(*value >> 16); + data[2] = (uint8_t)(*value >> 8); + data[3] = (uint8_t)(*value >> 0); + PackBytes(message, data, sizeof(data)); +} + +void Pack_uint64_t(ODK_Message* message, const uint64_t* value) { + assert(value); + uint32_t hi = (uint32_t)(*value >> 32); + uint32_t lo = (uint32_t)(*value); + Pack_uint32_t(message, &hi); + Pack_uint32_t(message, &lo); +} + +void PackArray(ODK_Message* message, const uint8_t* base, size_t size) { + PackBytes(message, base, size); +} + +void Pack_OEMCrypto_Substring(ODK_Message* message, + const OEMCrypto_Substring* obj) { + assert(obj); + uint32_t offset = (uint32_t)obj->offset; + uint32_t length = (uint32_t)obj->length; + Pack_uint32_t(message, &offset); + Pack_uint32_t(message, &length); +} + +static void UnpackBytes(ODK_Message* message, uint8_t* ptr, size_t count) { + assert(ptr); + ODK_Message_Impl* message_impl = GetMessageImpl(message); + if (!message_impl) return; + if (count <= message_impl->size - message_impl->read_offset) { + memcpy((void*)ptr, (void*)(message_impl->base + message_impl->read_offset), + count); + message_impl->read_offset += count; + } else { + message_impl->status = MESSAGE_STATUS_UNDERFLOW_ERROR; + } +} + +int Unpack_enum(ODK_Message* message) { + uint32_t v32; + Unpack_uint32_t(message, &v32); + return (int)v32; +} + +void Unpack_bool(ODK_Message* message, bool* value) { + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + assert(value); + *value = (0 != data[3]); +} + +void Unpack_uint8_t(ODK_Message* message, uint8_t* value) { + assert(value); + uint8_t data[1] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; +} + +void Unpack_uint16_t(ODK_Message* message, uint16_t* value) { + assert(value); + uint8_t data[2] = {0}; + UnpackBytes(message, data, sizeof(data)); + *value = data[0]; + *value = *value << 8 | data[1]; +} + +void Unpack_uint32_t(ODK_Message* message, uint32_t* value) { + ODK_Message_Impl* message_impl = (ODK_Message_Impl*)message; + if (!message_impl) return; + uint8_t data[4] = {0}; + UnpackBytes(message, data, sizeof(data)); + assert(value); + *value = data[0]; + *value = *value << 8 | data[1]; + *value = *value << 8 | data[2]; + *value = *value << 8 | data[3]; +} + +void Unpack_uint64_t(ODK_Message* message, uint64_t* value) { + uint32_t hi = 0; + uint32_t lo = 0; + Unpack_uint32_t(message, &hi); + Unpack_uint32_t(message, &lo); + assert(value); + *value = hi; + *value = *value << 32 | lo; +} + +void Unpack_OEMCrypto_Substring(ODK_Message* message, + OEMCrypto_Substring* obj) { + uint32_t offset = 0, length = 0; + Unpack_uint32_t(message, &offset); + Unpack_uint32_t(message, &length); + ODK_Message_Impl* message_impl = GetMessageImpl(message); + if (!message_impl) return; + + /* Each substring should be contained within the message body, which is in the + * total message, just after the core message. The offset of a substring is + * relative to the message body. So we need to verify: + * + * For non-empty substring: + * offset + length < message_impl->capacity - message_impl->size or + * offset + length + message_impl->size < message_impl->capacity + * + * For empty substring (length is 0): + * offset must be 0 + */ + if (length == 0 && offset != 0) { + message_impl->status = MESSAGE_STATUS_UNKNOWN_ERROR; + return; + } + size_t substring_end = 0; /* = offset + length; */ + size_t end = 0; /* = substring_end + message_impl->size; */ + if (odk_add_overflow_ux(offset, length, &substring_end) || + odk_add_overflow_ux(substring_end, message_impl->size, &end) || + end > message_impl->capacity) { + message_impl->status = MESSAGE_STATUS_OVERFLOW_ERROR; + return; + } + assert(obj); + obj->offset = offset; + obj->length = length; +} + +/* copy out */ +void UnpackArray(ODK_Message* message, uint8_t* address, size_t size) { + UnpackBytes(message, address, size); +} diff --git a/oemcrypto/odk/src/serialization_base.h b/oemcrypto/odk/src/serialization_base.h new file mode 100644 index 00000000..7b69e11f --- /dev/null +++ b/oemcrypto/odk/src/serialization_base.h @@ -0,0 +1,42 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ +#define WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "odk_message.h" + +void Pack_enum(ODK_Message* message, int value); +void Pack_bool(ODK_Message* message, const bool* value); +void Pack_uint8_t(ODK_Message* message, const uint8_t* value); +void Pack_uint16_t(ODK_Message* message, const uint16_t* value); +void Pack_uint32_t(ODK_Message* message, const uint32_t* value); +void Pack_uint64_t(ODK_Message* message, const uint64_t* value); +void PackArray(ODK_Message* message, const uint8_t* base, size_t size); +void Pack_OEMCrypto_Substring(ODK_Message* message, + const OEMCrypto_Substring* obj); + +int Unpack_enum(ODK_Message* message); +void Unpack_bool(ODK_Message* message, bool* value); +void Unpack_uint8_t(ODK_Message* message, uint8_t* value); +void Unpack_uint16_t(ODK_Message* message, uint16_t* value); +void Unpack_uint32_t(ODK_Message* message, uint32_t* value); +void Unpack_uint64_t(ODK_Message* message, uint64_t* value); +void UnpackArray(ODK_Message* message, uint8_t* address, + size_t size); /* copy out */ +void Unpack_OEMCrypto_Substring(ODK_Message* message, OEMCrypto_Substring* obj); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // WIDEVINE_ODK_SRC_SERIALIZATION_BASE_H_ diff --git a/oemcrypto/odk/test/fuzzing/Android.bp b/oemcrypto/odk/test/fuzzing/Android.bp new file mode 100644 index 00000000..3b8fe6d6 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/Android.bp @@ -0,0 +1,180 @@ +// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +// *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE +// CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE +// DEPENDING ON IT IN YOUR PROJECT. *** +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "vendor_widevine_license" + // to get the below license kinds: + // legacy_by_exception_only (by exception only) + default_applicable_licenses: ["vendor_widevine_license"], +} + +cc_defaults { + name: "odk_fuzz_library_defaults", + srcs: [ + "odk_fuzz_helper.cpp", + ], + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/odk/test", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/src", + ], +} + +cc_fuzz { + name: "odk_license_request_fuzz", + srcs: [ + "odk_license_request_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/license_request_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_renewal_request_fuzz", + srcs: [ + "odk_renewal_request_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/renewal_request_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_provisioning_request_fuzz", + srcs: [ + "odk_provisioning_request_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/provisioning_request_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_license_response_fuzz", + srcs: [ + "odk_license_response_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/license_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_renewal_response_fuzz", + srcs: [ + "odk_renewal_response_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/renewal_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_provisioning_response_fuzz", + srcs: [ + "odk_provisioning_response_fuzz.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/provisioning_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_license_response_fuzz_with_mutator", + srcs: [ + "odk_license_response_fuzz_with_mutator.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/license_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_renewal_response_fuzz_with_mutator", + srcs: [ + "odk_renewal_response_fuzz_with_mutator.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/renewal_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} + +cc_fuzz { + name: "odk_provisioning_response_fuzz_with_mutator", + srcs: [ + "odk_provisioning_response_fuzz_with_mutator.cpp", + ], + fuzz_config: { + componentid: 611718, + }, + corpus: ["corpus/little_endian_64bit/provisioning_response_corpus/*"], + static_libs: [ + "libwv_kdo", + "libwv_odk", + ], + defaults: ["odk_fuzz_library_defaults"], + proprietary: true, +} diff --git a/oemcrypto/odk/test/fuzzing/README.md b/oemcrypto/odk/test/fuzzing/README.md new file mode 100644 index 00000000..eb7da4fc --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/README.md @@ -0,0 +1,19 @@ +# ODK Fuzzing + +## Objective + +* Run fuzzing on ODK and KDO serialize and deserialize APIs using google + supported fuzzer engines to find security vulnerabilities. Any issues found + by clusterfuzz will be reported to + [odk fuzz buganizer](https://b.corp.google.com/issues?q=componentid:425099%20status:open%20reporter:cluster-fuzz-googleplex@google.com). + +## Run fuzz target on local machine + +* In order to run fuzz target locally and see code coverage, save binary input + to be tested against fuzz target into a temporary corpus directory and + execute following commands + + ```shell + $ blaze build --config=asan-fuzzer //your:target + $ blaze-bin/your/target FULL_CORPUS_DIR + ``` diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/602c63d2f3d13ca3206cdf204cde24e7d8f4266c b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/602c63d2f3d13ca3206cdf204cde24e7d8f4266c new file mode 100644 index 0000000000000000000000000000000000000000..dc8cabcb1f324b0841ba2624c4e06bdf1d78c325 GIT binary patch literal 20 bcmZQzWMXDvWn<^yMC+6cPpi1x5hB literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/8cebdcc0161125a10e19c45f055051712873de25 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/8cebdcc0161125a10e19c45f055051712873de25 new file mode 100644 index 0000000000000000000000000000000000000000..608e888d58863f67a23c07e34f765097056fea31 GIT binary patch literal 20 bcmZQzU|?imU=U$oVh~vSW4A2>1KTP96i5Sg literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/4e578d6c9628e832c099623b44f56d95aa37f94b b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/4e578d6c9628e832c099623b44f56d95aa37f94b new file mode 100644 index 0000000000000000000000000000000000000000..e319747664c3a072cb675e92f4c140a260b52d3e GIT binary patch literal 272 zcmZQ#5MTfS1r`PdMuyY9x_oQx7|%_vZN0{76TKI*#RUW4q{+YU|`?_v4NP+07yFkF~xw84*}4WBEJ9t literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/5b693511ef850e42c5ffded171794dbeb9460cc0 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/5b693511ef850e42c5ffded171794dbeb9460cc0 new file mode 100644 index 0000000000000000000000000000000000000000..0cf77b5db8a598c0ccdd7237a2ad9668abc9d9ad GIT binary patch literal 282200 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 MxN`v%e^yS0HEaTf(O?rGGx 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%0Dcjl!*KS8ewM*< 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 b6B?vL%6cq8&Y^N8CA^O#>Oy2JB-j1}NHrbT literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/b6b865b095697164ad032c2f695ed828f5754749 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/b6b865b095697164ad032c2f695ed828f5754749 new file mode 100644 index 00000000..225f87ae --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/b6b865b095697164ad032c2f695ed828f5754749 @@ -0,0 +1 @@ +{"componentid":425099} diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dba39b6cf6524e996397ddc1e08b928b5c92bb5d b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dba39b6cf6524e996397ddc1e08b928b5c92bb5d new file mode 100644 index 0000000000000000000000000000000000000000..51f67eb6f45ff450ea3514757025dad75ad500d1 GIT binary patch literal 352 zcmZQ#5Mb~Pe)&9``_n9l%6I{+~@fMf(VX%sP# k9UuTQ*8xgPKq6fYJ$2x&lh?fYKA7^a&_^ z14@5@(2NF9ngdEZKxwGCj2Te=3MkzGrJ?3Cg3VR^mVGHhZK~n=nCS9&UbkJIMlP}4 QB4^hv`22Eu*y2c60K#}7$^ZZW literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/53c26407b39c997143146a0dce8ff0ac11f565e1 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/53c26407b39c997143146a0dce8ff0ac11f565e1 new file mode 100644 index 0000000000000000000000000000000000000000..a2d65a82d61b8d3fa65e52c8ece687ad11b4b8fa GIT binary patch literal 88 zcmZQzU|?lnV2EH~Vi5Qh*1sLdjRf)~!!uJ-%QEv)LsE-N{PS`uy;Ccb@+%As41kiv F008%!4Ez89 literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/fab3c99d604bab7b7bf5c54c5bd995fc98d4d96f b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/fab3c99d604bab7b7bf5c54c5bd995fc98d4d96f new file mode 100644 index 0000000000000000000000000000000000000000..a4ded576fe810d7540ac36f2d1391e33d774e866 GIT binary patch literal 88 zcmZQzWMXDvWn<^yMC+6cQE@6%&_`l#-T_m6KOcR8m$^Ra4i{WB>!^MkXM= SrV7HtBrgbNY%FA8umJ!SD+jRv literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_response_corpus/91e10d030fbdd3374e57a2720f09488f2b03ce69 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_response_corpus/91e10d030fbdd3374e57a2720f09488f2b03ce69 new file mode 100644 index 0000000000000000000000000000000000000000..0616a29b7b8d7c332d597f338c650b780aec2e0b GIT binary patch literal 204 zcmZQ#5McPXg1d4m8w0~CAdqB$fbh(e)UwRH)R5HT692rMO7GOlr2Gm40|SsEA^^}7 npot763``6HmE0@-0l8Cwd`X<mNjIgCII05V1g_y7O^ literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/97bf96be666434bfa93dbfb36b81baeefed14170 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/97bf96be666434bfa93dbfb36b81baeefed14170 new file mode 100644 index 0000000000000000000000000000000000000000..4e6f216d56f5e652b0ee27cb42ee96cee2019eb8 GIT binary patch literal 76 zcmeZ&HYJV$2$G@n2`HTk5<~*5KsFAt@yd E0P{fz=l}o! literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/a7b0e7dca597331d7f051204096c9d01ba6d468e b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/a7b0e7dca597331d7f051204096c9d01ba6d468e new file mode 100644 index 0000000000000000000000000000000000000000..748c29cff8e661c14981c5078b2cb4a1b7170806 GIT binary patch literal 76 wcmeZ&HYJV$2$G@nAt)UW5<~*5KsFCY-aq8_W?*1n4OJEo01amf$^ZZW literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/38df40a320f60e955006aaa294b74d45a316e50f b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/38df40a320f60e955006aaa294b74d45a316e50f new file mode 100644 index 0000000000000000000000000000000000000000..4abb16a8b5bbb92fb8d3df63dd0eff3f8f8de061 GIT binary patch literal 148 zcmZQ#5MW5DXbe?gXJA+b1e2dmiDMuEz|^DD=ti&t1sEZMEI>+yfr&vNw6P+Efq`8C HB8(sb|K<=? literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/9962997b5ea87005276319cbfff67884846485cf b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/9962997b5ea87005276319cbfff67884846485cf new file mode 100644 index 0000000000000000000000000000000000000000..892a4edba65bf1e709cc7c507a1ac714561651fc GIT binary patch literal 148 zcmZQ#5MW5!JE?a$3j@O{ASix1C5{0I7#Ti(ss}NkfC`bq4aEQe literal 0 HcmV?d00001 diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp b/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp new file mode 100644 index 00000000..78519cb0 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/Android.bp @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +// ---------------------------------------------------------------- +// Builds odk_corpus_generator shared library, which can be used with +// LD_PRELOAD command to generate corpus by intercepting oemcrypto +// unit tests. +// ---------------------------------------------------------------- +// Builds libwv_odk.so, The ODK shared Library (libwv_odk) is used +// by the OEMCrypto unit tests to generate corpus for ODK fuzz scrips. +// *** THIS PACKAGE HAS SPECIAL LICENSING CONDITIONS. PLEASE +// CONSULT THE OWNERS AND opensource-licensing@google.com BEFORE +// DEPENDING ON IT IN YOUR PROJECT. *** +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "vendor_widevine_license" + // to get the below license kinds: + // legacy_by_exception_only (by exception only) + default_applicable_licenses: ["vendor_widevine_license"], +} + +cc_library_shared { + name: "libwv_odk_corpus_generator", + include_dirs: [ + "vendor/widevine/libwvdrmengine/oemcrypto/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/include", + "vendor/widevine/libwvdrmengine/oemcrypto/odk/test", + ], + host_ldlibs: ["-ldl"], + srcs: [ + "odk_corpus_generator.c", + "odk_corpus_generator_helper.c", + ], + proprietary: true, + + owner: "widevine", +} diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/README.md b/oemcrypto/odk/test/fuzzing/corpus_generator/README.md new file mode 100644 index 00000000..f6df3746 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/README.md @@ -0,0 +1,79 @@ +# Objective + +The Idea behind the corpus generator code is to intercept OEMCrypto unit test +calls to odk APIs using LD_PRELOAD and read the data into corpus files which can +be fed as corpus to fuzzer scripts. + +LD_PRELOAD command needs to be run from cdm repository while running oemcrypto +unit tests. + +## Get OEMCrypto and Build OEMCrypto unit tests: + +* Install Pre-requisites + + ```shell + $ sudo apt-get install gyp ninja-build + ``` + +* download cdm source code (including ODK & OEMCrypto unit tests): + + ```shell + $ git clone sso://widevine-internal/cdm + ``` + +* We need to run odk as a dynamic library in order to use LD_PRELOAD, apply + patch from go/wvgerrit/95090 to locally cloned repo which has changes to run + odk as dynamic library: + + ```shell + $ cd /path/to/cdm/repo + $ git fetch origin 209721cc901745999e08e35466e74f708321267e + $ git cherry-pick FETCH_HEAD + ``` + +* Build OEMCrypto unit tests: + + ```shell + $ cd /path/to/cdm/repo + $ export PATH_TO_CDM_DIR=.. + $ gyp --format=ninja --depth=$(pwd) oemcrypto/oemcrypto_unittests.gyp + $ ninja -C out/Default/ + ``` + +## Capture corpus for odk fuzzer by intercepting OEMCrypto unit tests: + +When we run LD_PRELOAD command odk_corpus_generator.so gets preloaded before +oemcrypto_unittests and odk_corpus_generator has functions to intercept calls to +ODK request and response APIs. Each call to odk API from oemcrypto_unittests +gets intercepted and input to ODK de serialize response APIs and output from ODK +serialize request APIs is captured in binary format and stored into corpus files + +In order to run LD_PRELOAD command, we need to compile corpus generator shared +library and need to preload that before OEMCrypto unit tests run + +* Compile shared library + + ```shell + $ cd /path/to/cdm/repo + $ gyp --format=ninja --depth=$(pwd) oemcrypto/odk/test/fuzzing/corpus_generator/odk_fuzz_corpus_generator.gyp + $ ninja -C out/Default/ + ``` + +* Preload the shared library before running OEMCrypto unit tests + + ```shell + $ cd oemcrypto/odk/test/fuzzing/corpus + $ mkdir license_request_corpus license_response_corpus renewal_request_corpus renewal_response_corpus provisioning_request_corpus provisioning_response_corpus + $ cd /path/to/cdm/repo + $ LD_PRELOAD=out/Default/lib/libodk_corpus_generator.so ./out/Default/oemcrypto_unittests + ``` + +LD_PRELOAD command runs oemcrypto_unittests with odk_corpus_generator as +interceptor. We should see unit tests being executed. The corpus files in binary +format will be captured into `oemcrypto/odk/test/fuzzing/corpus` path. These +files can be used as input corpus for ODK request and response fuzzer scripts. + +The generated corpus files can be minimized using go/testcorpus#minimize and +uploaded into google3 repository under following directory under respective +corpus types +`fuzzing/corpus` diff --git a/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c new file mode 100644 index 00000000..0a2d0747 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator.c @@ -0,0 +1,158 @@ +// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +// We must define this macro to get RTLD_NEXT definition from +#define _GNU_SOURCE + +#include + +#include "fuzzing/corpus_generator/odk_corpus_generator_helper.h" +#include "fuzzing/odk_fuzz_structs.h" +#include "odk_structs.h" + +OEMCryptoResult ODK_PrepareCoreLicenseRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values) { + OEMCryptoResult (*original_function)(uint8_t*, size_t, size_t*, + const ODK_NonceValues*); + original_function = dlsym(RTLD_NEXT, "ODK_PrepareCoreLicenseRequest"); + OEMCryptoResult oem_crypto_result = (*original_function)( + message, message_length, core_message_length, nonce_values); + char* file_name = GetFileName("license_request_corpus"); + + // License Request format expected by fuzzer - [Core License Request] + AppendToFile(file_name, (const char*)message, *core_message_length); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_ParseLicense( + const uint8_t* message, size_t message_length, size_t core_message_length, + bool initial_license_load, bool usage_entry_present, + const uint8_t* request_hash, ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_license) { + struct ODK_ParseLicense_Args parse_license_args; + parse_license_args.nonce_values = *nonce_values; + memcpy(parse_license_args.request_hash, request_hash, ODK_SHA256_HASH_SIZE); + parse_license_args.timer_limits = *timer_limits; + parse_license_args.clock_values = *clock_values; + parse_license_args.usage_entry_present = usage_entry_present; + parse_license_args.initial_license_load = initial_license_load; + OEMCryptoResult (*original_function)( + const uint8_t*, size_t, size_t, bool, bool, const uint8_t*, + ODK_TimerLimits*, ODK_ClockValues*, ODK_NonceValues*, ODK_ParsedLicense*); + original_function = dlsym(RTLD_NEXT, "ODK_ParseLicense"); + OEMCryptoResult oem_crypto_result = (*original_function)( + message, message_length, core_message_length, initial_license_load, + usage_entry_present, request_hash, timer_limits, clock_values, + nonce_values, parsed_license); + char* file_name = GetFileName("license_response_corpus"); + + // License Response format expected by fuzzer - [ODK_ParseLicense_Args][Core + // License Response] + AppendToFile(file_name, (const char*)&parse_license_args, + sizeof(struct ODK_ParseLicense_Args)); + AppendToFile(file_name, (const char*)message, core_message_length); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_PrepareCoreRenewalRequest(uint8_t* message, + size_t message_length, + size_t* core_message_size, + ODK_NonceValues* nonce_values, + ODK_ClockValues* clock_values, + uint64_t system_time_seconds) { + OEMCryptoResult (*original_function)( + uint8_t*, size_t, size_t*, ODK_NonceValues*, ODK_ClockValues*, uint64_t); + original_function = dlsym(RTLD_NEXT, "ODK_PrepareCoreRenewalRequest"); + OEMCryptoResult oem_crypto_result = + (*original_function)(message, message_length, core_message_size, + nonce_values, clock_values, system_time_seconds); + char* file_name = GetFileName("renewal_request_corpus"); + + // License Request format expected by fuzzer - [ODK_ClockValues][Core + // License Request] + AppendToFile(file_name, (const char*)clock_values, sizeof(ODK_ClockValues)); + AppendToFile(file_name, (const char*)message, *core_message_size); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_ParseRenewal(const uint8_t* message, size_t message_length, + size_t core_message_length, + const ODK_NonceValues* nonce_values, + uint64_t system_time, + const ODK_TimerLimits* timer_limits, + ODK_ClockValues* clock_values, + uint64_t* timer_value) { + struct ODK_ParseRenewal_Args parse_renewal_args; + parse_renewal_args.nonce_values = *nonce_values; + parse_renewal_args.clock_values = *clock_values; + parse_renewal_args.timer_limits = *timer_limits; + parse_renewal_args.system_time = system_time; + OEMCryptoResult (*original_function)( + const uint8_t*, size_t, size_t, const ODK_NonceValues*, uint64_t, + const ODK_TimerLimits*, ODK_ClockValues*, uint64_t*); + original_function = dlsym(RTLD_NEXT, "ODK_ParseRenewal"); + OEMCryptoResult oem_crypto_result = (*original_function)( + message, message_length, core_message_length, nonce_values, system_time, + timer_limits, clock_values, timer_value); + char* file_name = GetFileName("renewal_response_corpus"); + + // Renewal Response format expected by fuzzer - [ODK_ParseRenewal_Args][Core + // Renewal Response] + AppendToFile(file_name, (const char*)&parse_renewal_args, + sizeof(struct ODK_ParseRenewal_Args)); + AppendToFile(file_name, (const char*)message, core_message_length); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_PrepareCoreProvisioningRequest( + uint8_t* message, size_t message_length, size_t* core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length) { + OEMCryptoResult (*original_function)(uint8_t*, size_t, size_t*, + const ODK_NonceValues*, const uint8_t*, + size_t); + original_function = dlsym(RTLD_NEXT, "ODK_PrepareCoreProvisioningRequest"); + OEMCryptoResult oem_crypto_result = + (*original_function)(message, message_length, core_message_length, + nonce_values, device_id, device_id_length); + char* file_name = GetFileName("provisioning_request_corpus"); + + // Provisioning Request format expected by fuzzer - [Core Provisioning + // Request] + AppendToFile(file_name, (const char*)message, *core_message_length); + free(file_name); + return oem_crypto_result; +} + +OEMCryptoResult ODK_ParseProvisioning( + const uint8_t* message, size_t message_length, size_t core_message_length, + const ODK_NonceValues* nonce_values, const uint8_t* device_id, + size_t device_id_length, ODK_ParsedProvisioning* parsed_response) { + struct ODK_ParseProvisioning_Args parse_provisioning_args; + parse_provisioning_args.nonce_values = *nonce_values; + memcpy(parse_provisioning_args.device_id, device_id, device_id_length); + parse_provisioning_args.device_id_length = device_id_length; + OEMCryptoResult (*original_function)(const uint8_t*, size_t, size_t, + const ODK_NonceValues*, const uint8_t*, + size_t, ODK_ParsedProvisioning*); + original_function = dlsym(RTLD_NEXT, "ODK_ParseProvisioning"); + OEMCryptoResult oem_crypto_result = (*original_function)( + message, message_length, core_message_length, nonce_values, device_id, + device_id_length, parsed_response); + char* file_name = GetFileName("provisioning_response_corpus"); + + // Provisioning Response format expected by fuzzer - + // [ODK_ParseProvisioning_Args][Core Provisioning Response] + AppendToFile(file_name, (const char*)&parse_provisioning_args, + sizeof(struct ODK_ParseProvisioning_Args)); + AppendToFile(file_name, (const char*)message, core_message_length); + free(file_name); + return oem_crypto_result; +} 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 new file mode 100644 index 00000000..534b2457 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.c @@ -0,0 +1,22 @@ +// Copyright 2020 Google LLC. All rights reserved. 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" + +void AppendToFile(const char* file_name, const char* message, + const size_t message_size) { + FILE* fptr; + if ((fptr = fopen(file_name, "ab")) == NULL) { + printf("Error! opening file %s", file_name); + return; + } + fwrite(message, message_size, 1, fptr); + fclose(fptr); +} + +char* GetFileName(const char* directory) { + char* file_name; + file_name = malloc(150); + sprintf(file_name, "%s%s/%d", PATH_TO_CORPUS, directory, rand()); + return file_name; +} 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 new file mode 100644 index 00000000..d6c1e99a --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_corpus_generator_helper.h @@ -0,0 +1,18 @@ +// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +#ifndef WIDEVINE_ODK_TEST_FUZZING_CORPUS_GENERATOR_ODK_CORPUS_GENERATOR_HELPER_H_ +#define WIDEVINE_ODK_TEST_FUZZING_CORPUS_GENERATOR_ODK_CORPUS_GENERATOR_HELPER_H_ + +#define PATH_TO_CORPUS "./oemcrypto/odk/test/fuzzing/corpus/" + +#include +#include +#include + +void AppendToFile(const char* file_name, const char* message, + const size_t message_size); + +char* GetFileName(const char* directory); + +#endif // 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 new file mode 100644 index 00000000..83299841 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/corpus_generator/odk_fuzz_corpus_generator.gyp @@ -0,0 +1,33 @@ +# Copyright 2020 Google LLC. All rights reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. + +# Reference Link explaining flags for LD_PRELOAD: https://catonmat.net/simple-ld-preload-tutorial-part-two +{ + 'targets': [ + { + 'target_name': 'odk_corpus_generator', + 'type': 'shared_library', + 'cflags_cc': [ + '-g3', + '-O0', + '-fno-omit-frame-pointer', + '-Wall', + ], + 'include_dirs': [ + '../../../include', + '../../../test', + '../corpus_generator', + ], + 'ldflags': [ + '-fPIC', + ], + 'libraries': [ + '-ldl', + ], + 'sources': [ + 'odk_corpus_generator.c', + ], + } + ] +} diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz.gyp b/oemcrypto/odk/test/fuzzing/odk_fuzz.gyp new file mode 100644 index 00000000..7602e0a5 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz.gyp @@ -0,0 +1,44 @@ +# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. + +#TODO(b/151858867): Fix File paths +{ + 'targets': [ + { + 'target_name': 'odk_fuzz', + 'type': 'executable', + 'includes': [ + '../src/odk.gypi', + '../kdo/oec_util.gypi', + ], + 'include_dirs': [ + '../../include', + '../include', + '../src', + '../kdo/include', + ], + 'cflags': [ + # TODO(b/172518513): Remove this + '-Wno-error=cast-qual', + ], + 'cflags_cc': [ + '-std=c++11', + '-g3', + '-O0', + '-fsanitize=fuzzer,address,undefined', + '-fno-omit-frame-pointer', + ], + 'ldflags': [ + '-fPIC', + '-fsanitize=fuzzer,address,undefined', + ], + 'sources': [ + 'odk_fuzz.cpp', + ], + 'dependencies': [ + '../../../cdm/cdm.gyp:license_protocol' + ], + } + ] +} diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp new file mode 100644 index 00000000..1f3b2301 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.cpp @@ -0,0 +1,163 @@ +// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +#include "fuzzing/odk_fuzz_helper.h" + +#include + +#include "odk.h" + +namespace oemcrypto_core_message { +using features::CoreMessageFeatures; + +bool convert_byte_to_valid_boolean(const bool* in) { + const char* buf = reinterpret_cast(in); + for (int i = 0; i < sizeof(bool); i++) { + if (buf[i]) { + return true; + } + } + return false; +} + +void ConvertDataToValidBools(ODK_ParsedLicense* t) { + // Convert boolean flags in parsed_license to valid bytes to + // avoid errors from msan + t->nonce_required = convert_byte_to_valid_boolean(&t->nonce_required); + t->timer_limits.soft_enforce_playback_duration = + convert_byte_to_valid_boolean( + &t->timer_limits.soft_enforce_playback_duration); + t->timer_limits.soft_enforce_rental_duration = convert_byte_to_valid_boolean( + &t->timer_limits.soft_enforce_rental_duration); +} + +void ConvertDataToValidBools(ODK_PreparedRenewalRequest* t UNUSED) {} + +void ConvertDataToValidBools(ODK_ParsedProvisioning* t UNUSED) {} + +OEMCryptoResult odk_serialize_LicenseRequest( + const void* in UNUSED, uint8_t* out, size_t* size, + const ODK_LicenseRequest& core_license_request UNUSED, + const ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreLicenseRequest(out, SIZE_MAX, size, nonce_values); +} + +OEMCryptoResult odk_serialize_RenewalRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_RenewalRequest& core_renewal, ODK_NonceValues* nonce_values) { + ODK_ClockValues clock{}; + memcpy(&clock, in, sizeof(ODK_ClockValues)); + uint64_t system_time_seconds = core_renewal.playback_time_seconds; + return ODK_PrepareCoreRenewalRequest(out, SIZE_MAX, size, nonce_values, + &clock, system_time_seconds); +} + +OEMCryptoResult odk_serialize_ProvisioningRequest( + const void* in UNUSED, uint8_t* out, size_t* size, + const ODK_ProvisioningRequest& core_provisioning, + const ODK_NonceValues* nonce_values) { + const std::string& device_id = core_provisioning.device_id; + return ODK_PrepareCoreProvisioningRequest( + out, SIZE_MAX, size, nonce_values, + reinterpret_cast(device_id.data()), device_id.size()); +} + +OEMCryptoResult odk_deserialize_LicenseResponse(const uint8_t* message, + size_t core_message_length, + ODK_ParseLicense_Args* a, + ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_lic) { + return ODK_ParseLicense(message, SIZE_MAX, core_message_length, + static_cast(a->initial_license_load), + static_cast(a->usage_entry_present), + &a->timer_limits, &a->clock_values, nonce_values, + parsed_lic); +} + +OEMCryptoResult odk_deserialize_RenewalResponse( + const uint8_t* buf, size_t len, ODK_ParseRenewal_Args* a, + ODK_NonceValues* nonce_values, ODK_PreparedRenewalRequest* renewal_msg) { + /* Address Sanitizer doesn't like values other than 0 OR 1 for boolean + * variables. Input from fuzzer can be parsed and any random bytes can be + * assigned to boolean variables. Using the workaround to mitigate sanitizer + * errors in fuzzer code and converting random bytes to 0 OR 1. + * This has no negative security impact*/ + a->timer_limits.soft_enforce_playback_duration = + convert_byte_to_valid_boolean( + &a->timer_limits.soft_enforce_playback_duration); + a->timer_limits.soft_enforce_rental_duration = convert_byte_to_valid_boolean( + &a->timer_limits.soft_enforce_rental_duration); + uint64_t timer_value = 0; + OEMCryptoResult err = + ODK_ParseRenewal(buf, SIZE_MAX, len, nonce_values, a->system_time, + &a->timer_limits, &a->clock_values, &timer_value); + const bool is_parse_renewal_response_successful = + err == ODK_SET_TIMER || err == ODK_DISABLE_TIMER || + err == ODK_TIMER_EXPIRED || err == ODK_STALE_RENEWAL; + if (!is_parse_renewal_response_successful) { + return err; + } + // In order to capture playback_time information which is part of + // renewal_msg and will be later used in kdo_serialize_RenewalResponse in + // odk_kdo method, we call Unpack_ODK_PreparedRenewalRequest private method. + // playback_time cannot be captured from publicly exposed API + // ODK_ParseRenewal. + ODK_Message msg = ODK_Message_Create(const_cast(buf), len); + ODK_Message_SetSize(&msg, len); + Unpack_ODK_PreparedRenewalRequest(&msg, renewal_msg); + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult odk_deserialize_ProvisioningResponse( + const uint8_t* buf, size_t len, ODK_ParseProvisioning_Args* a, + ODK_NonceValues* nonce_values, ODK_ParsedProvisioning* parsed_prov) { + return ODK_ParseProvisioning(buf, SIZE_MAX, len, nonce_values, a->device_id, + a->device_id_length, parsed_prov); +} + +bool kdo_serialize_LicenseResponse(const ODK_ParseLicense_Args* args, + const ODK_ParsedLicense& parsed_lic, + std::string* oemcrypto_core_message) { + const auto& nonce_values = args->nonce_values; + ODK_LicenseRequest core_request{nonce_values.api_minor_version, + nonce_values.api_major_version, + nonce_values.nonce, nonce_values.session_id}; + std::string core_request_sha_256( + reinterpret_cast(args->request_hash), ODK_SHA256_HASH_SIZE); + return serialize::CreateCoreLicenseResponse( + CoreMessageFeatures::kDefaultFeatures, parsed_lic, core_request, + core_request_sha_256, oemcrypto_core_message); +} + +bool kdo_serialize_RenewalResponse( + const ODK_ParseRenewal_Args* args, + const ODK_PreparedRenewalRequest& renewal_msg, + std::string* oemcrypto_core_message) { + const auto& nonce_values = args->nonce_values; + ODK_RenewalRequest core_request{ + nonce_values.api_minor_version, nonce_values.api_major_version, + nonce_values.nonce, nonce_values.session_id, renewal_msg.playback_time}; + return serialize::CreateCoreRenewalResponse( + CoreMessageFeatures::kDefaultFeatures, core_request, + args->timer_limits.initial_renewal_duration_seconds, + oemcrypto_core_message); +} + +bool kdo_serialize_ProvisioningResponse( + const ODK_ParseProvisioning_Args* args, + const ODK_ParsedProvisioning& parsed_prov, + std::string* oemcrypto_core_message) { + const auto& nonce_values = args->nonce_values; + if (args->device_id_length > sizeof(args->device_id)) { + return false; + } + ODK_ProvisioningRequest core_request{ + nonce_values.api_minor_version, nonce_values.api_major_version, + nonce_values.nonce, nonce_values.session_id, + std::string(reinterpret_cast(args->device_id), + args->device_id_length)}; + return serialize::CreateCoreProvisioningResponse( + CoreMessageFeatures::kDefaultFeatures, parsed_prov, core_request, + oemcrypto_core_message); +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h new file mode 100644 index 00000000..0f454671 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_helper.h @@ -0,0 +1,206 @@ +// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +#ifndef WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_HELPER_H_ +#define WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_HELPER_H_ + +#include +#include + +#include "core_message_features.h" +#include "core_message_serialize.h" +#include "fuzzing/odk_fuzz_structs.h" +#include "odk_attributes.h" +#include "odk_serialize.h" + +namespace oemcrypto_core_message { +bool convert_byte_to_valid_boolean(const bool* in); + +OEMCryptoResult odk_serialize_LicenseRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_LicenseRequest& core_license_request, + const ODK_NonceValues* nonce_values); + +OEMCryptoResult odk_serialize_RenewalRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_RenewalRequest& core_renewal, ODK_NonceValues* nonce_values); + +OEMCryptoResult odk_serialize_ProvisioningRequest( + const void* in, uint8_t* out, size_t* size, + const ODK_ProvisioningRequest& core_provisioning, + const ODK_NonceValues* nonce_values); + +OEMCryptoResult odk_deserialize_LicenseResponse(const uint8_t* message, + size_t core_message_length, + ODK_ParseLicense_Args* a, + ODK_NonceValues* nonce_values, + ODK_ParsedLicense* parsed_lic); + +OEMCryptoResult odk_deserialize_RenewalResponse( + const uint8_t* buf, size_t len, ODK_ParseRenewal_Args* a, + ODK_NonceValues* nonce_values, ODK_PreparedRenewalRequest* renewal_msg); + +OEMCryptoResult odk_deserialize_ProvisioningResponse( + const uint8_t* buf, size_t len, ODK_ParseProvisioning_Args* a, + ODK_NonceValues* nonce_values, ODK_ParsedProvisioning* parsed_prov); + +bool kdo_serialize_LicenseResponse(const ODK_ParseLicense_Args* args, + const ODK_ParsedLicense& parsed_lic, + std::string* oemcrypto_core_message); + +bool kdo_serialize_RenewalResponse( + const ODK_ParseRenewal_Args* args, + const ODK_PreparedRenewalRequest& renewal_msg, + std::string* oemcrypto_core_message); + +bool kdo_serialize_ProvisioningResponse( + const ODK_ParseProvisioning_Args* args, + const ODK_ParsedProvisioning& parsed_prov, + std::string* oemcrypto_core_message); + +// Idea behind having three different functions is: +// Only ODK_ParseLicense structure had fields which needed additional +// procession. Having a single function with templated parameter T was +// failing during compile time because other two structures doesn't have +// fields that need additional processing. Hence to reduce code redundance and +// make us of common FuzzerMutateResponse across three response fuzzers, +// three independent functions were defined and renewal and provisioning +// functions would be empty as no additional processing is needed for them. +void ConvertDataToValidBools(ODK_ParsedLicense* t); + +void ConvertDataToValidBools(ODK_PreparedRenewalRequest* t); + +void ConvertDataToValidBools(ODK_ParsedProvisioning* t); + +// Forward-declare the libFuzzer's mutator callback. Mark it weak so that +// the program links successfully even outside of --config=asan-fuzzer +// (apparently the only config in which LLVM uses our custom mutator). +extern "C" size_t LLVMFuzzerMutate(uint8_t* Data, size_t Size, size_t MaxSize) + __attribute__((weak)); + +template +size_t FuzzerMutateResponse(uint8_t* data, size_t size, size_t max_size, + const F& odk_deserialize_fun, + const G& kdo_serialize_fun) { + const size_t kArgsSize = sizeof(A); + const size_t kCoreResponseSize = sizeof(T); + const size_t kTotalResponseSize = kArgsSize + kCoreResponseSize; + + // Deserializing data in order to make sure it deserializes properly. + // Input byte array format: [function arguments][data to parse]. + std::shared_ptr _args(new A()); + A* args = _args.get(); + memcpy(args, data, kArgsSize); + ODK_NonceValues nonce_values = args->nonce_values; + args->nonce_values.api_major_version = ODK_MAJOR_VERSION; + const uint8_t* buf = data + kArgsSize; + T t = {}; + OEMCryptoResult result = + odk_deserialize_fun(buf, size - kArgsSize, args, &nonce_values, &t); + + // If data doesn't deserialize successfully, We copy random bytes into + // T and serialize using kdo function + // which will create a valid oemcrypto core message using + // nonce and request hash from function args. OEMCrypto core message acts as + // input to odk_kdo. + if (result != OEMCrypto_SUCCESS) { + if (max_size < kTotalResponseSize) { + return 0; + } + // Initialize remaining bytes needed in data to zero. + if (size < kTotalResponseSize) { + memset(data + size, 0, kTotalResponseSize - size); + } + t = {}; + memcpy(&t, buf, kCoreResponseSize); + } + + // Ask LLVM to run its usual mutations, hopefully giving us interesting + // inputs. We copy deserialized data into pointer data, run mutations + // and copy back the mutated data to args and t + memcpy(data + kArgsSize, &t, kCoreResponseSize); + LLVMFuzzerMutate(data, kTotalResponseSize, kTotalResponseSize); + memcpy(args, data, kArgsSize); + memcpy(&t, data + kArgsSize, kCoreResponseSize); + // Convert boolean flags in parsed message to valid bytes to + // avoid errors from msan. Only needed for parsed license. + ConvertDataToValidBools(&t); + // Serialize the data after mutation. + std::string oemcrypto_core_message; + if (!kdo_serialize_fun(args, t, &oemcrypto_core_message)) { + return 0; + } + + // Copy mutated and serialized oemcrypto_core_message to data + // so that it acts as input to odk_kdo function. + memcpy(data + kArgsSize, oemcrypto_core_message.data(), + oemcrypto_core_message.size()); + return kArgsSize + oemcrypto_core_message.size(); +} + +/** + * Template arguments: + * A: struct holding function arguments + * T: odk deserialize output/kdo serialize input structure + * F: odk deserialize function + * G: kdo serialize function + * + * raw bytes -> F deserialize -> struct T -> G serialize -> raw bytes + */ +template +void odk_kdo(const F& odk_fun, const G& kdo_fun, const uint8_t* in, + const size_t size, const size_t args_size, uint8_t* out UNUSED) { + T t = {}; + // Input byte array format: [function arguments][data to parse] + if (size < args_size) { + return; + } + const uint8_t* buf = in + args_size; + std::shared_ptr _args(new A()); + A* args = _args.get(); + memcpy(args, in, args_size); + args->nonce_values.api_major_version = ODK_MAJOR_VERSION; + ODK_NonceValues nonce_values = args->nonce_values; + + OEMCryptoResult result = + odk_fun(buf, size - args_size, args, &nonce_values, &t); + if (result != OEMCrypto_SUCCESS) { + return; + } + std::string oemcrypto_core_message; + if (!kdo_fun(args, t, &oemcrypto_core_message)) { + return; + } +} + +/** + * Template arguments: + * T: kdo deserialize output/odk serialize input structure + * F: kdo deserialize function + * G: odk serialize function + * + * raw bytes -> F deserialize -> struct T -> G serialize -> raw bytes + */ +template +static void kdo_odk(const F& kdo_fun, const G& odk_fun, const uint8_t* in, + size_t size, const size_t clock_value_size, uint8_t* out) { + if (size <= clock_value_size) { + return; + } + // Input byte array format: [Clock Values][data to parse]. + // Only Renewal Request expects clock values to be present. + std::string input(reinterpret_cast(in) + clock_value_size, + size - clock_value_size); + T t = {}; + if (!kdo_fun(input, &t)) { + return; + } + ODK_NonceValues nonce_values = {t.api_minor_version, t.api_major_version, + t.nonce, t.session_id}; + OEMCryptoResult err = odk_fun(in, out, &size, t, &nonce_values); + if (OEMCrypto_SUCCESS != err) { + return; + } +} +} // namespace oemcrypto_core_message +#endif // 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 new file mode 100644 index 00000000..b35c56a0 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_fuzz_structs.h @@ -0,0 +1,28 @@ +// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. +#ifndef WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_STRUCTS_H_ +#define WIDEVINE_ODK_TEST_FUZZING_ODK_FUZZ_STRUCTS_H_ + +#include "odk_structs.h" + +struct ODK_ParseLicense_Args { + ODK_NonceValues nonce_values; + uint8_t initial_license_load; + uint8_t usage_entry_present; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; +}; +struct ODK_ParseRenewal_Args { + ODK_NonceValues nonce_values; + uint64_t system_time; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; +}; +struct ODK_ParseProvisioning_Args { + ODK_NonceValues nonce_values; + size_t device_id_length; + uint8_t device_id[64]; +}; +#endif // 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 new file mode 100644 index 00000000..d089c4ad --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_license_request_fuzz.cpp @@ -0,0 +1,22 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "core_message_deserialize.h" +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { +using oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::vector out(size); + const size_t kClockValueSize = 0; + kdo_odk(CoreLicenseRequestFromMessage, + odk_serialize_LicenseRequest, data, size, + kClockValueSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp new file mode 100644 index 00000000..f3655248 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz.cpp @@ -0,0 +1,20 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kLicenseResponseArgsSize = sizeof(ODK_ParseLicense_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_LicenseResponse, kdo_serialize_LicenseResponse, data, + size, kLicenseResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message 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 new file mode 100644 index 00000000..880e1d88 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_license_response_fuzz_with_mutator.cpp @@ -0,0 +1,36 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +// The custom mutator: Ensure that each input can be deserialized properly +// by ODK function after mutation. +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, + size_t max_size, + unsigned int seed UNUSED) { + const size_t kLicenseResponseArgsSize = sizeof(ODK_ParseLicense_Args); + if (size < kLicenseResponseArgsSize) { + return 0; + } + + // Mutate input data and return mutated input size. + return FuzzerMutateResponse( + data, size, max_size, odk_deserialize_LicenseResponse, + kdo_serialize_LicenseResponse); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kLicenseResponseArgsSize = sizeof(ODK_ParseLicense_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_LicenseResponse, kdo_serialize_LicenseResponse, data, + size, kLicenseResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp new file mode 100644 index 00000000..deac024a --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_request_fuzz.cpp @@ -0,0 +1,22 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "core_message_deserialize.h" +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { +using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::vector out(size); + const size_t kClockValueSize = 0; + kdo_odk(CoreProvisioningRequestFromMessage, + odk_serialize_ProvisioningRequest, data, + size, kClockValueSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp new file mode 100644 index 00000000..3a0457d4 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz.cpp @@ -0,0 +1,21 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kProvisioningResponseArgsSize = + sizeof(ODK_ParseProvisioning_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_ProvisioningResponse, kdo_serialize_ProvisioningResponse, + data, size, kProvisioningResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message 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 new file mode 100644 index 00000000..4ad8ca40 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_provisioning_response_fuzz_with_mutator.cpp @@ -0,0 +1,40 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "fuzzing/odk_fuzz_helper.h" +#include "odk_attributes.h" + +namespace oemcrypto_core_message { + +// The custom mutator: Ensure that each input can be deserialized properly +// by ODK function after mutation. +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, + size_t max_size, + unsigned int seed UNUSED) { + const size_t kProvisioningResponseArgsSize = + sizeof(ODK_ParseProvisioning_Args); + if (size < kProvisioningResponseArgsSize) { + return 0; + } + + // Mutate input data and return mutated input size. + return FuzzerMutateResponse( + data, size, max_size, odk_deserialize_ProvisioningResponse, + kdo_serialize_ProvisioningResponse); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kProvisioningResponseArgsSize = + sizeof(ODK_ParseProvisioning_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_ProvisioningResponse, kdo_serialize_ProvisioningResponse, + data, size, kProvisioningResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp new file mode 100644 index 00000000..d715eeb6 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_request_fuzz.cpp @@ -0,0 +1,22 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "core_message_deserialize.h" +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { +using oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage; + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + std::vector out(size); + const size_t kClockValueSize = sizeof(ODK_ClockValues); + kdo_odk(CoreRenewalRequestFromMessage, + odk_serialize_RenewalRequest, data, size, + kClockValueSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp new file mode 100644 index 00000000..c0903758 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz.cpp @@ -0,0 +1,20 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "fuzzing/odk_fuzz_helper.h" + +namespace oemcrypto_core_message { + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kRenewalResponseArgsSize = sizeof(ODK_ParseRenewal_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_RenewalResponse, kdo_serialize_RenewalResponse, data, + size, kRenewalResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message 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 new file mode 100644 index 00000000..2502ab80 --- /dev/null +++ b/oemcrypto/odk/test/fuzzing/odk_renewal_response_fuzz_with_mutator.cpp @@ -0,0 +1,38 @@ +/* Copyright 2020 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include + +#include "fuzzing/odk_fuzz_helper.h" +#include "odk_attributes.h" + +namespace oemcrypto_core_message { + +// The custom mutator: Ensure that each input can be deserialized properly +// by ODK function after mutation. +extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data, size_t size, + size_t max_size, + unsigned int seed UNUSED) { + const size_t kRenewalResponseArgsSize = sizeof(ODK_ParseRenewal_Args); + if (size < kRenewalResponseArgsSize) { + return 0; + } + + // Mutate input data and return mutated input size. + return FuzzerMutateResponse( + data, size, max_size, odk_deserialize_RenewalResponse, + kdo_serialize_RenewalResponse); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + const size_t kRenewalResponseArgsSize = sizeof(ODK_ParseRenewal_Args); + std::vector out(size); + odk_kdo( + odk_deserialize_RenewalResponse, kdo_serialize_RenewalResponse, data, + size, kRenewalResponseArgsSize, out.data()); + return 0; +} +} // namespace oemcrypto_core_message diff --git a/oemcrypto/odk/test/odk_core_message_test.cpp b/oemcrypto/odk/test/odk_core_message_test.cpp new file mode 100644 index 00000000..22051b22 --- /dev/null +++ b/oemcrypto/odk/test/odk_core_message_test.cpp @@ -0,0 +1,39 @@ +// Copyright 2020 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include + +#include "OEMCryptoCENCCommon.h" +#include "gtest/gtest.h" +#include "odk.h" +#include "third_party/absl/strings/escaping.h" + +namespace wvodk_test { +TEST(CoreMessageTest, RenwalRequest) { + std::string oem = + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrst" + "uvwxyzabcdefghijklmnopqrstuvwxyz"; + const uint8_t* buf = reinterpret_cast(oem.c_str()); + uint8_t* message = const_cast(buf); + size_t message_length = 88; + size_t core_message_length = 88; + uint16_t api_minor_version = 16; + uint16_t api_major_version = 16; + uint32_t nonce = 0; + uint32_t timer_status = 2; + uint64_t time = 10; + enum OEMCrypto_Usage_Entry_Status status = kInactiveUsed; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce}; + ODK_ClockValues clock_values{time, time, time, time, + time, timer_status, status}; + uint64_t system_time_seconds = 100; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewalRequest(message, message_length, + &core_message_length, &nonce_values, + &clock_values, system_time_seconds)); + // All messages have at least a five 4-byte fields. + char* m = reinterpret_cast(message); + VLOG(0) << absl::BytesToHexString(std::string(m, core_message_length)); +} +} // namespace wvodk_test diff --git a/oemcrypto/odk/test/odk_test.cpp b/oemcrypto/odk/test/odk_test.cpp new file mode 100644 index 00000000..a244d25b --- /dev/null +++ b/oemcrypto/odk/test/odk_test.cpp @@ -0,0 +1,975 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "odk.h" + +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "core_message_deserialize.h" +#include "core_message_features.h" +#include "core_message_serialize.h" +#include "core_message_types.h" +#include "gtest/gtest.h" +#include "odk_structs_priv.h" +#include "odk_test_helper.h" + +namespace wvodk_test { + +namespace { + +using oemcrypto_core_message::ODK_LicenseRequest; +using oemcrypto_core_message::ODK_ProvisioningRequest; +using oemcrypto_core_message::ODK_RenewalRequest; + +using oemcrypto_core_message::deserialize::CoreLicenseRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreProvisioningRequestFromMessage; +using oemcrypto_core_message::deserialize::CoreRenewalRequestFromMessage; +using oemcrypto_core_message::deserialize:: + CoreRenewedProvisioningRequestFromMessage; + +using oemcrypto_core_message::features::CoreMessageFeatures; + +using oemcrypto_core_message::serialize::CreateCoreLicenseResponse; +using oemcrypto_core_message::serialize::CreateCoreProvisioningResponse; +using oemcrypto_core_message::serialize::CreateCoreRenewalResponse; + +constexpr uint32_t kExtraPayloadSize = 128u; + +/* Used to parameterize tests by version number. The request is given one + * version number, and we will expect the response to have another version + * number. */ +struct VersionParameters { + uint32_t maximum_major_version; + uint16_t request_major_version; + uint16_t request_minor_version; + uint16_t response_major_version; + uint16_t response_minor_version; +}; + +// This function is called by GTest when a parameterized test fails in order +// to log the parameter used for the failing test. +void PrintTo(const VersionParameters& p, std::ostream* os) { + *os << "max=v" << p.maximum_major_version << ", request = v" + << p.request_major_version << "." << p.request_minor_version + << ", response = v" << p.response_major_version << "." + << p.response_minor_version; +} + +template +void ValidateRequest(uint32_t message_type, + const std::vector& extra_fields, + const F& odk_prepare_func, const G& kdo_parse_func) { + uint32_t message_size = 0; + uint16_t api_major_version = ODK_MAJOR_VERSION; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint32_t nonce = 0xdeadbeef; + uint32_t session_id = 0xcafebabe; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + std::vector total_fields = { + {ODK_UINT32, &message_type, "message_type"}, + {ODK_UINT32, &message_size, "message_size"}, + {ODK_UINT16, &api_minor_version, "api_minor_version"}, + {ODK_UINT16, &api_major_version, "api_major_version"}, + {ODK_UINT32, &nonce, "nonce"}, + {ODK_UINT32, &session_id, "session_id"}, + }; + + total_fields.insert(total_fields.end(), extra_fields.begin(), + extra_fields.end()); + for (auto& field : total_fields) { + message_size += ODK_FieldLength(field.type); + } + + // empty buf, expect core message length to be set correctly + size_t core_message_length = 0; + uint8_t* buf_empty = nullptr; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + odk_prepare_func(buf_empty, &core_message_length, &nonce_values)); + EXPECT_EQ(core_message_length, message_size); + + // non-empty buf, expect core message length to be set correctly, and buf is + // filled with ODK_Field values appropriately + uint8_t* buf = new uint8_t[message_size]{}; + EXPECT_EQ(OEMCrypto_SUCCESS, + odk_prepare_func(buf, &core_message_length, &nonce_values)); + EXPECT_EQ(core_message_length, message_size); + + uint8_t* buf_expected = new uint8_t[message_size]{}; + size_t buf_len_expected = 0; + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, buf_expected, SIZE_MAX, + &buf_len_expected, total_fields)); + EXPECT_EQ(buf_len_expected, message_size); + + EXPECT_NO_FATAL_FAILURE( + ODK_ExpectEqualBuf(buf_expected, buf, message_size, total_fields)); + + // odk kdo round-trip: deserialize from buf, then serialize it to buf2 + // expect them to be identical + T t = {}; + std::string oemcrypto_core_message(reinterpret_cast(buf), + message_size); + EXPECT_TRUE(kdo_parse_func(oemcrypto_core_message, &t)); + nonce_values.api_minor_version = t.api_minor_version; + nonce_values.api_major_version = t.api_major_version; + nonce_values.nonce = t.nonce; + nonce_values.session_id = t.session_id; + uint8_t* buf2 = new uint8_t[message_size]{}; + EXPECT_EQ(OEMCrypto_SUCCESS, + odk_prepare_func(buf2, &core_message_length, &nonce_values)); + EXPECT_EQ(core_message_length, message_size); + EXPECT_NO_FATAL_FAILURE( + ODK_ExpectEqualBuf(buf, buf2, message_size, total_fields)); + + delete[] buf; + delete[] buf_expected; + delete[] buf2; +} + +/** + * Template arguments: + * T: kdo input struct + * F: odk deserializer + * G: kdo serializer + */ +template +void ValidateResponse(const VersionParameters& versions, + ODK_CoreMessage* core_message, + const std::vector& extra_fields, + const F& odk_parse_func, const G& kdo_prepare_func) { + T t = {}; + t.api_major_version = versions.request_major_version; + t.api_minor_version = versions.request_minor_version; + t.nonce = core_message->nonce_values.nonce; + t.session_id = core_message->nonce_values.session_id; + + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(core_message, extra_fields, &buf, &buf_size); + + uint8_t* zero = new uint8_t[buf_size]{}; + size_t bytes_read = 0; + // zero-out input + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_READ, zero, buf_size, + &bytes_read, extra_fields)); + + // Parse buf with odk + const OEMCryptoResult parse_result = odk_parse_func(buf, buf_size); + EXPECT_EQ(OEMCrypto_SUCCESS, parse_result); + + size_t size_out = 0; + if (parse_result != OEMCrypto_SUCCESS) { + ODK_IterFields(ODK_FieldMode::ODK_DUMP, buf, buf_size, &size_out, + extra_fields); + } + + // serialize odk output to oemcrypto_core_message + std::string oemcrypto_core_message; + EXPECT_TRUE(kdo_prepare_func(t, &oemcrypto_core_message)); + + // verify round-trip works + EXPECT_NO_FATAL_FAILURE(ODK_ExpectEqualBuf(buf, oemcrypto_core_message.data(), + buf_size, extra_fields)); + delete[] buf; + delete[] zero; +} + +TEST(OdkTest, SerializeFields) { + uint32_t x[] = {0, 1, 2}; + uint64_t y[] = {3LL << 32, 4LL << 32, 5LL << 32}; + OEMCrypto_Substring s = {.offset = 6, .length = 7}; + std::vector fields = { + {ODK_UINT32, &x[0], "x[0]"}, {ODK_UINT32, &x[1], "x[1]"}, + {ODK_UINT32, &x[2], "x[2]"}, {ODK_UINT64, &y[0], "y[0]"}, + {ODK_UINT64, &y[1], "y[1]"}, {ODK_UINT64, &y[2], "y[2]"}, + {ODK_SUBSTRING, &s, "s"}, + }; + uint8_t buf[1024] = {0}; + uint8_t buf2[1024] = {0}; + size_t bytes_read = 0, bytes_written = 0; + ODK_IterFields(ODK_WRITE, buf, SIZE_MAX, &bytes_read, fields); + std::vector fields2(fields.size()); + ODK_ResetOdkFields(&fields); + ODK_IterFields(ODK_READ, buf, bytes_read, &bytes_written, fields); + ODK_IterFields(ODK_WRITE, buf2, SIZE_MAX, &bytes_read, fields); + + EXPECT_NO_FATAL_FAILURE(ODK_ExpectEqualBuf(buf, buf2, bytes_read, fields)); +} + +TEST(OdkTest, SerializeFieldsStress) { + const int n = 1024; + std::vector fields(n); + std::srand(0); + 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)); + fields[i].value = malloc(ODK_AllocSize(fields[i].type)); + fields[i].name = "stress"; + total_size += ODK_FieldLength(fields[i].type); + } + + uint8_t* buf = new uint8_t[total_size]{}; + for (size_t i = 0; i < total_size; i++) { + buf[i] = std::rand() & 0xff; + } + + size_t bytes_read = 0, bytes_written = 0; + uint8_t* buf2 = new uint8_t[total_size]{}; + ODK_IterFields(ODK_READ, buf, total_size, &bytes_read, fields); + EXPECT_EQ(bytes_read, total_size); + ODK_IterFields(ODK_WRITE, buf2, total_size, &bytes_written, fields); + EXPECT_EQ(bytes_written, total_size); + + EXPECT_NO_FATAL_FAILURE(ODK_ExpectEqualBuf(buf, buf2, total_size, fields)); + + // cleanup + for (int i = 0; i < n; i++) { + free(fields[i].value); + } + delete[] buf; + delete[] buf2; +} + +TEST(OdkTest, NullRequestTest) { + size_t core_message_length = 0; + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + ODK_ClockValues clock_values; + memset(&clock_values, 0, sizeof(clock_values)); + + // Assert that nullptr does not cause a core dump. + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, ODK_PrepareCoreLicenseRequest( + nullptr, 0uL, nullptr, &nonce_values)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreLicenseRequest(nullptr, 0uL, &core_message_length, + nullptr)); + + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewalRequest(nullptr, 0uL, nullptr, &nonce_values, + &clock_values, 0uL)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewalRequest(nullptr, 0uL, &core_message_length, + nullptr, &clock_values, 0uL)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewalRequest(nullptr, 0uL, &core_message_length, + &nonce_values, nullptr, 0uL)); + + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreProvisioningRequest( + nullptr, 0uL, &core_message_length, nullptr, nullptr, 0uL)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreProvisioningRequest(nullptr, 0uL, nullptr, + &nonce_values, nullptr, 0uL)); + + // Null device id in provisioning request is ok + uint8_t message[ODK_PROVISIONING_REQUEST_SIZE] = {0}; + core_message_length = ODK_PROVISIONING_REQUEST_SIZE; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreProvisioningRequest( + message, ODK_PROVISIONING_REQUEST_SIZE, &core_message_length, + &nonce_values, nullptr, 0uL)); + + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewedProvisioningRequest( + nullptr, 0uL, &core_message_length, nullptr, nullptr, 0uL, + OEMCrypto_RenewalACert, nullptr, 0uL)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewedProvisioningRequest( + nullptr, 0uL, nullptr, &nonce_values, nullptr, 0uL, + OEMCrypto_RenewalACert, nullptr, 0uL)); + + // Null device id in renewed provisioning request is ok + uint8_t renewed_message[ODK_RENEWED_PROVISIONING_REQUEST_SIZE] = {0}; + uint8_t renewal_data[ODK_KEYBOX_RENEWAL_DATA_SIZE] = {0}; + uint32_t renewal_data_length = ODK_KEYBOX_RENEWAL_DATA_SIZE; + core_message_length = ODK_RENEWED_PROVISIONING_REQUEST_SIZE; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewedProvisioningRequest( + renewed_message, ODK_RENEWED_PROVISIONING_REQUEST_SIZE, + &core_message_length, &nonce_values, nullptr, 0uL, + OEMCrypto_RenewalACert, renewal_data, renewal_data_length)); + + // Null renewal data in renewed provisioning request is ok + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + uint32_t device_id_length = ODK_DEVICE_ID_LEN_MAX; + core_message_length = ODK_RENEWED_PROVISIONING_REQUEST_SIZE; + ODK_PrepareCoreRenewedProvisioningRequest( + renewed_message, ODK_RENEWED_PROVISIONING_REQUEST_SIZE, + &core_message_length, &nonce_values, device_id, device_id_length, + OEMCrypto_RenewalACert, nullptr, 0uL); +} + +TEST(OdkTest, NullResponseTest) { + constexpr size_t message_size = 64; + uint8_t message[message_size] = {0}; + size_t core_message_length = message_size; + ODK_TimerLimits timer_limits; + ODK_ParsedLicense parsed_license; + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + ODK_ClockValues clock_values; + memset(&clock_values, 0, sizeof(clock_values)); + + // Assert that nullptr does not cause a core dump. + EXPECT_EQ( + ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, true, + &timer_limits, &clock_values, &nonce_values, nullptr)); + EXPECT_EQ( + ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, true, + &timer_limits, &clock_values, nullptr, &parsed_license)); + EXPECT_EQ( + ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, true, + &timer_limits, nullptr, &nonce_values, &parsed_license)); + EXPECT_EQ( + ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(message, message_size, core_message_length, true, true, + nullptr, &clock_values, &nonce_values, &parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseLicense(nullptr, message_size, core_message_length, true, + true, &timer_limits, &clock_values, &nonce_values, + &parsed_license)); + + constexpr uint64_t system_time = 0; + uint64_t timer_value = 0; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseRenewal(message, message_size, core_message_length, + &nonce_values, system_time, &timer_limits, nullptr, + &timer_value)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseRenewal(message, message_size, core_message_length, + &nonce_values, system_time, nullptr, &clock_values, + &timer_value)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseRenewal(message, message_size, core_message_length, + nullptr, system_time, &timer_limits, &clock_values, + &timer_value)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseRenewal(nullptr, message_size, core_message_length, + &nonce_values, system_time, &timer_limits, + &clock_values, &timer_value)); + + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + ODK_ParsedProvisioning parsed_response; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseProvisioning(message, message_size, core_message_length, + &nonce_values, device_id, + ODK_DEVICE_ID_LEN_MAX, nullptr)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseProvisioning(message, message_size, core_message_length, + &nonce_values, nullptr, 0, &parsed_response)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseProvisioning(message, message_size, core_message_length, + nullptr, device_id, ODK_DEVICE_ID_LEN_MAX, + &parsed_response)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_ParseProvisioning(nullptr, message_size, core_message_length, + &nonce_values, device_id, + ODK_DEVICE_ID_LEN_MAX, &parsed_response)); +} + +TEST(OdkTest, PrepareCoreLicenseRequest) { + uint8_t license_message[ODK_LICENSE_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(license_message); + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_PrepareCoreLicenseRequest( + license_message, sizeof(license_message), + &core_message_length, &nonce_values)); +} + +TEST(OdkTest, PrepareCoreLicenseRequestSize) { + uint8_t license_message[ODK_LICENSE_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(license_message); + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + // message length smaller than core message length + size_t core_message_length_invalid = core_message_length + 1; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreLicenseRequest( + license_message, sizeof(license_message), + &core_message_length_invalid, &nonce_values)); + // message length larger than core message length + uint8_t license_message_large[ODK_LICENSE_REQUEST_SIZE * 2] = {0}; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreLicenseRequest(license_message_large, + sizeof(license_message_large), + &core_message_length, &nonce_values)); +} + +TEST(OdkTest, PrepareCoreRenewalRequest) { + uint8_t renewal_message[ODK_RENEWAL_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(renewal_message); + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + ODK_ClockValues clock_values; + memset(&clock_values, 0, sizeof(clock_values)); + constexpr uint64_t system_time_seconds = 10; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewalRequest( + renewal_message, sizeof(renewal_message), &core_message_length, + &nonce_values, &clock_values, system_time_seconds)); +} + +TEST(OdkTest, PrepareCoreRenewalRequestTimer) { + uint8_t renewal_message[ODK_RENEWAL_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(renewal_message); + ODK_NonceValues nonce_values{2, 16, 0, 0}; + constexpr uint64_t system_time_seconds = 10; + ODK_ClockValues clock_values_updated; + memset(&clock_values_updated, 0, sizeof(clock_values_updated)); + // system time smaller than first decrypt time + clock_values_updated.time_of_first_decrypt = system_time_seconds + 1; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewalRequest( + renewal_message, sizeof(renewal_message), &core_message_length, + &nonce_values, &clock_values_updated, system_time_seconds)); + clock_values_updated.time_of_first_decrypt = system_time_seconds - 1; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewalRequest( + renewal_message, sizeof(renewal_message), &core_message_length, + &nonce_values, &clock_values_updated, system_time_seconds)); + // clock_values.time_of_renewal_request should get updated + EXPECT_EQ(system_time_seconds - clock_values_updated.time_of_first_decrypt, + clock_values_updated.time_of_renewal_request); +} + +TEST(OdkTest, PrepareCoreProvisioningRequest) { + uint8_t provisioning_message[ODK_PROVISIONING_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(provisioning_message); + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + EXPECT_EQ( + OEMCrypto_SUCCESS, + ODK_PrepareCoreProvisioningRequest( + provisioning_message, sizeof(provisioning_message), + &core_message_length, &nonce_values, device_id, sizeof(device_id))); +} + +TEST(OdkTest, PrepareCoreRenewedProvisioningRequest) { + uint8_t provisioning_message[ODK_RENEWED_PROVISIONING_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(provisioning_message); + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + uint8_t renewal_data[ODK_KEYBOX_RENEWAL_DATA_SIZE] = {0}; + EXPECT_EQ( + OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewedProvisioningRequest( + provisioning_message, sizeof(provisioning_message), + &core_message_length, &nonce_values, device_id, sizeof(device_id), + OEMCrypto_RenewalACert, renewal_data, sizeof(renewal_data))); +} + +TEST(OdkTest, PrepareCoreProvisioningRequestDeviceId) { + uint8_t provisioning_message[ODK_PROVISIONING_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(provisioning_message); + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + uint8_t device_id_invalid[ODK_DEVICE_ID_LEN_MAX + 1] = {0}; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreProvisioningRequest( + provisioning_message, sizeof(provisioning_message), + &core_message_length, &nonce_values, device_id_invalid, + sizeof(device_id_invalid))); +} + +TEST(OdkTest, PrepareCoreRenewedProvisioningRequestDeviceId) { + uint8_t provisioning_message[ODK_PROVISIONING_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(provisioning_message); + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + uint8_t device_id_invalid[ODK_DEVICE_ID_LEN_MAX + 1] = {0}; + uint8_t renewal_data[ODK_KEYBOX_RENEWAL_DATA_SIZE] = {0}; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewedProvisioningRequest( + provisioning_message, sizeof(provisioning_message), + &core_message_length, &nonce_values, device_id_invalid, + sizeof(device_id_invalid), OEMCrypto_RenewalACert, renewal_data, + sizeof(renewal_data))); +} + +TEST(OdkTest, PrepareCoreRenewedProvisioningRequestRenewalDataInvalid) { + uint8_t provisioning_message[ODK_PROVISIONING_REQUEST_SIZE] = {0}; + size_t core_message_length = sizeof(provisioning_message); + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + uint8_t renewal_data_invalid[ODK_KEYBOX_RENEWAL_DATA_SIZE + 1] = {0}; + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, + ODK_PrepareCoreRenewedProvisioningRequest( + provisioning_message, sizeof(provisioning_message), + &core_message_length, &nonce_values, device_id, + sizeof(device_id), OEMCrypto_RenewalACert, renewal_data_invalid, + sizeof(renewal_data_invalid))); +} + +// Serialize and de-serialize license request +TEST(OdkTest, LicenseRequestRoundtrip) { + std::vector empty; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreLicenseRequest(buf, SIZE_MAX, size, nonce_values); + }; + auto kdo_parse_func = CoreLicenseRequestFromMessage; + ValidateRequest(ODK_License_Request_Type, empty, + odk_prepare_func, kdo_parse_func); +} + +TEST(OdkTest, RenewalRequestRoundtrip) { + constexpr uint64_t system_time_seconds = 0xBADDCAFE000FF1CE; + uint64_t playback_time = 0xCAFE00000000; + const uint64_t playback_start = system_time_seconds - playback_time; + const std::vector extra_fields = { + {ODK_UINT64, &playback_time, "playback_time"}, + }; + ODK_ClockValues clock_values; + memset(&clock_values, 0, sizeof(clock_values)); + clock_values.time_of_first_decrypt = playback_start; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreRenewalRequest(buf, SIZE_MAX, size, nonce_values, + &clock_values, system_time_seconds); + }; + auto kdo_parse_func = [&](const std::string& oemcrypto_core_message, + ODK_RenewalRequest* core_renewal_request) { + bool ok = CoreRenewalRequestFromMessage(oemcrypto_core_message, + core_renewal_request); + return ok; + }; + ValidateRequest(ODK_Renewal_Request_Type, extra_fields, + odk_prepare_func, kdo_parse_func); +} + +TEST(OdkTest, ProvisionRequestRoundtrip) { + uint32_t device_id_length = ODK_DEVICE_ID_LEN_MAX / 2; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + memset(device_id, 0xff, device_id_length); + std::vector extra_fields = { + {ODK_UINT32, &device_id_length, "device_id_length"}, + {ODK_DEVICEID, device_id, "device_id"}, + }; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + const ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreProvisioningRequest(buf, SIZE_MAX, size, nonce_values, + device_id, device_id_length); + }; + auto kdo_parse_func = + [&](const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + bool ok = CoreProvisioningRequestFromMessage(oemcrypto_core_message, + core_provisioning_request); + return ok; + }; + ValidateRequest(ODK_Provisioning_Request_Type, + extra_fields, odk_prepare_func, + kdo_parse_func); +} + +TEST(OdkTest, RenewedProvisionRequestRoundtrip) { + uint32_t device_id_length = ODK_DEVICE_ID_LEN_MAX / 2; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + memset(device_id, 0xff, device_id_length); + uint16_t renewal_type = OEMCrypto_RenewalACert; + uint32_t renewal_data_length = ODK_KEYBOX_RENEWAL_DATA_SIZE / 2; + uint8_t renewal_data[ODK_KEYBOX_RENEWAL_DATA_SIZE] = {0}; + memset(renewal_data, 0xff, renewal_data_length); + std::vector extra_fields = { + {ODK_UINT32, &device_id_length, "device_id_length"}, + {ODK_DEVICEID, device_id, "device_id"}, + {ODK_UINT16, &renewal_type, "renewal_type"}, + {ODK_UINT32, &renewal_data_length, "renewal_data_length"}, + {ODK_RENEWALDATA, renewal_data, "renewal_data"}, + }; + auto odk_prepare_func = [&](uint8_t* const buf, size_t* size, + const ODK_NonceValues* nonce_values) { + return ODK_PrepareCoreRenewedProvisioningRequest( + buf, SIZE_MAX, size, nonce_values, device_id, device_id_length, + renewal_type, renewal_data, renewal_data_length); + }; + auto kdo_parse_func = + [&](const std::string& oemcrypto_core_message, + ODK_ProvisioningRequest* core_provisioning_request) { + bool ok = CoreRenewedProvisioningRequestFromMessage( + oemcrypto_core_message, core_provisioning_request); + return ok; + }; + ValidateRequest( + ODK_Renewed_Provisioning_Request_Type, extra_fields, odk_prepare_func, + kdo_parse_func); +} + +TEST(OdkTest, ParseLicenseErrorNonce) { + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms, ODK_MAJOR_VERSION); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + // temporarily mess up with nonce + params.core_message.nonce_values.nonce = 0; + OEMCryptoResult err = ODK_ParseLicense( + buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, + params.usage_entry_present, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + EXPECT_EQ(OEMCrypto_ERROR_INVALID_NONCE, err); + delete[] buf; +} + +TEST(OdkTest, ParseLicenseErrorUsageEntry) { + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms, ODK_MAJOR_VERSION); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + params.usage_entry_present = false; + OEMCryptoResult err = ODK_ParseLicense( + buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, + params.usage_entry_present, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, err); + delete[] buf; +} + +TEST(OdkTest, ParseLicenseNullSubstring) { + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms, ODK_MAJOR_VERSION); + params.parsed_license.srm_restriction_data.offset = 0; + params.parsed_license.srm_restriction_data.length = 0; + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + OEMCryptoResult result = ODK_ParseLicense( + buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, + params.usage_entry_present, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + EXPECT_EQ(OEMCrypto_SUCCESS, result); + delete[] buf; +} + +TEST(OdkTest, ParseLicenseErrorSubstringOffset) { + // offset out of range + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms, ODK_MAJOR_VERSION); + params.parsed_license.enc_mac_keys_iv.offset = 1024; + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + OEMCryptoResult err = ODK_ParseLicense( + buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, + params.usage_entry_present, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, err); + delete[] buf; + + // offset + length out of range + err = OEMCrypto_SUCCESS; + ODK_SetDefaultLicenseResponseParams(¶ms, ODK_MAJOR_VERSION); + params.parsed_license.enc_mac_keys_iv.length = buf_size; + buf = nullptr; + buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + err = ODK_ParseLicense( + buf, buf_size + kExtraPayloadSize, buf_size, params.initial_license_load, + params.usage_entry_present, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, err); + delete[] buf; +} + +TEST(OdkTest, ParseRenewalErrorTimer) { + ODK_RenewalResponseParams params; + ODK_SetDefaultRenewalResponseParams(¶ms); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + params.clock_values.time_of_renewal_request = 0; + OEMCryptoResult err = ODK_ParseRenewal( + buf, buf_size, buf_size, &(params.core_message.nonce_values), + params.system_time, &(params.timer_limits), &(params.clock_values), + &(params.playback_timer)); + EXPECT_EQ(ODK_STALE_RENEWAL, err); + delete[] buf; +} + +TEST(OdkTest, ParsePrivisioningErrorDeviceId) { + ODK_ProvisioningResponseParams params; + ODK_SetDefaultProvisioningResponseParams(¶ms); + uint8_t* buf = nullptr; + uint32_t buf_size = 0; + ODK_BuildMessageBuffer(&(params.core_message), params.extra_fields, &buf, + &buf_size); + // temporarily mess up with device_id + params.device_id[0] = 0; + OEMCryptoResult err = ODK_ParseProvisioning( + buf, buf_size + 16, buf_size, &(params.core_message.nonce_values), + params.device_id, params.device_id_length, &(params.parsed_provisioning)); + EXPECT_EQ(ODK_ERROR_CORE_MESSAGE, err); + delete[] buf; +} + +class OdkVersionTest : public ::testing::Test, + public ::testing::WithParamInterface { + protected: + template + void SetRequestVersion(P* params) { + params->core_message.nonce_values.api_major_version = + GetParam().response_major_version; + params->core_message.nonce_values.api_minor_version = + GetParam().response_minor_version; + features_ = + CoreMessageFeatures::DefaultFeatures(GetParam().maximum_major_version); + } + CoreMessageFeatures features_; +}; + +// Serialize and de-serialize license response +TEST_P(OdkVersionTest, LicenseResponseRoundtrip) { + ODK_LicenseResponseParams params; + ODK_SetDefaultLicenseResponseParams(¶ms, + GetParam().response_major_version); + SetRequestVersion(¶ms); + // For v17, we do not use the hash to verify the request. However, the server + // needs to be backwards compatible, so it still needs to pass the hash into + // CreateCoreLiceseseResponse below. Save a copy of params.request_hash as it + // will be zero out during the test + uint8_t request_hash_read[ODK_SHA256_HASH_SIZE]; + memcpy(request_hash_read, params.request_hash, sizeof(request_hash_read)); + auto odk_parse_func = [&](const uint8_t* buf, size_t size) { + return ODK_ParseLicense( + buf, size + kExtraPayloadSize, size, params.initial_license_load, + params.usage_entry_present, &(params.timer_limits), + &(params.clock_values), &(params.core_message.nonce_values), + &(params.parsed_license)); + }; + const std::string request_hash_string( + reinterpret_cast(request_hash_read), + sizeof(request_hash_read)); + auto kdo_prepare_func = [&](const ODK_LicenseRequest& core_request, + std::string* oemcrypto_core_message) { + return CreateCoreLicenseResponse(features_, params.parsed_license, + core_request, request_hash_string, + oemcrypto_core_message); + }; + ValidateResponse(GetParam(), &(params.core_message), + params.extra_fields, odk_parse_func, + kdo_prepare_func); +} + +TEST_P(OdkVersionTest, RenewalResponseRoundtrip) { + ODK_RenewalResponseParams params; + ODK_SetDefaultRenewalResponseParams(¶ms); + SetRequestVersion(¶ms); + const uint64_t playback_clock = params.playback_clock; + const uint64_t renewal_duration = params.renewal_duration; + auto odk_parse_func = [&](const uint8_t* buf, size_t size) { + OEMCryptoResult err = + ODK_ParseRenewal(buf, size, size, &(params.core_message.nonce_values), + params.system_time, &(params.timer_limits), + &(params.clock_values), &(params.playback_timer)); + + EXPECT_EQ(ODK_SET_TIMER, err); + EXPECT_EQ(renewal_duration, params.playback_timer); + EXPECT_EQ(params.clock_values.time_when_timer_expires, + params.system_time + params.playback_timer); + + return OEMCrypto_SUCCESS; + }; + auto kdo_prepare_func = [&](ODK_RenewalRequest& core_request, + std::string* oemcrypto_core_message) { + core_request.playback_time_seconds = playback_clock; + return CreateCoreRenewalResponse(features_, core_request, renewal_duration, + 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); + SetRequestVersion(¶ms); + // save a copy of params.device_id as it will be zero out during the test + const uint32_t device_id_length = params.device_id_length; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX] = {0}; + memcpy(device_id, params.device_id, device_id_length); + + auto odk_parse_func = [&](const uint8_t* buf, size_t size) { + OEMCryptoResult err = ODK_ParseProvisioning( + buf, size + 16, size, &(params.core_message.nonce_values), device_id, + device_id_length, &(params.parsed_provisioning)); + return err; + }; + auto kdo_prepare_func = [&](ODK_ProvisioningRequest& core_request, + std::string* oemcrypto_core_message) { + core_request.device_id.assign(reinterpret_cast(device_id), + device_id_length); + return CreateCoreProvisioningResponse(features_, params.parsed_provisioning, + core_request, oemcrypto_core_message); + }; + ValidateResponse(GetParam(), &(params.core_message), + params.extra_fields, odk_parse_func, + kdo_prepare_func); +} + +// If the minor version is positive, we can test an older minor version. +const uint16_t kOldMinor = ODK_MINOR_VERSION > 0 ? ODK_MINOR_VERSION - 1 : 0; +// Similarly, if this isn't the first major version, we can test an older major +// version. +// TODO(b/163416999): Remove it in the future. This will be unecessarily +// complicated after we upgrade to version 17. +const uint16_t kOldMajor = ODK_MAJOR_VERSION > ODK_FIRST_VERSION + ? ODK_MAJOR_VERSION - 1 + : ODK_FIRST_VERSION; +// If there is an older major, then we should accept any minor version. +// Otherwise, this test won't make sense and we should just use a minor of 0. +const uint16_t kOldMajorMinor = ODK_MAJOR_VERSION > ODK_FIRST_VERSION ? 42 : 0; + +// List of major and minor versions to test. +std::vector TestCases() { + std::vector test_cases{ + // Fields: maximum major version, + // request major, request minor, response major, response minor, + {ODK_MAJOR_VERSION, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, + ODK_MAJOR_VERSION, ODK_MINOR_VERSION}, + {ODK_MAJOR_VERSION, ODK_MAJOR_VERSION, ODK_MINOR_VERSION + 1, + ODK_MAJOR_VERSION, ODK_MINOR_VERSION}, + {ODK_MAJOR_VERSION, ODK_MAJOR_VERSION, kOldMinor, ODK_MAJOR_VERSION, + kOldMinor}, + {ODK_MAJOR_VERSION, ODK_MAJOR_VERSION, 0, ODK_MAJOR_VERSION, 0}, + {ODK_MAJOR_VERSION, ODK_MAJOR_VERSION + 1, 42, ODK_MAJOR_VERSION, + ODK_MINOR_VERSION}, + {ODK_MAJOR_VERSION, kOldMajor, 0, kOldMajor, 0}, + {ODK_MAJOR_VERSION, kOldMajor, kOldMajorMinor, kOldMajor, kOldMajorMinor}, + // If the server is restricted to v16, then the response can be at + // most 16.5 + {16, ODK_MAJOR_VERSION, ODK_MINOR_VERSION, 16, 5}, + // Here are some known good versions. Make extra sure they work. + {16, 16, 3, 16, 3}, + {16, 16, 4, 16, 4}, + {16, 16, 5, 16, 5}, + {17, 16, 3, 16, 3}, + {17, 16, 4, 16, 4}, + {17, 16, 5, 16, 5}, + {17, 17, 0, 17, 0}, + {17, 17, 1, 17, 1}, + }; + return test_cases; +} + +INSTANTIATE_TEST_SUITE_P(OdkVersionTests, OdkVersionTest, + ::testing::ValuesIn(TestCases())); + +TEST(OdkSizeTest, LicenseRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreLicenseRequest(message, message_length, + &core_message_length, &nonce_values)); + // the core_message_length should be appropriately set + EXPECT_EQ(ODK_LICENSE_REQUEST_SIZE, core_message_length); +} + +TEST(OdkSizeTest, RenewalRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = ODK_MAJOR_VERSION; + uint32_t nonce = 0; + uint32_t session_id = 0; + ODK_ClockValues clock_values = {}; + clock_values.time_of_first_decrypt = 10; + clock_values.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + uint64_t system_time_seconds = 15; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreRenewalRequest(message, message_length, + &core_message_length, &nonce_values, + &clock_values, system_time_seconds)); + // the core_message_length should be appropriately set + EXPECT_EQ(ODK_RENEWAL_REQUEST_SIZE, core_message_length); +} + +TEST(OdkSizeTest, ReleaseRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + ODK_ClockValues clock_values = {}; + clock_values.time_of_first_decrypt = 10; + clock_values.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_INACTIVE; + uint64_t system_time_seconds = 15; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + EXPECT_EQ(OEMCrypto_SUCCESS, + ODK_PrepareCoreRenewalRequest(message, message_length, + &core_message_length, &nonce_values, + &clock_values, system_time_seconds)); + // Release requests do not have a core message. + EXPECT_GE(core_message_length, 0u); +} + +TEST(OdkSizeTest, ProvisioningRequest) { + uint8_t* message = nullptr; + size_t message_length = 0; + size_t core_message_length = 0; + uint16_t api_minor_version = ODK_MINOR_VERSION; + uint16_t api_major_version = 0; + uint32_t nonce = 0; + uint32_t session_id = 0; + uint32_t device_id_length = 0; + ODK_NonceValues nonce_values{api_minor_version, api_major_version, nonce, + session_id}; + EXPECT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + ODK_PrepareCoreProvisioningRequest( + message, message_length, &core_message_length, &nonce_values, + nullptr, device_id_length)); + // the core_message_length should be appropriately set + EXPECT_EQ(ODK_PROVISIONING_REQUEST_SIZE, core_message_length); +} + +// Verify the version string contains the right version numbers. +TEST(OdkTest, CheckReleaseVersion) { + // Here are the version numbers. + std::string expected_version = std::to_string(ODK_MAJOR_VERSION) + "." + + std::to_string(ODK_MINOR_VERSION); + // Here is the version string. + EXPECT_NE(std::string(ODK_RELEASE_DATE).find(expected_version), + std::string::npos) + << "Version mismatch in odk_structs.h"; +} + +} // namespace + +} // namespace wvodk_test diff --git a/oemcrypto/odk/test/odk_test.gypi b/oemcrypto/odk/test/odk_test.gypi new file mode 100644 index 00000000..0cb8a00e --- /dev/null +++ b/oemcrypto/odk/test/odk_test.gypi @@ -0,0 +1,13 @@ +# Copyright 2019 Google LLC. All rights reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. + +{ + 'sources': [ + 'odk_test.cpp', + 'odk_test_helper.cpp', + 'odk_test_helper.h', + 'odk_timer_test.cpp', + ], +} + diff --git a/oemcrypto/odk/test/odk_test_helper.cpp b/oemcrypto/odk/test/odk_test_helper.cpp new file mode 100644 index 00000000..dab9afa3 --- /dev/null +++ b/oemcrypto/odk/test/odk_test_helper.cpp @@ -0,0 +1,656 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#include "odk_test_helper.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "OEMCryptoCENCCommon.h" +#include "gtest/gtest.h" +#include "odk_endian.h" +#include "odk_structs.h" +#include "odk_structs_priv.h" + +namespace wvodk_test { + +void ODK_SetDefaultCoreFields(ODK_CoreMessage* core_message, + ODK_MessageType message_type) { + ASSERT_TRUE(core_message != nullptr); + core_message->message_type = message_type; + core_message->message_length = 0; + core_message->nonce_values.api_minor_version = ODK_MINOR_VERSION; + core_message->nonce_values.api_major_version = ODK_MAJOR_VERSION; + core_message->nonce_values.nonce = 0xdeadbeef; + core_message->nonce_values.session_id = 0xcafebabe; +} + +void ODK_SetDefaultLicenseResponseParams(ODK_LicenseResponseParams* params, + uint32_t odk_major_version) { + ODK_SetDefaultCoreFields(&(params->core_message), ODK_License_Response_Type); + params->initial_license_load = true; + params->usage_entry_present = true; + params->parsed_license = { + .enc_mac_keys_iv = {.offset = 0, .length = 1}, + .enc_mac_keys = {.offset = 2, .length = 3}, + .pst = {.offset = 4, .length = 5}, + .srm_restriction_data = {.offset = 6, .length = 7}, + .license_type = OEMCrypto_EntitlementLicense, + .nonce_required = true, + .timer_limits = + { + .soft_enforce_rental_duration = true, + .soft_enforce_playback_duration = false, + .earliest_playback_start_seconds = 10, + .rental_duration_seconds = 11, + .total_playback_duration_seconds = 12, + .initial_renewal_duration_seconds = 13, + }, + .watermarking = 0, + .dtcp2_required = {.dtcp2_required = 0, + .cmi_descriptor_0 = + { + .id = 0, + .extension = 0, + .length = 1, + .data = 0, + }, + .cmi_descriptor_1 = + { + .id = 1, + .extension = 0, + .length = 3, + .data = {0, 0, 0}, + }, + .cmi_descriptor_2 = + { + .id = 2, + .extension = 0, + .length = 3, + .data = {0, 0, 0}, + }}, + .key_array_length = 3, + .key_array = + { + { + .key_id = {.offset = 15, .length = 16}, + .key_data_iv = {.offset = 17, .length = 18}, + .key_data = {.offset = 19, .length = 20}, + .key_control_iv = {.offset = 21, .length = 22}, + .key_control = {.offset = 23, .length = 24}, + }, + { + .key_id = {.offset = 25, .length = 26}, + .key_data_iv = {.offset = 27, .length = 28}, + .key_data = {.offset = 29, .length = 30}, + .key_control_iv = {.offset = 31, .length = 32}, + .key_control = {.offset = 33, .length = 34}, + }, + { + .key_id = {.offset = 35, .length = 36}, + .key_data_iv = {.offset = 37, .length = 38}, + .key_data = {.offset = 39, .length = 40}, + .key_control_iv = {.offset = 41, .length = 42}, + .key_control = {.offset = 43, .length = 44}, + }, + }, + }; + memset(params->request_hash, 0xaa, sizeof(params->request_hash)); + params->extra_fields = { + {ODK_SUBSTRING, &(params->parsed_license.enc_mac_keys_iv), + ".enc_mac_keys_iv"}, + {ODK_SUBSTRING, &(params->parsed_license.enc_mac_keys), ".enc_mac_keys"}, + {ODK_SUBSTRING, &(params->parsed_license.pst), ".pst"}, + {ODK_SUBSTRING, &(params->parsed_license.srm_restriction_data), + ".srm_restriction_data"}, + {ODK_UINT32, &(params->parsed_license.license_type), ".license_type"}, + {ODK_UINT32, &(params->parsed_license.nonce_required), ".nonce_required"}, + {ODK_BOOL, + &(params->parsed_license.timer_limits.soft_enforce_rental_duration), + ".soft_enforce_rental_duration"}, + {ODK_BOOL, + &(params->parsed_license.timer_limits.soft_enforce_playback_duration), + ".soft_enforce_playback_duration"}, + {ODK_UINT64, + &(params->parsed_license.timer_limits.earliest_playback_start_seconds), + ".earliest_playback_start_seconds"}, + {ODK_UINT64, + &(params->parsed_license.timer_limits.rental_duration_seconds), + ".rental_duration_seconds"}, + {ODK_UINT64, + &(params->parsed_license.timer_limits.total_playback_duration_seconds), + ".total_playback_duration_seconds"}, + {ODK_UINT64, + &(params->parsed_license.timer_limits.initial_renewal_duration_seconds), + ".initial_renewal_duration_seconds"}, + }; + if (odk_major_version >= 17) { + params->extra_fields.push_back( + {ODK_UINT32, &(params->parsed_license.watermarking), ".watermarking"}); + params->extra_fields.push_back( + {ODK_UINT8, &(params->parsed_license.dtcp2_required.dtcp2_required), + ".dtcp2_required"}); + if (params->parsed_license.dtcp2_required.dtcp2_required) { + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_0.id), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_0.extension), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT16, + &(params->parsed_license.dtcp2_required.cmi_descriptor_0.length), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_0.data), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_1.id), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_1.extension), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT16, + &(params->parsed_license.dtcp2_required.cmi_descriptor_1.length), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_1.data[0]), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_1.data[1]), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_1.data[2]), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_2.id), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_2.extension), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT16, + &(params->parsed_license.dtcp2_required.cmi_descriptor_2.length), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_2.data[0]), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_2.data[1]), + ".cmi_descriptor_data"}); + params->extra_fields.push_back( + {ODK_UINT8, + &(params->parsed_license.dtcp2_required.cmi_descriptor_2.data[2]), + ".cmi_descriptor_data"}); + } + } + params->extra_fields.push_back({ODK_UINT32, + &(params->parsed_license.key_array_length), + ".key_array_length"}); + params->extra_fields.push_back({ODK_SUBSTRING, + &(params->parsed_license.key_array[0].key_id), + ".key_id"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_data_iv), + ".key_data_iv"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_data), + ".key_data"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_control_iv), + ".key_control_iv"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[0].key_control), + ".key_control"}); + params->extra_fields.push_back({ODK_SUBSTRING, + &(params->parsed_license.key_array[1].key_id), + ".key_id"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_data_iv), + ".key_data_iv"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_data), + ".key_data"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_control_iv), + ".key_control_iv"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[1].key_control), + ".key_control"}); + params->extra_fields.push_back({ODK_SUBSTRING, + &(params->parsed_license.key_array[2].key_id), + ".key_id"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_data_iv), + ".key_data_iv"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_data), + ".key_data"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_control_iv), + ".key_control_iv"}); + params->extra_fields.push_back( + {ODK_SUBSTRING, &(params->parsed_license.key_array[2].key_control), + ".key_control"}); + if (odk_major_version == 16) { + params->extra_fields.push_back( + {ODK_HASH, params->request_hash, ".request_hash"}); + } +} + +void ODK_SetDefaultRenewalResponseParams(ODK_RenewalResponseParams* params) { + ODK_SetDefaultCoreFields(&(params->core_message), ODK_Renewal_Response_Type); + params->system_time = 0xfaceb00c; + params->playback_clock = 10; + params->playback_timer = 20; + params->renewal_duration = 300; + params->extra_fields = { + {ODK_UINT64, &(params->playback_clock), "playback_clock"}, + {ODK_UINT64, &(params->renewal_duration), "renewal_duration"}, + }; + params->timer_limits = { + .soft_enforce_rental_duration = false, + .soft_enforce_playback_duration = false, + .earliest_playback_start_seconds = 0, + .rental_duration_seconds = 1000, + .total_playback_duration_seconds = 2000, + .initial_renewal_duration_seconds = 300, + }; + params->clock_values = { + .time_of_license_request_signed = + params->system_time - params->playback_clock - 42, + .time_of_first_decrypt = params->system_time - params->playback_clock, + .time_of_last_decrypt = params->system_time - params->playback_clock, + .time_of_renewal_request = params->playback_clock, + .time_when_timer_expires = params->system_time + params->playback_timer, + .timer_status = ODK_CLOCK_TIMER_STATUS_ACTIVE, + .status = kActive, + }; +} + +void ODK_SetDefaultProvisioningResponseParams( + ODK_ProvisioningResponseParams* params) { + ODK_SetDefaultCoreFields(&(params->core_message), + ODK_Provisioning_Response_Type); + params->device_id_length = ODK_DEVICE_ID_LEN_MAX / 2; + memset(params->device_id, 0xff, params->device_id_length); + memset(params->device_id + params->device_id_length, 0, + ODK_DEVICE_ID_LEN_MAX - params->device_id_length); + params->parsed_provisioning = { + .key_type = OEMCrypto_RSA_Private_Key, + .enc_private_key = {.offset = 0, .length = 1}, + .enc_private_key_iv = {.offset = 2, .length = 3}, + .encrypted_message_key = {.offset = 4, .length = 5}, + }; + params->extra_fields = { + {ODK_UINT32, &(params->device_id_length), "device_id_length"}, + {ODK_DEVICEID, params->device_id, "device_id"}, + {ODK_UINT32, &(params->parsed_provisioning).key_type, "key_type"}, + {ODK_SUBSTRING, &(params->parsed_provisioning).enc_private_key, + "enc_private_key"}, + {ODK_SUBSTRING, &(params->parsed_provisioning).enc_private_key_iv, + "enc_private_key_iv"}, + {ODK_SUBSTRING, &(params->parsed_provisioning).encrypted_message_key, + "encrypted_message_key"}, + }; +} + +size_t ODK_FieldLength(ODK_FieldType type) { + switch (type) { + case ODK_UINT8: + return sizeof(uint8_t); + case ODK_UINT16: + return sizeof(uint16_t); + case ODK_UINT32: + 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_SUBSTRING: + return sizeof(uint32_t) + sizeof(uint32_t); + case ODK_DEVICEID: + return ODK_DEVICE_ID_LEN_MAX; + case ODK_RENEWALDATA: + return ODK_KEYBOX_RENEWAL_DATA_SIZE; + case ODK_HASH: + return ODK_SHA256_HASH_SIZE; + default: + return SIZE_MAX; + } +} + +size_t ODK_AllocSize(ODK_FieldType type) { + if (type == ODK_SUBSTRING) { + return sizeof(OEMCrypto_Substring); + } + return ODK_FieldLength(type); +} + +OEMCryptoResult ODK_WriteSingleField(uint8_t* buf, const ODK_Field* field) { + if (buf == nullptr || field == nullptr || field->value == nullptr) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT8: { + memcpy(buf, field->value, sizeof(uint8_t)); + break; + } + case ODK_UINT16: { + const uint16_t u16 = + oemcrypto_htobe16(*static_cast(field->value)); + memcpy(buf, &u16, sizeof(u16)); + break; + } + case ODK_UINT32: { + const uint32_t u32 = + oemcrypto_htobe32(*static_cast(field->value)); + memcpy(buf, &u32, sizeof(u32)); + break; + } + case ODK_UINT64: { + const uint64_t u64 = + oemcrypto_htobe64(*static_cast(field->value)); + memcpy(buf, &u64, sizeof(u64)); + break; + } + case ODK_BOOL: { + const bool value = *static_cast(field->value); + const uint32_t u32 = oemcrypto_htobe32(value ? 1 : 0); + memcpy(buf, &u32, sizeof(u32)); + break; + } + case ODK_SUBSTRING: { + OEMCrypto_Substring* s = static_cast(field->value); + const uint32_t off = oemcrypto_htobe32(s->offset); + const uint32_t len = oemcrypto_htobe32(s->length); + memcpy(buf, &off, sizeof(off)); + memcpy(buf + sizeof(off), &len, sizeof(len)); + break; + } + case ODK_DEVICEID: + case ODK_RENEWALDATA: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + const uint8_t* const id = static_cast(field->value); + memcpy(buf, id, field_len); + + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_ReadSingleField(const uint8_t* buf, + const ODK_Field* field) { + if (buf == nullptr || field == nullptr || field->value == nullptr) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT8: { + memcpy(field->value, buf, sizeof(uint8_t)); + break; + } + case ODK_UINT16: { + memcpy(field->value, buf, sizeof(uint16_t)); + uint16_t* u16p = static_cast(field->value); + *u16p = oemcrypto_be16toh(*u16p); + break; + } + case ODK_UINT32: { + memcpy(field->value, buf, sizeof(uint32_t)); + uint32_t* u32p = static_cast(field->value); + *u32p = oemcrypto_be32toh(*u32p); + break; + } + case ODK_UINT64: { + memcpy(field->value, buf, sizeof(uint64_t)); + uint64_t* u64p = static_cast(field->value); + *u64p = oemcrypto_be64toh(*u64p); + break; + } + case ODK_BOOL: { + uint32_t value; + memcpy(&value, buf, sizeof(uint32_t)); + value = oemcrypto_be32toh(value); + *static_cast(field->value) = (value != 0); + break; + } + case ODK_SUBSTRING: { + OEMCrypto_Substring* s = static_cast(field->value); + uint32_t off = 0; + uint32_t len = 0; + memcpy(&off, buf, sizeof(off)); + memcpy(&len, buf + sizeof(off), sizeof(len)); + s->offset = oemcrypto_be32toh(off); + s->length = oemcrypto_be32toh(len); + break; + } + case ODK_DEVICEID: + case ODK_RENEWALDATA: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + uint8_t* const id = static_cast(field->value); + memcpy(id, buf, field_len); + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +OEMCryptoResult ODK_DumpSingleField(const uint8_t* buf, + const ODK_Field* field) { + if (buf == nullptr || field == nullptr || field->value == nullptr) { + return ODK_ERROR_CORE_MESSAGE; + } + switch (field->type) { + case ODK_UINT8: { + uint8_t val; + memcpy(&val, buf, sizeof(uint8_t)); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_UINT16: { + uint16_t val; + memcpy(&val, buf, sizeof(uint16_t)); + val = oemcrypto_be16toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_BOOL: + case ODK_UINT32: { + uint32_t val; + memcpy(&val, buf, sizeof(uint32_t)); + val = oemcrypto_be32toh(val); + std::cerr << field->name << ": " << val << " = 0x" << std::hex << val + << "\n"; + break; + } + case ODK_UINT64: { + uint64_t val; + memcpy(&val, buf, sizeof(uint64_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; + memcpy(&off, buf, sizeof(off)); + memcpy(&len, buf + sizeof(off), sizeof(len)); + std::cerr << field->name << ": (off=" << off << ", len=" << len << ")\n"; + break; + } + case ODK_DEVICEID: + case ODK_RENEWALDATA: + case ODK_HASH: { + const size_t field_len = ODK_FieldLength(field->type); + std::cerr << field->name << ": "; + for (size_t i = 0; i < field_len; i++) { + std::cerr << std::hex << std::setfill('0') << std::setw(2) + << static_cast(buf[i]); + } + std::cerr << "\n"; + break; + } + default: + return ODK_ERROR_CORE_MESSAGE; + } + std::cerr << std::dec; // Return to normal. + return OEMCrypto_SUCCESS; +} + +/* + * Parameters: + * [in] size_in: buffer size + * [out] size_out: bytes processed + */ +OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* buf, + const size_t size_in, size_t* size_out, + const std::vector& fields) { + if (buf == nullptr || size_out == nullptr) { + return ODK_ERROR_CORE_MESSAGE; + } + size_t off = 0, off2 = 0; + for (size_t i = 0; i < fields.size(); i++) { + if (__builtin_add_overflow(off, ODK_FieldLength(fields[i].type), &off2) || + off2 > size_in) { + return ODK_ERROR_CORE_MESSAGE; + } + uintptr_t base = reinterpret_cast(buf); + if (__builtin_add_overflow(base, off, &base)) { + return ODK_ERROR_CORE_MESSAGE; + } + uint8_t* const buf_off = buf + off; + switch (mode) { + case ODK_WRITE: + ODK_WriteSingleField(buf_off, &fields[i]); + break; + case ODK_READ: + ODK_ReadSingleField(buf_off, &fields[i]); + break; + case ODK_DUMP: + ODK_DumpSingleField(buf_off, &fields[i]); + break; + default: + return ODK_ERROR_CORE_MESSAGE; + } + off = off2; + } + *size_out = off; + if (*size_out > size_in) { + return ODK_ERROR_CORE_MESSAGE; + } + return OEMCrypto_SUCCESS; +} + +std::vector ODK_MakeTotalFields( + const std::vector& extra_fields, ODK_CoreMessage* core_message) { + std::vector total_fields = { + {ODK_UINT32, &(core_message->message_type), "message_type"}, + {ODK_UINT32, &(core_message->message_length), "message_size"}, + {ODK_UINT16, &(core_message->nonce_values.api_minor_version), + "api_minor_version"}, + {ODK_UINT16, &(core_message->nonce_values.api_major_version), + "api_major_version"}, + {ODK_UINT32, &(core_message->nonce_values.nonce), "nonce"}, + {ODK_UINT32, &(core_message->nonce_values.session_id), "session_id"}, + }; + total_fields.insert(total_fields.end(), extra_fields.begin(), + extra_fields.end()); + return total_fields; +} + +// Expect the two buffers of size n to be equal. If not, dump the messages. +void ODK_ExpectEqualBuf(const void* s1, const void* s2, size_t n, + const std::vector& fields) { + if (memcmp(s1, s2, n) != 0) { + ODK_CoreMessage core_message; + std::vector total_fields = + ODK_MakeTotalFields(fields, &core_message); + const void* buffers[] = {s1, s2}; + for (int i = 0; i < 2; i++) { + char _tmp[] = "/tmp/fileXXXXXX"; + const int temp_fd = mkstemp(_tmp); + if (temp_fd >= 0) { + close(temp_fd); + } else { + std::cerr << "Failed to open temp file." << std::endl; + 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; + size_t bytes_written; + uint8_t* buf = + const_cast(reinterpret_cast(buffers[i])); + ODK_IterFields(ODK_DUMP, buf, n, &bytes_written, total_fields); + } + FAIL(); + } +} + +void ODK_ResetOdkFields(std::vector* fields) { + if (fields == nullptr) { + return; + } + for (auto& field : *fields) { + if (field.value != nullptr) { + const size_t size = ODK_AllocSize(field.type); + memset(field.value, 0, size); + } + } +} + +void ODK_BuildMessageBuffer(ODK_CoreMessage* core_message, + const std::vector& extra_fields, + uint8_t** buf, uint32_t* buf_size) { + ASSERT_TRUE(core_message != nullptr); + ASSERT_TRUE(buf_size != nullptr); + std::vector total_fields = + ODK_MakeTotalFields(extra_fields, core_message); + + for (auto& field : total_fields) { + *buf_size += ODK_FieldLength(field.type); + } + // update message_size + *(reinterpret_cast(total_fields[1].value)) = *buf_size; + + *buf = new uint8_t[*buf_size]{}; + size_t bytes_written = 0; + // serialize ODK fields to message buffer + EXPECT_EQ(OEMCrypto_SUCCESS, ODK_IterFields(ODK_WRITE, *buf, SIZE_MAX, + &bytes_written, total_fields)); + EXPECT_EQ(bytes_written, *buf_size); +} + +} // namespace wvodk_test diff --git a/oemcrypto/odk/test/odk_test_helper.h b/oemcrypto/odk/test/odk_test_helper.h new file mode 100644 index 00000000..f825af13 --- /dev/null +++ b/oemcrypto/odk/test/odk_test_helper.h @@ -0,0 +1,109 @@ +// Copyright 2019 Google LLC. All rights reserved. This file and proprietary +// source code may only be used and distributed under the Widevine +// License Agreement. + +#ifndef WIDEVINE_ODK_TEST_ODK_TEST_HELPER_H_ +#define WIDEVINE_ODK_TEST_ODK_TEST_HELPER_H_ + +#include +#include +#include + +#include "odk_structs.h" +#include "odk_structs_priv.h" + +namespace wvodk_test { + +enum ODK_FieldType { + ODK_UINT8, + ODK_UINT16, + ODK_UINT32, + ODK_UINT64, + ODK_SUBSTRING, + ODK_DEVICEID, + ODK_RENEWALDATA, + 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, + // Put boolean after ODK_LAST_STRESSABLE_TYPE, so that we skip boolean type in + // SerializeFieldsStress because we unpack any nonzero to 'true'. + ODK_BOOL, +}; + +enum ODK_FieldMode { + ODK_READ, + ODK_WRITE, + ODK_DUMP, +}; + +struct ODK_Field { + ODK_FieldType type; + void* value; + std::string name; +}; + +// This structure contains all parameters available in message version v16 +// through the current version. +struct ODK_LicenseResponseParams { + ODK_CoreMessage core_message; + bool initial_license_load; + bool usage_entry_present; + uint8_t request_hash[ODK_SHA256_HASH_SIZE]; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; + ODK_ParsedLicense parsed_license; + std::vector extra_fields; +}; + +struct ODK_RenewalResponseParams { + ODK_CoreMessage core_message; + uint64_t system_time; + uint64_t playback_clock; + uint64_t renewal_duration; + ODK_TimerLimits timer_limits; + ODK_ClockValues clock_values; + uint64_t playback_timer; + std::vector extra_fields; +}; + +struct ODK_ProvisioningResponseParams { + ODK_CoreMessage core_message; + uint8_t device_id[ODK_DEVICE_ID_LEN_MAX]; + uint32_t device_id_length; + ODK_ParsedProvisioning parsed_provisioning; + std::vector extra_fields; +}; + +// Default values in core_message for testing +void ODK_SetDefaultCoreFields(ODK_CoreMessage* core_message, + ODK_MessageType message_type); +void ODK_SetDefaultLicenseResponseParams(ODK_LicenseResponseParams* params, + uint32_t odk_major_version); +void ODK_SetDefaultRenewalResponseParams(ODK_RenewalResponseParams* params); +void ODK_SetDefaultProvisioningResponseParams( + ODK_ProvisioningResponseParams* params); + +size_t ODK_FieldLength(ODK_FieldType type); +size_t ODK_AllocSize(ODK_FieldType type); + +// Copy ODK_Field to buf +OEMCryptoResult ODK_WriteSingleField(uint8_t* buf, const ODK_Field* field); +// Load buf to ODK_Field +OEMCryptoResult ODK_ReadSingleField(const uint8_t* buf, const ODK_Field* field); +OEMCryptoResult ODK_DumpSingleField(const uint8_t* buf, const ODK_Field* field); +OEMCryptoResult ODK_IterFields(ODK_FieldMode mode, uint8_t* buf, + const size_t size_in, size_t* size_out, + const std::vector& fields); +void ODK_ExpectEqualBuf(const void* s1, const void* s2, size_t n, + const std::vector& fields); +void ODK_ResetOdkFields(std::vector* fields); + +// Serialize core_message and extra_fields into buf +void ODK_BuildMessageBuffer(ODK_CoreMessage* core_message, + const std::vector& extra_fields, + uint8_t** buf, uint32_t* buf_size); + +} // namespace wvodk_test + +#endif // WIDEVINE_ODK_TEST_ODK_TEST_HELPER_H_ diff --git a/oemcrypto/odk/test/odk_timer_test.cpp b/oemcrypto/odk/test/odk_timer_test.cpp new file mode 100644 index 00000000..84139608 --- /dev/null +++ b/oemcrypto/odk/test/odk_timer_test.cpp @@ -0,0 +1,1239 @@ +/* Copyright 2019 Google LLC. All rights reserved. This file and proprietary + * source code may only be used and distributed under the Widevine + * License Agreement. + */ + +#include "OEMCryptoCENCCommon.h" +#include "gtest/gtest.h" +#include "odk.h" +#include "odk_structs_priv.h" + +namespace { + +// The rental clock starts when the request is signed. If any test fails +// because a time is off by exactly 1000, that is a strong indication that we +// confused rental and system clocks. The system clock should only be used +// internally on the device, because it might not be synchronized from one +// device to another. Rental clock is used in the license message from the +// server. +constexpr uint64_t kRentalClockStart = 1000u; + +// The renewal grace period is used by the server and the CDM layer to allow +// time between when the renewal is requested and when playback is cutoff if the +// renewal is not loaded. +constexpr uint64_t kGracePeriod = 5u; + +TEST(OdkTimerBasicTest, NullTest) { + // Assert that nullptr does not cause a core dump. + ODK_InitializeClockValues(nullptr, 0u); + ODK_ReloadClockValues(nullptr, 0u, 0u, 0u, kActive, 0u); + ODK_AttemptFirstPlayback(0u, nullptr, nullptr, nullptr); + ODK_UpdateLastPlaybackTime(0, nullptr, nullptr); + ASSERT_TRUE(true); +} + +TEST(OdkTimerBasicTest, Init) { + // Verify that basic initialization sets all of the fields. + ODK_ClockValues clock_values; + memset(&clock_values, 0, sizeof(clock_values)); + uint64_t time = 42; + ODK_InitializeClockValues(&clock_values, time); + EXPECT_EQ(clock_values.time_of_license_request_signed, time); + EXPECT_EQ(clock_values.time_of_first_decrypt, 0u); + EXPECT_EQ(clock_values.time_of_last_decrypt, 0u); + EXPECT_EQ(clock_values.time_when_timer_expires, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + EXPECT_EQ(clock_values.status, kUnused); +} + +TEST(OdkTimerBasicTest, Reload) { + // Verify that reloading clock values uses the same values + // for fields that can be saved, and sets others to 0. + ODK_ClockValues clock_values; + memset(&clock_values, 0, sizeof(clock_values)); + uint64_t time = 42u; + uint64_t lic_signed = 1u; + uint64_t first_decrypt = 2u; + uint64_t last_decrypt = 3u; + enum OEMCrypto_Usage_Entry_Status status = kInactiveUsed; + ODK_ReloadClockValues(&clock_values, lic_signed, first_decrypt, last_decrypt, + status, time); + EXPECT_EQ(clock_values.time_of_license_request_signed, lic_signed); + EXPECT_EQ(clock_values.time_of_first_decrypt, first_decrypt); + EXPECT_EQ(clock_values.time_of_last_decrypt, last_decrypt); + EXPECT_EQ(clock_values.time_when_timer_expires, 0u); + EXPECT_EQ(clock_values.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + EXPECT_EQ(clock_values.status, status); +} + +// All of the following test cases are derived from this base class. It +// simulates loading a license, attempting playback, and reloading a license. +class ODKTimerTest : public ::testing::Test { + public: + ODKTimerTest() { + // These are reasonable initial values for most tests. This is an unlimited + // license. Most of the tests below will add some restrictions by changing + // these values, and then verify that the ODK library behaves correctly. + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.soft_enforce_playback_duration = true; + timer_limits_.earliest_playback_start_seconds = 0u; + // A duration of 0 means infinite. + timer_limits_.rental_duration_seconds = 0u; + timer_limits_.total_playback_duration_seconds = 0u; + timer_limits_.initial_renewal_duration_seconds = 0u; + + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + } + + protected: + void SetUp() override { + ::testing::Test::SetUp(); + // Start rental clock at kRentalClockStart. This happens when the license + // request is signed. + ODK_InitializeClockValues(&clock_values_, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_license_request_signed, kRentalClockStart); + } + + // Simulate loading or reloading a license in a new session. An offline + // license should have several of the clock_value's fields saved into the + // usage table. When it is reloaded those values should be reloaded. From + // these fields, the ODK function can tell if this is the first playback for + // the license, or just the first playback for this session. The key fields + // that should be saved are the status, and the times for license signed, and + // first and last playback times. + void ReloadLicense(uint64_t system_time) { + ODK_ClockValues old_clock_values = clock_values_; + // First clear out the old clock values. + memset(&clock_values_, 0, sizeof(clock_values_)); + // When the session is opened, the clock values are initialized. + ODK_InitializeClockValues(&clock_values_, 0); + // When the usage entry is reloaded, the clock values are reloaded. + ODK_ReloadClockValues(&clock_values_, + old_clock_values.time_of_license_request_signed, + old_clock_values.time_of_first_decrypt, + old_clock_values.time_of_last_decrypt, + old_clock_values.status, system_time); + EXPECT_EQ(clock_values_.timer_status, + ODK_CLOCK_TIMER_STATUS_LICENSE_NOT_LOADED); + // These shall not change: + EXPECT_EQ(clock_values_.time_of_license_request_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + // ODK_ParseLicense sets the new timer state. + clock_values_.timer_status = ODK_CLOCK_TIMER_STATUS_LICENSE_LOADED; + } + + // Simulate loading or reloading a license, then verify that we are allowed + // playback from |start| to |stop|. If |cutoff| is not 0, then expect the + // timer to expire at that time. If |cutoff| is 0, expect the timer is not + // set. If you refer to the diagrams in "License Duration and Renewal", this + // tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void LoadAndAllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ReloadLicense(start); + AllowPlayback(start, stop, cutoff); + } + + // Verify that we are allowed playback from |start| to |stop|. If |cutoff| is + // not 0, then expect the timer to expire at that time. If |cutoff| is 0, + // expect the timer is not set. If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void AllowPlayback(uint64_t start, uint64_t stop, uint64_t cutoff) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + uint64_t timer_value; + const OEMCryptoResult result = ODK_AttemptFirstPlayback( + start, &timer_limits_, &clock_values_, &timer_value); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Simulate loading or reloading a license, then attempt to play from |start| + // to |cutoff|. Verify that we are allowed playback from |start| to |cutoff|, + // but playback is not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. When + // nonzero |start|, and |cutoff| are all system times. + void LoadAndTerminatePlayback(uint64_t start, uint64_t cutoff) { + ReloadLicense(start); + TerminatePlayback(start, cutoff, cutoff + 10); + } + + // Attempt to play from |start| to |stop|. Verify that we are allowed playback + // from |start| to |cutoff|, but playback not allowed after |cutoff|. If you + // refer to the diagrams in "License Duration and Renewal", this tests a cyan + // bar with a black X. This assumes that |cutoff| is before |stop|. + // When nonzero |start|, |stop|, and |cutoff| are all system times. + void TerminatePlayback(uint64_t start, uint64_t cutoff, uint64_t stop) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + AllowPlayback(start, cutoff, cutoff); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + // times do not change if playback was not allowed. + CheckClockValues(cutoff); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that we are not allowed playback at the |start| time. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan line + // followed by a black X. The parameter |start| is system time. + void ForbidPlayback(uint64_t start) { + ReloadLicense(start); + ODK_ClockValues old_clock_values = clock_values_; + uint64_t timer_value; + EXPECT_EQ(ODK_AttemptFirstPlayback(start, &timer_limits_, &clock_values_, + &timer_value), + ODK_TIMER_EXPIRED); + // These should not have changed. In particular, if the license was unused + // before, it should reamin unused. + EXPECT_EQ(clock_values_.time_of_license_request_signed, + old_clock_values.time_of_license_request_signed); + EXPECT_EQ(clock_values_.time_of_first_decrypt, + old_clock_values.time_of_first_decrypt); + EXPECT_EQ(clock_values_.time_of_last_decrypt, + old_clock_values.time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, old_clock_values.status); + } + + // Verify that the clock values are correct. + void CheckClockValues(uint64_t time_of_last_decrypt) { + EXPECT_EQ(clock_values_.time_of_license_request_signed, kRentalClockStart); + EXPECT_EQ(clock_values_.time_of_first_decrypt, start_of_playback_); + EXPECT_EQ(clock_values_.time_of_last_decrypt, time_of_last_decrypt); + EXPECT_EQ(clock_values_.status, kActive); + } + + // Convert from rental time to system time. By "system time", we mean + // OEMCrypto's montonic clock. The spec does not specify a starting time for + // this clock. + uint64_t GetSystemTime(uint64_t rental_clock) { + return kRentalClockStart + rental_clock; + } + + // The end of the playback window. (using system clock) + // This is not useful if the playback duration is 0. + uint64_t EndOfPlaybackWindow() { + return start_of_playback_ + timer_limits_.total_playback_duration_seconds; + } + + // The end of the rental window. (using system clock) + // This is not useful if the rental duration is 0. + uint64_t EndOfRentalWindow() { + return GetSystemTime(timer_limits_.earliest_playback_start_seconds) + + timer_limits_.rental_duration_seconds; + } + + ODK_TimerLimits timer_limits_; + ODK_ClockValues clock_values_; + // The start of playback. This is set to the planned start at the beginning of + // the test. (using system clock) + uint64_t start_of_playback_; +}; + +TEST_F(ODKTimerTest, SimplePlayback) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// This tests that we are not allowed to start playback before the rental window +// opens. +TEST_F(ODKTimerTest, EarlyTest) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is earlier than we are allowed. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + // An attempt to start playback before the timer starts is an error. + // We use the TIMER_EXPIRED error to mean both early or late. + ForbidPlayback(bad_start_time); + // And times were not updated: + EXPECT_EQ(clock_values_.status, kUnused); + // This is when we will attempt the first valid playback. + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// This runs the same test as above, but explicitly gives the ODK library a null +// pointer for the timer value. A null pointer is allowed for OEMCrypto +// implementations that do not manage their own timer. +TEST_F(ODKTimerTest, NullTimer) { + timer_limits_.earliest_playback_start_seconds = 100u; + // This is the earlier, invalid start time. + const uint64_t bad_start_time = + GetSystemTime(timer_limits_.earliest_playback_start_seconds - 10); + timer_limits_.rental_duration_seconds = 300; + timer_limits_.soft_enforce_rental_duration = false; + ReloadLicense(GetSystemTime(bad_start_time)); + + // If OEMCrypto passes in a null pointer, then the ODK should allow this. + uint64_t* timer_value_pointer = nullptr; + EXPECT_EQ(ODK_AttemptFirstPlayback(bad_start_time, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_TIMER_EXPIRED); + start_of_playback_ = + GetSystemTime(timer_limits_.earliest_playback_start_seconds + 10); + EXPECT_EQ(ODK_AttemptFirstPlayback(start_of_playback_, &timer_limits_, + &clock_values_, timer_value_pointer), + ODK_SET_TIMER); + CheckClockValues(start_of_playback_); +} + +/*****************************************************************************/ +// Note on Use Case tests. The test classes below correspond to the use cases +// in the doucment "License Duration and Renewal.". Each diagram in that +// document has a test class below to verify the use case is supported. +// +// In the document, we use realistic rental times in hours or days. In these +// tests, we will use round numbers so that it is easier to read. For example, +// instead of a seven day rental duration, we will use a 700 rental duration. +/*****************************************************************************/ + +/*****************************************************************************/ +// Streaming is the simplest use case. The user has three hours to watch the +// movie from the time of the rental. (See above for note on Use Case tests) +class ODKUseCase_Streaming : public ODKTimerTest { + public: + ODKUseCase_Streaming() { + // Rental duration = 3 hours hard. (use 300 for readability) + // Playback duration = 0 (unlimited) + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 300; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// Playback within rental duration. +TEST_F(ODKUseCase_Streaming, Case1) { + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), + EndOfRentalWindow()); +} + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case2) { + // Allow playback within the rental window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within rental duration, last one exceeds rental +// duration. +TEST_F(ODKUseCase_Streaming, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfRentalWindow()); +} + +// Playback within rental duration, restart exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_Streaming, Case5) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Streaming Quick Start. The user must start watching within 30 seconds, and +// then has three hours to finish. (See above for note on Use Case tests) +class ODKUseCase_StreamingQuickStart : public ODKTimerTest { + public: + ODKUseCase_StreamingQuickStart() { + // Rental duration = 30 seconds, soft. + // Playback duration = 3 hours (use 300 for readability) + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 30; + timer_limits_.total_playback_duration_seconds = 300; + timer_limits_.soft_enforce_playback_duration = false; + + // A valid start of playback time. + start_of_playback_ = + GetSystemTime(timer_limits_.rental_duration_seconds - 10); + } +}; + +// Playback starts within rental duration, continues within playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case1) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_LE(EndOfRentalWindow(), EndOfPlaybackWindow()); + // Allow playback within the playback window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 110, start_of_playback_ + 120, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 200, EndOfPlaybackWindow()); +} + +// Initial playback exceeds rental duration. +TEST_F(ODKUseCase_StreamingQuickStart, Case4) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// smaller of the two time limits. The first four cases start on day +// three. (See above for note on Use Case tests) +class ODKUseCase_SevenHardTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoHard_Start6 + : public ODKUseCase_SevenHardTwoHard_Start3 { + public: + ODKUseCase_SevenHardTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one is terminated +// at the end of the rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + // Allow playback that starts within rental window, but terminate at end of + // rental window. + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// rental duration time limits. The first four cases start on day three. (See +// above for note on Use Case tests) +class ODKUseCase_SevenHardTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenHardTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfRentalWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and a little after. + // Timer expires at end of rental window. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, + EndOfRentalWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfRentalWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenHardTwoSoft_Start6 + : public ODKUseCase_SevenHardTwoSoft_Start3 { + public: + ODKUseCase_SevenHardTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window is exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfRentalWindow()); +} + +// Playback within playback duration, restart exceeds rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfRentalWindow()); + // Restart does not work after end of playback window. + ForbidPlayback(EndOfRentalWindow() + 10); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenHardTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is cutoff by the +// playback duration. The first four cases start on day three. (See above for +// note on Use Case tests) +class ODKUseCase_SevenSoftTwoHard_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoHard_Start3() { + // Rental duration = 700, hard + // Playback duration = 300, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = false; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, + EndOfPlaybackWindow()); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case2) { + // Allow playback within the playback window, but not beyond. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 125, EndOfPlaybackWindow()); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, + EndOfPlaybackWindow()); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoHard_Start6 + : public ODKUseCase_SevenSoftTwoHard_Start3 { + public: + ODKUseCase_SevenSoftTwoHard_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback to continue beyond the rental window, but not beyond the + // playback window. + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 90, EndOfPlaybackWindow()); +} + +// Restart exceeds rental duration, playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, + EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case8) { + LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoHard_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. Playback is not cutoff, +// but restarts are not allowed after playback duration. The first four cases +// start on day three. (See above for note on Use Case tests) +class ODKUseCase_SevenSoftTwoSoft_Start3 : public ODKTimerTest { + public: + ODKUseCase_SevenSoftTwoSoft_Start3() { + // Rental duration = 700, hard + // Playback duration = 200, hard + timer_limits_.rental_duration_seconds = 700; + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.total_playback_duration_seconds = 200; + timer_limits_.soft_enforce_playback_duration = true; + + start_of_playback_ = GetSystemTime(300); + } +}; + +// Playback within playback and rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case1) { + // As seen in the drawing, the playback window is within the rental window. + EXPECT_LT(EndOfPlaybackWindow(), EndOfRentalWindow()); + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 100, 0); +} + +// Playback exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case2) { + // Allow playback within the playback window, and beyond. No timer limit. + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + LoadAndAllowPlayback(start_of_playback_ + 75, start_of_playback_ + 100, 0); + LoadAndAllowPlayback(start_of_playback_ + 125, EndOfPlaybackWindow() + 50, 0); +} + +// Restart exceeds playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start3, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); + ForbidPlayback(EndOfPlaybackWindow() + 10); +} + +// The next four cases start on day six. +class ODKUseCase_SevenSoftTwoSoft_Start6 + : public ODKUseCase_SevenSoftTwoSoft_Start3 { + public: + ODKUseCase_SevenSoftTwoSoft_Start6() { + start_of_playback_ = GetSystemTime(600); + } +}; + +// Playback exceeds rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case5) { + // As seen in the drawing, the playback window exceeds the rental window. + EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow() + 25); + // Allow playback past the rental window, but within the playback window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + 25, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case6) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + LoadAndAllowPlayback(start_of_playback_ + 50, start_of_playback_ + 75, 0); + LoadAndAllowPlayback(start_of_playback_ + 100, EndOfPlaybackWindow() + 50, 0); +} + +// Playback with stops/restarts within playback duration, last one exceeds +// playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case7) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); + // Allow playback to start after end of rental window, and continue after + // playback window. + LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); + // But forbid restart after playback window. + ForbidPlayback(EndOfPlaybackWindow() + 20); +} + +// Playback exceeds rental and playback duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case8) { + LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 100, 0); +} + +// First playback cannot exceed rental duration. +TEST_F(ODKUseCase_SevenSoftTwoSoft_Start6, Case9) { + ForbidPlayback(EndOfRentalWindow() + 10); +} + +class RenewalTest : public ODKTimerTest { + protected: + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + uint64_t timer_value; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + &timer_value); + } + + // Verify that a renewal can be processed and playback may continue from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. + // This does the work of the function above, except it allows the caller to + // explicitly set the timer_value_pointer. The timer_value_pointer can be a + // nullptr. + void RenewAndContinue(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds, + uint64_t* timer_value_pointer) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + const OEMCryptoResult result = ODK_ComputeRenewalDuration( + &timer_limits_, &clock_values_, start, renewal_duration_seconds, + timer_value_pointer); + // After first playback, the license is active. + EXPECT_EQ(clock_values_.status, kActive); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + if (timer_value_pointer != nullptr) + EXPECT_EQ(*timer_value_pointer, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + EXPECT_EQ(ODK_UpdateLastPlaybackTime(start, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(start); + const uint64_t mid = (start + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(mid); + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + OEMCrypto_SUCCESS); + CheckClockValues(stop); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // after |cutoff|. Verify that we are allowed playback from |start| to + // |cutoff|, but playback not allowed after |cutoff|. If you refer to the + // diagrams in "License Duration and Renewal", this tests a cyan bar with a + // black X. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + RenewAndTerminate(start, cutoff, cutoff + 100, renewal_duration_seconds); + } + + // Verify that a renewal can be processed and attempt to play from |start| to + // |stop|. Verify that we are allowed playback from |start| to |cutoff|, but + // playback not allowed after |cutoff|. If you refer to the diagrams in + // "License Duration and Renewal", this tests a cyan bar with a black X. This + // assumes that |cutoff| is between |start| and |stop|. + void RenewAndTerminate(uint64_t start, uint64_t cutoff, uint64_t stop, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, cutoff); + ASSERT_LT(cutoff, stop); + RenewAndContinue(start, cutoff, cutoff, renewal_duration_seconds); + EXPECT_EQ( + ODK_UpdateLastPlaybackTime(cutoff + 1, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + const uint64_t mid = (cutoff + stop) / 2; + EXPECT_EQ(ODK_UpdateLastPlaybackTime(mid, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues( + cutoff); // times do not change if playback was not allowed. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(stop, &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(cutoff); + } + + // Verify that a renewal can be processed and playback may start from + // |start| to |stop|. If |cutoff| is not 0, then expect the timer to expire + // at that time. If |cutoff| is 0, expect the timer is not set. If you refer + // to the diagrams in "License Duration and Renewal", this tests a cyan bar + // with a green check mark. This is different from the previous functions, + // because the renewal is loaded before the first playback. + void RenewAndStart(uint64_t start, uint64_t stop, uint64_t cutoff, + uint64_t renewal_duration_seconds) { + ASSERT_LT(start, stop); + if (cutoff > 0) ASSERT_LE(stop, cutoff); + // We'll fake instantaneous renewal requests. Flight time not important. + clock_values_.time_of_renewal_request = start; + uint64_t timer_value; + const OEMCryptoResult result = + ODK_ComputeRenewalDuration(&timer_limits_, &clock_values_, start, + renewal_duration_seconds, &timer_value); + EXPECT_EQ(clock_values_.time_when_timer_expires, cutoff); + if (cutoff > 0) { // If we expect the timer to be set. + EXPECT_EQ(result, ODK_SET_TIMER); + EXPECT_EQ(timer_value, cutoff - start); + } else { + EXPECT_EQ(result, ODK_DISABLE_TIMER); + } + AllowPlayback(start, stop, cutoff); + } +}; + +// License with Renewal, limited by playback duration. (See above for note on +// Use Case tests) These tests are parameterized. If the parameter is 0, we +// limit the playback duration. If the parameter is 1, we limit the rental +// duration. The behavior is basically the same. +class ODKUseCase_LicenseWithRenewal + : public RenewalTest, + public ::testing::WithParamInterface { + public: + ODKUseCase_LicenseWithRenewal() { + // Either Playback or rental duration = 2 days hard + if (GetParam() == 0) { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 2000; + } else { + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; + } + timer_limits_.initial_renewal_duration_seconds = 50; + } + + void SetUp() override { + RenewalTest::SetUp(); + renewal_interval_ = + timer_limits_.initial_renewal_duration_seconds - kGracePeriod; + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, next_renewal, + next_renewal + kGracePeriod); + } + + uint64_t playback_end_restriction() { + if (GetParam() == 0) { + return EndOfRentalWindow(); + } else { + return EndOfPlaybackWindow(); + } + } + + protected: + // How long to wait before we load the next renewal. i.e. cutoff - + // kGracePeriod. This is because the renewal_duration includes the grace + // period. + uint64_t renewal_interval_; +}; + +// Playback within rental duration and renewal duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case1) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } +} + +// Playback within rental duration, last renewal_interval_ exceeds renewal +// duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case2) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Playback interrupted by late renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case3) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } + // We attempt to continue playing beyond the renewal renewal_interval_. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + + const uint64_t late_renewal = next_renewal + kGracePeriod + 10; + next_renewal = late_renewal + renewal_interval_; + RenewAndContinue(late_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Playback & restart within playback duration and renewal duration. Note: this +// simulates reloading a persistent license, and then reloading the last +// renewal. +TEST_P(ODKUseCase_LicenseWithRenewal, Case4) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } + // Reload license after timer expired. + uint64_t reload_time = next_renewal + kGracePeriod + 100; + next_renewal = reload_time + renewal_interval_; + ReloadLicense(reload_time); + RenewAndStart(reload_time, next_renewal, next_renewal + kGracePeriod, + timer_limits_.initial_renewal_duration_seconds); + for (int i = 0; i < 2; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } +} + +// Playback within renewal duration, but exceeds playback duration. +TEST_P(ODKUseCase_LicenseWithRenewal, Case5) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + timer_limits_.initial_renewal_duration_seconds); + } while ((next_renewal + renewal_interval_ + kGracePeriod) < + playback_end_restriction()); + // Attempt playing beyond the playback window. + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // stop: when timer expires. + timer_limits_.initial_renewal_duration_seconds); +} + +// Renewal duration increases over time. +TEST_P(ODKUseCase_LicenseWithRenewal, Case6) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + do { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; + } while (next_renewal + renewal_interval_ + kGracePeriod < + playback_end_restriction()); + // Attempt playing beyond the playback window: + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + playback_end_restriction(), // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); +} + +// Increasing renewal duration, playback exceeds last renewal. +// This is a mix between case 2 and case 6. +TEST_P(ODKUseCase_LicenseWithRenewal, Case7) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + for (int i = 0; i < 4; i++) { + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndContinue( + current_renewal, // start: when renewal is loaded. + next_renewal, // stop: expect play allowed. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); + // Renewal_Interval_ increases with each renewal. + renewal_interval_ += 100; + } + const uint64_t current_renewal = next_renewal; + next_renewal = current_renewal + renewal_interval_; + RenewAndTerminate(current_renewal, // start: when renewal is loaded. + next_renewal + kGracePeriod, // cutoff: when timer expires. + renewal_interval_ + kGracePeriod); +} + +// This is just like Case1, except we use a null pointer for the timer values in +// RenewAndContinue. It is not shown in the use case document. +TEST_P(ODKUseCase_LicenseWithRenewal, NullPointerTest) { + uint64_t next_renewal = start_of_playback_ + renewal_interval_; + const uint64_t start = next_renewal; + const uint64_t stop = start + renewal_interval_; + const uint64_t cutoff = stop + kGracePeriod; + const uint64_t renewal_duration_seconds = + timer_limits_.initial_renewal_duration_seconds; + uint64_t* timer_value_pointer = nullptr; + RenewAndContinue(start, stop, cutoff, renewal_duration_seconds, + timer_value_pointer); +} + +INSTANTIATE_TEST_SUITE_P(RestrictRenewal, ODKUseCase_LicenseWithRenewal, + ::testing::Values(0, 1)); + +// Limited Duration License. (See above for notes on Use Case tests). The user +// has 15 minutes to begin watching the movie. If a renewal is not received, +// playback is terminated after 30 seconds. If a renewal is received, playback +// may continue for two hours from playback start. +class ODKUseCase_LimitedDurationLicense : public RenewalTest { + public: + ODKUseCase_LimitedDurationLicense() { + renewal_delay_ = 30u; + time_of_renewal_ = start_of_playback_ + renewal_delay_; + + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 150; // 15 minutes. + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 2000; // Two hours. + timer_limits_.initial_renewal_duration_seconds = + renewal_delay_ + kGracePeriod; + } + uint64_t renewal_delay_; + uint64_t time_of_renewal_; +}; + +// Playback started within rental window and continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case1) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); +} + +// Playback started after rental duration. +TEST_F(ODKUseCase_LimitedDurationLicense, Case2) { + start_of_playback_ = EndOfRentalWindow() + 1; + ForbidPlayback(start_of_playback_); +} + +// Playback started within rental window but renewal not received. +TEST_F(ODKUseCase_LimitedDurationLicense, Case3) { + // Allow playback within the initial renewal window. + LoadAndTerminatePlayback(start_of_playback_, time_of_renewal_ + kGracePeriod); +} + +// Playback started within rental window, renewal is received, and playback +// continues. +TEST_F(ODKUseCase_LimitedDurationLicense, Case4) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + RenewAndTerminate(time_of_renewal_, // start: when renewal is loaded. + EndOfPlaybackWindow(), // stop: when timer expires. + renewal_duration); +} + +// Playback may be restarted. +TEST_F(ODKUseCase_LimitedDurationLicense, Case5) { + // Allow playback within the initial renewal window. + LoadAndAllowPlayback(start_of_playback_, time_of_renewal_, + time_of_renewal_ + kGracePeriod); + const uint64_t renewal_duration = 2000; // two hour renewal. + const uint64_t play_for_one_hour = GetSystemTime(1000); + RenewAndContinue(time_of_renewal_, // start: when renewal is loaded. + play_for_one_hour, // stop: expect play allowed. + EndOfPlaybackWindow(), // cutoff: when timer expires. + renewal_duration); + + const uint64_t reload_time = play_for_one_hour + 100; + ReloadLicense(reload_time); + // Simulate reloading the license, and then reloading the renewal, and then + // restarting playback. That is allowed, and playback shall be terminated at + // the end of the original playback window. + RenewAndStart(reload_time, EndOfPlaybackWindow(), EndOfPlaybackWindow(), + renewal_duration); + // But not one second more. + EXPECT_EQ(ODK_UpdateLastPlaybackTime(EndOfPlaybackWindow() + 1, + &timer_limits_, &clock_values_), + ODK_TIMER_EXPIRED); + CheckClockValues(EndOfPlaybackWindow()); +} + +// Verify that the backwards compatible function, ODK_InitializeV15Values, can +// set up the clock values and timer limits. +TEST_F(RenewalTest, V15Test) { + const uint32_t key_duration = 25; + ODK_NonceValues nonce_values; + memset(&nonce_values, 0, sizeof(nonce_values)); + const uint64_t license_loaded = GetSystemTime(10); + EXPECT_EQ( + ODK_InitializeV15Values(&timer_limits_, &clock_values_, &nonce_values, + key_duration, license_loaded), + OEMCrypto_SUCCESS); + const uint64_t later_on = GetSystemTime(200); + TerminatePlayback(license_loaded, license_loaded + key_duration, later_on); + const uint32_t new_key_duration = 100; + RenewAndTerminate(later_on, later_on + new_key_duration, new_key_duration); +} +} // namespace diff --git a/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi b/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi index cdbeadd0..f1064d86 100644 --- a/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi +++ b/oemcrypto/test/fuzz_tests/oemcrypto_fuzztests.gypi @@ -29,6 +29,7 @@ '<(util_dir)/src/platform.cpp', '<(util_dir)/src/rw_lock.cpp', '<(util_dir)/src/string_conversions.cpp', + '<(util_dir)/src/string_format.cpp', '<(util_dir)/test/test_sleep.cpp', '<(util_dir)/test/test_clock.cpp', ], @@ -113,7 +114,7 @@ # Include oemcrypto opk implementation code for building opk # implementation fuzz binaries. 'dependencies': [ - '<(oemcrypto_dir)/opk/ports/linux/wtpi_test_impl/wtpi_test_impl.gyp:oemcrypto_ta_test_impl_no_ipc', + '<(oemcrypto_dir)/opk/ports/linux/ta/common/wtpi_impl/wtpi_test_impl.gyp:oemcrypto_ta_test_impl_no_ipc', ], }], ], # conditions diff --git a/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi b/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi index 18eb2adf..ddf927dc 100644 --- a/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi +++ b/oemcrypto/test/fuzz_tests/partner_oemcrypto_fuzztests.gypi @@ -27,6 +27,7 @@ '<(util_dir)/src/platform.cpp', '<(util_dir)/src/rw_lock.cpp', '<(util_dir)/src/string_conversions.cpp', + '<(util_dir)/src/string_format.cpp', '<(util_dir)/test/test_sleep.cpp', '<(util_dir)/test/test_clock.cpp', ], diff --git a/oemcrypto/util/test/oem_cert_test.cpp b/oemcrypto/util/test/oem_cert_test.cpp index 234d31cf..e4dcd5d4 100644 --- a/oemcrypto/util/test/oem_cert_test.cpp +++ b/oemcrypto/util/test/oem_cert_test.cpp @@ -2,9 +2,10 @@ // source code may only be used and distributed under the Widevine License // Agreement. -#include "oem_cert.h" +#include "oem_cert_test.h" -namespace wvoec_ref { +namespace wvoec { +namespace util { namespace { const uint32_t kTestOemSystemId = 7913; @@ -529,4 +530,5 @@ const uint8_t* kOEMPublicCert = kTestOemPublicCert; const size_t kOEMPrivateKeySize = kTestOemPrivateKeySize; const size_t kOEMPublicCertSize = kTestOemPublicCertSize; -} // namespace wvoec_ref +} // namespace util +} // namespace wvoec diff --git a/oemcrypto/util/test/oem_cert_test.h b/oemcrypto/util/test/oem_cert_test.h new file mode 100644 index 00000000..a19b9520 --- /dev/null +++ b/oemcrypto/util/test/oem_cert_test.h @@ -0,0 +1,27 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +// +// This header is used to access the testing OEM certificate. +#ifndef OEM_CERT_TEST_H_ +#define OEM_CERT_TEST_H_ + +#include +#include + +namespace wvoec { +namespace util { + +// Refer to the following in main modules +extern const uint32_t kOEMSystemId; + +extern const uint8_t* kOEMPrivateKey; +extern const uint8_t* kOEMPublicCert; + +extern const size_t kOEMPrivateKeySize; +extern const size_t kOEMPublicCertSize; + +} // namespace util +} // namespace wvoec + +#endif // OEM_CERT_TEST_H_ diff --git a/oemcrypto/util/test/oemcrypto_oem_cert_unittest.cpp b/oemcrypto/util/test/oemcrypto_oem_cert_unittest.cpp index 2f220e1b..6239045a 100644 --- a/oemcrypto/util/test/oemcrypto_oem_cert_unittest.cpp +++ b/oemcrypto/util/test/oemcrypto_oem_cert_unittest.cpp @@ -7,16 +7,12 @@ #include #include "OEMCryptoCENCCommon.h" -#include "oem_cert.h" +#include "oem_cert_test.h" #include "oemcrypto_oem_cert.h" #include "oemcrypto_rsa_key.h" namespace wvoec { namespace util { -using wvoec_ref::kOEMPrivateKey; -using wvoec_ref::kOEMPrivateKeySize; -using wvoec_ref::kOEMPublicCert; -using wvoec_ref::kOEMPublicCertSize; namespace { const std::vector kOEMPrivateKeyVector(kOEMPrivateKey, kOEMPrivateKey + diff --git a/platforms/example-runtime-client-info/environment.py b/platforms/example-runtime-client-info/environment.py new file mode 100644 index 00000000..11840f25 --- /dev/null +++ b/platforms/example-runtime-client-info/environment.py @@ -0,0 +1,53 @@ +# Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. + +# GYP supports the following environment variables to control the paths to the +# toolchain: +# +# * CC - The C compiler +# * CXX - The C++ compiler +# * AR - The archive tool +# * NM - The symbol name tool +# * READELF - The ELF metadata tool +# +# If any are not specified, they default to the system's installed copy of that +# tool. +# +# Note that overriding the linker is NOT supported. The C or C++ compiler will +# always be used as a front-end to the linker. +# +# Any of these may have "_host" or "_target" appended to indicate that they +# should only be used when compiling for the host or for the target when +# cross-compiling. If GYP is cross-compiling and a specific "_host" or "_target" +# tool is not found, it will fall back to the normal tool. e.g. If there is no +# "CC_host", host builds will use "CC". +# +# It is recommended to always set GYP_CROSSCOMPILE to 1 when cross-compiling. + +# |export_variables| is a dictionary containing the variables to set. Relative +# paths are interpreted as being relative to this file. +export_variables = { + # Although we aren't actually cross-compiling in this build, settings.gypi + # relies on the cross-compiler's separation of host and target build + # artifacts. By setting this variable, we force GYP to use cross-compilation + # mode even though both modes use the same compiler binary. + 'GYP_CROSSCOMPILE': '1', + + # Typically, you will want to set these to the paths to your system's + # toolchain. but for this example, we'll just use the system install of GCC. + 'CC': 'gcc', + 'CXX': 'g++', + 'AR': 'ar', + + # Alternatively, here's how you could use the GCC as the "host" toolchain and + # Clang as the "target" toolchain: + + # 'CC_target': 'clang', + # 'CXX_target': 'clang++', + # 'AR_target': 'llvm-ar', + # + # 'CC_host': 'gcc', + # 'CXX_host': 'g++', + # 'AR_host': 'ar', +} diff --git a/platforms/example-runtime-client-info/read_client_info.cpp b/platforms/example-runtime-client-info/read_client_info.cpp new file mode 100644 index 00000000..04230b53 --- /dev/null +++ b/platforms/example-runtime-client-info/read_client_info.cpp @@ -0,0 +1,28 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "read_client_info.h" + +namespace widevine { + +bool ReadClientInformation(std::string* company_name, std::string* model_name, + std::string* model_year, std::string* product_name, + std::string* device_name, std::string* arch_name, + std::string* platform, std::string* form_factor, + std::string* version) { + // A real implementation would read these fields from a file or system API, + // but for this example implementation, hardcoded values are fine. + *company_name = "KubrickTech"; + *model_name = "HAL 9000 with runtime client info"; + *model_year = "2001"; + *product_name = "clarke"; + *device_name = "HAL"; + *arch_name = "ARMv7"; + *platform = "Linux"; + *form_factor = "TV"; + *version = "1.0.0"; + return true; +} + +} // namespace widevine diff --git a/platforms/example-runtime-client-info/read_client_info.gyp b/platforms/example-runtime-client-info/read_client_info.gyp new file mode 100644 index 00000000..eaf660e0 --- /dev/null +++ b/platforms/example-runtime-client-info/read_client_info.gyp @@ -0,0 +1,13 @@ +# Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. +{ + 'targets': [ + { + 'target_name': 'read_client_info', + 'type': 'static_library', + 'include_dirs': ['../../cdm/include'], + 'sources': ['read_client_info.cpp'], + }, + ], +} diff --git a/platforms/example-runtime-client-info/settings.gypi b/platforms/example-runtime-client-info/settings.gypi new file mode 100644 index 00000000..766c2aec --- /dev/null +++ b/platforms/example-runtime-client-info/settings.gypi @@ -0,0 +1,122 @@ +# Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. +{ + # Here you can override global gyp variables with platform-specific values. + # See cdm/platform_properties.gyp for a complete list of settings you can + # override. + 'variables': { + 'oemcrypto_lib': 'target', + 'oemcrypto_gyp_path': '../example/oemcrypto.gyp:oemcrypto', + + 'client_info_source': 'runtime', + 'read_client_info_path': 'read_client_info.gyp:read_client_info', + + 'asm_target_arch': 'x86-64', + }, # end variables + + # Here you can set platform-specific compiler settings. + 'target_defaults': { + # These are flags passed to the compiler for all C & C++ files. + 'cflags': [ + '-fPIC', + '-fvisibility=hidden', + '-fno-common', + '-Wno-error', + # This flag is not supported on GCC, but Widevine strongly encourages + # using it if you are building with Clang. + # '-ftrivial-auto-var-init=pattern', + ], + # These are flags passed to the compiler for plain C only. + 'cflags_c': [ + # Compile using the C11 standard with POSIX extensions + '-std=c11', + '-D_POSIX_C_SOURCE=200809L', + ], + # These are flags passed to the compiler for C++ only. + 'cflags_cc': [ + # Compile using the C++11 standard. + '-std=c++11', + # CE CDM does not use exceptions or RTTI. + '-fno-exceptions', + '-fno-rtti', + ], + # These are flags passed to the linker. + 'ldflags': [ + #'-static-libstdc++', + ], + # These are macros set by the compiler. + 'defines': [ + #'EXAMPLE_MACRO_WITH_NO_VALUE', + #'EXAMPLE_KEY=EXAMPLE_VALUE', + ], + # These are additional include paths to search for headers. + 'include_dirs': [ + #'/toolchain/include', + ], + 'target_conditions': [ + # Most of the build output is compiled for the target device, but the + # build may also sometimes compile files for the host device. For + # instance, if you compile Protobuf from source, the "protoc" compiler + # will be compiled for your host machine and used as part of the build + # process. + # + # These conditional blocks let you set compiler flags and other settings + # that should only apply on the target or host platforms. + ['_toolset == "target"', { + # These are additional settings specifically for the target toolchain. + 'cflags': [], + 'cflags_c': [], + 'cflags_cc': [], + 'ldflags': [], + 'defines': [], + 'include_dirs': [], + }], # end _toolset == "target" + ['_toolset == "host"', { + # These are additional settings specifically for the host toolchain. + 'cflags': [], + 'cflags_c': [], + 'cflags_cc': [], + 'ldflags': [], + 'defines': [], + 'include_dirs': [], + }], # end _toolset == "host" + ], # end target_conditions + + 'configurations': { + # These are additional settings per build configuration. + # You may specify any of the keys above in this section + # (cflags, cflags_c, cflags_cc, ldflags, defines, include_dirs). + # + # You are also not limited to the names "debug" and "release". You may use + # any names you like. The configuration will be used if you pass its name + # to build.py's "--config" or "-c" flag. However, "debug" and "release" + # have convenient shorthand flags. ("--debug"/"-d" and "--release"/"-r") + 'debug': { + 'cflags': [ + '-g3', + '-Og', + ], + 'defines': [ + # Widevine strongly recommends defining _DEBUG on debug builds + '_DEBUG', + '_GLIBCXX_DEBUG', + ], + }, + 'release': { + 'cflags': [ + '-O2', + '-g0', + ], + 'defines': [ + # Widevine strongly recommends defining NDEBUG on release builds + 'NDEBUG', + ], + 'ldflags': [ + '-flto', + '-s', + ], + }, + }, # end configurations + }, # end target_defaults +} diff --git a/platforms/example/no_oemcrypto.cpp b/platforms/example/no_oemcrypto.cpp new file mode 100644 index 00000000..03253cbd --- /dev/null +++ b/platforms/example/no_oemcrypto.cpp @@ -0,0 +1,495 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "OEMCryptoCENC.h" +#include "wv_attributes.h" + +#pragma message( \ + "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 \ +successfully compile and link, but the resulting binary will fail the unit \ +tests.") + +OEMCryptoResult OEMCrypto_SetSandbox(const uint8_t* sandbox_id UNUSED, + size_t sandbox_id_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_Initialize(void) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_Terminate(void) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_Idle(OEMCrypto_IdleState state UNUSED, + uint32_t os_specific_code UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_Wake(void) { return OEMCrypto_ERROR_NOT_IMPLEMENTED; } + +OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_CreateEntitledKeySession( + OEMCrypto_SESSION oec_session UNUSED, + OEMCrypto_SESSION* key_session UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_RemoveEntitledKeySession( + OEMCrypto_SESSION key_session UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GenerateDerivedKeys( + OEMCrypto_SESSION session UNUSED, + const OEMCrypto_SharedMemory* mac_key_context UNUSED, + size_t mac_key_context_length UNUSED, + const OEMCrypto_SharedMemory* enc_key_context UNUSED, + size_t enc_key_context_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey( + OEMCrypto_SESSION session UNUSED, const uint8_t* derivation_key UNUSED, + size_t derivation_key_length UNUSED, + const OEMCrypto_SharedMemory* mac_key_context UNUSED, + size_t mac_key_context_length UNUSED, + const OEMCrypto_SharedMemory* enc_key_context UNUSED, + size_t enc_key_context_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GenerateNonce(OEMCrypto_SESSION session UNUSED, + uint32_t* nonce UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_PrepAndSignLicenseRequest( + 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_PrepAndSignRenewalRequest( + 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_LoadKeys( + OEMCrypto_SESSION session UNUSED, const uint8_t* message UNUSED, + size_t message_length UNUSED, const uint8_t* signature UNUSED, + size_t signature_length UNUSED, OEMCrypto_Substring enc_mac_keys_iv UNUSED, + OEMCrypto_Substring enc_mac_keys UNUSED, size_t key_array_length UNUSED, + const OEMCrypto_KeyObject* key_array UNUSED, OEMCrypto_Substring pst UNUSED, + OEMCrypto_Substring srm_restriction_data UNUSED, + OEMCrypto_LicenseType license_type UNUSED) { + 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) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadEntitledContentKeys( + OEMCrypto_SESSION session UNUSED, const uint8_t* message UNUSED, + size_t message_length UNUSED, size_t key_array_length UNUSED, + const OEMCrypto_EntitledContentKeyObject* key_array UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_RefreshKeys( + OEMCrypto_SESSION session UNUSED, const uint8_t* message UNUSED, + size_t message_length UNUSED, const uint8_t* signature UNUSED, + size_t signature_length UNUSED, size_t num_keys UNUSED, + const OEMCrypto_KeyRefreshObject* key_array UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadRenewal(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_QueryKeyControl( + OEMCrypto_SESSION session UNUSED, const uint8_t* content_key_id UNUSED, + size_t content_key_id_length UNUSED, uint8_t* key_control_block UNUSED, + size_t* key_control_block_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_SelectKey(OEMCrypto_SESSION session UNUSED, + const uint8_t* content_key_id UNUSED, + size_t content_key_id_length UNUSED, + OEMCryptoCipherMode cipher_mode UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_DecryptCENC( + OEMCrypto_SESSION session UNUSED, + const OEMCrypto_SampleDescription* samples UNUSED, + size_t samples_length UNUSED, + const OEMCrypto_CENCEncryptPatternDesc* pattern UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_CopyBuffer( + OEMCrypto_SESSION session UNUSED, + const OEMCrypto_SharedMemory* data_addr UNUSED, + size_t data_addr_length UNUSED, + const OEMCrypto_DestBufferDesc* out_buffer_descriptor UNUSED, + uint8_t subsample_flags UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_Generic_Encrypt( + OEMCrypto_SESSION session UNUSED, + const OEMCrypto_SharedMemory* in_buffer UNUSED, + size_t in_buffer_length UNUSED, const uint8_t* iv UNUSED, + OEMCrypto_Algorithm algorithm UNUSED, + OEMCrypto_SharedMemory* out_buffer UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_Generic_Decrypt( + OEMCrypto_SESSION session UNUSED, + const OEMCrypto_SharedMemory* in_buffer UNUSED, + size_t in_buffer_length UNUSED, const uint8_t* iv UNUSED, + OEMCrypto_Algorithm algorithm UNUSED, + OEMCrypto_SharedMemory* out_buffer UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_Generic_Sign( + OEMCrypto_SESSION session UNUSED, + const OEMCrypto_SharedMemory* buffer UNUSED, size_t buffer_length UNUSED, + OEMCrypto_Algorithm algorithm UNUSED, + OEMCrypto_SharedMemory* signature UNUSED, size_t* signature_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_Generic_Verify( + OEMCrypto_SESSION session UNUSED, + const OEMCrypto_SharedMemory* buffer UNUSED, size_t buffer_length UNUSED, + OEMCrypto_Algorithm algorithm UNUSED, + const OEMCrypto_SharedMemory* signature UNUSED, + size_t signature_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_WrapKeyboxOrOEMCert( + const uint8_t* keybox_or_cert UNUSED, size_t keybox_or_cert_length UNUSED, + uint8_t* wrapped_keybox_or_cert UNUSED, + size_t* wrapped_keybox_or_cert_length UNUSED, + const uint8_t* transport_key UNUSED, size_t transport_key_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_InstallKeyboxOrOEMCert( + const uint8_t* keybox_or_cert UNUSED, size_t keybox_or_cert_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(void) { + return OEMCrypto_ProvisioningError; +} + +OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(void) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* device_id UNUSED, + size_t* device_id_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* key_data UNUSED, + size_t* key_data_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadTestKeybox(const uint8_t* buffer UNUSED, + size_t buffer_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadOEMPrivateKey(OEMCrypto_SESSION session UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetOEMPublicCertificate( + uint8_t* public_cert UNUSED, size_t* public_cert_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetRandom(uint8_t* random_data UNUSED, + size_t random_data_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +uint32_t OEMCrypto_APIVersion(void) { return 0; } + +uint32_t OEMCrypto_MinorAPIVersion(void) { return 0; } + +OEMCryptoResult OEMCrypto_BuildInformation(char* buffer UNUSED, + size_t* buffer_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +uint8_t OEMCrypto_Security_Patch_Level(void) { return 0; } + +OEMCrypto_Security_Level OEMCrypto_SecurityLevel(void) { + return OEMCrypto_Level_Unknown; +} + +OEMCryptoResult OEMCrypto_GetHDCPCapability( + OEMCrypto_HDCP_Capability* current UNUSED, + OEMCrypto_HDCP_Capability* maximum UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetDTCP2Capability( + OEMCrypto_DTCP2_Capability* capability UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +bool OEMCrypto_SupportsUsageTable(void) { return false; } + +size_t OEMCrypto_MaximumUsageTableHeaderSize(void) { return 0; } + +bool OEMCrypto_IsAntiRollbackHwPresent(void) { return false; } + +OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(size_t* count UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(size_t* max UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +uint32_t OEMCrypto_SupportedCertificates(void) { return 0; } + +OEMCryptoResult OEMCrypto_GetCurrentSRMVersion(uint16_t* version UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +uint32_t OEMCrypto_GetAnalogOutputFlags(void) { return 0; } + +uint32_t OEMCrypto_ResourceRatingTier(void) { return 0; } + +OEMCryptoResult OEMCrypto_ProductionReady(void) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCrypto_WatermarkingSupport OEMCrypto_GetWatermarkingSupport(void) { + return OEMCrypto_WatermarkingError; +} + +OEMCryptoResult OEMCrypto_LoadProvisioning( + 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, + uint8_t* wrapped_private_key UNUSED, + size_t* wrapped_private_key_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadDRMPrivateKey( + OEMCrypto_SESSION session UNUSED, OEMCrypto_PrivateKeyType key_type UNUSED, + const uint8_t* wrapped_private_key UNUSED, + size_t wrapped_private_key_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadTestRSAKey(void) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GenerateRSASignature( + OEMCrypto_SESSION session UNUSED, const uint8_t* message UNUSED, + size_t message_length UNUSED, uint8_t* signature UNUSED, + size_t* signature_length UNUSED, RSA_Padding_Scheme padding_scheme UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_PrepAndSignProvisioningRequest( + 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_CreateUsageTableHeader( + uint8_t* header_buffer UNUSED, size_t* header_buffer_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadUsageTableHeader(const uint8_t* buffer UNUSED, + size_t buffer_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_CreateNewUsageEntry( + OEMCrypto_SESSION session UNUSED, uint32_t* usage_entry_number UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_ReuseUsageEntry(OEMCrypto_SESSION session UNUSED, + uint32_t usage_entry_number UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadUsageEntry(OEMCrypto_SESSION session UNUSED, + uint32_t usage_entry_number UNUSED, + const uint8_t* buffer UNUSED, + size_t buffer_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_UpdateUsageEntry( + OEMCrypto_SESSION session UNUSED, + OEMCrypto_SharedMemory* header_buffer UNUSED, + size_t* header_buffer_length UNUSED, + OEMCrypto_SharedMemory* entry_buffer UNUSED, + size_t* entry_buffer_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_DeactivateUsageEntry(OEMCrypto_SESSION session UNUSED, + const uint8_t* pst UNUSED, + size_t pst_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_ReportUsage(OEMCrypto_SESSION session UNUSED, + const uint8_t* pst UNUSED, + size_t pst_length UNUSED, + uint8_t* buffer UNUSED, + size_t* buffer_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_MoveEntry(OEMCrypto_SESSION session UNUSED, + uint32_t new_index UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader( + uint32_t new_entry_count UNUSED, uint8_t* header_buffer UNUSED, + size_t* header_buffer_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetBootCertificateChain( + uint8_t* bcc UNUSED, size_t* bcc_length UNUSED, + uint8_t* additional_signature UNUSED, + size_t* additional_signature_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GenerateCertificateKeyPair( + OEMCrypto_SESSION session UNUSED, uint8_t* public_key UNUSED, + size_t* public_key_length UNUSED, uint8_t* public_key_signature UNUSED, + size_t* public_key_signature_length UNUSED, + uint8_t* wrapped_private_key UNUSED, + size_t* wrapped_private_key_length UNUSED, + OEMCrypto_PrivateKeyType* key_type UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +uint32_t OEMCrypto_SupportsDecryptHash(void) { return 0; } + +OEMCryptoResult OEMCrypto_SetDecryptHash(OEMCrypto_SESSION session UNUSED, + uint32_t frame_number UNUSED, + const uint8_t* hash UNUSED, + size_t hash_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetHashErrorCode( + OEMCrypto_SESSION session UNUSED, uint32_t* failed_frame_number UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_AllocateSecureBuffer( + OEMCrypto_SESSION session UNUSED, size_t buffer_size UNUSED, + OEMCrypto_DestBufferDesc* output_descriptor UNUSED, int* secure_fd UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_FreeSecureBuffer( + OEMCrypto_SESSION session UNUSED, + OEMCrypto_DestBufferDesc* output_descriptor UNUSED, int secure_fd UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_InstallOemPrivateKey( + OEMCrypto_SESSION session UNUSED, OEMCrypto_PrivateKeyType key_type UNUSED, + const uint8_t* wrapped_private_key UNUSED, + size_t wrapped_private_key_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_ReassociateEntitledKeySession( + OEMCrypto_SESSION key_session UNUSED, + OEMCrypto_SESSION oec_session UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_LoadCasECMKeys( + OEMCrypto_SESSION session UNUSED, const uint8_t* message UNUSED, + size_t message_length UNUSED, + const OEMCrypto_EntitledContentKeyObject* even_key UNUSED, + const OEMCrypto_EntitledContentKeyObject* odd_key UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_OPK_SerializationVersion(uint32_t* ree_major UNUSED, + uint32_t* ree_minor UNUSED, + uint32_t* tee_major UNUSED, + uint32_t* tee_minor UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GenerateOTARequest(OEMCrypto_SESSION session UNUSED, + uint8_t* buffer UNUSED, + size_t* buffer_length UNUSED, + uint32_t use_test_key UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session UNUSED, + const uint8_t* buffer UNUSED, + size_t buffer_length UNUSED, + uint32_t use_test_key UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_GetOEMKeyToken(OEMCrypto_SESSION key_session UNUSED, + uint8_t* key_token UNUSED, + size_t* key_token_length UNUSED) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} diff --git a/platforms/example/oemcrypto.gyp b/platforms/example/oemcrypto.gyp new file mode 100644 index 00000000..eb35408a --- /dev/null +++ b/platforms/example/oemcrypto.gyp @@ -0,0 +1,20 @@ +# Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. +{ + 'targets': [ + { + 'target_name': 'oemcrypto', + 'type': 'static_library', + # For a real device, this GYP target should include the files necessary to + # implement OEMCrypto. However, since the CE CDM repository does not + # include an OEMCrypto implementation, we have to include a dummy file + # here. + 'include_dirs': [ + '../../oemcrypto/include', + '../../util/include', + ], + 'sources': ['no_oemcrypto.cpp'], + }, + ], +} diff --git a/platforms/example/settings.gypi b/platforms/example/settings.gypi index 273595c7..11c0720a 100644 --- a/platforms/example/settings.gypi +++ b/platforms/example/settings.gypi @@ -16,6 +16,9 @@ 'client_form_factor': 'TV', 'client_version': '1.0.0', + 'oemcrypto_lib': 'target', + 'oemcrypto_gyp_path': 'oemcrypto.gyp:oemcrypto', + 'asm_target_arch': 'x86-64', }, # end variables diff --git a/third_party/gyp/input.py b/third_party/gyp/input.py index 013231dc..9327a4b3 100644 --- a/third_party/gyp/input.py +++ b/third_party/gyp/input.py @@ -1104,7 +1104,7 @@ def EvalSingleCondition( (str(e.args[0]), e.text, build_file, e.offset), e.filename, e.lineno, e.offset, e.text) raise syntax_error - except NameError as e: + except (NameError, TypeError) as e: gyp.common.ExceptionAppend(e, 'while evaluating condition \'%s\' in %s' % (cond_expr_expanded, build_file)) raise GypError(e) diff --git a/third_party/protoc.gypi b/third_party/protoc.gypi index e1330fe1..6159c8e6 100644 --- a/third_party/protoc.gypi +++ b/third_party/protoc.gypi @@ -18,12 +18,12 @@ ['protobuf_config=="target"', { # protobuf_lib is a gyp target. 'dependencies': [ - '<(protobuf_lib_target)', - '<(protoc_host_target)', + '<(protobuf_lib_path)', + '<(protoc_host_path)', ], 'export_dependent_settings': [ - '<(protobuf_lib_target)', - '<(protoc_host_target)', + '<(protobuf_lib_path)', + '<(protoc_host_path)', ], }], ['protobuf_config=="source"', { diff --git a/util/include/file_store.h b/util/include/file_store.h index 85943564..6bb1773c 100644 --- a/util/include/file_store.h +++ b/util/include/file_store.h @@ -28,7 +28,7 @@ static const std::string kOemCertificateFileName = "oemcert.bin"; static const std::string kOemCertificateFileNamePrefix = "oemcert_"; // File class. The implementation is platform dependent. -class CORE_UTIL_EXPORT File { +class File { public: File() {} virtual ~File() {} @@ -39,7 +39,7 @@ class CORE_UTIL_EXPORT File { CORE_DISALLOW_COPY_AND_ASSIGN(File); }; -class CORE_UTIL_EXPORT FileSystem { +class FileSystem { public: FileSystem(); FileSystem(const std::string& origin, void* extra_data); diff --git a/util/include/log.h b/util/include/log.h index d1658e3f..2854956d 100644 --- a/util/include/log.h +++ b/util/include/log.h @@ -77,18 +77,13 @@ struct LoggingUidSetter { // This function is supplied for cases where the system layer does not // initialize logging. This is also needed to initialize logging in // unit tests. -CORE_UTIL_EXPORT void InitLogging(); +void InitLogging(); #ifdef __GNUC__ -[[gnu::format(printf, 5, 6)]] CORE_UTIL_EXPORT void Log(const char* file, - const char* function, - int line, - LogPriority level, - const char* fmt, ...); -#else -CORE_UTIL_EXPORT void Log(const char* file, const char* function, int line, - LogPriority level, const char* fmt, ...); +[[gnu::format(printf, 5, 6)]] #endif +void Log(const char* file, const char* function, int line, + LogPriority level, const char* fmt, ...); // Log APIs #ifdef CDM_DISABLE_LOGGING diff --git a/util/include/platform.h b/util/include/platform.h index effc5144..d101dd6e 100644 --- a/util/include/platform.h +++ b/util/include/platform.h @@ -21,7 +21,7 @@ using ssize_t = SSIZE_T; inline void sleep(int seconds) { Sleep(seconds * 1000); } -CORE_UTIL_EXPORT int setenv(const char* key, const char* value, int overwrite); +int setenv(const char* key, const char* value, int overwrite); #else # include # include diff --git a/util/include/rw_lock.h b/util/include/rw_lock.h index 5ea3a976..40d79028 100644 --- a/util/include/rw_lock.h +++ b/util/include/rw_lock.h @@ -16,7 +16,7 @@ namespace wvutil { // A simple reader-writer mutex implementation that mimics the one from C++17 -class CORE_UTIL_EXPORT shared_mutex { +class shared_mutex { public: shared_mutex() : reader_count_(0), has_writer_(false) {} ~shared_mutex(); diff --git a/util/include/string_conversions.h b/util/include/string_conversions.h index e5944bd6..461ef41e 100644 --- a/util/include/string_conversions.h +++ b/util/include/string_conversions.h @@ -15,53 +15,46 @@ namespace wvutil { // ASCII hex to Binary conversion. -CORE_UTIL_EXPORT std::vector a2b_hex(const std::string& b); -CORE_UTIL_EXPORT std::vector a2b_hex(const std::string& label, - const std::string& b); -CORE_UTIL_EXPORT std::string a2bs_hex(const std::string& b); +std::vector a2b_hex(const std::string& b); +std::vector a2b_hex(const std::string& label, + const std::string& b); +std::string a2bs_hex(const std::string& b); // Binary to ASCII hex conversion. The default versions limit output to 2k to // protect us from log spam. The unlimited version has no length limit. -CORE_UTIL_EXPORT std::string b2a_hex(const std::vector& b); -CORE_UTIL_EXPORT std::string unlimited_b2a_hex(const std::vector& b); -CORE_UTIL_EXPORT std::string b2a_hex(const std::string& b); -CORE_UTIL_EXPORT std::string unlimited_b2a_hex(const std::string& b); -CORE_UTIL_EXPORT std::string HexEncode(const uint8_t* bytes, size_t size); -CORE_UTIL_EXPORT std::string UnlimitedHexEncode(const uint8_t* bytes, - size_t size); +std::string b2a_hex(const std::vector& b); +std::string unlimited_b2a_hex(const std::vector& b); +std::string b2a_hex(const std::string& b); +std::string unlimited_b2a_hex(const std::string& b); +std::string HexEncode(const uint8_t* bytes, size_t size); +std::string UnlimitedHexEncode(const uint8_t* bytes, size_t size); // Base64 encoding/decoding. // Converts binary data into the ASCII Base64 character set and vice // versa using the encoding rules defined in RFC4648 section 4. -CORE_UTIL_EXPORT std::string Base64Encode( - const std::vector& bin_input); -CORE_UTIL_EXPORT std::string Base64Encode(const std::string& bin_input); -CORE_UTIL_EXPORT std::vector Base64Decode( - const std::string& bin_input); +std::string Base64Encode(const std::vector& bin_input); +std::string Base64Encode(const std::string& bin_input); +std::vector Base64Decode(const std::string& bin_input); // URL-Safe Base64 encoding/decoding. // Converts binary data into the URL/Filename safe ASCII Base64 // character set and vice versa using the encoding rules defined in // RFC4648 section 5. -CORE_UTIL_EXPORT std::string Base64SafeEncode( - const std::vector& bin_input); -CORE_UTIL_EXPORT std::string Base64SafeEncode(const std::string& bin_input); -CORE_UTIL_EXPORT std::vector Base64SafeDecode( - const std::string& bin_input); +std::string Base64SafeEncode(const std::vector& bin_input); +std::string Base64SafeEncode(const std::string& bin_input); +std::vector Base64SafeDecode(const std::string& bin_input); // URL-Safe Base64 encoding without padding. // Similar to Base64SafeEncode(), without any padding character '=' // at the end. -CORE_UTIL_EXPORT std::string Base64SafeEncodeNoPad( - const std::vector& bin_input); -CORE_UTIL_EXPORT std::string Base64SafeEncodeNoPad( - const std::string& bin_input); +std::string Base64SafeEncodeNoPad(const std::vector& bin_input); +std::string Base64SafeEncodeNoPad(const std::string& bin_input); // Host to Network/Network to Host conversion. -CORE_UTIL_EXPORT int64_t htonll64(int64_t x); -CORE_UTIL_EXPORT inline int64_t ntohll64(int64_t x) { return htonll64(x); } +int64_t htonll64(int64_t x); +inline int64_t ntohll64(int64_t x) { return htonll64(x); } // Encode unsigned integer into a big endian formatted string. -CORE_UTIL_EXPORT std::string EncodeUint32(uint32_t u); +std::string EncodeUint32(uint32_t u); } // namespace wvutil diff --git a/util/include/string_format.h b/util/include/string_format.h new file mode 100644 index 00000000..62f9fd91 --- /dev/null +++ b/util/include/string_format.h @@ -0,0 +1,18 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_UTIL_STRING_FORMAT_H_ +#define WVCDM_UTIL_STRING_FORMAT_H_ + +#include + +namespace wvutil { + +#ifdef __GNUC__ +[[gnu::format(printf, 2, 3)]] +#endif +bool FormatString(std::string* out, const char* fmt, ...); + +} // namespace wvutil + +#endif // WVCDM_UTIL_STRING_FORMAT_H_ diff --git a/util/include/util_common.h b/util/include/util_common.h index 7b28c482..b1ddc16b 100644 --- a/util/include/util_common.h +++ b/util/include/util_common.h @@ -9,23 +9,11 @@ #ifdef _WIN32 -# ifdef CORE_UTIL_IMPLEMENTATION -# define CORE_UTIL_EXPORT __declspec(dllexport) -# else -# define CORE_UTIL_EXPORT __declspec(dllimport) -# endif - # define CORE_UTIL_IGNORE_DEPRECATED # define CORE_UTIL_RESTORE_WARNINGS #else -# ifdef CORE_UTIL_IMPLEMENTATION -# define CORE_UTIL_EXPORT __attribute__((visibility("default"))) -# else -# define CORE_UTIL_EXPORT -# endif - # ifdef __GNUC__ # define CORE_UTIL_IGNORE_DEPRECATED \ _Pragma("GCC diagnostic push") \ diff --git a/util/src/string_format.cpp b/util/src/string_format.cpp new file mode 100644 index 00000000..91cce37e --- /dev/null +++ b/util/src/string_format.cpp @@ -0,0 +1,40 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "string_format.h" + +#include +#include +#include +#include + +#include + +namespace wvutil { + +bool FormatString(std::string* out, const char* fmt, ...) { + if (out == nullptr || fmt == nullptr) return false; + + va_list ap1; + va_start(ap1, fmt); + const int desired_size = vsnprintf(nullptr, 0, fmt, ap1); + va_end(ap1); + + if (desired_size < 0) return false; + const size_t buffer_size = + static_cast(desired_size) + 1; // +1 for null + std::unique_ptr buffer(new char[buffer_size]); + + va_list ap2; + va_start(ap2, fmt); + const int actual_size = vsnprintf(buffer.get(), buffer_size, fmt, ap2); + va_end(ap2); + + if (actual_size != desired_size) return false; + + out->assign(buffer.get(), actual_size); + return true; +} + +} // namespace wvutil diff --git a/util/test/string_format_unittest.cpp b/util/test/string_format_unittest.cpp new file mode 100644 index 00000000..282a43d2 --- /dev/null +++ b/util/test/string_format_unittest.cpp @@ -0,0 +1,64 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "string_format.h" + +#include +#include + +#include + +namespace wvutil { + +TEST(StringFormatTest, SignedInteger) { + constexpr char kFormat[] = "Version %d"; + constexpr char kResult[] = "Version -123"; + std::string result; + EXPECT_TRUE(FormatString(&result, kFormat, -123)); + EXPECT_EQ(result, kResult); +} + +TEST(StringFormatTest, UnsignedInteger) { + constexpr char kFormat[] = "Version %u"; + constexpr char kResult[] = "Version 27"; + std::string result; + EXPECT_TRUE(FormatString(&result, kFormat, 27)); + EXPECT_EQ(result, kResult); +} + +TEST(StringFormatTest, HexInteger) { + constexpr char kFormat[] = "Version %X"; + constexpr char kResult[] = "Version FF"; + std::string result; + EXPECT_TRUE(FormatString(&result, kFormat, 0xFF)); + EXPECT_EQ(result, kResult); +} + +TEST(StringFormatTest, Strings) { + constexpr char kFormat[] = "Hello, %s."; + constexpr char kResult[] = "Hello, DRM."; + std::string result; + EXPECT_TRUE(FormatString(&result, kFormat, "DRM")); + EXPECT_EQ(result, kResult); +} + +TEST(StringFormatTest, Nothing) { + constexpr char kString[] = "No format fields."; + std::string result; + EXPECT_TRUE(FormatString(&result, kString)); + EXPECT_EQ(result, kString); +} + +TEST(StringFormatTest, NullOutput) { + constexpr char kString[] = "This will never be referenced."; + EXPECT_FALSE(FormatString(nullptr, kString)); +} + +TEST(StringFormatTest, NullFormat) { + std::string result; + EXPECT_FALSE(FormatString(&result, nullptr)); + EXPECT_TRUE(result.empty()); +} + +} // namespace wvutil