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 00000000..dc8cabcb Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/602c63d2f3d13ca3206cdf204cde24e7d8f4266c differ 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 00000000..608e888d Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_request_corpus/8cebdcc0161125a10e19c45f055051712873de25 differ 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 00000000..e3197476 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/4e578d6c9628e832c099623b44f56d95aa37f94b differ 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 00000000..0cf77b5d Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/5b693511ef850e42c5ffded171794dbeb9460cc0 differ 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 00000000..51f67eb6 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dba39b6cf6524e996397ddc1e08b928b5c92bb5d differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dd1bc1827a331b7aed2a6fb6740da032123aa0a8 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dd1bc1827a331b7aed2a6fb6740da032123aa0a8 new file mode 100644 index 00000000..a3f3b269 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/license_response_corpus/dd1bc1827a331b7aed2a6fb6740da032123aa0a8 differ 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 00000000..a2d65a82 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/53c26407b39c997143146a0dce8ff0ac11f565e1 differ 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 00000000..a4ded576 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_request_corpus/fab3c99d604bab7b7bf5c54c5bd995fc98d4d96f differ 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 00000000..0616a29b Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/provisioning_response_corpus/91e10d030fbdd3374e57a2720f09488f2b03ce69 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/12a72efb395e731ec4470b5f5b6768d6806e9131 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/12a72efb395e731ec4470b5f5b6768d6806e9131 new file mode 100644 index 00000000..7371c1f9 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/12a72efb395e731ec4470b5f5b6768d6806e9131 differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/21de033b9baf2a0e82ae3b4185b22aa0acf69bbc b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/21de033b9baf2a0e82ae3b4185b22aa0acf69bbc new file mode 100644 index 00000000..ef24b189 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/21de033b9baf2a0e82ae3b4185b22aa0acf69bbc differ 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 00000000..4e6f216d Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/97bf96be666434bfa93dbfb36b81baeefed14170 differ 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 00000000..748c29cf Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_request_corpus/a7b0e7dca597331d7f051204096c9d01ba6d468e differ 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 00000000..4abb16a8 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/38df40a320f60e955006aaa294b74d45a316e50f differ 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 00000000..892a4edb Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/9962997b5ea87005276319cbfff67884846485cf differ diff --git a/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/c84663115c890873dd585987c1223193d29aef16 b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/c84663115c890873dd585987c1223193d29aef16 new file mode 100644 index 00000000..b1fc6da6 Binary files /dev/null and b/oemcrypto/odk/test/fuzzing/corpus/little_endian_64bit/renewal_response_corpus/c84663115c890873dd585987c1223193d29aef16 differ 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