diff --git a/.gitallowed b/.gitallowed index 5826462a..8323eaa7 100644 --- a/.gitallowed +++ b/.gitallowed @@ -1 +1,5 @@ key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE +Key = 000102030405060708090A0B0C0D0E0F10111213 +key: "\000\001\002\003\004\005\006\007\010\t\n\013\014\r\016\017" +key: "x\241\334\006F\021\227\007\351\003QM\212\000s_" + diff --git a/CHANGELOG.md b/CHANGELOG.md index 322ee51e..80bccdea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,82 @@ [TOC] +## 17.1.0 (2022-06-29) + +**Note:** CE CDM 17.1.0 is the first release of the CE CDM 17 series. It is +numbered 17.1 to reflect that it supports and requires OEMCrypto v17.1. + +### Features: + + - Supports and requires OEMCrypto v17.1. + - Supports Provisioning 4.0, a new provisioning scheme that does not require + installing keyboxes in the factory. Talk to your Widevine Partner + Engineering contact if you would like to start using Provisioning 4.0. + - Includes a large number of additional tests that exercise edge-cases in the + CDM and OEMCrypto. The total run-time of the unit test suite is now very + long. As such, partners are recommended to run only a subset of the full + suite during development. Information on what subset to run and how to do + this can be found in the CE CDM Integration Guide. + - Note that running the full test suite is still required before you can + release your device. + - Documentation is no longer distributed with the CDM as a PDF and can now be + found on the [Widevine Developer Site][wv-devsite]. + - The CE CDM no longer includes a copy of OEMCrypto with the CDM. If you are + an OEMCrypto implementer, you should have access to the + [OEMCrypto partner repository][oec-partner-repo], which contains additional + source code and information about implementing OEMCrypto, including the + Widevine-written OEMCrypto implementation, the OEMCrypto Porting Kit. (OPK) + If you are not an OEMCrypto implementer, then you will need to get an + OEMCrypto implementation from your SoC manufacturer before you can use the + CE CDM. + - Added a method to retrieve the system ID of the underlying OEMCrypto + implementation. + - Client information is no longer passed into `Cdm::initialize()` at runtime. + Instead, client information is set at compile-time and baked into the CDM + binary. New variables have been added to `platform_properties.gypi` to + support this. + - A method has been added to retrieve the client information from the CDM. + - A new example platform directory has been released, `example/`, which will + provide a cleaner base to build your own platform files from than the + previous `x86-64/` directory. + +[wv-devsite]: https://developers.google.com/widevine/drm/client/ce-cdm +[oec-partner-repo]: https://widevine-partner.googlesource.com/oemcrypto/ + +### Dependency Updates: + + - The bundled version of Protobuf has been updated to [v3.19.1][proto-3.19.1]. + Note that, as before, Widevine CE CDM will work with any version of Protobuf + back to 2.6. However, we provide the version of Protobuf that we test with + internally as a default. + - The bundled version of BoringSSL has been updated to commit + [`731d6cbef936e60a04738edf4eb4fc93e187706a`][boringssl-731d6c]. + - The bundled version of googletest & googlemock have been updated to commit + [`e2f3978937c0244508135f126e2617a7734a68be`][googletest-e2f397]. + +[proto-3.19.1]: https://github.com/protocolbuffers/protobuf/releases/tag/v3.19.1 +[boringssl-731d6c]: https://boringssl.googlesource.com/boringssl/+/731d6cbef936e60a04738edf4eb4fc93e187706a +[googletest-e2f397]: https://github.com/google/googletest/commit/e2f3978937c0244508135f126e2617a7734a68be + +### Bugfixes: + +**Note:** As CE CDM v17.1 contains almost two years of bugfixes since the +previous release, this list contains only highlights and is not comprehensive. + + - Fixed an issue where the host interfaces could not be stored in smart + pointers due to the visibility of their destructors. + - Fixed an issue where the CDM could try to access the usage table header via + the wrong `IStorage` instance. + - Widevine now does development with a stricter set of flags and sanitizers, + which have enabled us to find and fix several issues in the code and should + allow it to build on a wider range of toolchains without customizing + compilation flags. + - Changed several log messages to format values in a more cross-platform way. + - Several crashes due to null pointers and thread-safety issues have been + fixed. + - Fixed an issue where the unit tests could crash depending on the order the + object files were linked into it. + ## 16.4.0 (2020-10-09) Features: diff --git a/README.md b/README.md index 0bd35992..3357038e 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,29 @@ -# Widevine CE CDM 16.4.0 +# Widevine CE CDM 17.1.0 -Released 2020-10-09 +Released 2022-06-29 -## Getting started +## Getting Started This project contains the sources for building a Widevine CDM module. Read the following to learn more about the contents of this project and how to use them: -[Widevine_CE_CDM_IntegrationGuide_16.4.0.pdf][integration-guide]\ -Documents the CDM API and describes how to integrate the CDM into -a system. +The [Widevine Developer Site][wv-devsite] documents the CDM API and describes +how to integrate the CDM into a system. -[CHANGELOG.md][changelog]\ -Lists the major changes for each release. +[CHANGELOG.md][changelog] lists the major changes for each release. -[integration-guide]: ./Widevine_CE_CDM_IntegrationGuide_16.4.0.pdf +[wv-devsite]: https://developers.google.com/widevine/drm/client/ce-cdm [changelog]: ./CHANGELOG.md -## Reference OEMCrypto Implementation +## Contains No OEMCrypto The CE CDM requires an implementation of OEMCrypto, our hardware abstraction -layer, in order to compile and run successfully. To facilitate testing and -development, a test-only software implementation of OEMCrypto is included in -the `oemcrypto/ref/` directory. The CE CDM links against this version of -OEMCrypto by default. **This implementation is *NOT* suitable for production use -and should *NOT* be released on devices.** It is included only so you can -compile and test the CE CDM on your platform before your own implementation of -OEMCrypto is ready. +layer, in order to compile and run successfully. If you are an OEMCrypto +implementer, you should have access to the +[OEMCrypto partner repository][oec-repo], which contains additional source code +and information about implementing OEMCrypto, including the Widevine-written +OEMCrypto implementation, the OEMCrypto Porting Kit. (OPK) If you are not an +OEMCrypto implementer, then you will need to get an OEMCrypto implementation +from your SoC manufacturer before you can use the CE CDM. + +[oec-repo]: https://widevine-partner.googlesource.com/oemcrypto/ diff --git a/Widevine_CE_CDM_IntegrationGuide_16.4.0.pdf b/Widevine_CE_CDM_IntegrationGuide_16.4.0.pdf deleted file mode 100644 index 159f574d..00000000 Binary files a/Widevine_CE_CDM_IntegrationGuide_16.4.0.pdf and /dev/null differ diff --git a/build.py b/build.py index 1c31e28c..ee45912e 100755 --- a/build.py +++ b/build.py @@ -1,7 +1,11 @@ #!/usr/bin/python3 -B # Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -# source code may only be used and distributed under the Widevine Master -# License Agreement. +# source code may only be used and distributed under the Widevine License +# Agreement. + +# b/230527132: Even though this usually uses Python3, we still need to support +# Python2 for Cobalt builds. + """Generates build files and builds the CDM source release.""" # Lint as: python2, python3 @@ -13,44 +17,20 @@ import os import subprocess import sys -# pylint: disable=C6204 +import build_utils + # Absolute path to Widevine CDM project repository. CDM_TOP_PATH = os.path.abspath(os.path.dirname(__file__)) +COBALT_TOP_PATH = os.path.join(CDM_TOP_PATH, '..', '..') -# If gyp has been installed locally in third_party, this will find it. -# Irrelevant if gyp has been installed globally. -sys.path.insert(1, os.path.join(CDM_TOP_PATH, 'third_party')) -import gyp -# pylint: enable=C6204 - -PLATFORMS_DIR_PATH = os.path.join(CDM_TOP_PATH, 'platforms') -OEMCRYPTO_FUZZTEST_DIR_PATH = os.path.join(CDM_TOP_PATH, 'oemcrypto', 'test', - 'fuzz_tests') +# NOTE: Use relative paths for most of this because the Xcode generator tends +# to ignore the output directory if you use absolute paths. +PLATFORMS_DIR_PATH = 'platforms' # Exit status for script if failure arises. EXIT_FAILURE = 1 -def LoadFields(path): - """Loads variables from an external python script. - - Attempts to load the specified python fields from the source file - specified at the given |path|. - - Args: - path: The path (absolute to relative) to the source file containing module - to be loaded. - - Returns: - The fields dictionary if it is successfully loaded. Otherwise - returns None - """ - fields = {} - with open(path) as f: - exec(f.read(), fields, fields) # pylint: disable=W0122 - return fields - - def IsNinjaInstalled(): """Determine if ninja is installed.""" try: @@ -84,7 +64,7 @@ def PlatformExists(platform, print_details=False): True if the there exists configuration files for the specified |platform|, False otherwise. """ - target_path = os.path.join(PLATFORMS_DIR_PATH, platform) + target_path = os.path.join(CDM_TOP_PATH, PLATFORMS_DIR_PATH, platform) platform_gypi_path = os.path.join(target_path, 'settings.gypi') platform_environment_path = os.path.join(target_path, 'environment.py') @@ -114,13 +94,14 @@ def RetrieveListOfPlatforms(): Returns: A list of strings containing the name of the platform """ - if not os.path.isdir(PLATFORMS_DIR_PATH): + path = os.path.join(CDM_TOP_PATH, PLATFORMS_DIR_PATH) + if not os.path.isdir(path): print( 'Cannot find platforms directory: expected_path = {}'.format( - PLATFORMS_DIR_PATH), + path), file=sys.stderr) return [] - return sorted(filter(PlatformExists, os.listdir(PLATFORMS_DIR_PATH))) + return sorted(filter(PlatformExists, os.listdir(path))) def VerboseSubprocess(args): @@ -129,47 +110,88 @@ def VerboseSubprocess(args): return subprocess.call(args) -def RunMake(unused_output_path, build_config, job_limit, verbose): +def RunMake(unused_output_path, options): """Run Make as build system.""" - os.environ['BUILDTYPE'] = build_config + os.environ['BUILDTYPE'] = options.build_config - if job_limit is not None: - if math.isinf(job_limit): + if options.jobs is not None: + if math.isinf(options.jobs): job_args = ['-j'] else: - job_args = ['-j', str(job_limit)] + job_args = ['-j', str(options.jobs)] else: job_args = [] - if verbose: + if options.verbose: job_args.append('-v') - job_args.extend(['all', 'widevine_ce_cdm_shared']) + job_args += options.target return VerboseSubprocess(['make', '-C', CDM_TOP_PATH] + job_args) -def RunNinja(output_path, build_config, job_limit, verbose): +def RunNinja(output_path, options): """Run Ninja as build system.""" - build_path = os.path.join(output_path, build_config) + build_path = os.path.join(output_path, options.build_config) - if job_limit is not None: - if math.isinf(job_limit): + if options.jobs is not None: + if math.isinf(options.jobs): print('Ninja cannot run an infinite number of jobs', file=sys.stderr) print('Running at most 1000 jobs') - job_limit = 1000 - job_args = ['-j', str(job_limit)] + options.jobs = 1000 + job_args = ['-j', str(options.jobs)] else: job_args = [] - if verbose: + if options.verbose: job_args += ['-v'] - job_args += ['all', 'widevine_ce_cdm_shared'] + job_args += options.target return VerboseSubprocess(['ninja', '-C', build_path] + job_args) +def RunXcode(output_path, options): + """Run Xcode as a build system.""" + if 'all' in options.target: + scheme = 'All' + elif len(options.target) == 1: + scheme = options.target[0] + else: + raise ValueError('Can only specify one target on Xcode') + + cmd = [ + 'xcodebuild', 'test' if options.xcode_test else 'build', + '-project', os.path.join(output_path, 'cdm', 'cdm_unittests.xcodeproj'), + '-scheme', scheme, + '-configuration', options.build_config, + '-derivedDataPath', os.path.join(output_path, 'DerivedData'), + ] + if options.xcode_test: + cmd += ['-only-testing:' + scheme] + if options.ios_device_name: + if options.ios: + cmd += ['-destination', 'platform=iOS,name=' + options.ios_device_name] + else: + cmd += [ + '-destination', + 'platform=iOS Simulator,name=' + options.ios_device_name, + ] + elif options.ios: + cmd += ['-destination', 'generic/platform=iOS'] + elif options.ios_sim: + cmd += ['-destination', 'generic/platform=iOS Simulator'] + else: + cmd += ['-destination', 'platform=macOS,arch=x86_64'] + + if options.jobs is not None: + cmd += ['-jobs', str(options.jobs)] + if not options.verbose: + cmd += ['-quiet'] + return VerboseSubprocess(cmd) + + # Map from generator name to generator invocation function. BUILDERS = { 'make': RunMake, 'ninja': RunNinja, + 'xcode': RunXcode, } @@ -177,7 +199,7 @@ def GetBuilder(generator): return BUILDERS.get(generator) -def ImportPlatform(platform, gyp_args, build_fuzz_tests): +def ImportPlatform(platform, is_cobalt, skip_deps, gyp_args): """Handles platform-specific setup for the named platform. Computes platform-specific paths, sets gyp arguments for platform-specific @@ -186,8 +208,8 @@ def ImportPlatform(platform, gyp_args, build_fuzz_tests): Args: platform: The name of the platform. + skip_deps: Whether to skip updating submodules. gyp_args: An array of gyp arguments to which this function will append. - build_fuzz_tests: True when we are building OEMCrypto fuzz tests. Returns: The path to the root of the build output. @@ -196,34 +218,54 @@ def ImportPlatform(platform, gyp_args, build_fuzz_tests): print(' Target Platform: ' + platform) assert PlatformExists(platform) - target_path = os.path.join(PLATFORMS_DIR_PATH, platform) + target_path = os.path.join(CDM_TOP_PATH, PLATFORMS_DIR_PATH, platform) platform_gypi_path = os.path.join(target_path, 'settings.gypi') - platform_environment_path = os.path.join(target_path, 'environment.py') - output_path = os.path.join(CDM_TOP_PATH, 'out', platform) + # Use an absolute path so the Ninja generator finds the correct path; this + # still works with the Xcode generator, which doesn't like absolute paths. + if is_cobalt: + output_path = os.path.join('out') + else: + output_path = os.path.join(CDM_TOP_PATH, 'out', platform) + gyp_args.append('--generator-output=' + output_path) gyp_args.append('--include=' + platform_gypi_path) - if build_fuzz_tests: - fuzzer_settings_path = os.path.join(OEMCRYPTO_FUZZTEST_DIR_PATH, - 'platforms/x86-64') - fuzzer_settings_gypi_path = os.path.join(fuzzer_settings_path, - 'fuzzer_settings.gypi') - gyp_args.append('--include=' + fuzzer_settings_gypi_path) gyp_args.append('-Goutput_dir=' + output_path) - platform_environment_path = os.path.join(target_path, 'environment.py') - target = LoadFields(platform_environment_path) + target = build_utils.LoadPlatform(platform) + + if not skip_deps and 'submodules' in target: + for path in target['submodules']: + print(' Updating submodule:', path) + if subprocess.call(['git', '-C', CDM_TOP_PATH, 'submodule', 'update', + '--init', path]) != 0: + return None + + if 'gyp_args' in target: + gyp_args.extend(target['gyp_args']) if 'export_variables' in target: for variable, value in target['export_variables'].items(): - if not os.environ.get(variable): + existing_value = os.environ.get(variable) + if not existing_value: + if '/' in value or '\\' in value: + # Use absolute paths since the output directory will be different. If + # "value" is already absolute, os.path.join will only use that part. + value = os.path.normpath(os.path.join(CDM_TOP_PATH, target_path, + value)) os.environ[variable] = value - print(' set {} to {}'.format(variable, value)) + print('* Set {} to "{}"'.format(variable, value)) + else: + print('* Did not set {} to "{}"'.format(variable, value)) + print(' {} is already set to "{}"'.format(variable, existing_value)) return output_path def main(args): - if IsNinjaInstalled(): + if sys.platform == 'darwin': + print('Host platform is Mac - use xcode for default generator') + default_generator = 'xcode' + elif IsNinjaInstalled(): print('ninja is installed - use ninja for default generator') default_generator = 'ninja' else: @@ -233,6 +275,7 @@ def main(args): parser = argparse.ArgumentParser() parser.add_argument( 'platform', + nargs='?', help=('The platform to target. To add a new platform, create a new ' 'platform directory with "environment.py" and "settings.gypi" ' 'files under platforms/.'), @@ -260,6 +303,16 @@ def main(args): help=('Select a build configuration to use. Any configuration defined in ' 'the chosen platform\'s "settings.gypi" file may be used.')) + ios_group = parser.add_mutually_exclusive_group() + ios_group.add_argument( + '--ios', + action='store_true', + help=('On Mac, build for iOS device instead.')) + ios_group.add_argument( + '--ios_sim', + action='store_true', + help=('On Mac, build for iOS simulator instead.')) + parser.add_argument( '-g', '--generator', @@ -267,6 +320,12 @@ def main(args): help='Which build system to use. Defaults to {}.'.format( default_generator), choices=BUILDERS.keys()) + parser.add_argument( + '-t', + '--target', + nargs='+', + default=['all', 'widevine_ce_cdm_shared'], + help='Which target(s) to build. Can specify multiple values.') parser.add_argument( '-j', '--jobs', @@ -291,19 +350,53 @@ def main(args): help=('External GYP file that is processed after the standard GYP files. ' '(May be specified multiple times.)')) parser.add_argument( - '-ft', - '--fuzz_tests', + '--xcode_test', action='store_true', - help='Set this flag if you want to build fuzz tests.') + help='When using Xcode, run tests in addition to building.') + parser.add_argument( + '--ios_device_name', + help='Use the given iOS device name for builds/tests.') parser.add_argument( '-v', '--verbose', action='store_true', help=('Print verbose build output, including verbose output from the ' 'generator.')) + parser.add_argument( + '--skip-deps', + action='store_true', + help="Don't download/update submodules.") + parser.add_argument( + '--cobalt', + metavar='CONFIG', + help=('Build using Cobalt tools using the given Cobalt config; project ' + 'must be checked out within the Cobalt source tree.')) options = parser.parse_args(args) + if options.cobalt: + if sys.version_info.major != 2: + parser.error('Must use python2 with --cobalt') + if options.ios or options.ios_sim: + parser.error('Cannot use --cobalt with --ios/--ios_sim') + if not options.platform: + options.platform = 'cobalt' + if not options.platform: + parser.error('Must specify platform or use --cobalt') + + # pylint: disable=C6204 + # If gyp has been installed locally in third_party, this will find it. + # Irrelevant if gyp has been installed globally. + if options.cobalt: + os.chdir(COBALT_TOP_PATH) + sys.path.insert(1, os.path.join(COBALT_TOP_PATH, 'tools', 'gyp', 'pylib')) + sys.path.append(COBALT_TOP_PATH) + else: + os.chdir(CDM_TOP_PATH) + sys.path.insert(1, os.path.join(CDM_TOP_PATH, 'third_party')) + import gyp + # pylint: enable=C6204 + if not PlatformExists(options.platform, print_details=True): platforms = RetrieveListOfPlatforms() if platforms: @@ -311,22 +404,44 @@ def main(args): return EXIT_FAILURE gyp_args = [ - '--format=' + options.generator, - '--depth=' + CDM_TOP_PATH, + '-D', 'generator=' + options.generator, + # On iOS, we can't easily pass environment variables, so we define a + # compiler flag to store the filter. + '-D', 'gtest_filter=' + os.environ.get('GTEST_FILTER', '*'), + '--depth', '.', ] - if options.fuzz_tests: - gyp_args.append( - os.path.join(OEMCRYPTO_FUZZTEST_DIR_PATH, 'oemcrypto_fuzztests.gyp')) + if options.cobalt: + os.environ['COBALT_CONFIG'] = options.cobalt + options.build_config = options.cobalt + '_' + options.build_config + gyp_args += [ + '-f', options.generator + '-' + options.cobalt, + '--toplevel-dir=.', + '-DOS=starboard', + '-Gconfig=' + options.build_config, + ] else: - gyp_args.append(os.path.join(CDM_TOP_PATH, 'cdm', 'cdm_unittests.gyp')) + gyp_args += [ + '-f', options.generator, + ] + + if options.ios or options.ios_sim: + gyp_args += ['-DOS=ios'] + for var in options.define: + gyp_args.append('-D' + var) for var in options.extra_gyp: gyp_args.append(var) - for var in options.define: - gyp_args.append('-D' + var) + output_path = ImportPlatform(options.platform, options.cobalt, + options.skip_deps, gyp_args) + if not output_path: + return 1 - output_path = ImportPlatform(options.platform, gyp_args, options.fuzz_tests) + if options.cobalt: + gyp_args.append(os.path.join('third_party', 'cdm', 'cdm', + 'cdm_unittests.gyp')) + else: + gyp_args.append(os.path.join('cdm', 'cdm_unittests.gyp')) print(' Running: {}'.format(' '.join(['gyp'] + gyp_args))) retval = gyp.main(gyp_args) @@ -341,8 +456,7 @@ def main(args): print(' Cannot automatically build with this generator', file=sys.stderr) print(' Please start the build manually', file=sys.stderr) return EXIT_FAILURE - return builder(output_path, options.build_config, options.jobs, - options.verbose) + return builder(output_path, options) if __name__ == '__main__': diff --git a/build_utils.py b/build_utils.py new file mode 100644 index 00000000..ffc29ae8 --- /dev/null +++ b/build_utils.py @@ -0,0 +1,25 @@ +# Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. + +import os +import sys + +CDM_TOP_PATH = os.path.abspath(os.path.dirname(__file__)) + + +def LoadPlatform(name): + """Loads and returns variables from the given platform.""" + # Don't use "import" since we don't want the module cached since they all have + # the same base name. This ensures we can load different platforms. + path = os.path.join(CDM_TOP_PATH, 'platforms', name, 'environment.py') + if sys.version_info.major == 2: + # Need to support Python2 for Cobalt + import imp + env = imp.load_source('environment', path) + else: + import importlib.util + spec = importlib.util.spec_from_file_location('environment', path) + env = importlib.util.module_from_spec(spec) + spec.loader.exec_module(env) + return vars(env) diff --git a/cdm/cdm.gyp b/cdm/cdm.gyp index adea015c..c8841f98 100644 --- a/cdm/cdm.gyp +++ b/cdm/cdm.gyp @@ -1,5 +1,5 @@ # Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -# source code may only be used and distributed under the Widevine Master License +# source code may only be used and distributed under the Widevine License # Agreement. # # Refer to the distribution package's integration guide @@ -13,17 +13,16 @@ 'core.gypi', 'util.gypi', ], # Get list of core source files. - 'conditions': [ - ['protobuf_config=="source"', { - # Include protobuf targets used by protobuf_config=='source' - 'includes': ['../third_party/protobuf.gypi'], - }], - ], # conditions + 'variables': { + 'has_dual_key%': 'false', + }, 'targets': [ { + 'toolsets' : [ 'target' ], 'target_name': 'license_protocol', 'type': 'static_library', 'standalone_static_library': 1, + 'hard_dependency': 1, 'includes': ['../third_party/protoc.gypi'], 'sources': [ '../core/src/license_protocol.proto', @@ -33,9 +32,11 @@ }, }, { + 'toolsets' : [ 'target' ], 'target_name': 'device_files', 'type': 'static_library', 'standalone_static_library': 1, + 'hard_dependency': 1, 'includes': ['../third_party/protoc.gypi'], 'sources': ['../core/src/device_files.proto',], 'variables': { @@ -43,9 +44,11 @@ }, }, { + 'toolsets' : [ 'target' ], 'target_name': 'metrics_proto', 'type': 'static_library', 'standalone_static_library': 1, + 'hard_dependency': 1, 'includes': ['../third_party/protoc.gypi'], 'sources': ['../metrics/src/wv_metrics.proto',], 'variables': { @@ -53,17 +56,22 @@ }, }, { + 'toolsets' : [ 'target' ], 'target_name': 'widevine_utils', 'type': 'static_library', + 'standalone_static_library': 1, + 'hard_dependency': 1, 'include_dirs': [ '../util/include', ], 'sources': [ '<@(wvutil_sources)'], }, { + 'toolsets' : [ 'target' ], 'target_name': 'widevine_cdm_core', 'type': 'static_library', 'standalone_static_library': 1, + 'hard_dependency': 1, 'dependencies': [ 'device_files', 'license_protocol', @@ -104,6 +112,16 @@ 'sources': [ '../core/src/privacy_crypto_<(privacy_crypto_impl).cpp', ], + 'conditions': [ + ['privacy_crypto_impl=="apple"', { + 'link_settings': { + 'libraries': [ + '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', + '$(SDKROOT)/System/Library/Frameworks/Security.framework', + ], + }, + }], + ], }], # end else ['oemcrypto_adapter_type=="dynamic"', { 'sources': [ @@ -112,14 +130,21 @@ }], ['oemcrypto_adapter_type=="static"', { 'sources': [ - '../core/src/oemcrypto_adapter_static.cpp', + '../core/src/oemcrypto_adapter_static.cpp', ], }], - # TODO(b/139814713): For testing and internal use only. - ['oemcrypto_adapter_type=="static_v15"', { + ['oemcrypto_adapter_type=="static_v16"', { 'sources': [ '../core/src/oemcrypto_adapter_static.cpp', - '../core/src/oemcrypto_adapter_static_v16.cpp', + '../core/src/oemcrypto_adapter_static_v17.cpp', + ], + }], + ['has_dual_key=="true"', { + 'defines': ['HAS_DUAL_KEY'], + }], + ['support_ota_keybox_functions=="false"', { + 'sources': [ + '../core/src/oemcrypto_ota_stubs.cpp', ], }], ], @@ -128,6 +153,7 @@ # This is the widevine_ce_cdm built as a static library. This name does # not mean that it uses oemcrypto's static adapter. Control over which # adapter is used comes from the settings file for the platform. + 'toolsets' : [ 'target' ], 'target_name': 'widevine_ce_cdm_static', 'type': 'static_library', 'standalone_static_library': 1, @@ -167,6 +193,7 @@ ], }, # widevine_cdm_static target { + 'toolsets' : [ 'target' ], 'target_name': 'widevine_ce_cdm_shared', 'type': 'shared_library', 'dependencies': [ @@ -179,6 +206,7 @@ }, }, { + 'toolsets' : [ 'target' ], 'target_name': 'dummy', 'type': 'none', }, diff --git a/cdm/cdm_reboot_tests.gyp b/cdm/cdm_reboot_tests.gyp new file mode 100644 index 00000000..6d980cc6 --- /dev/null +++ b/cdm/cdm_reboot_tests.gyp @@ -0,0 +1,145 @@ +# Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. +# +# Any top-level targets in this file (and their dependencies) will be built by +# the CE CDM's ./build.py build system. Refer to the distribution package's +# README for details. +{ + 'includes': [ + 'platform_properties.gypi', + ], + 'variables': { + # Directory where OEMCrypto header, test, and reference files lives. + 'oemcrypto_dir': '../oemcrypto', + # Directory where widevine utilities live. + 'util_dir': '../util', + 'metrics_target': 'cdm.gyp:metrics_proto', + 'device_files_target': 'cdm.gyp:device_files', + # The path to the test device cert source file. + 'device_cert_path%': 'test/device_cert.cpp', + }, + 'targets': [{ + 'toolsets' : [ 'target' ], + 'target_name': 'widevine_ce_cdm_reboot_tests', + 'sources': [ + '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', + '../core/test/fake_provisioning_server.cpp', + '../core/test/http_socket.cpp', + '../core/test/license_holder.cpp', + '../core/test/license_request.cpp', + '../core/test/reboot_test.cpp', + '../core/test/test_base.cpp', + '../core/test/test_printers.cpp', + '../core/test/url_request.cpp', + '../oemcrypto/test/oec_key_deriver.cpp', + '../oemcrypto/test/oec_device_features.cpp', + '../util/test/test_sleep.cpp', + ], + 'includes': [ + '../oemcrypto/odk/src/kdo.gypi', + '../util/libssl_dependency.gypi', + ], + 'include_dirs': [ + '../cdm/include', + '../core/include', + '../core/test', + '../metrics/include', + '../oemcrypto/test', + '../oemcrypto/include', + '../util/include', + '../util/test', + ], + 'dependencies': [ + 'cdm.gyp:widevine_ce_cdm_static', + '../oemcrypto/odk/src/odk.gyp:odk', + '../third_party/googletest.gyp:gmock', + '../third_party/googletest.gyp:gtest', + '<(metrics_target)', + '<(device_files_target)', + ], + 'defines': [ + # 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', + ], + 'msvs_settings': { + 'VCLinkerTool': { + # Additionally, since they are loaded locally, suppress these + # warnings. + 'AdditionalOptions': [ + '/IGNORE:4049', + '/IGNORE:4217', + ], + }, + }, + 'conditions': [ + ['OS=="ios"', { + 'type': 'loadable_module', + 'mac_xctest_bundle': '1', + 'defines': [ + 'GTEST_FILTER="<(gtest_filter)"', + ], + 'dependencies': [ + 'cdm_unittests.gyp:dummy_app', + ], + 'sources': [ + 'test/gtest_xctest_wrapper.mm', + ], + 'xcode_settings': { + 'BUNDLE_LOADER': '$(TEST_HOST)', + 'TEST_HOST': '<(PRODUCT_DIR)/dummy_app.app/dummy_app', + 'WRAPPER_EXTENSION': 'xctest', + }, + }, { + 'type': 'executable', + }], + ['oemcrypto_lib=="ref"', { + 'dependencies': [ + 'oemcrypto_reference_implementation.gyp:oec_ref', + ], + }], + # TODO(b/139814713): For testing and internal use only. + ['oemcrypto_adapter_type=="static_v15"', { + 'defines': [ + # This is used by the unit tests to use some v15 functions. + 'TEST_OEMCRYPTO_V15', + ], + }], + ['oemcrypto_lib=="level3"', { + 'sources': [ + # The test impl of OEMCrypto_Level3FileSystem and its factory. + 'test/level3_file_system_ce_test.h', + 'test/level3_file_system_ce_test.cpp', + 'test/level3_file_system_ce_test_factory.cpp', + ], + 'conditions': [ + ['oemcrypto_adapter_type=="dynamic" ', { + 'dependencies': [ + '../oemcrypto/level3/oec_level3.gyp:oec_level3_dynamic', + ], + }, { + 'dependencies': [ + '../oemcrypto/level3/oec_level3.gyp:oec_level3_static', + ], + }], + ], + }], + ['oemcrypto_lib=="target"', { + 'dependencies': [ + '<(oemcrypto_gyp_target)', + ], + }], + ], + }], +} diff --git a/cdm/cdm_unittests.gyp b/cdm/cdm_unittests.gyp index 42dd7c13..2d3e453c 100644 --- a/cdm/cdm_unittests.gyp +++ b/cdm/cdm_unittests.gyp @@ -1,9 +1,10 @@ # Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -# source code may only be used and distributed under the Widevine Master License +# source code may only be used and distributed under the Widevine License # Agreement. # -# Builds under the CDM ./build.py (target platform) build system -# Refer to the distribution package's README for details. +# Any top-level targets in this file (and their dependencies) will be built by +# the CE CDM's ./build.py build system. Refer to the distribution package's +# README for details. { 'includes': [ 'platform_properties.gypi', @@ -15,103 +16,262 @@ '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': [ - { - 'target_name': 'widevine_ce_cdm_unittest', - 'type': 'executable', - 'sources': [ - # The test runner and the testing device certificate. - 'test/cdm_test_main.cpp', - 'test/device_cert.cpp', - 'test/device_cert.h', - # The test host, which is required for all test suites on CE. - 'test/test_host.cpp', - 'test/test_host.h', - '../util/test/test_sleep.cpp', - ], - 'includes': [ - '../oemcrypto/test/oemcrypto_unittests.gypi', - '../oemcrypto/odk/src/kdo.gypi', - 'cdm_unittests.gypi', - 'core_unittests.gypi', - 'util_unittests.gypi', - ], - 'dependencies': [ - 'cdm.gyp:widevine_ce_cdm_static', - '../third_party/gmock.gyp:gmock', - '../third_party/gmock.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 - # warnings. - 'AdditionalOptions': [ - '/IGNORE:4049', - '/IGNORE:4217', - ], - }, + 'targets': [{ + 'toolsets' : [ 'target' ], + 'target_name': 'widevine_ce_cdm_unittest', + 'sources': [ + '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', + '../util/test/test_sleep.cpp', + ], + 'includes': [ + '../oemcrypto/test/oemcrypto_unittests.gypi', + '../oemcrypto/odk/src/kdo.gypi', + '../oemcrypto/odk/test/odk_test.gypi', + '../oemcrypto/util/oec_ref_util_unittests.gypi', + 'cdm_unittests.gypi', + 'core_unittests.gypi', + 'util_unittests.gypi', + ], + 'dependencies': [ + 'cdm.gyp:widevine_ce_cdm_static', + '../oemcrypto/util/oec_ref_util.gyp:oec_ref_util', + '../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 + # warnings. + 'AdditionalOptions': [ + '/IGNORE:4049', + '/IGNORE:4217', + ], }, - 'conditions': [ - ['oemcrypto_lib=="ref"', { - 'dependencies': [ - 'oec_ref', - ], - }], - # TODO(b/139814713): For testing and internal use only. - ['oemcrypto_adapter_type=="static_v15"', { - 'defines': [ - # This is used by the unit tests to use some v15 functions. - 'TEST_OEMCRYPTO_V15', - ], - }], - ['oemcrypto_lib=="level3"', { - 'sources': [ - # The test impl of OEMCrypto_Level3FileSystem and its factory. - 'test/level3_file_system_ce_test.h', - 'test/level3_file_system_ce_test.cpp', - 'test/level3_file_system_ce_test_factory.cpp', - ], - 'conditions': [ - ['oemcrypto_adapter_type=="dynamic" ', { - 'dependencies': [ - '../oemcrypto/level3/oec_level3.gyp:oec_level3_dynamic', - ], - }, { - 'dependencies': [ - '../oemcrypto/level3/oec_level3.gyp:oec_level3_static', + }, + 'conditions': [ + ['OS=="ios"', { + 'type': 'loadable_module', + 'mac_xctest_bundle': '1', + 'defines': [ + 'GTEST_FILTER="<(gtest_filter)"', + ], + 'dependencies': [ + 'dummy_app', + ], + 'sources': [ + 'test/gtest_xctest_wrapper.mm', + ], + 'xcode_settings': { + 'BUNDLE_LOADER': '$(TEST_HOST)', + 'INFOPLIST_FILE': 'test/info.plist', + 'PRODUCT_BUNDLE_IDENTIFIER': 'EQHXZ8M8AV.widevine_ce_cdm_unittest', + 'TEST_HOST': '<(PRODUCT_DIR)/dummy_app.app/dummy_app', + 'WRAPPER_EXTENSION': 'xctest', + }, + }, { + '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': [ + # This is used by the unit tests to use some v15 functions. + 'TEST_OEMCRYPTO_V15', + ], + }], + ['oemcrypto_lib=="level3"', { + 'sources': [ + # The test impl of OEMCrypto_Level3FileSystem and its factory. + 'test/level3_file_system_ce_test.h', + 'test/level3_file_system_ce_test.cpp', + 'test/level3_file_system_ce_test_factory.cpp', + ], + 'conditions': [ + ['oemcrypto_adapter_type=="dynamic" ', { + 'dependencies': [ + '../oemcrypto/level3/oec_level3.gyp:oec_level3_dynamic', + ], + }, { + 'dependencies': [ + '../oemcrypto/level3/oec_level3.gyp:oec_level3_static', + ], + }], + ], + }], + ['oemcrypto_lib=="target"', { + 'dependencies': [ + '<(oemcrypto_gyp_target)', + ], + }], + ], + }], + 'conditions': [ + ['supports_dynamic_perf_test', { + 'targets': [{ + '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', + '../core/test/url_request.cpp', + '../util/src/string_conversions.cpp', + '../util/test/test_sleep.cpp', + 'src/log.cpp', + 'test/perf_test.cpp', + 'test/perf_test_dynamic.cpp', + 'test/test_host.cpp', + ], + 'includes': [ '../util/libssl_dependency.gypi' ], + 'include_dirs': [ + '../core/include', + '../core/test', + '../util/include', + '../util/test', + 'include', + ], + 'dependencies': [ + '../third_party/googletest.gyp:gmock', + '../third_party/googletest.gyp:gtest', + ], + 'conditions': [ + ['OS=="linux"', { + 'libraries': [ '-ldl' ], + }], + ], + }], + }], # condition: supports_dynamic_perf_test + ['OS=="ios"', { + 'targets': [{ + 'toolsets' : [ 'target' ], + 'target_name': 'dummy_app', + 'type': 'executable', + 'mac_bundle': '1', + 'sources': [ + 'test/dummy_app.mm', + ], + 'libraries': [ + 'CoreFoundation.framework', + 'UIKit.framework', + ], + 'xcode_settings': { + 'INFOPLIST_FILE': 'test/info.plist', + }, + 'conditions': [ + ['prebuilt_cdm_path!=""', { + 'copies': [{ + 'destination': '<(PRODUCT_DIR)/dummy_app.app/Frameworks', + 'xcode_code_sign': 1, + 'files': [ + # GYP seems to always use relative paths here, even if they + # start with '/'. + ' +#include +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});""") diff --git a/cdm/include/cdm.h b/cdm/include/cdm.h index c6e9d57a..aa513d5b 100644 --- a/cdm/include/cdm.h +++ b/cdm/include/cdm.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // Based on the EME draft spec from 2016 June 10. // http://www.w3.org/TR/2016/WD-encrypted-media-20160610/" #ifndef WVCDM_CDM_CDM_H_ @@ -31,12 +31,13 @@ namespace widevine { class CDM_EXPORT ITimerClient { public: + virtual ~ITimerClient() {} + // Called by ITimer when a timer expires. virtual void onTimerExpired(void* context) = 0; protected: ITimerClient() {} - virtual ~ITimerClient() {} }; class CDM_EXPORT Cdm : public ITimerClient { @@ -194,6 +195,14 @@ class CDM_EXPORT Cdm : public ITimerClient { kL3 = 3, }; + // Status code returned by getProvisioningStatus API. + enum ProvisioningStatus : int32_t { + kProvisioned = 0, + kUnknownProvisionStatus = 1, + kNeedsDrmCertProvisioning = 2, + kNeedsOemCertProvisioning = 3, + }; + // A map of key statuses. // See Cdm::getKeyStatuses(). typedef std::map KeyStatusMap; @@ -203,6 +212,8 @@ class CDM_EXPORT Cdm : public ITimerClient { // See Cdm::createSession(). class IEventListener { public: + virtual ~IEventListener() {} + // A message (license request, renewal, etc.) to be dispatched to the // application's license server. // The response, if successful, should be provided back to the CDM via a @@ -220,7 +231,6 @@ class CDM_EXPORT Cdm : public ITimerClient { protected: IEventListener() {} - virtual ~IEventListener() {} }; // A storage interface provided by the application. This defines the "origin" @@ -240,6 +250,8 @@ class CDM_EXPORT Cdm : public ITimerClient { // See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo. class IStorage { public: + virtual ~IStorage() {} + virtual bool read(const std::string& name, std::string* data) = 0; virtual bool write(const std::string& name, const std::string& data) = 0; virtual bool exists(const std::string& name) = 0; @@ -256,7 +268,6 @@ class CDM_EXPORT Cdm : public ITimerClient { protected: IStorage() {} - virtual ~IStorage() {} }; // A clock interface provided by the application, independent of CDM @@ -264,12 +275,13 @@ class CDM_EXPORT Cdm : public ITimerClient { // See Cdm::initialize(). class IClock { public: + virtual ~IClock() {} + // Returns the current time in milliseconds since 1970 UTC. virtual int64_t now() = 0; protected: IClock() {} - virtual ~IClock() {} }; // A timer interface provided by the application, independent of CDM @@ -284,6 +296,8 @@ class CDM_EXPORT Cdm : public ITimerClient { // setTimeout() again inside the timeout callback. class ITimer { public: + virtual ~ITimer() {} + // This typedef is for backward compatibility with v3.0.0. typedef ITimerClient IClient; @@ -296,38 +310,40 @@ class CDM_EXPORT Cdm : public ITimerClient { protected: ITimer() {} - virtual ~ITimer() {} }; - // 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! + // The CE CDM has various pieces of client information baked into it at + // compile-time. These can be retrieved at runtime by Cdm::getClientInfo(), + // which returns them in this struct. + // These parameters match the client identification in license requests. 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. + // The name of the company who makes the client, e.g. "KubrickTech". + // This matches the "Make" field in the Widevine Integration Console. std::string company_name; - // The name of the device, e.g. "HAL" - std::string device_name; - - // The device model, e.g. "HAL 9000" - // Required. + // The client's model name, e.g. "HAL 9000". + // This matches the "Model" field in the Widevine Integration Console. std::string model_name; - // The architecture of the device, e.g. "x86-64" + // The client's model year, e.g. "2001". + // Can be used to distinguish different devices with the same model name. + // This matches the "Year" field in the Widevine Integration Console. + std::string model_year; + + // The name or codename of the product or application, e.g. "clarke". + // This may be the same as "model_name". + std::string product_name; + + // The name or codename of the client, e.g. "HAL". + // This may be the same as "model_name" or "product_name". + std::string device_name; + + // The CPU architecture of the client, e.g. "ARMv7". 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" + // A string containing build information about the CE CDM and the client + // it's integrated into. Consists of multiple values separated by spaces + // and vertical pipes. (e.g. " | ") std::string build_info; }; @@ -338,21 +354,23 @@ class CDM_EXPORT Cdm : public ITimerClient { // Logging is controlled by |verbosity|. // Must be called and must return kSuccess before create() is called. static Status initialize(SecureOutputType secure_output_type, - const ClientInfo& client_info, IStorage* storage, - IClock* clock, ITimer* timer, LogLevel verbosity); + IStorage* storage, IClock* clock, ITimer* timer, + LogLevel verbosity); // This is a variant of the above function that allows the caller to pass a // Sandbox ID. Platforms that use Sandbox IDs should use this initalize() // function instead of the previous one. Platforms that do not use Sandbox IDs // should not use this version of initialize(). static Status initialize(SecureOutputType secure_output_type, - const ClientInfo& client_info, IStorage* storage, - IClock* clock, ITimer* timer, LogLevel verbosity, - const std::string& sandbox_id); + IStorage* storage, IClock* clock, ITimer* timer, + LogLevel verbosity, const std::string& sandbox_id); // Query the CDM library version. static const char* version(); + // Retrieves the client information for this CE CDM integration. + static Status getClientInfo(ClientInfo* client_info); + // Constructs a new CDM instance. // initialize() must be called first and must return kSuccess before a CDM // instance may be constructed. @@ -387,7 +405,7 @@ class CDM_EXPORT Cdm : public ITimerClient { static Cdm* create(IEventListener* listener, IStorage* storage, bool privacy_mode, bool storage_is_read_only); - virtual ~Cdm() {} + ~Cdm() override {} // The following three methods relate to service certificates. A service // certificate holds the RSA public key for a server, as well as other fields @@ -449,6 +467,9 @@ class CDM_EXPORT Cdm : public ITimerClient { // and the license service should be used to make security decisions. virtual Status getRobustnessLevel(RobustnessLevel* level) = 0; + // Query the underlying OEMCrypto implementation's System ID. + virtual Status getSystemId(uint32_t* id) = 0; + // Returns the resource rating tier of the device, as reported by OEMCrypto. virtual Status getResourceRatingTier(uint32_t* tier) = 0; @@ -456,10 +477,10 @@ class CDM_EXPORT Cdm : public ITimerClient { // implementation. virtual Status getOemCryptoBuildInfo(std::string* build_info) = 0; - // Determine if the device has a Device Certificate (for the current origin). - // The Device Certificate is origin-specific, and the origin is - // dertermined by the CDM's current IStorage object. - virtual bool isProvisioned() = 0; + // Retrieves the current provisioning status. The Device Certificate is + // origin-specific, and the origin is determined by the CDM's current IStorage + // object. + virtual ProvisioningStatus getProvisioningStatus() = 0; // Creates a Provisioning Request message. // This is used to provision the device. The request should be sent to the diff --git a/cdm/include/cdm_version.h b/cdm/include/cdm_version.h index 2fb94a6d..5a8d8d58 100644 --- a/cdm/include/cdm_version.h +++ b/cdm/include/cdm_version.h @@ -1,5 +1,25 @@ +// Copyright 2015 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#define CDM_VERSION_STR_(x, y, z, w) #x "." #y "." #z w +#define CDM_VERSION_STR(x, y, z, w) CDM_VERSION_STR_(x, y, z, w) + // Widevine CE CDM Version -#ifndef CDM_VERSION -# define CDM_VERSION "16.4.0" +#ifndef CDM_VERSION_MAJOR +# define CDM_VERSION_MAJOR 17 #endif +#ifndef CDM_VERSION_MINOR +# define CDM_VERSION_MINOR 1 +#endif +#ifndef CDM_VERSION_PATCH +# define CDM_VERSION_PATCH 0 +#endif +#ifndef CDM_VERSION_TAG +# define CDM_VERSION_TAG "" +#endif +#define CDM_VERSION \ + CDM_VERSION_STR(CDM_VERSION_MAJOR, CDM_VERSION_MINOR, CDM_VERSION_PATCH, \ + CDM_VERSION_TAG) + #define EME_VERSION "https://www.w3.org/TR/2017/REC-encrypted-media-20170918" diff --git a/cdm/include/compiler_detection.h b/cdm/include/compiler_detection.h new file mode 100644 index 00000000..43bcebb2 --- /dev/null +++ b/cdm/include/compiler_detection.h @@ -0,0 +1,42 @@ +// 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_COMPILER_DETECTION_H_ +#define WVCDM_CDM_COMPILER_DETECTION_H_ + +// This file makes some best-effort attempts to detect details about the +// compilation environment used by CacheBuildInfo. + +#if defined(CDM_DISABLE_LOGGING) +# define LOGGING_MESSAGE "Logging Disabled" +#else +# define LOGGING_MESSAGE "Logging Enabled" +#endif + +#if defined(_DEBUG) +# define BUILD_FLAVOR_MESSAGE "Debug" +#elif defined(NDEBUG) +# define BUILD_FLAVOR_MESSAGE "Release" +#else +# define BUILD_FLAVOR_MESSAGE "Unknown" +#endif + +#if defined(__x86_64__) || defined(_M_X64) +# define CPU_ARCH_MESSAGE "x86-64" +#elif defined(__i386__) || defined(_M_IX86) +# define CPU_ARCH_MESSAGE "i386" +#elif defined(__aarch64__) || defined(_M_ARM64) +# define CPU_ARCH_MESSAGE "AArch64" +#elif defined(__arm__) || defined(_M_ARM) +# define CPU_ARCH_MESSAGE "AArch32" +#elif defined(__mips__) +# define CPU_ARCH_MESSAGE "MIPS" +#elif defined(__powerpc64__) +# define CPU_ARCH_MESSAGE "64-bit PowerPC" +#elif defined(__powerpc__) || defined(_M_PPC) +# define CPU_ARCH_MESSAGE "32-bit PowerPC" +#else +# define CPU_ARCH_MESSAGE "Unrecognized CPU" +#endif + +#endif // WVCDM_CDM_COMPILER_DETECTION_H_ diff --git a/cdm/include/properties_ce.h b/cdm/include/properties_ce.h index 8bcd9f03..83812f7a 100644 --- a/cdm/include/properties_ce.h +++ b/cdm/include/properties_ce.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// 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_ @@ -14,7 +14,6 @@ namespace widevine { class PropertiesCE { public: - static Cdm::ClientInfo GetClientInfo(); static Cdm::SecureOutputType GetSecureOutputType(); static void SetProvisioningMessagesAreBinary(bool bin_prov); // If this is set to an empty string, (which is the default) then PropertiesCE @@ -23,7 +22,6 @@ class PropertiesCE { private: static void SetSecureOutputType(Cdm::SecureOutputType secure_output_type); - static void SetClientInfo(const Cdm::ClientInfo& client_info); friend class Cdm; #if defined(UNIT_TEST) diff --git a/cdm/oemcrypto_reference_implementation.gyp b/cdm/oemcrypto_reference_implementation.gyp new file mode 100644 index 00000000..ed97d27f --- /dev/null +++ b/cdm/oemcrypto_reference_implementation.gyp @@ -0,0 +1,35 @@ +# Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +# source code may only be used and distributed under the Widevine License +# Agreement. +{ + 'includes': [ + 'platform_properties.gypi', + ], + 'variables': { + # Directory where OEMCrypto header, test, and reference files lives. + 'oemcrypto_dir': '../oemcrypto', + # Directory where widevine utilities live. + 'util_dir': '../util', + }, + 'targets': [ + { + 'target_name': 'oec_ref', + 'type': 'static_library', + 'standalone_static_library': 1, + 'hard_dependency': 1, + 'includes': [ + '../oemcrypto/ref/oec_ref.gypi', + ], + 'dependencies': [ + '<(oemcrypto_dir)/util/oec_ref_util.gyp:oec_ref_util', + ], + }, + { + 'target_name': 'oec_ref_shared', + 'type': 'shared_library', + 'dependencies': [ + 'oec_ref' + ], + }, + ], +} diff --git a/cdm/platform_properties.gypi b/cdm/platform_properties.gypi index 2c1adf60..daa61ff0 100644 --- a/cdm/platform_properties.gypi +++ b/cdm/platform_properties.gypi @@ -1,6 +1,6 @@ # Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -# source code may only be used and distributed under the Widevine Master -# License Agreement. +# source code may only be used and distributed under the Widevine License +# Agreement. # This file contains all the variables that Widevine exposes for integration # partners to customize in their platforms' settings.gypi files. Partners will @@ -16,6 +16,44 @@ # achieve. { 'variables': { + # 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) + + # 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%': '', + # The client's model name, e.g. "HAL 9000". + # This should match the "Model" field in the Widevine Integration Console. + '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%': '', + # The name or codename of the product or application, e.g. "clarke". + # This may be the same as "client_model_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%': '', + # The CPU architecture of the client, e.g. "ARMv7". + '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%': '', + # The form factor of the client, e.g. "TV". + # This should match the "Type" field in the Widevine Integration Console. + '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%': '', + + # Choose type of OEMCrypto library to compile in. Valid values are: # # 'vendor' - Production Level 1 systems should use 'vendor' to indicate that @@ -47,6 +85,12 @@ # other values - for internal testing. 'oemcrypto_adapter_type%': 'static', + # Whether to include OTA Keybox functionality or not. This is an optional + # feature that is only used a few platforms. Generally, to qualify as an L1 + # device, a keybox must be installed on the device in the factory. + # Valid values are 'true' or 'false'. + 'support_ota_keybox_functions%': 'false', + # Override this to indicate what CPU architecture's assembly-language files # should be used when building assembly language files. Or, set it to # "none" to turn off the use of assembly language. The default is "none" for @@ -58,7 +102,7 @@ # # Valid values are: # * x86 - # * x86-64 + # * x64 or x86-64 # * arm # * arm64 # * ppc64 @@ -96,8 +140,8 @@ # When using BoringSSL, the Widevine CE CDM defaults to the copy of # BoringSSL in third_party/boringssl/. If you have an alternative BoringSSL # GYP build, you can override these variables to point to it instead. - 'boringssl_libcrypto_path%': '../third_party/boringssl/boringssl.gyp:crypto', - 'boringssl_libssl_path%': '../third_party/boringssl/boringssl.gyp:ssl', + 'boringssl_libcrypto_path%': '<(DEPTH)/third_party/boringssl/boringssl.gyp:crypto', + 'boringssl_libssl_path%': '<(DEPTH)/third_party/boringssl/boringssl.gyp:ssl', # There are three protobuf configurations: # @@ -113,10 +157,29 @@ # Specify the path to protoc in protoc_bin. # # 3) protobuf_config == 'source' (default) - # Build protobuf and protoc from source. - # Specify the path to the protobuf source in protobuf_source. + # Build protobuf and protoc from the copy in third_party/protobuf. 'protobuf_config%': 'source', - 'protobuf_source%': '../third_party/protobuf', - 'protoc_host_target%': 'dummy', + 'protoc_bin%': '', + 'protoc_lib%': '', + 'protoc_lib_target%': '', + 'protoc_host_target%': '', }, # 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 02262e1a..5bc19c13 100644 --- a/cdm/src/cdm.cpp +++ b/cdm/src/cdm.cpp @@ -1,11 +1,11 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "cdm.h" #include #include // LLONG_MAX -#include // memcpy +#include #include #include @@ -37,13 +37,50 @@ namespace widevine { using namespace wvcdm; +using namespace wvutil; namespace { -constexpr const char* kNoSandboxId = ""; +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; @@ -197,11 +234,13 @@ class CdmImpl final : public Cdm, public WvCdmEventListener { Status getRobustnessLevel(RobustnessLevel* level) override; + Status getSystemId(uint32_t* id) override; + Status getResourceRatingTier(uint32_t* tier) override; Status getOemCryptoBuildInfo(std::string* build_info) override; - bool isProvisioned() override; + ProvisioningStatus getProvisioningStatus() override; Status getProvisioningRequest(std::string* request) override; @@ -446,6 +485,27 @@ Cdm::Status CdmImpl::getRobustnessLevel(RobustnessLevel* level) { return kSuccess; } +Cdm::Status CdmImpl::getSystemId(uint32_t* id) { + if (id == nullptr) { + LOGE("Missing id parameter to receive system ID."); + return kTypeError; + } + + std::string id_string; + const CdmResponseType result = + cdm_engine_->QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &id_string); + if (result == SYSTEM_INVALIDATED_ERROR) { + LOGE("System invalidated"); + return kSystemStateLost; + } else if (result != NO_ERROR) { + LOGE("Unexpected error %d", static_cast(result)); + return kUnexpectedError; + } + + *id = static_cast(std::stoul(id_string)); + return kSuccess; +} + Cdm::Status CdmImpl::getResourceRatingTier(uint32_t* tier) { if (tier == nullptr) { LOGE("Missing tier parameter to receive resource rating tier."); @@ -492,8 +552,22 @@ Cdm::Status CdmImpl::getOemCryptoBuildInfo(std::string* build_info) { return kSuccess; } -bool CdmImpl::isProvisioned() { - return cdm_engine_->IsProvisioned(kSecurityLevelL1); +Cdm::ProvisioningStatus CdmImpl::getProvisioningStatus() { + const CdmProvisioningStatus status = + cdm_engine_->GetProvisioningStatus(kSecurityLevelL1); + switch (status) { + case CdmProvisioningStatus::kProvisioned: + return kProvisioned; + case CdmProvisioningStatus::kNeedsDrmCertProvisioning: + return kNeedsDrmCertProvisioning; + case CdmProvisioningStatus::kNeedsOemCertProvisioning: + return kNeedsOemCertProvisioning; + case CdmProvisioningStatus::kUnknownProvisionStatus: + LOGW("Unknown provisioning status"); + return kUnknownProvisionStatus; + } + LOGE("Unexpected provisioning status %d", static_cast(status)); + return kUnknownProvisionStatus; } Cdm::Status CdmImpl::getProvisioningRequest(std::string* request) { @@ -1654,17 +1728,16 @@ Cdm::Status CdmImpl::ConvertHdcpLevel(const std::string& query_value, // static Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, - const ClientInfo& client_info, IStorage* storage, - IClock* clock, ITimer* timer, LogLevel verbosity) { - return initialize(secure_output_type, client_info, storage, clock, timer, - verbosity, kNoSandboxId); + IStorage* storage, IClock* clock, ITimer* timer, + LogLevel verbosity) { + return initialize(secure_output_type, storage, clock, timer, verbosity, + kNoSandboxId); } // static Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, - const ClientInfo& client_info, IStorage* storage, - IClock* clock, ITimer* timer, LogLevel verbosity, - const std::string& sandbox_id) { + IStorage* storage, IClock* clock, ITimer* timer, + LogLevel verbosity, const std::string& sandbox_id) { // Specify the maximum severity of message that will be output to // the console. See core/include/log.h for the valid priority values. g_cutoff = static_cast(verbosity); @@ -1679,11 +1752,7 @@ Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, return kTypeError; } - if (client_info.product_name.empty() || client_info.company_name.empty() || - client_info.model_name.empty()) { - LOGE("Client info requires product_name, company_name, model_name!"); - return kTypeError; - } + if (!clientInfoIsValid()) return kUnexpectedError; if (!storage || !clock || !timer) { LOGE("All interfaces are required!"); @@ -1691,7 +1760,6 @@ Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, } PropertiesCE::SetSecureOutputType(secure_output_type); - PropertiesCE::SetClientInfo(client_info); if (sandbox_id != kNoSandboxId) PropertiesCE::SetSandboxId(sandbox_id); Properties::Init(); host.storage = storage; @@ -1704,6 +1772,53 @@ Cdm::Status Cdm::initialize(SecureOutputType secure_output_type, // static const char* Cdm::version() { return CDM_VERSION; } +// static +Cdm::Status Cdm::getClientInfo(ClientInfo* client_info) { + if (client_info == nullptr) { + LOGE("Missing client_info parameter."); + return kTypeError; + } + + if (!clientInfoIsValid()) return kUnexpectedError; + + if (!Properties::GetCompanyName(&client_info->company_name)) { + LOGE("Unable to get Company Name."); + return kUnexpectedError; + } + + if (!Properties::GetModelName(&client_info->model_name)) { + LOGE("Unable to get Model Name."); + return kUnexpectedError; + } + + if (!Properties::GetModelYear(&client_info->model_year)) { + LOGE("Unable to get Model Year."); + return kUnexpectedError; + } + + if (!Properties::GetDeviceName(&client_info->device_name)) { + LOGE("Unable to get Device Name."); + return kUnexpectedError; + } + + if (!Properties::GetProductName(&client_info->product_name)) { + LOGE("Unable to get Product Name."); + return kUnexpectedError; + } + + if (!Properties::GetArchitectureName(&client_info->arch_name)) { + LOGE("Unable to get Architecture Name."); + return kUnexpectedError; + } + + if (!Properties::GetBuildInfo(&client_info->build_info)) { + LOGE("Unable to get Build Info."); + return kUnexpectedError; + } + + return kSuccess; +} + // static Cdm* Cdm::create(IEventListener* listener, IStorage* storage, bool privacy_mode) { @@ -1739,7 +1854,7 @@ Cdm* Cdm::create(IEventListener* listener, IStorage* storage, bool privacy_mode, } // namespace widevine // Missing symbols from core: -namespace wvcdm { +namespace wvutil { using namespace widevine; @@ -1840,4 +1955,4 @@ bool FileSystem::List(const std::string&, return file_names && impl_->storage_->list(file_names); } -} // namespace wvcdm +} // namespace wvutil diff --git a/cdm/src/log.cpp b/cdm/src/log.cpp index a1b3a3ae..11188f1d 100644 --- a/cdm/src/log.cpp +++ b/cdm/src/log.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // Log - implemented using stderr. @@ -11,9 +11,9 @@ #include #include -namespace wvcdm { +namespace wvutil { -LogPriority g_cutoff = LOG_WARN; +LogPriority g_cutoff = CDM_LOG_WARN; void InitLogging() {} @@ -39,4 +39,4 @@ void Log(const char* file, const char* function, int line, LogPriority level, fflush(stderr); } -} // namespace wvcdm +} // namespace wvutil diff --git a/cdm/src/properties_ce.cpp b/cdm/src/properties_ce.cpp index f6ab1481..8a22b721 100644 --- a/cdm/src/properties_ce.cpp +++ b/cdm/src/properties_ce.cpp @@ -1,9 +1,15 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "properties_ce.h" +#include + +#include +#include + #include "cdm_version.h" +#include "compiler_detection.h" #include "log.h" #include "properties.h" @@ -19,16 +25,20 @@ std::string sandbox_id_; widevine::Cdm::SecureOutputType secure_output_type_ = widevine::Cdm::kNoSecureOutput; -widevine::Cdm::ClientInfo client_info_; -bool GetValue(const std::string& source, std::string* output) { - if (!output) { +bool GetValue(const char* source, std::string* output) { + if (!source || !output) { return false; } *output = source; - return source.size() != 0; + 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 { @@ -63,14 +73,6 @@ Cdm::SecureOutputType PropertiesCE::GetSecureOutputType() { return secure_output_type_; } -// static -void PropertiesCE::SetClientInfo(const Cdm::ClientInfo& client_info) { - client_info_ = client_info; -} - -// static -Cdm::ClientInfo PropertiesCE::GetClientInfo() { return client_info_; } - // static void PropertiesCE::SetProvisioningMessagesAreBinary(bool new_setting) { set_provisioning_messages_to_binary_ = new_setting; @@ -100,32 +102,37 @@ void Properties::InitOnce() { // static bool Properties::GetCompanyName(std::string* company_name) { - return GetValue(client_info_.company_name, company_name); + return GetValue(CLIENT_COMPANY_NAME, company_name); } // static bool Properties::GetModelName(std::string* model_name) { - return GetValue(client_info_.model_name, model_name); + return GetValue(CLIENT_MODEL_NAME, model_name); +} + +// static +bool Properties::GetModelYear(std::string* model_year) { + return GetValue(CLIENT_MODEL_YEAR, model_year); } // static bool Properties::GetArchitectureName(std::string* arch_name) { - return GetValue(client_info_.arch_name, arch_name); + return GetValue(CLIENT_ARCH_NAME, arch_name); } // static bool Properties::GetDeviceName(std::string* device_name) { - return GetValue(client_info_.device_name, device_name); + return GetValue(CLIENT_DEVICE_NAME, device_name); } // static bool Properties::GetProductName(std::string* product_name) { - return GetValue(client_info_.product_name, product_name); + return GetValue(CLIENT_PRODUCT_NAME, product_name); } // static bool Properties::GetBuildInfo(std::string* build_info) { - return GetValue(client_info_.build_info, build_info); + return GetValue(kBuildInfo, build_info); } // static @@ -151,7 +158,13 @@ bool Properties::GetFactoryKeyboxPath(std::string*) { // static bool Properties::GetOEMCryptoPath(std::string* path) { if (path == nullptr) return false; - *path = "liboemcrypto.so"; + // 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; } diff --git a/cdm/test/cdm_reboot_test_main.cpp b/cdm/test/cdm_reboot_test_main.cpp new file mode 100644 index 00000000..de569e06 --- /dev/null +++ b/cdm/test/cdm_reboot_test_main.cpp @@ -0,0 +1,118 @@ +// 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 +#include +#include +#include +#include +#include +#include +#include + +#if defined(__linux__) +# include +#endif + +#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" +#include "test_host.h" + +using namespace widevine; + +// TODO(b/195338975): document how a partner should modify this so that they can +// use a real host. +TestHost* g_host = nullptr; + +namespace { +constexpr char kGlobalDumpFileName[] = "dumped_global_filesystem.dat"; +constexpr char kPerOriginDumpFileName[] = "dumped_per_origin_filesystem.dat"; + +using StorageMap = TestHost::Storage::StorageMap; + +// Load a TestHost file system from the real file system. +bool ReloadFileSystem(const char* file_name, StorageMap* map) { + std::ifstream input(file_name); + if (input.fail()) { + // This is OK for first pass, but an error for later passes. + LOGD("Could not read %s", file_name); + return false; + } + std::string dumped_file_system((std::istreambuf_iterator(input)), + std::istreambuf_iterator()); + if (!wvcdm::RebootTest::ParseDump(dumped_file_system, map)) { + LOGE("Could not parse %s", file_name); + return false; + } + return true; +} + +bool ReloadFileSystems() { + StorageMap global_map; + StorageMap per_origin_map; + if (!ReloadFileSystem(kGlobalDumpFileName, &global_map) || + !ReloadFileSystem(kPerOriginDumpFileName, &per_origin_map)) { + return false; + } + g_host->global_storage().ResetFiles(global_map); + g_host->per_origin_storage().ResetFiles(per_origin_map); + return true; +} + +// Dump a TestHost file system to the real file system. If the dump file +// cannot be written, this raises an exception and crashes the program. Since +// we usually build with exceptions turned off, what this means is that the test +// executable will halt if the file cannot be written. +void DumpFileSystem(const char* file_name, const StorageMap& map) { + std::string dump = wvcdm::RebootTest::DumpData(map); + std::ofstream output(file_name); + output << dump; + output.close(); +} + +void DumpFileSystems() { + DumpFileSystem(kGlobalDumpFileName, g_host->global_storage().files()); + DumpFileSystem(kPerOriginDumpFileName, g_host->per_origin_storage().files()); +} + +} // namespace + +int main(int argc, char** argv) { + // Set up a Host and initialize the library. This makes these services + // available to the tests. We would do this in the test suite itself, but the + // core & OEMCrypto tests don't know they depend on this for storage. + g_host = new TestHost(); + ReloadFileSystems(); + + // Partners will want to replace this with a real IStorage. + Cdm::IStorage* const storage = &g_host->global_storage(); + + // Partners may also want to replace this with real implementations of IClock + // and ITimer. If so, make not to set the command line argument + // "--fake_sleep". + Cdm::IClock* const clock = g_host; + Cdm::ITimer* const timer = g_host; + + // If the tests need a spearate file system from that used by the host, that + // should be set up here. For the reference code, we use a default test file + // system, but save the data from the file system. + // A separate file system might be needed, for example, if the test data needs + // to be saved off-device. Or, for example, if the main file system limits the + // names and types of files it can save to certificates and offline licenses. + wvutil::FileSystem* file_system = nullptr; + wvcdm::RebootTest::set_file_system(file_system); + + const int test_results = Main(storage, clock, timer, argc, argv); + DumpFileSystems(); + // This is used by the test driver to know what time to use for initializing + // the fake clock for the next pass. + std::cout << "END_OF_TEST " << wvutil::Clock().GetCurrentTime() << "\n"; + return test_results; +} diff --git a/cdm/test/cdm_test.cpp b/cdm/test/cdm_test.cpp index d3311ee8..12be3626 100644 --- a/cdm/test/cdm_test.cpp +++ b/cdm/test/cdm_test.cpp @@ -1,10 +1,15 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // This source file provides a basic set of unit tests for the Content // Decryption Module (CDM). +#include + +#include +#include +#include #include #include @@ -13,8 +18,10 @@ #include "OEMCryptoCENC.h" #include "cdm.h" #include "cdm_test_printers.h" +#include "cdm_version.h" #include "config_test_env.h" #include "decryption_test_data.h" +#include "file_store.h" #include "license_protocol.pb.h" #include "license_request.h" #include "log.h" @@ -31,6 +38,7 @@ using namespace testing; using namespace wvcdm; +using namespace wvutil; namespace widevine { @@ -47,8 +55,6 @@ const int kOfflineLicenseDurationMs = 604800 * 1000; constexpr size_t kMaxFetchAttempts = 5; -const std::string kDeviceCertFileName = "cert.bin"; - const Cdm::SessionType kBogusSessionType = static_cast(-1); const Cdm::InitDataType kBogusInitDataType = static_cast(-1); const std::string kBogusSessionId = "asdf"; @@ -204,12 +210,15 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { ~CdmTest() override {} // IEventListener mocks: - MOCK_METHOD3(onMessage, - void(const std::string& session_id, - Cdm::MessageType message_type, const std::string& message)); - MOCK_METHOD2(onKeyStatusesChange, - void(const std::string& session_id, bool has_new_usable_key)); - MOCK_METHOD1(onRemoveComplete, void(const std::string& session_id)); + MOCK_METHOD(void, onMessage, + (const std::string& session_id, Cdm::MessageType message_type, + const std::string& message), + (override)); + MOCK_METHOD(void, onKeyStatusesChange, + (const std::string& session_id, bool has_new_usable_key), + (override)); + MOCK_METHOD(void, onRemoveComplete, (const std::string& session_id), + (override)); protected: void SetUp() override { @@ -220,8 +229,8 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { // Reinit the library. Cdm::Status status = Cdm::initialize( - Cdm::kNoSecureOutput, PropertiesCE::GetClientInfo(), g_host, g_host, - g_host, static_cast(g_cutoff), g_sandbox_id); + Cdm::kNoSecureOutput, &g_host->global_storage(), g_host, g_host, + static_cast(g_cutoff), g_sandbox_id); ASSERT_EQ(Cdm::kSuccess, status); // Make a fresh CDM. @@ -239,13 +248,12 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { } void CreateAdditionalCdm(bool privacy_mode, std::unique_ptr* cdm) { - cdm->reset(Cdm::create(this, g_host, privacy_mode)); - ASSERT_NE((Cdm*)0, cdm->get()); + cdm->reset(Cdm::create(this, &g_host->per_origin_storage(), privacy_mode)); + ASSERT_NE(nullptr, cdm->get()); } bool Fetch(const std::string& url, const std::string& message, std::string* response, int* status_code) { - std::string http_response; for (size_t attempt = 1; attempt <= kMaxFetchAttempts; ++attempt) { UrlRequest url_request(url); @@ -280,10 +288,7 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { *response = http_response; } LOGV("Reply body(hex): \n%s\n", b2a_hex(*response).c_str()); - LOGV("Reply body(b64): \n%s\n", - Base64SafeEncode( - std::vector(response->begin(), response->end())) - .c_str()); + LOGV("Reply body(b64): \n%s\n", Base64SafeEncode(*response).c_str()); } return true; } @@ -309,10 +314,7 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { return false; } LOGV("Reply body(hex): \n%s\n", b2a_hex(*response).c_str()); - LOGV("Reply body(b64): \n%s\n", - Base64SafeEncode( - std::vector(response->begin(), response->end())) - .c_str()); + LOGV("Reply body(b64): \n%s\n", Base64SafeEncode(*response).c_str()); return true; } @@ -335,12 +337,12 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { } } - void FetchLicenseFailure(const std::string& message, - int expected_status_code) { + void FetchLicenseFailure(const std::string& message) { int status_code; - bool ok = Fetch(config_.license_server(), message, nullptr, &status_code); + const bool ok = + Fetch(config_.license_server(), message, nullptr, &status_code); ASSERT_TRUE(ok); - if (ok) ASSERT_EQ(expected_status_code, status_code); + ASSERT_THAT(status_code, AllOf(Ge(400), Le(599))); } void CreateSessionAndGenerateRequest(Cdm::SessionType session_type, @@ -367,7 +369,7 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { init_data_type_name = CENC_INIT_DATA_FORMAT; } } - if (g_cutoff >= LOG_DEBUG) { + if (g_cutoff >= CDM_LOG_DEBUG) { InitializationData parsed_init_data(init_data_type_name, init_data); parsed_init_data.DumpToLogs(); } @@ -463,7 +465,7 @@ class CdmTest : public WvCdmTestBase, public Cdm::IEventListener { Cdm::InitDataType init_data_type, const std::string& init_data) { const int num_retries = 5; - if (init_data_type == Cdm::kCenc && g_cutoff >= LOG_DEBUG) { + if (init_data_type == Cdm::kCenc && g_cutoff >= CDM_LOG_DEBUG) { InitializationData parsed_init_data(CENC_INIT_DATA_FORMAT, init_data); parsed_init_data.DumpToLogs(); } @@ -533,9 +535,95 @@ class MockTimerClient : public Cdm::ITimer::IClient { MockTimerClient() {} ~MockTimerClient() override {} - MOCK_METHOD1(onTimerExpired, void(void*)); + MOCK_METHOD(void, onTimerExpired, (void*), (override)); }; +const char* describe(Cdm::RobustnessLevel value) { + switch (value) { + case Cdm::kL1: + return "L1"; + case Cdm::kL2: + return "L2"; + case Cdm::kL3: + return "L3"; + } + return "Invalid Value"; +} + +const char* describe(OEMCrypto_WatermarkingSupport value) { + switch (value) { + case OEMCrypto_WatermarkingError: + return "Error"; + case OEMCrypto_WatermarkingNotSupported: + return "Not Supported"; + case OEMCrypto_WatermarkingConfigurable: + return "Configurable"; + case OEMCrypto_WatermarkingAlwaysOn: + return "Always-On"; + } + return "Invalid Value"; +} + +// Prints out various pieces of information about the client running the tests. +// This information is parsed by the integration console when partners upload +// their test results. +TEST_F(CdmTest, PrintClientInformation) { + using std::cout; + using std::endl; + + const time_t c_now = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + cout << "CE CDM Unit Tests for version " CDM_VERSION << endl; + cout << "Tests run at " << std::put_time(localtime(&c_now), "%F %T %Z") + << endl; + + // Collect CDM info + Cdm::ClientInfo client_info; + ASSERT_EQ(Cdm::getClientInfo(&client_info), Cdm::kSuccess); + + cout << endl << "CDM Information:" << endl; + cout << " CDM Version = " << Cdm::version() << endl; + cout << " Company Name = " << client_info.company_name << endl; + cout << " Model Name = " << client_info.model_name << endl; + cout << " Model Year = " << client_info.model_year << endl; + cout << " Device Name = " << client_info.device_name << endl; + cout << " Product Name = " << client_info.product_name << endl; + cout << " Arch Name = " << client_info.arch_name << endl; + cout << " Build Info = " << client_info.build_info << endl; + + // Collect OEMCrypto info + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()); + const uint32_t oec_major = OEMCrypto_APIVersion(); + const uint32_t oec_minor = OEMCrypto_MinorAPIVersion(); + const char* const supports_usage_tables = + OEMCrypto_SupportsUsageTable() ? "Supported" : "Not Supported"; + const char* const production_ready = + (OEMCrypto_ProductionReady() == OEMCrypto_SUCCESS) ? "Production Ready" + : "Not Ready"; + const OEMCrypto_WatermarkingSupport wm_support = + OEMCrypto_GetWatermarkingSupport(); + EXPECT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Terminate()); + + Cdm::RobustnessLevel robustness_level = static_cast(0); + EXPECT_EQ(cdm_->getRobustnessLevel(&robustness_level), Cdm::kSuccess); + uint32_t system_id = 0; + EXPECT_EQ(cdm_->getSystemId(&system_id), Cdm::kSuccess); + uint32_t resource_rating_tier = 0; + EXPECT_EQ(cdm_->getResourceRatingTier(&resource_rating_tier), Cdm::kSuccess); + std::string oec_build_info; + EXPECT_EQ(cdm_->getOemCryptoBuildInfo(&oec_build_info), Cdm::kSuccess); + + cout << endl << "OEMCrypto Information:" << endl; + cout << " OEMCrypto Version = " << oec_major << "." << oec_minor << endl; + cout << " Robustness Level = " << describe(robustness_level) << endl; + cout << " System ID = " << system_id << endl; + cout << " Usage Tables = " << supports_usage_tables << endl; + cout << " Resource Rating Tier = " << resource_rating_tier << endl; + cout << " Production Readiness = " << production_ready << endl; + cout << " Watermarking = " << describe(wm_support) << endl; + cout << " Build Info = " << oec_build_info << endl; +} + TEST_F(CdmTest, TestHostTimer) { // Validate that the TestHost timers are processed in the correct order and // on the correct timeouts. @@ -573,87 +661,39 @@ TEST_F(CdmTest, TestHostTimer) { TEST_F(CdmTest, Initialize) { // Try with an invalid output type. - Cdm::Status status = - Cdm::initialize(static_cast(-1), - PropertiesCE::GetClientInfo(), g_host, g_host, g_host, - static_cast(g_cutoff), g_sandbox_id); + Cdm::Status status = Cdm::initialize( + static_cast(-1), &g_host->global_storage(), g_host, + g_host, static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); - // Try with various client info properties missing. - Cdm::ClientInfo working_client_info = PropertiesCE::GetClientInfo(); - Cdm::ClientInfo broken_client_info; - - broken_client_info = working_client_info; - broken_client_info.product_name.clear(); - status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, - g_host, g_host, static_cast(g_cutoff), - g_sandbox_id); - EXPECT_EQ(Cdm::kTypeError, status); - - broken_client_info = working_client_info; - broken_client_info.company_name.clear(); - status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, - g_host, g_host, static_cast(g_cutoff), - g_sandbox_id); - EXPECT_EQ(Cdm::kTypeError, status); - - broken_client_info = working_client_info; - broken_client_info.device_name.clear(); // Not required - status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, - g_host, g_host, static_cast(g_cutoff), - g_sandbox_id); - EXPECT_EQ(Cdm::kSuccess, status); - - broken_client_info = working_client_info; - broken_client_info.model_name.clear(); - status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, - g_host, g_host, static_cast(g_cutoff), - g_sandbox_id); - EXPECT_EQ(Cdm::kTypeError, status); - - broken_client_info = working_client_info; - broken_client_info.arch_name.clear(); // Not required - status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, - g_host, g_host, static_cast(g_cutoff), - g_sandbox_id); - EXPECT_EQ(Cdm::kSuccess, status); - - broken_client_info = working_client_info; - broken_client_info.build_info.clear(); // Not required - status = Cdm::initialize(Cdm::kNoSecureOutput, broken_client_info, g_host, - g_host, g_host, static_cast(g_cutoff), - g_sandbox_id); - EXPECT_EQ(Cdm::kSuccess, status); - // Try with various host interfaces missing. - status = Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, nullptr, - g_host, g_host, static_cast(g_cutoff), - g_sandbox_id); + status = Cdm::initialize(Cdm::kNoSecureOutput, nullptr, g_host, g_host, + static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); - status = Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, + status = Cdm::initialize(Cdm::kNoSecureOutput, &g_host->global_storage(), nullptr, g_host, static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); - status = Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, + status = Cdm::initialize(Cdm::kNoSecureOutput, &g_host->global_storage(), g_host, nullptr, static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kTypeError, status); // Try all output types. - status = Cdm::initialize(Cdm::kDirectRender, working_client_info, g_host, + status = Cdm::initialize(Cdm::kDirectRender, &g_host->global_storage(), g_host, g_host, static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); - status = Cdm::initialize(Cdm::kOpaqueHandle, working_client_info, g_host, + status = Cdm::initialize(Cdm::kOpaqueHandle, &g_host->global_storage(), g_host, g_host, static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); // One last init with everything correct and working. - status = Cdm::initialize(Cdm::kNoSecureOutput, working_client_info, g_host, + status = Cdm::initialize(Cdm::kNoSecureOutput, &g_host->global_storage(), g_host, g_host, static_cast(g_cutoff), g_sandbox_id); EXPECT_EQ(Cdm::kSuccess, status); @@ -793,6 +833,9 @@ TEST_F(CdmTest, OpenSessionWithoutServiceCertificate) { generateRequestWithRetry(session_id, Cdm::kCenc, kCencInitData)); Mock::VerifyAndClear(this); + ASSERT_EQ(Cdm::kSuccess, cdm_->close(session_id)); + ASSERT_EQ(Cdm::kSuccess, cdm_->createSession(Cdm::kTemporary, &session_id)); + // Once a service certificate has been set, the existing session should be // able to generate a request. ASSERT_EQ(Cdm::kSuccess, @@ -803,6 +846,7 @@ TEST_F(CdmTest, OpenSessionWithoutServiceCertificate) { EXPECT_EQ(Cdm::kSuccess, generateRequestWithRetry(session_id, Cdm::kCenc, kCencInitData)); Mock::VerifyAndClear(this); + ASSERT_EQ(Cdm::kSuccess, cdm_->close(session_id)); } TEST_F(CdmTest, GetRobustnessLevel) { @@ -812,6 +856,13 @@ TEST_F(CdmTest, GetRobustnessLevel) { LOGI("Got robustness level %d", static_cast(level)); } +TEST_F(CdmTest, GetSystemId) { + uint32_t id; + const Cdm::Status status = cdm_->getSystemId(&id); + ASSERT_EQ(Cdm::kSuccess, status); + LOGI("Got system ID %u", id); +} + TEST_F(CdmTest, GetResourceRatingTier) { uint32_t tier; const Cdm::Status status = cdm_->getResourceRatingTier(&tier); @@ -829,11 +880,19 @@ TEST_F(CdmTest, GetOemCryptoBuildInfo) { TEST_F(CdmTest, CreateSession) { EnsureProvisioned(); - // Create a temporary session. std::string session_id; Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); EXPECT_EQ(Cdm::kSuccess, status); EXPECT_FALSE(session_id.empty()); +} + +TEST_F(CdmTest, CreateSession_ReuseSessionId) { + EnsureProvisioned(); + + // Create a temporary session. + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); + EXPECT_EQ(Cdm::kSuccess, status); // Create another using the same pointer to an already-filled-out string, // and expect the session ID to change. @@ -841,17 +900,29 @@ TEST_F(CdmTest, CreateSession) { status = cdm_->createSession(Cdm::kTemporary, &session_id); EXPECT_EQ(Cdm::kSuccess, status); EXPECT_NE(original_session_id, session_id); +} - // Create a persistent session. - status = cdm_->createSession(Cdm::kPersistentLicense, &session_id); +TEST_F(CdmTest, CreateSession_Persistent) { + EnsureProvisioned(); + + std::string session_id; + Cdm::Status status = + cdm_->createSession(Cdm::kPersistentLicense, &session_id); EXPECT_EQ(Cdm::kSuccess, status); +} - // Try a NULL pointer for session ID. - status = cdm_->createSession(Cdm::kTemporary, nullptr); +TEST_F(CdmTest, CreateSession_NullArgument) { + EnsureProvisioned(); + + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, nullptr); EXPECT_EQ(Cdm::kTypeError, status); +} - // Try a bogus session type. - status = cdm_->createSession(kBogusSessionType, &session_id); +TEST_F(CdmTest, CreateSession_BogusType) { + EnsureProvisioned(); + + std::string session_id; + Cdm::Status status = cdm_->createSession(kBogusSessionType, &session_id); EXPECT_EQ(Cdm::kNotSupported, status); } @@ -861,7 +932,17 @@ TEST_F(CdmTest, GenerateRequest) { Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); - // Generate a license request for CENC. + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); + status = generateRequestWithRetry(session_id, Cdm::kCenc, kCencInitData); + EXPECT_EQ(Cdm::kSuccess, status); +} + +TEST_F(CdmTest, GenerateRequest_ReuseSession) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); + ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); status = generateRequestWithRetry(session_id, Cdm::kCenc, kCencInitData); EXPECT_EQ(Cdm::kSuccess, status); @@ -871,35 +952,47 @@ TEST_F(CdmTest, GenerateRequest) { EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0); status = generateRequestWithRetry(session_id, Cdm::kCenc, kCencInitData); EXPECT_EQ(Cdm::kInvalidState, status); - Mock::VerifyAndClear(this); +} - // Create a new session and generate a license request for WebM. - status = cdm_->createSession(Cdm::kTemporary, &session_id); +TEST_F(CdmTest, GenerateRequest_WebM) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); status = generateRequestWithRetry(session_id, Cdm::kWebM, kWebMInitData); EXPECT_EQ(Cdm::kSuccess, status); - Mock::VerifyAndClear(this); +} - // Create a new session and try the as-yet-unsupported key-ids format. - status = cdm_->createSession(Cdm::kTemporary, &session_id); +TEST_F(CdmTest, GenerateRequest_KeyIds) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0); status = generateRequestWithRetry(session_id, Cdm::kKeyIds, kKeyIdsInitData); EXPECT_EQ(Cdm::kNotSupported, status); - Mock::VerifyAndClear(this); +} - // Create a new session and generate a license request for HLS. - status = cdm_->createSession(Cdm::kTemporary, &session_id); +TEST_F(CdmTest, GenerateRequest_HLS) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); status = generateRequestWithRetry(session_id, Cdm::kHls, kHlsInitData); EXPECT_EQ(Cdm::kSuccess, status); - Mock::VerifyAndClear(this); +} - // Create a new session and try a bogus init data type. - status = cdm_->createSession(Cdm::kTemporary, &session_id); +TEST_F(CdmTest, GenerateRequest_BogusType) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0); status = generateRequestWithRetry(session_id, kBogusInitDataType, "asdf"); EXPECT_EQ(Cdm::kTypeError, status); @@ -910,39 +1003,52 @@ TEST_F(CdmTest, GenerateRequest) { EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)); status = generateRequestWithRetry(session_id, Cdm::kCenc, kCencInitData); EXPECT_EQ(Cdm::kSuccess, status); - Mock::VerifyAndClear(this); +} - // Create a new session and try to pass empty init data. - status = cdm_->createSession(Cdm::kTemporary, &session_id); +TEST_F(CdmTest, GenerateRequest_Empty) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0); status = generateRequestWithRetry(session_id, Cdm::kCenc, ""); EXPECT_EQ(Cdm::kTypeError, status); - Mock::VerifyAndClear(this); +} - // Try to pass invalid CENC init data. - status = cdm_->createSession(Cdm::kTemporary, &session_id); +TEST_F(CdmTest, GenerateRequest_InvalidCenc) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0); status = generateRequestWithRetry(session_id, Cdm::kCenc, kInvalidCencInitData); EXPECT_EQ(Cdm::kNotSupported, status); - Mock::VerifyAndClear(this); +} - // Try to pass non-Widevine CENC init data. - status = cdm_->createSession(Cdm::kTemporary, &session_id); +TEST_F(CdmTest, GenerateRequest_NonWidevineCenc) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); + EXPECT_CALL(*this, onMessage(session_id, Cdm::kLicenseRequest, _)).Times(0); status = generateRequestWithRetry(session_id, Cdm::kCenc, kNonWidevineCencInitData); EXPECT_EQ(Cdm::kNotSupported, status); - Mock::VerifyAndClear(this); +} + +TEST_F(CdmTest, GenerateRequest_BogusSessionId) { + EnsureProvisioned(); + std::string session_id; + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); + ASSERT_EQ(Cdm::kSuccess, status); - // Try a bogus session ID. EXPECT_CALL(*this, onMessage(_, _, _)).Times(0); status = generateRequestWithRetry(kBogusSessionId, Cdm::kCenc, kCencInitData); EXPECT_EQ(Cdm::kSessionNotFound, status); - Mock::VerifyAndClear(this); } TEST_F(CdmTest, Update) { @@ -951,57 +1057,100 @@ TEST_F(CdmTest, Update) { std::string message; ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); - - // Acquire a license. std::string response; ASSERT_NO_FATAL_FAILURE( FetchLicense(config_.license_server(), message, &response)); - // Update the session. EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)); Cdm::Status status = updateWithRetry(session_id, response); EXPECT_EQ(Cdm::kSuccess, status); - Mock::VerifyAndClear(this); +} - // Try updating a bogus session ID. - status = updateWithRetry(kBogusSessionId, response); +TEST_F(CdmTest, Update_BogusSession) { + EnsureProvisioned(); + std::string session_id; + std::string message; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( + Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); + std::string response; + ASSERT_NO_FATAL_FAILURE( + FetchLicense(config_.license_server(), message, &response)); + + Cdm::Status status = updateWithRetry(kBogusSessionId, response); EXPECT_EQ(Cdm::kSessionNotFound, status); +} - // Try updating with an empty response. - status = updateWithRetry(session_id, ""); +TEST_F(CdmTest, Update_Empty) { + EnsureProvisioned(); + std::string session_id; + std::string message; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( + Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); + + Cdm::Status status = updateWithRetry(session_id, ""); EXPECT_EQ(Cdm::kTypeError, status); +} - // Try updating with a rejected device certificate. - { - EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)).Times(0); +TEST_F(CdmTest, Update_InvalidDrmCertError) { + EnsureProvisioned(); + std::string session_id; + std::string message; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( + Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); - LicenseError license_error; - std::string error_msg; - SignedMessage signed_message; - signed_message.set_type(SignedMessage::ERROR_RESPONSE); - std::string error_response; + EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)).Times(0); - // Invalid device certificate - license_error.set_error_code(LicenseError::INVALID_DRM_DEVICE_CERTIFICATE); - license_error.SerializeToString(&error_msg); - signed_message.set_msg(error_msg); - signed_message.SerializeToString(&error_response); - status = updateWithRetry(session_id, error_response); - EXPECT_EQ(Cdm::kNeedsDeviceCertificate, status); + LicenseError license_error; + std::string error_msg; + SignedMessage signed_message; + signed_message.set_type(SignedMessage::ERROR_RESPONSE); + std::string error_response; - // Revoked device certificate - license_error.set_error_code(LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE); - license_error.SerializeToString(&error_msg); - signed_message.set_msg(error_msg); - signed_message.SerializeToString(&error_response); - status = updateWithRetry(session_id, error_response); - EXPECT_EQ(Cdm::kUnexpectedError, status); + // Invalid device certificate + license_error.set_error_code(LicenseError::INVALID_DRM_DEVICE_CERTIFICATE); + license_error.SerializeToString(&error_msg); + signed_message.set_msg(error_msg); + signed_message.SerializeToString(&error_response); + Cdm::Status status = updateWithRetry(session_id, error_response); + EXPECT_EQ(Cdm::kNeedsDeviceCertificate, status); +} - Mock::VerifyAndClear(this); - } +TEST_F(CdmTest, Update_RevokedDrmCertError) { + EnsureProvisioned(); + std::string session_id; + std::string message; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( + Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); + + EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)).Times(0); + + LicenseError license_error; + std::string error_msg; + SignedMessage signed_message; + signed_message.set_type(SignedMessage::ERROR_RESPONSE); + std::string error_response; + + // Revoked device certificate + license_error.set_error_code(LicenseError::REVOKED_DRM_DEVICE_CERTIFICATE); + license_error.SerializeToString(&error_msg); + signed_message.set_msg(error_msg); + signed_message.SerializeToString(&error_response); + Cdm::Status status = updateWithRetry(session_id, error_response); + EXPECT_EQ(Cdm::kUnexpectedError, status); +} + +TEST_F(CdmTest, Update_UnexpectedUpdate) { + EnsureProvisioned(); + std::string session_id; + std::string message; + ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( + Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); + std::string response; + ASSERT_NO_FATAL_FAILURE( + FetchLicense(config_.license_server(), message, &response)); // Create a new session and try updating before generating a request. - status = cdm_->createSession(Cdm::kTemporary, &session_id); + Cdm::Status status = cdm_->createSession(Cdm::kTemporary, &session_id); ASSERT_EQ(Cdm::kSuccess, status); status = updateWithRetry(session_id, response); ASSERT_EQ(Cdm::kInvalidState, status); @@ -1159,23 +1308,13 @@ TEST_F(CdmTest, PerOriginLoadPersistent) { TestHost other_host; // Create a new CDM that uses the new host and new storage. - std::unique_ptr other_cdm( - Cdm::create(this, &other_host, /* privacy_mode */ true)); + std::unique_ptr other_cdm(Cdm::create( + this, &other_host.per_origin_storage(), /* privacy_mode */ true)); ASSERT_TRUE(other_cdm.get()); status = other_cdm->setServiceCertificate( Cdm::kLicensingService, config_.license_service_certificate()); ASSERT_EQ(Cdm::kSuccess, status); - // Should not be able to use the provisioning of another origin. - status = other_cdm->load(session_id); - EXPECT_EQ(Cdm::kNeedsDeviceCertificate, status); - - // Copy the old provisioning to the new storage so we don't get provisioning - // errors anymore. - std::string cert_bin; - g_host->read(kDeviceCertFileName, &cert_bin); - other_host.write(kDeviceCertFileName, cert_bin); - // Should not be able to see sessions from another origin. status = other_cdm->load(session_id); EXPECT_EQ(Cdm::kSessionNotFound, status); @@ -1486,8 +1625,8 @@ TEST_P(CdmTestWithRemoveParam, Remove) { EXPECT_EQ(Cdm::kRangeError, status); } -INSTANTIATE_TEST_CASE_P(CdmRemoveTest, CdmTestWithRemoveParam, Bool(), - testing::PrintToStringParamName()); +INSTANTIATE_TEST_SUITE_P(CdmRemoveTest, CdmTestWithRemoveParam, Bool(), + testing::PrintToStringParamName()); TEST_F(CdmTest, ForceRemove) { EnsureProvisioned(); @@ -1901,7 +2040,7 @@ TEST_F(CdmTest, RequestPersistentLicenseWithWrongInitData) { Mock::VerifyAndClear(this); // The license server will reject this. - FetchLicenseFailure(message, 500); + FetchLicenseFailure(message); } // TODO(b/34949512): Fix this test so it can be re-enabled. @@ -1939,7 +2078,7 @@ TEST_F(CdmTest, Renewal) { CreateSessionAndUpdate(Cdm::kTemporary, Cdm::kCenc, &session_id)); // We should have a timer. - EXPECT_NE(0, g_host->NumTimers()); + EXPECT_NE(0u, g_host->NumTimers()); // When we elapse time, we should get a renewal message. std::string message; @@ -1950,7 +2089,7 @@ TEST_F(CdmTest, Renewal) { ASSERT_FALSE(message.empty()); // Stop the test if no message came through. // When should still have a timer. - EXPECT_NE(0, g_host->NumTimers()); + EXPECT_NE(0u, g_host->NumTimers()); // We should be able to update the session. ASSERT_NO_FATAL_FAILURE(FetchLicenseAndUpdate(session_id, message)); @@ -2154,7 +2293,7 @@ TEST_F(CdmTest, HandlesKeyRotationWithOnlyOneLicenseRequest) { sample.input.subsamples_length = 1; std::vector output_buffer(sample.input.data_length); sample.output.data = output_buffer.data(); - sample.output.data_length = output_buffer.size(); + sample.output.data_length = static_cast(output_buffer.size()); // Set up batch to decrypt Cdm::DecryptionBatch batch; @@ -2164,7 +2303,7 @@ TEST_F(CdmTest, HandlesKeyRotationWithOnlyOneLicenseRequest) { // Attempt multiple decrypts with a key from the first license. batch.key_id = kKeyIdEntitlement1.data(); - batch.key_id_length = kKeyIdEntitlement1.size(); + batch.key_id_length = static_cast(kKeyIdEntitlement1.size()); sample.input.iv = kIvEntitlement1; sample.input.iv_length = kIvEntitlement1Size; ASSERT_EQ(Cdm::kSuccess, cdm_->decrypt(batch)); @@ -2182,7 +2321,7 @@ TEST_F(CdmTest, HandlesKeyRotationWithOnlyOneLicenseRequest) { // Attempt multiple decrypts with a key from the second license. batch.key_id = kKeyIdEntitlement2.data(); - batch.key_id_length = kKeyIdEntitlement2.size(); + batch.key_id_length = static_cast(kKeyIdEntitlement2.size()); sample.input.iv = kIvEntitlement2; sample.input.iv_length = kIvEntitlement2Size; memset(&(output_buffer[0]), 0, output_buffer.size()); @@ -2196,7 +2335,7 @@ TEST_F(CdmTest, HandlesKeyRotationWithOnlyOneLicenseRequest) { // Attempt multiple decrypts with a key from the first license again. batch.key_id = kKeyIdEntitlement1.data(); - batch.key_id_length = kKeyIdEntitlement1.size(); + batch.key_id_length = static_cast(kKeyIdEntitlement1.size()); sample.input.iv = kIvEntitlement1; sample.input.iv_length = kIvEntitlement1Size; memset(&(output_buffer[0]), 0, output_buffer.size()); @@ -2222,7 +2361,7 @@ TEST_F(CdmTest, ClearPlaybackWithoutAKeyIdOrIv) { sample.input.subsamples_length = 1; std::vector output_buffer(sample.input.data_length); sample.output.data = output_buffer.data(); - sample.output.data_length = output_buffer.size(); + sample.output.data_length = static_cast(output_buffer.size()); // Set up batch to decrypt Cdm::DecryptionBatch batch; @@ -2263,14 +2402,14 @@ TEST_F(CdmTest, EncryptedPlaybackWithoutALicense) { sample.input.subsamples_length = 1; std::vector output_buffer(sample.input.data_length); sample.output.data = output_buffer.data(); - sample.output.data_length = output_buffer.size(); + sample.output.data_length = static_cast(output_buffer.size()); // Set up batch to decrypt Cdm::DecryptionBatch batch; batch.samples = &sample; batch.samples_length = 1; batch.key_id = kKeyIdCtr.data(); - batch.key_id_length = kKeyIdCtr.size(); + batch.key_id_length = static_cast(kKeyIdCtr.size()); batch.encryption_scheme = Cdm::kAesCtr; // Create a session. @@ -2312,29 +2451,29 @@ TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) { // Set up subsample Cdm::Subsample subsample; if (param.scheme == Cdm::kClear) { - subsample.clear_bytes = param.input_size; + subsample.clear_bytes = static_cast(param.input_size); } else { - subsample.protected_bytes = param.input_size; + subsample.protected_bytes = static_cast(param.input_size); } // Set up sample Cdm::Sample sample; sample.input.iv = param.iv; - sample.input.iv_length = param.iv_size; + sample.input.iv_length = static_cast(param.iv_size); sample.input.data = param.input; - sample.input.data_length = param.input_size; + sample.input.data_length = static_cast(param.input_size); sample.input.subsamples = &subsample; sample.input.subsamples_length = 1; std::vector output_buffer(sample.input.data_length); sample.output.data = output_buffer.data(); - sample.output.data_length = output_buffer.size(); + sample.output.data_length = static_cast(output_buffer.size()); // Set up batch to decrypt Cdm::DecryptionBatch batch; batch.samples = &sample; batch.samples_length = 1; batch.key_id = param.key_id->data(); - batch.key_id_length = param.key_id->size(); + batch.key_id_length = static_cast(param.key_id->size()); batch.pattern = *param.pattern; batch.encryption_scheme = param.scheme; @@ -2361,7 +2500,7 @@ TEST_P(CdmTestWithDecryptParam, DecryptToClearBuffer) { EXPECT_EQ(expected_output, output_buffer); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( CdmDecryptTest, CdmTestWithDecryptParam, Values(DecryptParam("CENC 3.0 cenc Mode", Cdm::kCenc, kKeyIdCtr, kIvCenc, kIvCencSize, Cdm::kAesCtr, kPatternNone, kInput, @@ -2408,33 +2547,53 @@ class CdmIndividualizationTest : public CdmTest { LOGV("GetProvisioningResponse: response:\n%s\n", reply.c_str()); return reply; } + + void ProvisionDevice() { + std::string message; + Cdm::Status status = cdm_->getProvisioningRequest(&message); + EXPECT_EQ(Cdm::kSuccess, status); + std::string reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + status = cdm_->handleProvisioningResponse(reply); + EXPECT_EQ(Cdm::kSuccess, status); + + // Provisioning 4.0 requires a second provisioning request to get the DRM + // cert + if (wvoec::global_features.provisioning_method == + OEMCrypto_BootCertificateChain) { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); + status = cdm_->getProvisioningRequest(&message); + EXPECT_EQ(Cdm::kSuccess, status); + reply = GetProvisioningResponse(message); + ASSERT_FALSE(reply.empty()); + status = cdm_->handleProvisioningResponse(reply); + EXPECT_EQ(Cdm::kSuccess, status); + } + } }; TEST_F(CdmIndividualizationTest, BasicFlow) { if (!CheckProvisioningSupport()) return; // Clear any existing certificates. - g_host->remove(kDeviceCertFileName); + g_host->global_storage().remove(kOemCertificateFileName); + g_host->per_origin_storage().remove(kCertificateFileName); + g_host->per_origin_storage().remove(kLegacyCertificateFileName); - // Provision the device - std::string message; - Cdm::Status status = cdm_->getProvisioningRequest(&message); - EXPECT_EQ(Cdm::kSuccess, status); - std::string reply = GetProvisioningResponse(message); - ASSERT_FALSE(reply.empty()); - status = cdm_->handleProvisioningResponse(reply); - EXPECT_EQ(Cdm::kSuccess, status); + ProvisionDevice(); // We should now be able to create a session and generate a request. std::string session_id; + std::string message; ASSERT_NO_FATAL_FAILURE(CreateSessionAndGenerateRequest( Cdm::kTemporary, Cdm::kCenc, &session_id, &message)); // Acquire a license and update the session. + std::string reply; ASSERT_NO_FATAL_FAILURE( FetchLicense(config_.license_server(), message, &reply)); EXPECT_CALL(*this, onKeyStatusesChange(session_id, true)); - status = updateWithRetry(session_id, reply); + Cdm::Status status = updateWithRetry(session_id, reply); EXPECT_EQ(Cdm::kSuccess, status); Mock::VerifyAndClear(this); } @@ -2443,20 +2602,24 @@ TEST_F(CdmIndividualizationTest, IsProvisioned) { if (!CheckProvisioningSupport()) return; // Clear any existing certificates. - g_host->remove(kDeviceCertFileName); + g_host->global_storage().remove(kOemCertificateFileName); + g_host->per_origin_storage().remove(kCertificateFileName); + g_host->per_origin_storage().remove(kLegacyCertificateFileName); - EXPECT_FALSE(cdm_->isProvisioned()); + if (wvoec::global_features.provisioning_method == + OEMCrypto_BootCertificateChain) { + // For provisioning 4.0, an OEM cert is needed by the first stage of + // provisioning + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsOemCertProvisioning); + } else { + // For provisioning 3.0, an DRM cert is needed even though there is no OEM + // cert as well. + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); + } - // Provision the device - std::string message; - Cdm::Status status = cdm_->getProvisioningRequest(&message); - EXPECT_EQ(Cdm::kSuccess, status); - std::string reply = GetProvisioningResponse(message); - ASSERT_FALSE(reply.empty()); - status = cdm_->handleProvisioningResponse(reply); - EXPECT_EQ(Cdm::kSuccess, status); + ProvisionDevice(); - EXPECT_TRUE(cdm_->isProvisioned()); + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); } TEST_F(CdmIndividualizationTest, RemoveProvisioning) { @@ -2465,30 +2628,41 @@ TEST_F(CdmIndividualizationTest, RemoveProvisioning) { // Clear any existing certificates. EXPECT_EQ(Cdm::kSuccess, cdm_->removeProvisioning()); - EXPECT_FALSE(cdm_->isProvisioned()); + if (wvoec::global_features.provisioning_method == + OEMCrypto_BootCertificateChain) { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsOemCertProvisioning); + } else { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); + } - // Provision the device - std::string message; - Cdm::Status status = cdm_->getProvisioningRequest(&message); - EXPECT_EQ(Cdm::kSuccess, status); - std::string reply = GetProvisioningResponse(message); - ASSERT_FALSE(reply.empty()); - status = cdm_->handleProvisioningResponse(reply); - EXPECT_EQ(Cdm::kSuccess, status); + ProvisionDevice(); - EXPECT_TRUE(cdm_->isProvisioned()); + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); EXPECT_EQ(Cdm::kSuccess, cdm_->removeProvisioning()); - EXPECT_FALSE(cdm_->isProvisioned()); + if (wvoec::global_features.provisioning_method == + OEMCrypto_BootCertificateChain) { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsOemCertProvisioning); + } else { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); + } } TEST_F(CdmIndividualizationTest, NoCreateSessionWithoutProvisioning) { if (!CheckProvisioningSupport()) return; // Clear any existing certificates. - g_host->remove(kDeviceCertFileName); - ASSERT_FALSE(cdm_->isProvisioned()); + 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) { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsOemCertProvisioning); + } else { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); + } std::string session_id; EXPECT_EQ(Cdm::kNeedsDeviceCertificate, @@ -2500,12 +2674,55 @@ TEST_F(CdmIndividualizationTest, NoLoadWithoutProvisioning) { if (!CheckProvisioningSupport()) return; // Clear any existing certificates. - g_host->remove(kDeviceCertFileName); - ASSERT_FALSE(cdm_->isProvisioned()); + 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) { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsOemCertProvisioning); + } else { + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); + } EXPECT_EQ(Cdm::kNeedsDeviceCertificate, cdm_->load(kBogusSessionId)); } +class CdmProv40IndividualizationTest : public CdmTest {}; + +TEST_F(CdmProv40IndividualizationTest, NeedsOemCertProvisioning) { + // No OEM cert, no DRM cert. + g_host->global_storage().remove(kOemCertificateFileName); + g_host->per_origin_storage().remove(kCertificateFileName); + g_host->per_origin_storage().remove(kLegacyCertificateFileName); + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsOemCertProvisioning); +} + +TEST_F(CdmProv40IndividualizationTest, NeedsDrmCertProvisioning) { + // Has OEM cert, but no DRM cert. + g_host->global_storage().write(kOemCertificateFileName, "oem_cert"); + g_host->per_origin_storage().remove(kCertificateFileName); + g_host->per_origin_storage().remove(kLegacyCertificateFileName); + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kNeedsDrmCertProvisioning); +} + +TEST_F(CdmProv40IndividualizationTest, IsProvisionedAsLongAsDrmCertExists) { + // No OEM cert, has DRM cert. We treat this as provisioned since a license + // request can be made. + g_host->global_storage().remove(kOemCertificateFileName); + g_host->per_origin_storage().write(kCertificateFileName, "drm_cert"); + + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); +} + +TEST_F(CdmProv40IndividualizationTest, IsProvisioned) { + // Has OEM cert, has DRM cert. + g_host->global_storage().write(kOemCertificateFileName, "oem_cert"); + g_host->per_origin_storage().write(kCertificateFileName, "drm_cert"); + + EXPECT_EQ(cdm_->getProvisioningStatus(), Cdm::kProvisioned); +} + } // namespace } // namespace widevine diff --git a/cdm/test/cdm_test_main.cpp b/cdm/test/cdm_test_main.cpp index b981f8ab..155dba18 100644 --- a/cdm/test/cdm_test_main.cpp +++ b/cdm/test/cdm_test_main.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include #include @@ -10,11 +10,8 @@ #include #include -#if defined(__linux__) -# include -#endif - #include "cdm.h" +#include "cdm_test_runner.h" #include "device_cert.h" #include "log.h" #include "test_base.h" @@ -23,72 +20,22 @@ using namespace widevine; TestHost* g_host = nullptr; -std::string g_sandbox_id = ""; - -namespace { -constexpr const char kSandboxIdParam[] = "--sandbox_id="; - -// Following the pattern established by help text in test_base.cpp -constexpr const char kExtraHelpText[] = - " --sandbox_id=\n" - " Specifies the Sandbox ID that should be sent to OEMCrypto via\n" - " OEMCrypto_SetSandbox(). On most platforms, since Sandbox IDs are not\n" - " in use, this parameter should be omitted.\n"; -} // namespace int main(int argc, char** argv) { - // Find and filter out the Sandbox ID, if any. - std::vector args(argv, argv + argc); - auto sandbox_id_iter = std::find_if(std::begin(args) + 1, std::end(args), - [](const std::string& elem) -> bool { - return elem.find(kSandboxIdParam) == 0; - }); - if (sandbox_id_iter != std::end(args)) { - g_sandbox_id = sandbox_id_iter->substr(strlen(kSandboxIdParam)); - args.erase(sandbox_id_iter); - } - - // Set up a Host and initialize the library. This makes these services - // available to the tests. We would do this in the test suite itself, but the - // core & OEMCrypto tests don't know they depend on this for storage. g_host = new TestHost(); - Cdm::ClientInfo client_info; - // Set client info that denotes this as the test suite: - client_info.product_name = "CE cdm tests"; - client_info.company_name = "www"; - client_info.model_name = "www"; -#if defined(__linux__) - client_info.device_name = "Linux"; - { - struct utsname name; - if (uname(&name) == 0) { - client_info.arch_name = name.machine; - } - } -#else - client_info.device_name = "unknown"; -#endif - client_info.build_info = __DATE__; - Cdm::Status status = Cdm::initialize( - Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, - static_cast(wvcdm::g_cutoff), g_sandbox_id); - (void)status; // status is now used when assertions are turned off. - assert(status == Cdm::kSuccess); + // TODO(b/195338975): document that using a real IStorage means you should not + // run the tests in $CDM_DIR/cdm/test/cdm_test.cpp - std::vector new_argv(args.size()); - std::transform( - std::begin(args), std::end(args), std::begin(new_argv), - [](const std::string& arg) -> const char* { return arg.c_str(); }); - // This must take place after the call to Cdm::initialize() because it makes - // calls that are only valid after the library is initialized. - if (!wvcdm::WvCdmTestBase::Initialize(new_argv.size(), new_argv.data(), - kExtraHelpText)) { - return 0; - } + // Partners will want to replace this with a real IStorage. + Cdm::IStorage* const storage = &g_host->global_storage(); - // Init gtest after oemcrypto and cdm host have been initialized. - ::testing::InitGoogleTest(&argc, argv); + // Partners may also want to replace this with real implementations of IClock + // and ITimer. If so, make not to set the command line argument + // "--fake_sleep". + Cdm::IClock* const clock = g_host; + Cdm::ITimer* const timer = g_host; - return RUN_ALL_TESTS(); + const int test_results = Main(storage, clock, timer, argc, argv); + return test_results; } diff --git a/cdm/test/cdm_test_printers.cpp b/cdm/test/cdm_test_printers.cpp index 874a04b1..69980217 100644 --- a/cdm/test/cdm_test_printers.cpp +++ b/cdm/test/cdm_test_printers.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "cdm_test_printers.h" diff --git a/cdm/test/cdm_test_printers.h b/cdm/test/cdm_test_printers.h index a7c1b46b..6b3145cc 100644 --- a/cdm/test/cdm_test_printers.h +++ b/cdm/test/cdm_test_printers.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // This file adds some print methods so that when unit tests fail, the // will print the name of an enumeration instead of the numeric value. diff --git a/cdm/test/cdm_test_runner.cpp b/cdm/test/cdm_test_runner.cpp new file mode 100644 index 00000000..a1b0c3bc --- /dev/null +++ b/cdm/test/cdm_test_runner.cpp @@ -0,0 +1,68 @@ +// 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 +#include +#include +#include +#include +#include + +#include "cdm.h" +#include "device_cert.h" +#include "log.h" +#include "test_base.h" +#include "test_host.h" + +std::string g_sandbox_id = ""; + +namespace widevine { +namespace { +constexpr char kSandboxIdParam[] = "--sandbox_id="; + +// Following the pattern established by help text in test_base.cpp +constexpr char kExtraHelpText[] = + " --sandbox_id=\n" + " Specifies the Sandbox ID that should be sent to OEMCrypto via\n" + " OEMCrypto_SetSandbox(). On most platforms, since Sandbox IDs are not\n" + " in use, this parameter should be omitted.\n"; +} // namespace + +int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer, + int argc, char** argv) { + // Find and filter out the Sandbox ID, if any. + std::vector args(argv, argv + argc); + auto sandbox_id_iter = std::find_if(std::begin(args) + 1, std::end(args), + [](const std::string& elem) -> bool { + return elem.find(kSandboxIdParam) == 0; + }); + if (sandbox_id_iter != std::end(args)) { + g_sandbox_id = sandbox_id_iter->substr(strlen(kSandboxIdParam)); + args.erase(sandbox_id_iter); + } + + Cdm::Status status = Cdm::initialize( + Cdm::kOpaqueHandle, storage, clock, timer, + static_cast(wvutil::g_cutoff), g_sandbox_id); + (void)status; // status is now used when assertions are turned off. + assert(status == Cdm::kSuccess); + + std::vector new_argv(args.size()); + std::transform( + std::begin(args), std::end(args), std::begin(new_argv), + [](const std::string& arg) -> const char* { return arg.c_str(); }); + // This must take place after the call to Cdm::initialize() because it makes + // calls that are only valid after the library is initialized. + if (!wvcdm::WvCdmTestBase::Initialize(static_cast(new_argv.size()), + new_argv.data(), kExtraHelpText)) { + return 1; + } + + // Init gtest after oemcrypto and cdm host have been initialized. + ::testing::InitGoogleTest(&argc, argv); + + return RUN_ALL_TESTS(); +} +} // namespace widevine diff --git a/cdm/test/cdm_test_runner.h b/cdm/test/cdm_test_runner.h new file mode 100644 index 00000000..544d8b3b --- /dev/null +++ b/cdm/test/cdm_test_runner.h @@ -0,0 +1,20 @@ +// 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 WVCDM_CDM_TEST_CDM_TEST_RUNNER_H_ +#define WVCDM_CDM_TEST_CDM_TEST_RUNNER_H_ + +#include +#include "cdm.h" + +namespace widevine { +// Run all CDM tests using the specified storage, clock, and timer. It parses +// standard command line arguments, sets some default test client info, +// initializes a CDM object, and then runs all the tests. +// Returns 0 on success. +int Main(Cdm::IStorage* storage, Cdm::IClock* clock, Cdm::ITimer* timer, + int argc, char** argv); +} // namespace widevine + +#endif // WVCDM_CDM_TEST_CDM_TEST_RUNNER_H_ diff --git a/cdm/test/create_test_file_system.cpp b/cdm/test/create_test_file_system.cpp new file mode 100644 index 00000000..0b069cba --- /dev/null +++ b/cdm/test/create_test_file_system.cpp @@ -0,0 +1,11 @@ +// 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 "create_test_file_system.h" + +#include "test_host.h" + +wvutil::FileSystem* CreateTestFileSystem() { + return new wvutil::FileSystem("", &g_host->per_origin_storage()); +} diff --git a/cdm/test/decryption_test_data.h b/cdm/test/decryption_test_data.h index a8a82211..4b40af73 100644 --- a/cdm/test/decryption_test_data.h +++ b/cdm/test/decryption_test_data.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // This source file contains the test data used to verify decryption behavior // in cdm_test.cpp. diff --git a/cdm/test/device_cert.cpp b/cdm/test/device_cert.cpp index e2466064..f0bfce28 100644 --- a/cdm/test/device_cert.cpp +++ b/cdm/test/device_cert.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "device_cert.h" diff --git a/cdm/test/device_cert.h b/cdm/test/device_cert.h index 69ad0d28..f0b8c4ea 100644 --- a/cdm/test/device_cert.h +++ b/cdm/test/device_cert.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// 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_ diff --git a/cdm/test/dummy_app.mm b/cdm/test/dummy_app.mm new file mode 100644 index 00000000..878f12fa --- /dev/null +++ b/cdm/test/dummy_app.mm @@ -0,0 +1,14 @@ +// Copyright 2019 Google LLC. All Rights Reserved. + +#import + +@interface AppDelegate : UIResponder +@end + +@implementation AppDelegate { +} +@end + +int main(int argc, char** argv) { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); +} diff --git a/cdm/test/gtest_xctest_wrapper.mm b/cdm/test/gtest_xctest_wrapper.mm new file mode 100644 index 00000000..12b8caa6 --- /dev/null +++ b/cdm/test/gtest_xctest_wrapper.mm @@ -0,0 +1,23 @@ +// Copyright 2019 Google LLC. All Rights Reserved. + +#import + +#include + +// Defined in cdm_test_main.cpp. +int main(int argc, char** argv); + +@interface GtestTests : XCTestCase +@end + +@implementation GtestTests + +- (void)testAll { + testing::GTEST_FLAG(filter) = GTEST_FILTER; + + char arg0[] = "tests"; + char* argv[] = {arg0, nullptr}; + XCTAssertEqual(main(1, argv), 0); +} + +@end diff --git a/third_party/googletest/googletest/xcode/Samples/FrameworkSample/Info.plist b/cdm/test/info.plist similarity index 67% rename from third_party/googletest/googletest/xcode/Samples/FrameworkSample/Info.plist rename to cdm/test/info.plist index f3852ede..ecdb7b1d 100644 --- a/third_party/googletest/googletest/xcode/Samples/FrameworkSample/Info.plist +++ b/cdm/test/info.plist @@ -3,26 +3,23 @@ CFBundleDevelopmentRegion - English + en CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFile - + $(EXECUTABLE_NAME) CFBundleIdentifier - com.google.gtest.${PRODUCT_NAME:identifier} + EQHXZ8M8AV.$(PRODUCT_NAME:rfc1034identifier) CFBundleInfoDictionaryVersion 6.0 CFBundleName - ${PRODUCT_NAME} + $(PRODUCT_NAME) CFBundlePackageType - FMWK + APPL CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 - CSResourcesFileMapped - + diff --git a/cdm/test/level3_file_system_ce_test.cpp b/cdm/test/level3_file_system_ce_test.cpp index 58224f95..e9dda752 100644 --- a/cdm/test/level3_file_system_ce_test.cpp +++ b/cdm/test/level3_file_system_ce_test.cpp @@ -1,3 +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. + +/** + The test file system for the Level 3 OEMCrypto sits on top of the host file + system. A real production version may either use the host file system or it + may write files directly to the file system. + */ + #include #include #include @@ -5,14 +15,17 @@ #include "level3_file_system_ce_test.h" -namespace wvoec3 { +#include "test_host.h" -std::map OEMCrypto_Level3CETestFileSystem::files_; +namespace wvoec3 { ssize_t OEMCrypto_Level3CETestFileSystem::Read(const char* filename, void* buffer, size_t size) { - if (!Exists(filename)) return 0; - std::string data = files_[std::string(filename)]; + if (!g_host) return 0; + const std::string name(filename); + if (!g_host->global_storage().exists(name)) return 0; + std::string data; + if (!g_host->global_storage().read(name, &data)) return 0; size_t bytes_read = std::min(size, data.size()); memcpy(buffer, data.data(), bytes_read); return bytes_read; @@ -21,24 +34,31 @@ ssize_t OEMCrypto_Level3CETestFileSystem::Read(const char* filename, ssize_t OEMCrypto_Level3CETestFileSystem::Write(const char* filename, const void* buffer, size_t size) { + if (!g_host) return 0; std::string data(static_cast(buffer), size); - files_[std::string(filename)] = data; + const std::string name(filename); + if (!g_host->global_storage().write(name, data)) return 0; return size; } bool OEMCrypto_Level3CETestFileSystem::Exists(const char* filename) { - return files_.find(std::string(filename)) != files_.end(); + if (!g_host) return false; + const std::string name(filename); + return g_host->global_storage().exists(name); } ssize_t OEMCrypto_Level3CETestFileSystem::FileSize(const char* filename) { - if (!Exists(filename)) return -1; - return files_[std::string(filename)].size(); + if (!g_host) return 0; + const std::string name(filename); + if (!g_host->global_storage().exists(name)) return 0; + return static_cast(g_host->global_storage().size(name)); } bool OEMCrypto_Level3CETestFileSystem::Remove(const char* filename) { - if (!Exists(filename)) return false; - files_.erase(std::string(filename)); - return true; + if (!g_host) return false; + const std::string name(filename); + if (!g_host->global_storage().exists(name)) return false; + return g_host->global_storage().remove(name); } } // namespace wvoec3 diff --git a/cdm/test/level3_file_system_ce_test.h b/cdm/test/level3_file_system_ce_test.h index e7e51476..4165da0e 100644 --- a/cdm/test/level3_file_system_ce_test.h +++ b/cdm/test/level3_file_system_ce_test.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. /********************************************************************* * level3_file_system_ce_test.h @@ -26,9 +26,6 @@ class OEMCrypto_Level3CETestFileSystem : public OEMCrypto_Level3FileSystem { bool Exists(const char* filename) override; ssize_t FileSize(const char* filename) override; bool Remove(const char* filename) override; - - private: - static std::map files_; }; } // namespace wvoec3 diff --git a/cdm/test/perf_test.cpp b/cdm/test/perf_test.cpp new file mode 100644 index 00000000..fa204430 --- /dev/null +++ b/cdm/test/perf_test.cpp @@ -0,0 +1,340 @@ +// 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 "perf_test.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "config_test_env.h" +#include "license_request.h" +#include "test_host.h" +#include "url_request.h" + +#define ASSERT_SUCCESS(code) ASSERT_EQ(code, Cdm::kSuccess) +#define EXPECT_SUCCESS(code) EXPECT_EQ(code, Cdm::kSuccess) + +#define WALL_NOW std::chrono::high_resolution_clock::now() + +TestHost* g_host = nullptr; + +namespace widevine { + +namespace { + +constexpr const size_t kTestCount = 50; + +const wvcdm::ConfigTestEnv kTestData(wvcdm::kContentProtectionUatServer); +CreateFuncType create_func = nullptr; + +using TimeType = std::chrono::duration; + +struct PerfInfo { + double mean; + double min; + double max; + double std_dev; + + template + PerfInfo(const double (&values)[Size]) { + static_assert(Size > 0, "Must pass at least one value"); + + // First pass to calculate min/max/mean. + bool first = true; + double sum = 0; + for (auto v : values) { + sum += v; + if (first) { + min = max = v; + first = false; + } else { + if (v < min) min = v; + if (v > max) max = v; + } + } + mean = sum / Size; + + // Second pass to calculate standard deviation. + sum = 0; + for (auto v : values) { + sum += (v - mean) * (v - mean); + } + std_dev = std::sqrt(sum / Size); + } +}; + +std::ostream& operator<<(std::ostream& os, const PerfInfo& info) { + // mean=12.33442, std-dev=1.44421, min=1.22431, max=244.1133144 + return os << "mean=" << info.mean << ", std-dev=" << info.std_dev + << ", min=" << info.min << ", max=" << info.max; +} + +class PerfTracker { + public: + class Test { + public: + Test(PerfTracker* tracker) + : wall_start_(WALL_NOW), cpu_start_(std::clock()), tracker_(tracker) {} + + ~Test() { + tracker_->wall_times_[tracker_->index_] = + TimeType(WALL_NOW - wall_start_).count(); + tracker_->cpu_times_[tracker_->index_] = + (std::clock() - cpu_start_) * 1000.0 / CLOCKS_PER_SEC; + tracker_->index_++; + } + + private: + std::chrono::high_resolution_clock::time_point wall_start_; + std::clock_t cpu_start_; + PerfTracker* tracker_; + }; + + void Print(const std::string& name, size_t block_size_bytes = 0) { + PerfInfo wall_perf(wall_times_); + PerfInfo cpu_perf(cpu_times_); + std::cout << name << " (wall, ms): " << wall_perf << "\n"; + std::cout << name << " (cpu, ms): " << cpu_perf << "\n"; + if (block_size_bytes) { + // |mean| is in milliseconds. + std::cout << name << " (wall, MBit/sec): " + << (block_size_bytes * 8 * 1000 / wall_perf.mean / 1024 / 1024) + << "\n"; + std::cout << name << " (cpu, MBit/sec): " + << (block_size_bytes * 8 * 1000 / cpu_perf.mean / 1024 / 1024) + << "\n"; + } + } + + private: + double wall_times_[kTestCount]; + double cpu_times_[kTestCount]; + size_t index_ = 0; +}; + +#define MEASURE_PERF(tracker, code) \ + { \ + PerfTracker::Test test(&(tracker)); \ + code; \ + } + +class EventListener : public Cdm::IEventListener { + public: + struct MessageInfo { + std::string session_id; + std::string message; + Cdm::MessageType message_type; + }; + + void onMessage(const std::string& session_id, Cdm::MessageType message_type, + const std::string& message) override { + messages.push_back({session_id, message, message_type}); + } + void onKeyStatusesChange(const std::string& session_id, + bool has_new_usable_key) override {} + void onRemoveComplete(const std::string& session_id) override {} + + std::vector messages; +}; + +bool SendPost(const std::string& message, std::string* response) { + wvcdm::UrlRequest req(kTestData.license_server()); + std::string raw_response; + if (!req.is_connected() || !req.PostRequest(message) || + !req.GetResponse(&raw_response)) { + return false; + } + + wvcdm::LicenseRequest helper; + helper.GetDrmMessage(raw_response, *response); + return true; +} + +std::unique_ptr CreateCdm(EventListener* event_listener) { + std::unique_ptr ret( + create_func(event_listener, &g_host->per_origin_storage(), true)); + if (ret) { + EXPECT_SUCCESS(ret->setServiceCertificate( + Cdm::kProvisioningService, + kTestData.provisioning_service_certificate())); + EXPECT_SUCCESS(ret->setServiceCertificate( + Cdm::kLicensingService, kTestData.license_service_certificate())); + } + return ret; +} + +class GlobalEnv : public testing::Environment { + public: + GlobalEnv(InitFuncType init_func, const std::string& cert) + : init_func_(init_func), cert_(cert) {} + + void SetUp() override { + g_host = new TestHost; + if (!cert_.empty()) g_host->per_origin_storage().write("cert.bin", cert_); + + Cdm::LogLevel log_level = Cdm::kErrors; + if (const char* verbose = getenv("VERBOSE_OUTPUT")) { + if (std::strcmp(verbose, "1") == 0) log_level = Cdm::kVerbose; + } + ASSERT_SUCCESS(init_func_(Cdm::kNoSecureOutput, &g_host->global_storage(), + g_host, g_host, log_level)); + } + + private: + const InitFuncType init_func_; + const std::string cert_; +}; + +} // namespace + +class PerfTest : public testing::Test {}; + +TEST_F(PerfTest, LicenseExchange) { + EventListener event_listener; + auto cdm = CreateCdm(&event_listener); + ASSERT_TRUE(cdm); + ASSERT_EQ(cdm->getProvisioningStatus(), Cdm::kProvisioned); + + PerfTracker create; + PerfTracker generate; + PerfTracker update; + PerfTracker close; + for (size_t i = 0; i < kTestCount; i++) { + std::string session_id; + MEASURE_PERF(create, ASSERT_SUCCESS( + cdm->createSession(Cdm::kTemporary, &session_id))); + + MEASURE_PERF( + generate, + ASSERT_SUCCESS(cdm->generateRequest( + session_id, Cdm::kCenc, + wvcdm::ConfigTestEnv::GetInitData(wvcdm::kContentIdStreaming)))); + + std::string response; + ASSERT_TRUE(SendPost(event_listener.messages[0].message, &response)); + + MEASURE_PERF(update, ASSERT_SUCCESS(cdm->update(session_id, response))); + + MEASURE_PERF(close, ASSERT_SUCCESS(cdm->close(session_id))); + + event_listener.messages.pop_back(); + } + + create.Print("Create "); + generate.Print("Generate"); + update.Print("Update "); + close.Print("Close "); +} + +class DecryptPerfTest : public PerfTest, + public testing::WithParamInterface {}; + +TEST_P(DecryptPerfTest, Decrypt) { + EventListener event_listener; + auto cdm = CreateCdm(&event_listener); + ASSERT_TRUE(cdm); + ASSERT_EQ(cdm->getProvisioningStatus(), Cdm::kProvisioned); + + std::string session_id; + ASSERT_SUCCESS(cdm->createSession(Cdm::kTemporary, &session_id)); + ASSERT_SUCCESS(cdm->generateRequest( + session_id, Cdm::kCenc, + wvcdm::ConfigTestEnv::GetInitData(wvcdm::kContentIdStreaming))); + + std::string response; + ASSERT_TRUE(SendPost(event_listener.messages[0].message, &response)); + ASSERT_SUCCESS(cdm->update(session_id, response)); + + Cdm::KeyStatusMap statuses; + ASSERT_SUCCESS(cdm->getKeyStatuses(session_id, &statuses)); + ASSERT_GT(statuses.size(), 0u); + const std::string key_id = statuses.begin()->first; + + // Use in-place decrypt to avoid allocations. We don't care about the data, + // so we can just decrypt the same buffer again. + constexpr const size_t k16M = 16 * 1024 * 1024; + std::vector buffer(k16M); + uint8_t iv[16]; + for (auto& b : buffer) b = rand(); + + Cdm::DecryptionBatch batch; + batch.key_id = reinterpret_cast(key_id.data()); + batch.key_id_length = static_cast(key_id.size()); + if (GetParam()) { + batch.pattern.encrypted_blocks = batch.pattern.clear_blocks = 0; + } else { + batch.pattern.encrypted_blocks = 1; + batch.pattern.clear_blocks = 9; + } + batch.is_secure = false; + batch.encryption_scheme = GetParam() ? Cdm::kAesCtr : Cdm::kAesCbc; + batch.is_video = true; + + Cdm::Subsample subsample; + subsample.clear_bytes = 0; + // subsample.encrypted_bytes set in the test. + Cdm::Sample sample; + sample.input.iv = iv; + sample.input.iv_length = 16; + sample.input.data = buffer.data(); + // sample.data_length set in the test. + sample.input.subsamples = &subsample; + sample.input.subsamples_length = 1; + sample.output.data = buffer.data(); + sample.output.data_offset = 0; + sample.output.data_length = static_cast(buffer.size()); + batch.samples = &sample; + batch.samples_length = 1; + + constexpr const size_t block_sizes[] = {8 * 1024, 256 * 1024, k16M}; + constexpr const size_t sizes_count = + sizeof(block_sizes) / sizeof(block_sizes[0]); + const std::string block_names[] = {" 8k", "256k", " 16M"}; + PerfTracker perf[sizes_count]; + for (size_t i = 0; i < sizes_count; i++) { + subsample.protected_bytes = sample.input.data_length = + sample.output.data_length = static_cast(block_sizes[i]); + for (size_t j = 0; j < kTestCount; j++) { + MEASURE_PERF(perf[i], ASSERT_SUCCESS(cdm->decrypt(batch))); + } + } + + for (size_t i = 0; i < sizes_count; i++) { + perf[i].Print("Decrypt " + block_names[i], block_sizes[i]); + } +} + +std::string PrintDecryptParam(const testing::TestParamInfo& info) { + return info.param ? "CTR" : "CBC"; +} +INSTANTIATE_TEST_SUITE_P(Decrypt, DecryptPerfTest, testing::Bool(), + PrintDecryptParam); + +int PerfTestMain(InitFuncType init_func, CreateFuncType create, + const std::string& cert) { +#ifdef _DEBUG + // Don't use #error since we build all targets and we don't want to fail the + // debug build (and we can't have configuration-specific targets). + fprintf(stderr, "Don't run performance tests in Debug mode\n"); + return 1; +#else + create_func = create; + testing::AddGlobalTestEnvironment(new GlobalEnv(init_func, cert)); + + return RUN_ALL_TESTS(); +#endif +} + +} // namespace widevine diff --git a/cdm/test/perf_test.h b/cdm/test/perf_test.h new file mode 100644 index 00000000..3e8a4797 --- /dev/null +++ b/cdm/test/perf_test.h @@ -0,0 +1,23 @@ +// 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 WVCDM_CDM_TEST_PERF_TEST_H_ +#define WVCDM_CDM_TEST_PERF_TEST_H_ + +#include + +#include "cdm.h" + +namespace widevine { + +using InitFuncType = Cdm::Status (*)(Cdm::SecureOutputType, Cdm::IStorage*, + Cdm::IClock*, Cdm::ITimer*, Cdm::LogLevel); +using CreateFuncType = Cdm* (*)(Cdm::IEventListener*, Cdm::IStorage*, bool); + +int PerfTestMain(InitFuncType init_func, CreateFuncType create_func, + const std::string& cert); + +} // namespace widevine + +#endif // WVCDM_CDM_TEST_PERF_TEST_H_ diff --git a/cdm/test/perf_test_dynamic.cpp b/cdm/test/perf_test_dynamic.cpp new file mode 100644 index 00000000..b3e484b9 --- /dev/null +++ b/cdm/test/perf_test_dynamic.cpp @@ -0,0 +1,84 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +// This uses dlopen() which is only usable on POSIX platforms. The function +// names assume GCC/Clang. + +#include +#include +#include + +#include +#include + +#include "clock.h" +#include "perf_test.h" +#include "test_host.h" + +namespace widevine { + +constexpr char kInitName[] = + "_ZN8widevine3Cdm10initializeENS0_16SecureOutputTypeEPNS0_8IStorageEPNS0_" + "6IClockEPNS0_6ITimerENS0_8LogLevelE"; +constexpr char kCreateName[] = + "_ZN8widevine3Cdm6createEPNS0_14IEventListenerEPNS0_8IStorageEb"; + +bool ReadFile(const std::string& path, std::string* output) { + constexpr size_t kReadSize = 8 * 1024; + std::ifstream fs(path, std::ios::in | std::ios::binary); + if (!fs) return false; + while (true) { + const size_t offset = output->size(); + output->resize(output->size() + kReadSize); + fs.read(&output->at(offset), kReadSize); + if (fs.eof()) { + output->resize(offset + fs.gcount()); + return true; + } else if (!fs) { + fprintf(stderr, "Error reading from cert file\n"); + return false; + } + } +} + +std::tuple LoadCdm(const char* path) { + // Note we will leak the library object; but this is just for tests and will + // be unloaded when we exit anyway. + auto dll = dlopen(path, RTLD_NOW); + if (!dll) { + fprintf(stderr, "Error loading so file: %s\n", dlerror()); + exit(1); + } + + auto init = reinterpret_cast(dlsym(dll, kInitName)); + auto create = reinterpret_cast(dlsym(dll, kCreateName)); + if (!init || !create) { + fprintf(stderr, "Error finding CDM functions: %s\n", dlerror()); + exit(1); + } + return std::make_tuple(init, create); +} + +} // namespace widevine + +namespace wvutil { + +int64_t Clock::GetCurrentTime() { return g_host->now() / 1000; } + +} // namespace wvutil + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + if (argc != 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + std::string cert; + if (!widevine::ReadFile(argv[2], &cert)) + return 1; + + auto funcs = widevine::LoadCdm(argv[1]); + return widevine::PerfTestMain(std::get<0>(funcs), std::get<1>(funcs), cert); +} diff --git a/cdm/test/perf_test_xctest.mm b/cdm/test/perf_test_xctest.mm new file mode 100644 index 00000000..616d9626 --- /dev/null +++ b/cdm/test/perf_test_xctest.mm @@ -0,0 +1,29 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include +#import + +#include "cdm.h" +#include "device_cert.h" +#include "perf_test.h" + +@interface GtestTests : XCTestCase +@end + +@implementation GtestTests + +- (void)testAll { + testing::GTEST_FLAG(filter) = GTEST_FILTER; + char arg0[] = "tests"; + char* argv[] = {arg0, nullptr}; + 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); +} + +@end diff --git a/cdm/test/test_host.cpp b/cdm/test/test_host.cpp index cc31fd6d..054b89ab 100644 --- a/cdm/test/test_host.cpp +++ b/cdm/test/test_host.cpp @@ -1,12 +1,15 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "test_host.h" #include #include +#include +#include "cdm_version.h" #include "device_cert.h" +#include "file_store.h" #include "log.h" using namespace widevine; @@ -15,28 +18,40 @@ 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", + "okp.bin", + "keybox.dat", + wvutil::kOemCertificateFileName, +}; + } // namespace -TestHost::TestHost() { Reset(); } -TestHost::~TestHost() { wvcdm::TestSleep::set_callback(nullptr); } +TestHost::TestHost() : global_storage_(true), per_origin_storage_(false) { + Reset(); +} +TestHost::~TestHost() { wvutil::TestSleep::set_callback(nullptr); } void TestHost::Reset() { - auto now = std::chrono::steady_clock().now(); + auto now = std::chrono::system_clock().now(); now_ = now.time_since_epoch() / std::chrono::milliseconds(1); - wvcdm::TestSleep::set_callback(this); - - save_device_cert_ = false; + wvutil::TestSleep::set_callback(this); // Surprisingly, std::priority_queue has no clear(). while (!timers_.empty()) { timers_.pop(); } - files_.clear(); - files_[kCertificateFilename] = - (device_cert_.size() > 0) - ? device_cert_ - : std::string((const char*)kDeviceCert, kDeviceCertSize); + global_storage_.Reset(); + per_origin_storage_.Reset(); } void TestHost::ElapseTime(int64_t milliseconds) { @@ -64,58 +79,7 @@ void TestHost::ElapseTime(int64_t milliseconds) { now_ = goal_time; } -int TestHost::NumTimers() const { return timers_.size(); } - -bool TestHost::read(const std::string& name, std::string* data) { - StorageMap::iterator it = files_.find(name); - bool ok = it != files_.end(); - LOGV("read file: %s: %s", name.c_str(), ok ? "ok" : "fail"); - if (!ok) return false; - *data = it->second; - return true; -} - -bool TestHost::write(const std::string& name, const std::string& data) { - LOGV("write file: %s", name.c_str()); - files_[name] = data; - if (save_device_cert_ && name.compare(kCertificateFilename) == 0) { - device_cert_ = data; - save_device_cert_ = false; - } - return true; -} - -bool TestHost::exists(const std::string& name) { - StorageMap::iterator it = files_.find(name); - bool ok = it != files_.end(); - LOGV("exists? %s: %s", name.c_str(), ok ? "true" : "false"); - return ok; -} - -bool TestHost::remove(const std::string& name) { - LOGV("remove: %s", name.c_str()); - if (name.empty()) { - // If no name, delete all files (see DeviceFiles::DeleteAllFiles()) - files_.clear(); - return true; - } - - return files_.erase(name) > 0; -} - -int32_t TestHost::size(const std::string& name) { - StorageMap::iterator it = files_.find(name); - if (it == files_.end()) return -1; - return it->second.size(); -} - -bool TestHost::list(std::vector* names) { - names->clear(); - for (StorageMap::iterator it = files_.begin(); it != files_.end(); it++) { - names->push_back(it->first); - } - return true; -} +size_t TestHost::NumTimers() const { return timers_.size(); } int64_t TestHost::now() { return now_; } @@ -140,3 +104,77 @@ void TestHost::cancel(IClient* client) { // Now swap the queues. std::swap(timers_, others); } + +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) { + StorageMap::iterator it = files_.find(name); + bool ok = it != files_.end(); + LOGV("read file: %s: %s", name.c_str(), ok ? "ok" : "fail"); + if (!CheckFilename(name) || !ok) return false; + *data = it->second; + return true; +} + +bool TestHost::Storage::write(const std::string& name, + const std::string& data) { + LOGV("write file: %s", name.c_str()); + if (!CheckFilename(name)) return false; + files_[name] = data; + return true; +} + +bool TestHost::Storage::exists(const std::string& name) { + StorageMap::iterator it = files_.find(name); + bool ok = it != files_.end(); + LOGV("exists? %s: %s", name.c_str(), ok ? "true" : "false"); + if (!CheckFilename(name)) return false; + return ok; +} + +bool TestHost::Storage::remove(const std::string& name) { + if (name.empty()) { + // If no name, delete all files (see DeviceFiles::DeleteAllFiles()) + LOGV("remove all files"); + files_.clear(); + return true; + } + LOGV("remove: %s", name.c_str()); + if (!CheckFilename(name)) return false; + return files_.erase(name) > 0; +} + +int32_t TestHost::Storage::size(const std::string& name) { + StorageMap::iterator it = files_.find(name); + bool ok = (it != files_.end()); + LOGV("size? %s: %s", name.c_str(), ok ? "ok" : "fail"); + if (!CheckFilename(name) || !ok) return -1; + return static_cast(it->second.size()); +} + +bool TestHost::Storage::list(std::vector* names) { + names->clear(); + for (StorageMap::iterator it = files_.begin(); it != files_.end(); it++) { + names->push_back(it->first); + } + return true; +} + +bool TestHost::Storage::CheckFilename(const std::string& name) { + const bool is_global_filename = + (kGlobalFilenames.find(name) != kGlobalFilenames.end()); + if (is_global_ != is_global_filename) { + LOGE("Attempt to access %s in %s storage rejected.", name.c_str(), + is_global_ ? "global" : "per-origin"); + return false; + } + return true; +} diff --git a/cdm/test/test_host.h b/cdm/test/test_host.h index ff7316ff..b34f1a0a 100644 --- a/cdm/test/test_host.h +++ b/cdm/test/test_host.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CDM_TEST_TEST_HOST_H_ #define WVCDM_CDM_TEST_TEST_HOST_H_ @@ -15,30 +15,47 @@ // This provides a host environment for running CDM tests. It implements the // IStorage, IClock and ITimer interfaces that a host would normally implement, // while allowing them to be manipulated by the test. -class TestHost : public widevine::Cdm::IStorage, - public widevine::Cdm::IClock, +class TestHost : public widevine::Cdm::IClock, public widevine::Cdm::ITimer, - public wvcdm::TestSleep::CallBack { + public wvutil::TestSleep::CallBack { public: + class Storage : public widevine::Cdm::IStorage { + public: + typedef std::map StorageMap; + + explicit Storage(bool is_global); + ~Storage() override {} + void Reset(); + + // Reset the file system to contain the specified files. + void ResetFiles(const StorageMap& files) { files_ = files; }; + const StorageMap& files() const { return files_; } + + // widevine::Cdm::IStorage + bool read(const std::string& name, std::string* data) override; + bool write(const std::string& name, const std::string& data) override; + bool exists(const std::string& name) override; + bool remove(const std::string& name) override; + int32_t size(const std::string& name) override; + bool list(std::vector* names) override; + + private: + bool is_global_; + StorageMap files_; + + bool CheckFilename(const std::string& name); + }; + TestHost(); - ~TestHost(); + ~TestHost() override; void Reset(); // Used for manipulating and inspecting timer states during testing. void ElapseTime(int64_t milliseconds) override; - int NumTimers() const; + size_t NumTimers() const; - // This should be called before trying to write the cert.bin file. This is - // used when testing device provisioning. - void SaveProvisioningInformation() { save_device_cert_ = true; } - - // widevine::Cdm::IStorage - bool read(const std::string& name, std::string* data) override; - bool write(const std::string& name, const std::string& data) override; - bool exists(const std::string& name) override; - bool remove(const std::string& name) override; - int32_t size(const std::string& name) override; - bool list(std::vector* names) override; + Storage& global_storage() { return global_storage_; } + Storage& per_origin_storage() { return per_origin_storage_; } // widevine::Cdm::IClock int64_t now() override; @@ -71,11 +88,8 @@ class TestHost : public widevine::Cdm::IStorage, int64_t now_; std::priority_queue timers_; - std::string device_cert_; - bool save_device_cert_; - - typedef std::map StorageMap; - StorageMap files_; + Storage global_storage_; + Storage per_origin_storage_; }; // Owned and managed by the test runner. diff --git a/cdm/util.gypi b/cdm/util.gypi index 3536da24..60350f0b 100644 --- a/cdm/util.gypi +++ b/cdm/util.gypi @@ -1,5 +1,5 @@ # Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary -# source code may only be used and distributed under the Widevine Master License +# source code may only be used and distributed under the Widevine License # Agreement. { 'variables': { diff --git a/cdm/util_unittests.gypi b/cdm/util_unittests.gypi index d480cc9d..45c4b8ac 100644 --- a/cdm/util_unittests.gypi +++ b/cdm/util_unittests.gypi @@ -1,11 +1,11 @@ # Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary -# source code may only be used and distributed under the Widevine Master License +# source code may only be used and distributed under the Widevine License # Agreement. { 'sources': [ '../util/test/base64_test.cpp', '../util/test/cdm_random_unittest.cpp', - # TODO(b/119200528): Needs test vectors + # TODO(b/167126046): Needs test vectors # '../util/test/file_store_unittest.cpp', ], 'include_dirs': [ diff --git a/core/include/buffer_reader.h b/core/include/buffer_reader.h index e9bcfb60..4730610a 100644 --- a/core/include/buffer_reader.h +++ b/core/include/buffer_reader.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_BUFFER_READER_H_ #define WVCDM_CORE_BUFFER_READER_H_ diff --git a/core/include/cdm_client_property_set.h b/core/include/cdm_client_property_set.h index e6c7ba4c..4c3071db 100644 --- a/core/include/cdm_client_property_set.h +++ b/core/include/cdm_client_property_set.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_ #define WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_ diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h index 95ff40f6..2bc0a7a8 100644 --- a/core/include/cdm_engine.h +++ b/core/include/cdm_engine.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CDM_ENGINE_H_ #define WVCDM_CORE_CDM_ENGINE_H_ @@ -24,11 +24,11 @@ #include "wv_cdm_types.h" namespace wvcdm { - class CdmClientPropertySet; class CdmEngineFactory; class CdmSession; class CryptoEngine; +class OtaKeyboxProvisioner; class UsagePropertySet; class WvCdmEventListener; @@ -75,6 +75,10 @@ class CdmEngine { // app_parameters: Additional, application-specific parameters that factor // into the request generation. This is ignored for release // and renewal requests. + // Certain app parameter keys are reserved for CDM + // device identification on the license server. These + // parameters will be overwritten by the CDM request + // generator. // key_request: This must be non-null and point to a CdmKeyRequest. The // message field will be filled with the key request, the // type field will be filled with the key request type, @@ -92,14 +96,14 @@ class CdmEngine { // (c) accept a release response and release an offline license or secure // stop. // (d) accept a service certificate and cache that information for the - // the lifetime of the session. + // lifetime of the session. // // |session_id| identifies the session that generated the request and can // process the response. Should be empty if a release response. // |key_data| is the license, renewal, release response or service // certificate response. // |license_type| must not be null. If the result is KEY_ADDED, this out - // parameter indicates the type of license containd in + // parameter indicates the type of license contained in // key_data. For any other return code, no value is provided. // |key_set_id| should be non-null and specified if license release. // If offline license or streaming license associated with @@ -142,7 +146,7 @@ class CdmEngine { const CdmSessionId& session_id, const std::string& service_certificate); // Query system information - virtual CdmResponseType QueryStatus(SecurityLevel security_level, + virtual CdmResponseType QueryStatus(RequestedSecurityLevel security_level, const std::string& query_token, std::string* query_response); @@ -180,19 +184,25 @@ class CdmEngine { virtual CdmResponseType GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, const std::string& service_certificate, - SecurityLevel requested_security_level, CdmProvisioningRequest* request, - std::string* default_url); + RequestedSecurityLevel requested_security_level, + CdmProvisioningRequest* request, std::string* default_url); // Verify and process a provisioning response. virtual CdmResponseType HandleProvisioningResponse( const CdmProvisioningResponse& response, - SecurityLevel requested_security_level, std::string* cert, + RequestedSecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key); // Return true if there is a device certificate on the current // (origin-specific) file system. virtual bool IsProvisioned(CdmSecurityLevel security_level); + // Retrieves the current provisioning status based on whether a DRM + // certificate or an OEM certificate (in provisioning 4) exists the current + // (origin-specific) file system. + virtual CdmProvisioningStatus GetProvisioningStatus( + CdmSecurityLevel security_level); + // Remove device DRM certificate from the current (origin-specific) file // system. This will force the device to reprovision itself. virtual CdmResponseType Unprovision(CdmSecurityLevel security_level); @@ -233,15 +243,24 @@ class CdmEngine { int* error_detail, CdmUsageInfo* usage_info); - // Retrieve the usage info for the specified pst. - // Returns UNKNOWN_ERROR if no usage info was found. - // id. If |error_detail| is not null, an additional error code may be provided + // Retrieve usage info whose PST is specified by |ssid| + // If |error_detail| is not null, an additional error code may be provided // in the event of an error. virtual CdmResponseType GetUsageInfo(const std::string& app_id, const CdmSecureStopId& ssid, int* error_detail, CdmUsageInfo* usage_info); + // Retrieve usage info for a given security level and whose + // PST is specified by |ssid|. + // If |error_detail| is not null, an additional error code may be provided + // in the event of an error. + virtual CdmResponseType GetUsageInfo(const std::string& app_id, + const CdmSecureStopId& ssid, + RequestedSecurityLevel security_level, + int* error_detail, + CdmUsageInfo* usage_info); + // Remove all usage records for the current origin. virtual CdmResponseType RemoveAllUsageInfo(const std::string& app_id, CdmSecurityLevel security_level); @@ -299,6 +318,18 @@ class CdmEngine { virtual size_t SessionSize() const { return session_map_.Size(); } + // This tells the OEMCrypto adapter to ignore the next |count| keyboxes and + // report that it needs provisioning instead. + static CdmResponseType SetDebugIgnoreKeyboxCount(uint32_t count) { + return CryptoSession::SetDebugIgnoreKeyboxCount(count); + } + + // This tells the OEMCrypto adapter to allow the device to continue with a + // test keybox. Otherwise, the keybox is reported as invalid. + static CdmResponseType SetAllowTestKeybox(bool allow) { + return CryptoSession::SetAllowTestKeybox(allow); + } + static CdmResponseType ParseDecryptHashString(const std::string& hash_string, CdmSessionId* id, uint32_t* frame_number, @@ -344,11 +375,26 @@ class CdmEngine { } virtual const std::string& GetAppPackageName() { return app_package_name_; } virtual void SetSpoid(const std::string& spoid) { spoid_ = spoid; } + virtual CdmResponseType SetPlaybackId(const CdmSessionId& session_id, + const std::string& playback_id); + + virtual void SetUserId(uint32_t user_id) { user_id_ = user_id; } + virtual uint32_t GetUserId() const { return user_id_; } + + // Changes the rules used for calculating the fallback duration + // when OTA keybox provisioning fails. + // Default rules use fallback duration measured in days, with exponential + // backoff. + // Fast rules use fallback durations of a few seconds, without exponential + // backoff. + // This method has no effect if OTA keybox is not required. + virtual void SetDefaultOtaKeyboxFallbackDurationRules(); + virtual void SetFastOtaKeyboxFallbackDurationRules(); protected: friend class CdmEngineFactory; - CdmEngine(FileSystem* file_system, + CdmEngine(wvutil::FileSystem* file_system, std::shared_ptr metrics); private: @@ -361,7 +407,7 @@ class CdmEngine { bool ValidateKeySystem(const CdmKeySystem& key_system); CdmResponseType GetUsageInfo(const std::string& app_id, - SecurityLevel requested_security_level, + RequestedSecurityLevel requested_security_level, int* error_detail, CdmUsageInfo* usage_info); void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); @@ -370,6 +416,13 @@ class CdmEngine { void CloseExpiredReleaseSessions(); + // Returns "true" if |okp_provisioner_| should be checked. + bool OkpCheck(); + // Returns "true" if CdmEngine should always fallback to L3. + bool OkpIsInFallbackMode(); + void OkpTriggerFallback(); + void OkpCleanUp(); + // instance variables /* @@ -385,9 +438,10 @@ class CdmEngine { CdmSessionMap session_map_; CdmReleaseKeySetMap release_key_sets_; std::unique_ptr cert_provisioning_; - FileSystem* file_system_; - Clock clock_; + wvutil::FileSystem* file_system_; + wvutil::Clock clock_; std::string spoid_; + uint32_t user_id_; // Usage related variables // Used to isolate a single active usage information license. Loading, @@ -413,6 +467,23 @@ class CdmEngine { // occur that may subsequently call back into CdmEngine. std::recursive_mutex session_map_lock_; + // OTA Keybox Provisioning (OKP) + // Engine should check for the OKP status of the device before opening + // sessions or generating DRM cert provisioning requests. + bool okp_initialized_ = false; + // If OKP is required, then the engine should create an instance + // of |okp_provisioner_|. If the instance exists, it should be used + // for GetProvisionRequest, ProvideProvisionRequest, and + // OpenSession when requested with default security level. + std::unique_ptr okp_provisioner_; + // Should the engine need to fallback, this flag should be set to + // true and |okp_provisioner_| should be cleared. All follow-up + // requests from the app with security level default should use L3. + bool okp_fallback_ = false; + // To prevent race conditions around the engine's OKP state, this mutex + // should be locked before the use of any of the |okp_*| variables. + std::mutex okp_mutex_; + CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); }; diff --git a/core/include/cdm_engine_factory.h b/core/include/cdm_engine_factory.h index 5cd0e88e..7b37c2a4 100644 --- a/core/include/cdm_engine_factory.h +++ b/core/include/cdm_engine_factory.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CDM_ENGINE_FACTORY_H_ #define WVCDM_CORE_CDM_ENGINE_FACTORY_H_ @@ -17,7 +17,7 @@ class CdmEngineFactory { public: // Creates a new instance of a CdmEngine. Caller retains ownership of the // |files_system| which cannot be null. - static CdmEngine* CreateCdmEngine(FileSystem* file_system); + static CdmEngine* CreateCdmEngine(wvutil::FileSystem* file_system); private: CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngineFactory); diff --git a/core/include/cdm_engine_metrics_decorator.h b/core/include/cdm_engine_metrics_decorator.h index 5eb7d9a0..4d908af7 100644 --- a/core/include/cdm_engine_metrics_decorator.h +++ b/core/include/cdm_engine_metrics_decorator.h @@ -1,7 +1,7 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CDM_ENGINE_METRICS_DECORATOR_H_ #define WVCDM_CORE_CDM_ENGINE_METRICS_DECORATOR_H_ @@ -47,7 +47,7 @@ class CdmEngineMetricsImpl : public T { // |file_system| and |metrics| must not be null. // |metrics| is used within the base class constructor. So, it must be // passed in as a dependency and provided to the base constructor. - CdmEngineMetricsImpl(FileSystem* file_system, + CdmEngineMetricsImpl(wvutil::FileSystem* file_system, std::shared_ptr metrics) : T(file_system, metrics), metrics_(metrics) { metrics_->cdm_engine_creation_time_millis_.Record(clock_.GetCurrentTime()); @@ -153,12 +153,11 @@ class CdmEngineMetricsImpl : public T { return sts; } - CdmResponseType GetProvisioningRequest(CdmCertificateType cert_type, - const std::string& cert_authority, - const std::string& service_certificate, - SecurityLevel requested_security_level, - CdmProvisioningRequest* request, - std::string* default_url) override { + CdmResponseType GetProvisioningRequest( + CdmCertificateType cert_type, const std::string& cert_authority, + const std::string& service_certificate, + RequestedSecurityLevel requested_security_level, + CdmProvisioningRequest* request, std::string* default_url) override { CdmResponseType sts; M_TIME(sts = T::GetProvisioningRequest( cert_type, cert_authority, service_certificate, @@ -169,7 +168,7 @@ class CdmEngineMetricsImpl : public T { CdmResponseType HandleProvisioningResponse( const CdmProvisioningResponse& response, - SecurityLevel requested_security_level, std::string* cert, + RequestedSecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key) override { CdmResponseType sts; M_TIME(sts = T::HandleProvisioningResponse( @@ -270,7 +269,7 @@ class CdmEngineMetricsImpl : public T { private: std::shared_ptr metrics_; - Clock clock_; + wvutil::Clock clock_; }; } // namespace wvcdm diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h index e173e8a4..c98c8030 100644 --- a/core/include/cdm_session.h +++ b/core/include/cdm_session.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CDM_SESSION_H_ #define WVCDM_CORE_CDM_SESSION_H_ @@ -33,9 +33,9 @@ class CdmSession { public: // Creates a new instance of the CdmSession with the given |file_system| // and |metrics| parameters. Both parameters are owned by the caller and - // must remain in scope througout the scope of the new instance. |metrics| + // must remain in scope throughout the scope of the new instance. |metrics| // must not be null. - CdmSession(FileSystem* file_system, + CdmSession(wvutil::FileSystem* file_system, std::shared_ptr metrics); virtual ~CdmSession(); @@ -49,7 +49,7 @@ class CdmSession { // cached at the time Init() is called. virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set); - // Initializes this instance of CdmSession with the given parmeters. + // Initializes this instance of CdmSession with the given parameters. // All parameters are owned by the caller. // // |cdm_client_property_set| is caller owned, may be null, but must be in @@ -60,9 +60,13 @@ class CdmSession { // // |event_listener| is caller owned, may be null, but must be in scope as long // as the session is in scope. + // + // |forced_level3|_is used to specify whether the "default" security level + // should always use L3 even if L1 is available. virtual CdmResponseType Init(CdmClientPropertySet* cdm_client_property_set, const CdmSessionId* forced_session_id, - WvCdmEventListener* event_listener); + WvCdmEventListener* event_listener, + bool forced_level3); // Restores an offline session identified by the |key_set_id| and // |license_type|. The |error_detail| will be filled with an internal error @@ -137,7 +141,7 @@ class CdmSession { virtual void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); virtual void GetApplicationId(std::string* app_id); - virtual SecurityLevel GetRequestedSecurityLevel() { + virtual RequestedSecurityLevel GetRequestedSecurityLevel() { return requested_security_level_; } virtual CdmSecurityLevel GetSecurityLevel() { return security_level_; } @@ -160,8 +164,8 @@ class CdmSession { license_parser_->provider_session_token().size() > 0); } - virtual CdmUsageSupportType get_usage_support_type() { - return usage_support_type_; + virtual bool supports_usage_info() const { + return usage_table_header_ != nullptr; } // This method will remove keys by resetting crypto resources and @@ -219,11 +223,25 @@ class CdmSession { private: friend class CdmSessionTest; + // Both these methods will attempt to load wrapped key material and + // cache values in |drm_certificate_| and |wrapped_private_key_| + // if successful. + // This method will load the key from persistent storage. + CdmResponseType LoadPrivateKey(); + // This method will load the specified key if valid or otherwise load + // the information from the legacy certificate. + CdmResponseType LoadPrivateOrLegacyKey( + const std::string& certificate, + const CryptoWrappedKey& wrapped_private_key); + + CdmResponseType LoadPrivateKey(const std::string& certificate, + const CryptoWrappedKey& wrapped_private_key); + bool GenerateKeySetId(bool atsc_mode_enabled, CdmKeySetId* key_set_id); CdmResponseType StoreLicense(); - bool StoreLicense(DeviceFiles::LicenseState state, int* error_detail); + bool StoreLicense(CdmOfflineLicenseState state, int* error_detail); bool UpdateUsageInfo(); @@ -239,12 +257,6 @@ class CdmSession { // true otherwise. bool VerifyOfflineUsageEntry(); - // On android, we previously permitted a license to be loaded and restored - // in the same session. This method releases resources so that - // CdmSession::Init can safely be invoked before a new license is restored. - // TODO(b/161865160): Investigate whether we can dispense with this scenario. - virtual CdmResponseType ReleaseOfflineResources(); - // These setters are for testing only. Takes ownership of the pointers. void set_license_parser(CdmLicense* license_parser); void set_crypto_session(CryptoSession* crypto_session); @@ -254,14 +266,14 @@ class CdmSession { // instance variables std::shared_ptr metrics_; metrics::CryptoMetrics* crypto_metrics_; - metrics::TimerMetric life_span_; - metrics::TimerMetric license_request_latency_; + metrics::Timer life_span_; + metrics::Timer license_request_latency_; CdmKeyRequestType key_request_type_; bool initialized_; bool closed_; // Session closed, but final shared_ptr has not been released. CdmSessionId session_id_; - FileSystem* file_system_; + wvutil::FileSystem* file_system_; std::unique_ptr license_parser_; std::unique_ptr crypto_session_; std::unique_ptr policy_engine_; @@ -271,18 +283,33 @@ class CdmSession { bool is_release_; bool is_temporary_; CdmSecurityLevel security_level_; - SecurityLevel requested_security_level_; + RequestedSecurityLevel requested_security_level_; + // If |forced_level3_|, |security_level_| and |requested_security_level_| + // MUST be set to kSecurityLevelL3 and kLevel3, respectively. + bool forced_level3_ = false; CdmAppParameterMap app_parameters_; + bool atsc_mode_enabled_ = false; + std::string drm_certificate_; + CryptoWrappedKey wrapped_private_key_; - // decryption flags - bool is_initial_decryption_; - bool has_decrypted_since_last_report_; // ... last report to policy engine. + // Decryption flags. + // Indiates that the next call to Decrypt will be the first for this + // license. + bool is_initial_decryption_ = true; + // Set to true if a successful call to Decrypt has occurred. Cleared + // when the policy engine has been notified about successful + // decryption. + bool has_decrypted_since_last_report_ = false; + // Set to true if the last call to Decrypt resulted in a failure. + // Cleared when the call to decrypt has succeeded. + bool last_decrypt_failed_ = false; // Usage related flags and data bool is_initial_usage_update_; bool is_usage_update_needed_; - CdmUsageSupportType usage_support_type_; - UsageTableHeader* usage_table_header_; + // Only assign |usage_table_header_| if capable of supporting usage + // information. + UsageTableHeader* usage_table_header_ = nullptr; uint32_t usage_entry_number_; CdmUsageEntry usage_entry_; std::string usage_provider_session_token_; @@ -300,18 +327,6 @@ class CdmSession { // license type release and offline related information CdmKeySetId key_set_id_; - // TODO(b/161865160): Use these variables to cache Init parameters. Remove - // when b/ has been addressed. - // |cdm_client_property_set_| and |event_listener_| point to a data - // member of WVDrmPlugin or WVDrmPlugin itself. It is safe for CdmSession - // to make use of these objects without taking ownership since WVDrmPlugin - // lifetime exceeds CdmSession (WVDrmPlugin indirectly owns CdmSession - // objects). These pointers if set, should be valid till CdmSession - // destruction. - CdmClientPropertySet* cdm_client_property_set_ = nullptr; - CdmSessionId* forced_session_id_ = nullptr; - CdmSessionId forced_session_id_value_; - WvCdmEventListener* event_listener_ = nullptr; bool has_license_been_loaded_ = false; bool has_license_been_restored_ = false; diff --git a/core/include/cdm_session_map.h b/core/include/cdm_session_map.h index 74b54906..61897ec8 100644 --- a/core/include/cdm_session_map.h +++ b/core/include/cdm_session_map.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CDM_SESSION_MAP_H_ #define WVCDM_CORE_CDM_SESSION_MAP_H_ diff --git a/core/include/certificate_provisioning.h b/core/include/certificate_provisioning.h index 5d75c6a4..8189958b 100644 --- a/core/include/certificate_provisioning.h +++ b/core/include/certificate_provisioning.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CERTIFICATE_PROVISIONING_H_ #define WVCDM_CORE_CERTIFICATE_PROVISIONING_H_ @@ -16,11 +16,14 @@ #include "service_certificate.h" #include "wv_cdm_types.h" +namespace wvutil { +class FileSystem; +} + namespace wvcdm { class CdmClientPropertySet; class CdmSession; -class FileSystem; class ServiceCertificate; class CertificateProvisioning { @@ -35,17 +38,16 @@ class CertificateProvisioning { // Construct a valid provisioning request. // The request will be sent to the provisioning server. - CdmResponseType GetProvisioningRequest(SecurityLevel requested_security_level, - CdmCertificateType cert_type, - const std::string& cert_authority, - const std::string& origin, - const std::string& spoid, - CdmProvisioningRequest* request, - std::string* default_url); + CdmResponseType GetProvisioningRequest( + wvutil::FileSystem* file_system, + RequestedSecurityLevel requested_security_level, + CdmCertificateType cert_type, const std::string& cert_authority, + const std::string& origin, const std::string& spoid, + CdmProvisioningRequest* request, std::string* default_url); // Process the provisioning response. CdmResponseType HandleProvisioningResponse( - FileSystem* file_system, const CdmProvisioningResponse& response, + wvutil::FileSystem* file_system, const CdmProvisioningResponse& response, std::string* cert, std::string* wrapped_key); bool supports_core_messages() const { return supports_core_messages_; } @@ -54,26 +56,72 @@ class CertificateProvisioning { // Extract serial number and system ID from a DRM Device certificate. // Either |serial_number| or |system_id| may be null, but not both. + // Both |creation_time_seconds| and |expiration_time_seconds| may be null. + // |creation_time_seconds| and |expiration_time_seconds| will be set to -1 + // if not present, 0 if unlimited and a valid time otherwise static bool ExtractDeviceInfo(const std::string& device_certificate, - std::string* serial_number, - uint32_t* system_id); + std::string* serial_number, uint32_t* system_id, + int64_t* creation_time_seconds, + int64_t* expiration_time_seconds); // Removes json wrapping if applicable to extract the // SignedProvisioningMessage - static bool ExtractAndDecodeSignedMessageForTesting( + static bool ExtractAndDecodeSignedMessage( const std::string& provisioning_response, std::string* result); + // Retrieve the provisioning server URL used for certificate + // provisioning. This will be the same value as returned in + // |default_url| by GetProvisioningRequest(). + static void GetProvisioningServerUrl(std::string* default_url); + private: + CdmResponseType GetProvisioningRequestInternal( + wvutil::FileSystem* file_system, + RequestedSecurityLevel requested_security_level, + CdmCertificateType cert_type, const std::string& cert_authority, + const std::string& origin, const std::string& spoid, + CdmProvisioningRequest* request, std::string* default_url); + CdmResponseType GetProvisioning40RequestInternal( + wvutil::FileSystem* file_system, const std::string& origin, + const std::string& spoid, CdmProvisioningRequest* request, + std::string* default_url); + CdmResponseType FillEncryptedClientId( + const std::string& client_token, + video_widevine::ProvisioningRequest& provisioning_request, + const ServiceCertificate& service_certificate); + CdmResponseType FillEncryptedClientIdWithAdditionalParameter( + const std::string& client_token, + const CdmAppParameterMap& additional_parameter, + video_widevine::ProvisioningRequest& provisioning_request, + const ServiceCertificate& service_certificate); + CdmResponseType HandleProvisioning40Response( + wvutil::FileSystem* file_system, const std::string& response_message); + CdmResponseType SetSpoidParameter( const std::string& origin, const std::string& spoid, video_widevine::ProvisioningRequest* request); - video_widevine::SignedProvisioningMessage::ProtocolVersion - GetProtocolVersion(); + video_widevine::SignedProvisioningMessage::ProvisioningType + GetProvisioningType(); + + // Closes crypto session if one is open. Avoid calling this method when + // processing a response. Multiple provisioning responses might be + // simultaneously in flight. Only the response associated with the last + // provisioning request can be processed. All the other responses will + // fail. If the session is closed when these responses fail, even the one + // associated with the last provisioning request may fail. + CdmResponseType CloseSessionOnError(CdmResponseType status); + void CloseSession(); std::unique_ptr crypto_session_; CdmCertificateType cert_type_; std::unique_ptr service_certificate_; + // The wrapped private key in provisioning 4 generated by calling + // GenerateCertificateKeyPair. It will be saved to file system if a valid + // response is received. + std::string provisioning_40_wrapped_private_key_; + // Key type of the generated key pair in provisioning 4. + CryptoWrappedKey::Type provisioning_40_key_type_; // Indicates whether OEMCrypto supports core messages, and whether the // CDM should expect a core message in the response. This is primarily diff --git a/core/include/client_identification.h b/core/include/client_identification.h index dd9d3e57..35c106b7 100644 --- a/core/include/client_identification.h +++ b/core/include/client_identification.h @@ -1,39 +1,43 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CLIENT_IDENTIFICATION_H_ #define WVCDM_CORE_CLIENT_IDENTIFICATION_H_ +#include + // ClientIdentification fills in the ClientIdentification portion // of the License or Provisioning request messages. - #include "disallow_copy_and_assign.h" #include "license_protocol.pb.h" #include "wv_cdm_types.h" namespace wvcdm { - class CryptoSession; class ClientIdentification { public: - ClientIdentification() : is_license_request_(true) {} + ClientIdentification() {} virtual ~ClientIdentification() {} - // Call this method when used with provisioning requests - CdmResponseType Init(CryptoSession* crypto_session); + // Call this method when used with provisioning requests. |client_token| may + // be empty and the token will be retrieved from OEMCrypto. In case of the + // second stage of provisioning 4, an OEM cert must be provided via + // |client_token|. |crypto_session| must not be nullptr. + CdmResponseType InitForProvisioningRequest(const std::string& client_token, + CryptoSession* crypto_session); // Use in conjunction with license requests // |client_token| must be provided - // |device_id| optional // |crypto_session| input parameter, mandatory - CdmResponseType Init(const std::string& client_token, - const std::string& device_id, - CryptoSession* crypto_session); + CdmResponseType InitForLicenseRequest(const std::string& client_token, + CryptoSession* crypto_session); - // Fill the ClientIdentification portion of the license or provisioning - // request + CdmResponseType InitForOtaKeyboxProvisioning(CryptoSession* crypto_session); + + // Fill the ClientIdentification portion of the license, DRM cert + // provisioning or OTA keybox provisioning request. // |app_parameters| parameters provided by client/app to be included in // provisioning/license request. optional, only used // if |is_license_request| is true @@ -51,14 +55,13 @@ class ClientIdentification { bool GetProvisioningTokenType( video_widevine::ClientIdentification::TokenType* token_type); - bool is_license_request_; + bool is_license_request_ = false; + bool is_okp_request_ = false; std::string client_token_; std::string device_id_; - CryptoSession* crypto_session_; + CryptoSession* crypto_session_ = nullptr; CORE_DISALLOW_COPY_AND_ASSIGN(ClientIdentification); }; - } // namespace wvcdm - #endif // WVCDM_CORE_CLIENT_IDENTIFICATION_H_ diff --git a/core/include/content_key_session.h b/core/include/content_key_session.h index 9e784547..209242dc 100644 --- a/core/include/content_key_session.h +++ b/core/include/content_key_session.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CONTENT_KEY_SESSION_H_ #define WVCDM_CORE_CONTENT_KEY_SESSION_H_ diff --git a/core/include/crypto_key.h b/core/include/crypto_key.h index 9614e597..f0bfcfb9 100644 --- a/core/include/crypto_key.h +++ b/core/include/crypto_key.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CRYPTO_KEY_H_ #define WVCDM_CORE_CRYPTO_KEY_H_ diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h index 1e44b94a..20421510 100644 --- a/core/include/crypto_session.h +++ b/core/include/crypto_session.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_CRYPTO_SESSION_H_ #define WVCDM_CORE_CRYPTO_SESSION_H_ @@ -13,6 +13,7 @@ #include #include "OEMCryptoCENC.h" +#include "crypto_wrapped_key.h" #include "disallow_copy_and_assign.h" #include "key_session.h" #include "metrics_collections.h" @@ -22,10 +23,15 @@ #include "wv_cdm_types.h" namespace wvcdm { - class CryptoKey; +class CryptoSessionFactory; +class OtaKeyboxProvisioner; class UsageTableHeader; +namespace okp { +class SystemFallbackPolicy; +} // namespace okp + using CryptoKeyMap = std::map; // Crypto session utility functions used by KeySession implementations. @@ -39,8 +45,6 @@ OEMCrypto_Substring GetSubstring(const std::string& message = "", bool set_zero = false); OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode); -class CryptoSessionFactory; - class CryptoSession { public: using HdcpCapability = OEMCrypto_HDCP_Capability; @@ -54,6 +58,9 @@ class CryptoSession { bool rsa_2048_bit; bool rsa_3072_bit; bool rsa_cast; + bool ecc_secp256r1; + bool ecc_secp384r1; + bool ecc_secp521r1; }; // Creates an instance of CryptoSession with the given |crypto_metrics|. @@ -75,18 +82,37 @@ class CryptoSession { static void DisableDelayedTermination(); - virtual CdmResponseType GetProvisioningToken(std::string* client_token); + virtual CdmResponseType GetProvisioningToken( + RequestedSecurityLevel requested_security_level, std::string* token, + std::string* additional_token); + // Must be called after session is open. + virtual CdmResponseType GetProvisioningToken(std::string* token, + std::string* additional_token); + virtual CdmClientTokenType GetPreProvisionTokenType() { return pre_provision_token_type_; } + // Retrieves the key data portion of the OEMCrypto keybox. + // Only valid for keybox-based based devices. + // May return NEED_PROVISIONING if the device is keybox-based, but + // OTA keybox provisioning is required. + virtual CdmResponseType GetTokenFromKeybox( + RequestedSecurityLevel requested_security_level, std::string* key_data); + // Retrieves the public OEM certificate chain from OEMCrypto. + // Only valid for OEM certificate-based based devices. + virtual CdmResponseType GetTokenFromOemCert( + RequestedSecurityLevel requested_security_level, std::string* oem_cert); + // The overloaded methods with |requested_level| may be called // without a preceding call to Open. The other method must call Open first. virtual CdmSecurityLevel GetSecurityLevel(); - virtual CdmSecurityLevel GetSecurityLevel(SecurityLevel requested_level); + virtual CdmSecurityLevel GetSecurityLevel( + RequestedSecurityLevel requested_level); virtual bool GetApiVersion(uint32_t* version); - virtual bool GetApiVersion(SecurityLevel requested_level, uint32_t* version); - virtual bool GetApiMinorVersion(SecurityLevel requested_level, + virtual bool GetApiVersion(RequestedSecurityLevel requested_level, + uint32_t* version); + virtual bool GetApiMinorVersion(RequestedSecurityLevel requested_level, uint32_t* minor_version); // This method will return, for devices with a @@ -104,12 +130,17 @@ class CryptoSession { // - that does not implement |OEMCrypto_GetDeviceID|: the 32 byte hash // of the OEM public certificate. virtual CdmResponseType GetExternalDeviceUniqueId(std::string* device_id); - virtual bool GetSystemId(uint32_t* system_id); virtual CdmResponseType GetProvisioningId(std::string* provisioning_id); virtual uint8_t GetSecurityPatchLevel(); + virtual bool GetCachedSystemId(uint32_t* system_id); + // With provisioning 4.0, the system ID cannot reliably be found within + // OEMCrypto. The system ID can be assigned to the CryptoSession instance + // after the ID has been determined. + virtual void SetSystemId(uint32_t system_id); + virtual CdmResponseType Open() { return Open(kLevelDefault); } - virtual CdmResponseType Open(SecurityLevel requested_security_level); + virtual CdmResponseType Open(RequestedSecurityLevel requested_security_level); virtual void Close(); virtual bool IsOpen() { return open_; } @@ -123,6 +154,7 @@ class CryptoSession { virtual CdmResponseType PrepareAndSignLicenseRequest( const std::string& message, std::string* core_message, std::string* signature); + virtual CdmResponseType UseSecondaryKey(bool dual_key); // V15 licenses. virtual CdmResponseType LoadKeys(const std::string& message, const std::string& signature, @@ -167,7 +199,17 @@ class CryptoSession { const std::string& signature, std::string* wrapped_private_key); virtual CdmResponseType LoadCertificatePrivateKey( - const std::string& wrapped_key); + const CryptoWrappedKey& private_key); + virtual CdmResponseType GetBootCertificateChain( + RequestedSecurityLevel requested_security_level, std::string* bcc, + std::string* additional_signature); + virtual CdmResponseType GetBootCertificateChain( + std::string* bcc, std::string* additional_signature); + virtual CdmResponseType GenerateCertificateKeyPair( + std::string* public_key, std::string* public_key_signature, + std::string* wrapped_private_key, CryptoWrappedKey::Type* key_type); + virtual CdmResponseType LoadOemCertificatePrivateKey( + const CryptoWrappedKey& private_key); // Media data path virtual CdmResponseType Decrypt(const CdmDecryptionParametersV16& params); @@ -178,32 +220,40 @@ class CryptoSession { // preceding call to Open. The other methods must call Open first. virtual CdmResponseType GetHdcpCapabilities(HdcpCapability* current, HdcpCapability* max); - virtual CdmResponseType GetHdcpCapabilities(SecurityLevel security_level, - HdcpCapability* current, - HdcpCapability* max); + virtual CdmResponseType GetHdcpCapabilities( + RequestedSecurityLevel security_level, HdcpCapability* current, + HdcpCapability* max); virtual bool GetResourceRatingTier(uint32_t* tier); - virtual bool GetResourceRatingTier(SecurityLevel security_level, + virtual bool GetResourceRatingTier(RequestedSecurityLevel security_level, uint32_t* tier); virtual bool GetSupportedCertificateTypes(SupportedCertificateTypes* support); virtual CdmResponseType GetRandom(size_t data_length, uint8_t* random_data); - virtual CdmResponseType GetNumberOfOpenSessions(SecurityLevel security_level, - size_t* count); - virtual CdmResponseType GetMaxNumberOfSessions(SecurityLevel security_level, - size_t* max); + virtual CdmResponseType GetNumberOfOpenSessions( + RequestedSecurityLevel security_level, size_t* count); + virtual CdmResponseType GetMaxNumberOfSessions( + RequestedSecurityLevel security_level, size_t* max); virtual CdmResponseType GetSrmVersion(uint16_t* srm_version); - virtual bool IsSrmUpdateSupported(); - virtual CdmResponseType LoadSrm(const std::string& srm); - virtual bool GetBuildInformation(SecurityLevel security_level, + virtual bool GetBuildInformation(RequestedSecurityLevel security_level, std::string* info); virtual bool GetBuildInformation(std::string* info); - virtual bool GetMaximumUsageTableEntries(SecurityLevel security_level, - size_t* number_of_entries); + virtual bool GetWatermarkingSupport(CdmWatermarkingSupport* support); + virtual bool GetWatermarkingSupport( + RequestedSecurityLevel requested_security_level, + CdmWatermarkingSupport* support); - virtual bool GetDecryptHashSupport(SecurityLevel security_level, + virtual bool GetProductionReadiness(CdmProductionReadiness* readiness); + virtual bool GetProductionReadiness( + RequestedSecurityLevel requested_security_level, + CdmProductionReadiness* readiness); + + virtual bool GetMaximumUsageTableEntries( + RequestedSecurityLevel security_level, size_t* number_of_entries); + + virtual bool GetDecryptHashSupport(RequestedSecurityLevel security_level, uint32_t* hash_support); virtual CdmResponseType SetDecryptHash(uint32_t frame_number, @@ -234,14 +284,13 @@ class CryptoSession { // Used to manipulate the CDM managed usage table header & entries, // delegating calls to OEMCrypto. - // Usage support. - virtual CdmResponseType GetUsageSupportType(CdmUsageSupportType* type); - - // The overloaded method with |security_level| may be called without a - // preceding call to Open. The other method must call Open first. - virtual bool UsageInformationSupport(bool* has_support); - virtual bool UsageInformationSupport(SecurityLevel security_level, - bool* has_support); + // Determines whether the OEMCrypto library supports usage info. + // As of V16, the only valid type of support is usage table header + + // usage entries. + // The first method will use a cached value if present. + virtual bool HasUsageInfoSupport(bool* has_support); + virtual bool HasUsageInfoSupport(RequestedSecurityLevel security_level, + bool* has_support); // Usage report. virtual CdmResponseType DeactivateUsageInformation( @@ -258,13 +307,13 @@ class CryptoSession { // The following crypto methods do not require an open session to // complete the operations. virtual CdmResponseType CreateUsageTableHeader( - SecurityLevel requested_security_level, + RequestedSecurityLevel requested_security_level, CdmUsageTableHeader* usage_table_header); virtual CdmResponseType LoadUsageTableHeader( - SecurityLevel requested_security_level, + RequestedSecurityLevel requested_security_level, const CdmUsageTableHeader& usage_table_header); virtual CdmResponseType ShrinkUsageTableHeader( - SecurityLevel requested_security_level, uint32_t new_entry_count, + RequestedSecurityLevel requested_security_level, uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header); // Usage entry. @@ -283,7 +332,36 @@ class CryptoSession { virtual metrics::CryptoMetrics* GetCryptoMetrics() { return metrics_; } virtual CdmResponseType GetProvisioningMethod( - SecurityLevel requested_security_level, CdmClientTokenType* token_type); + RequestedSecurityLevel requested_security_level, + CdmClientTokenType* token_type); + + // OTA Provisioning + + static bool needs_keybox_provisioning() { return needs_keybox_provisioning_; } + + // This tells the OEMCrypto adapter to ignore the next |count| keyboxes and + // report that it needs provisioning instead. + static CdmResponseType SetDebugIgnoreKeyboxCount(uint32_t count); + + // This tells the OEMCrypto adapter to allow the device to continue with a + // test keybox. Otherwise, the keybox is reported as invalid. + static CdmResponseType SetAllowTestKeybox(bool allow); + + // Returns a system-wide singleton instance of SystemFallbackPolicy + // to be used for communicating OTA keybox provisioning state between + // apps. Returns a null pointer if OTA provisioning is not supported, + // or not required. + static okp::SystemFallbackPolicy* GetOkpFallbackPolicy(); + + // Generates an OTA provisioning request. + // This should only be called by an instance of OtaKeyboxProvisioner. + virtual CdmResponseType PrepareOtaProvisioningRequest(bool use_test_key, + std::string* request); + + // Loads an OTA provisioning response. + // This should only be called by an instance of OtaKeyboxProvisioner. + virtual CdmResponseType LoadOtaProvisioning(bool use_test_key, + const std::string& response); protected: // Creates an instance of CryptoSession with the given |crypto_metrics|. @@ -291,7 +369,11 @@ class CryptoSession { // exist as long as the new CryptoSession exists. explicit CryptoSession(metrics::CryptoMetrics* crypto_metrics); - int session_count() { return session_count_; } + int session_count() const { return session_count_; } + // Cache api version and fallback policy. Call this once at initialization. + void CacheVersion(); + // Re-initialize for running tests with a test keybox. + void ReinitializeForTest(); private: friend class CryptoSessionForTest; @@ -299,23 +381,26 @@ class CryptoSession { #if defined(UNIT_TEST) friend class CertificateProvisioningTest; friend class WvCdmTestBase; + friend class CdmOtaKeyboxTest; #endif // The global factory method can be set to generate special crypto sessions // just for testing. These sessions will avoid nonce floods and will ask // OEMCrypto to use a test keybox. - // Ownership of the object is transfered to CryptoSession. + // Ownership of the object is transferred to CryptoSession. static void SetCryptoSessionFactory(CryptoSessionFactory* factory) { std::unique_lock auto_lock(factory_mutex_); factory_.reset(factory); } void Init(); - CdmResponseType GetTokenFromKeybox(std::string* token); - CdmResponseType GetTokenFromOemCert(std::string* token); - static bool ExtractSystemIdFromOemCert(const std::string& oem_cert, - uint32_t* system_id); - CdmResponseType GetSystemIdInternal(uint32_t* system_id); + + // Will set up the UsageTableHeader for this session. This may require + // creating a new UsageTableHeader if the global instance has not + // been initialized. + // Note: This function will lock the global static field lock in write mode. + bool SetUpUsageTableHeader(RequestedSecurityLevel requested_security_level); + CdmResponseType GenerateRsaSignature(const std::string& message, std::string* signature); size_t GetMaxSubsampleRegionSize(); @@ -325,13 +410,11 @@ class CryptoSession { CdmResponseType SelectKey(const std::string& key_id, CdmCipherMode cipher_mode); - static const OEMCrypto_Algorithm kInvalidAlgorithm = - static_cast(-1); - - OEMCrypto_Algorithm GenericSigningAlgorithm(CdmSigningAlgorithm algorithm); - OEMCrypto_Algorithm GenericEncryptionAlgorithm( - CdmEncryptionAlgorithm algorithm); - size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm); + // Retrieves the OEMCrypto usage info support for the specified + // |requested_security_level|. + // Caller should acquire the OEMCrypto read lock before calling. + bool HasUsageInfoSupportInternal( + RequestedSecurityLevel requested_security_level, bool* has_support); // These methods fall back into each other in the order given, depending on // how much data they were given and how much data OEMCrypto can accept in one @@ -355,9 +438,15 @@ class CryptoSession { // These methods should be used to take the various CryptoSession mutexes in // preference to taking the mutexes directly. // - // A lock should be taken on the Static Field Mutex before accessing any of - // CryptoSession's non-atomic static fields. It can be taken as a reader or as - // a writer, depending on how you will be accessing the static fields. + // A lock should be taken on the Static Field Mutex before accessing + // any of CryptoSession's non-atomic static fields with the exception + // of the Usage Table Mutex. The Static Field Mutex can be taken as + // a reader or as a writer, depending on how you will be accessing + // the static fields. The Usage Table Mutex should be taken when + // reading and writing to the static usage table fields (creating, + // destroying or taking a pointer of the handles). The purpose of + // having a separate mutex for usage table is due to the recursive + // nature of initializing the global usage table. // // Before calling into OEMCrypto, code must take locks on the OEMCrypto Mutex // and/or the OEMCrypto Session Mutex. Which of them should be taken and how @@ -408,23 +497,32 @@ class CryptoSession { static bool IsInitialized(); - // Constants - static const size_t kAes128BlockSize = 16; // Block size for AES_CBC_128 - static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature - // The locking methods above should be used in preference to taking these // mutexes directly. If code takes these manually and needs to take more // than one, it must *always* take them in the order they are defined here. - static shared_mutex static_field_mutex_; - static shared_mutex oem_crypto_mutex_; + static wvutil::shared_mutex static_field_mutex_; + static wvutil::shared_mutex oem_crypto_mutex_; std::mutex oem_crypto_session_mutex_; + // Usage table mutex used only when performing write operations on + // the static usage table pointers. + static std::recursive_mutex usage_table_mutex_; static bool initialized_; static int session_count_; static int termination_counter_; + static bool needs_keybox_provisioning_; + + enum CachedBooleanProperty { + // Property has not yet been checked/cached. + kBooleanUnset, + // Property has been cached as false. + kBooleanFalse, + // Property has been cached as true. + kBooleanTrue + }; metrics::CryptoMetrics* metrics_; - metrics::TimerMetric life_span_; + metrics::Timer life_span_; uint32_t system_id_; bool open_; @@ -436,12 +534,15 @@ class CryptoSession { OEMCryptoBufferType destination_buffer_type_; bool is_destination_buffer_type_valid_; - SecurityLevel requested_security_level_; + RequestedSecurityLevel requested_security_level_; - CdmUsageSupportType usage_support_type_; - UsageTableHeader* usage_table_header_; - static UsageTableHeader* usage_table_header_l1_; - static UsageTableHeader* usage_table_header_l3_; + // Open session-cached result of OEMCrypto_SupportsUsageTable(). + CachedBooleanProperty has_usage_info_support_ = kBooleanUnset; + UsageTableHeader* usage_table_header_ = nullptr; + // These fields are protected by |usage_table_mutex_| and not + // |static_field_mutex_|. + static std::unique_ptr usage_table_header_l1_; + static std::unique_ptr usage_table_header_l3_; std::string request_id_; static std::atomic request_id_index_source_; @@ -458,10 +559,15 @@ class CryptoSession { // In order to avoid creating a deadlock if instantiation needs to take any // of the CryptoSession static mutexes, |factory_| is protected by its own - // mutex that is only used in the two funtions that interact with it. + // mutex that is only used in the two functions that interact with it. static std::mutex factory_mutex_; static std::unique_ptr factory_; + // A singleton instance of SystemFallbackPolicy. Only one will + // be created for the system if OTA keybox provisioning is both + // required and supported by L1. + static std::unique_ptr okp_fallback_policy_l1_; + CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); }; // class CryptoSession diff --git a/core/include/crypto_wrapped_key.h b/core/include/crypto_wrapped_key.h new file mode 100644 index 00000000..bf947bb9 --- /dev/null +++ b/core/include/crypto_wrapped_key.h @@ -0,0 +1,52 @@ +// Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_CRYPTO_WRAPPED_KEY_H_ +#define WVCDM_CORE_CRYPTO_WRAPPED_KEY_H_ + +#include + +namespace wvcdm { + +// Represents OEMCrypto's wrapped private DRM key. As of v16, it is +// possible for OEMCrypto to support ECC-based DRM certificates. The +// format of the wrapped key is vendor specific; however, the API +// requires that the CDM track whether the wrapped key is RSA or ECC. +class CryptoWrappedKey { + public: + enum Type : int32_t { kUninitialized = 0, kRsa = 1, kEcc = 2 }; + CryptoWrappedKey() {} + CryptoWrappedKey(Type type, const std::string& key) + : type_(type), key_(key) {} + + Type type() const { return type_; } + void set_type(Type type) { type_ = type; } + + const std::string& key() const { return key_; } + // Mutable reference getter for passing to OMECrypto. + std::string& key() { return key_; } + void set_key(const std::string& key) { key_ = key; } + + void Clear() { + type_ = kUninitialized; + key_.clear(); + } + // A valid key must have a known key type and have key data. + bool IsValid() const { return type_ != kUninitialized && !key_.empty(); } + // Equality operator is for testing only. Real keys may have + // different meta data but the same logical key. + bool operator==(const CryptoWrappedKey& other) const { + return type_ == other.type_ && key_ == other.key_; + } + + private: + // DRM key type of the wrapped key. For wrapped keys which the type + // of key is unknown, assume it to be RSA. + Type type_ = kUninitialized; + // Vendor-specific wrapped DRM key. + std::string key_; +}; // class CryptoWrappedKey + +} // namespace wvcdm + +#endif // WVCDM_CORE_CRYPTO_WRAPPED_KEY_H_ diff --git a/core/include/device_files.h b/core/include/device_files.h index 70f55e36..8b3926ef 100644 --- a/core/include/device_files.h +++ b/core/include/device_files.h @@ -1,16 +1,19 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // #ifndef WVCDM_CORE_DEVICE_FILES_H_ #define WVCDM_CORE_DEVICE_FILES_H_ +#include #include #include #include +#include "crypto_wrapped_key.h" #include "device_files.pb.h" #include "disallow_copy_and_assign.h" +#include "okp_info.h" #include "platform.h" #include "wv_cdm_types.h" @@ -18,17 +21,31 @@ # include #endif -namespace wvcdm { - +namespace wvutil { class FileSystem; +} + +namespace wvcdm { class DeviceFiles { public: typedef enum { - kLicenseStateActive, - kLicenseStateReleasing, - kLicenseStateUnknown, - } LicenseState; + kCertificateValid, + kCertificateExpired, + kCertificateNotFound, + kCertificateInvalid, + kCannotHandle, + } CertificateState; + + // |kCertificateDefault| includes an expiration time set by the provisioning + // service. This will replace any legacy certificates, if a forced + // reprovisioning happens at the client or by the license service. + // ATSC certificates are unaffected and have an unlimited lifetime. + typedef enum { + kCertificateDefault, + kCertificateLegacy, + kCertificateAtsc, + } CertificateType; // All error response codes start with 5000 to avoid overlap with other error // spaces. @@ -53,11 +70,17 @@ class DeviceFiles { kLicenseNotPresent = kResponseTypeBase + 16, }; + // Converts the different enum types to a human readable C-string for + // logging. + static const char* CertificateStateToString(CertificateState state); + static const char* CertificateTypeToString(CertificateType type); + static const char* ResponseTypeToString(ResponseType type); + // CdmLicenseData represents all of the data that is stored in CDM // license file. License data is uniquely keyed using |key_set_id|. struct CdmLicenseData { std::string key_set_id; - LicenseState state; + CdmOfflineLicenseState state; CdmInitData pssh_data; // License request / response. CdmKeyMessage license_request; @@ -76,6 +99,8 @@ class DeviceFiles { // Usage entry and index. CdmUsageEntry usage_entry; uint32_t usage_entry_number; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; }; struct CdmUsageData { @@ -85,9 +110,11 @@ class DeviceFiles { std::string key_set_id; CdmUsageEntry usage_entry; uint32_t usage_entry_number; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; }; - DeviceFiles(FileSystem*); + DeviceFiles(wvutil::FileSystem*); virtual ~DeviceFiles(); virtual bool Init(CdmSecurityLevel security_level); @@ -98,15 +125,30 @@ class DeviceFiles { // ATSC certificates are installed by the ATSC service. They can be read // and used but not written or removed. virtual bool StoreCertificate(const std::string& certificate, - const std::string& wrapped_private_key); - virtual bool RetrieveCertificate(bool atsc_mode_enabled, - std::string* certificate, - std::string* wrapped_private_key, - std::string* serial_number, - uint32_t* system_id); + const CryptoWrappedKey& private_key); + virtual CertificateState RetrieveCertificate(bool atsc_mode_enabled, + std::string* certificate, + CryptoWrappedKey* private_key, + std::string* serial_number, + uint32_t* system_id); + // Returns true if a DRM certificate is available. virtual bool HasCertificate(bool atsc_mode_enabled); + // Retrieves the legacy DRM certificate without performing expiry + // related validation. Use this only when restoring/releasing + // licenses/usage entries + virtual bool RetrieveLegacyCertificate(std::string* certificate, + CryptoWrappedKey* private_key, + std::string* serial_number, + uint32_t* system_id); virtual bool RemoveCertificate(); + virtual bool StoreOemCertificate(const std::string& certificate, + const CryptoWrappedKey& private_key); + virtual DeviceFiles::CertificateState RetrieveOemCertificate( + std::string* certificate, CryptoWrappedKey* wrapped_private_key); + virtual bool HasOemCertificate(); + virtual bool RemoveOemCertificate(); + virtual bool StoreLicense(const CdmLicenseData& license_data, ResponseType* result); @@ -134,13 +176,12 @@ class DeviceFiles { // required creation of reverse lookup tables (CdmUsageEntryInfo). // |app_id| however was hashed and unextractable, and necessitated the // switch to |usage_info_file_name| - virtual bool StoreUsageInfo(const std::string& provider_session_token, - const CdmKeyMessage& key_request, - const CdmKeyResponse& key_response, - const std::string& usage_info_file_name, - const std::string& key_set_id, - const CdmUsageEntry& usage_entry, - uint32_t usage_entry_number); + virtual bool StoreUsageInfo( + const std::string& provider_session_token, + const CdmKeyMessage& key_request, const CdmKeyResponse& key_response, + const std::string& usage_info_file_name, const std::string& key_set_id, + const CdmUsageEntry& usage_entry, uint32_t usage_entry_number, + const std::string& drm_certificate, const CryptoWrappedKey& wrapped_key); // Retrieve usage identifying information stored on the file system. // The caller needs to specify at least one of |ksids| or @@ -186,12 +227,6 @@ class DeviceFiles { virtual bool DeleteAllUsageInfo(); - // Retrieve one usage info from the file. Subsequent calls will retrieve - // subsequent entries in the table for this app_id. - virtual bool RetrieveUsageInfo( - const std::string& usage_info_file_name, - std::vector >* usage_info); - // Retrieve the usage info entry specified by |provider_session_token|. // Returns false if the entry could not be found. virtual bool RetrieveUsageInfo(const std::string& usage_info_file_name, @@ -199,14 +234,17 @@ class DeviceFiles { CdmKeyMessage* license_request, CdmKeyResponse* license_response, CdmUsageEntry* usage_entry, - uint32_t* usage_entry_number); + uint32_t* usage_entry_number, + std::string* drm_certificate, + CryptoWrappedKey* wrapped_key); // Retrieve the usage info entry specified by |key_set_id|. // Returns false if the entry could not be found. virtual bool RetrieveUsageInfoByKeySetId( const std::string& usage_info_file_name, const std::string& key_set_id, std::string* provider_session_token, CdmKeyMessage* license_request, CdmKeyResponse* license_response, CdmUsageEntry* usage_entry, - uint32_t* usage_entry_number); + uint32_t* usage_entry_number, std::string* drm_certificate, + CryptoWrappedKey* wrapped_key); // These APIs support upgrading from usage tables to usage tabler header + // entries introduced in OEMCrypto V13. @@ -245,9 +283,23 @@ class DeviceFiles { virtual bool DeleteUsageTableInfo(); + // OTA Keybox Provisioning (OKP) information. + virtual bool StoreOkpInfo(const okp::SystemFallbackInfo& info); + virtual bool RetrieveOkpInfo(okp::SystemFallbackInfo* info); + virtual bool DeleteOkpInfo(); + private: + // This method will retrieve the certificate and perform expiry validation + // appropriate for a given certificate type + CertificateState RetrieveCertificate(CertificateType certificate_type, + std::string* certificate, + CryptoWrappedKey* private_key, + std::string* serial_number, + uint32_t* system_id); + bool HasCertificate(CertificateType certificate_type); + // Helpers that wrap the File interface and automatically handle hashing, as - // well as adding the device files base path to to the file name. + // well as adding the device files base path to the file name. ResponseType StoreFileWithHash(const std::string& name, const std::string& serialized_file); ResponseType StoreFileRaw(const std::string& name, @@ -259,27 +311,45 @@ class DeviceFiles { bool RemoveFile(const std::string& name); ssize_t GetFileSize(const std::string& name); - static std::string GetCertificateFileName(bool atsc_mode_enabled); + static bool GetCertificateFileName(CertificateType certificate_type, + std::string* certificate_file_name); + static bool GetOemCertificateFileName(std::string* certificate_file_name); + static std::string GetHlsAttributesFileNameExtension(); static std::string GetLicenseFileNameExtension(); static std::string GetUsageTableFileName(); + static std::string GetOkpInfoFileName(); static std::string GetFileNameSafeHash(const std::string& input); #if defined(UNIT_TEST) - FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel); - FRIEND_TEST(DeviceCertificateTest, StoreCertificate); + FRIEND_TEST(DeviceFilesSecurityLevelTest, RequestedSecurityLevel); FRIEND_TEST(DeviceCertificateTest, ReadCertificate); - FRIEND_TEST(DeviceCertificateTest, HasCertificate); FRIEND_TEST(DeviceFilesStoreTest, StoreLicense); FRIEND_TEST(DeviceFilesHlsAttributesTest, Delete); FRIEND_TEST(DeviceFilesHlsAttributesTest, Read); FRIEND_TEST(DeviceFilesHlsAttributesTest, Store); - FRIEND_TEST(DeviceFilesTest, DeleteLicense); - FRIEND_TEST(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem); - FRIEND_TEST(DeviceFilesTest, RetrieveLicenses); FRIEND_TEST(DeviceFilesTest, AppParametersBackwardCompatibility); + FRIEND_TEST(DeviceFilesTest, DeleteLicense); + FRIEND_TEST(DeviceFilesTest, HasCertificateAtsc); + FRIEND_TEST(DeviceFilesTest, HasCertificateDefault); + FRIEND_TEST(DeviceFilesTest, HasCertificateLegacy); + FRIEND_TEST(DeviceFilesTest, HasCertificateNone); + FRIEND_TEST(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem); + FRIEND_TEST(DeviceFilesTest, RetrieveAtscCertificate); + FRIEND_TEST(DeviceFilesTest, RetrieveAtscCertificateNotFound); + FRIEND_TEST(DeviceFilesTest, RetrieveCertificateWithoutKeyType); + FRIEND_TEST(DeviceFilesTest, RetrieveDefaultCertificate); + FRIEND_TEST(DeviceFilesTest, RetrieveDefaultCertificateNeverExpires); + FRIEND_TEST(DeviceFilesTest, + RetrieveLegacyCertificateWithClientExpirationTime); + FRIEND_TEST(DeviceFilesTest, RetrieveLegacyCertificateWithoutExpirationTime); + FRIEND_TEST(DeviceFilesTest, RetrieveLicenses); + FRIEND_TEST(DeviceFilesTest, StoreCertificateInvalidParams); FRIEND_TEST(DeviceFilesTest, StoreLicenses); FRIEND_TEST(DeviceFilesTest, UpdateLicenseState); + FRIEND_TEST(DeviceFilesTest, OkpInfo_FileDoesNotExist); + FRIEND_TEST(DeviceFilesTest, OkpInfo_DeleteFile); + FRIEND_TEST(DeviceFilesTest, OkpInfo_StoreAndRetrieve); FRIEND_TEST(DeviceFilesUsageInfoTest, Delete); FRIEND_TEST(DeviceFilesUsageInfoTest, DeleteAll); FRIEND_TEST(DeviceFilesUsageInfoTest, Read); @@ -287,6 +357,9 @@ class DeviceFiles { FRIEND_TEST(DeviceFilesUsageTableTest, Read); FRIEND_TEST(DeviceFilesUsageTableTest, Store); FRIEND_TEST(DeviceFilesUsageTableTest, ReadWithoutLruData); + FRIEND_TEST(RetrieveDefaultCertificateTest, ErrorScenarios); + FRIEND_TEST(RetrieveLegacyCertificateTest, ErrorScenarios); + FRIEND_TEST(StoreCertificateTest, DefaultAndLegacy); FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest); FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test); FRIEND_TEST(WvCdmRequestLicenseTest, UsageInfoRetryTest); @@ -297,8 +370,9 @@ class DeviceFiles { #endif static std::set reserved_license_ids_; + static std::mutex reserved_license_ids_mutex_; - FileSystem* file_system_; + wvutil::FileSystem* file_system_; CdmSecurityLevel security_level_; bool initialized_; diff --git a/core/include/entitlement_key_session.h b/core/include/entitlement_key_session.h index 358ec92d..2c33b285 100644 --- a/core/include/entitlement_key_session.h +++ b/core/include/entitlement_key_session.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_ENTITLEMENT_KEY_SESSION_H_ #define WVCDM_CORE_ENTITLEMENT_KEY_SESSION_H_ @@ -19,7 +19,7 @@ class EntitlementKeySession : public ContentKeySession { public: EntitlementKeySession(CryptoSessionId oec_session_id, metrics::CryptoMetrics* metrics); - ~EntitlementKeySession() override {} + ~EntitlementKeySession() override; KeySessionType Type() override { return kEntitlement; } @@ -35,6 +35,9 @@ class EntitlementKeySession : public ContentKeySession { const std::vector& keys) override; OEMCryptoResult SelectKey(const std::string& key_id, CdmCipherMode cipher_mode) override; + OEMCryptoResult Decrypt( + const OEMCrypto_SampleDescription* samples, size_t samples_length, + const OEMCrypto_CENCEncryptPatternDesc& pattern) override; private: // The message is populated with the fields of the provided CryptoKey and the @@ -47,6 +50,7 @@ class EntitlementKeySession : public ContentKeySession { std::map entitled_keys_; // Find the current entitled content key id for the given entitlement key id. std::map current_loaded_content_keys_; + EntitledKeySessionId key_session_id_; }; } // namespace wvcdm diff --git a/core/include/initialization_data.h b/core/include/initialization_data.h index 1cca1b3b..02c216cc 100644 --- a/core/include/initialization_data.h +++ b/core/include/initialization_data.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef CORE_INCLUDE_INITIALIZATION_DATA_H_ #define CORE_INCLUDE_INITIALIZATION_DATA_H_ diff --git a/core/include/key_session.h b/core/include/key_session.h index c823ee49..22d50ea2 100644 --- a/core/include/key_session.h +++ b/core/include/key_session.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_KEY_SESSION_H_ #define WVCDM_CORE_KEY_SESSION_H_ diff --git a/core/include/license.h b/core/include/license.h index 98a139d9..ea03ed83 100644 --- a/core/include/license.h +++ b/core/include/license.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_LICENSE_H_ #define WVCDM_CORE_LICENSE_H_ @@ -20,9 +20,11 @@ class LicenseRequest; class VersionInfo; } // namespace video_widevine -namespace wvcdm { - +namespace wvutil { class Clock; +} + +namespace wvcdm { class CryptoSession; class PolicyEngine; class CdmSession; @@ -38,9 +40,7 @@ class CdmLicense { CdmLicense(const CdmSessionId& session_id); virtual ~CdmLicense(); - virtual bool Init(const std::string& client_token, - CdmClientTokenType client_token_type, - const std::string& device_id, bool use_privacy_mode, + virtual bool Init(bool use_privacy_mode, const std::string& signed_service_certificate, CryptoSession* session, PolicyEngine* policy_engine); @@ -50,9 +50,9 @@ class CdmLicense { const std::string& signed_service_certificate); virtual CdmResponseType PrepareKeyRequest( - const InitializationData& init_data, CdmLicenseType license_type, - const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request, - std::string* server_url); + const InitializationData& init_data, const std::string& client_token, + CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, + CdmKeyMessage* signed_request, std::string* server_url); virtual CdmResponseType PrepareKeyUpdateRequest( bool is_renewal, const CdmAppParameterMap& app_parameters, CdmSession* cdm_session, CdmKeyMessage* signed_request, @@ -65,13 +65,13 @@ class CdmLicense { const InitializationData& init_data); virtual CdmResponseType RestoreOfflineLicense( - const CdmKeyMessage& license_request, + const std::string& client_token, const CdmKeyMessage& license_request, const CdmKeyResponse& license_response, const CdmKeyResponse& license_renewal_response, int64_t playback_start_time, int64_t last_playback_time, int64_t grace_period_end_time, CdmSession* cdm_session); virtual CdmResponseType RestoreLicenseForRelease( - const CdmKeyMessage& license_request, + const std::string& client_token, const CdmKeyMessage& license_request, const CdmKeyResponse& license_response); virtual bool HasInitData() { return static_cast(stored_init_data_); } virtual bool IsKeyLoaded(const KeyId& key_id); @@ -139,8 +139,6 @@ class CdmLicense { PolicyEngine* policy_engine_; std::string server_url_; std::string client_token_; - CdmClientTokenType client_token_type_; - std::string device_id_; const CdmSessionId session_id_; std::unique_ptr stored_init_data_; bool initialized_; @@ -161,16 +159,16 @@ class CdmLicense { // Used for certificate based licensing CdmKeyMessage key_request_; - std::unique_ptr clock_; + std::unique_ptr clock_; // For testing // CdmLicense takes ownership of the clock. - CdmLicense(const CdmSessionId& session_id, Clock* clock); + CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock); // For entitlement key licensing. This holds the keys from the init_data. - // These keys are extracted from the pssh when we generate a license request. + // These keys are extracted from the PSSH when we generate a license request. // It is used to load content keys after we have received a license and - // entitelement keys. It is also used in updating the key status info. + // entitlement keys. It is also used in updating the key status info. std::vector wrapped_keys_; CdmLicenseKeyType license_key_type_; @@ -190,7 +188,6 @@ class CdmLicense { CORE_DISALLOW_COPY_AND_ASSIGN(CdmLicense); }; - } // namespace wvcdm #endif // WVCDM_CORE_LICENSE_H_ diff --git a/core/include/license_key_status.h b/core/include/license_key_status.h index 1c0d46f1..db7b9525 100644 --- a/core/include/license_key_status.h +++ b/core/include/license_key_status.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_LICENSE_KEY_STATUS_H_ #define WVCDM_CORE_LICENSE_KEY_STATUS_H_ @@ -59,7 +59,7 @@ class LicenseKeys { virtual bool MeetsSecurityLevelConstraints(const KeyId& key_id); // Applies a resolution and/or hdcp change to each key, updating their - // useability under their constraints. + // usability under their constraints. virtual void ApplyConstraints(uint32_t new_resolution, CryptoSession::HdcpCapability new_hdcp_level); diff --git a/core/include/oemcrypto_adapter.h b/core/include/oemcrypto_adapter.h index d39fbb37..33a10e1f 100644 --- a/core/include/oemcrypto_adapter.h +++ b/core/include/oemcrypto_adapter.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // #ifndef WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ #define WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ @@ -9,53 +9,70 @@ #include "wv_cdm_types.h" namespace wvcdm { +// Initialize OEMCrypto, then check the keybox and see if it is valid. If not, +// and OTA provisioning is supported, set needs_keybox_provisioning to true. +// If the keybox is not valid and OTA provisioning is not supported, set +// needs_keybox_provisioning to false and use L3 only. +OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( + bool* needs_keybox_provisioning); + +// This tells the OEMCrypto adapter to ignore the next |count| keyboxes and +// report that it needs provisioning instead. +OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count); + +// This tells the OEMCrypto adapter to allow the device to continue with a +// test keybox. Otherwise, the keybox is reported as invalid. +OEMCryptoResult OEMCrypto_SetAllowTestKeybox(bool allow); // This attempts to open a session at the desired security level. // If one level is not available, the other will be used instead. OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, - SecurityLevel level); + RequestedSecurityLevel level); OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox, size_t keyBoxLength, - SecurityLevel level); -OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(SecurityLevel level); + RequestedSecurityLevel level); OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength, - SecurityLevel level); + RequestedSecurityLevel level); OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength, - SecurityLevel level); -uint32_t OEMCrypto_APIVersion(SecurityLevel level); -uint32_t OEMCrypto_MinorAPIVersion(SecurityLevel level); -const char* OEMCrypto_SecurityLevel(SecurityLevel level); -OEMCryptoResult OEMCrypto_GetHDCPCapability(SecurityLevel level, + RequestedSecurityLevel level); +uint32_t OEMCrypto_APIVersion(RequestedSecurityLevel level); +uint32_t OEMCrypto_MinorAPIVersion(RequestedSecurityLevel level); +OEMCrypto_Security_Level OEMCrypto_SecurityLevel(RequestedSecurityLevel level); +OEMCryptoResult OEMCrypto_GetHDCPCapability(RequestedSecurityLevel level, OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* maximum); -bool OEMCrypto_SupportsUsageTable(SecurityLevel level); -bool OEMCrypto_IsAntiRollbackHwPresent(SecurityLevel level); -OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(SecurityLevel level, +bool OEMCrypto_SupportsUsageTable(RequestedSecurityLevel level); +bool OEMCrypto_IsAntiRollbackHwPresent(RequestedSecurityLevel level); +OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(RequestedSecurityLevel level, size_t* count); -OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(SecurityLevel level, +OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(RequestedSecurityLevel level, size_t* maximum); -uint8_t OEMCrypto_Security_Patch_Level(SecurityLevel level); +uint8_t OEMCrypto_Security_Patch_Level(RequestedSecurityLevel level); OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod( - SecurityLevel level); -uint32_t OEMCrypto_SupportedCertificates(SecurityLevel level); -OEMCryptoResult OEMCrypto_CreateUsageTableHeader(SecurityLevel level, + RequestedSecurityLevel level); +uint32_t OEMCrypto_SupportedCertificates(RequestedSecurityLevel level); +OEMCryptoResult OEMCrypto_CreateUsageTableHeader(RequestedSecurityLevel level, uint8_t* header_buffer, size_t* header_buffer_length); -OEMCryptoResult OEMCrypto_LoadUsageTableHeader(SecurityLevel level, +OEMCryptoResult OEMCrypto_LoadUsageTableHeader(RequestedSecurityLevel level, const uint8_t* buffer, size_t buffer_length); -OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(SecurityLevel level, +OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(RequestedSecurityLevel level, uint32_t new_table_size, uint8_t* header_buffer, size_t* header_buffer_length); -uint32_t OEMCrypto_GetAnalogOutputFlags(SecurityLevel level); -const char* OEMCrypto_BuildInformation(SecurityLevel level); -uint32_t OEMCrypto_ResourceRatingTier(SecurityLevel level); -uint32_t OEMCrypto_SupportsDecryptHash(SecurityLevel level); -size_t OEMCrypto_MaximumUsageTableHeaderSize(SecurityLevel level); +uint32_t OEMCrypto_GetAnalogOutputFlags(RequestedSecurityLevel level); +OEMCryptoResult OEMCrypto_BuildInformation(char* buffer, size_t* buffer_length, + RequestedSecurityLevel level); +uint32_t OEMCrypto_ResourceRatingTier(RequestedSecurityLevel level); +uint32_t OEMCrypto_SupportsDecryptHash(RequestedSecurityLevel level); +size_t OEMCrypto_MaximumUsageTableHeaderSize(RequestedSecurityLevel level); OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, size_t* public_cert_length, - SecurityLevel level); + RequestedSecurityLevel level); +OEMCrypto_WatermarkingSupport OEMCrypto_GetWatermarkingSupport( + RequestedSecurityLevel level); +OEMCryptoResult OEMCrypto_ProductionReady(RequestedSecurityLevel level); } // namespace wvcdm /* The following functions are deprecated in OEMCrypto v13. They are defined @@ -95,7 +112,7 @@ typedef struct { const uint8_t* key_control; } OEMCrypto_KeyObject_V14; -// Backwards compitiblity between v14 and v13. +// Backwards compatibility between v14 and v13. OEMCryptoResult OEMCrypto_LoadKeys_Back_Compat( OEMCrypto_SESSION session, const uint8_t* message, size_t message_length, const uint8_t* signature, size_t signature_length, diff --git a/core/include/okp_fallback_policy.h b/core/include/okp_fallback_policy.h new file mode 100644 index 00000000..37a79de8 --- /dev/null +++ b/core/include/okp_fallback_policy.h @@ -0,0 +1,123 @@ +// 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 WVCDM_CORE_OKP_FALLBACK_POLICY_H_ +#define WVCDM_CORE_OKP_FALLBACK_POLICY_H_ +#include + +#include +#include + +#include "clock.h" +#include "disallow_copy_and_assign.h" +#include "file_store.h" +#include "okp_info.h" + +namespace wvcdm { +class DeviceFiles; +// OTA Keybox Provisioning (OKP) +namespace okp { +static constexpr int64_t kSecondsPerHour = 60 * 60; +static constexpr int64_t kSecondsPerDay = kSecondsPerHour * 24; +// Initial backoff duration. Subsequent backoff durations for the +// same engine will double its previous duration. +static constexpr int64_t kAverageInitialBackoffDuration = kSecondsPerDay; +static constexpr int64_t kFastBackoffDuration = 30; // 30 seconds. +static constexpr int64_t kInitialBackoffDurationDelta = kSecondsPerHour * 12; +// Minimum backoff duration which an device will be required to +// backoff the first time. +static constexpr int64_t kMinInitialBackoffDuration = + kAverageInitialBackoffDuration - kInitialBackoffDurationDelta; +static constexpr int64_t kMaxInitialBackoffDuration = + kAverageInitialBackoffDuration + kInitialBackoffDurationDelta; + +// SystemFallbackPolicy is a centralized OKP state manager which allows +// multiple CDM engines to communicate between each other. In a production +// build, there should only be at most one SystemFallbackPolicy instance. +class SystemFallbackPolicy { + public: + // Creates a new instance of SystemFallbackPolicy. If there exists + // OKP information for the device in storage, it will be loaded and + // the system policy will resume from its previous state. If no + // OKP information exists, then the policy begins new. + // Caller should immediately mark the fallback policy as requiring + // provisioning. + static std::unique_ptr Create(); + // Creates a new instance of SystemFallbackPolicy for testing. + // The testing instance of SystemFallbackPolicy behaves similar to a + // production instance, except that it will not use device storage. + // Optionally, a fake clock may be used for timestamp operations + // and/or fake data may be used to initialize the policy. + // Params: + // - |info| (optional) + // Fake device OKP info to use as a resume point. If not + // specified, then policy begins the same as if no OKP + // device info exists. + // - |clock| (optional) + // Fake/mock clock to be used instead of the CDM's default + // Clock. + static std::unique_ptr CreateForTesting( + wvutil::Clock* clock = nullptr); + static std::unique_ptr CreateForTesting( + const SystemFallbackInfo& info, wvutil::Clock* clock = nullptr); + + // == System Info == + const SystemFallbackInfo& info() const { return info_; } + SystemState state() const { return info_.state(); } + void MarkNeedsProvisioning(); + void TriggerFallback(); + void MarkProvisioned(); + + bool IsProvisioned(); + bool IsInFallbackMode(); + + void SetDefaultBackoffDurationRules(); + void SetFastBackoffDurationRules(); + + ~SystemFallbackPolicy(); + + private: + SystemFallbackPolicy(); + + // Checks the device's file system for OKP info and loads it. + // If the info does not exist, policy begins fresh. + void TryRestore(); + + void StoreInfo(); + + int64_t GenerateInitialBackoffDuration(); + + int64_t GetSecondsSinceBackoffStart() const; + void EndBackoffPeriod(); + + void SetClockForTesting(wvutil::Clock* clock) { + clock_ref_ = (clock == nullptr) ? &clock_ : clock; + } + int64_t GetCurrentTime() const { return clock_ref_->GetCurrentTime(); } + + bool IsTestMode() const; + + SystemFallbackInfo info_; + + // When |fast_fallback_| is true, falling back only lasts a few + // seconds, and exponential backoff is disabled. + bool fast_fallback_ = false; + + // Handle for the DeviceFiles instance used to store the OKP + // information. + // Not set for test instances. + std::unique_ptr fs_; + std::unique_ptr device_files_; + + wvutil::Clock clock_; // System clock + wvutil::Clock* clock_ref_ = nullptr; // Pointer to clock to be used. + + // All public methods must lock to protect from simultaneous + // engine access. + mutable std::mutex mutex_; + + CORE_DISALLOW_COPY_AND_ASSIGN(SystemFallbackPolicy); +}; // class SystemFallbackPolicy +} // namespace okp +} // namespace wvcdm +#endif // WVCDM_CORE_OKP_FALLBACK_POLICY_H_ diff --git a/core/include/okp_info.h b/core/include/okp_info.h new file mode 100644 index 00000000..add43ea9 --- /dev/null +++ b/core/include/okp_info.h @@ -0,0 +1,79 @@ +// 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 WVCDM_CORE_OKP_SYSTEM_INFO_H_ +#define WVCDM_CORE_OKP_SYSTEM_INFO_H_ + +#include + +namespace wvcdm { +// OTA Keybox Provisioning (OKP) +namespace okp { +enum class SystemState { + kUnknown = 0, + kNeedsProvisioning = 1, + kFallbackMode = 2, // Fallback indicates provisioning is needed. + kProvisioned = 3 + // Note: "Not needed" is represented by an absence of info. +}; +// Converts a SystemState value to a human readable string. Intended +// to be used for debug logging. +const char* SystemStateToString(SystemState state); + +// Container for all the device information related to OKP. +class SystemFallbackInfo { + public: + SystemState state() const { return state_; } + void SetState(SystemState state) { state_ = state; } + + bool HasFirstCheckedTime() const { return first_checked_time_ != 0; } + int64_t first_checked_time() const { return first_checked_time_; } + void SetFirstCheckedTime(int64_t time) { + first_checked_time_ = (time > 0 ? time : 0); + } + + bool HasBackoffStartTime() const { return backoff_start_time_ > 0; } + int64_t backoff_start_time() const { return backoff_start_time_; } + void SetBackoffStartTime(int64_t time) { + backoff_start_time_ = (time > 0 ? time : 0); + } + void ClearBackoffStartTime() { backoff_start_time_ = 0; } + + bool HasBackoffDuration() const { return backoff_duration_ > 0; } + int64_t backoff_duration() const { return backoff_duration_; } + void SetBackoffDuration(int64_t duration) { + backoff_duration_ = (duration > 0 ? duration : 0); + } + void DoubleBackoffDuration() { backoff_duration_ *= 2; } + void ClearBackoffDuration() { backoff_duration_ = 0; } + + bool HasProvisioningTime() const { return provisioning_time_ != 0; } + int64_t provisioning_time() const { return provisioning_time_; } + void SetProvisioningTime(int64_t time) { + provisioning_time_ = (time > 0 ? time : 0); + } + void ClearProvisioningTime() { provisioning_time_ = 0; } + + void Clear() { + state_ = SystemState::kUnknown; + first_checked_time_ = 0; + backoff_start_time_ = 0; + backoff_duration_ = 0; + provisioning_time_ = 0; + } + + bool operator==(const SystemFallbackInfo& other) const; + bool operator!=(const SystemFallbackInfo& other) const { + return !(*this == other); + } + + private: + SystemState state_ = SystemState::kUnknown; + int64_t first_checked_time_ = 0; + int64_t backoff_start_time_ = 0; + int64_t backoff_duration_ = 0; + int64_t provisioning_time_ = 0; +}; // class SystemFallbackInfo +} // namespace okp +} // namespace wvcdm +#endif // WVCDM_CORE_OKP_SYSTEM_INFO_H_ diff --git a/core/include/ota_keybox_provisioner.h b/core/include/ota_keybox_provisioner.h new file mode 100644 index 00000000..0bb41dd5 --- /dev/null +++ b/core/include/ota_keybox_provisioner.h @@ -0,0 +1,85 @@ +// 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 WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ +#define WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ + +#include +#include + +#include "client_identification.h" +#include "disallow_copy_and_assign.h" +#include "metrics_collections.h" +#include "wv_cdm_types.h" + +namespace wvcdm { +class CryptoSession; +namespace okp { +class SystemFallbackPolicy; +} // namespace okp + +// A CdmEngine-specific OTA keybox provisioning context. +class OtaKeyboxProvisioner { + public: + // Creates a new OtaKeyboxProvisioner. + // Checks for the system fallback policy and if the device + // requires provisioning. + // |crypto_metrics| - CryptoMetrics instance that is used in the + // the calling EngineMetrics. + static std::unique_ptr Create( + metrics::CryptoMetrics* crypto_metrics); + static std::unique_ptr CreateForTesting( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy); + + ~OtaKeyboxProvisioner(); + + // Returns true if the underlying SystemFallbackPolicy is + // provisioned. + // Note: This may change without a call to HandleProvisioningResponse() + // on this instance as provisioning is a system-wide responsibility. + bool IsProvisioned() const; + bool IsInFallbackMode() const; + + // Indicates that a request has been successfully generated. + uint32_t request_generated() const { return request_generated_; } + // Indicates that a response has been successfully received by + // this provisioner. + bool response_received() const { return response_received_; } + + // === Request/response API === + + // Generates and prepares a OTA Keybox Provisioning request, packing + // it into a SignedProvisioningMessage. + // |default_url| will be populated with the URL of the provisioning + // server used for OTA keybox provisioning. + CdmResponseType GetProvisioningRequest(std::string* request, + std::string* default_url); + // Receives, unwraps and loads the OTA Keybox Provisioning response. + // |response| must be a SignedProvisioningMessage containing an + // OTA keybox provisioning response. + CdmResponseType HandleProvisioningResponse(const std::string& response); + + private: + OtaKeyboxProvisioner(std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy); + + bool Init(); + + void CleanUp(); + + std::unique_ptr crypto_session_; + ClientIdentification client_id_; + + // Pointer to the system-wide okp::SystemFallbackPolicy. This class + // does not take ownership of this pointer. + okp::SystemFallbackPolicy* fallback_policy_ = nullptr; + + // These flags are for debugging purposes. + bool request_generated_ = false; + bool response_received_ = false; + + CORE_DISALLOW_COPY_AND_ASSIGN(OtaKeyboxProvisioner); +}; // class OtaKeyboxProvisioner +} // namespace wvcdm +#endif // WVCDM_CORE_OTA_KEYBOX_PROVISIONER_H_ diff --git a/core/include/policy_engine.h b/core/include/policy_engine.h index d71c2387..9ef54364 100644 --- a/core/include/policy_engine.h +++ b/core/include/policy_engine.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_POLICY_ENGINE_H_ #define WVCDM_CORE_POLICY_ENGINE_H_ @@ -15,12 +15,15 @@ #include "license_protocol.pb.h" #include "wv_cdm_types.h" +namespace wvutil { +class Clock; +} + namespace wvcdm { using video_widevine::LicenseIdentification; using video_widevine::WidevinePsshData_EntitledKey; -class Clock; class CryptoSession; class PolicyTimers; class WvCdmEventListener; @@ -162,7 +165,7 @@ class PolicyEngine { // Test only methods // set_clock alters ownership of the passed-in pointer. - void set_clock(Clock* clock); + void set_clock(wvutil::Clock* clock); void SetSecurityLevelForTest(CdmSecurityLevel security_level); @@ -195,7 +198,7 @@ class PolicyEngine { CryptoSession* crypto_session_; std::unique_ptr policy_timers_; - std::unique_ptr clock_; + std::unique_ptr clock_; CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine); }; diff --git a/core/include/policy_timers.h b/core/include/policy_timers.h index c0df728d..4287b0cf 100644 --- a/core/include/policy_timers.h +++ b/core/include/policy_timers.h @@ -1,6 +1,6 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_POLICY_TIMERS_H_ #define WVCDM_CORE_POLICY_TIMERS_H_ diff --git a/core/include/policy_timers_v15.h b/core/include/policy_timers_v15.h index c1344d2e..a9f42c2d 100644 --- a/core/include/policy_timers_v15.h +++ b/core/include/policy_timers_v15.h @@ -1,6 +1,6 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_POLICY_TIMERS_V15_H_ #define WVCDM_CORE_POLICY_TIMERS_V15_H_ @@ -29,7 +29,7 @@ class PolicyTimersV15 : public PolicyTimers { public: PolicyTimersV15() : grace_period_end_time_(0) {} - virtual ~PolicyTimersV15() {} + ~PolicyTimersV15() override {} // UpdateLicense is used in handling a license response, a renewal response, // or when restoring or releasing a persistent license. diff --git a/core/include/policy_timers_v16.h b/core/include/policy_timers_v16.h index 924b5ea1..2e0c472d 100644 --- a/core/include/policy_timers_v16.h +++ b/core/include/policy_timers_v16.h @@ -1,6 +1,6 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_POLICY_TIMERS_V16_H_ #define WVCDM_CORE_POLICY_TIMERS_V16_H_ @@ -26,7 +26,7 @@ class PolicyTimersV16 : public PolicyTimers { public: PolicyTimersV16() {} - virtual ~PolicyTimersV16() {} + ~PolicyTimersV16() override {} // UpdateLicense is used in handling a license response, a renewal response, // or when restoring or releasing a persistent license. diff --git a/core/include/privacy_crypto.h b/core/include/privacy_crypto.h index 0ba6ba5e..eea1d3bd 100644 --- a/core/include/privacy_crypto.h +++ b/core/include/privacy_crypto.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // Description: // Declaration of classes representing AES and RSA public keys used diff --git a/core/include/properties.h b/core/include/properties.h index 04910ead..72b8e376 100644 --- a/core/include/properties.h +++ b/core/include/properties.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_PROPERTIES_H_ #define WVCDM_CORE_PROPERTIES_H_ @@ -66,6 +66,7 @@ class Properties { } static bool GetCompanyName(std::string* company_name); static bool GetModelName(std::string* model_name); + static bool GetModelYear(std::string* model_year); static bool GetArchitectureName(std::string* arch_name); static bool GetDeviceName(std::string* device_name); static bool GetProductName(std::string* product_name); diff --git a/core/include/service_certificate.h b/core/include/service_certificate.h index 75f2eec5..ced4f4c1 100644 --- a/core/include/service_certificate.h +++ b/core/include/service_certificate.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // #ifndef WVCDM_CORE_SERVICE_CERTIFICATE_H_ #define WVCDM_CORE_SERVICE_CERTIFICATE_H_ @@ -38,8 +38,8 @@ class ServiceCertificate { const std::string& provider_id() const { return provider_id_; } // Verify the signature for a message. - virtual CdmResponseType VerifySignedMessage(const std::string& message, - const std::string& signature); + virtual CdmResponseType VerifySignedMessage( + const std::string& message, const std::string& signature) const; // Encrypt the ClientIdentification message for a provisioning or // licensing request. Encryption is performed using the current @@ -50,7 +50,7 @@ class ServiceCertificate { virtual CdmResponseType EncryptClientId( CryptoSession* crypto_session, const video_widevine::ClientIdentification* clear_client_id, - video_widevine::EncryptedClientIdentification* encrypted_client_id); + video_widevine::EncryptedClientIdentification* encrypted_client_id) const; // Helper methods static bool GetRequest(CdmKeyMessage* request); @@ -63,7 +63,7 @@ class ServiceCertificate { // string to contain the decrypted data on return, and may not be null. // returns NO_ERROR if successful or an appropriate error code otherwise. virtual CdmResponseType EncryptRsaOaep(const std::string& plaintext, - std::string* ciphertext); + std::string* ciphertext) const; // Track whether object holds valid certificate bool has_certificate_; diff --git a/core/include/system_id_extractor.h b/core/include/system_id_extractor.h new file mode 100644 index 00000000..e98f03b8 --- /dev/null +++ b/core/include/system_id_extractor.h @@ -0,0 +1,68 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#ifndef WVCDM_CORE_SYSTEM_ID_EXTRACTOR_H_ +#define WVCDM_CORE_SYSTEM_ID_EXTRACTOR_H_ + +#include + +#include "wv_cdm_types.h" + +namespace wvutil { +class FileSystem; +} // namespace wvutil +namespace wvcdm { +class CryptoSession; +class DeviceFiles; + +// System ID extractor will find and extract the system ID of the device. +// Handles the different cases where the system ID may be found in +// different place. +class SystemIdExtractor { + public: + SystemIdExtractor(RequestedSecurityLevel security_level, + CryptoSession* crypto_session, wvutil::FileSystem* fs); + virtual ~SystemIdExtractor() {} + + // Disallow copy and move. + SystemIdExtractor(const SystemIdExtractor&) = delete; + SystemIdExtractor(SystemIdExtractor&&) = delete; + SystemIdExtractor& operator=(const SystemIdExtractor&) = delete; + SystemIdExtractor& operator=(SystemIdExtractor&&) = delete; + + virtual bool ExtractSystemId(uint32_t* system_id); + + // Extracts the system ID from a keybox key data (aka CA token). + static bool ExtractSystemIdFromKeyboxData(const std::string& key_data, + uint32_t* system_id); + // Extracts the system ID from a serialized OEM certificate. + static bool ExtractSystemIdFromOemCert(const std::string& oem_cert, + uint32_t* system_id); + + void SetDeviceFilesForTesting(DeviceFiles* device_files) { + test_device_files_ = device_files; + } + + private: + // Extracts the system ID from keybox-based OEMCrypto implementations. + // System ID is expected to be found in the keybox data. Devices + // which require OTA keybox provisioning will return a null system ID. + bool ExtractSystemIdProv20(uint32_t* system_id); + // Extracts the system ID from OEM certificate-based OEMCrypto + // implementations. System ID is expected to be in the manufacturers + // intermediate X.509 certificate. + bool ExtractSystemIdProv30(uint32_t* system_id); + // Extracts the system ID from BCC-based OEMCrypto implementations. + // System ID is expected to be found in the stored OEM certificate + // for the provided origin-identifier, after BCC provisioning. + // Clients which have not performed BCC provisioning will return + // a null system ID. + bool ExtractSystemIdProv40(uint32_t* system_id); + + RequestedSecurityLevel security_level_ = kLevelDefault; + CryptoSession* crypto_session_ = nullptr; + wvutil::FileSystem* fs_ = nullptr; + DeviceFiles* test_device_files_ = nullptr; +}; +} // namespace wvcdm +#endif // WVCDM_CORE_SYSTEM_ID_EXTRACTOR_H_ diff --git a/core/include/usage_table_header.h b/core/include/usage_table_header.h index d63bdcb1..66aef9b3 100644 --- a/core/include/usage_table_header.h +++ b/core/include/usage_table_header.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_USAGE_TABLE_HEADER_H_ #define WVCDM_CORE_USAGE_TABLE_HEADER_H_ @@ -41,7 +41,7 @@ namespace wvcdm { // initialized/terminated. // // Sessions and licenses are however handled by CdmSession and so most -// calls to maniplate the usage table header related to usage entries +// calls to manipulate the usage table header related to usage entries // are by CdmSession. // // Upgrades from a fixed size usage table (supported by previous @@ -53,20 +53,36 @@ class UsageTableHeader { UsageTableHeader(); virtual ~UsageTableHeader() {} - // |crypto_session| is used to create or load a usage master table and - // not cached beyound this call. + // |crypto_session| is used to create or load a usage master table + // and not cached beyound this call. + // First attempts to restore the usage table from the device files. + // If restoring fails, then a new usage table is created. + // Note: No OEMCrypto session for the same security level should be + // opened before calling. + // Threading: Method requires care of caller for exclusive access + // (ie. test setup or CryptoSession). bool Init(CdmSecurityLevel security_level, CryptoSession* crypto_session); - // |persistent_license| false indicates usage info record + // Adds a new entry to the OEMCrypto usage table header, and associates + // the entry with the provided |crypto_session|. The index of the new + // usage entry will be returned by |usage_entry_number|. + // + // Type of entry depends on the value of |persistent_license|: + // false - usage info / secure stop record + // true - offline license + // + // Threading: Method takes exclusive use of |usage_table_header_lock_|. virtual CdmResponseType AddEntry(CryptoSession* crypto_session, bool persistent_license, const CdmKeySetId& key_set_id, const std::string& usage_info_filename, const CdmKeyResponse& license_message, uint32_t* usage_entry_number); + // Threading: Method takes exclusive use of |usage_table_header_lock_|. virtual CdmResponseType LoadEntry(CryptoSession* crypto_session, const CdmUsageEntry& usage_entry, uint32_t usage_entry_number); + // Threading: Method takes exclusive use of |usage_table_header_lock_|. virtual CdmResponseType UpdateEntry(uint32_t usage_entry_number, CryptoSession* crypto_session, CdmUsageEntry* usage_entry); @@ -76,6 +92,8 @@ class UsageTableHeader { // to InvalidateEntry and MoveEntry are made. // If |defrag_table| is true, the table will be defragmented after // the entry has been invalidated. + // + // Threading: Method takes exclusive use of |usage_table_header_lock_|. virtual CdmResponseType InvalidateEntry(uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, @@ -87,8 +105,14 @@ class UsageTableHeader { // when InvalidateEntry is mocked. This allows one to test methods that are // dependent on InvalidateEntry without having to set expectations // for the objects that InvalidateEntry depends on. + // + // Threading: Method requires care of caller for exclusive access. void InvalidateEntryForTest(uint32_t usage_entry_number); + // == Table information methods == + // Threading: None of the following are thread safe. Intended for + // testing or internal use. + size_t size() { return usage_entry_info_.size(); } size_t potential_table_capacity() const { return potential_table_capacity_; } @@ -109,7 +133,7 @@ class UsageTableHeader { } // Set the reference clock used for the method GetCurrentTime(). - void SetClock(Clock* clock) { + void SetClock(wvutil::Clock* clock) { if (clock != nullptr) clock_ref_ = clock; else @@ -125,6 +149,81 @@ class UsageTableHeader { } private: + // == Initialization methods == + // These should only be called during table initialization. + // All parameters are assumed valid. + + // Creates a new, empty usage table. Any existing usage table files + // will be deleted. + // Threading: Method takes exclusive use of |usage_table_header_lock_| + // when required. + bool CreateNewTable(CryptoSession* const crypto_session); + // Attempts to restore the usage table from persistent storage, and + // loads the usage table header into OEMCrypto. + // Note: No other OEMCrypto session should be opened before calling. + // Threading: Method takes exclusive use of |usage_table_header_lock_| + // when required. + bool RestoreTable(CryptoSession* const crypto_session); + + // Performs a check that there are no open OEMCrypto sessions for + // the current security level of the usage table. + // Threading: No special threading requirements. + bool OpenSessionCheck(CryptoSession* const crypto_session); + // Performs a check that the OEMCrypto table can support at least + // one more entry if the table is at or near the reported capacity. + // If this check fails, a new usage table SHOULD be created. + // Threading: Method requires caller to take exclusive use of + // |usage_table_header_lock_|. + bool CapacityCheck(CryptoSession* const crypto_session); + + // Attempts to determine the capacity of the OEMCrypto usage table. + // Sets the result to |potential_table_capacity_|. + // Threading: Method requires caller to take exclusive use of + // |usage_table_header_lock_|. + bool DetermineTableCapacity(CryptoSession* crypto_session); + + // == Table operation methods == + // Threading: All of the following methods require caller to take + // exclusive use of |usage_table_header_lock_|. + + // Creates a new entry for the provided crypto session. If the + // entry is created successfully in OEMCrypto, then a new entry + // info is added to the table's vector of entry info. + CdmResponseType CreateEntry(CryptoSession* const crypto_session, + uint32_t* usage_entry_number); + + // Attempts to relocate a newly created usage entry associated with + // the provided |crypto_session| to the lowest unoccupied position in + // the table. + // |usage_entry_number| is treated as both an input and output. + // Returns NO_ERROR so long as no internal operation fails, + // regardless of whether the entry was moved or not. + CdmResponseType RelocateNewEntry(CryptoSession* const crypto_session, + uint32_t* usage_entry_number); + + // Checks if the specified |usage_entry_number| is known to be + // unoccupied (released). + bool IsEntryUnoccupied(const uint32_t usage_entry_number) const; + + // SetOfflineEntryInfo() and SetUsageInfoEntryInfo() populate the + // entry meta-data with the required information based on the type + // of entry. + void SetOfflineEntryInfo(const uint32_t usage_entry_number, + const std::string& key_set_id, + const CdmKeyResponse& license_message); + void SetUsageInfoEntryInfo(const uint32_t usage_entry_number, + const std::string& key_set_id, + const std::string& usage_info_file_name); + + // Shrinks the table, removing all trailing unoccupied entries. + // |usage_entry_info_| will be resized appropriately. + // Caller must store the table after a successful call. + CdmResponseType RefitTable(CryptoSession* const crypto_session); + + virtual CdmResponseType InvalidateEntryInternal( + uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, + metrics::CryptoMetrics* metrics); + CdmResponseType MoveEntry(uint32_t from /* usage entry number */, const CdmUsageEntry& from_usage_entry, uint32_t to /* usage entry number */, @@ -140,7 +239,7 @@ class UsageTableHeader { // Stores the usage table and it's info. This will increment // |store_table_counter_| if successful. - bool StoreTable(DeviceFiles* device_files); + bool StoreTable(); CdmResponseType Shrink(metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete); @@ -153,8 +252,6 @@ class UsageTableHeader { // evicted. CdmResponseType ReleaseOldestEntry(metrics::CryptoMetrics* metrics); - virtual bool is_inited() { return is_inited_; } - // Performs and LRU upgrade on all loaded CdmUsageEntryInfo from a // device file that had not yet been upgraded to use the LRU data. virtual bool LruUpgradeAllUsageEntries(); @@ -212,17 +309,16 @@ class UsageTableHeader { // usage_table_header. Usage entries should use the file system provided // by CdmSession. std::unique_ptr device_files_; - std::unique_ptr file_system_; - CdmSecurityLevel security_level_; - SecurityLevel requested_security_level_; + std::unique_ptr file_system_; + CdmSecurityLevel security_level_ = kSecurityLevelUninitialized; + RequestedSecurityLevel requested_security_level_ = kLevelDefault; CdmUsageTableHeader usage_table_header_; std::vector usage_entry_info_; - // Lock to ensure that a single object is created for each security level - // and data member to represent whether an object has been correctly - // initialized. - bool is_inited_; + // Table is sync with persistent storage and can be used by the CDM + // to interact with OEMCrypto. + bool is_initialized_ = false; // Synchonizes access to the Usage Table Header and bookkeeping // data-structures @@ -232,11 +328,11 @@ class UsageTableHeader { // |clock_| represents the system's "wall clock". For the clock's purpose // we do not need a more secure clock. - Clock clock_; + wvutil::Clock clock_; // |clock_ref_| is a pointer to the clock which is to be used for // obtaining the current time. By default, this points to the internal - // |clock_| variable, however, it can be overrided for testing purpose. - Clock* clock_ref_; + // |clock_| variable, however, it can be overridden for testing purpose. + wvutil::Clock* clock_ref_; // The maximum number of entries that the underlying OEMCrypto // implementation can support. Some implementations might not @@ -252,6 +348,7 @@ class UsageTableHeader { #if defined(UNIT_TEST) // Test related declarations friend class UsageTableHeaderTest; + FRIEND_TEST(UsageTableHeaderTest, Shrink_NoneOfTable); FRIEND_TEST(UsageTableHeaderTest, Shrink_PartOfTable); FRIEND_TEST(UsageTableHeaderTest, Shrink_AllOfTable); diff --git a/core/include/wv_cdm_constants.h b/core/include/wv_cdm_constants.h index 3db2a051..4aaaaff6 100644 --- a/core/include/wv_cdm_constants.h +++ b/core/include/wv_cdm_constants.h @@ -1,54 +1,71 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_WV_CDM_CONSTANTS_H_ #define WVCDM_CORE_WV_CDM_CONSTANTS_H_ +#include #include namespace wvcdm { -static const size_t KEY_CONTROL_SIZE = 16; -static const size_t KEY_ID_SIZE = 16; -static const size_t KEY_IV_SIZE = 16; -static const size_t KEY_PAD_SIZE = 16; -static const size_t CONTENT_KEY_SIZE = 16; -static const size_t SERVICE_KEY_SIZE = 16; -static const size_t MAC_KEY_SIZE = 32; -static const size_t KEYBOX_KEY_DATA_SIZE = 72; -static const size_t SRM_REQUIREMENT_SIZE = 12; +constexpr size_t KEY_CONTROL_SIZE = 16; +constexpr size_t KEY_ID_SIZE = 16; +constexpr size_t KEY_IV_SIZE = 16; +constexpr size_t KEY_PAD_SIZE = 16; +constexpr size_t CONTENT_KEY_SIZE = 16; +constexpr size_t SERVICE_KEY_SIZE = 16; +constexpr size_t MAC_KEY_SIZE = 32; +constexpr size_t ENTITLEMENT_KEY_SIZE = 32; +constexpr size_t KEYBOX_KEY_DATA_SIZE = 72; +constexpr size_t SRM_REQUIREMENT_SIZE = 12; + +constexpr size_t LICENSE_PROTOCOL_2_1_PADDING = 16; // Initial estimate of certificate size. Code that // uses this estimate should be able to adapt to a larger or smaller size. -static const size_t CERTIFICATE_DATA_SIZE = 4 * 1024; +constexpr size_t CERTIFICATE_DATA_SIZE = 4 * 1024; // Use 0 to represent never expired license as specified in EME spec // (NaN in JS translates to 0 in unix timestamp). -static const int64_t NEVER_EXPIRES = 0; -static const int64_t UNLIMITED_DURATION = 0; +constexpr int64_t NEVER_EXPIRES = 0; +constexpr int64_t UNLIMITED_DURATION = 0; +constexpr int64_t INVALID_TIME = -1; + +// Not a valid system ID. Used as a placeholder for systems without an ID. +// Will not be accepted for DRM provisioning requests or license requests. +static constexpr uint32_t NULL_SYSTEM_ID = + static_cast(std::numeric_limits::max()); // This is the lower limit. For OEMCrypto v16+ one can query and find how many // are supported -static constexpr size_t kMinimumUsageTableEntriesSupported = 200; +constexpr size_t kMinimumUsageTableEntriesSupported = 200; // Resource rating tiers -static const uint32_t RESOURCE_RATING_TIER_LOW = 1u; -static const uint32_t RESOURCE_RATING_TIER_MEDIUM = 2u; -static const uint32_t RESOURCE_RATING_TIER_HIGH = 3u; -static const uint32_t RESOURCE_RATING_TIER_VERY_HIGH = 4u; -static const uint32_t RESOURCE_RATING_TIER_MIN = RESOURCE_RATING_TIER_LOW; -static const uint32_t RESOURCE_RATING_TIER_MAX = RESOURCE_RATING_TIER_VERY_HIGH; +constexpr uint32_t RESOURCE_RATING_TIER_LOW = 1u; +constexpr uint32_t RESOURCE_RATING_TIER_MEDIUM = 2u; +constexpr uint32_t RESOURCE_RATING_TIER_HIGH = 3u; +constexpr uint32_t RESOURCE_RATING_TIER_VERY_HIGH = 4u; +constexpr uint32_t RESOURCE_RATING_TIER_MIN = RESOURCE_RATING_TIER_LOW; +constexpr uint32_t RESOURCE_RATING_TIER_MAX = RESOURCE_RATING_TIER_VERY_HIGH; // OEMCrypto features by version -static const uint32_t OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER = 15; +constexpr uint32_t OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER = 15; -static const char SESSION_ID_PREFIX[] = "sid"; -static const char ATSC_KEY_SET_ID_PREFIX[] = "atscksid"; -static const char KEY_SET_ID_PREFIX[] = "ksid"; -static const char KEY_SYSTEM[] = "com.widevine"; -static const char ATSC_APP_PACKAGE_NAME[] = "org.atsc"; +constexpr char SESSION_ID_PREFIX[] = "sid"; +constexpr char ATSC_KEY_SET_ID_PREFIX[] = "atscksid"; +constexpr char KEY_SET_ID_PREFIX[] = "ksid"; +constexpr char KEY_SYSTEM[] = "com.widevine"; +constexpr char ATSC_APP_PACKAGE_NAME[] = "org.atsc"; -// define query keys, values here +// Define query keys, values here. +// To expose these query items to Android update: +// android/mediadrm/src/WVDrmPlugin.cpp +// android/mediadrm/src_hidl/WVDrmPlugin.cpp +// Update test QueryStatus and QueryStatusL3 test for all possible outputs: +// android/cdm/test/request_license_test.cpp +// Update HIDL debug output: +// android/src_hidl/WVDrmFactory.cpp static const std::string QUERY_KEY_LICENSE_TYPE = "LicenseType"; // "Streaming", "Offline" static const std::string QUERY_KEY_PLAY_ALLOWED = @@ -61,6 +78,8 @@ static const std::string QUERY_KEY_LICENSE_DURATION_REMAINING = "LicenseDurationRemaining"; // non-negative integer denoting seconds static const std::string QUERY_KEY_PLAYBACK_DURATION_REMAINING = "PlaybackDurationRemaining"; // non-negative integer denoting seconds +static const std::string QUERY_KEY_RENTAL_DURATION_REMAINING = + "RentalDurationRemaining"; // non-negative integer denoting seconds static const std::string QUERY_KEY_RENEWAL_SERVER_URL = "RenewalServerUrl"; // url static const std::string QUERY_KEY_OEMCRYPTO_SESSION_ID = @@ -93,13 +112,21 @@ static const std::string QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION = static const std::string QUERY_KEY_DECRYPT_HASH_SUPPORT = "DecryptHashSupport"; static const std::string QUERY_KEY_PROVISIONING_MODEL = "ProvisioningModel"; static const std::string QUERY_KEY_MAX_USAGE_TABLE_ENTRIES = - "MaxNumberOfUsageTableEntries"; + "MaxUsageEntriesSupported"; static const std::string QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION = "OemCryptoApiMinorVersion"; +static const std::string QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES = + "AnalogOutputCapabilities"; +static const std::string QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT = + "CanDisableAnalogOutput"; +static const std::string QUERY_KEY_WATERMARKING_SUPPORT = "WatermarkingSupport"; +static const std::string QUERY_KEY_PRODUCTION_READY = "ProductionReady"; static const std::string QUERY_VALUE_TRUE = "True"; static const std::string QUERY_VALUE_FALSE = "False"; static const std::string QUERY_VALUE_NONE = "None"; +static const std::string QUERY_VALUE_SUPPORTED = "Supported"; +static const std::string QUERY_VALUE_UNKNOWN = "Unknown"; static const std::string QUERY_VALUE_STREAMING = "Streaming"; static const std::string QUERY_VALUE_OFFLINE = "Offline"; static const std::string QUERY_VALUE_SECURITY_LEVEL_L1 = "L1"; @@ -118,6 +145,12 @@ static const std::string QUERY_VALUE_HDCP_LEVEL_UNKNOWN = "HDCP-LevelUnknown"; static const std::string QUERY_VALUE_DRM_CERTIFICATE = "DrmCertificate"; static const std::string QUERY_VALUE_KEYBOX = "Keybox"; static const std::string QUERY_VALUE_OEM_CERTIFICATE = "OEMCertificate"; +static const std::string QUERY_VALUE_CGMS_A = "CGMS-A"; +static const std::string QUERY_VALUE_BOOT_CERTIFICATE_CHAIN = + "BootCertificateChain"; +static const std::string QUERY_VALUE_NOT_SUPPORTED = "NotSupported"; +static const std::string QUERY_VALUE_CONFIGURABLE = "Configurable"; +static const std::string QUERY_VALUE_ALWAYS_ON = "AlwaysOn"; static const std::string ISO_BMFF_VIDEO_MIME_TYPE = "video/mp4"; static const std::string ISO_BMFF_AUDIO_MIME_TYPE = "audio/mp4"; @@ -137,13 +170,13 @@ static const std::string HLS_METHOD_SAMPLE_AES = "SAMPLE-AES"; static const std::string HLS_IV_ATTRIBUTE = "IV"; static const std::string HLS_URI_ATTRIBUTE = "URI"; -static const char EMPTY_ORIGIN[] = ""; -static const char EMPTY_SPOID[] = ""; +constexpr char EMPTY_ORIGIN[] = ""; +constexpr char EMPTY_SPOID[] = ""; // Policy engine HDCP enforcement -static const uint32_t HDCP_UNSPECIFIED_VIDEO_RESOLUTION = 0; -static const int64_t HDCP_DEVICE_CHECK_INTERVAL = 10; -static const char EMPTY_APP_PACKAGE_NAME[] = ""; +constexpr uint32_t HDCP_UNSPECIFIED_VIDEO_RESOLUTION = 0; +constexpr int64_t HDCP_DEVICE_CHECK_INTERVAL = 10; +constexpr char EMPTY_APP_PACKAGE_NAME[] = ""; } // namespace wvcdm #endif // WVCDM_CORE_WV_CDM_CONSTANTS_H_ diff --git a/core/include/wv_cdm_event_listener.h b/core/include/wv_cdm_event_listener.h index 1713d172..1c030ff6 100644 --- a/core/include/wv_cdm_event_listener.h +++ b/core/include/wv_cdm_event_listener.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_ #define WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_ diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h index 5d6f8b9a..7ee68be8 100644 --- a/core/include/wv_cdm_types.h +++ b/core/include/wv_cdm_types.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_WV_CDM_TYPES_H_ #define WVCDM_CORE_WV_CDM_TYPES_H_ @@ -25,6 +25,7 @@ using CdmKeySetId = std::string; using RequestId = std::string; using CryptoResult = uint32_t; using CryptoSessionId = uint32_t; +using EntitledKeySessionId = uint32_t; using CdmAppParameterMap = std::map; using CdmQueryMap = std::map; using CdmUsageInfo = std::vector; @@ -416,6 +417,31 @@ enum CdmResponseType : int32_t { LOAD_USAGE_ENTRY_INVALID_SESSION = 361, RESTORE_OFFLINE_LICENSE_ERROR_3 = 362, NO_SRM_VERSION = 363, + SESSION_NOT_FOUND_23 = 364, + CERT_PROVISIONING_RESPONSE_ERROR_9 = 365, + CERT_PROVISIONING_RESPONSE_ERROR_10 = 366, + CLIENT_TOKEN_NOT_SET = 367, + USAGE_ENTRY_ALREADY_LOADED = 368, + PARSE_OKP_RESPONSE_ERROR = 369, + OKP_ALREADY_PROVISIONED = 370, + // The specific error code values below can be changed when merging master + // branch if there are conflicts. + PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR = 371, + GET_BOOT_CERTIFICATE_CHAIN_ERROR = 372, + GENERATE_CERTIFICATE_KEY_PAIR_ERROR = 373, + GENERATE_CERTIFICATE_KEY_PAIR_UNKNOWN_TYPE_ERROR = 374, + LOAD_OEM_CERTIFICATE_PRIVATE_KEY_ERROR = 375, + PROVISIONING_4_CRYPTO_SESSION_NOT_OPEN = 376, + PROVISIONING_4_FILE_SYSTEM_IS_NULL = 377, + PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES = 378, + PROVISIONING_4_RESPONSE_FAILED_TO_PARSE_MESSAGE = 379, + PROVISIONING_4_RESPONSE_HAS_ERROR_STATUS = 380, + PROVISIONING_4_RESPONSE_HAS_NO_CERTIFICATE = 381, + PROVISIONING_4_NO_PRIVATE_KEY = 382, + PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_2 = 383, + PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE = 384, + PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE = 385, + PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3 = 386, // Don't forget to add new values to // * core/test/test_printers.cpp. // * android/include/mapErrors-inl.h @@ -455,7 +481,7 @@ enum CdmLicenseKeyType : int32_t { kLicenseKeyTypeEntitlement, }; -enum SecurityLevel : uint32_t { kLevelDefault, kLevel3 }; +enum RequestedSecurityLevel : uint32_t { kLevelDefault, kLevel3 }; enum CdmSecurityLevel : int32_t { kSecurityLevelUninitialized, @@ -496,6 +522,7 @@ enum CdmClientTokenType : int32_t { kClientTokenDrmCert, kClientTokenOemCert, kClientTokenUninitialized, + kClientTokenBootCertChain, }; // kNonSecureUsageSupport - TEE does not provide any support for usage @@ -560,6 +587,25 @@ enum CdmKeySecurityLevel : int32_t { kKeySecurityLevelUnknown, }; +enum CdmProvisioningStatus : int32_t { + kProvisioned, + kUnknownProvisionStatus, + kNeedsDrmCertProvisioning, + kNeedsOemCertProvisioning, +}; + +enum CdmWatermarkingSupport : int32_t { + kWatermarkingNotSupported, + kWatermarkingConfigurable, + kWatermarkingAlwaysOn +}; + +enum CdmProductionReadiness : int32_t { + kProductionReadinessUnknown, + kProductionReadinessTrue, + kProductionReadinessFalse, +}; + class CdmKeyAllowedUsage { public: CdmKeyAllowedUsage() { Clear(); } @@ -789,6 +835,33 @@ class KeyMessage; class Request; class Key; +// Logging utilities for types defined above. +// Converts the different enum types to a human readable C-string for +// logging. Query strings values are used if available for the enum. +// These functions will fail silently to avoid double logging. +const char* CdmCertificateTypeToString(CdmCertificateType type); +const char* CdmClientTokenTypeToString(CdmClientTokenType type); +const char* CdmLicenseTypeToString(CdmLicenseType license_type); +const char* CdmOfflineLicenseStateToString( + CdmOfflineLicenseState license_state); +const char* CdmSecurityLevelToString(CdmSecurityLevel security_level); +const char* CdmUsageEntryStorageTypeToString(CdmUsageEntryStorageType type); +const char* RequestedSecurityLevelToString( + RequestedSecurityLevel security_level); +const char* CdmWatermarkingSupportToString(CdmWatermarkingSupport support); +const char* CdmProductionReadinessToString(CdmProductionReadiness readiness); +// Converts a generic, unknown enum value to a string representation +// containing its numeric value. +// The pointer returned from this function is thread_local. +const char* UnknownEnumValueToString(int value); + +// Both IdToString() and IdPtrToString() functions are used to convert +// session IDs, key set IDs or other CDM specific identifiers to a +// loggable format. +const char* IdToString(const std::string& id); +// Some CDM API function allow for optional string parameters to be +// provided as string pointers. +const char* IdPtrToString(const std::string* id); } // namespace wvcdm #endif // WVCDM_CORE_WV_CDM_TYPES_H_ diff --git a/core/src/Android.bp b/core/src/Android.bp index b0bdff09..5b6afc36 100644 --- a/core/src/Android.bp +++ b/core/src/Android.bp @@ -1,3 +1,16 @@ +// *** 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: + // SPDX-license-identifier-Apache-2.0 + // legacy_by_exception_only (by exception only) + default_applicable_licenses: ["vendor_widevine_license"], +} + cc_library { name: "libcdm_protos", diff --git a/core/src/buffer_reader.cpp b/core/src/buffer_reader.cpp index df648eb3..3601ac17 100644 --- a/core/src/buffer_reader.cpp +++ b/core/src/buffer_reader.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "buffer_reader.h" diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp index ec6fdc78..726763c8 100644 --- a/core/src/cdm_engine.cpp +++ b/core/src/cdm_engine.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "cdm_engine.h" @@ -12,6 +12,7 @@ #include #include #include +#include #include "cdm_random.h" #include "cdm_session.h" @@ -20,35 +21,25 @@ #include "device_files.h" #include "file_store.h" #include "log.h" +#include "okp_fallback_policy.h" +#include "ota_keybox_provisioner.h" #include "properties.h" #include "string_conversions.h" +#include "system_id_extractor.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" +namespace wvcdm { namespace { const uint64_t kReleaseSessionTimeToLive = 60; // seconds const uint32_t kUpdateUsageInformationPeriod = 60; // seconds - -wvcdm::CdmOfflineLicenseState MapDeviceFilesLicenseState( - wvcdm::DeviceFiles::LicenseState state) { - switch (state) { - case wvcdm::DeviceFiles::LicenseState::kLicenseStateActive: - return wvcdm::kLicenseStateActive; - case wvcdm::DeviceFiles::LicenseState::kLicenseStateReleasing: - return wvcdm::kLicenseStateReleasing; - default: - return wvcdm::kLicenseStateUnknown; - } -} } // namespace -namespace wvcdm { - class UsagePropertySet : public CdmClientPropertySet { public: UsagePropertySet() {} ~UsagePropertySet() override {} - void set_security_level(SecurityLevel security_level) { + void set_security_level(RequestedSecurityLevel security_level) { if (kLevel3 == security_level) security_level_ = QUERY_VALUE_SECURITY_LEVEL_L3; else @@ -71,7 +62,7 @@ class UsagePropertySet : public CdmClientPropertySet { const std::string empty_; }; -CdmEngine::CdmEngine(FileSystem* file_system, +CdmEngine::CdmEngine(wvutil::FileSystem* file_system, std::shared_ptr metrics) : metrics_(metrics), cert_provisioning_(), @@ -111,30 +102,78 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, WvCdmEventListener* event_listener, const CdmSessionId* forced_session_id, CdmSessionId* session_id) { - LOGI("Opening session"); - if (!ValidateKeySystem(key_system)) { - LOGI("Invalid key system: %s", key_system.c_str()); + LOGI("Invalid key system: %s", IdToString(key_system)); return INVALID_KEY_SYSTEM; } - if (!session_id && !forced_session_id) { - LOGE("No (forced/)session ID destination provided"); + if (session_id == nullptr && forced_session_id == nullptr) { + LOGE("Input |forced_session_id| and output |session_id| are both null"); return PARAMETER_NULL; } - if (forced_session_id) { + if (forced_session_id != nullptr) { + if (forced_session_id->empty()) { + // This should be enforce by the CE CDM code. + return EMPTY_SESSION_ID; + } if (session_map_.Exists(*forced_session_id)) { return DUPLICATE_SESSION_ID_SPECIFIED; } + LOGD("forced_session_id = %s", IdPtrToString(forced_session_id)); + } + + RequestedSecurityLevel requested_security_level = kLevelDefault; + if (property_set && + property_set->security_level() == QUERY_VALUE_SECURITY_LEVEL_L3) { + requested_security_level = kLevel3; + } + + bool forced_level3 = false; + if (requested_security_level == kLevelDefault) { + if (OkpCheck()) { + bool okp_provisioned = false; + bool fallback = false; + { + std::unique_lock lock(okp_mutex_); + if (!okp_provisioner_) { + // Very rare race condition. Possible if two calls to OpenSession + // occur the same time. Cleanup would have been performed. + if (okp_fallback_) { + fallback = true; + } else { + okp_provisioned = true; + } + } else if (okp_provisioner_->IsProvisioned()) { + okp_provisioned = true; + } else if (okp_provisioner_->IsInFallbackMode()) { + fallback = true; + } + } + if (okp_provisioned) { + // OKP not required, engine may assume normal operations. + OkpCleanUp(); + } else if (fallback) { + LOGD("Engine is falling back to L3"); + OkpTriggerFallback(); + forced_level3 = true; + } else { + // OKP is required. + return NEED_PROVISIONING; + } + } else { + std::unique_lock lock(okp_mutex_); + // |okp_fallback_| would have been set previously if required. + if (okp_fallback_) forced_level3 = true; + } } CloseExpiredReleaseSessions(); std::unique_ptr new_session( new CdmSession(file_system_, metrics_->AddSession())); - CdmResponseType sts = - new_session->Init(property_set, forced_session_id, event_listener); + const CdmResponseType sts = new_session->Init(property_set, forced_session_id, + event_listener, forced_level3); if (sts != NO_ERROR) { if (sts == NEED_PROVISIONING) { // Reserve a session ID so the CDM can return success. @@ -144,8 +183,8 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, } return sts; } - CdmSessionId id = new_session->session_id(); - LOGI("New session ID: %s", id.c_str()); + const CdmSessionId id = new_session->session_id(); + LOGI("New session: session_id = %s", IdToString(id)); std::unique_lock lock(session_map_lock_); session_map_.Add(id, new_session.release()); @@ -156,8 +195,7 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system, CdmResponseType CdmEngine::OpenKeySetSession( const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set, WvCdmEventListener* event_listener) { - LOGI("Opening key set session: key_set_id = %s", key_set_id.c_str()); - + LOGI("key_set_id = %s", IdToString(key_set_id)); if (key_set_id.empty()) { LOGE("Invalid key set ID"); return EMPTY_KEYSET_ID_ENG_1; @@ -191,10 +229,10 @@ CdmResponseType CdmEngine::OpenKeySetSession( } CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) { - LOGI("Closing session: session_id = %s", session_id.c_str()); + LOGI("session_id = %s", IdToString(session_id)); std::unique_lock lock(session_map_lock_); if (!session_map_.CloseSession(session_id)) { - LOGE("Session not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_1; } metrics_->ConsolidateSessions(); @@ -202,21 +240,18 @@ CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) { } CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) { - LOGI("Closing key set session: key_set_id = %s", key_set_id.c_str()); - + LOGI("key_set_id = %s", IdToString(key_set_id)); CdmSessionId session_id; { std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { - LOGE("Key set ID not found: %s", key_set_id.c_str()); + LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id)); return KEYSET_ID_NOT_FOUND_1; } session_id = iter->second.first; } - - CdmResponseType sts = CloseSession(session_id); - + const CdmResponseType sts = CloseSession(session_id); std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter != release_key_sets_.end()) { @@ -234,30 +269,33 @@ CdmResponseType CdmEngine::GenerateKeyRequest( const CdmSessionId& session_id, const CdmKeySetId& key_set_id, const InitializationData& init_data, const CdmLicenseType license_type, CdmAppParameterMap& app_parameters, CdmKeyRequest* key_request) { - LOGI("Generating key request: session_id = %s", session_id.c_str()); + LOGI("session_id = %s, key_set_id = %s, license_type = %s", + IdToString(session_id), IdToString(key_set_id), + CdmLicenseTypeToString(license_type)); + if (key_request == nullptr) { + LOGE("Output |key_request| is null"); + return PARAMETER_NULL; + } CdmSessionId id = session_id; - CdmResponseType sts; - // NOTE: If AlwaysUseKeySetIds() is true, there is no need to consult the - // release_key_sets_ map for release licenses. + // |release_key_sets_| map for release licenses. if (license_type == kLicenseTypeRelease && !Properties::AlwaysUseKeySetIds()) { if (key_set_id.empty()) { LOGE("Invalid key set ID"); return EMPTY_KEYSET_ID_ENG_2; } - LOGI("Key set ID: %s", key_set_id.c_str()); - if (!session_id.empty()) { - LOGE("Invalid session ID: %s", session_id.c_str()); + LOGE("Session ID should be empty: session_id = %s", + IdToString(session_id)); return INVALID_SESSION_ID; } std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { - LOGE("Key set ID not found: %s", key_set_id.c_str()); + LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id)); return KEYSET_ID_NOT_FOUND_2; } @@ -266,37 +304,34 @@ CdmResponseType CdmEngine::GenerateKeyRequest( std::shared_ptr session; if (!session_map_.FindSession(id, &session)) { - LOGE("Session ID not found: %s", id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(id)); return SESSION_NOT_FOUND_2; } - if (!key_request) { - LOGE("Output destination provided"); - return PARAMETER_NULL; - } - key_request->message.clear(); if (license_type == kLicenseTypeRelease && !session->license_received()) { int error_detail = NO_ERROR; - sts = session->RestoreOfflineSession(key_set_id, kLicenseTypeRelease, - &error_detail); + const CdmResponseType restore_status = session->RestoreOfflineSession( + key_set_id, kLicenseTypeRelease, &error_detail); session->GetMetrics()->cdm_session_restore_offline_session_.Increment( - sts, error_detail); - if (sts != KEY_ADDED) { - LOGE("Key release restoration failed, status = %d", - static_cast(sts)); - return sts; + restore_status, error_detail); + if (restore_status != KEY_ADDED) { + LOGE("Key release restoration failed: session_id = %s, status = %d", + IdToString(id), static_cast(restore_status)); + return restore_status; } } - sts = session->GenerateKeyRequest(init_data, license_type, app_parameters, - key_request); + const CdmResponseType sts = session->GenerateKeyRequest( + init_data, license_type, app_parameters, key_request); if (KEY_ADDED == sts) { return sts; - } else if (KEY_MESSAGE != sts) { - LOGE("Key request generation failed, status = %d", static_cast(sts)); + } + if (KEY_MESSAGE != sts) { + LOGE("CdmSession::GenerateKeyRequest failed: session_id = %s, status = %d", + IdToString(id), static_cast(sts)); return sts; } @@ -304,6 +339,9 @@ CdmResponseType CdmEngine::GenerateKeyRequest( OnKeyReleaseEvent(key_set_id); } + LOGD("key_request = (%zu) %s", key_request->message.size(), + wvutil::Base64SafeEncode(key_request->message).c_str()); + return KEY_MESSAGE; } @@ -311,18 +349,18 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, const CdmKeyResponse& key_data, CdmLicenseType* license_type, CdmKeySetId* key_set_id) { - LOGI("Adding key: session_id = %s", session_id.c_str()); + LOGI("session_id = %s, key_set_id = %s", IdToString(session_id), + IdPtrToString(key_set_id)); if (license_type == nullptr) { - LOGE("No license type provided"); + LOGE("Output |license_type| is null"); return PARAMETER_NULL; } CdmSessionId id = session_id; - bool license_type_release = session_id.empty(); - + const bool license_type_release = session_id.empty(); if (license_type_release) { - if (!key_set_id) { - LOGE("No key set ID provided"); + if (key_set_id == nullptr) { + LOGE("Input/output |key_set_id| is null"); return PARAMETER_NULL; } @@ -334,16 +372,18 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(*key_set_id); if (iter == release_key_sets_.end()) { - LOGE("Key set ID not found: %s", key_set_id->c_str()); + LOGE("Key set not found: key_set_id = %s", IdPtrToString(key_set_id)); return KEYSET_ID_NOT_FOUND_3; } - id = iter->second.first; + } else { + LOGD("key_data = (%zu) %s", key_data.size(), + wvutil::Base64SafeEncode(key_data).c_str()); } std::shared_ptr session; if (!session_map_.FindSession(id, &session)) { - LOGE("Session ID not found: %s", id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(id)); return SESSION_NOT_FOUND_3; } @@ -352,7 +392,15 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, return EMPTY_KEY_DATA_1; } - CdmResponseType sts = (session->AddKey(key_data)); + CdmResponseType sts = KEY_ADDED; + { + // TODO(rfrias): Refactor. For now lock while adding keys to prevent + // a race condition between this and the decryption thread. This may + // occur if |policy_timers_| is reset when PolicyEngine::SetLicense + // is called. + std::unique_lock lock(session_map_lock_); + sts = session->AddKey(key_data); + } if (sts == KEY_ADDED) { if (session->is_release()) { @@ -366,11 +414,11 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, } } - if (key_set_id) { + if (key_set_id != nullptr) { if ((session->is_offline() || session->has_provider_session_token()) && !license_type_release) { *key_set_id = session->key_set_id(); - LOGI("Key set ID: %s", key_set_id->c_str()); + LOGI("key_set_id = %s", IdPtrToString(key_set_id)); } else { key_set_id->clear(); } @@ -380,21 +428,21 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id, case KEY_ADDED: break; case NEED_KEY: - LOGI("Service certificate loaded: No key added"); + LOGI("Service certificate loaded, no key added: session_id = %s", + IdToString(id)); break; default: - LOGE("Keys not added: status = %d", static_cast(sts)); + LOGE("CdmSession::AddKey failed: session_id = %s, status = %d", + IdToString(id), static_cast(sts)); break; } - return sts; } CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id, const CdmKeySetId& key_set_id) { - LOGI("Restoring key: session_id = %s, key_set_id = %s", session_id.c_str(), - key_set_id.c_str()); - + LOGI("session_id = %s, key_set_id = %s", IdToString(session_id), + IdToString(key_set_id)); if (key_set_id.empty()) { LOGI("Invalid key set ID"); return EMPTY_KEYSET_ID_ENG_4; @@ -402,131 +450,124 @@ CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id, std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_4; } - CdmResponseType sts; int error_detail = NO_ERROR; - sts = session->RestoreOfflineSession(key_set_id, kLicenseTypeOffline, - &error_detail); + const CdmResponseType sts = session->RestoreOfflineSession( + key_set_id, kLicenseTypeOffline, &error_detail); session->GetMetrics()->cdm_session_restore_offline_session_.Increment( sts, error_detail); if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) { - LOGE("Restore offline session failed: status = %d", static_cast(sts)); + LOGE("Restore offline session failed: session_id = %s, status = %d", + IdToString(session_id), static_cast(sts)); } return sts; } CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) { - LOGI("Removing keys: session_id = %s", session_id.c_str()); - + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; + std::unique_lock lock(session_map_lock_); if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_5; } - session->RemoveKeys(); - return NO_ERROR; } CdmResponseType CdmEngine::RemoveLicense(const CdmSessionId& session_id) { - LOGI("Removing license: session_id = %s", session_id.c_str()); - + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; + std::unique_lock lock(session_map_lock_); if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_19; } - return session->RemoveLicense(); } CdmResponseType CdmEngine::GenerateRenewalRequest( const CdmSessionId& session_id, CdmKeyRequest* key_request) { - LOGI("Generating renewal request: session_id = %s", session_id.c_str()); - - std::shared_ptr session; - if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); - return SESSION_NOT_FOUND_6; - } - - if (!key_request) { - LOGE("No request destination"); + LOGI("session_id = %s", IdToString(session_id)); + if (key_request == nullptr) { + LOGE("Output |key_request| is null"); return PARAMETER_NULL; } - + std::shared_ptr session; + if (!session_map_.FindSession(session_id, &session)) { + LOGE("Session not found: session_id = %s", IdToString(session_id)); + return SESSION_NOT_FOUND_6; + } key_request->message.clear(); - - CdmResponseType sts = session->GenerateRenewalRequest(key_request); - + const CdmResponseType sts = session->GenerateRenewalRequest(key_request); if (KEY_MESSAGE != sts) { - LOGE("Key request gen. failed: status = %d", static_cast(sts)); + LOGE("Failed: session_id = %s, status = %d", IdToString(session_id), + static_cast(sts)); return sts; } - return KEY_MESSAGE; } CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id, const CdmKeyResponse& key_data) { - LOGI("Renewing key: session_id = %s", session_id.c_str()); - - std::shared_ptr session; - if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); - return SESSION_NOT_FOUND_7; - } - + LOGI("session_id = %s", IdToString(session_id)); if (key_data.empty()) { LOGE("No key data"); return EMPTY_KEY_DATA_2; } - + std::shared_ptr session; + if (!session_map_.FindSession(session_id, &session)) { + LOGE("Session not found: session_id = %s", IdToString(session_id)); + return SESSION_NOT_FOUND_7; + } CdmResponseType sts; M_TIME(sts = session->RenewKey(key_data), session->GetMetrics(), cdm_session_renew_key_, sts); - if (KEY_ADDED != sts) { - LOGE("Keys not added: status = %d", static_cast(sts)); + LOGE("Failed: session_id = %s, status = %d", IdToString(session_id), + static_cast(sts)); return sts; } - return KEY_ADDED; } CdmResponseType CdmEngine::SetSessionServiceCertificate( const CdmSessionId& session_id, const std::string& service_certificate) { - LOGI("Setting service certificate: session_id = %s", session_id.c_str()); - + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_22; } - return session->SetServiceCertificate(service_certificate); } -CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, +CdmResponseType CdmEngine::QueryStatus(RequestedSecurityLevel security_level, const std::string& query_token, std::string* query_response) { - LOGI("Querying status"); + LOGD("security_level = %s, query_token = %s", + RequestedSecurityLevelToString(security_level), IdToString(query_token)); + if (query_response == nullptr) { + LOGE("Output |query_response| is null"); + return PARAMETER_NULL; + } std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); - CdmResponseType status; - if (!query_response) { - LOGE("No query response destination"); - return PARAMETER_NULL; + // Force OKP check on CryptoSession. Only concerned if engine + // has fallen back to L3. + if (security_level == kLevelDefault && OkpIsInFallbackMode()) { + LOGD("Engine is falling back to L3 for query: token = %s", + query_token.c_str()); + security_level = kLevel3; } // Add queries here, that can be answered before a session is opened if (query_token == QUERY_KEY_SECURITY_LEVEL) { - CdmSecurityLevel found_security_level = + const CdmSecurityLevel found_security_level = crypto_session->GetSecurityLevel(security_level); switch (found_security_level) { case kSecurityLevelL1: @@ -548,12 +589,13 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, return UNKNOWN_ERROR; } return NO_ERROR; - } else if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL || - query_token == QUERY_KEY_MAX_HDCP_LEVEL) { + } + if (query_token == QUERY_KEY_CURRENT_HDCP_LEVEL || + query_token == QUERY_KEY_MAX_HDCP_LEVEL) { CryptoSession::HdcpCapability current_hdcp; CryptoSession::HdcpCapability max_hdcp; - status = crypto_session->GetHdcpCapabilities(security_level, ¤t_hdcp, - &max_hdcp); + const CdmResponseType status = crypto_session->GetHdcpCapabilities( + security_level, ¤t_hdcp, &max_hdcp); if (status != NO_ERROR) { LOGW("GetHdcpCapabilities failed: status = %d", static_cast(status)); @@ -562,13 +604,14 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, *query_response = MapHdcpVersion( query_token == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp : max_hdcp); return NO_ERROR; - } else if (query_token == QUERY_KEY_USAGE_SUPPORT) { + } + if (query_token == QUERY_KEY_USAGE_SUPPORT) { bool supports_usage_reporting; - const bool got_info = crypto_session->UsageInformationSupport( + const bool got_info = crypto_session->HasUsageInfoSupport( security_level, &supports_usage_reporting); if (!got_info) { - LOGW("UsageInformationSupport failed"); + LOGW("HasUsageInfoSupport failed"); metrics_->GetCryptoMetrics() ->crypto_session_usage_information_support_.SetError(got_info); return UNKNOWN_ERROR; @@ -580,89 +623,87 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, *query_response = supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; return NO_ERROR; - } else if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) { + } + if (query_token == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) { size_t number_of_open_sessions; - status = crypto_session->GetNumberOfOpenSessions(security_level, - &number_of_open_sessions); + const CdmResponseType status = crypto_session->GetNumberOfOpenSessions( + security_level, &number_of_open_sessions); if (status != NO_ERROR) { LOGW("GetNumberOfOpenSessions failed: status = %d", static_cast(status)); return status; } - *query_response = std::to_string(number_of_open_sessions); return NO_ERROR; - } else if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) { + } + if (query_token == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) { size_t maximum_number_of_sessions = 0; - status = crypto_session->GetMaxNumberOfSessions( + const CdmResponseType status = crypto_session->GetMaxNumberOfSessions( security_level, &maximum_number_of_sessions); - if (status != NO_ERROR) { LOGW("GetMaxNumberOfOpenSessions failed: status = %d", static_cast(status)); return status; } - *query_response = std::to_string(maximum_number_of_sessions); return NO_ERROR; - } else if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) { + } + if (query_token == QUERY_KEY_OEMCRYPTO_API_VERSION) { uint32_t api_version; if (!crypto_session->GetApiVersion(security_level, &api_version)) { LOGW("GetApiVersion failed"); return UNKNOWN_ERROR; } - *query_response = std::to_string(api_version); return NO_ERROR; - } else if (query_token == QUERY_KEY_CURRENT_SRM_VERSION) { + } + if (query_token == QUERY_KEY_CURRENT_SRM_VERSION) { uint16_t current_srm_version; - status = crypto_session->GetSrmVersion(¤t_srm_version); - switch (status) { - case NO_ERROR: { - *query_response = std::to_string(current_srm_version); - return NO_ERROR; - } - case NO_SRM_VERSION: { - // SRM is not supported or not applicable (ex. local display only). - *query_response = QUERY_VALUE_NONE; - return NO_ERROR; - } - default: { - LOGW("GetCurrentSRMVersion failed: status = %d", - static_cast(status)); - return status; - } + const CdmResponseType status = + crypto_session->GetSrmVersion(¤t_srm_version); + if (status == NO_ERROR) { + *query_response = std::to_string(current_srm_version); + return NO_ERROR; } - } else if (query_token == QUERY_KEY_SRM_UPDATE_SUPPORT) { - bool is_srm_update_supported = crypto_session->IsSrmUpdateSupported(); - *query_response = - is_srm_update_supported ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + if (status == NO_SRM_VERSION) { + // SRM is not supported or not applicable (ex. local display only). + *query_response = QUERY_VALUE_NONE; + return NO_ERROR; + } + LOGW("GetCurrentSRMVersion failed: status = %d", static_cast(status)); + return status; + } + if (query_token == QUERY_KEY_SRM_UPDATE_SUPPORT) { + *query_response = QUERY_VALUE_FALSE; return NO_ERROR; - } else if (query_token == QUERY_KEY_WVCDM_VERSION) { + } + if (query_token == QUERY_KEY_WVCDM_VERSION) { std::string cdm_version; if (!Properties::GetWVCdmVersion(&cdm_version)) { LOGW("GetWVCdmVersion failed"); return UNKNOWN_ERROR; } - *query_response = cdm_version; return NO_ERROR; - } else if (query_token == QUERY_KEY_RESOURCE_RATING_TIER) { + } + if (query_token == QUERY_KEY_RESOURCE_RATING_TIER) { uint32_t tier; if (!crypto_session->GetResourceRatingTier(security_level, &tier)) { LOGW("GetResourceRatingTier failed"); return UNKNOWN_ERROR; } - *query_response = std::to_string(tier); return NO_ERROR; - } else if (query_token == QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION) { + } + if (query_token == QUERY_KEY_OEMCRYPTO_BUILD_INFORMATION) { if (!crypto_session->GetBuildInformation(security_level, query_response)) { LOGW("GetBuildInformation failed"); + query_response->clear(); return UNKNOWN_ERROR; } return NO_ERROR; - } else if (query_token == QUERY_KEY_DECRYPT_HASH_SUPPORT) { + } + if (query_token == QUERY_KEY_DECRYPT_HASH_SUPPORT) { uint32_t hash_support = 0; if (!crypto_session->GetDecryptHashSupport(security_level, &hash_support)) { LOGW("GetDecryptHashSupport failed"); @@ -670,9 +711,11 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, } *query_response = std::to_string(hash_support); return NO_ERROR; - } else if (query_token == QUERY_KEY_PROVISIONING_MODEL) { + } + if (query_token == QUERY_KEY_PROVISIONING_MODEL) { CdmClientTokenType token_type = kClientTokenUninitialized; - status = crypto_session->GetProvisioningMethod(security_level, &token_type); + const CdmResponseType status = + crypto_session->GetProvisioningMethod(security_level, &token_type); if (status != NO_ERROR) { LOGW("GetProvisioningMethod failed: status = %d", static_cast(status)); @@ -688,14 +731,18 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, case kClientTokenOemCert: *query_response = QUERY_VALUE_OEM_CERTIFICATE; break; + case kClientTokenBootCertChain: + *query_response = QUERY_VALUE_BOOT_CERTIFICATE_CHAIN; + break; case kClientTokenUninitialized: default: - LOGW("GetProvisioningMethod returns invalid token type: %d", + LOGW("GetProvisioningMethod returned invalid method: token_type = %d", static_cast(token_type)); return GET_PROVISIONING_METHOD_ERROR; } return NO_ERROR; - } else if (query_token == QUERY_KEY_MAX_USAGE_TABLE_ENTRIES) { + } + if (query_token == QUERY_KEY_MAX_USAGE_TABLE_ENTRIES) { size_t max_number_of_usage_entries; if (!crypto_session->GetMaximumUsageTableEntries( security_level, &max_number_of_usage_entries)) { @@ -710,18 +757,103 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, } *query_response = std::to_string(max_number_of_usage_entries); return NO_ERROR; - } else if (query_token == QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION) { + } + if (query_token == QUERY_KEY_OEMCRYPTO_API_MINOR_VERSION) { uint32_t api_minor_version; if (!crypto_session->GetApiMinorVersion(security_level, &api_minor_version)) { LOGW("GetApiMinorVersion failed"); return UNKNOWN_ERROR; } - *query_response = std::to_string(api_minor_version); return NO_ERROR; } + if (query_token == QUERY_KEY_ANALOG_OUTPUT_CAPABILITIES) { + bool supported = false, can_disable = false, cgms_a = false; + if (crypto_session->GetAnalogOutputCapabilities(&supported, &can_disable, + &cgms_a)) { + if (supported) { + if (cgms_a) { + *query_response = QUERY_VALUE_CGMS_A; + } else { + *query_response = QUERY_VALUE_SUPPORTED; + } + } else { + *query_response = QUERY_VALUE_NONE; + } + } else { + *query_response = QUERY_VALUE_UNKNOWN; + } + return NO_ERROR; + } + if (query_token == QUERY_KEY_CAN_DISABLE_ANALOG_OUTPUT) { + bool supported = false, can_disable = false, cgms_a = false; + if (crypto_session->GetAnalogOutputCapabilities(&supported, &can_disable, + &cgms_a)) { + *query_response = can_disable ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + } else { + *query_response = QUERY_VALUE_UNKNOWN; + } + return NO_ERROR; + } + if (query_token == QUERY_KEY_WATERMARKING_SUPPORT) { + CdmWatermarkingSupport support; + if (!crypto_session->GetWatermarkingSupport(security_level, &support)) { + // Assume not supported. + support = kWatermarkingNotSupported; + } + switch (support) { + case kWatermarkingNotSupported: + *query_response = QUERY_VALUE_NOT_SUPPORTED; + break; + case kWatermarkingConfigurable: + *query_response = QUERY_VALUE_CONFIGURABLE; + break; + case kWatermarkingAlwaysOn: + *query_response = QUERY_VALUE_ALWAYS_ON; + break; + default: + LOGW("Unknown watermarking support: %d", static_cast(support)); + return UNKNOWN_ERROR; + } + return NO_ERROR; + } + if (query_token == QUERY_KEY_PRODUCTION_READY) { + CdmProductionReadiness readiness; + if (!crypto_session->GetProductionReadiness(security_level, &readiness)) { + LOGW("GetProductionReadiness failed"); + return UNKNOWN_ERROR; + } + switch (readiness) { + case kProductionReadinessUnknown: + *query_response = QUERY_VALUE_UNKNOWN; + break; + case kProductionReadinessTrue: + *query_response = QUERY_VALUE_TRUE; + break; + case kProductionReadinessFalse: + *query_response = QUERY_VALUE_FALSE; + break; + default: + LOGW("Unknown readiness: %d", static_cast(readiness)); + return UNKNOWN_ERROR; + } + return NO_ERROR; + } + if (query_token == QUERY_KEY_SYSTEM_ID) { + wvutil::FileSystem global_file_system; + SystemIdExtractor extractor(security_level, crypto_session.get(), + &global_file_system); + uint32_t system_id; + if (!extractor.ExtractSystemId(&system_id)) { + LOGW("ExtractSystemId failed"); + return UNKNOWN_ERROR; + } + *query_response = std::to_string(system_id); + return NO_ERROR; + } + CdmResponseType status; M_TIME(status = crypto_session->Open(security_level), metrics_->GetCryptoMetrics(), crypto_session_open_, status, security_level); @@ -730,65 +862,54 @@ CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level, // Add queries here, that need an open session before they can be answered if (query_token == QUERY_KEY_DEVICE_ID) { - std::string deviceId; - status = crypto_session->GetExternalDeviceUniqueId(&deviceId); + std::string device_id; + status = crypto_session->GetExternalDeviceUniqueId(&device_id); metrics_->GetCryptoMetrics() ->crypto_session_get_device_unique_id_.Increment(status); if (status != NO_ERROR) return status; - - *query_response = deviceId; - } else if (query_token == QUERY_KEY_SYSTEM_ID) { - uint32_t system_id; - bool got_id = crypto_session->GetSystemId(&system_id); - if (!got_id) { - LOGW("QUERY_KEY_SYSTEM_ID unknown failure"); - return UNKNOWN_ERROR; - } - - *query_response = std::to_string(system_id); - } else if (query_token == QUERY_KEY_PROVISIONING_ID) { + *query_response = device_id; + return NO_ERROR; + } + if (query_token == QUERY_KEY_PROVISIONING_ID) { std::string provisioning_id; status = crypto_session->GetProvisioningId(&provisioning_id); if (status != NO_ERROR) { LOGW("GetProvisioningId failed: status = %d", static_cast(status)); return status; } - *query_response = provisioning_id; - } else { - LOGW("Unknown status requested: query_token = %s", query_token.c_str()); - return INVALID_QUERY_KEY; + return NO_ERROR; } - - return NO_ERROR; + LOGW("Unknown status requested: query_token = %s", IdToString(query_token)); + return INVALID_QUERY_KEY; } CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id, CdmQueryMap* query_response) { - LOGI("Querying session status: session_id = %s", session_id.c_str()); + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_8; } return session->QueryStatus(query_response); } bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) { - LOGI("Check if release session: session_id = %s", session_id.c_str()); + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return false; } return session->is_release(); } bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) { - LOGI("Check if offline session: session_id = %s", session_id.c_str()); + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return false; } return session->is_offline(); @@ -796,10 +917,10 @@ bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) { CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id, CdmQueryMap* query_response) { - LOGI("Querying key status: session_id = %s", session_id.c_str()); + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_9; } return session->QueryKeyStatus(query_response); @@ -808,15 +929,15 @@ CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id, CdmResponseType CdmEngine::QueryKeyAllowedUsage(const CdmSessionId& session_id, const std::string& key_id, CdmKeyAllowedUsage* key_usage) { - LOGI("Querying allowed key usage: session_id = %s, key_id = %s", - session_id.c_str(), key_id.c_str()); - if (!key_usage) { - LOGE("No response destination"); + LOGI("session_id = %s, key_id = %s", IdToString(session_id), + IdToString(key_id)); + if (key_usage == nullptr) { + LOGE("Output |key_usage| is null"); return PARAMETER_NULL; } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_12; } return session->QueryKeyAllowedUsage(key_id, key_usage); @@ -824,22 +945,19 @@ CdmResponseType CdmEngine::QueryKeyAllowedUsage(const CdmSessionId& session_id, CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id, CdmKeyAllowedUsage* key_usage) { - LOGI("Querying allowed key useage (all sessions): key_id = %s", - key_id.c_str()); + LOGI("key_id = %s", IdToString(key_id)); if (!key_usage) { - LOGE("No response destination"); + LOGE("Output |key_usage| is null"); return PARAMETER_NULL; } key_usage->Clear(); - CdmSessionList sessions; session_map_.GetSessionList(sessions); - bool found = false; for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { CdmKeyAllowedUsage found_in_this_session; - CdmResponseType sts = + const CdmResponseType sts = (*iter)->QueryKeyAllowedUsage(key_id, &found_in_this_session); if (sts == NO_ERROR) { if (found) { @@ -863,32 +981,29 @@ CdmResponseType CdmEngine::QueryKeyAllowedUsage(const std::string& key_id, CdmResponseType CdmEngine::QueryOemCryptoSessionId( const CdmSessionId& session_id, CdmQueryMap* query_response) { - LOGI("Querying OEMCrypto Session ID: session_id = %s", session_id.c_str()); + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_10; } return session->QueryOemCryptoSessionId(query_response); } +// static bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) { - LOGI("Checking if security level is supported: level = %d", - static_cast(level)); + LOGI("level = %s", CdmSecurityLevelToString(level)); metrics::CryptoMetrics alternate_crypto_metrics; std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(&alternate_crypto_metrics)); - - switch (level) { - case kSecurityLevelL1: - return crypto_session->GetSecurityLevel(kLevelDefault) == - kSecurityLevelL1; - case kSecurityLevelL3: - return crypto_session->GetSecurityLevel(kLevel3) == kSecurityLevelL3; - default: - LOGE("Invalid security level: %d", static_cast(level)); - return false; + if (level == kSecurityLevelL1) { + return crypto_session->GetSecurityLevel(kLevelDefault) == kSecurityLevelL1; } + if (level == kSecurityLevelL3) { + return crypto_session->GetSecurityLevel(kLevel3) == kSecurityLevelL3; + } + LOGE("Unsupported value: level = %d", static_cast(level)); + return false; } /* @@ -901,32 +1016,69 @@ bool CdmEngine::IsSecurityLevelSupported(CdmSecurityLevel level) { CdmResponseType CdmEngine::GetProvisioningRequest( CdmCertificateType cert_type, const std::string& cert_authority, const std::string& service_certificate, - SecurityLevel requested_security_level, CdmProvisioningRequest* request, - std::string* default_url) { - LOGI("Getting provisioning request"); + RequestedSecurityLevel requested_security_level, + CdmProvisioningRequest* request, std::string* default_url) { + LOGI("cert_type = %s", CdmCertificateTypeToString(cert_type)); if (!request) { - LOGE("Invalid output parameters: request is null"); + LOGE("Output |request| is null"); return INVALID_PROVISIONING_REQUEST_PARAM_1; } if (!default_url) { - LOGE("Invalid output parameters: default_url is null"); + LOGE("Output |default_url| is null"); return INVALID_PROVISIONING_REQUEST_PARAM_2; } + if (requested_security_level == kLevelDefault) { + if (OkpCheck()) { + if (okp_provisioner_->IsProvisioned()) { + // OKP not required, engine may assume normal operations. + OkpCleanUp(); + } else if (okp_provisioner_->IsInFallbackMode()) { + LOGD("Engine is falling back to L3"); + OkpTriggerFallback(); + requested_security_level = kLevel3; + } else { + // OKP is required. + const CdmResponseType status = + okp_provisioner_->GetProvisioningRequest(request, default_url); + if (status == NO_ERROR) return NO_ERROR; + if (status == NOT_IMPLEMENTED_ERROR) { + LOGW("OKP not supoprted, falling back to L3"); + OkpTriggerFallback(); + requested_security_level = kLevel3; + } else if (status == OKP_ALREADY_PROVISIONED) { + LOGD("OKP already completed, continuing in normal operation"); + OkpCleanUp(); + // Continue with normal provisioning request. + } else { + LOGE("Failed to generate OKP request: status = %d", + static_cast(status)); + return status; + } + } + } else { + std::unique_lock lock(okp_mutex_); + if (okp_fallback_) { + requested_security_level = kLevel3; + } + } + } + // TODO(b/141705730): Remove usage entries on provisioning. if (!cert_provisioning_) { cert_provisioning_.reset( new CertificateProvisioning(metrics_->GetCryptoMetrics())); - CdmResponseType status = cert_provisioning_->Init(service_certificate); + const CdmResponseType status = + cert_provisioning_->Init(service_certificate); if (status != NO_ERROR) return status; } - CdmResponseType ret = cert_provisioning_->GetProvisioningRequest( - requested_security_level, cert_type, cert_authority, + const CdmResponseType status = cert_provisioning_->GetProvisioningRequest( + file_system_, requested_security_level, cert_type, cert_authority, file_system_->origin(), spoid_, request, default_url); - if (ret != NO_ERROR) { + if (status != NO_ERROR) { cert_provisioning_.reset(); // Release resources. } - return ret; + return status; } /* @@ -938,24 +1090,51 @@ CdmResponseType CdmEngine::GetProvisioningRequest( */ CdmResponseType CdmEngine::HandleProvisioningResponse( const CdmProvisioningResponse& response, - SecurityLevel requested_security_level, std::string* cert, + RequestedSecurityLevel requested_security_level, std::string* cert, std::string* wrapped_key) { - LOGI("Handling provision request"); + LOGI("response_size = %zu, security_level = %s", response.size(), + RequestedSecurityLevelToString(requested_security_level)); if (response.empty()) { LOGE("Empty provisioning response"); cert_provisioning_.reset(); return EMPTY_PROVISIONING_RESPONSE; } if (cert == nullptr) { - LOGE("Invalid certificate destination"); + LOGE("Output |cert| is null"); cert_provisioning_.reset(); return INVALID_PROVISIONING_PARAMETERS_1; } if (wrapped_key == nullptr) { - LOGE("Invalid wrapped key destination"); + LOGE("Output |wrapped_key| is null"); cert_provisioning_.reset(); return INVALID_PROVISIONING_PARAMETERS_2; } + + if (requested_security_level == kLevelDefault) { + bool use_okp = false; + CdmResponseType okp_res = UNKNOWN_ERROR; + { + std::unique_lock lock(okp_mutex_); + if (okp_provisioner_) { + use_okp = true; + // If the engine initiated OKP previously, it must complete it + // regardless of whether the device has fallen back to L3. + okp_res = okp_provisioner_->HandleProvisioningResponse(response); + } else if (okp_fallback_) { + requested_security_level = kLevel3; + } + } + if (use_okp) { + // Cannot hold lock when calling OkpCleanUp() or OkpTriggerFallback(). + if (okp_res == NO_ERROR) { + OkpCleanUp(); + } else { + OkpTriggerFallback(); + } + return okp_res; + } + } + if (!cert_provisioning_) { // Certificate provisioning object has been released. Check if a concurrent // provisioning attempt has succeeded before declaring failure. @@ -977,7 +1156,7 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( return NO_ERROR; } - CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse( + const CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse( file_system_, response, cert, wrapped_key); // Release resources only on success. It is possible that a provisioning // attempt was made after this one was requested but before the response was @@ -988,35 +1167,75 @@ CdmResponseType CdmEngine::HandleProvisioningResponse( } bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level) { + LOGI("security_level = %d", static_cast(security_level)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } // To validate whether the given security level is provisioned, we attempt to // initialize a CdmSession. This verifies the existence of a certificate and // attempts to load it. If this fails, initialization will return an error. UsagePropertySet property_set; property_set.set_security_level( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); + return GetProvisioningStatus(security_level) == kProvisioned; +} - CdmSession session(file_system_, metrics_->AddSession()); - - CdmResponseType status = session.Init(&property_set); - if (NO_ERROR != status) { - LOGE("Init failed: status = %d", static_cast(status)); +CdmProvisioningStatus CdmEngine::GetProvisioningStatus( + CdmSecurityLevel security_level) { + std::unique_ptr crypto_session( + CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); + CdmResponseType status = crypto_session->Open( + security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); + if (status != NO_ERROR) { + LOGE("Failed to open crypto session: status = %d", + static_cast(status)); + return kUnknownProvisionStatus; } - return status == NO_ERROR; + const CdmSecurityLevel cdm_security_level = + crypto_session->GetSecurityLevel(); + DeviceFiles handle(file_system_); + if (!handle.Init(cdm_security_level)) { + LOGE("Failed to initialize device files."); + return kUnknownProvisionStatus; + } + + UsagePropertySet property_set; + if (handle.HasCertificate(property_set.use_atsc_mode())) { + return kProvisioned; + } + if (crypto_session->GetPreProvisionTokenType() == kClientTokenBootCertChain) { + wvutil::FileSystem global_file_system; + DeviceFiles global_handle(&global_file_system); + if (!global_handle.Init(cdm_security_level)) { + LOGE("Failed to initialize global device files."); + return kUnknownProvisionStatus; + } + if (!global_handle.HasOemCertificate()) { + return kNeedsOemCertProvisioning; + } + } + return kNeedsDrmCertProvisioning; } CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { - LOGI("Unprovisioning: security_level = %d", static_cast(security_level)); + LOGI("security_level = %s", CdmSecurityLevelToString(security_level)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } // Devices with baked-in DRM certs cannot be reprovisioned and therefore must // not be unprovisioned. std::unique_ptr crypto_session( CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); CdmClientTokenType token_type = kClientTokenUninitialized; - CdmResponseType res = crypto_session->GetProvisioningMethod( + const CdmResponseType res = crypto_session->GetProvisioningMethod( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault, &token_type); if (res != NO_ERROR) { return res; - } else if (token_type == kClientTokenDrmCert) { + } + if (token_type == kClientTokenDrmCert) { return DEVICE_CANNOT_REPROVISION; } @@ -1027,29 +1246,31 @@ CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) { } // TODO(b/141705730): Remove usage entries during unprovisioning. - if (!file_system_->IsGlobal()) { - if (!handle.RemoveCertificate()) { + if (!handle.RemoveCertificate() || !handle.RemoveOemCertificate()) { LOGE("Unable to delete certificate"); return UNPROVISION_ERROR_2; } return NO_ERROR; - } else { - if (!handle.DeleteAllFiles()) { - LOGE("Unable to delete files"); - return UNPROVISION_ERROR_3; - } - return NO_ERROR; } + if (!handle.DeleteAllFiles()) { + LOGE("Unable to delete files"); + return UNPROVISION_ERROR_3; + } + return NO_ERROR; } CdmResponseType CdmEngine::ListStoredLicenses( CdmSecurityLevel security_level, std::vector* key_set_ids) { - DeviceFiles handle(file_system_); if (!key_set_ids) { - LOGE("No response destination"); + LOGE("Output |key_set_ids| is null"); return INVALID_PARAMETERS_ENG_22; } + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return LIST_LICENSE_ERROR_1; @@ -1065,17 +1286,22 @@ CdmResponseType CdmEngine::ListUsageIds( const std::string& app_id, CdmSecurityLevel security_level, std::vector* ksids, std::vector* provider_session_tokens) { - DeviceFiles handle(file_system_); if (!ksids && !provider_session_tokens) { - LOGE("No response destination"); + LOGE("Outputs |ksids| and |provider_session_tokens| are null"); return INVALID_PARAMETERS_ENG_23; } + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } + DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return LIST_USAGE_ERROR_1; } if (!handle.ListUsageIds(app_id, ksids, provider_session_tokens)) { - LOGE("ListUsageIds call failed"); + LOGE("Failed: app_id = %s, security_level = %s", IdToString(app_id), + CdmSecurityLevelToString(security_level)); return LIST_USAGE_ERROR_2; } return NO_ERROR; @@ -1084,48 +1310,55 @@ CdmResponseType CdmEngine::ListUsageIds( CdmResponseType CdmEngine::DeleteUsageRecord(const std::string& app_id, CdmSecurityLevel security_level, const std::string& key_set_id) { - LOGI("Deleting usage record: key_set_id = %s, app_id = %s", - key_set_id.c_str(), app_id.c_str()); - std::string provider_session_token; - + LOGI("app_id = %s, key_set_id = %s", IdToString(app_id), + IdToString(key_set_id)); + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Unable to initialize device files"); return DELETE_USAGE_ERROR_1; } + std::string provider_session_token; if (!handle.GetProviderSessionToken(app_id, key_set_id, &provider_session_token)) { LOGE("GetProviderSessionToken failed"); return DELETE_USAGE_ERROR_2; } - return RemoveUsageInfo(app_id, provider_session_token); } CdmResponseType CdmEngine::GetOfflineLicenseState( const CdmKeySetId& key_set_id, CdmSecurityLevel security_level, CdmOfflineLicenseState* license_state) { + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } DeviceFiles handle(file_system_); if (!handle.Init(security_level)) { LOGE("Cannot initialize device files"); return GET_OFFLINE_LICENSE_STATE_ERROR_1; } - DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType sub_error_code = DeviceFiles::kNoError; - - if (handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) { - *license_state = MapDeviceFilesLicenseState(license_data.state); - } else { + if (!handle.RetrieveLicense(key_set_id, &license_data, &sub_error_code)) { LOGE("Failed to retrieve license state: key_set_id = %s", - key_set_id.c_str()); + IdToString(key_set_id)); return GET_OFFLINE_LICENSE_STATE_ERROR_2; } + *license_state = license_data.state; return NO_ERROR; } CdmResponseType CdmEngine::RemoveOfflineLicense( const CdmKeySetId& key_set_id, CdmSecurityLevel security_level) { + if (security_level == kSecurityLevelL1 && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + security_level = kSecurityLevelL3; + } UsagePropertySet property_set; property_set.set_security_level( security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault); @@ -1140,14 +1373,14 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( CdmResponseType sts = OpenKeySetSession(key_set_id, &property_set, nullptr /* event listener */); if (sts != NO_ERROR) { - LOGE("Failed to open key set session: status = %d", static_cast(sts)); + LOGE("OpenKeySetSession failed: status = %d", static_cast(sts)); handle.DeleteLicense(key_set_id); return sts; } CdmSessionId session_id; CdmAppParameterMap dummy_app_params; - InitializationData dummy_init_data("", "", ""); + const InitializationData dummy_init_data("", "", ""); CdmKeyRequest key_request; // Calling with no session_id is okay sts = GenerateKeyRequest(session_id, key_set_id, dummy_init_data, @@ -1156,7 +1389,7 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( std::unique_lock lock(release_key_sets_lock_); CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); if (iter == release_key_sets_.end()) { - LOGE("Key set ID not found: %s", key_set_id.c_str()); + LOGE("Key set not found: key_set_id = %s", IdToString(key_set_id)); sts = REMOVE_OFFLINE_LICENSE_ERROR_2; } else { session_id = iter->second.first; @@ -1177,7 +1410,6 @@ CdmResponseType CdmEngine::RemoveOfflineLicense( handle.DeleteLicense(key_set_id); } CloseKeySetSession(key_set_id); - return sts; } @@ -1185,16 +1417,37 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, const CdmSecureStopId& ssid, int* error_detail, CdmUsageInfo* usage_info) { - LOGI("Getting usage info: app_id = %s, ssid = %s", app_id.c_str(), - ssid.c_str()); + // Try to find usage info at the default security level. If the + // security level is unprovisioned or we are unable to find it, + // try L3. + CdmResponseType status = + GetUsageInfo(app_id, ssid, kLevelDefault, error_detail, usage_info); + switch (status) { + case NEED_PROVISIONING: + case GET_USAGE_INFO_ERROR_1: + case GET_USAGE_INFO_ERROR_2: + case USAGE_INFO_NOT_FOUND: + status = GetUsageInfo(app_id, ssid, kLevel3, error_detail, usage_info); + return status; + default: + return status; + } +} + +CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, + const CdmSecureStopId& ssid, + RequestedSecurityLevel security_level, + int* error_detail, + CdmUsageInfo* usage_info) { + LOGI("app_id = %s, ssid = %s", IdToString(app_id), IdToString(ssid)); if (!usage_property_set_) { usage_property_set_.reset(new UsagePropertySet()); } - if (!usage_info) { - LOGE("No usage info destination"); + if (usage_info == nullptr) { + LOGE("Output |usage_info| is null"); return PARAMETER_NULL; } - usage_property_set_->set_security_level(kLevelDefault); + usage_property_set_->set_security_level(security_level); usage_property_set_->set_app_id(app_id); usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession())); CdmResponseType status = usage_session_->Init(usage_property_set_.get()); @@ -1257,18 +1510,17 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, int* error_detail, CdmUsageInfo* usage_info) { - LOGI("Getting usage info: app_id = %s", app_id.c_str()); - // Return a random usage report from a random security level - SecurityLevel security_level = - CdmRandom::RandomBool() ? kLevelDefault : kLevel3; - CdmResponseType status = UNKNOWN_ERROR; - if (!usage_info) { - LOGE("No usage info destination"); + LOGI("app_id = %s", IdToString(app_id)); + if (usage_info == nullptr) { + LOGE("Output |usage_info| is null"); return PARAMETER_NULL; } + // Return a random usage report from a random security level + RequestedSecurityLevel security_level = + wvutil::CdmRandom::RandomBool() ? kLevelDefault : kLevel3; + CdmResponseType status = UNKNOWN_ERROR; do { status = GetUsageInfo(app_id, security_level, error_detail, usage_info); - if (KEY_MESSAGE == status && !usage_info->empty()) { return status; } @@ -1284,17 +1536,19 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, return status; } -CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, - SecurityLevel requested_security_level, - int* error_detail, - CdmUsageInfo* usage_info) { - LOGI("Getting usage info: app_id = %s, security_level = %d", app_id.c_str(), - static_cast(requested_security_level)); - if (!usage_info) { - LOGE("No usage info destination"); +CdmResponseType CdmEngine::GetUsageInfo( + const std::string& app_id, RequestedSecurityLevel requested_security_level, + int* error_detail, CdmUsageInfo* usage_info) { + LOGI("app_id = %s, security_level = %s", IdToString(app_id), + RequestedSecurityLevelToString(requested_security_level)); + if (usage_info == nullptr) { + LOGE("Output |usage_info| is null"); return PARAMETER_NULL; } - + if (requested_security_level == kLevelDefault && OkpIsInFallbackMode()) { + LOGD("OKP fallback to L3"); + requested_security_level = kLevel3; + } if (!usage_property_set_) { usage_property_set_.reset(new UsagePropertySet()); } @@ -1327,7 +1581,7 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, return NO_ERROR; } - const size_t index = CdmRandom::RandomInRange(usage_data.size() - 1); + const size_t index = wvutil::CdmRandom::RandomInRange(usage_data.size() - 1); status = usage_session_->RestoreUsageSession(usage_data[index], error_detail); if (KEY_ADDED != status) { // TODO(b/141704872): Make multiple attempts. @@ -1361,8 +1615,8 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id, CdmResponseType CdmEngine::RemoveAllUsageInfo( const std::string& app_id, CdmSecurityLevel cdm_security_level) { - LOGI("Removing all usage info: app_id = %s, security_level = %d", - app_id.c_str(), static_cast(cdm_security_level)); + LOGI("app_id = %s, security_level = %s", IdToString(app_id), + CdmSecurityLevelToString(cdm_security_level)); if (!usage_property_set_) { usage_property_set_.reset(new UsagePropertySet()); } @@ -1371,13 +1625,13 @@ CdmResponseType CdmEngine::RemoveAllUsageInfo( CdmResponseType status = NO_ERROR; DeviceFiles handle(file_system_); if (handle.Init(cdm_security_level)) { - SecurityLevel security_level = + const RequestedSecurityLevel security_level = cdm_security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault; usage_property_set_->set_security_level(security_level); usage_session_.reset(new CdmSession(file_system_, metrics_->AddSession())); usage_session_->Init(usage_property_set_.get()); - if (usage_session_->get_usage_support_type() == kUsageEntrySupport) { + if (usage_session_->supports_usage_info()) { std::vector usage_data; // Retrieve all usage information but delete only one before // refetching. This is because deleting the usage entry @@ -1420,7 +1674,7 @@ CdmResponseType CdmEngine::RemoveAllUsageInfo( } CdmResponseType CdmEngine::RemoveAllUsageInfo(const std::string& app_id) { - LOGI("Removing all usage info: app_id = %s", app_id.c_str()); + LOGI("app_id = %s", IdToString(app_id)); const CdmResponseType status_l1 = RemoveAllUsageInfo(app_id, kSecurityLevelL1); const CdmResponseType status_l3 = @@ -1434,8 +1688,8 @@ CdmResponseType CdmEngine::RemoveAllUsageInfo(const std::string& app_id) { CdmResponseType CdmEngine::RemoveUsageInfo( const std::string& app_id, const CdmSecureStopId& provider_session_token) { - LOGI("Removing usage info: app_id = %s, pst = %s", app_id.c_str(), - provider_session_token.c_str()); + LOGI("app_id = %s, pst = %s", IdToString(app_id), + IdToString(provider_session_token)); if (!usage_property_set_) { usage_property_set_.reset(new UsagePropertySet()); } @@ -1445,7 +1699,7 @@ CdmResponseType CdmEngine::RemoveUsageInfo( for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) { DeviceFiles handle(file_system_); if (handle.Init(static_cast(j))) { - SecurityLevel security_level = + RequestedSecurityLevel security_level = static_cast(j) == kSecurityLevelL3 ? kLevel3 : kLevelDefault; usage_property_set_->set_security_level(security_level); @@ -1453,21 +1707,22 @@ CdmResponseType CdmEngine::RemoveUsageInfo( new CdmSession(file_system_, metrics_->AddSession())); usage_session_->Init(usage_property_set_.get()); - std::vector usage_data; CdmKeyMessage license_request; CdmKeyResponse license_response; CdmUsageEntry usage_entry; uint32_t usage_entry_number; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; - if (!handle.RetrieveUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), - provider_session_token, &license_request, - &license_response, &usage_entry, - &usage_entry_number)) { + if (!handle.RetrieveUsageInfo( + DeviceFiles::GetUsageInfoFileName(app_id), provider_session_token, + &license_request, &license_response, &usage_entry, + &usage_entry_number, &drm_certificate, &wrapped_private_key)) { // Try other security level continue; } - if (usage_session_->get_usage_support_type() == kUsageEntrySupport) { + if (usage_session_->supports_usage_info()) { status = usage_session_->DeleteUsageEntry(usage_entry_number); if (!handle.DeleteUsageInfo(DeviceFiles::GetUsageInfoFileName(app_id), provider_session_token)) { @@ -1487,13 +1742,12 @@ CdmResponseType CdmEngine::RemoveUsageInfo( CdmResponseType CdmEngine::ReleaseUsageInfo( const CdmUsageInfoReleaseMessage& message) { - LOGI("Releasing usage info"); + LOGI("message_size = %zu", message.size()); if (!usage_session_) { - LOGE("CDM session not initialized"); + LOGE("Usage session not initialized"); return RELEASE_USAGE_INFO_ERROR; } - - CdmResponseType status = usage_session_->ReleaseKey(message); + const CdmResponseType status = usage_session_->ReleaseKey(message); usage_session_.reset(); if (NO_ERROR != status) { LOGE("ReleaseKey failed: status = %d", status); @@ -1503,27 +1757,25 @@ CdmResponseType CdmEngine::ReleaseUsageInfo( CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, CdmKeyMessage* release_message) { - LOGI("Loading usage session: key_set_id = %s", key_set_id.c_str()); + LOGI("key_set_id = %s", IdToString(key_set_id)); // This method is currently only used by the CE CDM, in which all session IDs // are key set IDs. assert(Properties::AlwaysUseKeySetIds()); - if (key_set_id.empty()) { LOGE("Invalid key set ID"); return EMPTY_KEYSET_ID_ENG_5; } + if (release_message == nullptr) { + LOGE("Output |release_message| is null"); + return PARAMETER_NULL; + } std::shared_ptr session; if (!session_map_.FindSession(key_set_id, &session)) { - LOGE("Session ID not found: %s ", key_set_id.c_str()); + LOGE("Session not found: key_set_id = %s", IdToString(key_set_id)); return SESSION_NOT_FOUND_11; } - if (!release_message) { - LOGE("No release message destination"); - return PARAMETER_NULL; - } - DeviceFiles handle(file_system_); if (!handle.Init(session->GetSecurityLevel())) { LOGE("Unable to initialize device files"); @@ -1538,7 +1790,8 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, DeviceFiles::GetUsageInfoFileName(app_id), key_set_id, &(usage_data.provider_session_token), &(usage_data.license_request), &(usage_data.license), &(usage_data.usage_entry), - &(usage_data.usage_entry_number))) { + &(usage_data.usage_entry_number), &(usage_data.drm_certificate), + &(usage_data.wrapped_private_key))) { LOGE("Unable to find usage information"); return LOAD_USAGE_INFO_MISSING; } @@ -1550,15 +1803,14 @@ CdmResponseType CdmEngine::LoadUsageSession(const CdmKeySetId& key_set_id, session->GetMetrics()->cdm_session_restore_usage_session_.Increment( status, error_detail); if (KEY_ADDED != status) { - LOGE("Usage session error: status = %d", static_cast(status)); + LOGE("Restore failed: key_set_id = %s, status = %d", IdToString(key_set_id), + static_cast(status)); return status; } CdmKeyRequest request; status = session->GenerateReleaseRequest(&request); - - *release_message = request.message; - + *release_message = std::move(request.message); switch (status) { case KEY_MESSAGE: break; @@ -1613,12 +1865,12 @@ CdmResponseType CdmEngine::DecryptV16( } } if (!session) { - LOGE("Session not found: Empty session ID"); + LOGE("Session not found: session_id = "); return SESSION_NOT_FOUND_FOR_DECRYPT; } } else { if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session not found: session_id = %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_FOR_DECRYPT; } } @@ -1633,12 +1885,12 @@ CdmResponseType CdmEngine::GenericEncrypt(const std::string& session_id, CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { if (out_buffer == nullptr) { - LOGE("No out buffer provided"); + LOGE("Output |out_buffer| is null"); return PARAMETER_NULL; } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_13; } return session->GenericEncrypt(in_buffer, key_id, iv, algorithm, out_buffer); @@ -1651,12 +1903,12 @@ CdmResponseType CdmEngine::GenericDecrypt(const std::string& session_id, CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { if (out_buffer == nullptr) { - LOGE("No out buffer provided"); + LOGE("Output |out_buffer| is null"); return PARAMETER_NULL; } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_14; } return session->GenericDecrypt(in_buffer, key_id, iv, algorithm, out_buffer); @@ -1668,12 +1920,12 @@ CdmResponseType CdmEngine::GenericSign(const std::string& session_id, CdmSigningAlgorithm algorithm, std::string* signature) { if (signature == nullptr) { - LOGE("No signature buffer provided"); + LOGE("Output |signature| is null"); return PARAMETER_NULL; } std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_15; } return session->GenericSign(message, key_id, algorithm, signature); @@ -1686,7 +1938,7 @@ CdmResponseType CdmEngine::GenericVerify(const std::string& session_id, const std::string& signature) { std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { - LOGE("Session ID not found: %s", session_id.c_str()); + LOGE("Session not found: session_id = %s", IdToString(session_id)); return SESSION_NOT_FOUND_16; } return session->GenericVerify(message, key_id, algorithm, signature); @@ -1696,15 +1948,15 @@ CdmResponseType CdmEngine::ParseDecryptHashString( const std::string& hash_string, CdmSessionId* session_id, uint32_t* frame_number, std::string* hash) { if (session_id == nullptr) { - LOGE("Session ID was not provided"); + LOGE("Output |session_id| is null"); return PARAMETER_NULL; } if (frame_number == nullptr) { - LOGE("Frame number was not provided"); + LOGE("Output |frame_number| is null"); return PARAMETER_NULL; } if (hash == nullptr) { - LOGE("Hash was not provided"); + LOGE("Output |hash| is null"); return PARAMETER_NULL; } std::stringstream ss; @@ -1740,19 +1992,18 @@ CdmResponseType CdmEngine::ParseDecryptHashString( return INVALID_DECRYPT_HASH_FORMAT; } - std::vector hash_vec = wvcdm::a2b_hex(tokens[2]); - if (hash_vec.empty()) { + *hash = wvutil::a2bs_hex(tokens[2]); + if (hash->empty()) { LOGE("Malformed hash: %s", hash_string.c_str()); return INVALID_DECRYPT_HASH_FORMAT; } - hash->assign(hash_vec.begin(), hash_vec.end()); return NO_ERROR; } CdmResponseType CdmEngine::SetDecryptHash(const CdmSessionId& session_id, uint32_t frame_number, const std::string& hash) { - LOGI("Setting decrypt hash: session_id = %s", session_id.c_str()); + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { return SESSION_NOT_FOUND_20; @@ -1762,7 +2013,7 @@ CdmResponseType CdmEngine::SetDecryptHash(const CdmSessionId& session_id, CdmResponseType CdmEngine::GetDecryptHashError(const CdmSessionId& session_id, std::string* error_string) { - LOGI("Getting decrypt hash error: session_id = %s", session_id.c_str()); + LOGI("session_id = %s", IdToString(session_id)); std::shared_ptr session; if (!session_map_.FindSession(session_id, &session)) { return SESSION_NOT_FOUND_20; @@ -1786,18 +2037,17 @@ bool CdmEngine::IsKeyLoaded(const KeyId& key_id) { bool CdmEngine::FindSessionForKey(const KeyId& key_id, CdmSessionId* session_id) { if (session_id == nullptr) { - LOGE("No session ID destination provided"); + LOGE("Output |session_id| is null"); return false; } - - uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id); + const uint32_t session_sharing_id = + Properties::GetSessionSharingId(*session_id); std::unique_lock lock(session_map_lock_); CdmSessionList sessions; session_map_.GetSessionList(sessions); CdmSessionList::iterator session_iter = sessions.end(); - int64_t seconds_remaining = 0; for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { @@ -1835,9 +2085,8 @@ bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) { } void CdmEngine::OnTimerEvent() { - Clock clock; - uint64_t current_time = clock.GetCurrentTime(); - + wvutil::Clock clock; + const uint64_t current_time = clock.GetCurrentTime(); bool usage_update_period_expired = false; if (current_time - last_usage_information_update_time_ > kUpdateUsageInformationPeriod) { @@ -1847,7 +2096,6 @@ void CdmEngine::OnTimerEvent() { bool is_initial_usage_update = false; bool is_usage_update_needed = false; - { std::unique_lock lock(session_map_lock_); CdmSessionList sessions; @@ -1871,7 +2119,7 @@ void CdmEngine::OnTimerEvent() { for (CdmSessionList::iterator iter = sessions.begin(); iter != sessions.end(); ++iter) { (*iter)->reset_usage_flags(); - if ((*iter)->get_usage_support_type() == kUsageEntrySupport && + if ((*iter)->supports_usage_info() && (*iter)->has_provider_session_token()) { (*iter)->UpdateUsageEntryInformation(); } @@ -1884,7 +2132,6 @@ void CdmEngine::OnTimerEvent() { void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) { CdmSessionList sessions; session_map_.GetSessionList(sessions); - while (!sessions.empty()) { sessions.front()->OnKeyReleaseEvent(key_set_id); sessions.pop_front(); @@ -1896,6 +2143,19 @@ CdmResponseType CdmEngine::ValidateServiceCertificate(const std::string& cert) { return certificate.Init(cert); } +CdmResponseType CdmEngine::SetPlaybackId(const CdmSessionId& session_id, + const std::string& playback_id) { + LOGI("session_id = %s, playback_id = %s", IdToString(session_id), + IdToString(playback_id)); + std::shared_ptr session; + if (!session_map_.FindSession(session_id, &session)) { + LOGE("Session not found: session_id = %s", IdToString(session_id)); + return SESSION_NOT_FOUND_23; + } + session->GetMetrics()->playback_id_.Record(playback_id); + return NO_ERROR; +} + std::string CdmEngine::MapHdcpVersion(CryptoSession::HdcpCapability version) { switch (version) { case HDCP_NONE: @@ -1918,8 +2178,7 @@ std::string CdmEngine::MapHdcpVersion(CryptoSession::HdcpCapability version) { } void CdmEngine::CloseExpiredReleaseSessions() { - int64_t current_time = clock_.GetCurrentTime(); - + const int64_t current_time = clock_.GetCurrentTime(); std::set close_session_set; { std::unique_lock lock(release_key_sets_lock_); @@ -1940,4 +2199,97 @@ void CdmEngine::CloseExpiredReleaseSessions() { } } +bool CdmEngine::OkpCheck() { + std::unique_lock lock(okp_mutex_); + if (okp_initialized_) { + return static_cast(okp_provisioner_); + } + okp_initialized_ = true; + // Creating a CryptoSession will initialize OEMCrypto and flag the need + // for OKP. + std::unique_ptr crypto_session( + CryptoSession::MakeCryptoSession(metrics_->GetCryptoMetrics())); + if (!crypto_session->needs_keybox_provisioning()) { + // System does not require OKP provisioning. + return false; + } + okp_provisioner_ = OtaKeyboxProvisioner::Create(metrics_->GetCryptoMetrics()); + if (!okp_provisioner_) { + LOGE("Failed to create engine OKP handler, falling back to L3"); + okp_fallback_ = true; + return false; + } + if (okp_provisioner_->IsProvisioned()) { + // This should have been caught by call to needs_keybox_provisioning(), + // but possible with simultaneous apps. + okp_provisioner_.reset(); + return false; + } + if (okp_provisioner_->IsInFallbackMode()) { + LOGD("Engine is in OKP fallback mode"); + okp_fallback_ = true; + okp_provisioner_.reset(); + return false; + } + return true; +} + +bool CdmEngine::OkpIsInFallbackMode() { + const bool check = OkpCheck(); + std::unique_lock lock(okp_mutex_); + if (!check || !okp_provisioner_ || okp_fallback_) { + return okp_fallback_; + } + if (!okp_provisioner_->IsInFallbackMode()) { + return false; + } + // Trigger fallback. + LOGD("Engine is entering OKP fallback mode"); + okp_provisioner_.reset(); + okp_fallback_ = true; + return true; +} + +void CdmEngine::OkpTriggerFallback() { + std::unique_lock lock(okp_mutex_); + if (!okp_initialized_) { + LOGD("Call to OKP fallback before OKP setup"); + return; + } + if (okp_fallback_) return; + LOGD("Engine is entering OKP fallback mode"); + okp_provisioner_.reset(); + okp_fallback_ = true; +} + +void CdmEngine::OkpCleanUp() { + std::unique_lock lock(okp_mutex_); + if (!okp_initialized_) { + LOGD("Call to OKP fallback before OKP setup"); + return; + } + okp_provisioner_.reset(); +} + +void CdmEngine::SetDefaultOtaKeyboxFallbackDurationRules() { + OkpCheck(); + std::unique_lock lock(okp_mutex_); + auto* system_fallback_policy = CryptoSession::GetOkpFallbackPolicy(); + if (!system_fallback_policy) { + LOGW("No system fallback policy available"); + return; + } + system_fallback_policy->SetDefaultBackoffDurationRules(); +} + +void CdmEngine::SetFastOtaKeyboxFallbackDurationRules() { + OkpCheck(); + std::unique_lock lock(okp_mutex_); + auto* system_fallback_policy = CryptoSession::GetOkpFallbackPolicy(); + if (!system_fallback_policy) { + LOGW("No system fallback policy available"); + return; + } + system_fallback_policy->SetFastBackoffDurationRules(); +} } // namespace wvcdm diff --git a/core/src/cdm_engine_factory.cpp b/core/src/cdm_engine_factory.cpp index 28363fc4..8f232636 100644 --- a/core/src/cdm_engine_factory.cpp +++ b/core/src/cdm_engine_factory.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "cdm_engine_factory.h" @@ -14,7 +14,7 @@ namespace wvcdm { -CdmEngine* CdmEngineFactory::CreateCdmEngine(FileSystem* file_system) { +CdmEngine* CdmEngineFactory::CreateCdmEngine(wvutil::FileSystem* file_system) { std::unique_ptr engine_metrics( new metrics::EngineMetrics()); diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp index 42066141..87659180 100644 --- a/core/src/cdm_session.cpp +++ b/core/src/cdm_session.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "cdm_session.h" @@ -13,6 +13,7 @@ #include "cdm_engine.h" #include "clock.h" +#include "crypto_wrapped_key.h" #include "file_store.h" #include "log.h" #include "properties.h" @@ -23,7 +24,7 @@ // Stringify turns macro arguments into static C strings. // Example: STRINGIFY(this_argument) -> "this_argument" -#define STRINGIFY(PARAM...) #PARAM +#define STRINGIFY(PARAM) #PARAM #define RETURN_STATUS_IF_NULL(PARAM) \ if ((PARAM) == nullptr) { \ @@ -37,6 +38,7 @@ return false; \ } +namespace wvcdm { namespace { const size_t kKeySetIdLength = 14; @@ -48,11 +50,23 @@ void SetErrorDetail(int* error_detail, T error_code) { } } +int DrmKeyTypeToMetricValue(CryptoWrappedKey::Type type) { + constexpr int kUnknownMetricType = -1; + constexpr int kRsaMetricType = 0; + constexpr int kEccMetricType = 1; + switch (type) { + case CryptoWrappedKey::kRsa: + return kRsaMetricType; + case CryptoWrappedKey::kEcc: + return kEccMetricType; + default: + LOGE("Unexpected DRM key type: %d", static_cast(type)); + return kUnknownMetricType; + } +} } // namespace -namespace wvcdm { - -CdmSession::CdmSession(FileSystem* file_system, +CdmSession::CdmSession(wvutil::FileSystem* file_system, std::shared_ptr metrics) : metrics_(metrics), initialized_(false), @@ -64,11 +78,8 @@ CdmSession::CdmSession(FileSystem* file_system, is_temporary_(false), security_level_(kSecurityLevelUninitialized), requested_security_level_(kLevelDefault), - is_initial_decryption_(true), - has_decrypted_since_last_report_(false), is_initial_usage_update_(true), is_usage_update_needed_(false), - usage_support_type_(kNonSecureUsageSupport), usage_table_header_(nullptr), usage_entry_number_(0), mock_license_parser_in_use_(false), @@ -80,9 +91,7 @@ CdmSession::CdmSession(FileSystem* file_system, } CdmSession::~CdmSession() { - if (usage_support_type_ == kUsageEntrySupport && - has_provider_session_token() && usage_table_header_ != nullptr && - !is_release_) { + if (has_provider_session_token() && supports_usage_info() && !is_release_) { UpdateUsageEntryInformation(); } @@ -100,31 +109,21 @@ CdmSession::~CdmSession() { CdmResponseType CdmSession::Init( CdmClientPropertySet* cdm_client_property_set) { - return Init(cdm_client_property_set, nullptr, nullptr); + return Init(cdm_client_property_set, nullptr, nullptr, false); } CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, const CdmSessionId* forced_session_id, - WvCdmEventListener* event_listener) { + WvCdmEventListener* event_listener, + bool forced_level3) { if (initialized_) { LOGE("Failed due to previous initialization"); return REINIT_ERROR; } - // Save parameters in case Init needs to be called again (load and restore - // offline license) - if (cdm_client_property_set) - cdm_client_property_set_ = cdm_client_property_set; - - if (forced_session_id) { - forced_session_id_value_ = *forced_session_id; - forced_session_id_ = &forced_session_id_value_; - } - - if (event_listener) event_listener_ = event_listener; - - if (cdm_client_property_set && cdm_client_property_set->security_level() == - QUERY_VALUE_SECURITY_LEVEL_L3) { + if ((cdm_client_property_set && cdm_client_property_set->security_level() == + QUERY_VALUE_SECURITY_LEVEL_L3) || + forced_level3) { requested_security_level_ = kLevel3; security_level_ = kSecurityLevelL3; } @@ -148,63 +147,29 @@ CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, return SESSION_FILE_HANDLE_INIT_ERROR; } - if (crypto_session_->GetUsageSupportType(&usage_support_type_) == NO_ERROR) { - if (usage_support_type_ == kUsageEntrySupport) - usage_table_header_ = crypto_session_->GetUsageTableHeader(); - } else { - usage_support_type_ = kNonSecureUsageSupport; + bool has_support = false; + if (crypto_session_->HasUsageInfoSupport(&has_support) && has_support) { + usage_table_header_ = crypto_session_->GetUsageTableHeader(); } - // Device Provisioning state is not yet known. - // If not using certificates, then Keybox is client token for license - // requests. - // Otherwise, try to fetch device certificate. If not successful and - // provisioning is supported, return NEED_PROVISIONING. Otherwise, return - // an error. - // client_token and client_token_type are determined here; they are needed - // to initialize the license parser. - std::string client_token; - std::string serial_number; - CdmClientTokenType client_token_type = - crypto_session_->GetPreProvisionTokenType(); - - // License server client ID token is a stored certificate. Stage it or - // indicate that provisioning is needed. Get token from stored certificate - std::string wrapped_key; - bool atsc_mode_enabled = false; if (cdm_client_property_set != nullptr) - atsc_mode_enabled = cdm_client_property_set->use_atsc_mode(); - if (!file_handle_->RetrieveCertificate(atsc_mode_enabled, &client_token, - &wrapped_key, &serial_number, - nullptr)) { + atsc_mode_enabled_ = cdm_client_property_set->use_atsc_mode(); + + // If a DRM certificate does not exist, indicate that provisioning is needed. + // The actual validation and loading of a certificate will happen when + // a key request is generated or an offline license is loaded. + if (!file_handle_->HasCertificate(atsc_mode_enabled_)) return NEED_PROVISIONING; - } - CdmResponseType load_cert_sts; - M_TIME( - load_cert_sts = crypto_session_->LoadCertificatePrivateKey(wrapped_key), - crypto_metrics_, crypto_session_load_certificate_private_key_, - load_cert_sts); - switch (load_cert_sts) { - case NO_ERROR: - break; - case SESSION_LOST_STATE_ERROR: - case SYSTEM_INVALIDATED_ERROR: - return load_cert_sts; - default: - return NEED_PROVISIONING; - } - - client_token_type = kClientTokenDrmCert; - - // Session is provisioned with certificate needed to construct - // license request (or with keybox). if (forced_session_id) { key_set_id_ = *forced_session_id; } else { - bool ok = GenerateKeySetId(atsc_mode_enabled, &key_set_id_); - (void)ok; // ok is now used when assertions are turned off. + const bool ok = GenerateKeySetId(atsc_mode_enabled_, &key_set_id_); assert(ok); + if (!ok) { + // Assertions may be disabled + LOGE("Could not generate keyset ID"); + } } session_id_ = @@ -228,8 +193,7 @@ CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, if (!Properties::GetServiceCertificate(session_id_, &service_certificate)) service_certificate.clear(); - if (!license_parser_->Init(client_token, client_token_type, serial_number, - Properties::UsePrivacyMode(session_id_), + if (!license_parser_->Init(Properties::UsePrivacyMode(session_id_), service_certificate, crypto_session_.get(), policy_engine_.get())) return LICENSE_PARSER_INIT_ERROR; @@ -241,24 +205,6 @@ CdmResponseType CdmSession::Init(CdmClientPropertySet* cdm_client_property_set, return NO_ERROR; } -CdmResponseType CdmSession::ReleaseOfflineResources() { - // |license_parser_| and |policy_engine_| are reset in Init. No need to - // deallocate here. - if (usage_support_type_ == kUsageEntrySupport && - has_provider_session_token() && usage_table_header_ != nullptr && - !is_release_) { - UpdateUsageEntryInformation(); - } - - if (!key_set_id_.empty()) { - // Unreserve the license ID. - file_handle_->UnreserveLicenseId(key_set_id_); - } - crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_)); - initialized_ = false; - return NO_ERROR; -} - CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, CdmLicenseType license_type, int* error_detail) { @@ -269,31 +215,13 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, if (!key_set_id_.empty()) { file_handle_->UnreserveLicenseId(key_set_id_); } - - // On android, we previously permitted an offline license to be loaded and - // restored in the same session. OEMCrypto v16+ disallows it so we need to - // release and initialize an OEMCrypto session. We will still prohibit - // multiple restore attempts on the same session. - // TODO(b/161865160): reevalute this scenario. Should we also - // (a) only allow a restore for the same key set ID that was loaded - // (b) if (a) is true, indicate success and do nothing else rather than - // release resources and reinitialize. - // We need to investigate the conditions that caused an app failure and - // led us to add a test to support this use case as there were multiple - // related issues. - if (!has_license_been_loaded_ && has_license_been_restored_) { - LOGE("Disallow multiple offline license restores"); + if (has_license_been_loaded_ || has_license_been_restored_) { + LOGE( + "Disallow multiple offline license restores or restoring a license if " + "a license has already been loaded"); return RESTORE_OFFLINE_LICENSE_ERROR_3; } - if (has_license_been_loaded_) { - CdmResponseType status = ReleaseOfflineResources(); - if (status != NO_ERROR) return status; - status = - Init(cdm_client_property_set_, forced_session_id_, event_listener_); - if (status != NO_ERROR) return status; - } - has_license_been_restored_ = true; key_set_id_ = key_set_id; DeviceFiles::CdmLicenseData license_data; @@ -301,8 +229,9 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, if (!file_handle_->RetrieveLicense(key_set_id, &license_data, &sub_error_code)) { - LOGE("Failed to retrieve license: sub_error_code = %d, key_set_id = %s", - static_cast(sub_error_code), key_set_id.c_str()); + LOGE("Failed to retrieve license: sub_error_code = %s, key_set_id = %s", + DeviceFiles::ResponseTypeToString(sub_error_code), + IdToString(key_set_id)); SetErrorDetail(error_detail, sub_error_code); return sub_error_code == DeviceFiles::kFileNotFound ? KEYSET_ID_NOT_FOUND_4 : GET_LICENSE_ERROR; @@ -318,10 +247,14 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, usage_entry_ = std::move(license_data.usage_entry); usage_entry_number_ = license_data.usage_entry_number; + CdmResponseType result = LoadPrivateOrLegacyKey( + license_data.drm_certificate, license_data.wrapped_private_key); + if (result != NO_ERROR) return result; + // Attempts to restore a released offline license are treated as a release // retry. if (Properties::allow_restore_of_offline_licenses_with_release()) { - if (license_data.state == DeviceFiles::kLicenseStateReleasing) { + if (license_data.state == kLicenseStateReleasing) { license_type = kLicenseTypeRelease; } } @@ -329,18 +262,18 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, // Only restore offline licenses if they are active or this is a release // retry. if (!(license_type == kLicenseTypeRelease || - license_data.state == DeviceFiles::kLicenseStateActive)) { - LOGE("Invalid offline license state: state = %d, license_type = %d", - static_cast(license_data.state), static_cast(license_type)); + license_data.state == kLicenseStateActive)) { + LOGE("Invalid offline license state: state = %s, license_type = %s", + CdmOfflineLicenseStateToString(license_data.state), + CdmLicenseTypeToString(license_type)); return GET_RELEASED_LICENSE_ERROR; } std::string provider_session_token; bool sign_fake_request = false; // TODO(b/169483174): remove this variable. - if (usage_support_type_ == kUsageEntrySupport) { + if (supports_usage_info()) { if (!license_parser_->ExtractProviderSessionToken( - key_response_, &provider_session_token) || - usage_table_header_ == nullptr) { + key_response_, &provider_session_token)) { provider_session_token.clear(); sign_fake_request = true; // TODO(b/169483174): remove this line. } else if (!VerifyOfflineUsageEntry()) { @@ -350,6 +283,11 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, CdmResponseType sts = usage_table_header_->LoadEntry( crypto_session_.get(), usage_entry_, usage_entry_number_); crypto_metrics_->usage_table_header_load_entry_.Increment(sts); + if (sts == LOAD_USAGE_ENTRY_INVALID_SESSION) { + LOGE("License loaded in different session: key_set_id = %s", + IdToString(key_set_id)); + return USAGE_ENTRY_ALREADY_LOADED; + } if (sts != NO_ERROR) { LOGE("Failed to load usage entry: status = %d", static_cast(sts)); return sts; @@ -369,16 +307,14 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, std::string license_request_signature; // Sign a fake message so that OEMCrypto will start the rental clock. The // signature and generated core message are ignored. - const CdmResponseType status = - crypto_session_->PrepareAndSignLicenseRequest( - fake_message, &core_message, &license_request_signature); - if (status != NO_ERROR) return status; + result = crypto_session_->PrepareAndSignLicenseRequest( + fake_message, &core_message, &license_request_signature); + if (result != NO_ERROR) return result; } - CdmResponseType result; if (license_type == kLicenseTypeRelease) { - result = - license_parser_->RestoreLicenseForRelease(key_request_, key_response_); + result = license_parser_->RestoreLicenseForRelease( + license_data.drm_certificate, key_request_, key_response_); if (result != NO_ERROR) { SetErrorDetail(error_detail, result); @@ -386,17 +322,17 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, } } else { result = license_parser_->RestoreOfflineLicense( - key_request_, key_response_, offline_key_renewal_response_, - license_data.playback_start_time, license_data.last_playback_time, - license_data.grace_period_end_time, this); + license_data.drm_certificate, key_request_, key_response_, + offline_key_renewal_response_, license_data.playback_start_time, + license_data.last_playback_time, license_data.grace_period_end_time, + this); if (result != NO_ERROR) { SetErrorDetail(error_detail, result); return RESTORE_OFFLINE_LICENSE_ERROR_2; } } - if (usage_support_type_ == kUsageEntrySupport && - !provider_session_token.empty() && usage_table_header_ != nullptr) { + if (!provider_session_token.empty() && supports_usage_info()) { CdmResponseType sts = usage_table_header_->UpdateEntry( usage_entry_number_, crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { @@ -411,6 +347,7 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id, license_received_ = true; is_offline_ = true; is_release_ = license_type == kLicenseTypeRelease; + has_license_been_restored_ = true; return KEY_ADDED; } @@ -430,9 +367,12 @@ CdmResponseType CdmSession::RestoreUsageSession( usage_entry_number_ = usage_data.usage_entry_number; usage_provider_session_token_ = usage_data.provider_session_token; + CdmResponseType status = LoadPrivateOrLegacyKey( + usage_data.drm_certificate, usage_data.wrapped_private_key); + if (status != NO_ERROR) return status; + CdmResponseType sts = NO_ERROR; - if (usage_support_type_ == kUsageEntrySupport && - usage_table_header_ != nullptr) { + if (supports_usage_info()) { sts = usage_table_header_->LoadEntry(crypto_session_.get(), usage_entry_, usage_entry_number_); crypto_metrics_->usage_table_header_load_entry_.Increment(sts); @@ -442,15 +382,15 @@ CdmResponseType CdmSession::RestoreUsageSession( } } - sts = license_parser_->RestoreLicenseForRelease(key_request_, key_response_); + sts = license_parser_->RestoreLicenseForRelease(usage_data.drm_certificate, + key_request_, key_response_); if (sts != NO_ERROR) { SetErrorDetail(error_detail, sts); return RELEASE_LICENSE_ERROR_2; } - if (usage_support_type_ == kUsageEntrySupport && - usage_table_header_ != nullptr) { + if (supports_usage_info()) { sts = usage_table_header_->UpdateEntry( usage_entry_number_, crypto_session_.get(), &usage_entry_); if (sts != NO_ERROR) { @@ -522,7 +462,8 @@ CdmResponseType CdmSession::GenerateKeyRequestInternal( if (is_release_) { return GenerateReleaseRequest(key_request); - } else if (license_received_) { + } + if (license_received_) { // A call to GenerateKeyRequest after the initial license has been received // is either a renewal/release request or a key rotation event if (init_data.contains_entitled_keys()) { @@ -530,38 +471,40 @@ CdmResponseType CdmSession::GenerateKeyRequestInternal( key_request->type = kKeyRequestTypeNone; key_request->url.clear(); return license_parser_->HandleEmbeddedKeyData(init_data); - } else { - return GenerateRenewalRequest(key_request); } - } else { - key_request->type = kKeyRequestTypeInitial; - - if (!init_data.is_supported()) { - LOGW("Unsupported init data type: %s", init_data.type().c_str()); - return UNSUPPORTED_INIT_DATA; - } - if (init_data.IsEmpty() && !license_parser_->HasInitData()) { - LOGW("Init data absent"); - return INIT_DATA_NOT_FOUND; - } - if (is_offline_ && key_set_id_.empty()) { - LOGE("Unable to generate key set ID"); - return KEY_REQUEST_ERROR_1; - } - - app_parameters_ = app_parameters; - CdmResponseType status = license_parser_->PrepareKeyRequest( - init_data, license_type, app_parameters, &key_request->message, - &key_request->url); - if (status != KEY_MESSAGE) return status; - - key_request_ = key_request->message; - if (is_offline_) { - offline_init_data_ = init_data.data(); - offline_release_server_url_ = key_request->url; - } - return KEY_MESSAGE; + return GenerateRenewalRequest(key_request); } + // Otherwise, initialize license request. + key_request->type = kKeyRequestTypeInitial; + + if (!init_data.is_supported()) { + LOGW("Unsupported init data type: %s", init_data.type().c_str()); + return UNSUPPORTED_INIT_DATA; + } + if (init_data.IsEmpty() && !license_parser_->HasInitData()) { + LOGW("Init data absent"); + return INIT_DATA_NOT_FOUND; + } + if (is_offline_ && key_set_id_.empty()) { + LOGE("Key set ID not set"); + return KEY_REQUEST_ERROR_1; + } + // Attempt to load provisioned private key if available. + CdmResponseType status = LoadPrivateKey(); + if (status != NO_ERROR) return status; + + app_parameters_ = app_parameters; + status = license_parser_->PrepareKeyRequest( + init_data, drm_certificate_, license_type, app_parameters, + &key_request->message, &key_request->url); + if (status != KEY_MESSAGE) return status; + + key_request_ = key_request->message; + if (is_offline_) { + offline_init_data_ = init_data.data(); + offline_release_server_url_ = key_request->url; + } + return KEY_MESSAGE; } // This thin wrapper allows us to update metrics. @@ -579,84 +522,79 @@ CdmResponseType CdmSession::AddKeyInternal(const CdmKeyResponse& key_response) { } if (is_release_) { - CdmResponseType sts = ReleaseKey(key_response); + const CdmResponseType sts = ReleaseKey(key_response); return (sts == NO_ERROR) ? KEY_ADDED : sts; - } else if (license_received_) { // renewal + } + if (license_received_) { // renewal return RenewKey(key_response); - } else { - // If usage table header+entries are supported, preprocess the license - // to see if it has a provider session token. If so a new entry needs - // to be created. - CdmResponseType sts; - std::string provider_session_token; - if (usage_support_type_ == kUsageEntrySupport && - usage_table_header_ != nullptr) { - if (license_parser_->ExtractProviderSessionToken( - key_response, &provider_session_token) && - !provider_session_token.empty()) { - std::string app_id; - GetApplicationId(&app_id); - sts = usage_table_header_->AddEntry( - crypto_session_.get(), is_offline_, key_set_id_, - DeviceFiles::GetUsageInfoFileName(app_id), key_response, - &usage_entry_number_); - crypto_metrics_->usage_table_header_add_entry_.Increment(sts); - if (sts != NO_ERROR) return sts; - } - } - sts = license_parser_->HandleKeyResponse(/* is restore */ false, - key_response); - - // Update the license sdk and service versions. - const VersionInfo& version_info = license_parser_->GetServiceVersion(); - metrics_->license_sdk_version_.Record(version_info.license_sdk_version()); - metrics_->license_sdk_version_.Record( - version_info.license_service_version()); - - // Update or invalidate entry if usage table header+entries are supported - if (usage_support_type_ == kUsageEntrySupport && - !provider_session_token.empty() && usage_table_header_ != nullptr) { - if (sts != KEY_ADDED) { - const CdmResponseType invalidate_sts = - usage_table_header_->InvalidateEntry( - usage_entry_number_, true, file_handle_.get(), crypto_metrics_); - crypto_metrics_->usage_table_header_delete_entry_.Increment( - invalidate_sts); - if (invalidate_sts != NO_ERROR) { - LOGW("Invalidate usage entry failed: status = %d", - static_cast(invalidate_sts)); - } - } - } - - if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? ADD_KEY_ERROR : sts; - - license_received_ = true; - key_response_ = key_response; - - LOGV("Key added: provider_session_token = %s (size = %zu)", - license_parser_->provider_session_token().c_str(), - license_parser_->provider_session_token().size()); - - if ((is_offline_ || has_provider_session_token()) && !is_temporary_) { - if (has_provider_session_token() && - usage_support_type_ == kUsageEntrySupport && - usage_table_header_ != nullptr) { - usage_table_header_->UpdateEntry(usage_entry_number_, - crypto_session_.get(), &usage_entry_); - } - - if (!is_offline_) - usage_provider_session_token_ = - license_parser_->provider_session_token(); - - sts = StoreLicense(); + } + // If usage table header+entries are supported, preprocess the license + // to see if it has a provider session token. If so a new entry needs + // to be created. + CdmResponseType sts; + std::string provider_session_token; + if (supports_usage_info()) { + if (license_parser_->ExtractProviderSessionToken(key_response, + &provider_session_token) && + !provider_session_token.empty()) { + std::string app_id; + GetApplicationId(&app_id); + sts = usage_table_header_->AddEntry( + crypto_session_.get(), is_offline_, key_set_id_, + DeviceFiles::GetUsageInfoFileName(app_id), key_response, + &usage_entry_number_); + crypto_metrics_->usage_table_header_add_entry_.Increment(sts); if (sts != NO_ERROR) return sts; } - has_license_been_loaded_ = true; - - return KEY_ADDED; } + sts = + license_parser_->HandleKeyResponse(/* is restore */ false, key_response); + + // Update the license sdk and service versions. + const VersionInfo& version_info = license_parser_->GetServiceVersion(); + metrics_->license_sdk_version_.Record(version_info.license_sdk_version()); + metrics_->license_service_version_.Record( + version_info.license_service_version()); + + // Update or invalidate entry if usage table header+entries are supported + if (!provider_session_token.empty() && supports_usage_info()) { + if (sts != KEY_ADDED) { + const CdmResponseType invalidate_sts = + usage_table_header_->InvalidateEntry( + usage_entry_number_, true, file_handle_.get(), crypto_metrics_); + crypto_metrics_->usage_table_header_delete_entry_.Increment( + invalidate_sts); + if (invalidate_sts != NO_ERROR) { + LOGW("Invalidate usage entry failed: status = %d", + static_cast(invalidate_sts)); + } + } + } + + if (sts != KEY_ADDED) return (sts == KEY_ERROR) ? ADD_KEY_ERROR : sts; + + license_received_ = true; + key_response_ = key_response; + + LOGV("Key added: provider_session_token = %s (size = %zu)", + IdToString(license_parser_->provider_session_token()), + license_parser_->provider_session_token().size()); + + if ((is_offline_ || has_provider_session_token()) && !is_temporary_) { + if (has_provider_session_token() && supports_usage_info()) { + usage_table_header_->UpdateEntry(usage_entry_number_, + crypto_session_.get(), &usage_entry_); + } + + if (!is_offline_) + usage_provider_session_token_ = license_parser_->provider_session_token(); + + sts = StoreLicense(); + if (sts != NO_ERROR) return sts; + } + + has_license_been_loaded_ = true; + return KEY_ADDED; } CdmResponseType CdmSession::QueryStatus(CdmQueryMap* query_response) { @@ -740,16 +678,21 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParametersV16& params) { if (is_protected) { if (!policy_engine_->CanDecryptContent(params.key_id)) { if (policy_engine_->IsLicenseForFuture()) return DECRYPT_NOT_READY; - if (!policy_engine_->IsSufficientOutputProtection(params.key_id)) + if (!policy_engine_->IsSufficientOutputProtection(params.key_id)) { + LOGE("Key use prohibited as HDCP or resolution requirements not met"); return INSUFFICIENT_OUTPUT_PROTECTION; + } return NEED_KEY; } if (!policy_engine_->CanUseKeyForSecurityLevel(params.key_id)) { + LOGE( + "Key use prohibited as security level requirements in the policy" + " not met"); return KEY_PROHIBITED_FOR_SECURITY_LEVEL; } } - CdmResponseType status = crypto_session_->Decrypt(params); + const CdmResponseType status = crypto_session_->Decrypt(params); if (status == NO_ERROR) { if (is_initial_decryption_) { @@ -759,9 +702,17 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParametersV16& params) { if (!is_usage_update_needed_) { is_usage_update_needed_ = has_provider_session_token(); } + last_decrypt_failed_ = false; } else { - Clock clock; - int64_t current_time = clock.GetCurrentTime(); + if (!last_decrypt_failed_) { + // Only log on the first failure. Certain failures are likely + // to occur in succession of each other. + LOGE("Decryption failed: sid = %s, status = %d", IdToString(session_id_), + static_cast(status)); + } + last_decrypt_failed_ = true; + wvutil::Clock clock; + const int64_t current_time = clock.GetCurrentTime(); if (policy_engine_->HasLicenseOrRentalOrPlaybackDurationExpired( current_time)) { return NEED_KEY; @@ -813,8 +764,7 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { if (is_offline_) { offline_key_renewal_response_ = key_response; - if (!StoreLicense(DeviceFiles::kLicenseStateActive, - nullptr /* error_detail */)) + if (!StoreLicense(kLicenseStateActive, nullptr /* error_detail */)) return RENEW_KEY_ERROR_2; } return KEY_ADDED; @@ -836,8 +786,7 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) { if (KEY_MESSAGE != status) return status; - if (has_provider_session_token() && - usage_support_type_ == kUsageEntrySupport) { + if (has_provider_session_token() && supports_usage_info()) { status = usage_table_header_->UpdateEntry( usage_entry_number_, crypto_session_.get(), &usage_entry_); @@ -848,10 +797,10 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyRequest* key_request) { } if (is_offline_) { // Mark license as being released - if (!StoreLicense(DeviceFiles::kLicenseStateReleasing, nullptr)) + if (!StoreLicense(kLicenseStateReleasing, nullptr)) return RELEASE_KEY_REQUEST_ERROR; } else if (!usage_provider_session_token_.empty()) { - if (usage_support_type_ == kUsageEntrySupport) { + if (supports_usage_info()) { if (!UpdateUsageInfo()) return RELEASE_USAGE_INFO_FAILED; } } @@ -884,9 +833,8 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { LOGE("CDM session not initialized"); return NOT_INITIALIZED_ERROR; } - if (usage_support_type_ != kUsageEntrySupport) { - LOGE("Unexpected usage support type: %d", - static_cast(usage_support_type_)); + if (!supports_usage_info()) { + LOGE("Cannot delete entry, usage table not supported"); return INCORRECT_USAGE_SUPPORT_TYPE_1; } @@ -901,11 +849,9 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_number) { if (sts != NO_ERROR) return sts; usage_table_header_ = nullptr; - if (crypto_session_->GetUsageSupportType(&usage_support_type_) == NO_ERROR) { - if (usage_support_type_ == kUsageEntrySupport) - usage_table_header_ = crypto_session_->GetUsageTableHeader(); - } else { - usage_support_type_ = kNonSecureUsageSupport; + bool has_support = false; + if (crypto_session_->HasUsageInfoSupport(&has_support) && has_support) { + usage_table_header_ = crypto_session_->GetUsageTableHeader(); } if (usage_table_header_ == nullptr) { @@ -930,7 +876,7 @@ int64_t CdmSession::GetDurationRemaining() { CdmSessionId CdmSession::GenerateSessionId() { static int session_num = 1; - return SESSION_ID_PREFIX + IntToString(++session_num); + return SESSION_ID_PREFIX + std::to_string(++session_num); } bool CdmSession::GenerateKeySetId(bool atsc_mode_enabled, @@ -947,9 +893,9 @@ bool CdmSession::GenerateKeySetId(bool atsc_mode_enabled, } if (atsc_mode_enabled) - *key_set_id = ATSC_KEY_SET_ID_PREFIX + b2a_hex(random_data); + *key_set_id = ATSC_KEY_SET_ID_PREFIX + wvutil::b2a_hex(random_data); else - *key_set_id = KEY_SET_ID_PREFIX + b2a_hex(random_data); + *key_set_id = KEY_SET_ID_PREFIX + wvutil::b2a_hex(random_data); // key set collision if (file_handle_->LicenseExists(*key_set_id)) { @@ -978,7 +924,7 @@ CdmResponseType CdmSession::StoreLicense() { return OFFLINE_LICENSE_PROHIBITED; } - if (!StoreLicense(DeviceFiles::kLicenseStateActive, nullptr)) { + if (!StoreLicense(kLicenseStateActive, nullptr)) { LOGE("Unable to store license"); return STORE_LICENSE_ERROR_1; } @@ -997,14 +943,13 @@ CdmResponseType CdmSession::StoreLicense() { if (!file_handle_->StoreUsageInfo( provider_session_token, key_request_, key_response_, DeviceFiles::GetUsageInfoFileName(app_id), key_set_id_, usage_entry_, - usage_entry_number_)) { + usage_entry_number_, drm_certificate_, wrapped_private_key_)) { LOGE("Unable to store usage info"); // Usage info file is corrupt. Delete current usage entry and file. - if (usage_support_type_ == kUsageEntrySupport) { + if (supports_usage_info()) { DeleteUsageEntry(usage_entry_number_); } else { - LOGW("Unexpected usage support type: %d", - static_cast(usage_support_type_)); + LOGW("Cannot store, usage table not supported"); } std::vector provider_session_tokens; file_handle_->DeleteAllUsageInfoForApp( @@ -1015,8 +960,7 @@ CdmResponseType CdmSession::StoreLicense() { return NO_ERROR; } -bool CdmSession::StoreLicense(DeviceFiles::LicenseState state, - int* error_detail) { +bool CdmSession::StoreLicense(CdmOfflineLicenseState state, int* error_detail) { DeviceFiles::ResponseType error_detail_alt = DeviceFiles::kNoError; DeviceFiles::CdmLicenseData license_data{ key_set_id_, @@ -1032,7 +976,9 @@ bool CdmSession::StoreLicense(DeviceFiles::LicenseState state, policy_engine_->GetGracePeriodEndTime(), app_parameters_, usage_entry_, - usage_entry_number_}; + usage_entry_number_, + drm_certificate_, + wrapped_private_key_}; bool result = file_handle_->StoreLicense(license_data, &error_detail_alt); if (error_detail != nullptr) { @@ -1054,8 +1000,7 @@ CdmResponseType CdmSession::RemoveKeys() { CdmResponseType CdmSession::RemoveLicense() { if (is_offline_ || has_provider_session_token()) { - if (usage_support_type_ == kUsageEntrySupport && - has_provider_session_token()) { + if (has_provider_session_token() && supports_usage_info()) { DeleteUsageEntry(usage_entry_number_); } DeleteLicenseFile(); @@ -1086,7 +1031,7 @@ void CdmSession::OnTimerEvent(bool update_usage) { policy_engine_->DecryptionEvent(); has_decrypted_since_last_report_ = false; if (is_offline_ && !is_release_) { - StoreLicense(DeviceFiles::kLicenseStateActive, nullptr); + StoreLicense(kLicenseStateActive, nullptr); } } policy_engine_->OnTimerEvent(); @@ -1105,14 +1050,10 @@ void CdmSession::GetApplicationId(std::string* app_id) { } CdmResponseType CdmSession::UpdateUsageEntryInformation() { - if (usage_support_type_ != kUsageEntrySupport || - !has_provider_session_token() || usage_table_header_ == nullptr) { - LOGE( - "Unexpected state: usage support type = %d, PST present = %s, " - "usage table header available = %s", - static_cast(usage_support_type_), - has_provider_session_token() ? "yes" : "no", - usage_table_header_ == nullptr ? "no" : "yes"); + if (!has_provider_session_token() || !supports_usage_info()) { + LOGE("Unexpected state: usage_support = %s, PST present = %s, ", + supports_usage_info() ? "true" : "false", + has_provider_session_token() ? "yes" : "no"); return INCORRECT_USAGE_SUPPORT_TYPE_2; } @@ -1127,8 +1068,7 @@ CdmResponseType CdmSession::UpdateUsageEntryInformation() { if (sts != NO_ERROR) return sts; if (is_offline_) - StoreLicense(is_release_ ? DeviceFiles::kLicenseStateReleasing - : DeviceFiles::kLicenseStateActive, + StoreLicense(is_release_ ? kLicenseStateReleasing : kLicenseStateActive, nullptr); else if (!usage_provider_session_token_.empty()) UpdateUsageInfo(); @@ -1242,6 +1182,61 @@ bool CdmSession::VerifyOfflineUsageEntry() { return true; } +CdmResponseType CdmSession::LoadPrivateKey() { + std::string drm_certificate; + CryptoWrappedKey private_key; + uint32_t system_id; + + if (file_handle_->RetrieveCertificate(atsc_mode_enabled_, &drm_certificate, + &private_key, nullptr, &system_id) != + DeviceFiles::kCertificateValid) { + return NEED_PROVISIONING; + } + + return LoadPrivateKey(drm_certificate, private_key); +} + +CdmResponseType CdmSession::LoadPrivateOrLegacyKey( + const std::string& certificate, const CryptoWrappedKey& private_key) { + // Use provided key if valid + if (!certificate.empty() && private_key.IsValid()) + return LoadPrivateKey(certificate, private_key); + + // Otherwise use key from legacy certificate + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; + if (file_handle_->RetrieveLegacyCertificate( + &drm_certificate, &wrapped_private_key, nullptr, nullptr) != + DeviceFiles::kCertificateValid) + return NEED_PROVISIONING; + + return LoadPrivateKey(drm_certificate, wrapped_private_key); +} + +CdmResponseType CdmSession::LoadPrivateKey( + const std::string& drm_certificate, const CryptoWrappedKey& private_key) { + CdmResponseType load_cert_sts; + M_TIME( + load_cert_sts = crypto_session_->LoadCertificatePrivateKey(private_key), + crypto_metrics_, crypto_session_load_certificate_private_key_, + load_cert_sts); + + switch (load_cert_sts) { + case NO_ERROR: + metrics_->drm_certificate_key_type_.Record( + DrmKeyTypeToMetricValue(private_key.type())); + + drm_certificate_ = drm_certificate; + wrapped_private_key_ = std::move(private_key); + return NO_ERROR; + case SESSION_LOST_STATE_ERROR: + case SYSTEM_INVALIDATED_ERROR: + return load_cert_sts; + default: + return NEED_PROVISIONING; + } +} + // For testing only - takes ownership of pointers void CdmSession::set_license_parser(CdmLicense* license_parser) { @@ -1261,5 +1256,4 @@ void CdmSession::set_policy_engine(PolicyEngine* policy_engine) { void CdmSession::set_file_handle(DeviceFiles* file_handle) { file_handle_.reset(file_handle); } - } // namespace wvcdm diff --git a/core/src/cdm_session_map.cpp b/core/src/cdm_session_map.cpp index f7b107bf..6bc30cf8 100644 --- a/core/src/cdm_session_map.cpp +++ b/core/src/cdm_session_map.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "cdm_session_map.h" diff --git a/core/src/certificate_provisioning.cpp b/core/src/certificate_provisioning.cpp index 7f35608f..ca28ea9f 100644 --- a/core/src/certificate_provisioning.cpp +++ b/core/src/certificate_provisioning.cpp @@ -1,10 +1,11 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "certificate_provisioning.h" #include "client_identification.h" +#include "crypto_wrapped_key.h" #include "device_files.h" #include "file_store.h" #include "license_protocol.pb.h" @@ -14,8 +15,8 @@ #include "string_conversions.h" #include "wv_cdm_constants.h" +namespace wvcdm { namespace { - const std::string kEmptyString; // URL for Google Provisioning Server. @@ -25,9 +26,14 @@ const std::string kProvisioningServerUrl = "https://www.googleapis.com/" "certificateprovisioning/v1/devicecertificates/create" "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; +// In case of provisioning 4, the default url is used as a way to inform app of +// the current provisioning stage. In the first stage, this suffix is appended +// to kProvisioningServerUrl; in the second stage, there is no change to +// kProvisioningServerUrl. +const std::string kProv40FirstStageServerUrlSuffix = "&preProvisioning=true"; // NOTE: Provider ID = widevine.com -const std::string kCpProductionServiceCertificate = wvcdm::a2bs_hex( +const std::string kCpProductionServiceCertificate = wvutil::a2bs_hex( "0ab9020803121051434fe2a44c763bcc2c826a2d6ef9a718f7d793d005228e02" "3082010a02820101009e27088659dbd9126bc6ed594caf652b0eaab82abb9862" "ada1ee6d2cb5247e94b28973fef5a3e11b57d0b0872c930f351b5694354a8c77" @@ -51,72 +57,58 @@ const std::string kCpProductionServiceCertificate = wvcdm::a2bs_hex( "8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb" "1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28"); -/* - * Provisioning response is a base64-encoded protobuf, optionally within a - * JSON wrapper. If the JSON wrapper is present, extract the embedded response - * message. Then perform the base64 decode and return the result. - * - * If an error occurs during the parse or the decode, return an empty string. - */ -bool ExtractAndDecodeSignedMessage(const std::string& provisioning_response, - std::string* result) { - const std::string json_start_substr("\"signedResponse\": \""); - const std::string json_end_substr("\""); - std::string message_string; +// Used in provisioning 4 client identification name value pairs. +const std::string kKeyAppParameterSpoid = "spoid"; +const std::string kKeyAppParameterProviderId = "provider_id"; +const std::string kKeyAppParameterStableId = "stable_id"; - if (result == nullptr) { - LOGE("Output parameter |result| is not provided"); +// Retrieves |stored_oem_cert| from |file_handle|, and load the OEM private key +// to |crypto_session|. Returns true if all operations are successful. +bool RetrieveOemCertificateAndLoadPrivateKey(CryptoSession& crypto_session, + DeviceFiles& file_handle, + std::string& stored_oem_cert) { + stored_oem_cert.clear(); + CryptoWrappedKey wrapped_private_key; + if (file_handle.RetrieveOemCertificate(&stored_oem_cert, + &wrapped_private_key) != + DeviceFiles::kCertificateValid) { + LOGE("An invalid stored OEM certificated is retrieved"); + stored_oem_cert.clear(); return false; } - - size_t start = provisioning_response.find(json_start_substr); - - if (start == provisioning_response.npos) { - // Message is not properly wrapped - reject it. - LOGE("Cannot locate start substring"); - result->clear(); + if (crypto_session.LoadOemCertificatePrivateKey(wrapped_private_key) != + NO_ERROR) { + LOGE("Can not load the OEM private key"); + stored_oem_cert.clear(); return false; } - - // Appears to be JSON-wrapped protobuf - find end of protobuf portion. - const size_t end = provisioning_response.find( - json_end_substr, start + json_start_substr.length()); - if (end == provisioning_response.npos) { - LOGE("Cannot locate end substring"); - result->clear(); - return false; - } - - size_t b64_string_size = end - start - json_start_substr.length(); - message_string.assign(provisioning_response, - start + json_start_substr.length(), b64_string_size); - - if (message_string.empty()) { - LOGE("CDM provisioning response is empty"); - result->clear(); - return false; - } - - // Decode the base64-encoded message. - const std::vector decoded_message = - wvcdm::Base64SafeDecode(message_string); - result->assign(decoded_message.begin(), decoded_message.end()); return true; } } // namespace - -namespace wvcdm { // Protobuf generated classes. using video_widevine::ClientIdentification_ClientCapabilities; using video_widevine::ClientIdentification_NameValue; -using video_widevine::DrmDeviceCertificate; +using video_widevine::DrmCertificate; using video_widevine::EncryptedClientIdentification; using video_widevine::ProvisioningOptions; using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningResponse; -using video_widevine::SignedDrmDeviceCertificate; +using video_widevine::PublicKeyToCertify; +using video_widevine::SignedDrmCertificate; using video_widevine::SignedProvisioningMessage; +using video_widevine:: + SignedProvisioningMessage_ProvisioningProtocolVersion_VERSION_1_1; + +// static +void CertificateProvisioning::GetProvisioningServerUrl( + std::string* default_url) { + if (default_url == nullptr) { + LOGE("Output |default_url| is null"); + return; + } + default_url->assign(kProvisioningServerUrl); +} CdmResponseType CertificateProvisioning::Init( const std::string& service_certificate) { @@ -171,12 +163,16 @@ CdmResponseType CertificateProvisioning::SetSpoidParameter( * Return the provisioning protocol version - dictated by OEMCrypto * support for OEM certificates. */ -SignedProvisioningMessage::ProtocolVersion -CertificateProvisioning::GetProtocolVersion() { - if (crypto_session_->GetPreProvisionTokenType() == kClientTokenOemCert) - return SignedProvisioningMessage::PROVISIONING_30; - else - return SignedProvisioningMessage::PROVISIONING_20; +SignedProvisioningMessage::ProvisioningType +CertificateProvisioning::GetProvisioningType() { + switch (crypto_session_->GetPreProvisionTokenType()) { + case kClientTokenBootCertChain: + return SignedProvisioningMessage::PROVISIONING_40; + case kClientTokenOemCert: + return SignedProvisioningMessage::PROVISIONING_30; + default: + return SignedProvisioningMessage::PROVISIONING_20; + } } /* @@ -187,10 +183,22 @@ CertificateProvisioning::GetProtocolVersion() { * Returns NO_ERROR for success and CERT_PROVISIONING_REQUEST_ERROR_? if fails. */ CdmResponseType CertificateProvisioning::GetProvisioningRequest( - SecurityLevel requested_security_level, CdmCertificateType cert_type, - const std::string& cert_authority, const std::string& origin, - const std::string& spoid, CdmProvisioningRequest* request, - std::string* default_url) { + wvutil::FileSystem* file_system, + RequestedSecurityLevel requested_security_level, + CdmCertificateType cert_type, const std::string& cert_authority, + const std::string& origin, const std::string& spoid, + CdmProvisioningRequest* request, std::string* default_url) { + return CloseSessionOnError(GetProvisioningRequestInternal( + file_system, requested_security_level, cert_type, cert_authority, origin, + spoid, request, default_url)); +} + +CdmResponseType CertificateProvisioning::GetProvisioningRequestInternal( + wvutil::FileSystem* file_system, + RequestedSecurityLevel requested_security_level, + CdmCertificateType cert_type, const std::string& cert_authority, + const std::string& origin, const std::string& spoid, + CdmProvisioningRequest* request, std::string* default_url) { if (!request || !default_url) { LOGE("Output parameter |%s| is not provided", request ? "default_url" : "request"); @@ -199,7 +207,7 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( default_url->assign(kProvisioningServerUrl); - if (crypto_session_->IsOpen()) crypto_session_->Close(); + CloseSession(); CdmResponseType status = crypto_session_->Open(requested_security_level); if (NO_ERROR != status) { LOGE("Failed to create a crypto session: status = %d", @@ -207,30 +215,19 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( return status; } + if (crypto_session_->GetPreProvisionTokenType() == + kClientTokenBootCertChain) { + return GetProvisioning40RequestInternal(file_system, origin, spoid, request, + default_url); + } + // Prepare device provisioning request. ProvisioningRequest provisioning_request; - wvcdm::ClientIdentification id; - status = id.Init(crypto_session_.get()); + status = FillEncryptedClientId(/*client_token=*/"", provisioning_request, + *service_certificate_); if (status != NO_ERROR) return status; - video_widevine::ClientIdentification client_id; - - CdmAppParameterMap app_parameter; - status = id.Prepare(app_parameter, kEmptyString, &client_id); - if (status != NO_ERROR) return status; - - if (!service_certificate_->has_certificate()) { - LOGE("Service certificate not staged"); - return CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE; - } - - // Encrypt client identification - EncryptedClientIdentification* encrypted_client_id = - provisioning_request.mutable_encrypted_client_id(); - status = service_certificate_->EncryptClientId( - crypto_session_.get(), &client_id, encrypted_client_id); - uint32_t nonce; status = crypto_session_->GenerateNonce(&nonce); @@ -290,28 +287,260 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( SignedProvisioningMessage signed_provisioning_msg; signed_provisioning_msg.set_message(serialized_message); signed_provisioning_msg.set_signature(request_signature); - signed_provisioning_msg.set_protocol_version(GetProtocolVersion()); + signed_provisioning_msg.set_provisioning_type(GetProvisioningType()); if (core_message.empty()) { // OEMCrypto does not support core messages. supports_core_messages_ = false; } else { signed_provisioning_msg.set_oemcrypto_core_message(core_message); } + signed_provisioning_msg.set_protocol_version( + SignedProvisioningMessage_ProvisioningProtocolVersion_VERSION_1_1); std::string serialized_request; signed_provisioning_msg.SerializeToString(&serialized_request); if (!wvcdm::Properties::provisioning_messages_are_binary()) { // Return request as web-safe base64 string - std::vector request_vector(serialized_request.begin(), - serialized_request.end()); - *request = Base64SafeEncodeNoPad(request_vector); + *request = wvutil::Base64SafeEncodeNoPad(serialized_request); } else { *request = std::move(serialized_request); } return NO_ERROR; } +CdmResponseType CertificateProvisioning::GetProvisioning40RequestInternal( + wvutil::FileSystem* file_system, const std::string& origin, + const std::string& spoid, CdmProvisioningRequest* request, + std::string* default_url) { + if (!crypto_session_->IsOpen()) { + LOGE("Crypto session is not open"); + return PROVISIONING_4_CRYPTO_SESSION_NOT_OPEN; + } + + if (file_system == nullptr) { + LOGE("file_system is nullptr but is required in provisioning 4"); + return PROVISIONING_4_FILE_SYSTEM_IS_NULL; + } + const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); + wvutil::FileSystem global_file_system; + DeviceFiles global_file_handle(&global_file_system); + if (!global_file_handle.Init(security_level)) { + LOGE("Failed to initialize global DeviceFiles"); + return PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES; + } + + ProvisioningRequest provisioning_request; + // Determine the current stage by checking if OEM cert exists. + std::string stored_oem_cert; + if (global_file_handle.HasOemCertificate()) { + // This is second stage requesting for DRM cert. We try to use the stored + // OEM cert. In case of error, we just fall back to the first stage + // provisioning (request for an OEM cert). + if (!RetrieveOemCertificateAndLoadPrivateKey( + *crypto_session_, global_file_handle, stored_oem_cert)) { + stored_oem_cert.clear(); + LOGD("Deleting the stored OEM certificate due to unsuccessful read"); + if (!global_file_handle.RemoveOemCertificate()) { + // This should not happen. + LOGE("Failed to delete the OEM certificate certificate"); + } + } + } + + // Retrieve the Spoid, but put it to the client identification instead, so it + // is encrypted. + CdmAppParameterMap additional_parameter; + CdmResponseType status = + SetSpoidParameter(origin, spoid, &provisioning_request); + if (status != NO_ERROR) return status; + if (provisioning_request.has_spoid()) { + additional_parameter[kKeyAppParameterSpoid] = provisioning_request.spoid(); + provisioning_request.clear_spoid(); + } + if (provisioning_request.has_provider_id()) { + additional_parameter[kKeyAppParameterProviderId] = + provisioning_request.provider_id(); + provisioning_request.clear_provider_id(); + } + if (provisioning_request.has_stable_id()) { + additional_parameter[kKeyAppParameterStableId] = + provisioning_request.stable_id(); + provisioning_request.clear_stable_id(); + } + + if (stored_oem_cert.empty()) { + // This is the first stage provisioning. + default_url->assign(kProvisioningServerUrl + + kProv40FirstStageServerUrlSuffix); + + // First-stage provisioning always uses the WV production service cert for + // encryption. + ServiceCertificate wv_service_cert; + status = wv_service_cert.Init(kCpProductionServiceCertificate); + if (status != NO_ERROR) return status; + + // Since |stored_oem_cert| is empty, the client identification token will be + // retrieved from OEMCrypto, which is the BCC in this case. + status = FillEncryptedClientIdWithAdditionalParameter( + stored_oem_cert, additional_parameter, provisioning_request, + wv_service_cert); + if (status != NO_ERROR) return status; + } else { + // This is the second stage provisioning. + default_url->assign(kProvisioningServerUrl); + // Since |stored_oem_cert| is non-empty, it will be used as the client + // identification token. + status = FillEncryptedClientIdWithAdditionalParameter( + stored_oem_cert, additional_parameter, provisioning_request, + *service_certificate_); + if (status != NO_ERROR) return status; + } + + std::string public_key; + std::string public_key_signature; + provisioning_40_wrapped_private_key_.clear(); + provisioning_40_key_type_ = CryptoWrappedKey::kUninitialized; + status = crypto_session_->GenerateCertificateKeyPair( + &public_key, &public_key_signature, &provisioning_40_wrapped_private_key_, + &provisioning_40_key_type_); + if (status != NO_ERROR) return status; + + PublicKeyToCertify* key_to_certify = + provisioning_request.mutable_certificate_public_key(); + key_to_certify->set_public_key(public_key); + key_to_certify->set_signature(public_key_signature); + key_to_certify->set_key_type(provisioning_40_key_type_ == + CryptoWrappedKey::kRsa + ? PublicKeyToCertify::RSA + : PublicKeyToCertify::ECC); + + // In provisioning 4, the message is not signed. + SignedProvisioningMessage signed_provisioning_msg; + provisioning_request.SerializeToString( + signed_provisioning_msg.mutable_message()); + signed_provisioning_msg.set_provisioning_type(GetProvisioningType()); + signed_provisioning_msg.set_protocol_version( + SignedProvisioningMessage_ProvisioningProtocolVersion_VERSION_1_1); + + std::string serialized_request; + signed_provisioning_msg.SerializeToString(&serialized_request); + + if (!wvcdm::Properties::provisioning_messages_are_binary()) { + // Return request as web-safe base64 string + *request = wvutil::Base64SafeEncodeNoPad(serialized_request); + } else { + *request = std::move(serialized_request); + } + return NO_ERROR; +} + +CdmResponseType CertificateProvisioning::FillEncryptedClientId( + const std::string& client_token, ProvisioningRequest& provisioning_request, + const ServiceCertificate& service_certificate) { + CdmAppParameterMap app_parameter; + return FillEncryptedClientIdWithAdditionalParameter( + client_token, app_parameter, provisioning_request, service_certificate); +} + +CdmResponseType +CertificateProvisioning::FillEncryptedClientIdWithAdditionalParameter( + const std::string& client_token, + const CdmAppParameterMap& additional_parameter, + ProvisioningRequest& provisioning_request, + const ServiceCertificate& service_certificate) { + if (!crypto_session_->IsOpen()) { + return UNKNOWN_ERROR; + } + + wvcdm::ClientIdentification id; + CdmResponseType status = + id.InitForProvisioningRequest(client_token, crypto_session_.get()); + if (status != NO_ERROR) return status; + + video_widevine::ClientIdentification client_id; + status = id.Prepare(additional_parameter, kEmptyString, &client_id); + if (status != NO_ERROR) return status; + + if (!service_certificate.has_certificate()) { + LOGE("Service certificate not staged"); + return CERT_PROVISIONING_EMPTY_SERVICE_CERTIFICATE; + } + + // Encrypt client identification + return service_certificate.EncryptClientId( + crypto_session_.get(), &client_id, + provisioning_request.mutable_encrypted_client_id()); +} + +CdmResponseType CertificateProvisioning::HandleProvisioning40Response( + wvutil::FileSystem* file_system, const std::string& response_message) { + ProvisioningResponse provisioning_response; + if (response_message.empty() || + !provisioning_response.ParseFromString(response_message)) { + return PROVISIONING_4_RESPONSE_FAILED_TO_PARSE_MESSAGE; + } + if (provisioning_response.has_status() && + provisioning_response.status() != ProvisioningResponse::NO_ERROR) { + LOGE("Provisioning Response status: %d", provisioning_response.status()); + switch (provisioning_response.status()) { + case ProvisioningResponse::REVOKED_DEVICE_CREDENTIALS: + case ProvisioningResponse::REVOKED_DEVICE_SERIES: + return DEVICE_REVOKED; + default: + return PROVISIONING_4_RESPONSE_HAS_ERROR_STATUS; + } + } + + const std::string& device_certificate = + provisioning_response.device_certificate(); + if (device_certificate.empty()) { + LOGE("Provisioning response has no certificate"); + return PROVISIONING_4_RESPONSE_HAS_NO_CERTIFICATE; + } + + if (provisioning_40_wrapped_private_key_.empty()) { + LOGE("No private key was generated"); + return PROVISIONING_4_NO_PRIVATE_KEY; + } + const CryptoWrappedKey private_key(provisioning_40_key_type_, + provisioning_40_wrapped_private_key_); + + const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); + CloseSession(); + wvutil::FileSystem global_file_system; + DeviceFiles global_file_handle(&global_file_system); + if (!global_file_handle.Init(security_level)) { + LOGE("Failed to initialize global DeviceFiles"); + return PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_2; + } + + // Check the stage of the provisioning by checking if an OEM cert is already + // stored in the file system. + if (!global_file_handle.HasOemCertificate()) { + // No OEM cert already stored => the response is expected to be an OEM cert. + if (!global_file_handle.StoreOemCertificate(device_certificate, + private_key)) { + LOGE("Failed to store provisioning 4 OEM certificate"); + return PROVISIONING_4_FAILED_TO_STORE_OEM_CERTIFICATE; + } + } else { + // The response is assumed to be an DRM cert. + DeviceFiles per_origin_file_handle(file_system); + if (!per_origin_file_handle.Init(security_level)) { + LOGE("Failed to initialize per-origin DeviceFiles"); + return PROVISIONING_4_FAILED_TO_INITIALIZE_DEVICE_FILES_3; + } + if (!per_origin_file_handle.StoreCertificate(device_certificate, + private_key)) { + LOGE("Failed to store provisioning 4 DRM certificate"); + return PROVISIONING_4_FAILED_TO_STORE_DRM_CERTIFICATE; + } + } + + return NO_ERROR; +} + /* * The response message consists of a device certificate and the device RSA key. * The device RSA key is stored in the T.E.E. The device certificate is stored @@ -320,8 +549,9 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest( * Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails. */ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( - FileSystem* file_system, const CdmProvisioningResponse& response_message, - std::string* cert, std::string* wrapped_key) { + wvutil::FileSystem* file_system, + const CdmProvisioningResponse& response_message, std::string* cert, + std::string* wrapped_key) { if (response_message.empty()) { LOGE("Provisioning response message is empty"); return CERT_PROVISIONING_RESPONSE_ERROR_1; @@ -333,7 +563,8 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( } else { // The response is base64 encoded in a JSON wrapper. // Extract it and decode it. On error return an empty string. - bool result = ExtractAndDecodeSignedMessage(response_message, &response); + const bool result = + ExtractAndDecodeSignedMessage(response_message, &response); if (!result || response.empty()) { LOGE("Provisioning response message is an invalid JSON/base64 string"); return CERT_PROVISIONING_RESPONSE_ERROR_1; @@ -349,6 +580,11 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( return CERT_PROVISIONING_RESPONSE_ERROR_2; } + if (signed_response.provisioning_type() == + SignedProvisioningMessage::PROVISIONING_40) { + return HandleProvisioning40Response(file_system, signed_response.message()); + } + bool error = false; if (!signed_response.has_signature()) { LOGE("Signed response does not have signature"); @@ -391,9 +627,25 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( return CERT_PROVISIONING_RESPONSE_ERROR_4; } - std::string wrapped_private_key; + if (provisioning_response.has_status()) { + if (provisioning_response.status() != ProvisioningResponse::NO_ERROR) { + LOGE("Provisioning Response status: %d", provisioning_response.status()); + } + + switch (provisioning_response.status()) { + case ProvisioningResponse::NO_ERROR: + break; + case ProvisioningResponse::REVOKED_DEVICE_CREDENTIALS: + case ProvisioningResponse::REVOKED_DEVICE_SERIES: + return DEVICE_REVOKED; + default: + return CERT_PROVISIONING_RESPONSE_ERROR_10; + } + } + + CryptoWrappedKey private_key; const CdmResponseType status = crypto_session_->LoadProvisioning( - signed_message, core_message, signature, &wrapped_private_key); + signed_message, core_message, signature, &private_key.key()); if (status != NO_ERROR) { LOGE("LoadProvisioning failed: status = %d", static_cast(status)); @@ -401,18 +653,49 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( } const CdmSecurityLevel security_level = crypto_session_->GetSecurityLevel(); - crypto_session_->Close(); + CloseSession(); - // This is the entire certificate (SignedDrmDeviceCertificate). - const std::string& device_certificate = + // This is the entire certificate (SignedDrmCertificate). + const std::string& device_cert_data = provisioning_response.device_certificate(); if (cert_type_ == kCertificateX509) { - *cert = device_certificate; - *wrapped_key = wrapped_private_key; + *cert = device_cert_data; + *wrapped_key = private_key.key(); return NO_ERROR; } + // Need to parse cert for key type. + SignedDrmCertificate signed_device_cert; + if (!signed_device_cert.ParseFromString(device_cert_data)) { + LOGE("Failed to parse signed DRM certificate"); + return CERT_PROVISIONING_RESPONSE_ERROR_9; + } + DrmCertificate device_cert; + if (!device_cert.ParseFromString(signed_device_cert.drm_certificate())) { + LOGE("Failed to parse DRM certificate"); + return CERT_PROVISIONING_RESPONSE_ERROR_9; + } + if (!device_cert.has_algorithm()) { + LOGW("DRM certificate does not specify algorithm type, assuming RSA"); + private_key.set_type(CryptoWrappedKey::kRsa); + } else { + switch (device_cert.algorithm()) { + case DrmCertificate::RSA: + private_key.set_type(CryptoWrappedKey::kRsa); + break; + case DrmCertificate::ECC_SECP256R1: + case DrmCertificate::ECC_SECP384R1: + case DrmCertificate::ECC_SECP521R1: + private_key.set_type(CryptoWrappedKey::kEcc); + break; + default: + LOGE("Unknown DRM key type: algorithm = %d", + static_cast(device_cert.algorithm())); + return CERT_PROVISIONING_RESPONSE_ERROR_9; + } + } + // The certificate will be stored to the device as the final step in // the device provisioning process. @@ -421,7 +704,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( LOGE("Failed to initialize DeviceFiles"); return CERT_PROVISIONING_RESPONSE_ERROR_7; } - if (!handle.StoreCertificate(device_certificate, wrapped_private_key)) { + if (!handle.StoreCertificate(device_cert_data, private_key)) { LOGE("Failed to store provisioning certificate"); return CERT_PROVISIONING_RESPONSE_ERROR_8; } @@ -429,15 +712,62 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse( return NO_ERROR; } -// Static -bool CertificateProvisioning::ExtractAndDecodeSignedMessageForTesting( +// Provisioning response is a base64-encoded protobuf, optionally within a +// JSON wrapper. If the JSON wrapper is present, extract the embedded response +// message. Then perform the base64 decode and return the result. +// +// If an error occurs during the parse or the decode, return an empty string. +// static +bool CertificateProvisioning::ExtractAndDecodeSignedMessage( const std::string& provisioning_response, std::string* result) { - return ExtractAndDecodeSignedMessage(provisioning_response, result); + const std::string json_start_substr("\"signedResponse\": \""); + const std::string json_end_substr("\""); + std::string message_string; + + if (result == nullptr) { + LOGE("Output parameter |result| is not provided"); + return false; + } + + size_t start = provisioning_response.find(json_start_substr); + + if (start == provisioning_response.npos) { + // Message is not properly wrapped - reject it. + LOGE("Cannot locate start substring"); + result->clear(); + return false; + } + + // Appears to be JSON-wrapped protobuf - find end of protobuf portion. + const size_t end = provisioning_response.find( + json_end_substr, start + json_start_substr.length()); + if (end == provisioning_response.npos) { + LOGE("Cannot locate end substring"); + result->clear(); + return false; + } + + size_t b64_string_size = end - start - json_start_substr.length(); + message_string.assign(provisioning_response, + start + json_start_substr.length(), b64_string_size); + + if (message_string.empty()) { + LOGE("CDM provisioning response is empty"); + result->clear(); + return false; + } + + // Decode the base64-encoded message. + const std::vector decoded_message = + wvutil::Base64SafeDecode(message_string); + result->assign(decoded_message.begin(), decoded_message.end()); + return true; } bool CertificateProvisioning::ExtractDeviceInfo( const std::string& device_certificate, std::string* serial_number, - uint32_t* system_id) { + uint32_t* system_id, int64_t* creation_time_seconds, + int64_t* expiration_time_seconds) { LOGV("Extracting device info"); if (serial_number == nullptr && system_id == nullptr) { LOGE("Output parameters |serial_number| and |system_id| not provided"); @@ -445,27 +775,46 @@ bool CertificateProvisioning::ExtractDeviceInfo( } // Get serial number and system ID from certificate - SignedDrmDeviceCertificate signed_drm_device_certificate; - if (!signed_drm_device_certificate.ParseFromString(device_certificate) || - !signed_drm_device_certificate.has_drm_certificate()) { + SignedDrmCertificate signed_drm_certificate; + if (!signed_drm_certificate.ParseFromString(device_certificate) || + !signed_drm_certificate.has_drm_certificate()) { LOGE("Failed to parse signed DRM device certificate"); return false; } - DrmDeviceCertificate drm_device_certificate; - if (!drm_device_certificate.ParseFromString( - signed_drm_device_certificate.drm_certificate()) || - (drm_device_certificate.type() != - video_widevine::DrmDeviceCertificate::DRM_USER_DEVICE)) { + DrmCertificate drm_certificate; + if (!drm_certificate.ParseFromString( + signed_drm_certificate.drm_certificate()) || + (drm_certificate.type() != video_widevine::DrmCertificate::DEVICE)) { LOGE("Failed to parse DRM device certificate message"); return false; } if (serial_number != nullptr) { - *serial_number = drm_device_certificate.serial_number(); + *serial_number = drm_certificate.serial_number(); } if (system_id != nullptr) { - *system_id = drm_device_certificate.system_id(); + *system_id = drm_certificate.system_id(); + } + if (creation_time_seconds != nullptr) { + *creation_time_seconds = drm_certificate.has_creation_time_seconds() + ? drm_certificate.creation_time_seconds() + : INVALID_TIME; + } + if (expiration_time_seconds != nullptr) { + *expiration_time_seconds = drm_certificate.has_expiration_time_seconds() + ? drm_certificate.expiration_time_seconds() + : INVALID_TIME; } return true; } +void CertificateProvisioning::CloseSession() { + if (crypto_session_->IsOpen()) crypto_session_->Close(); +} + +CdmResponseType CertificateProvisioning::CloseSessionOnError( + CdmResponseType status) { + if (status != NO_ERROR) CloseSession(); + return status; +} + } // namespace wvcdm diff --git a/core/src/client_identification.cpp b/core/src/client_identification.cpp index c039a6a3..f3ae6ddc 100644 --- a/core/src/client_identification.cpp +++ b/core/src/client_identification.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "client_identification.h" @@ -13,24 +13,54 @@ #include "string_conversions.h" #include "wv_cdm_constants.h" +namespace wvcdm { namespace { const std::string kKeyCompanyName = "company_name"; const std::string kKeyModelName = "model_name"; +const std::string kKeyModelYear = "model_year"; const std::string kKeyArchitectureName = "architecture_name"; const std::string kKeyDeviceName = "device_name"; const std::string kKeyProductName = "product_name"; const std::string kKeyBuildInfo = "build_info"; -const std::string kKeyDeviceId = "device_id"; const std::string kKeyWvCdmVersion = "widevine_cdm_version"; const std::string kKeyOemCryptoSecurityPatchLevel = "oem_crypto_security_patch_level"; const std::string kKeyOemCryptoBuildInformation = "oem_crypto_build_information"; -} // unnamed namespace -namespace wvcdm { +// These client identification keys are used by the CDM for relaying +// important device information that cannot be overwritten by the app. +const std::array kReservedProperties = { + kKeyCompanyName, + kKeyModelName, + kKeyModelYear, + kKeyArchitectureName, + kKeyDeviceName, + kKeyProductName, + kKeyBuildInfo, + kKeyWvCdmVersion, + kKeyOemCryptoSecurityPatchLevel, + kKeyOemCryptoBuildInformation, + // TODO(b/148813171,b/142280599): include "origin" and "application_name" + // to this list once collection of this information has been moved + // to the core CDM. +}; + +// Checks if the client-provided |prop_name| is reserved for CDM device +// identification with the license server. Property keys which are +// reserved should be dropped from the request. +bool IsPropertyKeyReserved(const std::string& prop_name) { + for (const std::string& reserved_prop_name : kReservedProperties) { + if (prop_name == reserved_prop_name) return true; + } + return false; +} +} // namespace + // Protobuf generated classes. -using video_widevine::ClientIdentification_ClientCapabilities; +using ClientCapabilities = + video_widevine::ClientIdentification::ClientCapabilities; +using AnalogOutputCapabilities = ClientCapabilities::AnalogOutputCapabilities; using video_widevine::ClientIdentification_NameValue; using video_widevine::EncryptedClientIdentification; using video_widevine::ProvisioningOptions; @@ -38,37 +68,45 @@ using video_widevine::ProvisioningRequest; using video_widevine::ProvisioningResponse; using video_widevine::SignedProvisioningMessage; -CdmResponseType ClientIdentification::Init(CryptoSession* crypto_session) { +CdmResponseType ClientIdentification::InitForProvisioningRequest( + const std::string& client_token, CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return PARAMETER_NULL; } - is_license_request_ = false; + client_token_ = client_token; crypto_session_ = crypto_session; return NO_ERROR; } -CdmResponseType ClientIdentification::Init(const std::string& client_token, - const std::string& device_id, - CryptoSession* crypto_session) { +CdmResponseType ClientIdentification::InitForLicenseRequest( + const std::string& client_token, CryptoSession* crypto_session) { if (crypto_session == nullptr) { LOGE("Crypto session not provided"); return PARAMETER_NULL; } - if (client_token.empty()) { LOGE("Client token is empty"); return PARAMETER_NULL; } - is_license_request_ = true; - device_id_ = device_id; client_token_ = client_token; crypto_session_ = crypto_session; return NO_ERROR; } +CdmResponseType ClientIdentification::InitForOtaKeyboxProvisioning( + CryptoSession* crypto_session) { + if (crypto_session == nullptr) { + LOGE("Crypto session not provided"); + return PARAMETER_NULL; + } + is_okp_request_ = true; + crypto_session_ = crypto_session; + return NO_ERROR; +} + /* * Return the ClientIdentification message token type for provisioning request. * NOTE: a DRM Cert should never be presented to the provisioning server. @@ -81,7 +119,15 @@ CdmResponseType ClientIdentification::Prepare( client_id->set_type( video_widevine::ClientIdentification::DRM_DEVICE_CERTIFICATE); client_id->set_token(client_token_); - } else { + } else if (!client_token_.empty()) { + // A token has been provided via InitForProvisioningRequest. This can only + // happen in provisioning 4 (second stage) where an OEM cert is provided. + client_id->set_type( + video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE); + client_id->set_token(client_token_); + } else if (!is_okp_request_) { + // An OTA Keybox Provisioning request does not have a client id. + // Otherwise this is a regular provisioning request. video_widevine::ClientIdentification::TokenType token_type; if (!GetProvisioningTokenType(&token_type)) { LOGE("Failed to get provisioning token type"); @@ -90,19 +136,29 @@ CdmResponseType ClientIdentification::Prepare( client_id->set_type(token_type); std::string token; - CdmResponseType status = crypto_session_->GetProvisioningToken(&token); + std::string additional_token; + CdmResponseType status = + crypto_session_->GetProvisioningToken(&token, &additional_token); if (status != NO_ERROR) { LOGE("Failed to get provisioning token: status = %d", static_cast(status)); return status; } client_id->set_token(token); + if (!additional_token.empty()) { + client_id->mutable_device_credentials()->set_token(additional_token); + } } ClientIdentification_NameValue* client_info; if (is_license_request_) { CdmAppParameterMap::const_iterator iter; - for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) { + for (iter = app_parameters.begin(); iter != app_parameters.end(); ++iter) { + if (IsPropertyKeyReserved(iter->first)) { + LOGD("Discarding client property: name = \"%s\", value = \"%s\"", + iter->first.c_str(), iter->second.c_str()); + continue; + } client_info = client_id->add_client_info(); client_info->set_name(iter->first); client_info->set_value(iter->second); @@ -119,6 +175,11 @@ CdmResponseType ClientIdentification::Prepare( client_info->set_name(kKeyModelName); client_info->set_value(value); } + if (Properties::GetModelYear(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kKeyModelYear); + client_info->set_value(value); + } if (Properties::GetArchitectureName(&value)) { client_info = client_id->add_client_info(); client_info->set_name(kKeyArchitectureName); @@ -158,14 +219,19 @@ CdmResponseType ClientIdentification::Prepare( client_id->set_provider_client_token(provider_client_token); } - ClientIdentification_ClientCapabilities* client_capabilities = + if (is_okp_request_) { + // Capabilities is not important for OTA keybox provisionining. + return NO_ERROR; + } + + ClientCapabilities* client_capabilities = client_id->mutable_client_capabilities(); client_capabilities->set_client_token(true); if (is_license_request_) { bool supports_usage_information; - if (crypto_session_->UsageInformationSupport(&supports_usage_information)) { + if (crypto_session_->HasUsageInfoSupport(&supports_usage_information)) { client_capabilities->set_session_token(supports_usage_information); } @@ -185,38 +251,31 @@ CdmResponseType ClientIdentification::Prepare( switch (max_version) { case HDCP_NONE: client_capabilities->set_max_hdcp_version( - video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NONE); + ClientCapabilities::HDCP_NONE); break; case HDCP_V1: client_capabilities->set_max_hdcp_version( - video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V1); + ClientCapabilities::HDCP_V1); break; case HDCP_V2: client_capabilities->set_max_hdcp_version( - video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2); + ClientCapabilities::HDCP_V2); break; case HDCP_V2_1: client_capabilities->set_max_hdcp_version( - video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1); + ClientCapabilities::HDCP_V2_1); break; case HDCP_V2_2: client_capabilities->set_max_hdcp_version( - video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_2); + ClientCapabilities::HDCP_V2_2); break; case HDCP_V2_3: client_capabilities->set_max_hdcp_version( - video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_3); + ClientCapabilities::HDCP_V2_3); break; case HDCP_NO_DIGITAL_OUTPUT: client_capabilities->set_max_hdcp_version( - video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_NO_DIGITAL_OUTPUT); + ClientCapabilities::HDCP_NO_DIGITAL_OUTPUT); break; default: LOGW("Unexpected HDCP max capability version: max_version = %d", @@ -229,19 +288,28 @@ CdmResponseType ClientIdentification::Prepare( if (crypto_session_->GetSupportedCertificateTypes(&supported_certs)) { if (supported_certs.rsa_2048_bit) { client_capabilities->add_supported_certificate_key_type( - video_widevine:: - ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048); + ClientCapabilities::RSA_2048); } if (supported_certs.rsa_3072_bit) { client_capabilities->add_supported_certificate_key_type( - video_widevine:: - ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072); + ClientCapabilities::RSA_3072); + } + if (supported_certs.ecc_secp256r1) { + client_capabilities->add_supported_certificate_key_type( + ClientCapabilities::ECC_SECP256R1); + } + if (supported_certs.ecc_secp384r1) { + client_capabilities->add_supported_certificate_key_type( + ClientCapabilities::ECC_SECP384R1); + } + if (supported_certs.ecc_secp521r1) { + client_capabilities->add_supported_certificate_key_type( + ClientCapabilities::ECC_SECP521R1); } } if (is_license_request_) { - client_capabilities->set_can_update_srm( - crypto_session_->IsSrmUpdateSupported()); + client_capabilities->set_can_update_srm(false); uint16_t srm_version; if (crypto_session_->GetSrmVersion(&srm_version) == NO_ERROR) client_capabilities->set_srm_version(srm_version); @@ -251,33 +319,49 @@ CdmResponseType ClientIdentification::Prepare( bool can_support_cgms_a; if (crypto_session_->GetAnalogOutputCapabilities( &can_support_output, &can_disable_output, &can_support_cgms_a)) { - video_widevine::ClientIdentification_ClientCapabilities_AnalogOutputCapabilities - capabilities = video_widevine:: - ClientIdentification_ClientCapabilities_AnalogOutputCapabilities_ANALOG_OUTPUT_NONE; + AnalogOutputCapabilities capabilities = + ClientCapabilities::ANALOG_OUTPUT_NONE; if (can_support_output) { if (can_support_cgms_a) { - capabilities = video_widevine:: - ClientIdentification_ClientCapabilities_AnalogOutputCapabilities_ANALOG_OUTPUT_SUPPORTS_CGMS_A; + capabilities = ClientCapabilities::ANALOG_OUTPUT_SUPPORTS_CGMS_A; } else { - capabilities = video_widevine:: - ClientIdentification_ClientCapabilities_AnalogOutputCapabilities_ANALOG_OUTPUT_SUPPORTED; + capabilities = ClientCapabilities::ANALOG_OUTPUT_SUPPORTED; } } client_capabilities->set_analog_output_capabilities(capabilities); client_capabilities->set_can_disable_analog_output(can_disable_output); } else { client_capabilities->set_analog_output_capabilities( - video_widevine:: - ClientIdentification_ClientCapabilities_AnalogOutputCapabilities_ANALOG_OUTPUT_UNKNOWN); + ClientCapabilities::ANALOG_OUTPUT_UNKNOWN); } - uint32_t version, tier; - if (crypto_session_->GetApiVersion(&version)) { - if (version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) { - if (crypto_session_->GetResourceRatingTier(&tier)) { - client_capabilities->set_resource_rating_tier(tier); - } + if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) { + uint32_t tier; + if (crypto_session_->GetResourceRatingTier(&tier)) { + client_capabilities->set_resource_rating_tier(tier); + } + } + + if (is_license_request_) { + CdmWatermarkingSupport support; + if (!crypto_session_->GetWatermarkingSupport(&support)) { + // Assume not supported. + support = kWatermarkingNotSupported; + } + switch (support) { + case kWatermarkingNotSupported: + client_capabilities->set_watermarking_support( + ClientCapabilities::WATERMARKING_NOT_SUPPORTED); + break; + case kWatermarkingConfigurable: + client_capabilities->set_watermarking_support( + ClientCapabilities::WATERMARKING_CONFIGURABLE); + break; + case kWatermarkingAlwaysOn: + client_capabilities->set_watermarking_support( + ClientCapabilities::WATERMARKING_ALWAYS_ON); + break; } } @@ -295,6 +379,10 @@ bool ClientIdentification::GetProvisioningTokenType( *token_type = video_widevine::ClientIdentification::OEM_DEVICE_CERTIFICATE; return true; + case kClientTokenBootCertChain: + *token_type = + video_widevine::ClientIdentification::BOOT_CERTIFICATE_CHAIN; + return true; case kClientTokenDrmCert: default: // shouldn't happen diff --git a/core/src/content_key_session.cpp b/core/src/content_key_session.cpp index 83339740..3c808d32 100644 --- a/core/src/content_key_session.cpp +++ b/core/src/content_key_session.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "content_key_session.h" diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp index ba939545..eb715315 100644 --- a/core/src/crypto_session.cpp +++ b/core/src/crypto_session.cpp @@ -1,12 +1,13 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // Crypto - wrapper classes for OEMCrypto interface // #include "crypto_session.h" +#include #include #include @@ -19,6 +20,7 @@ #include "crypto_key.h" #include "entitlement_key_session.h" #include "log.h" +#include "okp_fallback_policy.h" #include "platform.h" #include "privacy_crypto.h" #include "properties.h" @@ -29,7 +31,7 @@ // Stringify turns macro arguments into static C strings. // Example: STRINGIFY(this_argument) -> "this_argument" -#define STRINGIFY(PARAM...) #PARAM +#define STRINGIFY(PARAM) #PARAM #define RETURN_IF_NULL(PARAM, ret_value) \ if ((PARAM) == nullptr) { \ @@ -49,21 +51,35 @@ return ret_value; \ } -namespace wvcdm { +#ifdef HAS_DUAL_KEY +/** + * Internal only OEMCrypto method. This is called before parsing the license + * response to indicate the response uses dual keys. If this isn't called, + * it is using single keys. + */ +extern "C" OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session, + bool dual_key); +#endif +namespace wvcdm { namespace { +using UsageTableLock = std::unique_lock; + constexpr size_t KiB = 1024; constexpr size_t MiB = 1024 * 1024; constexpr uint32_t kRsaSignatureLength = 256; constexpr size_t kEstimatedInitialUsageTableHeader = 40; +constexpr size_t kAes128BlockSize = 16; -// Constants and utility objects relating to OEM Certificates -constexpr const char* kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1"; constexpr int kMaxTerminateCountDown = 5; const std::string kStringNotAvailable = "NA"; +// TODO(b/174412779): Remove when b/170704368 is fixed. +// This is a Qualcomm specific error code +const int kRsaSsaPssSignatureLengthError = 10085; + // Constants relating to OEMCrypto resource rating tiers. These tables are // ordered by resource rating tier from lowest to highest. These should be // updated whenever the supported range of resource rating tiers changes. @@ -75,13 +91,15 @@ constexpr size_t kMaxSubsampleRegionSizes[] = { }; // The +1 in this calculation is because the bounds RESOURCE_RATING_TIER_MAX and // RESOURCE_RATING_TIER_MIN are inclusive. -static_assert(ArraySize(kMaxSubsampleRegionSizes) == +static_assert(wvutil::ArraySize(kMaxSubsampleRegionSizes) == RESOURCE_RATING_TIER_MAX - RESOURCE_RATING_TIER_MIN + 1, "The kMaxSubsampleRegionSizes table needs to be updated to " - "reflect the supported range of resource rating tiers."); + "reflect the supported range of resource rating tiers"); constexpr size_t kDefaultMaxSubsampleRegionSize = kMaxSubsampleRegionSizes[0]; +constexpr size_t kMaxExternalDeviceIdLength = 64; + // This maps a few common OEMCryptoResult to CdmResponseType. Many mappings // are not universal but are OEMCrypto method specific. Those will be // specified in the CryptoSession method rather than here. @@ -113,8 +131,8 @@ CdmResponseType MapOEMCryptoResult(OEMCryptoResult result, void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { switch (dest_buffer->type) { case OEMCrypto_BufferType_Clear: - dest_buffer->buffer.clear.address += bytes; - dest_buffer->buffer.clear.address_length -= bytes; + dest_buffer->buffer.clear.clear_buffer += bytes; + dest_buffer->buffer.clear.clear_buffer_length -= bytes; return; case OEMCrypto_BufferType_Secure: @@ -126,24 +144,59 @@ void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { return; } LOGE("Unrecognized OEMCryptoBufferType %u - doing nothing", - dest_buffer->type); + static_cast(dest_buffer->type)); +} + +bool GetGenericSigningAlgorithm(CdmSigningAlgorithm algorithm, + OEMCrypto_Algorithm* oec_algorithm) { + RETURN_IF_NULL(oec_algorithm, false); + if (kSigningAlgorithmHmacSha256 != algorithm) { + LOGW("Unrecognized signing algorithm: %d", algorithm); + return false; + } + *oec_algorithm = OEMCrypto_HMAC_SHA256; + return true; +} + +bool GetGenericEncryptionAlgorithm(CdmEncryptionAlgorithm algorithm, + OEMCrypto_Algorithm* oec_algorithm) { + RETURN_IF_NULL(oec_algorithm, false); + if (kEncryptionAlgorithmAesCbc128 != algorithm) { + LOGW("Unrecognized encryption algorithm: %d", algorithm); + return false; + } + *oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING; + return true; +} + +size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm) { + if (kEncryptionAlgorithmAesCbc128 != algorithm) { + LOGW("Unrecognized encryption algorithm: %d", algorithm); + return 0; + } + return kAes128BlockSize; } } // namespace -shared_mutex CryptoSession::static_field_mutex_; -shared_mutex CryptoSession::oem_crypto_mutex_; +// CryptoSession variables allocation. +wvutil::shared_mutex CryptoSession::static_field_mutex_; +wvutil::shared_mutex CryptoSession::oem_crypto_mutex_; bool CryptoSession::initialized_ = false; +bool CryptoSession::needs_keybox_provisioning_ = false; int CryptoSession::session_count_ = 0; int CryptoSession::termination_counter_ = 0; -UsageTableHeader* CryptoSession::usage_table_header_l1_ = nullptr; -UsageTableHeader* CryptoSession::usage_table_header_l3_ = nullptr; +std::unique_ptr CryptoSession::usage_table_header_l1_; +std::unique_ptr CryptoSession::usage_table_header_l3_; +std::recursive_mutex CryptoSession::usage_table_mutex_; std::atomic CryptoSession::request_id_index_source_(0); +std::unique_ptr + CryptoSession::okp_fallback_policy_l1_; size_t GetOffset(std::string message, std::string field) { size_t pos = message.find(field); if (pos == std::string::npos) { LOGE("Cannot find the |field| offset in message: field = %s", - field.c_str()); + IdToString(field)); pos = 0; } return pos; @@ -159,7 +212,7 @@ OEMCrypto_Substring GetSubstring(const std::string& message, size_t pos = message.find(field); if (pos == std::string::npos) { LOGW("Cannot find the |field| substring in message: field = %s", - field.c_str()); + IdToString(field)); substring.offset = 0; substring.length = 0; } else { @@ -183,7 +236,7 @@ void GenerateMacContext(const std::string& input_context, deriv_context->assign(kSigningKeyLabel); deriv_context->append(1, '\0'); deriv_context->append(input_context); - deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2)); + deriv_context->append(wvutil::EncodeUint32(kSigningKeySizeBits * 2)); } void GenerateEncryptContext(const std::string& input_context, @@ -199,24 +252,22 @@ void GenerateEncryptContext(const std::string& input_context, deriv_context->assign(kEncryptionKeyLabel); deriv_context->append(1, '\0'); deriv_context->append(input_context); - deriv_context->append(EncodeUint32(kEncryptionKeySizeBits)); + deriv_context->append(wvutil::EncodeUint32(kEncryptionKeySizeBits)); } OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) { - return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CTR - : OEMCrypto_CipherMode_CBC; + return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CENC + : OEMCrypto_CipherMode_CBCS; } CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics) : metrics_(metrics), - system_id_(-1), + system_id_(NULL_SYSTEM_ID), open_(false), pre_provision_token_type_(kClientTokenUninitialized), update_usage_table_after_close_session_(false), is_destination_buffer_type_valid_(false), requested_security_level_(kLevelDefault), - usage_support_type_(kUnknownUsageSupport), - usage_table_header_(nullptr), api_version_(0), max_subsample_region_size_(0) { assert(metrics); @@ -241,7 +292,8 @@ CryptoSession::~CryptoSession() { } CdmResponseType CryptoSession::GetProvisioningMethod( - SecurityLevel requested_security_level, CdmClientTokenType* token_type) { + RequestedSecurityLevel requested_security_level, + CdmClientTokenType* token_type) { OEMCrypto_ProvisioningMethod method; WithOecReadLock("GetProvisioningMethod", [&] { method = OEMCrypto_GetProvisioningMethod(requested_security_level); @@ -258,8 +310,16 @@ CdmResponseType CryptoSession::GetProvisioningMethod( case OEMCrypto_DrmCertificate: type = kClientTokenDrmCert; break; + case OEMCrypto_BootCertificateChain: + type = kClientTokenBootCertChain; + break; case OEMCrypto_ProvisioningError: default: + if (static_cast(method) == 0 && needs_keybox_provisioning_) { + LOGW("Overriding provisioning method, assuming keybox"); + type = kClientTokenKeybox; + break; + } LOGE("OEMCrypto_GetProvisioningMethod failed: method = %d", static_cast(method)); metrics_->oemcrypto_provisioning_method_.SetError(method); @@ -284,8 +344,9 @@ void CryptoSession::Init() { sandbox_id.length()); metrics_->oemcrypto_set_sandbox_.Record(sandbox_id); } - M_TIME(sts = OEMCrypto_Initialize(), metrics_, oemcrypto_initialize_, - sts); + M_TIME(sts = OEMCrypto_InitializeAndCheckKeybox( + &needs_keybox_provisioning_), + metrics_, oemcrypto_initialize_, sts); }); if (OEMCrypto_SUCCESS != sts) { LOGE("OEMCrypto_Initialize failed: status = %d", static_cast(sts)); @@ -300,32 +361,75 @@ void CryptoSession::Init() { }); if (initialized) { - uint32_t version; - std::string api_version = - CryptoSession::GetApiVersion(kLevelDefault, &version) - ? std::to_string(version) - : kStringNotAvailable; - std::string api_minor_version = - CryptoSession::GetApiMinorVersion(kLevelDefault, &version) - ? std::to_string(version) - : kStringNotAvailable; - LOGD("OEMCrypto version (default security level): %s.%s", - api_version.c_str(), api_minor_version.c_str()); + CacheVersion(); + } +} - api_version = CryptoSession::GetApiVersion(kLevel3, &version) - ? std::to_string(version) - : kStringNotAvailable; - api_minor_version = CryptoSession::GetApiMinorVersion(kLevel3, &version) - ? std::to_string(version) - : kStringNotAvailable; - LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), - api_minor_version.c_str()); +void CryptoSession::ReinitializeForTest() { + if (initialized_) { + const OEMCryptoResult status = OEMCrypto_Terminate(); + if (OEMCrypto_SUCCESS != status) { + LOGE("OEMCrypto_Terminate failed: %d", status); + return; + } + initialized_ = false; + // Tables will be reinitialized by tests when needed. + usage_table_header_l1_.reset(); + usage_table_header_l3_.reset(); + } + // Give up if we cannot initialize at all. + const OEMCryptoResult status = OEMCrypto_Initialize(); + if (OEMCrypto_SUCCESS != status) { + LOGE("OEMCrypto_Initialize failed: %d", status); + return; + } + initialized_ = true; + // For integration and unit tests we will install a test keybox and do not + // need to do keybox provisioning. + needs_keybox_provisioning_ = false; + // This was skipped in Init because initialization failed. + CacheVersion(); +} + +void CryptoSession::CacheVersion() { + uint32_t version; + std::string api_version = + CryptoSession::GetApiVersion(kLevelDefault, &version) + ? std::to_string(version) + : kStringNotAvailable; + std::string api_minor_version = + CryptoSession::GetApiMinorVersion(kLevelDefault, &version) + ? std::to_string(version) + : kStringNotAvailable; + LOGD("OEMCrypto version (default security level): %s.%s", api_version.c_str(), + api_minor_version.c_str()); + + api_version = CryptoSession::GetApiVersion(kLevel3, &version) + ? std::to_string(version) + : kStringNotAvailable; + api_minor_version = CryptoSession::GetApiMinorVersion(kLevel3, &version) + ? std::to_string(version) + : kStringNotAvailable; + LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), + api_minor_version.c_str()); + if (needs_keybox_provisioning_) { + WithStaticFieldWriteLock("SystemFallbackPolicy", [&] { + if (!okp_fallback_policy_l1_) { + LOGD("OEMCrypto needs keybox provisioning"); + // Only create once. Possible that OEMCrypto is initialized + // and terminated many times over the life cycle of the OTA + // keybox provisioning process. + okp_fallback_policy_l1_ = okp::SystemFallbackPolicy::Create(); + if (okp_fallback_policy_l1_) + okp_fallback_policy_l1_->MarkNeedsProvisioning(); + } + }); } } bool CryptoSession::TryTerminate() { LOGV("Terminating crypto session"); - WithStaticFieldWriteLock("TryTerminate", [&] { + const bool terminated = WithStaticFieldWriteLock("TryTerminate", [&] { LOGV( "Terminating crypto session: initialized_ = %s, session_count_ = %d, " "termination_counter_ = %d", @@ -337,25 +441,20 @@ bool CryptoSession::TryTerminate() { if (session_count_ > 0 || termination_counter_ > 0 || !initialized_) return false; - OEMCryptoResult sts; - WithOecWriteLock("Terminate", [&] { sts = OEMCrypto_Terminate(); }); + const OEMCryptoResult sts = + WithOecWriteLock("Terminate", [&] { return OEMCrypto_Terminate(); }); if (OEMCrypto_SUCCESS != sts) { LOGE("OEMCrypto_Terminate failed: status = %d", static_cast(sts)); } - - if (usage_table_header_l1_ != nullptr) { - delete usage_table_header_l1_; - usage_table_header_l1_ = nullptr; - } - if (usage_table_header_l3_ != nullptr) { - delete usage_table_header_l3_; - usage_table_header_l3_ = nullptr; - } - initialized_ = false; return true; }); - return true; + if (terminated) { + UsageTableLock lock(usage_table_mutex_); + usage_table_header_l1_.reset(); + usage_table_header_l3_.reset(); + } + return terminated; } void CryptoSession::DisableDelayedTermination() { @@ -364,73 +463,157 @@ void CryptoSession::DisableDelayedTermination() { [&] { termination_counter_ = 0; }); } -CdmResponseType CryptoSession::GetTokenFromKeybox(std::string* token) { - RETURN_IF_NULL(token, PARAMETER_NULL); - - std::string temp_buffer(KEYBOX_KEY_DATA_SIZE, '\0'); - size_t buf_size = temp_buffer.size(); - uint8_t* buf = reinterpret_cast(&temp_buffer[0]); - - OEMCryptoResult status; - WithOecReadLock("GetTokenFromKeybox", [&] { - M_TIME(status = - OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_), - metrics_, oemcrypto_get_key_data_, status, - metrics::Pow2Bucket(buf_size)); - }); - - if (OEMCrypto_SUCCESS == status) { - token->swap(temp_buffer); +bool CryptoSession::SetUpUsageTableHeader( + RequestedSecurityLevel requested_security_level) { + if (usage_table_header_ != nullptr) { + LOGE("Usage table is already set up for the current crypto session"); + return false; + } + const CdmSecurityLevel security_level = + GetSecurityLevel(requested_security_level); + if (security_level != kSecurityLevelL1 && + security_level != kSecurityLevelL3) { + LOGD("Unsupported security level for usage support: security_level = %d", + static_cast(security_level)); + return false; } + // Check if usage support is available. + bool supports_usage_table = false; + if (!HasUsageInfoSupport(requested_security_level, &supports_usage_table)) { + metrics_->oemcrypto_usage_table_support_.SetError( + USAGE_INFORMATION_SUPPORT_FAILED); + return false; + } + metrics_->oemcrypto_usage_table_support_.Record( + supports_usage_table ? kUsageEntrySupport : kNonSecureUsageSupport); + if (!supports_usage_table) { + return false; + } + + LOGV("Usage table lock: SetUpUsageTableHeader()"); + UsageTableLock auto_lock(usage_table_mutex_); + // TODO(b/141350978): Prevent any recursive logic. + + // Manipulate only the usage table for the requested security level. + std::unique_ptr& header = security_level == kSecurityLevelL1 + ? usage_table_header_l1_ + : usage_table_header_l3_; + if (!header) { + // This may be called twice within the same thread when the table + // is initialized. On the second call |header| will not be null, + // causing this block to be skipped. + header.reset(new UsageTableHeader()); + if (!header->Init(security_level, this)) { + LOGE("Failed to initialize and sync usage usage table"); + // Must be cleared globally to prevent the next session to be + // opened from using the invalid UsageTableHeader. + header.reset(); + return false; + } + } + usage_table_header_ = header.get(); + metrics_->usage_table_header_initial_size_.Record( + usage_table_header_->size()); + return true; +} + +CdmResponseType CryptoSession::GetTokenFromKeybox( + RequestedSecurityLevel requested_security_level, std::string* key_data) { + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + RETURN_IF_NULL(key_data, PARAMETER_NULL); + LOGV("requested_security_level = %s", + RequestedSecurityLevelToString(requested_security_level)); + // Devices with an invalid L1 keybox which support OTA keybox + // provisioning don't have keybox data. + const bool keybox_provisioning_required = WithStaticFieldReadLock( + "GetTokenFromKeybox - keybox_provisioning_required", [&] { + if (requested_security_level_ != kLevelDefault) return false; + return needs_keybox_provisioning_; + }); + if (keybox_provisioning_required) return NEED_PROVISIONING; + + size_t key_data_length = KEYBOX_KEY_DATA_SIZE; + key_data->assign(key_data_length, '\0'); + OEMCryptoResult status; + WithOecReadLock("GetTokenFromKeybox", [&] { + M_TIME(status = OEMCrypto_GetKeyData( + reinterpret_cast(&key_data->front()), &key_data_length, + requested_security_level), + metrics_, oemcrypto_get_key_data_, status, + metrics::Pow2Bucket(key_data_length)); + }); + if (OEMCrypto_SUCCESS == status) { + key_data->resize(key_data_length); + return NO_ERROR; + } + key_data->clear(); return MapOEMCryptoResult(status, GET_TOKEN_FROM_KEYBOX_ERROR, "GetTokenFromKeybox"); } -CdmResponseType CryptoSession::GetTokenFromOemCert(std::string* token) { - RETURN_IF_NULL(token, PARAMETER_NULL); +CdmResponseType CryptoSession::GetTokenFromOemCert( + RequestedSecurityLevel requested_security_level, std::string* oem_cert) { + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + RETURN_IF_NULL(oem_cert, PARAMETER_NULL); + LOGV("requested_security_level = %s", + RequestedSecurityLevelToString(requested_security_level)); - OEMCryptoResult status; - if (!oem_token_.empty()) { - token->assign(oem_token_); - return NO_ERROR; - } + const bool cache_success = + WithOecSessionLock("GetTokenFromOemCert - check cached", [&] { + if (oem_token_.empty()) { + return false; + } + oem_cert->assign(oem_token_); + return true; + }); + if (cache_success) return NO_ERROR; - std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0'); - bool retrying = false; - while (true) { - size_t buf_size = temp_buffer.size(); - uint8_t* buf = reinterpret_cast(&temp_buffer[0]); - WithOecSessionLock("GetTokenFromOemCert", [&] { - status = OEMCrypto_GetOEMPublicCertificate(buf, &buf_size, - requested_security_level_); + size_t oem_cert_length = CERTIFICATE_DATA_SIZE; + oem_cert->assign(oem_cert_length, '\0'); + OEMCryptoResult status = + WithOecReadLock("GetTokenFromOemCert - attempt 1", [&] { + return OEMCrypto_GetOEMPublicCertificate( + reinterpret_cast(&oem_cert->front()), &oem_cert_length, + requested_security_level); + }); + metrics_->oemcrypto_get_oem_public_certificate_.Increment(status); + if (status == OEMCrypto_ERROR_SHORT_BUFFER) { + oem_cert->assign(oem_cert_length, '\0'); + status = WithOecReadLock("GetTokenFromOemCert - attempt 2", [&] { + return OEMCrypto_GetOEMPublicCertificate( + reinterpret_cast(&oem_cert->front()), &oem_cert_length, + requested_security_level); }); metrics_->oemcrypto_get_oem_public_certificate_.Increment(status); - - if (OEMCrypto_SUCCESS == status) { - temp_buffer.resize(buf_size); - oem_token_.assign(temp_buffer); - token->assign(temp_buffer); - return NO_ERROR; - } - - if (status == OEMCrypto_ERROR_SHORT_BUFFER && !retrying) { - temp_buffer.resize(buf_size); - retrying = true; - continue; - } - - return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR, - "GetTokenFromOemCert"); } + + if (status == OEMCrypto_SUCCESS) { + oem_cert->resize(oem_cert_length); + WithOecSessionLock("GetTokenFromOemCert - set cache", + [&] { oem_token_ = *oem_cert; }); + return NO_ERROR; + } + oem_cert->clear(); + return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR, + "GetTokenFromOemCert"); } -CdmResponseType CryptoSession::GetProvisioningToken(std::string* token) { - if (token == nullptr) { +CdmResponseType CryptoSession::GetProvisioningToken( + std::string* token, std::string* additional_token) { + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); + return GetProvisioningToken(requested_security_level_, token, + additional_token); +} + +CdmResponseType CryptoSession::GetProvisioningToken( + RequestedSecurityLevel requested_security_level, std::string* token, + std::string* additional_token) { + if (token == nullptr || additional_token == nullptr) { metrics_->crypto_session_get_token_.Increment(PARAMETER_NULL); RETURN_IF_NULL(token, PARAMETER_NULL); + RETURN_IF_NULL(additional_token, PARAMETER_NULL); } - if (!IsInitialized()) { metrics_->crypto_session_get_token_.Increment( CRYPTO_SESSION_NOT_INITIALIZED); @@ -439,9 +622,12 @@ CdmResponseType CryptoSession::GetProvisioningToken(std::string* token) { CdmResponseType status = UNKNOWN_CLIENT_TOKEN_TYPE; if (pre_provision_token_type_ == kClientTokenKeybox) { - status = GetTokenFromKeybox(token); + status = GetTokenFromKeybox(requested_security_level, token); } else if (pre_provision_token_type_ == kClientTokenOemCert) { - status = GetTokenFromOemCert(token); + status = GetTokenFromOemCert(requested_security_level, token); + } else if (pre_provision_token_type_ == kClientTokenBootCertChain) { + status = GetBootCertificateChain(requested_security_level, token, + additional_token); } metrics_->crypto_session_get_token_.Increment(status); return status; @@ -454,37 +640,33 @@ CdmSecurityLevel CryptoSession::GetSecurityLevel() { } CdmSecurityLevel CryptoSession::GetSecurityLevel( - SecurityLevel requested_level) { - LOGV("Getting security level: requested_level = %d", - static_cast(requested_level)); + RequestedSecurityLevel requested_security_level) { + LOGV("Getting security level: requested_security_level = %s", + RequestedSecurityLevelToString(requested_security_level)); RETURN_IF_UNINITIALIZED(kSecurityLevelUninitialized); - - std::string security_level; - WithOecReadLock("GetSecurityLevel", [&] { - security_level = OEMCrypto_SecurityLevel(requested_level); - }); - - if ((security_level.size() != 2) || (security_level.at(0) != 'L')) { + const OEMCrypto_Security_Level level = WithOecReadLock( + "GetSecurityLevel", + [&] { return OEMCrypto_SecurityLevel(requested_security_level); }); + if (level == 0) { + LOGE("Security level is unknown: requested_security_level = %d", + static_cast(requested_security_level)); return kSecurityLevelUnknown; } - - CdmSecurityLevel cdm_security_level; - switch (security_level.at(1)) { - case '1': - cdm_security_level = kSecurityLevelL1; - break; - case '2': - cdm_security_level = kSecurityLevelL2; - break; - case '3': - cdm_security_level = kSecurityLevelL3; - break; - default: - cdm_security_level = kSecurityLevelUnknown; - break; + if (level == OEMCrypto_Level1) { + return kSecurityLevelL1; } - - return cdm_security_level; + if (level == OEMCrypto_Level2) { + return kSecurityLevelL2; + } + if (level == OEMCrypto_Level3) { + return kSecurityLevelL3; + } + LOGE( + "Ill-formed security level: " + "level = \"L%u\", requested_security_level = %s", + static_cast(level), + RequestedSecurityLevelToString(requested_security_level)); + return kSecurityLevelUnknown; } CdmResponseType CryptoSession::GetInternalDeviceUniqueId( @@ -492,68 +674,78 @@ CdmResponseType CryptoSession::GetInternalDeviceUniqueId( RETURN_IF_NULL(device_id, PARAMETER_NULL); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); - std::vector id; - size_t id_length = 32; - id.resize(id_length); + size_t device_id_length = 64; + device_id->assign(device_id_length, '\0'); - OEMCryptoResult sts; - WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] { - sts = OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_); - }); - // Increment the count of times this method was called. + OEMCryptoResult sts = + WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] { + return OEMCrypto_GetDeviceID( + reinterpret_cast(&device_id->front()), &device_id_length, + requested_security_level_); + }); metrics_->oemcrypto_get_device_id_.Increment(sts); + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { - id.resize(id_length); - WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] { - sts = - OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_); + device_id->resize(device_id_length, '\0'); + sts = WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] { + return OEMCrypto_GetDeviceID( + reinterpret_cast(&device_id->front()), &device_id_length, + requested_security_level_); }); metrics_->oemcrypto_get_device_id_.Increment(sts); } + // Either the authentication root is a keybox or the device has transitioned + // to using OEMCerts. + // OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to + // 3.0 would have a new device ID, which would affect SPOID calculation. + // In order to resolve this, we use OEMCrypto_GetDeviceID if it is + // implemented, so the OEMCrypto can continue to report the same device ID. + if (sts == OEMCrypto_SUCCESS) { + device_id->resize(device_id_length); + return NO_ERROR; + } + device_id->clear(); + if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED && pre_provision_token_type_ == kClientTokenOemCert) { - return GetTokenFromOemCert(device_id); - } else { - // Either the authentication root is a keybox or the device has transitioned - // to using OEMCerts. - // OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to - // 3.0 would have a new device ID, which would affect SPOID calculation. - // In order to resolve this, we use OEMCrypto_GetDeviceID if it is - // implemented, so the OEMCrypto can continue to report the same device ID. - if (sts == OEMCrypto_SUCCESS) { - device_id->assign(reinterpret_cast(&id[0]), id_length); - } - - return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR, - "GetInternalDeviceUniqueId"); + return GetTokenFromOemCert(requested_security_level_, device_id); } + + const bool use_null_device_id = WithStaticFieldReadLock( + "GetInternalDeviceUniqueId() use_null_device_id", [&] { + if (requested_security_level_ != kLevelDefault) return false; + if (!needs_keybox_provisioning_) return false; + if (sts != OEMCrypto_ERROR_KEYBOX_INVALID && + sts != OEMCrypto_ERROR_NO_DEVICEID) { + // Logging other error for debugging, but null device + // ID should still be returned. + LOGE("Unexpected error: sts = %d", sts); + } + return true; + }); + if (use_null_device_id) { + LOGD("Using null device ID"); + constexpr size_t kKeyboxDeviceIdLength = 32; + device_id->assign(kKeyboxDeviceIdLength, '\0'); + return NO_ERROR; + } + + return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR, + "GetInternalDeviceUniqueId"); } CdmResponseType CryptoSession::GetExternalDeviceUniqueId( std::string* device_id) { RETURN_IF_NULL(device_id, PARAMETER_NULL); - - std::string temp; - CdmResponseType status = GetInternalDeviceUniqueId(&temp); - + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + const CdmResponseType status = GetInternalDeviceUniqueId(device_id); if (status != NO_ERROR) return status; - - size_t id_length = 0; - OEMCryptoResult sts; - WithOecReadLock("GetExternalDeviceUniqueId", [&] { - sts = OEMCrypto_GetDeviceID(nullptr, &id_length, requested_security_level_); - }); - metrics_->oemcrypto_get_device_id_.Increment(sts); - - if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED && - pre_provision_token_type_ == kClientTokenOemCert) { + if (device_id->size() > kMaxExternalDeviceIdLength) { // To keep the size of the value passed back to the application down, hash // the large OEM Public Cert to a smaller value. - temp = Sha256Hash(temp); + *device_id = Sha256Hash(*device_id); } - - *device_id = temp; return NO_ERROR; } @@ -563,10 +755,10 @@ bool CryptoSession::GetApiVersion(uint32_t* version) { return GetApiVersion(requested_security_level_, version); } -bool CryptoSession::GetApiVersion(SecurityLevel security_level, +bool CryptoSession::GetApiVersion(RequestedSecurityLevel security_level, uint32_t* version) { - LOGV("Getting API version: security_level = %d", - static_cast(security_level)); + LOGV("Getting API version: security_level = %s", + RequestedSecurityLevelToString(security_level)); if (!version) { LOGE("Output parameter |version| not provided"); return false; @@ -581,10 +773,10 @@ bool CryptoSession::GetApiVersion(SecurityLevel security_level, return true; } -bool CryptoSession::GetApiMinorVersion(SecurityLevel security_level, +bool CryptoSession::GetApiMinorVersion(RequestedSecurityLevel security_level, uint32_t* minor_version) { - LOGV("Getting API minor version: security_level = %d", - static_cast(security_level)); + LOGV("Getting API minor version: security_level = %s", + RequestedSecurityLevelToString(security_level)); if (!minor_version) { LOGE("Output parameter |minor_version| not provided"); return false; @@ -600,69 +792,23 @@ bool CryptoSession::GetApiMinorVersion(SecurityLevel security_level, return true; } -bool CryptoSession::GetSystemId(uint32_t* system_id) { +bool CryptoSession::GetCachedSystemId(uint32_t* system_id) { RETURN_IF_NULL(system_id, false); - RETURN_IF_UNINITIALIZED(false); RETURN_IF_NOT_OPEN(false); + if (system_id_ == NULL_SYSTEM_ID) return false; *system_id = system_id_; return true; } -// This method gets the system id from the keybox key data. -// This method assumes that OEMCrypto has been initialized before making this -// call. -CdmResponseType CryptoSession::GetSystemIdInternal(uint32_t* system_id) { - RETURN_IF_NULL(system_id, PARAMETER_NULL); - - if (pre_provision_token_type_ == kClientTokenKeybox) { - std::string token; - CdmResponseType status = GetTokenFromKeybox(&token); - - if (status != NO_ERROR) return status; - - if (token.size() < 2 * sizeof(uint32_t)) { - LOGE("Keybox token size too small: token_size = %zu", token.size()); - return KEYBOX_TOKEN_TOO_SHORT; - } - - // Decode 32-bit int encoded as network-byte-order byte array starting at - // index 4. - uint32_t* id = reinterpret_cast(&token[4]); - *system_id = ntohl(*id); - return NO_ERROR; - - } else if (pre_provision_token_type_ == kClientTokenOemCert) { - // Get the OEM Cert - std::string oem_cert; - CdmResponseType status = GetTokenFromOemCert(&oem_cert); - - if (status != NO_ERROR) return status; - - if (!ExtractSystemIdFromOemCert(oem_cert, system_id)) - return EXTRACT_SYSTEM_ID_FROM_OEM_CERT_ERROR; - - return NO_ERROR; - - // TODO(blueeyes): Support loading the system id from a pre-provisioned - // Drm certificate. - } else if (pre_provision_token_type_ == kClientTokenDrmCert) { - return NO_ERROR; - } else { - LOGE("Unsupported pre-provision token type: %d", - static_cast(pre_provision_token_type_)); - return UNKNOWN_CLIENT_TOKEN_TYPE; - } -} - -bool CryptoSession::ExtractSystemIdFromOemCert(const std::string& oem_cert, - uint32_t* system_id) { - return ExtractExtensionValueFromCertificate( - oem_cert, kWidevineSystemIdExtensionOid, /* cert_index */ 1, system_id); +void CryptoSession::SetSystemId(uint32_t system_id) { + if (!IsOpen()) return; // Ignore silently. + system_id_ = system_id; + metrics_->crypto_session_system_id_.Record(system_id_); } CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) { RETURN_IF_NULL(provisioning_id, PARAMETER_NULL); - RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); if (pre_provision_token_type_ == kClientTokenOemCert) { // OEM Cert devices have no provisioning-unique ID embedded in them, so we @@ -678,9 +824,11 @@ CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) { } return NO_ERROR; - } else if (pre_provision_token_type_ == kClientTokenKeybox) { + } + if (pre_provision_token_type_ == kClientTokenKeybox) { std::string token; - CdmResponseType status = GetTokenFromKeybox(&token); + CdmResponseType status = + GetTokenFromKeybox(requested_security_level_, &token); if (status != NO_ERROR) return status; @@ -691,11 +839,10 @@ CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) { provisioning_id->assign(reinterpret_cast(&token[8]), 16); return NO_ERROR; - } else { - LOGE("Unsupported pre-provision token type: %d", - static_cast(pre_provision_token_type_)); - return UNKNOWN_CLIENT_TOKEN_TYPE; } + LOGE("Unsupported pre-provision token type: %d", + static_cast(pre_provision_token_type_)); + return UNKNOWN_CLIENT_TOKEN_TYPE; } uint8_t CryptoSession::GetSecurityPatchLevel() { @@ -707,14 +854,20 @@ uint8_t CryptoSession::GetSecurityPatchLevel() { return patch; } -CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { +CdmResponseType CryptoSession::Open( + RequestedSecurityLevel requested_security_level) { LOGD("Opening crypto session: requested_security_level = %s", - requested_security_level == kLevel3 - ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() - : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); - RETURN_IF_UNINITIALIZED(UNKNOWN_ERROR); + RequestedSecurityLevelToString(requested_security_level)); + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); if (open_) return NO_ERROR; + if (!SetUpUsageTableHeader(requested_security_level)) { + // Ignore errors since we do not know when a session is opened, + // if it is intended to be used for offline/usage session related + // or otherwise. + LOGW("Session opened without a usage table"); + } + CdmResponseType result = GetProvisioningMethod(requested_security_level, &pre_provision_token_type_); if (result != NO_ERROR) return result; @@ -747,16 +900,6 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { LOGV("Opened session: id = %u", oec_session_id_); open_ = true; - // Get System ID and save it. - result = GetSystemIdInternal(&system_id_); - if (result == NO_ERROR) { - metrics_->crypto_session_system_id_.Record(system_id_); - } else { - LOGE("Failed to fetch system ID"); - metrics_->crypto_session_system_id_.SetError(result); - return result; - } - // Set up request ID uint64_t request_id_base; OEMCryptoResult random_sts; @@ -767,79 +910,20 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { metrics_->oemcrypto_get_random_.Increment(random_sts); uint64_t request_id_index = request_id_index_source_.fetch_add(1, std::memory_order_relaxed); - request_id_ = HexEncode(reinterpret_cast(&request_id_base), - sizeof(request_id_base)) + - HexEncode(reinterpret_cast(&request_id_index), - sizeof(request_id_index)); + request_id_ = wvutil::HexEncode(reinterpret_cast(&request_id_base), + sizeof(request_id_base)) + + wvutil::HexEncode(reinterpret_cast(&request_id_index), + sizeof(request_id_index)); // Initialize key session WithOecSessionLock("Open() calling key_session_.reset()", [&] { key_session_.reset(new ContentKeySession(oec_session_id_, metrics_)); }); - // Set up Usage Table support - // - // This MUST be the last thing in the function because it contains the - // successful return paths of the function. It will attempt to initialize - // usage tables. However, whether they are employed is determined by - // information in the license, which will not be received until later. In case - // usage tables are not even needed, initialization errors here are ignored by - // returning successfully. - // - // TODO(b/141350978): Refactor this code so that it does not have this - // problem. if (!GetApiVersion(&api_version_)) { LOGE("Failed to get API version"); return USAGE_SUPPORT_GET_API_FAILED; } - - CdmUsageSupportType usage_support_type; - result = GetUsageSupportType(&usage_support_type); - if (result == NO_ERROR) { - metrics_->oemcrypto_usage_table_support_.Record(usage_support_type); - if (usage_support_type == kUsageEntrySupport) { - CdmSecurityLevel security_level = GetSecurityLevel(); - if (security_level == kSecurityLevelL1 || - security_level == kSecurityLevelL3) { - // This block cannot use |WithStaticFieldWriteLock| because it needs - // to unlock the lock partway through. - LOGV("Static field write lock: Open() initializing usage table"); - std::unique_lock auto_lock(static_field_mutex_); - - UsageTableHeader** header = security_level == kSecurityLevelL1 - ? &usage_table_header_l1_ - : &usage_table_header_l3_; - if (*header == nullptr) { - *header = new UsageTableHeader(); - // Ignore errors since we do not know when a session is opened, - // if it is intended to be used for offline/usage session related - // or otherwise. - auto_lock.unlock(); - bool is_usage_table_header_inited = - (*header)->Init(security_level, this); - auto_lock.lock(); - if (!is_usage_table_header_inited) { - delete *header; - *header = nullptr; - usage_table_header_ = nullptr; - return NO_ERROR; - } - } - usage_table_header_ = *header; - metrics_->usage_table_header_initial_size_.Record((*header)->size()); - } // End |static_field_mutex_| block. - } - } else { - metrics_->oemcrypto_usage_table_support_.SetError(result); - } - - // Do not add logic after this point as it may never get exercised. In the - // event of errors initializing the usage tables, the code will return early, - // never reaching this point. See the comment above the "Set up Usage Table - // support" section for details. - // - // TODO(b/139973602): Refactor this code. - return NO_ERROR; } @@ -848,11 +932,16 @@ void CryptoSession::Close() { open_ ? "true" : "false"); if (!open_) return; - OEMCryptoResult close_sts; - WithOecWriteLock( - "Close", [&] { close_sts = OEMCrypto_CloseSession(oec_session_id_); }); + const OEMCryptoResult close_sts = WithOecWriteLock( + "Close", [&] { return OEMCrypto_CloseSession(oec_session_id_); }); metrics_->oemcrypto_close_session_.Increment(close_sts); + // Clear cached values. + has_usage_info_support_ = kBooleanUnset; + oem_token_.clear(); + system_id_ = NULL_SYSTEM_ID; + pre_provision_token_type_ = kClientTokenUninitialized; + if (close_sts != OEMCrypto_SUCCESS) { LOGW("OEMCrypto_CloseSession failed: status = %d", static_cast(close_sts)); @@ -861,6 +950,7 @@ void CryptoSession::Close() { case OEMCrypto_SUCCESS: case OEMCrypto_ERROR_INVALID_SESSION: case OEMCrypto_ERROR_SYSTEM_INVALIDATED: + usage_table_header_ = nullptr; open_ = false; break; case OEMCrypto_ERROR_CLOSE_SESSION_FAILED: @@ -895,6 +985,14 @@ CdmResponseType CryptoSession::PrepareAndSignLicenseRequest( }); if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + // TODO(b/174412779): Remove when b/170704368 is fixed. + // Temporary workaround. If this error is returned the only way to + // recover is for the app to reprovision. + if (static_cast(sts) == kRsaSsaPssSignatureLengthError) { + LOGE("OEMCrypto PrepareAndSignLicenseRequest result = %d", + static_cast(sts)); + return NEED_PROVISIONING; + } return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "PrepareAndSignLicenseRequest"); } @@ -922,10 +1020,31 @@ CdmResponseType CryptoSession::PrepareAndSignLicenseRequest( core_message->resize(core_message_length); return NO_ERROR; } + // TODO(b/174412779): Remove when b/170704368 is fixed. + // Temporary workaround. If this error is returned the only way to + // recover is for the app to reprovision. + if (static_cast(sts) == kRsaSsaPssSignatureLengthError) { + LOGE("OEMCrypto PrepareAndSignLicenseRequest result = %d", + static_cast(sts)); + return NEED_PROVISIONING; + } return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "PrepareAndSignLicenseRequest"); } +CdmResponseType CryptoSession::UseSecondaryKey(bool dual_key) { +#ifdef HAS_DUAL_KEY + OEMCryptoResult sts; + WithOecSessionLock("UseSecondaryKey", [&] { + sts = OEMCrypto_UseSecondaryKey(oec_session_id_, dual_key); + }); + + return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "UseSecondaryKey"); +#else + return NO_ERROR; +#endif +} + CdmResponseType CryptoSession::LoadKeys( const std::string& message, const std::string& signature, const std::string& mac_key_iv, const std::string& mac_key, @@ -1139,7 +1258,7 @@ CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest( const OEMCryptoResult status = OEMCrypto_LoadOEMPrivateKey(oec_session_id_); if (status != OEMCrypto_SUCCESS) { return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR, - "GetTokenFromOemCert"); + "PrepareAndSignProvisioningRequest"); } } else { LOGE("Unknown method %d", pre_provision_token_type_); @@ -1220,41 +1339,171 @@ CdmResponseType CryptoSession::LoadEntitledContentKeys( } CdmResponseType CryptoSession::LoadCertificatePrivateKey( - const std::string& wrapped_key) { - // TODO(b/141655126): Getting the OEM Cert no longer loads the private key. - // Call OEMCrypto_GetOEMPublicCertificate before OEMCrypto_LoadDRMPrivateKey - // so it caches the OEMCrypto Public Key and then throw away result - std::string temp_buffer(CERTIFICATE_DATA_SIZE, '\0'); - size_t buf_size = temp_buffer.size(); - uint8_t* buf = reinterpret_cast(&temp_buffer[0]); - OEMCryptoResult sts; - WithOecSessionLock( - "LoadCertificatePrivateKey() calling OEMCrypto_GetOEMPublicCertificate", - [&] { - sts = OEMCrypto_GetOEMPublicCertificate(buf, &buf_size, - requested_security_level_); - }); - metrics_->oemcrypto_get_oem_public_certificate_.Increment(sts); + const CryptoWrappedKey& private_key) { + const OEMCrypto_PrivateKeyType key_type = + (private_key.type() == CryptoWrappedKey::kEcc) + ? OEMCrypto_ECC_Private_Key + : OEMCrypto_RSA_Private_Key; + const std::string& wrapped_key = private_key.key(); - LOGV("Loading device RSA key: id = %u", oec_session_id_); + LOGV("Loading device DRM key: id = %u", oec_session_id_); // TODO(b/140813486): determine if cert is RSA or ECC. + OEMCryptoResult sts; WithOecSessionLock( "LoadCertificatePrivateKey() calling OEMCrypto_LoadDRMPrivateKey()", [&] { M_TIME(sts = OEMCrypto_LoadDRMPrivateKey( - oec_session_id_, OEMCrypto_RSA_Private_Key, + oec_session_id_, key_type, reinterpret_cast(wrapped_key.data()), wrapped_key.size()), - metrics_, oemcrypto_load_device_rsa_key_, sts); + metrics_, oemcrypto_load_device_drm_key_, sts); }); return MapOEMCryptoResult(sts, LOAD_DEVICE_RSA_KEY_ERROR, "LoadCertificatePrivateKey"); } +CdmResponseType CryptoSession::GetBootCertificateChain( + std::string* bcc, std::string* additional_signature) { + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); + return GetBootCertificateChain(requested_security_level_, bcc, + additional_signature); +} + +CdmResponseType CryptoSession::GetBootCertificateChain( + RequestedSecurityLevel requested_security_level, std::string* bcc, + std::string* additional_signature) { + RETURN_IF_NULL(bcc, PARAMETER_NULL); + RETURN_IF_NULL(additional_signature, PARAMETER_NULL); + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + LOGV("requested_security_level = %s", + RequestedSecurityLevelToString(requested_security_level)); + if (pre_provision_token_type_ != kClientTokenBootCertChain) { + return PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR; + } + if (requested_security_level != kLevelDefault) { + LOGE("CDM only supports L1 BCC"); + return NOT_IMPLEMENTED_ERROR; + } + + size_t bcc_length = 0; + size_t additional_signature_length = 0; + OEMCryptoResult sts; + WithOecReadLock("GetBootCertificateChain Attempt 1", [&] { + sts = OEMCrypto_GetBootCertificateChain(nullptr, &bcc_length, nullptr, + &additional_signature_length); + }); + if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { + bcc->resize(bcc_length); + additional_signature->resize(additional_signature_length); + WithOecReadLock("GetBootCertificateChain Attempt 2", [&] { + sts = OEMCrypto_GetBootCertificateChain( + reinterpret_cast(&bcc->front()), &bcc_length, + reinterpret_cast(&additional_signature->front()), + &additional_signature_length); + }); + } + if (sts != OEMCrypto_SUCCESS) { + return MapOEMCryptoResult(sts, GET_BOOT_CERTIFICATE_CHAIN_ERROR, + "GetBootCertificateChain"); + } + bcc->resize(bcc_length); + additional_signature->resize(additional_signature_length); + return NO_ERROR; +} + +CdmResponseType CryptoSession::GenerateCertificateKeyPair( + std::string* public_key, std::string* public_key_signature, + std::string* wrapped_private_key, CryptoWrappedKey::Type* key_type) { + LOGV("Generating certificate key pair: id = %u", oec_session_id_); + RETURN_IF_NULL(public_key, PARAMETER_NULL); + RETURN_IF_NULL(public_key_signature, PARAMETER_NULL); + RETURN_IF_NULL(wrapped_private_key, PARAMETER_NULL); + RETURN_IF_NULL(key_type, PARAMETER_NULL); + RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); + + // Round 1, get the size of all the fields. + size_t public_key_length = 0; + size_t public_key_signature_length = 0; + size_t wrapped_private_key_length = 0; + OEMCrypto_PrivateKeyType oemcrypto_key_type; + OEMCryptoResult status; + WithOecSessionLock("GenerateCertificateKeyPair Attempt 1", [&] { + M_TIME(status = OEMCrypto_GenerateCertificateKeyPair( + oec_session_id_, nullptr, &public_key_length, nullptr, + &public_key_signature_length, nullptr, + &wrapped_private_key_length, &oemcrypto_key_type), + metrics_, oemcrypto_generate_certificate_key_pair_, status); + }); + + if (status != OEMCrypto_ERROR_SHORT_BUFFER) { + return MapOEMCryptoResult(status, GENERATE_CERTIFICATE_KEY_PAIR_ERROR, + "GenerateCertificateKeyPair"); + } + + public_key->resize(public_key_length); + public_key_signature->resize(public_key_signature_length); + wrapped_private_key->resize(wrapped_private_key_length); + WithOecSessionLock("GenerateCertificateKeyPair Attempt 2", [&] { + M_TIME( + status = OEMCrypto_GenerateCertificateKeyPair( + oec_session_id_, reinterpret_cast(&public_key->front()), + &public_key_length, + reinterpret_cast(&public_key_signature->front()), + &public_key_signature_length, + reinterpret_cast(&wrapped_private_key->front()), + &wrapped_private_key_length, &oemcrypto_key_type), + metrics_, oemcrypto_generate_certificate_key_pair_, status); + }); + if (status != OEMCrypto_SUCCESS) { + return MapOEMCryptoResult(status, GENERATE_CERTIFICATE_KEY_PAIR_ERROR, + "GenerateCertificateKeyPair"); + } + + public_key->resize(public_key_length); + public_key_signature->resize(public_key_signature_length); + wrapped_private_key->resize(wrapped_private_key_length); + + if (oemcrypto_key_type == OEMCrypto_RSA_Private_Key) { + *key_type = CryptoWrappedKey::kRsa; + } else if (oemcrypto_key_type == OEMCrypto_ECC_Private_Key) { + *key_type = CryptoWrappedKey::kEcc; + } else { + LOGE("Unexpected key type returned from GenerateCertificateKeyPair: %d", + static_cast(oemcrypto_key_type)); + return GENERATE_CERTIFICATE_KEY_PAIR_UNKNOWN_TYPE_ERROR; + } + + return NO_ERROR; +} + +CdmResponseType CryptoSession::LoadOemCertificatePrivateKey( + const CryptoWrappedKey& private_key) { + LOGV("Load OEM cert and private key: id = %u", oec_session_id_); + const OEMCrypto_PrivateKeyType key_type = + (private_key.type() == CryptoWrappedKey::kEcc) + ? OEMCrypto_ECC_Private_Key + : OEMCrypto_RSA_Private_Key; + const std::string& wrapped_private_key = private_key.key(); + + OEMCryptoResult status; + WithOecSessionLock("InstallOemPrivateKey", [&] { + M_TIME(status = OEMCrypto_InstallOemPrivateKey( + oec_session_id_, key_type, + reinterpret_cast(wrapped_private_key.data()), + wrapped_private_key.size()), + metrics_, oemcrypto_install_oem_private_key_, status); + }); + + return MapOEMCryptoResult(status, LOAD_OEM_CERTIFICATE_PRIVATE_KEY_ERROR, + "InstallOemPrivateKey"); +} + // Private. CdmResponseType CryptoSession::SelectKey(const std::string& key_id, CdmCipherMode cipher_mode) { + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); const OEMCryptoResult sts = WithOecSessionLock("SelectKey", [&] { + RETURN_IF_NULL(key_session_, OEMCrypto_ERROR_INVALID_SESSION); return key_session_->SelectKey(key_id, cipher_mode); }); @@ -1365,14 +1614,14 @@ size_t CryptoSession::GetMaxSubsampleRegionSize() { // Subtract RESOURCE_RATING_TIER_MIN to get a 0-based index into the // table. const uint32_t index = tier - RESOURCE_RATING_TIER_MIN; - if (index < ArraySize(kMaxSubsampleRegionSizes)) { + if (index < wvutil::ArraySize(kMaxSubsampleRegionSizes)) { max_subsample_region_size_ = kMaxSubsampleRegionSizes[index]; } } // If something went wrong, use the default. if (max_subsample_region_size_ == 0) { - LOGW("Unable to get maximum subsample region size. Defaulting to %zu.", + LOGW("Unable to get maximum subsample region size. Defaulting to %zu", kDefaultMaxSubsampleRegionSize); max_subsample_region_size_ = kDefaultMaxSubsampleRegionSize; } @@ -1426,16 +1675,16 @@ CdmResponseType CryptoSession::Decrypt( output_descriptor.type = output_descriptor_type; switch (output_descriptor.type) { case OEMCrypto_BufferType_Clear: - output_descriptor.buffer.clear.address = + output_descriptor.buffer.clear.clear_buffer = static_cast(sample.decrypt_buffer) + sample.decrypt_buffer_offset; - output_descriptor.buffer.clear.address_length = + output_descriptor.buffer.clear.clear_buffer_length = sample.decrypt_buffer_size - sample.decrypt_buffer_offset; break; case OEMCrypto_BufferType_Secure: - output_descriptor.buffer.secure.handle = sample.decrypt_buffer; + output_descriptor.buffer.secure.secure_buffer = sample.decrypt_buffer; output_descriptor.buffer.secure.offset = sample.decrypt_buffer_offset; - output_descriptor.buffer.secure.handle_length = + output_descriptor.buffer.secure.secure_buffer_length = sample.decrypt_buffer_size; break; case OEMCrypto_BufferType_Direct: @@ -1539,7 +1788,12 @@ CdmResponseType CryptoSession::Decrypt( "oec_session_id = %u", oec_session_id_); } else { - LOGE("OEMCrypto_DecryptCENC failed: status = %d", static_cast(sts)); + LOGE( + "OEMCrypto_DecryptCENC failed: oec_session_id = %u, " + "security_level = %s, status = %d", + oec_session_id_, + RequestedSecurityLevelToString(requested_security_level_), + static_cast(sts)); } } @@ -1571,19 +1825,39 @@ CdmResponseType CryptoSession::Decrypt( } } -bool CryptoSession::UsageInformationSupport(bool* has_support) { - LOGV("Checking if usage information is supported"); +bool CryptoSession::HasUsageInfoSupport(bool* has_support) { RETURN_IF_NOT_OPEN(false); - return UsageInformationSupport(requested_security_level_, has_support); + RETURN_IF_NULL(has_support, false); + return WithOecReadLock("HasUsageInfoSupport", [&] { + // Use cached value if set. + if (has_usage_info_support_ != kBooleanUnset) { + *has_support = (has_usage_info_support_ == kBooleanTrue); + return true; + } + if (!HasUsageInfoSupportInternal(requested_security_level_, has_support)) { + return false; + } + // Cache result if successful. + has_usage_info_support_ = (*has_support ? kBooleanTrue : kBooleanFalse); + return true; + }); } -bool CryptoSession::UsageInformationSupport(SecurityLevel security_level, - bool* has_support) { - LOGV("Checking if usage information is supported: security_level = %d", - static_cast(security_level)); +bool CryptoSession::HasUsageInfoSupport( + RequestedSecurityLevel requested_security_level, bool* has_support) { RETURN_IF_UNINITIALIZED(false); - WithOecReadLock("UsageInformationSupport", [&] { - *has_support = OEMCrypto_SupportsUsageTable(security_level); + RETURN_IF_NULL(has_support, false); + return WithOecReadLock("HasUsageInfoSupport", [&] { + return HasUsageInfoSupportInternal(requested_security_level, has_support); + }); +} + +bool CryptoSession::HasUsageInfoSupportInternal( + RequestedSecurityLevel requested_security_level, bool* has_support) { + LOGV("requested_security_level = %s", + RequestedSecurityLevelToString(requested_security_level)); + *has_support = WithOecReadLock("HasUsageInfoSupport", [&] { + return OEMCrypto_SupportsUsageTable(requested_security_level); }); return true; } @@ -1667,7 +1941,7 @@ CdmResponseType CryptoSession::GenerateUsageReport( (*usage_report) = std::string(reinterpret_cast(&buffer[0]), buffer.size()); - Unpacked_PST_Report pst_report(&buffer[0]); + wvutil::Unpacked_PST_Report pst_report(&buffer[0]); *usage_duration_status = kUsageDurationsInvalid; if (usage_length < pst_report.report_size()) { LOGE( @@ -1689,13 +1963,13 @@ CdmResponseType CryptoSession::GenerateUsageReport( static_cast(pst_report.pst_length())); LOGV("OEMCrypto_PST_Report.padding: %d\n", static_cast(pst_report.padding())); - LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %ld\n", + LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %" PRId64 "\n", pst_report.seconds_since_license_received()); - LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %ld\n", + LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %" PRId64 "\n", pst_report.seconds_since_first_decrypt()); - LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %ld\n", + LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %" PRId64 "\n", pst_report.seconds_since_last_decrypt()); - LOGV("OEMCrypto_PST_Report: %s\n", b2a_hex(*usage_report).c_str()); + LOGV("OEMCrypto_PST_Report: %s\n", wvutil::b2a_hex(*usage_report).c_str()); if (kInactiveUnused == pst_report.status()) { *usage_duration_status = kUsageDurationPlaybackNotBegun; @@ -1795,6 +2069,11 @@ CdmResponseType CryptoSession::LoadProvisioning( metrics_, oemcrypto_load_provisioning_, status); }); + if (status == OEMCrypto_SUCCESS) { + wrapped_private_key->resize(wrapped_private_key_length); + return NO_ERROR; + } + wrapped_private_key->clear(); return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR, "LoadProvisioning"); } @@ -1806,11 +2085,11 @@ CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current, return GetHdcpCapabilities(requested_security_level_, current, max); } -CdmResponseType CryptoSession::GetHdcpCapabilities(SecurityLevel security_level, - HdcpCapability* current, - HdcpCapability* max) { - LOGV("Getting HDCP capabilities: id = %u, security_level = %d", - oec_session_id_, static_cast(security_level)); +CdmResponseType CryptoSession::GetHdcpCapabilities( + RequestedSecurityLevel security_level, HdcpCapability* current, + HdcpCapability* max) { + LOGV("Getting HDCP capabilities: id = %u, security_level = %s", + oec_session_id_, RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(current, PARAMETER_NULL); RETURN_IF_NULL(max, PARAMETER_NULL); @@ -1837,14 +2116,16 @@ bool CryptoSession::GetSupportedCertificateTypes( LOGV("Getting supported certificate types: id = %u", oec_session_id_); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(support, false); - - uint32_t oec_support; - WithOecReadLock("GetSupportedCertificateTypes", [&] { - oec_support = OEMCrypto_SupportedCertificates(requested_security_level_); - }); + const uint32_t oec_support = + WithOecReadLock("GetSupportedCertificateTypes", [&] { + return OEMCrypto_SupportedCertificates(requested_security_level_); + }); support->rsa_2048_bit = oec_support & OEMCrypto_Supports_RSA_2048bit; support->rsa_3072_bit = oec_support & OEMCrypto_Supports_RSA_3072bit; support->rsa_cast = oec_support & OEMCrypto_Supports_RSA_CAST; + support->ecc_secp256r1 = oec_support & OEMCrypto_Supports_ECC_secp256r1; + support->ecc_secp384r1 = oec_support & OEMCrypto_Supports_ECC_secp384r1; + support->ecc_secp521r1 = oec_support & OEMCrypto_Supports_ECC_secp521r1; return true; } @@ -1861,9 +2142,9 @@ CdmResponseType CryptoSession::GetRandom(size_t data_length, } CdmResponseType CryptoSession::GetNumberOfOpenSessions( - SecurityLevel security_level, size_t* count) { - LOGV("Getting number of open sessions: id = %u, security_level = %d", - oec_session_id_, static_cast(security_level)); + RequestedSecurityLevel security_level, size_t* count) { + LOGV("Getting number of open sessions: id = %u, security_level = %s", + oec_session_id_, RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(count, PARAMETER_NULL); @@ -1885,9 +2166,9 @@ CdmResponseType CryptoSession::GetNumberOfOpenSessions( } CdmResponseType CryptoSession::GetMaxNumberOfSessions( - SecurityLevel security_level, size_t* max) { - LOGV("Getting max number of sessions: id = %u, security_level = %d", - oec_session_id_, static_cast(security_level)); + RequestedSecurityLevel security_level, size_t* max) { + LOGV("Getting max number of sessions: id = %u, security_level = %s", + oec_session_id_, RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(max, PARAMETER_NULL); @@ -1937,39 +2218,16 @@ CdmResponseType CryptoSession::GetSrmVersion(uint16_t* srm_version) { } } -bool CryptoSession::IsSrmUpdateSupported() { - LOGV("Checking if SRM update is supported"); - RETURN_IF_UNINITIALIZED(false); - return WithOecReadLock("IsSrmUpdateSupported", - [&] { return OEMCrypto_IsSRMUpdateSupported(); }); -} - -CdmResponseType CryptoSession::LoadSrm(const std::string& srm) { - LOGV("Loading SRM"); - RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); - if (srm.empty()) { - LOGE("SRM is empty"); - return INVALID_SRM_LIST; - } - - const OEMCryptoResult status = WithOecWriteLock("LoadSrm", [&] { - return OEMCrypto_LoadSRM(reinterpret_cast(srm.data()), - srm.size()); - }); - - return MapOEMCryptoResult(status, LOAD_SRM_ERROR, "LoadSRM"); -} - bool CryptoSession::GetResourceRatingTier(uint32_t* tier) { LOGV("Getting resource rating tier"); RETURN_IF_NOT_OPEN(false); return GetResourceRatingTier(requested_security_level_, tier); } -bool CryptoSession::GetResourceRatingTier(SecurityLevel security_level, +bool CryptoSession::GetResourceRatingTier(RequestedSecurityLevel security_level, uint32_t* tier) { - LOGV("Getting resource rating tier: security_level = %d", - static_cast(security_level)); + LOGV("Getting resource rating tier: security_level = %s", + RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(tier, false); @@ -1994,30 +2252,105 @@ bool CryptoSession::GetBuildInformation(std::string* info) { return GetBuildInformation(requested_security_level_, info); } -bool CryptoSession::GetBuildInformation(SecurityLevel security_level, +bool CryptoSession::GetBuildInformation(RequestedSecurityLevel security_level, std::string* info) { - LOGV("Getting build information: security_level = %d", - static_cast(security_level)); + LOGV("Getting build information: security_level = %s", + RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(info, false); - - const char* build_information; - WithOecReadLock("GetBuildInformation", [&] { - build_information = OEMCrypto_BuildInformation(security_level); + size_t info_length = 128; + info->assign(info_length, '\0'); + OEMCryptoResult result = WithOecReadLock("GetBuildInformation", [&] { + return OEMCrypto_BuildInformation(&info->front(), &info_length, + security_level); }); - if (build_information == nullptr) { - LOGE("OEMCrypto_BuildInformation failed: Returned null"); + if (result == OEMCrypto_ERROR_SHORT_BUFFER) { + info->assign(info_length, '\0'); + result = WithOecReadLock("GetBuildInformation Attempt 2", [&] { + return OEMCrypto_BuildInformation(&info->front(), &info_length, + security_level); + }); + } + if (result != OEMCrypto_SUCCESS) { + LOGE("GetBuildInformation failed: result = %d", result); + info->clear(); return false; } - - info->assign(build_information); + info->resize(info_length); return true; } -bool CryptoSession::GetMaximumUsageTableEntries(SecurityLevel security_level, - size_t* number_of_entries) { - LOGV("Getting maximum usage table entries: security_level = %d", - static_cast(security_level)); +bool CryptoSession::GetWatermarkingSupport(CdmWatermarkingSupport* support) { + RETURN_IF_NOT_OPEN(false); + return GetWatermarkingSupport(requested_security_level_, support); +} + +bool CryptoSession::GetWatermarkingSupport( + RequestedSecurityLevel security_level, CdmWatermarkingSupport* support) { + LOGV("security_level = %s", RequestedSecurityLevelToString(security_level)); + RETURN_IF_UNINITIALIZED(false); + RETURN_IF_NULL(support, false); + const OEMCrypto_WatermarkingSupport oec_support = WithOecReadLock( + "GetWatermarkingSupport", + [&] { return OEMCrypto_GetWatermarkingSupport(security_level); }); + switch (oec_support) { + case OEMCrypto_WatermarkingNotSupported: + *support = kWatermarkingNotSupported; + break; + case OEMCrypto_WatermarkingConfigurable: + *support = kWatermarkingConfigurable; + break; + case OEMCrypto_WatermarkingAlwaysOn: + *support = kWatermarkingAlwaysOn; + break; + case OEMCrypto_WatermarkingError: + default: + LOGE("GetWatermarkingSupport error: security_level = %s, result = %d", + RequestedSecurityLevelToString(security_level), + static_cast(oec_support)); + metrics_->oemcrypto_watermarking_support_.SetError(oec_support); + return false; + } + metrics_->oemcrypto_watermarking_support_.Record(oec_support); + return true; +} + +bool CryptoSession::GetProductionReadiness(CdmProductionReadiness* readiness) { + RETURN_IF_NOT_OPEN(false); + return GetProductionReadiness(requested_security_level_, readiness); +} + +bool CryptoSession::GetProductionReadiness( + RequestedSecurityLevel security_level, CdmProductionReadiness* readiness) { + LOGV("security_level = %s", RequestedSecurityLevelToString(security_level)); + RETURN_IF_UNINITIALIZED(false); + RETURN_IF_NULL(readiness, false); + const OEMCryptoResult result = WithOecReadLock("GetProductionReadiness", [&] { + return OEMCrypto_ProductionReady(security_level); + }); + metrics_->oemcrypto_production_readiness_.Record(result); + switch (result) { + case OEMCrypto_SUCCESS: + *readiness = kProductionReadinessTrue; + break; + case OEMCrypto_ERROR_NOT_IMPLEMENTED: + *readiness = kProductionReadinessUnknown; + break; + case OEMCrypto_ERROR_UNKNOWN_FAILURE: + default: // Other vendor-defined codes indicate not production ready. + LOGD("Not production ready: security_level = %s, result = %d", + RequestedSecurityLevelToString(security_level), + static_cast(result)); + *readiness = kProductionReadinessFalse; + break; + } + return true; +} + +bool CryptoSession::GetMaximumUsageTableEntries( + RequestedSecurityLevel security_level, size_t* number_of_entries) { + LOGV("Getting maximum usage table entries: security_level = %s", + RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(false); if (number_of_entries == nullptr) { LOGE("Output parameter |number_of_entries| not provided"); @@ -2028,7 +2361,7 @@ bool CryptoSession::GetMaximumUsageTableEntries(SecurityLevel security_level, }); // Record the number of entries into the metrics. metrics_->oemcrypto_maximum_usage_table_header_size_.Record( - *number_of_entries); + static_cast(*number_of_entries)); if (*number_of_entries == 0) { // Special value, indicating that the table size is not directly @@ -2038,7 +2371,7 @@ bool CryptoSession::GetMaximumUsageTableEntries(SecurityLevel security_level, return *number_of_entries >= kMinimumUsageTableEntriesSupported; } -bool CryptoSession::GetDecryptHashSupport(SecurityLevel security_level, +bool CryptoSession::GetDecryptHashSupport(RequestedSecurityLevel security_level, uint32_t* decrypt_hash_support) { LOGV("Checking if decrypt hash is supported"); RETURN_IF_UNINITIALIZED(false); @@ -2121,9 +2454,9 @@ CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer, LOGV("Generic encrypt: id = %u", oec_session_id_); RETURN_IF_NULL(out_buffer, PARAMETER_NULL); - OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm); + OEMCrypto_Algorithm oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING; if (iv.size() != GenericEncryptionBlockSize(algorithm) || - oec_algorithm == kInvalidAlgorithm) { + !GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) { return INVALID_PARAMETERS_ENG_13; } @@ -2182,9 +2515,9 @@ CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer, LOGV("Generic decrypt: id = %u", oec_session_id_); RETURN_IF_NULL(out_buffer, PARAMETER_NULL); - OEMCrypto_Algorithm oec_algorithm = GenericEncryptionAlgorithm(algorithm); + OEMCrypto_Algorithm oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING; if (iv.size() != GenericEncryptionBlockSize(algorithm) || - oec_algorithm == kInvalidAlgorithm) { + !GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) { return INVALID_PARAMETERS_ENG_14; } @@ -2242,8 +2575,8 @@ CdmResponseType CryptoSession::GenericSign(const std::string& message, LOGV("Generic sign: id = %u", oec_session_id_); RETURN_IF_NULL(signature, PARAMETER_NULL); - OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm); - if (oec_algorithm == kInvalidAlgorithm) { + OEMCrypto_Algorithm oec_algorithm = OEMCrypto_HMAC_SHA256; + if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) { return INVALID_PARAMETERS_ENG_15; } @@ -2308,8 +2641,8 @@ CdmResponseType CryptoSession::GenericVerify(const std::string& message, const std::string& signature) { LOGV("Generic verify: id = %u", oec_session_id_); - OEMCrypto_Algorithm oec_algorithm = GenericSigningAlgorithm(algorithm); - if (oec_algorithm == kInvalidAlgorithm) { + OEMCrypto_Algorithm oec_algorithm = OEMCrypto_HMAC_SHA256; + if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) { return INVALID_PARAMETERS_ENG_16; } @@ -2354,40 +2687,11 @@ CdmResponseType CryptoSession::GenericVerify(const std::string& message, } } -CdmResponseType CryptoSession::GetUsageSupportType( - CdmUsageSupportType* usage_support_type) { - LOGV("Getting usage support type: id = %u", oec_session_id_); - - RETURN_IF_NULL(usage_support_type, PARAMETER_NULL); - - if (usage_support_type_ != kUnknownUsageSupport) { - *usage_support_type = usage_support_type_; - return NO_ERROR; - } - - bool has_support = false; - if (!UsageInformationSupport(&has_support)) { - LOGE("UsageInformationSupport failed"); - return USAGE_INFORMATION_SUPPORT_FAILED; - } - - if (!has_support) { - *usage_support_type = usage_support_type_ = kNonSecureUsageSupport; - } else { - // As of v16, all supported version of OEMCrypto provide usage entry - // support or no usage info support. - *usage_support_type = usage_support_type_ = kUsageEntrySupport; - } - return NO_ERROR; -} - CdmResponseType CryptoSession::CreateUsageTableHeader( - SecurityLevel requested_security_level, + RequestedSecurityLevel requested_security_level, CdmUsageTableHeader* usage_table_header) { LOGV("Creating usage table header: requested_security_level = %s", - requested_security_level == kLevel3 - ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() - : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); + RequestedSecurityLevelToString(requested_security_level)); RETURN_IF_NULL(usage_table_header, PARAMETER_NULL); usage_table_header->resize(kEstimatedInitialUsageTableHeader); @@ -2425,21 +2729,18 @@ CdmResponseType CryptoSession::CreateUsageTableHeader( } CdmResponseType CryptoSession::LoadUsageTableHeader( - SecurityLevel requested_security_level, + RequestedSecurityLevel requested_security_level, const CdmUsageTableHeader& usage_table_header) { LOGV("Loading usage table header: requested_security_level = %s", - requested_security_level == kLevel3 - ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() - : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); + RequestedSecurityLevelToString(requested_security_level)); - OEMCryptoResult result; - WithOecWriteLock("LoadUsageTableHeader", [&] { - result = OEMCrypto_LoadUsageTableHeader( + const OEMCryptoResult result = WithOecWriteLock("LoadUsageTableHeader", [&] { + return OEMCrypto_LoadUsageTableHeader( requested_security_level, reinterpret_cast(usage_table_header.data()), usage_table_header.size()); - metrics_->oemcrypto_load_usage_table_header_.Increment(result); }); + metrics_->oemcrypto_load_usage_table_header_.Increment(result); if (result != OEMCrypto_SUCCESS) { if (result == OEMCrypto_WARNING_GENERATION_SKEW) { @@ -2469,12 +2770,10 @@ CdmResponseType CryptoSession::LoadUsageTableHeader( } CdmResponseType CryptoSession::ShrinkUsageTableHeader( - SecurityLevel requested_security_level, uint32_t new_entry_count, + RequestedSecurityLevel requested_security_level, uint32_t new_entry_count, CdmUsageTableHeader* usage_table_header) { LOGV("Shrinking usage table header: requested_security_level = %s", - requested_security_level == kLevel3 - ? QUERY_VALUE_SECURITY_LEVEL_L3.c_str() - : QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str()); + RequestedSecurityLevelToString(requested_security_level)); RETURN_IF_NULL(usage_table_header, PARAMETER_NULL); size_t usage_table_header_len = 0; @@ -2648,9 +2947,9 @@ bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output, bool* can_disable_output, bool* can_support_cgms_a) { LOGV("Getting analog output capabilities: id = %u", oec_session_id_); - uint32_t flags; - WithOecReadLock("GetAnalogOutputCapabilities", [&] { - flags = OEMCrypto_GetAnalogOutputFlags(requested_security_level_); + RETURN_IF_UNINITIALIZED(false); + const uint32_t flags = WithOecReadLock("GetAnalogOutputCapabilities", [&] { + return OEMCrypto_GetAnalogOutputFlags(requested_security_level_); }); if ((flags & OEMCrypto_Unknown_Analog_Output) != 0) return false; @@ -2660,33 +2959,6 @@ bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output, return true; } -OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm( - CdmSigningAlgorithm algorithm) { - if (kSigningAlgorithmHmacSha256 == algorithm) { - return OEMCrypto_HMAC_SHA256; - } else { - return kInvalidAlgorithm; - } -} - -OEMCrypto_Algorithm CryptoSession::GenericEncryptionAlgorithm( - CdmEncryptionAlgorithm algorithm) { - if (kEncryptionAlgorithmAesCbc128 == algorithm) { - return OEMCrypto_AES_CBC_128_NO_PADDING; - } else { - return kInvalidAlgorithm; - } -} - -size_t CryptoSession::GenericEncryptionBlockSize( - CdmEncryptionAlgorithm algorithm) { - if (kEncryptionAlgorithmAesCbc128 == algorithm) { - return kAes128BlockSize; - } else { - return 0; - } -} - // OEMCryptoResult OEMCrypto_DecryptCENC( // OEMCrypto_SESSION session, // const OEMCrypto_SampleDescription* samples, // an array of samples. @@ -2794,9 +3066,9 @@ OEMCryptoResult CryptoSession::DecryptSample( fake_sample.buffers.input_data += length; AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length); if (cipher_mode == kCipherModeCtr) { - AdvanceIvCtr(&fake_sample.iv, - original_subsample.block_offset + - original_subsample.num_bytes_encrypted); + wvutil::AdvanceIvCtr(&fake_sample.iv, + original_subsample.block_offset + + original_subsample.num_bytes_encrypted); } } } @@ -2951,7 +3223,8 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( if (cipher_mode == kCipherModeCtr) { // For 'cenc', update the IV depending on how many encrypted blocks // we passed. - AdvanceIvCtr(&fake_sample.iv, chunk_size + fake_subsample.block_offset); + wvutil::AdvanceIvCtr(&fake_sample.iv, + chunk_size + fake_subsample.block_offset); } else if (cipher_mode == kCipherModeCbc) { // For 'cbcs', use the last ciphertext block as the next IV. The last // block that was encrypted is probably not the last block of the @@ -2965,7 +3238,7 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( static_assert(sizeof(fake_sample.iv) == kAes128BlockSize, "The size of an AES-128 block and the size of an AES-128 " - "IV have become misaligned."); + "IV have become misaligned"); memcpy(fake_sample.iv, block_end - kAes128BlockSize, kAes128BlockSize); } } @@ -2976,15 +3249,81 @@ OEMCryptoResult CryptoSession::LegacyDecryptInChunks( remaining_input_data -= chunk_size; AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size); } - return sts; } +CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) { + OEMCryptoResult status = OEMCrypto_SetDebugIgnoreKeyboxCount(count); + return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount"); +} + +CdmResponseType CryptoSession::SetAllowTestKeybox(bool allow) { + OEMCryptoResult status = OEMCrypto_SetAllowTestKeybox(allow); + return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetAllowTestKeybox"); +} + +okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() { + const auto getter = [&]() -> okp::SystemFallbackPolicy* { + // If not set, then OTA keybox provisioning is not supported or + // not needed. + if (!okp_fallback_policy_l1_) return nullptr; + return okp_fallback_policy_l1_.get(); + }; + return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter); +} + +CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( + bool use_test_key, std::string* request) { + RETURN_IF_NULL(request, PARAMETER_NULL); + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); + size_t buffer_length = 0; + OEMCryptoResult status = + WithOecWriteLock("PrepareOtaProvisioningRequest", [&] { + return OEMCrypto_GenerateOTARequest( + oec_session_id_, nullptr, &buffer_length, use_test_key ? 1 : 0); + }); + if (status != OEMCrypto_ERROR_SHORT_BUFFER) + return MapOEMCryptoResult(status, UNKNOWN_ERROR, + "PrepareOtaProvisioningRequest"); + if (buffer_length == 0) { + LOGE("OTA request size is zero"); + return UNKNOWN_ERROR; + } + request->resize(buffer_length); + uint8_t* buf = reinterpret_cast(&request->front()); + status = WithOecWriteLock("PrepareOtaProvisioningRequest", [&] { + return OEMCrypto_GenerateOTARequest(oec_session_id_, buf, &buffer_length, + use_test_key ? 1 : 0); + }); + if (OEMCrypto_SUCCESS != status) { + request->clear(); + } else if (buffer_length != request->size()) { + request->resize(buffer_length); + } + return MapOEMCryptoResult(status, UNKNOWN_ERROR, + "PrepareOtaProvisioningRequest"); +} + +CdmResponseType CryptoSession::LoadOtaProvisioning( + bool use_test_key, const std::string& response) { + RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); + const OEMCryptoResult status = WithOecWriteLock("LoadOtaProvisioning", [&] { + return OEMCrypto_ProcessOTAKeybox( + oec_session_id_, reinterpret_cast(response.data()), + response.size(), use_test_key ? 1 : 0); + }); + if (status == OEMCrypto_SUCCESS) { + WithOecWriteLock("LoadOtaProvisioning", + [&] { needs_keybox_provisioning_ = false; }); + } + return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning"); +} + template auto CryptoSession::WithStaticFieldWriteLock(const char* tag, Func body) -> decltype(body()) { LOGV("Static field write lock: %s", tag); - std::unique_lock auto_lock(static_field_mutex_); + std::unique_lock auto_lock(static_field_mutex_); return body(); } @@ -2992,7 +3331,7 @@ template auto CryptoSession::WithStaticFieldReadLock(const char* tag, Func body) -> decltype(body()) { LOGV("Static field read lock: %s", tag); - shared_lock auto_lock(static_field_mutex_); + wvutil::shared_lock auto_lock(static_field_mutex_); return body(); } @@ -3000,7 +3339,7 @@ template auto CryptoSession::WithOecWriteLock(const char* tag, Func body) -> decltype(body()) { LOGV("OEMCrypto write lock: %s", tag); - std::unique_lock auto_lock(oem_crypto_mutex_); + std::unique_lock auto_lock(oem_crypto_mutex_); return body(); } @@ -3008,7 +3347,7 @@ template auto CryptoSession::WithOecReadLock(const char* tag, Func body) -> decltype(body()) { LOGV("OEMCrypto read lock: %s", tag); - shared_lock auto_lock(oem_crypto_mutex_); + wvutil::shared_lock auto_lock(oem_crypto_mutex_); return body(); } @@ -3016,7 +3355,7 @@ template auto CryptoSession::WithOecSessionLock(const char* tag, Func body) -> decltype(body()) { LOGV("OEMCrypto session lock: %s", tag); - shared_lock oec_auto_lock(oem_crypto_mutex_); + wvutil::shared_lock oec_auto_lock(oem_crypto_mutex_); std::unique_lock session_auto_lock(oem_crypto_session_mutex_); return body(); } @@ -3044,5 +3383,4 @@ CryptoSession* CryptoSessionFactory::MakeCryptoSession( metrics::CryptoMetrics* crypto_metrics) { return new CryptoSession(crypto_metrics); } - } // namespace wvcdm diff --git a/core/src/device_files.cpp b/core/src/device_files.cpp index 923de336..b637b6f2 100644 --- a/core/src/device_files.cpp +++ b/core/src/device_files.cpp @@ -1,15 +1,18 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "device_files.h" +#include #include #include #include +#include "cdm_random.h" #include "certificate_provisioning.h" +#include "clock.h" #include "file_store.h" #include "license_protocol.pb.h" #include "log.h" @@ -28,7 +31,9 @@ using video_widevine_client::sdk::License; using video_widevine_client::sdk::License_LicenseState_ACTIVE; using video_widevine_client::sdk::License_LicenseState_RELEASING; using video_widevine_client::sdk::NameValue; +using video_widevine_client::sdk::OemCertificate; using video_widevine_client::sdk::UsageInfo; +using video_widevine_client::sdk::UsageInfo_DrmUsageCertificate; using video_widevine_client::sdk::UsageInfo_ProviderSession; using video_widevine_client::sdk::UsageTableInfo; using video_widevine_client::sdk::UsageTableInfo_UsageEntryInfo; @@ -41,7 +46,19 @@ using video_widevine_client::sdk:: // Stringify turns macro arguments into static C strings. // Example: STRINGIFY(this_argument) -> "this_argument" -#define STRINGIFY(PARAM...) #PARAM +#define STRINGIFY(PARAM) #PARAM + +#define RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(PARAM) \ + if ((PARAM) == nullptr) { \ + LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ + return DeviceFiles::kCannotHandle; \ + } + +#define RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED() \ + if (!initialized_) { \ + LOGE("Device files is not initialized"); \ + return DeviceFiles::kCannotHandle; \ + } #define RETURN_FALSE_IF_NULL(PARAM) \ if ((PARAM) == nullptr) { \ @@ -69,26 +86,289 @@ using video_widevine_client::sdk:: return false; \ } +namespace wvcdm { +using UniqueLock = std::unique_lock; + namespace { - -const char kAtscCertificateFileName[] = "atsccert.bin"; -const char kCertificateFileName[] = "cert.bin"; -const char kHlsAttributesFileNameExt[] = ".hal"; -const char kUsageInfoFileNamePrefix[] = "usage"; -const char kUsageInfoFileNameExt[] = ".bin"; -const char kLicenseFileNameExt[] = ".lic"; const char kEmptyFileName[] = ""; +const char kFalse[] = "false"; +const char kHlsAttributesFileNameExt[] = ".hal"; +const char kLicenseFileNameExt[] = ".lic"; +const char kTrue[] = "true"; +const char kUsageInfoFileNameExt[] = ".bin"; +const char kUsageInfoFileNamePrefix[] = "usage"; const char kUsageTableFileName[] = "usgtable.bin"; +const char kOkpInfoFileName[] = "okp.bin"; const char kWildcard[] = "*"; +// TODO(b/192430982): Renable expiration of legacy DRM certificates +// constexpr int64_t kFourMonthsInSeconds = (2 * 30 + 2 * 31) * 24 * 60 * 60; +// Helper methods +bool SetDeviceCertificate(const std::string& certificate, + const CryptoWrappedKey& private_key, + DeviceCertificate* mutable_device_certificate) { + RETURN_FALSE_IF_NULL(mutable_device_certificate); + + mutable_device_certificate->set_certificate(certificate); + mutable_device_certificate->set_wrapped_private_key(private_key.key()); + switch (private_key.type()) { + case CryptoWrappedKey::kRsa: + mutable_device_certificate->set_key_type(DeviceCertificate::RSA); + return true; + case CryptoWrappedKey::kEcc: + mutable_device_certificate->set_key_type(DeviceCertificate::ECC); + return true; + case CryptoWrappedKey::kUninitialized: // Suppress compiler + // warnings. + default: + LOGE("Unexpected key type: %d", private_key.type()); + return false; + } +} + +bool ExtractFromDeviceCertificate(const DeviceCertificate& device_certificate, + std::string* certificate, + CryptoWrappedKey* private_key) { + RETURN_FALSE_IF_NULL(certificate); + RETURN_FALSE_IF_NULL(private_key); + + bool has_certificate = device_certificate.has_certificate(); + bool has_key = device_certificate.has_wrapped_private_key(); + + // If no certificate information, nothing to be done. DeviceCertificate + // is a legacy DRM certificate + if (!has_certificate && !has_key) return true; + + // Flag if not a default certificate + if (!has_certificate || !has_key) { + LOGE( + "Device certificate proto belongs to neither a default or legacy cert. " + "has_certificate: %s, has_key: %s", + has_certificate ? kTrue : kFalse, has_key ? kTrue : kFalse); + return false; + } + + if (device_certificate.certificate().empty() || + device_certificate.wrapped_private_key().empty()) { + LOGE( + "Device certificate proto belongs does not have a valid certificate or " + "wrapped key. certificate size: %zu, wrapped key size: %zu", + device_certificate.certificate().size(), + device_certificate.wrapped_private_key().size()); + return false; + } + + *certificate = device_certificate.certificate(); + private_key->Clear(); + private_key->set_key(device_certificate.wrapped_private_key()); + if (device_certificate.has_key_type()) { + const DeviceCertificate::PrivateKeyType key_type = + device_certificate.key_type(); + switch (key_type) { + case DeviceCertificate::RSA: + private_key->set_type(CryptoWrappedKey::kRsa); + break; + case DeviceCertificate::ECC: + private_key->set_type(CryptoWrappedKey::kEcc); + break; + default: + LOGW("Unknown DRM key type, defaulting to RSA: type = %d", key_type); + private_key->set_type(CryptoWrappedKey::kRsa); + break; + } + } else { + // Possible that device certificate is from V15, in this case, the + // only supported key of at that time was RSA. + LOGD("No key type info, assuming RSA"); + private_key->set_type(CryptoWrappedKey::kRsa); + } + return true; +} + +bool FindOrInsertUsageCertificate(const std::string& drm_certificate, + const CryptoWrappedKey& wrapped_private_key, + UsageInfo* usage_info, + uint32_t* drm_certificate_id) { + RETURN_FALSE_IF_NULL(usage_info); + RETURN_FALSE_IF_NULL(drm_certificate_id); + + // Scan |drm_certificate_cache| for |drm_certificate|. If present, + // return the id + std::set ids; + for (const UsageInfo_DrmUsageCertificate& drm_device_cert : + usage_info->drm_certificate_cache()) { + if (drm_device_cert.drm_certificate().certificate() == drm_certificate) { + *drm_certificate_id = drm_device_cert.drm_certificate_id(); + return true; + } + ids.insert(drm_device_cert.drm_certificate_id()); + } + + uint32_t last_id = 0; + + // |drm_certificate| is not in the cache. Find the first non-contiguous + // id number to insert + for (uint32_t id : ids) { + if (id > last_id + 1) { + break; + } + last_id = id; + } + + if (ids.empty()) + *drm_certificate_id = 0; + else + *drm_certificate_id = last_id + 1; + + // Now insert into |drm_certificate_cache| + UsageInfo_DrmUsageCertificate* drm_usage_certificate = + usage_info->add_drm_certificate_cache(); + drm_usage_certificate->set_drm_certificate_id(*drm_certificate_id); + + return SetDeviceCertificate(drm_certificate, wrapped_private_key, + drm_usage_certificate->mutable_drm_certificate()); +} + +bool FindUsageCertificate( + uint32_t drm_certificate_id, + const google::protobuf::RepeatedPtrField& + drm_certificate_cache, + std::string* drm_certificate, CryptoWrappedKey* wrapped_private_key) { + for (const UsageInfo_DrmUsageCertificate& drm_usage_cert : + drm_certificate_cache) { + if (drm_usage_cert.drm_certificate_id() == drm_certificate_id) { + return ExtractFromDeviceCertificate(drm_usage_cert.drm_certificate(), + drm_certificate, wrapped_private_key); + } + } + + LOGE("Unable to find any certificate in usage cache for entry: %d", + drm_certificate_id); + return false; +} + +bool UsageCertificateCacheCleanUp(UsageInfo* usage_info) { + const google::protobuf::RepeatedPtrField& + provider_sessions = usage_info->sessions(); + google::protobuf::RepeatedPtrField* + drm_certificate_cache = usage_info->mutable_drm_certificate_cache(); + + // Find all the DRM certificate ids in |drm_certificate_cache| + std::set ids; + for (const UsageInfo_DrmUsageCertificate& drm_usage_cert : + *drm_certificate_cache) { + ids.insert(drm_usage_cert.drm_certificate_id()); + } + + // Next find all the DRM certificate ids in |provider_sessions| + std::set session_ids; + for (const UsageInfo_ProviderSession& session : provider_sessions) { + session_ids.insert(session.drm_certificate_id()); + } + + // Now find all the entry numbers for DRM certificates in + // |drm_device_certificates| but not in |provider_sessions|. These need to + // be removed. + std::set ids_to_erase; + std::set_difference(ids.begin(), ids.end(), session_ids.begin(), + session_ids.end(), + std::inserter(ids_to_erase, ids_to_erase.begin())); + + const auto is_deletable = + [&ids_to_erase]( + const UsageInfo_DrmUsageCertificate& usage_certificate) -> bool { + return std::find(ids_to_erase.cbegin(), ids_to_erase.cend(), + usage_certificate.drm_certificate_id()) != + ids_to_erase.cend(); + }; + + drm_certificate_cache->erase( + std::remove_if(drm_certificate_cache->begin(), + drm_certificate_cache->end(), is_deletable), + drm_certificate_cache->end()); + + return true; +} } // namespace -namespace wvcdm { +// static +const char* DeviceFiles::CertificateStateToString(CertificateState state) { + switch (state) { + case kCertificateValid: + return "Valid"; + case kCertificateExpired: + return "Expired"; + case kCertificateNotFound: + return "NotFound"; + case kCertificateInvalid: + return "Invalid"; + case kCannotHandle: + return "CannotHandle"; + } + return UnknownEnumValueToString(static_cast(state)); +} + +// static +const char* DeviceFiles::CertificateTypeToString(CertificateType type) { + switch (type) { + case kCertificateDefault: + return "Default"; + case kCertificateLegacy: + return "Legacy"; + case kCertificateAtsc: + return "ATSC"; + } + return UnknownEnumValueToString(static_cast(type)); +} + +// static +const char* DeviceFiles::ResponseTypeToString(ResponseType type) { + switch (type) { + case kNoError: + return "NoError"; + case kObjectNotInitialized: + return "ObjectNotInitialized"; + case kParameterNull: + return "ParameterNull"; + case kBasePathUnavailable: + return "PathUnavailable"; + case kFileNotFound: + return "NotFound"; + case kFileOpenFailed: + return "OpenFailed"; + case kFileWriteError: + return "WriteError"; + case kFileReadError: + return "ReadError"; + case kInvalidFileSize: + return "InvalidFileSize"; + case kHashComputationFailed: + return "HashFailed"; + case kFileHashMismatch: + return "HashMismatch"; + case kFileParseError1: + return "ParseHashedFileError"; + case kFileParseError2: + return "ParseFileError"; + case kUnknownLicenseState: + return "UnknownLicenseState"; + case kIncorrectFileType: + return "IncorrectFileType"; + case kIncorrectFileVersion: + return "IncorrectFileVersion"; + case kLicenseNotPresent: + return "LicenseNotFound"; + case kResponseTypeBase: // Not a valid value. + break; + } + return UnknownEnumValueToString(static_cast(type)); +} // static std::set DeviceFiles::reserved_license_ids_; +std::mutex DeviceFiles::reserved_license_ids_mutex_; -DeviceFiles::DeviceFiles(FileSystem* file_system) +DeviceFiles::DeviceFiles(wvutil::FileSystem* file_system) : file_system_(file_system), security_level_(kSecurityLevelUninitialized), initialized_(false) {} @@ -97,7 +377,7 @@ DeviceFiles::~DeviceFiles() {} bool DeviceFiles::Init(CdmSecurityLevel security_level) { if (!file_system_) { - LOGE("Invalid FileSystem given"); + LOGE("Invalid wvutil::FileSystem given"); return false; } @@ -112,8 +392,16 @@ bool DeviceFiles::Init(CdmSecurityLevel security_level) { } bool DeviceFiles::StoreCertificate(const std::string& certificate, - const std::string& wrapped_private_key) { + const CryptoWrappedKey& private_key) { RETURN_FALSE_IF_UNINITIALIZED(); + if (certificate.empty()) { + LOGE("Missing certificate information"); + return false; + } + if (!private_key.IsValid()) { + LOGE("Private key is invalid"); + return false; + } // Fill in file information video_widevine_client::sdk::File file; @@ -122,68 +410,388 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate, file.set_version(video_widevine_client::sdk::File::VERSION_1); DeviceCertificate* device_certificate = file.mutable_device_certificate(); - device_certificate->set_certificate(certificate); - device_certificate->set_wrapped_private_key(wrapped_private_key); + + int64_t creation_time_seconds; + int64_t expiration_time_seconds; + uint32_t system_id; + + if (!CertificateProvisioning::ExtractDeviceInfo( + certificate, nullptr, &system_id, &creation_time_seconds, + &expiration_time_seconds)) + return false; + + if (creation_time_seconds <= 0) { + LOGE("Invalid certificate creation time %" PRId64, creation_time_seconds); + return false; + } + + const bool default_certificate = expiration_time_seconds >= 0; + + if (!SetDeviceCertificate(certificate, private_key, device_certificate)) + return false; + + if (default_certificate) { + wvutil::Clock clock; + device_certificate->set_acquisition_time_seconds(clock.GetCurrentTime()); + } + /* TODO(b/192430982): Renable expiration of legacy DRM certificates + else { + // Since certificates of type kCertificateAtsc are not allowed to be + // stored, this is a certificate of type kCertificateLegacy. + // The only time when a legacy certificate is stored is when it does not + // have an expiration time. Set expiration time to 6 months +- 2 months. + wvutil::Clock clock; + const int64_t current_time = clock.GetCurrentTime(); + wvutil::CdmRandomGenerator rng(current_time & 0xffffffff); + + device_certificate->set_expiration_time_seconds( + current_time + kFourMonthsInSeconds + + rng.RandomInRange(kFourMonthsInSeconds)); + } + */ std::string serialized_file; file.SerializeToString(&serialized_file); - return StoreFileWithHash(GetCertificateFileName(false), serialized_file) == - kNoError; + std::string certificate_file_name; + const CertificateType certificate_type = + default_certificate ? kCertificateDefault : kCertificateLegacy; + if (!GetCertificateFileName(certificate_type, &certificate_file_name)) { + LOGE("Unable to get certificate file name of type: %d", certificate_type); + return false; + } + return StoreFileWithHash(certificate_file_name, serialized_file) == kNoError; } -bool DeviceFiles::RetrieveCertificate(bool atsc_mode_enabled, - std::string* certificate, - std::string* wrapped_private_key, - std::string* serial_number, - uint32_t* system_id) { - RETURN_FALSE_IF_UNINITIALIZED(); +DeviceFiles::CertificateState DeviceFiles::RetrieveCertificate( + bool atsc_mode_enabled, std::string* certificate, + CryptoWrappedKey* private_key, std::string* serial_number, + uint32_t* system_id) { + RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED(); + RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate); + RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(private_key); if (!HasCertificate(atsc_mode_enabled)) { - return false; + LOGW("Unable to find certificate, atsc mode: %s", + atsc_mode_enabled ? "enabled" : "disabled"); + return kCertificateNotFound; + } + + if (atsc_mode_enabled) + return RetrieveCertificate(kCertificateAtsc, certificate, private_key, + serial_number, system_id); + + if (HasCertificate(kCertificateDefault)) + return RetrieveCertificate(kCertificateDefault, certificate, private_key, + serial_number, system_id); + + return RetrieveCertificate(kCertificateLegacy, certificate, private_key, + serial_number, system_id); +} + +DeviceFiles::CertificateState DeviceFiles::RetrieveCertificate( + CertificateType certificate_type, std::string* certificate, + CryptoWrappedKey* wrapped_private_key, std::string* serial_number, + uint32_t* system_id) { + RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate); + RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(wrapped_private_key); + + std::string certificate_file_name; + if (!GetCertificateFileName(certificate_type, &certificate_file_name)) { + LOGW("Unable to find certificate file name for type: %d", certificate_type); + return kCannotHandle; } video_widevine_client::sdk::File file; - if (RetrieveHashedFile(GetCertificateFileName(atsc_mode_enabled), &file) != - kNoError) { + if (RetrieveHashedFile(certificate_file_name, &file) != kNoError) { LOGW("Unable to retrieve certificate file"); - return false; + return kCertificateNotFound; } if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) { LOGE("Certificate file is of incorrect file type: type = %d", static_cast(file.type())); - return false; + return kCertificateInvalid; } if (file.version() != video_widevine_client::sdk::File::VERSION_1) { LOGE("Certificate file is of incorrect file version: version = %d", static_cast(file.version())); - return false; + return kCertificateInvalid; } if (!file.has_device_certificate()) { LOGE("Certificate not present"); - return false; + return kCertificateInvalid; } - DeviceCertificate device_certificate = file.device_certificate(); - *certificate = device_certificate.certificate(); - *wrapped_private_key = device_certificate.wrapped_private_key(); - return CertificateProvisioning::ExtractDeviceInfo( - device_certificate.certificate(), serial_number, system_id); + const DeviceCertificate& device_certificate = file.device_certificate(); + + if (!ExtractFromDeviceCertificate(device_certificate, certificate, + wrapped_private_key)) { + LOGE("Unable to extract from device certificate"); + return kCertificateInvalid; + } + + int64_t creation_time_seconds; + int64_t expiration_time_seconds; + + if (!CertificateProvisioning::ExtractDeviceInfo( + device_certificate.certificate(), serial_number, system_id, + &creation_time_seconds, &expiration_time_seconds)) + return kCertificateInvalid; + + wvutil::Clock clock; + const int64_t current_time = clock.GetCurrentTime(); + + switch (certificate_type) { + case kCertificateDefault: { + // Validation check for DRM certificate that includes an expiration + // time set by the provisioning service. Since provisioning and + // client clocks may not be in sync, verify by comparing time + // elapsed since the certificate was acquired with the expiration + // period specified by the service. + // First verify that all the fields are set to valid values. + // The service will validate certificate expiration so tampering of + // time values at the client is not a concern. + if (creation_time_seconds <= 0) { + LOGE("Invalid creation time of default certificate: %" PRId64, + creation_time_seconds); + return kCertificateInvalid; + } + if (expiration_time_seconds < 0) { + LOGE("Invalid expiration time of default certificate: %" PRId64, + expiration_time_seconds); + return kCertificateInvalid; + } + if (expiration_time_seconds == UNLIMITED_DURATION) + return kCertificateValid; + + if (!device_certificate.has_acquisition_time_seconds()) { + LOGE("Acquisition time of default certificate not available"); + return kCertificateInvalid; + } + const int64_t acquisition_time_seconds = + device_certificate.acquisition_time_seconds(); + if (acquisition_time_seconds <= 0) { + LOGE("Invalid acquisition time of default certificate: %" PRId64, + acquisition_time_seconds); + return kCertificateInvalid; + } + + if (current_time < acquisition_time_seconds) { + LOGE("Time not valid: current time: %" PRId64 + ", acquisition time: %" PRId64, + current_time, acquisition_time_seconds); + return kCannotHandle; + } + + if (expiration_time_seconds < creation_time_seconds) { + LOGE("Time not valid: expiration time: %" PRId64 + ", creation time: %" PRId64, + expiration_time_seconds, creation_time_seconds); + return kCertificateInvalid; + } + + // |current_time| and |acquisition_time_seconds| are client clock + // times while |expiration_time_seconds| and |creation_time_seconds| + // are times specified by the provisioning service + if (current_time - acquisition_time_seconds > + expiration_time_seconds - creation_time_seconds) { + return kCertificateExpired; + } + return kCertificateValid; + } + + case kCertificateLegacy: { + /* TODO(b/192430982): Renable expiration of legacy DRM certificates + // Validation check for DRM certificate without an expiration + // time set by the provisioning service. Add an expiry time + // within the next 6 months +/- 2 months, if one has not been set. + if (!device_certificate.has_expiration_time_seconds()) { + StoreCertificate(*certificate, *wrapped_private_key); + return kCertificateValid; + } + const int64_t expiration_time_seconds = + device_certificate.expiration_time_seconds(); + if (expiration_time_seconds <= 0) { + LOGE("Invalid expiration time of legacy certificate: %" PRId64, + expiration_time_seconds); + return kCertificateInvalid; + } + + if (current_time > expiration_time_seconds) return kCertificateExpired; + */ + + return kCertificateValid; + } + + case kCertificateAtsc: + // No expiration enforced + return kCertificateValid; + + default: + // Should never happen. This should be detected earlier when fetching + // the file name + LOGE("Invalid certificate type: %d", certificate_type); + return kCertificateInvalid; + } +} + +bool DeviceFiles::RetrieveLegacyCertificate(std::string* certificate, + CryptoWrappedKey* private_key, + std::string* serial_number, + uint32_t* system_id) { + RETURN_FALSE_IF_UNINITIALIZED(); + RETURN_FALSE_IF_NULL(certificate); + RETURN_FALSE_IF_NULL(private_key); + if (!HasCertificate(kCertificateLegacy)) return false; + + const CertificateState state = RetrieveCertificate( + kCertificateLegacy, certificate, private_key, serial_number, system_id); + if (state == kCertificateValid || state == kCertificateExpired) return true; + + return false; } bool DeviceFiles::HasCertificate(bool atsc_mode_enabled) { RETURN_FALSE_IF_UNINITIALIZED(); - return FileExists(GetCertificateFileName(atsc_mode_enabled)); + if (atsc_mode_enabled) return HasCertificate(kCertificateAtsc); + + return HasCertificate(kCertificateDefault) || + HasCertificate(kCertificateLegacy); } bool DeviceFiles::RemoveCertificate() { RETURN_FALSE_IF_UNINITIALIZED() - return RemoveFile(GetCertificateFileName(false)); + std::string certificate_file_name; + if (GetCertificateFileName(kCertificateLegacy, &certificate_file_name)) + RemoveFile(certificate_file_name); + if (GetCertificateFileName(kCertificateDefault, &certificate_file_name)) + return RemoveFile(certificate_file_name); + return true; +} + +bool DeviceFiles::RemoveOemCertificate() { + RETURN_FALSE_IF_UNINITIALIZED() + std::string certificate_file_name; + if (GetOemCertificateFileName(&certificate_file_name)) { + return RemoveFile(certificate_file_name); + } + return true; +} + +bool DeviceFiles::StoreOemCertificate(const std::string& certificate, + const CryptoWrappedKey& private_key) { + RETURN_FALSE_IF_UNINITIALIZED(); + if (certificate.empty()) { + LOGE("Missing certificate information"); + return false; + } + if (!private_key.IsValid()) { + LOGE("Private key is invalid"); + return false; + } + + std::string certificate_file_name; + if (!GetOemCertificateFileName(&certificate_file_name)) { + LOGE("Unable to get certificate file name"); + return false; + } + + // Fill in file information + video_widevine_client::sdk::File file; + file.set_type(video_widevine_client::sdk::File::OEM_CERTIFICATE); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + OemCertificate* oem_certificate = file.mutable_oem_certificate(); + oem_certificate->set_certificate(certificate); + oem_certificate->set_wrapped_private_key(private_key.key()); + switch (private_key.type()) { + case wvcdm::CryptoWrappedKey::kRsa: + oem_certificate->set_key_type(OemCertificate::RSA); + break; + case wvcdm::CryptoWrappedKey::kEcc: + oem_certificate->set_key_type(OemCertificate::ECC); + break; + case wvcdm::CryptoWrappedKey::kUninitialized: + default: + LOGE("Unexpected key type: %d", private_key.type()); + return false; + } + + std::string serialized_file; + file.SerializeToString(&serialized_file); + return StoreFileWithHash(certificate_file_name, serialized_file) == kNoError; +} + +DeviceFiles::CertificateState DeviceFiles::RetrieveOemCertificate( + std::string* certificate, CryptoWrappedKey* wrapped_private_key) { + RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_UNINITIALIZED(); + RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(certificate); + RETURN_CERTIFICATE_STATE_CANNOT_HANDLE_IF_NULL(wrapped_private_key); + + std::string certificate_file_name; + if (!GetOemCertificateFileName(&certificate_file_name)) { + LOGW("Unable to find certificate file name"); + return kCannotHandle; + } + + video_widevine_client::sdk::File file; + if (RetrieveHashedFile(certificate_file_name, &file) != kNoError) { + LOGW("Unable to retrieve certificate file"); + return kCertificateNotFound; + } + if (file.type() != video_widevine_client::sdk::File::OEM_CERTIFICATE) { + LOGE("Certificate file is of incorrect file type: type = %d", + static_cast(file.type())); + return kCertificateInvalid; + } + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGE("Certificate file is of incorrect file version: version = %d", + static_cast(file.version())); + return kCertificateInvalid; + } + if (!file.has_oem_certificate()) { + LOGE("Certificate not present"); + return kCertificateInvalid; + } + + const OemCertificate& oem_certificate = file.oem_certificate(); + if (oem_certificate.certificate().empty() || + oem_certificate.wrapped_private_key().empty()) { + LOGE("Empty certificate or private key"); + return kCertificateInvalid; + } + + *certificate = oem_certificate.certificate(); + wrapped_private_key->Clear(); + wrapped_private_key->set_key(oem_certificate.wrapped_private_key()); + switch (oem_certificate.key_type()) { + case OemCertificate::RSA: + wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kRsa); + break; + case OemCertificate::ECC: + wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kEcc); + break; + default: + LOGW("Unknown key type, defaulting to RSA: type = %d", + oem_certificate.key_type()); + wrapped_private_key->set_type(wvcdm::CryptoWrappedKey::kRsa); + break; + } + return kCertificateValid; +} + +bool DeviceFiles::HasOemCertificate() { + RETURN_FALSE_IF_UNINITIALIZED(); + + std::string certificate_file_name; + if (!GetOemCertificateFileName(&certificate_file_name)) { + return false; + } + return FileExists(certificate_file_name); } bool DeviceFiles::StoreLicense(const CdmLicenseData& license_data, @@ -231,10 +839,18 @@ bool DeviceFiles::StoreLicense(const CdmLicenseData& license_data, } license->set_usage_entry(license_data.usage_entry); license->set_usage_entry_number(license_data.usage_entry_number); + if (!license_data.drm_certificate.empty()) { + DeviceCertificate* device_certificate = license->mutable_drm_certificate(); + if (!SetDeviceCertificate(license_data.drm_certificate, + license_data.wrapped_private_key, + device_certificate)) + return false; + } std::string serialized_file; file.SerializeToString(&serialized_file); + UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.erase(license_data.key_set_id); *result = StoreFileWithHash(license_data.key_set_id + kLicenseFileNameExt, serialized_file); @@ -256,8 +872,8 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, video_widevine_client::sdk::File file; *result = RetrieveHashedFile(key_set_id + kLicenseFileNameExt, &file); if (*result != kNoError) { - LOGE("Unable to retrieve key set license file: result = %d", - static_cast(*result)); + LOGE("Unable to retrieve key set license file: result = %s", + ResponseTypeToString(*result)); return false; } @@ -313,8 +929,18 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, license.app_parameters(i).value(); } license_data->usage_entry = license.usage_entry(); - license_data->usage_entry_number = license.usage_entry_number(); - return true; + license_data->usage_entry_number = + static_cast(license.usage_entry_number()); + + if (!license.has_drm_certificate()) { + license_data->drm_certificate.clear(); + license_data->wrapped_private_key.Clear(); + return true; + } + + return ExtractFromDeviceCertificate(license.drm_certificate(), + &license_data->drm_certificate, + &license_data->wrapped_private_key); } bool DeviceFiles::DeleteLicense(const std::string& key_set_id) { @@ -362,29 +988,31 @@ bool DeviceFiles::DeleteAllFiles() { bool DeviceFiles::LicenseExists(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); + UniqueLock lock(reserved_license_ids_mutex_); return reserved_license_ids_.count(key_set_id) || FileExists(key_set_id + kLicenseFileNameExt); } bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); + UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.insert(key_set_id); return true; } bool DeviceFiles::UnreserveLicenseId(const std::string& key_set_id) { RETURN_FALSE_IF_UNINITIALIZED(); + UniqueLock lock(reserved_license_ids_mutex_); reserved_license_ids_.erase(key_set_id); return true; } -bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, - const CdmKeyMessage& key_request, - const CdmKeyResponse& key_response, - const std::string& usage_info_file_name, - const std::string& key_set_id, - const std::string& usage_entry, - uint32_t usage_entry_number) { +bool DeviceFiles::StoreUsageInfo( + const std::string& provider_session_token, const CdmKeyMessage& key_request, + const CdmKeyResponse& key_response, const std::string& usage_info_file_name, + const std::string& key_set_id, const std::string& usage_entry, + uint32_t usage_entry_number, const std::string& drm_certificate, + const CryptoWrappedKey& wrapped_private_key) { RETURN_FALSE_IF_UNINITIALIZED(); video_widevine_client::sdk::File file; if (!FileExists(usage_info_file_name)) { @@ -408,6 +1036,16 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token, provider_session->set_usage_entry(usage_entry); provider_session->set_usage_entry_number(usage_entry_number); + if (drm_certificate.size() > 0) { + uint32_t drm_certificate_id; + if (!FindOrInsertUsageCertificate(drm_certificate, wrapped_private_key, + usage_info, &drm_certificate_id)) { + LOGE("Unable to insert a certificate in the usage certificate cache"); + return false; + } + provider_session->set_drm_certificate_id(drm_certificate_id); + } + std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError; @@ -442,8 +1080,8 @@ bool DeviceFiles::ListUsageIds( if (ksids != nullptr) ksids->clear(); if (provider_session_tokens != nullptr) provider_session_tokens->clear(); - size_t num_records = file.usage_info().sessions_size(); - for (size_t i = 0; i < num_records; ++i) { + const int num_records = file.usage_info().sessions_size(); + for (int i = 0; i < num_records; ++i) { if ((ksids != nullptr) && !file.usage_info().sessions(i).key_set_id().empty()) { ksids->push_back(file.usage_info().sessions(i).key_set_id()); @@ -474,8 +1112,8 @@ bool DeviceFiles::GetProviderSessionToken(const std::string& app_id, return false; } - size_t num_records = file.usage_info().sessions_size(); - for (size_t i = 0; i < num_records; ++i) { + const int num_records = static_cast(file.usage_info().sessions_size()); + for (int i = 0; i < num_records; ++i) { if (file.usage_info().sessions(i).key_set_id() == key_set_id) { *provider_session_token = file.usage_info().sessions(i).token(); return true; @@ -505,7 +1143,7 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& usage_info_file_name, if (!found) { LOGE("Unable to find provider session token: pst = %s", - b2a_hex(provider_session_token).c_str()); + wvutil::b2a_hex(provider_session_token).c_str()); return false; } @@ -515,6 +1153,7 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& usage_info_file_name, sessions->SwapElements(index, usage_info->sessions_size() - 1); } sessions->RemoveLast(); + UsageCertificateCacheCleanUp(usage_info); std::string serialized_file; file.SerializeToString(&serialized_file); @@ -577,6 +1216,7 @@ bool DeviceFiles::DeleteMultipleUsageInfoByKeySetIds( } if (sessions->size() > 0) { + UsageCertificateCacheCleanUp(file.mutable_usage_info()); std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(usage_info_file_name, serialized_file) == kNoError; @@ -591,41 +1231,21 @@ bool DeviceFiles::DeleteAllUsageInfo() { kUsageInfoFileNameExt); } -bool DeviceFiles::RetrieveUsageInfo( - const std::string& usage_info_file_name, - std::vector >* usage_info) { - RETURN_FALSE_IF_UNINITIALIZED(); - RETURN_FALSE_IF_NULL(usage_info); - - if (!FileExists(usage_info_file_name) || - GetFileSize(usage_info_file_name) == 0) { - usage_info->resize(0); - return true; - } - - video_widevine_client::sdk::File file; - if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { - LOGE("Unable to retrieve usage info file"); - return false; - } - - usage_info->resize(file.usage_info().sessions_size()); - for (int i = 0; i < file.usage_info().sessions_size(); ++i) { - (*usage_info)[i] = - std::make_pair(file.usage_info().sessions(i).license_request(), - file.usage_info().sessions(i).license()); - } - - return true; -} - bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name, const std::string& provider_session_token, CdmKeyMessage* license_request, CdmKeyResponse* license, std::string* usage_entry, - uint32_t* usage_entry_number) { + uint32_t* usage_entry_number, + std::string* drm_certificate, + CryptoWrappedKey* wrapped_private_key) { RETURN_FALSE_IF_UNINITIALIZED(); + RETURN_FALSE_IF_NULL(license_request); + RETURN_FALSE_IF_NULL(license); + RETURN_FALSE_IF_NULL(usage_entry); + RETURN_FALSE_IF_NULL(usage_entry_number); + RETURN_FALSE_IF_NULL(drm_certificate); + RETURN_FALSE_IF_NULL(wrapped_private_key); video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { @@ -639,8 +1259,22 @@ bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name, *license_request = file.usage_info().sessions(index).license_request(); *license = file.usage_info().sessions(index).license(); *usage_entry = file.usage_info().sessions(index).usage_entry(); - *usage_entry_number = - file.usage_info().sessions(index).usage_entry_number(); + *usage_entry_number = static_cast( + file.usage_info().sessions(index).usage_entry_number()); + + if (!file.usage_info().sessions(index).has_drm_certificate_id()) { + drm_certificate->clear(); + wrapped_private_key->Clear(); + return true; + } + + if (!FindUsageCertificate( + file.usage_info().sessions(index).drm_certificate_id(), + file.usage_info().drm_certificate_cache(), drm_certificate, + wrapped_private_key)) { + LOGE("Unable to find DRM certificate information from usage cache"); + return false; + } return true; } } @@ -652,8 +1286,15 @@ bool DeviceFiles::RetrieveUsageInfoByKeySetId( const std::string& usage_info_file_name, const std::string& key_set_id, std::string* provider_session_token, CdmKeyMessage* license_request, CdmKeyResponse* license, std::string* usage_entry, - uint32_t* usage_entry_number) { + uint32_t* usage_entry_number, std::string* drm_certificate, + CryptoWrappedKey* wrapped_private_key) { RETURN_FALSE_IF_UNINITIALIZED(); + RETURN_FALSE_IF_NULL(license_request); + RETURN_FALSE_IF_NULL(license); + RETURN_FALSE_IF_NULL(usage_entry); + RETURN_FALSE_IF_NULL(usage_entry_number); + RETURN_FALSE_IF_NULL(drm_certificate); + RETURN_FALSE_IF_NULL(wrapped_private_key); video_widevine_client::sdk::File file; if (RetrieveHashedFile(usage_info_file_name, &file) != kNoError) { @@ -668,8 +1309,22 @@ bool DeviceFiles::RetrieveUsageInfoByKeySetId( *license_request = file.usage_info().sessions(index).license_request(); *license = file.usage_info().sessions(index).license(); *usage_entry = file.usage_info().sessions(index).usage_entry(); - *usage_entry_number = - file.usage_info().sessions(index).usage_entry_number(); + *usage_entry_number = static_cast( + file.usage_info().sessions(index).usage_entry_number()); + + if (!file.usage_info().sessions(index).has_drm_certificate_id()) { + drm_certificate->clear(); + wrapped_private_key->Clear(); + return true; + } + + if (!FindUsageCertificate( + file.usage_info().sessions(index).drm_certificate_id(), + file.usage_info().drm_certificate_cache(), drm_certificate, + wrapped_private_key)) { + LOGE("Unable to find DRM certificate information from usage cache"); + return false; + } return true; } } @@ -686,6 +1341,7 @@ bool DeviceFiles::StoreUsageInfo(const std::string& usage_info_file_name, file.set_version(video_widevine_client::sdk::File::VERSION_1); UsageInfo* usage_info = file.mutable_usage_info(); + for (size_t i = 0; i < usage_data.size(); ++i) { UsageInfo_ProviderSession* provider_session = usage_info->add_sessions(); @@ -699,6 +1355,17 @@ bool DeviceFiles::StoreUsageInfo(const std::string& usage_info_file_name, usage_data[i].key_set_id.size()); provider_session->set_usage_entry(usage_data[i].usage_entry); provider_session->set_usage_entry_number(usage_data[i].usage_entry_number); + + if (usage_data[i].drm_certificate.size() > 0) { + uint32_t drm_certificate_id; + if (!FindOrInsertUsageCertificate(usage_data[i].drm_certificate, + usage_data[i].wrapped_private_key, + usage_info, &drm_certificate_id)) { + LOGE("Unable to insert a certificate in the usage certificate cache"); + return false; + } + provider_session->set_drm_certificate_id(drm_certificate_id); + } } std::string serialized_file; @@ -734,6 +1401,17 @@ bool DeviceFiles::UpdateUsageInfo(const std::string& usage_info_file_name, provider_session->set_usage_entry(usage_data.usage_entry); provider_session->set_usage_entry_number(usage_data.usage_entry_number); + if (usage_data.drm_certificate.size() > 0) { + uint32_t drm_certificate_id; + if (!FindOrInsertUsageCertificate(usage_data.drm_certificate, + usage_data.wrapped_private_key, + usage_info, &drm_certificate_id)) { + LOGE("Unable to find a certificate in to update the usage info"); + return false; + } + provider_session->set_drm_certificate_id(drm_certificate_id); + } + std::string serialized_file; file.SerializeToString(&serialized_file); return StoreFileWithHash(usage_info_file_name, serialized_file) == @@ -770,8 +1448,21 @@ bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name, (*usage_data)[i].license = file.usage_info().sessions(i).license(); (*usage_data)[i].key_set_id = file.usage_info().sessions(i).key_set_id(); (*usage_data)[i].usage_entry = file.usage_info().sessions(i).usage_entry(); - (*usage_data)[i].usage_entry_number = - file.usage_info().sessions(i).usage_entry_number(); + (*usage_data)[i].usage_entry_number = static_cast( + file.usage_info().sessions(i).usage_entry_number()); + + if (!file.usage_info().sessions(i).has_drm_certificate_id()) { + (*usage_data)[i].drm_certificate.clear(); + (*usage_data)[i].wrapped_private_key.Clear(); + } else { + if (!FindUsageCertificate( + file.usage_info().sessions(i).drm_certificate_id(), + file.usage_info().drm_certificate_cache(), + &(*usage_data)[i].drm_certificate, + &(*usage_data)[i].wrapped_private_key)) { + LOGW("Unable to find DRM certificate information from usage cache"); + } + } } return true; @@ -799,8 +1490,22 @@ bool DeviceFiles::RetrieveUsageInfo(const std::string& usage_info_file_name, usage_data->license = file.usage_info().sessions(index).license(); usage_data->key_set_id = file.usage_info().sessions(index).key_set_id(); usage_data->usage_entry = file.usage_info().sessions(index).usage_entry(); - usage_data->usage_entry_number = - file.usage_info().sessions(index).usage_entry_number(); + usage_data->usage_entry_number = static_cast( + file.usage_info().sessions(index).usage_entry_number()); + + if (!file.usage_info().sessions(index).has_drm_certificate_id()) { + usage_data->drm_certificate.clear(); + usage_data->wrapped_private_key.Clear(); + return true; + } + + if (!FindUsageCertificate( + file.usage_info().sessions(index).drm_certificate_id(), + file.usage_info().drm_certificate_cache(), + &usage_data->drm_certificate, &usage_data->wrapped_private_key)) { + LOGE("Unable to find DRM certificate information from usage cache"); + return false; + } return true; } } @@ -981,7 +1686,7 @@ bool DeviceFiles::RetrieveUsageTableInfo( video_widevine_client::sdk::File file; if (RetrieveHashedFile(GetUsageTableFileName(), &file) != kNoError) { - LOGE("Unable to retrieve usage table file"); + LOGW("Unable to retrieve usage table file"); return false; } @@ -1042,6 +1747,174 @@ bool DeviceFiles::DeleteUsageTableInfo() { return RemoveFile(GetUsageTableFileName()); } +bool DeviceFiles::HasCertificate(CertificateType certificate_type) { + RETURN_FALSE_IF_UNINITIALIZED(); + + std::string certificate_file_name; + if (!GetCertificateFileName(certificate_type, &certificate_file_name)) + return false; + + return FileExists(certificate_file_name); +} + +bool DeviceFiles::StoreOkpInfo(const okp::SystemFallbackInfo& info) { + using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo; + using okp::SystemState; + RETURN_FALSE_IF_UNINITIALIZED(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + video_widevine_client::sdk::File file; + file.set_type(video_widevine_client::sdk::File::OKP_INFO); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + StoredOkpInfo* stored_info = file.mutable_okp_info(); + switch (info.state()) { + case SystemState::kNeedsProvisioning: + stored_info->set_state(StoredOkpInfo::OKP_NEEDS_PROVISIONING); + break; + case SystemState::kFallbackMode: + stored_info->set_state(StoredOkpInfo::OKP_FALLBACK_MODE); + break; + case SystemState::kProvisioned: + stored_info->set_state(StoredOkpInfo::OKP_PROVISIONED); + break; + case SystemState::kUnknown: + default: + LOGE("Unexpected OKP state: state = %d", static_cast(info.state())); + return false; + } + if (info.first_checked_time() <= 0) { + LOGE("OKP first checked time is missing"); + return false; + } + stored_info->set_first_checked_time(info.first_checked_time()); + + if (info.state() == SystemState::kProvisioned) { + if (!info.HasProvisioningTime()) { + LOGE("OKP set as provisioned, but missing provisioning time"); + return false; + } + stored_info->set_provisioning_time(info.provisioning_time()); + } else if (info.state() == SystemState::kFallbackMode) { + if (!info.HasBackoffStartTime() || !info.HasBackoffDuration()) { + LOGE("OKP fallback information is missing "); + return false; + } + stored_info->set_backoff_start_time(info.backoff_start_time()); + stored_info->set_backoff_duration(info.backoff_duration()); + } else { + if (info.HasBackoffDuration()) { + // Store backoff duration from before. + stored_info->set_backoff_duration(info.backoff_duration()); + } + } + + std::string serialized_file; + file.SerializeToString(&serialized_file); + return StoreFileWithHash(GetOkpInfoFileName(), serialized_file) == kNoError; +} + +bool DeviceFiles::RetrieveOkpInfo(okp::SystemFallbackInfo* info) { + using StoredOkpInfo = video_widevine_client::sdk::OtaKeyboxProvisioningInfo; + using okp::SystemState; + RETURN_FALSE_IF_UNINITIALIZED(); + RETURN_FALSE_IF_NULL(info); + info->Clear(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + // File meta-data validation. + video_widevine_client::sdk::File file; + if (RetrieveHashedFile(GetOkpInfoFileName(), &file) != kNoError) { + LOGW("Unable to retrieve OKP info file"); + return false; + } + if (file.type() != video_widevine_client::sdk::File::OKP_INFO) { + LOGE("Incorrect file type: type = %d, expected_type = %d", + static_cast(file.type()), + static_cast(video_widevine_client::sdk::File::OKP_INFO)); + return false; + } + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGE("Incorrect file version: version = %d, expected_version = %d", + static_cast(file.version()), + static_cast(video_widevine_client::sdk::File::VERSION_1)); + return false; + } + if (!file.has_okp_info()) { + // OKP info is only stored if at least 1 field is non-empty. This + // must be an error. + LOGD("OKP info is not present in file"); + return false; + } + + const StoredOkpInfo& stored_info = file.okp_info(); + switch (stored_info.state()) { + case StoredOkpInfo::OKP_NEEDS_PROVISIONING: + info->SetState(SystemState::kNeedsProvisioning); + break; + case StoredOkpInfo::OKP_FALLBACK_MODE: + info->SetState(SystemState::kFallbackMode); + break; + case StoredOkpInfo::OKP_PROVISIONED: + info->SetState(SystemState::kProvisioned); + break; + case StoredOkpInfo::OKP_UNKNOWN: + default: + LOGE("Unexpected OKP state: stored_state = %d", + static_cast(stored_info.state())); + return false; + } + + if (stored_info.first_checked_time() <= 0) { + LOGE("OKP first check time not present"); + info->Clear(); + return false; + } + info->SetFirstCheckedTime(stored_info.first_checked_time()); + + if (info->state() == SystemState::kProvisioned) { + if (stored_info.provisioning_time() <= 0) { + LOGE("OKP set as provisioned, but missing provisioning time"); + info->Clear(); + return false; + } + info->SetProvisioningTime(stored_info.provisioning_time()); + return true; + } + + if (info->state() == SystemState::kFallbackMode) { + if (stored_info.backoff_start_time() <= 0 || + stored_info.backoff_duration() <= 0) { + LOGE("OKP backoff information is missing"); + info->Clear(); + return false; + } + info->SetBackoffStartTime(stored_info.backoff_start_time()); + info->SetBackoffDuration(stored_info.backoff_duration()); + return true; + } + // Provisioned. + if (stored_info.backoff_duration() > 0) { + info->SetBackoffDuration(stored_info.backoff_duration()); + } + return true; +} + +bool DeviceFiles::DeleteOkpInfo() { + RETURN_FALSE_IF_UNINITIALIZED(); + if (security_level_ != kSecurityLevelL1) { + LOGE("OKP info is only supported by L1: level = %d", + static_cast(security_level_)); + return false; + } + return RemoveFile(GetOkpInfoFileName()); +} + DeviceFiles::ResponseType DeviceFiles::StoreFileWithHash( const std::string& name, const std::string& serialized_file) { std::string hash = Sha256Hash(serialized_file); @@ -1067,8 +1940,8 @@ DeviceFiles::ResponseType DeviceFiles::StoreFileRaw( path += name; - auto file = - file_system_->Open(path, FileSystem::kCreate | FileSystem::kTruncate); + auto file = file_system_->Open( + path, wvutil::FileSystem::kCreate | wvutil::FileSystem::kTruncate); if (!file) { LOGE("Failed to open file: path = %s", path.c_str()); return kFileOpenFailed; @@ -1126,7 +1999,7 @@ DeviceFiles::ResponseType DeviceFiles::RetrieveHashedFile( return kInvalidFileSize; } - auto file = file_system_->Open(path, FileSystem::kReadOnly); + auto file = file_system_->Open(path, wvutil::FileSystem::kReadOnly); if (!file) { return kFileOpenFailed; } @@ -1224,8 +2097,29 @@ ssize_t DeviceFiles::GetFileSize(const std::string& name) { return file_system_->FileSize(path); } -std::string DeviceFiles::GetCertificateFileName(bool atsc_mode_enabled) { - return atsc_mode_enabled ? kAtscCertificateFileName : kCertificateFileName; +bool DeviceFiles::GetCertificateFileName(CertificateType certificate_type, + std::string* certificate_file_name) { + RETURN_FALSE_IF_NULL(certificate_file_name); + switch (certificate_type) { + case kCertificateDefault: + *certificate_file_name = wvutil::kCertificateFileName; + return true; + case kCertificateLegacy: + *certificate_file_name = wvutil::kLegacyCertificateFileName; + return true; + case kCertificateAtsc: + *certificate_file_name = wvutil::kAtscCertificateFileName; + return true; + default: + return false; + } +} + +bool DeviceFiles::GetOemCertificateFileName( + std::string* certificate_file_name) { + RETURN_FALSE_IF_NULL(certificate_file_name); + *certificate_file_name = wvutil::kOemCertificateFileName; + return true; } std::string DeviceFiles::GetUsageTableFileName() { return kUsageTableFileName; } @@ -1246,10 +2140,10 @@ std::string DeviceFiles::GetUsageInfoFileName(const std::string& app_id) { return kUsageInfoFileNamePrefix + hash + kUsageInfoFileNameExt; } +std::string DeviceFiles::GetOkpInfoFileName() { return kOkpInfoFileName; } + std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) { - std::string hash = Md5Hash(input); - return wvcdm::Base64SafeEncode( - std::vector(hash.begin(), hash.end())); + return wvutil::Base64SafeEncode(Md5Hash(input)); } } // namespace wvcdm diff --git a/core/src/device_files.proto b/core/src/device_files.proto index 8190ba3d..b2e13f65 100644 --- a/core/src/device_files.proto +++ b/core/src/device_files.proto @@ -2,8 +2,8 @@ // device_files.proto // ---------------------------------------------------------------------------- // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // Description: // Format of various files stored at the device. @@ -20,9 +20,34 @@ message NameValue { optional string value = 2; } -message DeviceCertificate { +message OemCertificate { + enum PrivateKeyType { + RSA = 0; + ECC = 1; + } optional bytes certificate = 1; optional bytes wrapped_private_key = 2; + optional PrivateKeyType key_type = 3 [default = RSA]; +} + +// DRM certificate. +message DeviceCertificate { + enum PrivateKeyType { + RSA = 0; + ECC = 1; + } + optional bytes certificate = 1; + optional bytes wrapped_private_key = 2; + optional PrivateKeyType key_type = 3 [default = RSA]; + // Used by DRM certificates with an expiry time. Set by the client when + // the certificate is received. Aids expiration calculation at the + // client when provisioning server and client clocks are not aligned + optional int64 acquisition_time_seconds = 4; + // Used by DRM certificates without an expiration time. This is for + // upgrading devices with pre-existing DRM certificates. The client will + // calculate an expiration time 6 months into the future with a randomized + // +/-2 month window + optional int64 expiration_time_seconds = 5; } message License { @@ -51,6 +76,7 @@ message License { optional int64 grace_period_end_time = 11 [default = 0]; optional bytes usage_entry = 12; optional int64 usage_entry_number = 13; + optional DeviceCertificate drm_certificate = 14; } message UsageInfo { @@ -63,9 +89,19 @@ message UsageInfo { optional bytes key_set_id = 4; optional bytes usage_entry = 5; optional int64 usage_entry_number = 6; + // If not present, use the legacy DRM certificate rather than + // one in DrmDeviceCertificate + optional uint32 drm_certificate_id = 7; + } + + // A cache of DeviceCertificates associated with usage entries + message DrmUsageCertificate { + optional uint32 drm_certificate_id = 1; + optional DeviceCertificate drm_certificate = 2; } repeated ProviderSession sessions = 1; + repeated DrmUsageCertificate drm_certificate_cache = 2; } message HlsAttributes { @@ -87,7 +123,7 @@ message UsageTableInfo { optional UsageEntryStorage storage = 1; optional bytes key_set_id = 2; - optional bytes usage_info_file_name = 3; // hash of the app_id + optional bytes usage_info_file_name = 3; // hash of the app_id // LRU table replacement data. optional int64 last_use_time = 4 [default = 0]; @@ -100,6 +136,46 @@ message UsageTableInfo { optional bool use_lru = 3 [default = false]; } +// Stores information related to a device's experience with OTA Keybox +// Provisioning (OKP). Only devices which both support OKP and require +// OKP should create this file. Otherwise, this information is not +// needed. +message OtaKeyboxProvisioningInfo { + enum OkpDeviceState { + // Not yet checked for provisioning state. This should be a + // transitory state only. Device which do not need OTA Keybox + // Provisioning should simply not store this file. + OKP_UNKNOWN = 0; + // OEMCrypto has reported that keybox provisioning is required and + // that the device supports OKP. Device may or may not be in the + // process of performing provisioning. + OKP_NEEDS_PROVISIONING = 1; + // Device still needs provisioning, but has reached a condition + // where it should backoff from attempting OKP for a period of + // time. + OKP_FALLBACK_MODE = 2; + // The device has successfully provisioned its keybox. + OKP_PROVISIONED = 3; + } + // Device-wide OKP state. + optional OkpDeviceState state = 1; + // Time when the CDM service first discovers that it needs to + // provision the L1 keybox. + optional int64 first_checked_time = 2; + // Beginning of a backoff period. + // Zero indicates that engine is not in a backoff state. + optional int64 backoff_start_time = 3; + // Intended length of “backoff period”. This will be assigned a + // random duration initially, then double each time an engine enters + // a backoff state. This is based on Google's recommended exponential + // backoff rules. + // Value of 0 indicates that backoff has not yet occurred. + optional int64 backoff_duration = 4; + // System time of when a successful provisioning request has been + // received. Only relevant if |state| is OKP_PROVISIONED. + optional int64 provisioning_time = 5; +} + message File { enum FileType { DEVICE_CERTIFICATE = 1; @@ -107,11 +183,11 @@ message File { USAGE_INFO = 3; HLS_ATTRIBUTES = 4; USAGE_TABLE_INFO = 5; + OKP_INFO = 6; + OEM_CERTIFICATE = 7; } - enum FileVersion { - VERSION_1 = 1; - } + enum FileVersion { VERSION_1 = 1; } optional FileType type = 1; optional FileVersion version = 2 [default = VERSION_1]; @@ -120,6 +196,8 @@ message File { optional UsageInfo usage_info = 5; optional HlsAttributes hls_attributes = 6; optional UsageTableInfo usage_table_info = 7; + optional OtaKeyboxProvisioningInfo okp_info = 8; + optional OemCertificate oem_certificate = 9; } message HashedFile { diff --git a/core/src/entitlement_key_session.cpp b/core/src/entitlement_key_session.cpp index baf99d72..06ffd1cb 100644 --- a/core/src/entitlement_key_session.cpp +++ b/core/src/entitlement_key_session.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "entitlement_key_session.h" @@ -9,9 +9,25 @@ #include "log.h" namespace wvcdm { +namespace { +constexpr int kInvalidKeySessionId = 0; +} // namespace + EntitlementKeySession::EntitlementKeySession(CryptoSessionId oec_session_id, metrics::CryptoMetrics* metrics) - : ContentKeySession(oec_session_id, metrics), entitled_keys_() {} + : ContentKeySession(oec_session_id, metrics), + key_session_id_(kInvalidKeySessionId) {} + +EntitlementKeySession::~EntitlementKeySession() { + if (key_session_id_ != kInvalidKeySessionId) { + OEMCryptoResult result = + OEMCrypto_RemoveEntitledKeySession(key_session_id_); + if (result != OEMCrypto_SUCCESS) { + LOGW("OEMCrypto_RemoveEntitledKeySession failed: status = %d", + static_cast(result)); + } + } +} OEMCryptoResult EntitlementKeySession::LoadKeys( const std::string& message, const std::string& signature, @@ -49,6 +65,23 @@ OEMCryptoResult EntitlementKeySession::SelectKey(const std::string& key_id, return OEMCrypto_ERROR_NO_CONTENT_KEY; } + OEMCryptoResult result; + // Entitled key session not created. Create one now. + if (key_session_id_ == kInvalidKeySessionId) { + result = + OEMCrypto_CreateEntitledKeySession(oec_session_id_, &key_session_id_); + if (result == OEMCrypto_ERROR_NOT_IMPLEMENTED) { + LOGW("Entitled key session is not supported."); + // Use oec session id as the key session id for backward compatibility. + key_session_id_ = oec_session_id_; + } else if (result != OEMCrypto_SUCCESS || + key_session_id_ == kInvalidKeySessionId) { + LOGE("OEMCrypto_CreateEntitledKeySession failed: status = %d", + static_cast(result)); + return result; + } + } + CryptoKey entitled_content_key = entitled_keys_[key_id]; if (current_loaded_content_keys_[entitled_content_key.entitlement_key_id()] != key_id) { @@ -61,10 +94,9 @@ OEMCryptoResult EntitlementKeySession::SelectKey(const std::string& key_id, std::string message; OEMCrypto_EntitledContentKeyObject entitled_key = MakeOecEntitledKey(entitled_content_key, message); - OEMCryptoResult result = OEMCrypto_SUCCESS; M_TIME( result = OEMCrypto_LoadEntitledContentKeys( - oec_session_id_, reinterpret_cast(message.data()), + key_session_id_, reinterpret_cast(message.data()), message.size(), 1, &entitled_key), metrics_, oemcrypto_load_entitled_keys_, result); if (result != OEMCrypto_SUCCESS) { @@ -76,7 +108,28 @@ OEMCryptoResult EntitlementKeySession::SelectKey(const std::string& key_id, current_loaded_content_keys_[entitled_content_key.entitlement_key_id()] = key_id; } - return ContentKeySession::SelectKey(key_id, cipher_mode); + + M_TIME(result = OEMCrypto_SelectKey( + key_session_id_, reinterpret_cast(key_id.data()), + key_id.size(), ToOEMCryptoCipherMode(cipher_mode)), + metrics_, oemcrypto_select_key_, result); + return result; +} + +OEMCryptoResult EntitlementKeySession::Decrypt( + const OEMCrypto_SampleDescription* samples, size_t samples_length, + const OEMCrypto_CENCEncryptPatternDesc& pattern) { + size_t total_size = 0; + for (size_t i = 0; i < samples_length; ++i) { + total_size += samples[i].buffers.input_data_length; + } + + OEMCryptoResult sts; + M_TIME(sts = OEMCrypto_DecryptCENC(key_session_id_, samples, samples_length, + &pattern), + metrics_, oemcrypto_decrypt_cenc_, sts, + metrics::Pow2Bucket(total_size)); + return sts; } OEMCrypto_EntitledContentKeyObject EntitlementKeySession::MakeOecEntitledKey( diff --git a/core/src/initialization_data.cpp b/core/src/initialization_data.cpp index cfcc28f5..e819aef7 100644 --- a/core/src/initialization_data.cpp +++ b/core/src/initialization_data.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "initialization_data.h" @@ -452,7 +452,7 @@ bool InitializationData::ConstructWidevineInitData( } std::vector json_init_data = - Base64Decode(uri.substr(pos + kBase64String.size())); + wvutil::Base64Decode(uri.substr(pos + kBase64String.size())); if (json_init_data.size() == 0) { LOGV("Base64 decode of json data failed"); return false; @@ -519,7 +519,7 @@ bool InitializationData::ConstructWidevineInitData( std::string base64_content_id(json_string, tokens[i].start, tokens[i].end - tokens[i].start); std::vector content_id_data = - Base64Decode(base64_content_id); + wvutil::Base64Decode(base64_content_id); content_id.assign(reinterpret_cast(&content_id_data[0]), content_id_data.size()); } @@ -529,7 +529,7 @@ bool InitializationData::ConstructWidevineInitData( if (tokens[i].type == JSMN_ARRAY) { number_of_key_ids = tokens[i].size; } else if (tokens[i].type == JSMN_STRING) { - std::string key_id(a2bs_hex(json_string.substr( + std::string key_id(wvutil::a2bs_hex(json_string.substr( tokens[i].start, tokens[i].end - tokens[i].start))); if (key_id.size() == 16) key_ids.push_back(key_id); --number_of_key_ids; @@ -558,13 +558,16 @@ bool InitializationData::ConstructWidevineInitData( // Now format as Widevine init data protobuf WidevinePsshData cenc_header; - // TODO(rfrias): The algorithm is a deprecated field, but proto changes - // have not yet been pushed to production. Set until then. + // TODO(rfrias): The algorithm and provider are deprecated fields, but proto + // changes have not yet been pushed to production. Set until then. + CORE_UTIL_IGNORE_DEPRECATED cenc_header.set_algorithm(WidevinePsshData_Algorithm_AESCTR); + cenc_header.set_provider(provider); + CORE_UTIL_RESTORE_WARNINGS + for (size_t i = 0; i < key_ids.size(); ++i) { cenc_header.add_key_ids(key_ids[i]); } - cenc_header.set_provider(provider); cenc_header.set_content_id(content_id); if (method == kHlsMethodAes128) cenc_header.set_protection_scheme(kFourCcCbc1); @@ -597,7 +600,7 @@ bool InitializationData::ExtractHexAttribute(const std::string& attribute_list, for (size_t i = 2; i < val.size(); ++i) { if (!isxdigit(val[i])) return false; } - *value = a2b_hex(val.substr(2, val.size() - 2)); + *value = wvutil::a2b_hex(val.substr(2, val.size() - 2)); return result; } @@ -692,7 +695,7 @@ void InitializationData::DumpToLogs() const { video_widevine::WidevinePsshData pssh; if (!pssh.ParseFromString(data())) { - LOGD("InitData: invalid pssh: %s", b2a_hex(data()).c_str()); + LOGD("InitData: invalid pssh: %s", wvutil::b2a_hex(data()).c_str()); return; } if (pssh.has_content_id()) { @@ -700,8 +703,9 @@ void InitializationData::DumpToLogs() const { } if (pssh.has_protection_scheme()) { uint32_t scheme = pssh.protection_scheme(); - LOGD("InitData: Protection Scheme: %c%c%c%c", (scheme >> 24) & 0xFF, - (scheme >> 16) & 0xFF, (scheme >> 8) & 0xFF, (scheme >> 0) & 0xFF); + LOGD("InitData: Protection Scheme: %c%c%c%c", + static_cast(scheme >> 24), static_cast(scheme >> 16), + static_cast(scheme >> 8), static_cast(scheme >> 0)); } switch (pssh.type()) { case video_widevine::WidevinePsshData_Type_SINGLE: @@ -725,14 +729,15 @@ void InitializationData::DumpToLogs() const { LOGD("InitData: Key Sequence %u", pssh.key_sequence()); for (int i = 0; i < pssh.key_ids_size(); i++) { - LOGD("InitData: key_id %d: %s", i, b2a_hex(pssh.key_ids(i)).c_str()); + LOGD("InitData: key_id %d: %s", i, + wvutil::b2a_hex(pssh.key_ids(i)).c_str()); } for (int i = 0; i < pssh.entitled_keys_size(); i++) { video_widevine::WidevinePsshData_EntitledKey key = pssh.entitled_keys(i); LOGD("InitData: entitlement_key_id %d: %s -> %s", i, - b2a_hex(key.entitlement_key_id()).c_str(), - b2a_hex(key.key_id()).c_str()); + wvutil::b2a_hex(key.entitlement_key_id()).c_str(), + wvutil::b2a_hex(key.key_id()).c_str()); } } diff --git a/core/src/license.cpp b/core/src/license.cpp index 7d46bd99..2f99a056 100644 --- a/core/src/license.cpp +++ b/core/src/license.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "license.h" @@ -55,17 +55,23 @@ std::vector ExtractEntitlementKeys(const License& license) { for (int i = 0; i < license.key_size(); ++i) { CryptoKey key; - size_t length = 0; switch (license.key(i).type()) { case License_KeyContainer::ENTITLEMENT: { - // Strip off PKCS#5 padding - since we know the key is 32 or 48 bytes, - // the padding will always be 16 bytes. - if (license.key(i).key().size() > 32) { - length = license.key(i).key().size() - 16; - } else { - length = 0; + // We always take the first ENTITLEMENT_KEY_SIZE bytes and ignore the + // rest in order to ignore any padding. + // + // TODO(b/232464183): When we switch to License Protocol 2.2, there will + // no longer be padding on these keys, so this + // removal code can be removed at the same time. + if (license.key(i).key().size() < ENTITLEMENT_KEY_SIZE) { + LOGE( + "Skipping key %s because it is too small. Expected: %zu vs. " + "Actual: %zu", + license.key(i).id().c_str(), ENTITLEMENT_KEY_SIZE, + license.key(i).key().size()); + continue; } - key.set_key_data(license.key(i).key().substr(0, length)); + key.set_key_data(license.key(i).key().substr(0, ENTITLEMENT_KEY_SIZE)); key.set_key_data_iv(license.key(i).iv()); key.set_key_id(license.key(i).id()); key.set_track_label(license.key(i).track_label()); @@ -111,19 +117,32 @@ std::vector ExtractContentKeys(const License& license) { // Extract content key(s) for (int i = 0; i < license.key_size(); ++i) { CryptoKey key; - size_t length; switch (license.key(i).type()) { case License_KeyContainer::CONTENT: case License_KeyContainer::OPERATOR_SESSION: { key.set_key_id(license.key(i).id()); - // Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes, - // the padding will always be 16 bytes. - // TODO(b/111069024): remove this! - if (license.key(i).key().size() > 16) { - length = license.key(i).key().size() - 16; - } else { - length = 0; + // KeyContainers have a fixed 16 bytes of padding in License Protocol + // 2.1. Note that OPERATION_SESSION keys may be CONTENT_KEY_SIZE or + // MAC_KEY_SIZE, so we cannot assume the key size here. + // + // TODO(b/232464183): When we switch to License Protocol 2.2, there will + // no longer be padding on these keys, so this + // removal code must be removed at the same time. + if (license.key(i).key().size() != + CONTENT_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING && + license.key(i).key().size() != + MAC_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING) { + LOGE( + "Skipping key %s because it is an unexpected size. Expected: %zu " + "or %zu, Actual: %zu", + license.key(i).id().c_str(), + CONTENT_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING, + MAC_KEY_SIZE + LICENSE_PROTOCOL_2_1_PADDING, + license.key(i).key().size()); + continue; } + const size_t length = + license.key(i).key().size() - LICENSE_PROTOCOL_2_1_PADDING; key.set_key_data(license.key(i).key().substr(0, length)); key.set_key_data_iv(license.key(i).iv()); if (license.key(i).has_key_control()) { @@ -182,10 +201,10 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id) is_offline_(false), supports_core_messages_(true), use_privacy_mode_(false), - clock_(new Clock()), + clock_(new wvutil::Clock()), license_key_type_(kLicenseKeyTypeContent) {} -CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock) +CdmLicense::CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock) : crypto_session_(nullptr), policy_engine_(nullptr), session_id_(session_id), @@ -200,9 +219,7 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id, Clock* clock) CdmLicense::~CdmLicense() {} -bool CdmLicense::Init(const std::string& client_token, - CdmClientTokenType client_token_type, - const std::string& device_id, bool use_privacy_mode, +bool CdmLicense::Init(bool use_privacy_mode, const std::string& signed_service_certificate, CryptoSession* session, PolicyEngine* policy_engine) { if (!clock_) { @@ -213,10 +230,6 @@ bool CdmLicense::Init(const std::string& client_token, LOGE("Session ID not provided"); return false; } - if (client_token.size() == 0) { - LOGE("Client token not provided"); - return false; - } if (session == nullptr || !session->IsOpen()) { LOGE("Crypto session not provided or not open"); return false; @@ -231,9 +244,6 @@ bool CdmLicense::Init(const std::string& client_token, return false; } - client_token_ = client_token; - client_token_type_ = client_token_type; - device_id_ = device_id; crypto_session_ = session; policy_engine_ = policy_engine; use_privacy_mode_ = use_privacy_mode; @@ -248,18 +258,19 @@ CdmResponseType CdmLicense::SetServiceCertificate( } CdmResponseType CdmLicense::PrepareKeyRequest( - const InitializationData& init_data, CdmLicenseType license_type, - const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request, - std::string* server_url) { + const InitializationData& init_data, const std::string& client_token, + CdmLicenseType license_type, const CdmAppParameterMap& app_parameters, + CdmKeyMessage* signed_request, std::string* server_url) { if (!initialized_) { LOGE("CdmLicense not initialized"); return LICENSE_PARSER_NOT_INITIALIZED_4; } + client_token_ = client_token; if (init_data.IsEmpty() && stored_init_data_) { InitializationData restored_init_data = *stored_init_data_; stored_init_data_.reset(); - return PrepareKeyRequest(restored_init_data, license_type, app_parameters, - signed_request, server_url); + return PrepareKeyRequest(restored_init_data, client_token, license_type, + app_parameters, signed_request, server_url); } wrapped_keys_ = init_data.ExtractWrappedKeys(); if (!init_data.is_supported()) { @@ -445,9 +456,8 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest( } // TODO(rfrias): Refactor to avoid needing to call CdmSession - if (cdm_session && - cdm_session->get_usage_support_type() == kUsageEntrySupport) { - CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); + if (cdm_session && cdm_session->supports_usage_info()) { + const CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); if (NO_ERROR != status) return status; } @@ -615,7 +625,9 @@ CdmResponseType CdmLicense::HandleKeyResponse( mac_key_iv.assign(license.key(i).iv()); // Strip off PKCS#5 padding - mac_keys.assign(license.key(i).key().data(), kLicenseMacKeySize); + mac_keys.assign( + license.key(i).key().data(), + std::min(kLicenseMacKeySize, license.key(i).key().size())); } } if (license.policy().can_renew() || @@ -650,18 +662,6 @@ CdmResponseType CdmLicense::HandleKeyResponse( if (license.has_provider_client_token()) provider_client_token_ = license.provider_client_token(); - if (license.has_srm_update()) { - status = crypto_session_->LoadSrm(license.srm_update()); - switch (status) { - case NO_ERROR: - break; - case SYSTEM_INVALIDATED_ERROR: - return status; - default: - break; // Ignore - } - } - if (license.id().type() == video_widevine::OFFLINE && license.policy().can_persist()) is_offline_ = true; @@ -681,6 +681,11 @@ CdmResponseType CdmLicense::HandleKeyResponse( renew_with_client_id_ = license.policy().always_include_client_id(); } + // If the field is not set, it will default to false. + status = + crypto_session_->UseSecondaryKey(signed_response.using_secondary_key()); + if (status != NO_ERROR) return status; + CdmResponseType resp = NO_CONTENT_KEY; if (kLicenseKeyTypeEntitlement == key_type) { resp = HandleEntitlementKeyResponse(is_restore, signed_message, @@ -773,6 +778,11 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse( } CdmResponseType status; + // If the field is not set, it will default to false. + status = + crypto_session_->UseSecondaryKey(signed_response.using_secondary_key()); + if (status != NO_ERROR) return status; + if (supports_core_messages()) { status = crypto_session_->LoadRenewal(signed_message, core_message, signature); @@ -794,7 +804,7 @@ CdmResponseType CdmLicense::HandleEmbeddedKeyData( } CdmResponseType CdmLicense::RestoreOfflineLicense( - const CdmKeyMessage& license_request, + const std::string& client_token, const CdmKeyMessage& license_request, const CdmKeyResponse& license_response, const CdmKeyResponse& license_renewal_response, int64_t playback_start_time, int64_t last_playback_time, int64_t grace_period_end_time, @@ -809,6 +819,8 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( return EMPTY_LICENSE_RESPONSE_3; } + client_token_ = client_token; + SignedMessage signed_request; if (!signed_request.ParseFromString(license_request)) { LOGE("Failed to parse license request"); @@ -843,15 +855,14 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( if (!license_renewal_response.empty()) { sts = PrepareKeyUpdateReload(cdm_session); - if (sts != KEY_MESSAGE) return sts; + if (sts != KEY_MESSAGE && sts != NO_ERROR) return sts; sts = HandleKeyUpdateResponse(true, true, license_renewal_response); if (sts != KEY_ADDED) return sts; } if (!provider_session_token_.empty()) { - if (cdm_session && - cdm_session->get_usage_support_type() == kUsageEntrySupport) { - CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); + if (cdm_session && cdm_session->supports_usage_info()) { + const CdmResponseType status = cdm_session->UpdateUsageEntryInformation(); if (NO_ERROR != status) return sts; } @@ -872,13 +883,13 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( break; case CryptoSession::kUsageDurationsValid: { int64_t current_time = clock_->GetCurrentTime(); - if (current_time - seconds_since_started > 0) - playback_start_time = current_time - seconds_since_started; - if (current_time - last_playback_time > 0) - last_playback_time = current_time - seconds_since_last_played; + playback_start_time = current_time - seconds_since_started; + last_playback_time = current_time - seconds_since_last_played; break; } default: + // Use playback_start_time and last_playback_time from + // persistently stored license data break; } } @@ -890,7 +901,7 @@ CdmResponseType CdmLicense::RestoreOfflineLicense( } CdmResponseType CdmLicense::RestoreLicenseForRelease( - const CdmKeyMessage& license_request, + const std::string& client_token, const CdmKeyMessage& license_request, const CdmKeyResponse& license_response) { if (license_request.empty()) { LOGE("License request is empty"); @@ -902,6 +913,8 @@ CdmResponseType CdmLicense::RestoreLicenseForRelease( return EMPTY_LICENSE_RESPONSE_4; } + client_token_ = client_token; + SignedMessage signed_request; if (!signed_request.ParseFromString(license_request)) { LOGE("Failed to parse signed license request"); @@ -1047,7 +1060,13 @@ CdmResponseType CdmLicense::PrepareClientId( const CdmAppParameterMap& app_parameters, const std::string& provider_client_token, LicenseRequest* license_request) { wvcdm::ClientIdentification id; - CdmResponseType status = id.Init(client_token_, device_id_, crypto_session_); + if (client_token_.empty()) { + LOGE("Client token not set when preparing client ID"); + return CLIENT_TOKEN_NOT_SET; + } + + CdmResponseType status = + id.InitForLicenseRequest(client_token_, crypto_session_); if (status != NO_ERROR) return status; video_widevine::ClientIdentification* client_id = diff --git a/core/src/license_key_status.cpp b/core/src/license_key_status.cpp index d404492a..a4fc365b 100644 --- a/core/src/license_key_status.cpp +++ b/core/src/license_key_status.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "license_key_status.h" diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto index 11d6b6b1..9101ae7c 100644 --- a/core/src/license_protocol.proto +++ b/core/src/license_protocol.proto @@ -1,9 +1,6 @@ -// ---------------------------------------------------------------------------- -// license_protocol.proto -// ---------------------------------------------------------------------------- // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. syntax = "proto2"; @@ -71,10 +68,72 @@ message LicenseIdentification { optional LicenseType type = 4; optional int32 version = 5; optional bytes provider_session_token = 6; + // Set by the SDK representing the rental duration from the initial license. + optional int64 original_rental_duration_seconds = 7; + // Set by the SDK representing the playback duration from the initial license. + optional int64 original_playback_duration_seconds = 8; + // Set by the SDK representing the start time of the initial license in + // seconds (UTC). This is from the original license's license_start_time, + // which is from the LicenseRequest.request_time when set, or set by the + // server to be the time that the original license was processed. + optional int64 original_start_time_seconds = 9; + // Set by the SDK representing the renewal recovery duration from the initial + // license. + optional int64 original_renewal_recovery_duration_seconds = 10; + // Set by the SDK representing the renewal delay seconds from the original + // license. + optional int64 original_renewal_delay_seconds = 11; +} + +// This message is used to indicate the license cateogry spec for a license as +// a part of initial license issuance. +message LicenseCategorySpec { + // Possible license categories. + enum LicenseCategory { + // By default, License is used for single content. + SINGLE_CONTENT_LICENSE_DEFAULT = 0; + // License is used for multiple contents (could be a combination of + // single contents and groups of contents). + MULTI_CONTENT_LICENSE = 1; + // License is used for contents logically grouped. + GROUP_LICENSE = 2; + } + // Optional. License category indicates if license is used for single + // content, multiple contents (could be a combination of + // single contents and groups of contents) or a group of contents. + optional LicenseCategory license_category = 1; + // Optional. Content or group ID covered by the license. + oneof content_or_group_id { + // Content_id would be present if it is a license for single content. + bytes content_id = 2; + // Group_id would be present if the license is a multi_content_license or + // group_license. Group Id could be the name of a group of contents, + // defined by licensor. + bytes group_id = 3; + } +} + +message ProxyInfo { + // Indicates SDK type(Including UNKNOWN_SERVICE_TYPE, LICENSE_PROXY_SDK, + // CAS_PROXY_SDK). + optional DrmCertificate.ServiceType sdk_type = 1; + // Indicates the version of SDK. + optional string sdk_version = 2; } message License { message Policy { + // Client-side watermarking restrictions for the license. + enum WatermarkingControl { + // Watermarking may or may not be used, provider does not care. + WATERMARKING_CONTROL_UNSPECIFIED = 0; + // Watermarking must not be used. The device must disable watermarking + // if it supports it. + WATERMARKING_FORBIDDEN = 1; + // Watermarking is required if the device supports it. + WATERMARKING_REQUIRED = 2; + } + // Indicates that playback of the content is allowed. optional bool can_play = 1 [default = false]; @@ -110,8 +169,10 @@ message License { // specified URL. optional string renewal_server_url = 8; - // How many seconds after license_start_time, before renewal is first - // attempted. + // How many seconds after |license_start_time| before renewal is first + // attempted. If |renew_with_usage| is true in a new license, then this is + // the optional number of seconds after first playback, before renewal is + // first attempted. optional int64 renewal_delay_seconds = 9 [default = 0]; // Specifies the delay in seconds between subsequent license @@ -119,7 +180,8 @@ message License { optional int64 renewal_retry_interval_seconds = 10 [default = 0]; // Indicates that the license shall be sent for renewal when usage is - // started. + // started, i.e. on first playback. This should only be used for a new + // license. The client shall ignore this if set in a renewal. optional bool renew_with_usage = 11 [default = false]; // Indicates to client that license renewal and release requests ought to @@ -128,10 +190,11 @@ message License { // Duration of grace period before playback_duration_seconds (short window) // goes into effect. Optional. + // Deprecated in V16. optional int64 play_start_grace_period_seconds = 13 [default = 0]; // Enables "soft enforcement" of playback_duration_seconds, letting the user - // finish playback even if short window expires. Optional. + // finish playback even if playback window expires. Optional. optional bool soft_enforce_playback_duration = 14 [default = false]; // Enables "soft enforcement" of rental_duration_seconds. Initial playback @@ -140,16 +203,25 @@ message License { // soft_enforce_playback_duration must be true. Otherwise, subsequent // playbacks will not be allowed once rental duration expires. Optional. optional bool soft_enforce_rental_duration = 15 [default = true]; + + // Optional requirement to indicate watermarking is allowed. + optional WatermarkingControl watermarking_control = 16 + [default = WATERMARKING_CONTROL_UNSPECIFIED]; } message KeyContainer { enum KeyType { - SIGNING = 1; // Exactly one key of this type must appear. + SIGNING = 1; // No more than one signing key may appear. CONTENT = 2; // Content key. KEY_CONTROL = 3; // Key control block for license renewals. No key. OPERATOR_SESSION = 4; // wrapped keys for auxiliary crypto operations. ENTITLEMENT = 5; // Entitlement keys. OEM_CONTENT = 6; // Partner-specific content key. + // Public signing key provided by content providers. Currently used by CAS + // for verifying the received ECM/EMM signature. Only EC key is supported + // for now. + PROVIDER_ECM_VERIFIER_PUBLIC_KEY = 7; + OEM_ENTITLEMENT = 8; // Partner-specific entitlement key. } // The SecurityLevel enumeration allows the server to communicate the level @@ -176,8 +248,6 @@ message License { } message KeyControl { - // |key_control| is documented in: - // Widevine Modular DRM Security Integration Guide for CENC // If present, the key control must be communicated to the secure // environment prior to any usage. This message is automatically generated // by the Widevine License Server SDK. @@ -218,11 +288,15 @@ message License { // allow use of the key anyway. CURRENT_SRM = 1; } + optional HdcpSrmRule hdcp_srm_rule = 3 [default = HDCP_SRM_RULE_NONE]; // Optional requirement to indicate analog output is not allowed. optional bool disable_analog_output = 4 [default = false]; // Optional requirement to indicate digital output is not allowed. optional bool disable_digital_output = 5 [default = false]; + // Optional. If set, it indicates digital video recording (DVR) is + // allowed. + optional bool allow_record = 6 [default = false]; } message VideoResolutionConstraint { @@ -243,6 +317,28 @@ message License { optional bool allow_signature_verify = 4 [default = false]; } + // KeyCategorySpec message is used to identify if current key is generated + // for a single content or a group of contents. Currently it is only used in + // CAS request. + message KeyCategorySpec { + // Represents what kind of content a key is used for. + enum KeyCategory { + // By default, key is created for single content. + SINGLE_CONTENT_KEY_DEFAULT = 0; + // Key is created for a group of contents. + GROUP_KEY = 1; + } + // Indicate if the current key is created for single content or for group + // use. + optional KeyCategory key_category = 1; + // Id for key category. If it is a key for single content, this id + // represents the content_id. Otherwise, it represents a group_id. + oneof content_or_group_id { + bytes content_id = 2; + bytes group_id = 3; + } + } + optional bytes id = 1; optional bytes iv = 2; optional bytes key = 3; @@ -268,6 +364,10 @@ message License { // Optional not limited to commonly known track types such as SD, HD. // It can be some provider defined label to identify the track. optional string track_label = 12; + // Optional. It is used to identify if current key is generated for a + // single content or a group of contents. Currently it is only used in CAS + // request. + optional KeyCategorySpec key_category_spec = 13; } optional LicenseIdentification id = 1; @@ -277,7 +377,7 @@ message License { // LicenseRequest.request_time. If this time is not set in the request, // the local time at the license service is used in this field. optional int64 license_start_time = 4; - // TODO(b/65054419): Deprecate remote_attestation_verified in favor of + // Deprecate remote_attestation_verified in favor of // platform_verification_status, below. optional bool remote_attestation_verified = 5 [default = false]; // Client token generated by the content provider. Optional. @@ -288,8 +388,6 @@ message License { // 8 byte verification field "HDCPDATA" followed by unsigned 32 bit minimum // HDCP SRM version (whether the version is for HDCP1 SRM or HDCP2 SRM // depends on client max_hdcp_version). - // Additional details can be found in Widevine Modular DRM Security - // Integration Guide for CENC. optional bytes srm_requirement = 8; // If present this contains a signed SRM file (either HDCP1 SRM or HDCP2 SRM // depending on client max_hdcp_version) that should be installed on the @@ -301,6 +399,13 @@ message License { [default = PLATFORM_NO_VERIFICATION]; // IDs of the groups for which keys are delivered in this license, if any. repeated bytes group_ids = 11; + // Optional. LicenseCategorySpec is used to indicate the license cateogry for + // a license. It could be used as a part of initial license issuance or shown + // as a part of license in license response. + optional LicenseCategorySpec license_category_spec = 12; + // Optional: The provider key id indicates which provider key was used + // during provider key encryption. + optional uint32 provider_key_id = 13; } enum ProtocolVersion { @@ -386,6 +491,8 @@ message LicenseError { // The service is currently unavailable due to the backend being down // or similar circumstances. SERVICE_UNAVAILABLE = 3; + // The device credentials are expired. The device must re-provision. + EXPIRED_DRM_DEVICE_CERTIFICATE = 4; } optional Error error_code = 1; } @@ -438,8 +545,9 @@ message SignedMessage { enum SessionKeyType { UNDEFINED = 0; WRAPPED_AES_KEY = 1; - EPHERMERAL_ECC_PUBLIC_KEY = 2; + EPHEMERAL_ECC_PUBLIC_KEY = 2; } + optional MessageType type = 1; optional bytes msg = 2; // Required field that contains the signature of the bytes of msg. @@ -469,6 +577,26 @@ message SignedMessage { // The core message is the simple serialization of fields used by OEMCrypto. // This field was introduced in OEMCrypto API v16. optional bytes oemcrypto_core_message = 9; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 10; + // If true it indicates that a LICENSE message session key was based on an + // alternate key provided by the client credentials. + optional bool using_secondary_key = 11; +} + +// ---------------------------------------------------------------------------- +// hash_algorithm.proto +// ---------------------------------------------------------------------------- +// Description of section: +// Public protocol buffer definitions for Widevine Hash Algorithm protocol. + +enum HashAlgorithmProto { + // Unspecified hash algorithm: SHA_256 shall be used for ECC based algorithms + // and SHA_1 shall be used otherwise. + HASH_ALGORITHM_UNSPECIFIED = 0; + HASH_ALGORITHM_SHA_1 = 1; + HASH_ALGORITHM_SHA_256 = 2; + HASH_ALGORITHM_SHA_384 = 3; } // ---------------------------------------------------------------------------- @@ -478,20 +606,44 @@ message SignedMessage { // Public protocol buffer definitions for Widevine Device Certificate // Provisioning protocol. +// A KeyToCertify contains a client generated public key to be incorporated into +// a signed certificate. +message PublicKeyToCertify { + // A KeyType indicates a high level key type. + enum KeyType { + KEY_TYPE_UNSPECIFIED = 0; + RSA = 1; + ECC = 2; + } + + // |public_key| contains the bytes of a PKCS#1 ASN.1 DER-encoded public key. + optional bytes public_key = 1; + // KeyType contains a highlevel hint to use in parsing the serialized key + // contained in |public_key|. If the key is an EC key, curve parameters can be + // extracted from the deserialized key. + // Keys are expected to match the certificate key type in the device + // record. + optional KeyType key_type = 2; + // The signature of |public_key|. + // Keys that are signed using ECDSA or RSA should hash the message using + // SHA-256. + optional bytes signature = 3; +} + // ProvisioningOptions specifies the type of certificate to specify and // in the case of X509 certificates, the certificate authority to use. message ProvisioningOptions { enum CertificateType { - WIDEVINE_DRM = 0; // Default. The original certificate type. - X509 = 1; // X.509 certificate. + WIDEVINE_DRM = 0; // Default. The original certificate type. + X509 = 1; // X.509 certificate. WIDEVINE_KEYBOX = 2; } optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM]; // Contains the application-specific name used to identify the certificate - // authority for signing the generated certificate. This is required if the - // certificate type is X509. + // authority for signing the generated certificate. This is required if and + // only if the certificate type is X509. optional string certificate_authority = 2; // System ID for OTA keybox provisioning. Requires device secure boot. optional uint32 system_id = 3; @@ -517,6 +669,15 @@ message ProvisioningRequest { // Serialized, encrypted session keys. Required. optional bytes encrypted_session_keys = 2; } + // This message contains the custom serialized message for OTA provisioning + // using Android Attestation and a device id as authentication. + message AndroidAttestationOtaKeyboxRequest { + // The request contains custom serialized and signed data for the + // Android Attestation OTA request. + // see: go/wv_android_ota + optional bytes ota_request = 1; + } + oneof clear_or_encrypted_client_id { // Device root of trust and other client identification. Required. ClientIdentification client_id = 1; @@ -540,6 +701,12 @@ message ProvisioningRequest { // SessionKeys encrypted using a service cert public key. // Required for keybox provisioning. optional EncryptedSessionKeys encrypted_session_keys = 8; + // The custom request for Android Attestation OTA. + optional AndroidAttestationOtaKeyboxRequest android_ota_keybox_request = 9; + // Specifies the public key that should be certified by the provisioning + // server. The client holds the private key. If specified, the response no + // longer needs to contain server generated |device_rsa_key|. + optional PublicKeyToCertify certificate_public_key = 10; } // Provisioning response sent by the provisioning server to client devices. @@ -556,6 +723,23 @@ message ProvisioningResponse { // Device CA token component of the keybox. optional bytes device_ca_token = 3; } + enum ProvisioningStatus { + // Indicates a valid provisioning response + NO_ERROR = 0; + // The device credentials have been revoked. Provisioning is not possible. + REVOKED_DEVICE_CREDENTIALS = 1; + // Devices in this series have been revoked. Provisioning is not possible. + REVOKED_DEVICE_SERIES = 2; + } + // This message contains the custom response for Android Attestation OTA + // provisioning which uses the Android Attestation keybox and a device id + // from the chip set. + message AndroidAttestationOtaKeyboxResponse { + // The response contains custom serialized and signed data for the + // Android Attestation OTA keybox provisioning. + optional bytes ota_response = 1; + } + // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. // Required. For X.509 certificates, the private RSA key may also include // a prefix as specified by private_key_prefix in the X509CertificateMetadata @@ -575,11 +759,17 @@ message ProvisioningResponse { optional bytes wrapping_key = 5; // Only populated in OTA keybox provisioning response. optional OtaKeybox ota_keybox = 6; + // The provisioning service may return a ProvisioningStatus. Fields other + // than |status| may be empty and should be ignored if the |status| + // is present and not NO_ERROR + optional ProvisioningStatus status = 7; + // The Android Attestation OTA response. Only populated if the request + // was an Android Attestation OTA request. + optional AndroidAttestationOtaKeyboxResponse android_ota_keybox_response = 8; } // Protocol-specific context data used to hold the state of the server in -// stateful provisioning protocols. For more information, please refer to -// "Widevine DRM Provisioning using Third-Part and Stateful Protocols". +// stateful provisioning protocols. message ProvisioningContext { // Serialized ProvisioningContextKeyData. Required. optional bytes key_data = 1; @@ -594,6 +784,8 @@ message SignedProvisioningContext { // RSASSA-PSS signature of provisioning_context. Signed with service private // key. optional bytes signature = 2; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 3; } // Cryptographic tokens to be used for ProvisioningContext. @@ -607,26 +799,44 @@ message ProvisioningContextKeyData { // Serialized ProvisioningRequest or ProvisioningResponse signed with // The message authentication key. message SignedProvisioningMessage { - enum ProtocolVersion { + enum ProvisioningProtocolVersion { + VERSION_UNSPECIFIED = 0; + VERSION_1 = 1; + // Version 1.1 changed error handling. Some errors are returned as a field + // in a response message rather than being handled as errors via the API + // implementation. E.g. embedded in the ProvisioningResponse rather than + // returning a 400 error to the caller. + VERSION_1_1 = 2; + } + + enum ProvisioningType { // This enum was renamed to avoid confusion + PROVISIONING_TYPE_UNSPECIFIED = 0; SERVICE_CERTIFICATE_REQUEST = 1; // Service certificate request. PROVISIONING_20 = 2; // Keybox factory-provisioned devices. - PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. + PROVISIONING_30 = 3; // OEM certificate factory-provisioned devices. + // Devices use Boot Certificate Chain (BCC) to provision an OEM certificate. + PROVISIONING_40 = 5; + // These are provisioning methods that are only supported by internal + // Widevine services. They should not be exposed in the SDK. ARCPP_PROVISIONING = 4; // ChromeOS/Arc++ devices. - INTEL_SIGMA_101 = 101; // Intel Sigma 1.0.1 protocol. + // Android-Attestation-based OTA keyboxes. + ANDROID_ATTESTATION_KEYBOX_OTA = 6; + INTEL_SIGMA_101 = 101; // Intel Sigma 1.0.1 protocol. + INTEL_SIGMA_210 = 210; // Intel Sigma 2.1.0 protocol. } // Serialized protobuf message for the corresponding protocol and stage of // the provisioning exchange. ProvisioningRequest or ProvisioningResponse - // in the case of Provisioning 2.0, 3.0 and ARCPP_PROVISIONING. Required. + // in the case of Provisioning 2.0, 3.0, 4.0 and ARCPP_PROVISIONING. Required. optional bytes message = 1; // HMAC-SHA256 (Keybox) or RSASSA-PSS (OEM) signature of message. Required // for provisioning 2.0 and 3.0. For ARCPP_PROVISIONING, only used in // response. optional bytes signature = 2; // Version number of provisioning protocol. - optional ProtocolVersion protocol_version = 3 [default = PROVISIONING_20]; + optional ProvisioningType provisioning_type = 3 [default = PROVISIONING_20]; // Protocol-specific context / state information for multiple-exchange, - // stateful provisioing protocols. Optional. + // stateful provisioning protocols. Optional. optional SignedProvisioningContext signed_provisioning_context = 4; // Remote attestation data to authenticate that the ChromeOS client device // is operating in verified mode. Remote attestation challenge data is @@ -637,6 +847,10 @@ message SignedProvisioningMessage { // This field was introduced in OEMCrypto API v16. The core message format is // documented in the "Widevine Core Message Serialization". optional bytes oemcrypto_core_message = 6; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 7; + // Indicates which version of the protocol is in use. + optional ProvisioningProtocolVersion protocol_version = 8; } // ---------------------------------------------------------------------------- @@ -652,6 +866,7 @@ message ClientIdentification { DRM_DEVICE_CERTIFICATE = 1; REMOTE_ATTESTATION_CERTIFICATE = 2; OEM_DEVICE_CERTIFICATE = 3; + BOOT_CERTIFICATE_CHAIN = 4; } message NameValue { @@ -669,6 +884,13 @@ message ClientIdentification { HDCP_V2_1 = 3; HDCP_V2_2 = 4; HDCP_V2_3 = 5; + // The existing HDCP_V1 will be used for backwards compatibility with pre + // OEM crypto v17. + HDCP_V1_0 = 6; + HDCP_V1_1 = 7; + HDCP_V1_2 = 8; + HDCP_V1_3 = 9; + HDCP_V1_4 = 10; HDCP_NO_DIGITAL_OUTPUT = 0xff; } @@ -687,14 +909,20 @@ message ClientIdentification { ANALOG_OUTPUT_SUPPORTS_CGMS_A = 3; } + enum WatermarkingSupport { + WATERMARKING_SUPPORT_UNKNOWN = 0; + WATERMARKING_NOT_SUPPORTED = 1; + WATERMARKING_CONFIGURABLE = 2; + WATERMARKING_ALWAYS_ON = 3; + } + optional bool client_token = 1 [default = false]; optional bool session_token = 2 [default = false]; optional bool video_resolution_constraints = 3 [default = false]; optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE]; optional uint32 oem_crypto_api_version = 5; // Client has hardware support for protecting the usage table, such as - // storing the generation number in secure memory. For Details, see: - // Widevine Modular DRM Security Integration Guide for CENC + // storing the generation number in secure memory. optional bool anti_rollback_usage_table = 6 [default = false]; // The client shall report |srm_version| if available. optional uint32 srm_version = 7; @@ -710,9 +938,12 @@ message ClientIdentification { // quality of content to serve. Currently defined tiers are // 1 (low), 2 (medium) and 3 (high). Any other value indicates that // the resource rating is unavailable or reporting erroneous values - // for that device. For details see, - // Widevine Modular DRM Security Integration Guide for CENC + // for that device. optional uint32 resource_rating_tier = 12 [default = 0]; + // Watermarking support of OEMCrypto was introduced in v17. + // Support is optional. + // Value is only required to be set for license requests. + optional WatermarkingSupport watermarking_support = 13; } message ClientCredentials { @@ -736,7 +967,7 @@ message ClientIdentification { // Serialized VmpData message. Optional. optional bytes vmp_data = 7; // Optional field that may contain additional provisioning credentials. - repeated ClientCredentials device_credentials = 8; + optional ClientCredentials device_credentials = 8; } // EncryptedClientIdentification message used to hold ClientIdentification @@ -758,29 +989,94 @@ message EncryptedClientIdentification { } // ---------------------------------------------------------------------------- -// device_certificate.proto +// drm_certificate.proto // ---------------------------------------------------------------------------- // Description of section: -// Device certificate and certificate status list format definitions. +// Definition of the root of trust identifier proto. The proto message contains +// the EC-IES encrypted identifier (e.g. keybox unique id) for a device and +// an associated hash. These can be used by Widevine to identify the root of +// trust that was used to acquire a DRM certificate. +// +// In addition to the encrypted part and the hash, the proto contains the +// version of the root of trust id which implies the EC key algorithm that was +// used. +// Next id: 5 +message RootOfTrustId { + // The version specifies the EC algorithm that was used to generate the + // root of trust id. + enum RootOfTrustIdVersion { + // Should not be used. + ROOT_OF_TRUST_ID_VERSION_UNSPECIFIED = 0; + // Version 1 of the ID uses EC-IES with SECP256R1 curve. + ROOT_OF_TRUST_ID_VERSION_1 = 1; + } + optional RootOfTrustIdVersion version = 1; + // The key_id is used for key rotation. It indicates which key was used to + // generate the root of trust id. + optional uint32 key_id = 2; + + // The EC-IES encrypted message containing the unique_id. The bytes are + // a concatenation of + // 1) The ephemeral public key. Uncompressed keypoint format per X9.62. + // 2) The plaintext encrypted with the derived AES key using AES CBC, + // PKCS7 padding and a zerio iv. + // 3) The HMAC SHA256 of the cipher text. + optional bytes encrypted_unique_id = 3; + + // The hash of encrypted unique id and other values. + // unique_id_hash = SHA256( + // encrypted_unique_id || system_id || SHA256(unique_id || secret_sauce)). + optional bytes unique_id_hash = 4; +} // DRM certificate definition for user devices, intermediate, service, and root // certificates. -message DrmDeviceCertificate { - enum CertificateType { - ROOT = 0; - DRM_INTERMEDIATE = 1; - DRM_USER_DEVICE = 2; +// Next id: 13 +message DrmCertificate { + enum Type { + ROOT = 0; // ProtoBestPractices: ignore. + DEVICE_MODEL = 1; + DEVICE = 2; SERVICE = 3; PROVISIONER = 4; } + enum ServiceType { + UNKNOWN_SERVICE_TYPE = 0; + LICENSE_SERVER_SDK = 1; + LICENSE_SERVER_PROXY_SDK = 2; + PROVISIONING_SDK = 3; + CAS_PROXY_SDK = 4; + } + enum Algorithm { + UNKNOWN_ALGORITHM = 0; + RSA = 1; + ECC_SECP256R1 = 2; + ECC_SECP384R1 = 3; + ECC_SECP521R1 = 4; + } + + message EncryptionKey { + // Device public key. PKCS#1 ASN.1 DER-encoded. Required. + optional bytes public_key = 1; + // Required. The algorithm field contains the curve used to create the + // |public_key| if algorithm is one of the ECC types. + // The |algorithm| is used for both to determine the if the certificate is + // ECC or RSA. The |algorithm| also specifies the parameters that were used + // to create |public_key| and are used to create an ephemeral session key. + optional Algorithm algorithm = 2 [default = RSA]; + } // Type of certificate. Required. - optional CertificateType type = 1; + optional Type type = 1; // 128-bit globally unique serial number of certificate. // Value is 0 for root certificate. Required. optional bytes serial_number = 2; // POSIX time, in seconds, when the certificate was created. Required. optional uint32 creation_time_seconds = 3; + // POSIX time, in seconds, when the certificate should expire. Value of zero + // denotes indefinite expiry time. For more information on limited lifespan + // DRM certificates see (go/limited-lifespan-drm-certificates). + optional uint32 expiration_time_seconds = 12; // Device public key. PKCS#1 ASN.1 DER-encoded. Required. optional bytes public_key = 4; // Widevine system ID for the device. Required for intermediate and @@ -793,126 +1089,45 @@ message DrmDeviceCertificate { // Service identifier (web origin) for the provider which owns the // certificate. Required for service and provisioner certificates. optional string provider_id = 7; + // This field is used only when type = SERVICE to specify which SDK uses + // service certificate. This repeated field is treated as a set. A certificate + // may be used for the specified service SDK if the appropriate ServiceType + // is specified in this field. + repeated ServiceType service_types = 8; + // Required. The algorithm field contains the curve used to create the + // |public_key| if algorithm is one of the ECC types. + // The |algorithm| is used for both to determine the if the certificate is ECC + // or RSA. The |algorithm| also specifies the parameters that were used to + // create |public_key| and are used to create an ephemeral session key. + optional Algorithm algorithm = 9 [default = RSA]; + // Optional. May be present in DEVICE certificate types. This is the root + // of trust identifier that holds an encrypted value that identifies the + // keybox or other root of trust that was used to provision a DEVICE drm + // certificate. + optional RootOfTrustId rot_id = 10; + // Optional. May be present in devices that explicitly support dual keys. When + // present the |public_key| is used for verification of received license + // request messages. + optional EncryptionKey encryption_key = 11; } -// DeviceCertificate signed with intermediate or root certificate private key. -message SignedDrmDeviceCertificate { +// ---------------------------------------------------------------------------- +// signed_drm_certificate.proto +// ---------------------------------------------------------------------------- +// Description of section: +// Signed device certificate definition. + +// DrmCertificate signed by a higher (CA) DRM certificate. +message SignedDrmCertificate { // Serialized certificate. Required. optional bytes drm_certificate = 1; // Signature of certificate. Signed with root or intermediate // certificate specified below. Required. optional bytes signature = 2; - // SignedDrmDeviceCertificate used to sign this certificate. - optional SignedDrmDeviceCertificate signer = 3; -} - -// Contains the status of the root or an intermediate DeviceCertificate. -message DeviceCertificateStatus { - enum Status { - VALID = 0; - REVOKED = 1; - }; - - // Serial number of the intermediate DrmDeviceCertificate to which this - // message refers. Required. - optional bytes drm_serial_number = 1; - // Status of the certificate. Optional. - optional Status status = 2 [default = VALID]; - // Device model information about the device to which the intermediate - // certificate(s) correspond. - optional ProvisionedDeviceInfo device_info = 4; -} - -// List of DeviceCertificateStatus. Used to propagate certificate revocation -// status and device information. -message DeviceCertificateStatusList { - // POSIX time, in seconds, when the list was created. Required. - optional uint32 creation_time_seconds = 1; - // DeviceCertificateStatus for each system ID. - repeated DeviceCertificateStatus certificate_status = 2; -} - -// Signed CertificateStatusList -message SignedCertificateStatusList { - // Serialized DeviceCertificateStatusList. Required. - optional bytes certificate_status_list = 1; - // Signature of certificate_status_list. Signed with root certificate private - // key using RSASSA-PSS. Required. - optional bytes signature = 2; -} - -// ---------------------------------------------------------------------------- -// provisioned_device_info.proto -// ---------------------------------------------------------------------------- -// Description of section: -// Provisioned device info format definitions. - -// Contains device model information for a provisioned device. -message ProvisionedDeviceInfo { - enum WvSecurityLevel { - // Defined in "Widevine Security Integration Guide for DASH on Android" - LEVEL_UNSPECIFIED = 0; - LEVEL_1 = 1; - LEVEL_2 = 2; - LEVEL_3 = 3; - } - - // Widevine initial provisioning / bootstrapping method. DRM certificates are - // required for retrieving licenses, so if a DRM certificate is not initially - // provisioned, then the provisioned credentials will be used to provision - // a DRM certificate via the Widevine Provisioning Service. - enum ProvisioningMethod { - // Don't use this. - PROVISIONING_METHOD_UNSPECIFIED = 0; - // Factory-provisioned device-unique keybox. - FACTORY_KEYBOX = 1; - // Factory-provisioned device-unique OEM certificate. - FACTORY_OEM_DEVICE_CERTIFICATE = 2; - // Factory-provisioned model-group OEM certificate. - FACTORY_OEM_GROUP_CERTIFICATE = 3; - // Factory-provisioned model-group DRM certificate (Level-3 "baked in"). - FACTORY_DRM_GROUP_CERTIFICATE = 4; - // OTA-provisioned keybox (Level-1 ARC++). - OTA_KEYBOX = 5; - // OTA-provisioned device-unique OEM certificate. - OTA_OEM_DEVICE_CERTIFICATE = 6; - // OTA-provisioned model-group OEM certificate. - OTA_OEM_GROUP_CERTIFICATE = 7; - // OTA-provisioned device-unique DRM certificate (Bedrock). - OTA_DRM_DEVICE_CERTIFICATE = 8; - } - // Represents additional devices that are associated with the device. These - // are devices that have the systemID, but a different 'manufacturer'/'model' - // etc. - message ModelInfo { - // Represents the device manufacturer. Typically, this will be Philips, LG, - // Sharp, etc. - optional string manufacturer = 1; - // Model of the device. - optional string model = 2; - } - // Widevine system ID for the device. Mandatory. - optional uint32 system_id = 1; - // Name of system-on-a-chip. Optional. - optional string soc = 2; - // First registered manufacturer. Optional. - optional string manufacturer = 3; - // First registered manufacturer's model name. Matches "brand" in device - // metadata. Optional. - optional string model = 4; - // First registered type of device (Phone, Tablet, TV, etc). - optional string device_type = 5; - // First registered device model year. Optional. - optional uint32 model_year = 6; - // Widevine-defined security level. Optional. - optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED]; - // True if the certificate corresponds to a test (non production) device. - // Optional. - optional bool test_device = 8 [default = false]; - // Indicates the type of device root of trust which was factory provisioned. - optional ProvisioningMethod provisioning_method = 9; - // A list of ModelInfo using the same system_id. - repeated ModelInfo model_info = 10; + // SignedDrmCertificate used to sign this certificate. + optional SignedDrmCertificate signer = 3; + // Optional field that indicates the hash algorithm used in signature scheme. + optional HashAlgorithmProto hash_algorithm = 4; } // ---------------------------------------------------------------------------- @@ -942,7 +1157,7 @@ message WidevinePsshData { optional uint32 entitlement_key_size_bytes = 5 [default = 32]; } - // Entitlement or content key IDs. Can onnly present in SINGLE or ENTITLEMENT + // Entitlement or content key IDs. Can only present in SINGLE or ENTITLEMENT // PSSHs. May be repeated to facilitate delivery of multiple keys in a // single license. Cannot be used in conjunction with content_id or // group_ids, which are the preferred mechanism. @@ -983,8 +1198,8 @@ message WidevinePsshData { // Group identifiers for all groups to which the content belongs. This can // be used to deliver licenses to unlock multiple titles / channels. - // Optional, and may only be present in ENTITLEMENT and ENTITLED_KEY PSSHs, and - // not in conjunction with key_ids. + // Optional, and may only be present in ENTITLEMENT and ENTITLED_KEY PSSHs, + // and not in conjunction with key_ids. repeated bytes group_ids = 13; // Copy/copies of the content key used to decrypt the media stream in which @@ -1000,11 +1215,22 @@ message WidevinePsshData { // Current values are "HDR". optional string video_feature = 15; + // Audiofeature identifier, which is used in conjunction with |content_id| + // to determine the set of keys to be returned in the license. Cannot be + // present in conjunction with |key_ids|. + // Current values are "commentary". + optional string audio_feature = 16; + + // Entitlement period index for media using entitlement key rotation. Can only + // present in ENTITLEMENT PSSHs. It always corresponds to the entitlement key + // period. + optional uint32 entitlement_period_index = 17; + //////////////////////////// Deprecated Fields //////////////////////////// enum Algorithm { UNENCRYPTED = 0; AESCTR = 1; - }; + } optional Algorithm algorithm = 1 [deprecated = true]; optional string provider = 3 [deprecated = true]; optional string track_type = 5 [deprecated = true]; diff --git a/core/src/oemcrypto_adapter_static.cpp b/core/src/oemcrypto_adapter_static.cpp index 90811bc1..d3aa9994 100644 --- a/core/src/oemcrypto_adapter_static.cpp +++ b/core/src/oemcrypto_adapter_static.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // Wrapper of OEMCrypto APIs for platforms that support Level 1 only. // This should be used when liboemcrypto.so is linked with the CDM code at @@ -10,89 +10,113 @@ #include "oemcrypto_adapter.h" namespace wvcdm { +OEMCryptoResult OEMCrypto_InitializeAndCheckKeybox( + bool* needs_keybox_provisioning) { + if (!needs_keybox_provisioning) return OEMCrypto_ERROR_UNKNOWN_FAILURE; + const OEMCryptoResult status = ::OEMCrypto_Initialize(); + if (status != OEMCrypto_SUCCESS) return status; + const OEMCryptoResult keybox_status = ::OEMCrypto_IsKeyboxOrOEMCertValid(); + if (keybox_status == OEMCrypto_SUCCESS) { + *needs_keybox_provisioning = false; + return OEMCrypto_SUCCESS; + } + if (keybox_status == OEMCrypto_ERROR_NEEDS_KEYBOX_PROVISIONING) { + *needs_keybox_provisioning = true; + return OEMCrypto_SUCCESS; + } + // When running unit tests, we will try to re-initialize and load a test + // keybox. In order for that to work, we need to leave OEMCrypto in an + // uninitialized state. + ::OEMCrypto_Terminate(); + return keybox_status; +} + +OEMCryptoResult OEMCrypto_SetDebugIgnoreKeyboxCount(uint32_t count) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +OEMCryptoResult OEMCrypto_SetAllowTestKeybox(bool count) { + return OEMCrypto_SUCCESS; +} OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, - SecurityLevel) { + RequestedSecurityLevel) { return ::OEMCrypto_OpenSession(session); } OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox, - size_t keyBoxLength, SecurityLevel) { + size_t keyBoxLength, RequestedSecurityLevel) { return ::OEMCrypto_InstallKeybox(keybox, keyBoxLength); } -OEMCryptoResult OEMCrypto_IsKeyboxOrOEMCertValid(SecurityLevel) { - return ::OEMCrypto_IsKeyboxOrOEMCertValid(); -} - OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength, - SecurityLevel) { + RequestedSecurityLevel) { return ::OEMCrypto_GetDeviceID(deviceID, idLength); } OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength, - SecurityLevel) { + RequestedSecurityLevel) { return ::OEMCrypto_GetKeyData(keyData, keyDataLength); } -uint32_t OEMCrypto_APIVersion(SecurityLevel) { +uint32_t OEMCrypto_APIVersion(RequestedSecurityLevel) { return ::OEMCrypto_APIVersion(); } -uint8_t OEMCrypto_Security_Patch_Level(SecurityLevel) { +uint8_t OEMCrypto_Security_Patch_Level(RequestedSecurityLevel) { return ::OEMCrypto_Security_Patch_Level(); } -const char* OEMCrypto_SecurityLevel(SecurityLevel) { +OEMCrypto_Security_Level OEMCrypto_SecurityLevel(RequestedSecurityLevel) { return ::OEMCrypto_SecurityLevel(); } OEMCryptoResult OEMCrypto_GetHDCPCapability( - SecurityLevel, OEMCrypto_HDCP_Capability* current, + RequestedSecurityLevel, OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* maximum) { return ::OEMCrypto_GetHDCPCapability(current, maximum); } -bool OEMCrypto_SupportsUsageTable(SecurityLevel) { +bool OEMCrypto_SupportsUsageTable(RequestedSecurityLevel) { return ::OEMCrypto_SupportsUsageTable(); } -bool OEMCrypto_IsAntiRollbackHwPresent(SecurityLevel) { +bool OEMCrypto_IsAntiRollbackHwPresent(RequestedSecurityLevel) { return ::OEMCrypto_IsAntiRollbackHwPresent(); } -OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(SecurityLevel, +OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(RequestedSecurityLevel, size_t* count) { return ::OEMCrypto_GetNumberOfOpenSessions(count); } -OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(SecurityLevel, +OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(RequestedSecurityLevel, size_t* maximum) { return ::OEMCrypto_GetMaxNumberOfSessions(maximum); } -OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(SecurityLevel) { +OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod(RequestedSecurityLevel) { return ::OEMCrypto_GetProvisioningMethod(); } -uint32_t OEMCrypto_SupportedCertificates(SecurityLevel level) { +uint32_t OEMCrypto_SupportedCertificates(RequestedSecurityLevel level) { return ::OEMCrypto_SupportedCertificates(); } -OEMCryptoResult OEMCrypto_CreateUsageTableHeader(SecurityLevel level, +OEMCryptoResult OEMCrypto_CreateUsageTableHeader(RequestedSecurityLevel level, uint8_t* header_buffer, size_t* header_buffer_length) { return ::OEMCrypto_CreateUsageTableHeader(header_buffer, header_buffer_length); } -OEMCryptoResult OEMCrypto_LoadUsageTableHeader(SecurityLevel level, +OEMCryptoResult OEMCrypto_LoadUsageTableHeader(RequestedSecurityLevel level, const uint8_t* buffer, size_t buffer_length) { return ::OEMCrypto_LoadUsageTableHeader(buffer, buffer_length); } -OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(SecurityLevel level, +OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(RequestedSecurityLevel level, uint32_t new_table_size, uint8_t* header_buffer, size_t* header_buffer_length) { @@ -100,25 +124,26 @@ OEMCryptoResult OEMCrypto_ShrinkUsageTableHeader(SecurityLevel level, header_buffer_length); } -uint32_t OEMCrypto_GetAnalogOutputFlags(SecurityLevel) { +uint32_t OEMCrypto_GetAnalogOutputFlags(RequestedSecurityLevel) { return ::OEMCrypto_GetAnalogOutputFlags(); } -const char* OEMCrypto_BuildInformation(SecurityLevel) { - return ::OEMCrypto_BuildInformation(); +OEMCryptoResult OEMCrypto_BuildInformation(char* buffer, size_t* buffer_length, + RequestedSecurityLevel) { + return ::OEMCrypto_BuildInformation(buffer, buffer_length); } -uint32_t OEMCrypto_ResourceRatingTier(SecurityLevel) { +uint32_t OEMCrypto_ResourceRatingTier(RequestedSecurityLevel) { return ::OEMCrypto_ResourceRatingTier(); } -uint32_t OEMCrypto_SupportsDecryptHash(SecurityLevel) { +uint32_t OEMCrypto_SupportsDecryptHash(RequestedSecurityLevel) { return ::OEMCrypto_SupportsDecryptHash(); } OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(uint8_t* public_cert, size_t* public_cert_length, - SecurityLevel) { + RequestedSecurityLevel) { return ::OEMCrypto_GetOEMPublicCertificate(public_cert, public_cert_length); } @@ -136,12 +161,20 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys_Back_Compat( license_type); } -size_t OEMCrypto_MaximumUsageTableHeaderSize(SecurityLevel) { +size_t OEMCrypto_MaximumUsageTableHeaderSize(RequestedSecurityLevel) { return ::OEMCrypto_MaximumUsageTableHeaderSize(); } -uint32_t OEMCrypto_MinorAPIVersion(SecurityLevel) { +uint32_t OEMCrypto_MinorAPIVersion(RequestedSecurityLevel) { return ::OEMCrypto_MinorAPIVersion(); } +OEMCrypto_WatermarkingSupport OEMCrypto_GetWatermarkingSupport( + RequestedSecurityLevel) { + return ::OEMCrypto_GetWatermarkingSupport(); +} + +OEMCryptoResult OEMCrypto_ProductionReady(RequestedSecurityLevel) { + return ::OEMCrypto_ProductionReady(); +} } // namespace wvcdm diff --git a/core/src/oemcrypto_ota_stubs.cpp b/core/src/oemcrypto_ota_stubs.cpp new file mode 100644 index 00000000..2c0b59de --- /dev/null +++ b/core/src/oemcrypto_ota_stubs.cpp @@ -0,0 +1,18 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include "OEMCryptoCENC.h" + +extern "C" OEMCryptoResult OEMCrypto_GenerateOTARequest( + OEMCrypto_SESSION session, uint8_t* buffer, size_t* buffer_length, + uint32_t use_test_key) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} + +extern "C" OEMCryptoResult OEMCrypto_ProcessOTAKeybox(OEMCrypto_SESSION session, + const uint8_t* buffer, + size_t buffer_length, + uint32_t use_test_key) { + return OEMCrypto_ERROR_NOT_IMPLEMENTED; +} diff --git a/core/src/okp_fallback_policy.cpp b/core/src/okp_fallback_policy.cpp new file mode 100644 index 00000000..dbb80193 --- /dev/null +++ b/core/src/okp_fallback_policy.cpp @@ -0,0 +1,234 @@ +// 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 "okp_fallback_policy.h" + +#include + +#include "cdm_random.h" +#include "device_files.h" +#include "file_store.h" +#include "log.h" +#include "wv_cdm_types.h" + +namespace wvcdm { +namespace okp { +using UniqueLock = std::unique_lock; +namespace { +constexpr int64_t kErrorTime = -1; +} // namespace + +// static +std::unique_ptr SystemFallbackPolicy::Create() { + std::unique_ptr fs(new wvutil::FileSystem()); + std::unique_ptr device_files(new DeviceFiles(fs.get())); + if (!device_files->Init(kSecurityLevelL1)) { + LOGE("Failed to initialize device files"); + return nullptr; + } + std::unique_ptr policy(new SystemFallbackPolicy()); + policy->fs_ = std::move(fs); + policy->device_files_ = std::move(device_files); + policy->TryRestore(); + return policy; +} + +// static +std::unique_ptr SystemFallbackPolicy::CreateForTesting( + wvutil::Clock* clock) { + std::unique_ptr policy(new SystemFallbackPolicy()); + if (clock != nullptr) { + policy->SetClockForTesting(clock); + } + // Device files are not supported for test instances. + return policy; +} + +// static +std::unique_ptr SystemFallbackPolicy::CreateForTesting( + const SystemFallbackInfo& info, wvutil::Clock* clock) { + std::unique_ptr policy(new SystemFallbackPolicy()); + if (clock != nullptr) { + policy->SetClockForTesting(clock); + } + policy->info_ = info; + // Device files are not supported for test instances. + return policy; +} + +SystemFallbackPolicy::SystemFallbackPolicy() : clock_(), clock_ref_(&clock_) {} + +SystemFallbackPolicy::~SystemFallbackPolicy() { StoreInfo(); } + +void SystemFallbackPolicy::TryRestore() { + if (!device_files_->RetrieveOkpInfo(&info_)) { + info_.Clear(); + return; + } + LOGI("Restored OKP info: state = %s", + okp::SystemStateToString(info_.state())); + // Calling will end fallback mode if the backoff period has elapsed. + IsInFallbackMode(); +} + +void SystemFallbackPolicy::StoreInfo() { + if (IsTestMode()) { + // Testing instances may not set |device_files_|. + LOGV("Test instance, not storing"); + return; + } + device_files_->StoreOkpInfo(info_); +} + +// Can enter kNeedsProvisioning state from any other state other than +// kFallbackMode. +void SystemFallbackPolicy::MarkNeedsProvisioning() { + UniqueLock lock(mutex_); + switch (state()) { + case SystemState::kNeedsProvisioning: + case SystemState::kFallbackMode: + return; // Nothing to do. + case SystemState::kProvisioned: + case SystemState::kUnknown: + break; + default: + // Should not happen, but if it does continue anyways. + LOGW("Unknown state: %d", static_cast(state())); + } + info_.Clear(); + info_.SetFirstCheckedTime(GetCurrentTime()); + info_.SetState(SystemState::kNeedsProvisioning); + StoreInfo(); +} + +// Can only enter kFallbackMode if in state kNeedsProvisioning +void SystemFallbackPolicy::TriggerFallback() { + UniqueLock lock(mutex_); + switch (state()) { + case SystemState::kNeedsProvisioning: + case SystemState::kUnknown: // Should not happen. + break; // OK to fallback + case SystemState::kFallbackMode: + // Already in fallback mode. Expected if there are multiple + // engines are attempting OKP and fail for the same reason. + return; + case SystemState::kProvisioned: { + LOGW("Cannot fallback, already provisioned"); + return; + } + default: + LOGE("Unexpected state: %d", static_cast(state())); + return; + } + info_.SetState(SystemState::kFallbackMode); + const int64_t current_time = GetCurrentTime(); + if (!info_.HasFirstCheckedTime()) { + info_.SetFirstCheckedTime(current_time); + } + info_.SetBackoffStartTime(GetCurrentTime()); + if (!fast_fallback_ && info_.HasBackoffDuration()) { + // Doubling backoff duration for exponential backoff. Except when + // performing fast fallback off. + info_.DoubleBackoffDuration(); + } else { + info_.SetBackoffDuration(GenerateInitialBackoffDuration()); + } + StoreInfo(); +} + +void SystemFallbackPolicy::MarkProvisioned() { + UniqueLock lock(mutex_); + if (state() == SystemState::kProvisioned) return; + info_.SetState(SystemState::kProvisioned); + const int64_t current_time = GetCurrentTime(); + if (!info_.HasFirstCheckedTime()) { + info_.SetFirstCheckedTime(current_time); + } + info_.SetProvisioningTime(current_time); + info_.ClearBackoffStartTime(); + StoreInfo(); +} + +bool SystemFallbackPolicy::IsProvisioned() { + UniqueLock lock(mutex_); + return state() == SystemState::kProvisioned; +} + +bool SystemFallbackPolicy::IsInFallbackMode() { + UniqueLock lock(mutex_); + if (state() != SystemState::kFallbackMode) return false; + // Check if fallback period has ended. + const int64_t backoff_duration = info_.backoff_duration(); + const int64_t current_backoff_length = GetSecondsSinceBackoffStart(); + if (backoff_duration == 0 || current_backoff_length == kErrorTime) { + // Possible error condition if device files and/or system clock is + // modified. + LOGE( + "Unexpected backoff state, ending backoff: backoff_duration = " + "%" PRId64 ", current_backoff_length = %" PRId64, + backoff_duration, current_backoff_length); + EndBackoffPeriod(); + } else if (current_backoff_length < backoff_duration) { + // Still in backoff period, resume fallback mode. + return true; + } else { + // Backoff period has ended, may attempt OKP again. + LOGD("Backoff period has ended"); + EndBackoffPeriod(); + } + StoreInfo(); + return false; // Only stored if previously in fallback and has ended. +} + +void SystemFallbackPolicy::SetDefaultBackoffDurationRules() { + UniqueLock lock(mutex_); + fast_fallback_ = false; + if (state() == SystemState::kFallbackMode) { + LOGI("Ending fallback"); + EndBackoffPeriod(); + } + info_.ClearBackoffDuration(); + StoreInfo(); +} + +void SystemFallbackPolicy::SetFastBackoffDurationRules() { + UniqueLock lock(mutex_); + fast_fallback_ = true; + if (state() == SystemState::kFallbackMode) { + LOGI("Ending fallback"); + EndBackoffPeriod(); + } + info_.ClearBackoffDuration(); + StoreInfo(); +} + +int64_t SystemFallbackPolicy::GenerateInitialBackoffDuration() { + if (fast_fallback_) { + return kFastBackoffDuration; + } + // Use a random backoff period to avoid server spam across all devices. + return static_cast(wvutil::CdmRandom::RandomInRange( + kMinInitialBackoffDuration, kMaxInitialBackoffDuration)); +} + +int64_t SystemFallbackPolicy::GetSecondsSinceBackoffStart() const { + if (!info_.HasBackoffStartTime()) return 0; + const int64_t backoff_start_time = info_.backoff_start_time(); + const int64_t current_time = GetCurrentTime(); + if (current_time < backoff_start_time) { + LOGE("Current time is less than start of backoff"); + return kErrorTime; + } + return current_time - backoff_start_time; +} + +void SystemFallbackPolicy::EndBackoffPeriod() { + info_.SetState(SystemState::kNeedsProvisioning); + info_.ClearBackoffStartTime(); +} + +bool SystemFallbackPolicy::IsTestMode() const { + return !static_cast(device_files_); +} +} // namespace okp +} // namespace wvcdm diff --git a/core/src/okp_info.cpp b/core/src/okp_info.cpp new file mode 100644 index 00000000..30edfb4e --- /dev/null +++ b/core/src/okp_info.cpp @@ -0,0 +1,32 @@ +// 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 "okp_info.h" + +namespace wvcdm { +namespace okp { +const char* SystemStateToString(SystemState state) { + switch (state) { + case SystemState::kNeedsProvisioning: + return "NeedsProvisioning"; + case SystemState::kFallbackMode: + return "FallbackMode"; + case SystemState::kProvisioned: + return "Provisioned"; + case SystemState::kUnknown: + default: + return "Unknown"; + } +} + +bool SystemFallbackInfo::operator==(const SystemFallbackInfo& other) const { + if (this == &other) return true; + if (state_ != other.state_) return false; + if (first_checked_time_ != other.first_checked_time_) return false; + if (backoff_start_time_ != other.backoff_start_time_) return false; + if (backoff_duration_ != other.backoff_duration_) return false; + if (provisioning_time_ != other.provisioning_time_) return false; + return true; +} +} // namespace okp +} // namespace wvcdm diff --git a/core/src/ota_keybox_provisioner.cpp b/core/src/ota_keybox_provisioner.cpp new file mode 100644 index 00000000..1cbf035d --- /dev/null +++ b/core/src/ota_keybox_provisioner.cpp @@ -0,0 +1,282 @@ +// 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 "ota_keybox_provisioner.h" + +#include + +#include + +#include "certificate_provisioning.h" +#include "crypto_session.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "okp_fallback_policy.h" +#include "properties.h" +#include "string_conversions.h" + +namespace wvcdm { +using video_widevine::ProvisioningRequest; +using video_widevine::ProvisioningResponse; +using video_widevine::SignedProvisioningMessage; +using OtaRequest = ProvisioningRequest::AndroidAttestationOtaKeyboxRequest; +using OtaResponse = ProvisioningResponse::AndroidAttestationOtaKeyboxResponse; +namespace { +// Indicates not to use the test keybox for OTA provisioning. +constexpr bool kProductionKeybox = false; +const CdmAppParameterMap kEmptyAppParameters; +const std::string kEmptyString; +} // namespace + +// static +std::unique_ptr OtaKeyboxProvisioner::Create( + metrics::CryptoMetrics* crypto_metrics) { + if (crypto_metrics == nullptr) { + LOGE("Input |crypto_metrics| is null"); + return nullptr; + } + // Get system fallback policy. + okp::SystemFallbackPolicy* fallback_policy = + CryptoSession::GetOkpFallbackPolicy(); + if (fallback_policy == nullptr) { + LOGE("No system fallback policy"); + return nullptr; + } + // Setup crypto session. + std::unique_ptr crypto_session( + CryptoSession::MakeCryptoSession(crypto_metrics)); + crypto_session->Open(kLevelDefault); + if (!crypto_session->IsOpen()) { + LOGE("Failed to open crypto session for OKP provisioner"); + return nullptr; + } + const CdmSecurityLevel security_level = crypto_session->GetSecurityLevel(); + if (security_level != kSecurityLevelL1) { + LOGE("Failed to open L1 crypto session: security_level = %d", + static_cast(security_level)); + crypto_session.reset(); + return nullptr; + } + std::unique_ptr engine_provisioner( + new OtaKeyboxProvisioner(std::move(crypto_session), fallback_policy)); + if (!engine_provisioner->Init()) { + LOGE("Failed to initialize OKP provisioner"); + return nullptr; + } + return engine_provisioner; +} + +// static +std::unique_ptr OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy) { + if (!crypto_session) { + LOGE("Input |crypto_metrics| is null"); + return nullptr; + } + if (fallback_policy == nullptr) { + LOGE("Input |fallback_policy| is null"); + return nullptr; + } + std::unique_ptr engine_provisioner( + new OtaKeyboxProvisioner(std::move(crypto_session), fallback_policy)); + if (!engine_provisioner->Init()) { + LOGE("Failed to initialize OKP provisioner"); + return nullptr; + } + return engine_provisioner; +} + +OtaKeyboxProvisioner::OtaKeyboxProvisioner( + std::unique_ptr&& crypto_session, + okp::SystemFallbackPolicy* fallback_policy) + : crypto_session_(std::move(crypto_session)), + fallback_policy_(fallback_policy) { + assert(static_cast(crypto_session_)); + assert(fallback_policy != nullptr); +} + +OtaKeyboxProvisioner::~OtaKeyboxProvisioner() { + // If we sent a request, and did not get a response, and we still need to + // provision, then there was an error and we should trigger fallback. + if (request_generated_ && (!response_received_) && (!IsProvisioned())) { + LOGE("OTA Provisioning not processed. Falling back to L3"); + fallback_policy_->TriggerFallback(); + } + crypto_session_.reset(); + fallback_policy_ = nullptr; +} + +bool OtaKeyboxProvisioner::Init() { + const CdmResponseType result = + client_id_.InitForOtaKeyboxProvisioning(crypto_session_.get()); + if (result != NO_ERROR) { + LOGE("Failed to initialize OKP client ID"); + return false; + } + return true; +} + +bool OtaKeyboxProvisioner::IsProvisioned() const { + return fallback_policy_->IsProvisioned(); +} + +bool OtaKeyboxProvisioner::IsInFallbackMode() const { + return fallback_policy_->IsInFallbackMode(); +} + +CdmResponseType OtaKeyboxProvisioner::GetProvisioningRequest( + std::string* request, std::string* default_url) { + if (request == nullptr) { + LOGE("Output |request| is null"); + return PARAMETER_NULL; + } + if (default_url == nullptr) { + LOGE("Output |default_url| is null"); + return PARAMETER_NULL; + } + if (IsProvisioned()) { + LOGW("Already provisioned"); + CleanUp(); + return OKP_ALREADY_PROVISIONED; + } + if (!crypto_session_) { + LOGE("Crypto session has been released, OKP unavailable"); + // Caller should not reuse provisioner after failure. + return UNKNOWN_ERROR; + } + // Step 1: Generate raw request from OEMCrypto. + std::string ota_request_data; + CdmResponseType result = crypto_session_->PrepareOtaProvisioningRequest( + kProductionKeybox, &ota_request_data); + if (result == NOT_IMPLEMENTED_ERROR) { + LOGW("OKP is not supported by OEMCrypto"); + fallback_policy_->TriggerFallback(); + CleanUp(); + return result; + } else if (result != NO_ERROR) { + LOGE("Failed to generate OKP request: status = %d", + static_cast(result)); + return result; + } + // Step 2: Wrap in ProvisioningRequest. + ProvisioningRequest prov_request; + auto* client_id = prov_request.mutable_client_id(); + result = client_id_.Prepare(kEmptyAppParameters, kEmptyString, client_id); + if (result != NO_ERROR) { + LOGW("Failed to prepare client ID, continuing without: result = %d", + static_cast(result)); + client_id->Clear(); + } + LOGI("OTA request generated"); + LOGV("ota_request_data = %s", wvutil::b2a_hex(ota_request_data).c_str()); + OtaRequest* ota_request = prov_request.mutable_android_ota_keybox_request(); + ota_request->set_ota_request(ota_request_data); + + // Step 3: Wrap in SignedProvisioningMessage. + SignedProvisioningMessage signed_request; + std::string message; + prov_request.SerializeToString(&message); + signed_request.set_message(message); + signed_request.set_provisioning_type( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA); + signed_request.SerializeToString(request); + + if (!wvcdm::Properties::provisioning_messages_are_binary()) { + *request = wvutil::Base64SafeEncodeNoPad( + std::vector(request->begin(), request->end())); + } + + request_generated_ = true; + CertificateProvisioning::GetProvisioningServerUrl(default_url); + return NO_ERROR; +} + +CdmResponseType OtaKeyboxProvisioner::HandleProvisioningResponse( + const std::string& response) { + if (response.empty()) { + LOGE("Signed provisioning message is empty"); + return EMPTY_PROVISIONING_RESPONSE; + } + if (IsProvisioned()) { + LOGD("Already provisioned"); + response_received_ = true; + CleanUp(); + return NO_ERROR; + } + if (!request_generated_) { + LOGE("Received response without generating request"); + return UNKNOWN_ERROR; + } + if (!crypto_session_) { + LOGE("Crypto session has been released, OKP unavailable"); + // Caller should not reuse provisioner after failure. + return UNKNOWN_ERROR; + } + std::string decoded_response; + if (!wvcdm::Properties::provisioning_messages_are_binary()) { + if (!CertificateProvisioning::ExtractAndDecodeSignedMessage( + response, &decoded_response)) { + LOGE("Failed to extract OKP provisioning response"); + return PARSE_OKP_RESPONSE_ERROR; + } + } else { + decoded_response = response; + } + // Step 1: Unwrap from SignedProvisioningMessage; + SignedProvisioningMessage signed_response; + if (!signed_response.ParseFromString(decoded_response)) { + LOGE("Failed to parse SignedProvisioningMessage"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (!signed_response.has_message()) { + LOGE("Signed response is missing message"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (signed_response.provisioning_type() != + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA) { + LOGE("Unexpected protocol type/version: protocol_type = %d", + static_cast(signed_response.provisioning_type())); + return PARSE_OKP_RESPONSE_ERROR; + } + // Step 2: Unwrap from ProvisioningResponse. + ProvisioningResponse prov_response; + if (!prov_response.ParseFromString(signed_response.message())) { + LOGE("Failed to parse ProvisioningResponse"); + return PARSE_OKP_RESPONSE_ERROR; + } + if (!prov_response.has_android_ota_keybox_response()) { + LOGE("Missing OTA keybox response message"); + return PARSE_OKP_RESPONSE_ERROR; + } + const OtaResponse& ota_response = prov_response.android_ota_keybox_response(); + if (!ota_response.has_ota_response()) { + LOGE("Missing OTA keybox response data"); + return PARSE_OKP_RESPONSE_ERROR; + } + // Step 3: Load response. + const std::string ota_response_data = ota_response.ota_response(); + if (ota_response_data.empty()) { + LOGE("Raw OTA response is empty"); + return PARSE_OKP_RESPONSE_ERROR; + } + const CdmResponseType result = crypto_session_->LoadOtaProvisioning( + kProductionKeybox, ota_response_data); + if (result == NO_ERROR) { + LOGI("OTA response successfully processed"); + LOGV("ota_response_data = %s", wvutil::b2a_hex(ota_response_data).c_str()); + fallback_policy_->MarkProvisioned(); + response_received_ = true; + } else { + fallback_policy_->TriggerFallback(); + } + CleanUp(); + return result; +} + +void OtaKeyboxProvisioner::CleanUp() { + if (crypto_session_) { + crypto_session_.reset(); + } +} +} // namespace wvcdm diff --git a/core/src/policy_engine.cpp b/core/src/policy_engine.cpp index b244c675..60d6e836 100644 --- a/core/src/policy_engine.cpp +++ b/core/src/policy_engine.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "policy_engine.h" @@ -37,7 +37,7 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id, event_listener_(event_listener), license_keys_(new LicenseKeys(crypto_session->GetSecurityLevel())), policy_timers_(new PolicyTimersV15), - clock_(new Clock) { + clock_(new wvutil::Clock) { InitDevice(crypto_session); } @@ -48,7 +48,7 @@ bool PolicyEngine::CanDecryptContent(const KeyId& key_id) { return license_keys_->CanDecryptContent(key_id); } else { LOGE("Provided content key is not in license: key_id = %s", - b2a_hex(key_id).c_str()); + wvutil::b2a_hex(key_id).c_str()); return false; } } @@ -307,6 +307,8 @@ CdmResponseType PolicyEngine::Query(CdmQueryMap* query_response) { : QUERY_VALUE_FALSE; (*query_response)[QUERY_KEY_LICENSE_DURATION_REMAINING] = std::to_string( policy_timers_->GetLicenseOrRentalDurationRemaining(current_time)); + (*query_response)[QUERY_KEY_RENTAL_DURATION_REMAINING] = std::to_string( + policy_timers_->GetLicenseOrRentalDurationRemaining(current_time)); (*query_response)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = std::to_string( policy_timers_->GetPlaybackDurationRemaining(current_time)); (*query_response)[QUERY_KEY_RENEWAL_SERVER_URL] = @@ -422,7 +424,7 @@ int64_t PolicyEngine::GetCurrentTime() { return current_time; } -void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); } +void PolicyEngine::set_clock(wvutil::Clock* clock) { clock_.reset(clock); } void PolicyEngine::SetSecurityLevelForTest(CdmSecurityLevel security_level) { license_keys_->SetSecurityLevelForTest(security_level); diff --git a/core/src/policy_timers.cpp b/core/src/policy_timers.cpp index 84ba84da..55c54b4a 100644 --- a/core/src/policy_timers.cpp +++ b/core/src/policy_timers.cpp @@ -1,6 +1,6 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "policy_timers.h" diff --git a/core/src/policy_timers_v15.cpp b/core/src/policy_timers_v15.cpp index a8a33f01..f917c061 100644 --- a/core/src/policy_timers_v15.cpp +++ b/core/src/policy_timers_v15.cpp @@ -1,6 +1,6 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "policy_timers_v15.h" diff --git a/core/src/policy_timers_v16.cpp b/core/src/policy_timers_v16.cpp index 6af479d3..4395a562 100644 --- a/core/src/policy_timers_v16.cpp +++ b/core/src/policy_timers_v16.cpp @@ -1,6 +1,6 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "policy_timers_v16.h" diff --git a/core/src/privacy_crypto_apple.cpp b/core/src/privacy_crypto_apple.cpp index 99582eaa..788b1fad 100644 --- a/core/src/privacy_crypto_apple.cpp +++ b/core/src/privacy_crypto_apple.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // Description: // Privacy crypto implementation for iOS. This fully implements the diff --git a/core/src/privacy_crypto_boringssl.cpp b/core/src/privacy_crypto_boringssl.cpp index 1d9db5b1..86ddea95 100644 --- a/core/src/privacy_crypto_boringssl.cpp +++ b/core/src/privacy_crypto_boringssl.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // Description: // Definition of classes representing RSA public keys used @@ -29,7 +29,7 @@ const int kRsaPkcs1OaepPaddingLength = 41; RSA* GetKey(const std::string& serialized_key) { BIO* bio = BIO_new_mem_buf(const_cast(serialized_key.data()), - serialized_key.size()); + static_cast(serialized_key.size())); if (bio == nullptr) { LOGE("BIO_new_mem_buf failed: returned null"); return nullptr; @@ -123,11 +123,11 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out, } out->resize(in.size() + AES_BLOCK_SIZE); - int out_length = out->size(); + int out_length = static_cast(out->size()); if (EVP_EncryptUpdate( evp_cipher_ctx, reinterpret_cast(&(*out)[0]), &out_length, reinterpret_cast(const_cast(in.data())), - in.size()) == 0) { + static_cast(in.size())) == 0) { LOGE("AES CBC encryption failure: %s", ERR_error_string(ERR_get_error(), nullptr)); EVP_CIPHER_CTX_free(evp_cipher_ctx); @@ -195,7 +195,7 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message, encrypted_message->assign(rsa_size, 0); if (RSA_public_encrypt( - clear_message.size(), + static_cast(clear_message.size()), const_cast( reinterpret_cast(clear_message.data())), reinterpret_cast(&(*encrypted_message)[0]), key, @@ -212,9 +212,9 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message, // LogBoringSSLError is a callback from BoringSSL which is called with each // error in the thread's error queue. -static int LogBoringSSLError(const char* msg, size_t /* len */, - void* /* ctx */) { - LOGE(" %s", msg); +static int LogBoringSSLError(const char* msg, size_t /* length */, + void* /* user_data */) { + LOGE(" BoringSSL Error: %s", msg); return 1; } @@ -324,7 +324,7 @@ bool ExtractExtensionValueFromCertificate(const std::string& cert, reinterpret_cast(cert.data()); long cert_size = static_cast(cert.size()); boringssl_ptr pkcs7( - d2i_PKCS7(NULL, &cert_data, cert_size)); + d2i_PKCS7(nullptr, &cert_data, cert_size)); if (!pkcs7) { LOGE("Error parsing PKCS7 message"); return false; @@ -345,7 +345,8 @@ bool ExtractExtensionValueFromCertificate(const std::string& cert, return false; } - X509* const intermediate_cert = sk_X509_value(certs, cert_index); + X509* const intermediate_cert = + sk_X509_value(certs, static_cast(cert_index)); if (intermediate_cert == nullptr) { LOGE("Unable to get intermediate cert"); return false; diff --git a/core/src/privacy_crypto_dummy.cpp b/core/src/privacy_crypto_dummy.cpp index 87412a95..8b100c59 100644 --- a/core/src/privacy_crypto_dummy.cpp +++ b/core/src/privacy_crypto_dummy.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // // Description: // Dummy version of privacy crypto classes for systems which diff --git a/core/src/properties.cpp b/core/src/properties.cpp index e1bd17af..1eeb8e17 100644 --- a/core/src/properties.cpp +++ b/core/src/properties.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "properties.h" diff --git a/core/src/service_certificate.cpp b/core/src/service_certificate.cpp index 55ab57c4..2d2947bf 100644 --- a/core/src/service_certificate.cpp +++ b/core/src/service_certificate.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "service_certificate.h" @@ -129,10 +129,10 @@ namespace wvcdm { // Protobuf generated classes. using video_widevine::ClientIdentification; -using video_widevine::DrmDeviceCertificate; +using video_widevine::DrmCertificate; using video_widevine::EncryptedClientIdentification; using video_widevine::LicenseError; -using video_widevine::SignedDrmDeviceCertificate; +using video_widevine::SignedDrmCertificate; using video_widevine::SignedMessage; CdmResponseType ServiceCertificate::Init(const std::string& certificate) { @@ -140,12 +140,12 @@ CdmResponseType ServiceCertificate::Init(const std::string& certificate) { sizeof(kRootCertForProd)); // Load root cert public key. Don't bother verifying it. - SignedDrmDeviceCertificate signed_root_cert; + SignedDrmCertificate signed_root_cert; if (!signed_root_cert.ParseFromString(root_cert_str)) { LOGE("Failed to deserialize signed root certificate"); return DEVICE_CERTIFICATE_ERROR_1; } - DrmDeviceCertificate root_cert; + DrmCertificate root_cert; if (!root_cert.ParseFromString(signed_root_cert.drm_certificate())) { LOGE("Failed to deserialize root certificate"); return DEVICE_CERTIFICATE_ERROR_1; @@ -158,7 +158,7 @@ CdmResponseType ServiceCertificate::Init(const std::string& certificate) { // Load the provided service certificate. // First, parse it and verify its signature. - SignedDrmDeviceCertificate signed_service_cert; + SignedDrmCertificate signed_service_cert; if (!signed_service_cert.ParseFromString(certificate)) { LOGE("Failed to parse signed service certificate"); return DEVICE_CERTIFICATE_ERROR_2; @@ -174,19 +174,17 @@ CdmResponseType ServiceCertificate::Init(const std::string& certificate) { } #endif - DrmDeviceCertificate service_cert; + DrmCertificate service_cert; if (!service_cert.ParseFromString(signed_service_cert.drm_certificate())) { LOGE("Failed to parse service certificate"); return DEVICE_CERTIFICATE_ERROR_2; } - if (service_cert.type() != - video_widevine::DrmDeviceCertificate_CertificateType_SERVICE) { + if (service_cert.type() != video_widevine::DrmCertificate_Type_SERVICE) { LOGE( "DRM device certificate type is not a service certificate: " "type = %d, expected_type = %d", static_cast(service_cert.type()), - static_cast( - video_widevine::DrmDeviceCertificate_CertificateType_SERVICE)); + static_cast(video_widevine::DrmCertificate_Type_SERVICE)); return DEVICE_CERTIFICATE_ERROR_3; } @@ -208,7 +206,7 @@ CdmResponseType ServiceCertificate::Init(const std::string& certificate) { } CdmResponseType ServiceCertificate::VerifySignedMessage( - const std::string& message, const std::string& signature) { + const std::string& message, const std::string& signature) const { if (!public_key_) { LOGE("Service certificate not set"); return DEVICE_CERTIFICATE_ERROR_4; @@ -220,8 +218,8 @@ CdmResponseType ServiceCertificate::VerifySignedMessage( return NO_ERROR; } -CdmResponseType ServiceCertificate::EncryptRsaOaep(const std::string& plaintext, - std::string* ciphertext) { +CdmResponseType ServiceCertificate::EncryptRsaOaep( + const std::string& plaintext, std::string* ciphertext) const { if (!public_key_) { LOGE("Service certificate not set"); return DEVICE_CERTIFICATE_ERROR_4; @@ -235,7 +233,7 @@ CdmResponseType ServiceCertificate::EncryptRsaOaep(const std::string& plaintext, CdmResponseType ServiceCertificate::EncryptClientId( CryptoSession* crypto_session, const ClientIdentification* clear_client_id, - EncryptedClientIdentification* encrypted_client_id) { + EncryptedClientIdentification* encrypted_client_id) const { encrypted_client_id->set_provider_id(provider_id_); encrypted_client_id->set_service_certificate_serial_number(serial_number_); diff --git a/core/src/system_id_extractor.cpp b/core/src/system_id_extractor.cpp new file mode 100644 index 00000000..33da5757 --- /dev/null +++ b/core/src/system_id_extractor.cpp @@ -0,0 +1,194 @@ +// 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 "system_id_extractor.h" + +#include + +#include "crypto_session.h" +#include "device_files.h" +#include "privacy_crypto.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" + +namespace wvcdm { +namespace { +// Offset within the keybox data where the device ID is stored. +constexpr size_t kKeyboxSystemIdOffset = 4; +// Index of certificate within cerificate chain which contains the +// system ID (0 = leaf/device cert, 1 = intermediate/device family cert). +constexpr size_t kOemCertSystemIdIndex = 1; +// OID of X.509 certificate extension containing the Widevine system ID. +const std::string kWidevineSystemIdExtensionOid = "1.3.6.1.4.1.11129.4.1.1"; + +constexpr size_t kSystemIdLength = sizeof(uint32_t); + +constexpr bool IsSupportedSecurityLevel(CdmSecurityLevel security_level) { + return security_level == kSecurityLevelL1 || + security_level == kSecurityLevelL2 || + security_level == kSecurityLevelL3; +} +} // namespace + +SystemIdExtractor::SystemIdExtractor(RequestedSecurityLevel security_level, + CryptoSession* crypto_session, + wvutil::FileSystem* fs) + : security_level_(security_level), + crypto_session_(crypto_session), + fs_(fs) { + assert(crypto_session != nullptr); + assert(fs != nullptr); +} + +bool SystemIdExtractor::ExtractSystemId(uint32_t* system_id) { + if (system_id == nullptr) { + LOGE("Output |system_id| is null"); + return false; + } + if (crypto_session_->GetCachedSystemId(system_id)) { + return true; + } + CdmClientTokenType type = kClientTokenUninitialized; + const CdmResponseType status = + crypto_session_->GetProvisioningMethod(security_level_, &type); + if (status != NO_ERROR) { + LOGE("Failed to get provisioning method: security_level = %s, status = %d", + RequestedSecurityLevelToString(security_level_), + static_cast(status)); + return false; + } + bool success = false; + switch (type) { + case kClientTokenDrmCert: + LOGW( + "Cannot get a system ID from a DRM certificate, " + "using null system ID: security_level = %s", + RequestedSecurityLevelToString(security_level_)); + *system_id = NULL_SYSTEM_ID; + return true; + case kClientTokenKeybox: + success = ExtractSystemIdProv20(system_id); + break; + case kClientTokenOemCert: + success = ExtractSystemIdProv30(system_id); + break; + case kClientTokenBootCertChain: + success = ExtractSystemIdProv40(system_id); + break; + case kClientTokenUninitialized: + default: + LOGE("Unexpected token type: %d", type); + return false; + } + if (success && *system_id != NULL_SYSTEM_ID) { + crypto_session_->SetSystemId(*system_id); + } + return success; +} + +// static +bool SystemIdExtractor::ExtractSystemIdFromKeyboxData( + const std::string& key_data, uint32_t* system_id) { + if (system_id == nullptr) return false; + if (key_data.size() < (kKeyboxSystemIdOffset + kSystemIdLength)) { + LOGE("Keybox data does not contain system ID: key_data_size = %zu", + key_data.size()); + return false; + } + uint32_t system_id_nbo = 0; + memcpy(&system_id_nbo, &key_data[kKeyboxSystemIdOffset], + sizeof(system_id_nbo)); + *system_id = ntohl(system_id_nbo); + return true; +} + +// static +bool SystemIdExtractor::ExtractSystemIdFromOemCert(const std::string& oem_cert, + uint32_t* system_id) { + if (system_id == nullptr) return false; + return ExtractExtensionValueFromCertificate(oem_cert, + kWidevineSystemIdExtensionOid, + kOemCertSystemIdIndex, system_id); +} + +bool SystemIdExtractor::ExtractSystemIdProv20(uint32_t* system_id) { + std::string key_data; + const CdmResponseType status = + crypto_session_->GetTokenFromKeybox(security_level_, &key_data); + if (status == NEED_PROVISIONING) { + LOGD("Keybox provisioning required, using null system ID"); + *system_id = NULL_SYSTEM_ID; + return true; + } + if (status != NO_ERROR) { + LOGE("Failed to get keybox data: security_level = %s, status = %d", + RequestedSecurityLevelToString(security_level_), + static_cast(status)); + return false; + } + if (!ExtractSystemIdFromKeyboxData(key_data, system_id)) { + LOGE("Failed to extract system ID from keybox data"); + return false; + } + return true; +} + +bool SystemIdExtractor::ExtractSystemIdProv30(uint32_t* system_id) { + std::string oem_cert; + const CdmResponseType status = + crypto_session_->GetTokenFromOemCert(security_level_, &oem_cert); + if (status != NO_ERROR) { + LOGE("Failed to get OEM certificate: security_level = %s, status = %d", + RequestedSecurityLevelToString(security_level_), + static_cast(status)); + return false; + } + if (!ExtractSystemIdFromOemCert(oem_cert, system_id)) { + LOGE("Failed to extract system ID from OEM certificate"); + return false; + } + return true; +} + +bool SystemIdExtractor::ExtractSystemIdProv40(uint32_t* system_id) { + const CdmSecurityLevel security_level = + crypto_session_->GetSecurityLevel(security_level_); + if (!IsSupportedSecurityLevel(security_level)) { + LOGE("Unsupported security level: %s", + CdmSecurityLevelToString(security_level)); + return false; + } + DeviceFiles real_device_files(fs_); + // Mock DeviceFiles for testing. + DeviceFiles& device_files = + (test_device_files_ ? *test_device_files_ : real_device_files); + if (!device_files.Init(security_level)) { + LOGE("Failed to initialize device files: security_level = %s", + CdmSecurityLevelToString(security_level)); + return false; + } + std::string oem_cert; + CryptoWrappedKey wrapped_private_key_unused; + const DeviceFiles::CertificateState cert_state = + device_files.RetrieveOemCertificate(&oem_cert, + &wrapped_private_key_unused); + if (cert_state == DeviceFiles::kCertificateNotFound) { + LOGD("No OEM certificate available, using null system ID"); + *system_id = NULL_SYSTEM_ID; + return true; + } + if (cert_state != DeviceFiles::kCertificateValid) { + LOGE( + "Failed to retrieve OEM certificate: " + "security_level = %s, cert_state = %s", + CdmSecurityLevelToString(security_level), + DeviceFiles::CertificateStateToString(cert_state)); + return false; + } + if (!ExtractSystemIdFromOemCert(oem_cert, system_id)) { + LOGE("Failed to extract system ID from OEM certificate"); + return false; + } + return true; +} +} // namespace wvcdm diff --git a/core/src/usage_table_header.cpp b/core/src/usage_table_header.cpp index ecbcecfe..f0cc7969 100644 --- a/core/src/usage_table_header.cpp +++ b/core/src/usage_table_header.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "usage_table_header.h" @@ -14,14 +14,12 @@ #include "wv_cdm_constants.h" namespace wvcdm { - namespace { -std::string kEmptyString; -wvcdm::CdmKeySetId kDummyKeySetId = "DummyKsid"; -std::string kOldUsageEntryServerMacKey(wvcdm::MAC_KEY_SIZE, 0); -std::string kOldUsageEntryClientMacKey(wvcdm::MAC_KEY_SIZE, 0); -std::string kOldUsageEntryPoviderSessionToken = - "nahZ6achSheiqua3TohQuei0ahwohv"; +using TableLock = std::unique_lock; + +const std::string kEmptyString; +const wvcdm::CdmKeySetId kDummyKeySetId = "DummyKsid"; + constexpr int64_t kDefaultExpireDuration = 33 * 24 * 60 * 60; // 33 Days // Fraction of table capacity of number of unexpired offline licenses // before they are considered to be removed. This could occur if @@ -89,8 +87,8 @@ bool RetrieveOfflineLicense(DeviceFiles* device_files, DeviceFiles::CdmLicenseData license_data; DeviceFiles::ResponseType result = DeviceFiles::kNoError; if (!device_files->RetrieveLicense(key_set_id, &license_data, &result)) { - LOGW("Failed to retrieve license: key_set_id = %s, result = %d", - key_set_id.c_str(), static_cast(result)); + LOGW("Failed to retrieve license: key_set_id = %s, result = %s", + IdToString(key_set_id), DeviceFiles::ResponseTypeToString(result)); return false; } *license_message = std::move(license_data.license); @@ -118,14 +116,16 @@ bool RetrieveUsageInfoLicense(DeviceFiles* device_files, CdmUsageEntry usage_entry; std::string provider_session_token; CdmKeyMessage license_request; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; if (!device_files->RetrieveUsageInfoByKeySetId( usage_info_file_name, key_set_id, &provider_session_token, - &license_request, license_message, &usage_entry, - usage_entry_number)) { + &license_request, license_message, &usage_entry, usage_entry_number, + &drm_certificate, &wrapped_private_key)) { LOGW( "Failed to retrieve usage information: " "key_set_id = %s, usage_info_file_name = %s", - key_set_id.c_str(), usage_info_file_name.c_str()); + IdToString(key_set_id), IdToString(usage_info_file_name)); return false; } return true; @@ -140,170 +140,112 @@ bool EntryIsOfflineLicense(const CdmUsageEntryInfo& info) { // Used for stl filters. return info.storage_type == kStorageLicense; } + +bool IsValidCdmSecurityLevelForUsageInfo(CdmSecurityLevel security_level) { + return security_level == kSecurityLevelL1 || + security_level == kSecurityLevelL3; +} + +RequestedSecurityLevel CdmSecurityLevelToRequestedLevel( + CdmSecurityLevel security_level) { + return security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault; +} } // namespace -UsageTableHeader::UsageTableHeader() - : security_level_(kSecurityLevelUninitialized), - requested_security_level_(kLevelDefault), - is_inited_(false), - clock_ref_(&clock_) { - file_system_.reset(new FileSystem()); +UsageTableHeader::UsageTableHeader() : clock_ref_(&clock_) { + file_system_.reset(new wvutil::FileSystem()); device_files_.reset(new DeviceFiles(file_system_.get())); } bool UsageTableHeader::Init(CdmSecurityLevel security_level, CryptoSession* crypto_session) { - LOGI("Initializing usage table header: security_level = %d", - static_cast(security_level)); + LOGD("security_level = %s", CdmSecurityLevelToString(security_level)); if (crypto_session == nullptr) { LOGE("No crypto session provided"); return false; } - - switch (security_level) { - case kSecurityLevelL1: - case kSecurityLevelL3: - break; - default: - LOGE("Invalid security level provided: security_level = %d", - static_cast(security_level)); - return false; + if (is_initialized_) { + LOGE("Cannot reinitialize usage table: security_level = %s", + CdmSecurityLevelToString(security_level)); + return false; + } + if (!IsValidCdmSecurityLevelForUsageInfo(security_level)) { + LOGE("Invalid security level provided: security_level = %s", + CdmSecurityLevelToString(security_level)); + return false; } - security_level_ = security_level; - requested_security_level_ = - security_level_ == kSecurityLevelL3 ? kLevel3 : kLevelDefault; + requested_security_level_ = CdmSecurityLevelToRequestedLevel(security_level); - if (!crypto_session->GetMaximumUsageTableEntries( - requested_security_level_, &potential_table_capacity_)) { - LOGW( - "Could not determine usage table capacity, assuming default: " - "default = %zu", - kMinimumUsageTableEntriesSupported); - potential_table_capacity_ = kMinimumUsageTableEntriesSupported; - } else if (potential_table_capacity_ == 0) { - LOGD("Usage table capacity is unlimited: security_level = %d", - static_cast(security_level)); - } else if (potential_table_capacity_ < kMinimumUsageTableEntriesSupported) { - LOGW( - "Reported usage table capacity is smaller than minimally required: " - "capacity = %zu, minimum = %zu", - potential_table_capacity_, kMinimumUsageTableEntriesSupported); - potential_table_capacity_ = kMinimumUsageTableEntriesSupported; - } else { - LOGD("Usage table capacity: %zu, security_level = %d", - potential_table_capacity_, static_cast(security_level)); + if (!OpenSessionCheck(crypto_session)) { + return false; + } + if (!DetermineTableCapacity(crypto_session)) { + return false; } - if (!device_files_->Init(security_level)) { LOGE("Failed to initialize device files"); return false; } + // Attempt restoring first, if unable to restore, then create a new + // table. + return RestoreTable(crypto_session) || CreateNewTable(crypto_session); +} - CdmResponseType status = USAGE_INFO_NOT_FOUND; - metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); - if (metrics == nullptr) metrics = &alternate_crypto_metrics_; - +bool UsageTableHeader::RestoreTable(CryptoSession* const crypto_session) { bool run_lru_upgrade = false; - if (device_files_->RetrieveUsageTableInfo( + if (!device_files_->RetrieveUsageTableInfo( &usage_table_header_, &usage_entry_info_, &run_lru_upgrade)) { - LOGI("Number of usage entries: %zu", usage_entry_info_.size()); - status = crypto_session->LoadUsageTableHeader(requested_security_level_, - usage_table_header_); + LOGW("Could not retrieve usage table"); + return false; + } + LOGI("Found usage table to restore: entry_count = %zu", + usage_entry_info_.size()); - bool lru_success = true; - if (status == NO_ERROR && run_lru_upgrade) { - // If the loaded table info does not contain LRU information, then - // the information must be added immediately before being used. - if (!LruUpgradeAllUsageEntries()) { - LOGE( - "Unable to init usage table header: " - "Failed to perform LRU upgrade to usage entry table"); - lru_success = false; - } - } - - // If the usage table header has been successfully loaded, and is - // at minimum capacity (>200) or the table size does not have a - // hard limit, we need to make sure we can still add and remove - // entries. If not, clear files/data and recreate usage header - // table. - if (status == NO_ERROR && lru_success) { - if ((HasUnlimitedTableCapacity() && - usage_entry_info_.size() > kMinimumUsageTableEntriesSupported) || - (!HasUnlimitedTableCapacity() && - usage_entry_info_.size() > potential_table_capacity())) { - LOGD("Checking if new entry can be added: size = %zu, capacity = %s", - usage_entry_info_.size(), - HasUnlimitedTableCapacity() - ? "unlimited" - : std::to_string(potential_table_capacity()).c_str()); - uint32_t temporary_usage_entry_number; - - // Create a new temporary usage entry, close the session and then - // try to delete it. - CdmResponseType result = NO_ERROR; - { - // |local_crypto_session| points to an object whose scope is this - // method or a test object whose scope is the lifetime of this class - std::unique_ptr scoped_crypto_session; - CryptoSession* local_crypto_session = test_crypto_session_.get(); - if (local_crypto_session == nullptr) { - scoped_crypto_session.reset( - CryptoSession::MakeCryptoSession(metrics)); - local_crypto_session = scoped_crypto_session.get(); - } - - result = local_crypto_session->Open(requested_security_level_); - if (result == NO_ERROR) { - result = AddEntry(local_crypto_session, true, kDummyKeySetId, - kEmptyString, kEmptyString, - &temporary_usage_entry_number); - } - } - if (result == NO_ERROR) { - result = InvalidateEntry(temporary_usage_entry_number, - /* defrag_table = */ true, - device_files_.get(), metrics); - if (usage_entry_info_.size() > temporary_usage_entry_number) { - // The entry should have been deleted from the usage table, - // not just marked as type unknown. Failure to call - // Shrink() may be an indicator of other issues. - LOGE("Temporary entry was not deleted"); - result = UNKNOWN_ERROR; - } - } - if (result != NO_ERROR) { - LOGE( - "Unable to create/delete new entry, clearing usage entries: " - "security_level = %d, usage_entry_count = %zu", - static_cast(security_level), usage_entry_info_.size()); - status = result; - } - } - } - - if (status != NO_ERROR || !lru_success) { - LOGE("Failed to load usage table: security_level = %d, status = %d", - static_cast(security_level), static_cast(status)); - device_files_->DeleteAllLicenses(); - device_files_->DeleteAllUsageInfo(); - device_files_->DeleteUsageTableInfo(); - usage_entry_info_.clear(); - usage_table_header_.clear(); - status = crypto_session->CreateUsageTableHeader(requested_security_level_, - &usage_table_header_); - if (status != NO_ERROR) return false; - StoreTable(device_files_.get()); - } - } else { - status = crypto_session->CreateUsageTableHeader(requested_security_level_, - &usage_table_header_); - if (status != NO_ERROR) return false; - StoreTable(device_files_.get()); + const CdmResponseType status = crypto_session->LoadUsageTableHeader( + requested_security_level_, usage_table_header_); + if (status != NO_ERROR) { + LOGE("Failed to load usage table header: sts = %d", + static_cast(status)); + return false; } - is_inited_ = true; + // If the saved usage entries/meta data is missing LRU information, + // then the entries and their meta data must be updated. + if (run_lru_upgrade && !LruUpgradeAllUsageEntries()) { + LOGE("Failed to perform LRU upgrade to usage entry table"); + return false; + } + + if (!CapacityCheck(crypto_session)) { + LOGE("Cannot restore table due to failing capacity check"); + return false; + } + is_initialized_ = true; + return true; +} + +bool UsageTableHeader::CreateNewTable(CryptoSession* const crypto_session) { + LOGD("Removing all usage table files"); + // Existing files need to be deleted to avoid attempts to restore + // licenses which no longer have a usage entry. + device_files_->DeleteAllLicenses(); + device_files_->DeleteAllUsageInfo(); + device_files_->DeleteUsageTableInfo(); + usage_entry_info_.clear(); + usage_table_header_.clear(); + + const CdmResponseType status = crypto_session->CreateUsageTableHeader( + requested_security_level_, &usage_table_header_); + if (status != NO_ERROR) { + LOGE("Failed to create new usage table header"); + return false; + } + if (!StoreTable()) { + LOGE("Failed to store new usage table header"); + return false; + } + is_initialized_ = true; return true; } @@ -311,72 +253,39 @@ CdmResponseType UsageTableHeader::AddEntry( CryptoSession* crypto_session, bool persistent_license, const CdmKeySetId& key_set_id, const std::string& usage_info_file_name, const CdmKeyResponse& license_message, uint32_t* usage_entry_number) { - LOGI("Adding usage entry"); + LOGD("key_set_id = %s, type = %s, current_size = %zu", IdToString(key_set_id), + persistent_license ? "OfflineLicense" : "Streaming", + usage_entry_info_.size()); metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); if (metrics == nullptr) metrics = &alternate_crypto_metrics_; + TableLock auto_lock(usage_table_header_lock_); - CdmResponseType status = crypto_session->CreateUsageEntry(usage_entry_number); - + CdmResponseType status = CreateEntry(crypto_session, usage_entry_number); if (status == INSUFFICIENT_CRYPTO_RESOURCES) { LOGW("Usage table may be full, releasing oldest entry: size = %zu", usage_entry_info_.size()); status = ReleaseOldestEntry(metrics); if (status == NO_ERROR) { - status = crypto_session->CreateUsageEntry(usage_entry_number); + status = CreateEntry(crypto_session, usage_entry_number); } } - if (status != NO_ERROR) return status; - LOGI("Locking to add entry"); - std::unique_lock auto_lock(usage_table_header_lock_); - if (*usage_entry_number < usage_entry_info_.size()) { - LOGE( - "New entry number is smaller than table size: " - "entry_info_number = %u, table_size = %zu", - *usage_entry_number, usage_entry_info_.size()); - return USAGE_INVALID_NEW_ENTRY; - } + status = RelocateNewEntry(crypto_session, usage_entry_number); + if (status != NO_ERROR) return status; - if (*usage_entry_number > usage_entry_info_.size()) { - LOGW( - "New entry number is larger than table size, resizing: " - "entry_info_number = %u, table_size = %zu", - *usage_entry_number, usage_entry_info_.size()); - const size_t number_of_entries = usage_entry_info_.size(); - usage_entry_info_.resize(*usage_entry_number + 1); - for (size_t i = number_of_entries; i < usage_entry_info_.size() - 1; ++i) { - usage_entry_info_[i].Clear(); - } - } else /* *usage_entry_number == usage_entry_info_.size() */ { - usage_entry_info_.resize(*usage_entry_number + 1); - } - - usage_entry_info_[*usage_entry_number].storage_type = - persistent_license ? kStorageLicense : kStorageUsageInfo; - usage_entry_info_[*usage_entry_number].key_set_id = key_set_id; - usage_entry_info_[*usage_entry_number].last_use_time = GetCurrentTime(); - if (!persistent_license) { - usage_entry_info_[*usage_entry_number].usage_info_file_name = - usage_info_file_name; - usage_entry_info_[*usage_entry_number].offline_license_expiry_time = 0; + if (persistent_license) { + SetOfflineEntryInfo(*usage_entry_number, key_set_id, license_message); } else { - // Need to determine the expire time for offline licenses. - video_widevine::License license; - if (license_message.size() > 0 && - ParseLicenseFromLicenseMessage(license_message, &license)) { - const video_widevine::License::Policy& policy = license.policy(); - usage_entry_info_[*usage_entry_number].offline_license_expiry_time = - license.license_start_time() + policy.rental_duration_seconds() + - policy.playback_duration_seconds(); - } else { - // If the license duration cannot be determined for any reason, it - // is assumed to last at most 33 days. - usage_entry_info_[*usage_entry_number].offline_license_expiry_time = - usage_entry_info_[*usage_entry_number].last_use_time + - kDefaultExpireDuration; - } + SetUsageInfoEntryInfo(*usage_entry_number, key_set_id, + usage_info_file_name); + } + + status = RefitTable(crypto_session); + if (status != NO_ERROR) { + usage_entry_info_[*usage_entry_number].Clear(); + return status; } // Call to update the usage table header, but don't store the usage @@ -390,9 +299,7 @@ CdmResponseType UsageTableHeader::AddEntry( usage_entry_info_[*usage_entry_number].Clear(); return status; } - - LOGI("New usage entry: usage_entry_number = %u", *usage_entry_number); - StoreTable(device_files_.get()); + StoreTable(); return NO_ERROR; } @@ -400,7 +307,7 @@ CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session, const CdmUsageEntry& usage_entry, uint32_t usage_entry_number) { { - LOGI("Locking to load entry: usage_entry_number = %u", usage_entry_number); + LOGD("usage_entry_number = %u", usage_entry_number); std::unique_lock auto_lock(usage_table_header_lock_); if (usage_entry_number >= usage_entry_info_.size()) { @@ -427,7 +334,7 @@ CdmResponseType UsageTableHeader::LoadEntry(CryptoSession* crypto_session, CdmResponseType UsageTableHeader::UpdateEntry(uint32_t usage_entry_number, CryptoSession* crypto_session, CdmUsageEntry* usage_entry) { - LOGI("Locking to update entry"); + LOGD("usage_entry_number = %u", usage_entry_number); std::unique_lock auto_lock(usage_table_header_lock_); if (usage_entry_number >= usage_entry_info_.size()) { LOGE("Usage entry number %u is larger than usage entry size %zu", @@ -441,16 +348,22 @@ CdmResponseType UsageTableHeader::UpdateEntry(uint32_t usage_entry_number, if (status != NO_ERROR) return status; usage_entry_info_[usage_entry_number].last_use_time = GetCurrentTime(); - StoreTable(device_files_.get()); + StoreTable(); return NO_ERROR; } CdmResponseType UsageTableHeader::InvalidateEntry( uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, metrics::CryptoMetrics* metrics) { - LOGI("Locking to invalidate entry: usage_entry_number = %u", - usage_entry_number); - std::unique_lock auto_lock(usage_table_header_lock_); + LOGD("usage_entry_number = %u", usage_entry_number); + TableLock auto_lock(usage_table_header_lock_); + return InvalidateEntryInternal(usage_entry_number, defrag_table, device_files, + metrics); +} + +CdmResponseType UsageTableHeader::InvalidateEntryInternal( + uint32_t usage_entry_number, bool defrag_table, DeviceFiles* device_files, + metrics::CryptoMetrics* metrics) { // OEMCrypto does not have any concept of "deleting" an entry. // Instead, the CDM marks the entry's meta data as invalid (storage // type unknown) and then performs a "defrag" of the OEMCrypto table. @@ -478,41 +391,282 @@ CdmResponseType UsageTableHeader::InvalidateEntry( // changes to the table, and as a result, it will not store the // invalidated entry. LOGD("Table was not stored during defrag, storing now"); - StoreTable(device_files); + StoreTable(); } if (status == SYSTEM_INVALIDATED_ERROR) { LOGE("Invalidate entry failed due to system invalidation error"); return SYSTEM_INVALIDATED_ERROR; } } else { - StoreTable(device_files); + StoreTable(); } return NO_ERROR; } size_t UsageTableHeader::UsageInfoCount() const { - LOGI("Locking to count usage info (streaming license) entries"); - std::unique_lock auto_lock(usage_table_header_lock_); + LOGV("Locking to count usage info (streaming license) entries"); return std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), EntryIsUsageInfo); } size_t UsageTableHeader::OfflineEntryCount() const { - LOGI("Locking to count offline license entries"); - std::unique_lock auto_lock(usage_table_header_lock_); + LOGV("Locking to count offline license entries"); return std::count_if(usage_entry_info_.cbegin(), usage_entry_info_.cend(), EntryIsOfflineLicense); } +bool UsageTableHeader::OpenSessionCheck(CryptoSession* const crypto_session) { + // The UsageTableHeader for the specified |requested_security_level_| + // MUST be initialized before any sessions are opened. + size_t session_count = 0; + const CdmResponseType status = crypto_session->GetNumberOfOpenSessions( + requested_security_level_, &session_count); + if (status != NO_ERROR || session_count > 0) { + LOGE( + "Cannot initialize usage table header with open crypto session: " + "status = %d, count = %zu", + static_cast(status), session_count); + return false; + } + return true; +} + +bool UsageTableHeader::CapacityCheck(CryptoSession* const crypto_session) { + // If the table is around capacity or if unlimited and the table is + // larger than the minimally required capacity, then a test must be + // performed to ensure that the usage table is not in a state which + // will prevent create operations. + const size_t capacity_threshold = HasUnlimitedTableCapacity() + ? kMinimumUsageTableEntriesSupported + : potential_table_capacity(); + if (usage_entry_info_.size() <= capacity_threshold) { + // No need to perform test if below capacity. + return true; + } + + metrics::CryptoMetrics* metrics = crypto_session->GetCryptoMetrics(); + if (metrics == nullptr) metrics = &alternate_crypto_metrics_; + // |local_crypto_session| points to an object whose scope is this + // method or a test object whose scope is the lifetime of this class + CryptoSession* local_crypto_session = test_crypto_session_.get(); + std::unique_ptr scoped_crypto_session; + if (local_crypto_session == nullptr) { + scoped_crypto_session.reset(CryptoSession::MakeCryptoSession(metrics)); + local_crypto_session = scoped_crypto_session.get(); + } + + CdmResponseType status = + local_crypto_session->Open(requested_security_level_); + if (status != NO_ERROR) { + LOGE("Failed to open crypto session for capacity test: sts = %d", + static_cast(status)); + return false; + } + + uint32_t temporary_usage_entry_number; + status = AddEntry(local_crypto_session, true, kDummyKeySetId, kEmptyString, + kEmptyString, &temporary_usage_entry_number); + if (status != NO_ERROR) { + LOGE("Failed to add entry for capacity test: sts = %d", + static_cast(status)); + return false; + } + + status = + InvalidateEntry(temporary_usage_entry_number, + /* defrag_table = */ true, device_files_.get(), metrics); + if (status != NO_ERROR) { + LOGE("Failed to invalidate entry for capacity test: sts = %d", + static_cast(status)); + return false; + } + if (usage_entry_info_.size() > temporary_usage_entry_number) { + // The entry should have been deleted from the usage table, + // not just marked as type unknown. Failure to call + // Shrink() may be an indicator of other issues. + LOGE("Failed to shrink table for capacity test"); + return false; + } + return true; +} + +bool UsageTableHeader::DetermineTableCapacity(CryptoSession* crypto_session) { + if (!crypto_session->GetMaximumUsageTableEntries( + requested_security_level_, &potential_table_capacity_)) { + LOGW( + "Could not determine usage table capacity, assuming default: " + "default = %zu", + kMinimumUsageTableEntriesSupported); + potential_table_capacity_ = kMinimumUsageTableEntriesSupported; + } else if (potential_table_capacity_ == 0) { + LOGD("capacity = unlimited, security_level = %s", + CdmSecurityLevelToString(security_level_)); + } else if (potential_table_capacity_ < kMinimumUsageTableEntriesSupported) { + LOGW( + "Reported usage table capacity is smaller than minimally required: " + "capacity = %zu, minimum = %zu", + potential_table_capacity_, kMinimumUsageTableEntriesSupported); + potential_table_capacity_ = kMinimumUsageTableEntriesSupported; + } else { + LOGD("capacity = %zu, security_level = %s", potential_table_capacity_, + CdmSecurityLevelToString(security_level_)); + } + return true; +} + +CdmResponseType UsageTableHeader::CreateEntry( + CryptoSession* const crypto_session, uint32_t* usage_entry_number) { + const CdmResponseType status = + crypto_session->CreateUsageEntry(usage_entry_number); + if (status != NO_ERROR) return status; + // If the new entry number is smaller than expected, then the usage + // table may be out of sync or OEMCrypto has been rolled back. + // Not safe to continue. + if (*usage_entry_number < usage_entry_info_.size()) { + LOGE( + "New entry number is smaller than table size: " + "entry_info_number = %u, table_size = %zu", + *usage_entry_number, usage_entry_info_.size()); + return USAGE_INVALID_NEW_ENTRY; + } + LOGI("usage_entry_number = %u", *usage_entry_number); + const size_t previous_size = usage_entry_info_.size(); + usage_entry_info_.resize(*usage_entry_number + 1); + if (*usage_entry_number > previous_size) { + LOGW( + "New entry number is larger than table size, resizing: " + "entry_info_number = %u, table_size = %zu", + *usage_entry_number, previous_size); + for (size_t i = previous_size; i < usage_entry_info_.size() - 1; ++i) { + usage_entry_info_[i].Clear(); + } + } + usage_entry_info_[*usage_entry_number].Clear(); + return NO_ERROR; +} + +CdmResponseType UsageTableHeader::RelocateNewEntry( + CryptoSession* const crypto_session, uint32_t* usage_entry_number) { + static constexpr uint32_t kMinimumEntryNumber = 0; + const uint32_t initial_entry_number = *usage_entry_number; + if (initial_entry_number == kMinimumEntryNumber) { + // First entry in the table. + return NO_ERROR; + } + uint32_t unoccupied_entry_number = initial_entry_number; + for (uint32_t i = kMinimumEntryNumber; i < initial_entry_number; i++) { + if (IsEntryUnoccupied(i)) { + unoccupied_entry_number = i; + break; + } + } + if (unoccupied_entry_number == initial_entry_number) { + // No open position. + return NO_ERROR; + } + const CdmResponseType status = + crypto_session->MoveUsageEntry(unoccupied_entry_number); + if (status == MOVE_USAGE_ENTRY_DESTINATION_IN_USE) { + // Not unexpected, there is a window of time between releasing the + // entry and closing the OEMCrypto session. + LOGD("Released entry still in use: index = %u", unoccupied_entry_number); + return NO_ERROR; + } + if (status != NO_ERROR) return status; + LOGI("Entry moved: from_index = %u, to_index = %u", initial_entry_number, + unoccupied_entry_number); + *usage_entry_number = unoccupied_entry_number; + usage_entry_info_[unoccupied_entry_number] = + std::move(usage_entry_info_[initial_entry_number]); + usage_entry_info_[initial_entry_number].Clear(); + return NO_ERROR; +} + +bool UsageTableHeader::IsEntryUnoccupied( + const uint32_t usage_entry_number) const { + if (usage_entry_info_[usage_entry_number].storage_type != + kStorageTypeUnknown) { + return false; + } + // TODO(sigquit): Check that entry is not in use by another session. + // NOTE: The |storage_type| check will protect the integrity of the + // entry. Attempting to use an entry index that is used by another + // session is recoverable and will not affect any opened sessions. + return true; +} + +void UsageTableHeader::SetOfflineEntryInfo( + const uint32_t usage_entry_number, const std::string& key_set_id, + const CdmKeyResponse& license_message) { + CdmUsageEntryInfo& entry_info = usage_entry_info_[usage_entry_number]; + entry_info.Clear(); + entry_info.storage_type = kStorageLicense; + entry_info.key_set_id = key_set_id; + entry_info.last_use_time = GetCurrentTime(); + // Need to determine the expire time for offline licenses. + video_widevine::License license; + if (!license_message.empty() && + ParseLicenseFromLicenseMessage(license_message, &license)) { + const video_widevine::License::Policy& policy = license.policy(); + entry_info.offline_license_expiry_time = license.license_start_time() + + policy.rental_duration_seconds() + + policy.playback_duration_seconds(); + } else { + // If the license duration cannot be determined for any reason, it + // is assumed to last at most 33 days. + entry_info.offline_license_expiry_time = + entry_info.last_use_time + kDefaultExpireDuration; + } +} + +void UsageTableHeader::SetUsageInfoEntryInfo( + const uint32_t usage_entry_number, const std::string& key_set_id, + const std::string& usage_info_file_name) { + CdmUsageEntryInfo& entry_info = usage_entry_info_[usage_entry_number]; + entry_info.Clear(); + entry_info.storage_type = kStorageUsageInfo; + entry_info.key_set_id = key_set_id; + entry_info.last_use_time = GetCurrentTime(); + entry_info.usage_info_file_name = usage_info_file_name; +} + +CdmResponseType UsageTableHeader::RefitTable( + CryptoSession* const crypto_session) { + // Remove all unoccupied entries at end of the table. + uint32_t entries_to_remove = 0; + const uint32_t old_size = static_cast(usage_entry_info_.size()); + for (uint32_t i = 0; i < old_size; i++) { + const uint32_t usage_entry_number = old_size - i - 1; + if (!IsEntryUnoccupied(usage_entry_number)) break; + ++entries_to_remove; + } + if (entries_to_remove == 0) return NO_ERROR; + const uint32_t new_size = old_size - entries_to_remove; + const CdmResponseType status = crypto_session->ShrinkUsageTableHeader( + requested_security_level_, new_size, &usage_table_header_); + if (status == SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE) { + // This error likely indicates that another session has released + // its entry via a call to InvalidateEntry(), but has yet to close + // its OEMCrypto session. + // Safe to assume table state is not invalidated. + LOGW("Unexpected entry in use: range = [%u, %zu]", new_size, + usage_entry_info_.size() - 1); + return NO_ERROR; + } + if (status != NO_ERROR) return status; + LOGD("Table shrunk: old_size = %zu, new_size = %u", usage_entry_info_.size(), + new_size); + usage_entry_info_.resize(new_size); + return NO_ERROR; +} + CdmResponseType UsageTableHeader::MoveEntry( uint32_t from_usage_entry_number, const CdmUsageEntry& from_usage_entry, uint32_t to_usage_entry_number, DeviceFiles* device_files, metrics::CryptoMetrics* metrics) { - LOGI( - "Moving usage entry: " - "from_usage_entry_number = %u, to_usage_entry_number = %u", - from_usage_entry_number, to_usage_entry_number); + LOGD("from_usage_entry_number = %u, to_usage_entry_number = %u", + from_usage_entry_number, to_usage_entry_number); // crypto_session points to an object whose scope is this method or a test // object whose scope is the lifetime of this class @@ -563,7 +717,7 @@ CdmResponseType UsageTableHeader::MoveEntry( } // Store the usage table and usage entry after successful move. - StoreTable(device_files); + StoreTable(); StoreEntry(to_usage_entry_number, device_files, usage_entry); return NO_ERROR; @@ -572,11 +726,11 @@ CdmResponseType UsageTableHeader::MoveEntry( CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, DeviceFiles* device_files, CdmUsageEntry* usage_entry) { - LOGI("Getting usage entry: usage_entry_number = %u, storage_type: %d", - usage_entry_number, - static_cast(usage_entry_number < usage_entry_info_.size() - ? usage_entry_info_[usage_entry_number].storage_type - : kStorageTypeUnknown)); + LOGD("Getting usage_entry_number = %u, storage_type = %s", usage_entry_number, + CdmUsageEntryStorageTypeToString( + usage_entry_number < usage_entry_info_.size() + ? usage_entry_info_[usage_entry_number].storage_type + : kStorageTypeUnknown)); uint32_t entry_number; switch (usage_entry_info_[usage_entry_number].storage_type) { case kStorageLicense: { @@ -598,12 +752,14 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, std::string provider_session_token; CdmKeyMessage license_request; CdmKeyResponse license; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; if (!device_files->RetrieveUsageInfoByKeySetId( usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, &provider_session_token, &license_request, &license, usage_entry, - &entry_number)) { + &entry_number, &drm_certificate, &wrapped_private_key)) { LOGE("Failed to retrieve usage information"); return USAGE_GET_ENTRY_RETRIEVE_USAGE_INFO_FAILED; } @@ -632,11 +788,11 @@ CdmResponseType UsageTableHeader::GetEntry(uint32_t usage_entry_number, CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, DeviceFiles* device_files, const CdmUsageEntry& usage_entry) { - LOGI("Storing usage entry: usage_entry_number = %u, storage type: %d", - usage_entry_number, - static_cast(usage_entry_number < usage_entry_info_.size() - ? usage_entry_info_[usage_entry_number].storage_type - : kStorageTypeUnknown)); + LOGD("usage_entry_number = %u, storage_type = %s", usage_entry_number, + CdmUsageEntryStorageTypeToString( + usage_entry_number < usage_entry_info_.size() + ? usage_entry_info_[usage_entry_number].storage_type + : kStorageTypeUnknown)); switch (usage_entry_info_[usage_entry_number].storage_type) { case kStorageLicense: { @@ -646,8 +802,8 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, if (!device_files->RetrieveLicense( usage_entry_info_[usage_entry_number].key_set_id, &license_data, &sub_error_code)) { - LOGE("Failed to retrieve license: status = %d", - static_cast(sub_error_code)); + LOGE("Failed to retrieve license: status = %s", + DeviceFiles::ResponseTypeToString(sub_error_code)); return USAGE_STORE_ENTRY_RETRIEVE_LICENSE_FAILED; } @@ -656,8 +812,8 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, license_data.usage_entry_number = usage_entry_number; if (!device_files->StoreLicense(license_data, &sub_error_code)) { - LOGE("Failed to store license: status = %d", - static_cast(sub_error_code)); + LOGE("Failed to store license: status = %s", + DeviceFiles::ResponseTypeToString(sub_error_code)); return USAGE_STORE_LICENSE_FAILED; } break; @@ -667,11 +823,13 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, uint32_t entry_number; std::string provider_session_token, init_data, key_request, key_response, key_renewal_request; + std::string drm_certificate; + CryptoWrappedKey wrapped_private_key; if (!device_files->RetrieveUsageInfoByKeySetId( usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, &provider_session_token, &key_request, &key_response, &entry, - &entry_number)) { + &entry_number, &drm_certificate, &wrapped_private_key)) { LOGE("Failed to retrieve usage information"); return USAGE_STORE_ENTRY_RETRIEVE_USAGE_INFO_FAILED; } @@ -682,7 +840,7 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, provider_session_token, key_request, key_response, usage_entry_info_[usage_entry_number].usage_info_file_name, usage_entry_info_[usage_entry_number].key_set_id, usage_entry, - usage_entry_number)) { + usage_entry_number, drm_certificate, wrapped_private_key)) { LOGE("Failed to store usage information"); return USAGE_STORE_USAGE_INFO_FAILED; } @@ -699,10 +857,10 @@ CdmResponseType UsageTableHeader::StoreEntry(uint32_t usage_entry_number, return NO_ERROR; } -bool UsageTableHeader::StoreTable(DeviceFiles* device_files) { +bool UsageTableHeader::StoreTable() { LOGV("Storing usage table information"); - const bool result = - device_files->StoreUsageTableInfo(usage_table_header_, usage_entry_info_); + const bool result = device_files_->StoreUsageTableInfo(usage_table_header_, + usage_entry_info_); if (result) { ++store_table_counter_; } else { @@ -714,8 +872,8 @@ bool UsageTableHeader::StoreTable(DeviceFiles* device_files) { CdmResponseType UsageTableHeader::Shrink( metrics::CryptoMetrics* metrics, uint32_t number_of_usage_entries_to_delete) { - LOGI("Shrinking usage table: table_size = %zu, number_to_delete = %u", - usage_entry_info_.size(), number_of_usage_entries_to_delete); + LOGD("table_size = %zu, number_to_delete = %u", usage_entry_info_.size(), + number_of_usage_entries_to_delete); if (usage_entry_info_.empty()) { LOGE("Usage entry info table unexpectedly empty"); return NO_USAGE_ENTRIES; @@ -726,7 +884,8 @@ CdmResponseType UsageTableHeader::Shrink( "Cannot delete more entries than the table size, reducing to current " "table size: table_size = %zu, number_to_delete = %u", usage_entry_info_.size(), number_of_usage_entries_to_delete); - number_of_usage_entries_to_delete = usage_entry_info_.size(); + number_of_usage_entries_to_delete = + static_cast(usage_entry_info_.size()); } if (number_of_usage_entries_to_delete == 0) return NO_ERROR; @@ -740,21 +899,21 @@ CdmResponseType UsageTableHeader::Shrink( crypto_session = scoped_crypto_session.get(); } - const size_t new_size = - usage_entry_info_.size() - number_of_usage_entries_to_delete; + const uint32_t new_size = static_cast(usage_entry_info_.size()) - + number_of_usage_entries_to_delete; const CdmResponseType status = crypto_session->ShrinkUsageTableHeader( requested_security_level_, new_size, &usage_table_header_); if (status == NO_ERROR) { usage_entry_info_.resize(new_size); - StoreTable(device_files_.get()); + StoreTable(); } return status; } CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, metrics::CryptoMetrics* metrics) { - LOGV("Defragging table: current_size = %zu", usage_entry_info_.size()); + LOGV("current_size = %zu", usage_entry_info_.size()); // Defragging the usage table involves moving valid entries near the // end of the usage table to the position of invalid entries near the // front of the table. After the entries are moved, the CDM shrinks @@ -794,7 +953,8 @@ CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, entries_to_move.size() < entries_to_remove.size(); ++i) { // Search from the end of the table. - const uint32_t entry_index = usage_entry_info_.size() - i - 1; + const uint32_t entry_index = + static_cast(usage_entry_info_.size()) - i - 1; if (usage_entry_info_[entry_index].storage_type != kStorageTypeUnknown) { entries_to_move.push_back(entry_index); } @@ -805,7 +965,7 @@ CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, if (entries_to_move.empty()) { LOGD("No valid entries found, shrinking entire table: size = %zu", usage_entry_info_.size()); - return Shrink(metrics, usage_entry_info_.size()); + return Shrink(metrics, static_cast(usage_entry_info_.size())); } // Step 3: Ignore invalid entries that are after the last valid @@ -824,8 +984,9 @@ CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, // entry. In this case, no movement is required and the table can just // be shrunk to the last valid entry. if (entries_to_remove.empty()) { - const size_t to_remove = usage_entry_info_.size() - last_valid_entry - 1; - LOGD("Removing all entries after the last valid entry: count = %zu", + const uint32_t to_remove = + static_cast(usage_entry_info_.size()) - last_valid_entry - 1; + LOGD("Removing all entries after the last valid entry: count = %u", to_remove); return Shrink(metrics, to_remove); } @@ -937,9 +1098,11 @@ CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, } // End while loop. // Step 5: Find the new last valid entry. - uint32_t new_last_valid_entry = usage_entry_info_.size(); + uint32_t new_last_valid_entry = + static_cast(usage_entry_info_.size()); for (uint32_t i = 0; i < usage_entry_info_.size(); ++i) { - const uint32_t entry_index = usage_entry_info_.size() - i - 1; + const uint32_t entry_index = + static_cast(usage_entry_info_.size()) - i - 1; if (usage_entry_info_[entry_index].storage_type != kStorageTypeUnknown) { new_last_valid_entry = entry_index; break; @@ -953,10 +1116,11 @@ CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, LOGD( "All entries have been invalidated, shrinking entire table: size = %zu", usage_entry_info_.size()); - return Shrink(metrics, usage_entry_info_.size()); + return Shrink(metrics, static_cast(usage_entry_info_.size())); } - const size_t to_remove = usage_entry_info_.size() - new_last_valid_entry - 1; + const uint32_t to_remove = static_cast(usage_entry_info_.size()) - + new_last_valid_entry - 1; // Special case 6: It is possible that the last entry in the table // is valid and currently loaded in the table by another session. @@ -964,12 +1128,12 @@ CdmResponseType UsageTableHeader::DefragTable(DeviceFiles* device_files, // this case, nothing more to do. if (to_remove == 0) { LOGD("Defrag completed without shrinking table"); - StoreTable(device_files); + StoreTable(); return NO_ERROR; } // Step 6: Shrink table to the new size. - LOGD("Clean up complete, shrinking table: count = %zu", to_remove); + LOGD("Clean up complete, shrinking table: count = %u", to_remove); return Shrink(metrics, to_remove); } // End Defrag(). @@ -991,8 +1155,8 @@ CdmResponseType UsageTableHeader::ReleaseOldestEntry( const CdmUsageEntryStorageType storage_type = usage_entry_info.storage_type; const CdmResponseType status = - InvalidateEntry(entry_number_to_delete, /* defrag_table = */ true, - device_files_.get(), metrics); + InvalidateEntryInternal(entry_number_to_delete, /* defrag_table = */ true, + device_files_.get(), metrics); if (status != NO_ERROR) { LOGE("Failed to invalidate oldest entry: status = %d", @@ -1007,7 +1171,7 @@ CdmResponseType UsageTableHeader::ReleaseOldestEntry( // Test only method. void UsageTableHeader::InvalidateEntryForTest(uint32_t usage_entry_number) { - LOGV("Deleting entry for test: usage_entry_number = %u", usage_entry_number); + LOGD("usage_entry_number = %u", usage_entry_number); if (usage_entry_number >= usage_entry_info_.size()) { LOGE( "Requested usage entry number is larger than table size: " @@ -1030,7 +1194,7 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { // end, all problematic licenses will be marked as invalid. std::vector bad_license_file_entries; - for (size_t usage_entry_number = 0; + for (uint32_t usage_entry_number = 0; usage_entry_number < usage_entry_info_.size(); ++usage_entry_number) { CdmUsageEntryInfo& usage_entry_info = usage_entry_info_[usage_entry_number]; @@ -1055,7 +1219,7 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { bad_license_file_entries.push_back(usage_entry_number); continue; default: { - LOGW("Unknown usage entry storage type: %d, usage_entry_number = %zu", + LOGW("Unknown usage entry storage type: %d, usage_entry_number = %u", static_cast(usage_entry_info.storage_type), usage_entry_number); bad_license_file_entries.push_back(usage_entry_number); @@ -1063,7 +1227,7 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { } } if (!retrieve_response) { - LOGW("Could not retrieve license message: usage_entry_number = %zu", + LOGW("Could not retrieve license message: usage_entry_number = %u", usage_entry_number); bad_license_file_entries.push_back(usage_entry_number); continue; @@ -1071,7 +1235,7 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { if (retrieved_entry_number != usage_entry_number) { LOGW( - "Usage entry number mismatched: usage_entry_number = %zu, " + "Usage entry number mismatched: usage_entry_number = %u, " "retrieved_entry_number = %u", usage_entry_number, retrieved_entry_number); bad_license_file_entries.push_back(usage_entry_number); @@ -1080,7 +1244,7 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { video_widevine::License license; if (!ParseLicenseFromLicenseMessage(license_message, &license)) { - LOGW("Could not parse license: usage_entry_number = %zu", + LOGW("Could not parse license: usage_entry_number = %u", usage_entry_number); bad_license_file_entries.push_back(usage_entry_number); continue; @@ -1144,8 +1308,6 @@ bool UsageTableHeader::LruUpgradeAllUsageEntries() { } bool UsageTableHeader::GetRemovalCandidate(uint32_t* entry_to_remove) { - LOGI("Locking to determine removal candidates"); - std::unique_lock auto_lock(usage_table_header_lock_); const size_t lru_unexpired_threshold = HasUnlimitedTableCapacity() ? kLruUnexpiredThresholdFraction * size() diff --git a/core/src/wv_cdm_types.cpp b/core/src/wv_cdm_types.cpp new file mode 100644 index 00000000..3ba2d4b4 --- /dev/null +++ b/core/src/wv_cdm_types.cpp @@ -0,0 +1,160 @@ +// 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_cdm_types.h" + +#include + +#include "wv_cdm_constants.h" + +namespace wvcdm { +namespace { +const char kEmptyIdRep[] = ""; +const char kNullIdRep[] = ""; + +// Thread local buffer used by UnknownEnumValueToString() to represent +// unknown enum values. +thread_local char tl_unknown_rep_buf[32]; + +// Helper template function for casting enums to raw integer before +// formatting into a string representation. +template +const char* UnknownValueRep(EnumType value) { + return UnknownEnumValueToString(static_cast(value)); +} +} // namespace + +const char* CdmCertificateTypeToString(CdmCertificateType type) { + switch (type) { + case kCertificateWidevine: + return "Widevine"; + case kCertificateX509: + return "x509"; + } + return UnknownValueRep(type); +} + +const char* CdmClientTokenTypeToString(CdmClientTokenType type) { + switch (type) { + case kClientTokenKeybox: + return "Keybox"; + case kClientTokenDrmCert: + return "DrmCert"; + case kClientTokenOemCert: + return "OemCert"; + case kClientTokenBootCertChain: + return "BootCertChain"; + case kClientTokenUninitialized: + return "Uninitialized"; + } + return UnknownValueRep(type); +} + +const char* CdmLicenseTypeToString(CdmLicenseType license_type) { + switch (license_type) { + case kLicenseTypeOffline: + return "Offline"; + case kLicenseTypeStreaming: + return "Streaming"; + case kLicenseTypeRelease: + return "Release"; + case kLicenseTypeTemporary: + return "Temporary"; + case kLicenseTypeEmbeddedKeyData: + return "EmbeddedKeyData"; + } + return UnknownValueRep(license_type); +} + +const char* CdmSecurityLevelToString(CdmSecurityLevel security_level) { + switch (security_level) { + case kSecurityLevelUninitialized: + return "Uninitialized"; + case kSecurityLevelL1: + return QUERY_VALUE_SECURITY_LEVEL_L1.c_str(); + case kSecurityLevelL2: + return QUERY_VALUE_SECURITY_LEVEL_L2.c_str(); + case kSecurityLevelL3: + return QUERY_VALUE_SECURITY_LEVEL_L3.c_str(); + case kSecurityLevelUnknown: + return QUERY_VALUE_SECURITY_LEVEL_UNKNOWN.c_str(); + } + return UnknownValueRep(security_level); +} + +const char* CdmOfflineLicenseStateToString( + CdmOfflineLicenseState license_state) { + switch (license_state) { + case kLicenseStateActive: + return "Active"; + case kLicenseStateReleasing: + return "Release"; + case kLicenseStateUnknown: + return "Unknown"; + } + return UnknownValueRep(license_state); +} + +const char* CdmUsageEntryStorageTypeToString(CdmUsageEntryStorageType type) { + switch (type) { + case kStorageLicense: + return "License"; + case kStorageUsageInfo: + return "UsageInfo"; + case kStorageTypeUnknown: + // Special value used to indicate an empty entry. + return "None"; + } + return UnknownValueRep(type); +} + +const char* RequestedSecurityLevelToString( + RequestedSecurityLevel security_level) { + switch (security_level) { + case kLevelDefault: + return QUERY_VALUE_SECURITY_LEVEL_DEFAULT.c_str(); + case kLevel3: + return QUERY_VALUE_SECURITY_LEVEL_L3.c_str(); + } + return UnknownValueRep(security_level); +} + +const char* CdmWatermarkingSupportToString(CdmWatermarkingSupport support) { + switch (support) { + case kWatermarkingNotSupported: + return QUERY_VALUE_NOT_SUPPORTED.c_str(); + case kWatermarkingConfigurable: + return QUERY_VALUE_CONFIGURABLE.c_str(); + case kWatermarkingAlwaysOn: + return QUERY_VALUE_ALWAYS_ON.c_str(); + } + return UnknownValueRep(support); +} + +const char* CdmProductionReadinessToString(CdmProductionReadiness readiness) { + switch (readiness) { + case kProductionReadinessUnknown: + return QUERY_VALUE_UNKNOWN.c_str(); + case kProductionReadinessTrue: + return QUERY_VALUE_TRUE.c_str(); + case kProductionReadinessFalse: + return QUERY_VALUE_FALSE.c_str(); + } + return UnknownValueRep(readiness); +} + +const char* UnknownEnumValueToString(int value) { + snprintf(tl_unknown_rep_buf, sizeof(tl_unknown_rep_buf), "", + value); + return tl_unknown_rep_buf; +} + +const char* IdToString(const std::string& id) { + return id.empty() ? kEmptyIdRep : id.c_str(); +} + +const char* IdPtrToString(const std::string* id) { + if (id == nullptr) return kNullIdRep; + return id->empty() ? kEmptyIdRep : id->c_str(); +} +} // namespace wvcdm diff --git a/core/test/Android.bp b/core/test/Android.bp index 841d1302..c170be40 100644 --- a/core/test/Android.bp +++ b/core/test/Android.bp @@ -12,6 +12,19 @@ // See the License for the specific language governing permissions and$ // limitations under the License. +// *** 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: + // SPDX-license-identifier-Apache-2.0 + // legacy_by_exception_only (by exception only) + default_applicable_licenses: ["vendor_widevine_license"], +} + filegroup { name: "vts_cdm_core_test_srcs", srcs: [ diff --git a/core/test/buffer_reader_test.cpp b/core/test/buffer_reader_test.cpp index 4ebd0e10..14c5307a 100644 --- a/core/test/buffer_reader_test.cpp +++ b/core/test/buffer_reader_test.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include #include diff --git a/core/test/cdm_engine_metrics_decorator_unittest.cpp b/core/test/cdm_engine_metrics_decorator_unittest.cpp index 1c376dfa..b453235f 100644 --- a/core/test/cdm_engine_metrics_decorator_unittest.cpp +++ b/core/test/cdm_engine_metrics_decorator_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This fil and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // These tests are for the cdm engine metrics implementation. They ensure that // The cdm engine metrics impl is correctly forwarding calls to the internal @@ -13,6 +13,7 @@ #include "cdm_client_property_set.h" #include "cdm_engine.h" +#include "create_test_file_system.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include "wv_cdm_event_listener.h" @@ -31,81 +32,91 @@ namespace wvcdm { class MockCdmClientPropertySet : public CdmClientPropertySet { public: - MOCK_CONST_METHOD0(security_level, const std::string&()); - MOCK_CONST_METHOD0(use_privacy_mode, bool()); - MOCK_CONST_METHOD0(service_certificate, const std::string&()); - MOCK_METHOD1(set_service_certificate, void(const std::string&)); - MOCK_CONST_METHOD0(is_session_sharing_enabled, bool()); - MOCK_CONST_METHOD0(session_sharing_id, uint32_t()); - MOCK_METHOD1(set_session_sharing_id, void(uint32_t)); - MOCK_CONST_METHOD0(use_atsc_mode, bool()); - MOCK_METHOD1(set_use_atsc_mode, void(bool)); - MOCK_CONST_METHOD0(app_id, const std::string&()); + MOCK_METHOD(const std::string&, security_level, (), (const, override)); + MOCK_METHOD(bool, use_privacy_mode, (), (const, override)); + MOCK_METHOD(const std::string&, service_certificate, (), (const, override)); + MOCK_METHOD(void, set_service_certificate, (const std::string&), (override)); + MOCK_METHOD(bool, is_session_sharing_enabled, (), (const, override)); + MOCK_METHOD(uint32_t, session_sharing_id, (), (const, override)); + MOCK_METHOD(void, set_session_sharing_id, (uint32_t), (override)); + MOCK_METHOD(bool, use_atsc_mode, (), (const, override)); + MOCK_METHOD(const std::string&, app_id, (), (const, override)); }; class MockWvCdmEventListener : public WvCdmEventListener { - MOCK_METHOD1(OnSessionRenewalNeeded, void(const CdmSessionId&)); - MOCK_METHOD3(OnSessionKeysChange, - void(const CdmSessionId&, const CdmKeyStatusMap&, - bool has_new_usable_key)); - MOCK_METHOD2(OnExpirationUpdate, void(const CdmSessionId&, int64_t)); + MOCK_METHOD(void, OnSessionRenewalNeeded, (const CdmSessionId&), (override)); + MOCK_METHOD(void, OnSessionKeysChange, + (const CdmSessionId&, const CdmKeyStatusMap&, + bool has_new_usable_key), + (override)); + MOCK_METHOD(void, OnExpirationUpdate, (const CdmSessionId&, int64_t), + (override)); }; class MockCdmEngineImpl : public CdmEngine { public: - MockCdmEngineImpl(FileSystem* file_system, + MockCdmEngineImpl(wvutil::FileSystem* file_system, std::shared_ptr metrics) : CdmEngine(file_system, metrics) {} - MOCK_METHOD4(OpenSession, - CdmResponseType(const CdmKeySystem&, CdmClientPropertySet*, - const CdmSessionId&, WvCdmEventListener*)); - MOCK_METHOD4(OpenSession, - CdmResponseType(const CdmKeySystem&, CdmClientPropertySet*, - WvCdmEventListener*, CdmSessionId*)); - MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&)); - MOCK_METHOD3(OpenKeySetSession, - CdmResponseType(const CdmKeySetId&, CdmClientPropertySet*, - WvCdmEventListener*)); - MOCK_METHOD6(GenerateKeyRequest, - CdmResponseType(const CdmSessionId&, const CdmKeySetId&, - const InitializationData&, const CdmLicenseType, - CdmAppParameterMap&, CdmKeyRequest*)); - MOCK_METHOD4(AddKey, - CdmResponseType(const CdmSessionId&, const CdmKeyResponse&, - CdmLicenseType*, CdmKeySetId*)); - MOCK_METHOD2(RestoreKey, - CdmResponseType(const CdmSessionId&, const CdmKeySetId&)); - MOCK_METHOD1(RemoveKeys, CdmResponseType(const CdmSessionId&)); - MOCK_METHOD2(QueryKeyStatus, - CdmResponseType(const CdmSessionId&, CdmQueryMap*)); - MOCK_METHOD6(GetProvisioningRequest, - CdmResponseType(CdmCertificateType, const std::string&, - const std::string&, SecurityLevel, - CdmProvisioningRequest*, std::string*)); - MOCK_METHOD4(HandleProvisioningResponse, - CdmResponseType(const CdmProvisioningResponse&, SecurityLevel, - std::string*, std::string*)); - MOCK_METHOD1(Unprovision, CdmResponseType(CdmSecurityLevel)); - MOCK_METHOD4(ListUsageIds, - CdmResponseType(const std::string&, CdmSecurityLevel, - std::vector*, - std::vector*)); - MOCK_METHOD1(RemoveAllUsageInfo, CdmResponseType(const std::string&)); - MOCK_METHOD2(RemoveAllUsageInfo, - CdmResponseType(const std::string&, CdmSecurityLevel)); - MOCK_METHOD2(RemoveUsageInfo, - CdmResponseType(const std::string&, const CdmSecureStopId&)); - MOCK_METHOD1(ReleaseUsageInfo, - CdmResponseType(const CdmUsageInfoReleaseMessage&)); - MOCK_METHOD2(DecryptV16, CdmResponseType(const CdmSessionId&, - const CdmDecryptionParametersV16&)); - MOCK_METHOD2(FindSessionForKey, bool(const KeyId&, CdmSessionId*)); + MOCK_METHOD(CdmResponseType, OpenSession, + (const CdmKeySystem&, CdmClientPropertySet*, const CdmSessionId&, + WvCdmEventListener*), + (override)); + MOCK_METHOD(CdmResponseType, OpenSession, + (const CdmKeySystem&, CdmClientPropertySet*, WvCdmEventListener*, + CdmSessionId*), + (override)); + MOCK_METHOD(CdmResponseType, CloseSession, (const CdmSessionId&), (override)); + MOCK_METHOD(CdmResponseType, OpenKeySetSession, + (const CdmKeySetId&, CdmClientPropertySet*, WvCdmEventListener*), + (override)); + MOCK_METHOD(CdmResponseType, GenerateKeyRequest, + (const CdmSessionId&, const CdmKeySetId&, + const InitializationData&, const CdmLicenseType, + CdmAppParameterMap&, CdmKeyRequest*), + (override)); + MOCK_METHOD(CdmResponseType, AddKey, + (const CdmSessionId&, const CdmKeyResponse&, CdmLicenseType*, + CdmKeySetId*), + (override)); + MOCK_METHOD(CdmResponseType, RestoreKey, + (const CdmSessionId&, const CdmKeySetId&), (override)); + MOCK_METHOD(CdmResponseType, RemoveKeys, (const CdmSessionId&), (override)); + MOCK_METHOD(CdmResponseType, QueryKeyStatus, + (const CdmSessionId&, CdmQueryMap*), (override)); + MOCK_METHOD(CdmResponseType, GetProvisioningRequest, + (CdmCertificateType, const std::string&, const std::string&, + wvcdm::RequestedSecurityLevel, CdmProvisioningRequest*, + std::string*), + (override)); + MOCK_METHOD(CdmResponseType, HandleProvisioningResponse, + (const CdmProvisioningResponse&, wvcdm::RequestedSecurityLevel, + std::string*, std::string*), + (override)); + MOCK_METHOD(CdmResponseType, Unprovision, (CdmSecurityLevel), (override)); + MOCK_METHOD(CdmResponseType, ListUsageIds, + (const std::string&, CdmSecurityLevel, std::vector*, + std::vector*), + (override)); + MOCK_METHOD(CdmResponseType, RemoveAllUsageInfo, (const std::string&), + (override)); + MOCK_METHOD(CdmResponseType, RemoveAllUsageInfo, + (const std::string&, CdmSecurityLevel), (override)); + MOCK_METHOD(CdmResponseType, RemoveUsageInfo, + (const std::string&, const CdmSecureStopId&), (override)); + MOCK_METHOD(CdmResponseType, ReleaseUsageInfo, + (const CdmUsageInfoReleaseMessage&), (override)); + MOCK_METHOD(CdmResponseType, DecryptV16, + (const CdmSessionId&, const CdmDecryptionParametersV16&), + (override)); + MOCK_METHOD(bool, FindSessionForKey, (const KeyId&, CdmSessionId*), + (override)); }; class WvCdmEngineMetricsImplTest : public ::testing::Test { public: void SetUp() override { - file_system_.reset(new FileSystem); + file_system_.reset(CreateTestFileSystem()); std::shared_ptr engine_metrics(new EngineMetrics); test_cdm_metrics_engine_.reset( new CdmEngineMetricsImpl>( @@ -113,7 +124,7 @@ class WvCdmEngineMetricsImplTest : public ::testing::Test { } protected: - std::unique_ptr file_system_; + std::unique_ptr file_system_; std::unique_ptr>> test_cdm_metrics_engine_; }; diff --git a/core/test/cdm_engine_test.cpp b/core/test/cdm_engine_test.cpp index fe78782a..8f266bed 100644 --- a/core/test/cdm_engine_test.cpp +++ b/core/test/cdm_engine_test.cpp @@ -1,22 +1,24 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // These tests are for the cdm engine, and code below it in the stack. In -// particular, we assume that the OEMCrypo layer works, and has a valid keybox. +// particular, we assume that the OEMCrypto layer works, and has a valid keybox. // This is because we need a valid RSA certificate, and will attempt to connect // to the provisioning server to request one if we don't. +#include "cdm_engine.h" + #include #include #include #include "OEMCryptoCENC.h" -#include "cdm_engine.h" #include "config_test_env.h" #include "device_files.h" #include "file_store.h" #include "initialization_data.h" +#include "license_holder.h" #include "license_request.h" #include "log.h" #include "properties.h" @@ -63,7 +65,7 @@ class WvCdmEnginePreProvTest : public WvCdmTestBaseWithEngine { CdmResponseType status = cdm_engine_.OpenSession( config_.key_system(), nullptr, nullptr, &session_id_); if (status == NEED_PROVISIONING) { - Provision(); + EnsureProvisioned(); status = cdm_engine_.OpenSession(config_.key_system(), nullptr, nullptr, &session_id_); } @@ -107,9 +109,7 @@ class WvCdmEnginePreProvTest : public WvCdmTestBaseWithEngine { license_request.GetDrmMessage(http_response, *response); LOGV("response: size = %zu, string =\n%s\n", response->size(), - Base64SafeEncode( - std::vector(response->begin(), response->end())) - .c_str()); + wvutil::Base64SafeEncode(*response).c_str()); return true; } @@ -171,7 +171,7 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest { CdmKeySetId key_set_id; InitializationData init_data(init_data_type_string, key_id); - if (g_cutoff >= LOG_DEBUG) init_data.DumpToLogs(); + if (wvutil::g_cutoff >= wvutil::CDM_LOG_DEBUG) init_data.DumpToLogs(); CdmKeyRequest key_request; @@ -225,7 +225,9 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest { int status_code = url_request.GetStatusCode(response); if (expect_success) - EXPECT_EQ(kHttpOk, status_code) << "Error response: " << response; + EXPECT_EQ(kHttpOk, status_code) + << "Error response from " << server_url << ":\n" + << response; if (status_code != kHttpOk) { return ""; @@ -234,8 +236,8 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest { LicenseRequest lic_request; lic_request.GetDrmMessage(response, drm_msg); LOGV("drm msg: %zu bytes\r\n%s", drm_msg.size(), - HexEncode(reinterpret_cast(drm_msg.data()), - drm_msg.size()) + wvutil::HexEncode(reinterpret_cast(drm_msg.data()), + drm_msg.size()) .c_str()); return drm_msg; } @@ -291,7 +293,7 @@ class WvCdmEngineTest : public WvCdmEnginePreProvTest { ASSERT_TRUE(metrics_proto.SerializeToString(&serialized_metrics)); EXPECT_TRUE(has_request_type) << "Expected request type " << key_request_type << " was not found. " - << "metrics: " << wvcdm::b2a_hex(serialized_metrics); + << "metrics: " << wvutil::b2a_hex(serialized_metrics); } std::string server_url_; @@ -333,20 +335,22 @@ TEST_F(WvCdmEngineTest, SetLicensingServiceInvalidCertificate) { NO_ERROR); }; -TEST_F(WvCdmEnginePreProvTestStaging, ProvisioningTest) { Provision(); } +TEST_F(WvCdmEnginePreProvTestStaging, ProvisioningTest) { EnsureProvisioned(); } -TEST_F(WvCdmEnginePreProvTestUatBinary, ProvisioningTest) { Provision(); } +TEST_F(WvCdmEnginePreProvTestUatBinary, ProvisioningTest) { + EnsureProvisioned(); +} // Test that provisioning works. -TEST_F(WvCdmEngineTest, ProvisioningTest) { Provision(); } +TEST_F(WvCdmEngineTest, ProvisioningTest) { EnsureProvisioned(); } // Test that provisioning works, even if device is already provisioned. TEST_F(WvCdmEngineTest, ReprovisioningTest) { // Provision once. - Provision(); + EnsureProvisioned(); // Verify that we can provision a second time, even though we already // provisioned once. - Provision(); + EnsureProvisioned(); } TEST_F(WvCdmEngineTest, BaseIsoBmffMessageTest) { @@ -386,14 +390,11 @@ TEST_F(WvCdmEngineTest, DISABLED_NormalDecryptionWebm) { TEST_F(WvCdmEngineTest, LoadKey) { EnsureProvisioned(); - TestLicenseHolder holder(&cdm_engine_); - holder.OpenSession(config_.key_system()); - holder.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE); - holder.CreateDefaultLicense(); - std::vector key_data(CONTENT_KEY_SIZE, '1'); - wvoec::KeyControlBlock block = {}; - holder.AddKey("key_one", key_data, block); - holder.SignAndLoadLicense(); + LicenseHolder holder("CDM_Streaming", &cdm_engine_, config_); + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); } // This test generates a renewal and then requests the renewal using the server @@ -401,6 +402,7 @@ TEST_F(WvCdmEngineTest, LoadKey) { // skip this test when you want to set the license and renewal server on the // command line. TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) { + EnsureProvisioned(); GenerateKeyRequest(binary_key_id(), kCencMimeType); VerifyNewKeyResponse(config_.license_server(), config_.client_auth()); @@ -419,14 +421,15 @@ TEST_F(WvCdmEngineTest, LicenseRenewalSpecifiedServer) { } } -// This test generates a renewal and then requests the renewal from the same -// server from which we requested the original license. -TEST_F(WvCdmEngineTest, LicenseRenewalSameServer) { +// This test generates a renewal and then requests it from the server specified +// by the current test configuration. +TEST_F(WvCdmEngineTest, LicenseRenewal) { + EnsureProvisioned(); GenerateKeyRequest(binary_key_id(), kCencMimeType); VerifyNewKeyResponse(config_.license_server(), config_.client_auth()); GenerateRenewalRequest(); - VerifyRenewalKeyResponse(config_.license_server(), config_.client_auth()); + VerifyRenewalKeyResponse(config_.renewal_server(), config_.client_auth()); } TEST_F(WvCdmEngineTest, ParseDecryptHashStringTest) { @@ -442,7 +445,7 @@ TEST_F(WvCdmEngineTest, ParseDecryptHashStringTest) { std::vector binary_hash{0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0xFF}; const std::string test_valid_decoded_hash(binary_hash.begin(), binary_hash.end()); - const std::string test_valid_hash = b2a_hex(binary_hash); + const std::string test_valid_hash = wvutil::b2a_hex(binary_hash); const std::string test_invalid_hash_string = "sample hash string"; const std::string test_valid_hash_string = test_session_id + kComma + test_frame_number_string + kComma + diff --git a/core/test/cdm_session_unittest.cpp b/core/test/cdm_session_unittest.cpp index 9fd6fba0..5ec877dc 100644 --- a/core/test/cdm_session_unittest.cpp +++ b/core/test/cdm_session_unittest.cpp @@ -1,14 +1,16 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. -#include +#include "cdm_session.h" #include #include -#include "cdm_session.h" +#include + #include "crypto_key.h" +#include "crypto_wrapped_key.h" #include "properties.h" #include "service_certificate.h" #include "string_conversions.h" @@ -35,7 +37,7 @@ namespace { const std::string kEmptyString; -const std::string kToken = a2bs_hex( +const std::string kToken = wvutil::a2bs_hex( "0AAE02080212107E0A892DEEB021E7AF696B938BB1D5B1188B85AD9D05228E023082010A02" "82010100DBEDF2BFB0EC98213766E65049B9AB176FA4B1FBFBB2A0C96C87D9F2B895E0ED77" "93BDA057E6BC3E0CA2348BC6831E03609445CA4D418CB98EAC98FFC87AB2364CE76BA26BEE" @@ -71,7 +73,7 @@ const std::string kToken = a2bs_hex( "8CD5A9DF6E3D3A99B806F6D60991358C5BE77117D4F3168F3348E9A048539F892F4D783152" "C7A8095224AA56B78C5CF7BD1AB1B179C0C0D11E3C3BAC84C141A00191321E3ACC17242E68" "3C"); -const std::string kWrappedKey = a2bs_hex( +const std::string kWrappedKeyData = wvutil::a2bs_hex( "3B84252DD84F1A710365014A114507FFFA3DD404625D61D1EEC7C3A39D72CB8D9318ADE9DA" "05D69F9776DAFDA49A97BC30E84CA275925DFD98CA04F7DB23465103A224852192DE232902" "99FF82024F5CCA7716ACA9BE0B56348BA16B9E3136D73789C842CB2ECA4820DDAAF59CCB9B" @@ -108,47 +110,52 @@ const std::string kWrappedKey = a2bs_hex( "33EF70621A98184DDAB5E14BC971CF98CF6C91A37FFA83B00AD3BCABBAAB2DEF1C52F43003" "E74C92B44F9205D22262FB47948654229DE1920F8EDF96A19A88A1CA1552F8856FB4CBF83B" "AA3348419159D207F65FCE9C1A500C6818"); +const CryptoWrappedKey kWrappedKey = {CryptoWrappedKey::kRsa, kWrappedKeyData}; class MockDeviceFiles : public DeviceFiles { public: MockDeviceFiles() : DeviceFiles(nullptr) {} - MOCK_METHOD1(Init, bool(CdmSecurityLevel)); - MOCK_METHOD5(RetrieveCertificate, - bool(bool, std::string*, std::string*, std::string*, uint32_t*)); + MOCK_METHOD(bool, Init, (CdmSecurityLevel), (override)); + MOCK_METHOD(DeviceFiles::CertificateState, RetrieveCertificate, + (bool, std::string*, CryptoWrappedKey*, std::string*, uint32_t*), + (override)); + MOCK_METHOD(bool, HasCertificate, (bool), (override)); }; class MockUsageTableHeader : public UsageTableHeader { public: MockUsageTableHeader() : UsageTableHeader() {} - MOCK_METHOD3(UpdateEntry, CdmResponseType(uint32_t usage_entry_number, - CryptoSession* crypto_session, - CdmUsageEntry* usage_entry)); + MOCK_METHOD(CdmResponseType, UpdateEntry, + (uint32_t usage_entry_number, CryptoSession* crypto_session, + CdmUsageEntry* usage_entry), + (override)); }; class MockCryptoSession : public TestCryptoSession { public: MockCryptoSession(metrics::CryptoMetrics* crypto_metrics) : TestCryptoSession(crypto_metrics) { - // By default, call the concrete implementation of GetUsageSupportType. - ON_CALL(*this, GetUsageSupportType(_)) + // By default, call the concrete implementation of HasUsageInfoSupport. + ON_CALL(*this, HasUsageInfoSupport(_)) .WillByDefault( - Invoke(this, &MockCryptoSession::BaseGetUsageSupportType)); + Invoke(this, &MockCryptoSession::BaseHasUsageInfoSupport)); } - MOCK_METHOD1(GetClientToken, bool(std::string*)); - MOCK_METHOD1(GetProvisioningToken, CdmResponseType(std::string*)); - MOCK_METHOD0(GetPreProvisionTokenType, CdmClientTokenType()); - MOCK_METHOD0(GetSecurityLevel, CdmSecurityLevel()); - MOCK_METHOD0(Open, CdmResponseType()); - MOCK_METHOD1(Open, CdmResponseType(SecurityLevel)); - MOCK_METHOD1(LoadCertificatePrivateKey, CdmResponseType(const std::string&)); - MOCK_METHOD0(DeleteAllUsageReports, CdmResponseType()); - MOCK_METHOD1(GetUsageSupportType, CdmResponseType(CdmUsageSupportType* type)); - MOCK_METHOD0(GetUsageTableHeader, UsageTableHeader*()); + MOCK_METHOD(CdmResponseType, GetProvisioningToken, + (std::string*, std::string*), (override)); + MOCK_METHOD(CdmClientTokenType, GetPreProvisionTokenType, (), (override)); + MOCK_METHOD(CdmSecurityLevel, GetSecurityLevel, (), (override)); + MOCK_METHOD(CdmResponseType, Open, (), (override)); + MOCK_METHOD(CdmResponseType, Open, (wvcdm::RequestedSecurityLevel), + (override)); + MOCK_METHOD(CdmResponseType, LoadCertificatePrivateKey, + (const CryptoWrappedKey&), (override)); + MOCK_METHOD(bool, HasUsageInfoSupport, (bool*), (override)); + MOCK_METHOD(UsageTableHeader*, GetUsageTableHeader, (), (override)); - CdmResponseType BaseGetUsageSupportType(CdmUsageSupportType* type) { - return CryptoSession::GetUsageSupportType(type); + bool BaseHasUsageInfoSupport(bool* has_support) { + return CryptoSession::HasUsageInfoSupport(has_support); } }; @@ -164,10 +171,10 @@ class MockCdmLicense : public CdmLicense { public: MockCdmLicense(const CdmSessionId& session_id) : CdmLicense(session_id) {} - MOCK_METHOD7(Init, - bool(const std::string&, CdmClientTokenType, const std::string&, - bool, const std::string&, CryptoSession*, PolicyEngine*)); - MOCK_METHOD0(provider_session_token, std::string()); + MOCK_METHOD(bool, Init, + (bool, const std::string&, CryptoSession*, PolicyEngine*), + (override)); + MOCK_METHOD(std::string, provider_session_token, (), (override)); }; } // namespace @@ -214,20 +221,13 @@ TEST_F(CdmSessionTest, InitWithBuiltInCertificate) { EXPECT_CALL(*crypto_session_, GetSecurityLevel()) .InSequence(crypto_session_seq) .WillOnce(Return(level)); - EXPECT_CALL(*crypto_session_, GetPreProvisionTokenType()) - .WillOnce(Return(kClientTokenDrmCert)); - EXPECT_CALL(*file_handle_, - RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) - .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), - Return(true))); - EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) - .InSequence(crypto_session_seq) - .WillOnce(Return(NO_ERROR)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*license_parser_, - Init(Eq(kToken), Eq(kClientTokenDrmCert), Eq(kEmptyString), false, - Eq(kEmptyString), Eq(crypto_session_), Eq(policy_engine_))) + EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), + Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, provider_session_token()) + .WillRepeatedly(Return("Mock provider session token")); ASSERT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); } @@ -241,20 +241,13 @@ TEST_F(CdmSessionTest, InitWithCertificate) { EXPECT_CALL(*crypto_session_, GetSecurityLevel()) .InSequence(crypto_session_seq) .WillOnce(Return(level)); - EXPECT_CALL(*crypto_session_, GetPreProvisionTokenType()) - .WillOnce(Return(kClientTokenKeybox)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, - RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) - .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), - Return(true))); - EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) - .InSequence(crypto_session_seq) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*license_parser_, - Init(Eq(kToken), Eq(kClientTokenDrmCert), Eq(kEmptyString), false, - Eq(kEmptyString), Eq(crypto_session_), Eq(policy_engine_))) + EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), + Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, provider_session_token()) + .WillRepeatedly(Return("Mock provider session token")); ASSERT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); } @@ -268,20 +261,13 @@ TEST_F(CdmSessionTest, ReInitFail) { EXPECT_CALL(*crypto_session_, GetSecurityLevel()) .InSequence(crypto_session_seq) .WillOnce(Return(level)); - EXPECT_CALL(*crypto_session_, GetPreProvisionTokenType()) - .WillOnce(Return(kClientTokenKeybox)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, - RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) - .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), - Return(true))); - EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) - .InSequence(crypto_session_seq) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*license_parser_, - Init(Eq(kToken), Eq(kClientTokenDrmCert), Eq(kEmptyString), false, - Eq(kEmptyString), Eq(crypto_session_), Eq(policy_engine_))) + EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), + Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); + EXPECT_CALL(*license_parser_, provider_session_token()) + .WillRepeatedly(Return("Mock provider session token")); ASSERT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); ASSERT_NE(NO_ERROR, cdm_session_->Init(nullptr)); @@ -290,29 +276,12 @@ TEST_F(CdmSessionTest, ReInitFail) { TEST_F(CdmSessionTest, InitFailCryptoError) { EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) .WillOnce(Return(UNKNOWN_ERROR)); + EXPECT_CALL(*license_parser_, provider_session_token()) + .WillRepeatedly(Return("Mock provider session token")); ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init(nullptr)); } -TEST_F(CdmSessionTest, InitNeedsProvisioning) { - Sequence crypto_session_seq; - CdmSecurityLevel level = kSecurityLevelL1; - EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault))) - .InSequence(crypto_session_seq) - .WillOnce(Return(NO_ERROR)); - EXPECT_CALL(*crypto_session_, GetSecurityLevel()) - .InSequence(crypto_session_seq) - .WillOnce(Return(level)); - EXPECT_CALL(*crypto_session_, GetPreProvisionTokenType()) - .WillOnce(Return(kClientTokenKeybox)); - EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, - RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) - .WillOnce(Return(false)); - - ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init(nullptr)); -} - TEST_F(CdmSessionTest, UpdateUsageEntry) { // Setup common expectations for initializing the CdmSession object. Sequence crypto_session_seq; @@ -323,35 +292,24 @@ TEST_F(CdmSessionTest, UpdateUsageEntry) { EXPECT_CALL(*crypto_session_, GetSecurityLevel()) .InSequence(crypto_session_seq) .WillOnce(Return(level)); - EXPECT_CALL(*crypto_session_, GetPreProvisionTokenType()) - .WillOnce(Return(kClientTokenKeybox)); EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true)); - EXPECT_CALL(*file_handle_, - RetrieveCertificate(false, NotNull(), NotNull(), NotNull(), _)) - .WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey), - Return(true))); - EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey))) - .InSequence(crypto_session_seq) - .WillOnce(Return(NO_ERROR)); + EXPECT_CALL(*file_handle_, HasCertificate(false)).WillOnce(Return(true)); EXPECT_CALL(*crypto_session_, GetUsageTableHeader()) .WillOnce(Return(&usage_table_header_)); - EXPECT_CALL(*license_parser_, - Init(Eq(kToken), Eq(kClientTokenDrmCert), Eq(kEmptyString), false, - Eq(kEmptyString), Eq(crypto_session_), Eq(policy_engine_))) + EXPECT_CALL(*license_parser_, Init(false, Eq(kEmptyString), + Eq(crypto_session_), Eq(policy_engine_))) .WillOnce(Return(true)); // Set up mocks and expectations for the UpdateUsageEntryInformation call. - EXPECT_CALL(*crypto_session_, GetUsageSupportType(_)) - .WillRepeatedly( - DoAll(SetArgPointee<0>(kUsageEntrySupport), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, HasUsageInfoSupport(_)) + .WillRepeatedly(DoAll(SetArgPointee<0>(true), Return(true))); EXPECT_CALL(*license_parser_, provider_session_token()) .WillRepeatedly(Return("Mock provider session token")); EXPECT_CALL(usage_table_header_, UpdateEntry(_, NotNull(), NotNull())) .WillRepeatedly(Return(NO_ERROR)); EXPECT_EQ(NO_ERROR, cdm_session_->Init(nullptr)); - EXPECT_EQ(kUsageEntrySupport, cdm_session_->get_usage_support_type()) - << "Usage support type: " << cdm_session_->get_usage_support_type(); + EXPECT_TRUE(cdm_session_->supports_usage_info()); EXPECT_EQ(NO_ERROR, cdm_session_->UpdateUsageEntryInformation()); // Verify the UsageEntry metric is set. @@ -363,7 +321,7 @@ TEST_F(CdmSessionTest, UpdateUsageEntry) { metrics.crypto_metrics().usage_table_header_update_entry_time_us().size(), 0) << "Missing update usage entry metric. Metrics: " - << wvcdm::b2a_hex(serialized_metrics); + << wvutil::b2a_hex(serialized_metrics); } } // namespace wvcdm diff --git a/core/test/certificate_provisioning_unittest.cpp b/core/test/certificate_provisioning_unittest.cpp index 6c7d0b28..2076d922 100644 --- a/core/test/certificate_provisioning_unittest.cpp +++ b/core/test/certificate_provisioning_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "certificate_provisioning.h" @@ -16,18 +16,38 @@ namespace { -const std::string kSignedDeviceCertificate = wvcdm::a2bs_hex( - "0A350802121B7769646576696E655F746573745F73657269616C5F6E756D62657228D2093A" - "11746573742E7769646576696E652E636F6D12097369676E6174757265"); -const std::string kSignedDeviceCertificateInvalid = wvcdm::a2bs_hex( +const std::string kSignedDeviceCertificate = wvutil::a2bs_hex( + "0A3D0802121B7769646576696E655F746573745F73657269616C5F6E756D62657218C09A0C" + "28D2093A11746573742E7769646576696E652E636F6D6080B51812097369676E617475726" + "5"); +const std::string kSignedDeviceCertificateCreationTimeUnlimited = + wvutil::a2bs_hex( + "0A370802121B7769646576696E655F746573745F73657269616C5F6E756D6265721800" + "28D2093A11746573742E7769646576696E652E636F6D12097369676E6174757265"); +const std::string kSignedDeviceCertificateExpirationTimeInvalid = + wvutil::a2bs_hex( + "0A390802121B7769646576696E655F746573745F73657269616C5F6E756D62657218C0" + "9A0C28D2093A11746573742E7769646576696E652E636F6D12097369676E617475726" + "5"); +const std::string kSignedDeviceCertificateExpirationTimeUnlimited = + wvutil::a2bs_hex( + "0A3B0802121B7769646576696E655F746573745F73657269616C5F6E756D62657218C0" + "9A0C28D2093A11746573742E7769646576696E652E636F6D600012097369676E617475" + "7265"); +const std::string kSignedDeviceCertificateInvalid = wvutil::a2bs_hex( "76340802121B7769646576696E655F746573745F73657269616C5F6E756D62657228D2093A" "11746573742E7769646576696E652E636F6D12097369676E6174757265"); -const std::string kSignedDeviceCertificateNoDrmCertificate = - wvcdm::a2bs_hex("12097369676E6174757265"); const std::string kSignedDeviceCertificateInvalidCertificateType = - wvcdm::a2bs_hex( + wvutil::a2bs_hex( "0A350801121B7769646576696E655F746573745F73657269616C5F6E756D62657228D2" "093A11746573742E7769646576696E652E636F6D12097369676E6174757265"); +const std::string kSignedDeviceCertificateNoDrmCertificate = + wvutil::a2bs_hex("12097369676E6174757265"); +const std::string kSignedDeviceCertificateTimesInvalid = wvutil::a2bs_hex( + "0A350802121B7769646576696E655F746573745F73657269616C5F6E756D62657228D2093A" + "11746573742E7769646576696E652E636F6D12097369676E6174757265"); +const int64_t kCreationTime = 200000; +const int64_t kExpirationTime = 400000; const std::string kSerialNumber = "widevine_test_serial_number"; const uint32_t kSystemId = 1234; @@ -39,24 +59,30 @@ class MockCryptoSession : public TestCryptoSession { public: MockCryptoSession(metrics::CryptoMetrics* metrics) : TestCryptoSession(metrics) {} - MOCK_METHOD1(Open, CdmResponseType(SecurityLevel)); + MOCK_METHOD(CdmResponseType, Open, (wvcdm::RequestedSecurityLevel), + (override)); // Usage Table Header. - MOCK_METHOD2(CreateUsageTableHeader, - CdmResponseType(SecurityLevel, CdmUsageTableHeader*)); - MOCK_METHOD2(LoadUsageTableHeader, - CdmResponseType(SecurityLevel, const CdmUsageTableHeader&)); - MOCK_METHOD3(ShrinkUsageTableHeader, - CdmResponseType(SecurityLevel, uint32_t, CdmUsageTableHeader*)); + MOCK_METHOD(CdmResponseType, CreateUsageTableHeader, + (wvcdm::RequestedSecurityLevel, CdmUsageTableHeader*), + (override)); + MOCK_METHOD(CdmResponseType, LoadUsageTableHeader, + (wvcdm::RequestedSecurityLevel, const CdmUsageTableHeader&), + (override)); + MOCK_METHOD(CdmResponseType, ShrinkUsageTableHeader, + (wvcdm::RequestedSecurityLevel, uint32_t, CdmUsageTableHeader*), + (override)); // Usage Entry. - MOCK_METHOD1(CreateUsageEntry, CdmResponseType(uint32_t*)); - MOCK_METHOD2(LoadUsageEntry, CdmResponseType(uint32_t, const CdmUsageEntry&)); - MOCK_METHOD2(UpdateUsageEntry, - CdmResponseType(CdmUsageTableHeader*, CdmUsageEntry*)); - MOCK_METHOD1(MoveUsageEntry, CdmResponseType(uint32_t)); + MOCK_METHOD(CdmResponseType, CreateUsageEntry, (uint32_t*), (override)); + MOCK_METHOD(CdmResponseType, LoadUsageEntry, (uint32_t, const CdmUsageEntry&), + (override)); + MOCK_METHOD(CdmResponseType, UpdateUsageEntry, + (CdmUsageTableHeader*, CdmUsageEntry*), (override)); + MOCK_METHOD(CdmResponseType, MoveUsageEntry, (uint32_t), (override)); }; class TestStubCryptoSessionFactory : public CryptoSessionFactory { - CryptoSession* MakeCryptoSession(metrics::CryptoMetrics* crypto_metrics) { + CryptoSession* MakeCryptoSession( + metrics::CryptoMetrics* crypto_metrics) override { return new MockCryptoSession(crypto_metrics); } }; @@ -91,17 +117,20 @@ TEST_F(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) { uint32_t system_id; EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( - kSignedDeviceCertificate, nullptr, nullptr)); + kSignedDeviceCertificate, nullptr, nullptr, nullptr, nullptr)); + + int64_t creation_time_seconds, expiration_time_seconds; + EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( + kSignedDeviceCertificateInvalid, &serial_number, &system_id, + &creation_time_seconds, &expiration_time_seconds)); EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( - kSignedDeviceCertificateInvalid, &serial_number, &system_id)); - - EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( - kSignedDeviceCertificateNoDrmCertificate, &serial_number, &system_id)); + kSignedDeviceCertificateNoDrmCertificate, &serial_number, &system_id, + &creation_time_seconds, &expiration_time_seconds)); EXPECT_FALSE(certificate_provisioning_->ExtractDeviceInfo( kSignedDeviceCertificateInvalidCertificateType, &serial_number, - &system_id)); + &system_id, &creation_time_seconds, &expiration_time_seconds)); } // Tests ExtractDeviceInfo success scenarios @@ -111,19 +140,72 @@ TEST_F(CertificateProvisioningTest, ExtractDeviceInfo_InvalidInput) { TEST_F(CertificateProvisioningTest, ExtractDeviceInfo) { std::string serial_number; uint32_t system_id; + int64_t creation_time_seconds, expiration_time_seconds; EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( - kSignedDeviceCertificate, &serial_number, &system_id)); + kSignedDeviceCertificateTimesInvalid, &serial_number, &system_id, + &creation_time_seconds, &expiration_time_seconds)); EXPECT_EQ(kSerialNumber, serial_number); EXPECT_EQ(kSystemId, system_id); + EXPECT_EQ(INVALID_TIME, creation_time_seconds); + EXPECT_EQ(INVALID_TIME, expiration_time_seconds); + + EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( + kSignedDeviceCertificateTimesInvalid, nullptr, &system_id, nullptr, + nullptr)); + EXPECT_EQ(kSystemId, system_id); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( - kSignedDeviceCertificate, nullptr, &system_id)); - EXPECT_EQ(kSystemId, system_id); + kSignedDeviceCertificateTimesInvalid, &serial_number, nullptr, nullptr, + nullptr)); + EXPECT_EQ(kSerialNumber, serial_number); EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( - kSignedDeviceCertificate, &serial_number, nullptr)); + kSignedDeviceCertificateCreationTimeUnlimited, &serial_number, &system_id, + &creation_time_seconds, &expiration_time_seconds)); EXPECT_EQ(kSerialNumber, serial_number); + EXPECT_EQ(kSystemId, system_id); + EXPECT_EQ(UNLIMITED_DURATION, creation_time_seconds); + EXPECT_EQ(INVALID_TIME, expiration_time_seconds); + + EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( + kSignedDeviceCertificateExpirationTimeInvalid, &serial_number, &system_id, + &creation_time_seconds, &expiration_time_seconds)); + EXPECT_EQ(kSerialNumber, serial_number); + EXPECT_EQ(kSystemId, system_id); + EXPECT_EQ(kCreationTime, creation_time_seconds); + EXPECT_EQ(INVALID_TIME, expiration_time_seconds); + + EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( + kSignedDeviceCertificateExpirationTimeUnlimited, &serial_number, + &system_id, &creation_time_seconds, &expiration_time_seconds)); + EXPECT_EQ(kSerialNumber, serial_number); + EXPECT_EQ(kSystemId, system_id); + EXPECT_EQ(kCreationTime, creation_time_seconds); + EXPECT_EQ(UNLIMITED_DURATION, expiration_time_seconds); + + EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( + kSignedDeviceCertificate, &serial_number, &system_id, + &creation_time_seconds, &expiration_time_seconds)); + EXPECT_EQ(kSerialNumber, serial_number); + EXPECT_EQ(kSystemId, system_id); + EXPECT_EQ(kCreationTime, creation_time_seconds); + EXPECT_EQ(kExpirationTime, expiration_time_seconds); + + EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( + kSignedDeviceCertificate, &serial_number, &system_id, nullptr, + &expiration_time_seconds)); + EXPECT_EQ(kSerialNumber, serial_number); + EXPECT_EQ(kSystemId, system_id); + EXPECT_EQ(kExpirationTime, expiration_time_seconds); + + EXPECT_TRUE(certificate_provisioning_->ExtractDeviceInfo( + kSignedDeviceCertificateExpirationTimeUnlimited, &serial_number, + &system_id, &creation_time_seconds, nullptr)); + EXPECT_EQ(kSerialNumber, serial_number); + EXPECT_EQ(kSystemId, system_id); + EXPECT_EQ(kCreationTime, creation_time_seconds); + EXPECT_EQ(kExpirationTime, expiration_time_seconds); } } // namespace wvcdm diff --git a/core/test/config_test_env.cpp b/core/test/config_test_env.cpp index de3621d4..2f1d98b9 100644 --- a/core/test/config_test_env.cpp +++ b/core/test/config_test_env.cpp @@ -1,8 +1,9 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "config_test_env.h" + #include "string_conversions.h" // Holds the data needed to talk to the various provisioning and @@ -82,6 +83,32 @@ const std::string kCpStagingProvisioningServiceCertificate = "8598ed5751b38694419242a875d9e00d5a5832933024b934859ec8be78adccbb" "1ec7127ae9afeef9c5cd2e15bd3048e8ce652f7d8c5d595a0323238c598a28"; +// Service certificate for qa.widevine.com +const std::string kCpQAProvisioningServiceCertificate = + "0abc02080312100ec8164669cc2fdfc253b3b5e763276e18abd8cdcf05228e02" + "3082010a0282010100b24d497c0cc6ab5072f97623daa49b8d5564360654d8e5" + "8df8db7a23158f1afdd04724cbadbe87001532d9d6dec3b06973666da7759ec3" + "bf3083e2d9b85e7a47c340db796b085493a460eef31d56e3f15d857713c55cdb" + "164fe09e2a06be7fb979ad55e33a59ade3712aed2445b89fc145556a9e0093fa" + "36fc3ff4d05291a9633d20c80a13cd0d924ed9078395714c30b49019f4d6f5ba" + "093ad958aee9a164ba73ec298f905662de5859d3e6fae41c063f262d29dae75e" + "8654ac9d68f3e3fccc809573d0f90704a77f9bce391d0a5f265f438119e4fb0b" + "ec27706f5c7fc888f665730b691a0431e30cb3b57dfd078838c44550c3b79b35" + "0552a92a760f90c6cf02030100013a0f71612e7769646576696e652e636f6d12" + "800331ca1662fdc97e02debdca5b6de35dce5da87b5f61b15745ccf83e66197c" + "e31bc6379ae4f6a5e4fd8a0e76f979701c5a715c06d70908563626d0dd3986b5" + "e623a7b6336789c67f0fc68f9ec68e045f85d9a06942f4af0fe47d801cf035af" + "27924f1c4cd395d15ce2f92f48044254fdefe37320471d7009160f5293183ca4" + "a09bca71f76f1457a80eebcf12706bd79256f1b02e67dc002fc81e18c00d880f" + "04b0187e6ef59ae75eaaf6b16672a887b9657f1796607d1585d1998283af1650" + "9bd9a170c262056aad69e222a4c3180d104a76d76da29082e4f2e5297d1ec44d" + "ed98c999981688089d8ff2d62f0f13b96ce5e89a4c215d60f025fa29811fcdc1" + "848fe0581f612f45733da4b4c8803ae8088dcb3b811ea9c691daccfbe9cbf603" + "13e8f85eb68f2f1d8cdf9e4fc91a46157a90fffafbd9d408b319307ea4d3d4a9" + "d2f177355ad361f5284057dec1b186beb85dcbda64bf00a164cecc66c1878961" + "7748618d069c39f365e8347acdae777cc4e3c3c3c3fe9698c4f5ee1285b0e6a9" + "675e"; + // ----------------------------------------------------------------------------- // Below are several choices for licenseing servers: production, UAT, staging // and staging and maybe Google Play. We haven't tested with Google Play in a @@ -309,42 +336,30 @@ ConfigTestEnv::ConfigTestEnv(ServerConfigurationId server_id, bool streaming, } } -ConfigTestEnv& ConfigTestEnv::operator=(const ConfigTestEnv& other) { - this->server_id_ = other.server_id_; - this->client_auth_ = other.client_auth_; - this->key_id_ = other.key_id_; - this->key_system_ = other.key_system_; - this->license_server_ = other.license_server_; - this->provisioning_server_ = other.provisioning_server_; - this->license_service_certificate_ = other.license_service_certificate_; - this->provisioning_service_certificate_ = - other.provisioning_service_certificate_; - return *this; -} - void ConfigTestEnv::Init(ServerConfigurationId server_id) { this->server_id_ = server_id; client_auth_ = license_servers[server_id].client_tag; key_id_ = license_servers[server_id].key_id; key_system_ = kWidevineKeySystem; license_server_ = license_servers[server_id].license_server_url; + renewal_server_.clear(); provisioning_server_ = license_servers[server_id].provisioning_server_url; license_service_certificate_ = - a2bs_hex(license_servers[server_id].license_service_certificate); - provisioning_service_certificate_ = - a2bs_hex(license_servers[server_id].provisioning_service_certificate); + wvutil::a2bs_hex(license_servers[server_id].license_service_certificate); + provisioning_service_certificate_ = wvutil::a2bs_hex( + license_servers[server_id].provisioning_service_certificate); } const CdmInitData ConfigTestEnv::GetInitData(ContentId content_id) { switch (content_id) { case kContentIdStreaming: - return wvcdm::a2bs_hex(kCpKeyId); + return wvutil::a2bs_hex(kCpKeyId); case kContentIdOffline: - return wvcdm::a2bs_hex(kCpOfflineKeyId); + return wvutil::a2bs_hex(kCpOfflineKeyId); case kContentIdStagingSrmOuputProtectionRequested: - return wvcdm::a2bs_hex(kCpStagingSrmOuputProtectionRequested); + return wvutil::a2bs_hex(kCpStagingSrmOuputProtectionRequested); case kContentIdStagingSrmOuputProtectionRequired: - return wvcdm::a2bs_hex(kCpStagingSrmOuputProtectionRequired); + return wvutil::a2bs_hex(kCpStagingSrmOuputProtectionRequired); default: return kEmptyData; } @@ -366,4 +381,8 @@ const std::string& ConfigTestEnv::GetLicenseServerUrl( } } +std::string ConfigTestEnv::QAProvisioningServiceCertificate() { + return wvutil::a2bs_hex(kCpQAProvisioningServiceCertificate); +} + } // namespace wvcdm diff --git a/core/test/config_test_env.h b/core/test/config_test_env.h index 4daa28e9..1bd6e0de 100644 --- a/core/test/config_test_env.h +++ b/core/test/config_test_env.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef CDM_TEST_CONFIG_TEST_ENV_H_ #define CDM_TEST_CONFIG_TEST_ENV_H_ @@ -61,9 +61,12 @@ class ConfigTestEnv { ConfigTestEnv(ServerConfigurationId server_id, bool streaming); ConfigTestEnv(ServerConfigurationId server_id, bool streaming, bool renew, bool release); - // Allow copy and assign. Performance is not an issue in test initialization. - ConfigTestEnv(const ConfigTestEnv &other) { *this = other; }; - ConfigTestEnv& operator=(const ConfigTestEnv &other); + // Allow copy, assign, and move. Performance is not an issue in test + // initialization. + ConfigTestEnv(const ConfigTestEnv&) = default; + ConfigTestEnv(ConfigTestEnv&&) = default; + ConfigTestEnv& operator=(const ConfigTestEnv&) = default; + ConfigTestEnv& operator=(ConfigTestEnv&&) = default; ~ConfigTestEnv() {}; @@ -72,6 +75,12 @@ class ConfigTestEnv { const KeyId& key_id() const { return key_id_; } const CdmKeySystem& key_system() const { return key_system_; } const std::string& license_server() const { return license_server_; } + const std::string& renewal_server() const { + if (!renewal_server_.empty()) + return renewal_server_; + else + return license_server_; + } const std::string& provisioning_server() const { return provisioning_server_; } @@ -81,6 +90,8 @@ class ConfigTestEnv { const std::string& provisioning_service_certificate() const { return provisioning_service_certificate_; } + int test_pass() const { return test_pass_; } + const std::string& test_data_path() const { return test_data_path_; } static const CdmInitData GetInitData(ContentId content_id); static const std::string& GetLicenseServerUrl( @@ -93,6 +104,9 @@ class ConfigTestEnv { void set_license_server(const std::string& license_server) { license_server_.assign(license_server); } + void set_renewal_server(const std::string& renewal_server) { + renewal_server_.assign(renewal_server); + } void set_license_service_certificate( const std::string& license_service_certificate) { license_service_certificate_.assign(license_service_certificate); @@ -104,6 +118,12 @@ class ConfigTestEnv { const std::string& provisioning_service_certificate) { provisioning_service_certificate_.assign(provisioning_service_certificate); } + void set_test_pass(int test_pass) { test_pass_ = test_pass; } + void set_test_data_path(const std::string& test_data_path) { + test_data_path_ = test_data_path; + } + // The QA service certificate, used for a local provisioning server. + static std::string QAProvisioningServiceCertificate(); private: void Init(ServerConfigurationId server_id); @@ -113,9 +133,12 @@ class ConfigTestEnv { KeyId key_id_; CdmKeySystem key_system_; std::string license_server_; + std::string renewal_server_; std::string provisioning_server_; std::string license_service_certificate_; std::string provisioning_service_certificate_; + int test_pass_; + std::string test_data_path_; // Where to store test data for reboot tests. }; // The default provisioning server URL for a default provisioning request. diff --git a/core/test/create_test_file_system.h b/core/test/create_test_file_system.h new file mode 100644 index 00000000..fbd6505f --- /dev/null +++ b/core/test/create_test_file_system.h @@ -0,0 +1,15 @@ +// 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 CDM_TEST_CREATE_TEST_FILE_SYSTEM_H_ +#define CDM_TEST_CREATE_TEST_FILE_SYSTEM_H_ + +#include "file_store.h" + +// Create a new FileSystem object that is suitable for using to create a new +// CdmEngine object. How this is implemented is platform-specific. The caller +// owns the returned pointer and is responsible for deleting it. +wvutil::FileSystem* CreateTestFileSystem(); + +#endif // CDM_TEST_CREATE_TEST_FILE_SYSTEM_H_ diff --git a/core/test/crypto_session_unittest.cpp b/core/test/crypto_session_unittest.cpp index 96bba6af..3e67165b 100644 --- a/core/test/crypto_session_unittest.cpp +++ b/core/test/crypto_session_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include #include @@ -24,235 +24,12 @@ using ::testing::AllOf; using ::testing::Ge; using ::testing::Le; -namespace { - -const uint8_t kOemCert[] = { - 0x30, 0x82, 0x09, 0xf7, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, - 0x01, 0x07, 0x02, 0xa0, 0x82, 0x09, 0xe8, 0x30, 0x82, 0x09, 0xe4, 0x02, - 0x01, 0x01, 0x31, 0x00, 0x30, 0x0f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x02, 0x04, 0x00, 0xa0, 0x82, 0x09, - 0xc8, 0x30, 0x82, 0x04, 0x1a, 0x30, 0x82, 0x03, 0x02, 0xa0, 0x03, 0x02, - 0x01, 0x02, 0x02, 0x11, 0x00, 0xf2, 0xa1, 0x08, 0xdf, 0x12, 0x84, 0xb9, - 0x73, 0x6c, 0x23, 0x73, 0xe1, 0x1f, 0xf3, 0xac, 0x7a, 0x30, 0x0d, 0x06, - 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, - 0x30, 0x81, 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, - 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, - 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, - 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x08, - 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, - 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, - 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, 0x30, 0x30, 0x2e, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x27, 0x47, 0x6f, 0x6f, 0x67, 0x6c, - 0x65, 0x20, 0x4f, 0x45, 0x4d, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x44, - 0x65, 0x76, 0x69, 0x63, 0x65, 0x3b, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, - 0x6d, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x37, 0x33, 0x34, 0x36, 0x30, 0x1e, - 0x17, 0x0d, 0x31, 0x37, 0x30, 0x33, 0x31, 0x33, 0x30, 0x30, 0x30, 0x30, - 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x30, 0x38, 0x30, - 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6d, 0x31, 0x12, 0x30, 0x10, - 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x09, 0x37, 0x33, 0x34, 0x36, 0x2d, - 0x6c, 0x65, 0x61, 0x66, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, - 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, - 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, - 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, - 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, - 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x30, 0x82, 0x01, - 0xa2, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, - 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x8f, 0x00, 0x30, 0x82, 0x01, - 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xf5, 0x09, 0x64, 0x4a, 0x26, 0xfe, - 0xc0, 0x98, 0x55, 0x6a, 0x1d, 0x5d, 0x1c, 0xc7, 0x38, 0xaf, 0xfd, 0x49, - 0x9e, 0x85, 0x3f, 0xd6, 0x45, 0x0e, 0x99, 0x09, 0x85, 0x69, 0x84, 0x3c, - 0xfe, 0x72, 0xa5, 0x56, 0xfa, 0x11, 0x4f, 0x6b, 0x7d, 0x32, 0x2b, 0x0c, - 0xbf, 0x8f, 0xac, 0x47, 0x96, 0x22, 0x82, 0x3d, 0xf5, 0x64, 0x74, 0x7e, - 0x62, 0x68, 0x74, 0xcd, 0x0a, 0xec, 0x84, 0xc5, 0x15, 0x06, 0x0e, 0x5a, - 0x2f, 0x20, 0xe3, 0xc9, 0x67, 0xcd, 0xdd, 0x01, 0xb8, 0xb3, 0x18, 0x87, - 0x8c, 0xa9, 0x58, 0x86, 0x0f, 0xb6, 0xc3, 0x42, 0x7e, 0x87, 0x48, 0x5e, - 0x10, 0x49, 0xc7, 0xd7, 0xb7, 0xb8, 0xa6, 0x34, 0x08, 0x0c, 0x94, 0xf4, - 0xbb, 0x2a, 0x06, 0xa4, 0x4f, 0xec, 0xbc, 0xc4, 0x37, 0xbe, 0x99, 0x10, - 0x23, 0x37, 0x24, 0xb1, 0xdf, 0xcb, 0xe6, 0x3f, 0xc1, 0xf0, 0x0f, 0x04, - 0x03, 0xc8, 0xb0, 0x1e, 0xd6, 0xb8, 0xae, 0x77, 0xe1, 0x4d, 0x6d, 0x97, - 0x69, 0x6d, 0x8a, 0x73, 0x66, 0x32, 0x57, 0x6f, 0xcf, 0xea, 0x1e, 0x7b, - 0x87, 0x03, 0x75, 0xb1, 0xef, 0x83, 0x64, 0x26, 0xf1, 0x3f, 0xbf, 0xe6, - 0x28, 0x03, 0x72, 0x57, 0xbf, 0x47, 0x29, 0x99, 0x8f, 0x74, 0x1d, 0x01, - 0x16, 0xad, 0xb2, 0xdf, 0x80, 0xa4, 0xd3, 0x8b, 0xeb, 0x61, 0xd1, 0x40, - 0x68, 0xb9, 0xa2, 0xa5, 0xef, 0x2b, 0xe5, 0x78, 0xe8, 0x28, 0x88, 0x87, - 0xb7, 0x53, 0x49, 0xbb, 0xe4, 0xea, 0x0d, 0x5e, 0x96, 0xa5, 0xdd, 0x1f, - 0x0b, 0x25, 0x8b, 0xb5, 0x95, 0x46, 0xe7, 0xba, 0xb8, 0xc4, 0x0a, 0x36, - 0xb1, 0x89, 0xeb, 0x27, 0x5d, 0xd9, 0x97, 0x24, 0x59, 0xa3, 0x9b, 0xb0, - 0x23, 0x0b, 0xd2, 0xec, 0x65, 0x91, 0xf9, 0xf0, 0xa0, 0x74, 0x5f, 0xb4, - 0xce, 0x22, 0x27, 0x18, 0x37, 0xe2, 0x4b, 0xfc, 0x91, 0xf9, 0x09, 0x15, - 0xe6, 0xdb, 0x06, 0x9b, 0x4d, 0x82, 0xdc, 0x36, 0x14, 0x48, 0xc6, 0xd5, - 0x87, 0xca, 0xec, 0x5a, 0xa2, 0x29, 0x33, 0xef, 0x22, 0x0c, 0x4b, 0xbf, - 0xe7, 0x2f, 0x95, 0xe1, 0xd3, 0xa5, 0xd8, 0xaa, 0x44, 0x77, 0x29, 0xa3, - 0x20, 0x33, 0xd2, 0x51, 0xa2, 0xf9, 0x4a, 0x6f, 0xf7, 0x3e, 0xf7, 0x0b, - 0x8a, 0xec, 0xc1, 0x99, 0x1d, 0x47, 0xf3, 0x74, 0x02, 0x04, 0xab, 0x8e, - 0x62, 0x4c, 0x9e, 0x00, 0xc2, 0x84, 0xd7, 0xd0, 0xf8, 0xe4, 0x1c, 0x9d, - 0x98, 0x15, 0xa8, 0x8f, 0x08, 0x98, 0x4e, 0x5a, 0xfa, 0xd6, 0x60, 0x87, - 0x12, 0xdc, 0x8e, 0xfd, 0xcb, 0xb3, 0x13, 0x97, 0x7a, 0xa8, 0x8c, 0x56, - 0x2e, 0x49, 0x26, 0x60, 0xe9, 0x4a, 0xdc, 0xec, 0x3f, 0xf0, 0x94, 0xcd, - 0x90, 0x8e, 0x7c, 0x21, 0x3f, 0x80, 0x14, 0x33, 0xdd, 0xb0, 0x00, 0xe2, - 0x09, 0x37, 0x06, 0xdd, 0x17, 0x69, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, - 0x16, 0x30, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, - 0xd6, 0x79, 0x04, 0x01, 0x01, 0x04, 0x04, 0x02, 0x02, 0x1c, 0xb2, 0x30, - 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, - 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x8e, 0x2d, 0x13, 0x1e, 0x60, - 0xaa, 0xda, 0x52, 0x53, 0x55, 0x64, 0x3a, 0xdc, 0xb6, 0x7a, 0xc0, 0xba, - 0xfa, 0xeb, 0x20, 0xab, 0xb6, 0x63, 0xcf, 0xcd, 0x9b, 0xdb, 0x71, 0xf3, - 0xa0, 0xd6, 0x91, 0xbf, 0x0c, 0xc1, 0xae, 0x8f, 0x02, 0x18, 0x00, 0x54, - 0xfb, 0x49, 0x03, 0x34, 0x8d, 0x92, 0x9d, 0x5d, 0x8d, 0xa8, 0x1c, 0x20, - 0x0f, 0x85, 0x60, 0xf9, 0xf6, 0x8b, 0xbb, 0x2b, 0x82, 0xce, 0xb3, 0xe2, - 0x91, 0xe7, 0xbd, 0x91, 0x61, 0x52, 0x36, 0x40, 0x9f, 0x2f, 0x5e, 0xa6, - 0x5d, 0x2f, 0xb3, 0x81, 0xe7, 0xf1, 0x87, 0xbe, 0xc5, 0x9d, 0x67, 0x5a, - 0xf7, 0x41, 0x1e, 0x73, 0xb0, 0x1e, 0xdc, 0x4f, 0x8d, 0x53, 0x21, 0x38, - 0x1b, 0xfd, 0x92, 0x43, 0x68, 0x83, 0x03, 0xd0, 0x9a, 0xca, 0x92, 0x14, - 0x73, 0x04, 0x94, 0x2a, 0x93, 0x22, 0x60, 0x5e, 0xee, 0xb6, 0xec, 0x0f, - 0xb0, 0xc8, 0x92, 0x97, 0xfb, 0x5d, 0xed, 0x1f, 0xa0, 0x5f, 0xe4, 0x98, - 0x2f, 0xf6, 0x13, 0x78, 0x99, 0xec, 0xb3, 0xf1, 0x0d, 0x27, 0xaa, 0x19, - 0x95, 0x39, 0xdb, 0xb0, 0x7b, 0x96, 0x74, 0x03, 0x5e, 0x51, 0xf5, 0x15, - 0x27, 0xce, 0xca, 0x0b, 0x2a, 0x0d, 0x43, 0xb3, 0x68, 0x17, 0x1e, 0x11, - 0x60, 0xd9, 0x84, 0x9b, 0xc3, 0x53, 0xce, 0xbd, 0xf4, 0x61, 0x51, 0x4b, - 0x41, 0x00, 0x7e, 0xe1, 0x5f, 0x69, 0xb3, 0x4a, 0x89, 0x7e, 0x47, 0x67, - 0xfd, 0x76, 0xf8, 0x94, 0x2f, 0x72, 0xb6, 0x14, 0x08, 0x2c, 0x16, 0x4e, - 0x9d, 0x37, 0x62, 0xbf, 0x11, 0x67, 0xc0, 0x70, 0x71, 0xec, 0x55, 0x51, - 0x4e, 0x46, 0x76, 0xb4, 0xc3, 0xeb, 0x52, 0x06, 0x17, 0x06, 0xce, 0x61, - 0x43, 0xce, 0x26, 0x80, 0x68, 0xb6, 0x2d, 0x57, 0xba, 0x8c, 0x7d, 0xb7, - 0xc5, 0x05, 0x2c, 0xf8, 0xa3, 0x69, 0xf8, 0x96, 0xad, 0xac, 0xd1, 0x30, - 0x82, 0x05, 0xa6, 0x30, 0x82, 0x03, 0x8e, 0xa0, 0x03, 0x02, 0x01, 0x02, - 0x02, 0x10, 0x73, 0xd1, 0xe1, 0x1d, 0xa9, 0x75, 0xfd, 0x0c, 0xda, 0x7f, - 0xfa, 0x43, 0x3c, 0x26, 0xbd, 0x3d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, - 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x7e, 0x31, - 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, - 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, - 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x11, 0x30, - 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, - 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, - 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, - 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, - 0x76, 0x69, 0x6e, 0x65, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, - 0x03, 0x0c, 0x1a, 0x77, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x65, 0x6d, 0x2d, 0x72, 0x6f, 0x6f, 0x74, - 0x2d, 0x70, 0x72, 0x6f, 0x64, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x37, 0x30, - 0x33, 0x31, 0x34, 0x30, 0x33, 0x30, 0x32, 0x34, 0x31, 0x5a, 0x17, 0x0d, - 0x32, 0x37, 0x30, 0x33, 0x31, 0x34, 0x30, 0x33, 0x30, 0x32, 0x34, 0x31, - 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, - 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, - 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, - 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, - 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, - 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, 0x30, 0x30, - 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x27, 0x47, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x20, 0x4f, 0x45, 0x4d, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, - 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x3b, 0x20, 0x73, 0x79, 0x73, 0x74, - 0x65, 0x6d, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x37, 0x33, 0x34, 0x36, 0x30, - 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, - 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, - 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa5, 0x45, 0x13, 0xf2, - 0xb2, 0xcb, 0x4b, 0x0f, 0xb4, 0x44, 0x25, 0x9c, 0x8a, 0x68, 0x54, 0xd5, - 0x45, 0x1e, 0x15, 0x89, 0x5b, 0xb8, 0xce, 0xda, 0x5a, 0x42, 0xe6, 0x9a, - 0x8c, 0xc1, 0xcb, 0xe8, 0xc5, 0xf5, 0x8f, 0x49, 0x0e, 0x02, 0xef, 0x5e, - 0x97, 0x1a, 0x91, 0xa4, 0x94, 0xc3, 0x50, 0x13, 0xe5, 0x13, 0xb7, 0x7f, - 0x26, 0x53, 0x19, 0xb0, 0x37, 0xa5, 0xef, 0xe6, 0x2a, 0x39, 0xdc, 0x93, - 0x37, 0xe2, 0x3d, 0x7f, 0xcb, 0x4b, 0x93, 0xa2, 0xc3, 0x69, 0x78, 0xc9, - 0x01, 0xfa, 0x68, 0x3b, 0xe0, 0xe2, 0x22, 0x6c, 0xeb, 0xe4, 0x8a, 0xa8, - 0x3e, 0xf5, 0x20, 0x82, 0xa8, 0x62, 0x68, 0x59, 0x78, 0x24, 0xde, 0xef, - 0x47, 0x43, 0xb1, 0x6c, 0x38, 0x29, 0xd3, 0x69, 0x3f, 0xae, 0x35, 0x57, - 0x75, 0x80, 0xc9, 0x21, 0xe7, 0x01, 0xb9, 0x54, 0x8b, 0x6e, 0x4e, 0x2e, - 0x5a, 0x5b, 0x77, 0xa4, 0x22, 0xc2, 0x7b, 0x95, 0xb9, 0x39, 0x2c, 0xbd, - 0xc2, 0x1e, 0x02, 0xa6, 0xb2, 0xbc, 0x0f, 0x7a, 0xcb, 0xdc, 0xbc, 0xbc, - 0x90, 0x66, 0xe3, 0xca, 0x46, 0x53, 0x3e, 0x98, 0xff, 0x2e, 0x78, 0x9f, - 0xd3, 0xa1, 0x12, 0x93, 0x66, 0x7d, 0xcc, 0x94, 0x6b, 0xec, 0x19, 0x0e, - 0x20, 0x45, 0x22, 0x57, 0x6d, 0x9e, 0xd0, 0x89, 0xf2, 0xa9, 0x34, 0xdc, - 0xab, 0xa5, 0x73, 0x47, 0x38, 0xe3, 0x7f, 0x98, 0x3a, 0x61, 0xae, 0x6c, - 0x4d, 0xf2, 0x31, 0x90, 0xcb, 0x83, 0xc1, 0xee, 0xb4, 0xf2, 0x9a, 0x28, - 0x5f, 0xbb, 0x7d, 0x89, 0xdf, 0xa2, 0x31, 0xb6, 0x1d, 0x39, 0x2b, 0x70, - 0xbf, 0x1e, 0xad, 0xe1, 0x74, 0x94, 0x1d, 0xf8, 0xc5, 0x1a, 0x8d, 0x13, - 0x45, 0xf0, 0x6a, 0x80, 0x0c, 0x5d, 0xbb, 0x46, 0x8a, 0x43, 0xd0, 0xff, - 0x21, 0x39, 0x57, 0x53, 0x5b, 0x51, 0xf8, 0xa2, 0x8f, 0x7f, 0x27, 0xc7, - 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x10, 0x30, 0x82, 0x01, - 0x0c, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, - 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, - 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, - 0x04, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, - 0xe8, 0xe9, 0xac, 0x16, 0x5c, 0x5e, 0xb2, 0xe8, 0xeb, 0xff, 0x57, 0x27, - 0x20, 0x08, 0x72, 0x63, 0x9b, 0xe5, 0xb5, 0x16, 0x30, 0x81, 0xb2, 0x06, - 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0xaa, 0x30, 0x81, 0xa7, 0x80, 0x14, - 0x04, 0x94, 0x66, 0xaa, 0xf9, 0x61, 0x89, 0xb6, 0xdb, 0xb5, 0xf7, 0x13, - 0x38, 0x3d, 0x62, 0x84, 0xb8, 0x18, 0x0a, 0x8f, 0xa1, 0x81, 0x83, 0xa4, - 0x81, 0x80, 0x30, 0x7e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, - 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, - 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, - 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, - 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, - 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, - 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, - 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, 0x23, 0x30, - 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1a, 0x77, 0x69, 0x64, 0x65, - 0x76, 0x69, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x65, 0x6d, - 0x2d, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x70, 0x72, 0x6f, 0x64, 0x82, 0x09, - 0x00, 0xdf, 0x86, 0x05, 0x31, 0x01, 0xbe, 0x9a, 0x9a, 0x30, 0x12, 0x06, - 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x04, 0x01, 0x01, 0x04, - 0x04, 0x02, 0x02, 0x1c, 0xb2, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, - 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, - 0x00, 0x25, 0xce, 0xd2, 0x02, 0x48, 0xbb, 0xbe, 0xfc, 0xb6, 0xa4, 0x87, - 0x87, 0xe0, 0x21, 0x7d, 0xfa, 0x23, 0xc3, 0x0d, 0x73, 0x8f, 0x46, 0xe7, - 0x09, 0x59, 0xda, 0x2e, 0x55, 0x59, 0xff, 0x3c, 0x1b, 0xf6, 0xf8, 0x9a, - 0xc4, 0x1c, 0xf7, 0xac, 0xca, 0xe7, 0x63, 0xf2, 0xc7, 0xd6, 0x0c, 0x2d, - 0xa6, 0xad, 0x55, 0xf4, 0x10, 0x0e, 0xa8, 0x82, 0x0f, 0x88, 0xb5, 0x44, - 0xe8, 0x8e, 0x84, 0x08, 0xf7, 0xdd, 0xe7, 0x10, 0xce, 0x71, 0x56, 0x57, - 0x3f, 0xed, 0x48, 0xee, 0xe2, 0x5d, 0x08, 0x0a, 0x58, 0xe4, 0xfe, 0xbc, - 0x8c, 0x27, 0x1a, 0x46, 0x3f, 0xd5, 0x2d, 0xdb, 0x0b, 0x71, 0x73, 0xd1, - 0x49, 0xf3, 0x5c, 0x86, 0x4d, 0x0a, 0xe1, 0xeb, 0x53, 0x21, 0x38, 0x4f, - 0xec, 0x1e, 0xc2, 0x68, 0x1f, 0x7d, 0xa6, 0x33, 0xe9, 0xa5, 0x37, 0x2a, - 0xef, 0xcd, 0x78, 0x56, 0xb3, 0x39, 0x60, 0xf4, 0xa5, 0xf9, 0x2b, 0x85, - 0xcf, 0xe6, 0x1c, 0x7c, 0x8a, 0x5d, 0xe8, 0x26, 0x02, 0xcf, 0x7a, 0x56, - 0x1f, 0xae, 0x0d, 0x71, 0x20, 0xee, 0xec, 0x3b, 0xae, 0x95, 0x25, 0x15, - 0xc8, 0xf6, 0x92, 0x5d, 0xb8, 0x9b, 0xc2, 0xb4, 0x95, 0x33, 0x13, 0x76, - 0x45, 0xbe, 0x21, 0xe2, 0x3a, 0x69, 0x66, 0xd7, 0xff, 0x22, 0x00, 0x89, - 0xc9, 0x44, 0xb6, 0x54, 0x38, 0x1f, 0x33, 0xe4, 0xda, 0x7b, 0x87, 0xf3, - 0x23, 0xed, 0xf5, 0x16, 0x08, 0xbe, 0x4b, 0xea, 0x91, 0x8f, 0x91, 0x8b, - 0x4e, 0xd1, 0x02, 0x06, 0xa2, 0x77, 0x15, 0x03, 0x46, 0x11, 0x7d, 0x5b, - 0xea, 0x7a, 0xf6, 0x86, 0x7d, 0x96, 0xb7, 0x73, 0x9b, 0x5b, 0x32, 0xc3, - 0xf8, 0x92, 0x36, 0xe3, 0xe3, 0x2f, 0xe8, 0xf1, 0x72, 0xec, 0x0d, 0x50, - 0xd4, 0x86, 0xc5, 0x62, 0x83, 0xf1, 0x2a, 0x4c, 0xd1, 0xbf, 0x76, 0x62, - 0xd4, 0x21, 0x11, 0x68, 0xb2, 0xd6, 0x8d, 0xc4, 0xf8, 0xe4, 0x70, 0x85, - 0x19, 0xa7, 0x82, 0x27, 0x2c, 0x24, 0x21, 0x7a, 0x3b, 0xad, 0x8a, 0xd3, - 0xae, 0xda, 0x78, 0x3c, 0x6c, 0xab, 0xa2, 0xaa, 0x36, 0xf0, 0x1c, 0x58, - 0xd4, 0x72, 0x5e, 0xe8, 0x8b, 0x41, 0x08, 0xf5, 0x85, 0xdd, 0xee, 0x99, - 0x12, 0xf4, 0xd6, 0x41, 0x83, 0x69, 0xe7, 0x79, 0x19, 0xa3, 0x74, 0xc4, - 0x34, 0x2a, 0x8a, 0x7e, 0x4d, 0xbb, 0x2c, 0x49, 0x19, 0xf7, 0x98, 0x98, - 0xfc, 0x81, 0xf7, 0x9b, 0x7f, 0xff, 0xd9, 0x66, 0xf4, 0x51, 0x14, 0x29, - 0x2a, 0x14, 0x1d, 0x4f, 0xbd, 0x91, 0xba, 0x6f, 0x32, 0x34, 0x3c, 0x40, - 0x28, 0x6c, 0x97, 0xf8, 0x6d, 0x38, 0xcd, 0xa3, 0x7b, 0x18, 0xc8, 0x77, - 0x58, 0x4d, 0x53, 0x30, 0x7f, 0x4d, 0x89, 0xca, 0x95, 0x6e, 0xb5, 0xb8, - 0x8e, 0xc8, 0x2d, 0x18, 0x2f, 0x52, 0x2a, 0xde, 0xac, 0x56, 0x8d, 0x8c, - 0x67, 0x14, 0xf6, 0xb9, 0xf1, 0x65, 0xd3, 0x22, 0x43, 0xa3, 0x98, 0x42, - 0x20, 0x43, 0x4c, 0xdf, 0xf2, 0xeb, 0x31, 0x8c, 0x0e, 0x53, 0x5b, 0x99, - 0x82, 0xc3, 0x48, 0x04, 0x53, 0xad, 0x96, 0xb6, 0x9f, 0x52, 0xcc, 0x01, - 0xc8, 0xb3, 0x87, 0x6b, 0x9e, 0xea, 0xa9, 0xeb, 0xda, 0xac, 0xf9, 0x6f, - 0xde, 0xa1, 0x44, 0x32, 0x52, 0x49, 0x47, 0xff, 0x65, 0x79, 0x1e, 0xc5, - 0x73, 0x17, 0xb3, 0x36, 0xfc, 0x45, 0xca, 0x90, 0x37, 0x59, 0x1e, 0x16, - 0xab, 0x09, 0x69, 0xcf, 0xda, 0x56, 0x51, 0xfd, 0xeb, 0xcf, 0xcb, 0x8f, - 0xb1, 0xc3, 0x45, 0x2b, 0x7c, 0x0a, 0xa5, 0x9c, 0x0d, 0x2c, 0xad, 0x1c, - 0xd3, 0x33, 0xdd, 0xfe, 0x93, 0x69, 0xa2, 0x4b, 0x4b, 0xcf, 0x1d, 0x20, - 0x98, 0x4a, 0x4f, 0x5b, 0xe9, 0x24, 0xca, 0xfa, 0x18, 0x11, 0x81, 0x8b, - 0x7a, 0xb4, 0x5a, 0xc8, 0xdf, 0x6f, 0x5f, 0x21, 0x07, 0x31, 0x00}; - -const uint32_t kOemCertSystemId = 7346; - -} // namespace - namespace wvcdm { - class CryptoSessionForTest : public TestCryptoSession, public WvCdmTestBase { public: - using CryptoSession::ExtractSystemIdFromOemCert; CryptoSessionForTest() : TestCryptoSession(metrics_.GetCryptoMetrics()) {} - void SetUp() {} + void SetUp() override {} KeySession* key_session() { return key_session_.get(); } @@ -262,26 +39,19 @@ class CryptoSessionForTest : public TestCryptoSession, public WvCdmTestBase { metrics::SessionMetrics CryptoSessionForTest::metrics_; -TEST(CryptoSessionTest, CanExtractSystemIdFromOemCertificate) { - std::string oem_cert(reinterpret_cast(kOemCert), - sizeof(kOemCert)); - uint32_t system_id; - ASSERT_TRUE( - CryptoSessionForTest::ExtractSystemIdFromOemCert(oem_cert, &system_id)); - ASSERT_EQ(kOemCertSystemId, system_id); -} - class CryptoSessionMetricsTest : public WvCdmTestBase { protected: uint32_t FindKeyboxSystemID() { - OEMCryptoResult sts; + if (CryptoSession::needs_keybox_provisioning()) { + return NULL_SYSTEM_ID; + } uint8_t key_data[256]; size_t key_data_len = sizeof(key_data); - sts = OEMCrypto_GetKeyData(key_data, &key_data_len, kLevelDefault); - if (sts != OEMCrypto_SUCCESS) return 0; - uint32_t* data = reinterpret_cast(key_data); - uint32_t system_id = htonl(data[1]); - return system_id; + const OEMCryptoResult sts = + OEMCrypto_GetKeyData(key_data, &key_data_len, kLevelDefault); + if (sts != OEMCrypto_SUCCESS) return NULL_SYSTEM_ID; + const uint32_t* data = reinterpret_cast(key_data); + return htonl(data[1]); } }; @@ -291,8 +61,8 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) { CryptoSession::MakeCryptoSession(&crypto_metrics)); session->Open(wvcdm::kLevelDefault); // Exercise a method that will touch a metric. - CdmUsageSupportType usage_type; - ASSERT_EQ(NO_ERROR, session->GetUsageSupportType(&usage_type)); + bool supports_usage_table; + ASSERT_TRUE(session->HasUsageInfoSupport(&supports_usage_table)); drm_metrics::WvCdmMetrics::CryptoMetrics metrics_proto; crypto_metrics.Serialize(&metrics_proto); @@ -309,6 +79,8 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) { metrics_proto.oemcrypto_initialize_time_us(0).operation_count()); EXPECT_TRUE(metrics_proto.oemcrypto_initialize_time_us(0).has_mean()); + const CdmUsageSupportType usage_type = + supports_usage_table ? kUsageEntrySupport : kNonSecureUsageSupport; EXPECT_EQ(usage_type, metrics_proto.oemcrypto_usage_table_support().int_value()); @@ -316,27 +88,17 @@ TEST_F(CryptoSessionMetricsTest, OpenSessionValidMetrics) { CdmClientTokenType token_type = session->GetPreProvisionTokenType(); if (token_type == kClientTokenKeybox) { - uint32_t system_id = FindKeyboxSystemID(); - EXPECT_EQ(system_id, metrics_proto.crypto_session_system_id().int_value()); EXPECT_EQ(OEMCrypto_Keybox, metrics_proto.oemcrypto_provisioning_method().int_value()); - EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); } else if (token_type == kClientTokenOemCert) { - // Recent devices all have a system id between 1k and 6 or 7k. Errors - // we are trying to catch are 0, byte swapped 32 bit numbers, or - // garbage. These errors will most likely be outside the range of 1000 - // to 2^16. - EXPECT_LE(1000, metrics_proto.crypto_session_system_id().int_value()); - EXPECT_GT(0X10000, metrics_proto.crypto_session_system_id().int_value()); - EXPECT_EQ(OEMCrypto_OEMCertificate, metrics_proto.oemcrypto_provisioning_method().int_value()); - ASSERT_EQ(1, metrics_proto.oemcrypto_get_oem_public_certificate().size()); - EXPECT_THAT(metrics_proto.oemcrypto_get_oem_public_certificate(0).count(), - AllOf(Ge(1), Le(2))); } else if (token_type == kClientTokenDrmCert) { // TODO(blueeyes): Add support for getting the system id from a // pre-installed DRM certificate.. + } else if (token_type == kClientTokenBootCertChain) { + EXPECT_EQ(OEMCrypto_BootCertificateChain, + metrics_proto.oemcrypto_provisioning_method().int_value()); } else { FAIL() << "Unexpected token type: " << token_type; } @@ -355,36 +117,29 @@ TEST_F(CryptoSessionMetricsTest, GetProvisioningTokenValidMetrics) { // DRM Certificate provisioning method does not support a provisioning // token. Otherwise, we should be able to fetch the token. std::string token; + std::string additional_token; if (token_type != kClientTokenDrmCert) { - ASSERT_EQ(NO_ERROR, session->GetProvisioningToken(&token)); + ASSERT_EQ(NO_ERROR, + session->GetProvisioningToken(&token, &additional_token)); } drm_metrics::WvCdmMetrics::CryptoMetrics metrics_proto; crypto_metrics.Serialize(&metrics_proto); if (token_type == kClientTokenKeybox) { - ASSERT_EQ(1, metrics_proto.crypto_session_get_token().size()); - EXPECT_EQ(1, metrics_proto.crypto_session_get_token(0).count()); - - uint32_t system_id = FindKeyboxSystemID(); - EXPECT_EQ(system_id, metrics_proto.crypto_session_system_id().int_value()); - EXPECT_EQ(1, metrics_proto.oemcrypto_get_key_data_time_us().size()); + EXPECT_EQ(OEMCrypto_Keybox, + metrics_proto.oemcrypto_provisioning_method().int_value()); + ASSERT_GE(metrics_proto.crypto_session_get_token().size(), 1); + EXPECT_GE(metrics_proto.crypto_session_get_token(0).count(), 1); } else if (token_type == kClientTokenOemCert) { - // Recent devices all have a system id between 1k and 6 or 7k. Errors - // we are trying to catch are 0, byte swapped 32 bit numbers, or - // garbage. These errors will most likely be outside the range of 1000 - // to 2^16. - EXPECT_LE(1000, metrics_proto.crypto_session_system_id().int_value()); - EXPECT_GT(0X10000, metrics_proto.crypto_session_system_id().int_value()); - EXPECT_EQ(OEMCrypto_OEMCertificate, metrics_proto.oemcrypto_provisioning_method().int_value()); - ASSERT_EQ(1, metrics_proto.oemcrypto_get_oem_public_certificate().size()); + ASSERT_GE(metrics_proto.oemcrypto_get_oem_public_certificate().size(), 1); EXPECT_THAT(metrics_proto.oemcrypto_get_oem_public_certificate(0).count(), AllOf(Ge(1), Le(2))); - - ASSERT_EQ(1, metrics_proto.crypto_session_get_token().size()); - EXPECT_EQ(1, metrics_proto.crypto_session_get_token(0).count()); + } else if (token_type == kClientTokenBootCertChain) { + EXPECT_EQ(OEMCrypto_BootCertificateChain, + metrics_proto.oemcrypto_provisioning_method().int_value()); } else { ASSERT_EQ(0, metrics_proto.crypto_session_get_token().size()); } diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp index 1c8d6ed2..8c6acb66 100644 --- a/core/test/device_files_unittest.cpp +++ b/core/test/device_files_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "device_files.h" @@ -13,7 +13,9 @@ #include "arraysize.h" #include "cdm_random.h" +#include "crypto_wrapped_key.h" #include "file_store.h" +#include "okp_info.h" #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -22,110 +24,1598 @@ namespace wvcdm { namespace { +using wvutil::a2bs_hex; +using wvutil::ArraySize; +using wvutil::b2a_hex; +using wvutil::CdmRandom; +using wvutil::File; +using wvutil::FileSystem; const uint32_t kCertificateLen = 700; const uint32_t kWrappedKeyLen = 500; +constexpr size_t kZero = 0; +const std::string kOemCertificate = "an oem certificate"; +const std::string kDrmCertificate = "a drm certificate"; +const std::string kAnotherDrmCertificate = "another drm certificate"; +const std::string kWrappedPrivateKey = "a wrapped private key"; +const std::string kAnotherWrappedPrivateKey = "another wrapped private key"; +const CryptoWrappedKey kCryptoWrappedKey(CryptoWrappedKey::kRsa, + kWrappedPrivateKey); +const CryptoWrappedKey kAnotherCryptoWrappedKey(CryptoWrappedKey::kEcc, + kAnotherWrappedPrivateKey); const std::string kEmptyString; -// Structurally valid test certificate. -// The data elements in this module are used to test the storage and -// retrieval of certificates and licenses -const std::string kTestCertificate = a2bs_hex( - "0A98030802120D73657269616C5F6E756D62657218B4B2CDE00422E8024D49494243674B43" - "415145412B78475A2F77637A39756746705030374E73706F365531376C3059684669467078" - "78553470546B334C69667A3952337A734973754552777461372B66574966784F6F32303865" - "74742F6A68736B69566F645345743351424768345842697079576F704B775A393348486144" - "565A41414C692F32412B785442745764456F37584755756A4B447643322F615A4B756B666A" - "704F6955493841684C41666A6D6C63442F555A31515068306D4873676C524E436D7043776D" - "7753584139564E6D687A2B5069422B446D6C3457576E4B572F56486F32756A54587871372B" - "65664D55344832666E79335365334B594F73465046475A31544E5153596C46755368577248" - "5074694C6D5564506F50364356326D4D4C31746B2B6C3744494971587251684C554B444143" - "654D35726F4D78306B4C6855574238502B30756A31434E6C4E4E344A525A6C433778466671" - "694D62465255395A344E3659774944415141422899203A11746573742E7769646576696E65" - "2E636F6D128202307836353063396632653637303165336665373364333035343930346139" - "61346262646239363733336631633463373433656635373361643661633134633561336266" - "38613437333166366536323736666165613532343733303336373766623864626466323466" - "66373865353363323530353263646361383765656366656538353437366263623861303563" - "62396131656665663763623837646436383232336531313763653830306163343631373731" - "37323534343735376134383762653332663561623866653038373966613861646437386265" - "34363565613866386435616366393737653966316165333664346434373831366561366564" - "343133373262"); +// Structurally valid test certificate of provisioning 3.0. +// {'certificate': {'algorithm': 'RSA', +// 'creation_time': '2020-12-14T23:17:27', +// 'public_key': ... 270 bytes, +// 'serial_number': '7CB49F987A635E1E0A52184694582D6E', +// 'type': 'DRM_USER_DEVICE'}, +// 'signature': ... 256 bytes, +// 'signer': { +// 'certificate': {'creation_time': '2017-11-17T13:21:39', +// 'public_key': ... 270 bytes, +// 'serial_number': '65802C9B625E5A319C33DC1CB7C3C6D4', +// 'type': 'DRM_INTERMEDIATE'}, +// 'signature': ... 384 bytes } +// } +// Value of |certificate| in DeviceCertFile proto messages +// kTestCertificateFileData and kTestCertificateFileWithoutKeyTypeData +// This can be used for both ATSC and Legacy certificate +const std::string kTestCertificateWithoutExpiration = a2bs_hex( + "0AEB03080212107CB49F987A635E1E0A52184694582D6E1887C6E1FE05228E023082010A" + "0282010100DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D" + "7343553442A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC7" + "58FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C1" + "3E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B2780344" + "DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FAB25A" + "EE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D654851103F857" + "A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB" + "3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D480152AA01" + "080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE2235717A44F953" + "B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923F173186081" + "5999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035" + "041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A" + "2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB1280028CD44E12AA7C1A" + "8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2A" + "C1CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540" + "BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017" + "898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552F" + "B4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD292093" + "29C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6" + "E71E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB405" + "0AAE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A" + "0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A562" + "9B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC8" + "45AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E3143862" + "34C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475C55C608E771C763AB025" + "06F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B9274C16066F74FC401EA355F" + "0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A107C36223404F2BF1FCA16FD0" + "A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5" + "E36EE36E245859FC0F020301000128E83D1280037E06581A019184AB572AFDCADDD03F16" + "1CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9B3F3FB6DDFD92EF92DE62D41" + "D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FAA6DD98D95A143CC8C1CB6A" + "DDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A86669710A1AD7A44BF92180" + "27460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D53EB5732F8F91E9A96B" + "3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E059F9734A96BE25AA44560DBA8" + "C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A50B23251A088136" + "D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A30E3447634AB" + "3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD6" + "45A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B25564A73" + "A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA" + "98383E3CD2ED4830"); + +// Certificate data: +// Similar to kTestCertificateWithoutExpiration +// creation_time_seconds: 0 +// expiration_time_seconds: unset +const std::string kTestCertificateNoExpirationWithUnlimitedCreationTime = + a2bs_hex( + "0AE703080212107CB49F987A635E1E0A52184694582D6E1800228E023082010A028201" + "0100DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D7343" + "553442A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC758" + "FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C1" + "3E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B27803" + "44DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FA" + "B25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D6548511" + "03F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A1" + "85B97FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D" + "480152AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE22" + "35717A44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB5" + "8923F1731860815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B" + "624A92158AC91035041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBB" + "CA2220595267DCA89A2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB" + "1280028CD44E12AA7C1A8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52" + "A0E18E929A4923A4172C2AC1CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB906163" + "2C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE9" + "35FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE" + "93A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502EB649D982F06D308178" + "642C1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB9" + "40299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D2847783338" + "D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A319C33DC" + "1CB7C3C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A0FD8D25C61" + "3E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3D" + "F6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4" + "A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF912" + "6C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE" + "7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F" + "8DB2A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14A" + "CAE3B05A038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F02" + "0301000128E83D1280037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD16" + "1947360BC8D49C0D68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3" + "970A3A39D25B2662ECB03B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C372" + "3FAF95A29CDC3E968B6821A91C051CA280A86669710A1AD7A44BF9218027460DF694E2" + "E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C5" + "1339EA284D4D0EDD55B6AD56F7416420E05E059F9734A96BE25AA44560DBA8C38755A4" + "2A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A50B23251A088136D6E8F4" + "75299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A30E3447634AB3408" + "F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD645" + "A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B25564A73" + "A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558" + "FA98383E3CD2ED4830"); + +// Certificate data: +// Similar to kTestCertificateWithoutExpiration +// creation_time_seconds: -5 +// expiration_time_seconds: unset +const std::string kTestCertificateWithInvalidCreationTime = a2bs_hex( + "0AEB03080212107CB49F987A635E1E0A52184694582D6E18FBFFFFFF0F228E023082010A02" + "82010100DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D7343" + "553442A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC758FB9E" + "06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C13E184034" + "EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71" + "F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D" + "614974942A36527C62B73A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99" + "713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92" + "67020301000128E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F" + "554B9400E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B" + "10106CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0D174" + "06B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408" + "A07C103DF860DC0520C3664EEB1280028CD44E12AA7C1A8EBF88C81A2A54EFD29F8BC6C377" + "B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7833AA0DE9D09F685" + "DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE521308F3D4CF5" + "13C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D8DA39E769E8D" + "1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502EB649D982F06D" + "308178642C1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A89E13D4AB2E8D" + "B940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D2847783338D7" + "4F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3" + "C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E" + "349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B9" + "83A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D70" + "5520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC4" + "75C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B9274" + "C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A107C362" + "23404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47F" + "B9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06581A019184AB57" + "2AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9B3F3FB6DDFD9" + "2EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FAA6DD98D95A14" + "3CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A86669710A1AD7A4" + "4BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D53EB5732F8F91" + "E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E059F9734A96BE25AA44560" + "DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A50B23251A0881" + "36D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A30E3447634AB" + "3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD645" + "A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B25564A73A30E" + "2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA98383E" + "3CD2ED4830"); + +// Certificate data: +// Similar to kTestCertificateWithoutExpiration +// creation_time_seconds: ~ 03/16/2021 +// expiration_time_seconds: ~ 03/2031 +const std::string kTestCertificateWithFutureExpiration = a2bs_hex( + "0AF103080212107CB49F987A635E1E0A52184694582D6E1887C6E1FE05228E023082010A02" + "82010100DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D7343" + "553442A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC758FB9E" + "06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C13E184034" + "EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71" + "F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D" + "614974942A36527C62B73A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99" + "713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92" + "67020301000128E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F" + "554B9400E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B" + "10106CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0D174" + "06B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408" + "A07C103DF860DC0520C3664EEB60E29D8399071280028CD44E12AA7C1A8EBF88C81A2A54EF" + "D29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7833A" + "A0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE5" + "21308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D" + "8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502E" + "B649D982F06D308178642C1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A8" + "9E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D" + "2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A31" + "9C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A0FD8D25C" + "613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6" + "860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D920" + "0FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA9" + "3BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C640769053" + "3BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD79" + "3296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4" + "391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E0658" + "1A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9" + "B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FA" + "A6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A866" + "69710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D5" + "3EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E059F9734A9" + "6BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A5" + "0B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A" + "30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C" + "29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B" + "25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D42" + "6558FA98383E3CD2ED4830"); + +// Certificate data: +// Similar to kTestCertificateWithoutExpiration +// creation_time_seconds: ~ 03/17/2021 +// expiration_time_seconds: 0 +const std::string kTestCertificateNeverExpires = a2bs_hex( + "0AED03080212107CB49F987A635E1E0A52184694582D6E1894AECC8206228E023082010A02" + "82010100DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D7343" + "553442A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC758FB9E" + "06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C13E184034" + "EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71" + "F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D" + "614974942A36527C62B73A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99" + "713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92" + "67020301000128E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F" + "554B9400E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B" + "10106CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0D174" + "06B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408" + "A07C103DF860DC0520C3664EEB60001280028CD44E12AA7C1A8EBF88C81A2A54EFD29F8BC6" + "C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7833AA0DE9D09" + "F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE521308F3D" + "4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D8DA39E76" + "9E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502EB649D982" + "F06D308178642C1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A89E13D4AB" + "2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D28477833" + "38D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A319C33DC1C" + "B7C3C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E" + "3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB" + "60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED4" + "5D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA" + "3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B" + "9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A107" + "C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4391BBFA7" + "A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06581A019184" + "AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9B3F3FB6D" + "DFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FAA6DD98D9" + "5A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A86669710A1A" + "D7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D53EB5732F" + "8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E059F9734A96BE25AA4" + "4560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A50B23251A" + "088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A30E34476" + "34AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C29CED5EA" + "D645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B25564A73" + "A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA98" + "383E3CD2ED4830"); + +// Certificate data: +// Similar to kTestCertificateWithoutExpiration +// creation_time_seconds: ~ 03/07/2021 +// expiration_time_seconds: ~ 03/08/2021 +const std::string kTestCertificateExpired = a2bs_hex( + "0AF103080212107CB49F987A635E1E0A52184694582D6E189EF0968206228E023082010A02" + "82010100DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D7343" + "553442A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC758FB9E" + "06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C13E184034" + "EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71" + "F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D" + "614974942A36527C62B73A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99" + "713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92" + "67020301000128E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F" + "554B9400E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B" + "10106CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0D174" + "06B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408" + "A07C103DF860DC0520C3664EEB609E939C82061280028CD44E12AA7C1A8EBF88C81A2A54EF" + "D29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7833A" + "A0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE5" + "21308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D" + "8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502E" + "B649D982F06D308178642C1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A8" + "9E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D" + "2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A31" + "9C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A0FD8D25C" + "613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6" + "860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D920" + "0FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA9" + "3BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C640769053" + "3BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD79" + "3296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4" + "391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E0658" + "1A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9" + "B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FA" + "A6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A866" + "69710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D5" + "3EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E059F9734A9" + "6BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A5" + "0B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A" + "30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C" + "29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B" + "25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D42" + "6558FA98383E3CD2ED4830"); + +// Certificate data: +// Similar to kTestCertificateWithoutExpiration +// creation_time_seconds: unset +// expiration_time_seconds: ~ 03/08/2031 +const std::string kTestCertificateWithInvalidCreationFutureExpiration = + a2bs_hex( + "0AEB03080212107CB49F987A635E1E0A52184694582D6E228E023082010A0282010100" + "DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D73435534" + "42A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC758FB9E" + "06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C13E18" + "4034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B2780344DD" + "5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FAB25A" + "EE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D654851103F8" + "57A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A185B9" + "7FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D4801" + "52AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE223571" + "7A44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923" + "F1731860815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A" + "92158AC91035041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA22" + "20595267DCA89A2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB60FD" + "8AFC98071280028CD44E12AA7C1A8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D" + "8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7833AA0DE9D09F685DAC9ACC702" + "CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE521308F3D4CF513C205" + "00064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D8DA39E769E8D14" + "85253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502EB649D982F0" + "6D308178642C1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A89E13D4" + "AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D28" + "47783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A" + "319C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A0F" + "D8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70" + "BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F" + "842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9A" + "DB4EF9126C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA" + "64B1EBDE7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0" + "C89E6E1F8DB2A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB624" + "07F8F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E2458" + "59FC0F020301000128E83D1280037E06581A019184AB572AFDCADDD03F161CE68200F8" + "E6F8AD161947360BC8D49C0D68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D" + "81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D" + "2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A86669710A1AD7A44BF921802746" + "0DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B" + "8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E059F9734A96BE25AA44560DBA8" + "C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A50B23251A0881" + "36D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A30E34476" + "34AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C29CE" + "D5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B" + "25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E" + "3D426558FA98383E3CD2ED4830"); + +// Certificate data: +// Similar to kTestCertificateWithoutExpiration +// creation_time_seconds: 0 +// expiration_time_seconds: ~2031 +const std::string kTestCertificateWithUnlimitedCreationFutureExpiration = + a2bs_hex( + "0AEB03080212107CB49F987A635E1E0A52184694582D6E228E023082010A0282010100" + "DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D73435534" + "42A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC758FB9E" + "06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C13E18" + "4034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B2780344DD" + "5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FAB25A" + "EE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D654851103F8" + "57A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A185B9" + "7FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D4801" + "52AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE223571" + "7A44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923" + "F1731860815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A" + "92158AC91035041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA22" + "20595267DCA89A2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB60AE" + "91FC98071280028CD44E12AA7C1A8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D" + "8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7833AA0DE9D09F685DAC9ACC702" + "CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE521308F3D4CF513C205" + "00064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D8DA39E769E8D14" + "85253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502EB649D982F0" + "6D308178642C1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A89E13D4" + "AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D28" + "47783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A" + "319C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A0F" + "D8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70" + "BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F" + "842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9A" + "DB4EF9126C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA" + "64B1EBDE7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0" + "C89E6E1F8DB2A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB624" + "07F8F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E2458" + "59FC0F020301000128E83D1280037E06581A019184AB572AFDCADDD03F161CE68200F8" + "E6F8AD161947360BC8D49C0D68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D" + "81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D" + "2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A86669710A1AD7A44BF921802746" + "0DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B" + "8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E059F9734A96BE25AA44560DBA8" + "C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A50B23251A0881" + "36D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A30E34476" + "34AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C29CE" + "D5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B" + "25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E" + "3D426558FA98383E3CD2ED4830"); + +// Certificate data: +// Similar to kTestCertificateWithoutExpiration +// creation_time_seconds: 03/17/2021 +// expiration_time_seconds: 03/07/2021 +const std::string kTestCertificateExpiresBeforeCreationTime = a2bs_hex( + "0AF103080212107CB49F987A635E1E0A52184694582D6E18EC95CC8206228E023082010A02" + "82010100DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24448D07C30D7343" + "553442A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6CF544FC1FB3FC758FB9E" + "06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36583C8FDB839C2752C13E184034" + "EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71" + "F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D" + "614974942A36527C62B73A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99" + "713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92" + "67020301000128E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F" + "554B9400E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B" + "10106CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0D174" + "06B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408" + "A07C103DF860DC0520C3664EEB60ECB79782061280028CD44E12AA7C1A8EBF88C81A2A54EF" + "D29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7833A" + "A0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE5" + "21308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D" + "8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502E" + "B649D982F06D308178642C1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A8" + "9E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D" + "2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A31" + "9C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B80502043C2A8A0FD8D25C" + "613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6" + "860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D920" + "0FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA9" + "3BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C640769053" + "3BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD79" + "3296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4" + "391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E0658" + "1A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9" + "B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FA" + "A6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A866" + "69710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0FEB3D5" + "3EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E059F9734A9" + "6BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A8838C423D824A5" + "0B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A" + "30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C" + "29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B" + "25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D42" + "6558FA98383E3CD2ED4830"); // A Wrapped Private Key -// The data elements in this module are used to test the storage and -// retrieval of certificates and licenses -const std::string kTestWrappedPrivateKey = - "4F724B065326371A2F5F6F51467C2E26555C453B5C7C1B4F2738454B782E3E7B5340435A" - "66374D0612052C521A233D7A67194871751C78575E5177070130264C4F037633320E667B" - "1A49192924491338693D106E6113014A733A241A1A033E28352178146B4F543D38104A59" - "19120325502C31365506096D59585E08774B5B567A7B5D03451E6B11633E52672C226103" - "104B3E4C031A6403050F3A574D2C501711773802741F7F3A0D364757101D02181C7D4D35" - "207167506A424C094E4A72316F791F162D76657D2B5D3C2D7B273A286927717561316518" - "7E55282430491467086425432347701C3116446D21645C756B2D3D0F797C3220322D622A" - "254D0B7D4F1D5D0C0A36755D1246741A34783C45157247091C78232B7D2E0E1F637A2A37" - "39085D76166747034350613969072F5B5C5B21657E470C7E513B3F091D74455A3A073705" - "7B7E3B5337191D4E7536087C334B6028530F3F5B23380B6A076031294501003D6D1F240F" - "63053D5D0B271B6A0F26185650731308660B0447566041684F584C22216E567D3B775569" - "5F7F3D6B64525E7227165948101540243C19495C4C702F37490F26613353797825624143" - "263043020E1E6760123D51056F2F1E482F2E3D021B27677D3E7E3C0C11757C3448275E08" - "382E111263644C6D224714706D760A054A586E17505C3429575A41043F184209"; +// Value of |wrapped_private_key| in DeviceCertFile proto messages +// kTestCertificateFileData and kTestCertificateFileWithoutKeyTypeData. +// Value is randomly generated value and is not compatible with a real +// OEMCrypto implementation. +const std::string kTestWrappedPrivateKeyData = a2bs_hex( + "B36550E6BEACCB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61" + "E21CEE503E7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA6095618" + "D647F5212C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979D61AE6" + "59E340489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC408B602B0B" + "C099920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC9B30784952" + "B3A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C" + "231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C7D3B8C75CE588D" + "B0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA84CD01E335E68AF15" + "3B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F8702941409E7276910CE118" + "19649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C84CB21AB6E75E59DAFBE7" + "01D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B69180B697521F3865B4A05DF2B" + "B51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14E5BA19DBF9394E18E474E790" + "63B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC5DEC2A81B5359771849E" + "7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068ADFAFAD4081ACA67FD"); +const CryptoWrappedKey kTestWrappedKey = {CryptoWrappedKey::kRsa, + kTestWrappedPrivateKeyData}; -// The test certificate in file storage format. -// The data elements in this module are used to test the storage and -// retrieval of certificates and licenses -const std::string kTestCertificateFileData = a2bs_hex( - "0A950D080110011A8E0D0AA0050A98030802120D73657269616C5F6E756D62657218B4B2CD" - "E00422E8024D49494243674B43415145412B78475A2F77637A39756746705030374E73706F" - "365531376C305968466946707878553470546B334C69667A3952337A734973754552777461" - "372B66574966784F6F3230386574742F6A68736B69566F6453457433514247683458426970" - "79576F704B775A393348486144565A41414C692F32412B785442745764456F37584755756A" - "4B447643322F615A4B756B666A704F6955493841684C41666A6D6C63442F555A3151506830" - "6D4873676C524E436D7043776D7753584139564E6D687A2B5069422B446D6C3457576E4B57" - "2F56486F32756A54587871372B65664D55344832666E79335365334B594F73465046475A31" - "544E5153596C467553685772485074694C6D5564506F50364356326D4D4C31746B2B6C3744" - "494971587251684C554B444143654D35726F4D78306B4C6855574238502B30756A31434E6C" - "4E4E344A525A6C433778466671694D62465255395A344E3659774944415141422899203A11" - "746573742E7769646576696E652E636F6D1282023078363530633966326536373031653366" - "65373364333035343930346139613462626462393637333366316334633734336566353733" - "61643661633134633561336266386134373331663665363237366661656135323437333033" - "36373766623864626466323466663738653533633235303532636463613837656563666565" - "38353437366263623861303563623961316566656637636238376464363832323365313137" - "63653830306163343631373731373235343437353761343837626533326635616238666530" - "38373966613861646437386265343635656138663864356163663937376539663161653336" - "6434643437383136656136656434313337326212E807344637323442303635333236333731" - "41324635463646353134363743324532363535354334353342354337433142344632373338" - "34353442373832453345374235333430343335413636333734443036313230353243353231" - "41323333443741363731393438373137353143373835373545353137373037303133303236" - "34433446303337363333333230453636374231413439313932393234343931333338363933" - "44313036453631313330313441373333413234314131413033334532383335323137383134" - "36423446353433443338313034413539313931323033323535303243333133363535303630" - "39364435393538354530383737344235423536374137423544303334353145364231313633" - "33453532363732433232363130333130344233453443303331413634303330353046334135" - "37344432433530313731313737333830323734314637463341304433363437353731303144" - "30323138314337443444333532303731363735303641343234433039344534413732333136" - "46373931463136324437363635374432423544334332443742323733413238363932373731" - "37353631333136353138374535353238323433303439313436373038363432353433323334" - "37373031433331313634343644323136343543373536423244334430463739374333323230" - "33323244363232413235344430423744344631443544304330413336373535443132343637" - "34314133343738334334353135373234373039314337383233324237443245304531463633" - "37413241333733393038354437363136363734373033343335303631333936393037324635" - "42354335423231363537453437304337453531334233463039314437343435354133413037" - "33373035374237453342353333373139314434453735333630383743333334423630323835" - "33304633463542323333383042364130373630333132393435303130303344364431463234" - "30463633303533443544304232373142364130463236313835363530373331333038363630" - "42303434373536363034313638344635383443323232313645353637443342373735353639" - "35463746334436423634353235453732323731363539343831303135343032343343313934" - "39354334433730324633373439304632363631333335333739373832353632343134333236" - "33303433303230453145363736303132334435313035364632463145343832463245334430" - "32314232373637374433453745334330433131373537433334343832373545303833383245" - "31313132363336343443364432323437313437303644373630413035344135383645313735" - "303543333432393537354134313034334631383432303912205C6993E9656F73A41739773A" - "0FCBA8AE232CD8856ACE585FF6BFB2A09C20061E"); +// Structurally valid test certificate device file, missing |key_type| field. +// {'certificate': kTestCertificate, +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// } +const std::string kTestCertificateFileWithoutKeyTypeData = a2bs_hex( + "0AA90F080110011AA20F0AA80B0AEB03080212107CB49F987A635E1E0A52184694582D6E" + "1887C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A9" + "9129BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398" + "0512445EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9" + "830E0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C" + "9CF1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78" + "471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828" + "EB598DA59060D654851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888" + "AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C77696465" + "76696E652E636F6D480152AA01080110001A8101044F554B9400E10B17185036B6A1628E" + "FC61B22166DE2235717A44F953B7928F3415B9D113835B10106CB6C2187F34188723D82E" + "CF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096A" + "AEC3AC761B624A92158AC91035041173392B1E495428F0D17406B10889B6B701FAF08D22" + "84F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3" + "664EEB1280028CD44E12AA7C1A8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9E" + "AD52A0E18E929A4923A4172C2AC1CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061" + "632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE9" + "35FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93" + "A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502EB649D982F06D308178642C" + "1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D" + "1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D2847783338D74F66D2" + "DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3C6D4" + "18E3A5BDD005228E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E34" + "9F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B9" + "83A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D" + "705520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA" + "3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD689" + "0B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296" + "A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE439" + "1BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E0658" + "1A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644" + "F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B683" + "02FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA2" + "80A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4" + "D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E" + "059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A88" + "38C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB" + "813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177C" + "DD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E" + "07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAED" + "AD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEACCB34F6C3B2AB" + "F86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A418" + "6A362D9E6F88DD48D4516635C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172B" + "C691530703FEDDFDB25ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445" + "A4B0AE88A3A7F29ACE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C" + "2B2E8CA5C1EEC0844BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C" + "0557EBE7484FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449D" + "E4785F1D4835AC44E39B119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518E" + "F964C9D985AFD3677F0D13015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C638" + "535FA3F215F750F6954AC395F8702941409E7276910CE11819649641318B5BD1B78DECEA" + "DB2B562312CC286DB0BCC14A60C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422E" + "D8EB5ECE330C9886406B3B69180B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C36" + "0C759F5B10E0D354D63D5A14E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B" + "091B6D7A21DD9D6D6750C1CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F5" + "7F035DFB50EC4354D7E068ADFAFAD4081ACA67FD1220F07050C50264B496211432D47DAA" + "88EE59BAD141B8FD372BAE67A6FF05C74DAC"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithoutExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': unset +// 'expiration_time_seconds': unset +// } +const std::string kTestCertificateFileDataWithoutExpiration = a2bs_hex( + "0AAB0F080110011AA40F0AA80B0AEB03080212107CB49F987A635E1E0A52184694582D6E" + "1887C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A9" + "9129BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398" + "0512445EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9" + "830E0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C" + "9CF1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78" + "471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828" + "EB598DA59060D654851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888" + "AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C77696465" + "76696E652E636F6D480152AA01080110001A8101044F554B9400E10B17185036B6A1628E" + "FC61B22166DE2235717A44F953B7928F3415B9D113835B10106CB6C2187F34188723D82E" + "CF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096A" + "AEC3AC761B624A92158AC91035041173392B1E495428F0D17406B10889B6B701FAF08D22" + "84F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3" + "664EEB1280028CD44E12AA7C1A8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9E" + "AD52A0E18E929A4923A4172C2AC1CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061" + "632C1C82333A6FB6BC9C4B2540BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE9" + "35FDDF7BBAC0BA99AA7FA66017898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93" + "A97B35A8EAE8D3213D392B552FB4B4A37955EBE7362287502EB649D982F06D308178642C" + "1F69B12383B050CF60CD29209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D" + "1414AF30DDF0D06AF55C1978F6E71E4548F20FFAE953A99D492F3D2847783338D74F66D2" + "DFEBB50896ACBC4795A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3C6D4" + "18E3A5BDD005228E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E34" + "9F332F04516A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B9" + "83A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D" + "705520A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA" + "3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD689" + "0B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296" + "A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE439" + "1BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E0658" + "1A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644" + "F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B683" + "02FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA2" + "80A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4" + "D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E" + "059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A88" + "38C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB" + "813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177C" + "DD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E" + "07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAED" + "AD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEACCB34F6C3B2AB" + "F86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A418" + "6A362D9E6F88DD48D4516635C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172B" + "C691530703FEDDFDB25ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445" + "A4B0AE88A3A7F29ACE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C" + "2B2E8CA5C1EEC0844BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C" + "0557EBE7484FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449D" + "E4785F1D4835AC44E39B119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518E" + "F964C9D985AFD3677F0D13015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C638" + "535FA3F215F750F6954AC395F8702941409E7276910CE11819649641318B5BD1B78DECEA" + "DB2B562312CC286DB0BCC14A60C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422E" + "D8EB5ECE330C9886406B3B69180B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C36" + "0C759F5B10E0D354D63D5A14E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B" + "091B6D7A21DD9D6D6750C1CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F5" + "7F035DFB50EC4354D7E068ADFAFAD4081ACA67FD18001220A28ED0C0D4697C870B56192C" + "F2AF86D7362398EB250F6A29BE3A0C4887F0D653"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithoutExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': unset +// 'expiration_time_seconds': ~2031 +// } +const std::string kTestLegacyCertificateFileDataWithClientExpiration = a2bs_hex( + "0AB10F080110011AAA0F0AA80B0AEB03080212107CB49F987A635E1E0A52184694582D6E18" + "87C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129" + "BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398051244" + "5EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36" + "583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B3" + "9BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99D" + "F98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D6" + "54851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88" + "A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D48" + "0152AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE2235717A" + "44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923F17318" + "60815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC910" + "35041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A" + "2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB1280028CD44E12AA7C1A8E" + "BF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CD" + "ADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CE" + "D4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F" + "6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955" + "EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD29209329C148FB4F42" + "2ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFA" + "E953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065" + "802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B8050204" + "3C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7A" + "FE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F" + "842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4E" + "F9126C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE" + "7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2" + "A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A" + "038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E8" + "3D1280037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D" + "68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB0" + "3B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A9" + "1C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A" + "5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420" + "E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A" + "8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB" + "813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD" + "101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B1" + "6DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA0" + "6ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEACCB34F6C3B2ABF86634EE" + "5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A4186A362D9E6F" + "88DD48D4516635C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172BC691530703FE" + "DDFDB25ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445A4B0AE88A3A7F2" + "9ACE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC084" + "4BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C0557EBE7484FDABFBC" + "A0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B" + "119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13" + "015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395" + "F8702941409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60" + "C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B69180B" + "697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14E5BA19" + "DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC5DEC" + "2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068ADFAFAD4081A" + "CA67FD180028B7BA8499071220752DEC6BBB7DCB2750411F58DEBA61BFE55AEDC0EE92C3C6" + "BCDBC0C86A75798C"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithoutExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': unset +// 'expiration_time_seconds': ~2020 +// } +const std::string kTestLegacyCertificateFileDataClientExpired = a2bs_hex( + "0AB10F080110011AAA0F0AA80B0AEB03080212107CB49F987A635E1E0A52184694582D6E18" + "87C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129" + "BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398051244" + "5EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36" + "583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B3" + "9BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99D" + "F98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D6" + "54851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88" + "A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D48" + "0152AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE2235717A" + "44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923F17318" + "60815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC910" + "35041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A" + "2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB1280028CD44E12AA7C1A8E" + "BF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CD" + "ADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CE" + "D4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F" + "6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955" + "EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD29209329C148FB4F42" + "2ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFA" + "E953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065" + "802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B8050204" + "3C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7A" + "FE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F" + "842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4E" + "F9126C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE" + "7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2" + "A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A" + "038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E8" + "3D1280037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D" + "68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB0" + "3B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A9" + "1C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A" + "5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420" + "E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A" + "8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB" + "813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD" + "101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B1" + "6DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA0" + "6ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEACCB34F6C3B2ABF86634EE" + "5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A4186A362D9E6F" + "88DD48D4516635C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172BC691530703FE" + "DDFDB25ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445A4B0AE88A3A7F2" + "9ACE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC084" + "4BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C0557EBE7484FDABFBC" + "A0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B" + "119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13" + "015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395" + "F8702941409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60" + "C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B69180B" + "697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14E5BA19" + "DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC5DEC" + "2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068ADFAFAD4081A" + "CA67FD180028B9A8C2F3051220D2F932E432C200B5B30228317A3BA4A207C429B3F788C072" + "8F1E9615DFDD7A34"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithoutExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': unset +// 'expiration_time_seconds': -5 +// } +const std::string kTestLegacyCertificateFileDataInvalidClientExpiration = + a2bs_hex( + "0AB60F080110011AAF0F0AA80B0AEB03080212107CB49F987A635E1E0A52184694582D" + "6E1887C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A7" + "74A99129BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDC" + "BCF3980512445EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2" + "D6043CA9830E0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A163" + "19706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4" + "F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B7" + "3A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99713D31A646059328" + "33E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92670203010001" + "28E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F554B9400" + "E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B1010" + "6CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0" + "D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D" + "12742408A07C103DF860DC0520C3664EEB1280028CD44E12AA7C1A8EBF88C81A2A54EF" + "D29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7" + "833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0A" + "F1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F" + "5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A379" + "55EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD29209329C148" + "FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E" + "4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050A" + "AE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A" + "0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5" + "629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014" + "DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E3" + "14386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475C55C608E771C" + "763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B9274C16066F74F" + "C401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A107C36223404F" + "2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47FB9" + "D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06581A019184AB" + "572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9B3F3FB" + "6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FAA6" + "DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A8" + "6669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0" + "FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E" + "059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A" + "8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD2" + "4FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760F" + "CD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68" + "D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA99" + "2A5296FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEAC" + "CB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E" + "7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA6095618D647F521" + "2C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979D61AE659E340" + "489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC408B602B0BC099" + "920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC9B30784952B3" + "A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C" + "231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C7D3B8C75CE58" + "8DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA84CD01E335E68" + "AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F8702941409E727691" + "0CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C84CB21AB6E75E" + "59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B69180B697521F386" + "5B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14E5BA19DBF939" + "4E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC5DEC2A" + "81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068ADFAFAD408" + "1ACA67FD180028FBFFFFFFFFFFFFFFFF0112201CC3506DE1B3FC6A8DBB4AD85D34B62C" + "7EBA023FAD1AACCDBE1C932CFB6A1369"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithFutureExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': 03/17/2021 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataFutureExpiration = a2bs_hex( + "0AB70F080110011AB00F0AAE0B0AF103080212107CB49F987A635E1E0A52184694582D6E18" + "87C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129" + "BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398051244" + "5EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36" + "583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B3" + "9BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99D" + "F98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D6" + "54851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88" + "A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D48" + "0152AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE2235717A" + "44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923F17318" + "60815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC910" + "35041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A" + "2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB60E29D8399071280028CD4" + "4E12AA7C1A8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923" + "A4172C2AC1CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C" + "4B2540BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA6" + "6017898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B55" + "2FB4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD292093" + "29C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E7" + "1E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE" + "020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A028201" + "0100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027" + "AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C66" + "00562E9D904F842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655" + "851FCD9ADB4EF9126C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6AB" + "F7EA64B1EBDE7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0" + "C89E6E1F8DB2A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8" + "F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F02" + "0301000128E83D1280037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947" + "360BC8D49C0D68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39" + "D25B2662ECB03B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC" + "3E968B6821A91C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963" + "F21EE6AA220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6" + "AD56F7416420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667" + "B33B8114C76A8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A5" + "72125CD24FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962" + "760FCD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68" + "D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A52" + "96FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEACCB34F6C3" + "B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A4" + "186A362D9E6F88DD48D4516635C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172B" + "C691530703FEDDFDB25ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445A4" + "B0AE88A3A7F29ACE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C2B2E" + "8CA5C1EEC0844BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C0557EB" + "E7484FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449DE4785F1D" + "4835AC44E39B119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985" + "AFD3677F0D13015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F7" + "50F6954AC395F8702941409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC28" + "6DB0BCC14A60C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886" + "406B3B69180B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D6" + "3D5A14E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750" + "C1CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068" + "ADFAFAD4081ACA67FD180020EA80CA820612204DDB25CD2B324880675C1006CB104524B42C" + "9BBA110F0304E6C1E4C6ADF5DA6C"); + +// Structurally valid test certificate device file. +// +// {'certificate': kTestCertificateNeverExpires, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': 03/17/2021 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataNeverExpires = a2bs_hex( + "0AB30F080110011AAC0F0AAA0B0AED03080212107CB49F987A635E1E0A52184694582D6E18" + "94AECC8206228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129" + "BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398051244" + "5EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36" + "583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B3" + "9BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99D" + "F98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D6" + "54851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88" + "A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D48" + "0152AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE2235717A" + "44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923F17318" + "60815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC910" + "35041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A" + "2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB60001280028CD44E12AA7C" + "1A8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2A" + "C1CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE" + "18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898D" + "EE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A3" + "7955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD29209329C148FB" + "4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F2" + "0FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE02080112" + "1065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B805" + "02043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C75" + "9B7AFE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D" + "904F842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9A" + "DB4EF9126C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1" + "EBDE7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F" + "8DB2A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3" + "B05A038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F0203010001" + "28E83D1280037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D4" + "9C0D68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662" + "ECB03B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B68" + "21A91C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA" + "220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F741" + "6420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114" + "C76A8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD2" + "4FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD17" + "7CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E" + "07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD" + "7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEACCB34F6C3B2ABF866" + "34EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A4186A362D" + "9E6F88DD48D4516635C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172BC6915307" + "03FEDDFDB25ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445A4B0AE88A3" + "A7F29ACE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C2B2E8CA5C1EE" + "C0844BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C0557EBE7484FDA" + "BFBCA0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44" + "E39B119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F" + "0D13015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954A" + "C395F8702941409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC1" + "4A60C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B69" + "180B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14E5" + "BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC" + "5DEC2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068ADFAFAD4" + "081ACA67FD18002094AECC82061220494C9C49993FA8A9F0982FD684A62B99CC442E2AF264" + "CA351478C2BA1077A394"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateExpired +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': 03/12/2021 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataExpired = a2bs_hex( + "0AB70F080110011AB00F0AAE0B0AF103080212107CB49F987A635E1E0A52184694582D6E18" + "9EF0968206228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129" + "BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398051244" + "5EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36" + "583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B3" + "9BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99D" + "F98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D6" + "54851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88" + "A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D48" + "0152AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE2235717A" + "44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923F17318" + "60815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC910" + "35041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A" + "2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB609E939C82061280028CD4" + "4E12AA7C1A8EBF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923" + "A4172C2AC1CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C" + "4B2540BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA6" + "6017898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B55" + "2FB4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD292093" + "29C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E7" + "1E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE" + "020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A028201" + "0100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027" + "AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C66" + "00562E9D904F842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655" + "851FCD9ADB4EF9126C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6AB" + "F7EA64B1EBDE7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0" + "C89E6E1F8DB2A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8" + "F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F02" + "0301000128E83D1280037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947" + "360BC8D49C0D68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39" + "D25B2662ECB03B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC" + "3E968B6821A91C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963" + "F21EE6AA220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6" + "AD56F7416420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667" + "B33B8114C76A8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A5" + "72125CD24FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962" + "760FCD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68" + "D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A52" + "96FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEACCB34F6C3" + "B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A4" + "186A362D9E6F88DD48D4516635C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172B" + "C691530703FEDDFDB25ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445A4" + "B0AE88A3A7F29ACE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C2B2E" + "8CA5C1EEC0844BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C0557EB" + "E7484FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449DE4785F1D" + "4835AC44E39B119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985" + "AFD3677F0D13015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F7" + "50F6954AC395F8702941409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC28" + "6DB0BCC14A60C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886" + "406B3B69180B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D6" + "3D5A14E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750" + "C1CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068" + "ADFAFAD4081ACA67FD1800209E9FB182061220AB902564B722E023C7F31F485B194969C7D9" + "F4FB6ADB4EEF1312A0F663A3F092"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithInvalidCreationFutureExpiration +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': 03/17/2021 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateWithInvalidCreationFutureExpiration = + a2bs_hex( + "0AB10F080110011AAA0F0AA80B0AEB03080212107CB49F987A635E1E0A52184694582D" + "6E228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129BD63" + "445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398051244" + "5EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E" + "0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9C" + "F1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78" + "471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF8" + "28EB598DA59060D654851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0" + "F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C77" + "69646576696E652E636F6D480152AA01080110001A8101044F554B9400E10B17185036" + "B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B10106CB6C2187F34" + "188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B991B5F915F2ADC" + "EE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0D17406B10889" + "B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408A07C" + "103DF860DC0520C3664EEB60FD8AFC98071280028CD44E12AA7C1A8EBF88C81A2A54EF" + "D29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7" + "833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0A" + "F1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F" + "5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A379" + "55EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD29209329C148" + "FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E" + "4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050A" + "AE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A" + "0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5" + "629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014" + "DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E3" + "14386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475C55C608E771C" + "763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B9274C16066F74F" + "C401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A107C36223404F" + "2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47FB9" + "D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06581A019184AB" + "572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9B3F3FB" + "6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FAA6" + "DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A8" + "6669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0" + "FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E" + "059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A" + "8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD2" + "4FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760F" + "CD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68" + "D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA99" + "2A5296FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEAC" + "CB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E" + "7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA6095618D647F521" + "2C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979D61AE659E340" + "489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC408B602B0BC099" + "920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC9B30784952B3" + "A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C" + "231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C7D3B8C75CE58" + "8DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA84CD01E335E68" + "AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F8702941409E727691" + "0CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C84CB21AB6E75E" + "59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B69180B697521F386" + "5B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14E5BA19DBF939" + "4E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC5DEC2A" + "81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068ADFAFAD408" + "1ACA67FD180020FD84CC8206122040597EA4CA5BDDB92960D3D616B402EFC44699E3C4" + "DF3E0F78A2D3218C3E3055"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithUnlimitedCreationFutureExpiration +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': 03/17/2021 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataCreationTimeUnlimited = + a2bs_hex( + "0AB10F080110011AAA0F0AA80B0AEB03080212107CB49F987A635E1E0A52184694582D" + "6E228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129BD63" + "445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398051244" + "5EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E" + "0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9C" + "F1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78" + "471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF8" + "28EB598DA59060D654851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0" + "F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C77" + "69646576696E652E636F6D480152AA01080110001A8101044F554B9400E10B17185036" + "B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B10106CB6C2187F34" + "188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B991B5F915F2ADC" + "EE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0D17406B10889" + "B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408A07C" + "103DF860DC0520C3664EEB60AE91FC98071280028CD44E12AA7C1A8EBF88C81A2A54EF" + "D29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD16E41A7" + "833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CED4AB0A" + "F1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F6F" + "5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A379" + "55EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD29209329C148" + "FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E" + "4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050A" + "AE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A" + "0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5" + "629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014" + "DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E3" + "14386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475C55C608E771C" + "763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B9274C16066F74F" + "C401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A107C36223404F" + "2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE4391BBFA7A47FB9" + "D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06581A019184AB" + "572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C4644F9B3F3FB" + "6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2DA7B68302FAA6" + "DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A91C051CA280A8" + "6669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A5EE4A4D0" + "FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420E05E" + "059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A" + "8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD2" + "4FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760F" + "CD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68" + "D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA99" + "2A5296FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEAC" + "CB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E" + "7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA6095618D647F521" + "2C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979D61AE659E340" + "489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC408B602B0BC099" + "920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC9B30784952B3" + "A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C" + "231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C7D3B8C75CE58" + "8DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA84CD01E335E68" + "AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F8702941409E727691" + "0CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C84CB21AB6E75E" + "59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B69180B697521F386" + "5B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14E5BA19DBF939" + "4E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC5DEC2A" + "81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068ADFAFAD408" + "1ACA67FD180020AE8BCC82061220BA04B988A5E9D931946F2AB8FF3E3DD31C630300CD" + "065083437401CD752F8CD2"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithoutExpiration +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': 03/17/2021 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataExpirationUnset = a2bs_hex( + "0AB10F080110011AAA0F0AA80B0AEB03080212107CB49F987A635E1E0A52184694582D6E18" + "D991CC8206228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129" + "BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF398051244" + "5EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F36" + "583C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB1B3" + "9BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C11B9E99D" + "F98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828EB598DA59060D6" + "54851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F888AE6E78EDC9DA0D88" + "A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C7769646576696E652E636F6D48" + "0152AA01080110001A8101044F554B9400E10B17185036B6A1628EFC61B22166DE2235717A" + "44F953B7928F3415B9D113835B10106CB6C2187F34188723D82ECF95CF5ECAB58923F17318" + "60815999F08BF4BE4A44DB7B991B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC910" + "35041173392B1E495428F0D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A" + "2E57E7D4CA3C62ED6D12742408A07C103DF860DC0520C3664EEB1280028CD44E12AA7C1A8E" + "BF88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CD" + "ADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18CE" + "D4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA66017898DEE6F" + "6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552FB4B4A37955" + "EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD29209329C148FB4F42" + "2ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55C1978F6E71E4548F20FFA" + "E953A99D492F3D2847783338D74F66D2DFEBB50896ACBC4795A81AB4050AAE020801121065" + "802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD005228E023082010A0282010100B8050204" + "3C2A8A0FD8D25C613E1E3E3B5E349F332F04516A7510D38021A5629B9AA027AEAD3C759B7A" + "FE70BED65F3DF6860FF5EB60B983A3FFA33FDE06F3B73014DFC845AB371C6600562E9D904F" + "842B8BA4A5D9200FFA3ED45D705520A5C372A889F9E314386234C6897AE655851FCD9ADB4E" + "F9126C78386EA93BCB25BA3EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE" + "7B95C6407690533BD6890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2" + "A47841CD0DAD793296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A" + "038BD3E4BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E8" + "3D1280037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D" + "68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB0" + "3B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A9" + "1C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA220A" + "5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56F7416420" + "E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667B33B8114C76A" + "8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF789A572125CD24FBB" + "813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540D9C57962760FCD177CDD" + "101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85074186080D68D13CD37E07B1" + "6DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA0" + "6ECD790F1E3D426558FA98383E3CD2ED483012F403B36550E6BEACCB34F6C3B2ABF86634EE" + "5383829C844F9B0C14DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A4186A362D9E6F" + "88DD48D4516635C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172BC691530703FE" + "DDFDB25ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445A4B0AE88A3A7F2" + "9ACE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC084" + "4BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C0557EBE7484FDABFBC" + "A0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B" + "119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13" + "015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395" + "F8702941409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60" + "C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B69180B" + "697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14E5BA19" + "DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC5DEC" + "2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E068ADFAFAD4081A" + "CA67FD180020D991CC82061220CD90FA6F091C73BA7CC7EF0B777B986F4799DCEB5B03C8BC" + "360092DCC97CEF0A"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateExpiresBeforeCreationTime +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': 03/17/2021 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataExpiresBeforeCreationTime = + a2bs_hex( + "0AB70F080110011AB00F0AAE0B0AF103080212107CB49F987A635E1E0A52184694582D" + "6E18EC95CC8206228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A7" + "74A99129BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDC" + "BCF3980512445EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2" + "D6043CA9830E0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A163" + "19706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4" + "F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B7" + "3A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99713D31A646059328" + "33E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92670203010001" + "28E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F554B9400" + "E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B1010" + "6CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0" + "D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D" + "12742408A07C103DF860DC0520C3664EEB60ECB79782061280028CD44E12AA7C1A8EBF" + "88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1" + "CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540" + "BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA660" + "17898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B" + "552FB4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD" + "29209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF5" + "5C1978F6E71E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC47" + "95A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD00522" + "8E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A" + "7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33F" + "DE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5" + "C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475" + "C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B92" + "74C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A1" + "07C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE439" + "1BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06" + "581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C" + "4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2D" + "A7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A9" + "1C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA" + "220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56" + "F7416420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667" + "B33B8114C76A8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF7" + "89A572125CD24FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540" + "D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85" + "074186080D68D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F" + "0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403" + "B36550E6BEACCB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD" + "61E21CEE503E7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA609" + "5618D647F5212C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979" + "D61AE659E340489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC40" + "8B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC" + "9B30784952B3A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB668" + "1C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C" + "7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA8" + "4CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F87029" + "41409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C8" + "4CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B6918" + "0B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14" + "E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1" + "CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E0" + "68ADFAFAD4081ACA67FD180020EC95CC82061220463AF8A7AE265E06A0BF07C366E6E0" + "52301A32F3A1DA487EA556519910C7534E"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithFutureExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': unset +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataAcquisitionTimeUnset = + a2bs_hex( + "0AB10F080110011AAA0F0AAE0B0AF103080212107CB49F987A635E1E0A52184694582D" + "6E1887C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A7" + "74A99129BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDC" + "BCF3980512445EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2" + "D6043CA9830E0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A163" + "19706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4" + "F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B7" + "3A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99713D31A646059328" + "33E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92670203010001" + "28E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F554B9400" + "E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B1010" + "6CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0" + "D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D" + "12742408A07C103DF860DC0520C3664EEB60E29D8399071280028CD44E12AA7C1A8EBF" + "88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1" + "CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540" + "BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA660" + "17898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B" + "552FB4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD" + "29209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF5" + "5C1978F6E71E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC47" + "95A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD00522" + "8E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A" + "7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33F" + "DE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5" + "C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475" + "C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B92" + "74C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A1" + "07C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE439" + "1BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06" + "581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C" + "4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2D" + "A7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A9" + "1C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA" + "220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56" + "F7416420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667" + "B33B8114C76A8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF7" + "89A572125CD24FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540" + "D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85" + "074186080D68D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F" + "0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403" + "B36550E6BEACCB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD" + "61E21CEE503E7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA609" + "5618D647F5212C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979" + "D61AE659E340489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC40" + "8B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC" + "9B30784952B3A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB668" + "1C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C" + "7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA8" + "4CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F87029" + "41409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C8" + "4CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B6918" + "0B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14" + "E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1" + "CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E0" + "68ADFAFAD4081ACA67FD180012205984768E4F372E0DF787C4215A337355CD62B5FC0A" + "EAE8CC5BA82EA29C2E7A01"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithFutureExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': -5 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataAcquisitionTimeInvalid = + a2bs_hex( + "0ABC0F080110011AB50F0AAE0B0AF103080212107CB49F987A635E1E0A52184694582D" + "6E1887C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A7" + "74A99129BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDC" + "BCF3980512445EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2" + "D6043CA9830E0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A163" + "19706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4" + "F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B7" + "3A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99713D31A646059328" + "33E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92670203010001" + "28E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F554B9400" + "E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B1010" + "6CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0" + "D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D" + "12742408A07C103DF860DC0520C3664EEB60E29D8399071280028CD44E12AA7C1A8EBF" + "88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1" + "CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540" + "BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA660" + "17898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B" + "552FB4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD" + "29209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF5" + "5C1978F6E71E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC47" + "95A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD00522" + "8E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A" + "7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33F" + "DE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5" + "C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475" + "C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B92" + "74C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A1" + "07C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE439" + "1BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06" + "581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C" + "4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2D" + "A7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A9" + "1C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA" + "220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56" + "F7416420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667" + "B33B8114C76A8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF7" + "89A572125CD24FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540" + "D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85" + "074186080D68D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F" + "0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403" + "B36550E6BEACCB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD" + "61E21CEE503E7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA609" + "5618D647F5212C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979" + "D61AE659E340489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC40" + "8B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC" + "9B30784952B3A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB668" + "1C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C" + "7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA8" + "4CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F87029" + "41409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C8" + "4CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B6918" + "0B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14" + "E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1" + "CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E0" + "68ADFAFAD4081ACA67FD180020FBFFFFFFFFFFFFFFFF011220F653E5406D56276BCB28" + "E9D1F8E9D83233A7AF24476732208AEBD9DD33BD6C41"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithFutureExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': 0 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataAcquisitionTimeUnlimited = + a2bs_hex( + "0AB30F080110011AAC0F0AAE0B0AF103080212107CB49F987A635E1E0A52184694582D" + "6E1887C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A7" + "74A99129BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDC" + "BCF3980512445EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2" + "D6043CA9830E0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A163" + "19706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4" + "F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B7" + "3A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99713D31A646059328" + "33E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92670203010001" + "28E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F554B9400" + "E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B1010" + "6CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0" + "D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D" + "12742408A07C103DF860DC0520C3664EEB60E29D8399071280028CD44E12AA7C1A8EBF" + "88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1" + "CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540" + "BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA660" + "17898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B" + "552FB4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD" + "29209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF5" + "5C1978F6E71E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC47" + "95A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD00522" + "8E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A" + "7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33F" + "DE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5" + "C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475" + "C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B92" + "74C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A1" + "07C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE439" + "1BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06" + "581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C" + "4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2D" + "A7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A9" + "1C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA" + "220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56" + "F7416420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667" + "B33B8114C76A8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF7" + "89A572125CD24FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540" + "D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85" + "074186080D68D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F" + "0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403" + "B36550E6BEACCB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD" + "61E21CEE503E7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA609" + "5618D647F5212C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979" + "D61AE659E340489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC40" + "8B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC" + "9B30784952B3A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB668" + "1C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C" + "7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA8" + "4CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F87029" + "41409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C8" + "4CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B6918" + "0B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14" + "E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1" + "CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E0" + "68ADFAFAD4081ACA67FD1800200012207CBD5A3A5258C9FDF467270ACD4F8B10B33FEC" + "3FBCD3409BFB38542C954B9BFD"); + +// Structurally valid test certificate device file. +// {'certificate': kTestCertificateWithFutureExpiration, +// 'key_type': 'RSA', +// 'wrapped_private_key': kTestWrappedPrivateKeyData +// 'acquisition_time_seconds': ~2030 +// 'expiration_time_seconds': unset +// } +const std::string kTestDefaultCertificateFileDataAcquisitionTimeInTheFuture = + a2bs_hex( + "0AB70F080110011AB00F0AAE0B0AF103080212107CB49F987A635E1E0A52184694582D" + "6E1887C6E1FE05228E023082010A0282010100DB13F5089C061E8EB62562692B3A06A7" + "74A99129BD63445FEC24448D07C30D7343553442A989AF000B7D962033C290D9A81DDC" + "BCF3980512445EB7E6CF544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2" + "D6043CA9830E0F36583C8FDB839C2752C13E184034EE412BA8A90271295B094255A163" + "19706F4D6C9CF1EBB1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4" + "F48E79DDFC78471C11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B7" + "3A6FB7CA9EF828EB598DA59060D654851103F857A041E66B2FFB99713D31A646059328" + "33E8CCDA6CF0F888AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D92670203010001" + "28E83D3A0C7769646576696E652E636F6D480152AA01080110001A8101044F554B9400" + "E10B17185036B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B1010" + "6CB6C2187F34188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B99" + "1B5F915F2ADCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0" + "D17406B10889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D" + "12742408A07C103DF860DC0520C3664EEB60E29D8399071280028CD44E12AA7C1A8EBF" + "88C81A2A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1" + "CDADD16E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540" + "BE18CED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA660" + "17898DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B" + "552FB4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD" + "29209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF5" + "5C1978F6E71E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC47" + "95A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD00522" + "8E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F04516A" + "7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA33F" + "DE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D705520A5" + "C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3EC475" + "C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6890B92" + "74C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD793296A1" + "07C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4BBBAE439" + "1BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1280037E06" + "581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C0D68009B1C" + "4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2662ECB03B2D" + "A7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC3E968B6821A9" + "1C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF221963F21EE6AA" + "220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D4D0EDD55B6AD56" + "F7416420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F88EDD19DF346A667" + "B33B8114C76A8838C423D824A50B23251A088136D6E8F475299D2AFD46CEA51B5CBDF7" + "89A572125CD24FBB813B387A10CD2A30E3447634AB3408F96B9CF3D98896D405F3F540" + "D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD645A95B698F1CDC6E1DB6678B85" + "074186080D68D13CD37E07B16DE370CD9AFB9B25564A73A30E2AF8085EA37D310C474F" + "0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA98383E3CD2ED483012F403" + "B36550E6BEACCB34F6C3B2ABF86634EE5383829C844F9B0C14DCF9A22FE3543CCBA8FD" + "61E21CEE503E7A40B93B07A4186A362D9E6F88DD48D4516635C6D0C253C03F12EFA609" + "5618D647F5212C518C4A6AA7172BC691530703FEDDFDB25ECF885A53FF2B4B98773979" + "D61AE659E340489811512A5C2FD445A4B0AE88A3A7F29ACE5B01ECF580D0993227BC40" + "8B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC0844BC19198ADADE47FB449DC" + "9B30784952B3A8131B912CE928070D665C0557EBE7484FDABFBCA0F2C2BFD4FBDDB668" + "1C4689FD276C231B72B15AC4E5C3C088449DE4785F1D4835AC44E39B119991EFF6E72C" + "7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964C9D985AFD3677F0D13015AD7BEA8" + "4CD01E335E68AF153B989FE8BEEC60A94753C638535FA3F215F750F6954AC395F87029" + "41409E7276910CE11819649641318B5BD1B78DECEADB2B562312CC286DB0BCC14A60C8" + "4CB21AB6E75E59DAFBE701D6405DD3F47D2F8A95422ED8EB5ECE330C9886406B3B6918" + "0B697521F3865B4A05DF2BB51D16CAFEF05866E5D55C360C759F5B10E0D354D63D5A14" + "E5BA19DBF9394E18E474E79063B4E877C2FE6BCA732ED39B091B6D7A21DD9D6D6750C1" + "CA2ABC5DEC2A81B5359771849E7B4560EB6D329E59455A70F57F035DFB50EC4354D7E0" + "68ADFAFAD4081ACA67FD1800208FC2F789071220E3A52D11E90193A9532977A681F032" + "D01C8F97E2EAB6C964A0F207D61499D679"); + +struct CertificateErrorData { + DeviceFiles::CertificateState certificate_state; + std::string file_data; +}; + +const CertificateErrorData kRetrieveLegacyCertificateErrorData[] = { + // Certificate expired based on expiration time set by the client + {DeviceFiles::kCertificateExpired, + kTestLegacyCertificateFileDataClientExpired}, + // Certificate contains an invalid expiration time set by the client + {DeviceFiles::kCertificateInvalid, + kTestLegacyCertificateFileDataInvalidClientExpiration}, +}; + +/* TODO(b/192430982): Renable expiration of legacy DRM certificates +constexpr size_t kNumberOfLegacyCertificates = + ArraySize(kRetrieveLegacyCertificateErrorData); +*/ + +const CertificateErrorData kRetrieveDefaultCertificateErrorData[] = { + // Certificate expired + {DeviceFiles::kCertificateExpired, kTestDefaultCertificateFileDataExpired}, + // Certificate has a creation time in the future + {DeviceFiles::kCertificateInvalid, + kTestDefaultCertificateWithInvalidCreationFutureExpiration}, + // Certificate has a never expires creation time + {DeviceFiles::kCertificateInvalid, + kTestDefaultCertificateFileDataCreationTimeUnlimited}, + // Certificate expiration time field is not set + {DeviceFiles::kCertificateInvalid, + kTestDefaultCertificateFileDataExpirationUnset}, + // Certificate expires before creation time + {DeviceFiles::kCertificateInvalid, + kTestDefaultCertificateFileDataExpiresBeforeCreationTime}, + // Certificate acqusition time field is not set + {DeviceFiles::kCertificateInvalid, + kTestDefaultCertificateFileDataAcquisitionTimeUnset}, + // Certificate acqusition time is invalid + {DeviceFiles::kCertificateInvalid, + kTestDefaultCertificateFileDataAcquisitionTimeInvalid}, + // Certificate has a never expires acqusition + {DeviceFiles::kCertificateInvalid, + kTestDefaultCertificateFileDataAcquisitionTimeUnlimited}, + // Certificate acqusition time is in the future + {DeviceFiles::kCannotHandle, + kTestDefaultCertificateFileDataAcquisitionTimeInTheFuture}, +}; + +constexpr size_t kNumberOfDefaultCertificates = + ArraySize(kRetrieveDefaultCertificateErrorData); struct LicenseInfo { std::string key_set_id; - DeviceFiles::LicenseState license_state; + CdmOfflineLicenseState license_state; std::string pssh_data; std::string key_request; std::string key_response; @@ -138,6 +1628,9 @@ struct LicenseInfo { std::string app_parameters; std::string usage_entry; uint32_t usage_entry_number; + std::string drm_certificate; + CryptoWrappedKey::Type key_type; + std::string private_key; std::string file_data; }; @@ -147,7 +1640,7 @@ struct LicenseInfo { const LicenseInfo kLicenseTestData[] = { // license 0 - {"ksid54C57C966E23CEF5", DeviceFiles::kLicenseStateActive, + {"ksid54C57C966E23CEF5", kLicenseStateActive, a2bs_hex("0801121030313233343536373839414243444546"), a2bs_hex("080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" "C4D07A7D507618A5D3A68F05228E023082010A0282010100A947904B8DBD" @@ -239,6 +1732,7 @@ const LicenseInfo kLicenseTestData[] = { "A68F051A20182F029E35047A3841FA176C74E5B387350E8D58DEA6878FF0" "BEA6CABACA1C2C"), "https://test.google.com/license/GetCencLicense", 0x0, 0x0, 0x0, "", "", 0, + "", CryptoWrappedKey::kUninitialized, "", a2bs_hex( "0AAE150802100122A7150801121408011210303132333435363738394142434445461" "A9D0E080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591BC4D0" @@ -323,7 +1817,7 @@ const LicenseInfo kLicenseTestData[] = { "FF232D23F98B72F1DCE96A")}, // license 1 - {"ksidC8EAA2579A282EB0", DeviceFiles::kLicenseStateReleasing, + {"ksidC8EAA2579A282EB0", kLicenseStateReleasing, a2bs_hex("0801121030313233343536373839414243444546"), a2bs_hex("080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" "C4D07A7D507618A5D3A68F05228E023082010A0282010100A947904B8DBD" @@ -419,7 +1913,7 @@ const LicenseInfo kLicenseTestData[] = { a2bs_hex( "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" "22232425262728292a2b2c2d2e2f"), - 5, + 5, "", CryptoWrappedKey::kUninitialized, "", a2bs_hex( "0AF7150802100122F0150802121408011210303132333435363738394142434445461" "A9D0E080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591BC4D0" @@ -506,7 +2000,7 @@ const LicenseInfo kLicenseTestData[] = { "17B2F8B2D7511C9DE89A87CB5208AB")}, // license 2 - {"ksidE8C37662C88DC673", DeviceFiles::kLicenseStateReleasing, + {"ksidE8C37662C88DC673", kLicenseStateReleasing, a2bs_hex("0801121030313233343536373839414243444546"), a2bs_hex("080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" "C4D07A7D507618A5D3A68F05228E023082010A0282010100A947904B8DBD" @@ -602,7 +2096,7 @@ const LicenseInfo kLicenseTestData[] = { a2bs_hex( "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" "22232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"), - 12, + 12, "", CryptoWrappedKey::kUninitialized, "", a2bs_hex( "0AAD160802100122A6160802121408011210303132333435363738394142434445461" "A9D0E080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591BC4D0" @@ -688,7 +2182,244 @@ const LicenseInfo kLicenseTestData[] = { "8006240000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E" "1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F680" "C12206AA0237760D1F06E5CB78F5AFC3D124BBF7C26921CB3CC2EA44766801E25D34" - "F")}}; // kLicenseTestData + "F")}, + + // license 3 + {"ksidF991C5F45E98CB97", kLicenseStateActive, + a2bs_hex("0801121030313233343536373839414243444546"), + a2bs_hex("080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" + "FA0E5DFC3DE9A34BA5F08BE349553C319A9FB274905A8770ADC9CA4A2CBC" + "D8E556A1587FA18BFD4D286C644A6904F19EAAFBDFADD3B371B306D0B289" + "F459B491C814B5AD1F747610E990A60248A7DA5152F1CCFC047EF4230013" + "1F9C4758F4D9F30579393B860AAD9AD2EE43D721D6DB9F5800EF188386B9" + "4825AE05A883AC976D6970DF43EA6C83B86CE6D0F540207725B9890FCCEC" + "83A49027872DAFD2740B7748E9C3B1752D6F12859CED07E8882969B433EC" + "66F17FFC29AC6FDBEA79230B0FAED5D94CF6B829A420BBE3270323941776" + "EE60DD6BFD660BDDCA870203010001288001300112800250D1F8B1ECF849" + "B60FF93E37C4DEEF09E6FFB10BCFC996A4A24B7AA96928835ED5A72E1584" + "6D0A14015733239BD8B6E6D5E5D229B08394CE1E0692C159C44337CA7CAF" + "88476449B068D9D2FADED8EB1BC0F4B8F0FCAF293E8690E7403209534180" + "3408A0E8279E545945EE97838FDE7812F7171C3CC4F5ECF9418BBF1D336C" + "E58F4CBB1B44D4ADE6BF3364BAE7EC093281846E569E13E7719014030A60" + "59044FE7BBFF3E8F5723AEDD54DC6E0D041B309D7700B55575690E95CE60" + "85D0914F991C5F45E98CBB9C45BA33F47FD0862EBCC7EEBA8E60643C86E8" + "5476B18AEF8DE871571A75681A75F75028A5B58751C09A5296AAE99CEDCD" + "9785E9E2103240D40A1AB6050AB002080112102CE5CCF42200D6B5BCCF33" + "D7CC2D9C7018EAD1B88D05228E023082010A0282010100BE1B661EEC4700" + "DF4B0C83292D02AE029B8A224DD3048125049F74E30E1257FC2BE8D9CFAF" + "0BFFCACAF7305351771C78FA451F13AF5EEBFB360941A4396A805833730D" + "C6E534C62408B7C5076FC22568021C59ED34F98487196DA32078DAFCA37C" + "7CFB8E79612FA384963DF2167D5E87305D7BC92D621C10160672C27B9A69" + "1B1534F60D78C5893E40C5FF8A3F9DF8898612E9A5CCB56F4A0CC2A61A7A" + "EB04A9DCC015D9BC37DEF2AB9EAA9AAFD838869081D9249755F129BB0DBE" + "CA3B894975A65A36FD005CE77CD407E925D3172E33122A11D327968A08F8" + "E771FAEB2540EB52D17C4906405F47C31F60F0AF6C78AF53291B236E692B" + "506A2AF92AF43E3A81020301000128800130011280033A08A60418E5C81B" + "8D71A5C0A28C26999FF4FA992E14107CA8A9E6A2B36259834000FE35DCD8" + "14426F9C5D332418ED94C9C0C992217B1B6DC01C99085A3C3956C8267B87" + "73BABCF3F2C841C67D830F9DBC780DD68BF4E2FE424C6A54123BE4B2A1F7" + "E1F4DB58AB1164DAE9CF75C3392284A44B8CDB85D837E86C6B908243987E" + "552C8693878C9A1B7BEA3759783036F1595C406D6CBBA7F8642A9B3B244D" + "AA1F00531D0B908ADE4B533FD9FAFA21D0FB0C033D2AD5DDF24C60F4FAC3" + "0820758877F2E1A78EB44E9336DCFAFDF572BB22A84A5DEFDF2EB87B61DE" + "26EE9C4CEAA646A2AFDB2BB953845E6D7FE6F79A9501D1C379C96961316B" + "5D2A66F38C222091AF74141B6CAF93507485A5D8F82808025451824F00C8" + "B6A0CD5803F6564584138C8B18BC679B442D837307B5CC90B1FD1FD32288" + "B4A5D18D2D80E5E6A7A9EFD255B8B363038BCC67AF534EAEE4A5903E304E" + "ED4990BB5BE735DB027A6DE35329D321EC051B956C55A5B11674017517FC" + "C3C7FF7397C13A7B7087A1F6AEC7F6761A130A0C636F6D70616E795F6E61" + "6D6512034C47451A150A0A6D6F64656C5F6E616D6512074E657875732034" + "1A200A116172636869746563747572655F6E616D65120B61726D65616269" + "2D7637611A130A0B6465766963655F6E616D6512046D616B6F1A150A0C70" + "726F647563745F6E616D6512056F6363616D1A440A0A6275696C645F696E" + "666F1236676F6F676C652F6F6363616D2F6D616B6F3A342E332F4A425F4D" + "52322F3731343239313A7573657264656275672F6465762D6B6579731A21" + "0A096465766963655F696412144C474D4332303132313030353030363339" + "32373812250A230A14080112103031323334353637383941424344454610" + "021A09393837363534333231180120002A0C333934303739343733370000" + "30151A8002734FBDED946EB74A1B61811C4C4A491214F6BEA125A80F0141" + "65B28AA97AD0AF60E4D129EB7F424AD659F24E5EED4B702BEE328E38B72C" + "A6F38CD0ECFD2E6D7B98147744C9B8A9610B3BDFE17675FF7D584C5BF680" + "64B0FE513FC322C9148795E4C2F2443C3024F5C1F29E6FEFB6D77005DAB2" + "2CD2B63131908DE4D88795BB931CEA38452CC568BE25032245E372F07A12" + "97F51748C7EA02F2C88360AFE7ABBC71DCDD5366126258E5AFA27C2A20B3" + "39FA1E7AE925B494B361F6F7116F20BE8EE6E446146027F4FD4300F4A0B0" + "A3361EE34925F338D0AACF20AE919B4BAE81C1D57A8D2B8FA38732A57697" + "55FB685FDB3025574517CCCC74EE4FEAF6629D5179A52FF85CE7409528EF" + "C316C180717C182A971C94E4AC4C7DF8F161CB8CC1"), + a2bs_hex("080212CC020A190A0939383736353433323112084B9F26DAB8B06E112002" + "342E7769646576696E652E6E65742F7769646576696E652F6367692D6269" + "6E2F64726D2E6367691A6612102531DFD6CCEA511D00F8C0172F1189AA1A" + "5057FF9D9DBD5A205B1DEB075E4A90467C1E074CDE6071BFF831AD590BD5" + "D117614F33CE2C3CE1824FC0D54B36ECEAE58DF5C8F9347C2FEED17A3327" + "E8F52B8ECA6313A1FA6A042EB9525DD328113C05F920011A7E0A10303132" + "3334353637383941424344454612106D23622142B58F6D1EDD33AF3ECD2C" + "7E1A20884EE13BEA9DECDDBF68B532131C82B11CEC4D23C7FA9F3EF4C5EE" + "172E7C9736200242340A2050BFE71BB1BA683E35E0B49BB33048E5103FBB" + "B9C3E1CD6EBCDA7DD485DBAF431210D69D6F14C95CB6CFDB998E50D00F4D" + "A020DBDFA68F051A20AE5D6895E70F86F42F5FE3C58A505A865D05AB94B1" + "ABAA6CC59C3322F61C458D228002331F2BE95B5C796E0921CC27A7295501" + "DA10044E5CA36C0E2866FF068EA3515A6786BD5D60D74D80C6BA8BE6AAD0" + "85AF967909A143171E9CDDE36EA528402867CD04FB6F97A150CDE55F9B81" + "9F4104BEF48E4280D76645569E10AEF524D34D865B5B9E3EBC66C45EEBBE" + "16AB04493E7AEC4F99E7A99F3FC08FA431BECCC1978A079FA4801DB75E13" + "29A9921604E6F80CB148AA2DD5C8348057E9F4FC2AEA57EA4D215D0A8D48" + "6294860DFB4F4C42D57D9542B76179E179DD4AA23F9F7B2AE432B39E4CE8" + "F156E84877DDA781AAAAFC797FF75AFE2019ADC3A2E419BF0253C705BD47" + "2800124108011801301E4239687474703A2F2F6B69723033666370673137" + "97A96866AC4C059AD8F2E9C6B617C60C6ADCDB894C25F0C7D29252F52FD5"), + a2bs_hex("08011231121D1A1B0A190A0939383736353433323112084B9F26DAB8B06E" + "4D2033E05DCC95DDFB278CFB5125A021C3C043A16ACC933A768A27112002" + "280018022A0C31353532333030360000000030151A20C30375683C"), + a2bs_hex("0802123B0A190A0939383736353433323112084B9F26DAB8B06E11200228" + "A68F051A20182F029E35047A3841FA176C74E5B387350E8D58DEA6878FF0" + "0112001A16200342120A106B63746C0000000000ECDCBE0000000020DBDF" + "BEA6CABACA1C2C"), + "https://test.google.com/license/GetCencLicense", 0x1234, 0x1324, 0x10, + "Name3 Value3", + a2bs_hex("08011231121D1A1B0A190A0939383736353433323112084B9F26DAB8B06E"), + 5, kTestCertificateWithFutureExpiration, CryptoWrappedKey::kRsa, + kTestWrappedPrivateKeyData, + a2bs_hex( + "0AEE240802100122E7240801121408011210303132333435363738394142434445461" + "AFF0D080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591BFA0E" + "5DFC3DE9A34BA5F08BE349553C319A9FB274905A8770ADC9CA4A2CBCD8E556A1587FA" + "18BFD4D286C644A6904F19EAAFBDFADD3B371B306D0B289F459B491C814B5AD1F7476" + "10E990A60248A7DA5152F1CCFC047EF42300131F9C4758F4D9F30579393B860AAD9AD" + "2EE43D721D6DB9F5800EF188386B94825AE05A883AC976D6970DF43EA6C83B86CE6D0" + "F540207725B9890FCCEC83A49027872DAFD2740B7748E9C3B1752D6F12859CED07E88" + "82969B433EC66F17FFC29AC6FDBEA79230B0FAED5D94CF6B829A420BBE32703239417" + "76EE60DD6BFD660BDDCA870203010001288001300112800250D1F8B1ECF849B60FF93" + "E37C4DEEF09E6FFB10BCFC996A4A24B7AA96928835ED5A72E15846D0A14015733239B" + "D8B6E6D5E5D229B08394CE1E0692C159C44337CA7CAF88476449B068D9D2FADED8EB1" + "BC0F4B8F0FCAF293E8690E74032095341803408A0E8279E545945EE97838FDE7812F7" + "171C3CC4F5ECF9418BBF1D336CE58F4CBB1B44D4ADE6BF3364BAE7EC093281846E569" + "E13E7719014030A6059044FE7BBFF3E8F5723AEDD54DC6E0D041B309D7700B5557569" + "0E95CE6085D0914F991C5F45E98CBB9C45BA33F47FD0862EBCC7EEBA8E60643C86E85" + "476B18AEF8DE871571A75681A75F75028A5B58751C09A5296AAE99CEDCD9785E9E210" + "3240D40A1AB6050AB002080112102CE5CCF42200D6B5BCCF33D7CC2D9C7018EAD1B88" + "D05228E023082010A0282010100BE1B661EEC4700DF4B0C83292D02AE029B8A224DD3" + "048125049F74E30E1257FC2BE8D9CFAF0BFFCACAF7305351771C78FA451F13AF5EEBF" + "B360941A4396A805833730DC6E534C62408B7C5076FC22568021C59ED34F98487196D" + "A32078DAFCA37C7CFB8E79612FA384963DF2167D5E87305D7BC92D621C10160672C27" + "B9A691B1534F60D78C5893E40C5FF8A3F9DF8898612E9A5CCB56F4A0CC2A61A7AEB04" + "A9DCC015D9BC37DEF2AB9EAA9AAFD838869081D9249755F129BB0DBECA3B894975A65" + "A36FD005CE77CD407E925D3172E33122A11D327968A08F8E771FAEB2540EB52D17C49" + "06405F47C31F60F0AF6C78AF53291B236E692B506A2AF92AF43E3A810203010001288" + "00130011280033A08A60418E5C81B8D71A5C0A28C26999FF4FA992E14107CA8A9E6A2" + "B36259834000FE35DCD814426F9C5D332418ED94C9C0C992217B1B6DC01C99085A3C3" + "956C8267B8773BABCF3F2C841C67D830F9DBC780DD68BF4E2FE424C6A54123BE4B2A1" + "F7E1F4DB58AB1164DAE9CF75C3392284A44B8CDB85D837E86C6B908243987E552C869" + "3878C9A1B7BEA3759783036F1595C406D6CBBA7F8642A9B3B244DAA1F00531D0B908A" + "DE4B533FD9FAFA21D0FB0C033D2AD5DDF24C60F4FAC30820758877F2E1A78EB44E933" + "6DCFAFDF572BB22A84A5DEFDF2EB87B61DE26EE9C4CEAA646A2AFDB2BB953845E6D7F" + "E6F79A9501D1C379C96961316B5D2A66F38C222091AF74141B6CAF93507485A5D8F82" + "808025451824F00C8B6A0CD5803F6564584138C8B18BC679B442D837307B5CC90B1FD" + "1FD32288B4A5D18D2D80E5E6A7A9EFD255B8B363038BCC67AF534EAEE4A5903E304EE" + "D4990BB5BE735DB027A6DE35329D321EC051B956C55A5B11674017517FCC3C7FF7397" + "C13A7B7087A1F6AEC7F6761A130A0C636F6D70616E795F6E616D6512034C47451A150" + "A0A6D6F64656C5F6E616D6512074E6578757320341A200A1161726368697465637475" + "72655F6E616D65120B61726D656162692D7637611A130A0B6465766963655F6E616D6" + "512046D616B6F1A150A0C70726F647563745F6E616D6512056F6363616D1A440A0A62" + "75696C645F696E666F1236676F6F676C652F6F6363616D2F6D616B6F3A342E332F4A4" + "25F4D52322F3731343239313A7573657264656275672F6465762D6B6579731A210A09" + "6465766963655F696412144C474D433230313231303035303036333932373812250A2" + "30A14080112103031323334353637383941424344454610021A093938373635343332" + "31180120002A0C33393430373934373337000030151A8002734FBDED946EB74A1B618" + "11C4C4A491214F6BEA125A80F014165B28AA97AD0AF60E4D129EB7F424AD659F24E5E" + "ED4B702BEE328E38B72CA6F38CD0ECFD2E6D7B98147744C9B8A9610B3BDFE17675FF7" + "D584C5BF68064B0FE513FC322C9148795E4C2F2443C3024F5C1F29E6FEFB6D77005DA" + "B22CD2B63131908DE4D88795BB931CEA38452CC568BE25032245E372F07A1297F5174" + "8C7EA02F2C88360AFE7ABBC71DCDD5366126258E5AFA27C2A20B339FA1E7AE925B494" + "B361F6F7116F20BE8EE6E446146027F4FD4300F4A0B0A3361EE34925F338D0AACF20A" + "E919B4BAE81C1D57A8D2B8FA38732A5769755FB685FDB3025574517CCCC74EE4FEAF6" + "629D5179A52FF85CE7409528EFC316C180717C182A971C94E4AC4C7DF8F161CB8CC12" + "2F604080212CC020A190A0939383736353433323112084B9F26DAB8B06E112002342E" + "7769646576696E652E6E65742F7769646576696E652F6367692D62696E2F64726D2E6" + "367691A6612102531DFD6CCEA511D00F8C0172F1189AA1A5057FF9D9DBD5A205B1DEB" + "075E4A90467C1E074CDE6071BFF831AD590BD5D117614F33CE2C3CE1824FC0D54B36E" + "CEAE58DF5C8F9347C2FEED17A3327E8F52B8ECA6313A1FA6A042EB9525DD328113C05" + "F920011A7E0A103031323334353637383941424344454612106D23622142B58F6D1ED" + "D33AF3ECD2C7E1A20884EE13BEA9DECDDBF68B532131C82B11CEC4D23C7FA9F3EF4C5" + "EE172E7C9736200242340A2050BFE71BB1BA683E35E0B49BB33048E5103FBBB9C3E1C" + "D6EBCDA7DD485DBAF431210D69D6F14C95CB6CFDB998E50D00F4DA020DBDFA68F051A" + "20AE5D6895E70F86F42F5FE3C58A505A865D05AB94B1ABAA6CC59C3322F61C458D228" + "002331F2BE95B5C796E0921CC27A7295501DA10044E5CA36C0E2866FF068EA3515A67" + "86BD5D60D74D80C6BA8BE6AAD085AF967909A143171E9CDDE36EA528402867CD04FB6" + "F97A150CDE55F9B819F4104BEF48E4280D76645569E10AEF524D34D865B5B9E3EBC66" + "C45EEBBE16AB04493E7AEC4F99E7A99F3FC08FA431BECCC1978A079FA4801DB75E132" + "9A9921604E6F80CB148AA2DD5C8348057E9F4FC2AEA57EA4D215D0A8D486294860DFB" + "4F4C42D57D9542B76179E179DD4AA23F9F7B2AE432B39E4CE8F156E84877DDA781AAA" + "AFC797FF75AFE2019ADC3A2E419BF0253C705BD472800124108011801301E42396874" + "74703A2F2F6B6972303366637067313797A96866AC4C059AD8F2E9C6B617C60C6ADCD" + "B894C25F0C7D29252F52FD52A5708011231121D1A1B0A190A09393837363534333231" + "12084B9F26DAB8B06E4D2033E05DCC95DDFB278CFB5125A021C3C043A16ACC933A768" + "A27112002280018022A0C31353532333030360000000030151A20C30375683C326108" + "02123B0A190A0939383736353433323112084B9F26DAB8B06E11200228A68F051A201" + "82F029E35047A3841FA176C74E5B387350E8D58DEA6878FF00112001A16200342120A" + "106B63746C0000000000ECDCBE0000000020DBDFBEA6CABACA1C2C3A2E68747470733" + "A2F2F746573742E676F6F676C652E636F6D2F6C6963656E73652F47657443656E634C" + "6963656E736540B42448A426520F0A054E616D6533120656616C7565335810621E080" + "11231121D1A1B0A190A0939383736353433323112084B9F26DAB8B06E680572AA0F0A" + "AE0B0AF103080212107CB49F987A635E1E0A52184694582D6E1887C6E1FE05228E023" + "082010A0282010100DB13F5089C061E8EB62562692B3A06A774A99129BD63445FEC24" + "448D07C30D7343553442A989AF000B7D962033C290D9A81DDCBCF3980512445EB7E6C" + "F544FC1FB3FC758FB9E06B6C28562A841E4AE2D3368795C41A2D6043CA9830E0F3658" + "3C8FDB839C2752C13E184034EE412BA8A90271295B094255A16319706F4D6C9CF1EBB" + "1B39BA2A7B9B2780344DD5834BF71F4D5185508D2FDFB10419BD4F48E79DDFC78471C" + "11B9E99DF98221D6FAB25AEE24574FB02D614974942A36527C62B73A6FB7CA9EF828E" + "B598DA59060D654851103F857A041E66B2FFB99713D31A64605932833E8CCDA6CF0F8" + "88AE6E78EDC9DA0D88A185B97FEB3EA74CF146BE7D9267020301000128E83D3A0C776" + "9646576696E652E636F6D480152AA01080110001A8101044F554B9400E10B17185036" + "B6A1628EFC61B22166DE2235717A44F953B7928F3415B9D113835B10106CB6C2187F3" + "4188723D82ECF95CF5ECAB58923F1731860815999F08BF4BE4A44DB7B991B5F915F2A" + "DCEE481E26096AAEC3AC761B624A92158AC91035041173392B1E495428F0D17406B10" + "889B6B701FAF08D2284F95DBBCA2220595267DCA89A2E57E7D4CA3C62ED6D12742408" + "A07C103DF860DC0520C3664EEB60E29D8399071280028CD44E12AA7C1A8EBF88C81A2" + "A54EFD29F8BC6C377B0C11C3404F84D8B9EAD52A0E18E929A4923A4172C2AC1CDADD1" + "6E41A7833AA0DE9D09F685DAC9ACC702CB9061632C1C82333A6FB6BC9C4B2540BE18C" + "ED4AB0AF1C3EFE521308F3D4CF513C20500064FE935FDDF7BBAC0BA99AA7FA6601789" + "8DEE6F6F5EF90C875D5D8DA39E769E8D1485253EEE93A97B35A8EAE8D3213D392B552" + "FB4B4A37955EBE7362287502EB649D982F06D308178642C1F69B12383B050CF60CD29" + "209329C148FB4F422ED5ED139A25A89E13D4AB2E8DB940299D1414AF30DDF0D06AF55" + "C1978F6E71E4548F20FFAE953A99D492F3D2847783338D74F66D2DFEBB50896ACBC47" + "95A81AB4050AAE020801121065802C9B625E5A319C33DC1CB7C3C6D418E3A5BDD0052" + "28E023082010A0282010100B80502043C2A8A0FD8D25C613E1E3E3B5E349F332F0451" + "6A7510D38021A5629B9AA027AEAD3C759B7AFE70BED65F3DF6860FF5EB60B983A3FFA" + "33FDE06F3B73014DFC845AB371C6600562E9D904F842B8BA4A5D9200FFA3ED45D7055" + "20A5C372A889F9E314386234C6897AE655851FCD9ADB4EF9126C78386EA93BCB25BA3" + "EC475C55C608E771C763AB02506F9B07252D6ABF7EA64B1EBDE7B95C6407690533BD6" + "890B9274C16066F74FC401EA355F0A02106814D49BF0C89E6E1F8DB2A47841CD0DAD7" + "93296A107C36223404F2BF1FCA16FD0A4B982634DB62407F8F14ACAE3B05A038BD3E4" + "BBBAE4391BBFA7A47FB9D01DE857EA88E5E36EE36E245859FC0F020301000128E83D1" + "280037E06581A019184AB572AFDCADDD03F161CE68200F8E6F8AD161947360BC8D49C" + "0D68009B1C4644F9B3F3FB6DDFD92EF92DE62D41D459D29D81BFAEF3970A3A39D25B2" + "662ECB03B2DA7B68302FAA6DD98D95A143CC8C1CB6ADDA76D2EE9C3723FAF95A29CDC" + "3E968B6821A91C051CA280A86669710A1AD7A44BF9218027460DF694E2E9270396DF2" + "21963F21EE6AA220A5EE4A4D0FEB3D53EB5732F8F91E9A96B3B8BE284C51339EA284D" + "4D0EDD55B6AD56F7416420E05E059F9734A96BE25AA44560DBA8C38755A42A82BD7F8" + "8EDD19DF346A667B33B8114C76A8838C423D824A50B23251A088136D6E8F475299D2A" + "FD46CEA51B5CBDF789A572125CD24FBB813B387A10CD2A30E3447634AB3408F96B9CF" + "3D98896D405F3F540D9C57962760FCD177CDD101EB8A4148B9C29CED5EAD645A95B69" + "8F1CDC6E1DB6678B85074186080D68D13CD37E07B16DE370CD9AFB9B25564A73A30E2" + "AF8085EA37D310C474F0E67AC00CA992A5296FAEDAD7AA06ECD790F1E3D426558FA98" + "383E3CD2ED483012F403B36550E6BEACCB34F6C3B2ABF86634EE5383829C844F9B0C1" + "4DCF9A22FE3543CCBA8FD61E21CEE503E7A40B93B07A4186A362D9E6F88DD48D45166" + "35C6D0C253C03F12EFA6095618D647F5212C518C4A6AA7172BC691530703FEDDFDB25" + "ECF885A53FF2B4B98773979D61AE659E340489811512A5C2FD445A4B0AE88A3A7F29A" + "CE5B01ECF580D0993227BC408B602B0BC099920C17044FE66242372C2B2E8CA5C1EEC" + "0844BC19198ADADE47FB449DC9B30784952B3A8131B912CE928070D665C0557EBE748" + "4FDABFBCA0F2C2BFD4FBDDB6681C4689FD276C231B72B15AC4E5C3C088449DE4785F1" + "D4835AC44E39B119991EFF6E72C7D3B8C75CE588DB0B3AD69EB79C19B22CB518EF964" + "C9D985AFD3677F0D13015AD7BEA84CD01E335E68AF153B989FE8BEEC60A94753C6385" + "35FA3F215F750F6954AC395F8702941409E7276910CE11819649641318B5BD1B78DEC" + "EADB2B562312CC286DB0BCC14A60C84CB21AB6E75E59DAFBE701D6405DD3F47D2F8A9" + "5422ED8EB5ECE330C9886406B3B69180B697521F3865B4A05DF2BB51D16CAFEF05866" + "E5D55C360C759F5B10E0D354D63D5A14E5BA19DBF9394E18E474E79063B4E877C2FE6" + "BCA732ED39B091B6D7A21DD9D6D6750C1CA2ABC5DEC2A81B5359771849E7B4560EB6D" + "329E59455A70F57F035DFB50EC4354D7E068ADFAFAD4081ACA67FD1800122056D505D" + "E2B3989161D0CF88CFFDA6D2E79DA49C87CF1917A3A20109D2F3676EE")}, +}; // kLicenseTestData constexpr size_t kNumberOfLicenses = ArraySize(kLicenseTestData); @@ -697,7 +2428,7 @@ constexpr size_t kNumberOfLicenses = ArraySize(kLicenseTestData); // The data is used to test license-related functions. const LicenseInfo kLicenseUpdateTestData[] = { // active license - {"key_set_id_: ksid2A048BC7FAEC885A", DeviceFiles::kLicenseStateActive, + {"key_set_id_: ksid2A048BC7FAEC885A", kLicenseStateActive, a2bs_hex("0801121030313233343536373839414243444546"), a2bs_hex("080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" "C4D07A7D5076189EDFB68F05228E023082010A0282010100CC1715C81AD3" @@ -793,7 +2524,7 @@ const LicenseInfo kLicenseUpdateTestData[] = { a2bs_hex( "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f2021" "22232425262728292a2b2c2d2e2f"), - 15, + 15, "", CryptoWrappedKey::kUninitialized, "", a2bs_hex( "0AEE150802100122E7150801121408011210303132333435363738394142434445461" "A9D0E080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591BC4D0" @@ -880,8 +2611,8 @@ const LicenseInfo kLicenseUpdateTestData[] = { "766D60B07CBC")}, // license being released. all fields are identical except for license // state and hashed file data - {"", DeviceFiles::kLicenseStateReleasing, "", "", "", "", "", "", 0, 0, 0, - "", "", 15, + {"", kLicenseStateReleasing, "", "", "", "", "", "", 0, 0, 0, "", "", 15, + "", CryptoWrappedKey::kUninitialized, "", a2bs_hex( "0AEE150802100122E7150802121408011210303132333435363738394142434445461" "A9D0E080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591BC4D0" @@ -973,7 +2704,7 @@ const LicenseInfo kLicenseUpdateTestData[] = { const LicenseInfo kLicenseAppParametersBackwardsCompatibilityTestData = { "ksid54C57C966E23CEF5", - DeviceFiles::kLicenseStateActive, + kLicenseStateActive, a2bs_hex("0801121030313233343536373839414243444546"), a2bs_hex("080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" "C4D07A7D507618A5D3A68F05228E023082010A0282010100A947904B8DBD" @@ -1071,6 +2802,9 @@ const LicenseInfo kLicenseAppParametersBackwardsCompatibilityTestData = { "", "", 0, + "", + CryptoWrappedKey::kUninitialized, + "", a2bs_hex( "0AA8150802100122A1150801121408011210303132333435363738394142434445461" "A9D0E080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591BC4D0" @@ -1213,6 +2947,8 @@ const UsageInfo kUsageInfoTestData[] = { "003b12a38554336305525fa6ab70f024a18c73631bb1531eca3f0782c72d" "ba017311b3f1e98c739632e305e4bc0b2561ae2b"), 5, + kEmptyString, + CryptoWrappedKey(), }, a2bs_hex( "0AA407080310012A9D070A9A070A20B8E7F26B6B8B59BABF05B5A1F8927B412A85BC8" @@ -1273,6 +3009,8 @@ const UsageInfo kUsageInfoTestData[] = { "335611bf3bf1a1c89e2dea27c17a9d9a58a74121e840b002e8a6fb590072" "45be786c1f64"), 9, + kEmptyString, + CryptoWrappedKey(), }, a2bs_hex( "0AE80C080310012AE10C0A9A070A20B8E7F26B6B8B59BABF05B5A1F8927B412A85BC8" @@ -1351,6 +3089,8 @@ const UsageInfo kUsageInfoTestData[] = { "41e610d1efb56ed7ce2228a70e2e150afb66edc2da066d463aa90ba0caff" "078fbfec05c8"), 0, + kEmptyString, + CryptoWrappedKey(), }, a2bs_hex("0AF404080310012AED040AEA040A20BB3370CCD3C3C49573D6B74386D1886D98" "88BD81FE3241BCD2BAC9407D1A834E12C001DC0E51CFA5863F6C0B32A4AD7FA4" @@ -1399,6 +3139,8 @@ const UsageInfo kUsageInfoTestData[] = { "9b17eaf020cede0a9e0e7b5d91e4db7abdce445936cb2deecdefefdb14b7" "8f67b7ca5c733c9e88446fd814584584b86becbf6eb2b0e3d5603e8b"), 25, + kEmptyString, + CryptoWrappedKey(), }, a2bs_hex( "0AE604080310012ADF040ADC040A1E9212A6926F21C6727C1EE89D5607047A1636F20" @@ -1447,6 +3189,8 @@ const UsageInfo kUsageInfoTestData[] = { "fd5ed05887b8fa3bfd6ecc7bc91e621342732062d2f4411b763e20328af6" "f8ef5030e2f8027aef9e"), 6, + kEmptyString, + CryptoWrappedKey(), }, a2bs_hex( "0AE809080310012AE1090ADC040A1E9212A6926F21C6727C1EE89D5607047A1636F20" @@ -1515,9 +3259,11 @@ const UsageInfo kUsageInfoTestData[] = { "4ac5bb1da69acb44da06e4522c9a93d310cdda5dac1e1e0b91abff41e4e2" "edda4001"), 7, + kDrmCertificate, + kCryptoWrappedKey, }, a2bs_hex( - "0AEC09080310012AE5090AEA040A20BB3370CCD3C3C49573D6B74386D1886D9888BD8" + "0AA00A080310012A990A0AEA040A20BB3370CCD3C3C49573D6B74386D1886D9888BD8" "1FE3241BCD2BAC9407D1A834E12C001DC0E51CFA5863F6C0B32A4AD7FA40625DADCC2" "DCDE9E7FA3983B8804D9966803181682FC8AE831472E0B2FC26276242FBCE624D286E" "EDECCE5555804913B4F8F86C5AE86160B8434B109169A63DA04C5265102D772C11805" @@ -1535,7 +3281,7 @@ const UsageInfo kUsageInfoTestData[] = { "3646535343430383365663465652A60EA106C124476B753D39368A5966972A2729BB8" "BBEA734A2B3E812B705EACE016C8A03C9A406094D80059EF4CA26F1928FA2DAA5DE9A" "6F22372E5C7A941E610D1EFB56ED7CE2228A70E2E150AFB66EDC2DA066D463AA90BA0" - "CAFF078FBFEC05C830000AF5040A21EACE80E30BFDA213F1CE4DBCFD9D4D24B8E2AE0" + "CAFF078FBFEC05C830000AF7040A21EACE80E30BFDA213F1CE4DBCFD9D4D24B8E2AE0" "0054D167D9D7AE99547062B911312B40168A7665A21348FC0590328608DC520BE40F5" "B749328568FE383EF69C1A587AB2446CF9C41D821373D0856A883B316519A42218F80" "E7BD5764D16BAC9A9B427A7278F5940E563FCF6DEE0FF3AADBB702EBF2C54EC354AE7" @@ -1545,16 +3291,18 @@ const UsageInfo kUsageInfoTestData[] = { "12A2D6DCB15383A6D9AF4519EF804C6053A10C436002DE3A4EFCC017755F4AD1101BD" "C813E2D211732418DEE529CBB413C48AA5884C76A5C6F556A715055560D4247F5BF31" "0956949A3A171A4AA608A48446884E7676D558FF64D392B84E617805693D90F1E9B7B" - "540C383D384D7F7CE06C23618681BD838CEB1A514047F1C562C43159CC5E21588FBFC" - "E8A354111160F1A1E2BD3D798A000579BDFDB977252809EE1502DF8045972FE8AAC84" - "0211C2F8D9E4D5BE18509C327C647D654C4B6CC430B98F1FF37C96FAB087FB561B8CC" - "18480F877C873594D3148FF74B0E3C6327C27CA876DAE7422398FC5E85269CBA49AD0" - "99221C6B7369643865383033353063626566363436336130303235653663632A7C7CC" - "C7CE96055E16A52FA192EA2CF3C9DF3E89B9133A52286F71E6C6D82D0435F6B2155DF" - "DE590B347D8C86F62D7DFBAAE640C237256F609E5DA9CC6C103465FE3441612BBDFDF" - "4D1C24B2147FEB8565CEF4993E439C9D564A39A4AC5BB1DA69ACB44DA06E4522C9A93" - "D310CDDA5DAC1E1E0B91ABFF41E4E2EDDA40013007122062B0F22E176F77881114844" - "DABC6F0EB0F80381D8A06ECDF09913A5CF8E85B3F")}, + "540C383D384D7F7CE06C23618681BD838CEB1A514047F1C562C43159CC5E21588FBF" + "CE8A354111160F1A1E2BD3D798A000579BDFDB977252809EE1502DF8045972FE8AAC8" + "40211C2F8D9E4D5BE18509C327C647D654C4B6CC430B98F1FF37C96FAB087FB561B8C" + "C18480F877C873594D3148FF74B0E3C6327C27CA876DAE7422398FC5E85269CBA49AD" + "099221C6B7369643865383033353063626566363436336130303235653663632A7C7C" + "CC7CE96055E16A52FA192EA2CF3C9DF3E89B9133A52286F71E6C6D82D0435F6B2155D" + "FDE590B347D8C86F62D7DFBAAE640C237256F609E5DA9CC6C103465FE3441612BBDFD" + "F4D1C24B2147FEB8565CEF4993E439C9D564A39A4AC5BB1DA69ACB44DA06E4522C9A9" + "3D310CDDA5DAC1E1E0B91ABFF41E4E2EDDA40013007380012300800122C0A11612064" + "726D20636572746966696361746512156120777261707065642070726976617465206" + "B657918001220BD67878F6AA958EC6996061F69BF65E9806221BCA1CF26FFA4BF1D0B" + "4ECE8806")}, // test vector 6, app id: "", usage entry 2 {"", { @@ -1581,6 +3329,8 @@ const UsageInfo kUsageInfoTestData[] = { "a96388bf6be3c4f4f0b7e2f59efc6b8e965d8fadd5ab86b2bb816d2573ec" "36eb42b571297681be152d40639d"), 0, + kEmptyString, + CryptoWrappedKey(), }, a2bs_hex( "0ADA11080310012AD3110A9A070A20B8E7F26B6B8B59BABF05B5A1F8927B412A85BC8" @@ -1675,9 +3425,11 @@ const UsageInfo kUsageInfoTestData[] = { "a6b634d840925f711ae330599f0e0863800902b05d201a8a87b88a4bc170" "65a1a8a556c34bf86b53afcc9951be15bea9ab55"), 27, + kAnotherDrmCertificate, + kAnotherCryptoWrappedKey, }, a2bs_hex( - "0AB60E080310012AAF0E0ADC040A1E9212A6926F21C6727C1EE89D5607047A1636F20" + "0AF60E080310012AEF0E0ADC040A1E9212A6926F21C6727C1EE89D5607047A1636F20" "6F70E21FDA86E01B6A4B512A401EF947ABED64078EDF5B21FE6D3FB65384595D63A6D" "03E4D1D397C5019DEEB6890D3EF8773002B91E255AF0820FB594069DF55D8ABF96498" "E493F5C70F6B85F50E12A1ED3C039AD0CD838FE44D3FA9E2BBDDEB2919041203111ED" @@ -1705,33 +3457,35 @@ const UsageInfo kUsageInfoTestData[] = { "6A462DEF7A18A7D0ACEBF9F6E8A604356ADE2C81450C5466A472890B03EEFCF65388F" "060E24551C67B7D46AE5D4D841D5CC63D137FD543FAE2C771756590B90E480CA0126F" "1FC0090ACE62499E47569FC52196C788F80139755BDF12A7ACB29FD6E23A46A4C036F" - "04FF1ED6CD714094253BF1C58762C93F0DDF8A73C4BE927FFEC2723A16D8FFE512885" - "1F58537461275F6AA1976E3B399B7243919207E040EC16C5328E8AB082278FCE0E5D3" - "DF5C5F92DBA51FA6613587D4ECE31F2C001B49BFAED434F9512E895C2E09C88DDBF18" - "4BFAFE4D82E5D05A26AC06CDE29FAF6AB05B96685649C923779CE5EF7F316531ADA8E" - "74E45AB1DC1D75648AA2DE052674728867E87639FF9B782A3322186B7369646531643" - "3306233336235356632646562343731362A64D44A9D70A7C582559F089B1C0FDFCBDA" - "F5E26B672FCA5D58E889B407A0BA8599079CDE11FADFAB23AA1B97622839F3B7E1A96" - "F8332BEC5FBCBC9EB64FD5ED05887B8FA3BFD6ECC7BC91E621342732062D2F4411B76" - "3E20328AF6F8EF5030E2F8027AEF9E30060ACB040A1B8F922E955B269458ED1345BDE" - "9A24516520A536817E8E8612154A112A401D4ACC596A52055CEE710E1FEC44796DBF3" - "AE6B017AB156D9BFF7BFDB8F1E6352BFBE453034968F940C36AC18800E22BB2FF7126" - "8053702EF3FCE3FB2D607A078E0D1449FCC9D0675D41B1A65F78E3C02370D18112AAE" - "1E2577FF9087825A45125DB5DEE8E27BD14EA8666B4E8E6ABA6811C40B585AABB9C91" - "85209A48D11130FF690316916961F28286C71C3E985D7DC3352166E414B89DA2C17CC" - "5B69FC9C00990697F51A9002169D3C432F9C2F8B99E11632BD7D6A63F3D57679C567B" - "EDCB2E596ACE1050453732040CB468E9C43F6009B430CA4A4046D017E67A4BADD5B71" - "C0C9FCE2274817F0BCDA311A4F8703E6DC32AEDF30E6F9ABD40E249FC8B0A5045CC1E" - "47E60A60B4893EF92602F5584E1162F4FF3EE6D906228F97B442ACE1FB175D113B671" - "BDBE4CEFFDD98F2BB094C0DFAC03B79541A44D8AFFDC987F4268706B5A554E998907E" - "B7126E8C6BC07C837D8AEEBEA3249E37B4B7DD7327300FE7E62C15981CF73A13E806D" - "065BCADC2C747256907A5493592B07A0C07F9CD805FCDC0D30F70E4C4B2959A0F5238" - "5C6BD3E6EEB4E3D81FDC1A9DC3C76FAF1BFED913D58567FA9B296D27DFF5217C583E7" - "C134A642601F8237221E6B73696465363834393138643663333962666136353261343" - "061643933362A50703F69807C8F4D140168874B924A625132EB3B896A381D617B8FB8" - "3C7314A6B634D840925F711AE330599F0E0863800902B05D201A8A87B88A4BC17065A" - "1A8A556C34BF86B53AFCC9951BE15BEA9AB55301B122002206F46D9D05740AD34B99F" - "10C21A2FA23B8E45DCB00713E32D5CECF239D0A8")}, + "04FF1ED6CD714094253BF1C58762C93F0DDF8A73C4BE927FFEC2723A16D8FFE51288" + "51F58537461275F6AA1976E3B399B7243919207E040EC16C5328E8AB082278FCE0E5D" + "3DF5C5F92DBA51FA6613587D4ECE31F2C001B49BFAED434F9512E895C2E09C88DDBF1" + "84BFAFE4D82E5D05A26AC06CDE29FAF6AB05B96685649C923779CE5EF7F316531ADA8" + "E74E45AB1DC1D75648AA2DE052674728867E87639FF9B782A3322186B736964653164" + "33306233336235356632646562343731362A64D44A9D70A7C582559F089B1C0FDFCBD" + "AF5E26B672FCA5D58E889B407A0BA8599079CDE11FADFAB23AA1B97622839F3B7E1A9" + "6F8332BEC5FBCBC9EB64FD5ED05887B8FA3BFD6ECC7BC91E621342732062D2F4411B7" + "63E20328AF6F8EF5030E2F8027AEF9E30060ACD040A1B8F922E955B269458ED1345BD" + "E9A24516520A536817E8E8612154A112A401D4ACC596A52055CEE710E1FEC44796DBF" + "3AE6B017AB156D9BFF7BFDB8F1E6352BFBE453034968F940C36AC18800E22BB2FF712" + "68053702EF3FCE3FB2D607A078E0D1449FCC9D0675D41B1A65F78E3C02370D18112AA" + "E1E2577FF9087825A45125DB5DEE8E27BD14EA8666B4E8E6ABA6811C40B585AABB9C9" + "185209A48D11130FF690316916961F28286C71C3E985D7DC3352166E414B89DA2C17C" + "C5B69FC9C00990697F51A9002169D3C432F9C2F8B99E11632BD7D6A63F3D57679C567" + "BEDCB2E596ACE1050453732040CB468E9C43F6009B430CA4A4046D017E67A4BADD5B7" + "1C0C9FCE2274817F0BCDA311A4F8703E6DC32AEDF30E6F9ABD40E249FC8B0A5045CC1" + "E47E60A60B4893EF92602F5584E1162F4FF3EE6D906228F97B442ACE1FB175D113B67" + "1BDBE4CEFFDD98F2BB094C0DFAC03B79541A44D8AFFDC987F4268706B5A554E998907" + "EB7126E8C6BC07C837D8AEEBEA3249E37B4B7DD7327300FE7E62C15981CF73A13E806" + "D065BCADC2C747256907A5493592B07A0C07F9CD805FCDC0D30F70E4C4B2959A0F523" + "85C6BD3E6EEB4E3D81FDC1A9DC3C76FAF1BFED913D58567FA9B296D27DFF5217C583E" + "7C134A642601F8237221E6B7369646536383439313864366333396266613635326134" + "3061643933362A50703F69807C8F4D140168874B924A625132EB3B896A381D617B8FB" + "83C7314A6B634D840925F711AE330599F0E0863800902B05D201A8A87B88A4BC17065" + "A1A8A556C34BF86B53AFCC9951BE15BEA9AB55301B3800123C080012380A17616E6F7" + "46865722064726D206365727469666963617465121B616E6F74686572207772617070" + "65642070726976617465206B65791801122082BB366A1D04CD51FA6BE0E5E1F7B9393" + "0C2E887586E2E5FBC6838ADDD3A209B")}, // test vector 8, app id: "app_1", usage entry 2 {"app_1", { @@ -1758,9 +3512,11 @@ const UsageInfo kUsageInfoTestData[] = { "09914a3d7e898d93170317bfcff34861c0d687048cc93542a75a2c99b232" "3fafea1ee0c3e3d24edf2633"), 7, + kDrmCertificate, + kCryptoWrappedKey, }, a2bs_hex( - "0ABD0E080310012AB60E0AEA040A20BB3370CCD3C3C49573D6B74386D1886D9888BD8" + "0AF30E080310012AEC0E0AEA040A20BB3370CCD3C3C49573D6B74386D1886D9888BD8" "1FE3241BCD2BAC9407D1A834E12C001DC0E51CFA5863F6C0B32A4AD7FA40625DADCC2" "DCDE9E7FA3983B8804D9966803181682FC8AE831472E0B2FC26276242FBCE624D286E" "EDECCE5555804913B4F8F86C5AE86160B8434B109169A63DA04C5265102D772C11805" @@ -1778,7 +3534,7 @@ const UsageInfo kUsageInfoTestData[] = { "3646535343430383365663465652A60EA106C124476B753D39368A5966972A2729BB8" "BBEA734A2B3E812B705EACE016C8A03C9A406094D80059EF4CA26F1928FA2DAA5DE9A" "6F22372E5C7A941E610D1EFB56ED7CE2228A70E2E150AFB66EDC2DA066D463AA90BA0" - "CAFF078FBFEC05C830000AF5040A21EACE80E30BFDA213F1CE4DBCFD9D4D24B8E2AE0" + "CAFF078FBFEC05C830000AF7040A21EACE80E30BFDA213F1CE4DBCFD9D4D24B8E2AE0" "0054D167D9D7AE99547062B911312B40168A7665A21348FC0590328608DC520BE40F5" "B749328568FE383EF69C1A587AB2446CF9C41D821373D0856A883B316519A42218F80" "E7BD5764D16BAC9A9B427A7278F5940E563FCF6DEE0FF3AADBB702EBF2C54EC354AE7" @@ -1796,25 +3552,27 @@ const UsageInfo kUsageInfoTestData[] = { "C7CE96055E16A52FA192EA2CF3C9DF3E89B9133A52286F71E6C6D82D0435F6B2155DF" "DE590B347D8C86F62D7DFBAAE640C237256F609E5DA9CC6C103465FE3441612BBDFDF" "4D1C24B2147FEB8565CEF4993E439C9D564A39A4AC5BB1DA69ACB44DA06E4522C9A93" - "D310CDDA5DAC1E1E0B91ABFF41E4E2EDDA400130070ACE040A20D0B9A07AD7FFEEC13" - "784BD60DA011BE3589F3E450227FD36B1A3F6786CDBFE8F129801A419C5687A592099" - "DC67DA8BC4F5EF238C80FE4CE3E2FCB025392EFB14384B581B595A0E8FA95DE637FB2" - "184719EB36AD6539EE9DF0F67697F91D0186E04552E811196029CF4E256518DDF3215" - "AF8EC61442C17D6753B93F9D3A9240BAE39BACF5563659CF47D3A611CE20ED3EBBF86" - "CDDAD60CC2847C4595DCFD934D012CE205960052158461D7C5D480DE2E597876E64E8" - "F8DE692829A31AA402F7C19357E50FC474437C1A635C5BAE8F6F51AFA20750766DB19" - "457DFF7AEF2CAE78848A225CC6A088BBCFFEAD5BE6AAB6FC8AF091BF459C3BD9BCFA1" - "8DE53EF76DB1B4826CF0B8FF7B2D7C44BBADB3CD7AEDD8F639D1F38C52A58611A9782" - "AEACE72BE69A73D2E091A1120DC63F7BA6F1CB6CDDD69E9A236232ED8C14CEE665756" - "BA51F1D2E2530AB3662CE1B6EFBA91C5F10C53ABC886D6F25B5DC40417E54270843F3" - "B454C8C047FC366249E30379B0FBE0174FCAB8B8405AE7F20F6F2B81F11082FF0E270" - "B75F1E1AA7ED5806F4E65B46B872DBCB703D7BF20B9ECAA481425A5218D85A49595F3" - "ED268D61F1BE8E38E6126EB075FA6B7AE80431C8521C4BC2CE701E45D33BFCA9A5B0B" - "66B550AAB21EAE41F84CADFD2517DEE9A2C139AD475C387D25221C6B7369643332316" - "262363336663861336635636435643534613233362A48C3CB027611397B5D70CC0B08" - "E0F5249CD19996DA674E33722902173D45D709914A3D7E898D93170317BFCFF34861C" - "0D687048CC93542A75A2C99B2323FAFEA1EE0C3E3D24EDF263330071220B174821B32" - "5B0A6A900AD8C660C755D3B0273CA6E81D70E2C548CDEC07BE53FA")}, + "D310CDDA5DAC1E1E0B91ABFF41E4E2EDDA4001300738000AD0040A20D0B9A07AD7FFE" + "EC13784BD60DA011BE3589F3E450227FD36B1A3F6786CDBFE8F129801A419C5687A59" + "2099DC67DA8BC4F5EF238C80FE4CE3E2FCB025392EFB14384B581B595A0E8FA95DE63" + "7FB2184719EB36AD6539EE9DF0F67697F91D0186E04552E811196029CF4E256518DDF" + "3215AF8EC61442C17D6753B93F9D3A9240BAE39BACF5563659CF47D3A611CE20ED3EB" + "BF86CDDAD60CC2847C4595DCFD934D012CE205960052158461D7C5D480DE2E597876E" + "64E8F8DE692829A31AA402F7C19357E50FC474437C1A635C5BAE8F6F51AFA20750766" + "DB19457DFF7AEF2CAE78848A225CC6A088BBCFFEAD5BE6AAB6FC8AF091BF459C3BD9B" + "CFA18DE53EF76DB1B4826CF0B8FF7B2D7C44BBADB3CD7AEDD8F639D1F38C52A58611A" + "9782AEACE72BE69A73D2E091A1120DC63F7BA6F1CB6CDDD69E9A236232ED8C14CEE66" + "5756BA51F1D2E2530AB3662CE1B6EFBA91C5F10C53ABC886D6F25B5DC40417E542708" + "43F3B454C8C047FC366249E30379B0FBE0174FCAB8B8405AE7F20F6F2B81F11082FF0" + "E270B75F1E1AA7ED5806F4E65B46B872DBCB703D7BF20B9ECAA481425A5218D85A495" + "95F3ED268D61F1BE8E38E6126EB075FA6B7AE80431C8521C4BC2CE701E45D33BFCA9A" + "5B0B66B550AAB21EAE41F84CADFD2517DEE9A2C139AD475C387D25221C6B736964333" + "2316262363336663861336635636435643534613233362A48C3CB027611397B5D70CC" + "0B08E0F5249CD19996DA674E33722902173D45D709914A3D7E898D93170317BFCFF34" + "861C0D687048CC93542A75A2C99B2323FAFEA1EE0C3E3D24EDF263330073800123008" + "00122C0A11612064726D2063657274696669636174651215612077726170706564207" + "0726976617465206B657918001220C662693A9E231B85CEB7A3A50DEAA8279777B363" + "2C6D9ABC12FBB295018482E4")}, }; // kUsageInfoTestData const DeviceFiles::CdmUsageData kUsageInfoUpdateTestData = { @@ -1849,6 +3607,8 @@ const DeviceFiles::CdmUsageData kUsageInfoUpdateTestData = { "003b12a3855016c8a03c9a406094d80059ef4ca26f1928fa2a3f0782c72d" "ba0e2228a70e2e150afb66e305e4bc0b2561ae2b"), 6, + kEmptyString, + CryptoWrappedKey(), }; // kUsageInfoUpdateTestData struct HlsAttributesInfo { @@ -1989,35 +3749,33 @@ const std::vector kHashedUsageInfoFileWithSingleKeySetList = { const std::vector kHashedUsageInfoFileKeySetList = { "key_set_id_1", "key_set_id_2", "key_set_id_3"}; +// Contains kOemCertificate and kCryptoWrappedKey +const std::string kFakeOemCertificateFile = a2bs_hex( + "0A33080710014A2D0A12616E206F656D206365727469666963617465121561207772617070" + "65642070726976617465206B6579180012201C910430EBCEDD66BF0FBFE52917A71C6B66D5" + "DF1E01EA43D3A375B0703E1E1B"); + class MockFile : public File { public: MockFile() {} - ~MockFile() {} + ~MockFile() override {} - MOCK_METHOD2(Read, ssize_t(char*, size_t)); - MOCK_METHOD2(Write, ssize_t(const char*, size_t)); + MOCK_METHOD(ssize_t, Read, (char*, size_t), (override)); + MOCK_METHOD(ssize_t, Write, (const char*, size_t), (override)); }; -class MockFileSystem : public FileSystem { +class MockFileSystem : public wvutil::FileSystem { public: MockFileSystem() {} - ~MockFileSystem() {} + ~MockFileSystem() override {} - // Until gmock is updated to a version post-April 2017, we need this - // workaround to test functions that take or return smart pointers. - // See - // https://github.com/abseil/googletest/blob/master/googlemock/docs/CookBook.md#legacy-workarounds-for-move-only-types - std::unique_ptr Open(const std::string& buffer, int flags) { - return std::unique_ptr(DoOpen(buffer, flags)); - } - - MOCK_METHOD2(DoOpen, File*(const std::string&, int flags)); - MOCK_METHOD0(IsFactoryReset, bool()); - - MOCK_METHOD1(Exists, bool(const std::string&)); - MOCK_METHOD1(Remove, bool(const std::string&)); - MOCK_METHOD1(FileSize, ssize_t(const std::string&)); - MOCK_METHOD2(List, bool(const std::string&, std::vector*)); + MOCK_METHOD(std::unique_ptr, Open, (const std::string&, int flags), + (override)); + MOCK_METHOD(bool, Exists, (const std::string&), (override)); + MOCK_METHOD(bool, Remove, (const std::string&), (override)); + MOCK_METHOD(ssize_t, FileSize, (const std::string&), (override)); + MOCK_METHOD(bool, List, (const std::string&, std::vector*), + (override)); }; } // namespace @@ -2026,12 +3784,15 @@ class MockFileSystem : public FileSystem { using ::testing::_; using ::testing::AllArgs; using ::testing::AllOf; +using ::testing::AtLeast; +using ::testing::ByMove; using ::testing::DoAll; using ::testing::Eq; using ::testing::Expectation; using ::testing::Gt; using ::testing::HasSubstr; using ::testing::InSequence; +using ::testing::Invoke; using ::testing::InvokeWithoutArgs; using ::testing::NotNull; using ::testing::Return; @@ -2055,7 +3816,7 @@ class DeviceFilesTest : public ::testing::Test { app_parameters_len += itr->first.length(); app_parameters_len += itr->second.length(); } - return sizeof(DeviceFiles::LicenseState) + data.pssh_data.size() + + return sizeof(CdmOfflineLicenseState) + data.pssh_data.size() + data.key_request.size() + data.key_response.size() + data.key_renewal_request.size() + data.key_renewal_response.size() + data.key_release_url.size() + 3 * sizeof(int64_t) + @@ -2071,8 +3832,8 @@ class DeviceFilesTest : public ::testing::Test { if (name_end_pos == std::string::npos) return app_parameters; if (name_end_pos + 1 >= len) return app_parameters; size_t value_end_pos = str.find(' ', name_end_pos + 1); - app_parameters[str.substr(start_pos, name_end_pos)] = - str.substr(name_end_pos + 1, value_end_pos); + app_parameters[str.substr(start_pos, name_end_pos - start_pos)] = + str.substr(name_end_pos + 1, value_end_pos - name_end_pos - 1); if (value_end_pos == std::string::npos || value_end_pos + 1 >= len) return app_parameters; start_pos = value_end_pos + 1; @@ -2086,10 +3847,6 @@ class DeviceFilesTest : public ::testing::Test { class DeviceFilesStoreTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; -class DeviceCertificateTest - : public DeviceFilesTest, - public ::testing::WithParamInterface {}; - class DeviceFilesSecurityLevelTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; @@ -2112,7 +3869,7 @@ class DeviceFilesDeleteMultipleUsageInfoTest : public DeviceFilesTest, public ::testing::WithParamInterface {}; -MATCHER(IsCreateFileFlagSet, "") { return FileSystem::kCreate & arg; } +MATCHER(IsCreateFileFlagSet, "") { return wvutil::FileSystem::kCreate & arg; } MATCHER_P(StrAndLenEq, str, "") { const std::string data(std::get<0>(arg), std::get<1>(arg)); return data == str; @@ -2127,45 +3884,109 @@ MATCHER_P(StrAndLenContains, str_vector, "") { return true; } -TEST_F(DeviceCertificateTest, StoreCertificate) { +TEST_F(DeviceFilesTest, StoreCertificateInvalidParams) { + const std::string certificate(CdmRandom::RandomData(kCertificateLen)); + const CryptoWrappedKey private_key(CryptoWrappedKey::kRsa, + CdmRandom::RandomData(kWrappedKeyLen)); + const CryptoWrappedKey empty_private_key; + MockFileSystem file_system; - std::string certificate(CdmRandom::RandomData(kCertificateLen)); - std::string wrapped_private_key(CdmRandom::RandomData(kWrappedKeyLen)); - std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(false); + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + // Empty parameters + EXPECT_FALSE(device_files.StoreCertificate(kEmptyString, private_key)); + EXPECT_FALSE(device_files.StoreCertificate(certificate, empty_private_key)); + + // Certificate is not a valid Signed DRM certificate + EXPECT_FALSE(device_files.StoreCertificate(certificate, private_key)); + + // Certificate has an invalid creation time (negative or unlimited) + EXPECT_FALSE(device_files.StoreCertificate( + kTestCertificateWithInvalidCreationTime, private_key)); + EXPECT_FALSE(device_files.StoreCertificate( + kTestCertificateNoExpirationWithUnlimitedCreationTime, private_key)); +} + +class StoreCertificateTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + +TEST_P(StoreCertificateTest, DefaultAndLegacy) { + MockFileSystem file_system; + const bool certificate_type_default = GetParam(); /* otherwise legacy */ + + const std::string& certificate = certificate_type_default + ? kTestCertificateWithFutureExpiration + : kTestCertificateWithoutExpiration; + + const CryptoWrappedKey private_key(CryptoWrappedKey::kRsa, + CdmRandom::RandomData(kWrappedKeyLen)); + std::string certificate_file_name; + if (certificate_type_default) { + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + } else { + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateLegacy, &certificate_file_name)); + } + const std::string device_certificate_path = + device_base_path_ + certificate_file_name; // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); EXPECT_CALL(file_system, - DoOpen(StrEq(device_certificate_path), IsCreateFileFlagSet())) - .WillOnce(Return(file)); + Open(StrEq(device_certificate_path), IsCreateFileFlagSet())) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) .With(AllArgs(StrAndLenContains( - std::vector{certificate, wrapped_private_key}))) + std::vector{certificate, private_key.key()}))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); + EXPECT_TRUE(device_files.StoreCertificate(certificate, private_key)); } -TEST_P(DeviceCertificateTest, ReadCertificate) { +INSTANTIATE_TEST_SUITE_P(CertificateTest, StoreCertificateTest, + ::testing::Values(false, true)); + +TEST_F(DeviceFilesTest, RetrieveCertificateInvalidParams) { + std::string certificate, serial_number; + CryptoWrappedKey wrapped_private_key; + uint32_t system_id; + MockFileSystem file_system; - const bool atsc_mode = GetParam(); - std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(atsc_mode); - std::string data = kTestCertificateFileData; + DeviceFiles device_files(&file_system); + EXPECT_EQ(DeviceFiles::kCannotHandle, + device_files.RetrieveCertificate(false, &certificate, nullptr, + &serial_number, &system_id)); + EXPECT_EQ( + DeviceFiles::kCannotHandle, + device_files.RetrieveCertificate(false, nullptr, &wrapped_private_key, + &serial_number, &system_id)); +} + +TEST_F(DeviceFilesTest, RetrieveAtscCertificate) { + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName(DeviceFiles::kCertificateAtsc, + &certificate_file_name)); + const std::string device_certificate_path = + device_base_path_ + certificate_file_name; + const std::string& data = kTestCertificateFileDataWithoutExpiration; // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) - .Times(2) + .Times(AtLeast(1)) .WillRepeatedly(Return(true)); EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) .WillOnce(Return(data.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(device_certificate_path), _)) - .WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(device_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); @@ -2174,69 +3995,519 @@ TEST_P(DeviceCertificateTest, ReadCertificate) { DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); - std::string certificate, wrapped_private_key; + std::string certificate; + CryptoWrappedKey private_key; std::string serial_number; uint32_t system_id = 0; - ASSERT_TRUE(device_files.RetrieveCertificate(atsc_mode, &certificate, - &wrapped_private_key, - &serial_number, &system_id)); - EXPECT_EQ(kTestCertificate, certificate); - EXPECT_EQ(kTestWrappedPrivateKey, wrapped_private_key); + ASSERT_EQ(DeviceFiles::kCertificateValid, + device_files.RetrieveCertificate(true, &certificate, &private_key, + &serial_number, &system_id)); + EXPECT_EQ(kTestCertificateWithoutExpiration, certificate); + EXPECT_EQ(kTestWrappedKey, private_key); + EXPECT_EQ("7CB49F987A635E1E0A52184694582D6E", b2a_hex(serial_number)); } -TEST_P(DeviceCertificateTest, HasCertificate) { +TEST_F(DeviceFilesTest, RetrieveAtscCertificateNotFound) { MockFileSystem file_system; - bool atsc_mode = GetParam(); - std::string device_certificate_path = - device_base_path_ + DeviceFiles::GetCertificateFileName(atsc_mode); + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName(DeviceFiles::kCertificateAtsc, + &certificate_file_name)); + const std::string device_certificate_path = + device_base_path_ + certificate_file_name; + + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .WillOnce(Return(false)); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + std::string serial_number; + uint32_t system_id = 0; + ASSERT_EQ(DeviceFiles::kCertificateNotFound, + device_files.RetrieveCertificate(true, &certificate, &private_key, + &serial_number, &system_id)); +} + +/* TODO(b/192430982): Renable expiration of legacy DRM certificates +TEST_F(DeviceFilesTest, RetrieveLegacyCertificateWithoutExpirationTime) { + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateLegacy, &certificate_file_name)); + const std::string device_legacy_certificate_path = + device_base_path_ + certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_default_certificate_path = + device_base_path_ + certificate_file_name; + const std::string& data = kTestCertificateFileDataWithoutExpiration; + + // Call to Open will return a unique_ptr, freeing this object. + MockFile* read_file = new MockFile(); + MockFile* write_file = new MockFile(); + EXPECT_CALL(file_system, Exists(StrEq(device_legacy_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, Exists(StrEq(device_default_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(file_system, FileSize(StrEq(device_legacy_certificate_path))) + .WillOnce(Return(data.size())); + // Retrieving the legacy license will cause a read as well as a write + // to fill in a random expiry date ~6 months later if one has not been set + EXPECT_CALL(file_system, Open(StrEq(device_legacy_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))) + .WillOnce(Return(ByMove(std::unique_ptr(write_file)))); + EXPECT_CALL(*read_file, Read(NotNull(), Eq(data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), + Return(data.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + EXPECT_CALL(*write_file, Read(_, _)).Times(0); + EXPECT_CALL(*write_file, Write(_, _)) + .With(AllArgs(StrAndLenContains(std::vector{ + kTestCertificateWithoutExpiration, kTestWrappedKey.key()}))) + .WillOnce(ReturnArg<1>()); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + std::string serial_number; + uint32_t system_id = 0; + + ASSERT_EQ(DeviceFiles::kCertificateValid, + device_files.RetrieveCertificate(false, &certificate, &private_key, + &serial_number, &system_id)); + EXPECT_EQ(kTestCertificateWithoutExpiration, certificate); + EXPECT_EQ(kTestWrappedKey, private_key); + EXPECT_EQ("7CB49F987A635E1E0A52184694582D6E", b2a_hex(serial_number)); +} + +TEST_F(DeviceFilesTest, RetrieveLegacyCertificateWithClientExpirationTime) { + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateLegacy, &certificate_file_name)); + const std::string device_legacy_certificate_path = + device_base_path_ + certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_default_certificate_path = + device_base_path_ + certificate_file_name; + const std::string& data = kTestLegacyCertificateFileDataWithClientExpiration; + + // Call to Open will return a unique_ptr, freeing this object. + MockFile* read_file = new MockFile(); + EXPECT_CALL(file_system, Exists(StrEq(device_legacy_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, Exists(StrEq(device_default_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(file_system, FileSize(StrEq(device_legacy_certificate_path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(device_legacy_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))); + EXPECT_CALL(*read_file, Read(NotNull(), Eq(data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), + Return(data.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + std::string serial_number; + uint32_t system_id = 0; + + // Retrieve the legacy certificate. The expiration data is in the future. + ASSERT_EQ(DeviceFiles::kCertificateValid, + device_files.RetrieveCertificate(false, &certificate, &private_key, + &serial_number, &system_id)); + EXPECT_EQ(kTestCertificateWithoutExpiration, certificate); + EXPECT_EQ(kTestWrappedKey, private_key); + EXPECT_EQ("7CB49F987A635E1E0A52184694582D6E", b2a_hex(serial_number)); +} + +class RetrieveLegacyCertificateTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + +TEST_P(RetrieveLegacyCertificateTest, ErrorScenarios) { + const size_t index = GetParam(); + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateLegacy, &certificate_file_name)); + const std::string device_legacy_certificate_path = + device_base_path_ + certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_default_certificate_path = + device_base_path_ + certificate_file_name; + const CertificateErrorData& param = + kRetrieveLegacyCertificateErrorData[index]; + const std::string& data = param.file_data; + + // Call to Open will return a unique_ptr, freeing this object. + MockFile* read_file = new MockFile(); + EXPECT_CALL(file_system, Exists(StrEq(device_legacy_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, Exists(StrEq(device_default_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(file_system, FileSize(StrEq(device_legacy_certificate_path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(device_legacy_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))); + EXPECT_CALL(*read_file, Read(NotNull(), Eq(data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), + Return(data.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + std::string serial_number; + uint32_t system_id = 0; + + // Retrieve the legacy certificate. The license has expired. + ASSERT_EQ(param.certificate_state, + device_files.RetrieveCertificate(false, &certificate, &private_key, + &serial_number, &system_id)); + + if (param.certificate_state == DeviceFiles::kCertificateExpired) { + EXPECT_EQ(kTestCertificateWithoutExpiration, certificate); + EXPECT_EQ(kTestWrappedKey, private_key); + EXPECT_EQ("7CB49F987A635E1E0A52184694582D6E", b2a_hex(serial_number)); + } +} + +INSTANTIATE_TEST_SUITE_P(CertificateTest, RetrieveLegacyCertificateTest, + ::testing::Range(kZero, kNumberOfLegacyCertificates)); + +TEST_F(DeviceFilesTest, RetrieveDefaultCertificate) { + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_certificate_path = + device_base_path_ + certificate_file_name; + const std::string& data = kTestDefaultCertificateFileDataFutureExpiration; + + // Call to Open will return a unique_ptr, freeing this object. + MockFile* read_file = new MockFile(); + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(device_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))); + EXPECT_CALL(*read_file, Read(NotNull(), Eq(data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), + Return(data.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + std::string serial_number; + uint32_t system_id = 0; + + // Retrieve the default certificate. It should be available. + ASSERT_EQ(DeviceFiles::kCertificateValid, + device_files.RetrieveCertificate(false, &certificate, &private_key, + &serial_number, &system_id)); + EXPECT_EQ(kTestCertificateWithFutureExpiration, certificate); + EXPECT_EQ(kTestWrappedKey, private_key); + EXPECT_EQ("7CB49F987A635E1E0A52184694582D6E", b2a_hex(serial_number)); +} +*/ + +TEST_F(DeviceFilesTest, RetrieveDefaultCertificateNeverExpires) { + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_certificate_path = + device_base_path_ + certificate_file_name; + const std::string& data = kTestDefaultCertificateFileDataNeverExpires; + + // Call to Open will return a unique_ptr, freeing this object. + MockFile* read_file = new MockFile(); + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(device_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))); + EXPECT_CALL(*read_file, Read(NotNull(), Eq(data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), + Return(data.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + std::string serial_number; + uint32_t system_id = 0; + + // Retrieve the default certificate. It should be available. + ASSERT_EQ(DeviceFiles::kCertificateValid, + device_files.RetrieveCertificate(false, &certificate, &private_key, + &serial_number, &system_id)); + EXPECT_EQ(kTestCertificateNeverExpires, certificate); + EXPECT_EQ(kTestWrappedKey, private_key); + EXPECT_EQ("7CB49F987A635E1E0A52184694582D6E", b2a_hex(serial_number)); +} + +class RetrieveDefaultCertificateTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + +TEST_P(RetrieveDefaultCertificateTest, ErrorScenarios) { + const size_t index = GetParam(); + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_certificate_path = + device_base_path_ + certificate_file_name; + const CertificateErrorData& param = + kRetrieveDefaultCertificateErrorData[index]; + const std::string& data = param.file_data; + + // Call to Open will return a unique_ptr, freeing this object. + MockFile* read_file = new MockFile(); + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(device_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))); + EXPECT_CALL(*read_file, Read(NotNull(), Eq(data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), + Return(data.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + std::string serial_number; + uint32_t system_id = 0; + + // Retrieve the default certificate. It should be available. + ASSERT_EQ(param.certificate_state, + device_files.RetrieveCertificate(false, &certificate, &private_key, + &serial_number, &system_id)); + if (param.certificate_state == DeviceFiles::kCertificateExpired) { + EXPECT_EQ(kTestCertificateExpired, certificate); + EXPECT_EQ(kTestWrappedKey, private_key); + EXPECT_EQ("7CB49F987A635E1E0A52184694582D6E", b2a_hex(serial_number)); + } +} + +INSTANTIATE_TEST_SUITE_P(CertificateTest, RetrieveDefaultCertificateTest, + ::testing::Range(kZero, kNumberOfDefaultCertificates)); + +TEST_F(DeviceFilesTest, RetrieveCertificateWithoutKeyType) { + // Stored files without an explicit key type should default to RSA. + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateLegacy, &certificate_file_name)); + const std::string device_legacy_certificate_path = + device_base_path_ + certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_default_certificate_path = + device_base_path_ + certificate_file_name; + const std::string& data = kTestCertificateFileWithoutKeyTypeData; + + // Call to Open will return a unique_ptr, freeing this object. + // The file will be re-written with a new client expiration time + MockFile* read_file = new MockFile(); + /* TODO(b/192430982): Renable expiration of legacy DRM certificates + MockFile* write_file = new MockFile(); + */ + EXPECT_CALL(file_system, Exists(StrEq(device_legacy_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, Exists(StrEq(device_default_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + EXPECT_CALL(file_system, FileSize(StrEq(device_legacy_certificate_path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file_system, Open(StrEq(device_legacy_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))); + /* TODO(b/192430982): Renable expiration of legacy DRM certificates + .WillOnce(Return(ByMove(std::unique_ptr(write_file)))); + */ + EXPECT_CALL(*read_file, Read(NotNull(), Eq(data.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(data.begin(), data.end()), + Return(data.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + /* TODO(b/192430982): Renable expiration of legacy DRM certificates + EXPECT_CALL(*write_file, Read(_, _)).Times(0); + EXPECT_CALL(*write_file, Write(_, _)) + .With(AllArgs(StrAndLenContains(std::vector{ + kTestCertificateWithoutExpiration, kTestWrappedKey.key()}))) + .WillOnce(ReturnArg<1>()); + */ + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + std::string serial_number; + uint32_t system_id = 0; + ASSERT_EQ(DeviceFiles::kCertificateValid, + device_files.RetrieveCertificate(false, &certificate, &private_key, + &serial_number, &system_id)); + EXPECT_EQ(kTestCertificateWithoutExpiration, certificate); + EXPECT_EQ(kTestWrappedKey, private_key); + EXPECT_EQ("7CB49F987A635E1E0A52184694582D6E", b2a_hex(serial_number)); +} + +TEST_F(DeviceFilesTest, HasCertificateAtsc) { + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName(DeviceFiles::kCertificateAtsc, + &certificate_file_name)); + const std::string device_certificate_path = + device_base_path_ + certificate_file_name; EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) .WillOnce(Return(false)) .WillOnce(Return(true)); - EXPECT_CALL(file_system, DoOpen(_, _)).Times(0); + EXPECT_CALL(file_system, Open(_, _)).Times(0); DeviceFiles device_files(&file_system); ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); // MockFile returns false. - EXPECT_FALSE(device_files.HasCertificate(atsc_mode)); + EXPECT_FALSE(device_files.HasCertificate(true)); // MockFile returns true. - EXPECT_TRUE(device_files.HasCertificate(atsc_mode)); + EXPECT_TRUE(device_files.HasCertificate(true)); } -INSTANTIATE_TEST_CASE_P(AtscMode, DeviceCertificateTest, - ::testing::Values(false, true)); - -TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { +TEST_F(DeviceFilesTest, HasCertificateDefault) { MockFileSystem file_system; - std::string certificate(CdmRandom::RandomData(kCertificateLen)); - std::string wrapped_private_key(CdmRandom::RandomData(kWrappedKeyLen)); + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_certificate_path = + device_base_path_ + certificate_file_name; + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + + EXPECT_TRUE(device_files.HasCertificate(false)); +} + +TEST_F(DeviceFilesTest, HasCertificateLegacy) { + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_default_certificate_path = + device_base_path_ + certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateLegacy, &certificate_file_name)); + const std::string device_legacy_certificate_path = + device_base_path_ + certificate_file_name; + + EXPECT_CALL(file_system, Exists(StrEq(device_default_certificate_path))) + .WillOnce(Return(false)); + EXPECT_CALL(file_system, Exists(StrEq(device_legacy_certificate_path))) + .WillOnce(Return(true)); + EXPECT_CALL(file_system, Open(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + + EXPECT_TRUE(device_files.HasCertificate(false)); +} + +TEST_F(DeviceFilesTest, HasCertificateNone) { + MockFileSystem file_system; + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_default_certificate_path = + device_base_path_ + certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateLegacy, &certificate_file_name)); + const std::string device_legacy_certificate_path = + device_base_path_ + certificate_file_name; + + EXPECT_CALL(file_system, Exists(StrEq(device_default_certificate_path))) + .WillOnce(Return(false)); + EXPECT_CALL(file_system, Exists(StrEq(device_legacy_certificate_path))) + .WillOnce(Return(false)); + EXPECT_CALL(file_system, Open(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + + EXPECT_FALSE(device_files.HasCertificate(false)); +} + +TEST_P(DeviceFilesSecurityLevelTest, RequestedSecurityLevel) { CdmSecurityLevel security_level = GetParam(); + MockFileSystem file_system; + std::string certificate(kTestCertificateWithFutureExpiration); + const CryptoWrappedKey private_key(CryptoWrappedKey::kRsa, + CdmRandom::RandomData(kWrappedKeyLen)); + std::string device_base_path; ASSERT_TRUE( Properties::GetDeviceFilesBasePath(security_level, &device_base_path)); - std::string device_certificate_path = - device_base_path + DeviceFiles::GetCertificateFileName(false); + std::string certificate_file_name; + EXPECT_TRUE(DeviceFiles::GetCertificateFileName( + DeviceFiles::kCertificateDefault, &certificate_file_name)); + const std::string device_certificate_path = + device_base_path + certificate_file_name; // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); EXPECT_CALL(file_system, - DoOpen(StrEq(device_certificate_path), IsCreateFileFlagSet())) - .WillOnce(Return(file)); + Open(StrEq(device_certificate_path), IsCreateFileFlagSet())) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) .With(AllArgs(StrAndLenContains( - std::vector{certificate, wrapped_private_key}))) + std::vector{certificate, private_key.key()}))) .WillOnce(ReturnArg<1>()); EXPECT_CALL(*file, Read(_, _)).Times(0); DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(security_level)); - EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); + EXPECT_TRUE(device_files.StoreCertificate(certificate, private_key)); } -INSTANTIATE_TEST_CASE_P(SecurityLevel, DeviceFilesSecurityLevelTest, - ::testing::Values(kSecurityLevelL1, kSecurityLevelL3)); +INSTANTIATE_TEST_SUITE_P(SecurityLevel, DeviceFilesSecurityLevelTest, + ::testing::Values(kSecurityLevelL1, kSecurityLevelL3)); TEST_P(DeviceFilesStoreTest, StoreLicense) { MockFileSystem file_system; @@ -2264,8 +4535,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); - EXPECT_CALL(file_system, DoOpen(StrEq(license_path), IsCreateFileFlagSet())) - .WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) .With(AllArgs(StrAndLenContains(expected_substrings))) .WillOnce(ReturnArg<1>()); @@ -2274,6 +4545,7 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { DeviceFiles::ResponseType sub_error_code; DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + CryptoWrappedKey wrapped_private_key; DeviceFiles::CdmLicenseData license_data{ kLicenseTestData[license_num].key_set_id, kLicenseTestData[license_num].license_state, @@ -2288,12 +4560,15 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) { kLicenseTestData[license_num].grace_period_end_time, app_parameters, kLicenseTestData[license_num].usage_entry, - kLicenseTestData[license_num].usage_entry_number}; + kLicenseTestData[license_num].usage_entry_number, + kLicenseTestData[license_num].drm_certificate, + CryptoWrappedKey(kLicenseTestData[license_num].key_type, + kLicenseTestData[license_num].private_key)}; EXPECT_TRUE(device_files.StoreLicense(license_data, &sub_error_code)); EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); } -INSTANTIATE_TEST_CASE_P(StoreLicense, DeviceFilesStoreTest, ::testing::Bool()); +INSTANTIATE_TEST_SUITE_P(StoreLicense, DeviceFilesStoreTest, ::testing::Bool()); TEST_F(DeviceFilesTest, StoreLicenses) { MockFileSystem file_system; @@ -2314,6 +4589,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) { kLicenseTestData[i].key_renewal_response, kLicenseTestData[i].key_release_url, kLicenseTestData[i].usage_entry, + kLicenseTestData[i].drm_certificate, + kLicenseTestData[i].private_key, }; for (const auto& iter : app_parameters) { expected_substrings.push_back(iter.first); @@ -2322,8 +4599,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) { // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); - EXPECT_CALL(file_system, DoOpen(StrEq(license_path), IsCreateFileFlagSet())) - .WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) .With(AllArgs(StrAndLenContains(expected_substrings))) @@ -2352,7 +4629,10 @@ TEST_F(DeviceFilesTest, StoreLicenses) { kLicenseTestData[i].grace_period_end_time, app_parameters, kLicenseTestData[i].usage_entry, - kLicenseTestData[i].usage_entry_number}; + kLicenseTestData[i].usage_entry_number, + kLicenseTestData[i].drm_certificate, + CryptoWrappedKey(kLicenseTestData[i].key_type, + kLicenseTestData[i].private_key)}; EXPECT_TRUE(device_files.StoreLicense(license_data, &sub_error_code)); EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); } @@ -2375,8 +4655,8 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { .WillOnce(Return(true)); EXPECT_CALL(file_system, FileSize(StrEq(license_path))) .WillOnce(Return(size)); - EXPECT_CALL(file_system, DoOpen(StrEq(license_path), _)) - .WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(size))) .WillOnce( DoAll(SetArrayArgument<0>(kLicenseTestData[i].file_data.begin(), @@ -2411,6 +4691,12 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { EXPECT_EQ(kLicenseTestData[i].usage_entry, license_data.usage_entry); EXPECT_EQ(kLicenseTestData[i].usage_entry_number, license_data.usage_entry_number); + EXPECT_EQ(kLicenseTestData[i].drm_certificate, + license_data.drm_certificate); + EXPECT_EQ(kLicenseTestData[i].key_type, + license_data.wrapped_private_key.type()); + EXPECT_EQ(kLicenseTestData[i].private_key, + license_data.wrapped_private_key.key()); EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); std::map::iterator itr; @@ -2421,6 +4707,7 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) { EXPECT_NE(std::string::npos, kLicenseTestData[i].app_parameters.find(itr->second)); } + license_data.app_parameters.clear(); } } @@ -2439,8 +4726,8 @@ TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { EXPECT_CALL(file_system, Exists(StrEq(license_path))).WillOnce(Return(true)); EXPECT_CALL(file_system, FileSize(StrEq(license_path))) .WillOnce(Return(size)); - EXPECT_CALL(file_system, DoOpen(StrEq(license_path), _)) - .WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(size))) .WillOnce(DoAll(SetArrayArgument<0>(test_data->file_data.begin(), test_data->file_data.end()), @@ -2469,6 +4756,9 @@ TEST_F(DeviceFilesTest, AppParametersBackwardCompatibility) { EXPECT_EQ(0u, license_data.app_parameters.size()); EXPECT_EQ(test_data->usage_entry, license_data.usage_entry); EXPECT_EQ(test_data->usage_entry_number, license_data.usage_entry_number); + EXPECT_EQ(test_data->drm_certificate, license_data.drm_certificate); + EXPECT_EQ(test_data->key_type, license_data.wrapped_private_key.type()); + EXPECT_EQ(test_data->private_key, license_data.wrapped_private_key.key()); EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); } @@ -2484,8 +4774,8 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { for (size_t i = 0; i < ArraySize(kLicenseUpdateTestData); i++) { // Call to Open will return a unique_ptr, freeing this object. MockFile* file = new MockFile(); - EXPECT_CALL(file_system, DoOpen(StrEq(license_path), IsCreateFileFlagSet())) - .WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(license_path), IsCreateFileFlagSet())) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) .With(AllArgs(StrAndLenEq(kLicenseUpdateTestData[i].file_data))) .WillOnce(ReturnArg<1>()); @@ -2504,7 +4794,10 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) { kLicenseUpdateTestData[0].grace_period_end_time, GetAppParameters(kLicenseTestData[0].app_parameters), kLicenseUpdateTestData[0].usage_entry, - kLicenseUpdateTestData[0].usage_entry_number}; + kLicenseUpdateTestData[0].usage_entry_number, + kLicenseUpdateTestData[0].drm_certificate, + CryptoWrappedKey(kLicenseTestData[0].key_type, + kLicenseTestData[0].private_key)}; DeviceFiles::ResponseType sub_error_code; EXPECT_TRUE(device_files.StoreLicense(license_data, &sub_error_code)); EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); @@ -2527,8 +4820,8 @@ TEST_F(DeviceFilesTest, DeleteLicense) { .WillOnce(Return(false)); EXPECT_CALL(file_system, FileSize(StrEq(license_path))) .WillOnce(Return(size)); - EXPECT_CALL(file_system, DoOpen(StrEq(license_path), _)) - .WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(license_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(size))) .WillOnce(DoAll(SetArrayArgument<0>(kLicenseTestData[0].file_data.begin(), kLicenseTestData[0].file_data.end()), @@ -2569,6 +4862,11 @@ TEST_F(DeviceFilesTest, DeleteLicense) { EXPECT_EQ(kLicenseTestData[0].usage_entry, license_data.usage_entry); EXPECT_EQ(kLicenseTestData[0].usage_entry_number, license_data.usage_entry_number); + EXPECT_EQ(kLicenseTestData[0].drm_certificate, license_data.drm_certificate); + EXPECT_EQ(kLicenseTestData[0].key_type, + license_data.wrapped_private_key.type()); + EXPECT_EQ(kLicenseTestData[0].private_key, + license_data.wrapped_private_key.key()); EXPECT_EQ(DeviceFiles::kNoError, sub_error_code); EXPECT_TRUE(device_files.DeleteLicense(kLicenseTestData[0].key_set_id)); @@ -2578,7 +4876,7 @@ TEST_F(DeviceFilesTest, DeleteLicense) { TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { // Validate that ReserveLicenseIds does not touch the file system. MockFileSystem file_system; - EXPECT_CALL(file_system, DoOpen(_, _)).Times(0); + EXPECT_CALL(file_system, Open(_, _)).Times(0); DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); @@ -2592,6 +4890,112 @@ TEST_F(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem) { } } +// OKP info can only be stored on L1 device files. +TEST_F(DeviceFilesTest, OkpInfo_L1Only) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL3)); + + okp::SystemFallbackInfo info; + info.SetState(okp::SystemState::kNeedsProvisioning); + info.SetFirstCheckedTime(1234); + + const std::string kErrorMessage = "OKP should not be available on L3"; + EXPECT_FALSE(device_files.StoreOkpInfo(info)) << kErrorMessage; + EXPECT_FALSE(device_files.RetrieveOkpInfo(&info)) << kErrorMessage; + EXPECT_FALSE(device_files.DeleteOkpInfo()) << kErrorMessage; +} + +// Uninitialized info cannot be stored. +TEST_F(DeviceFilesTest, OkpInfo_UninitializedInfo) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + okp::SystemFallbackInfo info; // Uninitialized. + EXPECT_FALSE(device_files.StoreOkpInfo(info)); +} + +TEST_F(DeviceFilesTest, OkpInfo_FileDoesNotExist) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + EXPECT_CALL(file_system, Exists(kOkpInfoPath)).WillOnce(Return(false)); + + okp::SystemFallbackInfo info; + EXPECT_FALSE(device_files.RetrieveOkpInfo(&info)); +} + +TEST_F(DeviceFilesTest, OkpInfo_RetrieveWithNull) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_FALSE(device_files.RetrieveOkpInfo(nullptr)); +} + +TEST_F(DeviceFilesTest, OkpInfo_DeleteFile) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + + // L1 - Should succeed. + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + EXPECT_CALL(file_system, Remove(kOkpInfoPath)).WillOnce(Return(true)); + EXPECT_TRUE(device_files.DeleteOkpInfo()); + + // L3 - Should fail. + EXPECT_TRUE(device_files.Init(kSecurityLevelL3)); + EXPECT_FALSE(device_files.DeleteOkpInfo()); +} + +TEST_F(DeviceFilesTest, OkpInfo_StoreAndRetrieve) { + MockFileSystem file_system; + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + // Prepare data. + okp::SystemFallbackInfo info; + info.SetState(okp::SystemState::kFallbackMode); + info.SetFirstCheckedTime(1234); + info.SetBackoffStartTime(2345); + info.SetBackoffDuration(1111); + + // Set store expectations. + const std::string kOkpInfoPath = + device_base_path_ + DeviceFiles::GetOkpInfoFileName(); + MockFile* file = new MockFile(); + EXPECT_CALL(file_system, Open(kOkpInfoPath, _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); + std::string serialized; + EXPECT_CALL(*file, Write(NotNull(), _)) + .WillOnce(DoAll(Invoke([&](const char* buf, size_t len) { + serialized.assign(buf, len); + }), + ReturnArg<1>())); + + EXPECT_TRUE(device_files.StoreOkpInfo(info)); + ASSERT_FALSE(serialized.empty()) << "OKP info was not serialized"; + + // Set retrieve expectations. + file = new MockFile(); + EXPECT_CALL(file_system, Exists(kOkpInfoPath)).WillOnce(Return(true)); + EXPECT_CALL(file_system, FileSize(kOkpInfoPath)) + .WillOnce(Return(serialized.size())); + EXPECT_CALL(file_system, Open(kOkpInfoPath, _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); + EXPECT_CALL(*file, Read(NotNull(), _)) + .WillOnce(DoAll(SetArrayArgument<0>(serialized.begin(), serialized.end()), + Return(serialized.size()))); + + okp::SystemFallbackInfo retrieved_info; + EXPECT_TRUE(device_files.RetrieveOkpInfo(&retrieved_info)); + + EXPECT_EQ(retrieved_info, info); +} + // From a usage info file containing 3 provider sessions, 2 will be // deleted using the |key_set_id| associated with them. // It is expected that once the provider sessions are deleted, the @@ -2617,8 +5021,8 @@ TEST_P(DeviceFilesDeleteMultipleUsageInfoTest, DeleteAllButOne) { // File read expectations. MockFile* file_in = new MockFile(); - EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) - .WillOnce(Return(file_in)); + EXPECT_CALL(file_system, Open(file_path, wvutil::FileSystem::kReadOnly)) + .WillOnce(Return(ByMove(std::unique_ptr(file_in)))); Expectation read_original = EXPECT_CALL(*file_in, Read(NotNull(), _)) .WillOnce( @@ -2629,9 +5033,9 @@ TEST_P(DeviceFilesDeleteMultipleUsageInfoTest, DeleteAllButOne) { // File write expectations. MockFile* file_out = new MockFile(); - EXPECT_CALL(file_system, - DoOpen(file_path, FileSystem::kCreate | FileSystem::kTruncate)) - .WillOnce(Return(file_out)); + EXPECT_CALL(file_system, Open(file_path, wvutil::FileSystem::kCreate | + wvutil::FileSystem::kTruncate)) + .WillOnce(Return(ByMove(std::unique_ptr(file_out)))); EXPECT_CALL(*file_out, Write(StrEq(result_hashed_usage_info_file), _)) .After(read_original) .WillOnce(Return(result_hashed_usage_info_file.size())); @@ -2648,8 +5052,9 @@ TEST_P(DeviceFilesDeleteMultipleUsageInfoTest, DeleteAllButOne) { kUsageInfoFileName, to_remove)); } -INSTANTIATE_TEST_CASE_P(DeviceFilesTest, DeviceFilesDeleteMultipleUsageInfoTest, - ::testing::Range(0, 3)); +INSTANTIATE_TEST_SUITE_P(DeviceFilesTest, + DeviceFilesDeleteMultipleUsageInfoTest, + ::testing::Range(0, 3)); // Delete all provider sessions from a usage info file. It is expected // that the usage info file will be deleted (not written back to with @@ -2670,8 +5075,8 @@ TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteAllKeySetIds) { EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true)); EXPECT_CALL(file_system, FileSize(file_path)) .WillOnce(Return(kHashedUsageInfoFileWithThreeKeySetIds.size())); - EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) - .WillOnce(Return(file_in)); + EXPECT_CALL(file_system, Open(file_path, wvutil::FileSystem::kReadOnly)) + .WillOnce(Return(ByMove(std::unique_ptr(file_in)))); EXPECT_CALL(*file_in, Read(NotNull(), _)) .WillOnce(DoAll( SetArrayArgument<0>(kHashedUsageInfoFileWithThreeKeySetIds.cbegin(), @@ -2707,8 +5112,8 @@ TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteNone) { MockFile* file_in = new MockFile(); EXPECT_CALL(file_system, FileSize(file_path)) .WillOnce(Return(kHashedUsageInfoFileWithThreeKeySetIds.size())); - EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) - .WillOnce(Return(file_in)); + EXPECT_CALL(file_system, Open(file_path, wvutil::FileSystem::kReadOnly)) + .WillOnce(Return(ByMove(std::unique_ptr(file_in)))); EXPECT_CALL(*file_in, Read(NotNull(), _)) .WillOnce(DoAll( SetArrayArgument<0>(kHashedUsageInfoFileWithThreeKeySetIds.cbegin(), @@ -2743,8 +5148,8 @@ TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteOne) { // File read expectations. MockFile* file_in = new MockFile(); - EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) - .WillOnce(Return(file_in)); + EXPECT_CALL(file_system, Open(file_path, wvutil::FileSystem::kReadOnly)) + .WillOnce(Return(ByMove(std::unique_ptr(file_in)))); Expectation read_original = EXPECT_CALL(*file_in, Read(NotNull(), _)) .WillOnce(DoAll( @@ -2754,9 +5159,9 @@ TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, DeleteOne) { // File write expectations. MockFile* file_out = new MockFile(); - EXPECT_CALL(file_system, - DoOpen(file_path, FileSystem::kCreate | FileSystem::kTruncate)) - .WillOnce(Return(file_out)); + EXPECT_CALL(file_system, Open(file_path, wvutil::FileSystem::kCreate | + wvutil::FileSystem::kTruncate)) + .WillOnce(Return(ByMove(std::unique_ptr(file_out)))); EXPECT_CALL(*file_out, Write(StrEq(kHashedUsageInfoFileWithKeySet1), _)) .After(read_original) .WillOnce(Return(kHashedUsageInfoFileWithKeySet1.size())); @@ -2794,8 +5199,8 @@ TEST_F(DeviceFilesDeleteMultipleUsageInfoTest, BadFile) { EXPECT_CALL(file_system, Exists(_)).WillRepeatedly(Return(true)); EXPECT_CALL(file_system, FileSize(file_path)) .WillOnce(Return(kHashlessUsageInfoFile.size())); - EXPECT_CALL(file_system, DoOpen(file_path, FileSystem::kReadOnly)) - .WillOnce(Return(file_in)); + EXPECT_CALL(file_system, Open(file_path, wvutil::FileSystem::kReadOnly)) + .WillOnce(Return(ByMove(std::unique_ptr(file_in)))); EXPECT_CALL(*file_in, Read(NotNull(), _)) .WillOnce(DoAll(SetArrayArgument<0>(kHashlessUsageInfoFile.cbegin(), kHashlessUsageInfoFile.cend()), @@ -2844,7 +5249,8 @@ TEST_F(DeviceFilesUsageInfoTest, ListUsageIds) { EXPECT_CALL(file_system, FileSize(StrEq(path))) .Times(2) .WillRepeatedly(Return(kUsageInfoTestData[index].file_data.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(kUsageInfoTestData[index].file_data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), @@ -2903,8 +5309,8 @@ TEST_P(DeviceFilesUsageInfoListTest, UsageInfoList) { ::testing::UnorderedElementsAreArray(expected_usage_file_list)); } -INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoListTest, - ::testing::Range(0, 7)); +INSTANTIATE_TEST_SUITE_P(UsageInfo, DeviceFilesUsageInfoListTest, + ::testing::Range(0, 7)); TEST_P(DeviceFilesUsageInfoTest, Store) { MockFileSystem file_system; @@ -2931,10 +5337,15 @@ TEST_P(DeviceFilesUsageInfoTest, Store) { usage_data_fields.push_back(kUsageInfoTestData[i].usage_data.license); usage_data_fields.push_back(kUsageInfoTestData[i].usage_data.key_set_id); usage_data_fields.push_back(kUsageInfoTestData[i].usage_data.usage_entry); + usage_data_fields.push_back( + kUsageInfoTestData[i].usage_data.drm_certificate); + usage_data_fields.push_back( + kUsageInfoTestData[i].usage_data.wrapped_private_key.key()); } } - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) .With(AllArgs(StrAndLenContains(usage_data_fields))) .WillOnce(ReturnArg<1>()); @@ -2966,7 +5377,8 @@ TEST_P(DeviceFilesUsageInfoTest, Retrieve) { EXPECT_CALL(file_system, FileSize(StrEq(path))) .Times(2) .WillRepeatedly(Return(kUsageInfoTestData[index].file_data.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(kUsageInfoTestData[index].file_data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), @@ -2998,6 +5410,12 @@ TEST_P(DeviceFilesUsageInfoTest, Retrieve) { usage_data_list[i].usage_entry); EXPECT_EQ(kUsageInfoTestData[j].usage_data.usage_entry_number, usage_data_list[i].usage_entry_number); + EXPECT_EQ(kUsageInfoTestData[j].usage_data.drm_certificate, + usage_data_list[i].drm_certificate); + EXPECT_EQ(kUsageInfoTestData[j].usage_data.wrapped_private_key.type(), + usage_data_list[i].wrapped_private_key.type()); + EXPECT_EQ(kUsageInfoTestData[j].usage_data.wrapped_private_key.key(), + usage_data_list[i].wrapped_private_key.key()); found = true; } ++j; @@ -3027,7 +5445,8 @@ TEST_P(DeviceFilesUsageInfoTest, ListKeySetIds) { EXPECT_CALL(file_system, FileSize(StrEq(path))) .Times(2) .WillRepeatedly(Return(kUsageInfoTestData[index].file_data.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(kUsageInfoTestData[index].file_data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), @@ -3077,7 +5496,8 @@ TEST_P(DeviceFilesUsageInfoTest, ListProviderSessionTokenIds) { EXPECT_CALL(file_system, FileSize(StrEq(path))) .Times(2) .WillRepeatedly(Return(kUsageInfoTestData[index].file_data.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(kUsageInfoTestData[index].file_data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), @@ -3134,7 +5554,8 @@ TEST_P(DeviceFilesUsageInfoTest, RetrieveByProviderSessionToken) { EXPECT_CALL(file_system, Exists(StrEq(path))).WillOnce(Return(true)); EXPECT_CALL(file_system, FileSize(StrEq(path))) .WillOnce(Return(file_data.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(file_data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), Return(file_data.size()))); @@ -3225,13 +5646,14 @@ TEST_P(DeviceFilesUsageInfoTest, UpdateUsageInfo) { bool write_called = false; if (index < 0) { - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); } else { MockFile* next_file = new MockFile(); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)) + EXPECT_CALL(file_system, Open(StrEq(path), _)) .Times(2) - .WillOnce(Return(file)) - .WillOnce(Return(next_file)); + .WillOnce(Return(ByMove(std::unique_ptr(file)))) + .WillOnce(Return(ByMove(std::unique_ptr(next_file)))); ON_CALL(*file, Write(_, _)) .With(AllArgs(StrAndLenContains(usage_data_fields))) .WillByDefault(DoAll(InvokeWithoutArgs([&write_called]() -> void { @@ -3256,8 +5678,8 @@ TEST_P(DeviceFilesUsageInfoTest, UpdateUsageInfo) { if (index >= 0) EXPECT_TRUE(write_called); } -INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest, - ::testing::Range(-1, 9)); +INSTANTIATE_TEST_SUITE_P(UsageInfo, DeviceFilesUsageInfoTest, + ::testing::Range(-1, 9)); TEST_P(DeviceFilesHlsAttributesTest, Read) { MockFileSystem file_system; @@ -3271,7 +5693,8 @@ TEST_P(DeviceFilesHlsAttributesTest, Read) { EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); EXPECT_CALL(file_system, FileSize(StrEq(path))) .WillRepeatedly(Return(param->file_data.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(param->file_data.size()))) .WillOnce(DoAll( SetArrayArgument<0>(param->file_data.begin(), param->file_data.end()), @@ -3287,7 +5710,8 @@ TEST_P(DeviceFilesHlsAttributesTest, Read) { ASSERT_TRUE(device_files.RetrieveHlsAttributes(param->key_set_id, &method, &media_segment_iv)); EXPECT_EQ(param->method, method); - EXPECT_EQ(b2a_hex(param->media_segment_iv), b2a_hex(media_segment_iv)); + EXPECT_EQ(b2a_hex(param->media_segment_iv), + wvutil::b2a_hex(media_segment_iv)); } TEST_P(DeviceFilesHlsAttributesTest, Store) { @@ -3300,7 +5724,8 @@ TEST_P(DeviceFilesHlsAttributesTest, Store) { DeviceFiles::GetHlsAttributesFileNameExtension(); EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) .With(AllArgs( StrAndLenContains(std::vector{param->media_segment_iv}))) @@ -3329,8 +5754,8 @@ TEST_P(DeviceFilesHlsAttributesTest, Delete) { ASSERT_TRUE(device_files.DeleteHlsAttributes(param->key_set_id)); } -INSTANTIATE_TEST_CASE_P(HlsAttributes, DeviceFilesHlsAttributesTest, - ::testing::Range(0, kNumberOfHlsAttributes)); +INSTANTIATE_TEST_SUITE_P(HlsAttributes, DeviceFilesHlsAttributesTest, + ::testing::Range(0, kNumberOfHlsAttributes)); TEST_P(DeviceFilesUsageTableTest, Store) { MockFileSystem file_system; @@ -3351,7 +5776,8 @@ TEST_P(DeviceFilesUsageTableTest, Store) { std::string path = device_base_path_ + DeviceFiles::GetUsageTableFileName(); EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Write(_, _)) .With(AllArgs(StrAndLenContains(entry_data))) .WillOnce(ReturnArg<1>()); @@ -3375,7 +5801,8 @@ TEST_P(DeviceFilesUsageTableTest, Read) { EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); EXPECT_CALL(file_system, FileSize(StrEq(path))) .WillRepeatedly(Return(file_data.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); EXPECT_CALL(*file, Read(NotNull(), Eq(file_data.size()))) .WillOnce(DoAll(SetArrayArgument<0>(file_data.begin(), file_data.end()), Return(file_data.size()))); @@ -3410,8 +5837,8 @@ TEST_P(DeviceFilesUsageTableTest, Read) { EXPECT_FALSE(lru_upgrade); } -INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageTableTest, - ::testing::Range(0, 6)); +INSTANTIATE_TEST_SUITE_P(UsageInfo, DeviceFilesUsageTableTest, + ::testing::Range(0, 6)); TEST_F(DeviceFilesUsageTableTest, ReadWithoutLruData) { // Setup file. @@ -3427,7 +5854,8 @@ TEST_F(DeviceFilesUsageTableTest, ReadWithoutLruData) { EXPECT_CALL(file_system, Exists(StrEq(path))).WillRepeatedly(Return(true)); EXPECT_CALL(file_system, FileSize(StrEq(path))) .WillRepeatedly(Return(kUsageTableWithoutLruData.size())); - EXPECT_CALL(file_system, DoOpen(StrEq(path), _)).WillOnce(Return(file)); + EXPECT_CALL(file_system, Open(StrEq(path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); DeviceFiles device_files(&file_system); EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); @@ -3449,4 +5877,160 @@ TEST_F(DeviceFilesUsageTableTest, ReadWithoutLruData) { EXPECT_TRUE(lru_upgrade); } +TEST_F(DeviceFilesTest, StoreOemCertificateSuccess) { + MockFileSystem file_system; + const std::string oem_certificate_path = + device_base_path_ + wvutil::kOemCertificateFileName; + // Call to Open will return a unique_ptr, freeing this object. + MockFile* file = new MockFile(); + EXPECT_CALL(file_system, + Open(StrEq(oem_certificate_path), IsCreateFileFlagSet())) + .WillOnce(Return(ByMove(std::unique_ptr(file)))); + EXPECT_CALL(*file, Write(_, _)) + .With(AllArgs(StrAndLenContains( + std::vector{kOemCertificate, kCryptoWrappedKey.key()}))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(*file, Read(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_TRUE( + device_files.StoreOemCertificate(kOemCertificate, kCryptoWrappedKey)); +} + +TEST_F(DeviceFilesTest, StoreOemCertificateEmptyCertFail) { + MockFileSystem file_system; + const CryptoWrappedKey private_key(CryptoWrappedKey::kRsa, + CdmRandom::RandomData(kWrappedKeyLen)); + EXPECT_CALL(file_system, Open(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_FALSE( + device_files.StoreOemCertificate(/*certificate=*/"", private_key)); +} + +TEST_F(DeviceFilesTest, StoreOemCertificateUninitializedKeyTypeFail) { + MockFileSystem file_system; + const std::string& certificate = "fake_oem_cert"; + const CryptoWrappedKey private_key(CryptoWrappedKey::kUninitialized, + CdmRandom::RandomData(kWrappedKeyLen)); + EXPECT_CALL(file_system, Open(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_FALSE(device_files.StoreOemCertificate(certificate, private_key)); +} + +TEST_F(DeviceFilesTest, StoreOemCertificateEmptyKeyFail) { + MockFileSystem file_system; + const std::string& certificate = "fake_oem_cert"; + const CryptoWrappedKey private_key(CryptoWrappedKey::kRsa, ""); + EXPECT_CALL(file_system, Open(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_FALSE(device_files.StoreOemCertificate(certificate, private_key)); +} + +TEST_F(DeviceFilesTest, HasOemCertificate) { + MockFileSystem file_system; + const std::string device_certificate_path = + device_base_path_ + wvutil::kOemCertificateFileName; + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_TRUE(device_files.HasOemCertificate()); +} + +TEST_F(DeviceFilesTest, HasNoOemCertificate) { + MockFileSystem file_system; + const std::string device_certificate_path = + device_base_path_ + wvutil::kOemCertificateFileName; + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + + DeviceFiles device_files(&file_system); + ASSERT_TRUE(device_files.Init(kSecurityLevelL1)); + EXPECT_FALSE(device_files.HasOemCertificate()); +} + +TEST_F(DeviceFilesTest, RetrieveOemCertificateSuccess) { + MockFileSystem file_system; + const std::string device_certificate_path = + device_base_path_ + wvutil::kOemCertificateFileName; + // Call to Open will return a unique_ptr, freeing this object. + MockFile* read_file = new MockFile(); + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) + .WillOnce(Return(kFakeOemCertificateFile.size())); + EXPECT_CALL(file_system, Open(StrEq(device_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))); + EXPECT_CALL(*read_file, Read(NotNull(), Eq(kFakeOemCertificateFile.size()))) + .WillOnce(DoAll(SetArrayArgument<0>(kFakeOemCertificateFile.begin(), + kFakeOemCertificateFile.end()), + Return(kFakeOemCertificateFile.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + ASSERT_EQ(device_files.RetrieveOemCertificate(&certificate, &private_key), + DeviceFiles::kCertificateValid); + EXPECT_EQ(certificate, kOemCertificate); + EXPECT_EQ(private_key, kCryptoWrappedKey); +} + +TEST_F(DeviceFilesTest, RetrieveOemCertificateRandomCertFail) { + MockFileSystem file_system; + const std::string device_certificate_path = + device_base_path_ + wvutil::kOemCertificateFileName; + std::string ramdom_cert = "random_cert"; + // Call to Open will return a unique_ptr, freeing this object. + MockFile* read_file = new MockFile(); + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file_system, FileSize(StrEq(device_certificate_path))) + .WillOnce(Return(ramdom_cert.size())); + EXPECT_CALL(file_system, Open(StrEq(device_certificate_path), _)) + .WillOnce(Return(ByMove(std::unique_ptr(read_file)))); + EXPECT_CALL(*read_file, Read(NotNull(), Eq(ramdom_cert.size()))) + .WillOnce( + DoAll(SetArrayArgument<0>(ramdom_cert.begin(), ramdom_cert.end()), + Return(ramdom_cert.size()))); + EXPECT_CALL(*read_file, Write(_, _)).Times(0); + + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + EXPECT_EQ(device_files.RetrieveOemCertificate(&certificate, &private_key), + DeviceFiles::kCertificateNotFound); +} + +TEST_F(DeviceFilesTest, RetrieveOemCertificateNotFoundFail) { + MockFileSystem file_system; + const std::string device_certificate_path = + device_base_path_ + wvutil::kOemCertificateFileName; + EXPECT_CALL(file_system, Exists(StrEq(device_certificate_path))) + .Times(AtLeast(1)) + .WillRepeatedly(Return(false)); + DeviceFiles device_files(&file_system); + EXPECT_TRUE(device_files.Init(kSecurityLevelL1)); + + std::string certificate; + CryptoWrappedKey private_key; + EXPECT_EQ(device_files.RetrieveOemCertificate(&certificate, &private_key), + DeviceFiles::kCertificateNotFound); +} } // namespace wvcdm diff --git a/core/test/duration_use_case_test.cpp b/core/test/duration_use_case_test.cpp index ab5b9056..31b8fd7b 100644 --- a/core/test/duration_use_case_test.cpp +++ b/core/test/duration_use_case_test.cpp @@ -1,23 +1,24 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // These tests perform various end-to-end actions similar to what an application // would, but in parallel, attempting to create as many collisions in the CDM // code as possible. +#include +#include +#include + #include #include #include -#include -#include - #include "cdm_engine.h" #include "clock.h" #include "config_test_env.h" #include "initialization_data.h" -#include "license_request.h" +#include "license_holder.h" #include "log.h" #include "metrics_collections.h" #include "odk_structs.h" @@ -26,6 +27,7 @@ #include "test_printers.h" #include "test_sleep.h" #include "url_request.h" +#include "wv_attributes.h" #include "wv_cdm_constants.h" #include "wv_cdm_event_listener.h" #include "wv_cdm_types.h" @@ -34,8 +36,6 @@ namespace wvcdm { namespace { -constexpr int kHttpOk = 200; -const std::string kCencMimeType = "cenc"; // How many seconds to fudge the start or stop playback times. We fudge these // times because there might be a little round off when sleeping, and because // the time that the license was signed or loaded might be off a little bit due @@ -45,7 +45,7 @@ const std::string kCencMimeType = "cenc"; // check. Similarly, when we verify that playback may continue until STOP, we // wait until STOP-kFudge and then check. License sign and load times are not // fudged because neither direction is more lenient than the other direction. -constexpr uint64_t kFudge = 1; +constexpr uint64_t kFudge = 2; // How long we allow for a full license round trip -- including time for // generating the request, sending the request to the server, processing the // request at the server, sending a response back. Since this is constant, we @@ -53,6 +53,9 @@ constexpr uint64_t kFudge = 1; // expire. This must be smaller than the renewal_recovery_duration for all of // our test cases below. constexpr uint64_t kRoundTripTime = 10; +// How long we use for a playback duration for many tests. This value should be +// short, but it should also be larger than our fudge value above. +constexpr uint64_t kPlayDuration = 4 + kFudge; // A renewal policy, whose values should match the policy on UAT and in // policies.dat with the same name. @@ -70,33 +73,17 @@ struct RenewalPolicy { uint64_t renewal_recovery_duration; }; -const RenewalPolicy kShortRenewal = {"CDM_LicenseWithRenewal_renewal", 25, 15}; -const RenewalPolicy kLongRenewal = {"CDM_LicenseWithRenewal_long_renewal", 40, - 15}; +const RenewalPolicy kShortRenewal = {"CDM_LicenseWithRenewal_renewal", 15, 10}; +const RenewalPolicy kLongRenewal = {"CDM_LicenseWithRenewal_long_renewal", 30, + 10}; const RenewalPolicy kLDLRenewal = {"CDM_LimitedDurationLicense_renewal", 0, 0}; +const RenewalPolicy kInfiniteRenewal = {"CDM_InfiniteRenewal_renewal", 0, 0}; +const RenewalPolicy kLicenseDurationWithRenewal = { + "CDM_LicenseDurationWithRenewal_renewal", 10, 0}; +const RenewalPolicy kHeartbeatRenewal = {"CDM_Heartbeat_renewal", 10, 30}; // Key ID in all duration tests. const KeyId kKeyId = "Duration_Key===="; - -class SimpleEventListener : public wvcdm::WvCdmEventListener { - public: - SimpleEventListener() { renewal_needed_ = false; } - // We will want to know when a renewal is needed. - void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override { - renewal_needed_ = true; - } - void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&, - bool) override {} - void OnExpirationUpdate(const CdmSessionId&, int64_t expiry_time) override {} - bool renewal_needed() { return renewal_needed_; } - void set_renewal_needed(bool renewal_needed) { - renewal_needed_ = renewal_needed; - } - - private: - bool renewal_needed_; -}; - } // namespace // All duration tests are parameterized by can_persist = true or false. @@ -104,7 +91,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, public ::testing::WithParamInterface { public: CdmDurationTest(const std::string& content_id) - : content_id_(content_id), + : license_holder_(content_id, &cdm_engine_, config_), first_load_occurred_(false), allow_lenience_(false) { // These are reasonable initial values for most tests. This is an unlimited @@ -140,23 +127,34 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, void SetUp() override { WvCdmTestBase::SetUp(); EnsureProvisioned(); - ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_)); - can_persist_ = GetParam(); - if (can_persist_) { + license_holder_.set_can_persist(GetParam()); + ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession()); + if (license_holder_.can_persist()) { // Each content/policy in a test class below should match two policies in // UAT. One policy id matches the string exactly, and one has // _can_persist appended. - content_id_ = content_id_ + "_can_persist"; - license_type_ = kLicenseTypeOffline; - } else { - license_type_ = kLicenseTypeStreaming; + license_holder_.set_content_id(license_holder_.content_id() + + "_can_persist"); } // All times in the license are relative to the rental clock. - start_of_rental_clock_ = wvcdm::Clock().GetCurrentTime(); - FetchLicense(); + start_of_rental_clock_ = wvutil::Clock().GetCurrentTime(); + license_holder_.FetchLicense(); } - void TearDown() override { cdm_engine_.CloseSession(session_id_); } + void TearDown() override { + license_holder_.CloseSession(); + // Log the time used in this test suite. When this comment was written, + // these tests took over three hours. If we want to improve that, we need to + // track these times. + static uint64_t first_time = start_of_rental_clock_; + uint64_t delta = wvutil::Clock().GetCurrentTime() - first_time; + uint64_t sec = delta % 60; + delta = delta / 60; + uint64_t min = delta % 60; + uint64_t hour = delta / 60; + LOGD("Time used in test suite so far: %" PRIu64 ":%02" PRIu64 ":%02" PRIu64, + hour, min, sec); + } // The end of the playback window. (using system clock) // This is not useful if the playback duration is 0, which means infinite. In @@ -184,143 +182,61 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, void SleepUntil(uint64_t desired_rental_time) { const uint64_t rental_time = CurrentRentalTime(); if (desired_rental_time >= rental_time) { - TestSleep::Sleep(desired_rental_time - rental_time); + const unsigned int sleep_time = + static_cast(desired_rental_time - rental_time); + wvutil::TestSleep::Sleep(sleep_time); } else { - LOGW("Test Clock skew sleeping from rental clock time %ld to %ld", + LOGW("Test Clock skew sleeping from rental clock time %" PRIu64 + " to %" PRIu64, rental_time, desired_rental_time); } cdm_engine_.OnTimerEvent(); } uint64_t CurrentRentalTime() { - const uint64_t now = wvcdm::Clock().GetCurrentTime(); + const uint64_t now = wvutil::Clock().GetCurrentTime(); return now - start_of_rental_clock_; } - void OpenSession(CdmSessionId* session_id) { - CdmResponseType status = cdm_engine_.OpenSession( - config_.key_system(), nullptr, &event_listener_, session_id); - ASSERT_EQ(NO_ERROR, status); - ASSERT_TRUE(cdm_engine_.IsOpenSession(*session_id)); - } - - void CloseSession(const CdmSessionId& session_id) { - CdmResponseType status = cdm_engine_.CloseSession(session_id); - ASSERT_EQ(NO_ERROR, status); - ASSERT_FALSE(cdm_engine_.IsOpenSession(session_id)); - } - - void FetchLicense() { - video_widevine::WidevinePsshData pssh; - pssh.set_content_id(content_id_); - const std::string init_data_string = MakePSSH(pssh); - const InitializationData init_data(kCencMimeType, init_data_string); - init_data.DumpToLogs(); - CdmKeyRequest key_request; - ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request)); - ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request)); - } - - void GenerateKeyRequest(const InitializationData& init_data, - CdmKeyRequest* key_request) { - CdmAppParameterMap empty_app_parameters; - CdmKeySetId empty_key_set_id; - CdmResponseType result = cdm_engine_.GenerateKeyRequest( - session_id_, empty_key_set_id, init_data, license_type_, - empty_app_parameters, key_request); - ASSERT_EQ(KEY_MESSAGE, result); - ASSERT_EQ(kKeyRequestTypeInitial, key_request->type); - } - - // Append the content/policy id for the test to the URL. - std::string MakeUrl(const std::string& policy_id) { - // For these tests, we want to specify the policy, but the UAT server only - // allows us to set the content id as the video_id. So each policy is - // matched to a single license with the same name. The local license - // server, on the other hand, wants to see the policy id in the url. So we - // have to guess which format to use based on the name of the server. - const std::string path = config_.license_server() + config_.client_auth(); - std::string video_query; - if (path.find("proxy.uat") != std::string::npos) { - // This is uat or uat-nightly. Set the video_id. - video_query = "video_id=" + policy_id; - } else { - // This is probably a local license server. Set the policy. - video_query = "policy=" + policy_id; - } - // If there is already a parameter, then we don't need to add another - // question mark. - return path + ((path.find("?") == std::string::npos) ? "?" : "&") + - video_query; - } - - void GetKeyResponse(const CdmKeyRequest& key_request) { - // The content id matches the policy id used on UAT. - const std::string url = MakeUrl(content_id_); - UrlRequest url_request(url); - ASSERT_TRUE(url_request.is_connected()); - - std::string http_response; - url_request.PostRequest(key_request.message); - ASSERT_TRUE(url_request.GetResponse(&http_response)); - int status_code = url_request.GetStatusCode(http_response); - ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url; - - LicenseRequest license_request; - license_request.GetDrmMessage(http_response, key_response_); - } - // This ensures that the licenses are loaded into the sessions. void LoadLicense() { if (!first_load_occurred_) { - CdmLicenseType license_type; - ASSERT_EQ(KEY_ADDED, cdm_engine_.AddKey(session_id_, key_response_, - &license_type, &key_set_id_)); - ASSERT_EQ(license_type_, license_type); + license_holder_.LoadLicense(); first_load_occurred_ = true; - } else if (can_persist_) { - // For the persistent license, we use restore key. - ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id_)); - CdmResponseType status = cdm_engine_.RestoreKey(session_id_, key_set_id_); - ASSERT_EQ(KEY_ADDED, status); + } else if (license_holder_.can_persist()) { + ASSERT_NO_FATAL_FAILURE(license_holder_.OpenSession()); + license_holder_.ReloadLicense(); } } void UnloadLicense() { - if (can_persist_) { - CloseSession(session_id_); + if (license_holder_.can_persist()) { + license_holder_.CloseSession(); } } // 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) { + // playback from |start| to |stop|. If you refer to the diagrams in "License + // Duration and Renewal", this tests a cyan bar with a green check mark. Both + // |start| and |stop| are system times. + void LoadAndAllowPlayback(uint64_t start, uint64_t stop) { SleepUntil(start); LoadLicense(); - AllowPlayback(start, stop, cutoff); + AllowPlayback(start, stop); } - // 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) { + // Verify that we are allowed playback from |start| to |stop|. If you refer to + // the diagrams in "License Duration and Renewal", this tests a cyan bar with + // a green check mark. Both |start| and |stop| are all system times. + void AllowPlayback(uint64_t start, uint64_t stop) { ASSERT_LT(start, stop); - if (cutoff > 0) ASSERT_LE(stop, cutoff); - SleepUntil(start + kFudge); + SleepUntil(start); Decrypt(); const uint64_t mid = (start + stop) / 2; SleepUntil(mid); Decrypt(); SleepUntil(stop - kFudge); Decrypt(); - // TODO: Is there a way to verify that playback will be terminated at - // cutoff? } // Simulate loading or reloading a license, then attempt to play from |start| @@ -331,19 +247,20 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, void LoadAndTerminatePlayback(uint64_t start, uint64_t cutoff) { SleepUntil(start); LoadLicense(); - TerminatePlayback(start, cutoff, cutoff + kFudge); + TerminatePlayback(start, cutoff); } - // 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); - SleepUntil(stop + kFudge); + // Attempt to play from |start| to |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. This assumes that |cutoff| is before + // |stop|. Both |start| and |cutoff| are system times. + void TerminatePlayback(uint64_t start, uint64_t cutoff) { + // We subtract some fudge from the cutoff to account for possible round off + // error when computing the playback window. + ASSERT_LT(start, cutoff - kFudge); + AllowPlayback(start, cutoff - kFudge); + SleepUntil(cutoff + kFudge); FailDecrypt(); } @@ -359,55 +276,46 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, void AllowLenience() { allow_lenience_ = true; } void Decrypt() { - constexpr size_t buffer_size = 500; - const std::vector input(buffer_size, 0); - std::vector output(buffer_size, 0); - const std::vector iv(KEY_IV_SIZE, 0); - Decrypt(session_id_, kKeyId, input, iv, &output, NO_ERROR); + const uint64_t now = CurrentRentalTime(); + EXPECT_EQ(NO_ERROR, license_holder_.Decrypt(kKeyId)) + << "Failed to decrypt when rental clock = " << now + << ", and playback clock = " + << ((now < start_of_playback_) ? 0 : (now - start_of_playback_)); } void FailDecrypt() { - CdmResponseType expected_status = NEED_KEY; - // On low end devices, for some tests, we are lenient on playback - // termination. This means that low end devices are not required to fail - // playback. Tests that allow lenience are enumerated in the - // documentation. See the section in "License Duration and Renewal" on - // lenient tests. + bool allow_success = false; + // We will be lenient for some tests if it is a low end device without a + // usage table or if it is a pre-v16 devices. We are only leinient + // for an offline license. Being lenient means we allow playback, even + // though the policy requests playback cutoff. Tests that allow lenience + // are enumerated in the documentation. See the section in "License + // Duration and Renewal" on lenient tests. These tests call the function + // AllowLenience to signal this case. const bool low_end_device = (wvoec::global_features.api_version < wvoec::kCoreMessagesAPI || !wvoec::global_features.usage_table); - if (allow_lenience_ && low_end_device && can_persist_) { - expected_status = NO_ERROR; - allow_lenience_ = false; // Only allow lenience once per test. + if (allow_lenience_ && low_end_device && license_holder_.can_persist()) { + allow_success = true; } - constexpr size_t buffer_size = 500; - const std::vector input(buffer_size, 0); - std::vector output(buffer_size, 0); - const std::vector iv(KEY_IV_SIZE, 0); - Decrypt(session_id_, kKeyId, input, iv, &output, expected_status); + allow_lenience_ = false; // Only allow lenience once per test. + const uint64_t now = CurrentRentalTime(); + CdmResponseType status = license_holder_.Decrypt(kKeyId); + // We always allow failure. that's what we usually expect. + if (status == NEED_KEY) return; + // No other error code is allowed: either NO_ERROR or NEED_KEY. + ASSERT_EQ(NO_ERROR, status) + << "Failed to decrypt with unexpected error code when rental clock = " + << now << ", and playback clock = " + << (now < start_of_playback_ ? 0 : now - start_of_playback_); + // However, for those cases decided above, we also allow success. + EXPECT_TRUE(allow_success) + << "Device was unexpectedly lenient when rental clock = " << now + << ", and playback clock = " + << (now < start_of_playback_ ? 0 : now - start_of_playback_); } - void Decrypt(const CdmSessionId& session_id, const KeyId& key_id, - const std::vector& input, - const std::vector& iv, std::vector* output, - CdmResponseType expected_status) { - CdmDecryptionParametersV16 params(key_id); - params.is_secure = false; - CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(), - iv); - CdmDecryptionSubsample subsample(0, input.size()); - sample.subsamples.push_back(subsample); - params.samples.push_back(sample); - - CdmResponseType status = cdm_engine_.DecryptV16(session_id, params); - ASSERT_EQ(expected_status, status); - } - - CdmSessionId session_id_; - std::string content_id_; - CdmLicenseType license_type_; - CdmKeySetId key_set_id_; - std::string key_response_; + LicenseHolder license_holder_; // Time license requests generated. All test times are relative to this value. uint64_t start_of_rental_clock_; // The start of playback. This is set to the planned start at the beginning of @@ -417,9 +325,7 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, // not have to reload the streaming license, and we should use RestoreKey // instead of AddKey for the offline license. bool first_load_occurred_; - bool can_persist_; ODK_TimerLimits timer_limits_; - SimpleEventListener event_listener_; // If this is set, then the next time we expect a playback to be terminated, // we will allow lenient failure. bool allow_lenience_; @@ -427,12 +333,12 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, /*****************************************************************************/ // 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 +// in the document "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 200 rental duration. +// instead of a seven day rental duration, we will use a 100 rental duration. /*****************************************************************************/ /*****************************************************************************/ @@ -441,10 +347,8 @@ class CdmDurationTest : public WvCdmTestBaseWithEngine, class CdmUseCase_Streaming : public CdmDurationTest { public: CdmUseCase_Streaming() : CdmDurationTest("CDM_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 = 35; + timer_limits_.rental_duration_seconds = 40; timer_limits_.total_playback_duration_seconds = 0; } }; @@ -452,8 +356,7 @@ class CdmUseCase_Streaming : public CdmDurationTest { // Playback within rental duration. TEST_P(CdmUseCase_Streaming, Case1) { // Allow playback within the rental window. - LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow(), - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow()); } // Playback exceeds rental duration. @@ -465,25 +368,26 @@ TEST_P(CdmUseCase_Streaming, Case2) { // Playback with stops/restarts within rental duration, last one exceeds rental // duration. TEST_P(CdmUseCase_Streaming, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 5, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 10, start_of_playback_ + 15, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndTerminatePlayback(start_of_playback_ + 20, EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfRentalWindow()); } // Playback within rental duration, restart exceeds rental duration. TEST_P(CdmUseCase_Streaming, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 5, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - ForbidPlayback(EndOfRentalWindow() + 5); + ForbidPlayback(EndOfRentalWindow() + kFudge); } // Initial playback exceeds rental duration. -TEST_P(CdmUseCase_Streaming, Case5) { ForbidPlayback(EndOfRentalWindow() + 5); } +TEST_P(CdmUseCase_Streaming, Case5) { + ForbidPlayback(EndOfRentalWindow() + kFudge); +} /*****************************************************************************/ // Streaming Quick Start. The user must start watching within 30 seconds, and @@ -500,7 +404,7 @@ class CdmUseCase_StreamingQuickStart : public CdmDurationTest { timer_limits_.soft_enforce_playback_duration = false; // A valid start of playback time. - start_of_playback_ = timer_limits_.rental_duration_seconds - 10; + start_of_playback_ = timer_limits_.rental_duration_seconds - kPlayDuration; } }; @@ -508,9 +412,9 @@ class CdmUseCase_StreamingQuickStart : public CdmDurationTest { TEST_P(CdmUseCase_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_ + 20, - EndOfPlaybackWindow()); + // Allow playback within the playback window, even if it is after the rental + // window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow() + kFudge); } // Playback exceeds playback duration. @@ -520,91 +424,107 @@ TEST_P(CdmUseCase_StreamingQuickStart, Case2) { } // Playback with stops/restarts within playback duration, last one exceeds -// playback duration. +// playback duration. This also tests that playback may be restarted after the +// rental window has closed, as long as the playback window is still open. TEST_P(CdmUseCase_StreamingQuickStart, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_, + start_of_playback_ + 2 * kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 15, start_of_playback_ + 20, - EndOfPlaybackWindow()); + // Check that we can restart the playback after the rental window has stopped. + const uint64_t after_rental = EndOfRentalWindow() + kPlayDuration; + LoadAndAllowPlayback(after_rental, after_rental + kPlayDuration); UnloadLicense(); - LoadAndTerminatePlayback(start_of_playback_ + 25, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfPlaybackWindow() - kPlayDuration, + EndOfPlaybackWindow()); } // Initial playback exceeds rental duration. TEST_P(CdmUseCase_StreamingQuickStart, Case4) { - ForbidPlayback(EndOfRentalWindow() + 10); + ForbidPlayback(EndOfRentalWindow() + kFudge); } +/*****************************************************************************/ +// Seven Day / Two Day. The user must start watching within 7 days. Once +// started, the user has two days to finish the video. This class sets up the +// times to match the diagrams in the document "License Duration and +// Renewal". There are four sets of classes below this separate out +// hard/soft for enforcement of rental and playback duration. +class CdmUseCase_SevenTwo : public CdmDurationTest { + public: + CdmUseCase_SevenTwo(const std::string& content_id) + : CdmDurationTest(content_id) { + // Rental Duration should be 7 days. In the diagram. + timer_limits_.rental_duration_seconds = seven_days_; + timer_limits_.total_playback_duration_seconds = two_days_; + } + + // Playback window is entirely contained in rental window. In diagram this is + // on day 3. + void StartDay3() { start_of_playback_ = day3_; } + + // Playback overlays end of rental window. In diagram, this is on day 6. + void StartDay6() { start_of_playback_ = day6_; } + static constexpr uint64_t seven_days_ = 100; + static constexpr uint64_t two_days_ = 50; + static constexpr uint64_t day3_ = 20; + static constexpr uint64_t day6_ = 70; +}; + /*****************************************************************************/ // 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 CdmUseCase_SevenHardTwoHard_Start3 : public CdmDurationTest { +class CdmUseCase_SevenHardTwoHard : public CdmUseCase_SevenTwo { public: - CdmUseCase_SevenHardTwoHard_Start3() - : CdmDurationTest("CDM_SevenHardTwoHard") { - // Rental duration = 200, hard - // Playback duration = 100, hard - timer_limits_.rental_duration_seconds = 200; // 7 days. + CdmUseCase_SevenHardTwoHard() : CdmUseCase_SevenTwo("CDM_SevenHardTwoHard") { timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.total_playback_duration_seconds = 100; // 2 days. timer_limits_.soft_enforce_playback_duration = false; - - start_of_playback_ = 50; // Start is less than rental - playback. } }; // Playback within playback and rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case1) { +TEST_P(CdmUseCase_SevenHardTwoHard, Case1) { + StartDay3(); // 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_ + 10, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); } // Playback exceeds playback duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case2) { +TEST_P(CdmUseCase_SevenHardTwoHard, Case2) { + StartDay3(); // 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_P(CdmUseCase_SevenHardTwoHard_Start3, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenHardTwoHard, Case3) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); AllowLenience(); - LoadAndTerminatePlayback(start_of_playback_ + 50, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow()); } // Restart exceeds playback duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start3, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenHardTwoHard, Case4) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); AllowLenience(); - ForbidPlayback(EndOfPlaybackWindow() + 10); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); } -// The next four cases start on day six. -class CdmUseCase_SevenHardTwoHard_Start6 - : public CdmUseCase_SevenHardTwoHard_Start3 { - public: - CdmUseCase_SevenHardTwoHard_Start6() { - // Start is more than rental - playback = 200 - 100 - start_of_playback_ = 150; - } -}; - // Playback exceeds rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case5) { +TEST_P(CdmUseCase_SevenHardTwoHard, Case5) { + StartDay6(); // As seen in the drawing, the playback window is exceeds the rental window. EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); // Allow playback within the rental window. @@ -613,35 +533,38 @@ TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case5) { // Playback with stops/restarts within playback duration, last one is terminated // at the end of the rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case6) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoHard, Case6) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); // Allow playback that starts within rental window, but terminate at end of // rental window. - LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfRentalWindow()); } // Playback within playback duration, restart exceeds rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case7) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoHard, Case7) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); // Restart does not work after end of playback window. - ForbidPlayback(EndOfRentalWindow() + 10); + ForbidPlayback(EndOfRentalWindow() + kPlayDuration); } // Playback exceeds rental and playback duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case8) { +TEST_P(CdmUseCase_SevenHardTwoHard, Case8) { + StartDay6(); LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); } // First playback cannot exceed rental duration. -TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case9) { - ForbidPlayback(EndOfRentalWindow() + 10); +TEST_P(CdmUseCase_SevenHardTwoHard, Case9) { + StartDay6(); + ForbidPlayback(EndOfRentalWindow() + kFudge); } /*****************************************************************************/ @@ -649,73 +572,60 @@ TEST_P(CdmUseCase_SevenHardTwoHard_Start6, Case9) { // 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 CdmUseCase_SevenHardTwoSoft_Start3 : public CdmDurationTest { +class CdmUseCase_SevenHardTwoSoft : public CdmUseCase_SevenTwo { public: - CdmUseCase_SevenHardTwoSoft_Start3() - : CdmDurationTest("CDM_SevenHardTwoSoft") { - // Rental duration = 200, hard - // Playback duration = 100, hard - timer_limits_.rental_duration_seconds = 200; + CdmUseCase_SevenHardTwoSoft() : CdmUseCase_SevenTwo("CDM_SevenHardTwoSoft") { timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.total_playback_duration_seconds = 100; timer_limits_.soft_enforce_playback_duration = true; - - start_of_playback_ = 50; // Start is less than rental - playback. } }; // Playback within playback and rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case1) { +TEST_P(CdmUseCase_SevenHardTwoSoft, Case1) { + StartDay3(); // 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_ + 10, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); } // Playback exceeds playback duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case2) { +TEST_P(CdmUseCase_SevenHardTwoSoft, Case2) { + StartDay3(); // Allow playback within the playback window, and a little after. // Timer expires at end of rental window. - LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 15, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_, + EndOfPlaybackWindow() + kPlayDuration); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case3) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 50, EndOfPlaybackWindow() + 15, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow() + kPlayDuration); } // Restart exceeds playback duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start3, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case4) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, + start_of_playback_ + 4 * kPlayDuration); UnloadLicense(); - if (!can_persist_) return; // streaming license cannot restart. + // streaming license cannot restart. + if (!license_holder_.can_persist()) return; AllowLenience(); - ForbidPlayback(EndOfPlaybackWindow() + 10); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); } -// The next four cases start on day six. -class CdmUseCase_SevenHardTwoSoft_Start6 - : public CdmUseCase_SevenHardTwoSoft_Start3 { - public: - CdmUseCase_SevenHardTwoSoft_Start6() { - // Start is more than rental - playback = 200 - 100 - start_of_playback_ = 150; - } -}; - // Playback exceeds rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case5) { +TEST_P(CdmUseCase_SevenHardTwoSoft, Case5) { + StartDay6(); // As seen in the drawing, the playback window is exceeds the rental window. EXPECT_GT(EndOfPlaybackWindow(), EndOfRentalWindow()); // Allow playback within the rental window. @@ -724,33 +634,36 @@ TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case5) { // Playback with stops/restarts within playback duration, last one exceeds // rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case6) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case6) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfRentalWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfRentalWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfRentalWindow()); } // Playback within playback duration, restart exceeds rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case7) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, - EndOfRentalWindow()); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case7) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); // Restart does not work after end of playback window. - ForbidPlayback(EndOfRentalWindow() + 10); + ForbidPlayback(EndOfRentalWindow() + kPlayDuration); } // Playback exceeds rental and playback duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case8) { +TEST_P(CdmUseCase_SevenHardTwoSoft, Case8) { + StartDay6(); LoadAndTerminatePlayback(start_of_playback_, EndOfRentalWindow()); } // First playback cannot exceed rental duration. -TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case9) { - ForbidPlayback(EndOfRentalWindow() + 10); +TEST_P(CdmUseCase_SevenHardTwoSoft, Case9) { + StartDay6(); + ForbidPlayback(EndOfRentalWindow() + kFudge); } /*****************************************************************************/ @@ -758,70 +671,57 @@ TEST_P(CdmUseCase_SevenHardTwoSoft_Start6, Case9) { // 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 CdmUseCase_SevenSoftTwoHard_Start3 : public CdmDurationTest { +class CdmUseCase_SevenSoftTwoHard : public CdmUseCase_SevenTwo { public: - CdmUseCase_SevenSoftTwoHard_Start3() - : CdmDurationTest("CDM_SevenSoftTwoHard") { - // Rental duration = 200, hard - // Playback duration = 100, hard - timer_limits_.rental_duration_seconds = 200; + CdmUseCase_SevenSoftTwoHard() : CdmUseCase_SevenTwo("CDM_SevenSoftTwoHard") { timer_limits_.soft_enforce_rental_duration = true; - timer_limits_.total_playback_duration_seconds = 100; timer_limits_.soft_enforce_playback_duration = false; - - start_of_playback_ = 50; // Start is less than rental - playback. } }; // Playback within playback and rental duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case1) { +TEST_P(CdmUseCase_SevenSoftTwoHard, Case1) { + StartDay3(); // 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_ + 10, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); } // Playback exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case2) { +TEST_P(CdmUseCase_SevenSoftTwoHard, Case2) { + StartDay3(); // 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_P(CdmUseCase_SevenSoftTwoHard_Start3, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case3) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); AllowLenience(); - LoadAndTerminatePlayback(start_of_playback_ + 50, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow()); } // Restart exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start3, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case4) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, + start_of_playback_ + 4 * kPlayDuration); UnloadLicense(); AllowLenience(); - ForbidPlayback(EndOfPlaybackWindow() + 10); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); } -// The next four cases start on day six. -class CdmUseCase_SevenSoftTwoHard_Start6 - : public CdmUseCase_SevenSoftTwoHard_Start3 { - public: - CdmUseCase_SevenSoftTwoHard_Start6() { - // Start is more than rental - playback = 200 - 100 - start_of_playback_ = 150; - } -}; - // Playback exceeds rental duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case5) { +TEST_P(CdmUseCase_SevenSoftTwoHard, Case5) { + StartDay6(); // 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 @@ -831,34 +731,38 @@ TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case5) { // Playback with stops/restarts within playback duration, last one exceeds // rental duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case6) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case6) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, - EndOfPlaybackWindow()); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); AllowLenience(); - LoadAndTerminatePlayback(start_of_playback_ + 40, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow()); } // Restart exceeds rental duration, playback exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case7) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, - EndOfPlaybackWindow()); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case7) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); AllowLenience(); - LoadAndTerminatePlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow()); + LoadAndTerminatePlayback(EndOfRentalWindow() + kPlayDuration, + EndOfPlaybackWindow()); } // Playback exceeds rental and playback duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case8) { +TEST_P(CdmUseCase_SevenSoftTwoHard, Case8) { + StartDay6(); LoadAndTerminatePlayback(start_of_playback_, EndOfPlaybackWindow()); } // First playback cannot exceed rental duration. -TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case9) { - ForbidPlayback(EndOfRentalWindow() + 10); +TEST_P(CdmUseCase_SevenSoftTwoHard, Case9) { + StartDay6(); + ForbidPlayback(EndOfRentalWindow() + kFudge); } /*****************************************************************************/ @@ -866,130 +770,122 @@ TEST_P(CdmUseCase_SevenSoftTwoHard_Start6, Case9) { // 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 CdmUseCase_SevenSoftTwoSoft_Start3 : public CdmDurationTest { +class CdmUseCase_SevenSoftTwoSoft : public CdmUseCase_SevenTwo { public: - CdmUseCase_SevenSoftTwoSoft_Start3() - : CdmDurationTest("CDM_SevenSoftTwoSoft") { - // Rental duration = 200, hard - // Playback duration = 100, hard - timer_limits_.rental_duration_seconds = 200; + CdmUseCase_SevenSoftTwoSoft() : CdmUseCase_SevenTwo("CDM_SevenSoftTwoSoft") { timer_limits_.soft_enforce_rental_duration = true; - timer_limits_.total_playback_duration_seconds = 100; timer_limits_.soft_enforce_playback_duration = true; - - start_of_playback_ = 50; // Start is less than rental - playback. } }; // Playback within playback and rental duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case1) { +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case1) { + StartDay3(); // 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_ + 10, 0); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); } // Playback exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case2) { +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case2) { + StartDay3(); // Allow playback within the playback window, and beyond. No timer limit. - LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 10, 0); + LoadAndAllowPlayback(start_of_playback_, + EndOfPlaybackWindow() + kPlayDuration); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case3) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case3) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, 0); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 50, EndOfPlaybackWindow() + 15, 0); + LoadAndAllowPlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow() + kPlayDuration); } // Restart exceeds playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start3, Case4) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 50, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case4) { + StartDay3(); + LoadAndAllowPlayback(start_of_playback_, + start_of_playback_ + 4 * kPlayDuration); UnloadLicense(); - if (!can_persist_) return; // streaming license cannot restart. + // streaming license cannot restart. + if (!license_holder_.can_persist()) return; AllowLenience(); - ForbidPlayback(EndOfPlaybackWindow() + 10); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); } -// The next four cases start on day six. -class CdmUseCase_SevenSoftTwoSoft_Start6 - : public CdmUseCase_SevenSoftTwoSoft_Start3 { - public: - CdmUseCase_SevenSoftTwoSoft_Start6() { - // Start is more than rental - playback = 200 - 100 - start_of_playback_ = 150; - } -}; - // Playback exceeds rental duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case5) { +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case5) { + StartDay6(); // As seen in the drawing, the playback window exceeds the rental window. // We should be able to play a little bit after the rental window. - // We'll use 10 as "a little bit". - const uint64_t end_play = EndOfRentalWindow() + 10; + const uint64_t end_play = EndOfRentalWindow() + kPlayDuration; // For this case, we are playing after the end of the playback window, too. EXPECT_GT(EndOfPlaybackWindow(), end_play); // Allow playback past the rental window, but within the playback window. - LoadAndAllowPlayback(start_of_playback_, end_play, 0); + LoadAndAllowPlayback(start_of_playback_, end_play); } // Playback with stops/restarts within playback duration, last one exceeds // rental and playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case6) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 10, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case6) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 20, start_of_playback_ + 30, 0); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); UnloadLicense(); - LoadAndAllowPlayback(start_of_playback_ + 40, EndOfPlaybackWindow() + 10, 0); + LoadAndAllowPlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfPlaybackWindow() + kPlayDuration); } // Playback with stops/restarts within playback duration, last one exceeds // playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case7) { - LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + 25, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case7) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); UnloadLicense(); // Allow playback to start after end of rental window, and continue after // playback window. - LoadAndAllowPlayback(EndOfRentalWindow() + 10, EndOfPlaybackWindow() + 10, 0); + LoadAndAllowPlayback(EndOfRentalWindow() + kPlayDuration, + EndOfPlaybackWindow() + kPlayDuration); UnloadLicense(); // But forbid restart after playback window. - if (!can_persist_) return; // streaming license cannot restart. - ForbidPlayback(EndOfPlaybackWindow() + 20); + if (!license_holder_.can_persist()) return; + ForbidPlayback(EndOfPlaybackWindow() + 2 * kPlayDuration); } // Playback exceeds rental and playback duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case8) { - LoadAndAllowPlayback(start_of_playback_, EndOfPlaybackWindow() + 20, 0); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case8) { + StartDay6(); + LoadAndAllowPlayback(start_of_playback_, + EndOfPlaybackWindow() + 2 * kPlayDuration); } // First playback cannot exceed rental duration. -TEST_P(CdmUseCase_SevenSoftTwoSoft_Start6, Case9) { - ForbidPlayback(EndOfRentalWindow() + 10); +TEST_P(CdmUseCase_SevenSoftTwoSoft, Case9) { + StartDay6(); + ForbidPlayback(EndOfRentalWindow() + kFudge); } -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_Streaming, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_StreamingQuickStart, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoHard_Start3, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoHard_Start6, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoSoft_Start3, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenHardTwoSoft_Start6, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoHard_Start3, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoHard_Start6, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoSoft_Start3, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_SevenSoftTwoSoft_Start6, - ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_Streaming, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_StreamingQuickStart, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_SevenHardTwoHard, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_SevenHardTwoSoft, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_SevenSoftTwoHard, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_SevenSoftTwoSoft, + ::testing::Values(false, true)); class RenewalTest : public CdmDurationTest { protected: @@ -1000,7 +896,7 @@ class RenewalTest : public CdmDurationTest { // Sleep until a renewal request event is generated. If one does not occur // before cutoff, an error happens. void SleepUntilRenewalNeeded(uint64_t cutoff) { - while (!event_listener_.renewal_needed()) { + while (!license_holder_.event_listener().renewal_needed()) { uint64_t now = CurrentRentalTime(); ASSERT_LT(now, cutoff); SleepUntil(now + 1); @@ -1014,33 +910,18 @@ class RenewalTest : public CdmDurationTest { } void RequestRenewal(const RenewalPolicy& renewal_policy) { - event_listener_.set_renewal_needed(false); - CdmKeyRequest request; - const CdmResponseType result = - cdm_engine_.GenerateRenewalRequest(session_id_, &request); - ASSERT_EQ(KEY_MESSAGE, result); - const std::string url = MakeUrl(renewal_policy.policy_id); - renewal_in_flight_.reset(new UrlRequest(url)); - ASSERT_TRUE(renewal_in_flight_->is_connected()); - renewal_in_flight_->PostRequest(request.message); + license_holder_.GenerateAndPostRenewalRequest(renewal_policy.policy_id); } void LoadRenewal(uint64_t time_of_load, const RenewalPolicy& renewal_policy) { - ASSERT_NE(renewal_in_flight_, nullptr); - std::string http_response; - // TODO(fredgc): Tune this. Most of the network latency will probably show - // up in the next few commands. I think the tests have enough slop to - // account for reasonable latency with the current value of - // kRoundTripTime. But We'll know I made a mistake if we see errors about - // "Test Clock skew..." in the SleepUntil call below. - ASSERT_TRUE(renewal_in_flight_->GetResponse(&http_response)); - int status_code = renewal_in_flight_->GetStatusCode(http_response); - ASSERT_EQ(kHttpOk, status_code); - + // Most of the network latency will probably show up in the next few + // commands. I think the tests have enough slop to account for reasonable + // latency with the current value of kRoundTripTime. But we'll know I made a + // mistake if we see errors about "Test Clock skew..." in the SleepUntil + // call below. + license_holder_.FetchRenewal(); SleepUntil(time_of_load); - LicenseRequest license_request; - license_request.GetDrmMessage(http_response, renewal_message_); - EXPECT_EQ(KEY_ADDED, cdm_engine_.RenewKey(session_id_, renewal_message_)); + license_holder_.LoadRenewal(); ComputeCutoff(time_of_load, renewal_policy); } @@ -1064,9 +945,9 @@ class RenewalTest : public CdmDurationTest { const RenewalPolicy& renewal_policy) { EXPECT_LE(start, load_time); EXPECT_LT(load_time, stop); - if (start < load_time) AllowPlayback(start, load_time, current_cutoff_); + if (start < load_time) AllowPlayback(start, load_time); LoadRenewal(load_time, renewal_policy); - AllowPlayback(load_time, stop, current_cutoff_); + AllowPlayback(load_time, stop); } // Verify that a renewal can be processed and attempt to play from |start| to @@ -1112,7 +993,7 @@ class RenewalTest : public CdmDurationTest { // license is reloaded. LoadLicense(); ComputeCutoff(start, renewal_policy); - AllowPlayback(start, stop, current_cutoff_); + AllowPlayback(start, stop); } std::unique_ptr renewal_in_flight_; @@ -1127,9 +1008,9 @@ class CdmUseCase_LicenseWithRenewal : public RenewalTest { public: CdmUseCase_LicenseWithRenewal() : RenewalTest("CDM_LicenseWithRenewal") { timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.rental_duration_seconds = 360u; - initial_policy_.renewal_delay = 25u; - initial_policy_.renewal_recovery_duration = 15u; + timer_limits_.rental_duration_seconds = 180u; + initial_policy_.renewal_delay = 15u; + initial_policy_.renewal_recovery_duration = 10u; timer_limits_.initial_renewal_duration_seconds = initial_policy_.renewal_delay + initial_policy_.renewal_recovery_duration; @@ -1144,7 +1025,7 @@ class CdmUseCase_LicenseWithRenewal : public RenewalTest { LoadLicense(); // Play until just before we expect a renewal to be generated. current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration; - AllowPlayback(start_of_playback_, next_renewal, current_cutoff_); + AllowPlayback(start_of_playback_, next_renewal); } protected: @@ -1206,7 +1087,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case3) { RenewAndTerminate(start, load_time, cutoff, kShortRenewal); } // We are late requesting the renewal. - TestSleep::Sleep(kRoundTripTime); + wvutil::TestSleep::Sleep(kRoundTripTime); // But after we request the renewal, we should be able to start playing again. { RequestRenewal(kShortRenewal); @@ -1231,7 +1112,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case4) { RenewAndContinue(start, load_time, stop, kShortRenewal); } UnloadLicense(); - if (can_persist_) { + if (license_holder_.can_persist()) { // Reload license after timer expired. const uint64_t reload_time = current_cutoff_ + 20; ReloadAndAllowPlayback( @@ -1252,7 +1133,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case4) { TEST_P(CdmUseCase_LicenseWithRenewal, Case5) { uint64_t stop = 0; const uint64_t cutoff = EndOfRentalWindow(); - while (stop <= cutoff) { + while (stop < cutoff) { SleepUntilRenewalNeeded(); RequestRenewal(kShortRenewal); const uint64_t start = CurrentRentalTime(); @@ -1261,6 +1142,10 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case5) { if (stop >= cutoff) { RenewAndTerminate(start, load_time, cutoff, kShortRenewal); } else { + if (stop + kRoundTripTime >= cutoff) { + // If the next load time intersects the cutoff, back off a little. + stop -= kRoundTripTime; + } RenewAndContinue(start, load_time, stop, kShortRenewal); } } @@ -1280,7 +1165,7 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case6) { // Change renewal duration, and playing until the end of the rental window. uint64_t stop = 0; const uint64_t cutoff = EndOfRentalWindow(); - while (stop <= cutoff) { + while (stop < cutoff) { SleepUntilRenewalNeeded(); RequestRenewal(kLongRenewal); const uint64_t start = CurrentRentalTime(); @@ -1289,6 +1174,10 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case6) { if (stop >= cutoff) { RenewAndTerminate(start, load_time, cutoff, kLongRenewal); } else { + if (stop + kRoundTripTime >= cutoff) { + // If the next load time intersects the cutoff, back off a little. + stop -= kRoundTripTime; + } RenewAndContinue(start, load_time, stop, kLongRenewal); } } @@ -1305,15 +1194,6 @@ TEST_P(CdmUseCase_LicenseWithRenewal, Case7) { const uint64_t stop = load_time + kShortRenewal.renewal_delay; RenewAndContinue(start, load_time, stop, kShortRenewal); } - // Change renewal duration, and playing until the end of the rental window. - for (int i = 0; i < 2; i++) { - SleepUntilRenewalNeeded(); - RequestRenewal(kLongRenewal); - const uint64_t start = CurrentRentalTime(); - const uint64_t load_time = start + kRoundTripTime; - const uint64_t stop = load_time + kLongRenewal.renewal_delay; - RenewAndContinue(start, load_time, stop, kLongRenewal); - } // We attempt to continue playing beyond the renewal cutoff. SleepUntilRenewalNeeded(); RequestRenewal(kLongRenewal); @@ -1333,9 +1213,9 @@ class CdmUseCase_LicenseWithRenewalPlayback : public RenewalTest { CdmUseCase_LicenseWithRenewalPlayback() : RenewalTest("CDM_LicenseWithRenewal_playback") { timer_limits_.soft_enforce_rental_duration = false; - timer_limits_.total_playback_duration_seconds = 360u; - initial_policy_.renewal_delay = 25u; - initial_policy_.renewal_recovery_duration = 15u; + timer_limits_.total_playback_duration_seconds = 180u; + initial_policy_.renewal_delay = 15u; + initial_policy_.renewal_recovery_duration = 10u; timer_limits_.initial_renewal_duration_seconds = initial_policy_.renewal_delay + initial_policy_.renewal_recovery_duration; @@ -1349,7 +1229,7 @@ class CdmUseCase_LicenseWithRenewalPlayback : public RenewalTest { LoadLicense(); // Play until just before we expect a renewal to be generated. current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration; - AllowPlayback(start_of_playback_, next_renewal, current_cutoff_); + AllowPlayback(start_of_playback_, next_renewal); } protected: @@ -1411,7 +1291,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case3) { RenewAndTerminate(start, load_time, cutoff, kShortRenewal); } // We are late requesting the renewal. - TestSleep::Sleep(kRoundTripTime); + wvutil::TestSleep::Sleep(kRoundTripTime); // But after we request the renewal, we should be able to start playing again. { RequestRenewal(kShortRenewal); @@ -1436,7 +1316,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case4) { RenewAndContinue(start, load_time, stop, kShortRenewal); } UnloadLicense(); - if (can_persist_) { + if (license_holder_.can_persist()) { // Reload license after timer expired. const uint64_t reload_time = current_cutoff_ + 20; ReloadAndAllowPlayback( @@ -1457,7 +1337,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case4) { TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case5) { uint64_t stop = 0; const uint64_t cutoff = EndOfPlaybackWindow(); - while (stop <= cutoff) { + while (stop < cutoff) { SleepUntilRenewalNeeded(); RequestRenewal(kShortRenewal); const uint64_t start = CurrentRentalTime(); @@ -1466,6 +1346,10 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case5) { if (stop >= cutoff) { RenewAndTerminate(start, load_time, cutoff, kShortRenewal); } else { + if (stop + kRoundTripTime >= cutoff) { + // If the next load time intersects the cutoff, back off a little. + stop -= kRoundTripTime; + } RenewAndContinue(start, load_time, stop, kShortRenewal); } } @@ -1485,7 +1369,7 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case6) { // Change renewal duration, and playing until the end of the rental window. uint64_t stop = 0; const uint64_t cutoff = EndOfPlaybackWindow(); - while (stop <= cutoff) { + while (stop < cutoff) { SleepUntilRenewalNeeded(); RequestRenewal(kLongRenewal); const uint64_t start = CurrentRentalTime(); @@ -1494,6 +1378,10 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case6) { if (stop >= cutoff) { RenewAndTerminate(start, load_time, cutoff, kLongRenewal); } else { + if (stop + kRoundTripTime >= cutoff) { + // If the next load time intersects the cutoff, back off a little. + stop -= kRoundTripTime; + } RenewAndContinue(start, load_time, stop, kLongRenewal); } } @@ -1510,15 +1398,6 @@ TEST_P(CdmUseCase_LicenseWithRenewalPlayback, Case7) { const uint64_t stop = load_time + kShortRenewal.renewal_delay; RenewAndContinue(start, load_time, stop, kShortRenewal); } - // Change renewal duration, and playing until the end of the rental window. - for (int i = 0; i < 2; i++) { - SleepUntilRenewalNeeded(); - RequestRenewal(kLongRenewal); - const uint64_t start = CurrentRentalTime(); - const uint64_t load_time = start + kRoundTripTime; - const uint64_t stop = load_time + kLongRenewal.renewal_delay; - RenewAndContinue(start, load_time, stop, kLongRenewal); - } // We attempt to continue playing beyond the renewal cutoff. SleepUntilRenewalNeeded(); RequestRenewal(kLongRenewal); @@ -1538,16 +1417,16 @@ class CdmUseCase_LimitedDurationLicense : public RenewalTest { CdmUseCase_LimitedDurationLicense() : RenewalTest("CDM_LimitedDurationLicense") { renewal_delay_ = 5u; - renewal_recovery_ = 55; + renewal_recovery_ = 15; // Pick a start of playback that is within the rental window, but so that // the initial renewal window is after rental window. - start_of_playback_ = 100; + start_of_playback_ = 10; timer_limits_.soft_enforce_rental_duration = true; - timer_limits_.rental_duration_seconds = 120; // 15 minutes. + timer_limits_.rental_duration_seconds = 15; // 15 minutes. timer_limits_.soft_enforce_playback_duration = false; - timer_limits_.total_playback_duration_seconds = 300; // --> 12 hours. + timer_limits_.total_playback_duration_seconds = 60; // --> 12 hours. timer_limits_.initial_renewal_duration_seconds = renewal_delay_ + renewal_recovery_; @@ -1563,14 +1442,16 @@ class CdmUseCase_LimitedDurationLicense : public RenewalTest { // Playback started within rental window and continues. TEST_P(CdmUseCase_LimitedDurationLicense, Case1) { // Allow playback within the initial renewal window. - LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, - current_cutoff_); + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime); SleepUntilRenewalNeeded(); RequestRenewal(kLDLRenewal); - // Continue playback for one hour (use 100 to condense test). - const uint64_t end_of_hour = renewal_load_time_ + 100; + // Continue playback until after renewal cutoff. + const uint64_t end_of_play = renewal_load_time_ + kPlayDuration; + const uint64_t cutoff = + start_of_playback_ + renewal_delay_ + renewal_recovery_; + ASSERT_GT(end_of_play, cutoff); const uint64_t start = CurrentRentalTime(); - RenewAndContinue(start, renewal_load_time_, end_of_hour, kLDLRenewal); + RenewAndContinue(start, renewal_load_time_, end_of_play, kLDLRenewal); } // Playback started after rental duration should fail. @@ -1591,8 +1472,7 @@ TEST_P(CdmUseCase_LimitedDurationLicense, Case3) { // continues. TEST_P(CdmUseCase_LimitedDurationLicense, Case4) { // Allow playback within the initial renewal window. - LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, - current_cutoff_); + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime); SleepUntilRenewalNeeded(); RequestRenewal(kLDLRenewal); const uint64_t start = CurrentRentalTime(); @@ -1603,30 +1483,281 @@ TEST_P(CdmUseCase_LimitedDurationLicense, Case4) { // Playback may be restarted. TEST_P(CdmUseCase_LimitedDurationLicense, Case5) { // Allow playback within the initial renewal window. - LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime, - current_cutoff_); + LoadAndAllowPlayback(start_of_playback_, renewal_load_time_ - kRoundTripTime); SleepUntilRenewalNeeded(); RequestRenewal(kLDLRenewal); // Continue playback for one hour (use 100 to condense test). - const uint64_t end_of_hour = renewal_load_time_ + 100; + const uint64_t end_of_play = renewal_load_time_ + kPlayDuration; const uint64_t start = CurrentRentalTime(); - RenewAndContinue(start, renewal_load_time_, end_of_hour, kLDLRenewal); + RenewAndContinue(start, renewal_load_time_, end_of_play, kLDLRenewal); UnloadLicense(); // 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. - const uint64_t reload_time = end_of_hour + 100; + const uint64_t reload_time = end_of_play + kPlayDuration; ReloadAndAllowPlayback(reload_time, EndOfPlaybackWindow(), kLDLRenewal); // But not beyond playback window. SleepUntil(EndOfPlaybackWindow() + 3 * kFudge); FailDecrypt(); } +// Renewal loaded before playback starts. +TEST_P(CdmUseCase_LimitedDurationLicense, Case6) { + LoadLicense(); + RequestRenewal(kLDLRenewal); + LoadRenewal(start_of_playback_ - 2 * kFudge, kLDLRenewal); + const uint64_t end_of_play = renewal_load_time_ + kPlayDuration; + AllowPlayback(start_of_playback_, end_of_play); +} + +// Heartbeat Playback Window License. (See above for notes on Use Case tests). +// This is similar to the renewal, but the renewal recovery is larger than the +// renewal delay. +class CdmUseCase_Heartbeat : public RenewalTest { + public: + CdmUseCase_Heartbeat() : RenewalTest("CDM_Heartbeat") { + timer_limits_.rental_duration_seconds = 0; + initial_policy_.renewal_delay = 10u; + initial_policy_.renewal_recovery_duration = 30u; + timer_limits_.initial_renewal_duration_seconds = + initial_policy_.renewal_delay + + initial_policy_.renewal_recovery_duration; + } + + void SetUp() override { + RenewalTest::SetUp(); + const uint64_t next_renewal = + start_of_playback_ + initial_policy_.renewal_delay; + // Allow playback within the initial renewal window. + SleepUntil(start_of_playback_); + LoadLicense(); + // Play until just before we expect a renewal to be generated. + current_cutoff_ = next_renewal + initial_policy_.renewal_recovery_duration; + AllowPlayback(start_of_playback_, next_renewal); + } + + protected: + RenewalPolicy initial_policy_; +}; + +// Playback within rental duration and renewal duration. +TEST_P(CdmUseCase_Heartbeat, Case1) { + for (int i = 0; i < 4; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kHeartbeatRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kHeartbeatRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kHeartbeatRenewal); + } +} + +// Playback within rental duration, last playback attempt exceeds renewal +// duration. +TEST_P(CdmUseCase_Heartbeat, Case2) { + for (int i = 0; i < 4; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kHeartbeatRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kHeartbeatRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kHeartbeatRenewal); + } + // We attempt to continue playing beyond the renewal cutoff. + SleepUntilRenewalNeeded(); + RequestRenewal(kHeartbeatRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + // For v16 and beyond, we restart the renewal timer when the renewal was + // loaded. For v15 and earlier, we restart the renewal when the renewal was + // granted, which is close to when the renewal was requested. + const uint64_t timer_start = + (wvoec::global_features.api_version < wvoec::kCoreMessagesAPI) + ? start + : load_time; + const uint64_t cutoff = timer_start + kHeartbeatRenewal.renewal_delay + + kHeartbeatRenewal.renewal_recovery_duration; + RenewAndTerminate(start, load_time, cutoff, kHeartbeatRenewal); +} + +/*****************************************************************************/ +// The unlimited streaming case has no limits on license use. This use case is +// not in the documentation because it is not recommended. +class CdmUseCase_UnlimitedStreaming : public CdmDurationTest { + public: + CdmUseCase_UnlimitedStreaming() : CdmDurationTest("CDM_UnlimitedStreaming") { + timer_limits_.rental_duration_seconds = 0; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// Playback within rental duration. +TEST_P(CdmUseCase_UnlimitedStreaming, Case1) { + // Allow playback within the rental window. + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); +} + +// Playback with stops/restarts within rental duration, last one exceeds rental +// duration. +TEST_P(CdmUseCase_UnlimitedStreaming, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); +} + +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_UnlimitedStreaming, + ::testing::Values(false, true)); + +/*****************************************************************************/ +// Some content providers set the license duration instead of the rental +// duration. The test behavior should be the same on the device. This use case +// is not in the documentation because it is not recommended. +class CdmUseCase_LicenseDuration : public CdmDurationTest { + public: + CdmUseCase_LicenseDuration() : CdmDurationTest("CDM_LicenseDuration") { + timer_limits_.soft_enforce_rental_duration = true; + // The policy does not specify a rental duration, but the server adjusts it + // so that it looks like this to a v16 device. + timer_limits_.rental_duration_seconds = 40u; + timer_limits_.soft_enforce_playback_duration = false; + timer_limits_.total_playback_duration_seconds = 40u; + } +}; + +// Playback within rental duration. +TEST_P(CdmUseCase_LicenseDuration, Case1) { + // Allow playback within the playback window. + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow()); +} + +// Playback exceeds rental duration. +TEST_P(CdmUseCase_LicenseDuration, Case2) { + LoadAndAllowPlayback(start_of_playback_, EndOfRentalWindow()); + // Do not allow playback after playback window. For a v16 CDM with a v15 + // OEMCrypto, playback is cutoff at the rental window, and for a v16 CDM with + // a v16 OEMCrypto, playback is allowed a little longer. In both cases, + // playback is not allowed after the end of the playback window. + SleepUntil(EndOfPlaybackWindow() + kFudge); + FailDecrypt(); +} + +// Playback with stops/restarts within rental duration, last one exceeds +// playback duration. +TEST_P(CdmUseCase_LicenseDuration, Case3) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + UnloadLicense(); + LoadAndAllowPlayback(start_of_playback_ + 2 * kPlayDuration, + start_of_playback_ + 3 * kPlayDuration); + UnloadLicense(); + AllowLenience(); + LoadAndAllowPlayback(start_of_playback_ + 4 * kPlayDuration, + EndOfRentalWindow()); + // As above, do not allow playback after playback window. + AllowLenience(); + SleepUntil(EndOfPlaybackWindow() + kFudge); + FailDecrypt(); +} + +// Playback within rental duration, restart exceeds playback duration. +TEST_P(CdmUseCase_LicenseDuration, Case4) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + UnloadLicense(); + ForbidPlayback(EndOfPlaybackWindow() + kFudge); +} + +// Initial playback exceeds rental duration. +TEST_P(CdmUseCase_LicenseDuration, Case5) { + ForbidPlayback(EndOfRentalWindow() + kFudge); +} + +// This use case is not documented because we don't recommend having a renewal +// with an infinite duration. However, some content providers use this policy, +// so we want to verify that we support it correctly. +class CdmUseCase_InfiniteRenewal : public RenewalTest { + public: + CdmUseCase_InfiniteRenewal() : RenewalTest("CDM_InfiniteRenewal") { + timer_limits_.soft_enforce_rental_duration = false; + timer_limits_.rental_duration_seconds = 50u; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// The renewal interval is infinite. We never need to load the renewal. +TEST_P(CdmUseCase_InfiniteRenewal, NoRenewal) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + SleepUntil(EndOfRentalWindow() + kPlayDuration); + FailDecrypt(); +} + +// If we do load the renewal, the playback window still cuts off playback. +TEST_P(CdmUseCase_InfiniteRenewal, WithRenewal) { + LoadAndAllowPlayback(start_of_playback_, start_of_playback_ + kPlayDuration); + RequestRenewal(kInfiniteRenewal); + const uint64_t start = start_of_playback_ + 2 * kPlayDuration; + const uint64_t load_time = start + kRoundTripTime; + const uint64_t cutoff = EndOfRentalWindow(); + RenewAndTerminate(start, load_time, cutoff, kInfiniteRenewal); +} + +// If we load the renewal before playback starts, then the rental window still +// cuts off playback. +TEST_P(CdmUseCase_InfiniteRenewal, RenewBeforePlayback) { + LoadLicense(); + RequestRenewal(kInfiniteRenewal); + LoadRenewal(start_of_playback_, kInfiniteRenewal); + AllowPlayback(start_of_playback_, start_of_playback_ + 2 * kPlayDuration); + SleepUntil(EndOfRentalWindow() + kPlayDuration); + FailDecrypt(); +} + +// This use case is not documented because we don't recommend specifying the +// license duration instead of rental or playback duration. However, some +// content providers use this policy, so we want to verify that we support it +// correctly. +class CdmUseCase_LicenseDurationWithRenewal : public RenewalTest { + public: + CdmUseCase_LicenseDurationWithRenewal() + : RenewalTest("CDM_LicenseDurationWithRenewal") { + timer_limits_.soft_enforce_rental_duration = true; + timer_limits_.rental_duration_seconds = 30u; + timer_limits_.total_playback_duration_seconds = 0; + } +}; + +// If we do load the renewal, we may continue playback past original window. +TEST_P(CdmUseCase_LicenseDurationWithRenewal, WithRenewal) { + LoadAndAllowPlayback( + start_of_playback_, + start_of_playback_ + kLicenseDurationWithRenewal.renewal_delay); + // Play through four renewals, which should be past the rental window. + for (int i = 0; i < 4; i++) { + SleepUntilRenewalNeeded(); + RequestRenewal(kLicenseDurationWithRenewal); + const uint64_t start = CurrentRentalTime(); + const uint64_t load_time = start + kRoundTripTime; + const uint64_t stop = load_time + kLicenseDurationWithRenewal.renewal_delay; + RenewAndContinue(start, load_time, stop, kLicenseDurationWithRenewal); + } + // Expect that renewals did let us play past the rental window. + EXPECT_GT(CurrentRentalTime(), EndOfRentalWindow() + kPlayDuration); +} + +/***********************************************************************/ // All duration tests are parameterized by can_persist = true or false. -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LicenseWithRenewal, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LicenseWithRenewalPlayback, - ::testing::Values(false, true)); -INSTANTIATE_TEST_CASE_P(Both, CdmUseCase_LimitedDurationLicense, - ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LicenseWithRenewal, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LicenseWithRenewalPlayback, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LimitedDurationLicense, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_InfiniteRenewal, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LicenseDuration, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_LicenseDurationWithRenewal, + ::testing::Values(false, true)); +INSTANTIATE_TEST_SUITE_P(Both, CdmUseCase_Heartbeat, + ::testing::Values(false, true)); + } // namespace wvcdm diff --git a/core/test/fake_provisioning_server.cpp b/core/test/fake_provisioning_server.cpp index d8d725a7..4b550450 100644 --- a/core/test/fake_provisioning_server.cpp +++ b/core/test/fake_provisioning_server.cpp @@ -1,6 +1,6 @@ // Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "fake_provisioning_server.h" @@ -10,7 +10,7 @@ #include "crypto_session.h" #include "license_protocol.pb.h" #include "log.h" -#include "oec_session_util.h" +#include "oec_key_deriver.h" #include "oec_test_data.h" #include "privacy_crypto.h" #include "service_certificate.h" @@ -34,7 +34,7 @@ namespace { // This is a sample RSA private key, it pairs with the public fake service // certificate below. // From file test_rsa_key_2_carmichael.pk8 in team shared drive. Size is 1216. -const std::string kPrivateKeyFakeServiceCert = a2bs_hex( +const std::string kPrivateKeyFakeServiceCert = wvutil::a2bs_hex( "308204bc020100300d06092a864886f70d0101010500048204a6308204a2020100028201" "0100a700366065dcbd545a2a40b4e1159458114f9458dddea71f3c2ce08809296157675e" "567eee278f59349a2aaa9db44efaa76ad4c97a53c14e9fe334f73db7c910474f28da3fce" @@ -73,7 +73,7 @@ const std::string kPrivateKeyFakeServiceCert = a2bs_hex( // This is a fake service certificate. // From the team shared drive file // oem-7913-leaf-and-intermediate-certs-test-key-2-carmichael.p7b, size 2353. -const std::string kPublicFakeServiceCert = a2bs_hex( +const std::string kPublicFakeServiceCert = wvutil::a2bs_hex( "3082092d06092a864886f70d010702a082091e3082091a0201013100300f06092a864886" "f70d010701a0020400a08208fe3082037130820259a003020102021100c28d2022828b9e" "639d15892ca98fd95d300d06092a864886f70d01010b0500306b310b3009060355040613" @@ -142,7 +142,7 @@ const std::string kPublicFakeServiceCert = a2bs_hex( "30eb823b06ab3c397dd0683100"); // This is a private RSA key that is paired with the DRM certificate below. -const std::string kPrivateKeySampleDRMCert = a2bs_hex( +const std::string kPrivateKeySampleDRMCert = wvutil::a2bs_hex( "308204BC020100300D06092A864886F70D0101010500048204A6308204A202010002820101" "00E68EAD7C67ED983A72C89BC55054D26821C3399702E7906B77C7E09AE607D40B0013484B" "0C557A810E19A814B4F14D55E60456EE21BC19F29EFFDA416BC9CBF0CE2C684E5A44F77008" @@ -179,7 +179,7 @@ const std::string kPrivateKeySampleDRMCert = a2bs_hex( // This is a DRM certificate that was intercepted from a provisioning response // from the production server to a device with the test keybox. -const std::string kPublicSampleDRMCert = a2bs_hex( +const std::string kPublicSampleDRMCert = wvutil::a2bs_hex( "0ABC02080212107CB49F987A635E1E0A52184694582D6E18A2C99EEC05228E023082010A02" "82010100E68EAD7C67ED983A72C89BC55054D26821C3399702E7906B77C7E09AE607D40B00" "13484B0C557A810E19A814B4F14D55E60456EE21BC19F29EFFDA416BC9CBF0CE2C684E5A44" @@ -221,8 +221,8 @@ FakeProvisioningServer::FakeProvisioningServer() { // Generate a service certificate that can convince the CDM we are a real // provisioning server. it only works if the CDM is compiled with the symbol // ACCEPT_TEST_CERT defined. - video_widevine::DrmDeviceCertificate cert; - cert.set_type(video_widevine::DrmDeviceCertificate_CertificateType_SERVICE); + video_widevine::DrmCertificate cert; + cert.set_type(video_widevine::DrmCertificate_Type_SERVICE); cert.set_public_key(kPublicFakeServiceCert); cert.set_serial_number("Serial Number 007"); @@ -231,7 +231,7 @@ FakeProvisioningServer::FakeProvisioningServer() { std::string serialized_cert; cert.SerializeToString(&serialized_cert); - video_widevine::SignedDrmDeviceCertificate signed_cert; + video_widevine::SignedDrmCertificate signed_cert; signed_cert.set_drm_certificate(serialized_cert); signed_cert.SerializeToString(&service_certificate_); } @@ -263,10 +263,11 @@ bool FakeProvisioningServer::MakeResponse( ? "WIDEVINE_DRM" : "X509"); - video_widevine::SignedProvisioningMessage::ProtocolVersion version = - signed_request.protocol_version(); - LOGD("Request uses protocol version: %d", version); - if (version != video_widevine::SignedProvisioningMessage::PROVISIONING_20) { + const video_widevine::SignedProvisioningMessage::ProvisioningType + provisioning_type = signed_request.provisioning_type(); + LOGD("Request uses provisioning type: %d", provisioning_type); + if (provisioning_type != + video_widevine::SignedProvisioningMessage::PROVISIONING_20) { LOGE("Fake provisioning server only handles Keyboxes"); return false; } @@ -283,7 +284,8 @@ bool FakeProvisioningServer::MakeResponse( wvoec::KeyDeriver key_deriver; // Not only is this Prov 2.0 specific, it assumes the device is using the // standard test keybox. - key_deriver.DeriveKeys(wvoec::kTestKeybox.device_key_, mac_context_v, + key_deriver.DeriveKeys(wvoec::kTestKeybox.device_key_, + sizeof(wvoec::kTestKeybox.device_key_), mac_context_v, enc_context_v); // Create a structure to hold the RSA private key. This is used by the key @@ -314,7 +316,7 @@ bool FakeProvisioningServer::MakeResponse( // Sign the response. video_widevine::SignedProvisioningMessage signed_response; - signed_response.set_protocol_version(signed_request.protocol_version()); + signed_response.set_provisioning_type(signed_request.provisioning_type()); std::string message; provisioning_response.SerializeToString(&message); signed_response.set_message(message); @@ -332,6 +334,7 @@ bool FakeProvisioningServer::MakeResponse( } std::string core_response; oemcrypto_core_message::serialize::CreateCoreProvisioningResponseFromProto( + oemcrypto_core_message::features::CoreMessageFeatures::kDefaultFeatures, message, core_request_data, &core_response); signed_response.set_oemcrypto_core_message(core_response); // Also, the signature should be over the concatenation of the core message @@ -351,11 +354,11 @@ bool FakeProvisioningServer::MakeResponse( // CDM. std::string response_data; signed_response.SerializeToString(&response_data); - std::vector response_data_v(response_data.begin(), - response_data.end()); - static const std::string json_start = "{ \"signedResponse\": \""; - static const std::string json_end = "\" }"; - *json_response = json_start + Base64SafeEncode(response_data_v) + json_end; + static const std::string kJsonStart = "{ \"signedResponse\": \""; + static const std::string kJsonEnd = "\" }"; + *json_response = kJsonStart; + json_response->append(wvutil::Base64SafeEncode(response_data)); + json_response->append(kJsonEnd); return true; } diff --git a/core/test/fake_provisioning_server.h b/core/test/fake_provisioning_server.h index 77dbeb8b..094c3a7a 100644 --- a/core/test/fake_provisioning_server.h +++ b/core/test/fake_provisioning_server.h @@ -1,6 +1,6 @@ // Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef WVCDM_CORE_FAKE_PROVISIONING_SERVER_H_ #define WVCDM_CORE_FAKE_PROVISIONING_SERVER_H_ diff --git a/core/test/generic_crypto_unittest.cpp b/core/test/generic_crypto_unittest.cpp index 7c638f60..1b8422bc 100644 --- a/core/test/generic_crypto_unittest.cpp +++ b/core/test/generic_crypto_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // These tests are for the generic crypto operations. They call on the // CdmEngine class and exercise the classes below it as well. In // particular, we assume that the OEMCrypo layer works, and has a valid keybox. @@ -13,7 +13,7 @@ #include "cdm_engine.h" #include "config_test_env.h" -#include "license_request.h" +#include "license_holder.h" #include "log.h" #include "oec_session_util.h" #include "oemcrypto_session_tests_helper.h" @@ -27,31 +27,35 @@ #include "wv_cdm_constants.h" #include "wv_cdm_types.h" +using wvutil::a2b_hex; + namespace wvcdm { class WvGenericCryptoTest : public WvCdmTestBaseWithEngine { public: - WvGenericCryptoTest() : holder_(&cdm_engine_) {} + WvGenericCryptoTest() : holder_("CDM_GenericCrypto", &cdm_engine_, config_) {} void SetUp() override { WvCdmTestBase::SetUp(); EnsureProvisioned(); - holder_.OpenSession(config_.key_system()); - holder_.GenerateKeyRequest(binary_key_id(), ISO_BMFF_VIDEO_MIME_TYPE); - holder_.CreateDefaultLicense(); + ASSERT_NO_FATAL_FAILURE(holder_.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder_.FetchLicense()); - ency_id_ = "ency"; - StripeBuffer(&ency_key_, CONTENT_KEY_SIZE, 'e'); - AddOneKey(ency_id_, ency_key_, wvoec::kControlAllowEncrypt); - dency_id_ = "dency"; - StripeBuffer(&dency_key_, CONTENT_KEY_SIZE, 'd'); - AddOneKey(dency_id_, dency_key_, wvoec::kControlAllowDecrypt); - siggy_id_ = "siggy"; - StripeBuffer(&siggy_key_, MAC_KEY_SIZE, 's'); - AddOneKey(siggy_id_, siggy_key_, wvoec::kControlAllowSign); - vou_id_ = "vou"; - StripeBuffer(&vou_key_, MAC_KEY_SIZE, 'v'); - AddOneKey(vou_id_, vou_key_, wvoec::kControlAllowVerify); + ency_id_ = "encrypt-key-----"; + ency_key_ = a2b_hex("0102030405060708090a0b0c0d0e0f10"); + dency_id_ = "decrypt-key-----"; + dency_key_ = a2b_hex("AA02030405060708090a0b0c0d0e0f10"); + siggy_id_ = "sign-key--------"; + siggy_key_ = a2b_hex( + "BB02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10"); + vou_id_ = "verify-key------"; + vou_key_ = a2b_hex( + "CC02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10"); + both_id_ = "enc-and-dec-key-"; + both_key_ = a2b_hex("DD02030405060708090a0b0c0d0e0f10"); + sign_and_verify_id_ = "sign-and-verify-"; + sign_and_verify_key_ = a2b_hex( + "EE02030405060708090a0b0c0d0e0f100102030405060708090a0b0c0d0e0f10"); StripeBuffer(&in_vector_, CONTENT_KEY_SIZE * 15, '1'); in_buffer_ = std::string(in_vector_.begin(), in_vector_.end()); @@ -62,26 +66,22 @@ class WvGenericCryptoTest : public WvCdmTestBaseWithEngine { void TearDown() override { holder_.CloseSession(); } - // Create a single key, and add it to the license. - void AddOneKey(const KeyId& key_id, const std::vector& key_data, - uint32_t key_control_block) { - wvoec::KeyControlBlock block = {}; - block.control_bits = htonl(key_control_block); - holder_.AddKey(key_id, key_data, block); - } - protected: - TestLicenseHolder holder_; + LicenseHolder holder_; KeyId ency_id_; KeyId dency_id_; KeyId siggy_id_; KeyId vou_id_; + KeyId both_id_; + KeyId sign_and_verify_id_; std::vector ency_key_; std::vector dency_key_; std::vector siggy_key_; std::vector vou_key_; + std::vector both_key_; + std::vector sign_and_verify_key_; std::vector in_vector_; std::vector iv_vector_; @@ -89,210 +89,155 @@ class WvGenericCryptoTest : public WvCdmTestBaseWithEngine { std::string iv_; }; -TEST_F(WvGenericCryptoTest, LoadSpecialKeys) { holder_.SignAndLoadLicense(); } +TEST_F(WvGenericCryptoTest, LoadSpecialKeys) { + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); +} TEST_F(WvGenericCryptoTest, GenericEncryptGood) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); std::string encrypted = Aes128CbcEncrypt(ency_key_, in_vector_, iv_vector_); std::string out_buffer; - - holder_.SignAndLoadLicense(); - cdm_sts = cdm_engine_.GenericEncrypt( - holder_.session_id(), in_buffer_, ency_id_, iv_, - wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer); - - EXPECT_EQ(NO_ERROR, cdm_sts); + EXPECT_EQ(NO_ERROR, cdm_engine_.GenericEncrypt( + holder_.session_id(), in_buffer_, ency_id_, iv_, + wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer)); EXPECT_EQ(encrypted, out_buffer); } TEST_F(WvGenericCryptoTest, GenericEncryptNoKey) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); std::string encrypted = Aes128CbcEncrypt(ency_key_, in_vector_, iv_vector_); std::string out_buffer; KeyId key_id("no_key"); - - holder_.SignAndLoadLicense(); - cdm_sts = cdm_engine_.GenericEncrypt( - holder_.session_id(), in_buffer_, key_id, iv_, - wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer); - EXPECT_NE(NO_ERROR, cdm_sts); + EXPECT_NE(NO_ERROR, cdm_engine_.GenericEncrypt( + holder_.session_id(), in_buffer_, key_id, iv_, + wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer)); EXPECT_NE(encrypted, out_buffer); } TEST_F(WvGenericCryptoTest, GenericEncryptKeyNotAllowed) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); // Trying to use Decrypt key to encrypt, which is not allowed. KeyId key_id = dency_id_; std::string encrypted = Aes128CbcEncrypt(dency_key_, in_vector_, iv_vector_); std::string out_buffer; - - holder_.SignAndLoadLicense(); - cdm_sts = cdm_engine_.GenericEncrypt( - holder_.session_id(), in_buffer_, key_id, iv_, - wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer); - EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); + EXPECT_EQ(UNKNOWN_ERROR, + cdm_engine_.GenericEncrypt( + holder_.session_id(), in_buffer_, key_id, iv_, + wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer)); EXPECT_NE(encrypted, out_buffer); } TEST_F(WvGenericCryptoTest, GenericDecryptGood) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_); std::string out_buffer; - - holder_.SignAndLoadLicense(); - cdm_sts = cdm_engine_.GenericDecrypt( - holder_.session_id(), in_buffer_, dency_id_, iv_, - wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer); - - EXPECT_EQ(NO_ERROR, cdm_sts); + EXPECT_EQ(NO_ERROR, cdm_engine_.GenericDecrypt( + holder_.session_id(), in_buffer_, dency_id_, iv_, + wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer)); EXPECT_EQ(decrypted, out_buffer); } TEST_F(WvGenericCryptoTest, GenericDecryptNoKey) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_); std::string out_buffer; KeyId key_id = "no_key"; - - holder_.SignAndLoadLicense(); - cdm_sts = cdm_engine_.GenericDecrypt( - holder_.session_id(), in_buffer_, key_id, iv_, - wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer); - EXPECT_NE(NO_ERROR, cdm_sts); + EXPECT_NE(NO_ERROR, cdm_engine_.GenericDecrypt( + holder_.session_id(), in_buffer_, key_id, iv_, + wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer)); EXPECT_NE(decrypted, out_buffer); } TEST_F(WvGenericCryptoTest, GenericDecryptKeyNotAllowed) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); // Trying to use Encrypt key to decrypt, which is not allowed. KeyId key_id = ency_id_; std::string decrypted = Aes128CbcDecrypt(ency_key_, in_vector_, iv_vector_); std::string out_buffer; - - holder_.SignAndLoadLicense(); - cdm_sts = cdm_engine_.GenericDecrypt( - holder_.session_id(), in_buffer_, key_id, iv_, - wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer); - EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); + EXPECT_EQ(UNKNOWN_ERROR, + cdm_engine_.GenericDecrypt( + holder_.session_id(), in_buffer_, key_id, iv_, + wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer)); EXPECT_NE(decrypted, out_buffer); } +TEST_F(WvGenericCryptoTest, GenericSignGood) { + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); + std::string out_buffer; + std::string signature = SignHMAC(in_buffer_, siggy_key_); + EXPECT_EQ(NO_ERROR, cdm_engine_.GenericSign( + holder_.session_id(), in_buffer_, siggy_id_, + wvcdm::kSigningAlgorithmHmacSha256, &out_buffer)); + EXPECT_EQ(signature, out_buffer); +} + TEST_F(WvGenericCryptoTest, GenericSignKeyNotAllowed) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); // Wrong key std::string key_id = vou_id_; std::string out_buffer; std::string signature = SignHMAC(in_buffer_, siggy_key_); - - holder_.SignAndLoadLicense(); - cdm_sts = + EXPECT_EQ( + UNKNOWN_ERROR, cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, key_id, - wvcdm::kSigningAlgorithmHmacSha256, &out_buffer); - EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); + wvcdm::kSigningAlgorithmHmacSha256, &out_buffer)); EXPECT_NE(signature, out_buffer); } -TEST_F(WvGenericCryptoTest, GenericSignGood) { - CdmResponseType cdm_sts; - std::string out_buffer; - std::string signature = SignHMAC(in_buffer_, siggy_key_); - - holder_.SignAndLoadLicense(); - cdm_sts = - cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, siggy_id_, - wvcdm::kSigningAlgorithmHmacSha256, &out_buffer); - EXPECT_EQ(NO_ERROR, cdm_sts); - EXPECT_EQ(signature, out_buffer); +TEST_F(WvGenericCryptoTest, GenericVerifyGood) { + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); + std::string signature = SignHMAC(in_buffer_, vou_key_); + EXPECT_EQ(NO_ERROR, cdm_engine_.GenericVerify( + holder_.session_id(), in_buffer_, vou_id_, + wvcdm::kSigningAlgorithmHmacSha256, signature)); } TEST_F(WvGenericCryptoTest, GenericVerifyKeyNotAllowed) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); // Wrong key std::string key_id = siggy_id_; std::string signature = SignHMAC(in_buffer_, siggy_key_); - - holder_.SignAndLoadLicense(); - cdm_sts = - cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, key_id, - wvcdm::kSigningAlgorithmHmacSha256, signature); - EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); + EXPECT_EQ(UNKNOWN_ERROR, cdm_engine_.GenericVerify( + holder_.session_id(), in_buffer_, key_id, + wvcdm::kSigningAlgorithmHmacSha256, signature)); } -TEST_F(WvGenericCryptoTest, GenericVerifyBadSignautre) { - CdmResponseType cdm_sts; +TEST_F(WvGenericCryptoTest, GenericVerifyBadSignature) { + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); std::string signature(MAC_KEY_SIZE, 's'); - - holder_.SignAndLoadLicense(); - cdm_sts = - cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, vou_id_, - wvcdm::kSigningAlgorithmHmacSha256, signature); // OEMCrypto error is OEMCrypto_ERROR_SIGNATURE_FAILURE - EXPECT_EQ(UNKNOWN_ERROR, cdm_sts); -} - -TEST_F(WvGenericCryptoTest, GenericVerifyGood) { - CdmResponseType cdm_sts; - std::string signature = SignHMAC(in_buffer_, vou_key_); - - holder_.SignAndLoadLicense(); - cdm_sts = - cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, vou_id_, - wvcdm::kSigningAlgorithmHmacSha256, signature); - EXPECT_EQ(NO_ERROR, cdm_sts); + EXPECT_EQ(UNKNOWN_ERROR, cdm_engine_.GenericVerify( + holder_.session_id(), in_buffer_, vou_id_, + wvcdm::kSigningAlgorithmHmacSha256, signature)); } TEST_F(WvGenericCryptoTest, GenericEncryptDecrypt) { - CdmResponseType cdm_sts; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); + std::string encrypted = Aes128CbcEncrypt(both_key_, in_vector_, iv_vector_); std::string out_buffer; - std::string clear_buffer; + EXPECT_EQ(NO_ERROR, cdm_engine_.GenericEncrypt( + holder_.session_id(), in_buffer_, both_id_, iv_, + wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer)); + EXPECT_EQ(encrypted, out_buffer); - KeyId key_id = "enc and dec"; - - std::vector key_data; - StripeBuffer(&key_data, CONTENT_KEY_SIZE, '3'); - AddOneKey(key_id, key_data, - wvoec::kControlAllowEncrypt | wvoec::kControlAllowDecrypt); - - holder_.SignAndLoadLicense(); - - cdm_sts = cdm_engine_.GenericEncrypt( - holder_.session_id(), in_buffer_, key_id, iv_, - wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer); - EXPECT_EQ(NO_ERROR, cdm_sts); - - cdm_sts = cdm_engine_.GenericDecrypt( - holder_.session_id(), out_buffer, key_id, iv_, - wvcdm::kEncryptionAlgorithmAesCbc128, &clear_buffer); - EXPECT_EQ(NO_ERROR, cdm_sts); - - EXPECT_EQ(in_buffer_, clear_buffer); + std::string decrypted = Aes128CbcDecrypt(dency_key_, in_vector_, iv_vector_); + EXPECT_EQ(NO_ERROR, cdm_engine_.GenericDecrypt( + holder_.session_id(), encrypted, both_id_, iv_, + wvcdm::kEncryptionAlgorithmAesCbc128, &out_buffer)); + EXPECT_EQ(in_buffer_, out_buffer); } TEST_F(WvGenericCryptoTest, GenericSignVerify) { - CdmResponseType cdm_sts; - std::string signature_buffer; + ASSERT_NO_FATAL_FAILURE(holder_.LoadLicense()); - KeyId key_id = "sign and ver"; - - std::vector key_data; - StripeBuffer(&key_data, MAC_KEY_SIZE, '4'); - AddOneKey(key_id, key_data, - wvoec::kControlAllowSign | wvoec::kControlAllowVerify); - std::string signature = SignHMAC(in_buffer_, key_data); - - holder_.SignAndLoadLicense(); - - cdm_sts = cdm_engine_.GenericSign(holder_.session_id(), in_buffer_, key_id, - wvcdm::kSigningAlgorithmHmacSha256, - &signature_buffer); - EXPECT_EQ(NO_ERROR, cdm_sts); - EXPECT_EQ(MAC_KEY_SIZE, signature_buffer.size()); - - cdm_sts = cdm_engine_.GenericVerify(holder_.session_id(), in_buffer_, key_id, - wvcdm::kSigningAlgorithmHmacSha256, - signature_buffer); - EXPECT_EQ(NO_ERROR, cdm_sts); - EXPECT_EQ(signature, signature_buffer); + std::string out_buffer; + std::string signature = SignHMAC(in_buffer_, sign_and_verify_key_); + EXPECT_EQ(NO_ERROR, cdm_engine_.GenericSign( + holder_.session_id(), in_buffer_, sign_and_verify_id_, + wvcdm::kSigningAlgorithmHmacSha256, &out_buffer)); + EXPECT_EQ(signature, out_buffer); + EXPECT_EQ(NO_ERROR, cdm_engine_.GenericVerify( + holder_.session_id(), in_buffer_, sign_and_verify_id_, + wvcdm::kSigningAlgorithmHmacSha256, signature)); } - } // namespace wvcdm diff --git a/core/test/http_socket.cpp b/core/test/http_socket.cpp index 6769ebb9..eaff0520 100644 --- a/core/test/http_socket.cpp +++ b/core/test/http_socket.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "http_socket.h" @@ -9,6 +9,7 @@ #include #include +#include #include #ifdef _WIN32 @@ -66,10 +67,10 @@ SSL_CTX* InitSslContext() { return ctx; } -static int LogBoringSslError(const char* message, size_t length, +static int LogBoringSslError(const char* message, size_t /* length */, void* /* user_data */) { LOGE(" BoringSSL Error: %s", message); - return length; + return 1; } bool IsRetryableSslError(int ssl_error) { @@ -108,40 +109,6 @@ void ShowServerCertificate(const SSL* ssl) { } #endif -// Wait for a socket to be ready for reading or writing. -// Establishing a connection counts as "ready for write". -// Returns false on select error or timeout. -// Returns true when the socket is ready. -bool SocketWait(int fd, bool for_read, int timeout_in_ms) { - fd_set fds; - FD_ZERO(&fds); - FD_SET(fd, &fds); - - struct timeval tv; - tv.tv_sec = timeout_in_ms / 1000; - tv.tv_usec = (timeout_in_ms % 1000) * 1000; - - fd_set* read_fds = nullptr; - fd_set* write_fds = nullptr; - if (for_read) { - read_fds = &fds; - } else { - write_fds = &fds; - } - - int ret = select(fd + 1, read_fds, write_fds, nullptr, &tv); - if (ret == 0) { - LOGE("socket timed out"); - return false; - } else if (ret == -1) { - LOGE("select failed, errno = %d", errno); - return false; - } - - // socket ready. - return true; -} - int GetError() { #ifdef _WIN32 return WSAGetLastError(); @@ -228,6 +195,8 @@ HttpSocket::HttpSocket(const std::string& url) : socket_fd_(-1), ssl_(nullptr), ssl_ctx_(nullptr) { valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_, &resource_path_); + create_time_ = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); InitSslLibrary(); } @@ -252,6 +221,16 @@ void HttpSocket::CloseSocket() { } } +bool HttpSocket::ConnectAndLogErrors(int timeout_in_ms) { + std::time_t start = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + bool result = Connect(timeout_in_ms); + std::time_t finish = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + if (!result) LogTime("socket connect error", start, finish); + return result; +} + bool HttpSocket::Connect(int timeout_in_ms) { if (!valid_url_) { LOGE("URL is invalid"); @@ -355,7 +334,7 @@ bool HttpSocket::Connect(int timeout_in_ms) { return false; } else { // in progress. block until timeout expired or connection established. - if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) { + if (!Wait(/* for_read */ false, timeout_in_ms)) { LOGE("cannot connect to %s", domain_name_.c_str()); CloseSocket(); return false; @@ -399,7 +378,7 @@ bool HttpSocket::Connect(int timeout_in_ms) { return false; } const bool for_read = (ssl_err == SSL_ERROR_WANT_READ); - if (!SocketWait(socket_fd_, for_read, timeout_in_ms)) { + if (!Wait(for_read, timeout_in_ms)) { LOGE("Cannot connect securely to %s", domain_name_.c_str()); CloseSocket(); return false; @@ -414,6 +393,16 @@ bool HttpSocket::Connect(int timeout_in_ms) { // Returns -1 for error, number of bytes read for success. // The timeout here only applies to the span between packets of data, for the // sake of simplicity. +int HttpSocket::ReadAndLogErrors(char* data, int len, int timeout_in_ms) { + std::time_t start = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + int result = Read(data, len, timeout_in_ms); + std::time_t finish = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + if (result < 0) LogTime("read error", start, finish); + return result; +} + int HttpSocket::Read(char* data, int len, int timeout_in_ms) { int total_read = 0; int to_read = len; @@ -423,8 +412,9 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) { return -1; } while (to_read > 0) { - if (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) { - LOGE("unable to read from %s", domain_name_.c_str()); + if (!Wait(/* for_read */ true, timeout_in_ms)) { + LOGE("unable to read from %s. len=%d, to_read=%d", domain_name_.c_str(), + len, to_read); return -1; } @@ -433,7 +423,7 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) { if (secure_connect_) { read = SSL_read(ssl_, data, to_read); } else { - read = recv(socket_fd_, data, to_read, 0); + read = static_cast(recv(socket_fd_, data, to_read, 0)); } if (read > 0) { @@ -480,6 +470,17 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) { // Returns -1 for error, number of bytes written for success. // The timeout here only applies to the span between packets of data, for the // sake of simplicity. +int HttpSocket::WriteAndLogErrors(const char* data, int len, + int timeout_in_ms) { + std::time_t start = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + int result = Write(data, len, timeout_in_ms); + std::time_t finish = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + if (result < 0) LogTime("write error", start, finish); + return result; +} + int HttpSocket::Write(const char* data, int len, int timeout_in_ms) { int total_sent = 0; int to_send = len; @@ -490,10 +491,11 @@ int HttpSocket::Write(const char* data, int len, int timeout_in_ms) { } while (to_send > 0) { int sent; - if (secure_connect_) + if (secure_connect_) { sent = SSL_write(ssl_, data, to_send); - else - sent = send(socket_fd_, data, to_send, 0); + } else { + sent = static_cast(send(socket_fd_, data, to_send, 0)); + } if (sent > 0) { to_send -= sent; @@ -501,7 +503,7 @@ int HttpSocket::Write(const char* data, int len, int timeout_in_ms) { total_sent += sent; } else if (sent == 0) { // We filled up the pipe. Wait for room to write. - if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) { + if (!Wait(/* for_read */ false, timeout_in_ms)) { LOGE("unable to write to %s", domain_name_.c_str()); return -1; } @@ -514,4 +516,50 @@ int HttpSocket::Write(const char* data, int len, int timeout_in_ms) { return total_sent; } +bool HttpSocket::Wait(bool for_read, int timeout_in_ms) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(socket_fd_, &fds); + + struct timeval tv; + tv.tv_sec = timeout_in_ms / 1000; + tv.tv_usec = (timeout_in_ms % 1000) * 1000; + + fd_set* read_fds = nullptr; + fd_set* write_fds = nullptr; + if (for_read) { + read_fds = &fds; + } else { + write_fds = &fds; + } + + const std::time_t start = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + int ret = select(socket_fd_ + 1, read_fds, write_fds, nullptr, &tv); + const std::time_t finish = + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); + if (ret == 0) { + LogTime("socket select timeout", start, finish); + // TODO(b/186031735): Remove this when the bug is fixed. + LOGE("Timeout = %0.3f. Consider adding a comment to http://b/186031735", + 0.001 * timeout_in_ms); + return false; + } else if (ret == -1) { + LOGE("select failed, errno = %d", errno); + return false; + } + + // socket ready. + return true; +} + +void HttpSocket::LogTime(const char* note, const std::time_t& start, + const std::time_t& finish) { + std::string start_string = std::string(std::ctime(&start)); + start_string.pop_back(); // Remove new line character. + LOGE("%s: start = %s = create + %0.3f, end = start + %0.3f", note, + start_string.c_str(), difftime(start, create_time_), + difftime(finish, start)); +} + } // namespace wvcdm diff --git a/core/test/http_socket.h b/core/test/http_socket.h index f3ce53c9..f32612a1 100644 --- a/core/test/http_socket.h +++ b/core/test/http_socket.h @@ -1,12 +1,13 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef CDM_TEST_HTTP_SOCKET_H_ #define CDM_TEST_HTTP_SOCKET_H_ #include +#include #include #include @@ -23,7 +24,7 @@ class HttpSocket { explicit HttpSocket(const std::string& url); ~HttpSocket(); - bool Connect(int timeout_in_ms); + bool ConnectAndLogErrors(int timeout_in_ms); void CloseSocket(); const std::string& scheme() const { return scheme_; } @@ -32,13 +33,25 @@ class HttpSocket { int port() const { return atoi(port_.c_str()); } const std::string& resource_path() const { return resource_path_; } - int Read(char* data, int len, int timeout_in_ms); - int Write(const char* data, int len, int timeout_in_ms); + int ReadAndLogErrors(char* data, int len, int timeout_in_ms); + int WriteAndLogErrors(const char* data, int len, int timeout_in_ms); private: static bool ParseUrl(const std::string& url, std::string* scheme, bool* secure_connect, std::string* domain_name, std::string* port, std::string* path); + // The following three functions do the work without logging errors. + bool Connect(int timeout_in_ms); + int Read(char* data, int len, int timeout_in_ms); + int Write(const char* data, int len, int timeout_in_ms); + // Log times with a note as an error. + void LogTime(const char* note, const std::time_t& start, + const std::time_t& finish); + // Wait for a socket to be ready for reading or writing. + // Establishing a connection counts as "ready for write". + // Returns false on select error or timeout. + // Returns true when the socket is ready. + bool Wait(bool for_read, int timeout_in_ms); FRIEND_TEST(HttpSocketTest, ParseUrlTest); std::string scheme_; @@ -52,6 +65,10 @@ class HttpSocket { SSL* ssl_; SSL_CTX* ssl_ctx_; + // When the socket was created. Logged on error to help debug flaky + // tests. e.g. b/186031735 + std::time_t create_time_; + CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket); }; diff --git a/core/test/http_socket_test.cpp b/core/test/http_socket_test.cpp index 72a0d043..19dd1951 100644 --- a/core/test/http_socket_test.cpp +++ b/core/test/http_socket_test.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include #include @@ -30,13 +30,13 @@ const int kTimeout = 3000; class HttpSocketTest : public testing::Test { public: HttpSocketTest() {} - ~HttpSocketTest() {} + ~HttpSocketTest() override {} protected: bool Connect(const std::string& server_url) { socket_.reset(new HttpSocket(server_url)); - if (socket_->Connect(kTimeout)) { + if (socket_->ConnectAndLogErrors(kTimeout)) { LOGD("connected to %s", socket_->domain_name().c_str()); return true; } else { @@ -73,7 +73,8 @@ class HttpSocketTest : public testing::Test { // append data request.append(data); - socket_->Write(request.c_str(), request.size(), kTimeout); + socket_->WriteAndLogErrors(request.c_str(), + static_cast(request.size()), kTimeout); LOGD("request: %s", request.c_str()); return true; @@ -81,7 +82,7 @@ class HttpSocketTest : public testing::Test { bool GetResponse(std::string* response) { char buffer[kHttpBufferSize]; - int bytes = socket_->Read(buffer, sizeof(buffer), kTimeout); + int bytes = socket_->ReadAndLogErrors(buffer, sizeof(buffer), kTimeout); if (bytes < 0) { LOGE("read error, errno = %d", errno); return false; @@ -163,7 +164,7 @@ ParseUrlTests parse_url_tests[] = { "8888", // port "/", // path }, - {nullptr, nullptr, false, nullptr, 0, nullptr} // list terminator + {} // list terminator }; TEST_F(HttpSocketTest, ParseUrlTest) { @@ -206,43 +207,3 @@ TEST_F(HttpSocketTest, RoundTripTest) { } } // namespace wvcdm - -int main(int argc, char** argv) { - using namespace wvcdm; - - ::testing::InitGoogleTest(&argc, argv); - - std::string temp; - std::string test_server(kHttpsTestServer); - std::string test_data(gTestData); - for (int i = 1; i < argc; i++) { - temp.assign(argv[i]); - if (temp.find("--server=") == 0) { - gTestServer.assign(temp.substr(strlen("--server="))); - } else if (temp.find("--data=") == 0) { - gTestData.assign(temp.substr(strlen("--data="))); - } else { - std::cout << "error: unknown option '" << argv[i] << "'" << std::endl; - std::cout << "usage: http_socket_test [options]" << std::endl - << std::endl; - std::cout << std::setw(30) << std::left << " --server="; - std::cout - << "configure the test server url, please include http[s] in the url" - << std::endl; - std::cout << std::setw(30) << std::left << " "; - std::cout << "default: " << test_server << std::endl; - std::cout << std::setw(30) << std::left << " --data="; - std::cout << "configure data to send, in ascii string format" - << std::endl; - std::cout << std::setw(30) << std::left << " "; - std::cout << "default: " << test_data << std::endl << std::endl; - return 0; - } - } - - std::cout << std::endl; - std::cout << "Server: " << gTestServer << std::endl; - std::cout << "Data: " << gTestData << std::endl; - - return RUN_ALL_TESTS(); -} diff --git a/core/test/initialization_data_unittest.cpp b/core/test/initialization_data_unittest.cpp index b30d2c7b..86cfe36d 100644 --- a/core/test/initialization_data_unittest.cpp +++ b/core/test/initialization_data_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include #include @@ -45,7 +45,7 @@ const std::string kJsonKeyIds = "key_ids"; const uint32_t kFourCcCbc1 = 0x63626331; const uint32_t kFourCcCbcs = 0x63626373; -const std::string kWidevinePssh = a2bs_hex( +const std::string kWidevinePssh = wvutil::a2bs_hex( // Widevine PSSH box "00000042" // atom size "70737368" // atom type="pssh" @@ -55,7 +55,7 @@ const std::string kWidevinePssh = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); -const std::string kWidevinePsshFirst = a2bs_hex( +const std::string kWidevinePsshFirst = wvutil::a2bs_hex( // first PSSH box, Widevine "00000042" // atom size "70737368" // atom type "pssh" @@ -74,7 +74,7 @@ const std::string kWidevinePsshFirst = a2bs_hex( // arbitrary data: "0102030405060708"); -const std::string kWidevinePsshAfterV0Pssh = a2bs_hex( +const std::string kWidevinePsshAfterV0Pssh = wvutil::a2bs_hex( // first PSSH box, Playready [1] "00000028" // atom size "70737368" // atom type "pssh" @@ -93,7 +93,7 @@ const std::string kWidevinePsshAfterV0Pssh = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); -const std::string kWidevinePsshAfterNonZeroFlags = a2bs_hex( +const std::string kWidevinePsshAfterNonZeroFlags = wvutil::a2bs_hex( // first PSSH box, Playready [1] "00000028" // atom size "70737368" // atom type "pssh" @@ -112,7 +112,7 @@ const std::string kWidevinePsshAfterNonZeroFlags = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); -const std::string kWidevinePsshAfterV1Pssh = a2bs_hex( +const std::string kWidevinePsshAfterV1Pssh = wvutil::a2bs_hex( // first PSSH box, generic CENC [2] "00000044" // atom size "70737368" // atom type "pssh" @@ -132,7 +132,7 @@ const std::string kWidevinePsshAfterV1Pssh = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); -const std::string kWidevineV1Pssh = a2bs_hex( +const std::string kWidevineV1Pssh = wvutil::a2bs_hex( // Widevine PSSH box, v1 format "00000066" // atom size "70737368" // atom type "pssh" @@ -145,7 +145,7 @@ const std::string kWidevineV1Pssh = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); -const std::string kOtherBoxFirst = a2bs_hex( +const std::string kOtherBoxFirst = wvutil::a2bs_hex( // first box, not a PSSH box "00000018" // atom size "77686174" // atom type "what" @@ -160,7 +160,7 @@ const std::string kOtherBoxFirst = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); -const std::string kZeroSizedPsshBox = a2bs_hex( +const std::string kZeroSizedPsshBox = wvutil::a2bs_hex( // Widevine PSSH box "00000000" // atom size (whole buffer) "70737368" // atom type="pssh" @@ -170,7 +170,7 @@ const std::string kZeroSizedPsshBox = a2bs_hex( // data: "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); -const std::string kMultipleWidevinePsshBox = a2bs_hex( +const std::string kMultipleWidevinePsshBox = wvutil::a2bs_hex( // first PSSH box, Widevine with single keys "00000042" // atom size "70737368" // atom type "pssh" @@ -219,9 +219,9 @@ const std::string kMultipleWidevinePsshBox = a2bs_hex( "d543101cc842bbec2d0b30"); // These are the data payloads of the two PSSH boxes in // kMultipleWidevinePsshBox. -const CdmInitData kSingleKeyWidevinePsshBoxData = a2bs_hex( +const CdmInitData kSingleKeyWidevinePsshBoxData = wvutil::a2bs_hex( "08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031"); -const CdmInitData kEntitledKeysWidevinePsshBoxData = a2bs_hex( +const CdmInitData kEntitledKeysWidevinePsshBoxData = wvutil::a2bs_hex( "220b47726f7570563254657374381448" "e3dc959b065002580272580a10668093" "381a8c5be48a0168ce372726ac1210c8" @@ -341,11 +341,8 @@ class VectorOfStrings { std::string GenerateHlsUriData(const std::string& provider, const std::string& content_id, const std::vector& key_ids) { - std::string json = GenerateJsonInitData(provider, content_id, key_ids); - std::vector json_init_data( - reinterpret_cast(json.data()), - reinterpret_cast(json.data() + json.size())); - return kHlsTestUriDataFormat + Base64Encode(json_init_data); + const std::string json = GenerateJsonInitData(provider, content_id, key_ids); + return kHlsTestUriDataFormat + wvutil::Base64Encode(json); } std::string CreateHlsAttributeList(const std::string& method, @@ -511,7 +508,7 @@ TEST_P(InitializationDataTest, Parse) { EXPECT_FALSE(init_data.IsEmpty()); } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( ParsePssh, InitializationDataTest, ::testing::Values(kWidevinePssh, kWidevinePsshFirst, kWidevinePsshAfterV0Pssh, kWidevinePsshAfterNonZeroFlags, @@ -557,7 +554,7 @@ TEST_P(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions) { } } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( HlsTest, HlsKeyFormatVersionsExtractionTest, ::testing::Values(VectorOfStrings(kHlsTestKeyFormatVersion1).Generate(), VectorOfStrings(kHlsTestKeyFormatVersion21).Generate(), @@ -587,7 +584,7 @@ TEST_P(HlsAttributeExtractionTest, ExtractAttribute) { } } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( HlsTest, HlsAttributeExtractionTest, ::testing::Values( HlsAttributeVariant(kHlsAttributeList, HLS_METHOD_ATTRIBUTE, @@ -647,14 +644,14 @@ TEST_P(HlsHexAttributeExtractionTest, ExtractHexAttribute) { if (param.success_) { EXPECT_TRUE(InitializationData::ExtractHexAttribute(param.attribute_list_, param.key_, &value)); - EXPECT_EQ(param.value_, b2a_hex(value)); + EXPECT_EQ(param.value_, wvutil::b2a_hex(value)); } else { EXPECT_FALSE(InitializationData::ExtractHexAttribute(param.attribute_list_, param.key_, &value)); } } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( HlsTest, HlsHexAttributeExtractionTest, ::testing::Values( HlsAttributeVariant(kHlsAttributeList, HLS_IV_ATTRIBUTE, kHlsIvHexValue, @@ -685,7 +682,7 @@ TEST_P(HlsQuotedAttributeExtractionTest, ExtractQuotedAttribute) { } } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( HlsTest, HlsQuotedAttributeExtractionTest, ::testing::Values( HlsAttributeVariant( @@ -716,21 +713,26 @@ TEST_P(HlsConstructionTest, InitData) { if (param.success_) { WidevinePsshData cenc_header; EXPECT_TRUE(cenc_header.ParseFromString(value)); + + CORE_UTIL_IGNORE_DEPRECATED EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR, cenc_header.algorithm()); + EXPECT_EQ(param.provider_, cenc_header.provider()); + CORE_UTIL_RESTORE_WARNINGS + for (size_t i = 0; i < param.key_ids_.size(); ++i) { bool key_id_found = false; if (param.key_ids_[i].size() != 32) continue; for (int j = 0; j < cenc_header.key_ids_size(); ++j) { - if (param.key_ids_[i] == b2a_hex(cenc_header.key_ids(j))) { + if (param.key_ids_[i] == wvutil::b2a_hex(cenc_header.key_ids(j))) { key_id_found = true; break; } } EXPECT_TRUE(key_id_found); } - EXPECT_EQ(param.provider_, cenc_header.provider()); - std::vector param_content_id_vec(Base64Decode(param.content_id_)); + std::vector param_content_id_vec( + wvutil::Base64Decode(param.content_id_)); EXPECT_EQ( std::string(param_content_id_vec.begin(), param_content_id_vec.end()), cenc_header.content_id()); @@ -749,7 +751,7 @@ TEST_P(HlsConstructionTest, InitData) { } } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( HlsTest, HlsConstructionTest, ::testing::Values( HlsInitDataVariant(kHlsMethodAes128, "", kHlsTestContentId, @@ -782,19 +784,16 @@ INSTANTIATE_TEST_CASE_P( .AddKeyId(kHlsTestKeyId1))); TEST_F(HlsInitDataConstructionTest, InvalidUriDataFormat) { - std::string json = + const std::string json = GenerateJsonInitData(kHlsTestProvider, kHlsTestContentId, VectorOfStrings(kHlsTestKeyId1).Generate()); - std::vector json_init_data( - reinterpret_cast(json.data()), - reinterpret_cast(json.data() + json.size())); std::string value; EXPECT_FALSE(InitializationData::ConstructWidevineInitData( - kHlsMethodAes128, Base64Encode(json_init_data), &value)); + kHlsMethodAes128, wvutil::Base64Encode(json), &value)); } TEST_F(HlsInitDataConstructionTest, InvalidUriBase64Encode) { - std::string json = + const std::string json = GenerateJsonInitData(kHlsTestProvider, kHlsTestContentId, VectorOfStrings(kHlsTestKeyId1).Generate()); std::string value; @@ -822,6 +821,8 @@ TEST_P(HlsParseTest, Parse) { WidevinePsshData cenc_header; EXPECT_TRUE(cenc_header.ParseFromString(init_data.data())); + + CORE_UTIL_IGNORE_DEPRECATED EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR, cenc_header.algorithm()); if (param.key_.compare(kJsonProvider) == 0) { @@ -829,17 +830,18 @@ TEST_P(HlsParseTest, Parse) { } else if (param.key_.compare(kJsonContentId) == 0) { EXPECT_EQ(param.value_, cenc_header.content_id()); } else if (param.key_.compare(kJsonKeyIds) == 0) { - EXPECT_EQ(param.value_, b2a_hex(cenc_header.key_ids(0))); + EXPECT_EQ(param.value_, wvutil::b2a_hex(cenc_header.key_ids(0))); } + CORE_UTIL_RESTORE_WARNINGS - EXPECT_EQ(kHlsIvHexValue, b2a_hex(init_data.hls_iv())); + EXPECT_EQ(kHlsIvHexValue, wvutil::b2a_hex(init_data.hls_iv())); } else { EXPECT_TRUE(init_data.is_hls()); EXPECT_TRUE(init_data.IsEmpty()); } } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( HlsTest, HlsParseTest, ::testing::Values( HlsAttributeVariant(kHlsAttributeList, "", "", true), diff --git a/core/test/keybox_ota_test.cpp b/core/test/keybox_ota_test.cpp new file mode 100644 index 00000000..cc3e1580 --- /dev/null +++ b/core/test/keybox_ota_test.cpp @@ -0,0 +1,177 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine Master +// License Agreement. + +#include +#include +#include +#include + +#include "create_test_file_system.h" +#include "crypto_session.h" +#include "properties.h" +#include "test_base.h" +#include "test_printers.h" +#include "test_sleep.h" +#include "url_request.h" + +using wvcdm::metrics::EngineMetrics; + +namespace wvcdm { +class CdmOtaKeyboxTest : public ::testing::Test { + public: + void SetUp() override { + ::testing::Test::SetUp(); + Properties::Init(); + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + LOGD("Running test %s.%s", test_info->test_case_name(), test_info->name()); + // Some test environments allow the model name of the device to be set + // dynamically using an environment variable. The model name will show up + // in the license server logs as the part of the device idenfication as + // "model_name". + std::string model_name = + std::string(test_info->test_case_name()) + "." + test_info->name(); + int overwrite = 1; // Set value even if already set. + setenv("MODEL_NAME", model_name.c_str(), overwrite); + // Reset the crypto session factory, so that a default one will be created. + // For these tests, we do *not* want to use the test crypto session factory + // because it automatically installs the test keybox on initialization. We + // do not want that because we are testing keybox provisioning. + CryptoSession::SetCryptoSessionFactory(nullptr); + } + + void Provision(TestCdmEngine* cdm_engine) { + ConfigTestEnv config = *WvCdmTestBase::default_config_; + CdmCertificateType cert_type = kCertificateWidevine; + std::string cert_authority; + CdmProvisioningRequest prov_request; + std::string provisioning_server_url; + CdmResponseType result = CERT_PROVISIONING_NONCE_GENERATION_ERROR; + // Get a provisioning request. We might need one retry if there is a nonce + // flood failure. + for (int i = 0; i < 2; i++) { + result = cdm_engine->GetProvisioningRequest( + cert_type, cert_authority, config.provisioning_service_certificate(), + kLevelDefault, &prov_request, &provisioning_server_url); + if (result == CERT_PROVISIONING_NONCE_GENERATION_ERROR) { + wvutil::TestSleep::Sleep(2); + continue; + } + break; + } + ASSERT_EQ(NO_ERROR, result); + LOGV("Provisioning request: req = %s", prov_request.c_str()); + + // Ignore URL provided by CdmEngine. Use ours, as configured + // for test vs. production server. + provisioning_server_url.assign(config.provisioning_server()); + + // Make request. + UrlRequest url_request(provisioning_server_url); + if (!url_request.is_connected()) { + LOGE("Failed to connect to provisioning server: url = %s", + provisioning_server_url.c_str()); + } + url_request.PostCertRequestInQueryString(prov_request); + + // Receive and parse response. + std::string http_message; + ASSERT_TRUE(url_request.GetResponse(&http_message)) + << "Failed to get provisioning response"; + LOGV("http_message: \n%s\n", http_message.c_str()); + + std::string cert, wrapped_key; + ASSERT_EQ(NO_ERROR, cdm_engine->HandleProvisioningResponse( + http_message, kLevelDefault, &cert, &wrapped_key)); + } +}; + +TEST_F(CdmOtaKeyboxTest, TestThatTheBuildFilesWork) { ASSERT_TRUE(true); } + +/** Verify that the CDM can open a session with at most two provisioning + * steps. Unlike almost all of the other Widevine CDM tests, this test does + * **not** install a test keybox before running. For that reason, this test is + * expected to fail if the device does not either support Keybox OTA recover or + * have an existing keybox or OEM Certificate. For those devices, this test + * should be skipped. + */ +TEST_F(CdmOtaKeyboxTest, BasicTest) { + std::unique_ptr file_system(CreateTestFileSystem()); + TestCdmEngine cdm_engine(file_system.get(), + std::shared_ptr(new EngineMetrics)); + // Remove any existing certificate. + cdm_engine.Unprovision(kSecurityLevelL1); + std::string system_id; + CdmResponseType system_id_status = + cdm_engine.QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id); + if (system_id_status == NO_ERROR) { + std::cout << " " + << "System ID before test: " << system_id << "\n"; + } else { + std::cout << " " + << "Could not find system id before test. "; + PrintTo(system_id_status, &std::cout); + std::cout << "\n"; + } + + CdmSessionId session_id; + ConfigTestEnv config = *WvCdmTestBase::default_config_; + + CdmResponseType status = cdm_engine.OpenSession(config.key_system(), nullptr, + nullptr, &session_id); + // If there is no keybox or there is no drm cert, we should get a + // NEED_PROVISIONING response. If there is a keybox and a drm cert, there + // should be no other error and this test is finished. + if (status != NEED_PROVISIONING) { + ASSERT_EQ(NO_ERROR, status); + std::cout << "Device does not need provisioning. Skipping rest of test.\n"; + return; + } + std::cout << "First provisioning process.\n"; + Provision(&cdm_engine); + system_id_status = + cdm_engine.QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id); + if (system_id_status == NO_ERROR) { + std::cout << " " + << "System ID after first provisioning: " << system_id << "\n"; + } else { + std::cout << " " + << "Could not find system id after first provisioning. "; + PrintTo(system_id_status, &std::cout); + std::cout << "\n"; + } + + // After the first provisioning pass, we try to open a session again. If the + // first provisioning was to install a keybox, this provisioning should be to + // install a drm cert. If the first provisioning step installed a drm cert, + // there should be no other error and this test is finished. + status = cdm_engine.OpenSession(config.key_system(), nullptr, nullptr, + &session_id); + if (status != NEED_PROVISIONING) { + ASSERT_EQ(NO_ERROR, status); + std::cout << "Device does not need provisioning. Skipping rest of test.\n"; + return; + } + std::cout << "Second provisioning process.\n"; + Provision(&cdm_engine); + system_id_status = + cdm_engine.QueryStatus(kLevelDefault, QUERY_KEY_SYSTEM_ID, &system_id); + if (system_id_status == NO_ERROR) { + std::cout << " " + << "System ID after second provisioning: " << system_id << "\n"; + } else { + std::cout << " " + << "Could not find system id after second provisioning. "; + PrintTo(system_id_status, &std::cout); + std::cout << "\n"; + } + + // After the second provisioning pass, we should be able to open a session + // and continue. + status = cdm_engine.OpenSession(config.key_system(), nullptr, nullptr, + &session_id); + ASSERT_EQ(NO_ERROR, status); + // Full recovery will be tested with higher level integration tests. +} +} // namespace wvcdm diff --git a/core/test/license_holder.cpp b/core/test/license_holder.cpp new file mode 100644 index 00000000..140029de --- /dev/null +++ b/core/test/license_holder.cpp @@ -0,0 +1,227 @@ +// 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 "license_holder.h" + +#include "license_request.h" +#include "oec_device_features.h" +#include "test_base.h" + +namespace wvcdm { +namespace { +constexpr int kHttpOk = 200; +const std::string kCencMimeType = "cenc"; +} // namespace + +LicenseHolder::~LicenseHolder() {} + +void LicenseHolder::OpenSession() { + CdmResponseType status = cdm_engine_->OpenSession( + config_.key_system(), nullptr, &event_listener_, &session_id_); + ASSERT_EQ(NO_ERROR, status); + ASSERT_TRUE(cdm_engine_->IsOpenSession(session_id_)); +} + +void LicenseHolder::CloseSession() { + CdmResponseType status = cdm_engine_->CloseSession(session_id_); + ASSERT_EQ(NO_ERROR, status); + ASSERT_FALSE(cdm_engine_->IsOpenSession(session_id_)); +} + +void LicenseHolder::FetchLicense() { + video_widevine::WidevinePsshData pssh; + pssh.set_content_id(content_id_); + const std::string init_data_string = MakePSSH(pssh); + const InitializationData init_data(kCencMimeType, init_data_string); + init_data.DumpToLogs(); + CdmKeyRequest key_request; + ASSERT_NO_FATAL_FAILURE(GenerateKeyRequest(init_data, &key_request)) + << "Failed for " << content_id(); + ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request)) + << "Failed for " << content_id(); +} + +void LicenseHolder::LoadLicense() { + CdmLicenseType license_type; + ASSERT_EQ(KEY_ADDED, cdm_engine_->AddKey(session_id_, key_response_, + &license_type, &key_set_id_)) + << "Failed to load license for " << content_id(); + if (can_persist_) { + ASSERT_EQ(license_type, kLicenseTypeOffline) + << "Failed for " << content_id(); + } else { + ASSERT_EQ(license_type, kLicenseTypeStreaming) + << "Failed for " << content_id(); + } +} + +void LicenseHolder::ReloadLicense() { + CdmResponseType status = cdm_engine_->RestoreKey(session_id_, key_set_id_); + ASSERT_EQ(KEY_ADDED, status) + << "Failed to reload license for " << content_id(); +} + +void LicenseHolder::GenerateAndPostRenewalRequest( + const std::string& policy_id) { + event_listener_.set_renewal_needed(false); + CdmKeyRequest request; + const CdmResponseType result = + cdm_engine_->GenerateRenewalRequest(session_id_, &request); + ASSERT_EQ(KEY_MESSAGE, result) << "Failed for " << content_id(); + const std::string url = MakeUrl(config_.renewal_server(), policy_id); + renewal_in_flight_.reset(new UrlRequest(url)); + ASSERT_TRUE(renewal_in_flight_->is_connected()) + << "Failed for " << content_id(); + renewal_in_flight_->PostRequest(request.message); +} +void LicenseHolder::FetchRenewal() { + ASSERT_NE(renewal_in_flight_, nullptr) << "Failed for " << content_id(); + ASSERT_TRUE(renewal_in_flight_->GetResponse(&renewal_response_)) + << "Failed for " << content_id(); + int status_code = renewal_in_flight_->GetStatusCode(renewal_response_); + ASSERT_EQ(kHttpOk, status_code) << "Failed for " << content_id(); +} + +void LicenseHolder::LoadRenewal() { + LicenseRequest license_request; + license_request.GetDrmMessage(renewal_response_, renewal_message_); + EXPECT_EQ(KEY_ADDED, cdm_engine_->RenewKey(session_id_, renewal_message_)) + << "Failed for " << content_id(); +} + +void LicenseHolder::RemoveLicense() { + EXPECT_EQ(NO_ERROR, cdm_engine_->RemoveLicense(session_id_)) + << "Failed for " << content_id(); +} + +CdmResponseType LicenseHolder::Decrypt(const std::string& key_id) { + constexpr size_t buffer_size = 500; + const std::vector input(buffer_size, 0); + std::vector output(buffer_size, 0); + const std::vector iv(KEY_IV_SIZE, 0); + CdmDecryptionParametersV16 params(key_id); + params.is_secure = false; + CdmDecryptionSample sample(input.data(), output.data(), 0, input.size(), iv); + CdmDecryptionSubsample subsample(0, input.size()); + sample.subsamples.push_back(subsample); + params.samples.push_back(sample); + return cdm_engine_->DecryptV16(session_id_, params); +} + +void LicenseHolder::DecryptSecure(const KeyId& key_id) { + ASSERT_TRUE(wvoec::global_features.test_secure_buffers); + constexpr size_t buffer_size = 500; + const std::vector input(buffer_size, 0); + const std::vector iv(KEY_IV_SIZE, 0); + + // To create a secure buffer, we need to know the OEMCrypto session id. + CdmQueryMap query_map; + cdm_engine_->QueryOemCryptoSessionId(session_id_, &query_map); + const std::string oec_session_id_string = + query_map[QUERY_KEY_OEMCRYPTO_SESSION_ID]; + uint32_t oec_session_id = std::stoi(oec_session_id_string); + + int secure_buffer_fid; + OEMCrypto_DestBufferDesc output_descriptor; + output_descriptor.type = OEMCrypto_BufferType_Secure; + output_descriptor.buffer.secure.secure_buffer_length = buffer_size; + ASSERT_EQ( + OEMCrypto_AllocateSecureBuffer(oec_session_id, buffer_size, + &output_descriptor, &secure_buffer_fid), + OEMCrypto_SUCCESS); + + ASSERT_NE(output_descriptor.buffer.secure.secure_buffer, nullptr); + // It is OK if OEMCrypto changes the maximum size, but there must + // still be enough room for our data. + ASSERT_GE(output_descriptor.buffer.secure.secure_buffer_length, buffer_size); + output_descriptor.buffer.secure.offset = 0; + + // Now create a sample array for the CDM layer. + CdmDecryptionParametersV16 params(key_id); + params.is_secure = true; + CdmDecryptionSample sample(input.data(), + output_descriptor.buffer.secure.secure_buffer, 0, + input.size(), iv); + CdmDecryptionSubsample subsample(0, input.size()); + sample.subsamples.push_back(subsample); + params.samples.push_back(sample); + CdmResponseType status = cdm_engine_->DecryptV16(session_id_, params); + + // Free the secure buffer before we check the return status. + EXPECT_EQ(OEMCrypto_FreeSecureBuffer(oec_session_id, &output_descriptor, + secure_buffer_fid), + OEMCrypto_SUCCESS); + + ASSERT_EQ(status, NO_ERROR); +} + +void LicenseHolder::FailDecrypt(const KeyId& key_id, + CdmResponseType expected_status) { + CdmResponseType status = Decrypt(key_id); + // If the server knows we cannot handle the key, it would not have given us + // the key. In that case, the status should indicate no key. + if (status != NEED_KEY) { + // Otherwise, we should have gotten the expected error. + ASSERT_EQ(expected_status, status); + } +} + +void LicenseHolder::GenerateKeyRequest(const InitializationData& init_data, + CdmKeyRequest* key_request) { + CdmAppParameterMap empty_app_parameters; + CdmKeySetId empty_key_set_id; + CdmLicenseType license_type = + can_persist_ ? kLicenseTypeOffline : kLicenseTypeStreaming; + CdmResponseType result = cdm_engine_->GenerateKeyRequest( + session_id_, empty_key_set_id, init_data, license_type, + empty_app_parameters, key_request); + ASSERT_EQ(KEY_MESSAGE, result); + ASSERT_EQ(kKeyRequestTypeInitial, key_request->type); +} + +std::string LicenseHolder::MakeUrl(const std::string& server_url, + const std::string& policy_id) { + // For tests, we want to specify the policy, but the UAT server only allows us + // to set the content id as the video_id. So each policy is matched to a + // single license with the same name. The local license server, on the other + // hand, wants to see the policy id in the url. So we have to guess which + // format to use based on the name of the server. + const std::string path = server_url + config_.client_auth(); + std::string video_query; + if (!policy_id.empty()) { + if (path.find("proxy.uat") != std::string::npos) { + // This is uat or uat-nightly. Set the video_id. + video_query = "video_id=" + policy_id; + } else { + // This is probably a local license server. Set the policy. + video_query = "policy=" + policy_id; + } + // If there is already a parameter, then we don't need to add another + // question mark. + return path + ((path.find("?") == std::string::npos) ? "?" : "&") + + video_query; + } else { + return path; + } +} + +void LicenseHolder::GetKeyResponse(const CdmKeyRequest& key_request) { + // The content id matches the policy id used on UAT. + const std::string url = MakeUrl(config_.license_server(), ""); + UrlRequest url_request(url); + ASSERT_TRUE(url_request.is_connected()); + + std::string http_response; + url_request.PostRequest(key_request.message); + ASSERT_TRUE(url_request.GetResponse(&http_response)); + int status_code = url_request.GetStatusCode(http_response); + ASSERT_EQ(kHttpOk, status_code) << "Error with url = " << url << "\n" + << "content_id = " << content_id() << "\n" + << "response = " << http_response; + + LicenseRequest license_request; + license_request.GetDrmMessage(http_response, key_response_); +} + +} // namespace wvcdm diff --git a/core/test/license_holder.h b/core/test/license_holder.h new file mode 100644 index 00000000..eb1e5e37 --- /dev/null +++ b/core/test/license_holder.h @@ -0,0 +1,133 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +#ifndef WVCDM_CORE_TEST_LICENSE_HOLDER_H_ +#define WVCDM_CORE_TEST_LICENSE_HOLDER_H_ + +#include +#include + +#include + +#include "cdm_engine.h" +#include "config_test_env.h" +#include "url_request.h" +#include "wv_attributes.h" +#include "wv_cdm_event_listener.h" + +#include "log.h" + +namespace wvcdm { + +// An event listener to tell when a renewal is needed. +class SimpleEventListener : public wvcdm::WvCdmEventListener { + public: + SimpleEventListener() { renewal_needed_ = false; } + // We will want to know when a renewal is needed. + void OnSessionRenewalNeeded(const CdmSessionId& /*session*/) override { + renewal_needed_ = true; + } + void OnSessionKeysChange(const CdmSessionId&, const CdmKeyStatusMap&, + bool) override {} + void OnExpirationUpdate(const CdmSessionId&, + int64_t expiry_time UNUSED) override {} + bool renewal_needed() { return renewal_needed_; } + void set_renewal_needed(bool renewal_needed) { + renewal_needed_ = renewal_needed; + } + + private: + bool renewal_needed_ = false; +}; + +class LicenseHolder { + public: + // Set up a new license holder with the specified content id. + LicenseHolder(const std::string& content_id, CdmEngine* cdm_engine, + const ConfigTestEnv& config) + : content_id_(content_id), cdm_engine_(cdm_engine), config_(config) {} + ~LicenseHolder(); + // Open a session for the license. Must be called before FetchLicense or + // ReloadLicense. + void OpenSession(); + // Close the session and release resources. + void CloseSession(); + // Generate a license request, send it to the license server, and wait for the + // response. The response is *not* loaded into the cdm engine. + void FetchLicense(); + // Load the license response into the CDM engine. A call to FetchLicense() + // must be made first. + void LoadLicense(); + // Reload the license. Call OpenSession() before calling + // ReloadLicense(). Also, the key_set_id must have been set previously. The + // key_set_id is set by calling LoadLicense(), or by calling set_key_set_id(). + void ReloadLicense(); + // Generate the renewal request, and send it to the server. + void GenerateAndPostRenewalRequest(const std::string& policy_id); + // Fetch the renewal response. This can add a few seconds of latency. + void FetchRenewal(); + // Load the renewal response that was fetched in FetchRenewal(). + void LoadRenewal(); + // Releases the license and frees up entry in usage table. + void RemoveLicense(); + + // Try to decrypt some random data. This does not verify that the data is + // decrypted correctly. Returns the result of the decrypt operation. + CdmResponseType Decrypt(const std::string& key_id); + // Try to decrypt some random data to a secure buffer. If the test harness + // does not allow creating a secure buffer, then this function fails + // immediately. Otherwise, a secure buffer is created and used for a + // decryption operation. + void DecryptSecure(const KeyId& key_id); + // Try to decrypt some random data, but expect failure. The failure may + // be either the expected_status, or NEED_KEY. We allow NEED_KEY in case + // the server recognized that we cannot support the given key. + void FailDecrypt(const KeyId& key_id, CdmResponseType expected_status); + + const std::string& content_id() const { return content_id_; } + void set_content_id(const std::string& content_id) { + content_id_ = content_id; + } + // The session id. This is only valid after a call to OpenSession. + const std::string& session_id() { return session_id_; } + // Returns true if the license is offline. + bool can_persist() const { return can_persist_; } + // Sets whether the license is offline or not. + void set_can_persist(bool can_persist) { can_persist_ = can_persist; } + uint64_t start_of_rental_clock() const { return start_of_rental_clock_; } + const std::string& key_set_id() const { return key_set_id_; } + void set_key_set_id(const std::string& key_set_id) { + key_set_id_ = key_set_id; + } + SimpleEventListener& event_listener() { return event_listener_; } + + private: + std::string content_id_; + CdmSessionId session_id_; + CdmKeySetId key_set_id_; + std::string key_response_; + bool can_persist_ = false; + uint64_t start_of_rental_clock_ = 0u; + CdmEngine* cdm_engine_ = nullptr; + const ConfigTestEnv& config_; + SimpleEventListener event_listener_; + std::unique_ptr renewal_in_flight_; + std::string renewal_message_; + std::string renewal_response_; + + // Generate the license request. + void GenerateKeyRequest(const InitializationData& init_data, + CdmKeyRequest* key_request); + + // Generate a URL for the specified policy. The license request should be sent + // to this url. + std::string MakeUrl(const std::string& server_url, + const std::string& policy_id); + // Fetch the key response from the server. + void GetKeyResponse(const CdmKeyRequest& key_request); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_TEST_LICENSE_HOLDER_H_ diff --git a/core/test/license_keys_unittest.cpp b/core/test/license_keys_unittest.cpp index 5c08bd11..8a8f7b91 100644 --- a/core/test/license_keys_unittest.cpp +++ b/core/test/license_keys_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include #include @@ -201,8 +201,6 @@ class LicenseKeysTest : public ::testing::Test { EXPECT_EQ(key_usage.generic_verify, verify == kKeyFlagTrue); } - virtual int NumContentKeys() { return content_key_count_; } - virtual void StageContentKeys() { content_key_count_ = 0; AddContentKey(ck_sw_crypto, true, KeyContainer::SW_SECURE_CRYPTO); @@ -1440,9 +1438,9 @@ TEST_P(LicenseKeysSecurityLevelConstraintsTest, KeyStatusChange) { ExpectKeyStatusesEqual(key_status_map, kKeyStatusExpired); } -INSTANTIATE_TEST_CASE_P(KeyStatusChange, - LicenseKeysSecurityLevelConstraintsTest, - ::testing::Range(&key_security_level_test_vectors[0], - &key_security_level_test_vectors[5])); +INSTANTIATE_TEST_SUITE_P(KeyStatusChange, + LicenseKeysSecurityLevelConstraintsTest, + ::testing::Range(&key_security_level_test_vectors[0], + &key_security_level_test_vectors[5])); } // namespace wvcdm diff --git a/core/test/license_request.cpp b/core/test/license_request.cpp index 978c66a2..7403b21c 100644 --- a/core/test/license_request.cpp +++ b/core/test/license_request.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "license_request.h" #include "log.h" diff --git a/core/test/license_request.h b/core/test/license_request.h index 6188d8e4..37b42883 100644 --- a/core/test/license_request.h +++ b/core/test/license_request.h @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef CDM_TEST_LICENSE_REQUEST_H_ #define CDM_TEST_LICENSE_REQUEST_H_ diff --git a/core/test/license_unittest.cpp b/core/test/license_unittest.cpp index e311e747..c1acaa73 100644 --- a/core/test/license_unittest.cpp +++ b/core/test/license_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include #include @@ -24,6 +24,7 @@ namespace wvcdm { namespace { +using wvutil::a2bs_hex; const std::string kEmptyString; const std::string kAesKey = a2bs_hex("000102030405060708090a0b0c0d0e0f"); @@ -116,7 +117,10 @@ const std::string kLicenseRequestSignature = a2bs_hex( const std::string kFakeCoreMessage = a2bs_hex("DEADBEEF"); const CryptoSession::SupportedCertificateTypes kDefaultSupportedCertTypes = { - true, true, true}; + /* RSA 2048 */ true, /* RSA 3072 */ true, + /* RSA CAST */ false, + /* ECC 256 */ true, /* ECC 384 */ true, + /* ECC 521 */ true}; const std::string kFakeEntitlementKeyId = a2bs_hex("2a538231c616c67143032a645f9c545d"); @@ -131,46 +135,48 @@ const std::string kFakeKeyTooLong = a2bs_hex("d4bc8605d662878a46adb2adb6bf3c0b30a54a0c2f"); const std::string kFakeKeyTooShort = a2bs_hex("06e247e7f924208011"); const std::string kFakeIv = a2bs_hex("3d515a3ee0be1687080ac59da9e0d69a"); +const std::string kFakeBuildInfo = "Mock Crypto Session - License Test"; class MockCryptoSession : public TestCryptoSession { public: MockCryptoSession(metrics::CryptoMetrics* crypto_metrics) : TestCryptoSession(crypto_metrics) {} - MOCK_METHOD0(IsOpen, bool()); - MOCK_METHOD0(request_id, const std::string&()); - MOCK_METHOD1(UsageInformationSupport, bool(bool*)); - MOCK_METHOD2(GetHdcpCapabilities, - CdmResponseType(HdcpCapability*, HdcpCapability*)); - MOCK_METHOD1(GetSupportedCertificateTypes, bool(SupportedCertificateTypes*)); - MOCK_METHOD1(GetApiVersion, bool(uint32_t*)); - MOCK_METHOD1(GenerateNonce, CdmResponseType(uint32_t*)); - MOCK_METHOD3(PrepareAndSignLicenseRequest, - CdmResponseType(const std::string&, std::string*, std::string*)); - MOCK_METHOD1(LoadEntitledContentKeys, - CdmResponseType(const std::vector& key_array)); - MOCK_METHOD1(GetResourceRatingTier, bool(uint32_t*)); + MOCK_METHOD(bool, IsOpen, (), (override)); + MOCK_METHOD(const std::string&, request_id, (), (override)); + MOCK_METHOD(bool, HasUsageInfoSupport, (bool*), (override)); + MOCK_METHOD(CdmResponseType, GetHdcpCapabilities, + (HdcpCapability*, HdcpCapability*), (override)); + MOCK_METHOD(bool, GetSupportedCertificateTypes, (SupportedCertificateTypes*), + (override)); + MOCK_METHOD(bool, GetApiVersion, (uint32_t*), (override)); + MOCK_METHOD(CdmResponseType, GenerateNonce, (uint32_t*), (override)); + MOCK_METHOD(CdmResponseType, PrepareAndSignLicenseRequest, + (const std::string&, std::string*, std::string*), (override)); + MOCK_METHOD(CdmResponseType, LoadEntitledContentKeys, + (const std::vector& key_array), (override)); + MOCK_METHOD(bool, GetResourceRatingTier, (uint32_t*), (override)); + MOCK_METHOD(bool, GetWatermarkingSupport, (CdmWatermarkingSupport*), + (override)); + MOCK_METHOD(bool, GetBuildInformation, (std::string*), (override)); + + CdmSecurityLevel GetSecurityLevel() override { return kSecurityLevelL1; } }; class MockPolicyEngine : public PolicyEngine { public: MockPolicyEngine(CryptoSession* crypto) : PolicyEngine("mock_session_id", nullptr, crypto) {} - MOCK_METHOD1( - SetEntitledLicenseKeys, - void(const std::vector&)); -}; - -class MockInitializationData : public InitializationData { - public: - MockInitializationData(const std::string& type, const CdmInitData& data) - : InitializationData(type, data) {} - MOCK_METHOD0(is_supported, bool()); - MOCK_METHOD0(is_cenc, bool()); + MOCK_METHOD( + void, SetEntitledLicenseKeys, + (const std::vector&), + (override)); }; } // namespace // Protobuf generated classes +using ClientCapabilities = + video_widevine::ClientIdentification::ClientCapabilities; using video_widevine::ClientIdentification; using video_widevine::License; using video_widevine::License_KeyContainer; @@ -193,7 +199,7 @@ using ::testing::Values; class CdmLicenseTestPeer : public CdmLicense { public: - CdmLicenseTestPeer(const CdmSessionId& session_id, Clock* clock) + CdmLicenseTestPeer(const CdmSessionId& session_id, wvutil::Clock* clock) : CdmLicense(session_id, clock) {} using CdmLicense::HandleNewEntitledKeys; @@ -211,7 +217,7 @@ class CdmLicenseTest : public WvCdmTestBase { WvCdmTestBase::SetUp(); clock_ = new MockClock(); crypto_session_ = new MockCryptoSession(&crypto_metrics_); - init_data_ = new MockInitializationData(CENC_INIT_DATA_FORMAT, pssh_); + init_data_ = new InitializationData(CENC_INIT_DATA_FORMAT, pssh_); policy_engine_ = new MockPolicyEngine(crypto_session_); ON_CALL(*crypto_session_, GetSupportedCertificateTypes(NotNull())) @@ -232,12 +238,12 @@ class CdmLicenseTest : public WvCdmTestBase { clock_ = nullptr; } - CdmLicenseTestPeer* cdm_license_; - MockClock* clock_; + CdmLicenseTestPeer* cdm_license_ = nullptr; + MockClock* clock_ = nullptr; metrics::CryptoMetrics crypto_metrics_; - MockCryptoSession* crypto_session_; - MockInitializationData* init_data_; - MockPolicyEngine* policy_engine_; + MockCryptoSession* crypto_session_ = nullptr; + InitializationData* init_data_ = nullptr; + MockPolicyEngine* policy_engine_ = nullptr; std::string pssh_; }; @@ -245,22 +251,13 @@ TEST_F(CdmLicenseTest, InitSuccess) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); CreateCdmLicense(); - EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, kEmptyString, - false, kEmptyServiceCertificate, + EXPECT_TRUE(cdm_license_->Init(false, kEmptyServiceCertificate, crypto_session_, policy_engine_)); } -TEST_F(CdmLicenseTest, InitFail_EmptyToken) { - CreateCdmLicense(); - EXPECT_FALSE(cdm_license_->Init("", kClientTokenDrmCert, "", false, - kEmptyServiceCertificate, crypto_session_, - policy_engine_)); -} - TEST_F(CdmLicenseTest, InitFail_CryptoSessionNull) { CreateCdmLicense(); - EXPECT_FALSE(cdm_license_->Init(kToken, kClientTokenDrmCert, "", false, - kEmptyServiceCertificate, nullptr, + EXPECT_FALSE(cdm_license_->Init(false, kEmptyServiceCertificate, nullptr, policy_engine_)); } @@ -268,58 +265,57 @@ TEST_F(CdmLicenseTest, InitFail_PolicyEngineNull) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); CreateCdmLicense(); - EXPECT_FALSE(cdm_license_->Init(kToken, kClientTokenDrmCert, "", false, - kEmptyServiceCertificate, crypto_session_, - nullptr)); + EXPECT_FALSE(cdm_license_->Init(false, kEmptyServiceCertificate, + crypto_session_, nullptr)); } TEST_F(CdmLicenseTest, InitWithEmptyServiceCert) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); CreateCdmLicense(); - EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, "", true, - kEmptyServiceCertificate, crypto_session_, - policy_engine_)); + EXPECT_TRUE(cdm_license_->Init(true, kEmptyServiceCertificate, + crypto_session_, policy_engine_)); } TEST_F(CdmLicenseTest, InitWithInvalidServiceCert) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); CreateCdmLicense(); - EXPECT_FALSE(cdm_license_->Init(kToken, kClientTokenDrmCert, "", true, - kInvalidServiceCertificate, crypto_session_, - policy_engine_)); + EXPECT_FALSE(cdm_license_->Init(true, kInvalidServiceCertificate, + crypto_session_, policy_engine_)); } TEST_F(CdmLicenseTest, InitWithServiceCert) { EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); CreateCdmLicense(); - EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, "", true, - kDefaultServiceCertificate, crypto_session_, - policy_engine_)); + EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, + crypto_session_, policy_engine_)); } TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { bool usage_information_support = true; CryptoSession::HdcpCapability current_hdcp_version = HDCP_NO_DIGITAL_OUTPUT; CryptoSession::HdcpCapability max_hdcp_version = HDCP_V2_1; - uint32_t crypto_session_api_version = 9; + const uint32_t crypto_session_api_version = 16; + const uint32_t resource_rating_tier = RESOURCE_RATING_TIER_LOW; EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); EXPECT_CALL(*crypto_session_, request_id()) .WillOnce(ReturnRef(kCryptoRequestId)); - EXPECT_CALL(*crypto_session_, UsageInformationSupport(NotNull())) + EXPECT_CALL(*crypto_session_, HasUsageInfoSupport(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(usage_information_support), Return(true))); EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(NotNull(), NotNull())) .WillOnce(DoAll(SetArgPointee<0>(current_hdcp_version), SetArgPointee<1>(max_hdcp_version), Return(NO_ERROR))); + // Supported certificates set by SetUp(). EXPECT_CALL(*crypto_session_, GetSupportedCertificateTypes(NotNull())); EXPECT_CALL(*crypto_session_, GetApiVersion(NotNull())) - .Times(2) - .WillRepeatedly( + .WillOnce( DoAll(SetArgPointee<0>(crypto_session_api_version), Return(true))); + EXPECT_CALL(*crypto_session_, GetResourceRatingTier(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(resource_rating_tier), Return(true))); EXPECT_CALL(*clock_, GetCurrentTime()).WillOnce(Return(kLicenseStartTime)); EXPECT_CALL(*crypto_session_, GenerateNonce(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(kNonce), Return(NO_ERROR))); @@ -328,18 +324,22 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { .WillOnce(DoAll(SetArgPointee<1>(kFakeCoreMessage), SetArgPointee<2>(kLicenseRequestSignature), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, GetBuildInformation(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kFakeBuildInfo), Return(true))); + EXPECT_CALL(*crypto_session_, GetWatermarkingSupport(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kWatermarkingConfigurable), Return(true))); CreateCdmLicense(); - EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, kEmptyString, - true, kDefaultServiceCertificate, + EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, crypto_session_, policy_engine_)); CdmAppParameterMap app_parameters; CdmKeyMessage signed_request; std::string server_url; - EXPECT_EQ(cdm_license_->PrepareKeyRequest(*init_data_, kLicenseTypeStreaming, - app_parameters, &signed_request, - &server_url), + EXPECT_EQ(cdm_license_->PrepareKeyRequest( + *init_data_, kToken, kLicenseTypeStreaming, app_parameters, + &signed_request, &server_url), KEY_MESSAGE); EXPECT_TRUE(!signed_request.empty()); @@ -379,25 +379,25 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { EXPECT_FALSE(client_id.has_provider_client_token()); EXPECT_FALSE(client_id.has_license_counter()); - const ::video_widevine::ClientIdentification_ClientCapabilities& - client_capabilities = client_id.client_capabilities(); + const ClientCapabilities& client_capabilities = + client_id.client_capabilities(); EXPECT_TRUE(client_capabilities.has_client_token()); EXPECT_TRUE(client_capabilities.has_session_token()); EXPECT_FALSE(client_capabilities.video_resolution_constraints()); - EXPECT_EQ(video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1, + EXPECT_EQ(ClientCapabilities::HDCP_V2_1, client_capabilities.max_hdcp_version()); EXPECT_EQ(crypto_session_api_version, client_capabilities.oem_crypto_api_version()); - EXPECT_THAT( - client_capabilities.supported_certificate_key_type(), - UnorderedElementsAre( - video_widevine:: - ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048, - video_widevine:: - ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072)); - EXPECT_FALSE(client_capabilities.has_resource_rating_tier()); + EXPECT_THAT(client_capabilities.supported_certificate_key_type(), + UnorderedElementsAre(ClientCapabilities::RSA_2048, + ClientCapabilities::RSA_3072, + ClientCapabilities::ECC_SECP256R1, + ClientCapabilities::ECC_SECP384R1, + ClientCapabilities::ECC_SECP521R1)); + EXPECT_TRUE(client_capabilities.has_resource_rating_tier()); + EXPECT_EQ(client_capabilities.watermarking_support(), + ClientCapabilities::WATERMARKING_CONFIGURABLE); // Verify Content Identification const LicenseRequest_ContentIdentification& content_id = @@ -425,16 +425,17 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) { } TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { - bool usage_information_support = true; - CryptoSession::HdcpCapability current_hdcp_version = HDCP_NO_DIGITAL_OUTPUT; - CryptoSession::HdcpCapability max_hdcp_version = HDCP_V2_1; - uint32_t crypto_session_api_version = 15; - uint32_t resource_rating_tier = RESOURCE_RATING_TIER_LOW; + const bool usage_information_support = true; + const CryptoSession::HdcpCapability current_hdcp_version = + HDCP_NO_DIGITAL_OUTPUT; + const CryptoSession::HdcpCapability max_hdcp_version = HDCP_V2_1; + const uint32_t crypto_session_api_version = 15; + const uint32_t resource_rating_tier = RESOURCE_RATING_TIER_LOW; EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true)); EXPECT_CALL(*crypto_session_, request_id()) .WillOnce(ReturnRef(kCryptoRequestId)); - EXPECT_CALL(*crypto_session_, UsageInformationSupport(NotNull())) + EXPECT_CALL(*crypto_session_, HasUsageInfoSupport(NotNull())) .WillOnce( DoAll(SetArgPointee<0>(usage_information_support), Return(true))); EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(NotNull(), NotNull())) @@ -442,8 +443,7 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { SetArgPointee<1>(max_hdcp_version), Return(NO_ERROR))); EXPECT_CALL(*crypto_session_, GetSupportedCertificateTypes(NotNull())); EXPECT_CALL(*crypto_session_, GetApiVersion(NotNull())) - .Times(2) - .WillRepeatedly( + .WillOnce( DoAll(SetArgPointee<0>(crypto_session_api_version), Return(true))); EXPECT_CALL(*crypto_session_, GetResourceRatingTier(NotNull())) .WillOnce(DoAll(SetArgPointee<0>(resource_rating_tier), Return(true))); @@ -455,18 +455,22 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { .WillOnce(DoAll(SetArgPointee<1>(kFakeCoreMessage), SetArgPointee<2>(kLicenseRequestSignature), Return(NO_ERROR))); + EXPECT_CALL(*crypto_session_, GetBuildInformation(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kFakeBuildInfo), Return(true))); + EXPECT_CALL(*crypto_session_, GetWatermarkingSupport(NotNull())) + .WillOnce( + DoAll(SetArgPointee<0>(kWatermarkingNotSupported), Return(true))); CreateCdmLicense(); - EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, kEmptyString, - true, kDefaultServiceCertificate, + EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, crypto_session_, policy_engine_)); CdmAppParameterMap app_parameters; CdmKeyMessage signed_request; std::string server_url; - EXPECT_EQ(cdm_license_->PrepareKeyRequest(*init_data_, kLicenseTypeStreaming, - app_parameters, &signed_request, - &server_url), + EXPECT_EQ(cdm_license_->PrepareKeyRequest( + *init_data_, kToken, kLicenseTypeStreaming, app_parameters, + &signed_request, &server_url), KEY_MESSAGE); EXPECT_TRUE(!signed_request.empty()); @@ -506,25 +510,25 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { EXPECT_FALSE(client_id.has_provider_client_token()); EXPECT_FALSE(client_id.has_license_counter()); - const ::video_widevine::ClientIdentification_ClientCapabilities& - client_capabilities = client_id.client_capabilities(); + const ClientCapabilities& client_capabilities = + client_id.client_capabilities(); EXPECT_TRUE(client_capabilities.has_client_token()); EXPECT_TRUE(client_capabilities.has_session_token()); EXPECT_FALSE(client_capabilities.video_resolution_constraints()); - EXPECT_EQ(video_widevine:: - ClientIdentification_ClientCapabilities_HdcpVersion_HDCP_V2_1, + EXPECT_EQ(ClientCapabilities::HDCP_V2_1, client_capabilities.max_hdcp_version()); EXPECT_EQ(crypto_session_api_version, client_capabilities.oem_crypto_api_version()); - EXPECT_THAT( - client_capabilities.supported_certificate_key_type(), - UnorderedElementsAre( - video_widevine:: - ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_2048, - video_widevine:: - ClientIdentification_ClientCapabilities_CertificateKeyType_RSA_3072)); + EXPECT_THAT(client_capabilities.supported_certificate_key_type(), + UnorderedElementsAre(ClientCapabilities::RSA_2048, + ClientCapabilities::RSA_3072, + ClientCapabilities::ECC_SECP256R1, + ClientCapabilities::ECC_SECP384R1, + ClientCapabilities::ECC_SECP521R1)); EXPECT_EQ(resource_rating_tier, client_capabilities.resource_rating_tier()); + EXPECT_EQ(client_capabilities.watermarking_support(), + ClientCapabilities::WATERMARKING_NOT_SUPPORTED); // Verify Content Identification const LicenseRequest_ContentIdentification& content_id = @@ -552,9 +556,12 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidationV15) { } struct EntitledKeyVariant { - EntitledKeyVariant(const char* name, const std::string& key, - bool should_succeed) - : name(name), key(key), should_succeed(should_succeed) {} + EntitledKeyVariant(const char* name_param, const std::string& key_param, + bool should_succeed_param) + : name(name_param), + key(key_param), + should_succeed(should_succeed_param) {} + const std::string name; const std::string key; const bool should_succeed; @@ -600,8 +607,7 @@ TEST_P(CdmLicenseEntitledKeyTest, LoadsEntitledKeys) { // Set up the CdmLicense with the mocks and fake entitlement key CreateCdmLicense(); - EXPECT_TRUE(cdm_license_->Init(kToken, kClientTokenDrmCert, kEmptyString, - true, kDefaultServiceCertificate, + EXPECT_TRUE(cdm_license_->Init(true, kDefaultServiceCertificate, crypto_session_, policy_engine_)); cdm_license_->set_entitlement_keys(entitlement_license); @@ -615,7 +621,7 @@ TEST_P(CdmLicenseEntitledKeyTest, LoadsEntitledKeys) { } } -INSTANTIATE_TEST_CASE_P( +INSTANTIATE_TEST_SUITE_P( EntitledKeyTests, CdmLicenseEntitledKeyTest, Values(EntitledKeyVariant("UnpaddedKey", kFakeUnpaddedKey, true), EntitledKeyVariant("PaddedKey", kFakePaddedKey, true), diff --git a/core/test/mock_clock.h b/core/test/mock_clock.h index 98aae348..d068c0e8 100644 --- a/core/test/mock_clock.h +++ b/core/test/mock_clock.h @@ -1,24 +1,25 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #ifndef CDM_TEST_MOCK_CLOCK_H_ #define CDM_TEST_MOCK_CLOCK_H_ -#include "clock.h" #include +#include "clock.h" + namespace wvcdm { -class MockClock : public Clock { +class MockClock : public wvutil::Clock { public: - MOCK_METHOD0(GetCurrentTime, int64_t()); + MOCK_METHOD(int64_t, GetCurrentTime, (), (override)); }; // Frozen clock will always return the same value for the current time. // Intended to be used for testing where using the actual time would // cause flaky tests. -class FrozenClock : public Clock { +class FrozenClock : public wvutil::Clock { int64_t always_time_; public: @@ -27,6 +28,6 @@ class FrozenClock : public Clock { void SetTime(int64_t new_time) { always_time_ = new_time; } }; -} // wvcdm +} // namespace wvcdm #endif // CDM_TEST_MOCK_CLOCK_H_ diff --git a/core/test/okp_fallback_policy_test.cpp b/core/test/okp_fallback_policy_test.cpp new file mode 100644 index 00000000..ba6dddbf --- /dev/null +++ b/core/test/okp_fallback_policy_test.cpp @@ -0,0 +1,477 @@ +// 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 "okp_fallback_policy.h" + +#include + +#include + +#include "mock_clock.h" +#include "test_printers.h" + +namespace wvcdm { +namespace okp { +namespace { +// Thu 29 Jul 2021 10:43:21 PM UTC +constexpr int64_t kInitialTime = 1627598601; + +void SetSystemInfoAsProvisioned(SystemFallbackInfo* info, + int64_t provisioning_time) { + info->SetState(SystemState::kProvisioned); + if (!info->HasFirstCheckedTime()) { + info->SetFirstCheckedTime(kInitialTime); + } + info->ClearBackoffStartTime(); + info->SetProvisioningTime(provisioning_time); +} + +void SetSystemInfoAsFallback(SystemFallbackInfo* info, + int64_t backoff_start_time, + int64_t backoff_duration) { + info->SetState(SystemState::kFallbackMode); + if (!info->HasFirstCheckedTime()) { + info->SetFirstCheckedTime(kInitialTime); + } + info->SetBackoffStartTime(backoff_start_time); + info->SetBackoffDuration(backoff_duration); + info->ClearProvisioningTime(); +} + +class OkpFallbackPolicyTest : public ::testing::Test { + protected: + void SetUp() override { + clock_.SetTime(kInitialTime); + system_policy_ = SystemFallbackPolicy::CreateForTesting(&clock_); + ASSERT_TRUE(system_policy_); + system_policy_->MarkNeedsProvisioning(); + } + + void SetUpWithInfo(const SystemFallbackInfo& info) { + TearDown(); + system_policy_ = SystemFallbackPolicy::CreateForTesting(info, &clock_); + } + + void TearDown() override { system_policy_.reset(); } + + FrozenClock clock_; + std::unique_ptr system_policy_; +}; // class OkpFallbackPolicyTest +} // namespace + +// Test ensures that test fixture is initialized correctly. +TEST_F(OkpFallbackPolicyTest, Initialization) { + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kInitialTime, system_policy_->info().first_checked_time()); + EXPECT_EQ(SystemState::kNeedsProvisioning, system_policy_->state()); +} + +// Setup: +// 1) Device needs OKP +// 2) Some CDM engine successfully provisions, and marks the fallback +// policy as provisioned. +// Expectation: +// Policy is marked as provisioned and timestamp is updated. +TEST_F(OkpFallbackPolicyTest, SuccessfulProvisioning) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // Final checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device state is restored and is already provisioned +// Expectation: +// Fallback policy should still be marked as provisioned. +TEST_F(OkpFallbackPolicyTest, Restore_DeviceProvisioned) { + SystemFallbackInfo info; + SetSystemInfoAsProvisioned(&info, kInitialTime); + // Reinitialize. + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} + +// Setup: +// 1) Device needs OKP +// 2) App attempts OKP, but response fails and triggers fallback. +// Expectation: +// Policy should indicate fallback, and update info. +TEST_F(OkpFallbackPolicyTest, TriggerFallback) { + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kInitialTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFallbackTime); + EXPECT_GE(system_policy_->info().backoff_duration(), + kMinInitialBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app successfully provisions device +// 4) The second app succeeds (see notes) shortly after +// Expectation: +// The time of the first provisioning should be recorded, the +// second should not. +// Note: +// The CDM should silently drop responses which arrive after +// the first successful response. The fallback policy is agnostic +// to the exact mechanism to enforce this behavor. +TEST_F(OkpFallbackPolicyTest, TwoSuccessfulProvisionings) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kFirstProvisioningTime = kInitialTime + 10; + clock_.SetTime(kFirstProvisioningTime); + system_policy_->MarkProvisioned(); + + // App calls provideProvisionResponse() and fails. + constexpr int64_t kSecondProvisioningTime = kFirstProvisioningTime + 10; + clock_.SetTime(kSecondProvisioningTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kFirstProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app successfully provisions device +// 4) The second app fails, and triggers fallback +// Expectation: +// Once provisioned, fallback policy should remain provisioned. +// Note: +// In this case, the second engine should check if provisioned +// before triggering its own L3 fallback. +TEST_F(OkpFallbackPolicyTest, TriggerFallbackAfterProvisioning) { + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kProvisioningTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); + EXPECT_FALSE(system_policy_->info().HasBackoffStartTime()); + EXPECT_FALSE(system_policy_->info().HasBackoffDuration()); +} + +// Setup: +// 1) Device needs OKP +// 2) Two apps are performing provisioning simultaneously +// 3) The first app fails, and triggers fallback +// 4) The seoncd app successfully provisions device +// Expectation: +// Fallback policy should indicate that the device is provisioned; +// overriding the fallback. +// Note: +// Depending on the exact timing, the first app may or may not fallback +// to L3 for the remainder of the apps life cycle. If the app did +// fallback to L3, it will be able to resume use of L1 when it restarts. +TEST_F(OkpFallbackPolicyTest, ProvisionAfterFallback) { + // App calls provideProvisionResponse() and fails. + constexpr int64_t kFallbackTime = kInitialTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + + // App calls provideProvisionResponse() and succeeds. + constexpr int64_t kProvisioningTime = kFallbackTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + + // Checks. + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); + EXPECT_FALSE(system_policy_->info().HasBackoffStartTime()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, not in fallback mode +// 3) Provisioning is attempted +// Expectation: +// Policy should resume, allowing provisioning. +TEST_F(OkpFallbackPolicyTest, Restore_NeedsProvisioning) { + SystemFallbackInfo info; + info.SetState(SystemState::kNeedsProvisioning); + info.SetFirstCheckedTime(kInitialTime); + constexpr int64_t kRestoreTime = kInitialTime + 10; + clock_.SetTime(kRestoreTime); + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().first_checked_time(), kInitialTime); + // Provision + constexpr int64_t kProvisioningTime = kRestoreTime + 10; + clock_.SetTime(kProvisioningTime); + system_policy_->MarkProvisioned(); + // Checks + EXPECT_TRUE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(kProvisioningTime, system_policy_->info().provisioning_time()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Device is still in "backoff period" +// Expectation: +// Fallback policy should continue to be in fallback mode after restore. +TEST_F(OkpFallbackPolicyTest, Restore_InBackoffPeriod) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration / 2); + clock_.SetTime(kRestoreTime); // Within backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Backoff period is over +// Expectation: +// Fallback policy should no longer be in fallback mode. +TEST_F(OkpFallbackPolicyTest, Restore_AfterBackoffPeriod) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration * 2); + clock_.SetTime(kRestoreTime); // After backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + // Backoff duration should still be recorded as it will be required + // for exponential backoff. + EXPECT_EQ(system_policy_->info().backoff_duration(), kBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Device system clock has been rewound to a time before backoff started +// Expectation: +// Restore should be successful, fallback may be canceled, provisioning +// should still be needed. +// Note: +// This is not an expected circumstance, but may occur if a user changes +// their devices system time. +TEST_F(OkpFallbackPolicyTest, Restore_InBackoffPeriod_SystemTimeRollback) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime - 250; + clock_.SetTime(kRestoreTime); // Before backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback policy is restored, but had previously entered fallback mode +// 3) Backoff period is over +// 4) OKP fails again, tiggering fallback again +// Expectation: +// Fallback policy should re-enter fallback mode, with a backoff period +// double that of before. +TEST_F(OkpFallbackPolicyTest, Restore_AfterBackoff_FallbackAgain) { + SystemFallbackInfo info; + constexpr int64_t kBackoffDuration = 100; + constexpr int64_t kFallbackTime = kInitialTime + 10; + SetSystemInfoAsFallback(&info, kFallbackTime, kBackoffDuration); + constexpr int64_t kRestoreTime = kFallbackTime + (kBackoffDuration * 2); + clock_.SetTime(kRestoreTime); // After backoff period. + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + ASSERT_FALSE(system_policy_->IsInFallbackMode()); + + // Trigger second fallback. + constexpr int64_t kSecondFallbackTime = kRestoreTime + 10; + clock_.SetTime(kSecondFallbackTime); + system_policy_->TriggerFallback(); + + constexpr int64_t kExpectedBackoffDuration = kBackoffDuration * 2; + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kSecondFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), + kExpectedBackoffDuration); +} + +// Setup: +// 1) Device needs OKP again (see notes) +// 2) Fallback policy is restored, previously being marked as provisioned +// 3) Fallback policy is marked as needing provisioning +// Expectation: +// Once marked as needing re-provisioning, the fallback policy should +// clear is self and indicate that provisioning is needed. +// Note: +// Ideally this should never happen. Once a device is provisioned, is +// should never require OKP again. However, the device should have never +// required OKP in the first place. If a future bug arises, and a device +// requires OKP again, the fallback policy should be able to handle +// this situation without bricking L1. +TEST_F(OkpFallbackPolicyTest, Restore_NeedsProvisioningAgain) { + SystemFallbackInfo info; + constexpr int64_t kInitialProvisioningTime = kInitialTime; + SetSystemInfoAsProvisioned(&info, kInitialProvisioningTime); + constexpr int64_t kRestoreTime = kInitialProvisioningTime + 100; + clock_.SetTime(kRestoreTime); + // Restore + SetUpWithInfo(info); + ASSERT_TRUE(system_policy_); + ASSERT_TRUE(system_policy_->IsProvisioned()); + system_policy_->MarkNeedsProvisioning(); + + // Checks + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().first_checked_time(), kRestoreTime); +} + +// Setup: +// 1) Device needs OKP +// 2) App requests using fast backoff settings. +// 3) Fallback occurs +// 4) After the fast fallback duration, check for if in fallback +// Expectation: +// Policy should indicate fallback, duration should be "fast" and +// the info is updated. +// After the fast fallback duration has passed, the system should +// leave fallback state. +TEST_F(OkpFallbackPolicyTest, FastRules) { + system_policy_->SetFastBackoffDurationRules(); + constexpr int64_t kFallbackTime = kInitialTime + 10; + clock_.SetTime(kFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kFastBackoffDuration); + + constexpr int64_t kPostFallbackTime = + kFallbackTime + kFastBackoffDuration + 5; + clock_.SetTime(kPostFallbackTime); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} + +// Setup: +// 1) Device needs OKP +// 2) Fallback occurs +// 3) App requests using fast backoff settings. +// 4) Another fallback occurs +// Expectation: +// 1) Setting rules to fast should end fallback +// 2) Second fallback should have a short duration. +TEST_F(OkpFallbackPolicyTest, FastRules_AfterFallback) { + // First fallback. + constexpr int64_t kFirstFallbackTime = kInitialTime + 10; + clock_.SetTime(kFirstFallbackTime); + system_policy_->TriggerFallback(); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + + // Set fast fallback. + system_policy_->SetFastBackoffDurationRules(); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + + // Second fallaback. + constexpr int64_t kSecondFallbackTime = kFirstFallbackTime + 10; + clock_.SetTime(kSecondFallbackTime); + system_policy_->TriggerFallback(); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kSecondFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kFastBackoffDuration); +} + +// Setup: +// 1) Device needs OKP +// 2) App requests using fast backoff settings. +// 3) Fallback occurs +// 4) After the fast fallback duration, check for if in fallback +// 5) Another fallback occurs +// 6) After the fast fallback duration, check for if in fallback +// Expectation: +// There should not be any exponential backoff, similar to FastRules +// in all other ways. +TEST_F(OkpFallbackPolicyTest, FastRules_FallbackTwice) { + system_policy_->SetFastBackoffDurationRules(); + constexpr int64_t kFirstFallbackTime = kInitialTime + 10; + clock_.SetTime(kFirstFallbackTime); + system_policy_->TriggerFallback(); + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kFirstFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kFastBackoffDuration); + + constexpr int64_t kPostFirstFallbackTime = + kFirstFallbackTime + kFastBackoffDuration + 5; + clock_.SetTime(kPostFirstFallbackTime); + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); + + constexpr int64_t kSecondFallbackTime = kPostFirstFallbackTime + 10; + clock_.SetTime(kSecondFallbackTime); + system_policy_->TriggerFallback(); + + // Checks. + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_TRUE(system_policy_->IsInFallbackMode()); + EXPECT_EQ(system_policy_->info().backoff_start_time(), kSecondFallbackTime); + EXPECT_EQ(system_policy_->info().backoff_duration(), kFastBackoffDuration); + + constexpr int64_t kPostSecondFallbackTime = + kSecondFallbackTime + kFastBackoffDuration + 5; + clock_.SetTime(kPostSecondFallbackTime); + EXPECT_FALSE(system_policy_->IsProvisioned()); + EXPECT_FALSE(system_policy_->IsInFallbackMode()); +} +} // namespace okp +} // namespace wvcdm diff --git a/core/test/ota_keybox_provisioner_test.cpp b/core/test/ota_keybox_provisioner_test.cpp new file mode 100644 index 00000000..a0913d4d --- /dev/null +++ b/core/test/ota_keybox_provisioner_test.cpp @@ -0,0 +1,379 @@ +// 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 "ota_keybox_provisioner.h" + +#include +#include + +#include "crypto_session.h" +#include "mock_clock.h" +#include "okp_fallback_policy.h" +#include "properties.h" +#include "string_conversions.h" + +namespace wvcdm { +using ::testing::DoAll; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; +using ::video_widevine::ProvisioningResponse; +using ::video_widevine::SignedProvisioningMessage; +using ProvisioningType = SignedProvisioningMessage::ProvisioningType; + +namespace { +constexpr int64_t kInitialTime = 1629321960; // Wed 18 Aug 2021 09:26:00 PM +const std::string kEmptyString; +const std::string kFakeOtaProvisioningRequest = + "Totally real device ID, not fake" // Device ID : 32 bytes + "Totally real model ID, also real" // Model ID : 32 bytes + "Super secure key" // Encryped session key : 16 bytes + "Undoubtedly authentic signature!"; // Signature : 32 bytes + +const std::string kFakeOtaProvisioningResponse = + "Definitely an IV" // IV : 16 bytes + // Keybox : 128 bytes + "I'm a keybox, look at my keys and box-like appearance []. You might " + "be thinking 'you are not a real keybox', but you'd be wrong" + "Scribble scribble dot slash dot "; // Signature : 32 bytes + +const std::string kAnotherFakeOtaProvisioningResponse = + "Looks like an IV" // IV : 16 bytes + // Keybox : 128 bytes + "I am also a keybox. It's so safe to assume I'm a real keybox that " + "attempting to verify that will look very embarrassing for you" + "A drawing of boat with dolphins "; // Signature : 32 bytes + +class MockCryptoSession : public CryptoSession { + public: + MockCryptoSession() : CryptoSession(&crypto_metrics_) {} + ~MockCryptoSession() override {} + + bool IsOpen() override { return is_open_; } + void SetIsOpen(bool is_open) { is_open_ = is_open; } + + MOCK_METHOD(CdmResponseType, PrepareOtaProvisioningRequest, + (bool, std::string*), (override)); + MOCK_METHOD(CdmResponseType, LoadOtaProvisioning, (bool, const std::string&), + (override)); + + // Client ID + uint8_t GetSecurityPatchLevel() override { return 0x00; } + bool GetBuildInformation(std::string* info) override { + if (info == nullptr) return false; + info->assign("OtaKeyboxProvisionerTest"); + return true; + } + + void ExpectRequest(const std::string& request, CdmResponseType result) { + if (result == NO_ERROR) { + EXPECT_CALL(*this, PrepareOtaProvisioningRequest(false, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(request), Return(NO_ERROR))); + } else { + EXPECT_CALL(*this, PrepareOtaProvisioningRequest(false, NotNull())) + .WillOnce(Return(result)); + } + } + + void ExpectResponse(const std::string& response, CdmResponseType result) { + EXPECT_CALL(*this, LoadOtaProvisioning(false, response)) + .WillOnce(Return(result)); + } + + private: + bool is_open_ = false; + + static metrics::CryptoMetrics crypto_metrics_; +}; + +metrics::CryptoMetrics MockCryptoSession::crypto_metrics_; + +std::vector ToVector(const std::string& s) { + return std::vector(s.begin(), s.end()); +} +std::string FromVector(const std::vector& v) { + return std::string(v.begin(), v.end()); +} +} // namespace + +class OtaKeyboxProvisionerTest : public ::testing::Test { + protected: + void SetUp() override { + clock_.SetTime(kInitialTime); + fallback_policy_ = okp::SystemFallbackPolicy::CreateForTesting(&clock_); + ASSERT_TRUE(fallback_policy_); + fallback_policy_->MarkNeedsProvisioning(); + crypto_session_ = new MockCryptoSession(); + crypto_session_->SetIsOpen(true); + provisioner_ = OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr(crypto_session_), + fallback_policy_.get()); + ASSERT_TRUE(provisioner_); + } + + void SetUpWithInfo(const okp::SystemFallbackInfo& info) { + TearDown(); + fallback_policy_ = + okp::SystemFallbackPolicy::CreateForTesting(info, &clock_); + ASSERT_TRUE(fallback_policy_); + fallback_policy_->MarkNeedsProvisioning(); + crypto_session_ = new MockCryptoSession(); + crypto_session_->SetIsOpen(true); + provisioner_ = OtaKeyboxProvisioner::CreateForTesting( + std::unique_ptr(crypto_session_), + fallback_policy_.get()); + } + + void TearDown() override { + provisioner_.reset(); + crypto_session_ = nullptr; + fallback_policy_.reset(); + } + + FrozenClock clock_; + std::unique_ptr fallback_policy_; + MockCryptoSession* crypto_session_ = nullptr; + std::unique_ptr provisioner_; + + bool restore_messages_are_json_ = false; +}; + +// Used to make a variety of SignedProvisioningMessage, including invalid +// responses. +// |provisioning_type| - ProvisioningType of outer message. +// Valid response is ANDROID_ATTESTATION_KEYBOX_OTA +// |include_response| - Include the actual ProvisioningResponse message +// bytes. Valid response is true. +// |include_ota_response| - Includes the |android_ota_keybox_response| +// field of the ProvisioningResponse message. +// |ota_response| - Raw bytes of the ota_response field of the +// AndroidAttestationOtaKeyboxResponse. +// Valid responses contain the response to be passed +// into OEMCrypto. +void MakeSignedOtaProvisioningResponseEx(ProvisioningType provisioning_type, + bool include_response, + bool include_ota_response, + const std::string& ota_response, + std::string* response) { + ProvisioningResponse prov_response; + if (include_ota_response) { + prov_response.mutable_android_ota_keybox_response()->set_ota_response( + ota_response); + } + std::string message; + prov_response.SerializeToString(&message); + + SignedProvisioningMessage signed_response; + signed_response.set_protocol_version(SignedProvisioningMessage::VERSION_1); + signed_response.set_provisioning_type(provisioning_type); + if (include_response) { + signed_response.set_message(message); + } + std::string proto_response; + signed_response.SerializeToString(&proto_response); + + if (Properties::provisioning_messages_are_binary()) { + *response = std::move(proto_response); + return; + } + response->assign("{\n\"signedResponse\": \""); + response->append(wvutil::Base64SafeEncodeNoPad(ToVector(proto_response))); + response->append("\"\n}\n"); +} + +// Make a valid SignedProvisioningMessage containing an OTA keybox response. +void MakeSignedOtaProvisioningResponse(const std::string& ota_response, + std::string* response) { + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, true, true, + ota_response, response); +} + +bool RequestContains(const std::string& request, const std::string& part) { + if (Properties::provisioning_messages_are_binary()) { + const auto request_pos = request.find(part); + return request_pos != std::string::npos; + } + const std::string decoded_request = + FromVector(wvutil::Base64SafeDecode(request)); + if (decoded_request.empty()) return false; + const auto request_pos = decoded_request.find(part); + return request_pos != std::string::npos; +} + +TEST_F(OtaKeyboxProvisionerTest, FullProvisioning) { + // Pre-request conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); + + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); + EXPECT_TRUE( + RequestContains(signed_prov_message, kFakeOtaProvisioningRequest)); + EXPECT_FALSE(default_url.empty()); + + // Post-request, pre-response conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); + + // Load response. + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + crypto_session_->ExpectResponse(kFakeOtaProvisioningResponse, NO_ERROR); + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + // Post-response conditions. + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_TRUE(provisioner_->response_received()); + EXPECT_EQ(kProvisioningTime, fallback_policy_->info().provisioning_time()); +} + +// The OTA keybox provisioning is a system-wide provisioning processes. +// After a particular CDM engine (A) begins the request, it may be +// completed by a different CDM engine (B) before A receives a response. +// Provisioning from A perspective should complete without issues. +TEST_F(OtaKeyboxProvisionerTest, CompletedInDifferentEngine) { + // Generate request (engine A). + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); + + // Complete provisioning in different context (outside of engine A's scope). + constexpr int64_t kProvisioningTime = kInitialTime + 10; + clock_.SetTime(kProvisioningTime); + fallback_policy_->MarkProvisioned(); + // Engine provisioner should still indicate that provisioning is complete. + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_FALSE(provisioner_->response_received()); + + // Load engine A's response. + constexpr int64_t kResponseTime = kProvisioningTime + 10; + clock_.SetTime(kResponseTime); + std::string response; + MakeSignedOtaProvisioningResponse(kAnotherFakeOtaProvisioningResponse, + &response); + // OtaKeyboxProvisioner::HandleProvisioningResponse will be called, but + // not OEMCrypto. + // Expect success. + EXPECT_EQ(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + // Post-engine-response.; + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->response_received()); + EXPECT_EQ(kProvisioningTime, fallback_policy_->info().provisioning_time()); +} + +// Test to ensure there are no critical failures when receiving a +// malformed SignedProvisioningMessage. +TEST_F(OtaKeyboxProvisionerTest, MalformedResponseMessage) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string signed_prov_message, default_url; + EXPECT_EQ(NO_ERROR, provisioner_->GetProvisioningRequest(&signed_prov_message, + &default_url)); + + // Invalid response 1: Cannot SignedProvisioningMessage. + std::string response = "Not a SignedProvisioningMessage"; + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 2: Wrong SignedProvisioningMessage protocol. + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::PROVISIONING_30, true, true, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 3: Missing serialized ProvisioningResponse message. + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, false, true, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 4: Missing AndroidAttestationOtaKeyboxResponse message, + MakeSignedOtaProvisioningResponseEx( + SignedProvisioningMessage::ANDROID_ATTESTATION_KEYBOX_OTA, true, false, + kFakeOtaProvisioningResponse, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Invalid response 5: Missing raw OTA keybox response. + MakeSignedOtaProvisioningResponse(kEmptyString, &response); + EXPECT_EQ(PARSE_OKP_RESPONSE_ERROR, + provisioner_->HandleProvisioningResponse(response)); + + // Post-response failure conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); +} + +// Test case where OEMCrypto rejects the provided OTA keybox response. +TEST_F(OtaKeyboxProvisionerTest, RejectedResponse) { + // Generate request. + crypto_session_->ExpectRequest(kFakeOtaProvisioningRequest, NO_ERROR); + std::string request, default_url; + EXPECT_EQ(NO_ERROR, + provisioner_->GetProvisioningRequest(&request, &default_url)); + + // Load response. OEMCrypto returns error. + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + crypto_session_->ExpectResponse(kFakeOtaProvisioningResponse, UNKNOWN_ERROR); + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + // Post-response failure conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->IsInFallbackMode()); + EXPECT_TRUE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); +} + +TEST_F(OtaKeyboxProvisionerTest, OtaProvisioningNotImplemented) { + // Generate request. + crypto_session_->ExpectRequest(kEmptyString, NOT_IMPLEMENTED_ERROR); + std::string request, default_url; + EXPECT_EQ(NOT_IMPLEMENTED_ERROR, + provisioner_->GetProvisioningRequest(&request, &default_url)); + + // Post-response failure conditions. + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_TRUE(provisioner_->IsInFallbackMode()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); +} + +TEST_F(OtaKeyboxProvisionerTest, GenerateRequestAfterProvisioning) { + fallback_policy_->MarkProvisioned(); + // Generate request. + std::string request, default_url; + EXPECT_EQ(OKP_ALREADY_PROVISIONED, + provisioner_->GetProvisioningRequest(&request, &default_url)); + + EXPECT_TRUE(provisioner_->IsProvisioned()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); +} + +TEST_F(OtaKeyboxProvisionerTest, ResponseWithoutRequest) { + std::string response; + MakeSignedOtaProvisioningResponse(kFakeOtaProvisioningResponse, &response); + // Does not trigger system-wide fallback. This is a bad app. + EXPECT_NE(NO_ERROR, provisioner_->HandleProvisioningResponse(response)); + + EXPECT_FALSE(provisioner_->IsProvisioned()); + EXPECT_FALSE(provisioner_->IsInFallbackMode()); + EXPECT_FALSE(provisioner_->request_generated()); + EXPECT_FALSE(provisioner_->response_received()); // Not actually received. +} +} // namespace wvcdm diff --git a/core/test/parallel_operations_test.cpp b/core/test/parallel_operations_test.cpp index 995e65e9..2439c61e 100644 --- a/core/test/parallel_operations_test.cpp +++ b/core/test/parallel_operations_test.cpp @@ -1,11 +1,14 @@ // Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // These tests perform various end-to-end actions similar to what an application // would, but in parallel, attempting to create as many collisions in the CDM // code as possible. +#include +#include + #include #include #include @@ -13,9 +16,6 @@ #include #include -#include -#include - #include "cdm_engine.h" #include "config_test_env.h" #include "initialization_data.h" @@ -49,9 +49,11 @@ constexpr size_t kMaxKeyRequestAttempts = 5; // Wait time between failed key requests. constexpr const auto kRequestRetryWait = std::chrono::milliseconds(10); -const std::vector kKeyId = a2b_hex("371ea35e1a985d75d198a7f41020dc23"); -const std::vector kIv = a2b_hex("cedc47cccd6cb437af41325953c2e5e0"); -const std::vector kEncryptedData = a2b_hex( +const std::vector kKeyId = + wvutil::a2b_hex("371ea35e1a985d75d198a7f41020dc23"); +const std::vector kIv = + wvutil::a2b_hex("cedc47cccd6cb437af41325953c2e5e0"); +const std::vector kEncryptedData = wvutil::a2b_hex( "6274b3237fe5991ff570ca902e4b3306" "8be1e782cc97944686223fe6a2bc0936" "71644c1963ffc465b8b5731a9914ce26" @@ -59,7 +61,7 @@ const std::vector kEncryptedData = a2b_hex( "8f77a6194674e61888803d0d0b5a6670" "9b92b79ee5a1ccaa5a6edd290b994657" "b201e2fc"); -const std::vector kClearData = a2b_hex( +const std::vector kClearData = wvutil::a2b_hex( "21fd60f6e690ff0b0cb5f89540380f92" "ca7a3c4326110261d1f88ab33af1e9a3" "dc12574b7f55bdac821ddbacfe2f2158" @@ -313,7 +315,7 @@ TEST_P(ParallelCdmTest, ParallelDecryptsInSameSession) { ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); } -INSTANTIATE_TEST_CASE_P(Repeated, ParallelCdmTest, - ::testing::Range(0, kRepetitions)); +INSTANTIATE_TEST_SUITE_P(Repeated, ParallelCdmTest, + ::testing::Range(0, kRepetitions)); } // namespace wvcdm diff --git a/core/test/policy_engine_constraints_unittest.cpp b/core/test/policy_engine_constraints_unittest.cpp index dce74ef0..726ab3bc 100644 --- a/core/test/policy_engine_constraints_unittest.cpp +++ b/core/test/policy_engine_constraints_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include @@ -70,18 +70,21 @@ class HdcpOnlyMockCryptoSession : public TestCryptoSession { HdcpOnlyMockCryptoSession(metrics::CryptoMetrics* crypto_metrics) : TestCryptoSession(crypto_metrics) {} - MOCK_METHOD2(GetHdcpCapabilities, - CdmResponseType(HdcpCapability*, HdcpCapability*)); + MOCK_METHOD(CdmResponseType, GetHdcpCapabilities, + (HdcpCapability*, HdcpCapability*), (override)); }; class MockCdmEventListener : public WvCdmEventListener { public: - MOCK_METHOD1(OnSessionRenewalNeeded, void(const CdmSessionId& session_id)); - MOCK_METHOD3(OnSessionKeysChange, void(const CdmSessionId& session_id, - const CdmKeyStatusMap& keys_status, - bool has_new_usable_key)); - MOCK_METHOD2(OnExpirationUpdate, void(const CdmSessionId& session_id, - int64_t new_expiry_time_seconds)); + MOCK_METHOD(void, OnSessionRenewalNeeded, (const CdmSessionId& session_id), + (override)); + MOCK_METHOD(void, OnSessionKeysChange, + (const CdmSessionId& session_id, + const CdmKeyStatusMap& keys_status, bool has_new_usable_key), + (override)); + MOCK_METHOD(void, OnExpirationUpdate, + (const CdmSessionId& session_id, int64_t new_expiry_time_seconds), + (override)); }; } // namespace diff --git a/core/test/policy_engine_unittest.cpp b/core/test/policy_engine_unittest.cpp index b4063f73..c6ebf0ae 100644 --- a/core/test/policy_engine_unittest.cpp +++ b/core/test/policy_engine_unittest.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include @@ -55,8 +55,8 @@ class HdcpOnlyMockCryptoSession : public TestCryptoSession { HdcpOnlyMockCryptoSession(metrics::CryptoMetrics* metrics) : TestCryptoSession(metrics) {} - MOCK_METHOD2(GetHdcpCapabilities, - CdmResponseType(HdcpCapability*, HdcpCapability*)); + MOCK_METHOD(CdmResponseType, GetHdcpCapabilities, + (HdcpCapability*, HdcpCapability*), (override)); CdmResponseType DoRealGetHdcpCapabilities(HdcpCapability* current, HdcpCapability* max) { return CryptoSession::GetHdcpCapabilities(current, max); @@ -65,12 +65,15 @@ class HdcpOnlyMockCryptoSession : public TestCryptoSession { class MockCdmEventListener : public WvCdmEventListener { public: - MOCK_METHOD1(OnSessionRenewalNeeded, void(const CdmSessionId& session_id)); - MOCK_METHOD3(OnSessionKeysChange, void(const CdmSessionId& session_id, - const CdmKeyStatusMap& keys_status, - bool has_new_usable_key)); - MOCK_METHOD2(OnExpirationUpdate, void(const CdmSessionId& session_id, - int64_t new_expiry_time_seconds)); + MOCK_METHOD(void, OnSessionRenewalNeeded, (const CdmSessionId& session_id), + (override)); + MOCK_METHOD(void, OnSessionKeysChange, + (const CdmSessionId& session_id, + const CdmKeyStatusMap& keys_status, bool has_new_usable_key), + (override)); + MOCK_METHOD(void, OnExpirationUpdate, + (const CdmSessionId& session_id, int64_t new_expiry_time_seconds), + (override)); }; } // namespace @@ -809,9 +812,8 @@ TEST_F(PolicyEngineTest, policy->clear_rental_duration_seconds(); // Only |playback_duration_seconds| set. - policy_engine_->BeginDecryption(); - EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(kLicenseStartTime - 10)) .WillOnce(Return(kLicenseStartTime + 1)) .WillOnce(Return(kPlaybackStartTime)) .WillOnce(Return(kPlaybackStartTime + kPlaybackDuration - 10)) @@ -823,6 +825,9 @@ TEST_F(PolicyEngineTest, EXPECT_CALL(mock_event_listener_, OnExpirationUpdate(_, kPlaybackStartTime + kPlaybackDuration)); + // Attempt decryption before the license has been received + policy_engine_->BeginDecryption(); + policy_engine_->SetLicense(license_, false, false); policy_engine_->BeginDecryption(); policy_engine_->OnTimerEvent(); @@ -1723,9 +1728,9 @@ TEST_P(PolicyEngineKeySecurityLevelTest, CanUseKeyForSecurityLevel) { policy_engine_->CanUseKeyForSecurityLevel(kKeyId)); } -INSTANTIATE_TEST_CASE_P(PolicyEngine, PolicyEngineKeySecurityLevelTest, - ::testing::Range(&key_security_test_vectors[0], - &key_security_test_vectors[5])); +INSTANTIATE_TEST_SUITE_P(PolicyEngine, PolicyEngineKeySecurityLevelTest, + ::testing::Range(&key_security_test_vectors[0], + &key_security_test_vectors[5])); class PolicyEngineKeyAllowedUsageTest : public PolicyEngineTest { protected: diff --git a/core/test/policy_integration_test.cpp b/core/test/policy_integration_test.cpp index 75d948f3..2135b951 100644 --- a/core/test/policy_integration_test.cpp +++ b/core/test/policy_integration_test.cpp @@ -1,6 +1,6 @@ // Copyright 2020 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. // These tests perform various end-to-end actions similar to what an application // would do. They verify that policies specified on UAT are honored on the @@ -13,26 +13,15 @@ #include #include "cdm_engine.h" -#include "clock.h" -#include "config_test_env.h" -#include "initialization_data.h" -#include "license_request.h" +#include "license_holder.h" #include "log.h" -#include "metrics_collections.h" +#include "oec_device_features.h" #include "test_base.h" #include "test_printers.h" -#include "test_sleep.h" -#include "url_request.h" -#include "wv_cdm_constants.h" + #include "wv_cdm_types.h" namespace wvcdm { - -namespace { -constexpr int kHttpOk = 200; -const std::string kCencMimeType = "cenc"; -} // namespace - // Core Policy Integration Test class CorePIGTest : public WvCdmTestBaseWithEngine { protected: @@ -40,156 +29,84 @@ class CorePIGTest : public WvCdmTestBaseWithEngine { WvCdmTestBase::SetUp(); EnsureProvisioned(); } - - void OpenSession(CdmSessionId* session_id) { - CdmResponseType status = cdm_engine_.OpenSession( - config_.key_system(), nullptr, nullptr, session_id); - ASSERT_EQ(NO_ERROR, status); - ASSERT_TRUE(cdm_engine_.IsOpenSession(*session_id)); - } - - void CloseSession(const CdmSessionId& session_id) { - CdmResponseType status = cdm_engine_.CloseSession(session_id); - ASSERT_EQ(NO_ERROR, status); - ASSERT_FALSE(cdm_engine_.IsOpenSession(session_id)); - } - - // Create a license request for the given content_id and requesting the - // specified license_type. - void GenerateKeyRequest(const CdmSessionId& session_id, - const std::string& content_id, - CdmKeyRequest* key_request, - CdmLicenseType license_type) { - video_widevine::WidevinePsshData pssh; - pssh.set_content_id(content_id); - const std::string init_data_string = MakePSSH(pssh); - const InitializationData init_data(kCencMimeType, init_data_string); - init_data.DumpToLogs(); - CdmAppParameterMap empty_app_parameters; - CdmKeySetId empty_key_set_id; - CdmResponseType result = cdm_engine_.GenerateKeyRequest( - session_id, empty_key_set_id, init_data, license_type, - empty_app_parameters, key_request); - ASSERT_EQ(KEY_MESSAGE, result); - ASSERT_EQ(kKeyRequestTypeInitial, key_request->type); - } - - // Send the request to the server and get the response. - void GetKeyResponse(const CdmKeyRequest& key_request, - std::string* key_response) { - const std::string url = config_.license_server() + config_.client_auth(); - UrlRequest url_request(url); - ASSERT_TRUE(url_request.is_connected()); - - std::string http_response; - url_request.PostRequest(key_request.message); - ASSERT_TRUE(url_request.GetResponse(&http_response)); - int status_code = url_request.GetStatusCode(http_response); - ASSERT_EQ(kHttpOk, status_code); - - LicenseRequest license_request; - license_request.GetDrmMessage(http_response, *key_response); - } - - // Load the license response into the specified session. Verify it has the - // correct license type (either streaming or offline). - void AddKey(const CdmSessionId& session_id, const std::string& key_response, - CdmLicenseType expected_license_type, CdmKeySetId* key_set_id) { - CdmLicenseType license_type; - CdmResponseType status = - cdm_engine_.AddKey(session_id, key_response, &license_type, key_set_id); - ASSERT_EQ(KEY_ADDED, status); - ASSERT_EQ(expected_license_type, license_type); - } - - // Reload the license response into the specified session. - void RestoreKey(const CdmSessionId& session_id, - const CdmKeySetId& key_set_id) { - CdmResponseType status = cdm_engine_.RestoreKey(session_id, key_set_id); - ASSERT_EQ(KEY_ADDED, status); - } - - // Use the key to decrypt. - void Decrypt(const CdmSessionId& session_id, const KeyId& key_id) { - constexpr size_t buffer_size = 500; - const std::vector input(buffer_size, 0); - std::vector output(buffer_size, 0); - const std::vector iv(KEY_IV_SIZE, 0); - Decrypt(session_id, key_id, input, iv, &output, NO_ERROR); - } - - // Try to use the key to decrypt, but expect the key has expired. - void FailDecrypt(const CdmSessionId& session_id, const KeyId& key_id) { - constexpr size_t buffer_size = 500; - const std::vector input(buffer_size, 0); - std::vector output(buffer_size, 0); - const std::vector iv(KEY_IV_SIZE, 0); - Decrypt(session_id, key_id, input, iv, &output, NEED_KEY); - } - - void Decrypt(const CdmSessionId& session_id, const KeyId& key_id, - const std::vector& input, - const std::vector& iv, std::vector* output, - CdmResponseType expected_status) { - CdmDecryptionParametersV16 params(key_id); - params.is_secure = false; - CdmDecryptionSample sample(input.data(), output->data(), 0, input.size(), - iv); - CdmDecryptionSubsample subsample(0, input.size()); - sample.subsamples.push_back(subsample); - params.samples.push_back(sample); - - CdmResponseType status = cdm_engine_.DecryptV16(session_id, params); - ASSERT_EQ(expected_status, status); - } }; // An offline license with nonce not required. TEST_F(CorePIGTest, OfflineNoNonce) { - const std::string content_id = "2015_tears"; + LicenseHolder holder("CDM_OfflineNoNonce", &cdm_engine_, config_); + holder.set_can_persist(true); const KeyId key_id = "0000000000000000"; - const CdmLicenseType license_type = kLicenseTypeOffline; - CdmSessionId session_id; - ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); - CdmKeyRequest key_request; - ASSERT_NO_FATAL_FAILURE( - GenerateKeyRequest(session_id, content_id, &key_request, license_type)); - std::string key_response; - ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response)); - CdmKeySetId key_set_id; - ASSERT_NO_FATAL_FAILURE( - AddKey(session_id, key_response, license_type, &key_set_id)); - ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); - ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); - ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); - ASSERT_NO_FATAL_FAILURE(RestoreKey(session_id, key_set_id)); - ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); - ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); + EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + // Should be able to close the previous session, open a new session, + // and reload the license. + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense()); + EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); } // An offline license with nonce and provider session token. TEST_F(CorePIGTest, OfflineWithPST) { - const std::string content_id = "offline_clip2"; - const KeyId key_id = - "\x32\x60\xF3\x9E\x12\xCC\xF6\x53\x52\x99\x90\x16\x8A\x35\x83\xFF"; - const CdmLicenseType license_type = kLicenseTypeOffline; - CdmSessionId session_id; - ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); - CdmKeyRequest key_request; - ASSERT_NO_FATAL_FAILURE( - GenerateKeyRequest(session_id, content_id, &key_request, license_type)); - std::string key_response; - ASSERT_NO_FATAL_FAILURE(GetKeyResponse(key_request, &key_response)); - CdmKeySetId key_set_id; - ASSERT_NO_FATAL_FAILURE( - AddKey(session_id, key_response, license_type, &key_set_id)); - ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); - ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); - ASSERT_NO_FATAL_FAILURE(OpenSession(&session_id)); - ASSERT_NO_FATAL_FAILURE(RestoreKey(session_id, key_set_id)); - ASSERT_NO_FATAL_FAILURE(Decrypt(session_id, key_id)); - ASSERT_NO_FATAL_FAILURE(CloseSession(session_id)); + LicenseHolder holder("CDM_OfflineWithPST", &cdm_engine_, config_); + holder.set_can_persist(true); + const KeyId key_id = "0000000000000000"; + + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); + EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + // Should be able to close the previous session, open a new session, + // and reload the license. + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense()); + EXPECT_EQ(NO_ERROR, holder.Decrypt(key_id)); + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); +} + +// This test verifies that the system can download and install license with a +// key that requires secure buffers. It also verifies that we cannot decrypt to +// a non-secure buffer using this key, but that we can decrypt to a secure +// buffer, if the test harness supports secure buffers. +TEST_F(CorePIGTest, OfflineHWSecureRequired) { + LicenseHolder holder("CDM_OfflineHWSecureRequired", &cdm_engine_, config_); + holder.set_can_persist(true); + const KeyId sw_key_id = "0000000000000000"; + const KeyId hw_key_id = "0000000000000001"; + + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.FetchLicense()); + ASSERT_NO_FATAL_FAILURE(holder.LoadLicense()); + EXPECT_EQ(NO_ERROR, holder.Decrypt(sw_key_id)); + ASSERT_NO_FATAL_FAILURE(holder.FailDecrypt(hw_key_id, DECRYPT_ERROR)); + // Next, if possible, we try to decrypt to a secure buffer, and verify + // success. + if (wvoec::global_features.test_secure_buffers) { + ASSERT_NO_FATAL_FAILURE(holder.DecryptSecure(hw_key_id)); + } else { + LOGI("Test harness cannot create secure buffers. test skipped."); + } + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); + + // Should be able to close the previous session, open a new session, + // and reload the license. + ASSERT_NO_FATAL_FAILURE(holder.OpenSession()); + ASSERT_NO_FATAL_FAILURE(holder.ReloadLicense()); + EXPECT_EQ(NO_ERROR, holder.Decrypt(sw_key_id)); + ASSERT_NO_FATAL_FAILURE(holder.FailDecrypt(hw_key_id, DECRYPT_ERROR)); + // Next, if possible, we try to decrypt to a secure buffer, and verify + // success. + if (wvoec::global_features.test_secure_buffers) { + ASSERT_NO_FATAL_FAILURE(holder.DecryptSecure(hw_key_id)); + } else { + LOGI("Test harness cannot create secure buffers. test skipped."); + } + ASSERT_NO_FATAL_FAILURE(holder.CloseSession()); } } // namespace wvcdm diff --git a/core/test/reboot_test.cpp b/core/test/reboot_test.cpp new file mode 100644 index 00000000..32950f7b --- /dev/null +++ b/core/test/reboot_test.cpp @@ -0,0 +1,730 @@ +// Copyright 2021 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. + +// These tests perform various end-to-end actions similar to what an application +// would do. They verify that policies specified on UAT are honored on the +// device. + +#include "reboot_test.h" + +#include + +#include "create_test_file_system.h" +#include "license_holder.h" +#include "log.h" +#include "test_sleep.h" + +using wvutil::a2b_hex; +using wvutil::FileSystem; +using wvutil::TestSleep; +using wvutil::unlimited_b2a_hex; + +namespace wvcdm { +FileSystem* RebootTest::file_system_; + +namespace { +// How much fudge or round off error do we allow in license durations for reboot +// tests. +constexpr int64_t kFudge = 10; + +// We will encode a value string by wrapping it in braces, or as hex. +// If the string is not printable, or if it has unmatched braces, then we use +// hex. Otherwise, we surround the whole string with braces. +std::string EncodeString(const std::string& data) { + int braces_count = 0; + for (size_t i = 0; i < data.length(); i++) { + if (data[i] == '{') braces_count++; + if (data[i] == '}') braces_count--; + // If printable or whitespace (because '\n' is not printable?!?). + bool printable = isprint(data[i]) || isspace(data[i]); + // If there are any unprintable characters, except whitespace, or if we + // close a brace before we open it, then just use hex. + if (!printable || braces_count < 0) { + return "0x" + unlimited_b2a_hex(data) + ","; + } + } + // If we left any braces open, then use hex. + if (braces_count != 0) return "0x" + unlimited_b2a_hex(data) + ","; + return "{" + data + "},"; +} + +// Encode a map key for dumping. When we encode a map, we expect the keys to be +// like filenames, so we can separate them with colons and whitespace. If the +// key has these special characters, we will encode as hex. +std::string EncodeKey(const std::string& data) { + if (data.length() == 0) { + LOGE("Encoding empty string as key!"); + return "EMPTY:"; + } + // When decoding, we assume that a key starting with "0x" is in hex. So we + // can't have any keys that start with "0x". + if (data.substr(0, 2) == "0x") return "0x" + unlimited_b2a_hex(data) + ":"; + // If the key is just is not printable, or if it has unmatched braces, then + // we use hex. Otherwise, we surround the whole string with braces. + for (size_t i = 0; i < data.length(); i++) { + if (!isprint(data[i]) || (data[i] == ':')) { + return "0x" + unlimited_b2a_hex(data) + ":"; + } + } + return data + ":"; +} + +// In between keys and values, we will ignore whitespace. This allows a human to +// edit the persistent data a little bit without breaking anything. +void SkipSpace(const std::string& encoded, size_t* index) { + if (!index) return; + while (*index < encoded.length() && isspace(encoded[*index])) (*index)++; +} + +// Decode a string that was encoded using EncodeString. +std::string DecodeString(const std::string& encoded, size_t* index) { + if (!index) return ""; + SkipSpace(encoded, index); + if (*index + 2 >= + encoded.length()) { // Encoded string has at least 3 characters. + LOGE("Error decoding short string from %s at %zd", encoded.c_str(), *index); + *index = encoded.length(); + return ""; + } + if (encoded[*index] == '{') { + (*index)++; + size_t start = *index; + int braces_count = 1; + while (*index < encoded.length()) { + if (encoded[*index] == '{') braces_count++; + if (encoded[*index] == '}') braces_count--; + if (braces_count == 0) { + size_t end = *index; + (*index) += 2; // absorb the comma and the '}', too. + return encoded.substr(start, end - start); + } + (*index)++; + } + std::string tail = encoded.substr(start); + LOGE("Non-terminated brace %s at %zd: %s", encoded.c_str(), start, + tail.c_str()); + *index = encoded.length(); + return ""; + } + if (encoded[*index] != '0' || encoded[*index + 1] != 'x') { + std::string tail = encoded.substr(*index); + LOGE("Hex should start with 0x in %s at %zd: %s", encoded.c_str(), *index, + tail.c_str()); + *index = encoded.length(); + return ""; + } + *index += 2; + size_t start = *index; + while (*index < encoded.length()) { + if (encoded[*index] == ',') { + size_t end = *index; + std::vector result = a2b_hex(encoded.substr(start, end - start)); + (*index)++; // absorb the comma. + return std::string(result.begin(), result.end()); + } + (*index)++; + } + std::string tail = encoded.substr(start); + LOGE("Bad encoding in %s at %zd: %s", encoded.c_str(), start, tail.c_str()); + *index = encoded.length(); + return ""; +} + +// Decode a string that was encoded with EncodeKey. +std::string DecodeKey(const std::string& encoded, size_t* index) { + if (!index) return ""; + SkipSpace(encoded, index); + if (*index + 1 >= encoded.length()) { + LOGE("Error decoding key from %s at %zd", encoded.c_str(), *index); + *index = encoded.length(); + return ""; + } + // If it starts with 0x, then it is in hex. + if (encoded[*index] == '0' && encoded[*index + 1] == 'x') { + size_t start = *index + 2; + while (*index < encoded.length() && encoded[*index] != ':') (*index)++; + size_t end = *index; + std::vector result = a2b_hex(encoded.substr(start, end - start)); + (*index)++; // skip the colon. + return std::string(result.begin(), result.end()); + } + size_t start = *index; + while (*index < encoded.length() && encoded[*index] != ':') (*index)++; + size_t end = *index; + (*index)++; // skip the colon. + return encoded.substr(start, end - start); +} +} // namespace + +std::string RebootTest::DumpData( + const std::map& data) { + std::ostringstream output; + output << "{\n"; + for (const auto& entry : data) { + output << " " << EncodeKey(entry.first) << " " + << EncodeString(entry.second) + "\n"; + } + output << "}\n"; + return output.str(); +} + +bool RebootTest::ParseDump(const std::string& dump, + std::map* data) { + size_t index = 0; + SkipSpace(dump, &index); + if (index >= dump.length()) return false; + if (dump[index] != '{') { + LOGE("Dump does not start with '{'"); + return false; + } + index++; // absorb '{' + while (true) { + SkipSpace(dump, &index); + if (index >= dump.length()) return false; + if (dump[index] == '}') { + index++; // absorb '}' + SkipSpace(dump, &index); + if (index != dump.length()) { + std::string tail = dump.substr(index); + LOGE("Trailing data in dump. %s at %zd: %s", dump.c_str(), index, + tail.c_str()); + return false; + } + return true; + } + std::string tail = dump.substr(index); + std::string key = DecodeKey(dump, &index); + std::string value = DecodeString(dump, &index); + (*data)[key] = value; + } +} + +void RebootTest::SetUp() { + WvCdmTestBase::SetUp(); + if (!file_system_) file_system_ = CreateTestFileSystem(); + + const ::testing::TestInfo* const test_info = + ::testing::UnitTest::GetInstance()->current_test_info(); + std::string test_name = + std::string(test_info->test_case_name()) + "-" + test_info->name(); + persistent_data_filename_ = + config_.test_data_path() + "/" + test_name + ".dat"; + LOGD("Running test pass %d for %s", test_pass(), test_name.c_str()); + // Don't read data on the first pass, but do read data for all other passes. + if (test_pass() > 0) { + EXPECT_TRUE(file_system_->Exists(persistent_data_filename_)); + ssize_t file_size = file_system_->FileSize(persistent_data_filename_); + auto file = + file_system_->Open(persistent_data_filename_, file_system_->kReadOnly); + ASSERT_TRUE(file); + std::string dump(file_size, ' '); + ssize_t read = file->Read(&dump[0], dump.size()); + EXPECT_EQ(read, file_size) << "Error reading persistent data file."; + EXPECT_TRUE(ParseDump(dump, &persistent_data_)); + } +} + +void RebootTest::TearDown() { + auto file = file_system_->Open(persistent_data_filename_, + FileSystem::kCreate | FileSystem::kTruncate); + ASSERT_TRUE(file) << "Failed to open file: " << persistent_data_filename_; + std::string dump = DumpData(persistent_data_); + const ssize_t bytes_written = file->Write(dump.data(), dump.length()); + EXPECT_EQ(bytes_written, static_cast(dump.length())); + WvCdmTestBase::TearDown(); +} + +int64_t RebootTest::LoadTime(const std::string& key) { + int64_t value = 0; + std::istringstream input(persistent_data_[key]); + input >> value; + if (input.fail()) { + LOGE("Could not parse time '%s'", persistent_data_[key].c_str()); + } + if (!input.eof()) { + LOGE("Extra text at end of time '%s'", persistent_data_[key].c_str()); + } + return value; +} + +void RebootTest::SaveTime(const std::string& key, int64_t time) { + persistent_data_[key] = std::to_string(time); +} + +/** Test the dump and restore functions above. This does not test CDM + functionality. */ +TEST_F(RebootTest, TestDumpUtil) { + // Check that an empty map can be saved. + std::map map1; + const std::string dump = DumpData(map1); + std::map map2; + EXPECT_TRUE(ParseDump(dump, &map2)); + EXPECT_EQ(map1, map2); + // Now fill it with some data and try again. + map1["key1"] = "this is a string. "; + map1["key2"] = "mismatch } {"; + map1["key3"] = "mismatch } "; + map1["key4"] = "mismatch {"; + map1["key5"] = "this: { has { matched } } braces { /.,)(**&^$&^% }"; + map1["key6"] = ""; + map1["00 whitespace in key 00"] = "value is ok"; + // This key looks like it might be hex. It should show up as hex in the + // save file. + map1["0x_bad_key_00"] = "value is ok"; + std::string big_string = "start with something {binary"; + // Double big_string 8 times, i.e. times 256, so it's bigger than 2k: + for (int i = 0; i < 8; i++) big_string = big_string + big_string; + map1["big_file"] = big_string; + const std::string dump2 = DumpData(map1); + std::map map3; + EXPECT_TRUE(ParseDump(dump2, &map3)); + EXPECT_EQ(map1, map3); + if (test_pass() == 0) { + persistent_data_ = map1; + } else { + EXPECT_EQ(persistent_data_, map1); + } +} + +/** Verify that the file system stores files from one test pass to the next. */ +TEST_F(RebootTest, FilesArePersistent) { + const std::string key = "saved_value"; + const std::string value = "the string that is saved"; + if (test_pass() == 0) { + // There should be no persistent data on the first pass. + EXPECT_NE(persistent_data_[key], value); + // We will save some data for the next pass. + persistent_data_[key] = value; + } else { + // There should be persistent data set in first pass. + EXPECT_EQ(persistent_data_[key], value); + } +} + +/** Verify that the clock moves forward over a reboot. */ +TEST_F(RebootTest, TimeMovesForward) { + wvutil::TestSleep::Sleep(2); + const int64_t start = wvutil::Clock().GetCurrentTime(); + wvutil::TestSleep::Sleep(2); + const int64_t end = wvutil::Clock().GetCurrentTime(); + EXPECT_NEAR(end - start, 2.0, 1.0); + const std::string key = "end_time"; + if (test_pass() == 0) { + // Save off the end of pass 1. + SaveTime(key, end); + } else { + int64_t previous_end = LoadTime(key); + EXPECT_LT(previous_end, start); + } +} + +/** Test offline license durations work correctly after reboot. For time + constraints, this runs four sets of tests in parallel. For each set of tests + we load a list of licenses with a range of rental or playback durations so + that we can be confident that a short wait will find at least one duration + in the range that has expired and at least one duration that has not + expired. + + RDa. Load an offline license, reboot the device, reload the license and + verify that the rental duration is enforced from the time the license was + requested. + + RDb. Load an offline license, begin playback, reboot the device, reload the + license and verify that the rental duration is enforced from the time the + license was requested. + + PDa. Load an offline license, reboot the device, reload the license and + verify that the playback duration is enforced from the time initial playback + started. + + PDb. Load an offline license, begin playback, reboot the device, reload the + license and verify that the playback duration is enforced from the time + initial playback started. + + Each of these four sets contains licenses with various durations so that the + test can run with a reasonable reboot time. We will assume that a reboot can + take anywhere from 10 seconds to almost an hour. With this in mind, we will + create a license that has a 10 second duration, one with a 20 second + duration, ... and one with an hour duration. All of these licenses will be + loaded before the reboot. Then after the reboot we should be in a situation + like this: + :-----------------------------------------> time axis. + : start of test. + :: licenses loaded. + :: : reboot starts + :: : : reboot finished. test resumes. + :: : : + :[---] 10s - expired : + :[-------] 20s expired : + : ... : + :[---------------------------] this license has not yet expired + :[----------------------------------------------] license not expired. + : ... : + :[---------------------------------------------------] 1 hour. not expired + + After the test resumes, we will have at least one license that has not yet + expired. We will then sleep we are near the expiration time of that + license. We can then carefully test that this one license from the set + enforces its duration. + + This is complicated by the fact that we are testing four different sets. So + what we really have is something like this: + :-----------------------------------------> time axis. + : start of test. + :: licenses loaded. + :: : reboot starts + :: : : reboot finished. test resumes. + :: : : + :[-----------------------------] first RDa license that has not exipred. + :[--------------------------] first RDb license that has not exipred. + :[---------------------------] first PDa license that has not exipred. + :[-------------------------------] first PDb license that has not exipred. + + Since we want to test all four of these licenses, we will compute the + interesting times for each license. These are the times just before, and + just after the expiration time. After sorting these interesting times, we + will test each one in order. + */ +class OfflineLicense { + public: + OfflineLicense(const std::string& test_type, CdmEngine* cdm_engine, + const ConfigTestEnv& config, int64_t duration, + bool play_before_reboot) + : content_id_("CDM_Reboot_" + test_type + + (play_before_reboot ? "b_" : "a_") + + std::to_string(duration)), + duration_(duration), + play_before_reboot_(play_before_reboot), + license_holder_(content_id_, cdm_engine, config) { + license_holder_.set_can_persist(true); + } + + virtual ~OfflineLicense() {} + + // Fetch and load the license. The session is left open. + void LoadLicense() { + license_holder_.OpenSession(); + start_of_rental_clock_ = wvutil::Clock().GetCurrentTime(); + license_holder_.FetchLicense(); + license_holder_.LoadLicense(); + } + + // Reload the license. The session is left open. + void ReloadLicense() { + license_holder_.OpenSession(); + license_holder_.ReloadLicense(); + } + + // Action to be taken after loading the license, but before the reboot. + virtual void BeforeReboot() { + if (play_before_reboot_) { + Decrypt(); + } + } + + // Action to be taken after reloading the license. + virtual void AfterReboot() {} + + void CloseSession() { license_holder_.CloseSession(); } + + // The time this license should be cutoff. Decrypt should succeed before this + // time and should fail after this time. + virtual int64_t cutoff() = 0; + + // Verify that the license may be used to decrypt content. + void Decrypt() { + if (start_of_playback_ == 0) { + start_of_playback_ = wvutil::Clock().GetCurrentTime(); + } + const KeyId key_id = "0000000000000000"; + EXPECT_EQ(NO_ERROR, license_holder_.Decrypt(key_id)) + << "Failed for " << content_id_ + << ", now = " << wvutil::Clock().GetCurrentTime() << ", rental_clock=" + << (wvutil::Clock().GetCurrentTime() - start_of_rental_clock_) + << ", playback_clock = " + << (wvutil::Clock().GetCurrentTime() - start_of_playback_) + << ", delta to cutoff = " + << (wvutil::Clock().GetCurrentTime() - cutoff()); + } + + // Verify that the license has expired, and may not be used to decrypt + // content. + void FailDecrypt() { + const KeyId key_id = "0000000000000000"; + EXPECT_EQ(NEED_KEY, license_holder_.Decrypt(key_id)) + << "Decrypt should have failed for " << content_id_ + << ", now = " << wvutil::Clock().GetCurrentTime() << ", rental_clock=" + << (wvutil::Clock().GetCurrentTime() - start_of_rental_clock_) + << ", playback_clock = " + << (wvutil::Clock().GetCurrentTime() - start_of_playback_) + << ", delta to cutoff = " + << (wvutil::Clock().GetCurrentTime() - cutoff()); + } + + // Save times and the key set id to persistent data. + void SaveData(RebootTest* reboot_test, + std::map* persistent_data) { + reboot_test->SaveTime("start_of_rental_" + content_id_, + start_of_rental_clock_); + reboot_test->SaveTime("start_of_playback_" + content_id_, + start_of_playback_); + (*persistent_data)["key_set_id_" + content_id_] = + license_holder_.key_set_id(); + } + + // Load times and the key set id from persistent data. + void LoadData(RebootTest* reboot_test, + std::map* persistent_data) { + start_of_rental_clock_ = + reboot_test->LoadTime("start_of_rental_" + content_id_); + start_of_playback_ = + reboot_test->LoadTime("start_of_playback_" + content_id_); + license_holder_.set_key_set_id( + (*persistent_data)["key_set_id_" + content_id_]); + } + + const std::string& content_id() const { return content_id_; } + + protected: + const std::string content_id_; + int64_t duration_; + int64_t start_of_rental_clock_ = 0; + int64_t start_of_playback_ = 0; + bool play_before_reboot_; + LicenseHolder license_holder_; +}; + +// Holds an offline license that has a limit on the rental duration. +class RentalDurationLicense : public OfflineLicense { + public: + RentalDurationLicense(CdmEngine* cdm_engine, const ConfigTestEnv& config, + int64_t duration, bool play_before_reboot) + : OfflineLicense("RD", cdm_engine, config, duration, play_before_reboot) { + } + + int64_t cutoff() override { return start_of_rental_clock_ + duration_; } +}; + +// Holds an offline license that has a limit on the playback duration. +class PlaybackDurationLicense : public OfflineLicense { + public: + PlaybackDurationLicense(CdmEngine* cdm_engine, const ConfigTestEnv& config, + int64_t duration, bool play_before_reboot) + : OfflineLicense("PD", cdm_engine, config, duration, play_before_reboot) { + } + + // If we did not start playback before the reboot, we will start playback + // just after reloading the license, post-reboot. + void AfterReboot() override { + if (!play_before_reboot_) { + Decrypt(); + } + } + + int64_t cutoff() override { return start_of_playback_ + duration_; } +}; + +// Test that the rental and playback durations are enforced across a reboot. +class OfflineLicenseTest : public RebootTest { + public: + void SetUp() override { + RebootTest::SetUp(); + EnsureProvisioned(); + // Run each of the following test cases in parallel so that we don't have to + // sleep separately for each one. These durations need to match the polices + // specified on the UAT license server. + test_case_.resize(4); + const std::vector duration_range = {10, 20, 30, 45, + 60, 300, 900, 3600}; + for (size_t i = 0; i < duration_range.size(); i++) { + test_case_[0].push_back( + std::unique_ptr(new RentalDurationLicense( + &cdm_engine_, config_, duration_range[i], false))); + test_case_[1].push_back( + std::unique_ptr(new RentalDurationLicense( + &cdm_engine_, config_, duration_range[i], true))); + test_case_[2].push_back( + std::unique_ptr(new PlaybackDurationLicense( + &cdm_engine_, config_, duration_range[i], false))); + test_case_[3].push_back( + std::unique_ptr(new PlaybackDurationLicense( + &cdm_engine_, config_, duration_range[i], true))); + } + } + + // Load all of the licenses. For the tests that require playback before the + // reboot, we start playback here. + void LoadAllLicenses() { + DeleteAllLicenses(); + // For each test case, load an offline license and save the data. + for (size_t n = 0; n < test_case_.size(); n++) { + for (size_t i = 0; i < test_case_[n].size(); i++) { + ASSERT_NO_FATAL_FAILURE(test_case_[n][i]->LoadLicense()); + test_case_[n][i]->BeforeReboot(); // For some tests, we decrypt here. + test_case_[n][i]->SaveData(this, &persistent_data_); + test_case_[n][i]->CloseSession(); + } + } + } + + // Reload all of the licenses. We also go through each license and figure out + // which ones have already expired, or close to expire. We then find the first + // license from each set that will be next to expire, so that we can test + // those more carefully. + void ReloadAllLicense() { + // first_valid is the index of the first license that has not expired and is + // not just about to expire. + first_valid_.resize(test_case_.size()); + for (size_t n = 0; n < test_case_.size(); n++) { + for (size_t i = 0; i < test_case_[n].size(); i++) { + OfflineLicense* license = test_case_[n][i].get(); + license->LoadData(this, &persistent_data_); + ASSERT_NO_FATAL_FAILURE(license->ReloadLicense()); + license->AfterReboot(); + // if past cutoff. make sure decrypt fails. + if (license->cutoff() + kFudge < wvutil::Clock().GetCurrentTime()) { + license->FailDecrypt(); + } + // If past cutoff, or near cutoff, then we don't want to use it + // as our first valid. + if (license->cutoff() - 2 * kFudge < wvutil::Clock().GetCurrentTime()) { + first_valid_[n] = i + 1; + } + license->CloseSession(); + } + // We expect there to be at least one license that has not expired yet. + // If this is not true, then the reboot time was probably longer than + // expected. If it is important to run these tests with a very long reboot + // time, then please ask a Widevine engineer to generate more license + // policies with longer reboot times. + ASSERT_LT(first_valid_[n], test_case_[n].size()) + << "For n=" << n + << ", content_id = " << test_case_[n][0]->content_id() + << ", time=" << wvutil::Clock().GetCurrentTime() << "\n" + << "This is an indication that the reboot took a very long time.\n"; + } + } + + // Take the first_valid_ array, which tells us which licenses will expire + // soon, and compute the times we are interested in: a little before and a + // little after the cutoff. We want to test that the license is valid just + // before the cutoff and that the license is not valid just after the + // cutoff. + // + // The interesting_times_ is a std::set, so that we may iterate through the + // times in order. + void FindInterestingTimes() { + for (size_t n = 0; n < test_case_.size(); n++) { + OfflineLicense* license = test_case_[n][first_valid_[n]].get(); + interesting_times_.insert(license->cutoff() - kFudge); + interesting_times_.insert(license->cutoff() + kFudge); + } + } + + // Test at each of the interesting times. These are the times just before and + // after the cutoff of the next license to expire from each list of test + // cases. + void TestInterestingTimes() { + int decrypt_count = 0; + int fail_count = 0; + for (auto time : interesting_times_) { + int64_t now = wvutil::Clock().GetCurrentTime(); + int64_t delta = (time - now); + // It is not necessarily an error for the delta to be negative. But it is + // an indication that the we are near an error condition. If the current + // time is a few seconds past the interesting time, then the license + // should still be valid or expired. If delta is very large relative to + // kFudge, then the test might fail. This is still an indication that + // something is wrong: reloading a license and decrypting some content + // should not take multiple seconds. + if (delta < 0) LOGW("Sleep delta would be negative: %ld", delta); + if (delta > 0) TestSleep::Sleep(static_cast(delta)); + // We look at each of the four license that we used to generate the + // interesting times. It's possible that two licenses share the same + // interesting time, so we have to check each of the four against each + // time instead of keeping track of which license generated which time. + for (size_t n = 0; n < test_case_.size(); n++) { + OfflineLicense* license = test_case_[n][first_valid_[n]].get(); + ASSERT_NO_FATAL_FAILURE(license->ReloadLicense()); + if (time == license->cutoff() - kFudge) { + license->Decrypt(); + decrypt_count++; + } + if (time == license->cutoff() + kFudge) { + license->FailDecrypt(); + fail_count++; + } + license->CloseSession(); + } + } + EXPECT_EQ(decrypt_count, 4) << "Test error. I missed a cutoff"; + EXPECT_EQ(fail_count, 4) << "Test error. I missed a cutoff"; + } + + // After we have tested one license from each list of test cases carefully, + // all the rest of the licenses can be tested. We do not sleep in this + // function, we only test each license that is still valid, or has already + // expired. We ignore licenses that are near the cutoff because we already + // tested those license in the previous function. + void TestAfterInterestingTimes() { + // Make sure that all the rest of the licenses are still valid. + for (size_t n = 0; n < test_case_.size(); n++) { + for (size_t i = first_valid_[n] + 1; i < test_case_[n].size(); i++) { + OfflineLicense* license = test_case_[n][i].get(); + ASSERT_NO_FATAL_FAILURE(license->ReloadLicense()); + int64_t now = wvutil::Clock().GetCurrentTime(); + if (now <= license->cutoff() - kFudge) { + license->Decrypt(); + } + if (now >= license->cutoff() + kFudge) { + license->FailDecrypt(); + } + license->CloseSession(); + } + } + } + + void TearDown() override { + // Clean up licenses if any test failed. + if (::testing::Test::HasFailure() || test_pass() == 1) { + DeleteAllLicenses(); + } + RebootTest::TearDown(); + } + + void DeleteAllLicenses() { + std::vector key_set_ids; + EXPECT_EQ(NO_ERROR, + cdm_engine_.ListStoredLicenses(kSecurityLevelL1, &key_set_ids)); + for (auto key_set : key_set_ids) { + cdm_engine_.RemoveOfflineLicense(key_set, kSecurityLevelL1); + } + // TODO(b/215230202): Is this necessary? It doesn't seem to work. + std::vector ksids; + std::vector pst; + std::string app_id = ""; + EXPECT_EQ(NO_ERROR, + cdm_engine_.ListUsageIds(app_id, kSecurityLevelL1, &ksids, &pst)); + for (auto k : ksids) { + EXPECT_EQ(NO_ERROR, + cdm_engine_.DeleteUsageRecord(app_id, kSecurityLevelL1, k)); + } + } + + std::vector>> test_case_; + std::set interesting_times_; + std::vector first_valid_; +}; + +TEST_F(OfflineLicenseTest, VariousTests) { + if (test_pass() == 0) { + LoadAllLicenses(); + } else if (test_pass() == 1) { + ReloadAllLicense(); + FindInterestingTimes(); + TestInterestingTimes(); + TestAfterInterestingTimes(); + } +} +} // namespace wvcdm diff --git a/core/test/reboot_test.h b/core/test/reboot_test.h new file mode 100644 index 00000000..fae4993e --- /dev/null +++ b/core/test/reboot_test.h @@ -0,0 +1,60 @@ +// 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 WVCDM_CORE_REBOOT_TEST_H_ +#define WVCDM_CORE_REBOOT_TEST_H_ + +#include +#include +#include + +#include + +#include "file_store.h" +#include "test_base.h" + +namespace wvcdm { +class RebootTest : public WvCdmTestBaseWithEngine { + public: + // The main test driver may inject the file system for saving persistent test + // data. + static void set_file_system(wvutil::FileSystem* file_system) { + file_system_ = file_system; + } + // Dump a map to a std string in an almost human readable way so that the map + // can be rebuilt using ParseDump below. The keys in the map must be standard + // identifier strings, which means no special characters or whitespace. By + // "almost human readable", we mean that a human debugging the dump will be + // able to find the keys, and see the values if they are printable or see a + // hex dump of the values if they are not. + static std::string DumpData(const std::map& data); + // Parse a dump generated by DumpData and recreate the original data map. + // Returns true on success. + static bool ParseDump(const std::string& dump, + std::map* data); + + static int test_pass() { return default_config_->test_pass(); } + + // Load a previously saved time. Returns 0 if the value does not exist or + // cannot be parsed. + int64_t LoadTime(const std::string& key); + // Save a time to persistent storage. + void SaveTime(const std::string& key, int64_t time); + + protected: + void SetUp() override; + void TearDown() override; + + // This is used to store each test's persistent data. + static wvutil::FileSystem* file_system_; + + // The persistent data for the current test. + std::map persistent_data_; + // Where to store and restore the persistent data for a single test. + std::string persistent_data_filename_; +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_REBOOT_TEST_H_ diff --git a/core/test/rw_lock_test.cpp b/core/test/rw_lock_test.cpp index 68d6e56a..a0dbe954 100644 --- a/core/test/rw_lock_test.cpp +++ b/core/test/rw_lock_test.cpp @@ -1,6 +1,11 @@ // Copyright 2019 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. + +#include "rw_lock.h" + +#include +#include #include #include @@ -9,14 +14,9 @@ #include #include -#include -#include - -#include "rw_lock.h" - using std::this_thread::sleep_for; -namespace wvcdm { +namespace wvutil { namespace { @@ -290,4 +290,4 @@ TEST(SharedMutexUnitTests, LargeVolumeOfThreadsSortsItselfOutEventually) { } } -} // namespace wvcdm +} // namespace wvutil diff --git a/core/test/service_certificate_unittest.cpp b/core/test/service_certificate_unittest.cpp index 6958d307..9d3dfea8 100644 --- a/core/test/service_certificate_unittest.cpp +++ b/core/test/service_certificate_unittest.cpp @@ -1,11 +1,14 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "service_certificate.h" + #include #include + #include + #include "properties.h" #include "string_conversions.h" #include "wv_cdm_constants.h" @@ -19,7 +22,7 @@ const CdmSessionId kTestSessionId1 = "sid1"; const CdmSessionId kTestSessionId2 = "sid2"; const std::string kAppId = "com.example.test"; -const std::string kTestSignedCertificate = a2bs_hex( +const std::string kTestSignedCertificate = wvutil::a2bs_hex( "0AC102080312101705B917CC1204868B06333A2F772A8C1882B4829205228E023082010A02" "8201010099ED5B3B327DAB5E24EFC3B62A95B598520AD5BCCB37503E0645B814D876B8DF40" "510441AD8CE3ADB11BB88C4E725A5E4A9E0795291D58584023A7E1AF0E38A9127939300861" @@ -79,7 +82,7 @@ class StubCdmClientPropertySet : public CdmClientPropertySet { } uint32_t session_sharing_id() const override { return session_sharing_id_; } - virtual bool use_atsc_mode() const { return false; } + bool use_atsc_mode() const override { return false; } void set_session_sharing_id(uint32_t id) override { session_sharing_id_ = id; diff --git a/core/test/system_id_extractor_unittest.cpp b/core/test/system_id_extractor_unittest.cpp new file mode 100644 index 00000000..93635479 --- /dev/null +++ b/core/test/system_id_extractor_unittest.cpp @@ -0,0 +1,537 @@ +// Copyright 2022 Google LLC. All Rights Reserved. This file and proprietary +// source code may only be used and distributed under the Widevine License +// Agreement. +#include +#include +#include + +#include +#include + +#include "cdm_random.h" +#include "crypto_session.h" +#include "device_files.h" +#include "file_store.h" +#include "system_id_extractor.h" +#include "test_base.h" +#include "wv_cdm_types.h" + +namespace wvcdm { +using ::testing::_; +using ::testing::DoAll; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::SetArgPointee; + +namespace { +const uint8_t kOemCert[] = { + 0x30, 0x82, 0x09, 0xf7, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x02, 0xa0, 0x82, 0x09, 0xe8, 0x30, 0x82, 0x09, 0xe4, 0x02, + 0x01, 0x01, 0x31, 0x00, 0x30, 0x0f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x02, 0x04, 0x00, 0xa0, 0x82, 0x09, + 0xc8, 0x30, 0x82, 0x04, 0x1a, 0x30, 0x82, 0x03, 0x02, 0xa0, 0x03, 0x02, + 0x01, 0x02, 0x02, 0x11, 0x00, 0xf2, 0xa1, 0x08, 0xdf, 0x12, 0x84, 0xb9, + 0x73, 0x6c, 0x23, 0x73, 0xe1, 0x1f, 0xf3, 0xac, 0x7a, 0x30, 0x0d, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, + 0x30, 0x81, 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, + 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, + 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, + 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x08, + 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, + 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, 0x30, 0x30, 0x2e, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x27, 0x47, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x20, 0x4f, 0x45, 0x4d, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, 0x3b, 0x20, 0x73, 0x79, 0x73, 0x74, 0x65, + 0x6d, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x37, 0x33, 0x34, 0x36, 0x30, 0x1e, + 0x17, 0x0d, 0x31, 0x37, 0x30, 0x33, 0x31, 0x33, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x31, 0x39, 0x31, 0x32, 0x30, 0x38, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6d, 0x31, 0x12, 0x30, 0x10, + 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x09, 0x37, 0x33, 0x34, 0x36, 0x2d, + 0x6c, 0x65, 0x61, 0x66, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, + 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, + 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, + 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, + 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x30, 0x82, 0x01, + 0xa2, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x8f, 0x00, 0x30, 0x82, 0x01, + 0x8a, 0x02, 0x82, 0x01, 0x81, 0x00, 0xf5, 0x09, 0x64, 0x4a, 0x26, 0xfe, + 0xc0, 0x98, 0x55, 0x6a, 0x1d, 0x5d, 0x1c, 0xc7, 0x38, 0xaf, 0xfd, 0x49, + 0x9e, 0x85, 0x3f, 0xd6, 0x45, 0x0e, 0x99, 0x09, 0x85, 0x69, 0x84, 0x3c, + 0xfe, 0x72, 0xa5, 0x56, 0xfa, 0x11, 0x4f, 0x6b, 0x7d, 0x32, 0x2b, 0x0c, + 0xbf, 0x8f, 0xac, 0x47, 0x96, 0x22, 0x82, 0x3d, 0xf5, 0x64, 0x74, 0x7e, + 0x62, 0x68, 0x74, 0xcd, 0x0a, 0xec, 0x84, 0xc5, 0x15, 0x06, 0x0e, 0x5a, + 0x2f, 0x20, 0xe3, 0xc9, 0x67, 0xcd, 0xdd, 0x01, 0xb8, 0xb3, 0x18, 0x87, + 0x8c, 0xa9, 0x58, 0x86, 0x0f, 0xb6, 0xc3, 0x42, 0x7e, 0x87, 0x48, 0x5e, + 0x10, 0x49, 0xc7, 0xd7, 0xb7, 0xb8, 0xa6, 0x34, 0x08, 0x0c, 0x94, 0xf4, + 0xbb, 0x2a, 0x06, 0xa4, 0x4f, 0xec, 0xbc, 0xc4, 0x37, 0xbe, 0x99, 0x10, + 0x23, 0x37, 0x24, 0xb1, 0xdf, 0xcb, 0xe6, 0x3f, 0xc1, 0xf0, 0x0f, 0x04, + 0x03, 0xc8, 0xb0, 0x1e, 0xd6, 0xb8, 0xae, 0x77, 0xe1, 0x4d, 0x6d, 0x97, + 0x69, 0x6d, 0x8a, 0x73, 0x66, 0x32, 0x57, 0x6f, 0xcf, 0xea, 0x1e, 0x7b, + 0x87, 0x03, 0x75, 0xb1, 0xef, 0x83, 0x64, 0x26, 0xf1, 0x3f, 0xbf, 0xe6, + 0x28, 0x03, 0x72, 0x57, 0xbf, 0x47, 0x29, 0x99, 0x8f, 0x74, 0x1d, 0x01, + 0x16, 0xad, 0xb2, 0xdf, 0x80, 0xa4, 0xd3, 0x8b, 0xeb, 0x61, 0xd1, 0x40, + 0x68, 0xb9, 0xa2, 0xa5, 0xef, 0x2b, 0xe5, 0x78, 0xe8, 0x28, 0x88, 0x87, + 0xb7, 0x53, 0x49, 0xbb, 0xe4, 0xea, 0x0d, 0x5e, 0x96, 0xa5, 0xdd, 0x1f, + 0x0b, 0x25, 0x8b, 0xb5, 0x95, 0x46, 0xe7, 0xba, 0xb8, 0xc4, 0x0a, 0x36, + 0xb1, 0x89, 0xeb, 0x27, 0x5d, 0xd9, 0x97, 0x24, 0x59, 0xa3, 0x9b, 0xb0, + 0x23, 0x0b, 0xd2, 0xec, 0x65, 0x91, 0xf9, 0xf0, 0xa0, 0x74, 0x5f, 0xb4, + 0xce, 0x22, 0x27, 0x18, 0x37, 0xe2, 0x4b, 0xfc, 0x91, 0xf9, 0x09, 0x15, + 0xe6, 0xdb, 0x06, 0x9b, 0x4d, 0x82, 0xdc, 0x36, 0x14, 0x48, 0xc6, 0xd5, + 0x87, 0xca, 0xec, 0x5a, 0xa2, 0x29, 0x33, 0xef, 0x22, 0x0c, 0x4b, 0xbf, + 0xe7, 0x2f, 0x95, 0xe1, 0xd3, 0xa5, 0xd8, 0xaa, 0x44, 0x77, 0x29, 0xa3, + 0x20, 0x33, 0xd2, 0x51, 0xa2, 0xf9, 0x4a, 0x6f, 0xf7, 0x3e, 0xf7, 0x0b, + 0x8a, 0xec, 0xc1, 0x99, 0x1d, 0x47, 0xf3, 0x74, 0x02, 0x04, 0xab, 0x8e, + 0x62, 0x4c, 0x9e, 0x00, 0xc2, 0x84, 0xd7, 0xd0, 0xf8, 0xe4, 0x1c, 0x9d, + 0x98, 0x15, 0xa8, 0x8f, 0x08, 0x98, 0x4e, 0x5a, 0xfa, 0xd6, 0x60, 0x87, + 0x12, 0xdc, 0x8e, 0xfd, 0xcb, 0xb3, 0x13, 0x97, 0x7a, 0xa8, 0x8c, 0x56, + 0x2e, 0x49, 0x26, 0x60, 0xe9, 0x4a, 0xdc, 0xec, 0x3f, 0xf0, 0x94, 0xcd, + 0x90, 0x8e, 0x7c, 0x21, 0x3f, 0x80, 0x14, 0x33, 0xdd, 0xb0, 0x00, 0xe2, + 0x09, 0x37, 0x06, 0xdd, 0x17, 0x69, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, + 0x16, 0x30, 0x14, 0x30, 0x12, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, + 0xd6, 0x79, 0x04, 0x01, 0x01, 0x04, 0x04, 0x02, 0x02, 0x1c, 0xb2, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, + 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x8e, 0x2d, 0x13, 0x1e, 0x60, + 0xaa, 0xda, 0x52, 0x53, 0x55, 0x64, 0x3a, 0xdc, 0xb6, 0x7a, 0xc0, 0xba, + 0xfa, 0xeb, 0x20, 0xab, 0xb6, 0x63, 0xcf, 0xcd, 0x9b, 0xdb, 0x71, 0xf3, + 0xa0, 0xd6, 0x91, 0xbf, 0x0c, 0xc1, 0xae, 0x8f, 0x02, 0x18, 0x00, 0x54, + 0xfb, 0x49, 0x03, 0x34, 0x8d, 0x92, 0x9d, 0x5d, 0x8d, 0xa8, 0x1c, 0x20, + 0x0f, 0x85, 0x60, 0xf9, 0xf6, 0x8b, 0xbb, 0x2b, 0x82, 0xce, 0xb3, 0xe2, + 0x91, 0xe7, 0xbd, 0x91, 0x61, 0x52, 0x36, 0x40, 0x9f, 0x2f, 0x5e, 0xa6, + 0x5d, 0x2f, 0xb3, 0x81, 0xe7, 0xf1, 0x87, 0xbe, 0xc5, 0x9d, 0x67, 0x5a, + 0xf7, 0x41, 0x1e, 0x73, 0xb0, 0x1e, 0xdc, 0x4f, 0x8d, 0x53, 0x21, 0x38, + 0x1b, 0xfd, 0x92, 0x43, 0x68, 0x83, 0x03, 0xd0, 0x9a, 0xca, 0x92, 0x14, + 0x73, 0x04, 0x94, 0x2a, 0x93, 0x22, 0x60, 0x5e, 0xee, 0xb6, 0xec, 0x0f, + 0xb0, 0xc8, 0x92, 0x97, 0xfb, 0x5d, 0xed, 0x1f, 0xa0, 0x5f, 0xe4, 0x98, + 0x2f, 0xf6, 0x13, 0x78, 0x99, 0xec, 0xb3, 0xf1, 0x0d, 0x27, 0xaa, 0x19, + 0x95, 0x39, 0xdb, 0xb0, 0x7b, 0x96, 0x74, 0x03, 0x5e, 0x51, 0xf5, 0x15, + 0x27, 0xce, 0xca, 0x0b, 0x2a, 0x0d, 0x43, 0xb3, 0x68, 0x17, 0x1e, 0x11, + 0x60, 0xd9, 0x84, 0x9b, 0xc3, 0x53, 0xce, 0xbd, 0xf4, 0x61, 0x51, 0x4b, + 0x41, 0x00, 0x7e, 0xe1, 0x5f, 0x69, 0xb3, 0x4a, 0x89, 0x7e, 0x47, 0x67, + 0xfd, 0x76, 0xf8, 0x94, 0x2f, 0x72, 0xb6, 0x14, 0x08, 0x2c, 0x16, 0x4e, + 0x9d, 0x37, 0x62, 0xbf, 0x11, 0x67, 0xc0, 0x70, 0x71, 0xec, 0x55, 0x51, + 0x4e, 0x46, 0x76, 0xb4, 0xc3, 0xeb, 0x52, 0x06, 0x17, 0x06, 0xce, 0x61, + 0x43, 0xce, 0x26, 0x80, 0x68, 0xb6, 0x2d, 0x57, 0xba, 0x8c, 0x7d, 0xb7, + 0xc5, 0x05, 0x2c, 0xf8, 0xa3, 0x69, 0xf8, 0x96, 0xad, 0xac, 0xd1, 0x30, + 0x82, 0x05, 0xa6, 0x30, 0x82, 0x03, 0x8e, 0xa0, 0x03, 0x02, 0x01, 0x02, + 0x02, 0x10, 0x73, 0xd1, 0xe1, 0x1d, 0xa9, 0x75, 0xfd, 0x0c, 0xda, 0x7f, + 0xfa, 0x43, 0x3c, 0x26, 0xbd, 0x3d, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x7e, 0x31, + 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, + 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, + 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x11, 0x30, + 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, 0x08, 0x4b, 0x69, 0x72, 0x6b, + 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, + 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x31, 0x11, 0x30, + 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, 0x08, 0x57, 0x69, 0x64, 0x65, + 0x76, 0x69, 0x6e, 0x65, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x0c, 0x1a, 0x77, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x65, 0x6d, 0x2d, 0x72, 0x6f, 0x6f, 0x74, + 0x2d, 0x70, 0x72, 0x6f, 0x64, 0x30, 0x1e, 0x17, 0x0d, 0x31, 0x37, 0x30, + 0x33, 0x31, 0x34, 0x30, 0x33, 0x30, 0x32, 0x34, 0x31, 0x5a, 0x17, 0x0d, + 0x32, 0x37, 0x30, 0x33, 0x31, 0x34, 0x30, 0x33, 0x30, 0x32, 0x34, 0x31, + 0x5a, 0x30, 0x81, 0x8b, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, + 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, + 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, + 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, + 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, 0x30, 0x30, + 0x2e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x27, 0x47, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x20, 0x4f, 0x45, 0x4d, 0x20, 0x54, 0x65, 0x73, 0x74, 0x20, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x3b, 0x20, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x20, 0x69, 0x64, 0x3a, 0x20, 0x37, 0x33, 0x34, 0x36, 0x30, + 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, + 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa5, 0x45, 0x13, 0xf2, + 0xb2, 0xcb, 0x4b, 0x0f, 0xb4, 0x44, 0x25, 0x9c, 0x8a, 0x68, 0x54, 0xd5, + 0x45, 0x1e, 0x15, 0x89, 0x5b, 0xb8, 0xce, 0xda, 0x5a, 0x42, 0xe6, 0x9a, + 0x8c, 0xc1, 0xcb, 0xe8, 0xc5, 0xf5, 0x8f, 0x49, 0x0e, 0x02, 0xef, 0x5e, + 0x97, 0x1a, 0x91, 0xa4, 0x94, 0xc3, 0x50, 0x13, 0xe5, 0x13, 0xb7, 0x7f, + 0x26, 0x53, 0x19, 0xb0, 0x37, 0xa5, 0xef, 0xe6, 0x2a, 0x39, 0xdc, 0x93, + 0x37, 0xe2, 0x3d, 0x7f, 0xcb, 0x4b, 0x93, 0xa2, 0xc3, 0x69, 0x78, 0xc9, + 0x01, 0xfa, 0x68, 0x3b, 0xe0, 0xe2, 0x22, 0x6c, 0xeb, 0xe4, 0x8a, 0xa8, + 0x3e, 0xf5, 0x20, 0x82, 0xa8, 0x62, 0x68, 0x59, 0x78, 0x24, 0xde, 0xef, + 0x47, 0x43, 0xb1, 0x6c, 0x38, 0x29, 0xd3, 0x69, 0x3f, 0xae, 0x35, 0x57, + 0x75, 0x80, 0xc9, 0x21, 0xe7, 0x01, 0xb9, 0x54, 0x8b, 0x6e, 0x4e, 0x2e, + 0x5a, 0x5b, 0x77, 0xa4, 0x22, 0xc2, 0x7b, 0x95, 0xb9, 0x39, 0x2c, 0xbd, + 0xc2, 0x1e, 0x02, 0xa6, 0xb2, 0xbc, 0x0f, 0x7a, 0xcb, 0xdc, 0xbc, 0xbc, + 0x90, 0x66, 0xe3, 0xca, 0x46, 0x53, 0x3e, 0x98, 0xff, 0x2e, 0x78, 0x9f, + 0xd3, 0xa1, 0x12, 0x93, 0x66, 0x7d, 0xcc, 0x94, 0x6b, 0xec, 0x19, 0x0e, + 0x20, 0x45, 0x22, 0x57, 0x6d, 0x9e, 0xd0, 0x89, 0xf2, 0xa9, 0x34, 0xdc, + 0xab, 0xa5, 0x73, 0x47, 0x38, 0xe3, 0x7f, 0x98, 0x3a, 0x61, 0xae, 0x6c, + 0x4d, 0xf2, 0x31, 0x90, 0xcb, 0x83, 0xc1, 0xee, 0xb4, 0xf2, 0x9a, 0x28, + 0x5f, 0xbb, 0x7d, 0x89, 0xdf, 0xa2, 0x31, 0xb6, 0x1d, 0x39, 0x2b, 0x70, + 0xbf, 0x1e, 0xad, 0xe1, 0x74, 0x94, 0x1d, 0xf8, 0xc5, 0x1a, 0x8d, 0x13, + 0x45, 0xf0, 0x6a, 0x80, 0x0c, 0x5d, 0xbb, 0x46, 0x8a, 0x43, 0xd0, 0xff, + 0x21, 0x39, 0x57, 0x53, 0x5b, 0x51, 0xf8, 0xa2, 0x8f, 0x7f, 0x27, 0xc7, + 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x82, 0x01, 0x10, 0x30, 0x82, 0x01, + 0x0c, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, + 0x08, 0x30, 0x06, 0x01, 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0e, 0x06, + 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, + 0x04, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, + 0xe8, 0xe9, 0xac, 0x16, 0x5c, 0x5e, 0xb2, 0xe8, 0xeb, 0xff, 0x57, 0x27, + 0x20, 0x08, 0x72, 0x63, 0x9b, 0xe5, 0xb5, 0x16, 0x30, 0x81, 0xb2, 0x06, + 0x03, 0x55, 0x1d, 0x23, 0x04, 0x81, 0xaa, 0x30, 0x81, 0xa7, 0x80, 0x14, + 0x04, 0x94, 0x66, 0xaa, 0xf9, 0x61, 0x89, 0xb6, 0xdb, 0xb5, 0xf7, 0x13, + 0x38, 0x3d, 0x62, 0x84, 0xb8, 0x18, 0x0a, 0x8f, 0xa1, 0x81, 0x83, 0xa4, + 0x81, 0x80, 0x30, 0x7e, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, + 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, + 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, 0x69, 0x6e, 0x67, 0x74, + 0x6f, 0x6e, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x07, 0x0c, + 0x08, 0x4b, 0x69, 0x72, 0x6b, 0x6c, 0x61, 0x6e, 0x64, 0x31, 0x0f, 0x30, + 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x31, 0x11, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x0c, + 0x08, 0x57, 0x69, 0x64, 0x65, 0x76, 0x69, 0x6e, 0x65, 0x31, 0x23, 0x30, + 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x1a, 0x77, 0x69, 0x64, 0x65, + 0x76, 0x69, 0x6e, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x65, 0x6d, + 0x2d, 0x72, 0x6f, 0x6f, 0x74, 0x2d, 0x70, 0x72, 0x6f, 0x64, 0x82, 0x09, + 0x00, 0xdf, 0x86, 0x05, 0x31, 0x01, 0xbe, 0x9a, 0x9a, 0x30, 0x12, 0x06, + 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x04, 0x01, 0x01, 0x04, + 0x04, 0x02, 0x02, 0x1c, 0xb2, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x02, 0x01, + 0x00, 0x25, 0xce, 0xd2, 0x02, 0x48, 0xbb, 0xbe, 0xfc, 0xb6, 0xa4, 0x87, + 0x87, 0xe0, 0x21, 0x7d, 0xfa, 0x23, 0xc3, 0x0d, 0x73, 0x8f, 0x46, 0xe7, + 0x09, 0x59, 0xda, 0x2e, 0x55, 0x59, 0xff, 0x3c, 0x1b, 0xf6, 0xf8, 0x9a, + 0xc4, 0x1c, 0xf7, 0xac, 0xca, 0xe7, 0x63, 0xf2, 0xc7, 0xd6, 0x0c, 0x2d, + 0xa6, 0xad, 0x55, 0xf4, 0x10, 0x0e, 0xa8, 0x82, 0x0f, 0x88, 0xb5, 0x44, + 0xe8, 0x8e, 0x84, 0x08, 0xf7, 0xdd, 0xe7, 0x10, 0xce, 0x71, 0x56, 0x57, + 0x3f, 0xed, 0x48, 0xee, 0xe2, 0x5d, 0x08, 0x0a, 0x58, 0xe4, 0xfe, 0xbc, + 0x8c, 0x27, 0x1a, 0x46, 0x3f, 0xd5, 0x2d, 0xdb, 0x0b, 0x71, 0x73, 0xd1, + 0x49, 0xf3, 0x5c, 0x86, 0x4d, 0x0a, 0xe1, 0xeb, 0x53, 0x21, 0x38, 0x4f, + 0xec, 0x1e, 0xc2, 0x68, 0x1f, 0x7d, 0xa6, 0x33, 0xe9, 0xa5, 0x37, 0x2a, + 0xef, 0xcd, 0x78, 0x56, 0xb3, 0x39, 0x60, 0xf4, 0xa5, 0xf9, 0x2b, 0x85, + 0xcf, 0xe6, 0x1c, 0x7c, 0x8a, 0x5d, 0xe8, 0x26, 0x02, 0xcf, 0x7a, 0x56, + 0x1f, 0xae, 0x0d, 0x71, 0x20, 0xee, 0xec, 0x3b, 0xae, 0x95, 0x25, 0x15, + 0xc8, 0xf6, 0x92, 0x5d, 0xb8, 0x9b, 0xc2, 0xb4, 0x95, 0x33, 0x13, 0x76, + 0x45, 0xbe, 0x21, 0xe2, 0x3a, 0x69, 0x66, 0xd7, 0xff, 0x22, 0x00, 0x89, + 0xc9, 0x44, 0xb6, 0x54, 0x38, 0x1f, 0x33, 0xe4, 0xda, 0x7b, 0x87, 0xf3, + 0x23, 0xed, 0xf5, 0x16, 0x08, 0xbe, 0x4b, 0xea, 0x91, 0x8f, 0x91, 0x8b, + 0x4e, 0xd1, 0x02, 0x06, 0xa2, 0x77, 0x15, 0x03, 0x46, 0x11, 0x7d, 0x5b, + 0xea, 0x7a, 0xf6, 0x86, 0x7d, 0x96, 0xb7, 0x73, 0x9b, 0x5b, 0x32, 0xc3, + 0xf8, 0x92, 0x36, 0xe3, 0xe3, 0x2f, 0xe8, 0xf1, 0x72, 0xec, 0x0d, 0x50, + 0xd4, 0x86, 0xc5, 0x62, 0x83, 0xf1, 0x2a, 0x4c, 0xd1, 0xbf, 0x76, 0x62, + 0xd4, 0x21, 0x11, 0x68, 0xb2, 0xd6, 0x8d, 0xc4, 0xf8, 0xe4, 0x70, 0x85, + 0x19, 0xa7, 0x82, 0x27, 0x2c, 0x24, 0x21, 0x7a, 0x3b, 0xad, 0x8a, 0xd3, + 0xae, 0xda, 0x78, 0x3c, 0x6c, 0xab, 0xa2, 0xaa, 0x36, 0xf0, 0x1c, 0x58, + 0xd4, 0x72, 0x5e, 0xe8, 0x8b, 0x41, 0x08, 0xf5, 0x85, 0xdd, 0xee, 0x99, + 0x12, 0xf4, 0xd6, 0x41, 0x83, 0x69, 0xe7, 0x79, 0x19, 0xa3, 0x74, 0xc4, + 0x34, 0x2a, 0x8a, 0x7e, 0x4d, 0xbb, 0x2c, 0x49, 0x19, 0xf7, 0x98, 0x98, + 0xfc, 0x81, 0xf7, 0x9b, 0x7f, 0xff, 0xd9, 0x66, 0xf4, 0x51, 0x14, 0x29, + 0x2a, 0x14, 0x1d, 0x4f, 0xbd, 0x91, 0xba, 0x6f, 0x32, 0x34, 0x3c, 0x40, + 0x28, 0x6c, 0x97, 0xf8, 0x6d, 0x38, 0xcd, 0xa3, 0x7b, 0x18, 0xc8, 0x77, + 0x58, 0x4d, 0x53, 0x30, 0x7f, 0x4d, 0x89, 0xca, 0x95, 0x6e, 0xb5, 0xb8, + 0x8e, 0xc8, 0x2d, 0x18, 0x2f, 0x52, 0x2a, 0xde, 0xac, 0x56, 0x8d, 0x8c, + 0x67, 0x14, 0xf6, 0xb9, 0xf1, 0x65, 0xd3, 0x22, 0x43, 0xa3, 0x98, 0x42, + 0x20, 0x43, 0x4c, 0xdf, 0xf2, 0xeb, 0x31, 0x8c, 0x0e, 0x53, 0x5b, 0x99, + 0x82, 0xc3, 0x48, 0x04, 0x53, 0xad, 0x96, 0xb6, 0x9f, 0x52, 0xcc, 0x01, + 0xc8, 0xb3, 0x87, 0x6b, 0x9e, 0xea, 0xa9, 0xeb, 0xda, 0xac, 0xf9, 0x6f, + 0xde, 0xa1, 0x44, 0x32, 0x52, 0x49, 0x47, 0xff, 0x65, 0x79, 0x1e, 0xc5, + 0x73, 0x17, 0xb3, 0x36, 0xfc, 0x45, 0xca, 0x90, 0x37, 0x59, 0x1e, 0x16, + 0xab, 0x09, 0x69, 0xcf, 0xda, 0x56, 0x51, 0xfd, 0xeb, 0xcf, 0xcb, 0x8f, + 0xb1, 0xc3, 0x45, 0x2b, 0x7c, 0x0a, 0xa5, 0x9c, 0x0d, 0x2c, 0xad, 0x1c, + 0xd3, 0x33, 0xdd, 0xfe, 0x93, 0x69, 0xa2, 0x4b, 0x4b, 0xcf, 0x1d, 0x20, + 0x98, 0x4a, 0x4f, 0x5b, 0xe9, 0x24, 0xca, 0xfa, 0x18, 0x11, 0x81, 0x8b, + 0x7a, 0xb4, 0x5a, 0xc8, 0xdf, 0x6f, 0x5f, 0x21, 0x07, 0x31, 0x00}; + +const std::string kOemCertStr(kOemCert, kOemCert + sizeof(kOemCert)); + +const uint32_t kOemCertSystemId = 7346; + +// clang-format off +const uint8_t kKeyboxData[72] = { + // Version = 1 - 4 bytes + 0x00, 0x00, 0x00, 0x01, + // System ID = 1337 - 4 bytes + 0x00, 0x00, 0x05, 0x39, + // Encrypted info = arbitrary - 64 bytes + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, +}; +// clang-format on + +const uint32_t kKeyboxSystemId = 1337; +const std::string kKeyboxDataStr(kKeyboxData, + kKeyboxData + sizeof(kKeyboxData)); + +class MockCryptoSession : public CryptoSession { + public: + MockCryptoSession(metrics::CryptoMetrics* metrics) : CryptoSession(metrics) {} + // ~MockCryptoSession() override {} + + bool IsOpen() override { return true; } + CdmSecurityLevel GetSecurityLevel() override { return kSecurityLevelL1; } + CdmSecurityLevel GetSecurityLevel( + RequestedSecurityLevel security_level) override { + return security_level == kLevelDefault ? kSecurityLevelL1 + : kSecurityLevelL3; + } + + MOCK_METHOD(bool, GetCachedSystemId, (uint32_t*), (override)); + MOCK_METHOD(void, SetSystemId, (uint32_t), (override)); + + void SetSystemIdBase(uint32_t system_id) { + CryptoSession::SetSystemId(system_id); + } + + MOCK_METHOD(CdmResponseType, GetProvisioningMethod, + (RequestedSecurityLevel, CdmClientTokenType*), (override)); + MOCK_METHOD(CdmResponseType, GetTokenFromKeybox, + (RequestedSecurityLevel, std::string*), (override)); + MOCK_METHOD(CdmResponseType, GetTokenFromOemCert, + (RequestedSecurityLevel, std::string*), (override)); +}; + +class MockDeviceFiles : public DeviceFiles { + public: + MockDeviceFiles(wvutil::FileSystem* fs) : DeviceFiles(fs) {} + // ~MockDeviceFiles() override {} + + MOCK_METHOD(bool, Init, (CdmSecurityLevel), (override)); + MOCK_METHOD(DeviceFiles::CertificateState, RetrieveOemCertificate, + (std::string*, CryptoWrappedKey*), (override)); +}; +} // namespace + +class SystemIdExtractorTest : public WvCdmTestBase { + protected: + void SetUp() override { + WvCdmTestBase::SetUp(); + crypto_session_.reset(new MockCryptoSession(&crypto_metrics_)); + ASSERT_TRUE(crypto_session_); + device_files_.reset(new MockDeviceFiles(&fs_)); + ASSERT_TRUE(device_files_); + } + + void TearDown() override { + device_files_.reset(); + crypto_session_.reset(); + WvCdmTestBase::TearDown(); + } + + std::unique_ptr CreateExtractor( + RequestedSecurityLevel security_level) { + std::unique_ptr extractor( + new SystemIdExtractor(security_level, crypto_session_.get(), &fs_)); + extractor->SetDeviceFilesForTesting(device_files_.get()); + return extractor; + } + + void ExpectProvisioningType(CdmClientTokenType type) { + EXPECT_CALL(*crypto_session_, GetCachedSystemId).WillOnce(Return(false)); + EXPECT_CALL(*crypto_session_, GetProvisioningMethod(_, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(type), Return(NO_ERROR))); + } + + void ExpectSet(uint32_t system_id) { + EXPECT_CALL(*crypto_session_, SetSystemId(system_id)); + } + + std::unique_ptr crypto_session_ = nullptr; + std::unique_ptr device_files_ = nullptr; + wvutil::FileSystem fs_; + metrics::CryptoMetrics crypto_metrics_; +}; + +TEST_F(SystemIdExtractorTest, ExtractSystemIdFromOemCert) { + uint32_t system_id = 0; + EXPECT_TRUE( + SystemIdExtractor::ExtractSystemIdFromOemCert(kOemCertStr, &system_id)); + EXPECT_EQ(system_id, kOemCertSystemId); +} + +TEST_F(SystemIdExtractorTest, ExtractSystemIdFromKeyboxData) { + uint32_t system_id = 0; + EXPECT_TRUE(SystemIdExtractor::ExtractSystemIdFromKeyboxData(kKeyboxDataStr, + &system_id)); + EXPECT_EQ(system_id, kKeyboxSystemId); +} + +TEST_F(SystemIdExtractorTest, CachedSystemId) { + const uint32_t kCachedSystemId = 1234; + EXPECT_CALL(*crypto_session_, GetCachedSystemId(NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kCachedSystemId), Return(true))); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_TRUE(extractor->ExtractSystemId(&system_id)); + EXPECT_EQ(system_id, kCachedSystemId); +} + +TEST_F(SystemIdExtractorTest, SetSystemIdMetrics) { + const uint32_t kSystemId = 4321; + crypto_session_->SetSystemIdBase(kSystemId); + drm_metrics::WvCdmMetrics::CryptoMetrics metrics_proto; + crypto_metrics_.Serialize(&metrics_proto); + const uint32_t recorded_system_id = static_cast( + metrics_proto.crypto_session_system_id().int_value()); + EXPECT_EQ(recorded_system_id, kSystemId); +} + +TEST_F(SystemIdExtractorTest, GetProvisioningMethod_Failed) { + EXPECT_CALL(*crypto_session_, GetCachedSystemId).WillOnce(Return(false)); + EXPECT_CALL(*crypto_session_, GetProvisioningMethod(_, NotNull())) + .WillOnce(Return(UNKNOWN_ERROR)); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_FALSE(extractor->ExtractSystemId(&system_id)); +} + +TEST_F(SystemIdExtractorTest, GetProvisioningMethod_Unsupported) { + ExpectProvisioningType(static_cast(9999)); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_FALSE(extractor->ExtractSystemId(&system_id)); +} + +TEST_F(SystemIdExtractorTest, DrmCertDevice_NullSystemId) { + ExpectProvisioningType(kClientTokenDrmCert); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_TRUE(extractor->ExtractSystemId(&system_id)); + EXPECT_EQ(system_id, NULL_SYSTEM_ID); +} + +TEST_F(SystemIdExtractorTest, KeyboxDevice_Success) { + ExpectProvisioningType(kClientTokenKeybox); + EXPECT_CALL(*crypto_session_, GetTokenFromKeybox(kLevelDefault, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kKeyboxDataStr), Return(NO_ERROR))); + ExpectSet(kKeyboxSystemId); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_TRUE(extractor->ExtractSystemId(&system_id)); + EXPECT_EQ(system_id, kKeyboxSystemId); +} + +TEST_F(SystemIdExtractorTest, KeyboxDevice_NeedsOtaKeyboxProvisioning) { + ExpectProvisioningType(kClientTokenKeybox); + EXPECT_CALL(*crypto_session_, GetTokenFromKeybox(kLevelDefault, NotNull())) + .WillOnce(Return(NEED_PROVISIONING)); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_TRUE(extractor->ExtractSystemId(&system_id)); + EXPECT_EQ(system_id, NULL_SYSTEM_ID); +} + +TEST_F(SystemIdExtractorTest, KeyboxDevice_FailedToGetKeyboxData) { + ExpectProvisioningType(kClientTokenKeybox); + EXPECT_CALL(*crypto_session_, GetTokenFromKeybox(kLevel3, NotNull())) + .WillOnce(Return(UNKNOWN_ERROR)); + auto extractor = CreateExtractor(kLevel3); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_FALSE(extractor->ExtractSystemId(&system_id)); +} + +TEST_F(SystemIdExtractorTest, KeyboxDevice_FailedToParse) { + const std::string kShortKeyData = "123456"; + ExpectProvisioningType(kClientTokenKeybox); + EXPECT_CALL(*crypto_session_, GetTokenFromKeybox(kLevel3, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kShortKeyData), Return(NO_ERROR))); + auto extractor = CreateExtractor(kLevel3); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_FALSE(extractor->ExtractSystemId(&system_id)); +} + +TEST_F(SystemIdExtractorTest, OemCertDevice_Success) { + ExpectProvisioningType(kClientTokenOemCert); + EXPECT_CALL(*crypto_session_, GetTokenFromOemCert(kLevel3, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kOemCertStr), Return(NO_ERROR))); + ExpectSet(kOemCertSystemId); + auto extractor = CreateExtractor(kLevel3); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_TRUE(extractor->ExtractSystemId(&system_id)); + EXPECT_EQ(system_id, kOemCertSystemId); +} + +TEST_F(SystemIdExtractorTest, OemCertDevice_FailedToGetCert) { + ExpectProvisioningType(kClientTokenOemCert); + EXPECT_CALL(*crypto_session_, GetTokenFromOemCert(kLevelDefault, NotNull())) + .WillOnce(Return(UNKNOWN_ERROR)); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_FALSE(extractor->ExtractSystemId(&system_id)); +} + +TEST_F(SystemIdExtractorTest, OemCertDevice_FailedToParse) { + const std::string kNotACertChain = + wvutil::CdmRandom::RandomData(kOemCertStr.size()); + ExpectProvisioningType(kClientTokenOemCert); + EXPECT_CALL(*crypto_session_, GetTokenFromOemCert(kLevelDefault, NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(kNotACertChain), Return(NO_ERROR))); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_FALSE(extractor->ExtractSystemId(&system_id)); +} + +TEST_F(SystemIdExtractorTest, BccDevice_Success) { + ExpectProvisioningType(kClientTokenBootCertChain); + EXPECT_CALL(*device_files_, Init(kSecurityLevelL1)).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, RetrieveOemCertificate(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kOemCertStr), + Return(DeviceFiles::kCertificateValid))); + ExpectSet(kOemCertSystemId); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_TRUE(extractor->ExtractSystemId(&system_id)); + EXPECT_EQ(system_id, kOemCertSystemId); +} + +TEST_F(SystemIdExtractorTest, BccDevice_NotAvailable) { + ExpectProvisioningType(kClientTokenBootCertChain); + EXPECT_CALL(*device_files_, Init(kSecurityLevelL1)).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, RetrieveOemCertificate(NotNull(), NotNull())) + .WillOnce(Return(DeviceFiles::kCertificateNotFound)); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_TRUE(extractor->ExtractSystemId(&system_id)); + EXPECT_EQ(system_id, NULL_SYSTEM_ID); +} + +TEST_F(SystemIdExtractorTest, BccDevice_FailedToRetrieve) { + ExpectProvisioningType(kClientTokenBootCertChain); + EXPECT_CALL(*device_files_, Init(kSecurityLevelL1)).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, RetrieveOemCertificate(NotNull(), NotNull())) + .WillOnce(Return(DeviceFiles::kCertificateInvalid)); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_FALSE(extractor->ExtractSystemId(&system_id)); +} + +TEST_F(SystemIdExtractorTest, BccDevice_FailedToParse) { + const std::string kNotACertChain = + wvutil::CdmRandom::RandomData(kOemCertStr.size()); + ExpectProvisioningType(kClientTokenBootCertChain); + EXPECT_CALL(*device_files_, Init(kSecurityLevelL1)).WillOnce(Return(true)); + EXPECT_CALL(*device_files_, RetrieveOemCertificate(NotNull(), NotNull())) + .WillOnce(DoAll(SetArgPointee<0>(kNotACertChain), + Return(DeviceFiles::kCertificateValid))); + auto extractor = CreateExtractor(kLevelDefault); + ASSERT_TRUE(extractor); + uint32_t system_id; + EXPECT_FALSE(extractor->ExtractSystemId(&system_id)); +} +} // namespace wvcdm diff --git a/core/test/test_base.cpp b/core/test/test_base.cpp index 7496bb2c..51470ace 100644 --- a/core/test/test_base.cpp +++ b/core/test/test_base.cpp @@ -1,6 +1,6 @@ // Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary -// source code may only be used and distributed under the Widevine Master -// License Agreement. +// source code may only be used and distributed under the Widevine License +// Agreement. #include "test_base.h" @@ -93,17 +93,48 @@ void show_menu(const char* prog_name, const std::string& extra_help_text) { << " in the url" << std::endl << std::endl; + std::cout << " --renewal_server_url=" << std::endl; + std::cout << " configure the renewal server url, please include http[s] " + "in the url" + << std::endl + << " If not set, this defaults to be the same url used by the " + "license server." + << std::endl + << " Some tests, such as LicenseRenewalSpecifiedServer, will " + "ignore this setting. " + << std::endl + << " See comments in the code for an explanation." << std::endl + << std::endl; + std::cout << " --provisioning_server_url=" << std::endl; std::cout << " configure the provisioning server url, please include http[s]" << " in the url" << std::endl << std::endl; + std::cout << " --qa_provisioning" << std::endl; + std::cout << " use the QA provisioning cert and QA test keybox" + << std::endl + << std::endl; + std::cout << " --fake_sleep" << std::endl; std::cout << " Use a fake clock to sleep for duration tests. This cannot" << " be used with a real OEMCrypto." << std::endl << std::endl; + std::cout << " --initial_time=