diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..87a1910f --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# GYP generated makefiles +Makefile +*.Makefile +*.mk + +# Output directory +out/ + +# Certificate created by unit tests +cert.bin + +# Python object files +*.pyc diff --git a/README b/README new file mode 100644 index 00000000..007cd0d0 --- /dev/null +++ b/README @@ -0,0 +1,255 @@ +README for Widevine CDM Partner Kit v2.0 +Date: 3/28/2014 + +This document provides additional details on installation, system +setup, building, and testing components of the Widevine Content +Decryption Module supplied in the Widevine CDM Partner Kit. +This document supplements the information found in the "WV Modular DRM +Security Integration Guide for Common Encryption (CENC): EME/Embedded +Device Supplement v2.1", which is the EME-specific portion of the +dcoeument "WV Modular DRM Security Integration Guide for Common +Encryption (CENC)". + +Kit Contents + +The Widevine CDM Partner Kit is distributed as an XZipped tar file +(.tar.xz). To unpack the kit into your current working directory: + + tar xf widevine-cdm_.tar.xz + + cd widevine-cdm_ + +The remaining instructions will refer to this directory as . + +The top level directories and files of the kit are as follows: + build - build scripts + cdm - the CDM interface declarations and definitions, and tests + core - the CDM implementation, and tests + linux - platform-specific files for the CDM implementation + oemcrypto - OEMCrypto declarations, a mock implementation, and tests + README - this file + run_test.sh - builds and tests the CDM with mock OEMCrypto + +System Setup + +Some third-party software packages are required to build the CDM, OEMCrypto, +and various test modules. The actual versions that have been used and +verified are listed here, but earlier or later versions may also work. If +you already have a different version of a particular package installed +on your system, then you should try the build and unit tests to see if +there are any issues. + +The current set of third-party packages includes: + + - gTest and gMock (version 1.6.0) + - protobufs (Google Protocol Buffers) (version 2.5) + - OpenSSL (libssl and libcrypto) (version 1.0.1e) + - stringencoders (https://code.google.com/p/stringencoders) + - python (version 2.7 or better) + - GYP (python-based build tool) (https://code.google.com/p/gyp, + svn revision 1846) + +The following sections provide information about acquiring +and installing some of these packages. This is not a complete list, but +it tries to cover the items that do not follow the typical methodologies. +Also, the instructions apply to and were tested on an up-to-date Ubuntu +system. They may not work on your system exactly as shown. + +GYP + + The kit uses GYP (Generate Your Projects) to create makefiles. + + The standard GYP source release is through a subversion (svn) checkout + of trunk. Most of this kit's development and testing was performed with + GYP revision 1846. The newest revision at the time of this document + is 1884. You may already have GYP installed on your system, or you may + try "apt-get install gyp" to install the version packaged for your + system. If you choose to install the latest revision (option 1) or install + revision 1846 (option 2), follow the instructions below. + + GYP requires Python. You should already have python 2.7 or better on your + system. There is also a python package called setuptools that GYP requires. + If the GYP build fails, see the section below to install setuptools. + + python --version # will return "Python 2.7.3" or something similar + + mkdir + cd + + # checkout latest GYP (option 1) + svn checkout http://gyp.googlecode.com/svn/trunk/ gyp-read-only + + # checkout GYP revision 1846 (option 2) + svn checkout http://gyp.googlecode.com/svn/trunk/ gyp-read-only -r1846 + + cd gyp-read-only/ + ./setup.py build # this will fail if setuptools is not installed + sudo ./setup.py install + +Python setuptools + + Do this if the "setup.py build" fails. + + mkdir + cd + wget https://bitbucket.org/pypa/setuptools/raw/bootstrap/ez_setup.py \ + -O - | sudo python + + Return to the GYP installation. + +Google Protocol Buffers (protobufs) + + The following instructions will install the protobuf compiler and libraries + in /usr. The default installation directory is /usr/local, which you may + also use, but you should make sure that an older version is not installed + in /usr. + + protoc --version # test whether protobufs already on system + which protoc # where is it installed? + mkdir + cd + wget http://protobuf.googlecode.com/files/protobuf-2.5.0.tar.gz + tar xzf protobuf-2.5.0.tar.gz + cd protobuf-2.5.0/ + ./configure --prefix=/usr --exec-prefix=/usr + make + sudo make install + protoc --version + + The final command should return: libprotoc 2.5.0 + +Google C++ Mocking Framework (gmock and gtest) + + The following instructions will install the Google Mock and + Google Test frameworks. The package can exist anywhere on + the build system. The kit uses gmock-1.6.0.zip and can be + downloaded from https://code.google.com/p/googlemock/downloads/list. + + mkdir + cd + wget https://googlemock.googlecode.com/files/gmock-1.6.0.zip + unzip gmock-1.6.0.zip + cd + mkdir third_party + cd third_party + ln -sf /gmock-1.6.0 gmock + + The kit build will use the symlink when building the unit tests. + +StringEncoders 3.10.3 (stringencoders) + + The following instructions will install and configure the + stringencoders package. After configuring and building the + package, several files must be copied to the kit. + The kit uses stringencoders-v3.10.3.tar.gz which can be downloaded from + https://code.google.com/p/stringencoders/downloads/list. + + mkdir + cd + wget \ + https://stringencoders.googlecode.com/files/stringencoders-v3.10.3.tar.gz + tar xzvf stringencoders-v3.10.3.tar.gz + cd stringencoders-v3.10.3 + ./configure --with-b64wchars='-_=' CFLAGS=-Wno-unused-but-set-variable + make + mkdir -p /third_party/stringencoders/src + cp modp_b64w_data.h /third_party/stringencoders/src + cp src/modp_b64w.c \ + /third_party/stringencoders/src/modp_b64w.cpp + cp src/modp_b64w.h /third_party/stringencoders/src + + Note that the file extension of the source is changed from .c to .cpp. + + The final step will be to edit + /third_party/stringencoders/src/modp_b64w.cpp + and comment out the line '#include "config.h"'. + +What's in the kit? + +The kit contains two major components and a small suite of unit tests: +a Widevine CDM and a Mock OEMCrypto. These components will enable you +to create an OEMCrypto library and a CDM module for your target devices. + +The CDM relies on OEMCrypto for performing secure operations. The Mock +OEMCrypto uses OpenSSL to perform all OEMCrypto functions. This library +is *NOT* secure and it *CANNOT* be used in a production environment, but +it allows you to verify your CDM port before you have a working OEMCrypto +for your device, plus it provides a reference implementation to help you +implement and verify your OEMCrypto. + +The CDM uses a "Host" interface to communicate with the upper layers of +the system and also to obtain certain services it needs, such as timers +and file I/O support. This kit contains a simple implementation of the +Host interface. You will create a complete Host interface for each of +your target devices. + +Building components + +The easiest way to build is to navigate to the top level directory of +the installed kit and run the following script. This does a full build +of the linux target and runs wvcdm_shared_api_unittest: + + ./run_test.sh + +The top-level build script is build/build.py. It configures the build +environment, runs GYP to generate a series of make files. and starts the +top level make. The build scripts are parameterized to build for multiple +targets. All the pieces are provided to build for x86-64 (linux). You +can use the x86-64 build as a template for building for your embedded targets. +The "CDM Porting Guide" supplied with this kit discusses the steps involved. + +Configuring for a new target device + +The kit "out of the box" builds a CDM module and links it into a unit +test executable image that can be run on your linux system. To build +the CDM for a different target device, you will have to install an appropriate +toolchain and SDK for your targets. Then you will have to extend +the build scripts to utilize these resources. You will also have to modify +or add certain source modules to integrate the CDM into your target's +media and/or browser environment. This process is documented in the +"CDM Porting Guide". + +Tests + +Read the sections related to testing in the "WV Modular DRM Security +Integration Guide for Common Encryption (CENC): EME/Embedded Device +Supplement v2.1" + +Test directories can be found in many of the kit's directories. The GYP files +for building the test images are found in the test directory or one level +above the test directory. The test-releated GYP targets will have "test" or +"unittest" in the name. + +Here's a current list of GYP files and test-related targets: + + File - Targets Purpose + cdm/cdm_api_internal.gyp + license_protocol Create protobuf sources for license protocol + device_files Create protobuf sources for license storage + wvcdm_sysdep Build system-dependent layer of CDM + wvcdm_shared Build CDM shared library + wvcdm_shared_api_unittest Build CDM unit tests + cdm/test/gtest.gyp + gtest Provides gtest modules for unit tests + cdm/test/gmock.gyp + gmock Provides gmock modules for unit tests + gmock_main Provides main routine for unit tests + build/platforms/x86-64/oemcrypto/oec_build.gyp + oemcrypto Top level target to build/link OEMCrypto + oemcrypto/oemcrypto.gyp + oec_lib Build OEMCrypto library + oec_mock Build mock OEMCrypto library + oec_unittest Build OEMCrypto unit tests + oemcrypto/mock/oec_mock.gyp + oec_mock Build mock OEMCrypto sources + oemcrypto/prebuilt/oec_prebuilt.gyp + oec_prebuilt Integrate prebuilt OEMCrypto library + +The "CDM Porting Guide" discusses how these targets are used and how you +will modify them to build libraries and test binaries for your target devices. + +The "CDM Porting Guide", the "WV Modular DRM Security Integration Guide for +Common Encryption (CENC): EME/Embedded Device Supplement v2.1", and the +"WV Modular DRM Security Integration Guide for Common Encryption (CENC)" +should provide the information you need to create your OEMCrypto and CDM +implementations for your target devices. diff --git a/build/build.py b/build/build.py new file mode 100755 index 00000000..03237172 --- /dev/null +++ b/build/build.py @@ -0,0 +1,117 @@ +#!/usr/bin/python2 +# Copyright 2013 Google Inc. All rights reserved. + +import gyp +import os +from os import path +import string +import subprocess +import sys +import argparse + +toolchain = {} +starting_dir = os.getcwd() + +toolset_variables = ['CC', 'CXX', 'AR'] +env_variables = ['CFLAGS', 'CXXFLAGS', 'LDFLAGS'] + +gyp_args = string.split('--format=make --depth=.') + +parser = argparse.ArgumentParser(description='Process build options.') +parser_arguments = [] +build_config = '--build=Debug' +global_include_file = '--include=build/global_config.gypi' + +print ' Gyp Args: %s' % (gyp_args) + +def ImportPlatform(name): + os.chdir('build/platforms') + sys.path.insert(0, os.getcwd()) + platforms_list = __import__('cdm_platforms') + platforms = platforms_list.platforms + target_platform = platforms[name] + print ' Target Platform: %s' % (target_platform) + target_folder = target_platform['folder'] + print ' Target Folder: %s' % (target_folder) + target_file = target_platform['file'] + print ' Target File: %s' % (target_file) + global target_env + target_env = target_file + + os.chdir(target_folder) + sys.path.insert(0, os.getcwd()) + target = __import__(target_file) + tooldir = '' + if 'tooldir' in target.toolchain: + tooldir = target.toolchain['tooldir'] + print ' tooldir: %s' % (tooldir) + for i, v in enumerate(toolset_variables): + if v in target.toolchain: + os.environ[v] = path.join(tooldir, target.toolchain[v]) + print ' tool: %s' % (os.environ[v]) + if 'tooldir' in target.toolchain: + tooldir = target.toolchain['tooldir'] + + for i, v in enumerate(env_variables): + if v in target.toolchain: + os.environ[v] = target.toolchain[v] + if hasattr(target, 'gyp_variables'): + global gyp_args + gyp_args += target.gyp_variables + + if hasattr(target, 'export_variables'): + print '---------------has export variables' + + for v in target.export_variables: + first = v.split(':') + x = first[0] + y = target.export_variables[x] + if x=="ADD_PATH": + os.environ["PATH"] = os.environ["PATH"] + y + print ' just updated to PATH' + print os.environ["PATH"] + else: + os.environ[x] = y + print ' just set Env variable %s to %s' % (x, y) + else: + print '-------has NO export variables' + + os.chdir(starting_dir) + os.environ['CDM_TOP'] = starting_dir + + #print ' Actual: %s' % (starting_dir) + +def RunGyp(args): + print ' Args: %s' % (args) + global gyp_args + global build_config + if parser_arguments.release: + build_config = '--build=Release' + gyp_args.append(build_config) + # Append the global include file last. + # This allows a platform definition to exist + # in a gypi file adding the ability for the + # platform definition to use the features of gyp. + gyp_args.append(global_include_file) + gyp.main(gyp_args) + +def ParseOptions(args): + parser = argparse.ArgumentParser() + parser.add_argument("platform", help="The platform configuration you want to build") + parser.add_argument("-r", "--release", help='build release build', action='store_true') + global parser_arguments + parser_arguments = parser.parse_args() + +def main(args): + def exitOnFailure(result): + if not result == 0: + sys.exit(result) + + ParseOptions(args) + ImportPlatform(parser_arguments.platform) + print ' Running Gyp with: %s' % args + + RunGyp(args) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/build/global_config.gypi b/build/global_config.gypi new file mode 100644 index 00000000..dac2751d --- /dev/null +++ b/build/global_config.gypi @@ -0,0 +1,43 @@ +# Copyright 2013 Google Inc. All rights reserved. +{ + # Global place to define variables and defines. + # This file is passed to gyp and the contents included in + # every gyp referenced. + + 'variables': { + 'target_build%':"x86-64", + 'company_name%':'"UndefinedCompanyName"', + 'model_name%':'"UndefinedModelName"', + 'architecture_name%':'"UndefinedArchitectureName"', + 'device_name%':'"UndefinedDeviceName"', + 'product_name%':'"UndefinedProductName"', + 'buildinfo_data%':'"UndefinedBuildInfo"', + 'target_oemcrypto%': 'oec_mock', + 'oemcrypto_target%':'../oemcrypto/mock/oec_mock.gyp:oec_mock', + 'protoc_dir%':'/usr/bin', + 'certificate_provision%':'false', + }, + 'target_defaults': { + 'default_configuration': 'Release', + 'configurations': { + 'Debug': { + 'cflags': ['-g'], + }, + 'Release': {}, + }, # end configurations + 'target_conditions': [ + ['_type=="static_library"', { + 'standalone_static_library': 1, + }] + ], + 'defines': [ + 'PLATFORM_COMPANY_NAME_WV=<(company_name)', + 'PLATFORM_MODEL_NAME_WV=<(model_name)', + 'PLATFORM_ARCHITECTURE_NAME_WV=<(architecture_name)', + 'PLATFORM_DEVICE_NAME_WV=<(device_name)', + 'PLATFORM_PRODUCT_NAME_WV=<(product_name)', + 'PLATFORM_BUILDINFO_WV=<(buildinfo_data)', + 'PLATFORM_CERTIFICATE_PROV=<(certificate_provision)', + ], + } # end target_defaults +} diff --git a/build/platforms/cdm_platforms.py b/build/platforms/cdm_platforms.py new file mode 100644 index 00000000..5f7f6759 --- /dev/null +++ b/build/platforms/cdm_platforms.py @@ -0,0 +1,12 @@ +#!/usr/bin/python2 +# Copyright 2013 Google Inc. All rights reserved. + +# cdm_platforms.py --- Supported platforms +# Modify this file to extend the platforms that can be built by build.py + +platforms = { + 'x86-64': { + 'folder': 'x86-64', + 'file': 'x86-64', + }, +} diff --git a/build/platforms/x86-64/oemcrypto/oec_build.gyp b/build/platforms/x86-64/oemcrypto/oec_build.gyp new file mode 100644 index 00000000..8d817284 --- /dev/null +++ b/build/platforms/x86-64/oemcrypto/oec_build.gyp @@ -0,0 +1,24 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +# +# Build the vendor's oemcrypto (simulated with mock for x86-64) +# +{ + 'targets': [ + { + 'target_name': 'oemcrypto', + 'type': 'static_library', + 'include_dirs': [ + '../../../../core/include', + '../../../../oemcrypto/include', + '../../../../', + ], + 'sources': [ + '../../../../oemcrypto/mock/src/oemcrypto_engine_mock.cpp', + '../../../../oemcrypto/mock/src/oemcrypto_key_mock.cpp', + '../../../../oemcrypto/mock/src/oemcrypto_keybox_mock.cpp', + '../../../../oemcrypto/mock/src/oemcrypto_mock.cpp', + '../../../../oemcrypto/mock/src/wvcrc.cpp', + ], + }, + ], +} diff --git a/build/platforms/x86-64/x86-64.gypi b/build/platforms/x86-64/x86-64.gypi new file mode 100644 index 00000000..5a1b0970 --- /dev/null +++ b/build/platforms/x86-64/x86-64.gypi @@ -0,0 +1,6 @@ +# Copyright 2013 Google Inc. All rights reserved. +{ + 'variables': { + 'certificate_provision':'true', + }, +} diff --git a/build/platforms/x86-64/x86-64.py b/build/platforms/x86-64/x86-64.py new file mode 100644 index 00000000..b1b7c5de --- /dev/null +++ b/build/platforms/x86-64/x86-64.py @@ -0,0 +1,50 @@ +#!/usr/bin/python2 +# Copyright 2013 Google Inc. All rights reserved. +# +# x86-64 baseline +# Definitions: + # external_build - boolean if 0, we will build libwvcdm_shared from source. + # Comment out "V:1" for non-verbose make, it's not based on bool value. + # +toolchain = { + 'tooldir': '/usr/bin/', + 'CC' : 'gcc', + 'CXX' : 'g++', + 'AR' : 'ar', + 'LD' : 'ld', + 'CFLAGS': '-fno-exceptions -fPIC \ + -I/usr/local/include \ + -I./build/platforms/x86-64', + 'CXXFLAGS': '-fno-exceptions -fPIC \ + -I/usr/local/include \ + -I$(CDM_TOP)/build/platforms/x86-64', + 'LDFLAGS': '-L$(CDM_TOP)/prebuilt/gtest/out_x86_linux/Release/lib', +} + +# gyp_variables +# Definitions: + # external_build = vendor can only get binary libwvcdm, no source. + # oemcrypto_lib = where the vendor's oemcrypto lib is if not a mock. + # target_oemcrypto = oemcrypto_lib or comment out for default of oec_mock. +gyp_variables = [ + '-Duse_system_protobuf=1', + '-Dexternal_build=0', + '-Dtarget_build=x86-64', + '-Doemcrypto_target=../build/platforms/x86-64/oemcrypto/oec_build.gyp:oemcrypto', + '-Doemcrypto_lib=', + 'cdm/cdm_api_external.gyp', + '-Dcompany_name="www"', + '-Dmodel_name="www"', + '-Darchitecture_name="x86-64"', + '-Ddevice_name="x86-64 Linux"', + '-Dproduct_name="x86-64 cdm"', + '-Dbuildinfo_data="cdm_partner_2.0"', + '--include=build/platforms/x86-64/x86-64.gypi', +] + +# Comment out "V:1" if you want non-verbose make, it's not based on bool value. + +export_variables = { + 'V':'1', \ + 'builddir_name':'$(CDM_TOP)/out/x86-64', \ +} diff --git a/build/protoc.gypi b/build/protoc.gypi new file mode 100644 index 00000000..cc397ccd --- /dev/null +++ b/build/protoc.gypi @@ -0,0 +1,40 @@ +# Copyright 2014 Google Inc. All rights reserved. +{ + 'variables': { + 'cc_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out/<(proto_out_dir)', + 'proto_in_dir%': '.', + }, + 'rules': [ + { + 'rule_name': 'genproto', + 'extension': 'proto', + 'outputs': [ + '<(cc_dir)/<(RULE_INPUT_ROOT).pb.cc', + '<(cc_dir)/<(RULE_INPUT_ROOT).pb.h', + ], + 'action': [ + '<(protoc_dir)/protoc', + '--proto_path=<(proto_in_dir)', + # Naively you'd use <(RULE_INPUT_PATH) here, but protoc requires + # --proto_path is a strict prefix of the path given as an argument. + '<(proto_in_dir)/<(RULE_INPUT_ROOT)<(RULE_INPUT_EXT)', + '--cpp_out=<(cc_dir)', + ], + 'message': 'Generating C++ code from <(RULE_INPUT_PATH) ccdir=<(cc_dir)', + 'process_outputs_as_sources': 1, + }, + ], + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/protoc_out', + '<(proto_out_dir)', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/protoc_out', + '<(cc_dir)', + ] + }, + # This target exports a hard dependency because it generates header + # files. + 'hard_dependency': 1, +} diff --git a/cdm/cdm_api_external.gyp b/cdm/cdm_api_external.gyp new file mode 100644 index 00000000..644f28cd --- /dev/null +++ b/cdm/cdm_api_external.gyp @@ -0,0 +1,54 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +# +# Builds under the CDM ./build/build.py (target platform) build system +# Refer to the distribution package's README for details. +# +{ + 'targets': [ + { + 'target_name': 'wvcdm_shared_api_unittest', + 'type': 'executable', + 'sources': [ + '../cdm/test/cdm_api_test.cpp', + '../core/test/config_test_env.cpp', + '../core/test/license_request.cpp', + '../core/test/http_socket.cpp', + '../core/test/url_request.cpp', + ], + 'include_dirs': [ + 'include', + '../cdm/include', + '../core/include', + '../core/test', + '../oemcrypto/include', + ], + 'ldflags': [ + '-L$(builddir_name)/$(BUILDTYPE)/lib.target', + '-L$(builddir_name)/$(BUILDTYPE)', + '-Wl,-rpath=$(builddir_name)/$(BUILDTYPE)/lib.target/', + ], + 'libraries': [ + '$(builddir_name)/$(BUILDTYPE)/lib.target/libwvcdm_shared.so', + '-lssl', + '-lcrypto', + '-lpthread', + '-lprotobuf', + ], + 'dependencies': [ + 'test/gmock.gyp:gmock', + 'test/gmock.gyp:gmock_main', + 'test/gtest.gyp:gtest', + ], + 'conditions': [ + [ 'external_build==0', + { + 'dependencies': [ + '../cdm/cdm_api_internal.gyp:wvcdm_shared', + ] + } + ] + ], + }, + ], +} + diff --git a/cdm/cdm_api_internal.gyp b/cdm/cdm_api_internal.gyp new file mode 100644 index 00000000..187ecff5 --- /dev/null +++ b/cdm/cdm_api_internal.gyp @@ -0,0 +1,89 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +# +# Refer to the distribution package's README for information about +# setting up your system, performing the build, and using/testing +# the build targets. +# +# Set external_build=0 via GYP_DEFINES if debug information in +# libwvcdm_shared.so is desirable. +{ + 'targets': [ + { + 'target_name': 'license_protocol', + 'type': 'static_library', + 'sources': ['../core/src/license_protocol.proto',], + 'variables': { + 'proto_in_dir': '../core/src', + 'proto_out_dir': 'license_protocol', + }, + 'includes': ['../build/protoc.gypi'], + }, + { + 'target_name': 'device_files', + 'type': 'static_library', + 'sources': ['../core/src/device_files.proto',], + 'variables': { + 'proto_in_dir': '../core/src', + 'proto_out_dir': 'device_files', + }, + 'includes': ['../build/protoc.gypi'], + }, + { + 'target_name': 'wvcdm_sysdep', + 'type': 'static_library', + 'defines': ['CDM_IMPLEMENTATION'], + 'include_dirs': [ + '../cdm/include', + '../core/include', + '../linux/include', + '../third_party/stringencoders/src', + ], + 'sources': [ + '../cdm/src/file_store.cpp', + '../cdm/src/properties_common.cpp', + '../core/src/string_conversions.cpp', + '../linux/src/lock.cpp', + '../linux/src/log.cpp', + '../third_party/stringencoders/src/modp_b64w.cpp', + ], + }, + { + 'target_name': 'wvcdm_shared', + 'type': 'shared_library', + 'defines': ['CDM_IMPLEMENTATION'], + 'dependencies': [ + 'license_protocol', + 'wvcdm_sysdep', + 'device_files', + '<(oemcrypto_target)', + ], + 'include_dirs': [ + '../cdm/include', + '../core/include', + '../third_party/gmock/include', + '../linux/include', + '../oemcrypto/include', + ], + 'sources': [ + # uses common published api + '../cdm/src/clock.cpp', + '../cdm/src/host_event_listener.cpp', + '../cdm/src/wv_content_decryption_module.cpp', + '../core/src/buffer_reader.cpp', + '../core/src/cdm_engine.cpp', + '../core/src/cdm_session.cpp', + '../core/src/certificate_provisioning.cpp', + '../core/src/crypto_session.cpp', + '../core/src/device_files.cpp', + '../core/src/license.cpp', + '../core/src/oemcrypto_adapter_static.cpp', + '../core/src/policy_engine.cpp', + '../core/src/privacy_crypto.cpp', + '../core/src/properties.cpp', + ], + 'direct_dependencies': [ + 'license_protocol', + ], + }, + ], +} diff --git a/cdm/include/LICENSE b/cdm/include/LICENSE new file mode 100644 index 00000000..a83f8956 --- /dev/null +++ b/cdm/include/LICENSE @@ -0,0 +1,30 @@ +// Copyright for the following files derived from the Chromium project +// content_decryption_module.h +// +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cdm/include/cdm_host_clock.h b/cdm/include/cdm_host_clock.h new file mode 100644 index 00000000..7a074200 --- /dev/null +++ b/cdm/include/cdm_host_clock.h @@ -0,0 +1,26 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_CDM_HOST_CLOCK_H_ +#define WVCDM_CDM_CDM_HOST_CLOCK_H_ + +namespace wvcdm { + +class IClock { + public: + IClock(){} + virtual ~IClock(); + virtual int64_t GetCurrentTimeInSeconds() = 0; +}; + +class HostClock { + friend class Clock; + friend class IClock; + public: + static void SetClockInterface(IClock* iclock); + int64_t GetCurrentTimeInSeconds(); + private: + static IClock* impl_; +}; +} // namspace wvcdm + +#endif // WVCDM_CDM_CDM_HOST_CLOCK_H_ diff --git a/cdm/include/cdm_host_file.h b/cdm/include/cdm_host_file.h new file mode 100644 index 00000000..867e8991 --- /dev/null +++ b/cdm/include/cdm_host_file.h @@ -0,0 +1,43 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_CDM_HOST_FILE_H_ +#define WVCDM_CDM_CDM_HOST_FILE_H_ + +#include "file_store.h" +#include "content_decryption_module.h" + +namespace wvcdm { + +class IFileFactory { + protected: + IFileFactory(){} + virtual ~IFileFactory(){} + public: + virtual File::Impl* NewFileImpl () = 0; +}; + +class File::Impl { + public: + explicit Impl(cdm::Host* const host) : host_(host) {} + FILE* file_; + static void RegisterFileFactory(IFileFactory* factory) { + factory_ = factory; + } + static IFileFactory* factory_; + + virtual bool Exists(const std::string& name); + virtual bool Open(const std::string& name); + virtual bool Close(); + virtual bool Remove(const std::string& name); + virtual size_t Read(char* buffer, size_t bytes); + virtual size_t Write(const char* buffer, size_t bytes); + virtual size_t FileSize(const std::string& name); + + private: + cdm::Host* const host_; + std::string fname_; +}; + +} // namespace wvcdm + +#endif // WVCDM_CDM_CDM_HOST_FILE_H_ diff --git a/cdm/include/cdm_host_timer.h b/cdm/include/cdm_host_timer.h new file mode 100644 index 00000000..c1485f14 --- /dev/null +++ b/cdm/include/cdm_host_timer.h @@ -0,0 +1,45 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_CDM_HOST_TIMER_H_ +#define WVCDM_CDM_CDM_HOST_TIMER_H_ + +#include +#include +#include + +#include "content_decryption_module.h" +#include "timer.h" + +namespace wvcdm { + class ITimerFactory { + public: + virtual Timer::Impl* NewTimerImpl() = 0; + }; + + class Timer::Impl { + friend class wvcdm::Timer; + typedef enum {kIdle, kRunning} TimerState; + public: + static void RegisterTimerFactory(ITimerFactory* factory); + + explicit Impl(cdm::Host* const host); + virtual ~Impl(){} + + void Start(TimerHandler *handler, uint32_t time_in_secs); + + void Stop(); + + bool IsRunning(){return state_ == kRunning;} + + void OnTimerEvent(); + + private: + static ITimerFactory* factory_; + cdm::Host* const host_; + TimerHandler* handler_; + int64_t delay_ms_; + TimerState state_; + }; +} // namespace wvcdm + +#endif // WVCDM_CDM_CDM_HOST_TIMER_H_ diff --git a/cdm/include/content_decryption_module.h b/cdm/include/content_decryption_module.h new file mode 100644 index 00000000..12a7ffa9 --- /dev/null +++ b/cdm/include/content_decryption_module.h @@ -0,0 +1,573 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef WVCDM_CDM_CONTENT_DECRYPTION_MODULE_H_ +#define WVCDM_CDM_CONTENT_DECRYPTION_MODULE_H_ + +#if defined(_MSC_VER) +typedef unsigned char uint8_t; +typedef unsigned int uint32_t; +typedef int int32_t; +typedef __int64 int64_t; +#else +#include +#endif + +// Define CDM_EXPORT so that functionality implemented by the CDM module +// can be exported to consumers. +#if defined(WIN32) + +#if defined(CDM_IMPLEMENTATION) +#define CDM_EXPORT __declspec(dllexport) +#else +#define CDM_EXPORT __declspec(dllimport) +#endif // defined(CDM_IMPLEMENTATION) + +#else // defined(WIN32) + +#if defined(CDM_IMPLEMENTATION) +#define CDM_EXPORT __attribute__((visibility("default"))) +#else +#define CDM_EXPORT +#endif + +#endif // defined(WIN32) + +// The version number must be rolled when the exported functions are updated! +// If the CDM and the adapter use different versions of these functions, the +// adapter will fail to load or crash! +#define INITIALIZE_CDM_MODULE InitializeCdmModule_1 + +extern "C" { +CDM_EXPORT void INITIALIZE_CDM_MODULE(); + +CDM_EXPORT void DeinitializeCdmModule(); + +// Returns a pointer to the requested CDM Host interface upon success. +// Returns NULL if the requested CDM Host interface is not supported. +// The caller should cast the returned pointer to the type matching +// |host_interface_version|. +typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data); + +// Returns a pointer to the requested CDM upon success. +// Returns NULL if an error occurs or the requested |cdm_interface_version| or +// |key_system| is not supported or another error occurs. +// The caller should cast the returned pointer to the type matching +// |cdm_interface_version|. +// Caller retains ownership of arguments and must call Destroy() on the returned +// object. +CDM_EXPORT void* CreateCdmInstance( + int cdm_interface_version, + const char* key_system, int key_system_size, + GetCdmHostFunc get_cdm_host_func, void* user_data); + +CDM_EXPORT int GetCdmVersion(); +} + +namespace cdm { + +class AudioFrames; +class DecryptedBlock; +class VideoFrame; + +enum Status { + kSuccess = 0, + kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample. + kNoKey, // The required decryption key is not available. + kSessionError, // Session management error. + kDecryptError, // Decryption failed. + kDecodeError, // Error decoding audio or video. + kRetry, // Buffer temporarily cannot be accepted. Retry after a short delay. + kNeedsDeviceCertificate // Requires Device Certificate for content licensing +}; + +// This must be consistent with MediaKeyError defined in the +// Encrypted media Extensions (EME) specification: http://goo.gl/3Df8h +enum MediaKeyError { + kUnknownError = 1, + kClientError, + kServiceError, + kOutputError, + kHardwareChangeError, + kDomainError +}; + +// An input buffer can be split into several continuous subsamples. +// A SubsampleEntry specifies the number of clear and cipher bytes in each +// subsample. For example, the following buffer has three subsamples: +// +// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->| +// | clear1 | cipher1 | clear2 | cipher2 | clear3 | cipher3 | +// +// For decryption, all of the cipher bytes in a buffer should be concatenated +// (in the subsample order) into a single logical stream. The clear bytes should +// not be considered as part of decryption. +// +// Stream to decrypt: | cipher1 | cipher2 | cipher3 | +// Decrypted stream: | decrypted1| decrypted2 | decrypted3 | +// +// After decryption, the decrypted bytes should be copied over the position +// of the corresponding cipher bytes in the original buffer to form the output +// buffer. Following the above example, the decrypted buffer should be: +// +// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->| +// | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 | +// +struct SubsampleEntry { + SubsampleEntry(int32_t clear_bytes, int32_t cipher_bytes) + : clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {} + + int32_t clear_bytes; + int32_t cipher_bytes; +}; + +// Represents an input buffer to be decrypted (and possibly decoded). It +// does not own any pointers in this struct. +struct InputBuffer { + InputBuffer() + : data(NULL), + data_size(0), + data_offset(0), + key_id(NULL), + key_id_size(0), + iv(NULL), + iv_size(0), + subsamples(NULL), + num_subsamples(0), + timestamp(0) {} + + const uint8_t* data; // Pointer to the beginning of the input data. + int32_t data_size; // Size (in bytes) of |data|. + + int32_t data_offset; // Number of bytes to be discarded before decryption. + + const uint8_t* key_id; // Key ID to identify the decryption key. + int32_t key_id_size; // Size (in bytes) of |key_id|. + + const uint8_t* iv; // Initialization vector. + int32_t iv_size; // Size (in bytes) of |iv|. + + const struct SubsampleEntry* subsamples; + int32_t num_subsamples; // Number of subsamples in |subsamples|. + + int64_t timestamp; // Presentation timestamp in microseconds. +}; + +struct AudioDecoderConfig { + enum AudioCodec { + kUnknownAudioCodec = 0, + kCodecVorbis, + kCodecAac + }; + + AudioDecoderConfig() + : codec(kUnknownAudioCodec), + channel_count(0), + bits_per_channel(0), + samples_per_second(0), + extra_data(NULL), + extra_data_size(0) {} + + AudioCodec codec; + int32_t channel_count; + int32_t bits_per_channel; + int32_t samples_per_second; + + // Optional byte data required to initialize audio decoders, such as the + // vorbis setup header. + uint8_t* extra_data; + int32_t extra_data_size; +}; + +// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php +enum VideoFormat { + kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting. + kYv12, // 12bpp YVU planar 1x1 Y, 2x2 VU samples. + kI420 // 12bpp YVU planar 1x1 Y, 2x2 UV samples. +}; + +struct Size { + Size() : width(0), height(0) {} + Size(int32_t width, int32_t height) : width(width), height(height) {} + + int32_t width; + int32_t height; +}; + +struct VideoDecoderConfig { + enum VideoCodec { + kUnknownVideoCodec = 0, + kCodecVp8, + kCodecH264 + }; + + enum VideoCodecProfile { + kUnknownVideoCodecProfile = 0, + kVp8ProfileMain, + kH264ProfileBaseline, + kH264ProfileMain, + kH264ProfileExtended, + kH264ProfileHigh, + kH264ProfileHigh10, + kH264ProfileHigh422, + kH264ProfileHigh444Predictive + }; + + VideoDecoderConfig() + : codec(kUnknownVideoCodec), + profile(kUnknownVideoCodecProfile), + format(kUnknownVideoFormat), + extra_data(NULL), + extra_data_size(0) {} + + VideoCodec codec; + VideoCodecProfile profile; + VideoFormat format; + + // Width and height of video frame immediately post-decode. Not all pixels + // in this region are valid. + Size coded_size; + + // Optional byte data required to initialize video decoders, such as H.264 + // AAVC data. + uint8_t* extra_data; + int32_t extra_data_size; +}; + +enum StreamType { + kStreamTypeAudio = 0, + kStreamTypeVideo = 1 +}; + +// ContentDecryptionModule interface that all CDMs need to implement. +// The interface is versioned for backward compatibility. +// Note: ContentDecryptionModule implementations must use the allocator +// provided in CreateCdmInstance() to allocate any Buffer that needs to +// be passed back to the caller. Implementations must call Buffer::Destroy() +// when a Buffer is created that will never be returned to the caller. +class ContentDecryptionModule_1 { + public: + // Generates a |key_request| given |type| and |init_data|. + // + // Returns kSuccess if the key request was successfully generated, in which + // case the CDM must send the key message by calling Host::SendKeyMessage(). + // Returns kSessionError if any error happened, in which case the CDM must + // send a key error by calling Host::SendKeyError(). + virtual Status GenerateKeyRequest( + const char* type, int type_size, + const uint8_t* init_data, int init_data_size) = 0; + + // Adds the |key| to the CDM to be associated with |key_id|. + // + // Returns kSuccess if the key was successfully added, kSessionError + // otherwise. + virtual Status AddKey(const char* session_id, int session_id_size, + const uint8_t* key, int key_size, + const uint8_t* key_id, int key_id_size) = 0; + + // Cancels any pending key request made to the CDM for |session_id|. + // + // Returns kSuccess if all pending key requests for |session_id| were + // successfully canceled or there was no key request to be canceled, + // kSessionError otherwise. + virtual Status CancelKeyRequest( + const char* session_id, int session_id_size) = 0; + + // Performs scheduled operation with |context| when the timer fires. + virtual void TimerExpired(void* context) = 0; + + // Decrypts the |encrypted_buffer|. + // + // Returns kSuccess if decryption succeeded, in which case the callee + // should have filled the |decrypted_buffer| and passed the ownership of + // |data| in |decrypted_buffer| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kDecryptError if any other error happened. + // If the return value is not kSuccess, |decrypted_buffer| should be ignored + // by the caller. + virtual Status Decrypt(const InputBuffer& encrypted_buffer, + DecryptedBlock* decrypted_buffer) = 0; + + // Initializes the CDM audio decoder with |audio_decoder_config|. + // This function is only needed if DecryptAndDecodeSamples() or + // DecryptDecodeAndRenderSamples() is used. It must be called before either + // of these routines is called. + // + // Returns kSuccess if the |audio_decoder_config| is supported and the CDM + // audio decoder is successfully initialized. + // Returns kSessionError if |audio_decoder_config| is not supported. The CDM + // may still be able to do Decrypt(). + // + virtual Status InitializeAudioDecoder( + const AudioDecoderConfig& audio_decoder_config) = 0; + + // Initializes the CDM video decoder with |video_decoder_config|. + // This function is only needed if DecryptAndDecodeFrame() or + // DecryptDecodeAndRenderFrame() is used. It must be called before either + // of these routines is called. + // + // Returns kSuccess if the |video_decoder_config| is supported and the CDM + // video decoder is successfully initialized. + // Returns kSessionError if |video_decoder_config| is not supported. The CDM + // may still be able to do Decrypt(). + // + virtual Status InitializeVideoDecoder( + const VideoDecoderConfig& video_decoder_config) = 0; + + // De-initializes the CDM decoder and sets it to an uninitialized state. The + // caller can initialize the decoder again after this call to re-initialize + // it. This can be used to reconfigure the decoder if the configuration + // changes. + virtual void DeinitializeDecoder(StreamType decoder_type) = 0; + + // Resets the CDM decoder to an initialized clean state. All internal buffers + // MUST be flushed. + virtual void ResetDecoder(StreamType decoder_type) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a + // |video_frame|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |video_frame| (|format| == kEmptyVideoFrame) is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled the |video_frame| and passed the ownership of + // |frame_buffer| in |video_frame| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // a decoded frame (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |video_frame| should be ignored by + // the caller. + virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer, + VideoFrame* video_frame) = 0; + + // Decrypts the |encrypted_buffer| and decodes the decrypted buffer into + // |audio_frames|. Upon end-of-stream, the caller should call this function + // repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty + // |audio_frames| is produced. + // + // Returns kSuccess if decryption and decoding both succeeded, in which case + // the callee will have filled |audio_frames| and passed the ownership of + // |data| in |audio_frames| to the caller. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kNeedMoreData if more data was needed by the decoder to generate + // audio samples (e.g. during initialization and end-of-stream). + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |audio_frames| should be ignored by + // the caller. + virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer, + AudioFrames* audio_frames) = 0; + + // Decrypts the |encrypted_buffer|, decodes the decrypted buffer into a + // video frame, and passes the frame to the rendering FW/HW. No data + // is returned. + // + // Returns kSuccess if decryption, decoding, and rendering all succeeded. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kRetry if |encrypted_buffer| cannot be accepted (e.g, video + // pipeline is full). Caller should retry after a short delay. + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess, |video_frame| should be ignored by + // the caller. + virtual Status DecryptDecodeAndRenderFrame( + const InputBuffer& encrypted_buffer) = 0; + + // Decrypts the |encrypted_buffer|, decodes the decrypted buffer into + // audio frames, and passes the samples to the rendering FW/HW. No + // data is returned. + // + // Returns kSuccess if decryption, decoding, and rendering all succeeded. + // Returns kNoKey if the CDM did not have the necessary decryption key + // to decrypt. + // Returns kRetry if |encrypted_buffer| cannot be accepted (e.g., audio + // pipeline is full). Caller should retry after a short delay. + // Returns kDecryptError if any decryption error happened. + // Returns kDecodeError if any decoding error happened. + // If the return value is not kSuccess or kRetry, the audiostream has failed + // and should be reset. + virtual Status DecryptDecodeAndRenderSamples( + const InputBuffer& encrypted_buffer) = 0; + + // Destroys the object in the same context as it was created. + virtual void Destroy() = 0; + + // Provisioning related methods + virtual Status GetProvisioningRequest( + std::string* request, std::string* default_url) = 0; + + virtual cdm::Status HandleProvisioningResponse( + std::string& response) = 0; + + protected: + ContentDecryptionModule_1() {} + virtual ~ContentDecryptionModule_1() {} +}; + +const int kWidevineCdmInterfaceVersion_1 = 1002; + +typedef ContentDecryptionModule_1 ContentDecryptionModule; +const int kCdmInterfaceVersion = kWidevineCdmInterfaceVersion_1; + +// Represents a buffer created by Allocator implementations. +class Buffer { + public: + // Destroys the buffer in the same context as it was created. + virtual void Destroy() = 0; + + virtual int32_t Capacity() const = 0; + virtual uint8_t* Data() = 0; + virtual void SetSize(int32_t size) = 0; + virtual int32_t Size() const = 0; + + protected: + Buffer() {} + virtual ~Buffer() {} + + private: + Buffer(const Buffer&); + void operator=(const Buffer&); +}; + +// Host interface that the CDM can call into to access browser side services. +// Host interfaces are versioned for backward compatibility. CDM should use +// HostFactory object to request a Host interface of a particular version. +class Host_1 { + public: + // Returns a Buffer* containing non-zero members upon success, or NULL on + // failure. The caller owns the Buffer* after this call. The buffer is not + // guaranteed to be zero initialized. The capacity of the allocated Buffer + // is guaranteed to be not less than |capacity|. + virtual Buffer* Allocate(int32_t capacity) = 0; + + // Requests the host to call ContentDecryptionModule::TimerExpired() in + // |delay_ms| from now with |context|. + virtual void SetTimer(int64_t delay_ms, void* context) = 0; + + // Returns the current epoch wall time in seconds. + virtual double GetCurrentWallTimeInSeconds() = 0; + + // Sends a keymessage event to the application. + // Length parameters should not include null termination. + virtual void SendKeyMessage( + const char* session_id, int32_t session_id_length, + const char* message, int32_t message_length, + const char* default_url, int32_t default_url_length) = 0; + + // Sends a keyerror event to the application. + // |session_id_length| should not include null termination. + virtual void SendKeyError(const char* session_id, + int32_t session_id_length, + MediaKeyError error_code, + uint32_t system_code) = 0; + + // Get private data from the host. This function is limited to internal use. + typedef const void* (*GetPrivateInterface)(const char* interface_name); + virtual void GetPrivateData(int32_t* instance, + GetPrivateInterface* get_interface) = 0; + +//Version 1.3 + // These Virtual member functions extend the cdm::Host interface to allow + // the sharing for platform specific information, between the cdm::Host and + // the CDM. + + virtual int GetPlatformString(const std::string& name, + std::string* value) = 0; + virtual int SetPlatformString(const std::string& name, + const std::string& value) = 0; + virtual int PersistPlatformString(const std::string& name, + const std::string& value) = 0; + virtual int GetPlatformByteArray(const std::string& name, + std::vector* value) = 0; + virtual int SetPlatformByteArray(const std::string& name, + const std::vector& value) = 0; + virtual int PersistPlatformByteArray(const std::string& name, + const std::vector& value) = 0; + + protected: + Host_1() {} + virtual ~Host_1() {} +}; + +const int kWidevineHostInterfaceVersion_1 = 1002; + +typedef Host_1 Host; +const int kHostInterfaceVersion = kWidevineHostInterfaceVersion_1; + +// Represents a decrypted block that has not been decoded. +class DecryptedBlock { + public: + virtual void SetDecryptedBuffer(Buffer* buffer) = 0; + virtual Buffer* DecryptedBuffer() = 0; + + virtual void SetTimestamp(int64_t timestamp) = 0; + virtual int64_t Timestamp() const = 0; + + protected: + DecryptedBlock() {} + virtual ~DecryptedBlock() {} +}; + +class VideoFrame { + public: + enum VideoPlane { + kYPlane = 0, + kUPlane = 1, + kVPlane = 2, + kMaxPlanes = 3, + }; + + virtual void SetFormat(VideoFormat format) = 0; + virtual VideoFormat Format() const = 0; + + virtual void SetSize(cdm::Size size) = 0; + virtual cdm::Size Size() const = 0; + + virtual void SetFrameBuffer(Buffer* frame_buffer) = 0; + virtual Buffer* FrameBuffer() = 0; + + virtual void SetPlaneOffset(VideoPlane plane, int32_t offset) = 0; + virtual int32_t PlaneOffset(VideoPlane plane) = 0; + + virtual void SetStride(VideoPlane plane, int32_t stride) = 0; + virtual int32_t Stride(VideoPlane plane) = 0; + + virtual void SetTimestamp(int64_t timestamp) = 0; + virtual int64_t Timestamp() const = 0; + + protected: + VideoFrame() {} + virtual ~VideoFrame() {} +}; + +// Represents decrypted and decoded audio frames. AudioFrames can contain +// multiple audio output buffers, which are serialized into this format: +// +// |<------------------- serialized audio buffer ------------------->| +// | int64_t timestamp | int64_t length | length bytes of audio data | +// +// For example, with three audio output buffers, the AudioFrames will look +// like this: +// +// |<----------------- AudioFrames ------------------>| +// | audio buffer 0 | audio buffer 1 | audio buffer 2 | +class AudioFrames { + public: + virtual void SetFrameBuffer(Buffer* buffer) = 0; + virtual Buffer* FrameBuffer() = 0; + + protected: + AudioFrames() {} + virtual ~AudioFrames() {} +}; + +} // namespace cdm + +#endif // WVCDM_CDM_CONTENT_DECRYPTION_MODULE_H_ diff --git a/cdm/include/host_event_listener.h b/cdm/include/host_event_listener.h new file mode 100644 index 00000000..8455e85a --- /dev/null +++ b/cdm/include/host_event_listener.h @@ -0,0 +1,33 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_HOST_EVENT_LISTENER_H_ +#define WVCDM_CDM_HOST_EVENT_LISTENER_H_ + +#include "cdm_engine.h" +#include "content_decryption_module.h" +#include "wv_cdm_common.h" +#include "wv_cdm_event_listener.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class HostEventListener : public WvCdmEventListener { + public: + HostEventListener(cdm::Host* host, CdmEngine* cdm_engine) + : host_(host), cdm_engine_(cdm_engine) {} + virtual ~HostEventListener() {} + + // wvcdm::WvCdmEventListener implementation. + virtual void OnEvent(const CdmSessionId& session_id, + CdmEventType cdm_event) OVERRIDE; + + private: + cdm::Host* const host_; + CdmEngine* const cdm_engine_; + + CORE_DISALLOW_COPY_AND_ASSIGN(HostEventListener); +}; + +} // namespace wvcdm + +#endif // WVCDM_CDM_HOST_EVENT_LISTENER_H_ diff --git a/cdm/include/properties_configuration.h b/cdm/include/properties_configuration.h new file mode 100644 index 00000000..bb484278 --- /dev/null +++ b/cdm/include/properties_configuration.h @@ -0,0 +1,47 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_PROPERTIES_CONFIGURATION_H_ +#define WVCDM_CDM_PROPERTIES_CONFIGURATION_H_ + +#include "wv_cdm_constants.h" +#include "properties.h" + +namespace wvcdm { + +// If false begin license usage on first playback +const bool kPropertyBeginLicenseUsageWhenReceived = false; + +// If false, calls to Generate Key request, after the first one, +// will result in a renewal request being generated +const bool kPropertyRequireExplicitRenewRequest = false; + +// Set only one of the three below to true. If secure buffer +// is selected, fallback to userspace buffers may occur +// if L1/L2 OEMCrypto APIs fail +const bool kPropertyOemCryptoUseSecureBuffers = false; +const bool kPropertyOemCryptoUseFifo = true; +const bool kPropertyOemCryptoUseUserSpaceBuffers = true; + +// If false, keyboxes will be used as client identification +// and passed as the token in the license request. +// The default value of false for PLATFORM_CERTIFICATE_PROV is set in +// global_config.gypi. It can be overridden to true in the platform specific +// .gypi files if you want your device to use certificates for provisioning. +const bool kPropertyUseCertificatesAsIdentification = PLATFORM_CERTIFICATE_PROV; + +// If false, extraction of widevine PSSH information from the PSSH box +// takes place external to the CDM. This will become the default behaviour +// once all platforms support it. +const bool kExtractPsshData = true; + +// If true, session_id parameter to CdmEngine::Decrypt can be empty; the +// function will try to find out the session_id from the key_id. +const bool kDecryptWithEmptySessionSupport = true; + +// If true, device files will be moved to the directory specified by +// Properties::GetDeviceFilesBasePath +const bool kSecurityLevelPathBackwardCompatibilitySupport = false; + +} // namespace wvcdm + +#endif // WVCDM_CDM_PROPERTIES_CONFIGURATION_H_ diff --git a/cdm/include/wv_cdm_common.h b/cdm/include/wv_cdm_common.h new file mode 100644 index 00000000..8f24843d --- /dev/null +++ b/cdm/include/wv_cdm_common.h @@ -0,0 +1,12 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_WV_CDM_COMMON_H_ +#define WVCDM_CDM_WV_CDM_COMMON_H_ + +#if defined(COMPILER_MSVC) || defined(__clang__) +#define OVERRIDE override +#else +#define OVERRIDE +#endif + +#endif // WVCDM_CDM_WV_CDM_COMMON_H_ diff --git a/cdm/include/wv_cdm_version.h b/cdm/include/wv_cdm_version.h new file mode 100644 index 00000000..ab7972ae --- /dev/null +++ b/cdm/include/wv_cdm_version.h @@ -0,0 +1,3 @@ +// Widevine CDM Kit Version + #define WV_CDM_VERSION "v2.0.8-0-679" + diff --git a/cdm/include/wv_content_decryption_module.h b/cdm/include/wv_content_decryption_module.h new file mode 100644 index 00000000..44407532 --- /dev/null +++ b/cdm/include/wv_content_decryption_module.h @@ -0,0 +1,162 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_ +#define WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_ + +#include "cdm_client_property_set.h" +#include "cdm_engine.h" +#include "cdm_host_clock.h" +#include "cdm_host_file.h" + +#include "clock.h" +#include "content_decryption_module.h" +#include "host_event_listener.h" + +#include "wv_cdm_common.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class WVClientPropertySet : public wvcdm::CdmClientPropertySet { + public: + WVClientPropertySet() + : use_privacy_mode_(false) {} + + virtual ~WVClientPropertySet() {} + + void set_security_level(const std::string& securityLevel) { + security_level_ = securityLevel; + } + + virtual std::string security_level() const { + return security_level_; + } + + void set_use_privacy_mode(bool usePrivacyMode) { + use_privacy_mode_ = usePrivacyMode; + } + + virtual bool use_privacy_mode() const { + return use_privacy_mode_; + } + + void set_service_certificate(const std::vector& serviceCertificate) { + service_certificate_ = serviceCertificate; + } + + virtual std::vector service_certificate() const { + return service_certificate_; + } + + virtual bool is_session_sharing_enabled() const { + return true; // This is unused by common cdm but we need a definition + // for the pure virtual methods. + } + + void set_is_session_sharing_enabled(bool shareKeys) { + return; // This is unused by common cdm but we need a definition + // for the pure virtual methods. + } + + virtual uint32_t session_sharing_id() const { + return 1; // This is unused by common cdm but we need a + // definition for the pure virtual methods. + } + + virtual void set_session_sharing_id(uint32_t id) { + return; // This is unused by common cdm but we need a + // definition for the pure virtual methods. + } + private: + CORE_DISALLOW_COPY_AND_ASSIGN(WVClientPropertySet); + + std::string security_level_; + bool use_privacy_mode_; + std::vector service_certificate_; +}; + +class WvContentDecryptionModule : public cdm::ContentDecryptionModule, + public IFileFactory, + public IClock { + File::Impl* NewFileImpl() { return new File::Impl(host_); } + public: + explicit WvContentDecryptionModule(cdm::Host* host) + : host_(host), host_event_listener_(host, &cdm_engine_) { + File::Impl::RegisterFileFactory(this); + HostClock::SetClockInterface(this); + } + virtual ~WvContentDecryptionModule(); + // cdm::ContentDecryptionModule implementation. + virtual cdm::Status GenerateKeyRequest(const char* type, int type_size, + const uint8_t* init_data, + int init_data_size) OVERRIDE; + + virtual cdm::Status AddKey(const char* session_id, int session_id_size, + const uint8_t* key, int key_size, + const uint8_t* key_id, int key_id_size) OVERRIDE; + + virtual cdm::Status CancelKeyRequest(const char* session_id, + int session_id_size) OVERRIDE; + + virtual void TimerExpired(void* context) OVERRIDE; + + virtual cdm::Status Decrypt(const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_buffer) OVERRIDE; + + virtual cdm::Status InitializeAudioDecoder( + const cdm::AudioDecoderConfig& audio_decoder_config) OVERRIDE; + + virtual cdm::Status InitializeVideoDecoder( + const cdm::VideoDecoderConfig& video_decoder_config) OVERRIDE; + + virtual void DeinitializeDecoder(cdm::StreamType decoder_type) OVERRIDE; + + virtual void ResetDecoder(cdm::StreamType decoder_type) OVERRIDE; + + virtual cdm::Status DecryptAndDecodeFrame( + const cdm::InputBuffer& encrypted_buffer, + cdm::VideoFrame* video_frame) OVERRIDE; + + virtual cdm::Status DecryptAndDecodeSamples( + const cdm::InputBuffer& encrypted_buffer, + cdm::AudioFrames* audio_frames) OVERRIDE; + + virtual cdm::Status DecryptDecodeAndRenderFrame( + const cdm::InputBuffer& encrypted_buffer) OVERRIDE; + + virtual cdm::Status DecryptDecodeAndRenderSamples( + const cdm::InputBuffer& encrypted_buffer) OVERRIDE; + + virtual void Destroy() OVERRIDE; + + // Provisioning related methods + virtual cdm::Status GetProvisioningRequest( + std::string* request, std::string* default_url); + + virtual cdm::Status HandleProvisioningResponse( + std::string& response); + + void EnablePolicyTimer(); + void DisablePolicyTimer(); + void OnTimerEvent(); + + private: + virtual int64_t GetCurrentTimeInSeconds() { + return host_->GetCurrentWallTimeInSeconds(); + } + cdm::Status DoSubsampleDecrypt(CdmSessionId& session_id, + CdmDecryptionParameters& parameters, + std::vector& iv, + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block); + CdmEngine cdm_engine_; + cdm::Host* const host_; + HostEventListener host_event_listener_; + WVClientPropertySet property_set_; + + CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule); +}; + +} // namespace wvcdm + +#endif // WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_ diff --git a/cdm/src/clock.cpp b/cdm/src/clock.cpp new file mode 100644 index 00000000..3850ca22 --- /dev/null +++ b/cdm/src/clock.cpp @@ -0,0 +1,24 @@ +#include "clock.h" + +#include + +#include "cdm_host_clock.h" + +namespace wvcdm { + +IClock* HostClock::impl_ = NULL; + +IClock::~IClock() { + HostClock::impl_ = NULL; +} + +int64_t Clock::GetCurrentTime() { + return HostClock::impl_ ? + HostClock::impl_->GetCurrentTimeInSeconds() : -1; +} + +void HostClock::SetClockInterface(IClock* iclock) { + HostClock::impl_ = iclock; +} + +} // namespace wvcdm diff --git a/cdm/src/file_store.cpp b/cdm/src/file_store.cpp new file mode 100644 index 00000000..3c78908d --- /dev/null +++ b/cdm/src/file_store.cpp @@ -0,0 +1,146 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include +#include +#include "cdm_host_file.h" + +namespace wvcdm { + +IFileFactory* File::Impl::factory_ = NULL; + +// File::Impl() Section +// The file handler for cert.bin, aka DeviceCertificate is all we're +// setting up for now. + +bool File::Impl::Exists(const std::string& name) { + if (name == "cert.bin") { + std::vector value; + if (host_->GetPlatformByteArray("DeviceCertificate", &value)) { + return true; + } + } + return false; +} + +bool File::Impl::Open(const std::string& name) { + if (name == "cert.bin") { + fname_= name; + return true; + } + return false; +} + +bool File::Impl::Close() { + fname_ = ""; + return true; +} + +bool File::Impl::Remove(const std::string& name) { + if (Exists(name)) { + std::vector value(0); + if (host_->SetPlatformByteArray("DeviceCertificate", value)) { + return true; + } + } + return false; +} + +size_t File::Impl::Read(char* buffer, size_t bytes) { + if (fname_ == "cert.bin") { + std::vector value; + if (host_->GetPlatformByteArray("DeviceCertificate", &value)) { + memcpy(buffer, &value[0], bytes); + return bytes; + } + } + return 0; +} + +size_t File::Impl::Write(const char* buffer, size_t bytes) { + if (fname_ == "cert.bin") { + std::vector value; + value.resize(bytes); + memcpy(&value[0], buffer, bytes); + if (host_->PersistPlatformByteArray("DeviceCertificate", value)) { + return bytes; + } + } + return 0; +} + +size_t File::Impl::FileSize(const std::string& name) { + if (name == "cert.bin") { + std::vector value; + if (host_->GetPlatformByteArray("DeviceCertificate", &value)) { + return value.size(); + } + } + return 0; +} + +File::File() : impl_(File::Impl::factory_->NewFileImpl()) {} + +File::~File() { + Close(); + delete impl_; +} + +bool File::Open(const std::string& name, int flags) { + return impl_->Open(name); +} + +void File::Close() { + impl_->Close(); + return; +} + +ssize_t File::Read(char* buffer, size_t bytes) { + return impl_->Read(buffer, bytes); +} + +ssize_t File::Write(const char* buffer, size_t bytes) { + return impl_->Write(buffer, bytes); +} + +bool File::Exists(const std::string& path) { + return impl_->Exists(path); +} + +bool File::Remove(const std::string& path) { + return impl_->Remove(path); +} + +bool File::Copy(const std::string& from, const std::string& to) { + // Required for linkage only - no current API implementation in the partner CDM is required. + return false; +} + +bool File::List(const std::string& path, std::vector* files) { + // Required for linkage only - no current API implementation in the partner CDM is required. + return false; +} + +bool File::CreateDirectory(std::string path) { + // Required for linkage only - no current API implementation in the partner CDM is required. + return true; +} + +bool File::IsDirectory(const std::string& path) { + // Required for linkage only - no current API implementation in the partner CDM is required. + return false; +} + +bool File::IsRegularFile(const std::string& path) { + // Required for linkage only - no current API implementation in the partner CDM is required. + return false; +} + +ssize_t File::FileSize(const std::string& path) { + size_t size = impl_->FileSize(path); + if (size > 0) { + return size; + } + return -1; +} + +} // namespace wvcdm diff --git a/cdm/src/host_event_listener.cpp b/cdm/src/host_event_listener.cpp new file mode 100644 index 00000000..7135009d --- /dev/null +++ b/cdm/src/host_event_listener.cpp @@ -0,0 +1,38 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "host_event_listener.h" + +#include "log.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +void HostEventListener::OnEvent(const CdmSessionId& session_id, + CdmEventType cdm_event) { + switch (cdm_event) { + case LICENSE_RENEWAL_NEEDED_EVENT: { + wvcdm::CdmKeyMessage cdm_message; + std::string server_url; + CdmResponseType result = cdm_engine_->GenerateRenewalRequest( + session_id, &cdm_message, &server_url); + if (result == wvcdm::KEY_MESSAGE) { + host_->SendKeyMessage(session_id.data(), session_id.length(), + cdm_message.data(), cdm_message.length(), + server_url.data(), server_url.length()); + } else { + LOGD("Error on Generating a Renewal Request!"); + host_->SendKeyError(session_id.data(), session_id.size(), + cdm::kUnknownError, 0); + } + break; + } + case LICENSE_EXPIRED_EVENT: { + host_->SendKeyError(session_id.data(), session_id.size(), + cdm::kUnknownError, 0); + break; + } + } +} + +} // namespace wvcdm diff --git a/cdm/src/properties_common.cpp b/cdm/src/properties_common.cpp new file mode 100644 index 00000000..539e28f8 --- /dev/null +++ b/cdm/src/properties_common.cpp @@ -0,0 +1,95 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "properties.h" + +#include +#include + +#include "log.h" + +namespace wvcdm { + +bool Properties::GetCompanyName(std::string* company_name) { + if (!company_name) { + LOGW("Properties::GetCompanyName: Invalid parameter"); + return false; + } + *company_name = PLATFORM_COMPANY_NAME_WV; + + return true; +} + +bool Properties::GetModelName(std::string* model_name) { + if (!model_name) { + LOGW("Properties::GetModelName: Invalid parameter"); + return false; + } + *model_name = PLATFORM_MODEL_NAME_WV; + return true; + +} + +bool Properties::GetArchitectureName(std::string* arch_name) { + if (!arch_name) { + LOGW("Properties::GetArchitectureName: Invalid parameter"); + return false; + } + *arch_name = PLATFORM_ARCHITECTURE_NAME_WV; + return true; + +} + +bool Properties::GetDeviceName(std::string* device_name) { + if (!device_name) { + LOGW("Properties::GetDeviceName: Invalid parameter"); + return false; + } + *device_name = PLATFORM_DEVICE_NAME_WV; + return true; + +} + +bool Properties::GetProductName(std::string* product_name) { + if (!product_name) { + LOGW("Properties::GetProductName: Invalid parameter"); + return false; + } + *product_name = PLATFORM_PRODUCT_NAME_WV; + return true; + +} + +bool Properties::GetBuildInfo(std::string* build_info) { + if (!build_info) { + LOGW("Properties::GetBuildInfo: Invalid parameter"); + return false; + } + *build_info = PLATFORM_BUILDINFO_WV; + return true; + +} + +bool Properties::GetDeviceFilesBasePath(CdmSecurityLevel security_level, + std::string* base_path) { + // no-op + return true; +} + +bool Properties::GetFactoryKeyboxPath(std::string* keybox) { + if (!keybox) { + LOGW("Properties::GetFactoryKeyboxPath: Invalid parameter"); + return false; + } + return true; +} + +bool Properties::GetOEMCryptoPath(std::string* library_name) { + if (!library_name) { + LOGW("Properties::GetOEMCryptoPath: Invalid parameter"); + return false; + } + *library_name = "out/Default/lib.target/liboemcrypto.so"; + return true; +} + +} // namespace wvcdm diff --git a/cdm/src/timer.cpp b/cdm/src/timer.cpp new file mode 100644 index 00000000..50aa3cdb --- /dev/null +++ b/cdm/src/timer.cpp @@ -0,0 +1,76 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Timer class - provides a simple timer implementation +// +#include "cdm_host_timer.h" +#include "scoped_ptr.h" +#include "timer.h" + +#include +#include +#include +#include +#include + +namespace wvcdm { + +ITimerFactory* Timer::Impl::factory_ = NULL; + +Timer::Impl::Impl(cdm::Host* const host) + : host_(host), handler_(NULL), delay_ms_(0), + state_(kIdle) { +} + +void Timer::Impl::RegisterTimerFactory(ITimerFactory* factory) { + factory_ = factory; +} + +void Timer::Impl::Start(TimerHandler* handler, uint32_t time_in_secs) { + handler_ = handler; + delay_ms_ = time_in_secs * 1000; + state_ = kRunning; + host_->SetTimer(delay_ms_, this); +} + +void Timer::Impl::OnTimerEvent() { + if (kRunning == state_) { + handler_->OnTimerEvent(); + host_->SetTimer(delay_ms_, this); + } +} + +void Timer::Impl::Stop() { + state_ = kIdle; +} + +Timer::Timer() : impl_(NULL) {} + +Timer::~Timer() { + if (impl_) + delete impl_; +} + +bool Timer::Start(TimerHandler* handler, uint32_t time_in_secs) { + if (!handler || 0 == time_in_secs) + return false; + if (!impl_ && Impl::factory_) + impl_ = Impl::factory_->NewTimerImpl(); + if(!impl_) + return false; + + impl_->Start(handler, time_in_secs); + return IsRunning(); +} + +void Timer::Stop() { + if (impl_) + impl_->Stop(); +} + +bool Timer::IsRunning() { + if (!impl_) + return false; + return impl_->IsRunning(); +} + +} // namespace wvcdm diff --git a/cdm/src/wv_content_decryption_module.cpp b/cdm/src/wv_content_decryption_module.cpp new file mode 100644 index 00000000..710b9260 --- /dev/null +++ b/cdm/src/wv_content_decryption_module.cpp @@ -0,0 +1,410 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "wv_content_decryption_module.h" + +#include +#include + +#include "cdm_client_property_set.h" +#include "content_decryption_module.h" + +#include "log.h" +#include "OEMCryptoCENC.h" +#include "properties.h" + +#include "wv_cdm_constants.h" +#include "wv_cdm_types.h" +#include "wv_cdm_version.h" + +void INITIALIZE_CDM_MODULE() {} + +void DeinitializeCdmModule() {} + +void* CreateCdmInstance(int cdm_interface_version, const char* key_system, + int key_system_size, GetCdmHostFunc get_cdm_host_func, + void* user_data) { + if (cdm_interface_version != cdm::kCdmInterfaceVersion) return NULL; + + cdm::Host* host = static_cast( + get_cdm_host_func(cdm::kHostInterfaceVersion, user_data)); + + if (!host) return NULL; + + return static_cast( + new wvcdm::WvContentDecryptionModule(host)); +} + +int GetCdmVersion() { return cdm::kCdmInterfaceVersion; } + +namespace { +static const std::string kWvCdmVersionString(WV_CDM_VERSION); + +const int kCdmPolicyTimerDurationSeconds = 1; +const int kCdmPolicyTimerCancel = 0; + +// The iso spec only uses the lower 8 bytes of the iv as +// the counter. +const uint32_t kCencIvSize = 8; +const uint32_t kIvSize = 16; + +bool Ctr128Add(size_t block_count, uint8_t* counter) { + if (NULL == counter) + return false; + if (0 == block_count) + return true; + uint8_t carry = 0; + uint8_t n = kIvSize - 1; + while (n >= kCencIvSize) { + uint32_t temp = block_count & 0xff; + temp += counter[n]; + temp += carry; + counter[n] = temp & 0xff; + carry = (temp & 0x100) ? 1 : 0; + block_count = block_count >> 8; + n--; + if (!block_count && !carry) { + break; + } + } + return true; +} +} // namespace + +namespace wvcdm { + +// An empty iv string signals that the frame is unencrypted. +bool IsBufferEncrypted(const cdm::InputBuffer& input_buffer) { + return input_buffer.iv_size != 0; +} + +// cdm::ContentDecryptionModule implementation. + +WvContentDecryptionModule::~WvContentDecryptionModule() { + DisablePolicyTimer(); +} + +cdm::Status WvContentDecryptionModule::GenerateKeyRequest( + const char* type, int type_size, const uint8_t* init_data, + int init_data_size) { + LOGI("Enter WvContentDecryptionModule::GenerateKeyRequest()"); + CdmInitData init_data_internal(reinterpret_cast(init_data), + init_data_size); + CdmKeyMessage key_request; + CdmSessionId session_id; + + std::string security_level; + std::string privacy_mode; + kVectorBytes service_certificate; + + host_->GetPlatformString("SecurityLevel", &security_level); + host_->GetPlatformString("PrivacyOn", &privacy_mode); + host_->GetPlatformByteArray("ServiceCertificate", &service_certificate); + + property_set_.set_security_level(security_level); + property_set_.set_use_privacy_mode(privacy_mode == "True" ? 1 : 0 ); + property_set_.set_service_certificate(service_certificate); + + CdmResponseType result = + cdm_engine_.OpenSession("com.widevine.alpha", &property_set_, &session_id); + + if (NEED_PROVISIONING == result) { + LOGI("Need to aquire a Device Certificate from the Provisioning Server"); + return cdm::kNeedsDeviceCertificate; + } + + if (NO_ERROR != result) return cdm::kSessionError; + + if (!cdm_engine_.AttachEventListener(session_id, &host_event_listener_)) { + cdm_engine_.CloseSession(session_id); + return cdm::kSessionError; + } + + CdmAppParameterMap app_parameters; // empty + CdmKeySetId key_set_id; // empty + std::string server_url; + + result = cdm_engine_.GenerateKeyRequest( + session_id, key_set_id, init_data_internal, kLicenseTypeStreaming, + app_parameters, &key_request, &server_url); + if (KEY_MESSAGE != result) { + cdm_engine_.CloseSession(session_id); + return cdm::kSessionError; + } + + host_->SendKeyMessage(session_id.data(), session_id.length(), + key_request.data(), key_request.length(), + server_url.data(), server_url.length()); + + return cdm::kSuccess; +} + +cdm::Status WvContentDecryptionModule::AddKey(const char* session_id, + int session_id_size, + const uint8_t* key, int key_size, + const uint8_t* key_id, + int key_id_size) { + LOGI("Enter WvContentDecryptionModule::AddKey()\n"); + CdmSessionId session_id_internal(session_id, session_id_size); + CdmKeyResponse key_data((const char*)key, key_size); + CdmKeySetId key_set_id; + + CdmResponseType response = cdm_engine_.AddKey(session_id_internal, + key_data, &key_set_id); + + if (response == KEY_ADDED) { + EnablePolicyTimer(); + return cdm::kSuccess; + } else { + return cdm::kSessionError; + } + +} + +cdm::Status WvContentDecryptionModule::CancelKeyRequest(const char* session_id, + int session_id_size) { + LOGI("Enter WvContentDecryptionModule::CancelKeyRequest()\n"); + CdmSessionId session_id_internal(session_id, session_id_size); + return cdm_engine_.CancelKeyRequest(session_id_internal) == NO_ERROR + ? cdm::kSuccess + : cdm::kSessionError; +} + +void WvContentDecryptionModule::TimerExpired(void* context) { + LOGI("Timer expired, send cdm_engine OnTimerEvent"); + if (this != context) { + LOGD("Context should have been set, Timer Expired Error\n"); + return; + } + OnTimerEvent(); +} + +cdm::Status WvContentDecryptionModule::Decrypt( + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block) { + LOGI("=>Enter WvContentDecryptionModule::Decrypt()\n"); + if (encrypted_buffer.iv_size != KEY_IV_SIZE) + return cdm::kDecryptError; + std::vector < uint8_t > iv(KEY_IV_SIZE); + memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size); + + KeyId key_id(reinterpret_cast(encrypted_buffer.key_id), + encrypted_buffer.key_id_size); + + CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id? + + if (NULL == encrypted_buffer.subsamples + || encrypted_buffer.num_subsamples <= 0) + return cdm::kDecryptError; + + CdmDecryptionParameters parameters(&key_id, + encrypted_buffer.data, 0, &iv, 0, + NULL); + parameters.is_secure = false; + return DoSubsampleDecrypt(session_id, + parameters, + iv, + encrypted_buffer, + decrypted_block); +} + +cdm::Status WvContentDecryptionModule::InitializeAudioDecoder( + const cdm::AudioDecoderConfig& audio_decoder_config) { + LOGI("WvContentDecryptionModule::InitializeAudioDecoder() Not implemented\n"); + return cdm::kDecodeError; +} + +cdm::Status WvContentDecryptionModule::InitializeVideoDecoder( + const cdm::VideoDecoderConfig& video_decoder_config) { + LOGI("WvContentDecryptionModule::InitializeVideoDecoder() Not implemented\n"); + return cdm::kDecodeError; +} + +void WvContentDecryptionModule::DeinitializeDecoder( + cdm::StreamType decoder_type) { + LOGI("WvContentDecryptionModule::DeInitializeDecoder() Not implemented\n"); +} + +void WvContentDecryptionModule::ResetDecoder(cdm::StreamType decoder_type) { + LOGI("WvContentDecryptionModule::ResetDecoder() Not implemented\n"); +} + +cdm::Status WvContentDecryptionModule::DecryptAndDecodeFrame( + const cdm::InputBuffer& encrypted_buffer, cdm::VideoFrame* video_frame) { + LOGI("WvContentDecryptionModule::DecryptAndDecodeFrame() Not implemented\n"); + return cdm::kDecodeError; +} + +cdm::Status WvContentDecryptionModule::DecryptAndDecodeSamples( + const cdm::InputBuffer& encrypted_buffer, cdm::AudioFrames* audio_frames) { + LOGI( + "WvContentDecryptionModule::DecryptAndDecodeSamples() Not implemented\n"); + return cdm::kDecodeError; +} + +// This is the Level 1 API. When the host application calls the CDM's +// DecryptDecodeAndRenderFrame(), rather than the CDM's Decrypt(), +// OEMCrypto_DecryptCTR() will be told to use direct rendering with no +// cleartext in the return. +cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderFrame( + const cdm::InputBuffer& encrypted_buffer) { + LOGI("WvContentDecryptionModule::DecryptDecodeAndRenderFrame()\n"); + + if (encrypted_buffer.iv_size != KEY_IV_SIZE) return cdm::kDecryptError; + + std::vector iv(KEY_IV_SIZE); + memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size); + KeyId key_id(reinterpret_cast(encrypted_buffer.key_id), + encrypted_buffer.key_id_size); + CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id. + + if (NULL == encrypted_buffer.subsamples + || encrypted_buffer.num_subsamples <= 0) + return cdm::kDecryptError; + + CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0, + NULL); + return DoSubsampleDecrypt(session_id, parameters, iv, encrypted_buffer, + NULL); +} + +// This is the Level 1 API. When the host application calls the CDM's +// DecryptDecodeAndRenderSamples(), rather than the CDM's Decrypt(), +// OEMCrypto_DecryptCTR() will be told to use direct rendering with no cleartext +// in the return. +cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderSamples( + const cdm::InputBuffer& encrypted_buffer) { + LOGI("WvContentDecryptionModule::DecryptDecodeAndRenderSamples()\n"); + + if (encrypted_buffer.iv_size != KEY_IV_SIZE) return cdm::kDecryptError; + + std::vector iv(KEY_IV_SIZE); + memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size); + KeyId key_id(reinterpret_cast(encrypted_buffer.key_id), + encrypted_buffer.key_id_size); + CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id. + + if (NULL == encrypted_buffer.subsamples || + encrypted_buffer.num_subsamples <= 0) + return cdm::kDecryptError; + + CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0, + NULL); + parameters.is_video = false; // override the default true value for audio. + return DoSubsampleDecrypt(session_id, parameters, iv, encrypted_buffer, + NULL); +} + +void WvContentDecryptionModule::Destroy() { delete this; } + +// Provisioning related methods +cdm::Status WvContentDecryptionModule::GetProvisioningRequest( + std::string* request, std::string* provisioning_server_url) { + if (cdm_engine_.GetProvisioningRequest( + static_cast(request), + provisioning_server_url) == NO_ERROR) { + return cdm::kSuccess; + } + return cdm::kSessionError; +} + +cdm::Status WvContentDecryptionModule::HandleProvisioningResponse( + std::string& response) { + if (cdm_engine_.HandleProvisioningResponse( + static_cast(response)) == NO_ERROR) { + return cdm::kSuccess; + } + return cdm::kSessionError; +} + +void WvContentDecryptionModule::EnablePolicyTimer() { + LOGI("WvContentDecryptionModule::EnablePolicyTimer()\n"); + host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, this); +} + +void WvContentDecryptionModule::DisablePolicyTimer() { + LOGI("WvContentDecryptionModule::DisablePolicyTimer()\n"); + host_->SetTimer(kCdmPolicyTimerCancel, NULL); +} + +void WvContentDecryptionModule::OnTimerEvent() { + + LOGI("WvContentDecryptionModule::OnTimerEvent()\n"); + cdm_engine_.OnTimerEvent(); +} + +cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt( + CdmSessionId& session_id, + CdmDecryptionParameters& parameters, + std::vector < uint8_t >& iv, + const cdm::InputBuffer& encrypted_buffer, + cdm::DecryptedBlock* decrypted_block) { + + /* This routine assumes session_id and iv have already + been initialized by the caller and encrypted_buffer contains subsample + information. Also, parameters is expected to be pre-initialized with any + needed parameters not related to subsample parsing. + decrypted_block may be NULL. */ + CdmResponseType status = NO_ERROR; + uint8_t* output_buffer = decrypted_block + ? reinterpret_cast( + decrypted_block->DecryptedBuffer()->Data()) + : NULL; + size_t offset = 0; + size_t encrypted_offset = 0; + uint32_t block_ctr = 0; + const cdm::SubsampleEntry *subsamples = encrypted_buffer.subsamples; + bool first = true; + + for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) { + const cdm::SubsampleEntry& subsample = subsamples[i]; + + for (int is_encrypted = 0; is_encrypted < 2; ++is_encrypted) { + size_t bytes = + is_encrypted ? subsample.cipher_bytes : subsample.clear_bytes; + if (0 == bytes) + continue; + if (is_encrypted) { + uint32_t counter = encrypted_offset / kIvSize; + ::Ctr128Add(counter - block_ctr, &iv[0]); + block_ctr = counter; + } + + parameters.encrypt_buffer = &encrypted_buffer.data[encrypted_buffer + .data_offset + offset]; + if (output_buffer) + parameters.decrypt_buffer = &output_buffer[offset]; + + parameters.encrypt_length = bytes; + parameters.decrypt_buffer_length = encrypted_buffer.data_size - offset; + parameters.block_offset = encrypted_offset % kIvSize; + + offset += bytes; + if (is_encrypted) + encrypted_offset += bytes; + + parameters.is_encrypted = is_encrypted; + parameters.subsample_flags = + (true == first) ? OEMCrypto_FirstSubsample : 0; + parameters.subsample_flags |= ( + offset == encrypted_buffer.data_size ? OEMCrypto_LastSubsample : 0); + + first = false; + + status = cdm_engine_.Decrypt(session_id, parameters); + + switch (status) { + case wvcdm::NEED_KEY: + return cdm::kNoKey; + break; + case wvcdm::NO_ERROR: + break; + default: + return cdm::kDecryptError; + break; + } + } + } + return cdm::kSuccess; +} + +} // namespace wvcdm diff --git a/cdm/test/cdm_api_test.cpp b/cdm/test/cdm_api_test.cpp new file mode 100644 index 00000000..b92527c9 --- /dev/null +++ b/cdm/test/cdm_api_test.cpp @@ -0,0 +1,1595 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// This source file provides a basic set of unit tests for the Content +// Decryption Module (CDM). It exercises much of the API that will be +// required by the host application to get the license and keys for +// rendering protected content. + +// Review the TestHost class below to observe how the CDM interfaces with +// the host application. +// +#include + +#include +#include +#include +#include + +#include "clock.h" +#include "config_test_env.h" +#include "content_decryption_module.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "license_request.h" +#include "log.h" + +#include "scoped_ptr.h" +#include "string_conversions.h" +#include "url_request.h" +#include "wv_cdm_types.h" + +#include "wv_cdm_common.h" + +using ::testing::_; +using ::testing::AtLeast; +using ::testing::Invoke; +using ::testing::Return; +using wvcdm::scoped_ptr; + +static const int kTestPolicyRenewalDelaySeconds = 60; +static const int kDelayWaitToForRenewalMessageSeconds = 2; +// Linux based get time for host. +// This can be tailored if required. +double GetCurrentTestTime() { + struct timeval tv; + tv.tv_sec = tv.tv_usec = 0; + gettimeofday(&tv, NULL); + return tv.tv_sec; +} + +using wvcdm::kStringPairs; +using wvcdm::kVectorPairs; + +// These classes below are naive implementation of the abstract classes defined +// in the CDM interface (content_decryptiom_module.h), which are used for tests +// only. +class TestBuffer : public cdm::Buffer { + public: + static TestBuffer* Create(uint32_t capacity); + + virtual void Destroy() OVERRIDE; + + virtual int32_t Capacity() const OVERRIDE; + virtual uint8_t* Data() OVERRIDE; + virtual void SetSize(int32_t size) OVERRIDE; + virtual int32_t Size() const OVERRIDE; + + private: + // TestBuffer can only be created by calling Create(). + explicit TestBuffer(uint32_t capacity); + // TestBuffer can only be destroyed by calling Destroy(). + virtual ~TestBuffer(); + + uint8_t* buffer_; + int32_t capacity_; + int32_t size_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TestBuffer); +}; + +class TestHostFile { + public: + TestHostFile() : fname_(""), file_(NULL) {} + virtual ~TestHostFile() { if (file_) fclose(file_); } + virtual bool Init(const std::string& name); + virtual bool Exists(); + virtual bool Remove(); + virtual size_t Size(); + virtual size_t Read(char* buffer, size_t bytes); + virtual size_t Write(const char* buffer, size_t bytes); + virtual std::string GetFileName() { return fname_;} + + private: + std::string fname_; + FILE* file_; + CORE_DISALLOW_COPY_AND_ASSIGN(TestHostFile); +}; + +bool::TestHostFile::Init(const std::string& name) { + fname_ = name; +} + +bool TestHostFile::Exists() { + file_ = fopen(fname_.c_str(), "rb"); + bool exists = file_; + if (file_) { + fclose(file_); + file_ = NULL; + } + return (exists); +} + +bool TestHostFile::Remove() { + if (Exists()) { + if (remove(fname_.c_str())) { + return true; + } + } + return false; +} + +size_t TestHostFile::Size() { + file_ = fopen(fname_.c_str(), "rb"); + if (file_) { + fseek (file_, 0, SEEK_END); + size_t size = ftell (file_); + rewind (file_); + + if (file_) { + fclose(file_); + file_ = NULL; + } + return size; + } + return 0; +} + +size_t TestHostFile::Read(char* buffer, size_t bytes) { + file_ = fopen(fname_.c_str(), "rb"); + if (file_) { + fseek (file_, 0, SEEK_END); + size_t size = ftell (file_); + rewind (file_); + size_t check = fread (buffer,1,size,file_); + if (file_) { + fclose(file_); + file_ = NULL; + } + return check; + } + return 0; +} + +size_t TestHostFile::Write(const char* buffer, size_t bytes) { + FILE* file_ = fopen(fname_.c_str(), "w+"); + if (file_) { + size_t check = fwrite (buffer,1,bytes,file_); + if (file_) { + fclose(file_); + file_ = NULL; + } + return check; + } + return 0; +} + +class IHostTime { + public: + virtual double GetCurrentTestTime() = 0; +}; + +class SimplePThread { +public: + SimplePThread(); + ~SimplePThread(); + void Start(uint64_t periodicity, void* host_ptr, void* context); + void Stop(); + uint32_t GetTickCount(); + +private: + static bool is_running_; + pthread_t simple_timer_thread_; + + typedef void* (*TimeThreadFunc)(void *arg_list); + static void* Tthread_function(void *arg_list); + TimeThreadFunc time_thread_function_; + struct arg_list_struct { + uint64_t periodicity; + void* host_ptr; + void* context; + }; + static uint32_t tick_count_; +}; + +bool SimplePThread::is_running_ = false; +uint32_t SimplePThread::tick_count_ = 0; + +SimplePThread::SimplePThread() : simple_timer_thread_(0) { +} + +SimplePThread::~SimplePThread() { + LOGI("SimplePThread DTOR: calling Stop() to shutdown thread."); + Stop(); +} + +void SimplePThread::Stop() { + if (is_running_) { + void* result = NULL; + LOGI("SimplePThread is running and now it will be cancelled\n"); + pthread_cancel(simple_timer_thread_); + pthread_join(simple_timer_thread_, &result); + is_running_ = false; + tick_count_ = 0; + } +} + +void SimplePThread::Start(uint64_t periodicity, void* host_ptr, void* context) { + struct arg_list_struct* arg_list = new(struct arg_list_struct); + + arg_list->periodicity = periodicity; + arg_list->host_ptr = host_ptr; + arg_list->context = context; + + time_thread_function_ = &Tthread_function; + Stop(); + is_running_ = true; + if(pthread_create(&simple_timer_thread_, NULL, time_thread_function_, + arg_list)) { + LOGI("Error creating pthread\n"); + return; + } + LOGI("Created the Simple thread to support the timer callback\n"); +} + +void* SimplePThread::Tthread_function(void *arg_list) { + LOGI("Entered the SimplePThread::Tthread_function\n"); + + uint64_t periodicity = (static_cast(arg_list))->periodicity; + void* host_ptr = (static_cast(arg_list))->host_ptr; + void* context = (static_cast(arg_list))->context; + delete static_cast(arg_list); + + LOGI("starting the while loop in the secondary thread\n"); + + while (is_running_) { + usleep(static_cast(periodicity * 1000)); + if (is_running_) { + ((cdm::ContentDecryptionModule*)host_ptr)->TimerExpired(context); + tick_count_++; + } + } + + return NULL; +} + +uint32_t SimplePThread::GetTickCount() { + return tick_count_; +} + +class TestHost : public cdm::Host, public IHostTime { + public: + // These structs are used to store the KeyMessages and KeyErrors passed to + // this class' objects. + struct KeyMessage { + KeyMessage() {} + std::string session_id; + std::string message; + std::string default_url; + }; + + struct KeyError { + KeyError() : error_code(cdm::kUnknownError), system_code(0) {} + std::string session_id; + cdm::MediaKeyError error_code; + uint32_t system_code; + }; + + TestHost(); + virtual ~TestHost(); + + // cdm::Host implementation. + virtual cdm::Buffer* Allocate(int32_t capacity) OVERRIDE; + + MOCK_METHOD2(SetTimer, void(int64_t, void*)); + virtual void DoSetTimer(int64_t delay_ms, void* context); + // Returns the current "global" time from the GetCurrentTestTime() function + // unless the time is manually set using TestHost::SetCurrentTime()(). + MOCK_METHOD0(GetCurrentWallTimeInSeconds, double(void)); + + virtual void SendKeyMessage(const char* session_id, int32_t session_id_length, + const char* message, int32_t message_length, + const char* default_url, + int32_t default_url_length) OVERRIDE; + + virtual void SendKeyError(const char* session_id, int32_t session_id_length, + cdm::MediaKeyError error_code, + uint32_t system_code) OVERRIDE; + + virtual void GetPrivateData(int32_t* instance, + GetPrivateInterface* get_interface) OVERRIDE; + + virtual int GetPlatformString(const std::string& name, + std::string* value) OVERRIDE; + + virtual int SetPlatformString(const std::string& name, + const std::string& value) OVERRIDE;; + + virtual int PersistPlatformString(const std::string& name, + const std::string& value) OVERRIDE;; + + virtual int GetPlatformByteArray(const std::string& name, + std::vector* value) OVERRIDE; + + virtual int SetPlatformByteArray(const std::string& name, + const std::vector& value) OVERRIDE; + + virtual int PersistPlatformByteArray(const std::string& name, + const std::vector& value) OVERRIDE; + + // Methods only for this test. + void SetCurrentTime(double current_time); + void FastForwardTimeForNextTimerEvent(); + int KeyMessagesSize() const; + int KeyErrorsSize() const; + + // Returns Key{Message,Error} (replace Message with Error for KeyError). It + // returns the most recent message passed to SendKeyMessage(). Another call + // to this method without a new SendKeyMessage() call will return an empty + // KeyMessage struct. + KeyMessage GetLastKeyMessage(); + KeyError GetLastKeyError(); + + KeyMessage GetKeyMessage(int index) const; + KeyError GetKeyError(int index) const; + + void* context() { return context_; } + void SetCdmPtr(cdm::ContentDecryptionModule* host_cdm_ptr); + + virtual double GetCurrentTestTime(); + uint32_t GetTickCount(); + + private: + // This is used in GetCurrentWallTimeInSeconds(). This field is used to + // control the "current time". + double current_time_; + bool current_time_is_set_; + + std::vector key_messages_; + std::vector key_errors_; + + bool has_new_key_message_; + bool has_new_key_error_; + + void* context_; + int64_t delay_ms_; + + cdm::ContentDecryptionModule* host_cdm_ptr_; + + // These are containers for the platform sharing data. + std::set platform_strings_set_; + std::set platform_vectors_set_; + TestHostFile test_host_file_; + void LoadPersistentValues(); + SimplePThread simple_thread_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TestHost); +}; + +class TestHostFactory { + public: + TestHost* GetTestHost(); + + private: + scoped_ptr test_host_; +}; + +TestBuffer* TestBuffer::Create(uint32_t capacity) { + return new TestBuffer(capacity); +} + +void TestBuffer::Destroy() { + if (buffer_) { + delete[] buffer_; + buffer_ = NULL; + } + delete this; +} + +int32_t TestBuffer::Capacity() const { return capacity_; } + +uint8_t* TestBuffer::Data() { return buffer_; } + +void TestBuffer::SetSize(int32_t size) { size_ = size; } + +int32_t TestBuffer::Size() const { return size_; } + +TestBuffer::TestBuffer(uint32_t capacity) + : buffer_(new uint8_t[capacity]), capacity_(capacity) {} + +TestBuffer::~TestBuffer() {} + +TestHost::TestHost() + : current_time_(::GetCurrentTestTime()), + current_time_is_set_(false), + has_new_key_message_(false), + has_new_key_error_(false), + context_(NULL), + delay_ms_(0), + host_cdm_ptr_(NULL) { + LoadPersistentValues(); +} + +TestHost::~TestHost() {} + +cdm::Buffer* TestHost::Allocate(int32_t capacity) { + return TestBuffer::Create(capacity); +} + +// SetTimer is a mock method mapping to DoSetTimer(). The cdm::Host +// calls back to the TimerExpired callback +// every delay_ms. Zero delay_ms means WV is requesting +// termination of timer services. +void TestHost::DoSetTimer(int64_t delay_ms, void* context) { + if (delay_ms == 0) { + simple_thread_.Stop(); + return; + } + simple_thread_.Start(delay_ms, (void*)host_cdm_ptr_, context); +} + +void TestHost::SendKeyMessage(const char* session_id, int32_t session_id_length, + const char* message, int32_t message_length, + const char* default_url, + int32_t default_url_length) { + KeyMessage key_message; + key_message.session_id.assign(session_id, session_id_length); + key_message.message.assign(message, message_length); + key_message.default_url.assign(default_url, default_url_length); + key_messages_.push_back(key_message); + has_new_key_message_ = true; +} + +void TestHost::SendKeyError(const char* session_id, int32_t session_id_length, + cdm::MediaKeyError error_code, + uint32_t system_code) { + KeyError key_error; + key_error.session_id.assign(session_id, session_id_length); + key_error.error_code = error_code; + key_error.system_code = system_code; + key_errors_.push_back(key_error); + has_new_key_error_ = true; +} + +void TestHost::SetCurrentTime(double current_time) { + current_time_is_set_ = true; + current_time_ = current_time; +} + +void TestHost::FastForwardTimeForNextTimerEvent() { + current_time_is_set_ = true; + current_time_ += (delay_ms_ / 1000.0); + delay_ms_ = 0; +} + +void TestHost::GetPrivateData(int32_t* instance, + GetPrivateInterface* get_interface) { + LOGI("NOTIMPLEMENTED()"); +} + +// The Platform methods below are implemented to fill and retrieve +// platform specific information. The host application takes responsibility for +// this interface. + +int TestHost::GetPlatformString(const std::string& name, + std::string* value) { + std::set::iterator it; + + for (it = platform_strings_set_.begin(); + it != platform_strings_set_.end(); ++it) { + + kStringPairs sp = *it; + std::string x = sp.first; + std::string y = sp.second; + if (x == name) { + *value = y; + return 1; + } + } + *value = "Not Found!"; + return 0; +} + +int TestHost::SetPlatformString(const std::string& name, + const std::string& value) { + kStringPairs sp(name, value); + platform_strings_set_.insert(sp); + return 1; +} + +int TestHost::PersistPlatformString(const std::string& name, + const std::string& value) { + kStringPairs sp(name, value); + // Write the pairs to a file such that they can be retrieved by + // GetPlatformString() even after a power cycle. + return 1; +} + +int TestHost::SetPlatformByteArray(const std::string& name, + const std::vector& value) { + // A zero value pair is an erase only. + std::set::iterator it; + for (it = platform_vectors_set_.begin(); + it != platform_vectors_set_.end(); ++it) { + kVectorPairs vp = *it; + if (vp.first == name) { + platform_vectors_set_.erase(vp); + } + } + + if (value.size() > 0) { + kVectorPairs vp(name, value); + platform_vectors_set_.insert(vp); + } + + return 1; +} + +int TestHost::PersistPlatformByteArray(const std::string& name, + const std::vector& value) { + + // Assume input name = "DeviceCertificate" and + // test_host_file.fname_ == "cert.bin" + // No other cases are currently needed or supported. + if (value.size() == 0) { + remove(test_host_file_.GetFileName().c_str()); + + } else { + std::string string_buffer; + string_buffer.resize(value.size()); + memcpy(&string_buffer[0], &value[0], value.size()); + test_host_file_.Write(&string_buffer[0], value.size()); + } + kVectorPairs vp(name, value); + + SetPlatformByteArray(name, value); + // open file and persist this so that it can be retrieved as a string/vector + // correlated pair. Vendor needs to implement this! + return 1; +} + +int TestHost::GetPlatformByteArray(const std::string& name, + std::vector* value) { + std::set::iterator it; + for (it = platform_vectors_set_.begin(); + it != platform_vectors_set_.end(); ++it) { + + kVectorPairs vp = *it; + std::string x = vp.first; + if (x == name) { + *value = vp.second; + return 1; + } + } + + value = NULL; + return 0; +} + +int TestHost::KeyMessagesSize() const { return key_messages_.size(); } + +int TestHost::KeyErrorsSize() const { return key_errors_.size(); } + +double TestHost::GetCurrentTestTime() { + current_time_is_set_ = true; + FastForwardTimeForNextTimerEvent(); + return current_time_; +} + +TestHost::KeyMessage TestHost::GetLastKeyMessage() { + if (!has_new_key_message_) { + LOGD("No NEW"); + return KeyMessage(); + } + + if (key_messages_.empty()) { + LOGD("empty"); + return KeyMessage(); + } + + LOGD("not empty"); + has_new_key_message_ = false; + return key_messages_.back(); +} + +TestHost::KeyError TestHost::GetLastKeyError() { + if (!has_new_key_error_) return KeyError(); + + if (key_errors_.empty()) return KeyError(); + + has_new_key_error_ = false; + return key_errors_.back(); +} + +TestHost::KeyMessage TestHost::GetKeyMessage(int index) const { + return key_messages_[index]; +} + +TestHost::KeyError TestHost::GetKeyError(int index) const { + return key_errors_[index]; +} + +void TestHost::SetCdmPtr(cdm::ContentDecryptionModule* host_cdm_ptr) { + host_cdm_ptr_ = host_cdm_ptr; +} + +uint32_t TestHost::GetTickCount() { + return simple_thread_.GetTickCount(); +} + +TestHost* TestHostFactory::GetTestHost() { + + if (!test_host_.get()) test_host_.reset(new TestHost()); + + return test_host_.get(); + + return NULL; +} + +// The CDM::Host object is responsible for storing and retrieving items +// that are accessed via GetPlatformByteArray. Currently, support for +// the device certificate is all that is required. + +void TestHost::LoadPersistentValues() { + test_host_file_.Init("cert.bin"); + if (test_host_file_.Exists()) { + size_t size = test_host_file_.Size(); + std::string string_buffer; + string_buffer.resize(size); + test_host_file_.Read(&string_buffer[0], size); + + std::vector vector_buffer(size); + memcpy(&vector_buffer[0], &string_buffer[0], size); + SetPlatformByteArray("DeviceCertificate", vector_buffer); + + } +} + +class TestDecryptedBlock : public cdm::DecryptedBlock { + public: + TestDecryptedBlock(); + virtual ~TestDecryptedBlock(); + + virtual void SetDecryptedBuffer(cdm::Buffer* buffer) OVERRIDE; + virtual cdm::Buffer* DecryptedBuffer() OVERRIDE; + + virtual void SetTimestamp(int64_t timestamp) OVERRIDE; + virtual int64_t Timestamp() const OVERRIDE; + + private: + cdm::Buffer* buffer_; + int64_t timestamp_; + + CORE_DISALLOW_COPY_AND_ASSIGN(TestDecryptedBlock); +}; + +TestDecryptedBlock::TestDecryptedBlock() : buffer_(NULL), timestamp_(0) {} + +TestDecryptedBlock::~TestDecryptedBlock() { + if (buffer_) { + buffer_->Destroy(); + buffer_ = NULL; + } +} + +void TestDecryptedBlock::SetDecryptedBuffer(cdm::Buffer* buffer) { + if (buffer_) buffer_->Destroy(); + buffer_ = buffer; +} + +cdm::Buffer* TestDecryptedBlock::DecryptedBuffer() { return buffer_; } + +void TestDecryptedBlock::SetTimestamp(int64_t timestamp) { + timestamp_ = timestamp; +} + +int64_t TestDecryptedBlock::Timestamp() const { return timestamp_; } + +namespace { +// Default license server, can be configured using --server command line option +// Default key id (pssh), can be configured using --keyid command line option +const char kKeySystemWidevine[] = "com.widevine.alpha"; +std::string g_client_auth; +wvcdm::KeyId g_key_id; +wvcdm::CdmKeySystem g_key_system; +std::string g_license_server; +std::string g_port; +wvcdm::KeyId g_wrong_key_id; +int g_use_full_path = 0; // cannot use boolean in getopt_long + +} // namespace + +namespace wvcdm { + +void* GetCdmHost(int host_interface_version, void* user_data); + +class WvCdmApiTest : public testing::Test { + public: + WvCdmApiTest() : cdm_(NULL) {} + ~WvCdmApiTest() { + if (cdm_) cdm_->Destroy(); + } + + protected: + virtual void SetUp() { + cdm_ = reinterpret_cast(::CreateCdmInstance( + cdm::kCdmInterfaceVersion, kKeySystemWidevine, + strlen(kKeySystemWidevine), GetCdmHost, &host_factory_)); + host_ = host_factory_.GetTestHost(); + host_->SetCdmPtr(cdm_); + + host_->SetPlatformString("SecurityLevel", "L1"); + host_->SetPlatformString("PrivacyOn", "False"); + host_->SetPlatformString("UsesServiceCertificates", "False"); + + // By default we allow the timer to fire twice; once + // when the addkey comes, and once when the wvcdm destructs and + // disables the timer. + EXPECT_CALL(*host_, SetTimer(_,_)) + .Times(AtLeast(2)) + .WillRepeatedly(Invoke(host_, &TestHost::DoSetTimer)); + + EXPECT_CALL(*host_, GetCurrentWallTimeInSeconds()) + .Times(AtLeast(1)) + .WillRepeatedly(Invoke(host_, &TestHost::GetCurrentTestTime)); + + //emulate pulling a certificate out of persistent storage. + static const size_t kPrivacyCertSize = 256; + uint8_t privacyCertRaw[kPrivacyCertSize]; + memset(privacyCertRaw, kPrivacyCertSize, 0); + for (int i = 0; i < 256; i++) {privacyCertRaw[i] = i;} + kVectorBytes cert(kPrivacyCertSize); + memcpy(&cert[0], privacyCertRaw, kPrivacyCertSize); + + host_->SetPlatformByteArray("ServiceCertificate", cert); + INITIALIZE_CDM_MODULE(); + } + + void GenerateKeyRequest(const std::string& key_system, + const std::string& key_id) { + + std::string init_data = key_id; + + char type = 0; // unused but as published + int type_size; // unused but as published + + cdm::Status status = cdm_->GenerateKeyRequest( + &type, type_size, (const uint8_t*)init_data.data(), init_data.length()); + + // cdm::Host must handle the certificate provisioning request. + if (status == cdm::kNeedsDeviceCertificate) { + std::string provisioning_server_url; + std::string prov_request; + if (cdm_->GetProvisioningRequest(&prov_request, + &provisioning_server_url) == cdm::kSuccess) { + + UrlRequest url_request(provisioning_server_url, + kDefaultHttpsPort, true, true); + + url_request.PostCertRequestInQueryString(prov_request); + std::string message; + int resp_bytes = url_request.GetResponse(&message); + + if (resp_bytes) { + if (cdm_->HandleProvisioningResponse(message) == cdm::kSuccess) { + status = cdm_->GenerateKeyRequest(&type, type_size, + (const uint8_t*)init_data.data(), init_data.length()); + } + } + } + } + EXPECT_EQ(cdm::kSuccess, status); + } + + // posts a request and extracts the drm message from the response + std::string GetKeyRequestResponse(const std::string& server_url, + const std::string& client_auth, + int expected_response) { + // Use secure connection and chunk transfer coding. + UrlRequest url_request(server_url + client_auth, g_port, true, true); + if (!url_request.is_connected()) { + return ""; + } + + url_request.PostRequest(key_msg_); + std::string response; + int resp_bytes = url_request.GetResponse(&response); + LOGD("response:\r\n%s", response.c_str()); + LOGD("end %d bytes response dump", resp_bytes); + + // Some license servers return 400 for invalid message, some + // return 500; treat anything other than 200 as an invalid message. + int status_code = url_request.GetStatusCode(response); + int kHttpOk = 200; + if (expected_response == kHttpOk) { + EXPECT_EQ(kHttpOk, status_code); + } else { + EXPECT_NE(kHttpOk, status_code); + } + + if (status_code != kHttpOk) { + return ""; + } else { + std::string drm_msg; + LicenseRequest lic_request; + lic_request.GetDrmMessage(response, drm_msg); + LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(), + HexEncode(reinterpret_cast(drm_msg.data()), + drm_msg.size()).c_str()); + return drm_msg; + } + } + + void CancelKeyRequest(std::string session_id) { + cdm::Status status = + cdm_->CancelKeyRequest(session_id.data(), session_id.length()); + EXPECT_EQ(cdm::kSuccess, status); + } + + void AddKey(std::string& session_id, std::string& drm_msg) { + cdm::Status status = + cdm_->AddKey(session_id.data(), session_id.size(), + (const uint8_t*)drm_msg.data(), drm_msg.size(), NULL, 0); + EXPECT_EQ(cdm::kSuccess, status); + } + + // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. + void DecryptClearPayloadTest() { + typedef struct DecryptionData { + bool is_encrypted; + bool is_secure; + wvcdm::KeyId key_id; + std::vector encrypt_data; + std::vector iv; + size_t block_offset; + std::vector decrypt_data; + } DecryptionData; + + DecryptionData data; + data.is_encrypted = true; + data.is_secure = false; + + // WHAT: Key ID of key used to encrypt the test content. + // WHY: This is used by the secure layer to look up the content key + data.key_id = wvcdm::a2bs_hex("92D20D250D425ABD992AF42E4CD64165"); + + // WHAT: Dummy encrypted data. + data.encrypt_data = wvcdm::a2b_hex( + "3b2cbde084973539329bd5656da22d20396249bf4a18a51c38c4743360cc9fea" + "a1c78d53de1bd7e14dc5d256fd20a57178a98b83804258c239acd7aa38f2d7d2" + "eca614965b3d22049e19e236fc1800e60965d8b36415677bf2f843d50a6943c4" + "683c07c114a32f5e5fbc9939c483c3a1b2ecd3d82b554d649798866191724283" + "f0ab082eba2da79aaca5c4eaf186f9ee9a0c568f621f705a578f30e4e2ef7b96" + "5e14cc046ce6dbf272ee5558b098f332333e95fc879dea6c29bf34acdb649650" + "f08201b9e649960f2493fd7677cc3abf5ae70e5445845c947ba544456b431646" + "d95a133bff5f57614dda5e4446cd8837901d074149dadf4b775b5b07bb88ca20"); + data.iv = wvcdm::a2b_hex("4cca615fc013102892f91efee936639b"); + data.block_offset = 0; + // WHAT: Expected decrypted data. + data.decrypt_data = wvcdm::a2b_hex( + "FAE501E845FD5F83583F45B6D011A4A0EFC7190AF2A7314985B436BA7A946428" + "74FD5578FA0A32FAC3DC2ACB3088315A4CBE8013A2C18C0774B117E00554EBE7" + "508E5EF43276F77F57E19ADF3B8177F2E8132AABE61B08D801E47653393F5E34" + "D66B45909C527D36B19FB5D8B996040211AA8A492B34BE9A5B0EBBC66EE253F4" + "7AB0EDE457FDB5F3855C96572EBA1C8F474DE0A46F8A4D0D498902332F0C92D3" + "FD981301BC705A6748C88B20C82F9C4E9FC574372A6EE5EFD6C1A70D9D9C1E6C" + "202FF87D59EBEB29EF3D494B297BAF3AB6D9656CEC1C2E263E8262D6BC260F94" + "B0F1CC98A1882D142F068DA90E6594D40716F01541F10247B8D12EAE3A8998B2"); + + cdm::InputBuffer buf; + + buf.data = &data.encrypt_data[0]; + buf.data_size = data.encrypt_data.size(); + buf.key_id = (const uint8_t*)&data.key_id[0]; + buf.key_id_size = data.key_id.length(); + buf.iv = &data.iv[0]; + buf.iv_size = data.iv.size(); + buf.data_offset = 0; + cdm::SubsampleEntry sub(0, buf.data_size); + buf.subsamples = ⊂ + buf.num_subsamples = 1; + buf.timestamp = 10; + + TestBuffer* out_buf = + dynamic_cast(host_->Allocate(buf.data_size)); + memset(out_buf->Data(), 0, buf.data_size); + + TestDecryptedBlock output; + output.SetDecryptedBuffer(out_buf); + + cdm::Status status; + status = cdm_->Decrypt(buf, &output); + + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], + buf.data_size)); + LOGI("Verbose Acknowledgement of Test Pass!!!\n"); + } + + // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. + void DecryptClearSubsampleTest() { + typedef struct DecryptionData { + bool is_encrypted; + bool is_secure; + wvcdm::KeyId key_id; + std::vector encrypt_data; + std::vector iv; + size_t block_offset; + std::vector decrypt_data; + } DecryptionData; + + DecryptionData data; + data.is_encrypted = true; + data.is_secure = false; + + // WHAT: Key ID of key used to encrypt the test content. + // WHY: This is used by the secure layer to look up the content key + data.key_id = wvcdm::a2bs_hex("92D20D250D425ABD992AF42E4CD64165"); + + // WHAT: Dummy encrypted data. This is a combination of clear and + // encrypted data. + data.encrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "3b2cbde084973539329bd5656da22d20396249bf4a18a51c38c4743360cc9fea" + "a1c78d53de1bd7e14dc5d256fd20a57178a98b83804258c239acd7aa38f2d7d2" + "5a36c0" + "eca614965b" + "58b938c2e3ca4c2ce4" + "3d22049e19e236fc1800e60965d8b36415677bf2f843d50a6943c4" + "683c07c114a32f5e5fbc9939c483c3a1b2ecd3d82b554d649798866191724283" + "f0ab082eba2da79aaca5c4eaf186f9ee9a0c568f621f705a578f30e4e2ef7b96" + "5e14cc046ce6dbf272ee5558b098f332333e95fc879dea6c29bf34acdb649650" + "f08201b9e649960f2493fd7677cc3abf5ae70e5445845c947ba544456b431646" + "d95a133bff5f57614dda5e4446cd8837901d074149dadf4b775b5b07bb88ca20"); + + data.iv = wvcdm::a2b_hex("4cca615fc013102892f91efee936639b"); + data.block_offset = 0; + + // WHAT: Expected decrypted data. + data.decrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "FAE501E845FD5F83583F45B6D011A4A0EFC7190AF2A7314985B436BA7A946428" + "74FD5578FA0A32FAC3DC2ACB3088315A4CBE8013A2C18C0774B117E00554EBE7" + "5a36c0" + "508E5EF432" + "58b938c2e3ca4c2ce4" + "76F77F57E19ADF3B8177F2E8132AABE61B08D801E47653393F5E34" + "D66B45909C527D36B19FB5D8B996040211AA8A492B34BE9A5B0EBBC66EE253F4" + "7AB0EDE457FDB5F3855C96572EBA1C8F474DE0A46F8A4D0D498902332F0C92D3" + "FD981301BC705A6748C88B20C82F9C4E9FC574372A6EE5EFD6C1A70D9D9C1E6C" + "202FF87D59EBEB29EF3D494B297BAF3AB6D9656CEC1C2E263E8262D6BC260F94" + "B0F1CC98A1882D142F068DA90E6594D40716F01541F10247B8D12EAE3A8998B2"); + + cdm::InputBuffer buf; + + buf.data = &data.encrypt_data[0]; + buf.data_size = data.encrypt_data.size(); + buf.key_id = (const uint8_t*) &data.key_id[0]; + buf.key_id_size = data.key_id.length(); + buf.iv = &data.iv[0]; + buf.iv_size = data.iv.size(); + buf.data_offset = 0; + std::vector sub; + sub.push_back(cdm::SubsampleEntry(32, 64)); + sub.push_back(cdm::SubsampleEntry(3, 5)); + sub.push_back( + cdm::SubsampleEntry(9, + data.encrypt_data.size() - (32 + 64 + 3 + 5 + 9))); + buf.subsamples = &sub[0]; + buf.num_subsamples = sub.size(); + buf.timestamp = 10; + + TestBuffer* out_buf = dynamic_cast(host_->Allocate( + buf.data_size)); + memset(out_buf->Data(), 0, buf.data_size); + + TestDecryptedBlock output; + output.SetDecryptedBuffer(out_buf); + + cdm::Status status; + status = cdm_->Decrypt(buf, &output); + + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ( + 0, + memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], + buf.data_size)); + LOGI("Verbose Acknowledgement of Test Pass!!!\n"); + } + + // Level 1 / Level 2 payload comes back in the cpu memory as cleartext. + void DecryptClearSubsampleTestWithMissingSubsampleInfo() { + typedef struct DecryptionData { + bool is_encrypted; + bool is_secure; + wvcdm::KeyId key_id; + std::vector encrypt_data; + std::vector iv; + size_t block_offset; + std::vector decrypt_data; + } DecryptionData; + + DecryptionData data; + data.is_encrypted = true; + data.is_secure = false; + + // WHAT: Key ID of key used to encrypt the test content. + // WHY: This is used by the secure layer to look up the content key + data.key_id = wvcdm::a2bs_hex("92D20D250D425ABD992AF42E4CD64165"); + + // WHAT: Dummy encrypted data. + // This is a combination of clear and encrypted data. + data.encrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "3b2cbde084973539329bd5656da22d20396249bf4a18a51c38c4743360cc9fea" + "a1c78d53de1bd7e14dc5d256fd20a57178a98b83804258c239acd7aa38f2d7d2" + "5a36c0" + "eca614965b" + "58b938c2e3ca4c2ce4" + "3d22049e19e236fc1800e60965d8b36415677bf2f843d50a6943c4" + "683c07c114a32f5e5fbc9939c483c3a1b2ecd3d82b554d649798866191724283" + "f0ab082eba2da79aaca5c4eaf186f9ee9a0c568f621f705a578f30e4e2ef7b96" + "5e14cc046ce6dbf272ee5558b098f332333e95fc879dea6c29bf34acdb649650" + "f08201b9e649960f2493fd7677cc3abf5ae70e5445845c947ba544456b431646" + "d95a133bff5f57614dda5e4446cd8837901d074149dadf4b775b5b07bb88ca20"); + data.iv = wvcdm::a2b_hex("4cca615fc013102892f91efee936639b"); + data.block_offset = 0; + + // WHAT: Expected decrypted data. + data.decrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "FAE501E845FD5F83583F45B6D011A4A0EFC7190AF2A7314985B436BA7A946428" + "74FD5578FA0A32FAC3DC2ACB3088315A4CBE8013A2C18C0774B117E00554EBE7" + "5a36c0" + "508E5EF432" + "58b938c2e3ca4c2ce4" + "76F77F57E19ADF3B8177F2E8132AABE61B08D801E47653393F5E34" + "D66B45909C527D36B19FB5D8B996040211AA8A492B34BE9A5B0EBBC66EE253F4" + "7AB0EDE457FDB5F3855C96572EBA1C8F474DE0A46F8A4D0D498902332F0C92D3" + "FD981301BC705A6748C88B20C82F9C4E9FC574372A6EE5EFD6C1A70D9D9C1E6C" + "202FF87D59EBEB29EF3D494B297BAF3AB6D9656CEC1C2E263E8262D6BC260F94" + "B0F1CC98A1882D142F068DA90E6594D40716F01541F10247B8D12EAE3A8998B2"); + + cdm::InputBuffer buf; + + buf.data = &data.encrypt_data[0]; + buf.data_size = data.encrypt_data.size(); + buf.key_id = (const uint8_t*)&data.key_id[0]; + buf.key_id_size = data.key_id.length(); + buf.iv = &data.iv[0]; + buf.iv_size = data.iv.size(); + buf.data_offset = 0; + std::vector sub; + sub.push_back(cdm::SubsampleEntry(32, 64)); + sub.push_back(cdm::SubsampleEntry(3, 5)); + sub.push_back(cdm::SubsampleEntry( + 9, data.encrypt_data.size() - (32 + 64 + 3 + 5 + 9))); + //buf.subsamples = &sub[0]; + //buf.num_subsamples = sub.size(); + buf.timestamp = 10; + + TestBuffer* out_buf = + dynamic_cast(host_->Allocate(buf.data_size)); + memset(out_buf->Data(), 0, buf.data_size); + + TestDecryptedBlock output; + output.SetDecryptedBuffer(out_buf); + + cdm::Status status; + status = cdm_->Decrypt(buf, &output); + EXPECT_EQ(cdm::kDecryptError, status); + + buf.subsamples = &sub[0]; + status = cdm_->Decrypt(buf, &output); + EXPECT_EQ(cdm::kDecryptError, status); + + buf.num_subsamples = sub.size(); + status = cdm_->Decrypt(buf, &output); + EXPECT_EQ(cdm::kSuccess, status); + EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0], + buf.data_size)); + + buf.subsamples = NULL; + status = cdm_->Decrypt(buf, &output); + EXPECT_EQ(cdm::kDecryptError, status); + LOGI("Verbose Acknowledgement of Test Pass!!!\n"); + } + + // Level 1 passes encrypted payload straight through. By calling the + // CDM's DecryptDecodeAndRenderSamples, and/or DecryptDecodeAndRenderFrame, + // OEMCrypto_DecryptCTR will be told to use Direct Rendering. + void SecureDecryptLevel1Test() { + typedef struct DecryptionData { + bool is_encrypted; + bool is_secure; + wvcdm::KeyId key_id; + std::vector encrypt_data; + std::vector iv; + size_t block_offset; + std::vector decrypt_data; + } DecryptionData; + + DecryptionData data; + data.is_encrypted = true; + data.is_secure = false; + + // WHAT: Key ID of key used to encrypt the test content. + // WHY: This is used by the secure layer to look up the content key + data.key_id = wvcdm::a2bs_hex("92D20D250D425ABD992AF42E4CD64165"); + + // WHAT: Dummy encrypted data. + data.encrypt_data = wvcdm::a2b_hex( + "3b2cbde084973539329bd5656da22d20396249bf4a18a51c38c4743360cc9fea" + "a1c78d53de1bd7e14dc5d256fd20a57178a98b83804258c239acd7aa38f2d7d2" + "eca614965b3d22049e19e236fc1800e60965d8b36415677bf2f843d50a6943c4" + "683c07c114a32f5e5fbc9939c483c3a1b2ecd3d82b554d649798866191724283" + "f0ab082eba2da79aaca5c4eaf186f9ee9a0c568f621f705a578f30e4e2ef7b96" + "5e14cc046ce6dbf272ee5558b098f332333e95fc879dea6c29bf34acdb649650" + "f08201b9e649960f2493fd7677cc3abf5ae70e5445845c947ba544456b431646" + "d95a133bff5f57614dda5e4446cd8837901d074149dadf4b775b5b07bb88ca20"); + data.iv = wvcdm::a2b_hex("4cca615fc013102892f91efee936639b"); + data.block_offset = 0; + // WHAT: Expected decrypted data. + data.decrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "eff34b8d9b7b6352e7d72de991b599662aa475da355033620152e2356ebfadee" + "06172be9e1058fa177e223b9fdd191380cff53c3ea810c6fd852a1df4967b799" + "415179a2276ec388ef763bab89605b9c6952c28dc8d6bf86b03fabbb46b392a3" + "1dad15be602eeeeabb45070b3e25d6bb0217073b1fc44c9fe848594121fd6a91" + "304d605e21f69615e1b57db18312b6b948725724b74e91d8aea7371e99532469" + "1b358bdee873f1936b63efe83d190a53c2d21754d302d63ff285174023473755" + "58b938c2e3ca4c2ce48942da97f9e45797f2c074ac6004734e93784a48af6160"); + + cdm::InputBuffer buf; + + buf.data = &data.encrypt_data[0]; + buf.data_size = data.encrypt_data.size(); + buf.key_id = (const uint8_t*)&data.key_id[0]; + buf.key_id_size = data.key_id.length(); + buf.iv = &data.iv[0]; + buf.iv_size = data.iv.size(); + buf.data_offset = 0; + cdm::SubsampleEntry sub(0, buf.data_size); + buf.subsamples = ⊂ + buf.num_subsamples = 1; + buf.timestamp = 10; + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kSuccess, status); + + LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n"); + } + + // Level 1 passes encrypted payload straight through. By calling the + // CDM's DecryptDecodeAndRenderSamples, and/or DecryptDecodeAndRenderFrame, + // OEMCrypto_DecryptCTR will be told to use Direct Rendering. + void SecureDecryptLevel1MultipleSubsamplesTest() { + typedef struct DecryptionData { + bool is_encrypted; + bool is_secure; + wvcdm::KeyId key_id; + std::vector encrypt_data; + std::vector iv; + size_t block_offset; + std::vector decrypt_data; + } DecryptionData; + + DecryptionData data; + data.is_encrypted = true; + data.is_secure = false; + + // WHAT: Key ID of key used to encrypt the test content. + // WHY: This is used by the secure layer to look up the content key + data.key_id = wvcdm::a2bs_hex("92D20D250D425ABD992AF42E4CD64165"); + + // WHAT: Dummy encrypted data. + data.encrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "3b2cbde084973539329bd5656da22d20396249bf4a18a51c38c4743360cc9fea" + "a1c78d53de1bd7e14dc5d256fd20a57178a98b83804258c239acd7aa38f2d7d2" + "5a36c0" + "eca614965b" + "58b938c2e3ca4c2ce4" + "3d22049e19e236fc1800e60965d8b36415677bf2f843d50a6943c4" + "683c07c114a32f5e5fbc9939c483c3a1b2ecd3d82b554d649798866191724283" + "f0ab082eba2da79aaca5c4eaf186f9ee9a0c568f621f705a578f30e4e2ef7b96" + "5e14cc046ce6dbf272ee5558b098f332333e95fc879dea6c29bf34acdb649650" + "f08201b9e649960f2493fd7677cc3abf5ae70e5445845c947ba544456b431646" + "d95a133bff5f57614dda5e4446cd8837901d074149dadf4b775b5b07bb88ca20"); + data.iv = wvcdm::a2b_hex("4cca615fc013102892f91efee936639b"); + data.block_offset = 0; + // WHAT: Expected decrypted data. + data.decrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "eff34b8d9b7b6352e7d72de991b599662aa475da355033620152e2356ebfadee" + "5a36c0" + "06172be9e1" + "58b938c2e3ca4c2ce4" + "058fa177e223b9fdd191380cff53c3ea810c6fd852a1df4967b799" + "415179a2276ec388ef763bab89605b9c6952c28dc8d6bf86b03fabbb46b392a3" + "1dad15be602eeeeabb45070b3e25d6bb0217073b1fc44c9fe848594121fd6a91" + "304d605e21f69615e1b57db18312b6b948725724b74e91d8aea7371e99532469" + "1b358bdee873f1936b63efe83d190a53c2d21754d302d63ff285174023473755" + "58b938c2e3ca4c2ce48942da97f9e45797f2c074ac6004734e93784a48af6160"); + + cdm::InputBuffer buf; + + buf.data = &data.encrypt_data[0]; + buf.data_size = data.encrypt_data.size(); + buf.key_id = (const uint8_t*)&data.key_id[0]; + buf.key_id_size = data.key_id.length(); + buf.iv = &data.iv[0]; + buf.iv_size = data.iv.size(); + buf.data_offset = 0; + std::vector sub; + sub.push_back(cdm::SubsampleEntry(32, 64)); + sub.push_back(cdm::SubsampleEntry(3, 5)); + sub.push_back(cdm::SubsampleEntry( + 9, data.encrypt_data.size() - (32 + 64 + 3 + 5 + 9))); + buf.subsamples = &sub[0]; + buf.num_subsamples = sub.size(); + buf.timestamp = 10; + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kSuccess, status); + + LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n"); + } + + void WithMissingSubsampleInfoTest() { + typedef struct DecryptionData { + bool is_encrypted; + bool is_secure; + wvcdm::KeyId key_id; + std::vector encrypt_data; + std::vector iv; + size_t block_offset; + std::vector decrypt_data; + } DecryptionData; + + DecryptionData data; + data.is_encrypted = true; + data.is_secure = false; + + // WHAT: Key ID of key used to encrypt the test content. + // WHY: This is used by the secure layer to look up the content key + data.key_id = wvcdm::a2bs_hex("92D20D250D425ABD992AF42E4CD64165"); + + // WHAT: Dummy encrypted data. + data.encrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "3b2cbde084973539329bd5656da22d20396249bf4a18a51c38c4743360cc9fea" + "a1c78d53de1bd7e14dc5d256fd20a57178a98b83804258c239acd7aa38f2d7d2" + "5a36c0" + "eca614965b" + "58b938c2e3ca4c2ce4" + "3d22049e19e236fc1800e60965d8b36415677bf2f843d50a6943c4" + "683c07c114a32f5e5fbc9939c483c3a1b2ecd3d82b554d649798866191724283" + "f0ab082eba2da79aaca5c4eaf186f9ee9a0c568f621f705a578f30e4e2ef7b96" + "5e14cc046ce6dbf272ee5558b098f332333e95fc879dea6c29bf34acdb649650" + "f08201b9e649960f2493fd7677cc3abf5ae70e5445845c947ba544456b431646" + "d95a133bff5f57614dda5e4446cd8837901d074149dadf4b775b5b07bb88ca20"); + data.iv = wvcdm::a2b_hex("4cca615fc013102892f91efee936639b"); + data.block_offset = 0; + // WHAT: Expected decrypted data. + data.decrypt_data = wvcdm::a2b_hex( + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "5a36c0b633b58faf22156d78fdfb608e54a8095788b2b0463ef78d030b4abf82" + "eff34b8d9b7b6352e7d72de991b599662aa475da355033620152e2356ebfadee" + "5a36c0" + "06172be9e1" + "58b938c2e3ca4c2ce4" + "058fa177e223b9fdd191380cff53c3ea810c6fd852a1df4967b799" + "415179a2276ec388ef763bab89605b9c6952c28dc8d6bf86b03fabbb46b392a3" + "1dad15be602eeeeabb45070b3e25d6bb0217073b1fc44c9fe848594121fd6a91" + "304d605e21f69615e1b57db18312b6b948725724b74e91d8aea7371e99532469" + "1b358bdee873f1936b63efe83d190a53c2d21754d302d63ff285174023473755" + "58b938c2e3ca4c2ce48942da97f9e45797f2c074ac6004734e93784a48af6160"); + + cdm::InputBuffer buf; + + buf.data = &data.encrypt_data[0]; + buf.data_size = data.encrypt_data.size(); + buf.key_id = (const uint8_t*)&data.key_id[0]; + buf.key_id_size = data.key_id.length(); + buf.iv = &data.iv[0]; + buf.iv_size = data.iv.size(); + buf.data_offset = 0; + std::vector sub; + sub.push_back(cdm::SubsampleEntry(32, 64)); + sub.push_back(cdm::SubsampleEntry(3, 5)); + sub.push_back(cdm::SubsampleEntry( + 9, data.encrypt_data.size() - (32 + 64 + 3 + 5 + 9))); + buf.timestamp = 10; + + cdm::Status status; + + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kDecryptError, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kDecryptError, status); + + buf.subsamples = &sub[0]; + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kDecryptError, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kDecryptError, status); + + buf.num_subsamples = sub.size(); + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kSuccess, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kSuccess, status); + + buf.subsamples = NULL; + status = cdm_->DecryptDecodeAndRenderSamples(buf); + EXPECT_EQ(cdm::kDecryptError, status); + status = cdm_->DecryptDecodeAndRenderFrame(buf); + EXPECT_EQ(cdm::kDecryptError, status); + + LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n"); + } + + void TimerTest(void* session_id) { host_->SetTimer(1000, session_id); } + + std::string key_msg_; + std::string session_id_; + std::string server_url_; + + cdm::ContentDecryptionModule* cdm_; + TestHostFactory host_factory_; + TestHost* host_; +}; + +// Note that these tests, BaseMessageTest, NormalDecryption and TimeTest, +// are dependent on getting back a license from the license server where the +// url for the license server is defined in the conf_test_env.cpp. If these +// tests fail immediately, verify that the license server URL is correct +// and works in your test environment. + +TEST_F(WvCdmApiTest, DeviceCertificateTest) { + std::vector value(0); + host_->PersistPlatformByteArray("DeviceCertificate", value); + GenerateKeyRequest(g_key_system, g_key_id); // It will have to provision - + // in here. + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + session_id_ = key_msg.session_id; + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + CancelKeyRequest(session_id_); +} + +TEST_F(WvCdmApiTest, BaseMessageTest) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + session_id_ = key_msg.session_id; + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + CancelKeyRequest(session_id_); +} + +TEST_F(WvCdmApiTest, BaseTimerTest) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + session_id_ = key_msg.session_id; + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + + LOGI("Sleeping and allowing the time expirations to increment"); + uint32_t sleep_period = static_cast(12000 / 1000); + sleep(sleep_period); + LOGI("Tick count is %ld\n", host_->GetTickCount()); + // We should have close to, maybe slightly less than sleep_period ticks. + // If it ticked at all then we pass as the timer must be expriring. + EXPECT_GE(host_->GetTickCount(), 1); + CancelKeyRequest(session_id_); +} + +TEST_F(WvCdmApiTest, NormalDecryption) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + DecryptClearPayloadTest(); +} + +TEST_F(WvCdmApiTest, NormalSubSampleDecryptionWithSubsampleInfo) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + DecryptClearSubsampleTest(); +} + +TEST_F(WvCdmApiTest, NormalSubSampleDecryptionWithMissingSubsampleInfo) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + DecryptClearSubsampleTestWithMissingSubsampleInfo(); +} + +TEST_F(WvCdmApiTest, TimeTest) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + session_id_ = key_msg.session_id; + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + + host_->SetCurrentTime(host_->GetCurrentTestTime() + kTestPolicyRenewalDelaySeconds); + sleep(kDelayWaitToForRenewalMessageSeconds); + + // When the timer expired, we should have sent a renewal, so we can + // add this renewed key now, assuming things are working as expected. + TestHost::KeyMessage key_msg2 = host_->GetLastKeyMessage(); + session_id_ = key_msg2.session_id; + key_msg_ = key_msg2.message; + + drm_msg = GetKeyRequestResponse(key_msg2.default_url, g_client_auth, 200); + AddKey(key_msg2.session_id, drm_msg); +} + +TEST_F(WvCdmApiTest, SecureDecryptionLevel1) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + SecureDecryptLevel1Test(); +} + +TEST_F(WvCdmApiTest, SecureDecryptionLevel1WithSubsampleInfo) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + SecureDecryptLevel1MultipleSubsamplesTest(); +} + +TEST_F(WvCdmApiTest, SecureDecryptionLevel1WithMissingSubsampleInfo) { + GenerateKeyRequest(g_key_system, g_key_id); + TestHost::KeyMessage key_msg = host_->GetLastKeyMessage(); + key_msg_ = key_msg.message; + std::string drm_msg = GetKeyRequestResponse(g_license_server, + g_client_auth, 200); + AddKey(key_msg.session_id, drm_msg); + WithMissingSubsampleInfoTest(); +} + +void* GetCdmHost(int host_interface_version, void* user_data) { + if (!host_interface_version || !user_data) return NULL; + + if (host_interface_version != cdm::kHostInterfaceVersion) return NULL; + + TestHostFactory* host_factory = reinterpret_cast(user_data); + return host_factory->GetTestHost(); +} + +} // namespace wvcdm + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + wvcdm::InitLogging(argc, argv); + + wvcdm::ConfigTestEnv config(wvcdm::kGoogleLicenseServerTest); + g_client_auth.assign(config.client_auth()); + g_key_system.assign(config.key_system()); + g_wrong_key_id.assign(config.wrong_key_id()); + + // The following variables are configurable through command line options. + g_license_server.assign(config.license_server()); + g_key_id.assign(config.key_id()); + g_port.assign(config.port()); + std::string license_server(g_license_server); + + int show_usage = 0; + static const struct option long_options[] = { + {"use_full_path", no_argument, &g_use_full_path, 0}, + {"keyid", required_argument, NULL, 'k'}, + {"port", required_argument, NULL, 'p'}, + {"server", required_argument, NULL, 's'}, + {"vmodule", required_argument, NULL, 0}, + {"v", required_argument, NULL, 0}, + {NULL, 0, NULL, '\0'}}; + + int option_index = 0; + int opt = 0; + while ((opt = getopt_long(argc, argv, "k:p:s:u:v", long_options, + &option_index)) != -1) { + switch (opt) { + case 'k': { + g_key_id.clear(); + g_key_id.assign(optarg); + break; + } + case 'p': { + g_port.clear(); + g_port.assign(optarg); + break; + } + case 's': { + g_license_server.clear(); + g_license_server.assign(optarg); + break; + } + case 'u': { + g_use_full_path = 1; + break; + } + case '?': { + show_usage = 1; + break; + case 'v': + break; + } + } + } + + if (show_usage) { + std::cout << std::endl; + std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl; + std::cout << " enclose multiple arguments in '' when using adb shell" + << std::endl; + std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" + << std::endl << std::endl; + + std::cout << std::setw(30) << std::left << " --port="; + std::cout << "specifies the port number, in decimal format" << std::endl; + std::cout << std::setw(30) << std::left << " "; + std::cout << "default: " << g_port << std::endl; + + std::cout << std::setw(30) << std::left << " --server="; + std::cout + << "configure the license server url, please include http[s] in the url" + << std::endl; + std::cout << std::setw(30) << std::left << " "; + std::cout << "default: " << license_server << std::endl; + + std::cout << std::setw(30) << std::left << " --keyid="; + std::cout << "configure the key id or pssh, in hex format" << std::endl; + std::cout << std::setw(30) << std::left << " default keyid:"; + std::cout << g_key_id << std::endl; + + std::cout << std::setw(30) << std::left << " --use_full_path"; + std::cout << "specify server url is not a proxy server" << std::endl; + std::cout << std::endl; + return 0; + } + + std::cout << std::endl; + std::cout << "Server: " << g_license_server << std::endl; + std::cout << "Port: " << g_port << std::endl; + std::cout << "KeyID: " << g_key_id << std::endl << std::endl; + + g_key_id = wvcdm::a2bs_hex(g_key_id); + config.set_license_server(g_license_server); + config.set_port(g_port); + config.set_key_id(g_key_id); + + return RUN_ALL_TESTS(); +} diff --git a/cdm/test/gmock.gyp b/cdm/test/gmock.gyp new file mode 100644 index 00000000..23f89c86 --- /dev/null +++ b/cdm/test/gmock.gyp @@ -0,0 +1,31 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +{ + 'target_defaults': { + 'type': 'static_library', + 'include_dirs': [ + '../../third_party/gmock', + '../../third_party/gmock/include', + '../../third_party/gmock/gtest/include', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '../../third_party/gmock/include', + '../../third_party/gmock/gtest/include', + ], + }, + }, + 'targets': [ + { + 'target_name': 'gmock', + 'sources': [ + '../../third_party/gmock/src/gmock-all.cc', + ], + }, + { + 'target_name': 'gmock_main', + 'sources': [ + '../../third_party/gmock/src/gmock_main.cc', + ], + }, + ], +} diff --git a/cdm/test/gtest.gyp b/cdm/test/gtest.gyp new file mode 100644 index 00000000..9cf72694 --- /dev/null +++ b/cdm/test/gtest.gyp @@ -0,0 +1,16 @@ +# Copyright 2013 Google Inc. All Rights Reserved. +{ + 'targets': [ + { + 'target_name': 'gtest', + 'type': 'static_library', + 'include_dirs': [ + '../../third_party/gmock/gtest', + '../../third_party/gmock/gtest/include', + ], + 'sources': [ + '../../third_party/gmock/gtest/src/gtest-all.cc', + ], + }, + ], +} diff --git a/core/include/buffer_reader.h b/core/include/buffer_reader.h new file mode 100644 index 00000000..a692517d --- /dev/null +++ b/core/include/buffer_reader.h @@ -0,0 +1,68 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_BUFFER_READER_H_ +#define WVCDM_CORE_BUFFER_READER_H_ + +#include +#include +#include + +#include "wv_cdm_types.h" + +namespace wvcdm { + +// Annotate a function indicating the caller must examine the return value. +// Use like: +// int foo() WARN_UNUSED_RESULT; +// To explicitly ignore a result, see |ignore_result()| in . +#if defined(COMPILER_GCC) +#define WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define WARN_UNUSED_RESULT +#endif + +class BufferReader { + public: + BufferReader(const uint8_t* buf, size_t size) + : buf_(buf), size_(size), pos_(0) {} + + bool HasBytes(int count) { return (pos() + count <= size()); } + + // Read a value from the stream, performing endian correction, + // and advance the stream pointer. + bool Read1(uint8_t* v) WARN_UNUSED_RESULT; + bool Read2(uint16_t* v) WARN_UNUSED_RESULT; + bool Read2s(int16_t* v) WARN_UNUSED_RESULT; + bool Read4(uint32_t* v) WARN_UNUSED_RESULT; + bool Read4s(int32_t* v) WARN_UNUSED_RESULT; + bool Read8(uint64_t* v) WARN_UNUSED_RESULT; + bool Read8s(int64_t* v) WARN_UNUSED_RESULT; + + bool ReadString(std::string* str, int count) WARN_UNUSED_RESULT; + bool ReadVec(std::vector* t, int count) WARN_UNUSED_RESULT; + + // These variants read a 4-byte integer of the corresponding signedness and + // store it in the 8-byte return type. + bool Read4Into8(uint64_t* v) WARN_UNUSED_RESULT; + bool Read4sInto8s(int64_t* v) WARN_UNUSED_RESULT; + + // Advance the stream by this many bytes. + bool SkipBytes(int nbytes) WARN_UNUSED_RESULT; + + const uint8_t* data() const { return buf_; } + size_t size() const { return size_; } + size_t pos() const { return pos_; } + + protected: + const uint8_t* buf_; + size_t size_; + size_t pos_; + + template bool Read(T* t) WARN_UNUSED_RESULT; + + CORE_DISALLOW_COPY_AND_ASSIGN(BufferReader); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_BUFFER_READER_H_ diff --git a/core/include/cdm_client_property_set.h b/core/include/cdm_client_property_set.h new file mode 100644 index 00000000..a081a285 --- /dev/null +++ b/core/include/cdm_client_property_set.h @@ -0,0 +1,26 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_ +#define WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_ + +#include +#include +#include + +namespace wvcdm { + +class CdmClientPropertySet { + public: + virtual ~CdmClientPropertySet() {} + + virtual std::string security_level() const = 0; + virtual bool use_privacy_mode() const = 0; + virtual std::vector service_certificate() const = 0; + virtual bool is_session_sharing_enabled() const = 0; + virtual uint32_t session_sharing_id() const = 0; + virtual void set_session_sharing_id(uint32_t id) = 0; +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_ diff --git a/core/include/cdm_engine.h b/core/include/cdm_engine.h new file mode 100644 index 00000000..f2ad6aae --- /dev/null +++ b/core/include/cdm_engine.h @@ -0,0 +1,124 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_CDM_ENGINE_H_ +#define WVCDM_CORE_CDM_ENGINE_H_ + +#include "certificate_provisioning.h" +#include "oemcrypto_adapter.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class CdmClientPropertySet; +class CdmSession; +class CryptoEngine; +class WvCdmEventListener; + +typedef std::map CdmSessionMap; +typedef std::map CdmReleaseKeySetMap; + +class CdmEngine { + public: + CdmEngine(); + virtual ~CdmEngine(); + + // Session related methods + CdmResponseType OpenSession(const CdmKeySystem& key_system, + const CdmClientPropertySet* property_set, + CdmSessionId* session_id); + CdmResponseType CloseSession(const CdmSessionId& session_id); + + CdmResponseType OpenKeySetSession(const CdmKeySetId& key_set_id); + CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id); + + // License related methods + // Construct a valid license request + CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id, + const CdmKeySetId& key_set_id, + const CdmInitData& init_data, + const CdmLicenseType license_type, + CdmAppParameterMap& app_parameters, + CdmKeyMessage* key_request, + std::string* server_url); + + // Accept license response and extract key info. + CdmResponseType AddKey(const CdmSessionId& session_id, + const CdmKeyResponse& key_data, + CdmKeySetId* key_set_id); + + CdmResponseType RestoreKey(const CdmSessionId& session_id, + const CdmKeySetId& key_set_id); + + CdmResponseType CancelKeyRequest(const CdmSessionId& session_id); + + // Construct valid renewal request for the current session keys. + CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id, + CdmKeyMessage* key_request, + std::string* server_url); + + // Accept renewal response and update key info. + CdmResponseType RenewKey(const CdmSessionId& session_id, + const CdmKeyResponse& key_data); + + // Query system information + CdmResponseType QueryStatus(CdmQueryMap* info); + + // Query session information + virtual CdmResponseType QuerySessionStatus(const CdmSessionId& session_id, + CdmQueryMap* key_info); + + // Query license information + CdmResponseType QueryKeyStatus(const CdmSessionId& session_id, + CdmQueryMap* key_info); + + // Query seesion control information + CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id, + CdmQueryMap* key_info); + + // Provisioning related methods + CdmResponseType GetProvisioningRequest(CdmProvisioningRequest* request, + std::string* default_url); + + CdmResponseType HandleProvisioningResponse(CdmProvisioningResponse& response); + + // Decryption and key related methods + // Accept encrypted buffer and return decrypted data. + CdmResponseType Decrypt(const CdmSessionId& session_id, + const CdmDecryptionParameters& parameters); + + // Is the key known to any session? + bool IsKeyLoaded(const KeyId& key_id); + bool FindSessionForKey(const KeyId& key_id, CdmSessionId* sessionId); + + // Event listener related methods + bool AttachEventListener(const CdmSessionId& session_id, + WvCdmEventListener* listener); + bool DetachEventListener(const CdmSessionId& session_id, + WvCdmEventListener* listener); + + // Parse a blob of multiple concatenated PSSH atoms to extract the first + // widevine pssh + static bool ExtractWidevinePssh(const CdmInitData& init_data, + CdmInitData* output); + + // Timer expiration method + void OnTimerEvent(); + + private: + // private methods + bool ValidateKeySystem(const CdmKeySystem& key_system); + + void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); + + // instance variables + CdmSessionMap sessions_; + CdmReleaseKeySetMap release_key_sets_; + CertificateProvisioning cert_provisioning_; + SecurityLevel cert_provisioning_requested_security_level_; + + CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_CDM_ENGINE_H_ diff --git a/core/include/cdm_session.h b/core/include/cdm_session.h new file mode 100644 index 00000000..cce3fa74 --- /dev/null +++ b/core/include/cdm_session.h @@ -0,0 +1,130 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_CDM_SESSION_H_ +#define WVCDM_CORE_CDM_SESSION_H_ + +#include + +#include "crypto_session.h" +#include "device_files.h" +#include "license.h" +#include "oemcrypto_adapter.h" +#include "policy_engine.h" +#include "scoped_ptr.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class CdmClientPropertySet; +class WvCdmEventListener; + +class CdmSession { + public: + explicit CdmSession(const CdmClientPropertySet* cdm_client_property_set); + ~CdmSession(); + + CdmResponseType Init(); + + CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id, + const CdmLicenseType license_type); + + void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; } + const CdmKeySystem& key_system() { return key_system_; } + + const CdmSessionId& session_id() { return session_id_; } + + CdmResponseType GenerateKeyRequest(const CdmInitData& init_data, + const CdmLicenseType license_type, + const CdmAppParameterMap& app_parameters, + CdmKeyMessage* key_request, + std::string* server_url); + + // AddKey() - Accept license response and extract key info. + CdmResponseType AddKey(const CdmKeyResponse& key_response, + CdmKeySetId* key_set_id); + + // CancelKeyRequest() - Cancel session. + CdmResponseType CancelKeyRequest(); + + // Query session status + CdmResponseType QueryStatus(CdmQueryMap* key_info); + + // Query license information + CdmResponseType QueryKeyStatus(CdmQueryMap* key_info); + + // Query session control info + CdmResponseType QueryKeyControlInfo(CdmQueryMap* key_info); + + // Decrypt() - Accept encrypted buffer and return decrypted data. + CdmResponseType Decrypt(const CdmDecryptionParameters& parameters); + + // License renewal + // GenerateRenewalRequest() - Construct valid renewal request for the current + // session keys. + CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request, + std::string* server_url); + + // RenewKey() - Accept renewal response and update key info. + CdmResponseType RenewKey(const CdmKeyResponse& key_response); + + // License release + // GenerateReleaseRequest() - Construct valid release request for the current + // session keys. + CdmResponseType GenerateReleaseRequest(CdmKeyMessage* key_request, + std::string* server_url); + + // ReleaseKey() - Accept response and release key. + CdmResponseType ReleaseKey(const CdmKeyResponse& key_response); + + bool IsKeyLoaded(const KeyId& key_id); + + bool AttachEventListener(WvCdmEventListener* listener); + bool DetachEventListener(WvCdmEventListener* listener); + + void OnTimerEvent(); + void OnKeyReleaseEvent(const CdmKeySetId& key_set_id); + + SecurityLevel GetRequestedSecurityLevel(); + + private: + + // Generate unique ID for each new session. + CdmSessionId GenerateSessionId(); + bool GenerateKeySetId(CdmKeySetId* key_set_id); + + bool StoreLicense(DeviceFiles::LicenseState state); + + // instance variables + const CdmSessionId session_id_; + CdmKeySystem key_system_; + CdmLicense license_parser_; + scoped_ptr crypto_session_; + PolicyEngine policy_engine_; + bool license_received_; + bool reinitialize_session_; + + CdmLicenseType license_type_; + + // license type offline related information + CdmInitData offline_pssh_data_; + CdmKeyMessage offline_key_request_; + CdmKeyResponse offline_key_response_; + CdmKeyMessage offline_key_renewal_request_; + CdmKeyResponse offline_key_renewal_response_; + std::string offline_release_server_url_; + + // license type release and offline related information + CdmKeySetId key_set_id_; + + // Used for certificate based licensing + std::string wrapped_key_; + bool is_certificate_loaded_; + + std::set listeners_; + + CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_CDM_SESSION_H_ diff --git a/core/include/certificate_provisioning.h b/core/include/certificate_provisioning.h new file mode 100644 index 00000000..365c0a40 --- /dev/null +++ b/core/include/certificate_provisioning.h @@ -0,0 +1,38 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_CERTIFICATE_PROVISIONING_H_ +#define WVCDM_CORE_CERTIFICATE_PROVISIONING_H_ + +#include "crypto_session.h" +#include "oemcrypto_adapter.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class CdmSession; + +class CertificateProvisioning { + public: + CertificateProvisioning() {}; + ~CertificateProvisioning() {}; + + // Provisioning related methods + CdmResponseType GetProvisioningRequest(SecurityLevel requested_security_level, + CdmProvisioningRequest* request, + std::string* default_url); + CdmResponseType HandleProvisioningResponse(CdmProvisioningResponse& response); + + private: + void ComposeJsonRequestAsQueryString(const std::string& message, + CdmProvisioningRequest* request); + bool ParseJsonResponse(const CdmProvisioningResponse& json_str, + const std::string& start_substr, + const std::string& end_substr, + std::string* result); + CryptoSession crypto_session_; + + CORE_DISALLOW_COPY_AND_ASSIGN(CertificateProvisioning); +}; +} // namespace wvcdm + +#endif // WVCDM_CORE_CERTIFICATE_PROVISIONING_H_ diff --git a/core/include/clock.h b/core/include/clock.h new file mode 100644 index 00000000..461b2bfc --- /dev/null +++ b/core/include/clock.h @@ -0,0 +1,25 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Clock - Platform independent interface for a time library +// +#ifndef WVCDM_CORE_CLOCK_H_ +#define WVCDM_CORE_CLOCK_H_ + +#include + +namespace wvcdm { + +// Provides time related information. The implementation is platform dependent. +class Clock { + + public: + Clock() {} + virtual ~Clock() {} + + // Provides the number of seconds since an epoch - 01/01/1970 00:00 UTC + virtual int64_t GetCurrentTime(); +}; + +}; // namespace wvcdm + +#endif // WVCDM_CORE_CLOCK_H_ diff --git a/core/include/crypto_key.h b/core/include/crypto_key.h new file mode 100644 index 00000000..765f7275 --- /dev/null +++ b/core/include/crypto_key.h @@ -0,0 +1,40 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_CRYPTO_KEY_H_ +#define WVCDM_CORE_CRYPTO_KEY_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +class CryptoKey { +public: + CryptoKey() {}; + ~CryptoKey() {}; + + const std::string& key_id() const { return key_id_; } + const std::string& key_data() const { return key_data_; } + const std::string& key_data_iv() const { return key_data_iv_; } + const std::string& key_control() const { return key_control_; } + const std::string& key_control_iv() const { return key_control_iv_; } + void set_key_id(const std::string& key_id) { key_id_ = key_id; } + void set_key_data(const std::string& key_data) { key_data_ = key_data; } + void set_key_data_iv(const std::string& iv) { key_data_iv_ = iv; } + void set_key_control(const std::string& ctl) { key_control_ = ctl; } + void set_key_control_iv(const std::string& ctl_iv) { + key_control_iv_ = ctl_iv; + } + + bool HasKeyControl() const { return key_control_.size() >= 16; } + +private: + std::string key_id_; + std::string key_data_iv_; + std::string key_data_; + std::string key_control_; + std::string key_control_iv_; +}; + +}; // namespace wvcdm + +#endif // WVCDM_CORE_CRYPTO_KEY_H_ diff --git a/core/include/crypto_session.h b/core/include/crypto_session.h new file mode 100644 index 00000000..725b18b5 --- /dev/null +++ b/core/include/crypto_session.h @@ -0,0 +1,104 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_CRYPTO_SESSSION_H_ +#define WVCDM_CORE_CRYPTO_SESSSION_H_ + +#include +#include + +#include "lock.h" +#include "oemcrypto_adapter.h" +#include "OEMCryptoCENC.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class CryptoKey; +typedef std::map CryptoKeyMap; + +class CryptoSession { + public: + CryptoSession(); + ~CryptoSession(); + + bool ValidateKeybox(); + bool GetToken(std::string* token); + CdmSecurityLevel GetSecurityLevel(); + bool GetDeviceUniqueId(std::string* device_id); + bool GetSystemId(uint32_t* system_id); + bool GetProvisioningId(std::string* provisioning_id); + + CdmResponseType Open() { return Open(kLevelDefault); } + CdmResponseType Open(SecurityLevel requested_security_level); + void Close(); + + bool IsOpen() { return open_; } + CryptoSessionId oec_session_id() { return oec_session_id_; } + + // Key request/response + void GenerateRequestId(std::string& req_id_str); + bool PrepareRequest(const std::string& key_deriv_message, + bool is_provisioning, std::string* signature); + bool PrepareRenewalRequest(const std::string& message, + std::string* signature); + CdmResponseType LoadKeys(const std::string& message, + const std::string& signature, + const std::string& mac_key_iv, + const std::string& mac_key, + int num_keys, const CryptoKey* key_array); + bool LoadCertificatePrivateKey(std::string& wrapped_key); + bool RefreshKeys(const std::string& message, const std::string& signature, + int num_keys, const CryptoKey* key_array); + bool GenerateNonce(uint32_t* nonce); + bool GenerateDerivedKeys(const std::string& message); + bool GenerateDerivedKeys(const std::string& message, + const std::string& session_key); + bool RewrapDeviceRSAKey(const std::string& message, + const std::string& signature, + const std::string& nonce, + const std::string& enc_rsa_key, + const std::string& rsa_key_iv, + std::string* wrapped_rsa_key); + + // Media data path + CdmResponseType Decrypt(const CdmDecryptionParameters& parameters); + + bool GetRandom(size_t data_length, uint8_t* random_data); + + private: + void Init(); + void Terminate(); + void GenerateMacContext(const std::string& input_context, + std::string* deriv_context); + void GenerateEncryptContext(const std::string& input_context, + std::string* deriv_context); + bool GenerateSignature(const std::string& message, bool use_rsa, + std::string* signature); + size_t GetOffset(std::string message, std::string field); + bool SetDestinationBufferType(); + + bool SelectKey(const std::string& key_id); + + static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature + static Lock crypto_lock_; + static bool initialized_; + static int session_count_; + + bool open_; + CryptoSessionId oec_session_id_; + + OEMCryptoBufferType destination_buffer_type_; + bool is_destination_buffer_type_valid_; + SecurityLevel requested_security_level_; + + KeyId key_id_; + + uint64_t request_id_base_; + static uint64_t request_id_index_; + + CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession); +}; + +}; // namespace wvcdm + +#endif // WVCDM_CORE_CRYPTO_SESSSION_H_ diff --git a/core/include/device_files.h b/core/include/device_files.h new file mode 100644 index 00000000..a438ebf7 --- /dev/null +++ b/core/include/device_files.h @@ -0,0 +1,76 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +#ifndef WVCDM_CORE_DEVICE_FILES_H_ +#define WVCDM_CORE_DEVICE_FILES_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +class File; + +class DeviceFiles { + public: + typedef enum { + kLicenseStateActive, + kLicenseStateReleasing, + kLicenseStateUnknown, + } LicenseState; + + DeviceFiles(): file_(NULL), security_level_(kSecurityLevelUninitialized), + initialized_(false) {} + virtual ~DeviceFiles() {} + + virtual bool Init(const File* handle, CdmSecurityLevel security_level); + + virtual bool StoreCertificate(const std::string& certificate, + const std::string& wrapped_private_key); + virtual bool RetrieveCertificate(std::string* certificate, + std::string* wrapped_private_key); + + virtual bool StoreLicense(const std::string& key_set_id, + const LicenseState state, + const CdmInitData& pssh_data, + const CdmKeyMessage& key_request, + const CdmKeyResponse& key_response, + const CdmKeyMessage& key_renewal_request, + const CdmKeyResponse& key_renewal_response, + const std::string& release_server_url); + virtual bool RetrieveLicense(const std::string& key_set_id, + LicenseState* state, + CdmInitData* pssh_data, + CdmKeyMessage* key_request, + CdmKeyResponse* key_response, + CdmKeyMessage* key_renewal_request, + CdmKeyResponse* key_renewal_response, + std::string* release_server_url); + virtual bool DeleteLicense(const std::string& key_set_id); + virtual bool DeleteAllFiles(); + virtual bool DeleteAllLicenses(); + virtual bool LicenseExists(const std::string& key_set_id); + + // For testing only + static std::string GetCertificateFileName(); + static std::string GetLicenseFileNameExtension(); + + protected: + bool Hash(const std::string& data, std::string* hash); + bool StoreFile(const char* name, const std::string& data); + bool RetrieveFile(const char* name, std::string* data); + + private: + // Certificate and offline licenses are now stored in security + // level specific directories. In an earlier version they were + // stored in a common directory and need to be copied over. + virtual void SecurityLevelPathBackwardCompatibility(); + + File* file_; + CdmSecurityLevel security_level_; + bool initialized_; + + CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_DEVICE_FILES_H_ diff --git a/core/include/file_store.h b/core/include/file_store.h new file mode 100644 index 00000000..5794240d --- /dev/null +++ b/core/include/file_store.h @@ -0,0 +1,51 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// File - Platform independent interface for a File class +// +#ifndef WVCDM_CORE_FILE_STORE_H_ +#define WVCDM_CORE_FILE_STORE_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +// File class. The implementation is platform dependent. +class File { + public: + class Impl; + + // defines as bit flag + enum OpenFlags { + kNoFlags = 0, + kBinary = 1, + kCreate = 2, + kReadOnly = 4, // defaults to read and write access + kTruncate = 8 + }; + + File(); + virtual ~File(); + + virtual bool Open(const std::string& file_path, int flags); + virtual ssize_t Read(char* buffer, size_t bytes); + virtual ssize_t Write(const char* buffer, size_t bytes); + virtual void Close(); + + virtual bool Exists(const std::string& file_path); + virtual bool Remove(const std::string& file_path); + virtual bool Copy(const std::string& old_path, const std::string& new_path); + virtual bool List(const std::string& path, std::vector* files); + virtual bool CreateDirectory(const std::string dir_path); + virtual bool IsDirectory(const std::string& dir_path); + virtual bool IsRegularFile(const std::string& file_path); + virtual ssize_t FileSize(const std::string& file_path); + + private: + Impl *impl_; + + CORE_DISALLOW_COPY_AND_ASSIGN(File); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_FILE_STORE_H_ diff --git a/core/include/license.h b/core/include/license.h new file mode 100644 index 00000000..58446f11 --- /dev/null +++ b/core/include/license.h @@ -0,0 +1,74 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_LICENSE_H_ +#define WVCDM_CORE_LICENSE_H_ + +#include + +#include "wv_cdm_types.h" + +namespace video_widevine_server { +namespace sdk { +class SignedMessage; +} +} // namespace video_widevine_server + +namespace wvcdm { + +class CryptoSession; +class PolicyEngine; + +class CdmLicense { + public: + + CdmLicense() : session_(NULL), initialized_(false) {} + ~CdmLicense() {} + + bool Init(const std::string& token, CryptoSession* session, + PolicyEngine* policy_engine); + + bool PrepareKeyRequest(const CdmInitData& pssh_data, + const CdmLicenseType license_type, + const CdmAppParameterMap& app_parameters, + const CdmSessionId& session_id, + CdmKeyMessage* signed_request, + std::string* server_url); + bool PrepareKeyUpdateRequest(bool is_renewal, CdmKeyMessage* signed_request, + std::string* server_url); + CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response); + CdmResponseType HandleKeyUpdateResponse( + bool is_renewal, const CdmKeyResponse& license_response); + + bool RestoreOfflineLicense(CdmKeyMessage& license_request, + CdmKeyResponse& license_response, + CdmKeyResponse& license_renewal_response); + bool HasInitData() { return !init_data_.empty(); } + bool IsKeyLoaded(const KeyId& key_id); + + private: + bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, + std::string* server_url); + CdmResponseType HandleServiceCertificateResponse( + const video_widevine_server::sdk::SignedMessage& signed_message); + + CdmResponseType HandleKeyErrorResponse( + const video_widevine_server::sdk::SignedMessage& signed_message); + + CryptoSession* session_; + PolicyEngine* policy_engine_; + std::string server_url_; + std::string token_; + std::string service_certificate_; + std::string init_data_; + bool initialized_; + std::set loaded_keys_; + + // Used for certificate based licensing + CdmKeyMessage key_request_; + + CORE_DISALLOW_COPY_AND_ASSIGN(CdmLicense); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_LICENSE_H_ diff --git a/core/include/lock.h b/core/include/lock.h new file mode 100644 index 00000000..1f502975 --- /dev/null +++ b/core/include/lock.h @@ -0,0 +1,61 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Lock - Platform independent interface for a Mutex class +// +#ifndef WVCDM_CORE_LOCK_H_ +#define WVCDM_CORE_LOCK_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +// Simple lock class. The implementation is platform dependent. +// +// The lock must be unlocked by the thread that locked it. +// The lock is also not recursive (ie. cannot be taken multiple times). +class Lock { + public: + Lock(); + ~Lock(); + + void Acquire(); + void Release(); + + // Acquires a lock if not held and returns true. + // Returns false if the lock is held by another thread. + bool Try(); + + friend class AutoLock; + + private: + class Impl; + Impl *impl_; + + CORE_DISALLOW_COPY_AND_ASSIGN(Lock); +}; + +// Manages the lock automatically. It will be locked when AutoLock +// is constructed and release when AutoLock goes out of scope. +class AutoLock { + public: + explicit AutoLock(Lock& lock) : lock_(&lock) { + lock_->Acquire(); + } + + explicit AutoLock(Lock* lock) : lock_(lock) { + lock_->Acquire(); + } + + ~AutoLock() { + lock_->Release(); + } + + private: + Lock *lock_; + + CORE_DISALLOW_COPY_AND_ASSIGN(AutoLock); +}; + +}; // namespace wvcdm + +#endif // WVCDM_CORE_LOCK_H_ diff --git a/core/include/log.h b/core/include/log.h new file mode 100644 index 00000000..642ad710 --- /dev/null +++ b/core/include/log.h @@ -0,0 +1,37 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Log - Platform independent interface for a Logging class +// +#ifndef WVCDM_CORE_LOG_H_ +#define WVCDM_CORE_LOG_H_ + +namespace wvcdm { + +// Simple logging class. The implementation is platform dependent. + +typedef enum { + LOG_ERROR, + LOG_WARN, + LOG_INFO, + LOG_DEBUG, + LOG_VERBOSE +} LogPriority; + +// Enable/disable verbose logging (LOGV). +// This function is supplied for cases where the system layer does not +// initialize logging. This is also needed to initialize logging in +// unit tests. +void InitLogging(int argc, const char* const* argv); + +void Log(const char* file, int line, LogPriority level, const char* fmt, ...); + +// Log APIs +#define LOGE(...) Log(__FILE__, __LINE__, wvcdm::LOG_ERROR, __VA_ARGS__) +#define LOGW(...) Log(__FILE__, __LINE__, wvcdm::LOG_WARN, __VA_ARGS__) +#define LOGI(...) Log(__FILE__, __LINE__, wvcdm::LOG_INFO, __VA_ARGS__) +#define LOGD(...) Log(__FILE__, __LINE__, wvcdm::LOG_DEBUG, __VA_ARGS__) +#define LOGV(...) Log(__FILE__, __LINE__, wvcdm::LOG_VERBOSE, __VA_ARGS__) + +}; // namespace wvcdm + +#endif // WVCDM_CORE_LOG_H_ diff --git a/core/include/oemcrypto_adapter.h b/core/include/oemcrypto_adapter.h new file mode 100644 index 00000000..82d0ff92 --- /dev/null +++ b/core/include/oemcrypto_adapter.h @@ -0,0 +1,31 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +#ifndef WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ +#define WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ + +#include "OEMCryptoCENC.h" + +namespace wvcdm { + +enum SecurityLevel { + kLevelDefault, + kLevel3 +}; + +/* 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); +OEMCryptoResult OEMCrypto_IsKeyboxValid(SecurityLevel level); +OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength, + SecurityLevel level); +OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength, + SecurityLevel level); +OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox, + size_t keyBoxLength, + SecurityLevel level); +uint32_t OEMCrypto_APIVersion(SecurityLevel level); +const char* OEMCrypto_SecurityLevel(SecurityLevel level); +} // namespace wvcdm + +#endif // WVCDM_CORE_OEMCRYPTO_ADAPTER_H_ diff --git a/core/include/policy_engine.h b/core/include/policy_engine.h new file mode 100644 index 00000000..e327391f --- /dev/null +++ b/core/include/policy_engine.h @@ -0,0 +1,121 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_POLICY_ENGINE_H_ +#define WVCDM_CORE_POLICY_ENGINE_H_ + +#include + +#include "license_protocol.pb.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class Clock; +class PolicyEngineTest; + +// This acts as an oracle that basically says "Yes(true) you may still decrypt +// or no(false) you may not decrypt this data anymore." +class PolicyEngine { + public: + PolicyEngine(); + ~PolicyEngine(); + + // The value returned should be taken as a hint rather than an absolute + // status. It is computed during the last call to either SetLicense/ + // UpdateLicense/OnTimerEvent/BeginDecryption and may be out of sync + // depending on the amount of time elapsed. The current decryption + // status is not calculated to avoid overhead in the decryption path. + inline bool can_decrypt() { return can_decrypt_; } + + // OnTimerEvent is called when a timer fires. It notifies the Policy Engine + // that the timer has fired and that it should check whether any events have + // occurred since the last timer event. If so, it sets event_occurred to true + // and sets event to point to the event that occurred. If not, it sets + // event_occurred to false. + void OnTimerEvent(bool* event_occurred, CdmEventType* event); + + // SetLicense is used in handling the initial license response. It stores + // an exact copy of the policy information stored in the license. + // The license state transitions to kLicenseStateCanPlay if the license + // permits playback. + void SetLicense(const video_widevine_server::sdk::License& license); + + // Call this on first decrypt to set the start of playback. This is + // for cases where usage begins not when the license is received, + // but at the start of playback + void BeginDecryption(void); + + // UpdateLicense is used in handling a license response for a renewal request. + // The response may only contain any policy fields that have changed. In this + // case an exact copy is not what we want to happen. We also will receive an + // updated license_start_time from the server. The license will transition to + // kLicenseStateCanPlay if the license permits playback. + void UpdateLicense(const video_widevine_server::sdk::License& license); + + CdmResponseType Query(CdmQueryMap* key_info); + + const video_widevine_server::sdk::LicenseIdentification& license_id() { + return license_id_; + } + + bool IsLicenseDurationExpired(int64_t current_time); + bool IsPlaybackDurationExpired(int64_t current_time); + + private: + typedef enum { + kLicenseStateInitial, + kLicenseStateInitialPendingUsage, + kLicenseStateCanPlay, + kLicenseStateNeedRenewal, + kLicenseStateWaitingLicenseUpdate, + kLicenseStateExpired + } LicenseState; + + void Init(Clock* clock); + + bool IsRenewalDelayExpired(int64_t current_time); + bool IsRenewalRecoveryDurationExpired(int64_t current_time); + bool IsRenewalRetryIntervalExpired(int64_t current_time); + + void UpdateRenewalRequest(int64_t current_time); + + LicenseState license_state_; + bool can_decrypt_; + + // This is the current policy information for this license. This gets updated + // as license renewals occur. + video_widevine_server::sdk::License::Policy policy_; + + // This is the license id field from server response. This data gets passed + // back to the server in each renewal request. When we get a renewal response + // from the license server we will get an updated id field. + video_widevine_server::sdk::LicenseIdentification license_id_; + + // This is the license start time that gets sent from the server in each + // license request or renewal. + int64_t license_start_time_; + + // This is the time at which the license was received and playback was + // started. These times are based off the local clock in case there is a + // discrepency between local and server time. + int64_t license_received_time_; + int64_t playback_start_time_; + + // This is used as a reference point for policy management. This value + // represents an offset from license_received_time_. This is used to + // calculate the time where renewal retries should occur. + int64_t next_renewal_time_; + int64_t policy_max_duration_seconds_; + + Clock* clock_; + + // For testing + friend class PolicyEngineTest; + PolicyEngine(Clock* clock); + + CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine); +}; + +} // wvcdm + +#endif // WVCDM_CORE_POLICY_ENGINE_H_ diff --git a/core/include/privacy_crypto.h b/core/include/privacy_crypto.h new file mode 100644 index 00000000..69e292a7 --- /dev/null +++ b/core/include/privacy_crypto.h @@ -0,0 +1,74 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// Declaration of classes representing AES and RSA public keys used +// for signature verification and encryption. +// +// AES encryption details: +// Algorithm: AES-CBC +// +// RSA signature details: +// Algorithm: RSASSA-PSS +// Hash algorithm: SHA1 +// Mask generation function: mgf1SHA1 +// Salt length: 20 bytes +// Trailer field: 0xbc +// +// RSA encryption details: +// Algorithm: RSA-OAEP +// Mask generation function: mgf1SHA1 +// Label (encoding paramter): empty string +// +#ifndef WVCDM_CORE_PRIVACY_CRYPTO_H_ +#define WVCDM_CORE_PRIVACY_CRYPTO_H_ + +#include + +#include "openssl/evp.h" +#include "openssl/rsa.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +class AesCbcKey { + public: + AesCbcKey() : initialized_(false) {}; + ~AesCbcKey() {}; + + bool Init(const std::string& key); + bool Encrypt(const std::string& in, std::string* out, std::string* iv); + + private: + EVP_CIPHER_CTX ctx_; + bool initialized_; + + CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey); +}; + +class RsaPublicKey { + public: + RsaPublicKey() : key_(NULL) {} + ~RsaPublicKey(); + + // Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey + bool Init(const std::string& serialized_key); + + // Encrypt a message using RSA-OAEP. Caller retains ownership of all + // parameters. Returns true if successful, false otherwise. + bool Encrypt(const std::string& plaintext, + std::string* ciphertext); + + // Verify RSSASSA-PSS signature. Caller retains ownership of all parameters. + // Returns true if validation succeeds, false otherwise. + bool VerifySignature(const std::string& message, + const std::string& signature); + + private: + RSA* key_; + + CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_PRIVACY_CRYPTO_H_ diff --git a/core/include/properties.h b/core/include/properties.h new file mode 100644 index 00000000..31621148 --- /dev/null +++ b/core/include/properties.h @@ -0,0 +1,121 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_PROPERTIES_H_ +#define WVCDM_CORE_PROPERTIES_H_ + +#include +#include + +#include "cdm_client_property_set.h" +#include "lock.h" +#include "scoped_ptr.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +typedef std::map + CdmClientPropertySetMap; + +// This class saves information about features and properties enabled +// for a given platform. At initialization it initializes properties from +// property_configuration.h. That file specifies features selected for each +// platform. Core CDM can then query enabled features though specific getter +// methods. +// Setter methods are provided but their only planned use is for testing. +class Properties { + public: + static void Init(); + + static inline bool begin_license_usage_when_received() { + return begin_license_usage_when_received_; + } + static inline bool require_explicit_renew_request() { + return require_explicit_renew_request_; + } + static inline bool oem_crypto_use_secure_buffers() { + return oem_crypto_use_secure_buffers_; + } + static inline bool oem_crypto_use_fifo() { return oem_crypto_use_fifo_; } + static inline bool oem_crypto_use_userspace_buffers() { + return oem_crypto_use_userspace_buffers_; + } + static inline bool use_certificates_as_identification() { + return use_certificates_as_identification_; + } + static inline bool extract_pssh_data() { return extract_pssh_data_; } + static inline bool decrypt_with_empty_session_support() { + return decrypt_with_empty_session_support_; + } + static inline bool security_level_path_backward_compatibility_support() { + return security_level_path_backward_compatibility_support_; + } + static bool GetCompanyName(std::string* company_name); + static bool GetModelName(std::string* model_name); + static bool GetArchitectureName(std::string* arch_name); + static bool GetDeviceName(std::string* device_name); + static bool GetProductName(std::string* product_name); + static bool GetBuildInfo(std::string* build_info); + static bool GetDeviceFilesBasePath(CdmSecurityLevel security_level, + std::string* base_path); + static bool GetFactoryKeyboxPath(std::string* keybox); + static bool GetOEMCryptoPath(std::string* library_name); + static bool GetSecurityLevelDirectories(std::vector* dirs); + static const std::string GetSecurityLevel(const CdmSessionId& session_id); + static const std::vector GetServiceCertificate( + const CdmSessionId& session_id); + static bool UsePrivacyMode(const CdmSessionId& session_id); + static uint32_t GetSessionSharingId(const CdmSessionId& session_id); + + static bool AddSessionPropertySet(const CdmSessionId& session_id, + const CdmClientPropertySet* property_set); + static bool RemoveSessionPropertySet(const CdmSessionId& session_id); + + private: + static const CdmClientPropertySet* GetCdmClientPropertySet( + const CdmSessionId& session_id); + static void set_begin_license_usage_when_received(bool flag) { + begin_license_usage_when_received_ = flag; + } + static void set_require_explicit_renew_request(bool flag) { + require_explicit_renew_request_ = flag; + } + static void set_oem_crypto_use_secure_buffers(bool flag) { + oem_crypto_use_secure_buffers_ = flag; + } + static void set_oem_crypto_use_fifo(bool flag) { + oem_crypto_use_fifo_ = flag; + } + static void set_oem_crypto_use_userspace_buffers(bool flag) { + oem_crypto_use_userspace_buffers_ = flag; + } + static void set_use_certificates_as_identification(bool flag) { + use_certificates_as_identification_ = flag; + } + static void set_extract_pssh_data(bool flag) { extract_pssh_data_ = flag; } + + static void set_decrypt_with_empty_session_support(bool flag) { + decrypt_with_empty_session_support_ = flag; + } + static void set_security_level_path_backward_compatibility_support( + bool flag) { + security_level_path_backward_compatibility_support_ = flag; + } + + private: + static bool begin_license_usage_when_received_; + static bool require_explicit_renew_request_; + static bool oem_crypto_use_secure_buffers_; + static bool oem_crypto_use_fifo_; + static bool oem_crypto_use_userspace_buffers_; + static bool use_certificates_as_identification_; + static bool extract_pssh_data_; + static bool decrypt_with_empty_session_support_; + static bool security_level_path_backward_compatibility_support_; + static scoped_ptr session_property_set_; + + CORE_DISALLOW_COPY_AND_ASSIGN(Properties); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_PROPERTIES_H_ diff --git a/core/include/scoped_ptr.h b/core/include/scoped_ptr.h new file mode 100644 index 00000000..7c50f74f --- /dev/null +++ b/core/include/scoped_ptr.h @@ -0,0 +1,64 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// A simple and partial implementation of scoped_ptr class. +// The implementation is copied from gtest/include/gtest/internal/gtest-port.h. +// +#ifndef WVCDM_CORE_SCOPED_PTR_H_ +#define WVCDM_CORE_SCOPED_PTR_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +// A scoped_ptr is like a T*, except that the destructor of scoped_ptr +// automatically deletes the pointer it holds (if any). +// That is, scoped_ptr owns the T object that it points to. +// Like a T*, a scoped_ptr may hold either NULL or a pointer to a T object. +// Also like T*, scoped_ptr is thread-compatible, and once you +// dereference it, you get the thread safety guarantees of T. +// +// The size of scoped_ptr is small. On most compilers, sizeof(scoped_ptr) +// == sizeof(T*). +// +// Current implementation targets having a strict subset of C++11's +// unique_ptr<> features. Known deficiencies include not supporting move-only +// deleteres, function pointers as deleters, and deleters with reference +// types. + +// This implementation of scoped_ptr is PARTIAL, e.g. it does not support move, +// custom deleter etc. +template +class scoped_ptr { + public: + typedef T element_type; + + explicit scoped_ptr(T* p = NULL) : ptr_(p) {} + ~scoped_ptr() { reset(); } + + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + T* get() const { return ptr_; } + + T* release() { + T* const ptr = ptr_; + ptr_ = NULL; + return ptr; + } + + void reset(T* p = NULL) { + if (p != ptr_) { + if (sizeof(T) > 0) { // Makes sure T is a complete type. + delete ptr_; + } + ptr_ = p; + } + } + private: + T* ptr_; + + CORE_DISALLOW_COPY_AND_ASSIGN(scoped_ptr); +}; + +}; // namespace wvcdm + +#endif // WVCDM_CORE_SCOPED_PTR_H_ diff --git a/core/include/string_conversions.h b/core/include/string_conversions.h new file mode 100644 index 00000000..5a1e3024 --- /dev/null +++ b/core/include/string_conversions.h @@ -0,0 +1,26 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_STRING_CONVERSIONS_H_ +#define WVCDM_CORE_STRING_CONVERSIONS_H_ + +#include +#include +#include +#include + +namespace wvcdm { + +std::vector a2b_hex(const std::string& b); +std::string a2bs_hex(const std::string& b); +std::string b2a_hex(const std::vector& b); +std::string b2a_hex(const std::string& b); +std::string Base64SafeEncode(const std::vector& bin_input); +std::string Base64SafeEncodeNoPad(const std::vector& bin_input); +std::vector Base64SafeDecode(const std::string& bin_input); +std::string HexEncode(const uint8_t* bytes, unsigned size); +std::string IntToString(int value); +std::string UintToString(unsigned int value); + +}; // namespace wvcdm + +#endif // WVCDM_CORE_STRING_CONVERSIONS_H_ diff --git a/core/include/timer.h b/core/include/timer.h new file mode 100644 index 00000000..9e3f8e10 --- /dev/null +++ b/core/include/timer.h @@ -0,0 +1,51 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Timer - Platform independent interface for a Timer class +// +#ifndef WVCDM_CORE_TIMER_H_ +#define WVCDM_CORE_TIMER_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +// Timer Handler class. +// +// Derive from this class if you wish to receive events when the timer +// expires. Provide the handler when setting up a new Timer. + +class TimerHandler { + public: + TimerHandler() {}; + virtual ~TimerHandler() {}; + + virtual void OnTimerEvent() = 0; +}; + +// Timer class. The implementation is platform dependent. +// +// This class provides a simple recurring timer API. The class receiving +// timer expiry events should derive from TimerHandler. +// Specify the receiver class and the periodicty of timer events when +// the timer is initiated by calling Start. + +class Timer { + public: + class Impl; + + Timer(); + ~Timer(); + + bool Start(TimerHandler *handler, uint32_t time_in_secs); + void Stop(); + bool IsRunning(); + + private: + Impl *impl_; + + CORE_DISALLOW_COPY_AND_ASSIGN(Timer); +}; + +}; // namespace wvcdm + +#endif // WVCDM_CORE_TIMER_H_ diff --git a/core/include/wv_cdm_constants.h b/core/include/wv_cdm_constants.h new file mode 100644 index 00000000..90edd3e2 --- /dev/null +++ b/core/include/wv_cdm_constants.h @@ -0,0 +1,58 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_WV_CDM_CONSTANTS_H_ +#define WVCDM_CORE_WV_CDM_CONSTANTS_H_ + +#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 KEY_SIZE = 16; +static const size_t MAC_KEY_SIZE = 32; +static const size_t KEYBOX_KEY_DATA_SIZE = 72; + +static const char SESSION_ID_PREFIX[] = "sid"; +static const char KEY_SET_ID_PREFIX[] = "ksid"; +static const char KEY_SYSTEM[] = "com.widevine"; + +// define query keys, values here +static const std::string QUERY_KEY_LICENSE_TYPE = "LicenseType"; + // "Streaming", "Offline" +static const std::string QUERY_KEY_PLAY_ALLOWED = "PlayAllowed"; + // "True", "False" +static const std::string QUERY_KEY_PERSIST_ALLOWED = "PersistAllowed"; + // "True", "False" +static const std::string QUERY_KEY_RENEW_ALLOWED = "RenewAllowed"; + // "True", "False" +static const std::string QUERY_KEY_LICENSE_DURATION_REMAINING = + "LicenseDurationRemaining"; // non-negative integer +static const std::string QUERY_KEY_PLAYBACK_DURATION_REMAINING = + "PlaybackDurationRemaining"; // non-negative integer +static const std::string QUERY_KEY_RENEWAL_SERVER_URL = "RenewalServerUrl"; + // url +static const std::string QUERY_KEY_OEMCRYPTO_SESSION_ID = "OemCryptoSessionId"; + // session id +static const std::string QUERY_KEY_SECURITY_LEVEL = "SecurityLevel"; + // "L1", "L3" +static const std::string QUERY_KEY_DEVICE_ID = "DeviceID"; + // device unique id +static const std::string QUERY_KEY_SYSTEM_ID = "SystemID"; + // system id +static const std::string QUERY_KEY_PROVISIONING_ID = "ProvisioningID"; + // provisioning unique id + +static const std::string QUERY_VALUE_TRUE = "True"; +static const std::string QUERY_VALUE_FALSE = "False"; +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"; +static const std::string QUERY_VALUE_SECURITY_LEVEL_L2 = "L2"; +static const std::string QUERY_VALUE_SECURITY_LEVEL_L3 = "L3"; +static const std::string QUERY_VALUE_SECURITY_LEVEL_UNKNOWN = "Unknown"; + +} // 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 new file mode 100644 index 00000000..35fca737 --- /dev/null +++ b/core/include/wv_cdm_event_listener.h @@ -0,0 +1,28 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_ +#define WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_ + +#include "wv_cdm_types.h" + +namespace wvcdm { + +// Listener for events from the Content Decryption Module. +// The caller of the CDM API must provide an implementation for OnEvent +// and signal its intent by using the Attach/DetachEventListener methods +// in the WvContentDecryptionModule class. +class WvCdmEventListener { + public: + WvCdmEventListener() {} + virtual ~WvCdmEventListener() {} + + virtual void OnEvent(const CdmSessionId& session_id, + CdmEventType cdm_event) = 0; + + private: + CORE_DISALLOW_COPY_AND_ASSIGN(WvCdmEventListener); +}; + +} // namespace wvcdm + +#endif // WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_ diff --git a/core/include/wv_cdm_types.h b/core/include/wv_cdm_types.h new file mode 100644 index 00000000..acf8c035 --- /dev/null +++ b/core/include/wv_cdm_types.h @@ -0,0 +1,121 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef WVCDM_CORE_WV_CDM_TYPES_H_ +#define WVCDM_CORE_WV_CDM_TYPES_H_ + +#include +#include +#include +#include + +namespace wvcdm { + +typedef std::string CdmKeySystem; +typedef std::string CdmInitData; +typedef std::string CdmKeyMessage; +typedef std::string CdmKeyResponse; +typedef std::string KeyId; +typedef std::string CdmSessionId; +typedef std::string CdmKeySetId; +typedef std::string RequestId; +typedef uint32_t CryptoResult; +typedef uint32_t CryptoSessionId; +typedef std::string CryptoKeyId; +typedef std::map CdmAppParameterMap; +typedef std::map CdmQueryMap; +typedef std::string CdmProvisioningRequest; +typedef std::string CdmProvisioningResponse; + +// Types for shared host/cdm interface pairs used to shared vendor data. +typedef std::pair kStringPairs; +typedef std::vector kVectorBytes; +typedef std::pair kVectorPairs; + +enum CdmResponseType { + NO_ERROR, + UNKNOWN_ERROR, + KEY_ADDED, + KEY_ERROR, + KEY_MESSAGE, + NEED_KEY, + KEY_CANCELED, + NEED_PROVISIONING, + DEVICE_REVOKED, + INSUFFICIENT_CRYPTO_RESOURCES, +}; + +#define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const TypeName&) + +enum CdmEventType { + LICENSE_EXPIRED_EVENT, + LICENSE_RENEWAL_NEEDED_EVENT +}; + +enum CdmLicenseType { + kLicenseTypeOffline, + kLicenseTypeStreaming, + kLicenseTypeRelease +}; + +enum CdmSecurityLevel { + kSecurityLevelUninitialized, + kSecurityLevelL1, + kSecurityLevelL2, + kSecurityLevelL3, + kSecurityLevelUnknown +}; + +struct CdmDecryptionParameters { + bool is_encrypted; + bool is_secure; + const KeyId* key_id; + const uint8_t* encrypt_buffer; + size_t encrypt_length; + const std::vector* iv; + size_t block_offset; + void* decrypt_buffer; + size_t decrypt_buffer_length; + size_t decrypt_buffer_offset; + uint8_t subsample_flags; + bool is_video; + CdmDecryptionParameters() + : is_encrypted(true), + is_secure(true), + key_id(NULL), + encrypt_buffer(NULL), + encrypt_length(0), + iv(NULL), + block_offset(0), + decrypt_buffer(NULL), + decrypt_buffer_length(0), + decrypt_buffer_offset(0), + subsample_flags(0), + is_video(true) {} + CdmDecryptionParameters(const KeyId* key, const uint8_t* encrypted_buffer, + size_t encrypted_length, + const std::vector* initialization_vector, + size_t offset, void* decrypted_buffer) + : is_encrypted(true), + is_secure(true), + key_id(key), + encrypt_buffer(encrypted_buffer), + encrypt_length(encrypted_length), + iv(initialization_vector), + block_offset(offset), + decrypt_buffer(decrypted_buffer), + decrypt_buffer_length(encrypted_length), + decrypt_buffer_offset(0), + subsample_flags(0), + is_video(true) {} +}; + +// forward class references +class KeyMessage; +class Request; +class Key; + +} // namespace wvcdm + +#endif // WVCDM_CORE_WV_CDM_TYPES_H_ diff --git a/core/src/buffer_reader.cpp b/core/src/buffer_reader.cpp new file mode 100644 index 00000000..a548b8a9 --- /dev/null +++ b/core/src/buffer_reader.cpp @@ -0,0 +1,97 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#include "buffer_reader.h" + +#include "log.h" + +namespace wvcdm { + +bool BufferReader::Read1(uint8_t* v) { + if (!HasBytes(1)) { + LOGE("BufferReader::Read1 : Failure while parsing: Not enough bytes (1)"); + return false; + } + + *v = buf_[pos_++]; + return true; +} + +// Internal implementation of multi-byte reads +template bool BufferReader::Read(T* v) { + if (!HasBytes(sizeof(T))) { + LOGE("BufferReader::Read : Failure during parse: Not enough bytes (%u)", + sizeof(T)); + return false; + } + + T tmp = 0; + for (size_t i = 0; i < sizeof(T); i++) { + tmp <<= 8; + tmp += buf_[pos_++]; + } + *v = tmp; + return true; +} + +bool BufferReader::Read2(uint16_t* v) { return Read(v); } +bool BufferReader::Read2s(int16_t* v) { return Read(v); } +bool BufferReader::Read4(uint32_t* v) { return Read(v); } +bool BufferReader::Read4s(int32_t* v) { return Read(v); } +bool BufferReader::Read8(uint64_t* v) { return Read(v); } +bool BufferReader::Read8s(int64_t* v) { return Read(v); } + +bool BufferReader::ReadString(std::string* str, int count) { + if (!HasBytes(count)) { + LOGE("BufferReader::ReadString : Parse Failure: Not enough bytes (%d)", + count); + return false; + } + + str->assign(buf_ + pos_, buf_ + pos_ + count); + pos_ += count; + return true; +} + +bool BufferReader::ReadVec(std::vector* vec, int count) { + if (!HasBytes(count)) { + LOGE("BufferReader::ReadVec : Parse Failure: Not enough bytes (%d)", count); + return false; + } + + vec->clear(); + vec->insert(vec->end(), buf_ + pos_, buf_ + pos_ + count); + pos_ += count; + return true; +} + +bool BufferReader::SkipBytes(int bytes) { + if (!HasBytes(bytes)) { + LOGE("BufferReader::SkipBytes : Parse Failure: Not enough bytes (%d)", + bytes); + return false; + } + + pos_ += bytes; + return true; +} + +bool BufferReader::Read4Into8(uint64_t* v) { + uint32_t tmp; + if (!Read4(&tmp)) { + return false; + } + *v = tmp; + return true; +} + +bool BufferReader::Read4sInto8s(int64_t* v) { + // Beware of the need for sign extension. + int32_t tmp; + if (!Read4s(&tmp)) { + return false; + } + *v = tmp; + return true; +} + +} // namespace wvcdm diff --git a/core/src/cdm_engine.cpp b/core/src/cdm_engine.cpp new file mode 100644 index 00000000..a9458ffb --- /dev/null +++ b/core/src/cdm_engine.cpp @@ -0,0 +1,684 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "cdm_engine.h" + +#include +#include + +#include "buffer_reader.h" +#include "cdm_session.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "properties.h" +#include "scoped_ptr.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_event_listener.h" + +namespace wvcdm { + +CdmEngine::CdmEngine() + : cert_provisioning_requested_security_level_(kLevelDefault) { + Properties::Init(); +} + +CdmEngine::~CdmEngine() { + CdmSessionMap::iterator i(sessions_.begin()); + for (; i != sessions_.end(); ++i) { + delete i->second; + } + sessions_.clear(); +} + +CdmResponseType CdmEngine::OpenSession( + const CdmKeySystem& key_system, + const CdmClientPropertySet* property_set, + CdmSessionId* session_id) { + LOGI("CdmEngine::OpenSession"); + + if (!ValidateKeySystem(key_system)) { + LOGI("CdmEngine::OpenSession: invalid key_system = %s", key_system.c_str()); + return KEY_ERROR; + } + + if (!session_id) { + LOGE("CdmEngine::OpenSession: no session ID destination provided"); + return KEY_ERROR; + } + + scoped_ptr new_session(new CdmSession(property_set)); + if (new_session->session_id().empty()) { + LOGE("CdmEngine::OpenSession: failure to generate session ID"); + return UNKNOWN_ERROR; + } + + CdmResponseType sts = new_session->Init(); + if (sts != NO_ERROR) { + if (sts == NEED_PROVISIONING) { + cert_provisioning_requested_security_level_ = + new_session->GetRequestedSecurityLevel(); + } else { + LOGE("CdmEngine::OpenSession: bad session init: %u", sts); + } + return sts; + } + *session_id = new_session->session_id(); + sessions_[*session_id] = new_session.release(); + return NO_ERROR; +} + +CdmResponseType CdmEngine::OpenKeySetSession(const CdmKeySetId& key_set_id) { + LOGI("CdmEngine::OpenKeySetSession"); + + if (key_set_id.empty()) { + LOGE("CdmEngine::OpenKeySetSession: invalid key set id"); + return KEY_ERROR; + } + + CdmSessionId session_id; + CdmResponseType sts = OpenSession(KEY_SYSTEM, NULL, &session_id); + + if (sts != NO_ERROR) + return sts; + + release_key_sets_[key_set_id] = session_id; + return NO_ERROR; +} + +CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) { + LOGI("CdmEngine::CloseSession"); + + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::CloseSession: session not found = %s", session_id.c_str()); + return KEY_ERROR; + } + + delete iter->second; + sessions_.erase(session_id); + return NO_ERROR; +} + +CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) { + LOGI("CdmEngine::CloseKeySetSession"); + + CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); + if (iter == release_key_sets_.end()) { + LOGE("CdmEngine::CloseKeySetSession: key set id not found = %s", + key_set_id.c_str()); + return KEY_ERROR; + } + + CdmResponseType sts = CloseSession(iter->second); + release_key_sets_.erase(iter); + return sts; +} + +CdmResponseType CdmEngine::GenerateKeyRequest( + const CdmSessionId& session_id, + const CdmKeySetId& key_set_id, + const CdmInitData& init_data, + const CdmLicenseType license_type, + CdmAppParameterMap& app_parameters, + CdmKeyMessage* key_request, + std::string* server_url) { + LOGI("CdmEngine::GenerateKeyRequest"); + + CdmSessionId id = session_id; + CdmResponseType sts; + + if (license_type == kLicenseTypeRelease) { + if (key_set_id.empty()) { + LOGE("CdmEngine::GenerateKeyRequest: invalid key set ID"); + return UNKNOWN_ERROR; + } + + if (!session_id.empty()) { + LOGE("CdmEngine::GenerateKeyRequest: invalid session ID = %s", + session_id.c_str()); + return UNKNOWN_ERROR; + } + + CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id); + if (iter == release_key_sets_.end()) { + LOGE("CdmEngine::GenerateKeyRequest: key set ID not found = %s", + key_set_id.c_str()); + return UNKNOWN_ERROR; + } + + id = iter->second; + } + + CdmSessionMap::iterator iter = sessions_.find(id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s", + id.c_str()); + return KEY_ERROR; + } + + if (!key_request) { + LOGE("CdmEngine::GenerateKeyRequest: no key request destination provided"); + return KEY_ERROR; + } + + key_request->clear(); + + if (license_type == kLicenseTypeRelease) { + sts = iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeRelease); + if (sts != KEY_ADDED) { + LOGE("CdmEngine::GenerateKeyRequest: key release restoration failed," + "sts = %d", (int)sts); + return sts; + } + } + + sts = iter->second->GenerateKeyRequest(init_data, license_type, + app_parameters, key_request, + server_url); + + if (KEY_MESSAGE != sts) { + if (sts == NEED_PROVISIONING) { + cert_provisioning_requested_security_level_ = + iter->second->GetRequestedSecurityLevel(); + } + LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, " + "sts = %d", (int)sts); + return sts; + } + + if (license_type == kLicenseTypeRelease) { + OnKeyReleaseEvent(key_set_id); + } + + return KEY_MESSAGE; +} + +CdmResponseType CdmEngine::AddKey( + const CdmSessionId& session_id, + const CdmKeyResponse& key_data, + CdmKeySetId* key_set_id) { + LOGI("CdmEngine::AddKey"); + + CdmSessionId id = session_id; + bool license_type_release = session_id.empty(); + + if (license_type_release) { + if (!key_set_id) { + LOGE("CdmEngine::AddKey: no key set id provided"); + return KEY_ERROR; + } + + if (key_set_id->empty()) { + LOGE("CdmEngine::AddKey: invalid key set id"); + return KEY_ERROR; + } + + CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(*key_set_id); + if (iter == release_key_sets_.end()) { + LOGE("CdmEngine::AddKey: key set id not found = %s", key_set_id->c_str()); + return KEY_ERROR; + } + + id = iter->second; + } + + CdmSessionMap::iterator iter = sessions_.find(id); + + if (iter == sessions_.end()) { + LOGE("CdmEngine::AddKey: session id not found = %s", id.c_str()); + return KEY_ERROR; + } + + if (key_data.empty()) { + LOGE("CdmEngine::AddKey: no key_data"); + return KEY_ERROR; + } + + CdmResponseType sts = iter->second->AddKey(key_data, key_set_id); + + if (KEY_ADDED != sts) { + LOGE("CdmEngine::AddKey: keys not added, result = %d", (int)sts); + return sts; + } + + return KEY_ADDED; +} + +CdmResponseType CdmEngine::RestoreKey( + const CdmSessionId& session_id, + const CdmKeySetId& key_set_id) { + LOGI("CdmEngine::RestoreKey"); + + if (key_set_id.empty()) { + LOGI("CdmEngine::RestoreKey: invalid key set id"); + return KEY_ERROR; + } + + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::RestoreKey: session_id not found = %s ", + session_id.c_str()); + return UNKNOWN_ERROR; + } + + CdmResponseType sts = + iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeOffline); + if (sts == NEED_PROVISIONING) { + cert_provisioning_requested_security_level_ = + iter->second->GetRequestedSecurityLevel(); + } + return sts; +} + +CdmResponseType CdmEngine::CancelKeyRequest(const CdmSessionId& session_id) { + LOGI("CdmEngine::CancelKeyRequest"); + + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::CancelKeyRequest: session_id not found = %s", + session_id.c_str()); + return KEY_ERROR; + } + + // Re-initialize to release crypto session/keys without closing session + iter->second->Init(); + return NO_ERROR; +} + +CdmResponseType CdmEngine::GenerateRenewalRequest( + const CdmSessionId& session_id, + CdmKeyMessage* key_request, + std::string* server_url) { + LOGI("CdmEngine::GenerateRenewalRequest"); + + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::GenerateRenewalRequest: session_id not found = %s", + session_id.c_str()); + return KEY_ERROR; + } + + if (!key_request) { + LOGE("CdmEngine::GenerateRenewalRequest: no key request destination"); + return KEY_ERROR; + } + + key_request->clear(); + + CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request, + server_url); + + if (KEY_MESSAGE != sts) { + LOGE("CdmEngine::GenerateRenewalRequest: key request gen. failed, sts=%d", + (int)sts); + return sts; + } + + return KEY_MESSAGE; +} + +CdmResponseType CdmEngine::RenewKey( + const CdmSessionId& session_id, + const CdmKeyResponse& key_data) { + LOGI("CdmEngine::RenewKey"); + + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::RenewKey: session_id not found = %s", session_id.c_str()); + return KEY_ERROR; + } + + if (key_data.empty()) { + LOGE("CdmEngine::RenewKey: no key_data"); + return KEY_ERROR; + } + + CdmResponseType sts = iter->second->RenewKey(key_data); + if (KEY_ADDED != sts) { + LOGE("CdmEngine::RenewKey: keys not added, sts=%d", (int)sts); + return sts; + } + + return KEY_ADDED; +} + +CdmResponseType CdmEngine::QueryStatus(CdmQueryMap* key_info) { + LOGI("CdmEngine::QueryStatus"); + CryptoSession crypto_session; + switch (crypto_session.GetSecurityLevel()) { + case kSecurityLevelL1: + (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1; + break; + case kSecurityLevelL2: + (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2; + break; + case kSecurityLevelL3: + (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3; + break; + case kSecurityLevelUninitialized: + case kSecurityLevelUnknown: + (*key_info)[QUERY_KEY_SECURITY_LEVEL] = + QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; + break; + default: + return KEY_ERROR; + } + + std::string deviceId; + bool success = crypto_session.GetDeviceUniqueId(&deviceId); + if (success) { + (*key_info)[QUERY_KEY_DEVICE_ID] = deviceId; + } + + uint32_t system_id; + success = crypto_session.GetSystemId(&system_id); + if (success) { + std::ostringstream system_id_stream; + system_id_stream << system_id; + (*key_info)[QUERY_KEY_SYSTEM_ID] = system_id_stream.str(); + } + + std::string provisioning_id; + success = crypto_session.GetProvisioningId(&provisioning_id); + if (success) { + (*key_info)[QUERY_KEY_PROVISIONING_ID] = provisioning_id; + } + + return NO_ERROR; +} + +CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id, + CdmQueryMap* key_info) { + LOGI("CdmEngine::QuerySessionStatus"); + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::QuerySessionStatus: session_id not found = %s", + session_id.c_str()); + return KEY_ERROR; + } + return iter->second->QueryStatus(key_info); +} + +CdmResponseType CdmEngine::QueryKeyStatus( + const CdmSessionId& session_id, + CdmQueryMap* key_info) { + LOGI("CdmEngine::QueryKeyStatus"); + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::QueryKeyStatus: session_id not found = %s", + session_id.c_str()); + return KEY_ERROR; + } + return iter->second->QueryKeyStatus(key_info); +} + +CdmResponseType CdmEngine::QueryKeyControlInfo( + const CdmSessionId& session_id, + CdmQueryMap* key_info) { + LOGI("CdmEngine::QueryKeyControlInfo"); + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + LOGE("CdmEngine::QueryKeyControlInfo: session_id not found = %s", + session_id.c_str()); + return KEY_ERROR; + } + return iter->second->QueryKeyControlInfo(key_info); +} + +/* + * Composes a device provisioning request and output the request in JSON format + * in *request. It also returns the default url for the provisioning server + * in *default_url. + * + * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. + */ +CdmResponseType CdmEngine::GetProvisioningRequest( + CdmProvisioningRequest* request, + std::string* default_url) { + if (!request || !default_url) { + LOGE("CdmEngine::GetProvisioningRequest: invalid input parameters"); + return UNKNOWN_ERROR; + } + return cert_provisioning_.GetProvisioningRequest( + cert_provisioning_requested_security_level_, + request, + default_url); +} + +/* + * 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 + * in the device. + * + * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. + */ +CdmResponseType CdmEngine::HandleProvisioningResponse( + CdmProvisioningResponse& response) { + if (response.empty()) { + LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response."); + return UNKNOWN_ERROR; + } + return cert_provisioning_.HandleProvisioningResponse(response); +} + +CdmResponseType CdmEngine::Decrypt( + const CdmSessionId& session_id, + const CdmDecryptionParameters& parameters) { + if (parameters.key_id == NULL) { + LOGE("CdmEngine::Decrypt: no key_id"); + return KEY_ERROR; + } + + if (parameters.encrypt_buffer == NULL) { + LOGE("CdmEngine::Decrypt: no src encrypt buffer"); + return KEY_ERROR; + } + + if (parameters.iv == NULL) { + LOGE("CdmEngine::Decrypt: no iv"); + return KEY_ERROR; + } + + if (parameters.decrypt_buffer == NULL) { + if (!parameters.is_secure && + !Properties::Properties::oem_crypto_use_fifo()) { + LOGE("CdmEngine::Decrypt: no dest decrypt buffer"); + return KEY_ERROR; + } // else we must be level 1 direct and we don't need to return a buffer. + } + + CdmSessionMap::iterator iter; + if (session_id.empty()) { + if (!Properties::decrypt_with_empty_session_support()) return KEY_ERROR; + + // Loop through the sessions to find the session containing the key_id. + for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) { + if (iter->second->IsKeyLoaded(*parameters.key_id)) { + break; + } + } + } else { + iter = sessions_.find(session_id); + } + if (iter == sessions_.end()) { + LOGE("CdmEngine::Decrypt: session_id not found[%d] = %s", + session_id.size(), + session_id.c_str()); + return KEY_ERROR; + } + + return iter->second->Decrypt(parameters); +} + +bool CdmEngine::IsKeyLoaded(const KeyId& key_id) { + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { + if (iter->second->IsKeyLoaded(key_id)) { + return true; + } + } + return false; +} + +bool CdmEngine::FindSessionForKey( + const KeyId& key_id, + CdmSessionId* session_id) { + if (NULL == session_id) { + LOGE("CdmEngine::FindSessionForKey: session id not provided"); + return false; + } + + CdmSessionMap::iterator iter = sessions_.find(*session_id); + if (iter != sessions_.end()) { + if (iter->second->IsKeyLoaded(key_id)) { + return true; + } + } + + uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id); + + for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) { + CdmSessionId local_session_id = iter->second->session_id(); + if (Properties::GetSessionSharingId(local_session_id) == + session_sharing_id) { + if (iter->second->IsKeyLoaded(key_id)) { + *session_id = local_session_id; + return true; + } + } + } + return false; +} + +bool CdmEngine::AttachEventListener( + const CdmSessionId& session_id, + WvCdmEventListener* listener) { + + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + return false; + } + + return iter->second->AttachEventListener(listener); +} + +bool CdmEngine::DetachEventListener( + const CdmSessionId& session_id, + WvCdmEventListener* listener) { + + CdmSessionMap::iterator iter = sessions_.find(session_id); + if (iter == sessions_.end()) { + return false; + } + + return iter->second->DetachEventListener(listener); +} + +bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) { + return (key_system.find("widevine") != std::string::npos); +} + +// Parse a blob of multiple concatenated PSSH atoms to extract the first +// widevine pssh +bool CdmEngine::ExtractWidevinePssh( + const CdmInitData& init_data, CdmInitData* output) { + + BufferReader reader( + reinterpret_cast(init_data.data()), init_data.length()); + + static const uint8_t kWidevineSystemId[] = { + 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, + 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED, + }; + + // one PSSH blob consists of: + // 4 byte size of the PSSH atom, inclusive + // "pssh" + // 4 byte flags, value 0 + // 16 byte system id + // 4 byte size of PSSH data, exclusive + while (1) { + // size of PSSH atom, used for skipping + uint32_t size; + if (!reader.Read4(&size)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH atom size"); + return false; + } + + // "pssh" + std::vector pssh; + if (!reader.ReadVec(&pssh, 4)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH literal"); + return false; + } + if (memcmp(&pssh[0], "pssh", 4)) { + LOGW("CdmEngine::ExtractWidevinePssh: PSSH literal not present"); + return false; + } + + // flags + uint32_t flags; + if (!reader.Read4(&flags)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH flags"); + return false; + } + if (flags != 0) { + LOGW("CdmEngine::ExtractWidevinePssh: PSSH flags not zero"); + return false; + } + + // system id + std::vector system_id; + if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read system ID"); + return false; + } + + if (memcmp(&system_id[0], kWidevineSystemId, + sizeof(kWidevineSystemId))) { + // skip the remaining contents of the atom, + // after size field, atom name, flags and system id + if (!reader.SkipBytes( + size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to rest of PSSH atom"); + return false; + } + continue; + } + + // size of PSSH box + uint32_t pssh_length; + if (!reader.Read4(&pssh_length)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH box size"); + return false; + } + + output->clear(); + if (!reader.ReadString(output, pssh_length)) { + LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH"); + return false; + } + + return true; + } + + // we did not find a matching record + return false; +} + +void CdmEngine::OnTimerEvent() { + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { + iter->second->OnTimerEvent(); + } +} + +void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) { + + for (CdmSessionMap::iterator iter = sessions_.begin(); + iter != sessions_.end(); ++iter) { + iter->second->OnKeyReleaseEvent(key_set_id); + } +} + +} // namespace wvcdm diff --git a/core/src/cdm_session.cpp b/core/src/cdm_session.cpp new file mode 100644 index 00000000..84234649 --- /dev/null +++ b/core/src/cdm_session.cpp @@ -0,0 +1,453 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#include "cdm_session.h" + +#include +#include +#include + +#include "cdm_engine.h" +#include "clock.h" +#include "crypto_session.h" +#include "device_files.h" +#include "file_store.h" +#include "log.h" +#include "properties.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" +#include "wv_cdm_event_listener.h" + +namespace { +const size_t kKeySetIdLength = 14; +} // namespace + +namespace wvcdm { + +typedef std::set::iterator CdmEventListenerIter; + +CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set) + : session_id_(GenerateSessionId()), + crypto_session_(NULL), + license_received_(false), + reinitialize_session_(false), + license_type_(kLicenseTypeStreaming), + is_certificate_loaded_(false) { + if (cdm_client_property_set) { + Properties::AddSessionPropertySet(session_id_, cdm_client_property_set); + } +} + +CdmSession::~CdmSession() { Properties::RemoveSessionPropertySet(session_id_); } + +CdmResponseType CdmSession::Init() { + scoped_ptr session(new CryptoSession()); + + CdmResponseType sts = session->Open(GetRequestedSecurityLevel()); + if (NO_ERROR != sts) return sts; + + std::string token; + if (Properties::use_certificates_as_identification()) { + File file; + DeviceFiles handle; + if (!handle.Init(&file, session.get()->GetSecurityLevel()) || + !handle.RetrieveCertificate(&token, &wrapped_key_)) { + return NEED_PROVISIONING; + } + } else { + if (!session->GetToken(&token)) return UNKNOWN_ERROR; + } + + if (!license_parser_.Init(token, session.get(), &policy_engine_)) + return UNKNOWN_ERROR; + + crypto_session_.reset(session.release()); + license_received_ = false; + reinitialize_session_ = false; + return NO_ERROR; +} + +CdmResponseType CdmSession::RestoreOfflineSession( + const CdmKeySetId& key_set_id, const CdmLicenseType license_type) { + key_set_id_ = key_set_id; + + // Retrieve license information from persistent store + File file; + DeviceFiles handle; + if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) + return UNKNOWN_ERROR; + + DeviceFiles::LicenseState license_state; + + if (!handle.RetrieveLicense(key_set_id, &license_state, &offline_pssh_data_, + &offline_key_request_, &offline_key_response_, + &offline_key_renewal_request_, + &offline_key_renewal_response_, + &offline_release_server_url_)) { + LOGE("CdmSession::Init failed to retrieve license. key set id = %s", + key_set_id.c_str()); + return UNKNOWN_ERROR; + } + + if (license_state != DeviceFiles::kLicenseStateActive) { + LOGE("CdmSession::Init invalid offline license state = %s", license_state); + return UNKNOWN_ERROR; + } + + if (Properties::use_certificates_as_identification()) { + if (is_certificate_loaded_ || + crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) { + is_certificate_loaded_ = true; + } else { + return NEED_PROVISIONING; + } + } + + if (!license_parser_.RestoreOfflineLicense(offline_key_request_, + offline_key_response_, + offline_key_renewal_response_)) { + return UNKNOWN_ERROR; + } + + license_received_ = true; + license_type_ = license_type; + return KEY_ADDED; +} + +CdmResponseType CdmSession::GenerateKeyRequest( + const CdmInitData& init_data, const CdmLicenseType license_type, + const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request, + std::string* server_url) { + if (reinitialize_session_) { + CdmResponseType sts = Init(); + if (sts != NO_ERROR) { + LOGW("CdmSession::GenerateKeyRequest: Reinitialization failed"); + return sts; + } + } + + if (crypto_session_.get() == NULL) { + LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session"); + return UNKNOWN_ERROR; + } + + if (!crypto_session_->IsOpen()) { + LOGW("CdmSession::GenerateKeyRequest: Crypto session not open"); + return UNKNOWN_ERROR; + } + + license_type_ = license_type; + + if (license_type_ == kLicenseTypeRelease) { + return GenerateReleaseRequest(key_request, server_url); + } else if (license_received_) { // renewal + return Properties::require_explicit_renew_request() + ? UNKNOWN_ERROR + : GenerateRenewalRequest(key_request, server_url); + } else { + if (init_data.empty() && !license_parser_.HasInitData()) { + LOGW("CdmSession::GenerateKeyRequest: init data absent"); + return KEY_ERROR; + } + + CdmInitData pssh_data = init_data; + if (Properties::extract_pssh_data()) { + if (!CdmEngine::ExtractWidevinePssh(init_data, &pssh_data)) { + return KEY_ERROR; + } + } + + if (Properties::use_certificates_as_identification()) { + if (is_certificate_loaded_ || + crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) { + is_certificate_loaded_ = true; + } else { + reinitialize_session_ = true; + return NEED_PROVISIONING; + } + } + + if (!license_parser_.PrepareKeyRequest(pssh_data, license_type, + app_parameters, session_id_, + key_request, server_url)) { + return KEY_ERROR; + } + + if (license_type_ == kLicenseTypeOffline) { + offline_pssh_data_ = pssh_data; + offline_key_request_ = *key_request; + offline_release_server_url_ = *server_url; + } + + return KEY_MESSAGE; + } +} + +// AddKey() - Accept license response and extract key info. +CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response, + CdmKeySetId* key_set_id) { + if (crypto_session_.get() == NULL) { + LOGW("CdmSession::AddKey: Invalid crypto session"); + return UNKNOWN_ERROR; + } + + if (!crypto_session_->IsOpen()) { + LOGW("CdmSession::AddKey: Crypto session not open"); + return UNKNOWN_ERROR; + } + + if (license_type_ == kLicenseTypeRelease) { + return ReleaseKey(key_response); + } else if (license_received_) { // renewal + return Properties::require_explicit_renew_request() + ? UNKNOWN_ERROR + : RenewKey(key_response); + } else { + CdmResponseType sts = license_parser_.HandleKeyResponse(key_response); + + if (sts != KEY_ADDED) return sts; + + license_received_ = true; + + if (license_type_ == kLicenseTypeOffline) { + offline_key_response_ = key_response; + if (!GenerateKeySetId(&key_set_id_)) { + LOGE("CdmSession::AddKey: Unable to generate key set Id"); + return UNKNOWN_ERROR; + } + + if (!StoreLicense(DeviceFiles::kLicenseStateActive)) { + LOGE("CdmSession::AddKey: Unable to store license"); + CdmResponseType sts = Init(); + if (sts != NO_ERROR) { + LOGW("CdmSession::AddKey: Reinitialization failed"); + return sts; + } + + key_set_id_.clear(); + return UNKNOWN_ERROR; + } + } + + *key_set_id = key_set_id_; + return KEY_ADDED; + } +} + +CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) { + if (crypto_session_.get() == NULL) { + LOGE("CdmSession::QueryStatus: Invalid crypto session"); + return UNKNOWN_ERROR; + } + + if (!crypto_session_->IsOpen()) { + LOGE("CdmSession::QueryStatus: Crypto session not open"); + return UNKNOWN_ERROR; + } + + switch (crypto_session_->GetSecurityLevel()) { + case kSecurityLevelL1: + (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1; + break; + case kSecurityLevelL2: + (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2; + break; + case kSecurityLevelL3: + (*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3; + break; + case kSecurityLevelUninitialized: + case kSecurityLevelUnknown: + (*key_info)[QUERY_KEY_SECURITY_LEVEL] = + QUERY_VALUE_SECURITY_LEVEL_UNKNOWN; + break; + default: + return KEY_ERROR; + } + return NO_ERROR; +} + +CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) { + return policy_engine_.Query(key_info); +} + +CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) { + if (crypto_session_.get() == NULL) { + LOGW("CdmSession::QueryKeyControlInfo: Invalid crypto session"); + return UNKNOWN_ERROR; + } + + if (!crypto_session_->IsOpen()) { + LOGW("CdmSession::QueryKeyControlInfo: Crypto session not open"); + return UNKNOWN_ERROR; + } + + std::stringstream ss; + ss << crypto_session_->oec_session_id(); + (*key_info)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str(); + return NO_ERROR; +} + +// CancelKeyRequest() - Cancel session. +CdmResponseType CdmSession::CancelKeyRequest() { + crypto_session_->Close(); + return NO_ERROR; +} + +// Decrypt() - Accept encrypted buffer and return decrypted data. +CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) { + if (crypto_session_.get() == NULL || !crypto_session_->IsOpen()) + return UNKNOWN_ERROR; + + CdmResponseType status = crypto_session_->Decrypt(params); + if (UNKNOWN_ERROR == status) { + // Decrypt failed - check status of license and keys. + Clock clock; + int64_t current_time = clock.GetCurrentTime(); + if (policy_engine_.IsLicenseDurationExpired(current_time) || + policy_engine_.IsPlaybackDurationExpired(current_time)) { + return NEED_KEY; + } + } + return status; +} + +// License renewal +// GenerateRenewalRequest() - Construct valid renewal request for the current +// session keys. +CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request, + std::string* server_url) { + if (!license_parser_.PrepareKeyUpdateRequest(true, key_request, server_url)) { + LOGE("CdmSession::GenerateRenewalRequest: ERROR on prepare"); + return KEY_ERROR; + } + + if (license_type_ == kLicenseTypeOffline) { + offline_key_renewal_request_ = *key_request; + } + return KEY_MESSAGE; +} + +// RenewKey() - Accept renewal response and update key info. +CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) { + CdmResponseType sts = + license_parser_.HandleKeyUpdateResponse(true, key_response); + if (sts != KEY_ADDED) return sts; + + if (license_type_ == kLicenseTypeOffline) { + offline_key_renewal_response_ = key_response; + if (!StoreLicense(DeviceFiles::kLicenseStateActive)) return UNKNOWN_ERROR; + } + return KEY_ADDED; +} + +CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request, + std::string* server_url) { + if (license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url)) { + // Mark license as being released + if (StoreLicense(DeviceFiles::kLicenseStateReleasing)) return KEY_MESSAGE; + } + return UNKNOWN_ERROR; +} + +// ReleaseKey() - Accept release response and release license. +CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) { + CdmResponseType sts = + license_parser_.HandleKeyUpdateResponse(false, key_response); + File file; + DeviceFiles handle; + if (handle.Init(&file, crypto_session_->GetSecurityLevel())) + handle.DeleteLicense(key_set_id_); + + return sts; +} + +bool CdmSession::IsKeyLoaded(const KeyId& key_id) { + return license_parser_.IsKeyLoaded(key_id); +} + +CdmSessionId CdmSession::GenerateSessionId() { + static int session_num = 1; + return SESSION_ID_PREFIX + IntToString(++session_num); +} + +bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) { + if (!key_set_id) { + LOGW("CdmSession::GenerateKeySetId: key set id destination not provided"); + return false; + } + + std::vector random_data( + (kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0); + + File file; + DeviceFiles handle; + if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) + return false; + + while (key_set_id->empty()) { + if (!crypto_session_->GetRandom(random_data.size(), &random_data[0])) + return false; + + *key_set_id = KEY_SET_ID_PREFIX + b2a_hex(random_data); + + // key set collision + if (handle.LicenseExists(*key_set_id)) { + key_set_id->clear(); + } + } + return true; +} + +bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) { + File file; + DeviceFiles handle; + if (!handle.Init(&file, crypto_session_->GetSecurityLevel())) + return false; + + return handle.StoreLicense( + key_set_id_, state, offline_pssh_data_, offline_key_request_, + offline_key_response_, offline_key_renewal_request_, + offline_key_renewal_response_, offline_release_server_url_); +} + +bool CdmSession::AttachEventListener(WvCdmEventListener* listener) { + std::pair result = listeners_.insert(listener); + return result.second; +} + +bool CdmSession::DetachEventListener(WvCdmEventListener* listener) { + return (listeners_.erase(listener) == 1); +} + +void CdmSession::OnTimerEvent() { + bool event_occurred = false; + CdmEventType event; + + policy_engine_.OnTimerEvent(&event_occurred, &event); + + if (event_occurred) { + for (CdmEventListenerIter iter = listeners_.begin(); + iter != listeners_.end(); ++iter) { + (*iter)->OnEvent(session_id_, event); + } + } +} + +void CdmSession::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) { + if (key_set_id_ == key_set_id) { + for (CdmEventListenerIter iter = listeners_.begin(); + iter != listeners_.end(); ++iter) { + (*iter)->OnEvent(session_id_, LICENSE_EXPIRED_EVENT); + } + } +} + +SecurityLevel CdmSession::GetRequestedSecurityLevel() { + if (Properties::GetSecurityLevel(session_id_) + .compare(QUERY_VALUE_SECURITY_LEVEL_L3) == 0) { + return kLevel3; + } + + return kLevelDefault; +} + +} // namespace wvcdm diff --git a/core/src/certificate_provisioning.cpp b/core/src/certificate_provisioning.cpp new file mode 100644 index 00000000..cd5eb699 --- /dev/null +++ b/core/src/certificate_provisioning.cpp @@ -0,0 +1,250 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "certificate_provisioning.h" +#include "device_files.h" +#include "file_store.h" +#include "license_protocol.pb.h" +#include "log.h" +#include "string_conversions.h" + +namespace { + +// WHAT: URL for Google Provisioning Server. +// WHY: The provisioning server supplies the certificate that is needed +// to communicate with the License Server. +const std::string kProvisioningServerUrl = + "https://www.googleapis.com/" + "certificateprovisioning/v1/devicecertificates/create" + "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; +} + +namespace wvcdm { +// Protobuf generated classes. +using video_widevine_server::sdk::ClientIdentification; +using video_widevine_server::sdk::ProvisioningRequest; +using video_widevine_server::sdk::ProvisioningResponse; +using video_widevine_server::sdk::SignedProvisioningMessage; + +/* + * This function converts SignedProvisioningRequest into base64 string. + * It then wraps it in JSON format expected by the Apiary frontend. + * Apiary requires the base64 encoding to replace '+' with minus '-', + * and '/' with underscore '_'; opposite to stubby's. + * + * Returns the JSON formated string in *request. The JSON string will be + * appended as a query parameter, i.e. signedRequest=. All base64 '=' padding chars must be removed. + * + * The JSON formated request takes the following format: + * + * base64 encoded message + */ +void CertificateProvisioning::ComposeJsonRequestAsQueryString( + const std::string& message, + CdmProvisioningRequest* request) { + + // Performs base64 encoding for message + std::vector message_vector(message.begin(), message.end()); + std::string message_b64 = Base64SafeEncodeNoPad(message_vector); + request->assign(message_b64); +} + +/* + * Composes a device provisioning request and output the request in JSON format + * in *request. It also returns the default url for the provisioning server + * in *default_url. + * + * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. + */ +CdmResponseType CertificateProvisioning::GetProvisioningRequest( + SecurityLevel requested_security_level, + CdmProvisioningRequest* request, + std::string* default_url) { + if (!default_url) { + LOGE("GetProvisioningRequest: pointer for returning URL is NULL"); + return UNKNOWN_ERROR; + } + + default_url->assign(kProvisioningServerUrl); + + CdmResponseType sts = crypto_session_.Open(requested_security_level); + if (NO_ERROR != sts) { + LOGE("GetProvisioningRequest: fails to create a crypto session"); + return sts; + } + + // Prepares device provisioning request. + ProvisioningRequest provisioning_request; + ClientIdentification* client_id = provisioning_request.mutable_client_id(); + client_id->set_type(ClientIdentification::KEYBOX); + std::string token; + if (!crypto_session_.GetToken(&token)) { + LOGE("GetProvisioningRequest: fails to get token"); + return UNKNOWN_ERROR; + } + client_id->set_token(token); + + uint32_t nonce; + if (!crypto_session_.GenerateNonce(&nonce)) { + LOGE("GetProvisioningRequest: fails to generate a nonce"); + return UNKNOWN_ERROR; + } + + // The provisioning server does not convert the nonce to uint32_t, it just + // passes the binary data to the response message. + std::string the_nonce(reinterpret_cast(&nonce), sizeof(nonce)); + provisioning_request.set_nonce(the_nonce); + + std::string serialized_message; + provisioning_request.SerializeToString(&serialized_message); + + // Derives signing and encryption keys and constructs signature. + std::string request_signature; + if (!crypto_session_.PrepareRequest(serialized_message, true, + &request_signature)) { + LOGE("GetProvisioningRequest: fails to prepare request"); + return UNKNOWN_ERROR; + } + if (request_signature.empty()) { + LOGE("GetProvisioningRequest: request signature is empty"); + return UNKNOWN_ERROR; + } + + SignedProvisioningMessage signed_provisioning_msg; + signed_provisioning_msg.set_message(serialized_message); + signed_provisioning_msg.set_signature(request_signature); + + std::string serialized_request; + signed_provisioning_msg.SerializeToString(&serialized_request); + + // Converts request into JSON string + ComposeJsonRequestAsQueryString(serialized_request, request); + return NO_ERROR; +} + +/* + * Parses the input json_str and locates substring using start_substr and + * end_stubstr. The found base64 substring is then decoded and returns + * in *result. + * + * Returns true for success and false if fails. + */ +bool CertificateProvisioning::ParseJsonResponse( + const CdmProvisioningResponse& json_str, + const std::string& start_substr, + const std::string& end_substr, + std::string* result) { + std::string b64_string; + size_t start = json_str.find(start_substr); + if (start == json_str.npos) { + LOGE("ParseJsonResponse: cannot find start substring"); + return false; + } + size_t end = json_str.find(end_substr, start + start_substr.length()); + if (end == json_str.npos) { + LOGE("ParseJsonResponse cannot locate end substring"); + return false; + } + + size_t b64_string_size = end - start - start_substr.length(); + b64_string.assign(json_str, start + start_substr.length(), b64_string_size); + + // Decodes base64 substring and returns it in *result + std::vector result_vector = Base64SafeDecode(b64_string); + result->assign(result_vector.begin(), result_vector.end()); + + return true; +} + +/* + * 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 + * in the device. + * + * Returns NO_ERROR for success and UNKNOWN_ERROR if fails. + */ +CdmResponseType CertificateProvisioning::HandleProvisioningResponse( + CdmProvisioningResponse& response) { + + // Extracts signed response from JSON string, decodes base64 signed response + const std::string kMessageStart = "\"signedResponse\": \""; + const std::string kMessageEnd = "\""; + std::string serialized_signed_response; + if (!ParseJsonResponse(response, kMessageStart, kMessageEnd, + &serialized_signed_response)) { + LOGE("Fails to extract signed serialized response from JSON response"); + return UNKNOWN_ERROR; + } + + // Authenticates provisioning response using D1s (server key derived from + // the provisioing request's input). Validate provisioning response and + // stores private device RSA key and certificate. + SignedProvisioningMessage signed_response; + if (!signed_response.ParseFromString(serialized_signed_response)) { + LOGE("HandleProvisioningResponse: fails to parse signed response"); + return UNKNOWN_ERROR; + } + + bool error = false; + if (!signed_response.has_signature()) { + LOGE("HandleProvisioningResponse: signature not found"); + error = true; + } + + if (!signed_response.has_message()) { + LOGE("HandleProvisioningResponse: message not found"); + error = true; + } + + if (error) + return UNKNOWN_ERROR; + + const std::string& signed_message = signed_response.message(); + ProvisioningResponse provisioning_response; + + if (!provisioning_response.ParseFromString(signed_message)) { + LOGE("HandleProvisioningResponse: Fails to parse signed message"); + return UNKNOWN_ERROR; + } + + if (!provisioning_response.has_device_rsa_key()) { + LOGE("HandleProvisioningResponse: key not found"); + return UNKNOWN_ERROR; + } + + const std::string& enc_rsa_key = provisioning_response.device_rsa_key(); + const std::string& nonce = provisioning_response.nonce(); + const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv(); + const std::string& signature = signed_response.signature(); + std::string wrapped_rsa_key; + if (!crypto_session_.RewrapDeviceRSAKey(signed_message, + signature, + nonce, + enc_rsa_key, + rsa_key_iv, + &wrapped_rsa_key)){ + LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails"); + return UNKNOWN_ERROR; + } + + crypto_session_.Close(); + + const std::string& device_certificate = + provisioning_response.device_certificate(); + + File file; + DeviceFiles handle; + if (!handle.Init(&file, crypto_session_.GetSecurityLevel())) { + LOGE("HandleProvisioningResponse: failed to init DeviceFiles"); + return UNKNOWN_ERROR; + } + if (!handle.StoreCertificate(device_certificate, wrapped_rsa_key)) { + LOGE("HandleProvisioningResponse: failed to save provisioning certificate"); + return UNKNOWN_ERROR; + } + handle.DeleteAllLicenses(); + + return NO_ERROR; +} + +} // namespace wvcdm diff --git a/core/src/crypto_session.cpp b/core/src/crypto_session.cpp new file mode 100644 index 00000000..dff206ce --- /dev/null +++ b/core/src/crypto_session.cpp @@ -0,0 +1,705 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Crypto - wrapper classes for OEMCrypto interface +// + +#include "crypto_session.h" + +#include // needed for ntoh() +#include + +#include "crypto_key.h" +#include "log.h" +#include "properties.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" + +namespace { +// Encode unsigned integer into a big endian formatted string +std::string EncodeUint32(unsigned int u) { + std::string s; + s.append(1, (u >> 24) & 0xFF); + s.append(1, (u >> 16) & 0xFF); + s.append(1, (u >> 8) & 0xFF); + s.append(1, (u >> 0) & 0xFF); + return s; +} +} + +namespace wvcdm { + +Lock CryptoSession::crypto_lock_; +bool CryptoSession::initialized_ = false; +int CryptoSession::session_count_ = 0; +uint64_t CryptoSession::request_id_index_ = 0; + +CryptoSession::CryptoSession() + : open_(false), + is_destination_buffer_type_valid_(false), + requested_security_level_(kLevelDefault), + request_id_base_(0) { + Init(); +} + +CryptoSession::~CryptoSession() { + if (open_) { + Close(); + } + Terminate(); +} + +void CryptoSession::Init() { + LOGV("CryptoSession::Init"); + AutoLock auto_lock(crypto_lock_); + session_count_ += 1; + if (initialized_) return; + OEMCryptoResult sts = OEMCrypto_Initialize(); + if (OEMCrypto_SUCCESS != sts) { + LOGE("OEMCrypto_Initialize failed: %d", sts); + return; + } + initialized_ = true; +} + +void CryptoSession::Terminate() { + LOGV("CryptoSession::Terminate"); + AutoLock auto_lock(crypto_lock_); + session_count_ -= 1; + if (session_count_ > 0 || !initialized_) return; + OEMCryptoResult sts = OEMCrypto_Terminate(); + if (OEMCrypto_SUCCESS != sts) { + LOGE("OEMCrypto_Terminate failed: %d", sts); + } + initialized_ = false; +} + +bool CryptoSession::ValidateKeybox() { + LOGV("CryptoSession::ValidateKeybox: Lock"); + AutoLock auto_lock(crypto_lock_); + if (!initialized_) { + return false; + } + OEMCryptoResult result = OEMCrypto_IsKeyboxValid(requested_security_level_); + return (OEMCrypto_SUCCESS == result); +} + +bool CryptoSession::GetToken(std::string* token) { + if (!token) { + LOGE("CryptoSession::GetToken : No token passed to method."); + return false; + } + uint8_t buf[KEYBOX_KEY_DATA_SIZE]; + size_t bufSize = sizeof(buf); + LOGV("CryptoSession::GetToken: Lock"); + AutoLock auto_lock(crypto_lock_); + if (!initialized_) { + return false; + } + OEMCryptoResult sts = + OEMCrypto_GetKeyData(buf, &bufSize, requested_security_level_); + if (OEMCrypto_SUCCESS != sts) { + return false; + } + token->assign((const char*)buf, (size_t)bufSize); + return true; +} + +CdmSecurityLevel CryptoSession::GetSecurityLevel() { + LOGV("CryptoSession::GetSecurityLevel: Lock"); + AutoLock auto_lock(crypto_lock_); + if (!initialized_) { + return kSecurityLevelUninitialized; + } + + std::string security_level = + OEMCrypto_SecurityLevel(requested_security_level_); + + if ((security_level.size() != 2) || (security_level.at(0) != 'L')) { + return kSecurityLevelUnknown; + } + + switch (security_level.at(1)) { + case '1': + return kSecurityLevelL1; + case '2': + return kSecurityLevelL2; + case '3': + return kSecurityLevelL3; + default: + return kSecurityLevelUnknown; + } + + return kSecurityLevelUnknown; +} + +bool CryptoSession::GetDeviceUniqueId(std::string* device_id) { + if (!device_id) { + LOGE("CryptoSession::GetDeviceUniqueId : No buffer passed to method."); + return false; + } + + std::vector id; + size_t id_length = 32; + + id.resize(id_length); + + LOGV("CryptoSession::GetDeviceUniqueId: Lock"); + AutoLock auto_lock(crypto_lock_); + if (!initialized_) { + return false; + } + OEMCryptoResult sts = + OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_); + + if (OEMCrypto_SUCCESS != sts) { + return false; + } + + device_id->assign(reinterpret_cast(&id[0]), id_length); + return true; +} + +bool CryptoSession::GetSystemId(uint32_t* system_id) { + if (!system_id) { + LOGE("CryptoSession::GetSystemId : No buffer passed to method."); + return false; + } + + uint8_t buf[KEYBOX_KEY_DATA_SIZE]; + size_t buf_size = sizeof(buf); + + LOGV("CryptoSession::GetSystemId: Lock"); + AutoLock auto_lock(crypto_lock_); + if (!initialized_) { + return false; + } + OEMCryptoResult sts = + OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_); + + if (OEMCrypto_SUCCESS != sts) { + return false; + } + + // Decode 32-bit int encoded as network-byte-order byte array starting at + // index 4. + uint32_t* id = reinterpret_cast(&buf[4]); + + *system_id = ntohl(*id); + return true; +} + +bool CryptoSession::GetProvisioningId(std::string* provisioning_id) { + if (!provisioning_id) { + LOGE("CryptoSession::GetProvisioningId : No buffer passed to method."); + return false; + } + + uint8_t buf[KEYBOX_KEY_DATA_SIZE]; + size_t buf_size = sizeof(buf); + + LOGV("CryptoSession::GetProvisioningId: Lock"); + AutoLock auto_lock(crypto_lock_); + if (!initialized_) { + return false; + } + OEMCryptoResult sts = + OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_); + + if (OEMCrypto_SUCCESS != sts) { + return false; + } + + provisioning_id->assign(reinterpret_cast(&buf[8]), 16); + return true; +} + +CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) { + LOGV("CryptoSession::Open: Lock"); + AutoLock auto_lock(crypto_lock_); + if (!initialized_) return UNKNOWN_ERROR; + if (open_) return NO_ERROR; + + OEMCrypto_SESSION sid; + requested_security_level_ = requested_security_level; + OEMCryptoResult sts = OEMCrypto_OpenSession(&sid, requested_security_level); + if (OEMCrypto_SUCCESS == sts) { + oec_session_id_ = static_cast(sid); + LOGV("OpenSession: id= %ld", (uint32_t)oec_session_id_); + open_ = true; + } else if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) { + return INSUFFICIENT_CRYPTO_RESOURCES; + } + if (!open_) return UNKNOWN_ERROR; + + OEMCrypto_GetRandom(reinterpret_cast(&request_id_base_), + sizeof(request_id_base_)); + ++request_id_index_; + return NO_ERROR; + +} + +void CryptoSession::Close() { + LOGV("CloseSession: id=%ld open=%s", (uint32_t)oec_session_id_, + open_ ? "true" : "false"); + AutoLock auto_lock(crypto_lock_); + if (!open_) return; + if (OEMCrypto_SUCCESS == OEMCrypto_CloseSession(oec_session_id_)) { + open_ = false; + } +} + +void CryptoSession::GenerateRequestId(std::string& req_id_str) { + LOGV("CryptoSession::GenerateRequestId: Lock"); + AutoLock auto_lock(crypto_lock_); + req_id_str = HexEncode(reinterpret_cast(&request_id_base_), + sizeof(request_id_base_)) + + HexEncode(reinterpret_cast(&request_id_index_), + sizeof(request_id_index_)); +} + +bool CryptoSession::PrepareRequest(const std::string& message, + bool is_provisioning, + std::string* signature) { + LOGV("CryptoSession::PrepareRequest: Lock"); + AutoLock auto_lock(crypto_lock_); + + if (!signature) { + LOGE("CryptoSession::PrepareRequest : No output destination provided."); + return false; + } + + if (!Properties::use_certificates_as_identification() || is_provisioning) { + if (!GenerateDerivedKeys(message)) return false; + + if (!GenerateSignature(message, false, signature)) return false; + } else { + if (!GenerateSignature(message, true, signature)) return false; + } + + return true; +} + +bool CryptoSession::PrepareRenewalRequest(const std::string& message, + std::string* signature) { + LOGV("CryptoSession::PrepareRenewalRequest: Lock"); + AutoLock auto_lock(crypto_lock_); + + if (!signature) { + LOGE("CryptoSession::PrepareRenewalRequest : No output destination " + "provided."); + return false; + } + + if (!GenerateSignature(message, false, signature)) { + return false; + } + + return true; +} + +void CryptoSession::GenerateMacContext(const std::string& input_context, + std::string* deriv_context) { + if (!deriv_context) { + LOGE("CryptoSession::GenerateMacContext : No output destination provided."); + return; + } + + const std::string kSigningKeyLabel = "AUTHENTICATION"; + const size_t kSigningKeySizeBits = MAC_KEY_SIZE * 8; + + deriv_context->assign(kSigningKeyLabel); + deriv_context->append(1, '\0'); + deriv_context->append(input_context); + deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2)); +} + +void CryptoSession::GenerateEncryptContext(const std::string& input_context, + std::string* deriv_context) { + if (!deriv_context) { + LOGE("CryptoSession::GenerateEncryptContext : No output destination " + "provided."); + return; + } + + const std::string kEncryptionKeyLabel = "ENCRYPTION"; + const size_t kEncryptionKeySizeBits = KEY_SIZE * 8; + + deriv_context->assign(kEncryptionKeyLabel); + deriv_context->append(1, '\0'); + deriv_context->append(input_context); + deriv_context->append(EncodeUint32(kEncryptionKeySizeBits)); +} + +size_t CryptoSession::GetOffset(std::string message, std::string field) { + size_t pos = message.find(field); + if (pos == std::string::npos) { + LOGE("CryptoSession::GetOffset : Cannot find offset for %s", field.c_str()); + pos = 0; + } + return pos; +} + +CdmResponseType CryptoSession::LoadKeys(const std::string& message, + const std::string& signature, + const std::string& mac_key_iv, + const std::string& mac_key, + int num_keys, + const CryptoKey* key_array) { + LOGV("CryptoSession::LoadKeys: Lock"); + AutoLock auto_lock(crypto_lock_); + + const uint8_t* msg = reinterpret_cast(message.data()); + const uint8_t* enc_mac_key = NULL; + const uint8_t* enc_mac_key_iv = NULL; + if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) { + enc_mac_key = msg + GetOffset(message, mac_key); + enc_mac_key_iv = msg + GetOffset(message, mac_key_iv); + } else { + LOGV("CryptoSession::LoadKeys: enc_mac_key not set"); + } + std::vector load_key_array(num_keys); + for (int i = 0; i < num_keys; ++i) { + const CryptoKey* ki = &key_array[i]; + OEMCrypto_KeyObject* ko = &load_key_array[i]; + ko->key_id = msg + GetOffset(message, ki->key_id()); + ko->key_id_length = ki->key_id().length(); + ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv()); + ko->key_data = msg + GetOffset(message, ki->key_data()); + ko->key_data_length = ki->key_data().length(); + if (ki->HasKeyControl()) { + ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv()); + ko->key_control = msg + GetOffset(message, ki->key_control()); + } else { + LOGE("For key %d: XXX key has no control block. size=%d", i, + ki->key_control().size()); + ko->key_control_iv = NULL; + ko->key_control = NULL; + } + } + LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_); + OEMCryptoResult sts = OEMCrypto_LoadKeys( + oec_session_id_, msg, message.size(), + reinterpret_cast(signature.data()), signature.size(), + enc_mac_key_iv, enc_mac_key, num_keys, &load_key_array[0]); + + if (OEMCrypto_SUCCESS == sts) { + return KEY_ADDED; + } else if (OEMCrypto_ERROR_TOO_MANY_KEYS == sts) { + return INSUFFICIENT_CRYPTO_RESOURCES; + } else { + return KEY_ERROR; + } +} + +bool CryptoSession::LoadCertificatePrivateKey(std::string& wrapped_key) { + LOGV("CryptoSession::LoadCertificatePrivateKey: Lock"); + AutoLock auto_lock(crypto_lock_); + + LOGV("LoadDeviceRSAKey: id=%ld", (uint32_t)oec_session_id_); + OEMCryptoResult sts = OEMCrypto_LoadDeviceRSAKey( + oec_session_id_, reinterpret_cast(wrapped_key.data()), + wrapped_key.size()); + + if (OEMCrypto_SUCCESS != sts) { + LOGD("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts); + return false; + } + + return true; +} + +bool CryptoSession::RefreshKeys(const std::string& message, + const std::string& signature, int num_keys, + const CryptoKey* key_array) { + LOGV("CryptoSession::RefreshKeys: Lock"); + AutoLock auto_lock(crypto_lock_); + + const uint8_t* msg = reinterpret_cast(message.data()); + std::vector load_key_array(num_keys); + for (int i = 0; i < num_keys; ++i) { + const CryptoKey* ki = &key_array[i]; + OEMCrypto_KeyRefreshObject* ko = &load_key_array[i]; + if (ki->key_id().empty()) { + ko->key_id = NULL; + } else { + ko->key_id = msg + GetOffset(message, ki->key_id()); + } + if (ki->HasKeyControl()) { + if (ki->key_control_iv().empty()) { + ko->key_control_iv = NULL; + } else { + ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv()); + } + ko->key_control = msg + GetOffset(message, ki->key_control()); + } else { + ko->key_control_iv = NULL; + ko->key_control = NULL; + } + } + LOGV("RefreshKeys: id=%ld", static_cast(oec_session_id_)); + return ( + OEMCrypto_SUCCESS == + OEMCrypto_RefreshKeys(oec_session_id_, msg, message.size(), + reinterpret_cast(signature.data()), + signature.size(), num_keys, &load_key_array[0])); +} + +bool CryptoSession::SelectKey(const std::string& key_id) { + const uint8_t* key_id_string = + reinterpret_cast(key_id.data()); + + OEMCryptoResult sts = + OEMCrypto_SelectKey(oec_session_id_, key_id_string, key_id.size()); + if (OEMCrypto_SUCCESS != sts) { + return false; + } + return true; +} + +bool CryptoSession::GenerateDerivedKeys(const std::string& message) { + std::string mac_deriv_message; + std::string enc_deriv_message; + GenerateMacContext(message, &mac_deriv_message); + GenerateEncryptContext(message, &enc_deriv_message); + + LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_); + OEMCryptoResult sts = OEMCrypto_GenerateDerivedKeys( + oec_session_id_, + reinterpret_cast(mac_deriv_message.data()), + mac_deriv_message.size(), + reinterpret_cast(enc_deriv_message.data()), + enc_deriv_message.size()); + + if (OEMCrypto_SUCCESS != sts) { + LOGD("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts); + return false; + } + + return true; +} + +bool CryptoSession::GenerateDerivedKeys(const std::string& message, + const std::string& session_key) { + std::string mac_deriv_message; + std::string enc_deriv_message; + GenerateMacContext(message, &mac_deriv_message); + GenerateEncryptContext(message, &enc_deriv_message); + + LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_); + OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey( + oec_session_id_, reinterpret_cast(session_key.data()), + session_key.size(), + reinterpret_cast(mac_deriv_message.data()), + mac_deriv_message.size(), + reinterpret_cast(enc_deriv_message.data()), + enc_deriv_message.size()); + + if (OEMCrypto_SUCCESS != sts) { + LOGD("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts); + return false; + } + + return true; +} + +bool CryptoSession::GenerateSignature(const std::string& message, bool use_rsa, + std::string* signature) { + LOGV("GenerateSignature: id=%ld", (uint32_t)oec_session_id_); + if (!signature) return false; + + size_t length = 0; + OEMCryptoResult sts = OEMCrypto_SUCCESS; + if (use_rsa) { + sts = OEMCrypto_GenerateRSASignature( + oec_session_id_, reinterpret_cast(message.data()), + message.size(), NULL, &length); + if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { + LOGD("GenerateSignature: OEMCrypto_GenerateRSASignature err=%d", sts); + return false; + } + } else { + sts = OEMCrypto_GenerateSignature( + oec_session_id_, reinterpret_cast(message.data()), + message.size(), NULL, &length); + } + + signature->resize(length); + + if (use_rsa) { + sts = OEMCrypto_GenerateRSASignature( + oec_session_id_, reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(const_cast(signature->data())), + &length); + } else { + sts = OEMCrypto_GenerateSignature( + oec_session_id_, reinterpret_cast(message.data()), + message.size(), + reinterpret_cast(const_cast(signature->data())), + &length); + } + + if (OEMCrypto_SUCCESS != sts) { + LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); + return false; + } + + signature->resize(length); + + return true; +} + +CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) { + if (!is_destination_buffer_type_valid_) { + if (!SetDestinationBufferType()) return UNKNOWN_ERROR; + } + + AutoLock auto_lock(crypto_lock_); + // Check if key needs to be selected + if (params.is_encrypted) { + if (key_id_ != *params.key_id) { + if (SelectKey(*params.key_id)) { + key_id_ = *params.key_id; + } else { + return NEED_KEY; + } + } + } + + OEMCrypto_DestBufferDesc buffer_descriptor; + buffer_descriptor.type = + params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear; + + switch (buffer_descriptor.type) { + case OEMCrypto_BufferType_Clear: + buffer_descriptor.buffer.clear.address = + static_cast(params.decrypt_buffer) + + params.decrypt_buffer_offset; + buffer_descriptor.buffer.clear.max_length = params.decrypt_buffer_length; + break; + case OEMCrypto_BufferType_Secure: + buffer_descriptor.buffer.secure.handle = params.decrypt_buffer; + buffer_descriptor.buffer.secure.offset = params.decrypt_buffer_offset; + buffer_descriptor.buffer.secure.max_length = params.decrypt_buffer_length; + break; + case OEMCrypto_BufferType_Direct: + buffer_descriptor.type = OEMCrypto_BufferType_Direct; + buffer_descriptor.buffer.direct.is_video = params.is_video; + break; + } + + OEMCryptoResult sts = OEMCrypto_DecryptCTR( + oec_session_id_, params.encrypt_buffer, params.encrypt_length, + params.is_encrypted, &(*params.iv).front(), params.block_offset, + &buffer_descriptor, params.subsample_flags); + + switch (sts) { + case OEMCrypto_SUCCESS: + return NO_ERROR; + case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: + return INSUFFICIENT_CRYPTO_RESOURCES; + case OEMCrypto_ERROR_KEY_EXPIRED: + return NEED_KEY; + default: + return UNKNOWN_ERROR; + } +} + +bool CryptoSession::GenerateNonce(uint32_t* nonce) { + if (!nonce) { + LOGE("input parameter is null"); + return false; + } + + LOGV("CryptoSession::GenerateNonce: Lock"); + AutoLock auto_lock(crypto_lock_); + + return (OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(oec_session_id_, nonce)); +} + +bool CryptoSession::SetDestinationBufferType() { + if (Properties::oem_crypto_use_secure_buffers()) { + if (GetSecurityLevel() == kSecurityLevelL1) { + destination_buffer_type_ = OEMCrypto_BufferType_Secure; + } else { + destination_buffer_type_ = OEMCrypto_BufferType_Clear; + } + } else if (Properties::oem_crypto_use_fifo()) { + destination_buffer_type_ = OEMCrypto_BufferType_Direct; + } else if (Properties::oem_crypto_use_userspace_buffers()) { + destination_buffer_type_ = OEMCrypto_BufferType_Clear; + } else { + return false; + } + + is_destination_buffer_type_valid_ = true; + return true; +} + +bool CryptoSession::RewrapDeviceRSAKey(const std::string& message, + const std::string& signature, + const std::string& nonce, + const std::string& enc_rsa_key, + const std::string& rsa_key_iv, + std::string* wrapped_rsa_key) { + LOGD("CryptoSession::RewrapDeviceRSAKey, session id=%ld", + static_cast(oec_session_id_)); + + const uint8_t* signed_msg = reinterpret_cast(message.data()); + const uint8_t* msg_rsa_key = NULL; + const uint8_t* msg_rsa_key_iv = NULL; + const uint32_t* msg_nonce = NULL; + if (enc_rsa_key.size() >= MAC_KEY_SIZE && rsa_key_iv.size() >= KEY_IV_SIZE) { + msg_rsa_key = signed_msg + GetOffset(message, enc_rsa_key); + msg_rsa_key_iv = signed_msg + GetOffset(message, rsa_key_iv); + msg_nonce = reinterpret_cast(signed_msg + + GetOffset(message, nonce)); + } + + // Gets wrapped_rsa_key_length by passing NULL as uint8_t* wrapped_rsa_key + // and 0 as wrapped_rsa_key_length. + size_t wrapped_rsa_key_length = 0; + OEMCryptoResult status = OEMCrypto_RewrapDeviceRSAKey( + oec_session_id_, signed_msg, message.size(), + reinterpret_cast(signature.data()), signature.size(), + msg_nonce, msg_rsa_key, enc_rsa_key.size(), msg_rsa_key_iv, NULL, + &wrapped_rsa_key_length); + if (status != OEMCrypto_ERROR_SHORT_BUFFER) { + LOGE("OEMCrypto_RewrapDeviceRSAKey fails to get wrapped_rsa_key_length"); + return false; + } + + wrapped_rsa_key->resize(wrapped_rsa_key_length); + status = OEMCrypto_RewrapDeviceRSAKey( + oec_session_id_, signed_msg, message.size(), + reinterpret_cast(signature.data()), signature.size(), + msg_nonce, msg_rsa_key, enc_rsa_key.size(), msg_rsa_key_iv, + reinterpret_cast(&(*wrapped_rsa_key)[0]), + &wrapped_rsa_key_length); + + wrapped_rsa_key->resize(wrapped_rsa_key_length); + + if (OEMCrypto_SUCCESS != status) { + LOGE("OEMCrypto_RewrapDeviceRSAKey fails with %d", status); + return false; + } + + return true; +} + +bool CryptoSession::GetRandom(size_t data_length, uint8_t* random_data) { + if (random_data == NULL) { + LOGE("CryptoSession::GetRandom: random data destination not provided"); + return false; + } + OEMCryptoResult sts = OEMCrypto_GetRandom(random_data, data_length); + + if (sts != OEMCrypto_SUCCESS) { + LOGE("OEMCrypto_GetRandom fails with %d", sts); + return false; + } + + return true; +} + +}; // namespace wvcdm diff --git a/core/src/device_files.cpp b/core/src/device_files.cpp new file mode 100644 index 00000000..75bb2055 --- /dev/null +++ b/core/src/device_files.cpp @@ -0,0 +1,528 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "device_files.h" + +#include +#include + +#include "device_files.pb.h" +#include "file_store.h" +#include "log.h" +#include "openssl/sha.h" +#include "properties.h" + +// Protobuf generated classes. +using video_widevine_client::sdk::DeviceCertificate; +using video_widevine_client::sdk::HashedFile; +using video_widevine_client::sdk::License; +using video_widevine_client::sdk::License_LicenseState_ACTIVE; +using video_widevine_client::sdk::License_LicenseState_RELEASING; + +namespace { +const char kCertificateFileName[] = "cert.bin"; +const char kLicenseFileNameExt[] = ".lic"; +const char kWildcard[] = "*"; +const char kDirectoryDelimiter = '/'; +const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"}; +size_t kSecurityLevelPathCompatibilityExclusionListSize = + sizeof(kSecurityLevelPathCompatibilityExclusionList) / + sizeof(*kSecurityLevelPathCompatibilityExclusionList); +} // namespace + +namespace wvcdm { + +bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) { + if (handle == NULL) { + LOGW("DeviceFiles::Init: Invalid file handle parameter"); + return false; + } + switch (security_level) { + case kSecurityLevelL1: + case kSecurityLevelL2: + case kSecurityLevelL3: + break; + default: + LOGW("DeviceFiles::Init: Unsupported security level %d", security_level); + return false; + } + file_ = const_cast(handle); + security_level_ = security_level; + initialized_ = true; + return true; +} + +bool DeviceFiles::StoreCertificate(const std::string& certificate, + const std::string& wrapped_private_key) { + if (!initialized_) { + LOGW("DeviceFiles::StoreCertificate: not initialized"); + return false; + } + + // Fill in file information + video_widevine_client::sdk::File file; + + file.set_type(video_widevine_client::sdk::File::DEVICE_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); + + std::string serialized_string; + file.SerializeToString(&serialized_string); + + // calculate SHA hash + std::string hash; + if (!Hash(serialized_string, &hash)) { + LOGW("DeviceFiles::StoreCertificate: Hash computation failed"); + return false; + } + + // Fill in hashed file data + HashedFile hashed_file; + hashed_file.set_file(serialized_string); + hashed_file.set_hash(hash); + + hashed_file.SerializeToString(&serialized_string); + + return StoreFile(kCertificateFileName, serialized_string); +} + +bool DeviceFiles::RetrieveCertificate(std::string* certificate, + std::string* wrapped_private_key) { + if (!initialized_) { + LOGW("DeviceFiles::RetrieveCertificate: not initialized"); + return false; + } + + if (Properties::security_level_path_backward_compatibility_support()) { + SecurityLevelPathBackwardCompatibility(); + } + + std::string serialized_hashed_file; + if (!RetrieveFile(kCertificateFileName, &serialized_hashed_file)) + return false; + + HashedFile hashed_file; + if (!hashed_file.ParseFromString(serialized_hashed_file)) { + LOGW("DeviceFiles::RetrieveCertificate: Unable to parse hash file"); + return false; + } + + std::string hash; + if (!Hash(hashed_file.file(), &hash)) { + LOGW("DeviceFiles::RetrieveCertificate: Hash computation failed"); + return false; + } + + if (hash.compare(hashed_file.hash())) { + LOGW("DeviceFiles::RetrieveCertificate: Hash mismatch"); + return false; + } + + video_widevine_client::sdk::File file; + if (!file.ParseFromString(hashed_file.file())) { + LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file"); + return false; + } + + if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) { + LOGW("DeviceFiles::RetrieveCertificate: Incorrect file type"); + return false; + } + + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGW("DeviceFiles::RetrieveCertificate: Incorrect file version"); + return false; + } + + if (!file.has_device_certificate()) { + LOGW("DeviceFiles::RetrieveCertificate: Certificate not present"); + return false; + } + + DeviceCertificate device_certificate = file.device_certificate(); + + *certificate = device_certificate.certificate(); + *wrapped_private_key = device_certificate.wrapped_private_key(); + return true; +} + +bool DeviceFiles::StoreLicense(const std::string& key_set_id, + const LicenseState state, + const CdmInitData& pssh_data, + const CdmKeyMessage& license_request, + const CdmKeyResponse& license_message, + const CdmKeyMessage& license_renewal_request, + const CdmKeyResponse& license_renewal, + const std::string& release_server_url) { + if (!initialized_) { + LOGW("DeviceFiles::StoreLicense: not initialized"); + return false; + } + + // Fill in file information + video_widevine_client::sdk::File file; + + file.set_type(video_widevine_client::sdk::File::LICENSE); + file.set_version(video_widevine_client::sdk::File::VERSION_1); + + License* license = file.mutable_license(); + switch (state) { + case kLicenseStateActive: + license->set_state(License_LicenseState_ACTIVE); + break; + case kLicenseStateReleasing: + license->set_state(License_LicenseState_RELEASING); + break; + default: + LOGW("DeviceFiles::StoreLicense: Unknown license state: %u", state); + return false; + break; + } + license->set_pssh_data(pssh_data); + license->set_license_request(license_request); + license->set_license(license_message); + license->set_renewal_request(license_renewal_request); + license->set_renewal(license_renewal); + license->set_release_server_url(release_server_url); + + std::string serialized_string; + file.SerializeToString(&serialized_string); + + // calculate SHA hash + std::string hash; + if (!Hash(serialized_string, &hash)) { + LOGW("DeviceFiles::StoreLicense: Hash computation failed"); + return false; + } + + // File in hashed file data + HashedFile hashed_file; + hashed_file.set_file(serialized_string); + hashed_file.set_hash(hash); + + hashed_file.SerializeToString(&serialized_string); + + std::string file_name = key_set_id + kLicenseFileNameExt; + return StoreFile(file_name.c_str(), serialized_string); +} + +bool DeviceFiles::RetrieveLicense(const std::string& key_set_id, + LicenseState* state, CdmInitData* pssh_data, + CdmKeyMessage* license_request, + CdmKeyResponse* license_message, + CdmKeyMessage* license_renewal_request, + CdmKeyResponse* license_renewal, + std::string* release_server_url) { + if (!initialized_) { + LOGW("DeviceFiles::RetrieveLicense: not initialized"); + return false; + } + + std::string serialized_hashed_file; + std::string file_name = key_set_id + kLicenseFileNameExt; + if (!RetrieveFile(file_name.c_str(), &serialized_hashed_file)) return false; + + HashedFile hashed_file; + if (!hashed_file.ParseFromString(serialized_hashed_file)) { + LOGW("DeviceFiles::RetrieveLicense: Unable to parse hash file"); + return false; + } + + std::string hash; + if (!Hash(hashed_file.file(), &hash)) { + LOGW("DeviceFiles::RetrieveLicense: Hash computation failed"); + return false; + } + + if (hash.compare(hashed_file.hash())) { + LOGW("DeviceFiles::RetrieveLicense: Hash mismatch"); + return false; + } + + video_widevine_client::sdk::File file; + if (!file.ParseFromString(hashed_file.file())) { + LOGW("DeviceFiles::RetrieveLicense: Unable to parse file"); + return false; + } + + if (file.type() != video_widevine_client::sdk::File::LICENSE) { + LOGW("DeviceFiles::RetrieveLicense: Incorrect file type"); + return false; + } + + if (file.version() != video_widevine_client::sdk::File::VERSION_1) { + LOGW("DeviceFiles::RetrieveLicense: Incorrect file version"); + return false; + } + + if (!file.has_license()) { + LOGW("DeviceFiles::RetrieveLicense: License not present"); + return false; + } + + License license = file.license(); + + switch (license.state()) { + case License_LicenseState_ACTIVE: + *state = kLicenseStateActive; + break; + case License_LicenseState_RELEASING: + *state = kLicenseStateReleasing; + break; + default: + LOGW("DeviceFiles::RetrieveLicense: Unrecognized license state: %u", + kLicenseStateUnknown); + *state = kLicenseStateUnknown; + break; + } + *pssh_data = license.pssh_data(); + *license_request = license.license_request(); + *license_message = license.license(); + *license_renewal_request = license.renewal_request(); + *license_renewal = license.renewal(); + *release_server_url = license.release_server_url(); + return true; +} + +bool DeviceFiles::DeleteLicense(const std::string& key_set_id) { + if (!initialized_) { + LOGW("DeviceFiles::DeleteLicense: not initialized"); + return false; + } + + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + LOGW("DeviceFiles::DeleteLicense: Unable to get base path"); + return false; + } + path.append(key_set_id); + path.append(kLicenseFileNameExt); + + return file_->Remove(path); +} + +bool DeviceFiles::DeleteAllLicenses() { + if (!initialized_) { + LOGW("DeviceFiles::DeleteAllLicenses: not initialized"); + return false; + } + + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + LOGW("DeviceFiles::DeleteAllLicenses: Unable to get base path"); + return false; + } + path.append(kWildcard); + path.append(kLicenseFileNameExt); + + return file_->Remove(path); +} + +bool DeviceFiles::DeleteAllFiles() { + if (!initialized_) { + LOGW("DeviceFiles::DeleteAllFiles: not initialized"); + return false; + } + + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + LOGW("DeviceFiles::DeleteAllFiles: Unable to get base path"); + return false; + } + + return file_->Remove(path); +} + +bool DeviceFiles::LicenseExists(const std::string& key_set_id) { + if (!initialized_) { + LOGW("DeviceFiles::LicenseExists: not initialized"); + return false; + } + + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + LOGW("DeviceFiles::StoreFile: Unable to get base path"); + return false; + } + path.append(key_set_id); + path.append(kLicenseFileNameExt); + + return file_->Exists(path); +} + +bool DeviceFiles::Hash(const std::string& data, std::string* hash) { + if (!hash) return false; + + hash->resize(SHA256_DIGEST_LENGTH); + SHA256_CTX sha256; + SHA256_Init(&sha256); + SHA256_Update(&sha256, data.data(), data.size()); + SHA256_Final(reinterpret_cast(&(*hash)[0]), &sha256); + return true; +} + +bool DeviceFiles::StoreFile(const char* name, const std::string& data) { + if (!file_) { + LOGW("DeviceFiles::StoreFile: Invalid file handle"); + return false; + } + + if (!name) { + LOGW("DeviceFiles::StoreFile: Unspecified file name parameter"); + return false; + } + + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + LOGW("DeviceFiles::StoreFile: Unable to get base path"); + return false; + } + + if (!file_->IsDirectory(path)) { + if (!file_->CreateDirectory(path)) return false; + } + + path += name; + + if (!file_->Open(path, File::kCreate | File::kTruncate | File::kBinary)) { + LOGW("DeviceFiles::StoreFile: File open failed: %s", path.c_str()); + return false; + } + + ssize_t bytes = file_->Write(data.data(), data.size()); + file_->Close(); + + if (bytes != static_cast(data.size())) { + LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes); + return false; + } + + LOGV("DeviceFiles::StoreFile: success: %s (%db)", path.c_str(), data.size()); + return true; +} + +bool DeviceFiles::RetrieveFile(const char* name, std::string* data) { + if (!file_) { + LOGW("DeviceFiles::RetrieveFile: Invalid file handle"); + return false; + } + + if (!name) { + LOGW("DeviceFiles::RetrieveFile: Unspecified file name parameter"); + return false; + } + + if (!data) { + LOGW("DeviceFiles::RetrieveFile: Unspecified data parameter"); + return false; + } + + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + LOGW("DeviceFiles::StoreFile: Unable to get base path"); + return false; + } + + path += name; + + if (!file_->Exists(path)) { + LOGW("DeviceFiles::RetrieveFile: %s does not exist", path.c_str()); + return false; + } + + ssize_t bytes = file_->FileSize(path); + if (bytes <= 0) { + LOGW("DeviceFiles::RetrieveFile: File size invalid: %d", path.c_str()); + return false; + } + + if (!file_->Open(path, File::kReadOnly | File::kBinary)) { + return false; + } + + data->resize(bytes); + bytes = file_->Read(&(*data)[0], data->size()); + file_->Close(); + + if (bytes != static_cast(data->size())) { + LOGW("DeviceFiles::RetrieveFile: read failed"); + return false; + } + + LOGV("DeviceFiles::RetrieveFile: success: %s (%db)", path.c_str(), + data->size()); + return true; +} + +void DeviceFiles::SecurityLevelPathBackwardCompatibility() { + std::string path; + if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) { + LOGW("DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to " + "get base path"); + return; + } + + std::vector security_dirs; + if (!Properties::GetSecurityLevelDirectories(&security_dirs)) { + LOGW("DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to " + "get security directories"); + return; + } + + size_t pos = std::string::npos; + for (size_t i = 0; i < security_dirs.size(); ++i) { + pos = path.find(security_dirs[i]); + if (pos != std::string::npos && pos > 0 && + pos == path.size() - security_dirs[i].size() && + path[pos - 1] == kDirectoryDelimiter) { + break; + } + } + + if (pos == std::string::npos) { + LOGV("DeviceFiles::SecurityLevelPathBackwardCompatibility: Security level " + "specific path not found. Check properties?"); + return; + } + + std::string from_dir(path, 0, pos); + + std::vector files; + if (!file_->List(from_dir, &files)) { + return; + } + + for (size_t i = 0; i < files.size(); ++i) { + std::string from = from_dir + files[i]; + bool exclude = false; + for (size_t j = 0; j < kSecurityLevelPathCompatibilityExclusionListSize; + ++j) { + if (files[i] == kSecurityLevelPathCompatibilityExclusionList[j]) { + exclude = true; + break; + } + } + if (exclude) continue; + if (!file_->IsRegularFile(from)) continue; + + for (size_t j = 0; j < security_dirs.size(); ++j) { + std::string to_dir = from_dir + security_dirs[j]; + if (!file_->Exists(to_dir)) file_->CreateDirectory(to_dir); + std::string to = to_dir + files[i]; + file_->Copy(from, to); + } + file_->Remove(from); + } +} + +std::string DeviceFiles::GetCertificateFileName() { + return kCertificateFileName; +} + +std::string DeviceFiles::GetLicenseFileNameExtension() { + return kLicenseFileNameExt; +} + +} // namespace wvcdm diff --git a/core/src/device_files.proto b/core/src/device_files.proto new file mode 100644 index 00000000..d561c831 --- /dev/null +++ b/core/src/device_files.proto @@ -0,0 +1,55 @@ +// ---------------------------------------------------------------------------- +// device_files.proto +// ---------------------------------------------------------------------------- +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// Format of various files stored at the device. +// +syntax = "proto2"; + +package video_widevine_client.sdk; + +// need this if we are using libprotobuf-cpp-2.3.0-lite +option optimize_for = LITE_RUNTIME; + +message DeviceCertificate { + optional bytes certificate = 1; + optional bytes wrapped_private_key = 2; +} + +message License { + enum LicenseState { + ACTIVE = 1; + RELEASING = 2; + } + + optional LicenseState state = 1; + optional bytes pssh_data = 2; + optional bytes license_request = 3; + optional bytes license = 4; + optional bytes renewal_request = 5; + optional bytes renewal = 6; + optional bytes release_server_url = 7; +} + +message File { + enum FileType { + DEVICE_CERTIFICATE = 1; + LICENSE = 2; + } + + enum FileVersion { + VERSION_1 = 1; + } + + optional FileType type = 1; + optional FileVersion version = 2 [default = VERSION_1]; + optional DeviceCertificate device_certificate = 3; + optional License license = 4; +} + +message HashedFile { + optional bytes file = 1; + optional bytes hash = 2; +} diff --git a/core/src/license.cpp b/core/src/license.cpp new file mode 100644 index 00000000..0851c1cb --- /dev/null +++ b/core/src/license.cpp @@ -0,0 +1,750 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#include "license.h" + +#include + +#include "crypto_key.h" +#include "crypto_session.h" +#include "log.h" +#include "policy_engine.h" +#include "properties.h" +#include "privacy_crypto.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" + +namespace { +std::string kCompanyNameKey = "company_name"; +std::string kModelNameKey = "model_name"; +std::string kArchitectureNameKey = "architecture_name"; +std::string kDeviceNameKey = "device_name"; +std::string kProductNameKey = "product_name"; +std::string kBuildInfoKey = "build_info"; +std::string kDeviceIdKey = "device_id"; +const unsigned char kServiceCertificateCAPublicKey[] = { + 0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81, + 0x00, 0xb4, 0xfe, 0x39, 0xc3, 0x65, 0x90, 0x03, + 0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68, 0xcd, + 0xf2, 0xc3, 0x5e, 0x9b, 0xf2, 0xe7, 0x4d, 0x23, + 0xb1, 0x10, 0xdb, 0x87, 0x65, 0xdf, 0xdc, 0xfb, + 0x9f, 0x35, 0xa0, 0x57, 0x03, 0x53, 0x4c, 0xf6, + 0x6d, 0x35, 0x7d, 0xa6, 0x78, 0xdb, 0xb3, 0x36, + 0xd2, 0x3f, 0x9c, 0x40, 0xa9, 0x95, 0x26, 0x72, + 0x7f, 0xb8, 0xbe, 0x66, 0xdf, 0xc5, 0x21, 0x98, + 0x78, 0x15, 0x16, 0x68, 0x5d, 0x2f, 0x46, 0x0e, + 0x43, 0xcb, 0x8a, 0x84, 0x39, 0xab, 0xfb, 0xb0, + 0x35, 0x80, 0x22, 0xbe, 0x34, 0x23, 0x8b, 0xab, + 0x53, 0x5b, 0x72, 0xec, 0x4b, 0xb5, 0x48, 0x69, + 0x53, 0x3e, 0x47, 0x5f, 0xfd, 0x09, 0xfd, 0xa7, + 0x76, 0x13, 0x8f, 0x0f, 0x92, 0xd6, 0x4c, 0xdf, + 0xae, 0x76, 0xa9, 0xba, 0xd9, 0x22, 0x10, 0xa9, + 0x9d, 0x71, 0x45, 0xd6, 0xd7, 0xe1, 0x19, 0x25, + 0x85, 0x9c, 0x53, 0x9a, 0x97, 0xeb, 0x84, 0xd7, + 0xcc, 0xa8, 0x88, 0x82, 0x20, 0x70, 0x26, 0x20, + 0xfd, 0x7e, 0x40, 0x50, 0x27, 0xe2, 0x25, 0x93, + 0x6f, 0xbc, 0x3e, 0x72, 0xa0, 0xfa, 0xc1, 0xbd, + 0x29, 0xb4, 0x4d, 0x82, 0x5c, 0xc1, 0xb4, 0xcb, + 0x9c, 0x72, 0x7e, 0xb0, 0xe9, 0x8a, 0x17, 0x3e, + 0x19, 0x63, 0xfc, 0xfd, 0x82, 0x48, 0x2b, 0xb7, + 0xb2, 0x33, 0xb9, 0x7d, 0xec, 0x4b, 0xba, 0x89, + 0x1f, 0x27, 0xb8, 0x9b, 0x88, 0x48, 0x84, 0xaa, + 0x18, 0x92, 0x0e, 0x65, 0xf5, 0xc8, 0x6c, 0x11, + 0xff, 0x6b, 0x36, 0xe4, 0x74, 0x34, 0xca, 0x8c, + 0x33, 0xb1, 0xf9, 0xb8, 0x8e, 0xb4, 0xe6, 0x12, + 0xe0, 0x02, 0x98, 0x79, 0x52, 0x5e, 0x45, 0x33, + 0xff, 0x11, 0xdc, 0xeb, 0xc3, 0x53, 0xba, 0x7c, + 0x60, 0x1a, 0x11, 0x3d, 0x00, 0xfb, 0xd2, 0xb7, + 0xaa, 0x30, 0xfa, 0x4f, 0x5e, 0x48, 0x77, 0x5b, + 0x17, 0xdc, 0x75, 0xef, 0x6f, 0xd2, 0x19, 0x6d, + 0xdc, 0xbe, 0x7f, 0xb0, 0x78, 0x8f, 0xdc, 0x82, + 0x60, 0x4c, 0xbf, 0xe4, 0x29, 0x06, 0x5e, 0x69, + 0x8c, 0x39, 0x13, 0xad, 0x14, 0x25, 0xed, 0x19, + 0xb2, 0xf2, 0x9f, 0x01, 0x82, 0x0d, 0x56, 0x44, + 0x88, 0xc8, 0x35, 0xec, 0x1f, 0x11, 0xb3, 0x24, + 0xe0, 0x59, 0x0d, 0x37, 0xe4, 0x47, 0x3c, 0xea, + 0x4b, 0x7f, 0x97, 0x31, 0x1c, 0x81, 0x7c, 0x94, + 0x8a, 0x4c, 0x7d, 0x68, 0x15, 0x84, 0xff, 0xa5, + 0x08, 0xfd, 0x18, 0xe7, 0xe7, 0x2b, 0xe4, 0x47, + 0x27, 0x12, 0x11, 0xb8, 0x23, 0xec, 0x58, 0x93, + 0x3c, 0xac, 0x12, 0xd2, 0x88, 0x6d, 0x41, 0x3d, + 0xc5, 0xfe, 0x1c, 0xdc, 0xb9, 0xf8, 0xd4, 0x51, + 0x3e, 0x07, 0xe5, 0x03, 0x6f, 0xa7, 0x12, 0xe8, + 0x12, 0xf7, 0xb5, 0xce, 0xa6, 0x96, 0x55, 0x3f, + 0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, 0x5f, + 0x91, 0x02, 0x03, 0x01, 0x00, 0x01}; +} + +namespace wvcdm { + +// Protobuf generated classes. +using video_widevine_server::sdk::ClientIdentification; +using video_widevine_server::sdk::ClientIdentification_NameValue; +using video_widevine_server::sdk::DeviceCertificate; +using video_widevine_server::sdk::EncryptedClientIdentification; +using video_widevine_server::sdk::LicenseRequest; +using video_widevine_server::sdk::LicenseRequest_ContentIdentification; +using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC; +using video_widevine_server::sdk:: + LicenseRequest_ContentIdentification_ExistingLicense; +using video_widevine_server::sdk::License; +using video_widevine_server::sdk::License_KeyContainer; +using video_widevine_server::sdk::LicenseError; +using video_widevine_server::sdk::SignedDeviceCertificate; +using video_widevine_server::sdk::SignedMessage; + +static std::vector ExtractContentKeys(const License& license) { + std::vector key_array; + + // 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. + if (license.key(i).key().size() > 16) { + length = license.key(i).key().size() - 16; + } else { + length = 0; + } + 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()) { + key.set_key_control(license.key(i).key_control().key_control_block()); + key.set_key_control_iv(license.key(i).key_control().iv()); + } + key_array.push_back(key); + break; + case License_KeyContainer::KEY_CONTROL: + if (license.key(i).has_key_control()) { + key.set_key_control(license.key(i).key_control().key_control_block()); + if (license.key(i).key_control().has_iv()) { + key.set_key_control_iv(license.key(i).key_control().iv()); + } + key_array.push_back(key); + } + break; + default: + // Ignore SIGNING key types as they are not content related + break; + } + } + + return key_array; +} + +bool CdmLicense::Init(const std::string& token, CryptoSession* session, + PolicyEngine* policy_engine) { + if (token.size() == 0) { + LOGE("CdmLicense::Init: empty token provided"); + return false; + } + if (session == NULL || !session->IsOpen()) { + LOGE("CdmLicense::Init: crypto session not provided or not open"); + return false; + } + if (policy_engine == NULL) { + LOGE("CdmLicense::Init: no policy engine provided"); + return false; + } + token_ = token; + session_ = session; + policy_engine_ = policy_engine; + initialized_ = true; + return true; +} + +bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data, + const CdmLicenseType license_type, + const CdmAppParameterMap& app_parameters, + const CdmSessionId& session_id, + CdmKeyMessage* signed_request, + std::string* server_url) { + if (!initialized_) { + LOGE("CdmLicense::PrepareKeyRequest: not initialized"); + return false; + } + if (init_data.empty() && init_data_.empty()) { + LOGE("CdmLicense::PrepareKeyRequest: empty init data provided"); + return false; + } + if (session_id.empty()) { + LOGE("CdmLicense::PrepareKeyRequest: empty session id provided"); + return false; + } + if (!signed_request) { + LOGE("CdmLicense::PrepareKeyRequest: no signed request provided"); + return false; + } + if (!server_url) { + LOGE("CdmLicense::PrepareKeyRequest: no server url provided"); + return false; + } + + bool privacy_mode_enabled = Properties::UsePrivacyMode(session_id); + std::vector cert = Properties::GetServiceCertificate(session_id); + std::string serialized_service_certificate(cert.begin(), cert.end()); + + if (serialized_service_certificate.empty()) + serialized_service_certificate = service_certificate_; + + if (privacy_mode_enabled && serialized_service_certificate.empty()) { + init_data_ = init_data; + return PrepareServiceCertificateRequest(signed_request, server_url); + } + + std::string request_id; + session_->GenerateRequestId(request_id); + + LicenseRequest license_request; + ClientIdentification* client_id = license_request.mutable_client_id(); + + if (Properties::use_certificates_as_identification()) + client_id->set_type(ClientIdentification::DEVICE_CERTIFICATE); + else + client_id->set_type(ClientIdentification::KEYBOX); + client_id->set_token(token_); + + ClientIdentification_NameValue* client_info; + CdmAppParameterMap::const_iterator iter; + for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) { + client_info = client_id->add_client_info(); + client_info->set_name(iter->first); + client_info->set_value(iter->second); + } + std::string value; + if (Properties::GetCompanyName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kCompanyNameKey); + client_info->set_value(value); + } + if (Properties::GetModelName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kModelNameKey); + client_info->set_value(value); + } + if (Properties::GetArchitectureName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kArchitectureNameKey); + client_info->set_value(value); + } + if (Properties::GetDeviceName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kDeviceNameKey); + client_info->set_value(value); + } + if (Properties::GetProductName(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kProductNameKey); + client_info->set_value(value); + } + if (Properties::GetBuildInfo(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kBuildInfoKey); + client_info->set_value(value); + } + + if (session_->GetDeviceUniqueId(&value)) { + client_info = client_id->add_client_info(); + client_info->set_name(kDeviceIdKey); + client_info->set_value(value); + } + + if (privacy_mode_enabled) { + EncryptedClientIdentification* encrypted_client_id = + license_request.mutable_encrypted_client_id(); + DeviceCertificate service_certificate; + + if (!service_certificate.ParseFromString(serialized_service_certificate)) { + LOGE("CdmLicense::PrepareKeyRequest: unable to parse retrieved " + "service certificate"); + return false; + } + + if (service_certificate.type() != + video_widevine_server::sdk::DeviceCertificate_CertificateType_SERVICE) { + LOGE("CdmLicense::PrepareKeyRequest: retrieved certificate not of type" + " service, %d", service_certificate.type()); + return false; + } + encrypted_client_id->set_service_id(service_certificate.service_id()); + encrypted_client_id->set_service_certificate_serial_number( + service_certificate.serial_number()); + + std::string iv(KEY_IV_SIZE, 0); + std::string key(KEY_SIZE, 0); + + if (!session_->GetRandom(key.size(), reinterpret_cast(&key[0]))) { + return false; + } + if (!session_->GetRandom(iv.size(), reinterpret_cast(&iv[0]))) { + return false; + } + std::string id, enc_id, enc_key; + client_id->SerializeToString(&id); + + AesCbcKey aes; + if (!aes.Init(key)) return false; + if (!aes.Encrypt(id, &enc_id, &iv)) return false; + + RsaPublicKey rsa; + if (!rsa.Init(service_certificate.public_key())) return false; + if (!rsa.Encrypt(key, &enc_key)) return false; + + encrypted_client_id->set_encrypted_client_id_iv(iv); + encrypted_client_id->set_encrypted_privacy_key(enc_key); + encrypted_client_id->set_encrypted_client_id(enc_id); + license_request.clear_client_id(); + } + + // Content Identification may be a cenc_id, a webm_id or a license_id + LicenseRequest_ContentIdentification* content_id = + license_request.mutable_content_id(); + + LicenseRequest_ContentIdentification_CENC* cenc_content_id = + content_id->mutable_cenc_id(); + + if (!init_data.empty()) { + cenc_content_id->add_pssh(init_data); + } else if (privacy_mode_enabled && !init_data_.empty()) { + cenc_content_id->add_pssh(init_data_); + } else { + LOGD("CdmLicense::PrepareKeyRequest: init data not available"); + return false; + } + + switch (license_type) { + case kLicenseTypeOffline: + cenc_content_id->set_license_type(video_widevine_server::sdk::OFFLINE); + break; + case kLicenseTypeStreaming: + cenc_content_id->set_license_type(video_widevine_server::sdk::STREAMING); + break; + default: + LOGD("CdmLicense::PrepareKeyRequest: Unknown license type = %d", + license_type); + return false; + break; + } + cenc_content_id->set_request_id(request_id); + + // The time field will be updated once the cdm wrapper + // has been updated to pass us in the time. + license_request.set_request_time(0); + + license_request.set_type(LicenseRequest::NEW); + + // Get/set the nonce. This value will be reflected in the Key Control Block + // of the license response. + uint32_t nonce; + if (!session_->GenerateNonce(&nonce)) { + return false; + } + license_request.set_key_control_nonce(nonce); + LOGD("PrepareKeyRequest: nonce=%u", nonce); + license_request.set_protocol_version(video_widevine_server::sdk::VERSION_2_1); + + // License request is complete. Serialize it. + std::string serialized_license_req; + license_request.SerializeToString(&serialized_license_req); + + if (Properties::use_certificates_as_identification()) + key_request_ = serialized_license_req; + + // Derive signing and encryption keys and construct signature. + std::string license_request_signature; + if (!session_->PrepareRequest(serialized_license_req, false, + &license_request_signature)) { + signed_request->clear(); + return false; + } + + if (license_request_signature.empty()) { + LOGE("CdmLicense::PrepareKeyRequest: License request signature empty"); + signed_request->clear(); + return false; + } + + // Put serialize license request and signature together + SignedMessage signed_message; + signed_message.set_type(SignedMessage::LICENSE_REQUEST); + signed_message.set_signature(license_request_signature); + signed_message.set_msg(serialized_license_req); + + signed_message.SerializeToString(signed_request); + + *server_url = server_url_; + return true; +} + +bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal, + CdmKeyMessage* signed_request, + std::string* server_url) { + if (!initialized_) { + LOGE("CdmLicense::PrepareKeyUpdateRequest: not initialized"); + return false; + } + if (!signed_request) { + LOGE("CdmLicense::PrepareKeyUpdateRequest: No signed request provided"); + return false; + } + if (!server_url) { + LOGE("CdmLicense::PrepareKeyUpdateRequest: No server url provided"); + return false; + } + + LicenseRequest license_request; + if (is_renewal) + license_request.set_type(LicenseRequest::RENEWAL); + else + license_request.set_type(LicenseRequest::RELEASE); + + LicenseRequest_ContentIdentification_ExistingLicense* current_license = + license_request.mutable_content_id()->mutable_license(); + current_license->mutable_license_id()->CopyFrom(policy_engine_->license_id()); + + // Get/set the nonce. This value will be reflected in the Key Control Block + // of the license response. + uint32_t nonce; + if (!session_->GenerateNonce(&nonce)) { + return false; + } + license_request.set_key_control_nonce(nonce); + LOGD("PrepareKeyUpdateRequest: nonce=%u", nonce); + license_request.set_protocol_version(video_widevine_server::sdk::VERSION_2_1); + + // License request is complete. Serialize it. + std::string serialized_license_req; + license_request.SerializeToString(&serialized_license_req); + + // Construct signature. + std::string license_request_signature; + if (!session_->PrepareRenewalRequest(serialized_license_req, + &license_request_signature)) + return false; + + if (license_request_signature.empty()) { + LOGE("CdmLicense::PrepareKeyUpdateRequest: empty license request" + " signature"); + return false; + } + + // Put serialize license request and signature together + SignedMessage signed_message; + signed_message.set_type(SignedMessage::LICENSE_REQUEST); + signed_message.set_signature(license_request_signature); + signed_message.set_msg(serialized_license_req); + + signed_message.SerializeToString(signed_request); + *server_url = server_url_; + return true; +} + +CdmResponseType CdmLicense::HandleKeyResponse( + const CdmKeyResponse& license_response) { + if (!initialized_) { + LOGE("CdmLicense::HandleKeyResponse: not initialized"); + return KEY_ERROR; + } + if (license_response.empty()) { + LOGE("CdmLicense::HandleKeyResponse: empty license response"); + return KEY_ERROR; + } + + SignedMessage signed_response; + if (!signed_response.ParseFromString(license_response)) { + LOGE("CdmLicense::HandleKeyResponse: unable to parse signed license" + " response"); + return KEY_ERROR; + } + + switch (signed_response.type()) { + case SignedMessage::LICENSE: + break; + case SignedMessage::SERVICE_CERTIFICATE: + return CdmLicense::HandleServiceCertificateResponse(signed_response); + case SignedMessage::ERROR: + return HandleKeyErrorResponse(signed_response); + default: + LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d" + , signed_response.type()); + return KEY_ERROR; + } + + if (!signed_response.has_signature()) { + LOGE("CdmLicense::HandleKeyResponse: license response is not signed"); + return KEY_ERROR; + } + + License license; + if (!license.ParseFromString(signed_response.msg())) { + LOGE("CdmLicense::HandleKeyResponse: unable to parse license response"); + return KEY_ERROR; + } + + if (Properties::use_certificates_as_identification()) { + if (!signed_response.has_session_key()) { + LOGE("CdmLicense::HandleKeyResponse: no session keys present"); + return KEY_ERROR; + } + + if (!session_->GenerateDerivedKeys(key_request_, + signed_response.session_key())) + return KEY_ERROR; + } + + // Extract mac key + std::string mac_key_iv; + std::string mac_key; + if (license.policy().can_renew()) { + for (int i = 0; i < license.key_size(); ++i) { + if (license.key(i).type() == License_KeyContainer::SIGNING) { + mac_key_iv.assign(license.key(i).iv()); + + // Strip off PKCS#5 padding + mac_key.assign(license.key(i).key().data(), MAC_KEY_SIZE); + } + } + + if (mac_key_iv.size() != KEY_IV_SIZE || mac_key.size() != MAC_KEY_SIZE) { + LOGE("CdmLicense::HandleKeyResponse: mac key/iv size error" + "(key/iv size expected: %d/%d, actual: %d/%d", + MAC_KEY_SIZE, KEY_IV_SIZE, mac_key.size(), mac_key_iv.size()); + return KEY_ERROR; + } + } + + std::vector key_array = ExtractContentKeys(license); + if (!key_array.size()) { + LOGE("CdmLicense::HandleKeyResponse : No content keys."); + return KEY_ERROR; + } + + if (license.policy().has_renewal_server_url()) { + server_url_ = license.policy().renewal_server_url(); + } + + policy_engine_->SetLicense(license); + + CdmResponseType resp = session_->LoadKeys(signed_response.msg(), + signed_response.signature(), + mac_key_iv, + mac_key, + key_array.size(), + &key_array[0]); + + if (KEY_ADDED == resp) { + loaded_keys_.clear(); + for (std::vector::iterator it = key_array.begin(); + it != key_array.end(); + ++it) { + loaded_keys_.insert(it->key_id()); + } + } + return resp; +} + +CdmResponseType CdmLicense::HandleKeyUpdateResponse( + bool is_renewal, const CdmKeyResponse& license_response) { + if (!initialized_) { + LOGE("CdmLicense::HandleKeyUpdateResponse: not initialized"); + return KEY_ERROR; + } + if (license_response.empty()) { + LOGE("CdmLicense::HandleKeyUpdateResponse : Empty license response."); + return KEY_ERROR; + } + + SignedMessage signed_response; + if (!signed_response.ParseFromString(license_response)) { + LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse signed message"); + return KEY_ERROR; + } + + if (signed_response.type() == SignedMessage::ERROR) { + return HandleKeyErrorResponse(signed_response); + } + + if (!signed_response.has_signature()) { + LOGE("CdmLicense::HandleKeyUpdateResponse: signature missing"); + return KEY_ERROR; + } + + License license; + if (!license.ParseFromString(signed_response.msg())) { + LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse license" + " from signed message"); + return KEY_ERROR; + } + + if (!license.has_id()) { + LOGE("CdmLicense::HandleKeyUpdateResponse: license id not present"); + return KEY_ERROR; + } + + if (is_renewal) { + if (license.policy().has_renewal_server_url() && + license.policy().renewal_server_url().size() > 0) { + server_url_ = license.policy().renewal_server_url(); + } + } + + policy_engine_->UpdateLicense(license); + + if (!is_renewal) return KEY_ADDED; + + std::vector key_array = ExtractContentKeys(license); + + if (session_->RefreshKeys(signed_response.msg(), signed_response.signature(), + key_array.size(), &key_array[0])) { + return KEY_ADDED; + } else { + return KEY_ERROR; + } +} + +bool CdmLicense::RestoreOfflineLicense( + CdmKeyMessage& license_request, CdmKeyResponse& license_response, + CdmKeyResponse& license_renewal_response) { + + if (license_request.empty() || license_response.empty()) { + LOGE("CdmLicense::RestoreOfflineLicense: key_request or response empty: " + "%u %u", + license_request.size(), license_response.size()); + return false; + } + + SignedMessage signed_request; + if (!signed_request.ParseFromString(license_request)) { + LOGE("CdmLicense::RestoreOfflineLicense: license_request parse failed"); + return false; + } + + if (signed_request.type() != SignedMessage::LICENSE_REQUEST) { + LOGE("CdmLicense::RestoreOfflineLicense: license request type: expected = " + "%d, actual = %d", + SignedMessage::LICENSE_REQUEST, signed_request.type()); + return false; + } + + if (Properties::use_certificates_as_identification()) { + key_request_ = signed_request.msg(); + } else { + if (!session_->GenerateDerivedKeys(signed_request.msg())) return false; + } + + CdmResponseType sts = HandleKeyResponse(license_response); + + if (sts != KEY_ADDED) return false; + + if (!license_renewal_response.empty()) { + sts = HandleKeyUpdateResponse(true, license_renewal_response); + + if (sts != KEY_ADDED) return false; + } + + return true; +} + +bool CdmLicense::PrepareServiceCertificateRequest(CdmKeyMessage* signed_request, + std::string* server_url) { + if (!initialized_) { + LOGE("CdmLicense::PrepareServiceCertificateRequest: not initialized"); + return false; + } + if (!signed_request) { + LOGE("CdmLicense::PrepareServiceCertificateRequest: no signed request" + " provided"); + return false; + } + if (!server_url) { + LOGE("CdmLicense::PrepareServiceCertificateRequest: no server url" + " provided"); + return false; + } + SignedMessage signed_message; + signed_message.set_type(SignedMessage::SERVICE_CERTIFICATE_REQUEST); + signed_message.SerializeToString(signed_request); + *server_url = server_url_; + + return true; +} + +CdmResponseType CdmLicense::HandleServiceCertificateResponse( + const video_widevine_server::sdk::SignedMessage& signed_response) { + + SignedDeviceCertificate signed_service_certificate; + if (!signed_service_certificate.ParseFromString(signed_response.msg())) { + LOGE("CdmLicense::HandleServiceCertificateResponse: unable to parse" + "signed device certificate"); + return KEY_ERROR; + } + + RsaPublicKey root_ca_key; + std::string ca_public_key( + &kServiceCertificateCAPublicKey[0], + &kServiceCertificateCAPublicKey[sizeof(kServiceCertificateCAPublicKey)]); + if (!root_ca_key.Init(ca_public_key)) { + LOGE("CdmLicense::HandleServiceCertificateResponse: public key " + "initialization failed"); + return KEY_ERROR; + } + + if (!root_ca_key.VerifySignature( + signed_service_certificate.device_certificate(), + signed_service_certificate.signature())) { + LOGE("CdmLicense::HandleServiceCertificateResponse: service " + "certificate verification failed"); + return KEY_ERROR; + } + + DeviceCertificate service_certificate; + if (!service_certificate.ParseFromString( + signed_service_certificate.device_certificate())) { + LOGE("CdmLicense::HandleServiceCertificateResponse: unable to parse " + "retrieved service certificate"); + return KEY_ERROR; + } + + if (service_certificate.type() != + video_widevine_server::sdk::DeviceCertificate_CertificateType_SERVICE) { + LOGE("CdmLicense::HandleServiceCertificateResponse: certificate not of type" + " service, %d", + service_certificate.type()); + return KEY_ERROR; + } + + service_certificate_ = signed_service_certificate.device_certificate(); + return NEED_KEY; +} + +CdmResponseType CdmLicense::HandleKeyErrorResponse( + const SignedMessage& signed_message) { + + LicenseError license_error; + if (!license_error.ParseFromString(signed_message.msg())) { + LOGE("CdmLicense::HandleKeyErrorResponse: Unable to parse license error"); + return KEY_ERROR; + } + + switch (license_error.error_code()) { + case LicenseError::INVALID_DEVICE_CERTIFICATE: + return NEED_PROVISIONING; + case LicenseError::REVOKED_DEVICE_CERTIFICATE: + return DEVICE_REVOKED; + case LicenseError::SERVICE_UNAVAILABLE: + default: + LOGW("CdmLicense::HandleKeyErrorResponse: Unknwon error type = %d", + license_error.error_code()); + return KEY_ERROR; + } +} + +bool CdmLicense::IsKeyLoaded(const KeyId& key_id) { + return loaded_keys_.find(key_id) != loaded_keys_.end(); +} + +} // namespace wvcdm diff --git a/core/src/license_protocol.proto b/core/src/license_protocol.proto new file mode 100644 index 00000000..110460c5 --- /dev/null +++ b/core/src/license_protocol.proto @@ -0,0 +1,469 @@ +// ---------------------------------------------------------------------------- +// license_protocol.proto +// ---------------------------------------------------------------------------- +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// Definitions of the protocol buffer messages used in the Widevine license +// exchange protocol. + +syntax = "proto2"; + +package video_widevine_server.sdk; + +// need this if we are using libprotobuf-cpp-2.3.0-lite +option optimize_for = LITE_RUNTIME; + +enum LicenseType { + STREAMING = 1; + OFFLINE = 2; +} + +// LicenseIdentification is propagated from LicenseRequest to License, +// incrementing version with each iteration. +message LicenseIdentification { + optional bytes request_id = 1; + optional bytes session_id = 2; + optional bytes purchase_id = 3; + optional LicenseType type = 4; + optional int32 version = 5; +} + +message License { + message Policy { + // Indicates that playback of the content is allowed. + optional bool can_play = 1 [default = false]; + + // Indicates that the license may be persisted to non-volatile + // storage for offline use. + optional bool can_persist = 2 [default = false]; + + // Indicates that renewal of this license is allowed. + optional bool can_renew = 3 [default = false]; + + // For the |*duration*| fields, playback must halt when + // license_start_time (seconds since the epoch (UTC)) + + // license_duration_seconds is exceeded. A value of 0 + // indicates that there is no limit to the duration. + + // Indicates the rental window. + optional int64 rental_duration_seconds = 4 [default = 0]; + + // Indicates the viewing window, once playback has begun. + optional int64 playback_duration_seconds = 5 [default = 0]; + + // Indicates the time window for this specific license. + optional int64 license_duration_seconds = 6 [default = 0]; + + // The |renewal*| fields only apply if |can_renew| is true. + + // The window of time, in which playback is allowed to continue while + // renewal is attempted, yet unsuccessful due to backend problems with + // the license server. + optional int64 renewal_recovery_duration_seconds = 7 [default = 0]; + + // All renewal requests for this license shall be directed to the + // specified URL. + optional string renewal_server_url = 8; + + // How many seconds after license_start_time, before renewal is first + // attempted. + optional int64 renewal_delay_seconds = 9 [default = 0]; + + // Specifies the delay in seconds between subsequent license + // renewal requests, in case of failure. + optional int64 renewal_retry_interval_seconds = 10 [default = 0]; + + // Indicates that the license shall be sent for renewal when usage is + // started. + optional bool renew_with_usage = 11 [default = false]; + } + + message KeyContainer { + enum KeyType { + // Exactly one key of this type must appear. + SIGNING = 1; + CONTENT = 2; + KEY_CONTROL = 3; + OPERATOR_SESSION = 4; + } + + // The SecurityLevel enumeration allows the server to communicate the level + // of robustness required by the client, in order to use the key. + enum SecurityLevel { + // Software-based whitebox crypto is required. + SW_SECURE_CRYPTO = 1; + + // Software crypto and an obfuscated decoder is required. + SW_SECURE_DECODE = 2; + + // The key material and crypto operations must be performed within a + // hardware backed trusted execution environment. + HW_SECURE_CRYPTO = 3; + + // The crypto and decoding of content must be performed within a hardware + // backed trusted execution environment. + HW_SECURE_DECODE = 4; + + // The crypto, decoding and all handling of the media (compressed and + // uncompressed) must be handled within a hardware backed trusted + // execution environment. + HW_SECURE_ALL = 5; + } + + message KeyControl { + // 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. + optional bytes key_control_block = 1; + optional bytes iv = 2; + } + + message OutputProtection { + // Indicates whether HDCP is required on digital outputs, and which + // version should be used. + enum HDCP { + HDCP_NONE = 0; + HDCP_V1 = 1; + HDCP_V2 = 2; + } + optional HDCP hdcp = 1 [default = HDCP_NONE]; + + // Indicate the CGMS setting to be inserted on analog output. + enum CGMS { + CGMS_NONE = 42; + COPY_FREE = 0; + COPY_ONCE = 2; + COPY_NEVER = 3; + } + optional CGMS cgms_flags = 2 [default = CGMS_NONE]; + } + + message OperatorSessionKeyPermissions { + // Permissions/key usage flags for operator service keys + // (type = OPERATOR_SESSION). + optional bool allow_encrypt = 1 [default = false]; + optional bool allow_decrypt = 2 [default = false]; + optional bool allow_sign = 3 [default = false]; + optional bool allow_signature_verify = 4 [default = false]; + } + + optional bytes id = 1; + optional bytes iv = 2; + optional bytes key = 3; + optional KeyType type = 4; + optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO]; + optional OutputProtection required_protection = 6; + optional OutputProtection requested_protection = 7; + optional KeyControl key_control = 8; + optional OperatorSessionKeyPermissions operator_session_key_permissions = 9; + } + + optional LicenseIdentification id = 1; + optional Policy policy = 2; + repeated KeyContainer key = 3; + optional int64 license_start_time = 4; +} + +enum ProtocolVersion { + VERSION_2_0 = 20; + VERSION_2_1 = 21; +} + +message LicenseRequest { + message ContentIdentification { + message CENC { + repeated bytes pssh = 1; + optional LicenseType license_type = 2; + optional bytes request_id = 3; // Opaque, client-specified. + } + + message WebM { + optional bytes header = 1; + optional LicenseType license_type = 2; + optional bytes request_id = 3; // Opaque, client-specified. + } + + message ExistingLicense { + optional LicenseIdentification license_id = 1; + optional int64 seconds_since_started = 2; + } + + // Exactly one of these must be present. + optional CENC cenc_id = 1; + optional WebM webm_id = 2; + optional ExistingLicense license = 3; + } + + enum RequestType { + NEW = 1; + RENEWAL = 2; + RELEASE = 3; + } + + // The client_id provides information authenticating the calling device. It + // contains the Widevine keybox token that was installed on the device at the + // factory. This field or encrypted_client_id below is required for a valid + // license request, but both should never be present in the same request. + optional ClientIdentification client_id = 1; + optional ContentIdentification content_id = 2; + optional RequestType type = 3; + optional int64 request_time = 4; + // Old-style decimal-encoded string key control nonce. + optional bytes key_control_nonce_deprecated = 5; + optional ProtocolVersion protocol_version = 6 [default = VERSION_2_0]; + // New-style uint32 key control nonce, please use instead of + // key_control_nonce_deprecated. + optional uint32 key_control_nonce = 7; + // Encrypted ClientIdentification message, used for privacy purposes. + optional EncryptedClientIdentification encrypted_client_id = 8; +} + +message LicenseError { + enum Error { + // The device credentials are invalid. The device must re-provision. + INVALID_DEVICE_CERTIFICATE = 1; + // The device credentials have been revoked. Re-provisioning is not + // possible. + REVOKED_DEVICE_CERTIFICATE = 2; + // The service is currently unavailable due to the backend being down + // or similar circumstances. + SERVICE_UNAVAILABLE = 3; + } + optional Error error_code = 1; +} + +message SignedMessage { + enum MessageType { + LICENSE_REQUEST = 1; + LICENSE = 2; + ERROR = 3; + SERVICE_CERTIFICATE_REQUEST = 4; + SERVICE_CERTIFICATE = 5; + } + + optional MessageType type = 1; + optional bytes msg = 2; + optional bytes signature = 3; + optional bytes session_key = 4; +} + +// This message is used to pass optional data on initial license issuance. +message SessionInit { + optional bytes session_id = 1; + optional bytes purchase_id = 2; + // master_signing_key should be 128 bits in length. + optional bytes master_signing_key = 3; + // signing_key should be 512 bits in length to be split into two + // (server || client) HMAC-SHA256 keys. + optional bytes signing_key = 4; + optional int64 license_start_time = 5; +} + +// This message is used by the server to preserve and restore session state. +message SessionState { + optional LicenseIdentification license_id = 1; + optional bytes signing_key = 2; + optional uint32 keybox_system_id = 3; +} + +// ---------------------------------------------------------------------------- +// certificate_provisioning.proto +// ---------------------------------------------------------------------------- +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// Public protocol buffer definitions for Widevine Device Certificate +// Provisioning protocol. + +// Provisioning request sent by client devices to provisioning service. +message ProvisioningRequest { + // Device root of trust and other client identification. Required. + optional ClientIdentification client_id = 1; + // Nonce value used to prevent replay attacks. Required. + optional bytes nonce = 2; +} + +// Provisioning response sent by the provisioning server to client devices. +message ProvisioningResponse { + // AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded. + // Required. + optional bytes device_rsa_key = 1; + // Initialization vector used to encrypt device_rsa_key. Required. + optional bytes device_rsa_key_iv = 2; + // Serialized SignedDeviceCertificate. Required. + optional bytes device_certificate = 3; + // Nonce value matching nonce in ProvisioningRequest. Required. + optional bytes nonce = 4; +} + +// Serialized ProvisioningRequest or ProvisioningResponse signed with +// The message authentication key. +message SignedProvisioningMessage { + // Serialized ProvisioningRequest or ProvisioningResponse. Required. + optional bytes message = 1; + // HMAC-SHA256 signature of message. Required. + optional bytes signature = 2; +} + +// ---------------------------------------------------------------------------- +// client_identification.proto +// ---------------------------------------------------------------------------- +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// ClientIdentification messages used by provisioning and license protocols. + +// ClientIdentification message used to authenticate the client device. +message ClientIdentification { + enum TokenType { + KEYBOX = 0; + DEVICE_CERTIFICATE = 1; + } + + message NameValue { + optional string name = 1; + optional string value = 2; + } + + // Type of factory-provisioned device root of trust. Optional. + optional TokenType type = 1 [default = KEYBOX]; + // Factory-provisioned device root of trust. Required. + optional bytes token = 2; + // Optional client information name/value pairs. + repeated NameValue client_info = 3; +} + +// EncryptedClientIdentification message used to hold ClientIdentification +// messages encrypted for privacy purposes. +message EncryptedClientIdentification { + // Service ID for which the ClientIdentifcation is encrypted (owner of service + // certificate). + optional string service_id = 1; + // Serial number for the service certificate for which ClientIdentification is + // encrypted. + optional string service_certificate_serial_number = 2; + // Serialized ClientIdentification message, encrypted with the privacy key + // using AES-128-CBC with PKCS#5 padding. + optional bytes encrypted_client_id = 3; + // Initialization vector needed to decrypt encrypted_client_id. + optional bytes encrypted_client_id_iv = 4; + // AES-128 privacy key, encrytped with the service public public key using + // RSA-OAEP. + optional bytes encrypted_privacy_key = 5; +}; + +// ---------------------------------------------------------------------------- +// device_certificate.proto +// ---------------------------------------------------------------------------- +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// Device certificate and certificate status list format definitions. + +// Certificate definition for user devices, intermediate, service, and root +// certificates. +message DeviceCertificate { + enum CertificateType { + ROOT = 0; + INTERMEDIATE = 1; + USER_DEVICE = 2; + SERVICE = 3; + } + + // Type of certificate. Required. + optional CertificateType 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; + // 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 + // user device certificates. + optional uint32 system_id = 5; + // True if the certificate corresponds to a test (non production) device or + // service. Optional. + optional bool test_device = 6 [default = false]; + // Service identifier (web origin) for the service which owns the certificate. + // Required for service certificates. + optional string service_id = 7; +} + +// DeviceCertificate signed with intermediate or root certificate private key. +message SignedDeviceCertificate { + // Serialized DeviceCertificate. Required. + optional bytes device_certificate = 1; + // Signature of device_certificate. Signed with root or intermediate + // certificate private key using RSASSA-PSS. Required. + optional bytes signature = 2; + // Intermediate signing certificate. Present only for user device + // certificates. All others signed with root certificate private key. + optional SignedDeviceCertificate signer = 3; +} + +// Contains device model information for a provisioned device. +message ProvisionedDeviceInfo { + enum WvSecurityLevel { + // Defined in "WV Modular DRM Security Integration Guide for + // Common Encryption (CENC)" + LEVEL_UNSPECIFIED = 0; + LEVEL_1 = 1; + LEVEL_2 = 2; + LEVEL_3 = 3; + } + + // Widevine system ID for the device. Mandatory. + optional uint32 system_id = 1; + // Name of system-on-a-chip. Optional. + optional string soc = 2; + // Name of manufacturer. Optional. + optional string manufacturer = 3; + // Manufacturer's model name. Matches "brand" in device metadata. Optional. + optional string model = 4; + // Type of device (Phone, Tablet, TV, etc). + optional string device_type = 5; + // 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]; +} + +// Contains the status of the root or an intermediate DeviceCertificate. +message DeviceCertificateStatus { + enum CertificateStatus { + VALID = 0; + REVOKED = 1; + }; + + // Serial number of the DeviceCertificate to which this message refers. + // Required. + optional bytes serial_number = 1; + // Status of the certificate. Optional. + optional CertificateStatus status = 2 [default = VALID]; + // Device model information about the device to which the certificate + // corresponds. Required. + optional ProvisionedDeviceInfo device_info = 4; +} + +// List of DeviceCertificateStatus. Used to propagate certificate revocation and +// update list. +message DeviceCertificateStatusList { + // POSIX time, in seconds, when the list was created. Required. + optional uint32 creation_time_seconds = 1; + // DeviceCertificateStatus for each certifificate. + 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; +} diff --git a/core/src/oemcrypto_adapter_static.cpp b/core/src/oemcrypto_adapter_static.cpp new file mode 100644 index 00000000..77dabd6e --- /dev/null +++ b/core/src/oemcrypto_adapter_static.cpp @@ -0,0 +1,46 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// 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 +// compile time. +// + +#include "OEMCryptoCENC.h" +#include "oemcrypto_adapter.h" + +namespace wvcdm { + +OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session, + SecurityLevel level) { + return ::OEMCrypto_OpenSession(session); +} + +OEMCryptoResult OEMCrypto_IsKeyboxValid(SecurityLevel level) { + return ::OEMCrypto_IsKeyboxValid(); +} + +OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength, + SecurityLevel level) { + return ::OEMCrypto_GetDeviceID(deviceID, idLength); +} + +OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength, + SecurityLevel level) { + return ::OEMCrypto_GetKeyData(keyData, keyDataLength); +} + +OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox, + size_t keyBoxLength, + SecurityLevel level) { + return ::OEMCrypto_InstallKeybox(keybox, keyBoxLength); +} + +uint32_t OEMCrypto_APIVersion(SecurityLevel level) { + return ::OEMCrypto_APIVersion(); +} + +const char* OEMCrypto_SecurityLevel(SecurityLevel level) { + return ::OEMCrypto_SecurityLevel(); +} + +}; // namespace wvcdm diff --git a/core/src/policy_engine.cpp b/core/src/policy_engine.cpp new file mode 100644 index 00000000..5d76079e --- /dev/null +++ b/core/src/policy_engine.cpp @@ -0,0 +1,287 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "policy_engine.h" + +#include +#include +#include +#include +#include + +#include "log.h" +#include "properties.h" +#include "string_conversions.h" +#include "clock.h" +#include "wv_cdm_constants.h" + +namespace wvcdm { + +PolicyEngine::PolicyEngine() { + Init(new Clock()); +} + +PolicyEngine::PolicyEngine(Clock* clock) { + Init(clock); +} + +PolicyEngine::~PolicyEngine() { + if (clock_) + delete clock_; +} + +void PolicyEngine::Init(Clock* clock) { + license_state_ = kLicenseStateInitial; + can_decrypt_ = false; + license_start_time_ = 0; + license_received_time_ = 0; + playback_start_time_ = 0; + next_renewal_time_ = 0; + policy_max_duration_seconds_ = 0; + clock_ = clock; +} + +void PolicyEngine::OnTimerEvent(bool* event_occurred, CdmEventType* event) { + *event_occurred = false; + int64_t current_time = clock_->GetCurrentTime(); + + // License expiration trumps all. + if ((IsLicenseDurationExpired(current_time) || + IsPlaybackDurationExpired(current_time)) && + license_state_ != kLicenseStateExpired) { + license_state_ = kLicenseStateExpired; + can_decrypt_ = false; + *event = LICENSE_EXPIRED_EVENT; + *event_occurred = true; + return; + } + + bool renewal_needed = false; + + // Test to determine if renewal should be attempted. + switch (license_state_) { + case kLicenseStateInitialPendingUsage: + case kLicenseStateCanPlay: { + if (IsRenewalDelayExpired(current_time)) + renewal_needed = true; + break; + } + + case kLicenseStateNeedRenewal: { + renewal_needed = true; + break; + } + + case kLicenseStateWaitingLicenseUpdate: { + if (IsRenewalRetryIntervalExpired(current_time)) + renewal_needed = true; + break; + } + + case kLicenseStateInitial: + case kLicenseStateExpired: { + break; + } + + default: { + license_state_ = kLicenseStateExpired; + can_decrypt_ = false; + break; + } + } + + if (renewal_needed) { + UpdateRenewalRequest(current_time); + *event = LICENSE_RENEWAL_NEEDED_EVENT; + *event_occurred = true; + } +} + +void PolicyEngine::SetLicense( + const video_widevine_server::sdk::License& license) { + license_id_.Clear(); + license_id_.CopyFrom(license.id()); + policy_.Clear(); + UpdateLicense(license); +} + +void PolicyEngine::UpdateLicense( + const video_widevine_server::sdk::License& license) { + if (!license.has_policy()) + return; + + if (kLicenseStateExpired == license_state_) { + LOGD("PolicyEngine::UpdateLicense: updating an expired license"); + } + + policy_.MergeFrom(license.policy()); + + if (!policy_.can_play()) { + license_state_ = kLicenseStateExpired; + return; + } + + // some basic license validation + if (license_state_ == kLicenseStateInitial) { + // license start time needs to be present in the initial response + if (!license.has_license_start_time()) + return; + } + else { + // if renewal, discard license if version has not been updated + if (license.id().version() > license_id_.version()) + license_id_.CopyFrom(license.id()); + else + return; + } + + // Update time information + int64_t current_time = clock_->GetCurrentTime(); + if (license.has_license_start_time()) + license_start_time_ = license.license_start_time(); + license_received_time_ = current_time; + next_renewal_time_ = current_time + + policy_.renewal_delay_seconds(); + + // Calculate policy_max_duration_seconds_. policy_max_duration_seconds_ + // will be set to the minimum of the following policies : + // rental_duration_seconds and license_duration_seconds. + // The value is used to determine when the license expires. + policy_max_duration_seconds_ = 0; + + if (policy_.has_rental_duration_seconds()) + policy_max_duration_seconds_ = policy_.rental_duration_seconds(); + + if ((policy_.license_duration_seconds() > 0) && + ((policy_.license_duration_seconds() < + policy_max_duration_seconds_) || + policy_max_duration_seconds_ == 0)) { + policy_max_duration_seconds_ = policy_.license_duration_seconds(); + } + + if (Properties::begin_license_usage_when_received()) + playback_start_time_ = current_time; + + // Update state + if (Properties::begin_license_usage_when_received()) { + if (policy_.renew_with_usage()) { + license_state_ = kLicenseStateNeedRenewal; + } + else { + license_state_ = kLicenseStateCanPlay; + can_decrypt_ = true; + } + } + else { + if (license_state_ == kLicenseStateInitial) { + license_state_ = kLicenseStateInitialPendingUsage; + } + else { + license_state_ = kLicenseStateCanPlay; + can_decrypt_ = true; + } + } +} + +void PolicyEngine::BeginDecryption() { + if ((playback_start_time_ == 0) && + (!Properties::begin_license_usage_when_received())) { + switch (license_state_) { + case kLicenseStateInitialPendingUsage: + case kLicenseStateNeedRenewal: + case kLicenseStateWaitingLicenseUpdate: + playback_start_time_ = clock_->GetCurrentTime(); + + if (policy_.renew_with_usage()) { + license_state_ = kLicenseStateNeedRenewal; + } + else { + license_state_ = kLicenseStateCanPlay; + can_decrypt_ = true; + } + break; + case kLicenseStateCanPlay: + case kLicenseStateInitial: + case kLicenseStateExpired: + default: + break; + } + } +} + +CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) { + std::stringstream ss; + int64_t current_time = clock_->GetCurrentTime(); + + if (license_state_ == kLicenseStateInitial) + return UNKNOWN_ERROR; + + (*key_info)[QUERY_KEY_LICENSE_TYPE] = + license_id_.type() == video_widevine_server::sdk::STREAMING ? + QUERY_VALUE_STREAMING : QUERY_VALUE_OFFLINE; + (*key_info)[QUERY_KEY_PLAY_ALLOWED] = policy_.can_play() ? + QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + (*key_info)[QUERY_KEY_PERSIST_ALLOWED] = policy_.can_persist() ? + QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + (*key_info)[QUERY_KEY_RENEW_ALLOWED] = policy_.can_renew() ? + QUERY_VALUE_TRUE : QUERY_VALUE_FALSE; + int64_t remaining_time = policy_max_duration_seconds_ + + license_received_time_ - current_time; + if (remaining_time < 0) + remaining_time = 0; + ss << remaining_time; + (*key_info)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str(); + remaining_time = policy_.playback_duration_seconds() + playback_start_time_ - + current_time; + if (remaining_time < 0) + remaining_time = 0; + ss << remaining_time; + (*key_info)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str(); + (*key_info)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url(); + + return NO_ERROR; +} + +void PolicyEngine::UpdateRenewalRequest(int64_t current_time) { + license_state_ = kLicenseStateWaitingLicenseUpdate; + next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds(); +} + +// For the policy time fields checked in the following methods, a value of 0 +// indicates that there is no limit to the duration. These methods +// will always return false if the value is 0. +bool PolicyEngine::IsLicenseDurationExpired(int64_t current_time) { + return policy_max_duration_seconds_ && + license_received_time_ + policy_max_duration_seconds_ <= + current_time; +} + +bool PolicyEngine::IsPlaybackDurationExpired(int64_t current_time) { + return (policy_.playback_duration_seconds() > 0) && + playback_start_time_ && + playback_start_time_ + policy_.playback_duration_seconds() <= + current_time; +} + +bool PolicyEngine::IsRenewalDelayExpired(int64_t current_time) { + return policy_.can_renew() && + (policy_.renewal_delay_seconds() > 0) && + license_received_time_ + policy_.renewal_delay_seconds() <= + current_time; +} + +bool PolicyEngine::IsRenewalRecoveryDurationExpired( + int64_t current_time) { +// NOTE: Renewal Recovery Duration is currently not used. + return (policy_.renewal_recovery_duration_seconds() > 0) && + license_received_time_ + policy_.renewal_recovery_duration_seconds() <= + current_time; +} + +bool PolicyEngine::IsRenewalRetryIntervalExpired( + int64_t current_time) { + return policy_.can_renew() && + (policy_.renewal_retry_interval_seconds() > 0) && + next_renewal_time_ <= current_time; +} + +} // wvcdm diff --git a/core/src/privacy_crypto.cpp b/core/src/privacy_crypto.cpp new file mode 100644 index 00000000..422e63dd --- /dev/null +++ b/core/src/privacy_crypto.cpp @@ -0,0 +1,217 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Description: +// Definition of classes representing RSA public keys used +// for signature verification and encryption and decryption. +// + +#include "privacy_crypto.h" + +#include "log.h" +#include "openssl/aes.h" +#include "openssl/bio.h" +#include "openssl/err.h" +#include "openssl/pem.h" +#include "openssl/sha.h" + +namespace { +const int kPssSaltLength = 20; +const int kRsaPkcs1OaepPaddingLength = 41; +} // namespace + +namespace wvcdm { + +bool AesCbcKey::Init(const std::string& key) { + if (key.empty()) { + LOGE("AesCbcKey::Init: no key provided"); + return false; + } + if (key.size() != AES_BLOCK_SIZE) { + LOGE("AesCbcKey::Init: unexpected key size: %d", key.size()); + return false; + } + + EVP_CIPHER_CTX_init(&ctx_); + if (EVP_EncryptInit(&ctx_, EVP_aes_128_cbc(), + reinterpret_cast(&key[0]), NULL) == 0) { + LOGE("AesCbcKey::Init: AES CBC key setup failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return false; + } + initialized_ = true; + return true; +} + +bool AesCbcKey::Encrypt(const std::string& in, std::string* out, + std::string* iv) { + if (in.empty()) { + LOGE("AesCbcKey::Encrypt: no cleartext provided"); + return false; + } + if (iv == NULL) { + LOGE("AesCbcKey::Encrypt: initialization vector destination not provided"); + return false; + } + if (iv->size() != AES_BLOCK_SIZE) { + LOGE("AesCbcKey::Encrypt: invalid iv size: %d", iv->size()); + return false; + } + if (out == NULL) { + LOGE("AesCbcKey::Encrypt: crypttext destination not provided"); + return false; + } + if (!initialized_) { + LOGE("AesCbcKey::Encrypt: AES key not initialized"); + return false; + } + + if (EVP_EncryptInit(&ctx_, NULL, NULL, + reinterpret_cast(iv->data())) == 0) { + LOGE("AesCbcKey::Encrypt: AES CBC iv setup failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + out->resize(in.size() + AES_BLOCK_SIZE); + int out_length = out->size(); + if (EVP_EncryptUpdate( + &ctx_, reinterpret_cast(&(*out)[0]), &out_length, + reinterpret_cast(const_cast(in.data())), + in.size()) == 0) { + LOGE("AesCbcKey::Encrypt: encryption failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + int padding = 0; + if (EVP_EncryptFinal(&ctx_, reinterpret_cast(&(*out)[out_length]), + &padding) == 0) { + LOGE("AesCbcKey::Encrypt: PKCS7 padding failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + out->resize(out_length + padding); + return true; +} + +RsaPublicKey::~RsaPublicKey() { + if (key_ != NULL) { + RSA_free(key_); + } +} + +bool RsaPublicKey::Init(const std::string& serialized_key) { + + if (serialized_key.empty()) { + LOGE("RsaPublicKey::Init: no serialized key provided"); + return false; + } + + BIO* bio = BIO_new_mem_buf(const_cast(serialized_key.data()), + serialized_key.size()); + if (bio == NULL) { + LOGE("RsaPublicKey::Init: BIO_new_mem_buf returned NULL"); + return false; + } + key_ = d2i_RSAPublicKey_bio(bio, NULL); + BIO_free(bio); + + if (key_ == NULL) { + LOGE("RsaPublicKey::Init: RSA key deserialization failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + return true; +} + +bool RsaPublicKey::Encrypt(const std::string& clear_message, + std::string* encrypted_message) { + if (clear_message.empty()) { + LOGE("RsaPublicKey::Encrypt: message to be encrypted is empty"); + return false; + } + if (encrypted_message == NULL) { + LOGE("RsaPublicKey::Encrypt: no encrypt message buffer provided"); + return false; + } + if (key_ == NULL) { + LOGE("RsaPublicKey::Encrypt: RSA key not initialized"); + return false; + } + + int rsa_size = RSA_size(key_); + if (static_cast(clear_message.size()) > + rsa_size - kRsaPkcs1OaepPaddingLength) { + LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d", + " max allowed %d)", clear_message.size(), + rsa_size - kRsaPkcs1OaepPaddingLength); + return false; + } + encrypted_message->assign(rsa_size, 0); + if (RSA_public_encrypt( + clear_message.size(), + const_cast( + reinterpret_cast(clear_message.data())), + reinterpret_cast(&(*encrypted_message)[0]), key_, + RSA_PKCS1_OAEP_PADDING) != rsa_size) { + LOGE("RsaPublicKey::Encrypt: encrypt failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return false; + } + return true; +} + +bool RsaPublicKey::VerifySignature(const std::string& message, + const std::string& signature) { + if (key_ == NULL) { + LOGE("RsaPublicKey::VerifySignature: RSA key not initialized"); + return false; + } + if (message.empty()) { + LOGE("RsaPublicKey::VerifySignature: signed message is empty"); + return false; + } + + int rsa_size = RSA_size(key_); + if (static_cast(signature.size()) != rsa_size) { + LOGE( + "RsaPublicKey::VerifySignature: message signature is of the wrong " + "size (expected %d, actual %d)", + rsa_size, signature.size()); + return false; + } + // Decrypt the signature. + std::string padded_digest(signature.size(), 0); + if (RSA_public_decrypt( + signature.size(), + const_cast( + reinterpret_cast(signature.data())), + reinterpret_cast(&padded_digest[0]), key_, + RSA_NO_PADDING) != rsa_size) { + LOGE("RsaPublicKey::VerifySignature: RSA public decrypt failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + // Hash the message using SHA1. + std::string message_digest(SHA_DIGEST_LENGTH, 0); + SHA1(reinterpret_cast(message.data()), message.size(), + reinterpret_cast(&message_digest[0])); + + // Verify PSS padding. + if (RSA_verify_PKCS1_PSS( + key_, reinterpret_cast(message_digest.data()), + EVP_sha1(), + reinterpret_cast(padded_digest.data()), + kPssSaltLength) == 0) { + LOGE("RsaPublicKey::VerifySignature: RSA verify failure: %s", + ERR_error_string(ERR_get_error(), NULL)); + return false; + } + + return true; +} + +} // namespace wvcdm diff --git a/core/src/properties.cpp b/core/src/properties.cpp new file mode 100644 index 00000000..60ffe4c3 --- /dev/null +++ b/core/src/properties.cpp @@ -0,0 +1,122 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "log.h" +#include "properties_configuration.h" +#include "wv_cdm_constants.h" + +namespace { +const char* kSecurityLevelDirs[] = {"L1/", "L3/"}; +} // namespace + +namespace wvcdm { +bool Properties::begin_license_usage_when_received_; +bool Properties::require_explicit_renew_request_; +bool Properties::oem_crypto_use_secure_buffers_; +bool Properties::oem_crypto_use_fifo_; +bool Properties::oem_crypto_use_userspace_buffers_; +bool Properties::use_certificates_as_identification_; +bool Properties::extract_pssh_data_; +bool Properties::decrypt_with_empty_session_support_; +bool Properties::security_level_path_backward_compatibility_support_; +scoped_ptr Properties::session_property_set_; + +void Properties::Init() { + begin_license_usage_when_received_ = kPropertyBeginLicenseUsageWhenReceived; + require_explicit_renew_request_ = kPropertyRequireExplicitRenewRequest; + oem_crypto_use_secure_buffers_ = kPropertyOemCryptoUseSecureBuffers; + oem_crypto_use_fifo_ = kPropertyOemCryptoUseFifo; + oem_crypto_use_userspace_buffers_ = kPropertyOemCryptoUseUserSpaceBuffers; + use_certificates_as_identification_ = + kPropertyUseCertificatesAsIdentification; + extract_pssh_data_ = kExtractPsshData; + decrypt_with_empty_session_support_ = kDecryptWithEmptySessionSupport; + security_level_path_backward_compatibility_support_ = + kSecurityLevelPathBackwardCompatibilitySupport; + session_property_set_.reset(new CdmClientPropertySetMap()); +} + +bool Properties::AddSessionPropertySet( + const CdmSessionId& session_id, const CdmClientPropertySet* property_set) { + if (NULL == session_property_set_.get()) { + return false; + } + std::pair result = + session_property_set_->insert( + std::pair( + session_id, property_set)); + return result.second; +} + +bool Properties::RemoveSessionPropertySet(const CdmSessionId& session_id) { + if (NULL == session_property_set_.get()) { + return false; + } + return (1 == session_property_set_->erase(session_id)); +} + +const CdmClientPropertySet* Properties::GetCdmClientPropertySet( + const CdmSessionId& session_id) { + if (NULL != session_property_set_.get()) { + CdmClientPropertySetMap::const_iterator it = + session_property_set_->find(session_id); + if (it != session_property_set_->end()) { + return it->second; + } + } + return NULL; +} + +const std::string Properties::GetSecurityLevel(const CdmSessionId& session_id) { + const CdmClientPropertySet* property_set = + GetCdmClientPropertySet(session_id); + if (NULL == property_set) { + LOGE("Properties::GetSecurityLevel: cannot find property set for %s", + session_id.c_str()); + return ""; + } + return property_set->security_level(); +} + +const std::vector Properties::GetServiceCertificate( + const CdmSessionId& session_id) { + const CdmClientPropertySet* property_set = + GetCdmClientPropertySet(session_id); + if (NULL == property_set) { + LOGE("Properties::GetServiceCertificate: cannot find property set for %s", + session_id.c_str()); + return std::vector(); + } + return property_set->service_certificate(); +} + +bool Properties::UsePrivacyMode(const CdmSessionId& session_id) { + const CdmClientPropertySet* property_set = + GetCdmClientPropertySet(session_id); + if (NULL == property_set) { + LOGE("Properties::UsePrivacyMode: cannot find property set for %s", + session_id.c_str()); + return false; + } + return property_set->use_privacy_mode(); +} + +uint32_t Properties::GetSessionSharingId(const CdmSessionId& session_id) { + const CdmClientPropertySet* property_set = + GetCdmClientPropertySet(session_id); + if (NULL == property_set) { + LOGE("Properties::GetSessionSharingId: cannot find property set for %s", + session_id.c_str()); + return 0; + } + return property_set->session_sharing_id(); +} + +bool Properties::GetSecurityLevelDirectories(std::vector* dirs) { + dirs->resize(sizeof(kSecurityLevelDirs) / sizeof(const char*)); + for (size_t i = 0; i < dirs->size(); ++i) { + (*dirs)[i] = kSecurityLevelDirs[i]; + } + return true; +} + +} // namespace wvcdm diff --git a/core/src/string_conversions.cpp b/core/src/string_conversions.cpp new file mode 100644 index 00000000..9a3eff77 --- /dev/null +++ b/core/src/string_conversions.cpp @@ -0,0 +1,160 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "string_conversions.h" + +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "modp_b64w.h" + +namespace wvcdm { + +static bool CharToDigit(char ch, unsigned char* digit) { + if (ch >= '0' && ch <= '9') { + *digit = ch - '0'; + } else { + ch = tolower(ch); + if ((ch >= 'a') && (ch <= 'f')) { + *digit = ch - 'a' + 10; + } else { + return false; + } + } + return true; +} + +// converts an ascii hex string(2 bytes per digit) into a decimal byte string +std::vector a2b_hex(const std::string& byte) { + std::vector array; + unsigned int count = byte.size(); + if (count == 0 || (count % 2) != 0) { + LOGE("Invalid input size %u for string %s", count, byte.c_str()); + return array; + } + + for (unsigned int i = 0; i < count / 2; ++i) { + unsigned char msb = 0; // most significant 4 bits + unsigned char lsb = 0; // least significant 4 bits + if (!CharToDigit(byte[i * 2], &msb) || + !CharToDigit(byte[i * 2 + 1], &lsb)) { + LOGE("Invalid hex value %c%c at index %d", byte[i*2], byte[i*2+1], i); + return array; + } + array.push_back((msb << 4) | lsb); + } + return array; +} + +std::string a2bs_hex(const std::string& byte) { + std::vector array = a2b_hex(byte); + return std::string(array.begin(), array.end()); +} + +std::string b2a_hex(const std::vector& byte) { + return HexEncode(&byte[0], byte.size()); +} + +std::string b2a_hex(const std::string& byte) { + return HexEncode(reinterpret_cast(byte.data()), + byte.length()); +} + +// Filename-friendly base64 encoding (RFC4648), commonly referred to +// as Base64WebSafeEncode. +// This is the encoding required to interface with the provisioning +// server's Apiary interface as well as for certain license server +// transactions. It is also used for logging certain strings. +// The difference between web safe encoding vs regular encoding is that +// the web safe version replaces '+' with '-' and '/' with '_'. +std::string Base64SafeEncode(const std::vector& bin_input) { + if (bin_input.empty()) { + return std::string(); + } + + int in_size = bin_input.size(); + std::string b64_output(modp_b64w_encode_len(in_size), 0); + + int out_size = modp_b64w_encode(&b64_output[0], + reinterpret_cast(&bin_input[0]), + in_size); + if (out_size == -1) { + LOGE("Base64SafeEncode failed"); + return std::string(); + } + + b64_output.resize(out_size); + return b64_output; +} + +std::string Base64SafeEncodeNoPad(const std::vector& bin_input) { + std::string b64_output = Base64SafeEncode(bin_input); + // Output size: ceiling [ bin_input.size() * 4 / 3 ]. + b64_output.resize((bin_input.size() * 4 + 2) / 3); + return b64_output; +} + +// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred +// as Base64WebSafeDecode. +std::vector Base64SafeDecode(const std::string& b64_input) { + if (b64_input.empty()) { + return std::vector(); + } + + int in_size = b64_input.size(); + std::vector bin_output(modp_b64w_decode_len(in_size), 0); + int out_size = modp_b64w_decode(reinterpret_cast(&bin_output[0]), + b64_input.data(), + in_size); + if (out_size == -1) { + LOGE("Base64SafeDecode failed"); + return std::vector(0); + } + + bin_output.resize(out_size); + return bin_output; +} + +std::string HexEncode(const uint8_t* in_buffer, unsigned int size) { + static const char kHexChars[] = "0123456789ABCDEF"; + + // Each input byte creates two output hex characters. + std::string out_buffer(size * 2, '\0'); + + for (unsigned int i = 0; i < size; ++i) { + char byte = in_buffer[i]; + out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf]; + out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf]; + } + return out_buffer; +} + +std::string IntToString(int value) { + // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4. + // So round up to allocate 3 output characters per byte, plus 1 for '-'. + const int kOutputBufSize = 3 * sizeof(int) + 1; + char buffer[kOutputBufSize]; + memset(buffer, 0, kOutputBufSize); + snprintf(buffer, kOutputBufSize, "%d", value); + + std::string out_string(buffer); + return out_string; +} + +std::string UintToString(unsigned int value) { + // log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4. + // So round up to allocate 3 output characters per byte. + const int kOutputBufSize = 3 * sizeof(unsigned int); + char buffer[kOutputBufSize]; + memset(buffer, 0, kOutputBufSize); + snprintf(buffer, kOutputBufSize, "%u", value); + + std::string out_string(buffer); + return out_string; +} + +}; // namespace wvcdm diff --git a/core/test/base64_test.cpp b/core/test/base64_test.cpp new file mode 100644 index 00000000..ffb012fa --- /dev/null +++ b/core/test/base64_test.cpp @@ -0,0 +1,73 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include + +#include "gtest/gtest.h" +#include "log.h" +#include "string_conversions.h" + +namespace { + +// Test vectors as suggested by http://tools.ietf.org/html/rfc4648#section-10 +const std::string kNullString(""); +const std::string kf("f"); +const std::string kfo("fo"); +const std::string kfoo("foo"); +const std::string kfoob("foob"); +const std::string kfooba("fooba"); +const std::string kfoobar("foobar"); +const std::string kfB64("Zg=="); +const std::string kfoB64("Zm8="); +const std::string kfooB64("Zm9v"); +const std::string kfoobB64("Zm9vYg=="); +const std::string kfoobaB64("Zm9vYmE="); +const std::string kfoobarB64("Zm9vYmFy"); + +// Arbitrary clear test vectors +const std::string kMultipleOf24BitsData("Good day!"); +const std::string kOneByteOverData("Hello Friend!"); +const std::string kTwoBytesOverData("Hello Friend!!"); +const std::string kTestData = + "\030\361\\\366\267> \331\210\360\\-\311:\324\256\376" + "\261\234\241\326d\326\177\346\346\223\333Y\305\214\330"; + +// Arbitrary encoded test vectors +const std::string kMultipleOf24BitsB64Data("R29vZCBkYXkh"); +const std::string kOneByteOverB64Data("SGVsbG8gR29vZ2xlcg=="); +const std::string kTwoBytesOverB64Data("SGVsbG8gR29vZ2xlcnM="); +const std::string kB64TestData = "GPFc9rc-INmI8FwtyTrUrv6xnKHWZNZ_5uaT21nFjNg="; + +const std::pair kBase64TestVectors[] = { + make_pair(&kNullString, &kNullString), + make_pair(&kf, &kfB64), + make_pair(&kfo, &kfoB64), + make_pair(&kfoo, &kfooB64), + make_pair(&kfoob, &kfoobB64), + make_pair(&kfooba, &kfoobaB64), + make_pair(&kfoobar, &kfoobarB64), + make_pair(&kMultipleOf24BitsData, &kMultipleOf24BitsB64Data), + make_pair(&kOneByteOverData, &kOneByteOverB64Data), + make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data), + make_pair(&kTestData, &kB64TestData), +}; + +} // unnamed namespace + +namespace wvcdm { + +class Base64EncodeDecodeTest : public ::testing::TestWithParam< + std::pair > {}; + +TEST_P(Base64EncodeDecodeTest, EncodeDecodeTest) { + std::pair values = GetParam(); + std::vector decoded_vector = Base64SafeDecode(values.second->data()); + std::string decoded_string(decoded_vector.begin(), decoded_vector.end()); + EXPECT_STREQ(values.first->data(), decoded_string.data()); + std::string b64_string = Base64SafeEncode(decoded_vector); + EXPECT_STREQ(values.second->data(), b64_string.data()); +} + +INSTANTIATE_TEST_CASE_P(ExecutesBase64Test, Base64EncodeDecodeTest, + ::testing::ValuesIn(kBase64TestVectors)); + +} // namespace wvcdm diff --git a/core/test/cdm_engine_test.cpp b/core/test/cdm_engine_test.cpp new file mode 100644 index 00000000..46dfac55 --- /dev/null +++ b/core/test/cdm_engine_test.cpp @@ -0,0 +1,324 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include +#include + +#if defined(CHROMIUM_BUILD) +#include "base/at_exit.h" +#include "base/message_loop/message_loop.h" +#endif +#include "cdm_engine.h" +#include "config_test_env.h" +#include "gtest/gtest.h" +#include "license_request.h" +#include "log.h" +#include "properties.h" +#include "scoped_ptr.h" +#include "string_conversions.h" +#include "url_request.h" +#include "wv_cdm_types.h" + +namespace { +// Http OK response code. +const int kHttpOk = 200; + +// Default license server, can be configured using --server command line option +// Default key id (pssh), can be configured using --keyid command line option +std::string g_client_auth; +wvcdm::KeyId g_key_id; +wvcdm::CdmKeySystem g_key_system; +std::string g_license_server; +std::string g_port; +wvcdm::KeyId g_wrong_key_id; +int g_use_full_path = 0; // cannot use boolean in getopt_long + +// WHAT: This is an RSA certificate message from the provisioning server. +// The client sends this certificate to a license server for +// device authentication by the license server. +// WHY: This certificate is used to test the CDM engine's provisioning +// response handling. +static wvcdm::CdmProvisioningResponse kValidJsonProvisioningResponse = + "{\"signedResponse\": {" + "\"message\": \"CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo" + "TP751tvoADf86iLrf73mEzF58eSlaOjCpJRf2R3dojbNeSTy3JICmCc8vKtMjZRX9QWTvJbq_cg" + "yMB8FQC8enuYhOaw1yJDYyCFHgik34NrUVUfmvaKKdSKQimqAZmjXi6P0znAn-XdPtz2xJVRxZp" + "NH3QCD1bGcH_O1ercBW2JwF9KNalKFsxQrBhIwvyx-q-Ah4vf4r3M2HzY6JTHvcYGGc7dJNA3Xe" + "WfCrYIvg0SGCP_z7Y2wICIA36VMwR3gnwNZlKkx6WGCCgsaU6IbLm4HpRBZfajuiOlasoYN4z1R" + "lQ14Z32fdaFy8xOqLl-ZukxjWa7wv9zOSveH6JcHap1FS3R-RZ7E5WhfjxSTS0nWWZgmAjS2PkP" + "9g4GPNsnpsrVymI39j6R6jPoc3__2EGN6qAvmp4pFKR7lQyslgNn2vYLuE0Ps5mIXVkxNiZOO3T" + "jxgZyHaHOm1KmAZKI0EfddMATJCTt-UeLG3haqS_pYaBWcQ_xzWhoEHWU7_6ZaWrWemV8CVCg6s" + "OB1SRI5MrkRBBSV0r8UKddLJGthZVjuTG75KK72KE9yhe86mCadvfVYe5keJ5GOC-t1EiFzBo4c" + "4oqwkOCkkmYX_BEuZ3pOWztFp1_Br2Tl_fziw4O2vNIPCXB9yEewV6PkYPziTue3x4vRqD_mYjm" + "1ia8fxISQnEC0vrqvrFFs9fLAHPlsvaRFnhv_XKpRwFoBdfqWTakb3k6uRz0Oh2SJ8euzFIyQNB" + "efesMWk45DSrQjnlwlKXwZSiDKjAss0W2WwIb9F_x5LdB1Aa-CBudLVdxf62ggYaNZ57qx3YeHA" + "jkqMGIF7Fq09D4OxM0jRsnrmXbJWKleUpJi7nHJgQGZk2ifN95gjuTNcRaGfYXMOsDoWdkrNAq0" + "LScsPB06xEUR0DcO9vWx0zAEK7gsxxHziR7ZaYiIIkPysRR92r2NoLFPOUXf8j8ait-51jZmPKn" + "bD6adieLy6ujSl907QsUgyGvokLs1OCsYHZr-X6vnyMjdk4G3QfmWwRepD_CMyXGvtLbTNCto7E" + "L_M2yPZveAwYWwNlBtWK21gwIU2dgY298z7_S6jaQBc29f25sREjvN793ttYsPaeyom08qHYDnb" + "jae3XX-2qqde6AGXlv__jO8WDZ5od6DWu2ThqV10ijVGFfGniRsSruzq0iq8zuAqTOGhmA9Dw7b" + "rNlI95P4LpJA5pbjmNdnX7CQa2oHUuojmwlXRYuOA28PNEf-sc7ZPmMyFzedJi4EpkqzeQspEdH" + "yNMf23iEjK6GOff7dgAaxg9vYHyprhkEml4BdmFVYwCYQy8o6KRcA0NgJb8c3tg4d3aRXWp6L-F" + "sVhwqvq6FLOunSTNRIqhr2mOjRpU5w4mx-9GJRtk4XEcKT9YgUHGOUjGwfhQ5gBQDyZZVTddIUb" + "MOThsSg7zr38oUCfgXeZaai3X2foKo1Bt94Q_q18dw5xNAN5e7rSwfilltHL23zbZduuhWkvp8S" + "dag_NbO2C4IRMkzbjQBmiO9ixjXRhdqHlRRWcfR0wbQvEhD47egRVfnhKZ0W9G2-FGhyGuwJCq4" + "CCAISEAfZ_94TqpXBImeAUzYhNr0Y48SbiwUijgIwggEKAoIBAQDRigR9nFm4mfBUh1Y3SGyOcF" + "E-yK2NtfDiQe9l70KtkOeH4sB6MMB8g1QKPbUE8SBjPvXVJC_2DAWKjALzk4Aw-K-VmYe_Ag9CH" + "JiS-XcfUYEGgK4jVMxadEq3LufEEREKUZnzjgQlR39dzgjFqIrC1bwfy3_99RsjPt6QpWPg36PI" + "O4UKlmwBDTFzSOJB-4IV8Opy5Zv84BqPuyO9P5e3bXj_shRfy_XAGG2HGP_PpOCZWEfxuce0Iyu" + "vpTPLQpTOgNw-VvUBGCWMZFoERopmqp_pQwWZ2a-EwlT_vvYY4SkuNjflBskR70xz4QzEo9665g" + "k6I-HbHrTv29KEiAllAgMBAAEomSASgAIkKz1CSdFJVKcpO56jW0vsjKp92_cdqXBSEY3nuhzug" + "_LFluMJx_IqATUcCOY-w6w0yKn2ezfZGE0MDIaCngEgQFI_DRoaSOBNNeirF59uYM0sK3P2eGS9" + "G6F0l-OUXJdSO0b_LO8AbAK9LA3j7UHaajupJI1mdc4VtJfPRTsml2vIeKhDWXWaSvmeHgfF_tp" + "-OV7oPuk6Ub26xpCp2He2rEAblCYEl25Zlz97K4DhyTOV5_xuSdSt-KbTLY9cWM5i9ncND1RzCc" + "4qOixKarnMM5DdpZhs3B5xVj3yBAM1mVxPD2sZnqHSEN2EK7BMlHEnnyxhX0MGE36TQZR7P-I-G" + "rUFCq8CCAESEDAxMjM0NTY3ODlBQkNERUYYspIEIo4CMIIBCgKCAQEApwA2YGXcvVRaKkC04RWU" + "WBFPlFjd3qcfPCzgiAkpYVdnXlZ-7iePWTSaKqqdtE76p2rUyXpTwU6f4zT3PbfJEEdPKNo_zjF" + "7_QYQ6_e-kvmv-z5o2u4aZEzzKfJznjnY9m_YsoCCcY61pPLCPs0KyrYEzZoTi1RzVCVUjL6Yem" + "et2rNOs_qCqEpnmFZXVHHNEn_towHAaoskA5aIvpdmKrxTyYMGUVqIZRMY5Drta_FhW0zIHvTCr" + "gheLV_4En-i_LshGDDa_kD7AcouNw7O3XaHgkYLOnePwHIHLH-dHoZb7Scp3wOXYu9E01s925xe" + "G3s5tAttBGu7uyxfz7N6BQIDAQABKNKF2MwEEoADe9NAqNAxHpU13bMgz8LPySZJU8hY1RLwcfT" + "UM47Xb3m-F-s2cfI7w08668f79kD45uRRzkVc8GbRIlVyzVC0WgIvtxEkYRKfgF_J7snUe2J2NN" + "1FrkK7H3oYhcfPyYZH_SPZJr5HPoBFQTmS5A4l24U1dzQ6Z7_q-oS6uT0DiagTnzWhEg6AEnIkT" + "sJtK3cZuKGYq3NDefZ7nslPuLXxdXl6SAEOtrk-RvCY6EBqYOuPUXgxXOEPbyM289R6aHQyPPYw" + "qs9Pt9_E4BuMqCsbf5H5mLms9FA-wRx6mK2IaOboT4tf9_YObp3hVeL3WyxzXncETzJdE1GPGlO" + "t_x5S_MylgJKbiWQYSdmqs3fzYExunw3wvI4tPHT_O8A_xKjyTEAvE5cBuCkfjwT716qUOzFUzF" + "gZYLHnFiQLZekZUbUUlWY_CwU9Cv0UtxqQ6Oa835_Ug8_n1BwX6BPbmbcWe2Y19laSnDWg4JBNl" + "F2CyP9N75jPtW9rVfjUSqKEPOwaIgwzNDkyMjM3NDcAAAA=\"," + "\"signature\": \"r-LpoZcbbr2KtoPaFnuWTVBh4Gup1k8vn0ClW2qm32A=\"}}"; +} // namespace + +namespace wvcdm { + +class WvCdmEngineTest : public testing::Test { + public: + virtual void SetUp() { + cdm_engine_.OpenSession(g_key_system, NULL, &session_id_); + } + + virtual void TearDown() { + cdm_engine_.CloseSession(session_id_); + } + + protected: + void GenerateKeyRequest(const std::string& key_system, + const std::string& key_id) { + CdmAppParameterMap app_parameters; + std::string server_url; + std::string init_data = key_id; + CdmKeySetId key_set_id; + + if (!Properties::extract_pssh_data()) { + EXPECT_TRUE(CdmEngine::ExtractWidevinePssh(key_id, &init_data)); + } + + EXPECT_EQ(KEY_MESSAGE, + cdm_engine_.GenerateKeyRequest(session_id_, + key_set_id, + init_data, + kLicenseTypeStreaming, + app_parameters, + &key_msg_, + &server_url)); + } + + void GenerateRenewalRequest(const std::string& key_system, + const std::string& init_data) { + EXPECT_EQ(KEY_MESSAGE, + cdm_engine_.GenerateRenewalRequest(session_id_, + &key_msg_, + &server_url_)); + } + + // posts a request and extracts the drm message from the response + std::string GetKeyRequestResponse(const std::string& server_url, + const std::string& client_auth) { + // Use secure connection and chunk transfer coding. + UrlRequest url_request(server_url + client_auth, g_port, true, true); + if (!url_request.is_connected()) { + return ""; + } + + url_request.PostRequest(key_msg_); + std::string response; + int resp_bytes = url_request.GetResponse(&response); + LOGD("response:\r\n%s", response.c_str()); + LOGD("end %d bytes response dump", resp_bytes); + + int status_code = url_request.GetStatusCode(response); + EXPECT_EQ(kHttpOk, status_code); + + if (status_code != kHttpOk) { + return ""; + } else { + std::string drm_msg; + LicenseRequest lic_request; + lic_request.GetDrmMessage(response, drm_msg); + LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(), + HexEncode(reinterpret_cast(drm_msg.data()), + drm_msg.size()).c_str()); + return drm_msg; + } + } + + void VerifyNewKeyResponse(const std::string& server_url, + const std::string& client_auth, + std::string& init_data){ + std::string resp = GetKeyRequestResponse(server_url, + client_auth); + CdmKeySetId key_set_id; + EXPECT_EQ(cdm_engine_.AddKey(session_id_, resp, &key_set_id), KEY_ADDED); + } + + void VerifyRenewalKeyResponse(const std::string& server_url, + const std::string& client_auth, + std::string& init_data) { + std::string resp = GetKeyRequestResponse(server_url, + client_auth); + EXPECT_EQ(cdm_engine_.RenewKey(session_id_, resp), wvcdm::KEY_ADDED); + } + + CdmEngine cdm_engine_; + std::string key_msg_; + std::string session_id_; + std::string server_url_; +}; + +TEST(WvCdmProvisioningTest, ProvisioningTest) { + CdmEngine cdm_engine; + CdmProvisioningRequest prov_request; + std::string provisioning_server_url; + + cdm_engine.GetProvisioningRequest(&prov_request, &provisioning_server_url); + cdm_engine.HandleProvisioningResponse(kValidJsonProvisioningResponse); +} + +TEST_F(WvCdmEngineTest, BaseMessageTest) { + GenerateKeyRequest(g_key_system, g_key_id); + GetKeyRequestResponse(g_license_server, g_client_auth); +} + +TEST_F(WvCdmEngineTest, WrongMessageTest) { + std::string wrong_message = a2bs_hex(g_wrong_key_id); + GenerateKeyRequest(g_key_system, wrong_message); + + // We should receive a response with no license, i.e. the extracted license + // response message should be empty. + ASSERT_EQ("", GetKeyRequestResponse(g_license_server, g_client_auth)); +} + +TEST_F(WvCdmEngineTest, NormalDecryption) { + GenerateKeyRequest(g_key_system, g_key_id); + VerifyNewKeyResponse(g_license_server, g_client_auth, g_key_id); +} + +TEST_F(WvCdmEngineTest, LicenseRenewal) { + GenerateKeyRequest(g_key_system, g_key_id); + VerifyNewKeyResponse(g_license_server, g_client_auth, g_key_id); + + GenerateRenewalRequest(g_key_system, g_key_id); + VerifyRenewalKeyResponse(server_url_.empty() ? g_license_server : server_url_, + g_client_auth, + g_key_id); +} + +} // namespace wvcdm + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + wvcdm::InitLogging(argc, argv); + + wvcdm::ConfigTestEnv config(wvcdm::kGoogleLicenseServerTest); + g_client_auth.assign(config.client_auth()); + g_key_system.assign(config.key_system()); + g_wrong_key_id.assign(config.wrong_key_id()); + + // The following variables are configurable through command line options. + g_license_server.assign(config.license_server()); + g_key_id.assign(config.key_id()); + g_port.assign(config.port()); + std::string license_server(g_license_server); + + int show_usage = 0; + static const struct option long_options[] = { + { "use_full_path", no_argument, &g_use_full_path, 0 }, + { "keyid", required_argument, NULL, 'k' }, + { "port", required_argument, NULL, 'p' }, + { "server", required_argument, NULL, 's' }, + { "vmodule", required_argument, NULL, 0 }, + { "v", required_argument, NULL, 0 }, + { NULL, 0, NULL, '\0' } + }; + + int option_index = 0; + int opt = 0; + while ((opt = getopt_long(argc, argv, "k:p:s:u", long_options, &option_index)) != -1) { + switch (opt) { + case 'k': { + g_key_id.clear(); + g_key_id.assign(optarg); + break; + } + case 'p': { + g_port.clear(); + g_port.assign(optarg); + break; + } + case 's': { + g_license_server.clear(); + g_license_server.assign(optarg); + break; + } + case 'u': { + g_use_full_path = 1; + break; + } + case '?': { + show_usage = 1; + break; + } + } + } + + if (show_usage) { + std::cout << std::endl; + std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl; + std::cout << " enclose multiple arguments in '' when using adb shell" << std::endl; + std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl; + + std::cout << std::setw(30) << std::left << " --port="; + std::cout << "specifies the port number, in decimal format" << std::endl; + std::cout << std::setw(30) << std::left << " "; + std::cout << "default: " << g_port << std::endl; + + std::cout << std::setw(30) << std::left << " --server="; + std::cout << "configure the license server url, please include http[s] in the url" << std::endl; + std::cout << std::setw(30) << std::left << " "; + std::cout << "default: " << license_server << std::endl; + + std::cout << std::setw(30) << std::left << " --keyid="; + std::cout << "configure the key id or pssh, in hex format" << std::endl; + std::cout << std::setw(30) << std::left << " default keyid:"; + std::cout << g_key_id << std::endl; + + std::cout << std::setw(30) << std::left << " --use_full_path"; + std::cout << "specify server url is not a proxy server" << std::endl; + std::cout << std::endl; + return 0; + } + + std::cout << std::endl; + std::cout << "Server: " << g_license_server << std::endl; + std::cout << "Port: " << g_port << std::endl; + std::cout << "KeyID: " << g_key_id << std::endl << std::endl; + + g_key_id = wvcdm::a2bs_hex(g_key_id); + config.set_license_server(g_license_server); + config.set_port(g_port); + config.set_key_id(g_key_id); + +#if defined(CHROMIUM_BUILD) + base::AtExitManager exit; + base::MessageLoop ttr(base::MessageLoop::TYPE_IO); +#endif + return RUN_ALL_TESTS(); +} diff --git a/core/test/config_test_env.cpp b/core/test/config_test_env.cpp new file mode 100644 index 00000000..313f7c40 --- /dev/null +++ b/core/test/config_test_env.cpp @@ -0,0 +1,108 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "config_test_env.h" + +namespace { + +// WHAT: URL of provisioning server (returned by GetProvisioningRequest()) +const std::string kProductionProvisioningServerUrl = + "https://www.googleapis.com/" + "certificateprovisioning/v1/devicecertificates/create" + "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; + +// WHAT: URL of test provisioning server - This is a placeholder for +// an alternate provisioning server. +// WHY: request_license_test uses this url. +const std::string kProductionTestProvisioningServerUrl = + "https://www.googleapis.com/" + "certificateprovisioning/v1/devicecertificates/create" + "?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE"; + +// Google test License Server parameters +// This is a test server that has limited content and relatively +// open access controls. It is maintained for integration testing of +// CDM and OEMCrypto implementations. + +// WHAT: URL for test license server. +const std::string kGgLicenseServer = + "http://widevine-proxy.appspot.com/proxy"; + +// WHAT: Test client authorization string. +// WHY: Needed to pass client info to server. +const std::string kGgClientAuth = ""; + +// WHAT: License info for test content. This is a valid +// license for test content, registered with the +// test license server. +// Video ID: CJTHyHGbKKA +// KeyId: 20ea8aa187aa50a7aa66723d7225c18f +// ContentID: 0894c7c8719b28a0 +// +// SD Video KeyID (142-144): 20EA8AA187AA50A7AA66723D7225C18F +// HD Video KeyID (145,146): 1BEFCD2630185DFEA1A36E5C91C0BA63 +// Audio KeyID (148,149,150): 812A73CD62A45336B22FD13D00102106 +const std::string kGgKeyId = + "000000347073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id + "08011210812A73CD62A45336B22FD13D00102106"; // pssh data + +// WHAT: An invalid license id, expected to fail +const std::string kWrongKeyId = + "000000347073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id + "0901121094889920E8D6520098577DF8F2DD5546"; // pssh data + +// Sample license server parameters +// NOTE: This data is not valid. It will be replaced with license +// server-specific data supplied by server administrators. + +// WHAT: URL for license server. +const std::string kTtCpLicenseServer = + "http://widevine-proxy.appspot.com/invalid"; + +// WHAT: Client authorization string for license server request +// WHY: May be needed to pass client info to server. +const std::string kTtCpClientAuth = ""; + +// WHAT: License ID for test license server. This is not +// a valid license ID unless the ID is registered +// with the server. A valid license ID will be supplied +// by server administrators. +const std::string kTtCpKeyId = + "000000347073736800000000" // blob size and pssh + "EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id + "0801121030313233343536373839616263646566"; // pssh data + +// WHAT: Table of license servers useable for testing. +// WHY: Allow testing against multiple license servers. +// Fields: +// id - enum for identifying/selecting this license server +// url - url of license server +// client_tag - default client authorization string +// key_id - license id for test license on this server +// port - http port +// use_chunked_transfer - server communication setting +// use_secure_transfer - server communication setting +const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = { + { wvcdm::kGoogleLicenseServerTest, kGgLicenseServer, + kGgClientAuth, kGgKeyId, kDefaultHttpsPort, true, true }, + { wvcdm::kPartnerLicenseServer, kTtCpLicenseServer, + kTtCpClientAuth, kTtCpKeyId, kDefaultHttpPort, false, false } +}; +} // namespace + +namespace wvcdm { + +ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) + : client_auth_(license_servers[server_id].client_tag), + key_id_(license_servers[server_id].key_id), + key_system_("com.widevine.alpha"), + license_server_(license_servers[server_id].url), + port_(license_servers[server_id].port), + provisioning_server_url_(kProductionProvisioningServerUrl), + provisioning_test_server_url_(kProductionTestProvisioningServerUrl), + use_chunked_transfer_(license_servers[server_id].use_chunked_transfer), + use_secure_transfer_(license_servers[server_id].use_secure_transfer), + wrong_key_id_(kWrongKeyId) {} + +} // namespace wvcdm diff --git a/core/test/config_test_env.h b/core/test/config_test_env.h new file mode 100644 index 00000000..696237c4 --- /dev/null +++ b/core/test/config_test_env.h @@ -0,0 +1,80 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef CDM_TEST_CONFIG_TEST_ENV_H_ +#define CDM_TEST_CONFIG_TEST_ENV_H_ + +#include +#include "wv_cdm_types.h" + +namespace { +const std::string kDefaultHttpsPort = "443"; +const std::string kDefaultHttpPort = "80"; +} + +namespace wvcdm { + +//WHAT: Index into table of alternate license servers (license_servers[]). +//WHY: Allow testing against multiple license server. +typedef enum { + kGoogleLicenseServerTest, + kPartnerLicenseServer +} LicenseServerId; + +// Configures default test environment. +class ConfigTestEnv { + public: + typedef struct { + LicenseServerId id; + std::string url; + std::string client_tag; + std::string key_id; + std::string port; + bool use_chunked_transfer; + bool use_secure_transfer; + } LicenseServerConfiguration; + + explicit ConfigTestEnv(LicenseServerId server_id); + ~ConfigTestEnv() {}; + + const std::string& client_auth() const { return client_auth_; } + 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& port() const { return port_; } + const std::string& provisioning_server_url() const { + return provisioning_server_url_; + } + const std::string& provisioning_test_server_url() const { + return provisioning_test_server_url_; + } + bool use_chunked_transfer() { return use_chunked_transfer_; } + bool use_secure_transfer() { return use_secure_transfer_; } + const KeyId& wrong_key_id() const { return wrong_key_id_; } + + void set_key_id(KeyId& key_id) { key_id_.assign(key_id); } + void set_key_system(CdmKeySystem& key_system) { + key_system_.assign(key_system); + } + void set_license_server(std::string& license_server) { + license_server_.assign(license_server); + } + void set_port(std::string& port) { port_.assign(port); } + + private: + std::string client_auth_; + KeyId key_id_; + CdmKeySystem key_system_; + std::string license_server_; + std::string port_; + std::string provisioning_server_url_; + std::string provisioning_test_server_url_; + bool use_chunked_transfer_; + bool use_secure_transfer_; + KeyId wrong_key_id_; + + CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv); +}; + +}; // namespace wvcdm + +#endif // CDM_TEST_CONFIG_TEST_ENV_H_ diff --git a/core/test/device_files_unittest.cpp b/core/test/device_files_unittest.cpp new file mode 100644 index 00000000..4ca9987e --- /dev/null +++ b/core/test/device_files_unittest.cpp @@ -0,0 +1,1478 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "device_files.h" +#include "file_store.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "properties.h" +#include "string_conversions.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +// gmock methods +using ::testing::_; +using ::testing::AllOf; +using ::testing::Eq; +using ::testing::Gt; +using ::testing::HasSubstr; +using ::testing::NotNull; +using ::testing::Return; +using ::testing::ReturnArg; +using ::testing::SetArgPointee; +using ::testing::SetArrayArgument; +using ::testing::StrEq; + +namespace { +const uint32_t kCertificateLen = 700; +const uint32_t kWrappedKeyLen = 500; +const uint32_t kProtobufEstimatedLen = 75; + +// WHAT: Structurally valid test certificate. +// WHY: The data elements in this module are used to test the storage +// and retrieval of certificates and licenses +const std::string kTestCertificate = + "124B035F3D256A656F0E505A085E7A6C482B61035E0C4A540F7803137F4C3B45206B7F33" + "347F4D7A005E56400F0955011F4E07072D0D46781817460974326A516E3944385760280E" + "4F166B380F033D045231201E6146041C3A6F01345C59300D32592732192C0F2310586306" + "7B31467B1477010D6F1D1944272509572A26217E1E6F7B666F46153E7749106E48760468" + "19467E164A731773155B3236537D5128682014174D125063380E48356A370B5015416A7F" + "672F132E37364E154B41540F440E47092775531508495F1E55576F363C0C190C3A332179" + "415B343905563E37645E68007053315A1A20286E7C3B4320424A5F7F36635558686C3565" + "762122237D344A411C0F00342135776753461D105C21111E5024434E5E0F275D12061658" + "4435410F210E5228532D214F505D0F0B3C34032C7C597F6159665E664C682C5A6C03212E" + "71333C3A642D796A65642E151827086E2D671C130B172C43192C792D294440630163526D" + "0658537A073E0F32231E7426593230692A4468386D3511542F1A6F71440128466E510445" + "294F4465113D1B1A711D4D67691363093B680854322B041C2F72524A513E5F0E407C6233" + "1728520E6C0C09107C26737B78287231661952283619647A6241391940297D2067036D44" + "3C64766918236C51175A636F000A2E5A4C5B725D5500652B1C39283037723F0255092976" + "6F2D204F0E616F1233206B75661B0F755E1E3807491079663A191C0B2D5E363B3768663A" + "4E222A1D32015D3D783E5148313F05713B140347231C59243648313C23770F554E012715" + "3350597775274A580306202E65265957291F490F642A2E7C6700716400617C7E6A303266" + "523B102906195E003C2D111A7D4740122C6941003726602B59263B5C09473D4E025E3541" + "701B122D340A3D145436137002687E4C470D2F6F4C357A3245384D737B734E2274301179" + "402473486311156E5A0C78644C593273"; + +// WHAT: A Wrapped Private Key +// WHY: 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"; + +// WHAT: The test certificate in file storage format. +// WHY: The data elements in this module are used to test the storage +// and retrieval of certificates and licenses +const std::string kTestCertificateFileData = + "0ABD09080110011AB6090ABC05124B035F3D256A656F0E505A085E7A6C482B61035E0C4A" + "540F7803137F4C3B45206B7F33347F4D7A005E56400F0955011F4E07072D0D4678181746" + "0974326A516E3944385760280E4F166B380F033D045231201E6146041C3A6F01345C5930" + "0D32592732192C0F23105863067B31467B1477010D6F1D1944272509572A26217E1E6F7B" + "666F46153E7749106E4876046819467E164A731773155B3236537D5128682014174D1250" + "63380E48356A370B5015416A7F672F132E37364E154B41540F440E47092775531508495F" + "1E55576F363C0C190C3A332179415B343905563E37645E68007053315A1A20286E7C3B43" + "20424A5F7F36635558686C3565762122237D344A411C0F00342135776753461D105C2111" + "1E5024434E5E0F275D120616584435410F210E5228532D214F505D0F0B3C34032C7C597F" + "6159665E664C682C5A6C03212E71333C3A642D796A65642E151827086E2D671C130B172C" + "43192C792D294440630163526D0658537A073E0F32231E7426593230692A4468386D3511" + "542F1A6F71440128466E510445294F4465113D1B1A711D4D67691363093B680854322B04" + "1C2F72524A513E5F0E407C62331728520E6C0C09107C26737B7828723166195228361964" + "7A6241391940297D2067036D443C64766918236C51175A636F000A2E5A4C5B725D550065" + "2B1C39283037723F02550929766F2D204F0E616F1233206B75661B0F755E1E3807491079" + "663A191C0B2D5E363B3768663A4E222A1D32015D3D783E5148313F05713B140347231C59" + "243648313C23770F554E0127153350597775274A580306202E65265957291F490F642A2E" + "7C6700716400617C7E6A303266523B102906195E003C2D111A7D4740122C694100372660" + "2B59263B5C09473D4E025E3541701B122D340A3D145436137002687E4C470D2F6F4C357A" + "3245384D737B734E2274301179402473486311156E5A0C78644C59327312F4034F724B06" + "5326371A2F5F6F51467C2E26555C453B5C7C1B4F2738454B782E3E7B5340435A66374D06" + "12052C521A233D7A67194871751C78575E5177070130264C4F037633320E667B1A491929" + "24491338693D106E6113014A733A241A1A033E28352178146B4F543D38104A5919120325" + "502C31365506096D59585E08774B5B567A7B5D03451E6B11633E52672C226103104B3E4C" + "031A6403050F3A574D2C501711773802741F7F3A0D364757101D02181C7D4D3520716750" + "6A424C094E4A72316F791F162D76657D2B5D3C2D7B273A2869277175613165187E552824" + "30491467086425432347701C3116446D21645C756B2D3D0F797C3220322D622A254D0B7D" + "4F1D5D0C0A36755D1246741A34783C45157247091C78232B7D2E0E1F637A2A3739085D76" + "166747034350613969072F5B5C5B21657E470C7E513B3F091D74455A3A0737057B7E3B53" + "37191D4E7536087C334B6028530F3F5B23380B6A076031294501003D6D1F240F63053D5D" + "0B271B6A0F26185650731308660B0447566041684F584C22216E567D3B7755695F7F3D6B" + "64525E7227165948101540243C19495C4C702F37490F2661335379782562414326304302" + "0E1E6760123D51056F2F1E482F2E3D021B27677D3E7E3C0C11757C3448275E08382E1112" + "63644C6D224714706D760A054A586E17505C3429575A41043F1842091220F8D0A23D4B1B" + "C7B23A38B921BC1EA8938D1FD22FF9A389B58DA856A3E2625F27"; + +struct LicenseInfo { + std::string key_set_id; + DeviceFiles::LicenseState license_state; + std::string pssh_data; + std::string key_request; + std::string key_response; + std::string key_renewal_request; + std::string key_renewal_response; + std::string key_release_url; + std::string file_data; +}; + +size_t kNumberOfLicenses = 3; + +// WHAT: Sample license data and related data for storage and +// use for offline playback. The license data and URLs +// in this test are not real. +// WHY: Test storage and retrieval of license-related data. +LicenseInfo license_test_data[] = { + + // license 0 + {"ksid54C57C966E23CEF5", DeviceFiles::kLicenseStateActive, + wvcdm::a2bs_hex("0801121030313233343536373839414243444546"), + wvcdm::a2bs_hex( + "080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" + "C4D07A7D507618A5D3A68F05228E023082010A0282010100A947904B8DBD" + "55FB685FDB3025574517CCCC74EE4FEAF6629D5179A52FF85CE7409528EF" + "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" + "C316C180717C182A971C94E4AC4C7DF8F161CB8CC1"), + wvcdm::a2bs_hex( + "080212CC020A190A0939383736353433323112084B9F26DAB8B06E112002" + "2800124108011801301E4239687474703A2F2F6B69723033666370673137" + "342E7769646576696E652E6E65742F7769646576696E652F6367692D6269" + "6E2F64726D2E6367691A6612102531DFD6CCEA511D00F8C0172F1189AA1A" + "5057FF9D9DBD5A205B1DEB075E4A90467C1E074CDE6071BFF831AD590BD5" + "D117614F33CE2C3CE1824FC0D54B36ECEAE58DF5C8F9347C2FEED17A3327" + "E8F52B8ECA6313A1FA6A042EB9525DD328113C05F920011A7E0A10303132" + "3334353637383941424344454612106D23622142B58F6D1EDD33AF3ECD2C" + "7E1A20884EE13BEA9DECDDBF68B532131C82B11CEC4D23C7FA9F3EF4C5EE" + "172E7C9736200242340A2050BFE71BB1BA683E35E0B49BB33048E5103FBB" + "B9C3E1CD6EBCDA7DD485DBAF431210D69D6F14C95CB6CFDB998E50D00F4D" + "A020DBDFA68F051A20AE5D6895E70F86F42F5FE3C58A505A865D05AB94B1" + "ABAA6CC59C3322F61C458D228002331F2BE95B5C796E0921CC27A7295501" + "DA10044E5CA36C0E2866FF068EA3515A6786BD5D60D74D80C6BA8BE6AAD0" + "85AF967909A143171E9CDDE36EA528402867CD04FB6F97A150CDE55F9B81" + "9F4104BEF48E4280D76645569E10AEF524D34D865B5B9E3EBC66C45EEBBE" + "16AB04493E7AEC4F99E7A99F3FC08FA431BECCC1978A079FA4801DB75E13" + "29A9921604E6F80CB148AA2DD5C8348057E9F4FC2AEA57EA4D215D0A8D48" + "6294860DFB4F4C42D57D9542B76179E179DD4AA23F9F7B2AE432B39E4CE8" + "F156E84877DDA781AAAAFC797FF75AFE2019ADC3A2E419BF0253C705BD47" + "97A96866AC4C059AD8F2E9C6B617C60C6ADCDB894C25F0C7D29252F52FD5"), + wvcdm::a2bs_hex( + "08011231121D1A1B0A190A0939383736353433323112084B9F26DAB8B06E" + "112002280018022A0C31353532333030360000000030151A20C30375683C" + "4D2033E05DCC95DDFB278CFB5125A021C3C043A16ACC933A768A27"), + wvcdm::a2bs_hex( + "0802123B0A190A0939383736353433323112084B9F26DAB8B06E11200228" + "0112001A16200342120A106B63746C0000000000ECDCBE0000000020DBDF" + "A68F051A20182F029E35047A3841FA176C74E5B387350E8D58DEA6878FF0" + "BEA6CABACA1C2C"), + "https://test.google.com/license/GetCencLicense", + wvcdm::a2bs_hex( + "0AAF150802100122A8150801121408011210303132333435363738394142" + "434445461A9D0E080112950C0AD70B080112EF090AB002080212103E560E" + "C5335E346F591BC4D07A7D507618A5D3A68F05228E023082010A02820101" + "00A947904B8DBD55FB685FDB3025574517CCCC74EE4FEAF6629D5179A52F" + "F85CE7409528EFFA0E5DFC3DE9A34BA5F08BE349553C319A9FB274905A87" + "70ADC9CA4A2CBCD8E556A1587FA18BFD4D286C644A6904F19EAAFBDFADD3" + "B371B306D0B289F459B491C814B5AD1F747610E990A60248A7DA5152F1CC" + "FC047EF42300131F9C4758F4D9F30579393B860AAD9AD2EE43D721D6DB9F" + "5800EF188386B94825AE05A883AC976D6970DF43EA6C83B86CE6D0F54020" + "7725B9890FCCEC83A49027872DAFD2740B7748E9C3B1752D6F12859CED07" + "E8882969B433EC66F17FFC29AC6FDBEA79230B0FAED5D94CF6B829A420BB" + "E3270323941776EE60DD6BFD660BDDCA8702030100012880013001128002" + "50D1F8B1ECF849B60FF93E37C4DEEF09E6FFB10BCFC996A4A24B7AA96928" + "835ED5A72E15846D0A14015733239BD8B6E6D5E5D229B08394CE1E0692C1" + "59C44337CA7CAF88476449B068D9D2FADED8EB1BC0F4B8F0FCAF293E8690" + "E74032095341803408A0E8279E545945EE97838FDE7812F7171C3CC4F5EC" + "F9418BBF1D336CE58F4CBB1B44D4ADE6BF3364BAE7EC093281846E569E13" + "E7719014030A6059044FE7BBFF3E8F5723AEDD54DC6E0D041B309D7700B5" + "5575690E95CE6085D0914F991C5F45E98CBB9C45BA33F47FD0862EBCC7EE" + "BA8E60643C86E85476B18AEF8DE871571A75681A75F75028A5B58751C09A" + "5296AAE99CEDCD9785E9E2103240D40A1AB6050AB002080112102CE5CCF4" + "2200D6B5BCCF33D7CC2D9C7018EAD1B88D05228E023082010A0282010100" + "BE1B661EEC4700DF4B0C83292D02AE029B8A224DD3048125049F74E30E12" + "57FC2BE8D9CFAF0BFFCACAF7305351771C78FA451F13AF5EEBFB360941A4" + "396A805833730DC6E534C62408B7C5076FC22568021C59ED34F98487196D" + "A32078DAFCA37C7CFB8E79612FA384963DF2167D5E87305D7BC92D621C10" + "160672C27B9A691B1534F60D78C5893E40C5FF8A3F9DF8898612E9A5CCB5" + "6F4A0CC2A61A7AEB04A9DCC015D9BC37DEF2AB9EAA9AAFD838869081D924" + "9755F129BB0DBECA3B894975A65A36FD005CE77CD407E925D3172E33122A" + "11D327968A08F8E771FAEB2540EB52D17C4906405F47C31F60F0AF6C78AF" + "53291B236E692B506A2AF92AF43E3A81020301000128800130011280033A" + "08A60418E5C81B8D71A5C0A28C26999FF4FA992E14107CA8A9E6A2B36259" + "834000FE35DCD814426F9C5D332418ED94C9C0C992217B1B6DC01C99085A" + "3C3956C8267B8773BABCF3F2C841C67D830F9DBC780DD68BF4E2FE424C6A" + "54123BE4B2A1F7E1F4DB58AB1164DAE9CF75C3392284A44B8CDB85D837E8" + "6C6B908243987E552C8693878C9A1B7BEA3759783036F1595C406D6CBBA7" + "F8642A9B3B244DAA1F00531D0B908ADE4B533FD9FAFA21D0FB0C033D2AD5" + "DDF24C60F4FAC30820758877F2E1A78EB44E9336DCFAFDF572BB22A84A5D" + "EFDF2EB87B61DE26EE9C4CEAA646A2AFDB2BB953845E6D7FE6F79A9501D1" + "C379C96961316B5D2A66F38C222091AF74141B6CAF93507485A5D8F82808" + "025451824F00C8B6A0CD5803F6564584138C8B18BC679B442D837307B5CC" + "90B1FD1FD32288B4A5D18D2D80E5E6A7A9EFD255B8B363038BCC67AF534E" + "AEE4A5903E304EED4990BB5BE735DB027A6DE35329D321EC051B956C55A5" + "B11674017517FCC3C7FF7397C13A7B7087A1F6AEC7F6761A130A0C636F6D" + "70616E795F6E616D6512034C47451A150A0A6D6F64656C5F6E616D651207" + "4E6578757320341A200A116172636869746563747572655F6E616D65120B" + "61726D656162692D7637611A130A0B6465766963655F6E616D6512046D61" + "6B6F1A150A0C70726F647563745F6E616D6512056F6363616D1A440A0A62" + "75696C645F696E666F1236676F6F676C652F6F6363616D2F6D616B6F3A34" + "2E332F4A425F4D52322F3731343239313A7573657264656275672F646576" + "2D6B6579731A210A096465766963655F696412144C474D43323031323130" + "3035303036333932373812250A230A140801121030313233343536373839" + "41424344454610021A09393837363534333231180120002A0C3339343037" + "3934373337000030151A8002734FBDED946EB74A1B61811C4C4A491214F6" + "BEA125A80F014165B28AA97AD0AF60E4D129EB7F424AD659F24E5EED4B70" + "2BEE328E38B72CA6F38CD0ECFD2E6D7B98147744C9B8A9610B3BDFE17675" + "FF7D584C5BF68064B0FE513FC322C9148795E4C2F2443C3024F5C1F29E6F" + "EFB6D77005DAB22CD2B63131908DE4D88795BB931CEA38452CC568BE2503" + "2245E372F07A1297F51748C7EA02F2C88360AFE7ABBC71DCDD5366126258" + "E5AFA27C2A20B339FA1E7AE925B494B361F6F7116F20BE8EE6E446146027" + "F4FD4300F4A0B0A3361EE34925F338D0AACF20AE919B4BAE81C1D57A8D2B" + "8FA38732A57697C316C180717C182A971C94E4AC4C7DF8F161CB8CC122F6" + "04080212CC020A190A0939383736353433323112084B9F26DAB8B06E1120" + "022800124108011801301E4239687474703A2F2F6B697230336663706731" + "37342E7769646576696E652E6E65742F7769646576696E652F6367692D62" + "696E2F64726D2E6367691A6612102531DFD6CCEA511D00F8C0172F1189AA" + "1A5057FF9D9DBD5A205B1DEB075E4A90467C1E074CDE6071BFF831AD590B" + "D5D117614F33CE2C3CE1824FC0D54B36ECEAE58DF5C8F9347C2FEED17A33" + "27E8F52B8ECA6313A1FA6A042EB9525DD328113C05F920011A7E0A103031" + "323334353637383941424344454612106D23622142B58F6D1EDD33AF3ECD" + "2C7E1A20884EE13BEA9DECDDBF68B532131C82B11CEC4D23C7FA9F3EF4C5" + "EE172E7C9736200242340A2050BFE71BB1BA683E35E0B49BB33048E5103F" + "BBB9C3E1CD6EBCDA7DD485DBAF431210D69D6F14C95CB6CFDB998E50D00F" + "4DA020DBDFA68F051A20AE5D6895E70F86F42F5FE3C58A505A865D05AB94" + "B1ABAA6CC59C3322F61C458D228002331F2BE95B5C796E0921CC27A72955" + "01DA10044E5CA36C0E2866FF068EA3515A6786BD5D60D74D80C6BA8BE6AA" + "D085AF967909A143171E9CDDE36EA528402867CD04FB6F97A150CDE55F9B" + "819F4104BEF48E4280D76645569E10AEF524D34D865B5B9E3EBC66C45EEB" + "BE16AB04493E7AEC4F99E7A99F3FC08FA431BECCC1978A079FA4801DB75E" + "1329A9921604E6F80CB148AA2DD5C8348057E9F4FC2AEA57EA4D215D0A8D" + "486294860DFB4F4C42D57D9542B76179E179DD4AA23F9F7B2AE432B39E4C" + "E8F156E84877DDA781AAAAFC797FF75AFE2019ADC3A2E419BF0253C705BD" + "4797A96866AC4C059AD8F2E9C6B617C60C6ADCDB894C25F0C7D29252F52F" + "D52A5708011231121D1A1B0A190A0939383736353433323112084B9F26DA" + "B8B06E112002280018022A0C31353532333030360000000030151A20C303" + "75683C4D2033E05DCC95DDFB278CFB5125A021C3C043A16ACC933A768A27" + "32610802123B0A190A0939383736353433323112084B9F26DAB8B06E1120" + "02280112001A16200342120A106B63746C0000000000ECDCBE0000000020" + "DBDFA68F051A20182F029E35047A3841FA176C74E5B387350E8D58DEA687" + "8FF0BEA6CABACA1C2C3A3968747470733A2F2F6A6D7431372E676F6F676C" + "652E636F6D2F766964656F2D6465762F6C6963656E73652F47657443656E" + "634C6963656E73651220F6974C1CFFD00E3144488FC092D3DF4F6007A3CA" + "C4756EB046DC74B1C2E512CC")}, + + // license 1 + {"ksidC8EAA2579A282EB0", DeviceFiles::kLicenseStateReleasing, + wvcdm::a2bs_hex("0801121030313233343536373839414243444546"), + wvcdm::a2bs_hex( + "080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" + "C4D07A7D507618A5D3A68F05228E023082010A0282010100A947904B8DBD" + "55FB685FDB3025574517CCCC74EE4FEAF6629D5179A52FF85CE7409528EF" + "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" + "021A09393837363534333231180120002A0C383837303136333500000000" + "30151A80023F7318E29C5A50C8ADAA4B09ADCD97B75588B17002C5C2BC9A" + "FA35C53098AF22DF5CC300407CD2E84EBE01911C785513649E2CCF4E4290" + "20D3B93F3A54748C11ECFF4D62F562A4D3E96812F663D4F761C00C3E88AB" + "D8A1DC10E017A44DD3E040775FED5F07649090D1142C9D21373CD604219E" + "24935E10F287F20B0E080FDF76B6096B24F82A3E37850DE229DE33EBCE7A" + "0FA53F652C33007EA7027F95A44C36D04CBD676EB5C0BF69508F45E0C322" + "0D1706B0B851B3FCAF7AC2370EAD80C5D1620887633A42024862FCEA9F95" + "A719AAB989C1923C6452ECB0B75AF1CAFBFB06C5EC31BBF0EE4D16ACCC9A" + "F05B77D61C4855491B3D4AC150F3BCB7AE536AF333"), + wvcdm::a2bs_hex( + "080212CC020A190A093938373635343332311208F97F2B3856CBB3DD2002" + "2800124108011801301E4239687474703A2F2F6B69723033666370673137" + "342E7769646576696E652E6E65742F7769646576696E652F6367692D6269" + "6E2F64726D2E6367691A661210C5C43FE0178AEE7B85042F749D5A40251A" + "5013A1501E0F90A64E103336944A37BAAAEAC17E46E880DF6EA23A7A890D" + "A082CBBF82710B8C3982E8AB25A208A89EEFB5250D4B2CCC2F362856E05D" + "1941E387801A19886B1F3AAE60D06EDA400087B06920011A7E0A10303132" + "333435363738394142434445461210A34D2B04D596DFE1DC29CFDF116E39" + "211A2031AD1B369D225842A14B5D5F8366F5FF8EB94AA7CD13EB45BA7291" + "68E19D5F5F200242340A20A0D6D65CC677C12B86A7A99F89F446BCFDA185" + "44B15B2FEF8349ED5C247F7BE91210ED8D58320B0F4F948F960C7D49872C" + "DE2083E5A68F051A207481A2B82C83DF3090D57EDC042711A42CF4F87E79" + "CE136DAFE25F48F4A9068322800256113CA771F4250CAD2928161D07B525" + "61019003DBFBD362F20587D51BD999D57D2B035BC115C54C8B4BC37661A6" + "6A101DE5B42D82E582309AFD8E211C947A2D33CAFB58F89EEE2DA9524614" + "0311134429D8A5D15E03A169B0EB2579DA3BD6E4322D6C46EE964F6931CF" + "9DA52FB59B1D3B9BCC5959211CC23D97690FA8E869ADF68BCDA8A1211DDB" + "EBF967617AF0BFDA73E0AE79D8A7CCED208602EDC72CEF44A02901A52EEB" + "87CF9841D186BC95A65956BAD48F3C9E43F027CC03B73DFF5CAFC0B64727" + "E2D7B3A9CF25F97C475207C8A9DF091A585288A71AE64B7B2089871F7272" + "381CCBEF55EBF3DCB21B134FE48BFD5299DCCA6B01B55EEA61F9F990D0AF"), + wvcdm::a2bs_hex( + "08011231121D1A1B0A190A093938373635343332311208F97F2B3856CBB3" + "DD2002280018022A0C33333932383235393733000030151A209ADE9B0A41" + "1583962BDA31BE5BE937E589BB3DCC06F6F4C48FBE4FAE86DC9ABA"), + wvcdm::a2bs_hex( + "0802123B0A190A093938373635343332311208F97F2B3856CBB3DD200228" + "0112001A16200342120A106B63746C00000000CA3A6A75000000002083E5" + "A68F051A20BDA6A56F7CBFD0942198F87C23A34AA5CBD64AFEB134277774" + "CCF8E789D815DD"), + "https://test.google.com/license/GetCencLicense", + wvcdm::a2bs_hex( + "0AC7150802100122C0150802121408011210303132333435363738394142" + "434445461A9D0E080112950C0AD70B080112EF090AB002080212103E560E" + "C5335E346F591BC4D07A7D507618A5D3A68F05228E023082010A02820101" + "00A947904B8DBD55FB685FDB3025574517CCCC74EE4FEAF6629D5179A52F" + "F85CE7409528EFFA0E5DFC3DE9A34BA5F08BE349553C319A9FB274905A87" + "70ADC9CA4A2CBCD8E556A1587FA18BFD4D286C644A6904F19EAAFBDFADD3" + "B371B306D0B289F459B491C814B5AD1F747610E990A60248A7DA5152F1CC" + "FC047EF42300131F9C4758F4D9F30579393B860AAD9AD2EE43D721D6DB9F" + "5800EF188386B94825AE05A883AC976D6970DF43EA6C83B86CE6D0F54020" + "7725B9890FCCEC83A49027872DAFD2740B7748E9C3B1752D6F12859CED07" + "E8882969B433EC66F17FFC29AC6FDBEA79230B0FAED5D94CF6B829A420BB" + "E3270323941776EE60DD6BFD660BDDCA8702030100012880013001128002" + "50D1F8B1ECF849B60FF93E37C4DEEF09E6FFB10BCFC996A4A24B7AA96928" + "835ED5A72E15846D0A14015733239BD8B6E6D5E5D229B08394CE1E0692C1" + "59C44337CA7CAF88476449B068D9D2FADED8EB1BC0F4B8F0FCAF293E8690" + "E74032095341803408A0E8279E545945EE97838FDE7812F7171C3CC4F5EC" + "F9418BBF1D336CE58F4CBB1B44D4ADE6BF3364BAE7EC093281846E569E13" + "E7719014030A6059044FE7BBFF3E8F5723AEDD54DC6E0D041B309D7700B5" + "5575690E95CE6085D0914F991C5F45E98CBB9C45BA33F47FD0862EBCC7EE" + "BA8E60643C86E85476B18AEF8DE871571A75681A75F75028A5B58751C09A" + "5296AAE99CEDCD9785E9E2103240D40A1AB6050AB002080112102CE5CCF4" + "2200D6B5BCCF33D7CC2D9C7018EAD1B88D05228E023082010A0282010100" + "BE1B661EEC4700DF4B0C83292D02AE029B8A224DD3048125049F74E30E12" + "57FC2BE8D9CFAF0BFFCACAF7305351771C78FA451F13AF5EEBFB360941A4" + "396A805833730DC6E534C62408B7C5076FC22568021C59ED34F98487196D" + "A32078DAFCA37C7CFB8E79612FA384963DF2167D5E87305D7BC92D621C10" + "160672C27B9A691B1534F60D78C5893E40C5FF8A3F9DF8898612E9A5CCB5" + "6F4A0CC2A61A7AEB04A9DCC015D9BC37DEF2AB9EAA9AAFD838869081D924" + "9755F129BB0DBECA3B894975A65A36FD005CE77CD407E925D3172E33122A" + "11D327968A08F8E771FAEB2540EB52D17C4906405F47C31F60F0AF6C78AF" + "53291B236E692B506A2AF92AF43E3A81020301000128800130011280033A" + "08A60418E5C81B8D71A5C0A28C26999FF4FA992E14107CA8A9E6A2B36259" + "834000FE35DCD814426F9C5D332418ED94C9C0C992217B1B6DC01C99085A" + "3C3956C8267B8773BABCF3F2C841C67D830F9DBC780DD68BF4E2FE424C6A" + "54123BE4B2A1F7E1F4DB58AB1164DAE9CF75C3392284A44B8CDB85D837E8" + "6C6B908243987E552C8693878C9A1B7BEA3759783036F1595C406D6CBBA7" + "F8642A9B3B244DAA1F00531D0B908ADE4B533FD9FAFA21D0FB0C033D2AD5" + "DDF24C60F4FAC30820758877F2E1A78EB44E9336DCFAFDF572BB22A84A5D" + "EFDF2EB87B61DE26EE9C4CEAA646A2AFDB2BB953845E6D7FE6F79A9501D1" + "C379C96961316B5D2A66F38C222091AF74141B6CAF93507485A5D8F82808" + "025451824F00C8B6A0CD5803F6564584138C8B18BC679B442D837307B5CC" + "90B1FD1FD32288B4A5D18D2D80E5E6A7A9EFD255B8B363038BCC67AF534E" + "AEE4A5903E304EED4990BB5BE735DB027A6DE35329D321EC051B956C55A5" + "B11674017517FCC3C7FF7397C13A7B7087A1F6AEC7F6761A130A0C636F6D" + "70616E795F6E616D6512034C47451A150A0A6D6F64656C5F6E616D651207" + "4E6578757320341A200A116172636869746563747572655F6E616D65120B" + "61726D656162692D7637611A130A0B6465766963655F6E616D6512046D61" + "6B6F1A150A0C70726F647563745F6E616D6512056F6363616D1A440A0A62" + "75696C645F696E666F1236676F6F676C652F6F6363616D2F6D616B6F3A34" + "2E332F4A425F4D52322F3731343239313A7573657264656275672F646576" + "2D6B6579731A210A096465766963655F696412144C474D43323031323130" + "3035303036333932373812250A230A140801121030313233343536373839" + "41424344454610021A09393837363534333231180120002A0C3838373031" + "3633350000000030151A80023F7318E29C5A50C8ADAA4B09ADCD97B75588" + "B17002C5C2BC9AFA35C53098AF22DF5CC300407CD2E84EBE01911C785513" + "649E2CCF4E429020D3B93F3A54748C11ECFF4D62F562A4D3E96812F663D4" + "F761C00C3E88ABD8A1DC10E017A44DD3E040775FED5F07649090D1142C9D" + "21373CD604219E24935E10F287F20B0E080FDF76B6096B24F82A3E37850D" + "E229DE33EBCE7A0FA53F652C33007EA7027F95A44C36D04CBD676EB5C0BF" + "69508F45E0C3220D1706B0B851B3FCAF7AC2370EAD80C5D1620887633A42" + "024862FCEA9F95A719AAB989C1923C6452ECB0B75AF1CAFBFB06C5EC31BB" + "F0EE4D16ACCC9AF05B77D61C4855491B3D4AC150F3BCB7AE536AF33322F6" + "04080212CC020A190A093938373635343332311208F97F2B3856CBB3DD20" + "022800124108011801301E4239687474703A2F2F6B697230336663706731" + "37342E7769646576696E652E6E65742F7769646576696E652F6367692D62" + "696E2F64726D2E6367691A661210C5C43FE0178AEE7B85042F749D5A4025" + "1A5013A1501E0F90A64E103336944A37BAAAEAC17E46E880DF6EA23A7A89" + "0DA082CBBF82710B8C3982E8AB25A208A89EEFB5250D4B2CCC2F362856E0" + "5D1941E387801A19886B1F3AAE60D06EDA400087B06920011A7E0A103031" + "32333435363738394142434445461210A34D2B04D596DFE1DC29CFDF116E" + "39211A2031AD1B369D225842A14B5D5F8366F5FF8EB94AA7CD13EB45BA72" + "9168E19D5F5F200242340A20A0D6D65CC677C12B86A7A99F89F446BCFDA1" + "8544B15B2FEF8349ED5C247F7BE91210ED8D58320B0F4F948F960C7D4987" + "2CDE2083E5A68F051A207481A2B82C83DF3090D57EDC042711A42CF4F87E" + "79CE136DAFE25F48F4A9068322800256113CA771F4250CAD2928161D07B5" + "2561019003DBFBD362F20587D51BD999D57D2B035BC115C54C8B4BC37661" + "A66A101DE5B42D82E582309AFD8E211C947A2D33CAFB58F89EEE2DA95246" + "140311134429D8A5D15E03A169B0EB2579DA3BD6E4322D6C46EE964F6931" + "CF9DA52FB59B1D3B9BCC5959211CC23D97690FA8E869ADF68BCDA8A1211D" + "DBEBF967617AF0BFDA73E0AE79D8A7CCED208602EDC72CEF44A02901A52E" + "EB87CF9841D186BC95A65956BAD48F3C9E43F027CC03B73DFF5CAFC0B647" + "27E2D7B3A9CF25F97C475207C8A9DF091A585288A71AE64B7B2089871F72" + "72381CCBEF55EBF3DCB21B134FE48BFD5299DCCA6B01B55EEA61F9F990D0" + "AF2A5708011231121D1A1B0A190A093938373635343332311208F97F2B38" + "56CBB3DD2002280018022A0C33333932383235393733000030151A209ADE" + "9B0A411583962BDA31BE5BE937E589BB3DCC06F6F4C48FBE4FAE86DC9ABA" + "32610802123B0A190A093938373635343332311208F97F2B3856CBB3DD20" + "02280112001A16200342120A106B63746C00000000CA3A6A750000000020" + "83E5A68F051A20BDA6A56F7CBFD0942198F87C23A34AA5CBD64AFEB13427" + "7774CCF8E789D815DD3A5168747470733A2F2F7777772E796F7574756265" + "2E636F6D2F6170692F64726D2F7769646576696E653F766964656F5F6964" + "3D3033363831323632646334313263303626736F757263653D594F555455" + "42451220EC449C6B026C43004743061B3A3DCB7208B2AD11600254841B96" + "1CFA1AD57172")}, + + // license 2 + {"ksidE8C37662C88DC673", DeviceFiles::kLicenseStateReleasing, + wvcdm::a2bs_hex("0801121030313233343536373839414243444546"), + wvcdm::a2bs_hex( + "080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" + "C4D07A7D507618A5D3A68F05228E023082010A0282010100A947904B8DBD" + "55FB685FDB3025574517CCCC74EE4FEAF6629D5179A52FF85CE7409528EF" + "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" + "021A09393837363534333231180120002A0C313038313531363936380000" + "30151A80027EA7ADEF77500FBC6A6081E739E0C50E1BDE6DE4AB39110938" + "6768A95A04A52BE6693A5A98A25AC8EB9CDD6F40DCCF86A3DA6C700E256A" + "676BD3D7E492090DCF732C57333D9370F6D7AB87661701597099CD45C2BC" + "DFF1D47183E510D7A6D3561EFC7D4EB21814CB2CA0777F26DD491B4D0146" + "9BB81A701545E2D3E98E1ADAB3A3BBD1D0433B312B3B5139E88D3A92520B" + "A399B2BE3489A72C3629745E4D8FC6DF6C8925A8FD8D6C809CA80DBC2903" + "0615A55523305BC64DDFF52A87BD0DE9EEAB6445C5A1847E5E6FE8D640C7" + "B07F3B066B911793F06E973A02FA6EDD274570C4CA982D353F1E72A5B776" + "95D554B4FB554B46F5FA5B3B00805C136A9ED21FC2"), + wvcdm::a2bs_hex( + "080212CC020A190A0939383736353433323112087AD49366C8D919132002" + "2800124108011801301E4239687474703A2F2F6B69723033666370673137" + "342E7769646576696E652E6E65742F7769646576696E652F6367692D6269" + "6E2F64726D2E6367691A6612109161841718D5D0A4C4368820F4D030721A" + "500F94F9BC0FF6B730709C6DEFD88D1CA8C7991A149D470493BDAD89E333" + "AFC949F77D995CEA5E3D3DA5F7DF84E90CD4A9B4E138EA5F7EA75A520A25" + "017D69A9460D46548259F82959304CDEFE41936BE420011A7E0A10303132" + "3334353637383941424344454612104F88BFEECE468B962BF09EA1257DA5" + "0B1A200D48C122E022033C3E67A6ED4DA99B8AEA6F4B9E78634A548C060F" + "49D39D9700200242340A209DE408B6F116F428C8E801C63AF34570A6C31D" + "72180AA11F85D8DD4BC1C4D35412104E73935C2CC38C21408C537B3A5F19" + "8B2081E7A68F051A20BC2696A2A1FBDF425675CAD455DEA2B44040D1F8F0" + "B6C675A28384CACFDF2F132280022D09FDA096972AA77FFEB09EA08AE882" + "E89AC8591B398452CFB1383CCA16611571E223FE8DE82CDE9111557B2A87" + "A253B87B822F037FB492DE4B91B8AD4DB2E2F8B2E81BF1DE36CC7520CB4B" + "B3516E18322777287310257F2EC7110332504756DA8BC873448E93BA05FD" + "1AEB7AD1016D7BBB7FF5E7111987005322E342679F3D241429AE930A479D" + "9F338699D3D6969A6479D1363AEB4AF19BDE9A73B33CD0EBFCF272FCEEC6" + "222AC08DCBD36077E0459D940BAE84ABA584700C02E70F3AE034ED7B764C" + "6EE5E85663D657270C9AB40D3109920AB1C1C5DA1358E384EDF673253C04" + "F20AA6B0CC98F421A4CD86C4C88042B0DE9902D5D00B6AD817B1A313ED5B"), + wvcdm::a2bs_hex( + "08011231121D1A1B0A190A0939383736353433323112087AD49366C8D919" + "132002280018022A0C35333631323234343600000030151A208CC3C7D328" + "DFACD43764C9FB582B858C8FF1D9863FF59C4D983478DB858AC32A"), + wvcdm::a2bs_hex( + "0802123B0A190A0939383736353433323112087AD49366C8D91913200228" + "0112001A16200342120A106B63746C000000001FF4944E000000002082E7" + "A68F051A2041EF0A9267D613D17AA90E1D1DA5BE091860E5E296D41D6D0F" + "75E73660C279B3"), + "https://test.google.com/license/GetCencLicense", + wvcdm::a2bs_hex( + "0A9F15080210012298150802121408011210303132333435363738394142" + "434445461A9D0E080112950C0AD70B080112EF090AB002080212103E560E" + "C5335E346F591BC4D07A7D507618A5D3A68F05228E023082010A02820101" + "00A947904B8DBD55FB685FDB3025574517CCCC74EE4FEAF6629D5179A52F" + "F85CE7409528EFFA0E5DFC3DE9A34BA5F08BE349553C319A9FB274905A87" + "70ADC9CA4A2CBCD8E556A1587FA18BFD4D286C644A6904F19EAAFBDFADD3" + "B371B306D0B289F459B491C814B5AD1F747610E990A60248A7DA5152F1CC" + "FC047EF42300131F9C4758F4D9F30579393B860AAD9AD2EE43D721D6DB9F" + "5800EF188386B94825AE05A883AC976D6970DF43EA6C83B86CE6D0F54020" + "7725B9890FCCEC83A49027872DAFD2740B7748E9C3B1752D6F12859CED07" + "E8882969B433EC66F17FFC29AC6FDBEA79230B0FAED5D94CF6B829A420BB" + "E3270323941776EE60DD6BFD660BDDCA8702030100012880013001128002" + "50D1F8B1ECF849B60FF93E37C4DEEF09E6FFB10BCFC996A4A24B7AA96928" + "835ED5A72E15846D0A14015733239BD8B6E6D5E5D229B08394CE1E0692C1" + "59C44337CA7CAF88476449B068D9D2FADED8EB1BC0F4B8F0FCAF293E8690" + "E74032095341803408A0E8279E545945EE97838FDE7812F7171C3CC4F5EC" + "F9418BBF1D336CE58F4CBB1B44D4ADE6BF3364BAE7EC093281846E569E13" + "E7719014030A6059044FE7BBFF3E8F5723AEDD54DC6E0D041B309D7700B5" + "5575690E95CE6085D0914F991C5F45E98CBB9C45BA33F47FD0862EBCC7EE" + "BA8E60643C86E85476B18AEF8DE871571A75681A75F75028A5B58751C09A" + "5296AAE99CEDCD9785E9E2103240D40A1AB6050AB002080112102CE5CCF4" + "2200D6B5BCCF33D7CC2D9C7018EAD1B88D05228E023082010A0282010100" + "BE1B661EEC4700DF4B0C83292D02AE029B8A224DD3048125049F74E30E12" + "57FC2BE8D9CFAF0BFFCACAF7305351771C78FA451F13AF5EEBFB360941A4" + "396A805833730DC6E534C62408B7C5076FC22568021C59ED34F98487196D" + "A32078DAFCA37C7CFB8E79612FA384963DF2167D5E87305D7BC92D621C10" + "160672C27B9A691B1534F60D78C5893E40C5FF8A3F9DF8898612E9A5CCB5" + "6F4A0CC2A61A7AEB04A9DCC015D9BC37DEF2AB9EAA9AAFD838869081D924" + "9755F129BB0DBECA3B894975A65A36FD005CE77CD407E925D3172E33122A" + "11D327968A08F8E771FAEB2540EB52D17C4906405F47C31F60F0AF6C78AF" + "53291B236E692B506A2AF92AF43E3A81020301000128800130011280033A" + "08A60418E5C81B8D71A5C0A28C26999FF4FA992E14107CA8A9E6A2B36259" + "834000FE35DCD814426F9C5D332418ED94C9C0C992217B1B6DC01C99085A" + "3C3956C8267B8773BABCF3F2C841C67D830F9DBC780DD68BF4E2FE424C6A" + "54123BE4B2A1F7E1F4DB58AB1164DAE9CF75C3392284A44B8CDB85D837E8" + "6C6B908243987E552C8693878C9A1B7BEA3759783036F1595C406D6CBBA7" + "F8642A9B3B244DAA1F00531D0B908ADE4B533FD9FAFA21D0FB0C033D2AD5" + "DDF24C60F4FAC30820758877F2E1A78EB44E9336DCFAFDF572BB22A84A5D" + "EFDF2EB87B61DE26EE9C4CEAA646A2AFDB2BB953845E6D7FE6F79A9501D1" + "C379C96961316B5D2A66F38C222091AF74141B6CAF93507485A5D8F82808" + "025451824F00C8B6A0CD5803F6564584138C8B18BC679B442D837307B5CC" + "90B1FD1FD32288B4A5D18D2D80E5E6A7A9EFD255B8B363038BCC67AF534E" + "AEE4A5903E304EED4990BB5BE735DB027A6DE35329D321EC051B956C55A5" + "B11674017517FCC3C7FF7397C13A7B7087A1F6AEC7F6761A130A0C636F6D" + "70616E795F6E616D6512034C47451A150A0A6D6F64656C5F6E616D651207" + "4E6578757320341A200A116172636869746563747572655F6E616D65120B" + "61726D656162692D7637611A130A0B6465766963655F6E616D6512046D61" + "6B6F1A150A0C70726F647563745F6E616D6512056F6363616D1A440A0A62" + "75696C645F696E666F1236676F6F676C652F6F6363616D2F6D616B6F3A34" + "2E332F4A425F4D52322F3731343239313A7573657264656275672F646576" + "2D6B6579731A210A096465766963655F696412144C474D43323031323130" + "3035303036333932373812250A230A140801121030313233343536373839" + "41424344454610021A09393837363534333231180120002A0C3130383135" + "3136393638000030151A80027EA7ADEF77500FBC6A6081E739E0C50E1BDE" + "6DE4AB391109386768A95A04A52BE6693A5A98A25AC8EB9CDD6F40DCCF86" + "A3DA6C700E256A676BD3D7E492090DCF732C57333D9370F6D7AB87661701" + "597099CD45C2BCDFF1D47183E510D7A6D3561EFC7D4EB21814CB2CA0777F" + "26DD491B4D01469BB81A701545E2D3E98E1ADAB3A3BBD1D0433B312B3B51" + "39E88D3A92520BA399B2BE3489A72C3629745E4D8FC6DF6C8925A8FD8D6C" + "809CA80DBC29030615A55523305BC64DDFF52A87BD0DE9EEAB6445C5A184" + "7E5E6FE8D640C7B07F3B066B911793F06E973A02FA6EDD274570C4CA982D" + "353F1E72A5B77695D554B4FB554B46F5FA5B3B00805C136A9ED21FC222F6" + "04080212CC020A190A0939383736353433323112087AD49366C8D9191320" + "022800124108011801301E4239687474703A2F2F6B697230336663706731" + "37342E7769646576696E652E6E65742F7769646576696E652F6367692D62" + "696E2F64726D2E6367691A6612109161841718D5D0A4C4368820F4D03072" + "1A500F94F9BC0FF6B730709C6DEFD88D1CA8C7991A149D470493BDAD89E3" + "33AFC949F77D995CEA5E3D3DA5F7DF84E90CD4A9B4E138EA5F7EA75A520A" + "25017D69A9460D46548259F82959304CDEFE41936BE420011A7E0A103031" + "323334353637383941424344454612104F88BFEECE468B962BF09EA1257D" + "A50B1A200D48C122E022033C3E67A6ED4DA99B8AEA6F4B9E78634A548C06" + "0F49D39D9700200242340A209DE408B6F116F428C8E801C63AF34570A6C3" + "1D72180AA11F85D8DD4BC1C4D35412104E73935C2CC38C21408C537B3A5F" + "198B2081E7A68F051A20BC2696A2A1FBDF425675CAD455DEA2B44040D1F8" + "F0B6C675A28384CACFDF2F132280022D09FDA096972AA77FFEB09EA08AE8" + "82E89AC8591B398452CFB1383CCA16611571E223FE8DE82CDE9111557B2A" + "87A253B87B822F037FB492DE4B91B8AD4DB2E2F8B2E81BF1DE36CC7520CB" + "4BB3516E18322777287310257F2EC7110332504756DA8BC873448E93BA05" + "FD1AEB7AD1016D7BBB7FF5E7111987005322E342679F3D241429AE930A47" + "9D9F338699D3D6969A6479D1363AEB4AF19BDE9A73B33CD0EBFCF272FCEE" + "C6222AC08DCBD36077E0459D940BAE84ABA584700C02E70F3AE034ED7B76" + "4C6EE5E85663D657270C9AB40D3109920AB1C1C5DA1358E384EDF673253C" + "04F20AA6B0CC98F421A4CD86C4C88042B0DE9902D5D00B6AD817B1A313ED" + "5B2A5708011231121D1A1B0A190A0939383736353433323112087AD49366" + "C8D919132002280018022A0C35333631323234343600000030151A208CC3" + "C7D328DFACD43764C9FB582B858C8FF1D9863FF59C4D983478DB858AC32A" + "32610802123B0A190A0939383736353433323112087AD49366C8D9191320" + "02280112001A16200342120A106B63746C000000001FF4944E0000000020" + "82E7A68F051A2041EF0A9267D613D17AA90E1D1DA5BE091860E5E296D41D" + "6D0F75E73660C279B33A29687474703A2F2F68616D69642E6B69722E636F" + "72702E676F6F676C652E636F6D3A383838382F64726D12205CD2C43C618C" + "CA27BBCB2EEBDE32B57CBD51B424FD85DAB715B7F5A87546FD40")}}; + +// WHAT: Sample license data and related data for storage and +// use for offline playback. The license data and URLs +// in this test are not real. +// WHY: Test license-related functions. +LicenseInfo license_update_test_data[] = { + // active license + {"key_set_id_: ksid2A048BC7FAEC885A", DeviceFiles::kLicenseStateActive, + wvcdm::a2bs_hex("0801121030313233343536373839414243444546"), + wvcdm::a2bs_hex( + "080112950C0AD70B080112EF090AB002080212103E560EC5335E346F591B" + "C4D07A7D5076189EDFB68F05228E023082010A0282010100CC1715C81AD3" + "F6F279C686F826E6D7C8961EB13318367D06B4061BBC57E3C616A226A10F" + "042CAD54D44C6484C725CD721A2A97C088E60AFF0E9C8E03477A1B56B4C5" + "55C27CEAF55024375D8D3FB352DA4AAA2E911C876CB1B36162922E9130CC" + "C5FB72F8DD41D05DE6889C4814A7344BA8C605DE399CA3CBBF1E7DE3411E" + "DFC60F9D3802C0BEE2B98FB71A5AB9C1A3D53FB55599183B84FDDC98AC30" + "96B2EF99C62B545C5DCA3371F4D27DEF2052A23F13DE42DE46B462CED2AB" + "ABB96B610A47E0620AA10D862FEB66BB4F00B13DFE61703AE872F0B4850C" + "39138FC5DE4538E27BEAC8A48CC9526401BE3B42C7C6C5D9624662081D7A" + "5A1C581EB09619DD9DD3020301000128800130011280026AB9AC42F1C17C" + "1ECFB710BF2C35383F41CF7EFAB0DFDCC69090C20DE141CB43055FD707C6" + "11CDAEE700076A1EBA32432D5C2B62A73B8B1672AD2C4303598C02D34823" + "A6BE387046937F55BB65F5B3571FDC6A1F0D947031003BA651F8E48BF33D" + "66B7A32A72CAC75EF66EF280B2D4F14FBCA70ECC508091FE83AD886A680F" + "55AB62F306435BC0043825F6A401BB9C341230127D3298B67F82767050C9" + "5769964B0B5C27A36FA76ED161ABE4B6C18556C807706509A5146ADD958A" + "F79B49EDE48CBCD6320C4DEC0BF564C5DD7E7EBA37A4CD1D27F8D80E1B69" + "31C92AC8E5C3BEC0ADAE621A3B78952485EBFC81A194BA75BBD2C821C28A" + "EB5D21CBE0A270E55E1AB6050AB002080112102CE5CCF42200D6B5BCCF33" + "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" + "021A09393837363534333231180120002A0C333037383036303230340000" + "30151A8002B5CA9C6B097EF2CBE2F8136C761130F3456ED706127260151B" + "4FF044DE233C1828B8618A312C031A2F844BEF0917F9B8C6B8993A5D33E2" + "4B57B672A6C79D93EC98C46C5263EB8195FF7A5EBEBA08A6F1080C19340A" + "068E575568AE5EBADDD638FB435AC3EEC901E5F250BC974C498D6378C8BC" + "1F4BACCED5725B8B77160444923DA3B729DCB681186565B49EEFFE27CF16" + "31F09EC31E543AAFE9F5996FB0BEAA5F80305D67ECF173A8BD4A3B2CC75C" + "EC3AA5881FF433E80838E1E1FA23CE7F22346DDDF7FEC3DB0CE2C3F845CF" + "9471088A022C8D0A63860764AE558BD0B5F66D78881ADBF2D398F9BA349E" + "FB2532C61E243DD45BB11C99422D13A82B7AAE9671"), + wvcdm::a2bs_hex( + "080212CC020A190A09393837363534333231120892BE96420F0D5BF32002" + "2800124108011801301E4239687474703A2F2F6B69723033666370673137" + "342E7769646576696E652E6E65742F7769646576696E652F6367692D6269" + "6E2F64726D2E6367691A661210BB2E2D2B9C39697794897E08262749B41A" + "50C0DED62431B1701F59E076E07EB0D2D43AEC6C589B35790739EB0B0ED4" + "7236D0ECCE9A5408BE5F46F412334A5F4A4E3E493F202A263E185F06AE37" + "BA4351647BB9E6C997189FE1A03DCBF3FC90F46E5120011A7E0A10303132" + "333435363738394142434445461210319D7FB66154DFEC2AEDB164F29AAC" + "301A207448440734605CB29424FD1DA435A405DEE837757EA6A68C633A65" + "228317843D200242340A207F287706380C8085A4E5F85843D1C3B379F9CE" + "19ED5A2DAAF476B8AFE10488BF12100C8CDB1DA4C9FEBE5BBB530FE0D3DA" + "8720F4DFB68F051A20F4BCCEEEA658C5DD18D7B841E6D8991E616B57B592" + "C44ED67050939B136815272280025CD92AB4672778CB865D528A2EAAAD06" + "435AE9186F1C159AFA1689473C4D8C8A5B8C64400CBBD0A02659EA0271A1" + "F40052030CA285B9C7211791BDD72193D5E01CEE43B0482DEAF034C8E9BD" + "88C7331BFA5CD71C2A3062EBD07CE1C80CCF3C5D7EC2D921D1BC5414D797" + "0CB098889D3FB5BF669EE5283E009CDCC880E79C77A21B12C7C0B8062D66" + "CBDEC2DCFD23885144C776B98C8A7A176C4EA183085EF02D2060904ADA3C" + "B161F4D198A0279DA19D7001EB2D20C70ABAA04B3760CB165006F6CBA4BE" + "0A2628C0C8398C122FF0DCF9292590E3C37BC7DB20F3B0921268F41FE76B" + "D3EE764EBA13A22FDABC170860503FB93CC4A08D61102519D56A25EB9E30"), + wvcdm::a2bs_hex( + "08011231121D1A1B0A190A09393837363534333231120892BE96420F0D5B" + "F32002280018022A0C31393132353333373731000030151A20F4FDBECE54" + "7252D12BB9D488DAD50C76577A2FBCCC73F36D3C6B35096B8A3DC6"), + wvcdm::a2bs_hex( + "0802123B0A190A09393837363534333231120892BE96420F0D5BF3200228" + "0112001A16200342120A106B63746C0000000071FEF30B0000000020F4DF" + "B68F051A2000351030900858FCFD6977B67803ADFD1280AA661E6B0BD30B" + "08B2C467355129"), + "https://test.google.com/license/GetCencLicense", + wvcdm::a2bs_hex( + "0A9F15080210012298150801121408011210303132333435363738394142" + "434445461A9D0E080112950C0AD70B080112EF090AB002080212103E560E" + "C5335E346F591BC4D07A7D5076189EDFB68F05228E023082010A02820101" + "00CC1715C81AD3F6F279C686F826E6D7C8961EB13318367D06B4061BBC57" + "E3C616A226A10F042CAD54D44C6484C725CD721A2A97C088E60AFF0E9C8E" + "03477A1B56B4C555C27CEAF55024375D8D3FB352DA4AAA2E911C876CB1B3" + "6162922E9130CCC5FB72F8DD41D05DE6889C4814A7344BA8C605DE399CA3" + "CBBF1E7DE3411EDFC60F9D3802C0BEE2B98FB71A5AB9C1A3D53FB5559918" + "3B84FDDC98AC3096B2EF99C62B545C5DCA3371F4D27DEF2052A23F13DE42" + "DE46B462CED2ABABB96B610A47E0620AA10D862FEB66BB4F00B13DFE6170" + "3AE872F0B4850C39138FC5DE4538E27BEAC8A48CC9526401BE3B42C7C6C5" + "D9624662081D7A5A1C581EB09619DD9DD302030100012880013001128002" + "6AB9AC42F1C17C1ECFB710BF2C35383F41CF7EFAB0DFDCC69090C20DE141" + "CB43055FD707C611CDAEE700076A1EBA32432D5C2B62A73B8B1672AD2C43" + "03598C02D34823A6BE387046937F55BB65F5B3571FDC6A1F0D947031003B" + "A651F8E48BF33D66B7A32A72CAC75EF66EF280B2D4F14FBCA70ECC508091" + "FE83AD886A680F55AB62F306435BC0043825F6A401BB9C341230127D3298" + "B67F82767050C95769964B0B5C27A36FA76ED161ABE4B6C18556C8077065" + "09A5146ADD958AF79B49EDE48CBCD6320C4DEC0BF564C5DD7E7EBA37A4CD" + "1D27F8D80E1B6931C92AC8E5C3BEC0ADAE621A3B78952485EBFC81A194BA" + "75BBD2C821C28AEB5D21CBE0A270E55E1AB6050AB002080112102CE5CCF4" + "2200D6B5BCCF33D7CC2D9C7018EAD1B88D05228E023082010A0282010100" + "BE1B661EEC4700DF4B0C83292D02AE029B8A224DD3048125049F74E30E12" + "57FC2BE8D9CFAF0BFFCACAF7305351771C78FA451F13AF5EEBFB360941A4" + "396A805833730DC6E534C62408B7C5076FC22568021C59ED34F98487196D" + "A32078DAFCA37C7CFB8E79612FA384963DF2167D5E87305D7BC92D621C10" + "160672C27B9A691B1534F60D78C5893E40C5FF8A3F9DF8898612E9A5CCB5" + "6F4A0CC2A61A7AEB04A9DCC015D9BC37DEF2AB9EAA9AAFD838869081D924" + "9755F129BB0DBECA3B894975A65A36FD005CE77CD407E925D3172E33122A" + "11D327968A08F8E771FAEB2540EB52D17C4906405F47C31F60F0AF6C78AF" + "53291B236E692B506A2AF92AF43E3A81020301000128800130011280033A" + "08A60418E5C81B8D71A5C0A28C26999FF4FA992E14107CA8A9E6A2B36259" + "834000FE35DCD814426F9C5D332418ED94C9C0C992217B1B6DC01C99085A" + "3C3956C8267B8773BABCF3F2C841C67D830F9DBC780DD68BF4E2FE424C6A" + "54123BE4B2A1F7E1F4DB58AB1164DAE9CF75C3392284A44B8CDB85D837E8" + "6C6B908243987E552C8693878C9A1B7BEA3759783036F1595C406D6CBBA7" + "F8642A9B3B244DAA1F00531D0B908ADE4B533FD9FAFA21D0FB0C033D2AD5" + "DDF24C60F4FAC30820758877F2E1A78EB44E9336DCFAFDF572BB22A84A5D" + "EFDF2EB87B61DE26EE9C4CEAA646A2AFDB2BB953845E6D7FE6F79A9501D1" + "C379C96961316B5D2A66F38C222091AF74141B6CAF93507485A5D8F82808" + "025451824F00C8B6A0CD5803F6564584138C8B18BC679B442D837307B5CC" + "90B1FD1FD32288B4A5D18D2D80E5E6A7A9EFD255B8B363038BCC67AF534E" + "AEE4A5903E304EED4990BB5BE735DB027A6DE35329D321EC051B956C55A5" + "B11674017517FCC3C7FF7397C13A7B7087A1F6AEC7F6761A130A0C636F6D" + "70616E795F6E616D6512034C47451A150A0A6D6F64656C5F6E616D651207" + "4E6578757320341A200A116172636869746563747572655F6E616D65120B" + "61726D656162692D7637611A130A0B6465766963655F6E616D6512046D61" + "6B6F1A150A0C70726F647563745F6E616D6512056F6363616D1A440A0A62" + "75696C645F696E666F1236676F6F676C652F6F6363616D2F6D616B6F3A34" + "2E332F4A425F4D52322F3731343239313A7573657264656275672F646576" + "2D6B6579731A210A096465766963655F696412144C474D43323031323130" + "3035303036333932373812250A230A140801121030313233343536373839" + "41424344454610021A09393837363534333231180120002A0C3330373830" + "3630323034000030151A8002B5CA9C6B097EF2CBE2F8136C761130F3456E" + "D706127260151B4FF044DE233C1828B8618A312C031A2F844BEF0917F9B8" + "C6B8993A5D33E24B57B672A6C79D93EC98C46C5263EB8195FF7A5EBEBA08" + "A6F1080C19340A068E575568AE5EBADDD638FB435AC3EEC901E5F250BC97" + "4C498D6378C8BC1F4BACCED5725B8B77160444923DA3B729DCB681186565" + "B49EEFFE27CF1631F09EC31E543AAFE9F5996FB0BEAA5F80305D67ECF173" + "A8BD4A3B2CC75CEC3AA5881FF433E80838E1E1FA23CE7F22346DDDF7FEC3" + "DB0CE2C3F845CF9471088A022C8D0A63860764AE558BD0B5F66D78881ADB" + "F2D398F9BA349EFB2532C61E243DD45BB11C99422D13A82B7AAE967122F6" + "04080212CC020A190A09393837363534333231120892BE96420F0D5BF320" + "022800124108011801301E4239687474703A2F2F6B697230336663706731" + "37342E7769646576696E652E6E65742F7769646576696E652F6367692D62" + "696E2F64726D2E6367691A661210BB2E2D2B9C39697794897E08262749B4" + "1A50C0DED62431B1701F59E076E07EB0D2D43AEC6C589B35790739EB0B0E" + "D47236D0ECCE9A5408BE5F46F412334A5F4A4E3E493F202A263E185F06AE" + "37BA4351647BB9E6C997189FE1A03DCBF3FC90F46E5120011A7E0A103031" + "32333435363738394142434445461210319D7FB66154DFEC2AEDB164F29A" + "AC301A207448440734605CB29424FD1DA435A405DEE837757EA6A68C633A" + "65228317843D200242340A207F287706380C8085A4E5F85843D1C3B379F9" + "CE19ED5A2DAAF476B8AFE10488BF12100C8CDB1DA4C9FEBE5BBB530FE0D3" + "DA8720F4DFB68F051A20F4BCCEEEA658C5DD18D7B841E6D8991E616B57B5" + "92C44ED67050939B136815272280025CD92AB4672778CB865D528A2EAAAD" + "06435AE9186F1C159AFA1689473C4D8C8A5B8C64400CBBD0A02659EA0271" + "A1F40052030CA285B9C7211791BDD72193D5E01CEE43B0482DEAF034C8E9" + "BD88C7331BFA5CD71C2A3062EBD07CE1C80CCF3C5D7EC2D921D1BC5414D7" + "970CB098889D3FB5BF669EE5283E009CDCC880E79C77A21B12C7C0B8062D" + "66CBDEC2DCFD23885144C776B98C8A7A176C4EA183085EF02D2060904ADA" + "3CB161F4D198A0279DA19D7001EB2D20C70ABAA04B3760CB165006F6CBA4" + "BE0A2628C0C8398C122FF0DCF9292590E3C37BC7DB20F3B0921268F41FE7" + "6BD3EE764EBA13A22FDABC170860503FB93CC4A08D61102519D56A25EB9E" + "302A5708011231121D1A1B0A190A09393837363534333231120892BE9642" + "0F0D5BF32002280018022A0C31393132353333373731000030151A20F4FD" + "BECE547252D12BB9D488DAD50C76577A2FBCCC73F36D3C6B35096B8A3DC6" + "32610802123B0A190A09393837363534333231120892BE96420F0D5BF320" + "02280112001A16200342120A106B63746C0000000071FEF30B0000000020" + "F4DFB68F051A2000351030900858FCFD6977B67803ADFD1280AA661E6B0B" + "D30B08B2C4673551293A29687474703A2F2F68616D69642E6B69722E636F" + "72702E676F6F676C652E636F6D3A383838382F64726D1220E9BF6AE79B64" + "B788838B5EDDEBEF9E20FD8CFFDEB037DEFEE982DF21A2D32031")}, + + // license being released. all fields are identical except for license + // state and hashed file data + {"", DeviceFiles::kLicenseStateReleasing, "", "", "", "", "", "", + wvcdm::a2bs_hex( + "0A9F15080210012298150802121408011210303132333435363738394142" + "434445461A9D0E080112950C0AD70B080112EF090AB002080212103E560E" + "C5335E346F591BC4D07A7D5076189EDFB68F05228E023082010A02820101" + "00CC1715C81AD3F6F279C686F826E6D7C8961EB13318367D06B4061BBC57" + "E3C616A226A10F042CAD54D44C6484C725CD721A2A97C088E60AFF0E9C8E" + "03477A1B56B4C555C27CEAF55024375D8D3FB352DA4AAA2E911C876CB1B3" + "6162922E9130CCC5FB72F8DD41D05DE6889C4814A7344BA8C605DE399CA3" + "CBBF1E7DE3411EDFC60F9D3802C0BEE2B98FB71A5AB9C1A3D53FB5559918" + "3B84FDDC98AC3096B2EF99C62B545C5DCA3371F4D27DEF2052A23F13DE42" + "DE46B462CED2ABABB96B610A47E0620AA10D862FEB66BB4F00B13DFE6170" + "3AE872F0B4850C39138FC5DE4538E27BEAC8A48CC9526401BE3B42C7C6C5" + "D9624662081D7A5A1C581EB09619DD9DD302030100012880013001128002" + "6AB9AC42F1C17C1ECFB710BF2C35383F41CF7EFAB0DFDCC69090C20DE141" + "CB43055FD707C611CDAEE700076A1EBA32432D5C2B62A73B8B1672AD2C43" + "03598C02D34823A6BE387046937F55BB65F5B3571FDC6A1F0D947031003B" + "A651F8E48BF33D66B7A32A72CAC75EF66EF280B2D4F14FBCA70ECC508091" + "FE83AD886A680F55AB62F306435BC0043825F6A401BB9C341230127D3298" + "B67F82767050C95769964B0B5C27A36FA76ED161ABE4B6C18556C8077065" + "09A5146ADD958AF79B49EDE48CBCD6320C4DEC0BF564C5DD7E7EBA37A4CD" + "1D27F8D80E1B6931C92AC8E5C3BEC0ADAE621A3B78952485EBFC81A194BA" + "75BBD2C821C28AEB5D21CBE0A270E55E1AB6050AB002080112102CE5CCF4" + "2200D6B5BCCF33D7CC2D9C7018EAD1B88D05228E023082010A0282010100" + "BE1B661EEC4700DF4B0C83292D02AE029B8A224DD3048125049F74E30E12" + "57FC2BE8D9CFAF0BFFCACAF7305351771C78FA451F13AF5EEBFB360941A4" + "396A805833730DC6E534C62408B7C5076FC22568021C59ED34F98487196D" + "A32078DAFCA37C7CFB8E79612FA384963DF2167D5E87305D7BC92D621C10" + "160672C27B9A691B1534F60D78C5893E40C5FF8A3F9DF8898612E9A5CCB5" + "6F4A0CC2A61A7AEB04A9DCC015D9BC37DEF2AB9EAA9AAFD838869081D924" + "9755F129BB0DBECA3B894975A65A36FD005CE77CD407E925D3172E33122A" + "11D327968A08F8E771FAEB2540EB52D17C4906405F47C31F60F0AF6C78AF" + "53291B236E692B506A2AF92AF43E3A81020301000128800130011280033A" + "08A60418E5C81B8D71A5C0A28C26999FF4FA992E14107CA8A9E6A2B36259" + "834000FE35DCD814426F9C5D332418ED94C9C0C992217B1B6DC01C99085A" + "3C3956C8267B8773BABCF3F2C841C67D830F9DBC780DD68BF4E2FE424C6A" + "54123BE4B2A1F7E1F4DB58AB1164DAE9CF75C3392284A44B8CDB85D837E8" + "6C6B908243987E552C8693878C9A1B7BEA3759783036F1595C406D6CBBA7" + "F8642A9B3B244DAA1F00531D0B908ADE4B533FD9FAFA21D0FB0C033D2AD5" + "DDF24C60F4FAC30820758877F2E1A78EB44E9336DCFAFDF572BB22A84A5D" + "EFDF2EB87B61DE26EE9C4CEAA646A2AFDB2BB953845E6D7FE6F79A9501D1" + "C379C96961316B5D2A66F38C222091AF74141B6CAF93507485A5D8F82808" + "025451824F00C8B6A0CD5803F6564584138C8B18BC679B442D837307B5CC" + "90B1FD1FD32288B4A5D18D2D80E5E6A7A9EFD255B8B363038BCC67AF534E" + "AEE4A5903E304EED4990BB5BE735DB027A6DE35329D321EC051B956C55A5" + "B11674017517FCC3C7FF7397C13A7B7087A1F6AEC7F6761A130A0C636F6D" + "70616E795F6E616D6512034C47451A150A0A6D6F64656C5F6E616D651207" + "4E6578757320341A200A116172636869746563747572655F6E616D65120B" + "61726D656162692D7637611A130A0B6465766963655F6E616D6512046D61" + "6B6F1A150A0C70726F647563745F6E616D6512056F6363616D1A440A0A62" + "75696C645F696E666F1236676F6F676C652F6F6363616D2F6D616B6F3A34" + "2E332F4A425F4D52322F3731343239313A7573657264656275672F646576" + "2D6B6579731A210A096465766963655F696412144C474D43323031323130" + "3035303036333932373812250A230A140801121030313233343536373839" + "41424344454610021A09393837363534333231180120002A0C3330373830" + "3630323034000030151A8002B5CA9C6B097EF2CBE2F8136C761130F3456E" + "D706127260151B4FF044DE233C1828B8618A312C031A2F844BEF0917F9B8" + "C6B8993A5D33E24B57B672A6C79D93EC98C46C5263EB8195FF7A5EBEBA08" + "A6F1080C19340A068E575568AE5EBADDD638FB435AC3EEC901E5F250BC97" + "4C498D6378C8BC1F4BACCED5725B8B77160444923DA3B729DCB681186565" + "B49EEFFE27CF1631F09EC31E543AAFE9F5996FB0BEAA5F80305D67ECF173" + "A8BD4A3B2CC75CEC3AA5881FF433E80838E1E1FA23CE7F22346DDDF7FEC3" + "DB0CE2C3F845CF9471088A022C8D0A63860764AE558BD0B5F66D78881ADB" + "F2D398F9BA349EFB2532C61E243DD45BB11C99422D13A82B7AAE967122F6" + "04080212CC020A190A09393837363534333231120892BE96420F0D5BF320" + "022800124108011801301E4239687474703A2F2F6B697230336663706731" + "37342E7769646576696E652E6E65742F7769646576696E652F6367692D62" + "696E2F64726D2E6367691A661210BB2E2D2B9C39697794897E08262749B4" + "1A50C0DED62431B1701F59E076E07EB0D2D43AEC6C589B35790739EB0B0E" + "D47236D0ECCE9A5408BE5F46F412334A5F4A4E3E493F202A263E185F06AE" + "37BA4351647BB9E6C997189FE1A03DCBF3FC90F46E5120011A7E0A103031" + "32333435363738394142434445461210319D7FB66154DFEC2AEDB164F29A" + "AC301A207448440734605CB29424FD1DA435A405DEE837757EA6A68C633A" + "65228317843D200242340A207F287706380C8085A4E5F85843D1C3B379F9" + "CE19ED5A2DAAF476B8AFE10488BF12100C8CDB1DA4C9FEBE5BBB530FE0D3" + "DA8720F4DFB68F051A20F4BCCEEEA658C5DD18D7B841E6D8991E616B57B5" + "92C44ED67050939B136815272280025CD92AB4672778CB865D528A2EAAAD" + "06435AE9186F1C159AFA1689473C4D8C8A5B8C64400CBBD0A02659EA0271" + "A1F40052030CA285B9C7211791BDD72193D5E01CEE43B0482DEAF034C8E9" + "BD88C7331BFA5CD71C2A3062EBD07CE1C80CCF3C5D7EC2D921D1BC5414D7" + "970CB098889D3FB5BF669EE5283E009CDCC880E79C77A21B12C7C0B8062D" + "66CBDEC2DCFD23885144C776B98C8A7A176C4EA183085EF02D2060904ADA" + "3CB161F4D198A0279DA19D7001EB2D20C70ABAA04B3760CB165006F6CBA4" + "BE0A2628C0C8398C122FF0DCF9292590E3C37BC7DB20F3B0921268F41FE7" + "6BD3EE764EBA13A22FDABC170860503FB93CC4A08D61102519D56A25EB9E" + "302A5708011231121D1A1B0A190A09393837363534333231120892BE9642" + "0F0D5BF32002280018022A0C31393132353333373731000030151A20F4FD" + "BECE547252D12BB9D488DAD50C76577A2FBCCC73F36D3C6B35096B8A3DC6" + "32610802123B0A190A09393837363534333231120892BE96420F0D5BF320" + "02280112001A16200342120A106B63746C0000000071FEF30B0000000020" + "F4DFB68F051A2000351030900858FCFD6977B67803ADFD1280AA661E6B0B" + "D30B08B2C4673551293A29687474703A2F2F68616D69642E6B69722E636F" + "72702E676F6F676C652E636F6D3A383838382F64726D1220003650AD34C0" + "CB1D348F83B6694E4983DA8BCF9AEFAA4A4B23022DA08CF3DA44")}}; + +} // namespace + +class MockFile : public File { + public: + MOCK_METHOD2(Open, bool(const std::string&, int flags)); + MOCK_METHOD0(Close, void()); + + MOCK_METHOD2(Read, ssize_t(char*, size_t)); + MOCK_METHOD2(Write, ssize_t(const char*, size_t)); + + MOCK_METHOD1(Exists, bool(const std::string&)); + MOCK_METHOD1(Remove, bool(const std::string&)); + MOCK_METHOD2(Copy, bool(const std::string&, const std::string&)); + MOCK_METHOD2(List, bool(const std::string&, std::vector*)); + MOCK_METHOD1(CreateDirectory, bool(const std::string)); + MOCK_METHOD1(IsDirectory, bool(const std::string&)); + MOCK_METHOD1(IsRegularFile, bool(const std::string&)); + MOCK_METHOD1(FileSize, ssize_t(const std::string&)); +}; + +class DeviceFilesTest : public ::testing::Test { + protected: + virtual void SetUp() { + ASSERT_TRUE(Properties::GetDeviceFilesBasePath(kSecurityLevelL1, + &device_base_path_)); + } + + std::string GenerateRandomData(uint32_t len) { + std::string data(len, 0); + for (size_t i = 0; i < len; i++) { + data[i] = rand() % 256; + } + return data; + } + + size_t GetLicenseDataSize(LicenseInfo& data) { + return sizeof(DeviceFiles::LicenseState) + 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(); + } + + std::string device_base_path_; +}; + +class DeviceFilesStoreTest : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + +class DeviceFilesSecurityLevelTest + : public DeviceFilesTest, + public ::testing::WithParamInterface {}; + +MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; } +MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; } +MATCHER_P(IsStrEq, str, "") { + // Estimating the length of data. We can have gmock provide length + // as well as pointer to data but that will introduce a dependency on tr1 + return memcmp(arg, str.c_str(), str.size()) == 0; +} +MATCHER_P2(Contains, str1, str2, "") { + // Estimating the length of data. We can have gmock provide length + // as well as pointer to data but that will introduce a dependency on tr1 + std::string data(arg, str1.size() + str2.size() + kProtobufEstimatedLen); + return (data.find(str1) != std::string::npos && + data.find(str2) != std::string::npos); +} +MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") { + // Estimating the length of data. We can have gmock provide length + // as well as pointer to data but that will introduce a dependency on tr1 + std::string data(arg, str1.size() + str2.size() + str3.size() + str4.size() + + str5.size() + str6.size() + kProtobufEstimatedLen); + return (data.find(str1) != std::string::npos && + data.find(str2) != std::string::npos && + data.find(str3) != std::string::npos && + data.find(str4) != std::string::npos && + data.find(str5) != std::string::npos && + data.find(str6) != std::string::npos); +} + +TEST_P(DeviceFilesStoreTest, StoreCertificate) { + MockFile file; + std::string certificate(GenerateRandomData(kCertificateLen)); + std::string wrapped_private_key(GenerateRandomData(kWrappedKeyLen)); + std::string device_certificate_path = + device_base_path_ + DeviceFiles::GetCertificateFileName(); + + bool dir_exists = GetParam(); + EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) + .WillOnce(Return(dir_exists)); + if (dir_exists) { + EXPECT_CALL(file, CreateDirectory(_)).Times(0); + } else { + EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path_))) + .WillOnce(Return(true)); + } + + EXPECT_CALL(file, Open(StrEq(device_certificate_path), + AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) + .WillOnce(Return(true)); + EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key), + Gt(certificate.size() + wrapped_private_key.size()))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Close()).Times(1); + EXPECT_CALL(file, Read(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); +} + +INSTANTIATE_TEST_CASE_P(StoreCertificate, DeviceFilesStoreTest, + ::testing::Values(true, false)); + +TEST_F(DeviceFilesTest, ReadCertificate) { + MockFile file; + std::string device_certificate_path = + device_base_path_ + DeviceFiles::GetCertificateFileName(); + std::string data = a2bs_hex(kTestCertificateFileData); + + EXPECT_CALL(file, Exists(StrEq(device_certificate_path))) + .WillOnce(Return(true)); + EXPECT_CALL(file, FileSize(StrEq(device_certificate_path))) + .WillOnce(Return(data.size())); + EXPECT_CALL(file, Open(StrEq(device_certificate_path), IsBinaryFileFlagSet())) + .WillOnce(Return(true)); + EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll( + SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); + EXPECT_CALL(file, Close()).Times(1); + EXPECT_CALL(file, Write(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + + std::string certificate, wrapped_private_key; + ASSERT_TRUE( + device_files.RetrieveCertificate(&certificate, &wrapped_private_key)); + EXPECT_EQ(kTestCertificate, b2a_hex(certificate)); + EXPECT_EQ(kTestWrappedPrivateKey, b2a_hex(wrapped_private_key)); +} + +TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) { + MockFile file; + std::string certificate(GenerateRandomData(kCertificateLen)); + std::string wrapped_private_key(GenerateRandomData(kWrappedKeyLen)); + + CdmSecurityLevel security_level = GetParam(); + std::string device_base_path; + ASSERT_TRUE( + Properties::GetDeviceFilesBasePath(security_level, &device_base_path)); + std::string device_certificate_path = + device_base_path + DeviceFiles::GetCertificateFileName(); + + EXPECT_CALL(file, IsDirectory(StrEq(device_base_path))) + .WillOnce(Return(false)); + EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path))) + .WillOnce(Return(true)); + + EXPECT_CALL(file, Open(StrEq(device_certificate_path), + AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) + .WillOnce(Return(true)); + EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key), + Gt(certificate.size() + wrapped_private_key.size()))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Close()).Times(1); + EXPECT_CALL(file, Read(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, security_level)); + EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key)); +} + +INSTANTIATE_TEST_CASE_P(SecurityLevel, DeviceFilesSecurityLevelTest, + ::testing::Values(kSecurityLevelL1, kSecurityLevelL3)); + +TEST_P(DeviceFilesStoreTest, StoreLicense) { + MockFile file; + size_t license_num = 0; + std::string license_path = device_base_path_ + + license_test_data[license_num].key_set_id + + DeviceFiles::GetLicenseFileNameExtension(); + + bool dir_exists = GetParam(); + EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) + .WillOnce(Return(dir_exists)); + if (dir_exists) { + EXPECT_CALL(file, CreateDirectory(_)).Times(0); + } else { + EXPECT_CALL(file, CreateDirectory(StrEq(device_base_path_))) + .WillOnce(Return(true)); + } + + EXPECT_CALL(file, Open(StrEq(license_path), + AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) + .WillOnce(Return(true)); + EXPECT_CALL( + file, Write(Contains(license_test_data[license_num].pssh_data, + license_test_data[license_num].key_request, + license_test_data[license_num].key_response, + license_test_data[license_num].key_renewal_request, + license_test_data[license_num].key_renewal_response, + license_test_data[license_num].key_release_url), + Gt(GetLicenseDataSize(license_test_data[license_num])))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Close()).Times(1); + EXPECT_CALL(file, Read(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.StoreLicense( + license_test_data[license_num].key_set_id, + license_test_data[license_num].license_state, + license_test_data[license_num].pssh_data, + license_test_data[license_num].key_request, + license_test_data[license_num].key_response, + license_test_data[license_num].key_renewal_request, + license_test_data[license_num].key_renewal_response, + license_test_data[license_num].key_release_url)); +} + +INSTANTIATE_TEST_CASE_P(StoreLicense, DeviceFilesStoreTest, + ::testing::Values(true, false)); + +TEST_F(DeviceFilesTest, StoreLicenses) { + MockFile file; + EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))) + .Times(kNumberOfLicenses).WillRepeatedly(Return(true)); + EXPECT_CALL(file, CreateDirectory(_)).Times(0); + + for (size_t i = 0; i < kNumberOfLicenses; ++i) { + std::string license_path = device_base_path_ + + license_test_data[i].key_set_id + + DeviceFiles::GetLicenseFileNameExtension(); + + EXPECT_CALL(file, Open(StrEq(license_path), + AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) + .WillOnce(Return(true)); + + EXPECT_CALL(file, Write(Contains(license_test_data[i].pssh_data, + license_test_data[i].key_request, + license_test_data[i].key_response, + license_test_data[i].key_renewal_request, + license_test_data[i].key_renewal_response, + license_test_data[i].key_release_url), + Gt(GetLicenseDataSize(license_test_data[i])))) + .WillOnce(ReturnArg<1>()); + } + EXPECT_CALL(file, Close()).Times(kNumberOfLicenses); + EXPECT_CALL(file, Read(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + for (size_t i = 0; i < kNumberOfLicenses; i++) { + EXPECT_TRUE(device_files.StoreLicense( + license_test_data[i].key_set_id, license_test_data[i].license_state, + license_test_data[i].pssh_data, license_test_data[i].key_request, + license_test_data[i].key_response, + license_test_data[i].key_renewal_request, + license_test_data[i].key_renewal_response, + license_test_data[i].key_release_url)); + } +} + +TEST_F(DeviceFilesTest, RetrieveLicenses) { + MockFile file; + + for (size_t i = 0; i < kNumberOfLicenses; ++i) { + std::string license_path = device_base_path_ + + license_test_data[i].key_set_id + + DeviceFiles::GetLicenseFileNameExtension(); + + size_t size = license_test_data[i].file_data.size(); + + EXPECT_CALL(file, Exists(StrEq(license_path))).WillOnce(Return(true)); + EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); + EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) + .WillOnce(Return(true)); + EXPECT_CALL(file, Read(NotNull(), Eq(size))).WillOnce( + DoAll(SetArrayArgument<0>(license_test_data[i].file_data.begin(), + license_test_data[i].file_data.end()), + Return(size))); + } + EXPECT_CALL(file, Close()).Times(kNumberOfLicenses); + EXPECT_CALL(file, Write(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + DeviceFiles::LicenseState license_state; + CdmInitData pssh_data; + CdmKeyMessage key_request; + CdmKeyResponse key_response; + CdmKeyMessage key_renewal_request; + CdmKeyResponse key_renewal_response; + std::string release_server_url; + + for (size_t i = 0; i < kNumberOfLicenses; i++) { + DeviceFiles::LicenseState license_state; + EXPECT_TRUE(device_files.RetrieveLicense( + license_test_data[i].key_set_id, &license_state, &pssh_data, + &key_request, &key_response, &key_renewal_request, + &key_renewal_response, &release_server_url)); + EXPECT_EQ(license_test_data[i].license_state, license_state); + EXPECT_EQ(license_test_data[i].pssh_data, pssh_data); + EXPECT_EQ(license_test_data[i].key_request, key_request); + EXPECT_EQ(license_test_data[i].key_response, key_response); + EXPECT_EQ(license_test_data[i].key_request, key_request); + EXPECT_EQ(license_test_data[i].key_response, key_response); + } +} + +TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) { + MockFile file; + std::vector security_dirs; + EXPECT_TRUE(Properties::GetSecurityLevelDirectories(&security_dirs)); + + size_t pos = std::string::npos; + for (size_t i = 0; i < security_dirs.size(); ++i) { + pos = device_base_path_.rfind(security_dirs[i]); + if (std::string::npos != pos) break; + } + + EXPECT_NE(std::string::npos, pos); + + std::string base_path(device_base_path_, 0, pos); + std::vector old_files; + std::string new_path; + for (size_t i = 0; i < security_dirs.size(); ++i) { + old_files.push_back(security_dirs[i]); + new_path = base_path + security_dirs[i]; + EXPECT_CALL(file, IsRegularFile(StrEq(new_path))).WillOnce(Return(false)); + EXPECT_CALL(file, Exists(StrEq(new_path))) + .WillOnce(Return(false)) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file, CreateDirectory(StrEq(new_path))).WillOnce(Return(true)); + } + + std::string old_path = base_path + DeviceFiles::GetCertificateFileName(); + old_files.push_back(DeviceFiles::GetCertificateFileName()); + EXPECT_CALL(file, IsRegularFile(StrEq(old_path))) + .WillOnce(Return(true)); + EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true)); + for (size_t i = 0; i < security_dirs.size(); ++i) { + new_path = base_path + security_dirs[i] + + DeviceFiles::GetCertificateFileName(); + EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path))) + .WillOnce(Return(true)); + } + + for (size_t j = 0; j < kNumberOfLicenses; ++j) { + std::string file_name = license_test_data[j].key_set_id + + DeviceFiles::GetLicenseFileNameExtension(); + old_path = base_path + file_name; + old_files.push_back(file_name); + EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true)); + EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true)); + for (size_t i = 0; i < security_dirs.size(); ++i) { + new_path = base_path + security_dirs[i] + file_name; + EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path))) + .WillOnce(Return(true)); + } + } + + EXPECT_CALL(file, List(StrEq(base_path), NotNull())) + .WillOnce(DoAll(SetArgPointee<1>(old_files), Return(true))); + + std::string data = a2bs_hex(kTestCertificateFileData); + + new_path = device_base_path_ + DeviceFiles::GetCertificateFileName(); + EXPECT_CALL(file, Exists(StrEq(new_path))).WillOnce(Return(true)); + EXPECT_CALL(file, FileSize(_)).WillOnce(Return(data.size())); + EXPECT_CALL(file, Open(_, _)).WillOnce(Return(true)); + EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll( + SetArrayArgument<0>(data.begin(), data.end()), Return(data.size()))); + EXPECT_CALL(file, Close()).Times(1); + EXPECT_CALL(file, Write(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + + Properties::Init(); + std::string certificate, wrapped_private_key; + ASSERT_TRUE( + device_files.RetrieveCertificate(&certificate, &wrapped_private_key)); +} + +TEST_F(DeviceFilesTest, UpdateLicenseState) { + MockFile file; + std::string license_path = device_base_path_ + + license_update_test_data[0].key_set_id + + DeviceFiles::GetLicenseFileNameExtension(); + + EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))).Times(2) + .WillRepeatedly(Return(true)); + EXPECT_CALL(file, CreateDirectory(_)).Times(0); + EXPECT_CALL(file, Open(StrEq(license_path), + AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet()))) + .Times(2).WillRepeatedly(Return(true)); + EXPECT_CALL(file, Write(IsStrEq(license_update_test_data[0].file_data), + Eq(license_update_test_data[0].file_data.size()))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Write(IsStrEq(license_update_test_data[1].file_data), + Eq(license_update_test_data[1].file_data.size()))) + .WillOnce(ReturnArg<1>()); + EXPECT_CALL(file, Close()).Times(2); + EXPECT_CALL(file, Read(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + EXPECT_TRUE(device_files.StoreLicense( + license_update_test_data[0].key_set_id, + license_update_test_data[0].license_state, + license_update_test_data[0].pssh_data, + license_update_test_data[0].key_request, + license_update_test_data[0].key_response, + license_update_test_data[0].key_renewal_request, + license_update_test_data[0].key_renewal_response, + license_update_test_data[0].key_release_url)); + + EXPECT_TRUE(device_files.StoreLicense( + license_update_test_data[0].key_set_id, + license_update_test_data[1].license_state, + license_update_test_data[0].pssh_data, + license_update_test_data[0].key_request, + license_update_test_data[0].key_response, + license_update_test_data[0].key_renewal_request, + license_update_test_data[0].key_renewal_response, + license_update_test_data[0].key_release_url)); +} + +TEST_F(DeviceFilesTest, DeleteLicense) { + MockFile file; + std::string license_path = device_base_path_ + + license_test_data[0].key_set_id + + DeviceFiles::GetLicenseFileNameExtension(); + + size_t size = license_test_data[0].file_data.size(); + + EXPECT_CALL(file, Exists(StrEq(license_path))).Times(2).WillOnce(Return(true)) + .WillOnce(Return(false)); + EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size)); + EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet())) + .WillOnce(Return(true)); + EXPECT_CALL(file, Read(NotNull(), Eq(size))).WillOnce( + DoAll(SetArrayArgument<0>(license_test_data[0].file_data.begin(), + license_test_data[0].file_data.end()), + Return(size))); + EXPECT_CALL(file, Remove(StrEq(license_path))).WillOnce(Return(true)); + EXPECT_CALL(file, Close()).Times(1); + EXPECT_CALL(file, Write(_, _)).Times(0); + + DeviceFiles device_files; + EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1)); + DeviceFiles::LicenseState license_state; + CdmInitData pssh_data; + CdmKeyMessage key_request; + CdmKeyResponse key_response; + CdmKeyMessage key_renewal_request; + CdmKeyResponse key_renewal_response; + std::string release_server_url; + + EXPECT_TRUE(device_files.RetrieveLicense( + license_test_data[0].key_set_id, &license_state, &pssh_data, &key_request, + &key_response, &key_renewal_request, &key_renewal_response, + &release_server_url)); + EXPECT_EQ(license_test_data[0].license_state, license_state); + EXPECT_EQ(license_test_data[0].pssh_data, pssh_data); + EXPECT_EQ(license_test_data[0].key_request, key_request); + EXPECT_EQ(license_test_data[0].key_response, key_response); + EXPECT_EQ(license_test_data[0].key_request, key_request); + EXPECT_EQ(license_test_data[0].key_response, key_response); + + EXPECT_TRUE(device_files.DeleteLicense(license_test_data[0].key_set_id)); + EXPECT_FALSE(device_files.LicenseExists(license_test_data[0].key_set_id)); +} + +} // namespace wvcdm diff --git a/core/test/file_store_unittest.cpp b/core/test/file_store_unittest.cpp new file mode 100644 index 00000000..e26f9b72 --- /dev/null +++ b/core/test/file_store_unittest.cpp @@ -0,0 +1,266 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "device_files.h" +#include "file_store.h" +#include "gtest/gtest.h" +#include "properties.h" +#include "test_vectors.h" + +namespace { +const std::string kTestDirName = "test_dir"; +const std::string kTestFileName = "test.txt"; +const std::string kTestFileName2 = "test2.txt"; +const std::string kTestFileNameExt = ".txt"; +const std::string kWildcard = "*"; +} // namespace + +namespace wvcdm { + +class FileTest : public testing::Test { + protected: + virtual void SetUp() { CreateTestDir(); } + virtual void TearDown() { RemoveTestDir(); } + + void CreateTestDir() { + File file; + if (!file.Exists(test_vectors::kTestDir)) { + EXPECT_TRUE(file.CreateDirectory(test_vectors::kTestDir)); + } + EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); + } + + void RemoveTestDir() { + File file; + EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); + } + + std::string GenerateRandomData(uint32_t len) { + std::string data(len, 0); + for (size_t i = 0; i < len; i++) { + data[i] = rand() % 256; + } + return data; + } +}; + +TEST_F(FileTest, FileExists) { + File file; + EXPECT_TRUE(file.Exists(test_vectors::kFileExists)); + EXPECT_TRUE(file.Exists(test_vectors::kDirExists)); + EXPECT_FALSE(file.Exists(test_vectors::kFileDoesNotExist)); + EXPECT_FALSE(file.Exists(test_vectors::kDirDoesNotExist)); +} + +TEST_F(FileTest, CreateDirectory) { + File file; + std::string dir_wo_delimiter = + test_vectors::kTestDir.substr(0, test_vectors::kTestDir.size() - 1); + if (file.Exists(dir_wo_delimiter)) EXPECT_TRUE(file.Remove(dir_wo_delimiter)); + EXPECT_FALSE(file.Exists(dir_wo_delimiter)); + EXPECT_TRUE(file.CreateDirectory(dir_wo_delimiter)); + EXPECT_TRUE(file.Exists(dir_wo_delimiter)); + EXPECT_TRUE(file.Remove(dir_wo_delimiter)); + EXPECT_TRUE(file.CreateDirectory(test_vectors::kTestDir)); + EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); + EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); +} + +TEST_F(FileTest, RemoveDir) { + File file; + EXPECT_TRUE(file.Remove(test_vectors::kTestDir)); + EXPECT_FALSE(file.Exists(test_vectors::kTestDir)); +} + +TEST_F(FileTest, OpenFile) { + std::string path = test_vectors::kTestDir + kTestFileName; + File handle; + EXPECT_TRUE(handle.Remove(path)); + + File file; + EXPECT_TRUE(file.Open(path, File::kCreate)); + file.Close(); + + EXPECT_TRUE(handle.Exists(path)); +} + +TEST_F(FileTest, RemoveDirAndFile) { + std::string path = test_vectors::kTestDir + kTestFileName; + + File file; + EXPECT_TRUE(file.Open(path, File::kCreate)); + file.Close(); + EXPECT_TRUE(file.Exists(path)); + EXPECT_TRUE(file.Remove(path)); + EXPECT_FALSE(file.Exists(path)); + + EXPECT_TRUE(file.Open(path, File::kCreate)); + file.Close(); + EXPECT_TRUE(file.Exists(path)); + RemoveTestDir(); + EXPECT_FALSE(file.Exists(test_vectors::kTestDir)); + EXPECT_FALSE(file.Exists(path)); +} + +TEST_F(FileTest, RemoveWildcardFiles) { + std::string path1 = test_vectors::kTestDir + kTestFileName; + std::string path2 = test_vectors::kTestDir + kTestFileName2; + std::string wildcard_path = + test_vectors::kTestDir + kWildcard + kTestFileNameExt; + + File file; + EXPECT_TRUE(file.Open(path1, File::kCreate)); + file.Close(); + EXPECT_TRUE(file.Open(path2, File::kCreate)); + file.Close(); + EXPECT_TRUE(file.Exists(path1)); + EXPECT_TRUE(file.Exists(path2)); + EXPECT_TRUE(file.Remove(wildcard_path)); + EXPECT_FALSE(file.Exists(path1)); + EXPECT_FALSE(file.Exists(path2)); +} + +TEST_F(FileTest, IsDir) { + std::string path = test_vectors::kTestDir + kTestFileName; + File file; + EXPECT_TRUE(file.Open(path, File::kCreate)); + file.Close(); + + EXPECT_TRUE(file.Exists(path)); + EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); + EXPECT_FALSE(file.IsDirectory(path)); + EXPECT_TRUE(file.IsDirectory(test_vectors::kTestDir)); +} + +TEST_F(FileTest, IsRegularFile) { + std::string path = test_vectors::kTestDir + kTestFileName; + File file; + EXPECT_TRUE(file.Open(path, File::kCreate)); + file.Close(); + + EXPECT_TRUE(file.Exists(path)); + EXPECT_TRUE(file.Exists(test_vectors::kTestDir)); + EXPECT_TRUE(file.IsRegularFile(path)); + EXPECT_FALSE(file.IsRegularFile(test_vectors::kTestDir)); +} + +TEST_F(FileTest, FileSize) { + std::string path = test_vectors::kTestDir + kTestFileName; + File file; + file.Remove(path); + + std::string write_data = GenerateRandomData(600); + File wr_file; + EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); + EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); + wr_file.Close(); + EXPECT_TRUE(file.Exists(path)); + + EXPECT_EQ(static_cast(write_data.size()), file.FileSize(path)); +} + +TEST_F(FileTest, WriteReadTextFile) { + std::string path = test_vectors::kTestDir + kTestFileName; + File file; + file.Remove(path); + + std::string write_data = "This is a test"; + File wr_file; + EXPECT_TRUE(wr_file.Open(path, File::kCreate)); + EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); + wr_file.Close(); + EXPECT_TRUE(file.Exists(path)); + + std::string read_data; + read_data.resize(file.FileSize(path)); + File rd_file; + EXPECT_TRUE(rd_file.Open(path, File::kReadOnly)); + EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); + rd_file.Close(); + EXPECT_EQ(write_data, read_data); +} + +TEST_F(FileTest, WriteReadBinaryFile) { + std::string path = test_vectors::kTestDir + kTestFileName; + File file; + file.Remove(path); + + std::string write_data = GenerateRandomData(600); + File wr_file; + EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); + EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); + wr_file.Close(); + EXPECT_TRUE(file.Exists(path)); + + std::string read_data; + read_data.resize(file.FileSize(path)); + File rd_file; + EXPECT_TRUE(rd_file.Open(path, File::kReadOnly)); + EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); + rd_file.Close(); + EXPECT_EQ(write_data, read_data); +} + +TEST_F(FileTest, CopyFile) { + std::string path = test_vectors::kTestDir + kTestFileName; + File file; + file.Remove(path); + + std::string write_data = GenerateRandomData(600); + File wr_file; + EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary)); + EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size())); + wr_file.Close(); + ASSERT_TRUE(file.Exists(path)); + + std::string path_copy = test_vectors::kTestDir + kTestFileName2; + EXPECT_FALSE(file.Exists(path_copy)); + EXPECT_TRUE(file.Copy(path, path_copy)); + + std::string read_data; + read_data.resize(file.FileSize(path_copy)); + File rd_file; + EXPECT_TRUE(rd_file.Open(path_copy, File::kReadOnly)); + EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size())); + rd_file.Close(); + EXPECT_EQ(write_data, read_data); + EXPECT_EQ(file.FileSize(path), file.FileSize(path_copy)); +} + +TEST_F(FileTest, ListEmptyDirectory) { + std::vector files; + File file; + EXPECT_TRUE(file.List(test_vectors::kTestDir, &files)); + EXPECT_EQ(0u, files.size()); +} + +TEST_F(FileTest, ListFiles) { + File file; + std::string path = test_vectors::kTestDir + kTestDirName; + EXPECT_TRUE(file.CreateDirectory(path)); + + path = test_vectors::kTestDir + kTestFileName; + std::string write_data = GenerateRandomData(600); + EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary)); + EXPECT_TRUE(file.Write(write_data.data(), write_data.size())); + file.Close(); + EXPECT_TRUE(file.Exists(path)); + + path = test_vectors::kTestDir + kTestFileName2; + write_data = GenerateRandomData(600); + EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary)); + EXPECT_TRUE(file.Write(write_data.data(), write_data.size())); + file.Close(); + EXPECT_TRUE(file.Exists(path)); + + std::vector files; + EXPECT_TRUE(file.List(test_vectors::kTestDir, &files)); + EXPECT_EQ(3u, files.size()); + + for (size_t i = 0; i < files.size(); ++i) { + EXPECT_TRUE(files[i] == kTestDirName || + files[i] == kTestFileName || + files[i] == kTestFileName2); + } +} + +} // namespace wvcdm diff --git a/core/test/http_socket.cpp b/core/test/http_socket.cpp new file mode 100644 index 00000000..e704eac3 --- /dev/null +++ b/core/test/http_socket.cpp @@ -0,0 +1,278 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "http_socket.h" + +#include +#include +#include +#include +#include + +#include "log.h" +#include "openssl/bio.h" +#include "openssl/err.h" +#include "openssl/x509.h" + +namespace wvcdm { + +SSL_CTX* HttpSocket::InitSslContext(void) { + const SSL_METHOD* method; + SSL_CTX* ctx; + + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + method = SSLv3_client_method(); + ctx = SSL_CTX_new(method); + if (NULL == ctx) { + LOGE("failed to create SSL context"); + } + return ctx; +} + +void HttpSocket::ShowServerCertificate(const SSL* ssl) { + X509* cert; + char* line; + + // gets the server certificate + cert = SSL_get_peer_certificate(ssl); + if (cert != NULL) { + LOGV("server certificate:"); + line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0); + LOGV("subject: %s", line); + free(line); + line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0); + LOGV("issuer: %s", line); + free(line); + X509_free(cert); + } else { + LOGE("Failed to get server certificate"); + } +} + +HttpSocket::HttpSocket() + : secure_connect_(true), + socket_fd_(-1), + ssl_(NULL), + ssl_ctx_(NULL), + timeout_enabled_(false) { + SSL_library_init(); +} + +HttpSocket::~HttpSocket() { CloseSocket(); } + +void HttpSocket::CloseSocket() { + if (socket_fd_ != -1) { + close(socket_fd_); + socket_fd_ = -1; + } + if (secure_connect_) { + if (ssl_) { + SSL_free(ssl_); + ssl_ = NULL; + } + if (ssl_ctx_) { + CloseSslContext(ssl_ctx_); + ssl_ctx_ = NULL; + } + } +} + +// Extracts the domain name and resource path from the input url parameter. +// The results are put in domain_name and resource_path respectively. +// The format of the url can begin with :://domain server/... +// or dowmain server/resource_path +void HttpSocket::GetDomainNameAndPathFromUrl(const std::string& url, + std::string& domain_name, + std::string& resource_path) { + domain_name.clear(); + resource_path.clear(); + + size_t start = url.find("//"); + size_t end = url.npos; + if (start != url.npos) { + end = url.find("/", start + 2); + if (end != url.npos) { + domain_name.assign(url, start + 2, end - start - 2); + resource_path.assign(url, end + 1, url.npos); + } else { + domain_name.assign(url, start + 2, url.npos); + } + } else { + // no scheme/protocol in url + end = url.find("/"); + if (end != url.npos) { + domain_name.assign(url, 0, end); + resource_path.assign(url, end + 1, url.npos); + } else { + domain_name.assign(url); + } + } + // strips port number if present, e.g. https://www.domain.com:8888/... + end = domain_name.find(":"); + if (end != domain_name.npos) { + domain_name.erase(end); + } +} + +bool HttpSocket::Connect(const char* url, const std::string& port, + bool enable_timeout, bool secure_connection) { + secure_connect_ = secure_connection; + if (secure_connect_) ssl_ctx_ = InitSslContext(); + + GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_); + + socket_fd_ = socket(AF_INET, SOCK_STREAM, 0); + if (socket_fd_ < 0) { + LOGE("cannot open socket %d", errno); + return false; + } + + int reuse = 1; + if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == + -1) { + CloseSocket(); + LOGE("setsockopt error %d", errno); + return false; + } + + struct addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + struct addrinfo* addr_info = NULL; + bool status = true; + int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info); + if (ret != 0) { + CloseSocket(); + LOGE("getaddrinfo failed with %d", ret); + status = false; + } else { + if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) { + CloseSocket(); + LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(), + errno); + status = false; + } + } + timeout_enabled_ = enable_timeout; + if (addr_info != NULL) { + freeaddrinfo(addr_info); + } + + if (!status) return false; + + // secures connection + if (secure_connect_ && ssl_ctx_) { + ssl_ = SSL_new(ssl_ctx_); + if (!ssl_) { + LOGE("failed SSL_new"); + return false; + } + + BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE); + if (!a_bio) { + LOGE("BIO_new_socket error"); + return false; + } + + SSL_set_bio(ssl_, a_bio, a_bio); + int ret = SSL_connect(ssl_); + if (1 != ret) { + char buf[256]; + LOGE("SSL_connect error:%s", ERR_error_string(ERR_get_error(), buf)); + return false; + } + } + return true; +} + +int HttpSocket::Read(char* data, int len) { return (Read(data, len, 0)); } + +// makes non-blocking mode only during read, it supports timeout for read +// returns -1 for error, number of bytes read for success +int HttpSocket::Read(char* data, int len, int timeout_in_ms) { + bool use_timeout = (timeout_enabled_ && (timeout_in_ms > 0)); + int original_flags = 0; + if (use_timeout) { + original_flags = fcntl(socket_fd_, F_GETFL, 0); + if (original_flags == -1) { + LOGE("fcntl error %d", errno); + return -1; + } + if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) { + LOGE("fcntl error %d", errno); + return -1; + } + } + + int total_read = 0; + int read = 0; + int to_read = len; + while (to_read > 0) { + if (use_timeout) { + fd_set read_fds; + struct timeval tv; + tv.tv_sec = timeout_in_ms / 1000; + tv.tv_usec = (timeout_in_ms % 1000) * 1000; + FD_ZERO(&read_fds); + FD_SET(socket_fd_, &read_fds); + if (select(socket_fd_ + 1, &read_fds, NULL, NULL, &tv) == -1) { + LOGE("select failed"); + break; + } + if (!FD_ISSET(socket_fd_, &read_fds)) { + LOGD("socket read timeout"); + break; + } + } + + if (secure_connect_) + read = SSL_read(ssl_, data, to_read); + else + read = recv(socket_fd_, data, to_read, 0); + + if (read > 0) { + to_read -= read; + data += read; + total_read += read; + } else if (read == 0) { + // in blocking mode, zero read mean's peer closed. + // in non-blocking mode, select said that there is data. so it should not + // happen + break; + } else { + LOGE("recv returned %d, error = %d", read, errno); + break; + } + } + + if (use_timeout) { + fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again + } + return total_read; +} + +int HttpSocket::Write(const char* data, int len) { + int total_sent = 0; + int sent = 0; + int to_send = len; + while (to_send > 0) { + if (secure_connect_) + sent = SSL_write(ssl_, data, to_send); + else + sent = send(socket_fd_, data, to_send, 0); + + if (sent > 0) { + to_send -= sent; + data += sent; + total_sent += sent; + } else if (sent == 0) { + usleep(10); // retry later + } else { + LOGE("send returned error %d", errno); + } + } + return total_sent; +} + +} // namespace wvcdm diff --git a/core/test/http_socket.h b/core/test/http_socket.h new file mode 100644 index 00000000..79363b10 --- /dev/null +++ b/core/test/http_socket.h @@ -0,0 +1,50 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef CDM_TEST_HTTP_SOCKET_H_ +#define CDM_TEST_HTTP_SOCKET_H_ + +#include +#include "openssl/ssl.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +// Provides basic Linux based TCP socket interface. +class HttpSocket { + public: + HttpSocket(); + ~HttpSocket(); + + void CloseSocket(); + bool Connect(const char* url, const std::string& port, bool enable_timeout, + bool secure_connection); + void GetDomainNameAndPathFromUrl(const std::string& url, + std::string& domain_name, + std::string& resource_path); + const std::string& domain_name() const { return domain_name_; }; + const std::string& resource_path() const { return resource_path_; }; + int Read(char* data, int len); + int Read(char* data, int len, int timeout_in_ms); + int Write(const char* data, int len); + + private: + void CloseSslContext(SSL_CTX* ctx) const { + if (ctx) SSL_CTX_free(ctx); + } + SSL_CTX* InitSslContext(void); + void ShowServerCertificate(const SSL* ssl); + + std::string domain_name_; + bool secure_connect_; + std::string resource_path_; + int socket_fd_; + SSL* ssl_; + SSL_CTX* ssl_ctx_; + bool timeout_enabled_; + + CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket); +}; + +}; // namespace wvcdm + +#endif // CDM_TEST_HTTP_SOCKET_H_ diff --git a/core/test/http_socket_test.cpp b/core/test/http_socket_test.cpp new file mode 100644 index 00000000..85770543 --- /dev/null +++ b/core/test/http_socket_test.cpp @@ -0,0 +1,211 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include +#include "gtest/gtest.h" +#include "http_socket.h" +#include "log.h" +#include "string_conversions.h" +#include "url_request.h" + +namespace { +// Random URL for tests. +const std::string kHttpsTestServer("https://www.google.com"); +std::string gTestServer(kHttpsTestServer); +std::string gTestData("Hello"); +const int kHttpBufferSize = 4096; +char gBuffer[kHttpBufferSize]; +} + +namespace wvcdm { + +class HttpSocketTest : public testing::Test { + public: + HttpSocketTest() {} + ~HttpSocketTest() { socket_.CloseSocket(); } + + protected: + bool Connect(const std::string& server_url, bool secure_connection) { + + std::string port = secure_connection ? "443" : "80"; + if (socket_.Connect(server_url.c_str(), port, true, secure_connection)) { + LOGD("connected to %s", socket_.domain_name().c_str()); + } else { + LOGE("failed to connect to %s", socket_.domain_name().c_str()); + return false; + } + return true; + } + + bool PostRequest(const std::string& data) { + std::string request("POST "); + if (socket_.resource_path().empty()) + request.append(socket_.domain_name()); + else + request.append(socket_.resource_path()); + request.append(" HTTP/1.1\r\n"); + request.append("Host: "); + request.append(socket_.domain_name()); + request.append("\r\nUser-Agent: httpSocketTest/1.0\r\n"); + request.append("Content-Length: "); + memset(gBuffer, 0, kHttpBufferSize); + snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast(data.size())); + request.append(gBuffer); + request.append("Content-Type: multipart/form-data\r\n"); + + // newline terminates header + request.append("\r\n"); + + // append data + request.append(data); + socket_.Write(request.c_str(), request.size()); + LOGD("request: %s", request.c_str()); + return true; + } + + bool GetResponse() { + int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000); + if (bytes < 0) { + LOGE("read error = ", errno); + return false; + } else { + LOGD("read %d bytes", bytes); + std::string response(gBuffer, bytes); + LOGD("response: %s", response.c_str()); + LOGD("end response dump"); + return true; + } + } + + HttpSocket socket_; + std::string domain_name_; + std::string resource_path_; +}; + +TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest) { + socket_.GetDomainNameAndPathFromUrl( + "https://code.google.com/p/googletest/wiki/Primer", domain_name_, + resource_path_); + EXPECT_STREQ("code.google.com", domain_name_.c_str()); + EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str()); + + socket_.GetDomainNameAndPathFromUrl( + "http://code.google.com/p/googletest/wiki/Primer/", domain_name_, + resource_path_); + EXPECT_STREQ("code.google.com", domain_name_.c_str()); + EXPECT_STREQ("p/googletest/wiki/Primer/", resource_path_.c_str()); + + socket_.GetDomainNameAndPathFromUrl("http://code.google.com/", domain_name_, + resource_path_); + EXPECT_STREQ("code.google.com", domain_name_.c_str()); + EXPECT_STREQ("", resource_path_.c_str()); + + socket_.GetDomainNameAndPathFromUrl("http://code.google.com", domain_name_, + resource_path_); + EXPECT_STREQ("code.google.com", domain_name_.c_str()); + EXPECT_STREQ("", resource_path_.c_str()); + + socket_.GetDomainNameAndPathFromUrl( + "code.google.com/p/googletest/wiki/Primer", domain_name_, resource_path_); + EXPECT_STREQ("code.google.com", domain_name_.c_str()); + EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str()); + + socket_.GetDomainNameAndPathFromUrl("code.google.com", domain_name_, + resource_path_); + EXPECT_STREQ("code.google.com", domain_name_.c_str()); + EXPECT_STREQ("", resource_path_.c_str()); + + socket_.GetDomainNameAndPathFromUrl("code.google.com/", domain_name_, + resource_path_); + EXPECT_STREQ("code.google.com", domain_name_.c_str()); + EXPECT_STREQ("", resource_path_.c_str()); + + socket_.GetDomainNameAndPathFromUrl("", domain_name_, resource_path_); + EXPECT_TRUE(domain_name_.empty()); + EXPECT_TRUE(resource_path_.empty()); + + // Test with random numeric URL + socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888/drm", + domain_name_, resource_path_); + EXPECT_STREQ("10.11.12.13", domain_name_.c_str()); + EXPECT_STREQ("drm", resource_path_.c_str()); + + socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888", domain_name_, + resource_path_); + EXPECT_STREQ("10.11.12.13", domain_name_.c_str()); + EXPECT_TRUE(resource_path_.empty()); +} + +TEST_F(HttpSocketTest, ConnectTest) { + const bool kUseSecureConnection = true; + + if (gTestServer.find("https") != std::string::npos) { + EXPECT_TRUE(Connect(gTestServer, kUseSecureConnection)); + socket_.CloseSocket(); + + // https connection allows insecure connection through port 80 as well + EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection)); + socket_.CloseSocket(); + } else { + EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection)); + socket_.CloseSocket(); + + // Test for the case that non-https connection must not use port 443 + EXPECT_FALSE(Connect(gTestServer, kUseSecureConnection)); + socket_.CloseSocket(); + } + + EXPECT_FALSE(Connect("ww.g.c", kUseSecureConnection)); + socket_.CloseSocket(); + + EXPECT_FALSE(Connect("ww.g.c", !kUseSecureConnection)); + socket_.CloseSocket(); +} + +TEST_F(HttpSocketTest, RoundTripTest) { + int secure_connection = + (gTestServer.find("https") != std::string::npos) ? true : false; + ASSERT_TRUE(Connect(gTestServer, secure_connection)); + EXPECT_TRUE(PostRequest(gTestData)); + GetResponse(); + socket_.CloseSocket(); +} + +} // namespace wvcdm + +int main(int argc, char** argv) { + ::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/license_request.cpp b/core/test/license_request.cpp new file mode 100644 index 00000000..f6bbd3de --- /dev/null +++ b/core/test/license_request.cpp @@ -0,0 +1,81 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "license_request.h" +#include "log.h" + +namespace wvcdm { + +static const std::string kTwoBlankLines("\r\n\r\n"); + +size_t LicenseRequest::FindHeaderEndPosition( + const std::string& response) const { + return(response.find(kTwoBlankLines)); +} + +// This routine parses the license server's response message and +// extracts the drm message from the response header. +void LicenseRequest::GetDrmMessage(const std::string& response, + std::string& drm_msg) { + if (response.empty()) { + drm_msg.clear(); + return; + } + + // Extracts DRM message. + // Content-Length = GLS line + Header(s) + empty line + drm message; + // we use the empty line to locate the drm message, and compute + // the drm message length as below instead of using Content-Length + size_t header_end_pos = FindHeaderEndPosition(response); + if (header_end_pos != std::string::npos) { + header_end_pos += kTwoBlankLines.size(); // points to response body + + drm_msg.clear(); + size_t drm_msg_pos = response.find(kTwoBlankLines, header_end_pos); + if (drm_msg_pos != std::string::npos) { + drm_msg_pos += kTwoBlankLines.size(); // points to drm message + } else { + // For backward compatibility, no blank line after error code + drm_msg_pos = response.find("\r\n", header_end_pos); + if (drm_msg_pos != std::string::npos) { + drm_msg_pos += 2; // points to drm message + } + } + + if (drm_msg_pos != std::string::npos) { + drm_msg = response.substr(drm_msg_pos); + } else { + drm_msg = response.substr(header_end_pos); + } + } else { + LOGE("response body not found"); + } +} + +// Returns heartbeat url in heartbeat_url. +// The heartbeat url is stored as meta data in the response message. +void LicenseRequest::GetHeartbeatUrl(const std::string& response, + std::string& heartbeat_url) { + if (response.empty()) { + heartbeat_url.clear(); + return; + } + + size_t header_end_pos = FindHeaderEndPosition(response); + if (header_end_pos != std::string::npos) { + header_end_pos += kTwoBlankLines.size(); // points to response body + + heartbeat_url.clear(); + size_t heartbeat_url_pos = response.find("Heartbeat-Url: ", + header_end_pos); + if (heartbeat_url_pos != std::string::npos) { + heartbeat_url_pos += sizeof("Heartbeat-Url: "); + heartbeat_url.assign(response.substr(heartbeat_url_pos)); + } else { + LOGE("heartbeat url not found"); + } + } else { + LOGE("response body not found"); + } +} + +} // namespace wvcdm diff --git a/core/test/license_request.h b/core/test/license_request.h new file mode 100644 index 00000000..11576c55 --- /dev/null +++ b/core/test/license_request.h @@ -0,0 +1,30 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef CDM_TEST_LICENSE_REQUEST_H_ +#define CDM_TEST_LICENSE_REQUEST_H_ + +#include +#include "wv_cdm_types.h" + +namespace wvcdm { + +// Parses response from a license request. +// This class assumes a particular response format defined by +// Google license servers. +class LicenseRequest { + public: + LicenseRequest() {}; + ~LicenseRequest() {}; + + void GetDrmMessage(const std::string& response, std::string& drm_msg); + void GetHeartbeatUrl(const std::string& response, std::string& heartbeat_url); + + private: + size_t FindHeaderEndPosition(const std::string& response) const; + + CORE_DISALLOW_COPY_AND_ASSIGN(LicenseRequest); +}; + +}; // namespace wvcdm + +#endif // CDM_TEST_LICENSE_REQUEST_H_ diff --git a/core/test/policy_engine_unittest.cpp b/core/test/policy_engine_unittest.cpp new file mode 100644 index 00000000..c4e10447 --- /dev/null +++ b/core/test/policy_engine_unittest.cpp @@ -0,0 +1,788 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +#include + +#include "clock.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "license.h" +#include "policy_engine.h" +#include "wv_cdm_constants.h" + +namespace wvcdm { + +//protobuf generated classes. +using video_widevine_server::sdk::License; +using video_widevine_server::sdk::License_Policy; +using video_widevine_server::sdk::LicenseIdentification; +using video_widevine_server::sdk::STREAMING; +using video_widevine_server::sdk::OFFLINE; + +// gmock methods +using ::testing::Return; +using ::testing::AtLeast; + + +class MockClock : public Clock { + public: + MOCK_METHOD0(GetCurrentTime, int64_t()); +}; + +class PolicyEngineTest : public ::testing::Test { + protected: + virtual void SetUp() { + mock_clock_ = new MockClock(); + policy_engine_ = new PolicyEngine(mock_clock_); + + license_start_time_ = 1413517500; // ~ 01/01/2013 + license_renewal_delay_ = 604200; // 7 days - 10 minutes + license_renewal_retry_interval_ = 30; + license_duration_ = 604800; // 7 days + playback_duration_ = 86400; // 24 hours + + license_.set_license_start_time(license_start_time_); + + LicenseIdentification* id = license_.mutable_id(); + id->set_version(1); + id->set_type(STREAMING); + + License_Policy* policy = license_.mutable_policy(); + policy = license_.mutable_policy(); + policy->set_can_play(true); + policy->set_can_persist(true); + policy->set_can_renew(true); + policy->set_rental_duration_seconds(license_duration_); + policy->set_playback_duration_seconds(playback_duration_); + policy->set_license_duration_seconds(license_duration_); + policy->set_renewal_recovery_duration_seconds(license_duration_ - + license_renewal_delay_); // 10 minutes + + // Note: not a real URL - used for testing Policy/PolicyEngine interfaces + policy->set_renewal_server_url( + "https://test.google.com/license/GetCencLicense"); + policy->set_renewal_delay_seconds(license_renewal_delay_); + policy->set_renewal_retry_interval_seconds( + license_renewal_retry_interval_); + policy->set_renew_with_usage(false); + } + + virtual void TearDown() { + delete policy_engine_; + // Done by policy engine: delete mock_clock_; + policy_engine_ = NULL; + mock_clock_ = NULL; + } + + MockClock* mock_clock_; + PolicyEngine* policy_engine_; + License license_; + License_Policy* policy_; + + int64_t license_start_time_; + int64_t license_renewal_delay_; + int64_t license_renewal_retry_interval_; + int64_t license_duration_; + int64_t playback_duration_; +}; + +TEST_F(PolicyEngineTest, NoLicense) { + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackSuccess) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 10)); + + policy_engine_->SetLicense(license_); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackFailed_CanPlayFalse) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 5)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_can_play(false); + + policy_engine_->SetLicense(license_); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->BeginDecryption(); + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackFails_RentalDurationExpired) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 3600)) + .WillOnce(Return(license_start_time_ + 3601)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_rental_duration_seconds(3600); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackFails_PlaybackDurationExpired) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 10000)) + .WillOnce(Return(license_start_time_ + 13598)) + .WillOnce(Return(license_start_time_ + 13602)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_playback_duration_seconds(3600); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackFails_LicenseDurationExpired) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 3600)) + .WillOnce(Return(license_start_time_ + 3601)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_license_duration_seconds(3600); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_RentalDuration0) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 3600)) + .WillOnce(Return(license_start_time_ + 3601)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_rental_duration_seconds(0); + policy->set_license_duration_seconds(3600); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_PlaybackDuration0) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 10000)) + .WillOnce(Return(license_start_time_ + 10005)) + .WillOnce(Return(license_start_time_ + 13598)) + .WillOnce(Return(license_start_time_ + 13602)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_playback_duration_seconds(0); + policy->set_license_duration_seconds(3600); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_LicenseDuration0) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 3600)) + .WillOnce(Return(license_start_time_ + 3601)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_license_duration_seconds(0); + policy->set_rental_duration_seconds(3600); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_Durations0) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 604800)) + .WillOnce(Return(license_start_time_ + 604810)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_rental_duration_seconds(0); + policy->set_playback_duration_seconds(0); + policy->set_license_duration_seconds(0); + policy->set_renewal_delay_seconds(604900); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + EXPECT_TRUE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackFailed_CanRenewFalse) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + license_duration_ - + playback_duration_ + 1)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10)) + .WillOnce(Return(license_start_time_ + license_duration_ + 10)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_can_renew(false); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccess) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + license_duration_ - + playback_duration_ + 1)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ - 15)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + + license_renewal_retry_interval_ + 10)); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + license_.set_license_start_time(license_start_time_ + + license_renewal_delay_ + 15); + LicenseIdentification* id = license_.mutable_id(); + id->set_version(2); + policy_engine_->UpdateLicense(license_); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + EXPECT_TRUE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackFailed_RenewFailedVersionNotUpdated) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + license_duration_ - + playback_duration_ + 1)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40)) + .WillOnce(Return(license_start_time_ + license_duration_ + 10)); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + license_.set_license_start_time(license_start_time_ + + license_renewal_delay_ + 15); + policy_engine_->UpdateLicense(license_); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackFailed_RepeatedRenewFailures) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + license_duration_ - + playback_duration_ + 1)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 70)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 80)) + .WillOnce(Return(license_start_time_ + license_duration_ + 15)); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterExpiry) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + license_duration_ - + playback_duration_ + 1)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 70)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 80)) + .WillOnce(Return(license_start_time_ + license_duration_ + 10)) + .WillOnce(Return(license_start_time_ + license_duration_ + 30)) + .WillOnce(Return(license_start_time_ + license_duration_ + 40)); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_EXPIRED_EVENT, event); + + EXPECT_FALSE(policy_engine_->can_decrypt()); + + license_.set_license_start_time(license_start_time_ + + license_duration_ + 20); + LicenseIdentification* id = license_.mutable_id(); + id->set_version(2); + License_Policy* policy = license_.mutable_policy(); + policy = license_.mutable_policy(); + policy->set_playback_duration_seconds(playback_duration_ + 100); + policy->set_license_duration_seconds(license_duration_ + 100); + + policy_engine_->UpdateLicense(license_); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + EXPECT_TRUE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterFailures) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + license_duration_ - + playback_duration_ + 1)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 55)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 67)) + .WillOnce(Return(license_start_time_ + license_renewal_delay_ + 200)); + + policy_engine_->SetLicense(license_); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + license_.set_license_start_time(license_start_time_ + + license_renewal_delay_ + 55); + LicenseIdentification* id = license_.mutable_id(); + id->set_version(2); + policy_engine_->UpdateLicense(license_); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + EXPECT_TRUE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + EXPECT_TRUE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, PlaybackOk_RenewedWithUsage) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 10)) + .WillOnce(Return(license_start_time_ + 20)) + .WillOnce(Return(license_start_time_ + 40)) + .WillOnce(Return(license_start_time_ + 50)); + + License_Policy* policy = license_.mutable_policy(); + policy->set_renew_with_usage(true); + + policy_engine_->SetLicense(license_); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->BeginDecryption(); + EXPECT_FALSE(policy_engine_->can_decrypt()); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_TRUE(event_occurred); + EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event); + + license_.set_license_start_time(license_start_time_ + 30); + policy->set_renew_with_usage(false); + LicenseIdentification* id = license_.mutable_id(); + id->set_version(2); + policy_engine_->UpdateLicense(license_); + + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + EXPECT_TRUE(policy_engine_->can_decrypt()); +} + +TEST_F(PolicyEngineTest, QueryFailed_LicenseNotReceived) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_)); + + CdmQueryMap query_info; + EXPECT_EQ(UNKNOWN_ERROR, policy_engine_->Query(&query_info)); +} + +TEST_F(PolicyEngineTest, QuerySuccess) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 100)); + + License_Policy* policy = license_.mutable_policy(); + + policy_engine_->SetLicense(license_); + + CdmQueryMap query_info; + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + int64_t remaining_time; + std::istringstream ss; + ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]); + ss >> remaining_time; + EXPECT_LT(0, remaining_time); + ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]); + ss >> remaining_time; + EXPECT_LT(0, remaining_time); + + EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL], + policy->renewal_server_url()); +} + +TEST_F(PolicyEngineTest, QuerySuccess_Offline) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 100)); + + LicenseIdentification* id = license_.mutable_id(); + id->set_type(OFFLINE); + + License_Policy* policy = license_.mutable_policy(); + policy->set_can_play(false); + policy->set_can_persist(false); + policy->set_can_renew(false); + + policy_engine_->SetLicense(license_); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->BeginDecryption(); + EXPECT_FALSE(policy_engine_->can_decrypt()); + + CdmQueryMap query_info; + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_OFFLINE, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + int64_t remaining_time; + std::istringstream ss; + ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]); + ss >> remaining_time; + EXPECT_EQ(0, remaining_time); + ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]); + ss >> remaining_time; + EXPECT_EQ(0, remaining_time); + + EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL], + policy->renewal_server_url()); +} + +TEST_F(PolicyEngineTest, QuerySuccess_DurationExpired) { + EXPECT_CALL(*mock_clock_, GetCurrentTime()) + .WillOnce(Return(license_start_time_ + 1)) + .WillOnce(Return(license_start_time_ + 5)) + .WillOnce(Return(license_start_time_ + 10)) + .WillOnce(Return(license_start_time_ + license_duration_ + 20)); + + LicenseIdentification* id = license_.mutable_id(); + id->set_type(OFFLINE); + + License_Policy* policy = license_.mutable_policy(); + + policy_engine_->SetLicense(license_); + + bool event_occurred; + CdmEventType event; + policy_engine_->OnTimerEvent(&event_occurred, &event); + EXPECT_FALSE(event_occurred); + + policy_engine_->BeginDecryption(); + EXPECT_TRUE(policy_engine_->can_decrypt()); + + CdmQueryMap query_info; + EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info)); + EXPECT_EQ(QUERY_VALUE_OFFLINE, query_info[QUERY_KEY_LICENSE_TYPE]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PERSIST_ALLOWED]); + EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]); + + int64_t remaining_time; + std::istringstream ss; + ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]); + ss >> remaining_time; + EXPECT_EQ(0, remaining_time); + ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]); + ss >> remaining_time; + EXPECT_EQ(0, remaining_time); + + EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL], + policy->renewal_server_url()); +} + +} // wvcdm diff --git a/core/test/timer_unittest.cpp b/core/test/timer_unittest.cpp new file mode 100644 index 00000000..edc63879 --- /dev/null +++ b/core/test/timer_unittest.cpp @@ -0,0 +1,54 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "gtest/gtest.h" +#include "timer.h" + +namespace wvcdm { + +class TestTimerHandler : public TimerHandler { + public: + TestTimerHandler() : timer_events_(0) {}; + virtual ~TestTimerHandler() {}; + + virtual void OnTimerEvent() { + timer_events_++; + } + + uint32_t timer_events() { return timer_events_; } + void ResetTimerEvents() { timer_events_ = 0; } + + private: + uint32_t timer_events_; +}; + +TEST(TimerTest, ParametersCheck) { + Timer timer; + EXPECT_FALSE(timer.Start(NULL, 10)); + + TestTimerHandler handler; + EXPECT_FALSE(timer.Start(&handler, 0)); +} + +TEST(TimerTest, TimerCheck) { + TestTimerHandler handler; + Timer timer; + uint32_t duration = 10; + + EXPECT_EQ(0u, handler.timer_events()); + EXPECT_FALSE(timer.IsRunning()); + + EXPECT_TRUE(timer.Start(&handler, 1)); + EXPECT_TRUE(timer.IsRunning()); + sleep(duration); + + EXPECT_LE(duration-1, handler.timer_events()); + EXPECT_LE(handler.timer_events(), duration+1); + timer.Stop(); + EXPECT_FALSE(timer.IsRunning()); + sleep(duration); + + EXPECT_LE(duration-1, handler.timer_events()); + EXPECT_LE(handler.timer_events(), duration+1); +} + +} diff --git a/core/test/url_request.cpp b/core/test/url_request.cpp new file mode 100644 index 00000000..1da99c1b --- /dev/null +++ b/core/test/url_request.cpp @@ -0,0 +1,218 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#include "url_request.h" + +#include +#include + +#include "http_socket.h" +#include "log.h" +#include "string_conversions.h" + +namespace { +const int kMaxReadAttempts = 4; +const int kSingleReadAttempt = 1; +} // namespace + +namespace wvcdm { + +UrlRequest::UrlRequest(const std::string& url, const std::string& port, + bool secure_connection, bool chunk_transfer_mode) + : chunk_transfer_mode_(chunk_transfer_mode), + is_connected_(false), + port_("80"), + request_(""), + server_url_(url) { + if (!port.empty()) { + port_.assign(port); + } + if (socket_.Connect((server_url_).c_str(), port_, true, secure_connection)) { + is_connected_ = true; + } else { + LOGE("failed to connect to %s, port=%s", socket_.domain_name().c_str(), + port.c_str()); + } +} + +UrlRequest::~UrlRequest() { socket_.CloseSocket(); } + +void UrlRequest::AppendChunkToUpload(const std::string& data) { + // format of chunk: + // size of chunk in hex\r\n + // data\r\n + // . . . + // 0\r\n + + // buffer to store length of chunk + memset(buffer_, 0, kHttpBufferSize); + snprintf(buffer_, kHttpBufferSize, "%zx\r\n", data.size()); + request_.append(buffer_); // appends size of chunk + LOGD("...\r\n%s", request_.c_str()); + request_.append(data); + request_.append("\r\n"); // marks end of data +} + +// Concatenate all chunks into one blob and returns the response with +// header information. +void UrlRequest::ConcatenateChunkedResponse(const std::string http_response, + std::string* modified_response) { + if (http_response.empty()) return; + + modified_response->clear(); + const std::string kChunkedTag = "Transfer-Encoding: chunked\r\n\r\n"; + size_t chunked_tag_pos = http_response.find(kChunkedTag); + if (std::string::npos != chunked_tag_pos) { + // processes chunked encoding + size_t chunk_size = 0; + size_t chunk_size_pos = chunked_tag_pos + kChunkedTag.size(); + sscanf(&http_response[chunk_size_pos], "%zx", &chunk_size); + if (chunk_size > http_response.size()) { + // precaution, in case we misread chunk size + LOGE("invalid chunk size %u", chunk_size); + return; + } + + // Search for chunks in the following format: + // header + // chunk size\r\n <-- chunk_size_pos @ beginning of chunk size + // chunk data\r\n <-- chunk_pos @ beginning of chunk data + // chunk size\r\n + // chunk data\r\n + // 0\r\n + const std::string kCrLf = "\r\n"; + size_t chunk_pos = http_response.find(kCrLf, chunk_size_pos); + modified_response->assign(http_response, 0, chunk_size_pos); + + while ((chunk_size > 0) && (std::string::npos != chunk_pos)) { + chunk_pos += kCrLf.size(); + modified_response->append(http_response, chunk_pos, chunk_size); + + // Search for next chunk + chunk_size_pos = chunk_pos + chunk_size + kCrLf.size(); + sscanf(&http_response[chunk_size_pos], "%zx", &chunk_size); + if (chunk_size > http_response.size()) { + // precaution, in case we misread chunk size + LOGE("invalid chunk size %u", chunk_size); + break; + } + chunk_pos = http_response.find(kCrLf, chunk_size_pos); + } + } else { + // Response is not chunked encoded + modified_response->assign(http_response); + } +} + +int UrlRequest::GetResponse(std::string* message) { + message->clear(); + + std::string response; + const int kTimeoutInMs = 3000; + int bytes = 0; + for (int attempts = kMaxReadAttempts; attempts > 0; --attempts) { + memset(buffer_, 0, kHttpBufferSize); + bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs); + if (bytes > 0) { + response.append(buffer_, bytes); + if (bytes < static_cast(kHttpBufferSize)) { + attempts = kSingleReadAttempt; + } + } else { + if (bytes < 0) LOGE("read error = ", errno); + // bytes == 0 indicates nothing to read + } + } + + ConcatenateChunkedResponse(response, message); + LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str()); + return message->size(); +} + +int UrlRequest::GetStatusCode(const std::string& response) { + const std::string kHttpVersion("HTTP/1.1"); + + int status_code = -1; + size_t pos = response.find(kHttpVersion); + if (pos != std::string::npos) { + pos += kHttpVersion.size(); + sscanf(response.substr(pos).c_str(), "%d", &status_code); + } + return status_code; +} + +bool UrlRequest::PostRequestChunk(const std::string& data) { + request_.assign("POST /"); + request_.append(socket_.resource_path()); + request_.append(" HTTP/1.1\r\n"); + request_.append("Host: "); + request_.append(socket_.domain_name()); + request_.append("\r\nConnection: Keep-Alive\r\n"); + request_.append("Transfer-Encoding: chunked\r\n"); + request_.append("User-Agent: Widevine CDM v1.0\r\n"); + request_.append("Accept-Encoding: gzip,deflate\r\n"); + request_.append("Accept-Language: en-us,fr\r\n"); + request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n"); + request_.append("\r\n"); // empty line to terminate header + + // calls AppendChunkToUpload repeatedly for multiple chunks + AppendChunkToUpload(data); + + // terminates last chunk with 0\r\n, then ends header with an empty line + request_.append("0\r\n\r\n"); + + socket_.Write(request_.c_str(), request_.size()); + return true; +} + +bool UrlRequest::PostRequest(const std::string& data) { + if (chunk_transfer_mode_) { + return PostRequestChunk(data); + } + request_.assign("POST /"); + request_.append(socket_.resource_path()); + request_.append(" HTTP/1.1\r\n"); + request_.append("Host: "); + request_.append(socket_.domain_name()); + request_.append("\r\nConnection: Keep-Alive\r\n"); + request_.append("User-Agent: Widevine CDM v1.0\r\n"); + request_.append("Accept-Encoding: gzip,deflate\r\n"); + request_.append("Accept-Language: en-us,fr\r\n"); + request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n"); + std::ostringstream ss; + ss << data.size(); + request_.append("Content-Length: "); + request_.append(ss.str()); + request_.append("\r\n\r\n"); + request_.append(data); + + // terminates with \r\n, then ends with an empty line + request_.append("\r\n\r\n"); + + socket_.Write(request_.c_str(), request_.size()); + LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str()); + LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str()); + return true; +} + +bool UrlRequest::PostCertRequestInQueryString(const std::string& data) { + request_.assign("POST /"); + request_.append(socket_.resource_path()); + request_.append("&signedRequest="); + request_.append(data); + request_.append(" HTTP/1.1\r\n"); + request_.append("User-Agent: Widevine CDM v1.0\r\n"); + request_.append("Host: "); + request_.append(socket_.domain_name()); + request_.append("\r\nAccept: */*"); + request_.append("\r\nContent-Type: application/json"); + request_.append("\r\nContent-Length: 0"); + request_.append("\r\n"); // empty line to terminate header + request_.append("\r\n"); // terminates the request + + socket_.Write(request_.c_str(), request_.size()); + LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str()); + LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str()); + return true; +} + +} // namespace wvcdm diff --git a/core/test/url_request.h b/core/test/url_request.h new file mode 100644 index 00000000..43358f16 --- /dev/null +++ b/core/test/url_request.h @@ -0,0 +1,45 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +#ifndef CDM_TEST_URL_REQUEST_H_ +#define CDM_TEST_URL_REQUEST_H_ + +#include +#include "http_socket.h" +#include "wv_cdm_types.h" + +namespace wvcdm { + +// Provides simple HTTP request and response service. +// Only POST request method is implemented. +class UrlRequest { + public: + UrlRequest(const std::string& url, const std::string& port, + bool secure_connect, bool chunk_transfer_mode); + ~UrlRequest(); + + void AppendChunkToUpload(const std::string& data); + void ConcatenateChunkedResponse(const std::string http_response, + std::string* modified_response); + int GetResponse(std::string* message); + int GetStatusCode(const std::string& response); + bool is_connected() const { return is_connected_; } + bool PostRequest(const std::string& data); + bool PostRequestChunk(const std::string& data); + bool PostCertRequestInQueryString(const std::string& data); + + private: + static const unsigned int kHttpBufferSize = 4096; + char buffer_[kHttpBufferSize]; + bool chunk_transfer_mode_; + bool is_connected_; + std::string port_; + std::string request_; + HttpSocket socket_; + std::string server_url_; + + CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest); +}; + +}; // namespace wvcdm + +#endif // CDM_TEST_URL_REQUEST_H_ diff --git a/linux/src/lock.cpp b/linux/src/lock.cpp new file mode 100644 index 00000000..0e2c6951 --- /dev/null +++ b/linux/src/lock.cpp @@ -0,0 +1,38 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// +// Lock class - provides a simple mutex implementation, modeled after Android + +#include "lock.h" + +#include + +namespace wvcdm { + +class Lock::Impl { + public: + pthread_mutex_t mutex_; +}; + +Lock::Lock() : impl_(new Lock::Impl()) { + pthread_mutex_init(&impl_->mutex_, NULL); +} + +Lock::~Lock() { + pthread_mutex_destroy(&impl_->mutex_); + delete impl_; +} + +void Lock::Acquire() { + pthread_mutex_lock(&impl_->mutex_); +} + +void Lock::Release() { + pthread_mutex_unlock(&impl_->mutex_); +} + +bool Lock::Try() { + int result = pthread_mutex_trylock(&impl_->mutex_); + return result == 0; +} + +}; // namespace wvcdm diff --git a/linux/src/log.cpp b/linux/src/log.cpp new file mode 100644 index 00000000..13c17448 --- /dev/null +++ b/linux/src/log.cpp @@ -0,0 +1,43 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Log - implemented using stdout. +// +#define LOG_BUF_SIZE 4096 + +#include +#include +#include +#include "log.h" + +namespace wvcdm { + +static LogPriority g_cutoff = LOG_WARN; + +void InitLogging(int argc, const char* const* argv) { + for (int i = 1; i < argc; i++) { + if (strncmp(argv[i], "-v", 2) == 0) { + g_cutoff = LOG_VERBOSE; + } + } +} + +void Log(const char* file, int line, LogPriority level, const char* fmt, ...) { + const char* severities[] = { "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE" }; + if (level >= sizeof(severities) / sizeof(*severities)) { + printf("[FATAL:%s(%d)] Invalid log priority level: %d\n", file, line, + level); + return; + } + if (level > g_cutoff) return; + + va_list ap; + char buf[LOG_BUF_SIZE]; + va_start(ap, fmt); + vsnprintf(buf, LOG_BUF_SIZE, fmt, ap); + va_end(ap); + printf("[%s:%s(%d)] ", severities[level], file, line); + fputs(buf, stdout); + putc('\n', stdout); +} + +}; // namespace wvcdm diff --git a/oemcrypto/include/OEMCryptoCENC.h b/oemcrypto/include/OEMCryptoCENC.h new file mode 100644 index 00000000..60e711c0 --- /dev/null +++ b/oemcrypto/include/OEMCryptoCENC.h @@ -0,0 +1,1328 @@ +// Copyright 2013 Google Inc. All Rights Reserved. + +/********************************************************************* + * OEMCryptoCENC.h + * + * Reference APIs needed to support Widevine's crypto algorithms. + *********************************************************************/ + +#ifndef OEMCRYPTO_CENC_H_ +#define OEMCRYPTO_CENC_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define OEMCRYPTO_VERSION "8.0" +static const char oec_version[] = OEMCRYPTO_VERSION; +static const uint32_t oec_latest_version = 8; + +typedef uint32_t OEMCrypto_SESSION; + +typedef enum OEMCryptoResult { + OEMCrypto_SUCCESS = 0, + OEMCrypto_ERROR_INIT_FAILED = 1, + OEMCrypto_ERROR_TERMINATE_FAILED = 2, + OEMCrypto_ERROR_OPEN_FAILURE = 3, + OEMCrypto_ERROR_CLOSE_FAILURE = 4, + OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5, + OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6, + OEMCrypto_ERROR_SHORT_BUFFER = 7, + OEMCrypto_ERROR_NO_DEVICE_KEY = 8, + OEMCrypto_ERROR_NO_ASSET_KEY = 9, + OEMCrypto_ERROR_KEYBOX_INVALID = 10, + OEMCrypto_ERROR_NO_KEYDATA = 11, + OEMCrypto_ERROR_NO_CW = 12, + OEMCrypto_ERROR_DECRYPT_FAILED = 13, + OEMCrypto_ERROR_WRITE_KEYBOX = 14, + OEMCrypto_ERROR_WRAP_KEYBOX = 15, + OEMCrypto_ERROR_BAD_MAGIC = 16, + OEMCrypto_ERROR_BAD_CRC = 17, + OEMCrypto_ERROR_NO_DEVICEID = 18, + OEMCrypto_ERROR_RNG_FAILED = 19, + OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20, + OEMCrypto_ERROR_SETUP = 21, + OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22, + OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23, + OEMCrypto_ERROR_INVALID_SESSION = 24, + OEMCrypto_ERROR_NOT_IMPLEMENTED = 25, + OEMCrypto_ERROR_NO_CONTENT_KEY = 26, + OEMCrypto_ERROR_CONTROL_INVALID = 27, + OEMCrypto_ERROR_UNKNOWN_FAILURE = 28, + OEMCrypto_ERROR_INVALID_CONTEXT = 29, + OEMCrypto_ERROR_SIGNATURE_FAILURE = 30, + OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31, + OEMCrypto_ERROR_INVALID_NONCE = 32, + OEMCrypto_ERROR_TOO_MANY_KEYS = 33, + OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34, + OEMCrypto_ERROR_INVALID_RSA_KEY = 35, + OEMCrypto_ERROR_KEY_EXPIRED = 36, + OEMCrypto_ERROR_INSUFFICIENT_RESOURCES = 37, +} OEMCryptoResult; + +/* + * OEMCrypto_DestBufferDesc + * Describes the type and access information for the memory to receive + * decrypted data. + * + * The OEMCrypto API supports a range of client device architectures. + * Different architectures have different methods for acquiring and securing + * buffers that will hold portions of the audio or video stream after + * decryption. Three basic strategies are recognized for handling decrypted + * stream data: + * 1. Return the decrypted data in the clear into normal user memory + * (ClearBuffer). The caller uses normal memory allocation methods to + * acquire a buffer, and supplies the memory address of the buffer in the + * descriptor. + * 2. Place the decrypted data into protected memory (SecureBuffer). The + * caller uses a platform-specific method to acquire the protected buffer + * and a user-memory handle that references it. The handle is supplied + * to the decrypt call in the descriptor. + * 3. Place the decrypted data directly into the audio or video decoder fifo + * (Direct). The caller will use platform-specific methods to initialize + * the fifo and the decoders. The decrypted stream data is not accessible + * to the caller. + * + * Specific fields are as follows: + * + * (type == OEMCrypto_BufferType_Clear) + * address - Address of start of user memory buffer. + * max_length - Size of user memory buffer. + * (type == OEMCrypto_BufferType_Secure) + * buffer - handle to a platform-specific secure buffer. + * max_length - Size of platform-specific secure buffer. + * (type == OEMCrypto_BufferType_Direct) + * is_video - If true, decrypted bytes are routed to the video + * decoder. If false, decrypted bytes are routed to the + * audio decoder. + */ +typedef enum OEMCryptoBufferType { + OEMCrypto_BufferType_Clear, + OEMCrypto_BufferType_Secure, + OEMCrypto_BufferType_Direct +} OEMCrytoBufferType; + +typedef struct { + OEMCryptoBufferType type; + union { + struct { // type == OEMCrypto_BufferType_Clear + uint8_t* address; + size_t max_length; + } clear; + struct { // type == OEMCrypto_BufferType_Secure + void* handle; + size_t max_length; + size_t offset; + } secure; + struct { // type == OEMCrypto_BufferType_Direct + bool is_video; + } direct; + } buffer; +} OEMCrypto_DestBufferDesc; + +/* + * OEMCrypto_KeyObject + * Points to the relevant fields for a content key. The fields are extracted + * from the License Response message offered to OEMCrypto_LoadKeys(). Each + * field points to one of the components of the key. Key data, key control, + * and both IV fields are 128 bits (16 bytes): + * key_id - the unique id of this key. + * key_id_length - the size of key_id. + * key_data_iv - the IV for performing AES-128-CBC decryption of the + * key_data field. + * key_data - the key data. It is encrypted (AES-128-CBC) with the + * session's derived encrypt key and the key_data_iv. + * key_control_iv - the IV for performing AES-128-CBC decryption of the + * key_control field. + * key_control - the key control block. It is encrypted (AES-128-CBC) with + * the content key from the key_data field. + * + * The memory for the OEMCrypto_KeyObject fields is allocated and freed + * by the caller of OEMCrypto_LoadKeys(). + */ +typedef struct { + const uint8_t* key_id; + size_t key_id_length; + const uint8_t* key_data_iv; + const uint8_t* key_data; + size_t key_data_length; + const uint8_t* key_control_iv; + const uint8_t* key_control; +} OEMCrypto_KeyObject; + +/* + * OEMCrypto_KeyRefreshObject + * Points to the relevant fields for renewing a content key. The fields are + * extracted from the License Renewal Response message offered to + * OEMCrypto_RefreshKeys(). Each field points to one of the components of + * the key. + * key_id - the unique id of this key. + * key_control_iv - the IV for performing AES-128-CBC decryption of the + * key_control field. 16 bytes. + * key_control - the key control block. It is encrypted (AES-128-CBC) with + * the content key from the key_data field. 16 bytes. + * + * The key_data is unchanged from the original OEMCrypto_LoadKeys() call. Some + * Key Control Block fields, especially those related to key lifetime, may + * change. + * + * The memory for the OEMCrypto_KeyRefreshObject fields is allocated and freed + * by the caller of OEMCrypto_RefreshKeys(). + */ +typedef struct { + const uint8_t* key_id; + size_t key_id_length; + const uint8_t* key_control_iv; + const uint8_t* key_control; +} OEMCrypto_KeyRefreshObject; + +/* + * OEMCrypto_Algorithm + * This is a list of valid algorithms for OEMCrypto_Generic_* functions. + * Some are valid for encryption/decryption, and some for signing/verifying. + */ +typedef enum OEMCrypto_Algorithm { + OEMCrypto_AES_CBC_128_NO_PADDING = 0, + OEMCrypto_HMAC_SHA256 = 1, +} OEMCrypto_Algorithm; + +/* + * Flags indicating data endpoints in OEMCrypto_DecryptCTR. + */ +#define OEMCrypto_FirstSubsample 1 +#define OEMCrypto_LastSubsample 2 + +/* Obfuscation Renames. */ +#define OEMCrypto_Initialize _oecc01 +#define OEMCrypto_Terminate _oecc02 +#define OEMCrypto_InstallKeybox _oecc03 +#define OEMCrypto_GetKeyData _oecc04 +#define OEMCrypto_IsKeyboxValid _oecc05 +#define OEMCrypto_GetRandom _oecc06 +#define OEMCrypto_GetDeviceID _oecc07 +#define OEMCrypto_WrapKeybox _oecc08 +#define OEMCrypto_OpenSession _oecc09 +#define OEMCrypto_CloseSession _oecc10 +#define OEMCrypto_DecryptCTR _oecc11 +#define OEMCrypto_GenerateDerivedKeys _oecc12 +#define OEMCrypto_GenerateSignature _oecc13 +#define OEMCrypto_GenerateNonce _oecc14 +#define OEMCrypto_LoadKeys _oecc15 +#define OEMCrypto_RefreshKeys _oecc16 +#define OEMCrypto_SelectKey _oecc17 +#define OEMCrypto_RewrapDeviceRSAKey _oecc18 +#define OEMCrypto_LoadDeviceRSAKey _oecc19 +#define OEMCrypto_GenerateRSASignature _oecc20 +#define OEMCrypto_DeriveKeysFromSessionKey _oecc21 +#define OEMCrypto_APIVersion _oecc22 +#define OEMCrypto_SecurityLevel _oecc23 +#define OEMCrypto_Generic_Encrypt _oecc24 +#define OEMCrypto_Generic_Decrypt _oecc25 +#define OEMCrypto_Generic_Sign _oecc26 +#define OEMCrypto_Generic_Verify _oecc27 + +/* + * OEMCrypto_Initialize + * + * Description: + * Initialize the crypto firmware/hardware. + * + * Parameters: + * N/A + * + * Threading: + * No other function calls will be made while this function is running. This + * function will not be called again before OEMCrypto_Terminate. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INIT_FAILED failed to initialize crypto hardware + * + * Version: + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_Initialize(void); + +/* + * OEMCrypto_Terminate + * + * Description: + * The API closes the crypto operation and releases all resources used. + * + * Parameters: + * N/A + * + * Threading: + * No other OEMCrypto calls are made while this function is running. After + * this function is called, no other OEMCrypto calls will be made until another + * call to OEMCrypto_Initialize is made. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_TERMINATE_FAILED failed to de-initialize crypto hardware + * + * Version: + * This method is all API versions. + */ +OEMCryptoResult OEMCrypto_Terminate(void); + +/* + * OEMCrypto_OpenSession + * + * Description: + * The API provides for session based crypto initialization for AES CTR mode. + * + * Parameters: + * session (out) - pointer to crypto session identifier. + * + * Threading: + * No other Open/Close session calls will be made while this function is + * running. Functions on existing sessions may be called while this function + * is active. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_TOO_MANY_SESSIONS failed because too many sessions are open + * OEMCrypto_ERROR_OPEN_SESSION_FAILED failed to initialize the crypto session + * + * Version: + * This method changed in API version 5. + */ +OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION *session); + +/* + * OEMCrypto_CloseSession + * + * Description: + * The API provides for session based crypto termination for AES CTR mode. + * + * Parameters: + * session (in) - crypto session identifier. + * + * Threading: + * No other Open/Close session calls will be made while this function is + * running. Functions on existing sessions may be called while this function + * is active. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION no open session with that id. + * OEMCrypto_ERROR_CLOSE_SESSION_FAILED failed to terminate the crypto session + * + * Version: + * This method changed in API version 5. + */ +OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session); + +/* + * OEMCrypto_GenerateDerivedKeys + * + * Description: + * Generates three secondary keys -- mac_key_server, mac_key_client, and + * encrypt_key -- for handling signing and content key decryption under the + * license server protocol for AES CTR mode. + * + * Refer to document "Widevine Modular DRM Security Integration Guide for + * CENC" for details. This function computes the AES-128-CMAC of the + * enc_key_context and stores it in secure memory as the encrypt_key. It + * then computes four cycles of AES-128-CMAC of the mac_key_context and + * stores it in the mac_keys. The first two cycles are used for + * mac_key_server and the second two cycles are used for mac_key_client. + * These three keys will be stored until the next call to LoadKeys. + * + * Parameters: + * session (in) - crypto session identifier. + * mac_key_context (in) - pointer to memory containing context data for + * computing the HMAC generation key. + * mac_key_context_length (in) - length of the HMAC key context data. + * enc_key_context (in) - pointer to memory containing context data for + * computing the encryption key. + * enc_key_context_length (in) - length of the encryption key context data. + * + * Results: + * mac_key_server: the 256 bit mac key is generated and stored in secure memory. + * mac_key_client: the 256 bit mac key is generated and stored in secure memory. + * enc_key: the 128 bit encryption key is generated and stored in secure memory. + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 8. + */ +OEMCryptoResult OEMCrypto_GenerateDerivedKeys( + OEMCrypto_SESSION session, + const uint8_t *mac_key_context, + uint32_t mac_key_context_length, + const uint8_t *enc_key_context, + uint32_t enc_key_context_length); + +/* + * OEMCrypto_GenerateNonce + * + * Description: + * Generates a 32-bit nonce to detect possible replay attack on the key + * control block. The nonce is stored in secure memory and will be used + * for the next call to LoadKeys. + * + * Refer to documents "Widevine Modular DRM Security Integration Guide for + * CENC". + * + * Parameters: + * session (in) - crypto session identifier. + * nonce (out) - pointer to memory to received the computed nonce. + * + * Results: + * nonce: the nonce is also stored in secure memory. + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 5. + */ +OEMCryptoResult OEMCrypto_GenerateNonce( + OEMCrypto_SESSION session, + uint32_t* nonce); + +/* + * OEMCrypto_GenerateSignature + * + * Description: + * Generates a HMAC-SHA256 signature for license request signing under the + * license server protocol for AES CTR mode. This uses the key mac_key_client. + * + * NOTE: OEMCrypto_GenerateDerivedKeys() must be called first to establish the + * mac_key_client. + * + * Refer to document "Widevine Modular DRM Security Integration Guide for + * CENC" for details. + * + * Parameters: + * session (in) - crypto session identifier. + * message (in) - pointer to memory containing message to be signed. + * message_length (in) - length of the message. + * signature (out) - pointer to memory to received the computed signature. + * signature_length (in/out) - (in) length of the signature buffer. + * (out) actual length of the signature + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_SHORT_BUFFER + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 5. + */ +OEMCryptoResult OEMCrypto_GenerateSignature( + OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length); + +/* + * OEMCrypto_LoadKeys + * + * Description: + * Installs a set of keys for performing decryption in the current session. + * + * The relevant fields have been extracted from the License Response protocol + * message, but the entire message and associated signature are provided so + * the message can be verified (using HMAC-SHA256 with the derived + * mac_key_server). If the signature verification fails, ignore all other + * arguments and return OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add the + * keys to the session context. + * + * The keys will be decrypted using the current encrypt_key (AES-128-CBC) and + * the IV given in the KeyObject. Each key control block will be decrypted + * using the corresponding content key (AES-128-CBC) and the IV given in the + * KeyObject. + * + * If any key's control block does not have valid verification fields, return + * OEMCrypto_ERROR_INVALID_CONTEXT and do not install any keys. + * + * If any key's control block requires a nonce, and the nonce in the control + * block is different from the current nonce, return + * OEMCrypto_ERROR_INVALID_NONCE. In that case, do not install any keys. + * + * The new mac_keys are decrypted with the current encrypt_key and the offered + * IV. They replace the current mac_keys. + * + * The mac_keys and encrypt_key were generated and stored by the previous call + * to OEMCrypto_GenerateDerivedKeys(). The nonce was generated and stored by + * the previous call to OEMCrypto_GenerateNonce(). + * + * This session’s elapsed time clock is started at 0. The clock will be used + * in OEMCrypto_DecryptCTR. + * + * NOTE: OEMCrypto_GenerateDerivedKeys() must be called first to establish + * the mac_keys and encrypt_key. + * + * Refer to document "Widevine Modular DRM Security Integration Guide for + * CENC" for details. + * + * Parameters: + * session (in) - crypto session identifier. + * message (in) - pointer to memory containing message to be verified. + * message_length (in) - length of the message. + * signature (in) - pointer to memory containing the signature. + * signature_length (in) - length of the signature. + * enc_mac_keys_iv (in) - IV for decrypting new mac_key. Size is 128 bits. + * enc_mac_keys (in) - encrypted mac_keys for generating new mac_keys. Size is + * 512 bits. + * num_keys (in) - number of keys present. + * key_array (in) - set of keys to be installed. + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_TOO_MANY_KEYS + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 8. + */ +OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + const uint8_t* signature, + size_t signature_length, + const uint8_t* enc_mac_keys_iv, + const uint8_t* enc_mac_keys, + size_t num_keys, + const OEMCrypto_KeyObject* key_array); + +/* + * OEMCrypto_RefreshKeys + * + * Description: + * Updates an existing set of keys for continuing decryption in the + * current session. + * + * The relevant fields have been extracted from the Renewal Response protocol + * message, but the entire message and associated signature are provided so + * the message can be verified (using HMAC-SHA256 with the current + * mac_key_server). If the signature verification fails, ignore all other + * arguments and return OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add + * the keys to the session context. + * + * NOTE: OEMCrypto_GenerateDerivedKeys() or OEMCrypto_LoadKeys() must be + * called first to establish the mac_keys. + * + * Refer to document "Widevine Modular DRM Security Integration Guide for + * CENC" for details. + * + * Parameters: + * session (in) - crypto session identifier. + * message (in) - pointer to memory containing message to be verified. + * message_length (in) - length of the message. + * signature (in) - pointer to memory containing the signature. + * signature_length (in) - length of the signature. + * num_keys (in) - number of keys present. + * key_array (in) - set of keys to be installed. + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 8. + */ +OEMCryptoResult +OEMCrypto_RefreshKeys(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + const uint8_t* signature, + size_t signature_length, + size_t num_keys, + const OEMCrypto_KeyRefreshObject* key_array); + +/* + * OEMCrypto_SelectKey + * + * Description: + * Select a content key and install it in the hardware key ladder for + * subsequent decryption operations (OEMCrypto_DecryptCTR()) for this session. + * The specified key must have been previously "installed" via + * OEMCrypto_LoadKeys() or OEMCrypto_RefreshKeys(). + * + * This session’s elapsed time clock is started at 0. The clock will be used + * in OEMCrypto_DecryptCTR. + * + * A key control block is associated with the key and the session, and is used + * to configure the session context. The Key Control data is documented in + * "Key Control Block Definition". + * + * Step 1: Lookup the content key data via the offered key_id. The key data + * includes the key value, the content key IV, the key control + * block, and the key control block IV. + * + * Step 2: Latch the content key into the hardware key ladder. Set + * permission flags and timers based on the key's control block. + * + * Step 3: use the latched content key to decrypt (AES-128-CTR) + * to decrypt buffers passed in via OEMCrypto_DecryptCTR(). Continue + * to use this key until OEMCrypto_SelectKey() is called again, or + * until OEMCrypto_CloseSession() is called. + * + * Parameters: + * session (in) - crypto session identifier + * key_id (in) - pointer to the Key ID + * key_id_length (in) - length of the Key ID in bytes + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION crypto session ID invalid or not open + * OEMCrypto_ERROR_NO_DEVICE_KEY failed to decrypt device key + * OEMCrypto_ERROR_NO_CONTENT_KEY failed to decrypt content key + * OEMCrypto_ERROR_CONTROL_INVALID invalid or unsupported control input + * OEMCrypto_ERROR_KEYBOX_INVALID cannot decrypt and read from Keybox + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 8. + */ +OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session, + const uint8_t* key_id, + size_t key_id_length); + +/* + * OEMCrypto_DecryptCTR + * + * Description: + * + * The API decrypts (AES-CTR) or copies the payload in the buffer referenced by + * the data_addr parameter to the buffer determined by out_buffer, using the key + * previously set by a call to OEMCrypto_SelectKey for the specified session. + * + * Parameters: + * session (in) - crypto session identifier. + * data_addr (in) - An unaligned pointer to this segment of the stream. + * data_length (in) - The length of this segment of the stream. + * is_encrypted (in) - True if the buffer described by data_addr, + * data_length is encrypted. If is_encrypted is false, only the + * data_addr and data_length parameters are used. The iv and offset + * arguments are ignored. + * iv (in) - The initial value block to be used for content decryption. + * This is discussed further below. + * block_offset (in) - If non-zero, the decryption block boundary is + * different from the start of the data. offset should be subtracted from + * data_addr to compute the starting address of the first decrypted + * block. The bytes between the decryption block start address and + * data_addr are discarded after decryption. This is only used to adjust + * the start of decryption block. It does not adjust the beginning of the + * source or destination data. 0 <= block_offset < 16. + * out_buffer (in) - A caller-owned descriptor that specifies the + * handling of the decrypted byte stream. See OEMCrypto_DestbufferDesc + * for details. + * subsample_flags (in) - bitwise flags indicating if this is the first, + * middle, or last subsample in a chunk of data. 1 = first subsample, + * 2 = last subsample, 3 = both first and last subsample, 0 = neither + * first nor last subsample. + * + * AES CTR is a stream cipher. The stream may be composed of arbitrary- + * length clear and encrypted segments. The encrypted portions of a sample + * are collectively treated as a continuous sequence of decryption + * block-sized blocks even though the sequence is interrupted by clear blocks. + * This means a given encrypted segment may not start or end on a decryption + * block boundary. + * + * If data_addr is not aligned with a decryption block boundary (offset != 0), + * the additional offset bytes before data_addr (pre-padding) are included in + * the decrypt operation, and they are dropped after decryption. If + * data_length + offset is not a multiple of the decryption block size, the + * extra bytes in the final decryption block (post-padding) are also dropped + * after decryption. The caller is responsible for guaranteeing that all + * memory addresses from (data-addr - pre-padding) to (data-addr + + * data-length + post-padding) are valid memory addresses. + * + * After decrypting the entire buffer including any pre-padding and + * post-padding, send data_length bytes starting at data_addr to the decoder. + * + * NOTES: + * IV points to the counter value to be used for the initial + * encrypted block of the input buffer. The IV length is the AES + * block size. For subsequent encrypted AES blocks the IV is + * calculated by incrementing the lower 64 bits (byte 8-15) of the + * IV value used for the previous block. The counter rolls over to + * zero when it reaches its maximum value (0xFFFFFFFFFFFFFFFF). + * The upper 64 bits (byte 0-7) of the IV do not change. + * + * This method may be called several times before the decrypted data is used. + * For this reason, the parameter subsample_flags may be used to optimize + * decryption. The first buffer in a chunk of data will have the + * OEMCrypto_FirstSubsample bit set in subsample_flags. The last buffer in a + * chunk of data will have the OEMCrypto_LastSubsample bit set in + * subsample_flags. The decrypted data will not be used until after + * OEMCrypto_LastSubsample has been set. If an implementation decrypts data + * immediately, it may ignore subsample_flags. + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_DECRYPT_FAILED + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 5. + */ +OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session, + const uint8_t *data_addr, + size_t data_length, + bool is_encrypted, + const uint8_t *iv, + size_t block_offset, + const OEMCrypto_DestBufferDesc* out_buffer, + uint8_t subsample_flags); + +/* + * OEMCrypto_WrapKeybox + * + * Description: + * Wrap the Keybox with a key derived from the device key. If transportKey + * is not NULL, the input keybox is encrypted with transportKey. If so, + * decrypt the input keybox before wrapping it, using transportKey in AES-CBC + * mode with an IV of all zeroes. This function is only needed if the + * provisioning method involves saving the keybox to the file system. + * + * Parameters: + * keybox (in) - Pointer to keybox data. + * keyboxLength - Length of the Keybox data in bytes + * wrappedKeybox (out) - Pointer to wrapped keybox + * wrappedKeyboxLength (out) - Pointer to the length of the wrapped keybox in + * bytes + * transportKey (in) - An optional AES transport key. If provided, the input + * keybox is encrypted with this transport key with AES-CBC + * and a null IV. + * transportKeyLength - number of bytes in the transportKey + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_WRAP_KEYBOX failed to wrap Keybox + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_NOT_IMPLEMENTED + * + * Version: + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_WrapKeybox(const uint8_t *keybox, + size_t keyBoxLength, + uint8_t *wrappedKeybox, + size_t *wrappedKeyBoxLength, + const uint8_t *transportKey, + size_t transportKeyLength); + + +/* + * OEMCrypto_InstallKeybox + * + * Description: + * Unwrap and store the keybox to persistent memory. + * The device key must be stored securely. + * + * This function is used once to load the keybox onto the device at + * provisioning time. + * + * Parameters: + * keybox (in) - Pointer to clear keybox data. Must have been originally + * wrapped with OEMCrypto_WrapKeybox. + * keyboxLength (in) - Length of the keybox data in bytes. + * + * Threading: + * This function is not called simultaneously with any other functions. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_WRITE_KEYBOX failed to handle and store Keybox + * + * Version: + * This method is all API versions. + */ +OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t *keybox, + size_t keyBoxLength); + +/* + * OEMCrypto_IsKeyboxValid + * + * Description: + * Validate the Widevine Keybox stored on the device. + * + * The API performs two verification steps on the Keybox. It first verifies + * the MAGIC field contains a valid signature (must be 'kbox'). The API then + * computes the CRC using CRC-32 (Posix 1003.2 standard) and compares the + * checksum to the CRC stored in the Keybox. The CRC is computed over the + * entire Keybox excluding the 4 CRC bytes (i.e. Keybox[0..123]). + * + * Parameters: + * none + * + * Threading: + * This function may be called simultaneously with any session functions. + * + * Returns: + * OEMCrypto_SUCCESS + * OEMCrypto_ERROR_BAD_MAGIC + * OEMCrypto_ERROR_BAD_CRC + * + * Version: + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_IsKeyboxValid(void); + +/* + * OEMCrypto_GetDeviceID + * + * Description: + * Retrieve the device's unique identifier from the Keybox. + * + * Parameters: + * deviceId (out) - pointer to the buffer that receives the Device ID + * idLength (in/out) - on input, size of the caller's device ID buffer. + * On output, the number of bytes written into the buffer. + * + * Threading: + * This function may be called simultaneously with any session functions. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER buffer is too small to return the device ID + * OEMCrypto_ERROR_NO_DEVICEID failed to return Device Id + * + * Version: + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, + size_t *idLength); + +/* + * OEMCrypto_GetKeyData + * + * Description: + * Returns the Key Data field from the Keybox. The Key Data field does not + * need to be encrypted by an OEM root key, but may be if desired. + * + * If the Key Data field was encrypted with an OEM root key when the Keybox + * was stored on the device, then this function should decrypt it and return + * the clear Key Data. If the Key Data was not encrypted, then this function + * should just access and return the clear Key data. + * + * Parameters: + * keyData (out) - pointer to a caller-managed buffer to hold the Key Data + * field from the Keybox + * dataLength (in/out) - on input, the allocated buffer size. On output, + * the number of bytes in KeyData. + * + * Threading: + * This function may be called simultaneously with any session functions. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER the buffer is too small to return the KeyData + * OEMCrypto_ERROR_NO_KEYDATA failed to return KeyData + * + * Version: + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, + size_t *keyDataLength); + +/* + * OEMCrypto_GetRandom + * + * Description: + * Return a buffer filled with hardware-generated random bytes. If the + * hardware feature does not exist, return OEMCrypto_ERROR_RNG_NOT_SUPPORTED. + * + * Parameters: + * randomData (out) - Pointer to caller-manager buffer that will receive the + * random data. + * dataLength (in) - Length of the random data buffer in bytes. + * + * Threading: + * This function may be called simultaneously with any session functions. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_RNG_FAILED failed to generate random number + * OEMCrypto_ERROR_RNG_NOT_SUPPORTED function not supported + * + * Version: + * This method is supported by all API versions. + */ +OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData, + size_t dataLength); + +/* + * OEMCrypto_RewrapDeviceRSAKey + * + * Description: + * Verifies an RSA provisioning response is valid and corresponds + * to the previous provisioning request by checking the nonce. The RSA + * private key is decrypted and stored in secure memory. The RSA key is then + * re-encrypted for storage on the filesystem. The OEM may either encrypt it + * with the private key from the Widevine Keybox, or with an OEM specific + * device key. The signature of the message is verified with the + * mac_key_server. + * + * Parameters: + * session (in) - crypto session identifier. + * message (in) - pointer to memory containing message to be + * - verified. + * message_length (in) - length of the message, in bytes. + * signature (in) - pointer to memory containing the HMAC-SHA256 + * - signature for message, received from the + * - provisioning server. + * signature_length (in) - length of the signature, in bytes. + * nonce (in) - The nonce provided in the provisioning response. + * enc_rsa_key (in) - Encrypted device private RSA key received from + * - the provisioning server. Format is PKCS#8 + * - PrivateKeyInfo, encrypted with the derived + * - encryption key, using AES-128-CBC with PKCS#5 + * - padding. + * enc_rsa_key_length (in) - length of the encrypted RSA key, in bytes. + * enc_rsa_key_iv (in) - IV for decrypting RSA key. Size is 128 bits. + * wrapped_rsa_key (out) - pointer to buffer in which encrypted RSA key + * - should be stored. May be null on the first call + * - in order to find required buffer size. + * wrapped_rsa_key_length (in/out) - length of the encrypted RSA key, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_RSA_KEY + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_INVALID_NONCE + * OEMCrypto_ERROR_SHORT_BUFFER + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API versions 8. + */ + +OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + const uint8_t* signature, + size_t signature_length, + const uint32_t *nonce, + const uint8_t* enc_rsa_key, + size_t enc_rsa_key_length, + const uint8_t* enc_rsa_key_iv, + uint8_t* wrapped_rsa_key, + size_t *wrapped_rsa_key_length); + +/* + * OEMCrypto_LoadDeviceRSAKey + * + * Description: + * Loads a wrapped RSA private key to secure memory for use by this session + * in future calls to OEMCrypto_GenerateRSASignature. The wrapped RSA key + * will be one verified and wrapped by OEMCrypto_RewrapDeviceRSAKey. The RSA + * private key should be stored in secure memory. + * + * Parameters: + * session (in) - crypto session identifier. + * wrapped_rsa_key (in) - wrapped device RSA key stored on the device. + * - Format is PKCS#8 PrivateKeyInfo, and + * - encrypted with a key internal to the OEMCrypto + * - instance, using AES-128-CBC with PKCS#5 + * - padding. This is the wrapped key generated + * - by OEMCrypto_RewrapDeviceRSAKey. + * wrapped_rsa_key_length (in) - length of the wrapped key buffer, in bytes. + * wrapped_rsa_key_iv (in) - The initialization vector used to encrypt + * - wrapped_rsa_key. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_RSA_KEY + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 6. + */ +OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(OEMCrypto_SESSION session, + const uint8_t* wrapped_rsa_key, + size_t wrapped_rsa_key_length); + +/* + * OEMCrypto_GenerateRSASignature + * + * Description: + * The OEMCrypto_GenerateRSASignature method is used to sign messages using + * the device private RSA key, specifically, it is used to sign the initial + * license request. + * + * Refer to the document "Widevine Security Integration Guide for DASH" for + * more details. + * + * Parameters: + * session (in) - crypto session identifier. + * message (in) - pointer to memory containing message to be + * - signed. + * message_length (in) - length of the message, in bytes. + * signature (out) - buffer to hold the message signature. On + * - return, it will contain the message signature + * - generated with the device private RSA key using + * - RSASSA-PSS. + * signature_length (in/out) - (in) length of the signature buffer, in bytes. + * - (out) actual length of the signature + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_SHORT_BUFFER if the signature buffer is too small. + * OEMCrypto_ERROR_INVALID_RSA_KEY + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 6. + */ +OEMCryptoResult OEMCrypto_GenerateRSASignature(OEMCrypto_SESSION session, + const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t *signature_length); + +/* + * OEMCrypto_DeriveKeysFromSessionKey + * + * Description: + * Generates three secondary keys -- mac_key_server, mac_key_client, and + * encrypt_key -- for handling signing and content key decryption under the + * license server protocol for AES CTR mode. + * + * This function is similar to OEMCrypto_GenerateDerivedKeys, except that it + * uses a session key to generate the secondary keys instead of the Widevine + * Keybox device key. These two keys will be stored in secure memory until + * the next call to LoadKeys. The session key is passed in encrypted by the + * device RSA public key, and must be decrypted with the RSA private key + * before use. Once the enc_key and mac_keys have been generated, all calls + * to LoadKeys and RefreshKeys proceed in the same manner for license + * requests using RSA or using a Widevine keybox token. + * + * Parameters: + * session (in) - crypto session identifier. + * enc_session_key (in) - session key, encrypted with the device RSA key + * - (from the device certifcate) using RSA-OAEP. + * enc_session_key_length (in) - length of session_key, in bytes. + * mac_key_context (in) - pointer to memory containing context data for + * - computing the HMAC generation key. + * mac_key_context_length (in) - length of the HMAC key context data, in bytes. + * enc_key_context (in) - pointer to memory containing context data for + * - computing the encryption key. + * enc_key_context_length (in) - length of the encryption key context data, in + * - bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INVALID_CONTEXT + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Version: + * This method changed in API version 8. + */ +OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(OEMCrypto_SESSION session, + const uint8_t* enc_session_key, + size_t enc_session_key_length, + const uint8_t *mac_key_context, + size_t mac_key_context_length, + const uint8_t *enc_key_context, + size_t enc_key_context_length); + + +/* + * OEMCrypto_APIVersion() + * + * Description: + * This function returns the current API version number. Because this + * API is part of a shared library, the version number allows the calling + * application to avoid version mis-match errors. + * + * There is a possibility that some API methods will be backwards compatible, + * or backwards compatible at a reduced security level. + * + * There is no plan to introduce forward-compatibility. I.e. applications + * will reject a library with a newer version of the API. + * + * Returns: + * The current version number. + * + * Version: + * This method should change in all API versions. + */ +uint32_t OEMCrypto_APIVersion(); + +/* + * OEMCrypto_SecurityLevel() + * + * Description: + * This function returns the security level of the OEMCrypto library. + * + * Since this function is spoofable, it is not relied on for security + * purposes. It is for information only. + * + * Returns: + * A null terminated string. Useful values are "L1", "L2" or "L3". + * + * Version: + * This method changed in API version 6. + */ +const char* OEMCrypto_SecurityLevel(); + +/* + * OEMCryptoResult OEMCrypto_Generic_Encrypt + * + * This function encrypts a generic buffer of data using the current key. + * + * Verification: + * The following checks should be performed. If any check fails, an error is + * returned, and the data is not encrypted. + * + * The control bit for the current key shall have the Allow_Encrypt set. If + * not, return OEMCrypto_ERROR_UNKNOWN_FAILURE. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] in_buffer: pointer to memory containing data to be encrypted. + * [in] buffer_length: length of the buffer, in bytes. + * [in] iv: IV for encrypting data. Size is specified by the algorithm. + * [in] algorithm: Specifies which encryption algorithm to use. See + * OEMCrypto_Algorithm for valid values. + * [out] out_buffer: pointer to buffer in which encrypted data should be stored. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Version: + * This method changed in API version 7. + */ +OEMCryptoResult OEMCrypto_Generic_Encrypt(OEMCrypto_SESSION session, + const uint8_t* in_buffer, + size_t buffer_length, + const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer); + +/* + * OEMCrypto_Generic_Decrypt + * + * This function decrypts a generic buffer of data using the current key. + * + * Verification: + * The following checks should be performed. If any check fails, an error is + * returned, and the data is not decrypted. + * + * The control bit for the current key shall have the Allow_Decrypt set. If + * not, return OEMCrypto_ERROR_DECRYPT_FAILED. + * If the current key’s control block has the Data_Path_Type bit set, then + * return OEMCrypto_ERROR_DECRYPT_FAILED. + * If the current key’s control block has the HDCP bit set, then return + * OEMCrypto_ERROR_DECRYPT_FAILED. + * + * Parameters: + * [in] session: crypto session identifier. + * [in] in_buffer: pointer to memory containing data to be encrypted. + * [in] buffer_length: length of the buffer, in bytes. + * [in] iv: IV for encrypting data. Size depends on the algorithm. + * [in] algorithm: Specifies which encryption algorithm to use. See + * OEMCrypto_Algorithm for valid values. + * [out] out_buffer: pointer to buffer in which decrypted data should be stored. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Version: + * This method changed in API version 7. + */ +OEMCryptoResult OEMCrypto_Generic_Decrypt(OEMCrypto_SESSION session, + const uint8_t* in_buffer, + size_t buffer_length, + const uint8_t* iv, + OEMCrypto_Algorithm algorithm, + uint8_t* out_buffer); + +/* + * OEMCrypto_Generic_Sign + * + * This function signs a generic buffer of data using the current key. + * + * Verification + * The following checks should be performed. If any check fails, + * an error is returned, and the signature is not generated. + * + * The control bit for the current key shall have the Allow_Sign set. + * + * Parameters + * [in] session: crypto session identifier. + * [in] in_buffer: pointer to memory containing data to be encrypted. + * [in] buffer_length: length of the buffer, in bytes. + * [in] algorithm: Specifies which algorithm to use. See + * OEMCrypto_Algorithm for valid values. + * [out] signature: pointer to buffer in which signature should be stored. + * [in/out] signature_length: (in) length of the signature buffer, in bytes. + * (out) actual length of the signature + * + * Returns + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_SHORT_BUFFER if signature buffer is not large enough to hold + * signature. + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_DECRYPT_FAILED + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Threading + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Version: + * This method changed in API version 7. + */ +OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session, + const uint8_t* in_buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + uint8_t* signature, + size_t* signature_length); + +/* + * OEMCrypto_Generic_Verify + * This function verfies the signature of a generic buffer of data using the + * current key. + * + * Verification + * The following checks should be performed. If any check fails, an error is + * returned, and the data is not signed. + * + * The control bit for the current key shall have the Allow_Verify set. + * The signature of the message shall be computed, and the API shall verify the + * computed signature matches the signature passed in. If not, return + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * + * Parameters + * [in] session: crypto session identifier. + * [in] in_buffer: pointer to memory containing data to be encrypted. + * [in] buffer_length: length of the buffer, in bytes. + * [in] algorithm: Specifies which algorithm to use. Current valid value is + * HMAC_SHA256. + * [in] signature: pointer to signature buffer. + * [in] signature_length: length of the signature buffer, in bytes. + * + * Returns: + * OEMCrypto_SUCCESS success + * OEMCrypto_ERROR_KEY_EXPIRED + * OEMCrypto_ERROR_SIGNATURE_FAILURE + * OEMCrypto_ERROR_NO_DEVICE_KEY + * OEMCrypto_ERROR_INVALID_SESSION + * OEMCrypto_ERROR_INSUFFICIENT_RESOURCES + * OEMCrypto_ERROR_UNKNOWN_FAILURE + * + * Threading: + * This function may be called simultaneously with functions on other sessions, + * but not with other functions on this session. + * + * Version: + * This method changed in API version 7. + */ +OEMCryptoResult OEMCrypto_Generic_Verify(OEMCrypto_SESSION session, + const uint8_t* in_buffer, + size_t buffer_length, + OEMCrypto_Algorithm algorithm, + const uint8_t* signature, + size_t signature_length); + +#ifdef __cplusplus +} +#endif + +#endif // OEMCRYPTO_CENC_H_ diff --git a/oemcrypto/test/oemcrypto_test.cpp b/oemcrypto/test/oemcrypto_test.cpp new file mode 100644 index 00000000..2281265b --- /dev/null +++ b/oemcrypto/test/oemcrypto_test.cpp @@ -0,0 +1,3887 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// OEMCrypto unit tests +// +#include // needed for ntoh() +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OEMCryptoCENC.h" +#include "oemcrypto_key_mock.h" +#include "string_conversions.h" +#include "wv_cdm_constants.h" +#include "wv_keybox.h" + +using namespace std; + +namespace { +const size_t kNumKeys = 4; +const size_t kBufferMaxLength = 256; +#if defined(TEST_SPEED_MULTIPLIER) // Can slow test time limits when + // debugging is slowing everything. +const int kSpeedMultiplier = TEST_SPEED_MULTIPLIER1; +#else +const int kSpeedMultiplier = 1; +#endif +const int kShortSleep = 1 * kSpeedMultiplier; +const int kLongSleep = 2 * kSpeedMultiplier; +const uint32_t kDuration = 2 * kSpeedMultiplier; +const uint32_t kLongDuration = 5 * kSpeedMultiplier; +} + +namespace wvoec { + +typedef struct { + uint8_t verification[4]; + uint32_t duration; + uint32_t nonce; + uint32_t control_bits; +} KeyControlBlock; + +const size_t kTestKeyIdLength = 12; // pick a length. any length. +typedef struct { + uint8_t key_id[kTestKeyIdLength]; + uint8_t key_data[wvcdm::MAC_KEY_SIZE]; + size_t key_data_length; + uint8_t key_iv[wvcdm::KEY_IV_SIZE]; + uint8_t control_iv[wvcdm::KEY_IV_SIZE]; + KeyControlBlock control; +} MessageKeyData; + +struct MessageData { + MessageKeyData keys[kNumKeys]; + uint8_t mac_key_iv[wvcdm::KEY_IV_SIZE]; + uint8_t mac_keys[2*wvcdm::MAC_KEY_SIZE]; +}; + +const size_t kMaxTestRSAKeyLength = 2000; // Rough estimate. +struct RSAPrivateKeyMessage { + uint8_t rsa_key[kMaxTestRSAKeyLength]; + uint8_t rsa_key_iv[wvcdm::KEY_IV_SIZE]; + size_t rsa_key_length; + uint32_t nonce; +}; + +//WHAT: These are test keyboxes. They will not be accepted by +// production systems. +//WHY: By using known keyboxes for these tests, the results for +// a given set of inputs to a test are predictable and can +// be compared to the actual results. +const wvoec_mock::WidevineKeybox kDefaultKeybox = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey01 + 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0xfb, 0xda, 0x04, 0x89, 0xa1, 0x58, 0x16, 0x0e, + 0xa4, 0x02, 0xe9, 0x29, 0xe3, 0xb6, 0x8f, 0x04, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, 0xc1, + 0x22, 0x67, 0x80, 0x53, 0x36, 0x21, 0x36, 0xbd, + 0xf8, 0x40, 0x8f, 0x82, 0x76, 0xe4, 0xc2, 0xd8, + 0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, 0x64, + 0x6e, 0x58, 0x73, 0x49, 0x30, 0xac, 0xeb, 0xe8, + 0x99, 0xb3, 0xe4, 0x64, 0x18, 0x9a, 0x14, 0xa8, + 0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, 0x64, + 0x0b, 0xd2, 0x2e, 0xf4, 0x4b, 0x2d, 0x7e, 0x39, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0x0a, 0x7a, 0x2c, 0x35, + } +}; + +static wvoec_mock::WidevineKeybox kValidKeybox02 = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey02 + 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0x76, 0x5d, 0xce, 0x01, 0x04, 0x89, 0xb3, 0xd0, + 0xdf, 0xce, 0x54, 0x8a, 0x49, 0xda, 0xdc, 0xb6, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0x92, 0x27, 0x0b, 0x1f, 0x1a, 0xd5, 0xc6, 0x93, + 0x19, 0x3f, 0xaa, 0x74, 0x1f, 0xdd, 0x5f, 0xb4, + 0xe9, 0x40, 0x2f, 0x34, 0xa4, 0x92, 0xf4, 0xae, + 0x9a, 0x52, 0x39, 0xbc, 0xb7, 0x24, 0x38, 0x13, + 0xab, 0xf4, 0x92, 0x96, 0xc4, 0x81, 0x60, 0x33, + 0xd8, 0xb8, 0x09, 0xc7, 0x55, 0x0e, 0x12, 0xfa, + 0xa8, 0x98, 0x62, 0x8a, 0xec, 0xea, 0x74, 0x8a, + 0x4b, 0xfa, 0x5a, 0x9e, 0xb6, 0x49, 0x0d, 0x80, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0x2a, 0x3b, 0x3e, 0xe4, + } +}; + +static wvoec_mock::WidevineKeybox kValidKeybox03 = { + // Sample keybox used for test vectors + { + // deviceID + 0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey03 + 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........ + }, { + // key + 0x25, 0xe5, 0x2a, 0x02, 0x29, 0x68, 0x04, 0xa2, + 0x92, 0xfd, 0x7c, 0x67, 0x0b, 0x67, 0x1f, 0x31, + }, { + // data + 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19, + 0xf4, 0x0a, 0x0e, 0xa2, 0x0a, 0x71, 0xd5, 0x92, + 0xfa, 0xa3, 0x25, 0xc6, 0x4b, 0x76, 0xf1, 0x64, + 0xf4, 0x60, 0xa0, 0x30, 0x72, 0x23, 0xbe, 0x03, + 0xcd, 0xde, 0x7a, 0x06, 0xd4, 0x01, 0xeb, 0xdc, + 0xe0, 0x50, 0xc0, 0x53, 0x0a, 0x50, 0xb0, 0x37, + 0xe5, 0x05, 0x25, 0x0e, 0xa4, 0xc8, 0x5a, 0xff, + 0x46, 0x6e, 0xa5, 0x31, 0xf3, 0xdd, 0x94, 0xb7, + 0xe0, 0xd3, 0xf9, 0x04, 0xb2, 0x54, 0xb1, 0x64, + }, { + // magic + 0x6b, 0x62, 0x6f, 0x78, + }, { + // Crc + 0xa1, 0x99, 0x5f, 0x46, + } +}; + +// WHAT: A 2048-bit test RSA Private Key +// WHY: This is used to verify the functions that manipulate +// RSA keys. +static const uint8_t kTestPKCS1RSAPrivateKey2_2048[] = { + 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, 0x00, 0x02, + 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, 0x36, 0x60, + 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, 0x40, 0xb4, + 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, 0x94, 0x58, + 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, 0xe0, 0x88, + 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, 0x56, 0x7e, + 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, 0x2a, 0xaa, + 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, + 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, 0x34, 0xf7, + 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, 0x28, 0xda, + 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, 0x10, 0xeb, + 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, 0x3e, 0x68, + 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, 0x29, 0xf2, + 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, + 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, 0xf2, 0xc2, + 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, 0xcd, 0x9a, + 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, 0x54, 0x8c, + 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, 0xb3, 0x4e, + 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, 0x98, 0x56, + 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, 0xed, 0xa3, + 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, 0x96, 0x88, + 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, 0xc9, 0x83, + 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, 0x18, 0xe4, + 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, 0x4c, 0xc8, + 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, 0x2d, 0x5f, + 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, 0x21, 0x18, + 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, 0xca, 0x2e, + 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, 0x82, 0x46, + 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, 0x07, 0x2c, + 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, 0x27, 0x29, + 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, 0xd3, 0x5b, + 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, 0x39, 0xb4, + 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, 0x2c, 0x5f, + 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, 0x01, 0x00, + 0x01, 0x02, 0x82, 0x01, 0x00, 0x5e, 0x79, 0x65, + 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, 0x45, 0x0f, + 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, 0xd5, 0xde, + 0x33, 0x63, 0xd8, 0xb8, 0xac, 0x97, 0xeb, 0x3f, + 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, 0x3b, 0x5c, + 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, 0x46, 0xf5, + 0xca, 0x2d, 0x8b, 0x3a, 0x7e, 0xdc, 0x45, 0x38, + 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, 0x5e, 0x79, + 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, 0xc1, 0x6b, + 0x78, 0x04, 0x4e, 0x8e, 0x79, 0xf9, 0x0a, 0xfc, + 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, 0x68, 0x7b, + 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, 0xa7, 0x79, + 0x5c, 0x7a, 0x81, 0xd1, 0x71, 0xe7, 0x00, 0x21, + 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, 0xbe, 0x09, + 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, 0x0e, 0x97, + 0x8d, 0x89, 0x6e, 0xf1, 0xe8, 0x88, 0x7a, 0xd1, + 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, 0x25, 0x0b, + 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, 0xb6, 0xda, + 0xc6, 0x24, 0x5a, 0xd0, 0x37, 0x14, 0x46, 0xc7, + 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, 0xde, 0x00, + 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, 0x68, 0x71, + 0x17, 0x66, 0x12, 0x1a, 0x87, 0x27, 0xf7, 0xef, + 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, 0x6f, 0x35, + 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, 0x13, 0x86, + 0x9c, 0x79, 0xd0, 0xb7, 0xb6, 0x64, 0xe8, 0x86, + 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, 0x1f, 0x51, + 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, 0x70, 0x98, + 0x0f, 0xee, 0xa8, 0x96, 0x07, 0x5f, 0x45, 0x6a, + 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, 0xf6, 0x06, + 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, 0x96, 0xa9, + 0x03, 0x17, 0xbb, 0x4e, 0xc9, 0x21, 0xe0, 0xac, + 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, 0xb2, 0x51, + 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, 0x81, 0x81, + 0x00, 0xcf, 0x73, 0x8c, 0xbe, 0x6d, 0x45, 0x2d, + 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, 0x78, 0xcc, + 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, 0x64, 0x60, + 0x8c, 0x43, 0xeb, 0x85, 0xab, 0x04, 0xb6, 0x7d, + 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, 0x84, 0x68, + 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, 0xde, 0x51, + 0x4b, 0xb6, 0x51, 0x86, 0x7b, 0xd0, 0xe6, 0x4d, + 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, 0x3a, 0x83, + 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, 0x93, 0xd7, + 0x9c, 0x27, 0x80, 0xb7, 0x1e, 0x64, 0x9e, 0xf7, + 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, 0x18, 0x6c, + 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, 0x90, 0x8f, + 0xa2, 0x16, 0x22, 0x6a, 0xcc, 0x48, 0x06, 0x74, + 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, 0x3c, 0x2d, + 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, 0x33, 0x85, + 0x26, 0x60, 0x48, 0x16, 0xcb, 0xef, 0xf8, 0xcd, + 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, 0x15, 0x43, + 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, 0xc3, 0x41, + 0x45, 0x97, 0xb1, 0x49, 0xc2, 0x19, 0x23, 0x87, + 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, 0xcb, 0x43, + 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, 0xbb, 0xdb, + 0xfd, 0x11, 0x9d, 0x17, 0x68, 0x78, 0x6d, 0x61, + 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, 0xa7, 0x5b, + 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, 0x91, 0x99, + 0xe5, 0x91, 0x32, 0x2d, 0xeb, 0x3f, 0xd8, 0x3e, + 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, 0xc1, 0xee, + 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, 0x58, 0xf4, + 0xd0, 0xb2, 0x74, 0x1d, 0x8e, 0x87, 0x46, 0xcd, + 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, 0x0d, 0x6c, + 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, 0xda, 0xfb, + 0xed, 0x83, 0x51, 0x67, 0xa9, 0x55, 0xab, 0x54, + 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, 0x53, 0xa8, + 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, 0x81, 0x80, + 0x67, 0x9c, 0x32, 0x83, 0x39, 0x57, 0xff, 0x73, + 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, 0x0a, 0x2d, + 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, 0xf3, 0x90, + 0x9a, 0xab, 0x9b, 0x0b, 0x1b, 0x43, 0x79, 0xa0, + 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, 0xeb, 0xdb, + 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, 0xb8, 0xe1, + 0xb3, 0xa1, 0x6c, 0x25, 0x92, 0xe4, 0x33, 0xb2, + 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, 0x37, 0x43, + 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, 0x20, 0x82, + 0xa1, 0x48, 0x2c, 0x2d, 0x45, 0xdc, 0x0f, 0x62, + 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, 0xf9, 0xca, + 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, 0xc8, 0x28, + 0x10, 0x1e, 0x08, 0x71, 0x16, 0xd8, 0x02, 0x71, + 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, 0xb1, 0x31, + 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, 0x35, 0x95, + 0x41, 0x29, 0x40, 0x19, 0x83, 0x35, 0x24, 0x69, + 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, 0xcc, 0x3b, + 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, 0x50, 0x76, + 0x63, 0x94, 0x49, 0x4c, 0xad, 0x10, 0xcb, 0x47, + 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, 0x6a, 0xab, + 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, 0x8a, 0xa2, + 0xb9, 0x8f, 0xce, 0xec, 0x5e, 0x61, 0xa8, 0xcd, + 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, 0x5f, 0xdf, + 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, 0xe4, 0x8b, + 0x01, 0x06, 0x59, 0x22, 0xfa, 0x34, 0x4b, 0x81, + 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, 0x77, 0xe6, + 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, 0x6c, 0xfa, + 0x2b, 0xd9, 0x83, 0x5a, 0x2d, 0x0c, 0x3b, 0x70, + 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, 0xd9, 0xbe, + 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, 0x66, 0xff, + 0x1c, 0x1b, 0xc8, 0x96, 0x76, 0xe8, 0x6f, 0x44, + 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, 0xac, 0x21, + 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, 0x2c, 0x7c, + 0xad, 0x1e, 0x75, 0xf6, 0x69, 0x1d, 0xe7, 0xa6, + 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, 0x28, 0x66, + 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, 0xae, 0xb7, + 0x65, 0x2c, 0x52, 0xf9, 0xe4, 0xc7, 0x81, 0x7b, + 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, 0x70, 0xcf, + 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, 0x75, 0x61, + 0x96, 0x86, 0x4b, 0xb6, 0x2b, 0xad, 0xf0, 0xad, + 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, 0x50, 0xcb, + 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, 0xd3, 0x29, + 0x23, 0x02, 0x60, 0xf7, 0xab, 0x30, 0x40, 0xda, + 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, 0xa2, 0x0d, + 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, 0xf4, 0xd4, + 0x52, 0x95, 0x00, 0xae, 0x84, 0x6b, 0x47, 0xb2, + 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, 0x72, 0x2c, + 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, 0x56, 0xfe, + 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 }; + +// WHAT: A 2048 bit RSA key in PKCS#8 PrivateKeyInfo format +// WHY: Used to verify the functions that manipulate RSA keys. +static const uint8_t kTestRSAPKCS8PrivateKeyInfo2_2048[] = { + 0x30, 0x82, 0x04, 0xbc, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa6, 0x30, 0x82, 0x04, 0xa2, 0x02, 0x01, + 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa7, 0x00, + 0x36, 0x60, 0x65, 0xdc, 0xbd, 0x54, 0x5a, 0x2a, + 0x40, 0xb4, 0xe1, 0x15, 0x94, 0x58, 0x11, 0x4f, + 0x94, 0x58, 0xdd, 0xde, 0xa7, 0x1f, 0x3c, 0x2c, + 0xe0, 0x88, 0x09, 0x29, 0x61, 0x57, 0x67, 0x5e, + 0x56, 0x7e, 0xee, 0x27, 0x8f, 0x59, 0x34, 0x9a, + 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, 0xfa, 0xa7, 0x6a, + 0xd4, 0xc9, 0x7a, 0x53, 0xc1, 0x4e, 0x9f, 0xe3, + 0x34, 0xf7, 0x3d, 0xb7, 0xc9, 0x10, 0x47, 0x4f, + 0x28, 0xda, 0x3f, 0xce, 0x31, 0x7b, 0xfd, 0x06, + 0x10, 0xeb, 0xf7, 0xbe, 0x92, 0xf9, 0xaf, 0xfb, + 0x3e, 0x68, 0xda, 0xee, 0x1a, 0x64, 0x4c, 0xf3, + 0x29, 0xf2, 0x73, 0x9e, 0x39, 0xd8, 0xf6, 0x6f, + 0xd8, 0xb2, 0x80, 0x82, 0x71, 0x8e, 0xb5, 0xa4, + 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, 0xca, 0xb6, 0x04, + 0xcd, 0x9a, 0x13, 0x8b, 0x54, 0x73, 0x54, 0x25, + 0x54, 0x8c, 0xbe, 0x98, 0x7a, 0x67, 0xad, 0xda, + 0xb3, 0x4e, 0xb3, 0xfa, 0x82, 0xa8, 0x4a, 0x67, + 0x98, 0x56, 0x57, 0x54, 0x71, 0xcd, 0x12, 0x7f, + 0xed, 0xa3, 0x01, 0xc0, 0x6a, 0x8b, 0x24, 0x03, + 0x96, 0x88, 0xbe, 0x97, 0x66, 0x2a, 0xbc, 0x53, + 0xc9, 0x83, 0x06, 0x51, 0x5a, 0x88, 0x65, 0x13, + 0x18, 0xe4, 0x3a, 0xed, 0x6b, 0xf1, 0x61, 0x5b, + 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, 0xae, 0x08, 0x5e, + 0x2d, 0x5f, 0xf8, 0x12, 0x7f, 0xa2, 0xfc, 0xbb, + 0x21, 0x18, 0x30, 0xda, 0xfe, 0x40, 0xfb, 0x01, + 0xca, 0x2e, 0x37, 0x0e, 0xce, 0xdd, 0x76, 0x87, + 0x82, 0x46, 0x0b, 0x3a, 0x77, 0x8f, 0xc0, 0x72, + 0x07, 0x2c, 0x7f, 0x9d, 0x1e, 0x86, 0x5b, 0xed, + 0x27, 0x29, 0xdf, 0x03, 0x97, 0x62, 0xef, 0x44, + 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, 0x5e, 0x1b, 0x7b, + 0x39, 0xb4, 0x0b, 0x6d, 0x04, 0x6b, 0xbb, 0xbb, + 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, 0x05, 0x02, 0x03, + 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x5e, + 0x79, 0x65, 0x49, 0xa5, 0x76, 0x79, 0xf9, 0x05, + 0x45, 0x0f, 0xf4, 0x03, 0xbd, 0xa4, 0x7d, 0x29, + 0xd5, 0xde, 0x33, 0x63, 0xd8, 0xb8, 0xac, 0x97, + 0xeb, 0x3f, 0x5e, 0x55, 0xe8, 0x7d, 0xf3, 0xe7, + 0x3b, 0x5c, 0x2d, 0x54, 0x67, 0x36, 0xd6, 0x1d, + 0x46, 0xf5, 0xca, 0x2d, 0x8b, 0x3a, 0x7e, 0xdc, + 0x45, 0x38, 0x79, 0x7e, 0x65, 0x71, 0x5f, 0x1c, + 0x5e, 0x79, 0xb1, 0x40, 0xcd, 0xfe, 0xc5, 0xe1, + 0xc1, 0x6b, 0x78, 0x04, 0x4e, 0x8e, 0x79, 0xf9, + 0x0a, 0xfc, 0x79, 0xb1, 0x5e, 0xb3, 0x60, 0xe3, + 0x68, 0x7b, 0xc6, 0xef, 0xcb, 0x71, 0x4c, 0xba, + 0xa7, 0x79, 0x5c, 0x7a, 0x81, 0xd1, 0x71, 0xe7, + 0x00, 0x21, 0x13, 0xe2, 0x55, 0x69, 0x0e, 0x75, + 0xbe, 0x09, 0xc3, 0x4f, 0xa9, 0xc9, 0x68, 0x22, + 0x0e, 0x97, 0x8d, 0x89, 0x6e, 0xf1, 0xe8, 0x88, + 0x7a, 0xd1, 0xd9, 0x09, 0x5d, 0xd3, 0x28, 0x78, + 0x25, 0x0b, 0x1c, 0x47, 0x73, 0x25, 0xcc, 0x21, + 0xb6, 0xda, 0xc6, 0x24, 0x5a, 0xd0, 0x37, 0x14, + 0x46, 0xc7, 0x94, 0x69, 0xe4, 0x43, 0x6f, 0x47, + 0xde, 0x00, 0x33, 0x4d, 0x8f, 0x95, 0x72, 0xfa, + 0x68, 0x71, 0x17, 0x66, 0x12, 0x1a, 0x87, 0x27, + 0xf7, 0xef, 0x7e, 0xe0, 0x35, 0x58, 0xf2, 0x4d, + 0x6f, 0x35, 0x01, 0xaa, 0x96, 0xe2, 0x3d, 0x51, + 0x13, 0x86, 0x9c, 0x79, 0xd0, 0xb7, 0xb6, 0x64, + 0xe8, 0x86, 0x65, 0x50, 0xbf, 0xcc, 0x27, 0x53, + 0x1f, 0x51, 0xd4, 0xca, 0xbe, 0xf5, 0xdd, 0x77, + 0x70, 0x98, 0x0f, 0xee, 0xa8, 0x96, 0x07, 0x5f, + 0x45, 0x6a, 0x7a, 0x0d, 0x03, 0x9c, 0x4f, 0x29, + 0xf6, 0x06, 0xf3, 0x5d, 0x58, 0x6c, 0x47, 0xd0, + 0x96, 0xa9, 0x03, 0x17, 0xbb, 0x4e, 0xc9, 0x21, + 0xe0, 0xac, 0xcd, 0x78, 0x78, 0xb2, 0xfe, 0x81, + 0xb2, 0x51, 0x53, 0xa6, 0x1f, 0x98, 0x45, 0x02, + 0x81, 0x81, 0x00, 0xcf, 0x73, 0x8c, 0xbe, 0x6d, + 0x45, 0x2d, 0x0c, 0x0b, 0x5d, 0x5c, 0x6c, 0x75, + 0x78, 0xcc, 0x35, 0x48, 0xb6, 0x98, 0xf1, 0xb9, + 0x64, 0x60, 0x8c, 0x43, 0xeb, 0x85, 0xab, 0x04, + 0xb6, 0x7d, 0x1b, 0x71, 0x75, 0x06, 0xe2, 0xda, + 0x84, 0x68, 0x2e, 0x7f, 0x4c, 0xe3, 0x73, 0xb4, + 0xde, 0x51, 0x4b, 0xb6, 0x51, 0x86, 0x7b, 0xd0, + 0xe6, 0x4d, 0xf3, 0xd1, 0xcf, 0x1a, 0xfe, 0x7f, + 0x3a, 0x83, 0xba, 0xb3, 0xe1, 0xff, 0x54, 0x13, + 0x93, 0xd7, 0x9c, 0x27, 0x80, 0xb7, 0x1e, 0x64, + 0x9e, 0xf7, 0x32, 0x2b, 0x46, 0x29, 0xf7, 0xf8, + 0x18, 0x6c, 0xf7, 0x4a, 0xbe, 0x4b, 0xee, 0x96, + 0x90, 0x8f, 0xa2, 0x16, 0x22, 0x6a, 0xcc, 0x48, + 0x06, 0x74, 0x63, 0x43, 0x7f, 0x27, 0x22, 0x44, + 0x3c, 0x2d, 0x3b, 0x62, 0xf1, 0x1c, 0xb4, 0x27, + 0x33, 0x85, 0x26, 0x60, 0x48, 0x16, 0xcb, 0xef, + 0xf8, 0xcd, 0x37, 0x02, 0x81, 0x81, 0x00, 0xce, + 0x15, 0x43, 0x6e, 0x4b, 0x0f, 0xf9, 0x3f, 0x87, + 0xc3, 0x41, 0x45, 0x97, 0xb1, 0x49, 0xc2, 0x19, + 0x23, 0x87, 0xe4, 0x24, 0x1c, 0x64, 0xe5, 0x28, + 0xcb, 0x43, 0x10, 0x14, 0x14, 0x0e, 0x19, 0xcb, + 0xbb, 0xdb, 0xfd, 0x11, 0x9d, 0x17, 0x68, 0x78, + 0x6d, 0x61, 0x70, 0x63, 0x3a, 0xa1, 0xb3, 0xf3, + 0xa7, 0x5b, 0x0e, 0xff, 0xb7, 0x61, 0x11, 0x54, + 0x91, 0x99, 0xe5, 0x91, 0x32, 0x2d, 0xeb, 0x3f, + 0xd8, 0x3e, 0xf7, 0xd4, 0xcb, 0xd2, 0xa3, 0x41, + 0xc1, 0xee, 0xc6, 0x92, 0x13, 0xeb, 0x7f, 0x42, + 0x58, 0xf4, 0xd0, 0xb2, 0x74, 0x1d, 0x8e, 0x87, + 0x46, 0xcd, 0x14, 0xb8, 0x16, 0xad, 0xb5, 0xbd, + 0x0d, 0x6c, 0x95, 0x5a, 0x16, 0xbf, 0xe9, 0x53, + 0xda, 0xfb, 0xed, 0x83, 0x51, 0x67, 0xa9, 0x55, + 0xab, 0x54, 0x02, 0x95, 0x20, 0xa6, 0x68, 0x17, + 0x53, 0xa8, 0xea, 0x43, 0xe5, 0xb0, 0xa3, 0x02, + 0x81, 0x80, 0x67, 0x9c, 0x32, 0x83, 0x39, 0x57, + 0xff, 0x73, 0xb0, 0x89, 0x64, 0x8b, 0xd6, 0xf0, + 0x0a, 0x2d, 0xe2, 0xaf, 0x30, 0x1c, 0x2a, 0x97, + 0xf3, 0x90, 0x9a, 0xab, 0x9b, 0x0b, 0x1b, 0x43, + 0x79, 0xa0, 0xa7, 0x3d, 0xe7, 0xbe, 0x8d, 0x9c, + 0xeb, 0xdb, 0xad, 0x40, 0xdd, 0xa9, 0x00, 0x80, + 0xb8, 0xe1, 0xb3, 0xa1, 0x6c, 0x25, 0x92, 0xe4, + 0x33, 0xb2, 0xbe, 0xeb, 0x4d, 0x74, 0x26, 0x5f, + 0x37, 0x43, 0x9c, 0x6c, 0x17, 0x76, 0x0a, 0x81, + 0x20, 0x82, 0xa1, 0x48, 0x2c, 0x2d, 0x45, 0xdc, + 0x0f, 0x62, 0x43, 0x32, 0xbb, 0xeb, 0x59, 0x41, + 0xf9, 0xca, 0x58, 0xce, 0x4a, 0x66, 0x53, 0x54, + 0xc8, 0x28, 0x10, 0x1e, 0x08, 0x71, 0x16, 0xd8, + 0x02, 0x71, 0x41, 0x58, 0xd4, 0x56, 0xcc, 0xf5, + 0xb1, 0x31, 0xa3, 0xed, 0x00, 0x85, 0x09, 0xbf, + 0x35, 0x95, 0x41, 0x29, 0x40, 0x19, 0x83, 0x35, + 0x24, 0x69, 0x02, 0x81, 0x80, 0x55, 0x10, 0x0b, + 0xcc, 0x3b, 0xa9, 0x75, 0x3d, 0x16, 0xe1, 0xae, + 0x50, 0x76, 0x63, 0x94, 0x49, 0x4c, 0xad, 0x10, + 0xcb, 0x47, 0x68, 0x7c, 0xf0, 0xe5, 0xdc, 0xb8, + 0x6a, 0xab, 0x8e, 0xf7, 0x9f, 0x08, 0x2c, 0x1b, + 0x8a, 0xa2, 0xb9, 0x8f, 0xce, 0xec, 0x5e, 0x61, + 0xa8, 0xcd, 0x1c, 0x87, 0x60, 0x4a, 0xc3, 0x1a, + 0x5f, 0xdf, 0x87, 0x26, 0xc6, 0xcb, 0x7c, 0x69, + 0xe4, 0x8b, 0x01, 0x06, 0x59, 0x22, 0xfa, 0x34, + 0x4b, 0x81, 0x87, 0x3c, 0x03, 0x6d, 0x02, 0x0a, + 0x77, 0xe6, 0x15, 0xd8, 0xcf, 0xa7, 0x68, 0x26, + 0x6c, 0xfa, 0x2b, 0xd9, 0x83, 0x5a, 0x2d, 0x0c, + 0x3b, 0x70, 0x1c, 0xd4, 0x48, 0xbe, 0xa7, 0x0a, + 0xd9, 0xbe, 0xdc, 0xc3, 0x0c, 0x21, 0x33, 0xb3, + 0x66, 0xff, 0x1c, 0x1b, 0xc8, 0x96, 0x76, 0xe8, + 0x6f, 0x44, 0x74, 0xbc, 0x9b, 0x1c, 0x7d, 0xc8, + 0xac, 0x21, 0xa8, 0x6e, 0x37, 0x02, 0x81, 0x80, + 0x2c, 0x7c, 0xad, 0x1e, 0x75, 0xf6, 0x69, 0x1d, + 0xe7, 0xa6, 0xca, 0x74, 0x7d, 0x67, 0xc8, 0x65, + 0x28, 0x66, 0xc4, 0x43, 0xa6, 0xbd, 0x40, 0x57, + 0xae, 0xb7, 0x65, 0x2c, 0x52, 0xf9, 0xe4, 0xc7, + 0x81, 0x7b, 0x56, 0xa3, 0xd2, 0x0d, 0xe8, 0x33, + 0x70, 0xcf, 0x06, 0x84, 0xb3, 0x4e, 0x44, 0x50, + 0x75, 0x61, 0x96, 0x86, 0x4b, 0xb6, 0x2b, 0xad, + 0xf0, 0xad, 0x57, 0xd0, 0x37, 0x0d, 0x1d, 0x35, + 0x50, 0xcb, 0x69, 0x22, 0x39, 0x29, 0xb9, 0x3a, + 0xd3, 0x29, 0x23, 0x02, 0x60, 0xf7, 0xab, 0x30, + 0x40, 0xda, 0x8e, 0x4d, 0x45, 0x70, 0x26, 0xf4, + 0xa2, 0x0d, 0xd0, 0x64, 0x5d, 0x47, 0x3c, 0x18, + 0xf4, 0xd4, 0x52, 0x95, 0x00, 0xae, 0x84, 0x6b, + 0x47, 0xb2, 0x3c, 0x82, 0xd3, 0x72, 0x53, 0xde, + 0x72, 0x2c, 0xf7, 0xc1, 0x22, 0x36, 0xd9, 0x18, + 0x56, 0xfe, 0x39, 0x28, 0x33, 0xe0, 0xdb, 0x03 }; + +// WHAT: A 2048 bit RSA Public key +// WHY: Used to verify the functions that manipulate RSA keys. +static const uint8_t kTestRSAPublicKey2_2048[] = { + 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, + 0x00, 0xa7, 0x00, 0x36, 0x60, 0x65, 0xdc, 0xbd, + 0x54, 0x5a, 0x2a, 0x40, 0xb4, 0xe1, 0x15, 0x94, + 0x58, 0x11, 0x4f, 0x94, 0x58, 0xdd, 0xde, 0xa7, + 0x1f, 0x3c, 0x2c, 0xe0, 0x88, 0x09, 0x29, 0x61, + 0x57, 0x67, 0x5e, 0x56, 0x7e, 0xee, 0x27, 0x8f, + 0x59, 0x34, 0x9a, 0x2a, 0xaa, 0x9d, 0xb4, 0x4e, + 0xfa, 0xa7, 0x6a, 0xd4, 0xc9, 0x7a, 0x53, 0xc1, + 0x4e, 0x9f, 0xe3, 0x34, 0xf7, 0x3d, 0xb7, 0xc9, + 0x10, 0x47, 0x4f, 0x28, 0xda, 0x3f, 0xce, 0x31, + 0x7b, 0xfd, 0x06, 0x10, 0xeb, 0xf7, 0xbe, 0x92, + 0xf9, 0xaf, 0xfb, 0x3e, 0x68, 0xda, 0xee, 0x1a, + 0x64, 0x4c, 0xf3, 0x29, 0xf2, 0x73, 0x9e, 0x39, + 0xd8, 0xf6, 0x6f, 0xd8, 0xb2, 0x80, 0x82, 0x71, + 0x8e, 0xb5, 0xa4, 0xf2, 0xc2, 0x3e, 0xcd, 0x0a, + 0xca, 0xb6, 0x04, 0xcd, 0x9a, 0x13, 0x8b, 0x54, + 0x73, 0x54, 0x25, 0x54, 0x8c, 0xbe, 0x98, 0x7a, + 0x67, 0xad, 0xda, 0xb3, 0x4e, 0xb3, 0xfa, 0x82, + 0xa8, 0x4a, 0x67, 0x98, 0x56, 0x57, 0x54, 0x71, + 0xcd, 0x12, 0x7f, 0xed, 0xa3, 0x01, 0xc0, 0x6a, + 0x8b, 0x24, 0x03, 0x96, 0x88, 0xbe, 0x97, 0x66, + 0x2a, 0xbc, 0x53, 0xc9, 0x83, 0x06, 0x51, 0x5a, + 0x88, 0x65, 0x13, 0x18, 0xe4, 0x3a, 0xed, 0x6b, + 0xf1, 0x61, 0x5b, 0x4c, 0xc8, 0x1e, 0xf4, 0xc2, + 0xae, 0x08, 0x5e, 0x2d, 0x5f, 0xf8, 0x12, 0x7f, + 0xa2, 0xfc, 0xbb, 0x21, 0x18, 0x30, 0xda, 0xfe, + 0x40, 0xfb, 0x01, 0xca, 0x2e, 0x37, 0x0e, 0xce, + 0xdd, 0x76, 0x87, 0x82, 0x46, 0x0b, 0x3a, 0x77, + 0x8f, 0xc0, 0x72, 0x07, 0x2c, 0x7f, 0x9d, 0x1e, + 0x86, 0x5b, 0xed, 0x27, 0x29, 0xdf, 0x03, 0x97, + 0x62, 0xef, 0x44, 0xd3, 0x5b, 0x3d, 0xdb, 0x9c, + 0x5e, 0x1b, 0x7b, 0x39, 0xb4, 0x0b, 0x6d, 0x04, + 0x6b, 0xbb, 0xbb, 0x2c, 0x5f, 0xcf, 0xb3, 0x7a, + 0x05, 0x02, 0x03, 0x01, 0x00, 0x01 }; + +// WHAT: A second 2048-bit RSA private key +// WHY: This is used to verify the functions that manipulate +// RSA keys. +static const uint8_t kTestPKCS1RSAPrivateKey3_2048[] = { + 0x30, 0x82, 0x04, 0xa4, 0x02, 0x01, 0x00, 0x02, + 0x82, 0x01, 0x01, 0x00, 0xa5, 0xd0, 0xd7, 0x3e, + 0x0e, 0x2d, 0xfb, 0x43, 0x51, 0x99, 0xea, 0x40, + 0x1e, 0x2d, 0x89, 0xe4, 0xa2, 0x3e, 0xfc, 0x51, + 0x3d, 0x0e, 0x83, 0xa7, 0xe0, 0xa5, 0x41, 0x04, + 0x1e, 0x14, 0xc5, 0xa7, 0x5c, 0x61, 0x36, 0x44, + 0xb3, 0x08, 0x05, 0x5b, 0x14, 0xde, 0x01, 0x0c, + 0x32, 0x3c, 0x9a, 0x91, 0x00, 0x50, 0xa8, 0x1d, + 0xcc, 0x9f, 0x8f, 0x35, 0xb7, 0xc2, 0x75, 0x08, + 0x32, 0x8b, 0x10, 0x3a, 0x86, 0xf9, 0xd7, 0x78, + 0xa3, 0x9d, 0x74, 0x10, 0xc6, 0x24, 0xb1, 0x7f, + 0xa5, 0xbf, 0x5f, 0xc2, 0xd7, 0x15, 0xa3, 0x1d, + 0xe0, 0x15, 0x6b, 0x1b, 0x0e, 0x38, 0xba, 0x34, + 0xbc, 0x95, 0x47, 0x94, 0x40, 0x70, 0xac, 0x99, + 0x1f, 0x0b, 0x8e, 0x56, 0x93, 0x36, 0x2b, 0x6d, + 0x04, 0xe7, 0x95, 0x1a, 0x37, 0xda, 0x16, 0x57, + 0x99, 0xee, 0x03, 0x68, 0x16, 0x31, 0xaa, 0xc3, + 0xb7, 0x92, 0x75, 0x53, 0xfc, 0xf6, 0x20, 0x55, + 0x44, 0xf8, 0xd4, 0x8d, 0x78, 0x15, 0xc7, 0x1a, + 0xb6, 0xde, 0x6c, 0xe8, 0x49, 0x5d, 0xaf, 0xa8, + 0x4e, 0x6f, 0x7c, 0xe2, 0x6a, 0x4c, 0xd5, 0xe7, + 0x8c, 0x8f, 0x0b, 0x5d, 0x3a, 0x09, 0xd6, 0xb3, + 0x44, 0xab, 0xe0, 0x35, 0x52, 0x7c, 0x66, 0x85, + 0xa4, 0x40, 0xd7, 0x20, 0xec, 0x24, 0x05, 0x06, + 0xd9, 0x84, 0x51, 0x5a, 0xd2, 0x38, 0xd5, 0x1d, + 0xea, 0x70, 0x2a, 0x21, 0xe6, 0x82, 0xfd, 0xa4, + 0x46, 0x1c, 0x4f, 0x59, 0x6e, 0x29, 0x3d, 0xae, + 0xb8, 0x8e, 0xee, 0x77, 0x1f, 0x15, 0x33, 0xcf, + 0x94, 0x1d, 0x87, 0x3c, 0x37, 0xc5, 0x89, 0xe8, + 0x7d, 0x85, 0xb3, 0xbc, 0xe8, 0x62, 0x6a, 0x84, + 0x7f, 0xfe, 0x9a, 0x85, 0x3f, 0x39, 0xe8, 0xaa, + 0x16, 0xa6, 0x8f, 0x87, 0x7f, 0xcb, 0xc1, 0xd6, + 0xf2, 0xec, 0x2b, 0xa7, 0xdd, 0x49, 0x98, 0x7b, + 0x6f, 0xdd, 0x69, 0x6d, 0x02, 0x03, 0x01, 0x00, + 0x01, 0x02, 0x82, 0x01, 0x00, 0x43, 0x8f, 0x19, + 0x83, 0xb1, 0x27, 0x4e, 0xee, 0x98, 0xba, 0xcb, + 0x54, 0xa0, 0x77, 0x11, 0x6d, 0xd4, 0x25, 0x31, + 0x8c, 0xb0, 0x01, 0xcf, 0xe6, 0x80, 0x83, 0x14, + 0x40, 0x67, 0x39, 0x33, 0x67, 0x03, 0x1e, 0xa0, + 0x8b, 0xd1, 0x1d, 0xfd, 0x80, 0xa4, 0xb9, 0xe7, + 0x57, 0x5e, 0xc8, 0x8e, 0x79, 0x71, 0xd5, 0x6b, + 0x09, 0xe9, 0x2b, 0x41, 0xa0, 0x33, 0x64, 0xc9, + 0x66, 0x33, 0xa1, 0xb1, 0x55, 0x07, 0x55, 0x98, + 0x53, 0x10, 0xe6, 0xc0, 0x39, 0x6d, 0x61, 0xd9, + 0xe8, 0x16, 0x52, 0x28, 0xe4, 0x2b, 0xda, 0x27, + 0x01, 0xaf, 0x21, 0x4a, 0xe8, 0x55, 0x1d, 0x0b, + 0xd1, 0x1c, 0xdc, 0xfd, 0xb3, 0x0b, 0xa6, 0x5c, + 0xcc, 0x6e, 0x77, 0xb8, 0xe0, 0xd1, 0x4e, 0x0a, + 0xd7, 0x7a, 0x5e, 0x18, 0xc3, 0xfb, 0xe9, 0xa1, + 0x9c, 0xc3, 0x9c, 0xd4, 0x4a, 0x7e, 0x70, 0x72, + 0x11, 0x18, 0x24, 0x56, 0x24, 0xdf, 0xf8, 0xba, + 0xac, 0x5b, 0x54, 0xd3, 0xc4, 0x65, 0x69, 0xc8, + 0x79, 0x94, 0x16, 0x88, 0x9a, 0x68, 0x1c, 0xbc, + 0xd4, 0xca, 0xec, 0x5e, 0x07, 0x4a, 0xc9, 0x54, + 0x7a, 0x4b, 0xdb, 0x19, 0x88, 0xf6, 0xbe, 0x50, + 0x9d, 0x9e, 0x9d, 0x88, 0x5b, 0x4a, 0x23, 0x86, + 0x2b, 0xa9, 0xa6, 0x6c, 0x70, 0x7d, 0xe1, 0x11, + 0xba, 0xbf, 0x03, 0x2e, 0xf1, 0x46, 0x7e, 0x1b, + 0xed, 0x06, 0x11, 0x57, 0xad, 0x4a, 0xcb, 0xe5, + 0xb1, 0x11, 0x05, 0x0a, 0x30, 0xb1, 0x73, 0x79, + 0xcd, 0x7a, 0x04, 0xcc, 0x70, 0xe9, 0x95, 0xe4, + 0x27, 0xc2, 0xd5, 0x2d, 0x92, 0x44, 0xdf, 0xb4, + 0x94, 0xa8, 0x73, 0xa1, 0x4a, 0xc3, 0xcc, 0xc4, + 0x0e, 0x8d, 0xa1, 0x6a, 0xc2, 0xd8, 0x03, 0x7f, + 0xfa, 0xa7, 0x76, 0x0d, 0xad, 0x87, 0x88, 0xa0, + 0x77, 0xaf, 0x3b, 0x23, 0xd1, 0x66, 0x0b, 0x31, + 0x2b, 0xaf, 0xef, 0xd5, 0x41, 0x02, 0x81, 0x81, + 0x00, 0xdb, 0xc1, 0xe7, 0xdd, 0xba, 0x3c, 0x1f, + 0x9c, 0x64, 0xca, 0xa0, 0x63, 0xdb, 0xd2, 0x47, + 0x5c, 0x6e, 0x8a, 0xa3, 0x16, 0xd5, 0xda, 0xc2, + 0x25, 0x64, 0x0a, 0x02, 0xbc, 0x7d, 0x7f, 0x50, + 0xab, 0xe0, 0x66, 0x03, 0x53, 0x7d, 0x77, 0x6d, + 0x6c, 0x61, 0x58, 0x09, 0x73, 0xcd, 0x18, 0xe9, + 0x53, 0x0b, 0x5c, 0xa2, 0x71, 0x14, 0x02, 0xfd, + 0x55, 0xda, 0xe9, 0x77, 0x24, 0x7c, 0x2a, 0x4e, + 0xb9, 0xd9, 0x5d, 0x58, 0xf6, 0x26, 0xd0, 0xd8, + 0x3d, 0xcf, 0x8c, 0x89, 0x65, 0x6c, 0x35, 0x19, + 0xb6, 0x63, 0xff, 0xa0, 0x71, 0x49, 0xcd, 0x6d, + 0x5b, 0x3d, 0x8f, 0xea, 0x6f, 0xa9, 0xba, 0x43, + 0xe5, 0xdd, 0x39, 0x3a, 0x78, 0x8f, 0x07, 0xb8, + 0xab, 0x58, 0x07, 0xb7, 0xd2, 0xf8, 0x07, 0x02, + 0x9b, 0x79, 0x26, 0x32, 0x22, 0x38, 0x91, 0x01, + 0x90, 0x81, 0x29, 0x94, 0xad, 0x77, 0xeb, 0x86, + 0xb9, 0x02, 0x81, 0x81, 0x00, 0xc1, 0x29, 0x88, + 0xbd, 0x96, 0x31, 0x33, 0x7b, 0x77, 0x5d, 0x32, + 0x12, 0x5e, 0xdf, 0x28, 0x0c, 0x96, 0x0d, 0xa8, + 0x22, 0xdf, 0xd3, 0x35, 0xd7, 0xb0, 0x41, 0xcb, + 0xe7, 0x94, 0x8a, 0xa4, 0xed, 0xd2, 0xfb, 0xd2, + 0xf3, 0xf2, 0x95, 0xff, 0xd8, 0x33, 0x3f, 0x8c, + 0xd7, 0x65, 0xe4, 0x0c, 0xcc, 0xfe, 0x32, 0x66, + 0xfa, 0x50, 0xe2, 0xcf, 0xf0, 0xbe, 0x05, 0xb1, + 0xbc, 0xbe, 0x44, 0x09, 0xb4, 0xfe, 0x95, 0x06, + 0x18, 0xd7, 0x59, 0xc6, 0xef, 0x2d, 0x22, 0xa0, + 0x73, 0x5e, 0x77, 0xdf, 0x8d, 0x09, 0x2c, 0xb8, + 0xcc, 0xeb, 0x10, 0x4d, 0xa7, 0xd0, 0x4b, 0x46, + 0xba, 0x7d, 0x8b, 0x6a, 0x55, 0x47, 0x55, 0xd3, + 0xd7, 0xb1, 0x88, 0xfd, 0x27, 0x3e, 0xf9, 0x5b, + 0x7b, 0xae, 0x6d, 0x08, 0x9f, 0x0c, 0x2a, 0xe1, + 0xdd, 0xb9, 0xe3, 0x55, 0x13, 0x55, 0xa3, 0x6d, + 0x06, 0xbb, 0xe0, 0x1e, 0x55, 0x02, 0x81, 0x80, + 0x61, 0x73, 0x3d, 0x64, 0xff, 0xdf, 0x05, 0x8d, + 0x8e, 0xcc, 0xa4, 0x0f, 0x64, 0x3d, 0x7d, 0x53, + 0xa9, 0xd9, 0x64, 0xb5, 0x0d, 0xa4, 0x72, 0x8f, + 0xae, 0x2b, 0x1a, 0x47, 0x87, 0xc7, 0x5b, 0x78, + 0xbc, 0x8b, 0xc0, 0x51, 0xd7, 0xc3, 0x8c, 0x0c, + 0x91, 0xa6, 0x3e, 0x9a, 0xd1, 0x8a, 0x88, 0x7d, + 0x40, 0xfe, 0x95, 0x32, 0x5b, 0xd3, 0x6f, 0x90, + 0x11, 0x01, 0x92, 0xc9, 0xe5, 0x1d, 0xc5, 0xc7, + 0x78, 0x72, 0x82, 0xae, 0xb5, 0x4b, 0xcb, 0x78, + 0xad, 0x7e, 0xfe, 0xb6, 0xb1, 0x23, 0x63, 0x01, + 0x94, 0x9a, 0x99, 0x05, 0x63, 0xda, 0xea, 0xf1, + 0x98, 0xfd, 0x26, 0xd2, 0xd9, 0x8b, 0x35, 0xec, + 0xcb, 0x0b, 0x43, 0xb8, 0x8e, 0x84, 0xb8, 0x09, + 0x93, 0x81, 0xe8, 0xac, 0x6f, 0x3c, 0x7c, 0x95, + 0x81, 0x45, 0xc4, 0xd9, 0x94, 0x08, 0x09, 0x8f, + 0x91, 0x17, 0x65, 0x4c, 0xff, 0x6e, 0xbc, 0x51, + 0x02, 0x81, 0x81, 0x00, 0xc1, 0x0d, 0x9d, 0xd8, + 0xbd, 0xaf, 0x56, 0xe0, 0xe3, 0x1f, 0x85, 0xd7, + 0xce, 0x72, 0x02, 0x38, 0xf2, 0x0f, 0x9c, 0x27, + 0x9e, 0xc4, 0x1d, 0x60, 0x00, 0x8d, 0x02, 0x19, + 0xe5, 0xdf, 0xdb, 0x8e, 0xc5, 0xfb, 0x61, 0x8e, + 0xe6, 0xb8, 0xfc, 0x07, 0x3c, 0xd1, 0x1b, 0x16, + 0x7c, 0x83, 0x3c, 0x37, 0xf5, 0x26, 0xb2, 0xbd, + 0x22, 0xf2, 0x4d, 0x19, 0x33, 0x11, 0xc5, 0xdd, + 0xf9, 0xdb, 0x4e, 0x48, 0x52, 0xd8, 0xe6, 0x4b, + 0x15, 0x90, 0x68, 0xbe, 0xca, 0xc1, 0x7c, 0xd3, + 0x51, 0x6b, 0x45, 0x46, 0x54, 0x11, 0x1a, 0x71, + 0xd3, 0xcd, 0x6b, 0x8f, 0x79, 0x22, 0x83, 0x02, + 0x08, 0x4f, 0xba, 0x6a, 0x98, 0xed, 0x32, 0xd8, + 0xb4, 0x5b, 0x51, 0x88, 0x53, 0xec, 0x2c, 0x7e, + 0xa4, 0x89, 0xdc, 0xbf, 0xf9, 0x0d, 0x32, 0xc8, + 0xc3, 0xec, 0x6d, 0x2e, 0xf1, 0xbc, 0x70, 0x4e, + 0xf6, 0x9e, 0xbc, 0x31, 0x02, 0x81, 0x81, 0x00, + 0xd3, 0x35, 0x1b, 0x19, 0x75, 0x3f, 0x61, 0xf2, + 0x55, 0x03, 0xce, 0x25, 0xa9, 0xdf, 0x0c, 0x0a, + 0x3b, 0x47, 0x42, 0xdc, 0x38, 0x4b, 0x13, 0x4d, + 0x1f, 0x86, 0x58, 0x4f, 0xd8, 0xee, 0xfa, 0x76, + 0x15, 0xfb, 0x6e, 0x55, 0x31, 0xf2, 0xd2, 0x62, + 0x32, 0xa5, 0xc4, 0x23, 0x5e, 0x08, 0xa9, 0x83, + 0x07, 0xac, 0x8c, 0xa3, 0x7e, 0x18, 0xc0, 0x1c, + 0x57, 0x63, 0x8d, 0x05, 0x17, 0x47, 0x1b, 0xd3, + 0x74, 0x73, 0x20, 0x04, 0xfb, 0xc8, 0x1a, 0x43, + 0x04, 0x36, 0xc8, 0x19, 0xbe, 0xdc, 0xa6, 0xe5, + 0x0f, 0x25, 0x62, 0x24, 0x96, 0x92, 0xb6, 0xb3, + 0x97, 0xad, 0x57, 0x9a, 0x90, 0x37, 0x4e, 0x31, + 0x44, 0x74, 0xfa, 0x7c, 0xb4, 0xea, 0xfc, 0x15, + 0xa7, 0xb0, 0x51, 0xcc, 0xee, 0x1e, 0xed, 0x5b, + 0x98, 0x18, 0x0e, 0x65, 0xb6, 0x4b, 0x69, 0x0b, + 0x21, 0xdc, 0x86, 0x17, 0x6e, 0xc8, 0xee, 0x24 }; + +// WHAT: A second 2048 bit RSA key in PKCS#8 PrivateKeyInfo format +// WHY: Used to verify the functions that manipulate RSA keys. +static const uint8_t kTestRSAPKCS8PrivateKeyInfo3_2048[] = { + 0x30, 0x82, 0x04, 0xbe, 0x02, 0x01, 0x00, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, + 0x04, 0xa8, 0x30, 0x82, 0x04, 0xa4, 0x02, 0x01, + 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0xa5, 0xd0, + 0xd7, 0x3e, 0x0e, 0x2d, 0xfb, 0x43, 0x51, 0x99, + 0xea, 0x40, 0x1e, 0x2d, 0x89, 0xe4, 0xa2, 0x3e, + 0xfc, 0x51, 0x3d, 0x0e, 0x83, 0xa7, 0xe0, 0xa5, + 0x41, 0x04, 0x1e, 0x14, 0xc5, 0xa7, 0x5c, 0x61, + 0x36, 0x44, 0xb3, 0x08, 0x05, 0x5b, 0x14, 0xde, + 0x01, 0x0c, 0x32, 0x3c, 0x9a, 0x91, 0x00, 0x50, + 0xa8, 0x1d, 0xcc, 0x9f, 0x8f, 0x35, 0xb7, 0xc2, + 0x75, 0x08, 0x32, 0x8b, 0x10, 0x3a, 0x86, 0xf9, + 0xd7, 0x78, 0xa3, 0x9d, 0x74, 0x10, 0xc6, 0x24, + 0xb1, 0x7f, 0xa5, 0xbf, 0x5f, 0xc2, 0xd7, 0x15, + 0xa3, 0x1d, 0xe0, 0x15, 0x6b, 0x1b, 0x0e, 0x38, + 0xba, 0x34, 0xbc, 0x95, 0x47, 0x94, 0x40, 0x70, + 0xac, 0x99, 0x1f, 0x0b, 0x8e, 0x56, 0x93, 0x36, + 0x2b, 0x6d, 0x04, 0xe7, 0x95, 0x1a, 0x37, 0xda, + 0x16, 0x57, 0x99, 0xee, 0x03, 0x68, 0x16, 0x31, + 0xaa, 0xc3, 0xb7, 0x92, 0x75, 0x53, 0xfc, 0xf6, + 0x20, 0x55, 0x44, 0xf8, 0xd4, 0x8d, 0x78, 0x15, + 0xc7, 0x1a, 0xb6, 0xde, 0x6c, 0xe8, 0x49, 0x5d, + 0xaf, 0xa8, 0x4e, 0x6f, 0x7c, 0xe2, 0x6a, 0x4c, + 0xd5, 0xe7, 0x8c, 0x8f, 0x0b, 0x5d, 0x3a, 0x09, + 0xd6, 0xb3, 0x44, 0xab, 0xe0, 0x35, 0x52, 0x7c, + 0x66, 0x85, 0xa4, 0x40, 0xd7, 0x20, 0xec, 0x24, + 0x05, 0x06, 0xd9, 0x84, 0x51, 0x5a, 0xd2, 0x38, + 0xd5, 0x1d, 0xea, 0x70, 0x2a, 0x21, 0xe6, 0x82, + 0xfd, 0xa4, 0x46, 0x1c, 0x4f, 0x59, 0x6e, 0x29, + 0x3d, 0xae, 0xb8, 0x8e, 0xee, 0x77, 0x1f, 0x15, + 0x33, 0xcf, 0x94, 0x1d, 0x87, 0x3c, 0x37, 0xc5, + 0x89, 0xe8, 0x7d, 0x85, 0xb3, 0xbc, 0xe8, 0x62, + 0x6a, 0x84, 0x7f, 0xfe, 0x9a, 0x85, 0x3f, 0x39, + 0xe8, 0xaa, 0x16, 0xa6, 0x8f, 0x87, 0x7f, 0xcb, + 0xc1, 0xd6, 0xf2, 0xec, 0x2b, 0xa7, 0xdd, 0x49, + 0x98, 0x7b, 0x6f, 0xdd, 0x69, 0x6d, 0x02, 0x03, + 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x43, + 0x8f, 0x19, 0x83, 0xb1, 0x27, 0x4e, 0xee, 0x98, + 0xba, 0xcb, 0x54, 0xa0, 0x77, 0x11, 0x6d, 0xd4, + 0x25, 0x31, 0x8c, 0xb0, 0x01, 0xcf, 0xe6, 0x80, + 0x83, 0x14, 0x40, 0x67, 0x39, 0x33, 0x67, 0x03, + 0x1e, 0xa0, 0x8b, 0xd1, 0x1d, 0xfd, 0x80, 0xa4, + 0xb9, 0xe7, 0x57, 0x5e, 0xc8, 0x8e, 0x79, 0x71, + 0xd5, 0x6b, 0x09, 0xe9, 0x2b, 0x41, 0xa0, 0x33, + 0x64, 0xc9, 0x66, 0x33, 0xa1, 0xb1, 0x55, 0x07, + 0x55, 0x98, 0x53, 0x10, 0xe6, 0xc0, 0x39, 0x6d, + 0x61, 0xd9, 0xe8, 0x16, 0x52, 0x28, 0xe4, 0x2b, + 0xda, 0x27, 0x01, 0xaf, 0x21, 0x4a, 0xe8, 0x55, + 0x1d, 0x0b, 0xd1, 0x1c, 0xdc, 0xfd, 0xb3, 0x0b, + 0xa6, 0x5c, 0xcc, 0x6e, 0x77, 0xb8, 0xe0, 0xd1, + 0x4e, 0x0a, 0xd7, 0x7a, 0x5e, 0x18, 0xc3, 0xfb, + 0xe9, 0xa1, 0x9c, 0xc3, 0x9c, 0xd4, 0x4a, 0x7e, + 0x70, 0x72, 0x11, 0x18, 0x24, 0x56, 0x24, 0xdf, + 0xf8, 0xba, 0xac, 0x5b, 0x54, 0xd3, 0xc4, 0x65, + 0x69, 0xc8, 0x79, 0x94, 0x16, 0x88, 0x9a, 0x68, + 0x1c, 0xbc, 0xd4, 0xca, 0xec, 0x5e, 0x07, 0x4a, + 0xc9, 0x54, 0x7a, 0x4b, 0xdb, 0x19, 0x88, 0xf6, + 0xbe, 0x50, 0x9d, 0x9e, 0x9d, 0x88, 0x5b, 0x4a, + 0x23, 0x86, 0x2b, 0xa9, 0xa6, 0x6c, 0x70, 0x7d, + 0xe1, 0x11, 0xba, 0xbf, 0x03, 0x2e, 0xf1, 0x46, + 0x7e, 0x1b, 0xed, 0x06, 0x11, 0x57, 0xad, 0x4a, + 0xcb, 0xe5, 0xb1, 0x11, 0x05, 0x0a, 0x30, 0xb1, + 0x73, 0x79, 0xcd, 0x7a, 0x04, 0xcc, 0x70, 0xe9, + 0x95, 0xe4, 0x27, 0xc2, 0xd5, 0x2d, 0x92, 0x44, + 0xdf, 0xb4, 0x94, 0xa8, 0x73, 0xa1, 0x4a, 0xc3, + 0xcc, 0xc4, 0x0e, 0x8d, 0xa1, 0x6a, 0xc2, 0xd8, + 0x03, 0x7f, 0xfa, 0xa7, 0x76, 0x0d, 0xad, 0x87, + 0x88, 0xa0, 0x77, 0xaf, 0x3b, 0x23, 0xd1, 0x66, + 0x0b, 0x31, 0x2b, 0xaf, 0xef, 0xd5, 0x41, 0x02, + 0x81, 0x81, 0x00, 0xdb, 0xc1, 0xe7, 0xdd, 0xba, + 0x3c, 0x1f, 0x9c, 0x64, 0xca, 0xa0, 0x63, 0xdb, + 0xd2, 0x47, 0x5c, 0x6e, 0x8a, 0xa3, 0x16, 0xd5, + 0xda, 0xc2, 0x25, 0x64, 0x0a, 0x02, 0xbc, 0x7d, + 0x7f, 0x50, 0xab, 0xe0, 0x66, 0x03, 0x53, 0x7d, + 0x77, 0x6d, 0x6c, 0x61, 0x58, 0x09, 0x73, 0xcd, + 0x18, 0xe9, 0x53, 0x0b, 0x5c, 0xa2, 0x71, 0x14, + 0x02, 0xfd, 0x55, 0xda, 0xe9, 0x77, 0x24, 0x7c, + 0x2a, 0x4e, 0xb9, 0xd9, 0x5d, 0x58, 0xf6, 0x26, + 0xd0, 0xd8, 0x3d, 0xcf, 0x8c, 0x89, 0x65, 0x6c, + 0x35, 0x19, 0xb6, 0x63, 0xff, 0xa0, 0x71, 0x49, + 0xcd, 0x6d, 0x5b, 0x3d, 0x8f, 0xea, 0x6f, 0xa9, + 0xba, 0x43, 0xe5, 0xdd, 0x39, 0x3a, 0x78, 0x8f, + 0x07, 0xb8, 0xab, 0x58, 0x07, 0xb7, 0xd2, 0xf8, + 0x07, 0x02, 0x9b, 0x79, 0x26, 0x32, 0x22, 0x38, + 0x91, 0x01, 0x90, 0x81, 0x29, 0x94, 0xad, 0x77, + 0xeb, 0x86, 0xb9, 0x02, 0x81, 0x81, 0x00, 0xc1, + 0x29, 0x88, 0xbd, 0x96, 0x31, 0x33, 0x7b, 0x77, + 0x5d, 0x32, 0x12, 0x5e, 0xdf, 0x28, 0x0c, 0x96, + 0x0d, 0xa8, 0x22, 0xdf, 0xd3, 0x35, 0xd7, 0xb0, + 0x41, 0xcb, 0xe7, 0x94, 0x8a, 0xa4, 0xed, 0xd2, + 0xfb, 0xd2, 0xf3, 0xf2, 0x95, 0xff, 0xd8, 0x33, + 0x3f, 0x8c, 0xd7, 0x65, 0xe4, 0x0c, 0xcc, 0xfe, + 0x32, 0x66, 0xfa, 0x50, 0xe2, 0xcf, 0xf0, 0xbe, + 0x05, 0xb1, 0xbc, 0xbe, 0x44, 0x09, 0xb4, 0xfe, + 0x95, 0x06, 0x18, 0xd7, 0x59, 0xc6, 0xef, 0x2d, + 0x22, 0xa0, 0x73, 0x5e, 0x77, 0xdf, 0x8d, 0x09, + 0x2c, 0xb8, 0xcc, 0xeb, 0x10, 0x4d, 0xa7, 0xd0, + 0x4b, 0x46, 0xba, 0x7d, 0x8b, 0x6a, 0x55, 0x47, + 0x55, 0xd3, 0xd7, 0xb1, 0x88, 0xfd, 0x27, 0x3e, + 0xf9, 0x5b, 0x7b, 0xae, 0x6d, 0x08, 0x9f, 0x0c, + 0x2a, 0xe1, 0xdd, 0xb9, 0xe3, 0x55, 0x13, 0x55, + 0xa3, 0x6d, 0x06, 0xbb, 0xe0, 0x1e, 0x55, 0x02, + 0x81, 0x80, 0x61, 0x73, 0x3d, 0x64, 0xff, 0xdf, + 0x05, 0x8d, 0x8e, 0xcc, 0xa4, 0x0f, 0x64, 0x3d, + 0x7d, 0x53, 0xa9, 0xd9, 0x64, 0xb5, 0x0d, 0xa4, + 0x72, 0x8f, 0xae, 0x2b, 0x1a, 0x47, 0x87, 0xc7, + 0x5b, 0x78, 0xbc, 0x8b, 0xc0, 0x51, 0xd7, 0xc3, + 0x8c, 0x0c, 0x91, 0xa6, 0x3e, 0x9a, 0xd1, 0x8a, + 0x88, 0x7d, 0x40, 0xfe, 0x95, 0x32, 0x5b, 0xd3, + 0x6f, 0x90, 0x11, 0x01, 0x92, 0xc9, 0xe5, 0x1d, + 0xc5, 0xc7, 0x78, 0x72, 0x82, 0xae, 0xb5, 0x4b, + 0xcb, 0x78, 0xad, 0x7e, 0xfe, 0xb6, 0xb1, 0x23, + 0x63, 0x01, 0x94, 0x9a, 0x99, 0x05, 0x63, 0xda, + 0xea, 0xf1, 0x98, 0xfd, 0x26, 0xd2, 0xd9, 0x8b, + 0x35, 0xec, 0xcb, 0x0b, 0x43, 0xb8, 0x8e, 0x84, + 0xb8, 0x09, 0x93, 0x81, 0xe8, 0xac, 0x6f, 0x3c, + 0x7c, 0x95, 0x81, 0x45, 0xc4, 0xd9, 0x94, 0x08, + 0x09, 0x8f, 0x91, 0x17, 0x65, 0x4c, 0xff, 0x6e, + 0xbc, 0x51, 0x02, 0x81, 0x81, 0x00, 0xc1, 0x0d, + 0x9d, 0xd8, 0xbd, 0xaf, 0x56, 0xe0, 0xe3, 0x1f, + 0x85, 0xd7, 0xce, 0x72, 0x02, 0x38, 0xf2, 0x0f, + 0x9c, 0x27, 0x9e, 0xc4, 0x1d, 0x60, 0x00, 0x8d, + 0x02, 0x19, 0xe5, 0xdf, 0xdb, 0x8e, 0xc5, 0xfb, + 0x61, 0x8e, 0xe6, 0xb8, 0xfc, 0x07, 0x3c, 0xd1, + 0x1b, 0x16, 0x7c, 0x83, 0x3c, 0x37, 0xf5, 0x26, + 0xb2, 0xbd, 0x22, 0xf2, 0x4d, 0x19, 0x33, 0x11, + 0xc5, 0xdd, 0xf9, 0xdb, 0x4e, 0x48, 0x52, 0xd8, + 0xe6, 0x4b, 0x15, 0x90, 0x68, 0xbe, 0xca, 0xc1, + 0x7c, 0xd3, 0x51, 0x6b, 0x45, 0x46, 0x54, 0x11, + 0x1a, 0x71, 0xd3, 0xcd, 0x6b, 0x8f, 0x79, 0x22, + 0x83, 0x02, 0x08, 0x4f, 0xba, 0x6a, 0x98, 0xed, + 0x32, 0xd8, 0xb4, 0x5b, 0x51, 0x88, 0x53, 0xec, + 0x2c, 0x7e, 0xa4, 0x89, 0xdc, 0xbf, 0xf9, 0x0d, + 0x32, 0xc8, 0xc3, 0xec, 0x6d, 0x2e, 0xf1, 0xbc, + 0x70, 0x4e, 0xf6, 0x9e, 0xbc, 0x31, 0x02, 0x81, + 0x81, 0x00, 0xd3, 0x35, 0x1b, 0x19, 0x75, 0x3f, + 0x61, 0xf2, 0x55, 0x03, 0xce, 0x25, 0xa9, 0xdf, + 0x0c, 0x0a, 0x3b, 0x47, 0x42, 0xdc, 0x38, 0x4b, + 0x13, 0x4d, 0x1f, 0x86, 0x58, 0x4f, 0xd8, 0xee, + 0xfa, 0x76, 0x15, 0xfb, 0x6e, 0x55, 0x31, 0xf2, + 0xd2, 0x62, 0x32, 0xa5, 0xc4, 0x23, 0x5e, 0x08, + 0xa9, 0x83, 0x07, 0xac, 0x8c, 0xa3, 0x7e, 0x18, + 0xc0, 0x1c, 0x57, 0x63, 0x8d, 0x05, 0x17, 0x47, + 0x1b, 0xd3, 0x74, 0x73, 0x20, 0x04, 0xfb, 0xc8, + 0x1a, 0x43, 0x04, 0x36, 0xc8, 0x19, 0xbe, 0xdc, + 0xa6, 0xe5, 0x0f, 0x25, 0x62, 0x24, 0x96, 0x92, + 0xb6, 0xb3, 0x97, 0xad, 0x57, 0x9a, 0x90, 0x37, + 0x4e, 0x31, 0x44, 0x74, 0xfa, 0x7c, 0xb4, 0xea, + 0xfc, 0x15, 0xa7, 0xb0, 0x51, 0xcc, 0xee, 0x1e, + 0xed, 0x5b, 0x98, 0x18, 0x0e, 0x65, 0xb6, 0x4b, + 0x69, 0x0b, 0x21, 0xdc, 0x86, 0x17, 0x6e, 0xc8, + 0xee, 0x24 }; + +// WHAT: A second 2048 bit RSA Public key +// WHY: Used to verify the functions that manipulate RSA keys. +static const uint8_t kTestRSAPublicKey3_2048[] = { + 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, + 0x00, 0xa5, 0xd0, 0xd7, 0x3e, 0x0e, 0x2d, 0xfb, + 0x43, 0x51, 0x99, 0xea, 0x40, 0x1e, 0x2d, 0x89, + 0xe4, 0xa2, 0x3e, 0xfc, 0x51, 0x3d, 0x0e, 0x83, + 0xa7, 0xe0, 0xa5, 0x41, 0x04, 0x1e, 0x14, 0xc5, + 0xa7, 0x5c, 0x61, 0x36, 0x44, 0xb3, 0x08, 0x05, + 0x5b, 0x14, 0xde, 0x01, 0x0c, 0x32, 0x3c, 0x9a, + 0x91, 0x00, 0x50, 0xa8, 0x1d, 0xcc, 0x9f, 0x8f, + 0x35, 0xb7, 0xc2, 0x75, 0x08, 0x32, 0x8b, 0x10, + 0x3a, 0x86, 0xf9, 0xd7, 0x78, 0xa3, 0x9d, 0x74, + 0x10, 0xc6, 0x24, 0xb1, 0x7f, 0xa5, 0xbf, 0x5f, + 0xc2, 0xd7, 0x15, 0xa3, 0x1d, 0xe0, 0x15, 0x6b, + 0x1b, 0x0e, 0x38, 0xba, 0x34, 0xbc, 0x95, 0x47, + 0x94, 0x40, 0x70, 0xac, 0x99, 0x1f, 0x0b, 0x8e, + 0x56, 0x93, 0x36, 0x2b, 0x6d, 0x04, 0xe7, 0x95, + 0x1a, 0x37, 0xda, 0x16, 0x57, 0x99, 0xee, 0x03, + 0x68, 0x16, 0x31, 0xaa, 0xc3, 0xb7, 0x92, 0x75, + 0x53, 0xfc, 0xf6, 0x20, 0x55, 0x44, 0xf8, 0xd4, + 0x8d, 0x78, 0x15, 0xc7, 0x1a, 0xb6, 0xde, 0x6c, + 0xe8, 0x49, 0x5d, 0xaf, 0xa8, 0x4e, 0x6f, 0x7c, + 0xe2, 0x6a, 0x4c, 0xd5, 0xe7, 0x8c, 0x8f, 0x0b, + 0x5d, 0x3a, 0x09, 0xd6, 0xb3, 0x44, 0xab, 0xe0, + 0x35, 0x52, 0x7c, 0x66, 0x85, 0xa4, 0x40, 0xd7, + 0x20, 0xec, 0x24, 0x05, 0x06, 0xd9, 0x84, 0x51, + 0x5a, 0xd2, 0x38, 0xd5, 0x1d, 0xea, 0x70, 0x2a, + 0x21, 0xe6, 0x82, 0xfd, 0xa4, 0x46, 0x1c, 0x4f, + 0x59, 0x6e, 0x29, 0x3d, 0xae, 0xb8, 0x8e, 0xee, + 0x77, 0x1f, 0x15, 0x33, 0xcf, 0x94, 0x1d, 0x87, + 0x3c, 0x37, 0xc5, 0x89, 0xe8, 0x7d, 0x85, 0xb3, + 0xbc, 0xe8, 0x62, 0x6a, 0x84, 0x7f, 0xfe, 0x9a, + 0x85, 0x3f, 0x39, 0xe8, 0xaa, 0x16, 0xa6, 0x8f, + 0x87, 0x7f, 0xcb, 0xc1, 0xd6, 0xf2, 0xec, 0x2b, + 0xa7, 0xdd, 0x49, 0x98, 0x7b, 0x6f, 0xdd, 0x69, + 0x6d, 0x02, 0x03, 0x01, 0x00, 0x01 }; + +static void dump_openssl_error() { + while (unsigned long err = ERR_get_error()) { + char buffer[120]; + cout << "openssl error -- " << ERR_error_string(err, buffer) << "\n"; + } +} + +class Session { + public: + Session() : valid_(false), open_(false) {} + + Session(string sname) : valid_(true), open_(false), sname_(sname), + mac_key_server_(wvcdm::MAC_KEY_SIZE), + mac_key_client_(wvcdm::MAC_KEY_SIZE), + enc_key_(wvcdm::KEY_SIZE), public_rsa_(0) {} + + bool isValid() { return valid_; } + bool isOpen() { return open_; } + bool successStatus() { return (OEMCrypto_SUCCESS == session_status_); } + OEMCryptoResult getStatus() { return session_status_; } + uint32_t get_nonce() { return nonce_; } + + uint32_t session_id() { return (uint32_t)session_id_; } + void set_session_id(uint32_t newsession) { + session_id_ = (OEMCrypto_SESSION)newsession; + } + + void open() { + EXPECT_TRUE(valid_); + EXPECT_TRUE(!open_); + session_status_ = OEMCrypto_OpenSession(&session_id_); + if (OEMCrypto_SUCCESS == session_status_) { + open_ = true; + } + } + + void close() { + EXPECT_TRUE(valid_); + session_status_ = OEMCrypto_CloseSession(session_id_); + if (OEMCrypto_SUCCESS == session_status_) { + open_ = false; + } + } + + void GenerateNonce(uint32_t* nonce) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateNonce(session_id(), nonce)); + } + + void FillDefaultContext(vector* mac_context, + vector* enc_context) { + /* WHAT: Context strings - these context strings are normally + * created by the CDM layer above from a license request message. + * WHY: Test MAC and ENC key generation. + */ + *mac_context = wvcdm::a2b_hex( + "41555448454e5449434154494f4e000a4c08001248000000020000101907d9ff" + "de13aa95c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e5873" + "4930acebe899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a" + "230a14080112100915007caa9b5931b76a3a85f046523e10011a093938373635" + "34333231180120002a0c31383836373837343035000000000200"); + *enc_context = wvcdm::a2b_hex( + "454e4352595054494f4e000a4c08001248000000020000101907d9ffde13aa95" + "c122678053362136bdf8408f8276e4c2d87ec52b61aa1b9f646e58734930aceb" + "e899b3e464189a14a87202fb02574e70640bd22ef44b2d7e3912250a230a1408" + "0112100915007caa9b5931b76a3a85f046523e10011a09393837363534333231" + "180120002a0c31383836373837343035000000000080"); + } + + void GenerateDerivedKeys() { + GenerateNonce(&nonce_); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_GenerateDerivedKeys( + session_id(), + &mac_context[0], mac_context.size(), + &enc_context[0], enc_context.size())); + + // WHAT: Expected MAC and ENC keys generated from context strings + // with test keybox "installed". + mac_key_server_ = wvcdm::a2b_hex( + "3CFD60254786AF350B353B4FBB700AB382558400356866BA16C256BCD8C502BF"); + mac_key_client_ = wvcdm::a2b_hex( + "A9DE7B3E4E199ED8D1FBC29CD6B4C772CC4538C8B0D3E208B3E76F2EC0FD6F47"); + enc_key_ = wvcdm::a2b_hex("D0BFC35DA9E33436E81C4229E78CB9F4"); + } + + void LoadTestKeys(uint32_t duration, uint32_t control, uint32_t nonce) { + MessageData data; + FillSimpleMessage(&data, duration, control, nonce); + MessageData encrypted; + EncryptMessage(data, &encrypted); + std::vector signature; + ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + FillKeyArray(encrypted, key_array); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadKeys(session_id(), message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, encrypted.mac_keys, + kNumKeys, key_array)); + // Update new generated keys. + memcpy(&mac_key_server_[0], data.mac_keys, wvcdm::MAC_KEY_SIZE); + memcpy(&mac_key_client_[0], data.mac_keys+wvcdm::MAC_KEY_SIZE, + wvcdm::MAC_KEY_SIZE); + } + + void RefreshTestKeys(const int key_count, uint32_t control_bits, + uint32_t nonce, bool expect_good) { + MessageData data; + + FillRefreshMessage(&data, key_count, control_bits, nonce); + + std::vector signature; + ServerSignMessage(data, &signature); + OEMCrypto_KeyRefreshObject key_array[key_count]; + + const uint8_t* message_ptr = reinterpret_cast(&data); + FillRefreshArray(data, key_array, key_count); + OEMCryptoResult sts = OEMCrypto_RefreshKeys(session_id(), message_ptr, + sizeof(data), + &signature[0], signature.size(), + key_count, key_array); + if( expect_good ) { + ASSERT_EQ(OEMCrypto_SUCCESS,sts); + } else { + ASSERT_NE(OEMCrypto_SUCCESS,sts); + } + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(session_id(), &keyId[0], keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + // WHAT: Dummy encrypted data. + vector encryptedData = wvcdm::a2b_hex( + "ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462" + "337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca" + "46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8" + "efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913" + "00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07" + "cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c" + "74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb" + "fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b"); + vector encryptionIv = wvcdm::a2b_hex( + "719dbcb253b2ec702bb8c1b1bc2f3bc6"); + // WHAT: Expected decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626" + "fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e" + "e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0" + "1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17" + "a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244" + "ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280" + "a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5" + "de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + session_id(), &encryptedData[0], + encryptedData.size(), true, &encryptionIv[0], 0, + &destBuffer, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + sleep(kShortSleep); // Should still be valid key. + + memset(outputBuffer, 0, sizeof(outputBuffer)); + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + session_id(), &encryptedData[0], + encryptedData.size(), true, &encryptionIv[0], 0, + &destBuffer, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + sleep(kShortSleep + kLongSleep); // Should be after first expiration. + + memset(outputBuffer, 0, sizeof(outputBuffer)); + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + session_id(), &encryptedData[0], + encryptedData.size(), true, &encryptionIv[0], 0, + &destBuffer, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + if( expect_good) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + } else { + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + } + } + + void FillSimpleMessage(MessageData* data, uint32_t duration, uint32_t control, + uint32_t nonce) { + OEMCrypto_GetRandom(data->mac_key_iv, sizeof(data->mac_key_iv)); + OEMCrypto_GetRandom(data->mac_keys, sizeof(data->mac_keys)); + for (unsigned int i = 0; i < kNumKeys; i++) { + memset(data->keys[i].key_id, i, kTestKeyIdLength); + OEMCrypto_GetRandom(data->keys[i].key_data, + sizeof(data->keys[i].key_data)); + data->keys[i].key_data_length = wvcdm::KEY_SIZE; + OEMCrypto_GetRandom(data->keys[i].key_iv, sizeof(data->keys[i].key_iv)); + OEMCrypto_GetRandom(data->keys[i].control_iv, + sizeof(data->keys[i].control_iv)); + memcpy(data->keys[i].control.verification, "kctl", 4); + data->keys[i].control.duration = htonl(duration); + data->keys[i].control.nonce = htonl(nonce); + data->keys[i].control.control_bits = htonl(control); + } + // WHAT: The first key for the canned decryption content. + vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); + memcpy(data->keys[0].key_data, &key[0], key.size()); + } + + void FillRefreshMessage(MessageData* data, int key_count, + uint32_t control_bits, uint32_t nonce) { + for (unsigned int i = 0; i < kNumKeys; i++) { + memset(data->keys[i].key_id, i, kTestKeyIdLength); + memcpy(data->keys[i].control.verification, "kctl", 4); + data->keys[i].control.duration = htonl(kLongDuration); + data->keys[i].control.nonce = htonl(nonce); + data->keys[i].control.control_bits = htonl(control_bits); + } + } + + void EncryptMessage(const MessageData& data, + MessageData* encrypted) { + *encrypted = data; + + uint8_t iv_buffer[16]; + memcpy(iv_buffer, &data.mac_key_iv[0], wvcdm::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&data.mac_keys[0], &encrypted->mac_keys[0], + 2*wvcdm::MAC_KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + + for (unsigned int i = 0; i < kNumKeys; i++) { + memcpy(iv_buffer, &data.keys[i].control_iv[0], wvcdm::KEY_IV_SIZE); + AES_set_encrypt_key(&data.keys[i].key_data[0], 128, &aes_key); + AES_cbc_encrypt(reinterpret_cast(&data.keys[i].control), + reinterpret_cast(&encrypted->keys[i].control), + wvcdm::KEY_SIZE, &aes_key, iv_buffer, AES_ENCRYPT); + + memcpy(iv_buffer, &data.keys[i].key_iv[0], wvcdm::KEY_IV_SIZE); + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&data.keys[i].key_data[0], + &encrypted->keys[i].key_data[0], + data.keys[i].key_data_length, + &aes_key, iv_buffer, AES_ENCRYPT); + } + } + + void EncryptMessage(RSAPrivateKeyMessage* data, + RSAPrivateKeyMessage* encrypted) { + *encrypted = *data; + size_t padding = wvcdm::KEY_SIZE-(data->rsa_key_length % wvcdm::KEY_SIZE); + memset(data->rsa_key + data->rsa_key_length, + static_cast(padding), padding); + encrypted->rsa_key_length = data->rsa_key_length + padding; + uint8_t iv_buffer[16]; + memcpy(iv_buffer, &data->rsa_key_iv[0], wvcdm::KEY_IV_SIZE); + AES_KEY aes_key; + AES_set_encrypt_key(&enc_key_[0], 128, &aes_key); + AES_cbc_encrypt(&data->rsa_key[0], &encrypted->rsa_key[0], + encrypted->rsa_key_length, &aes_key, iv_buffer, + AES_ENCRYPT); + } + + template + void ServerSignMessage(const T& data, std::vector* signature) { + signature->resize(SHA256_DIGEST_LENGTH); + unsigned int md_len = SHA256_DIGEST_LENGTH; + HMAC(EVP_sha256(), &mac_key_server_[0], SHA256_DIGEST_LENGTH, + reinterpret_cast(&data), sizeof(data), + &(signature->front()), &md_len); + } + + void ClientSignMessage(const vector &data, + std::vector* signature) { + signature->resize(SHA256_DIGEST_LENGTH); + unsigned int md_len = SHA256_DIGEST_LENGTH; + HMAC(EVP_sha256(), &mac_key_client_[0], SHA256_DIGEST_LENGTH, + &(data.front()), data.size(), &(signature->front()), &md_len); + } + + void FillKeyArray(const MessageData& data, + OEMCrypto_KeyObject* key_array) { + for (unsigned int i = 0; i < kNumKeys; i++) { + key_array[i].key_id = data.keys[i].key_id; + key_array[i].key_id_length = kTestKeyIdLength; + key_array[i].key_data_iv = data.keys[i].key_iv; + key_array[i].key_data = data.keys[i].key_data; + key_array[i].key_data_length = data.keys[i].key_data_length; + key_array[i].key_control_iv = data.keys[i].control_iv; + key_array[i].key_control + = reinterpret_cast(&data.keys[i].control); + } + } + + void FillRefreshArray(const MessageData& data, + OEMCrypto_KeyRefreshObject* key_array, + const int key_count) { + for (int i = 0; i < key_count; i++) { + if( key_count > 1 ) { + key_array[i].key_id = data.keys[i].key_id; + key_array[i].key_id_length = kTestKeyIdLength; + } else { + key_array[i].key_id = NULL; + key_array[i].key_id_length = 0; + } + key_array[i].key_control_iv = NULL; + key_array[i].key_control + = reinterpret_cast(&data.keys[i].control); + } + } + + void MakeRSACertificate(struct RSAPrivateKeyMessage* encrypted, + std::vector* signature) { + + // WHAT: Dummy context for testing signature generation. + vector context = wvcdm::a2b_hex( + "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" + "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" + "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" + "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" + "38373430350000"); + + OEMCryptoResult sts; + + // Generate signature + size_t gen_signature_length = 0; + sts = OEMCrypto_GenerateSignature(session_id(), &context[0], + context.size(), NULL, + &gen_signature_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_EQ(static_cast(32), gen_signature_length); + static const uint32_t SignatureBufferMaxLength = 256; + uint8_t gen_signature[SignatureBufferMaxLength]; + sts = OEMCrypto_GenerateSignature(session_id(), &context[0], + context.size(), gen_signature, + &gen_signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + std::vector expected_signature; + ClientSignMessage(context, &expected_signature); + ASSERT_EQ(0, memcmp(&expected_signature[0], gen_signature, + expected_signature.size())); + + // Rewrap Canned Response + // In the real world, the signature above would just have been used to + // contact the certificate provisioning server to get this response. + + struct RSAPrivateKeyMessage message; + memcpy(message.rsa_key, kTestRSAPKCS8PrivateKeyInfo2_2048, + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)); + OEMCrypto_GetRandom(message.rsa_key_iv, wvcdm::KEY_IV_SIZE); + message.rsa_key_length = sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048); + message.nonce = nonce_; + + EncryptMessage(&message, encrypted); + ServerSignMessage(*encrypted, signature); + } + + void RewrapRSAKey(const struct RSAPrivateKeyMessage& encrypted, + const std::vector& signature, + vector* wrapped_key) { + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey(session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key->clear(); + wrapped_key->resize(wrapped_key_length); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_RewrapDeviceRSAKey(session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, + &(wrapped_key->front()), + &wrapped_key_length)); + } + + bool PreparePublicKey(const uint8_t key[], size_t length) { + uint8_t const* p = key; + public_rsa_ = d2i_RSAPublicKey(0, &p , length); + if (!public_rsa_) { + cout << "d2i_RSAPrivateKey failed. "; + dump_openssl_error(); + return false; + } + return true; + } + + bool VerifyRSASignature(const uint8_t* message, + size_t message_length, + uint8_t* signature, + size_t* signature_length) { + if (!public_rsa_) { + cout << "No public RSA key loaded in test code.\n"; + return false; + } + if (*signature_length != static_cast(RSA_size(public_rsa_))) { + cout << "Signature size is wrong. " << *signature_length + << ", should be " << RSA_size(public_rsa_) << "\n"; + return false; + } + + // Hash the message using SHA1. + uint8_t hash[SHA_DIGEST_LENGTH]; + if (!SHA1(message, message_length, hash)) { + cout << "Error computing SHA1. "; + dump_openssl_error(); + return false; + } + + // Decrypt signature to padded digest. + uint8_t padded_digest[*signature_length]; + int status; + status = RSA_public_decrypt(*signature_length, signature, padded_digest, + public_rsa_, RSA_NO_PADDING); + if (status == -1) { + cout << "VerifyRSASignature. in RSA_Public_digest "; + dump_openssl_error(); + return false; + } + status = RSA_verify_PKCS1_PSS(public_rsa_, hash, EVP_sha1(), + padded_digest, SHA_DIGEST_LENGTH); + if (status != 1) { + cout << "VerifyRSASignature. in RSA_verify_PKCS1_PSS "; + dump_openssl_error(); + return false; + } + return true; + } + + bool GenerateRSASessionKey(vector* enc_session_key) { + if (!public_rsa_) { + cout << "No public RSA key loaded in test code.\n"; + return false; + } + vector session_key = wvcdm::a2b_hex( + "6fa479c731d2770b6a61a5d1420bb9d1"); + enc_session_key->assign(RSA_size(public_rsa_), 0); + int status = RSA_public_encrypt(session_key.size(), + &session_key[0], + &(enc_session_key->front()), + public_rsa_, RSA_PKCS1_OAEP_PADDING); + if (status != RSA_size(public_rsa_)) { + cout << "GenerateRSASessionKey error encrypting session key. "; + dump_openssl_error(); + return false; + } + return true; + } + + void InstallRSASessionTestKey(const vector& wrapped_rsa_key) { + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_LoadDeviceRSAKey(session_id(), &wrapped_rsa_key[0], + wrapped_rsa_key.size())); + GenerateNonce(&nonce_); + vector enc_session_key; + ASSERT_TRUE(PreparePublicKey(kTestRSAPublicKey2_2048, + sizeof(kTestRSAPublicKey2_2048))); + ASSERT_TRUE(GenerateRSASessionKey(&enc_session_key)); + vector mac_context; + vector enc_context; + FillDefaultContext(&mac_context, &enc_context); + ASSERT_EQ(OEMCrypto_SUCCESS, + OEMCrypto_DeriveKeysFromSessionKey( + session_id(), &enc_session_key[0], enc_session_key.size(), + &mac_context[0], mac_context.size(), + &enc_context[0], enc_context.size())); + + // WHAT: Expected MAC and ENC keys generated from context strings + // with RSA certificate "installed". + mac_key_server_ = wvcdm::a2b_hex( + "1E451E59CB663DA1646194DD28880788ED8ED2EFF913CBD6A0D535D1D5A90381"); + mac_key_client_ = wvcdm::a2b_hex( + "F9AAE74690909F2207B53B13307FCA096CA8C49CC6DFE3659873CB952889A74B"); + enc_key_ = wvcdm::a2b_hex("CB477D09014D72C9B8DCE76C33EA43B3"); + } + + private: + bool valid_; + bool open_; + string sname_; + OEMCrypto_SESSION session_id_; + OEMCryptoResult session_status_; + vector mac_key_server_; + vector mac_key_client_; + vector enc_key_; + uint32_t nonce_; + RSA* public_rsa_; +}; + +class OEMCryptoClientTest : public ::testing::Test { + protected: + + OEMCryptoClientTest() : alive_(false) {} + + bool init() { + OEMCryptoResult result; + if (!alive_) { + result = OEMCrypto_Initialize(); + alive_ = (OEMCrypto_SUCCESS == result); + } + return alive_; + } + + bool terminate() { + OEMCryptoResult result; + result = OEMCrypto_Terminate(); + if (OEMCrypto_SUCCESS == result) { + alive_ = false; + } + return !alive_; + } + + void testSetUp() { + init(); + } + + void CreateWrappedRSAKey(vector* wrapped_key) { + Session& s = createSession("RSA_Session"); + s.open(); + s.GenerateDerivedKeys(); + struct RSAPrivateKeyMessage encrypted; + std::vector signature; + s.MakeRSACertificate(&encrypted, &signature); + s.RewrapRSAKey(encrypted, signature, wrapped_key); + s.close(); + } + + void testTearDown() { + destroySessions(); + terminate(); + } + + void validateKeybox() { + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_IsKeyboxValid()); + } + + static Session badSession; + + Session& findSession(string sname) { + map::iterator it = _sessions.find(sname); + if (it != _sessions.end()) { + return it->second; + } + return badSession; + } + + Session& createSession(string sname) { + Session temp(sname); + _sessions.insert(pair(sname, temp)); + return findSession(sname); + } + + bool destroySession(string sname) { + Session& temp = findSession(sname); + if (!temp.isValid()) { + return false; + } + _sessions.erase(sname); + return true; + } + + bool destroySessions() { + _sessions.clear(); + return true; + } + + const uint8_t* find(const vector& message, + const vector& substring) { + vector::const_iterator pos = search(message.begin(), message.end(), + substring.begin(), + substring.end()); + if (pos == message.end()) { + return NULL; + } + return &(*pos); + } + + private: + bool alive_; + map _sessions; +}; + +Session OEMCryptoClientTest::badSession; + +// +// Keybox Tests +// These two tests are first, becuase it might give an idea why other +// tests are failing when the device has the wrong keybox installed. +TEST_F(OEMCryptoClientTest, VersionNumber) { + testSetUp(); + + const char* level = OEMCrypto_SecurityLevel(); + ASSERT_NE((char *)NULL, level); + ASSERT_EQ('L', level[0]); + cout << " OEMCrypto Security Level is "<< level << endl; + uint32_t version = OEMCrypto_APIVersion(); + cout << " OEMCrypto API version is " << version << endl; + ASSERT_EQ(oec_latest_version, version); + + testTearDown(); +} + +TEST_F(OEMCryptoClientTest, NormalGetKeyData) { + testSetUp(); + + OEMCryptoResult sts; + uint8_t key_data[256]; + size_t key_data_len = sizeof(key_data); + sts = OEMCrypto_GetKeyData(key_data, &key_data_len); + + uint32_t* data = reinterpret_cast(key_data); + printf(" NormalGetKeyData: system_id = %d = 0x%04X, version=%d\n", + htonl(data[1]), htonl(data[1]), htonl(data[0])); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + uint32_t system_id = htonl(data[1]); + if (system_id == 0x1019) { + cout << "======================================================================\n" + << "If you run this as \"oemcrypto_test --gtest_also_run_disabled_tests\",\n" + << "then a test keybox will be installed, and all tests will be run. \n" + << "======================================================================\n"; + } + testTearDown(); +} + +TEST_F(OEMCryptoClientTest, KeyboxValid) { + bool success; + success = init(); + EXPECT_TRUE(success); + validateKeybox(); + ASSERT_TRUE(success); + success = terminate(); + ASSERT_TRUE(success); +} + +TEST_F(OEMCryptoClientTest, NormalGetDeviceId) { + testSetUp(); + + OEMCryptoResult sts; + uint8_t dev_id[128] = {0}; + size_t dev_id_len = 128; + sts = OEMCrypto_GetDeviceID(dev_id, &dev_id_len); + cout << " NormalGetDeviceId: dev_id = " << dev_id + << " len = " << dev_id_len << endl; + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + testTearDown(); +} + +TEST_F(OEMCryptoClientTest, GetDeviceIdShortBuffer) { + testSetUp(); + + OEMCryptoResult sts; + uint8_t dev_id[128]; + uint32_t req_len = 11; + for (int i = 0; i < 128; ++i) { + dev_id[i] = 0x55; + } + dev_id[127] = '\0'; + size_t dev_id_len = req_len; + sts = OEMCrypto_GetDeviceID(dev_id, &dev_id_len); + // cout << "GetDeviceIdShortBuffer: sts = " << (int)sts << " request = " + // << req_len << " required = " << dev_id_len << endl; + + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + + // On short buffer error, function should return minimum buffer length + ASSERT_TRUE(dev_id_len > req_len); + + // cout << "NormalGetDeviceId: dev_id = " << dev_id + // << " len = " << dev_id_len << endl; + + testTearDown(); +} + +// +// initialization tests +// +TEST_F(OEMCryptoClientTest, NormalInitTermination) { + bool success; + success = init(); + EXPECT_TRUE(success); + success = terminate(); + ASSERT_TRUE(success); +} + +// +// Session Tests +// +TEST_F(OEMCryptoClientTest, NormalSessionOpenClose) { + Session& s = createSession("ONE"); + testSetUp(); + + s.open(); + ASSERT_TRUE(s.successStatus()); + ASSERT_TRUE(s.isOpen()); + + s.close(); + ASSERT_TRUE(s.successStatus()); + ASSERT_FALSE(s.isOpen()); + + testTearDown(); +} + +TEST_F(OEMCryptoClientTest, TwoSessionsOpenClose) { + Session& s1 = createSession("ONE"); + Session& s2 = createSession("TWO"); + testSetUp(); + + s1.open(); + ASSERT_TRUE(s1.successStatus()); + ASSERT_TRUE(s1.isOpen()); + + s2.open(); + ASSERT_TRUE(s2.successStatus()); + ASSERT_TRUE(s2.isOpen()); + + s1.close(); + ASSERT_TRUE(s1.successStatus()); + ASSERT_FALSE(s1.isOpen()); + + s2.close(); + ASSERT_TRUE(s2.successStatus()); + ASSERT_FALSE(s2.isOpen()); + + testTearDown(); +} + +TEST_F(OEMCryptoClientTest, EightSessionsOpenClose) { + Session& s1 = createSession("ONE"); + Session& s2 = createSession("TWO"); + Session& s3 = createSession("THREE"); + Session& s4 = createSession("FOUR"); + Session& s5 = createSession("FIVE"); + Session& s6 = createSession("SIX"); + Session& s7 = createSession("SEVEN"); + Session& s8 = createSession("EIGHT"); + testSetUp(); + + s1.open(); + ASSERT_TRUE(s1.successStatus()); + ASSERT_TRUE(s1.isOpen()); + + s2.open(); + ASSERT_TRUE(s2.successStatus()); + ASSERT_TRUE(s2.isOpen()); + + s3.open(); + ASSERT_TRUE(s3.successStatus()); + ASSERT_TRUE(s3.isOpen()); + + s4.open(); + ASSERT_TRUE(s4.successStatus()); + ASSERT_TRUE(s4.isOpen()); + + s5.open(); + ASSERT_TRUE(s5.successStatus()); + ASSERT_TRUE(s5.isOpen()); + + s6.open(); + ASSERT_TRUE(s6.successStatus()); + ASSERT_TRUE(s6.isOpen()); + + s7.open(); + ASSERT_TRUE(s7.successStatus()); + ASSERT_TRUE(s7.isOpen()); + + s8.open(); + ASSERT_TRUE(s8.successStatus()); + ASSERT_TRUE(s8.isOpen()); + + s1.close(); + ASSERT_TRUE(s1.successStatus()); + ASSERT_FALSE(s1.isOpen()); + + s8.close(); + ASSERT_TRUE(s8.successStatus()); + ASSERT_FALSE(s8.isOpen()); + + s3.close(); + ASSERT_TRUE(s3.successStatus()); + ASSERT_FALSE(s3.isOpen()); + + s6.close(); + ASSERT_TRUE(s6.successStatus()); + ASSERT_FALSE(s6.isOpen()); + + s5.close(); + ASSERT_TRUE(s5.successStatus()); + ASSERT_FALSE(s5.isOpen()); + + s4.close(); + ASSERT_TRUE(s4.successStatus()); + ASSERT_FALSE(s4.isOpen()); + + s7.close(); + ASSERT_TRUE(s7.successStatus()); + ASSERT_FALSE(s7.isOpen()); + + s2.close(); + ASSERT_TRUE(s2.successStatus()); + ASSERT_FALSE(s2.isOpen()); + + testTearDown(); +} + +TEST_F(OEMCryptoClientTest, GenerateNonce) { + Session& s = createSession("ONE"); + testSetUp(); + s.open(); + uint32_t nonce; + + s.GenerateNonce(&nonce); + s.close(); + ASSERT_TRUE(s.successStatus()); + ASSERT_FALSE(s.isOpen()); + testTearDown(); +} + +TEST_F(OEMCryptoClientTest, GenerateTwoNonces) { + Session& s = createSession("ONE"); + testSetUp(); + s.open(); + uint32_t nonce1; + uint32_t nonce2; + + s.GenerateNonce(&nonce1); + s.GenerateNonce(&nonce2); + ASSERT_TRUE(nonce1 != nonce2); + + s.close(); + ASSERT_TRUE(s.successStatus()); + ASSERT_FALSE(s.isOpen()); + testTearDown(); +} + +TEST_F(OEMCryptoClientTest, GenerateDerivedKeys) { + Session& s = createSession("ONE"); + testSetUp(); + s.open(); + + s.GenerateDerivedKeys(); + + s.close(); + ASSERT_TRUE(s.successStatus()); + ASSERT_FALSE(s.isOpen()); + testTearDown(); +} + +// +// AddKey Tests +// +// These tests will install a test keybox. This may be a problem +// on a production device, so they are disabled by default. +// Run this program with the command line argument: +// "--gtest_also_run_disabled_tests" +// to enable all of these tests. + +class DISABLED_TestKeybox : public OEMCryptoClientTest { + protected: + + void InstallKeybox(const wvoec_mock::WidevineKeybox& keybox, bool good) { + OEMCryptoResult sts; + uint8_t wrapped[sizeof(wvoec_mock::WidevineKeybox)]; + size_t length = sizeof(wvoec_mock::WidevineKeybox); + sts = OEMCrypto_WrapKeybox(reinterpret_cast(&keybox), + sizeof(keybox), + wrapped, + &length, + NULL, 0); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = OEMCrypto_InstallKeybox(wrapped, sizeof(keybox)); + if( good ) { + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } else { + // Can return error now, or return error on IsKeyboxValid. + } + } +}; + +TEST_F(DISABLED_TestKeybox, CheckSystemID) { + testSetUp(); + + OEMCryptoResult sts; + uint8_t key_data[256]; + size_t key_data_len = sizeof(key_data); + sts = OEMCrypto_GetKeyData(key_data, &key_data_len); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + uint32_t* data = reinterpret_cast(key_data); + uint32_t system_id = htonl(data[1]); + if (system_id != 0x1019) { + + cout << "================================================================\n" + << "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" + << "WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING \n" + << "You have enabled the keybox tests. This code WILL INSTALL A \n" + << "TEST KEYBOX. IT WILL REPLACE THE EXISTING KEYBOX, and you will.\n" + << "NOT have access to production content. Your current keybox has \n" + << "system id " << system_id << ".\n" + << "\n" + << "Continue? [y/N]:\n"; + int answer = getchar(); + if (tolower(answer) != 'y') { + cout << "Quitting tests.\n"; + exit(1); + } + } + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, GoodKeybox) { + testSetUp(); + wvoec_mock::WidevineKeybox keybox = kValidKeybox02; + OEMCryptoResult sts; + InstallKeybox(keybox, true); + sts = OEMCrypto_IsKeyboxValid(); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + keybox = kValidKeybox03; + InstallKeybox(keybox, true); + sts = OEMCrypto_IsKeyboxValid(); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); +} + +TEST_F(DISABLED_TestKeybox, DefaultKeybox) { + testSetUp(); + ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_Initialize()) + << "OEMCrypto_Initialize failed."; + OEMCryptoResult sts; + sts = OEMCrypto_IsKeyboxValid(); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); +} + +TEST_F(DISABLED_TestKeybox, BadCRCKeybox) { + testSetUp(); + wvoec_mock::WidevineKeybox keybox = kValidKeybox02; + keybox.crc_[1] ^= 42; + OEMCryptoResult sts; + InstallKeybox(keybox, false); + sts = OEMCrypto_IsKeyboxValid(); + ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); +} + +TEST_F(DISABLED_TestKeybox, BadMagicKeybox) { + testSetUp(); + wvoec_mock::WidevineKeybox keybox = kValidKeybox02; + keybox.magic_[1] ^= 42; + OEMCryptoResult sts; + InstallKeybox(keybox, false); + sts = OEMCrypto_IsKeyboxValid(); + ASSERT_EQ(OEMCrypto_ERROR_BAD_MAGIC, sts); +} + +TEST_F(DISABLED_TestKeybox, BadDataKeybox) { + testSetUp(); + wvoec_mock::WidevineKeybox keybox = kValidKeybox02; + keybox.data_[1] ^= 42; + OEMCryptoResult sts; + InstallKeybox(keybox, false); + sts = OEMCrypto_IsKeyboxValid(); + ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts); +} + +TEST_F(DISABLED_TestKeybox, GenerateSignature) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + // WHAT: Dummy context for testing signature generation. + vector context = wvcdm::a2b_hex( + "0a4c08001248000000020000101907d9ffde13aa95c122678053362136bdf840" + "8f8276e4c2d87ec52b61aa1b9f646e58734930acebe899b3e464189a14a87202" + "fb02574e70640bd22ef44b2d7e3912250a230a14080112100915007caa9b5931" + "b76a3a85f046523e10011a09393837363534333231180120002a0c3138383637" + "38373430350000"); + + static const uint32_t SignatureBufferMaxLength = 256; + uint8_t signature[SignatureBufferMaxLength]; + size_t signature_length = SignatureBufferMaxLength; + + OEMCryptoResult sts; + sts = OEMCrypto_GenerateSignature( + s.session_id(), + &context[0], context.size(), signature, &signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + static const uint32_t SignatureExpectedLength = 32; + ASSERT_EQ(signature_length, SignatureExpectedLength); + + std::vector expected_signature; + s.ClientSignMessage(context, &expected_signature); + ASSERT_EQ(0, memcmp(&expected_signature[0], signature, + expected_signature.size())); + s.close(); + ASSERT_TRUE(s.successStatus()); + ASSERT_FALSE(s.isOpen()); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyNoNonce) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, 0, 42); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithNonce) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + s.LoadTestKeys(0, wvoec_mock::kControlNonceEnabled, s.get_nonce()); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithNoMAC) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + OEMCryptoResult sts = OEMCrypto_LoadKeys(s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + NULL, NULL, + kNumKeys, key_array); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +// The Bad Range tests verify that OEMCrypto_LoadKeys checks the range +// of all the pointers. It should reject a message if the pointer does +// not point into the message buffer. +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange1) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + vector mac_keys(encrypted.mac_keys, + encrypted.mac_keys+sizeof(encrypted.mac_keys)); + + // mac_keys parameter points outside of buffer (should fail). + OEMCryptoResult sts = OEMCrypto_LoadKeys(s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + &mac_keys[0], + kNumKeys, key_array); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange2) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + vector mac_key_iv(encrypted.mac_key_iv, + encrypted.mac_key_iv+sizeof(encrypted.mac_key_iv)); + + OEMCryptoResult sts = OEMCrypto_LoadKeys(s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + &mac_key_iv[0], // bad. + encrypted.mac_keys, + kNumKeys, key_array); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange3) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + vector bad_buffer(encrypted.keys[0].key_id, + encrypted.keys[0].key_id+kTestKeyIdLength); + key_array[0].key_id = &bad_buffer[0]; + + OEMCryptoResult sts = OEMCrypto_LoadKeys(s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange4) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + vector bad_buffer(encrypted.keys[1].key_data, + encrypted.keys[1].key_data+wvcdm::KEY_SIZE); + key_array[1].key_data = &bad_buffer[0]; + + OEMCryptoResult sts = OEMCrypto_LoadKeys(s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange5) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + vector + bad_buffer(encrypted.keys[1].key_iv, + encrypted.keys[1].key_iv+sizeof(encrypted.keys[1].key_iv)); + key_array[1].key_data_iv = &bad_buffer[0]; + + OEMCryptoResult sts = OEMCrypto_LoadKeys(s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange6) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + vector + bad_buffer(key_array[2].key_control, + key_array[2].key_control+sizeof(encrypted.keys[1].control)); + key_array[2].key_control = &bad_buffer[0]; + + OEMCryptoResult sts = OEMCrypto_LoadKeys(s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadRange7) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + size_t iv_size = sizeof(encrypted.keys[1].control_iv); + vector bad_buffer(key_array[2].key_control_iv, + key_array[2].key_control_iv+iv_size); + key_array[2].key_control_iv = &bad_buffer[0]; + + OEMCryptoResult sts = OEMCrypto_LoadKeys(s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadNonce) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, wvoec_mock::kControlNonceEnabled, + 42); // bad nonce. + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + OEMCryptoResult sts = OEMCrypto_LoadKeys( + s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + + ASSERT_NE(OEMCrypto_SUCCESS, sts); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeyWithBadVerification) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + data.keys[1].control.verification[2] = 'Z'; + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + OEMCryptoResult sts = OEMCrypto_LoadKeys( + s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + + ASSERT_NE(OEMCrypto_SUCCESS, sts); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeysBadSignature) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + signature[0] ^= 42; // Bad signature. + + OEMCryptoResult sts = OEMCrypto_LoadKeys( + s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + + ASSERT_NE(OEMCrypto_SUCCESS, sts); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadKeysWithNoDerivedKeys) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + // Don't call, should fail: + // s.GenerateDerivedKeys(); + + MessageData data; + s.FillSimpleMessage(&data, 0, 0, 0); + + MessageData encrypted; + s.EncryptMessage(data, &encrypted); + std::vector signature; + s.ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s.FillKeyArray(encrypted, key_array); + + OEMCryptoResult sts = OEMCrypto_LoadKeys( + s.session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + + ASSERT_NE(OEMCrypto_SUCCESS, sts); + + s.close(); + testTearDown(); +} + +// +// Load, Refresh Keys Test +// + +class DISABLED_RefreshKeyTest : public DISABLED_TestKeybox { + public: + void RefreshWithNonce(const int key_count) { + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce()); + uint32_t nonce; + s.GenerateNonce(&nonce); + s.RefreshTestKeys(key_count, wvoec_mock::kControlNonceEnabled, nonce, true); + s.close(); + } + + void RefresNoNonce(const int key_count) { + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, 0, 0); + uint32_t nonce; + s.GenerateNonce(&nonce); + s.RefreshTestKeys(key_count,0, 0, true); + s.close(); + } + + void RefreshOldNonce(const int key_count) { + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce()); + uint32_t nonce = s.get_nonce(); + s.RefreshTestKeys(key_count, wvoec_mock::kControlNonceEnabled, nonce, + false); + s.close(); + } + void RefreshBadNonce(const int key_count) { + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce()); + uint32_t nonce; + s.GenerateNonce(&nonce); + nonce ^= 42; + s.RefreshTestKeys(key_count, wvoec_mock::kControlNonceEnabled, nonce, + false); + s.close(); + } + +}; + +TEST_F(DISABLED_RefreshKeyTest, RefreshAllKeys) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + RefreshWithNonce(1); // One key control block to refresh all keys. + RefreshOldNonce(1); + RefreshBadNonce(1); + testTearDown(); +} + +TEST_F(DISABLED_RefreshKeyTest, RefreshEachKeys) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + RefreshWithNonce(kNumKeys); // Each key control block updates different key. + RefreshOldNonce(kNumKeys); + RefreshBadNonce(kNumKeys); + testTearDown(); +} + +// +// Decrypt Tests +// + +TEST_F(DISABLED_TestKeybox, Decrypt) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, 0, 0); + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + // WHAT: Dummy encrypted data. + vector encryptedData = wvcdm::a2b_hex( + "ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462" + "337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca" + "46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8" + "efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913" + "00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07" + "cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c" + "74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb" + "fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b"); + vector encryptionIv = wvcdm::a2b_hex( + "719dbcb253b2ec702bb8c1b1bc2f3bc6"); + // WHAT: Expected decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626" + "fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e" + "e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0" + "1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17" + "a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244" + "ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280" + "a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5" + "de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], + encryptedData.size(), true, &encryptionIv[0], 0, + &destBuffer, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, DecryptZeroDuration) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + s.LoadTestKeys(0, 0, 0); + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + // WHAT: Dummy encrypted data. + vector encryptedData = wvcdm::a2b_hex( + "ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462" + "337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca" + "46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8" + "efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913" + "00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07" + "cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c" + "74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb" + "fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b"); + vector encryptionIv = wvcdm::a2b_hex( + "719dbcb253b2ec702bb8c1b1bc2f3bc6"); + // WHAT: Expected decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626" + "fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e" + "e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0" + "1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17" + "a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244" + "ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280" + "a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5" + "de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], + encryptedData.size(), true, &encryptionIv[0], 0, + &destBuffer, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, DecryptWithOffset) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, 0, 0); + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(s.session_id(), + &keyId[0], + keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + // WHAT: Dummy encrypted data. + vector encryptedData = wvcdm::a2b_hex( + "c17055d4e3ab8e892b40ca2deed7cd46b406cd41d50f23d5877b36" + "ad351887df2b3774dc413904afd958ba766cc6ab51a3ffd8f845296c5d8326ee" + "39c9d0fec79885515e6b8a12911831d9fb158ca2fd3dfcfcf228741a63734685" + "8dffc30f5871260c5cef8be61cfa08b191c837901f077046664c0c56db81d412" + "98b59e5655cd94871c3c226dc3565144297f1459cddba069d5d2d6206cfd5798" + "eda4b82e01a9966d48984d6ef3fbd326ba0f6fcbe52c95786d478c2f33398c62" + "ae5210c7472d7d8dc7d12f981679f4ea9793736f354747ef14165367b94e07fc" + "4bcc7bd14746304fea100dc6465ab51241355bb19e6c2cfb2bb6bbf709765d13"); + vector encryptionIv = wvcdm::a2b_hex( + "c09454479a280829c946df3c22f25539"); + // WHAT: Expected decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "f344d9cfe336c94cf4e3ea9e3446d1427bc02d2debe6dec5b272b8" + "a4004b696c4b37e01d7418510abf32bb071f9a4bc0d2ad7e874b648e50bd0e4f" + "7085b70bf9ad2c7f37025dd45f93e90304739b1ce098a52e7b99a90f92544a9b" + "dca6f49e0006c80a0cfa018600523ad30e483141fe720d045394815d5c875ad4" + "b4387b8d09b6119bd0943e51b0b9103034496b3a83ba593f79baa188aeb6e08f" + "f6475933e9ce1bb95fbb526424e7966e25830c20da73c65c6fbff110b08e4def" + "eae94f98296770275b0d738207a8217cd6118f6ebc6e393428f2268cfedf800e" + "a7ebc606471b9a9dfccd1589e86d88fde508261eaf190efd20554ce9e14ff3c9"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], + encryptedData.size(), true, &encryptionIv[0], 5, + &destBuffer, OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +// Increment counter for AES-CTR. The CENC spec specifies we increment only +// the low 64 bits of the IV counter, and leave the high 64 bits alone. This is +// different from the OpenSSL implementation, so we implement the CTR loop +// ourselves. +void ctr128_inc64(uint8_t* counter) { + uint32_t n = 16; + do { + if (++counter[--n] != 0) return; + } while (n>8); +} +vector EncryptCTR(const vector &key, + const vector &iv, + const vector &in, size_t block_offset) { + AES_KEY aes_key; + AES_set_encrypt_key(&key[0], AES_BLOCK_SIZE * 8, &aes_key); + + uint8_t aes_iv[AES_BLOCK_SIZE]; + memcpy(aes_iv, &iv[0], AES_BLOCK_SIZE); + + // Encrypt the IV. + uint8_t ecount_buf[AES_BLOCK_SIZE]; + + vector out( in.size()); + + size_t cipher_data_length = in.size(); + size_t l = 0; + while (l < cipher_data_length) { + AES_encrypt(aes_iv, ecount_buf, &aes_key); + for (int n = block_offset; n < AES_BLOCK_SIZE && l < cipher_data_length; + ++n, ++l) { + out[l] = in[l] ^ ecount_buf[n]; + } + ctr128_inc64(aes_iv); + block_offset = 0; + } + return out; +} + +TEST_F(DISABLED_TestKeybox, DecryptWithNearWrap) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, 0, 0); + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(s.session_id(), + &keyId[0], + keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + vector key = wvcdm::a2b_hex("39AD33E5719656069F9EDE9EBBA7A77D"); + vector encryptionIv = wvcdm::a2b_hex( + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE"); + // WHAT: Dummy decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "f344d9cfe336c94cf4e3ea9e3446d1427bc02d2debe6dec5b272b8" + "a4004b696c4b37e01d7418510abf32bb071f9a4bc0d2ad7e874b648e50bd0e4f" + "7085b70bf9ad2c7f37025dd45f93e90304739b1ce098a52e7b99a90f92544a9b" + "dca6f49e0006c80a0cfa018600523ad30e483141fe720d045394815d5c875ad4" + "b4387b8d09b6119bd0943e51b0b9103034496b3a83ba593f79baa188aeb6e08f" + "f6475933e9ce1bb95fbb526424e7966e25830c20da73c65c6fbff110b08e4def" + "eae94f98296770275b0d738207a8217cd6118f6ebc6e393428f2268cfedf800e" + "a7ebc606471b9a9dfccd1589e86d88fde508261eaf190efd20554ce9e14ff3c9"); + size_t block_offset = 5; + vector encryptedData = EncryptCTR(key, encryptionIv, unencryptedData, + block_offset) ; + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], encryptedData.size(), true, + &encryptionIv[0], block_offset, &destBuffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, DecryptUnencrypted) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, 0, 0); + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + // WHAT: Dummy decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "1558497b6d994be343ed1c6d6313e0537b843e9a9c0836d1e83fe33154191ce9" + "a14d8d95bebaddc03bd471827170f527c0a166b9068b273d1bc57fbb13975ee4" + "f6b9a31743da6c447acbb712e81b13eddfd4e96c76010ac9b8aa1b6b3152b0fc" + "39ad33e5719656069f9ede9ebba7a77dd2e2074eec5c1b7ffc427a6f1be168f0" + "b5857713a44623862c903284bc53417e23c65602b52c1cb699e8352453eb9698" + "0b31459b90c26c907b549c1ab293725e414d4e45f5b30af7a55f95499a7dc89f" + "7d13ba90b34aef6b49484b0701bf96ea8b660c24bb4e92a2d1c43beb434fa386" + "1071380388799ac31d79285f5817687ed3e2eeb73a30744e77b757686c9ba5ad"); + vector encryptionIv = wvcdm::a2b_hex( + "49fc3efaaf614ed81d595847b928edd0"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &unencryptedData[0], unencryptedData.size(), false, + &encryptionIv[0], 0, &destBuffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, DecryptUnencryptedNoKey) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("NOKEY"); + s.open(); + + // Clear data should be copied even if there is no key selected. + + // Set up our expected input and output + // WHAT: Dummy decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "1558497b6d994be343ed1c6d6313e0537b843e9a9c0836d1e83fe33154191ce9" + "a14d8d95bebaddc03bd471827170f527c0a166b9068b273d1bc57fbb13975ee4" + "f6b9a31743da6c447acbb712e81b13eddfd4e96c76010ac9b8aa1b6b3152b0fc" + "39ad33e5719656069f9ede9ebba7a77dd2e2074eec5c1b7ffc427a6f1be168f0" + "b5857713a44623862c903284bc53417e23c65602b52c1cb699e8352453eb9698" + "0b31459b90c26c907b549c1ab293725e414d4e45f5b30af7a55f95499a7dc89f" + "7d13ba90b34aef6b49484b0701bf96ea8b660c24bb4e92a2d1c43beb434fa386" + "1071380388799ac31d79285f5817687ed3e2eeb73a30744e77b757686c9ba5ad"); + vector encryptionIv = wvcdm::a2b_hex( + "49fc3efaaf614ed81d595847b928edd0"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &unencryptedData[0], unencryptedData.size(), false, + &encryptionIv[0], 0, &destBuffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, DecryptSecureToClear) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, wvoec_mock::kControlObserveDataPath + | wvoec_mock::kControlDataPathSecure, 0); + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + // WHAT: Dummy encrypted data. + vector encryptedData = wvcdm::a2b_hex( + "ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462" + "337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca" + "46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8" + "efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913" + "00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07" + "cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c" + "74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb" + "fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b"); + vector encryptionIv = wvcdm::a2b_hex( + "719dbcb253b2ec702bb8c1b1bc2f3bc6"); + // WHAT: Expected decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626" + "fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e" + "e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0" + "1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17" + "a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244" + "ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280" + "a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5" + "de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], encryptedData.size(), true, + &encryptionIv[0], 0, &destBuffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, KeyDuration) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + s.LoadTestKeys(kDuration, wvoec_mock::kControlNonceEnabled, s.get_nonce()); + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + // WHAT: Dummy encrypted data. + vector encryptedData = wvcdm::a2b_hex( + "ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462" + "337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca" + "46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8" + "efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913" + "00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07" + "cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c" + "74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb" + "fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b"); + vector encryptionIv = wvcdm::a2b_hex( + "719dbcb253b2ec702bb8c1b1bc2f3bc6"); + // WHAT: Expected decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626" + "fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e" + "e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0" + "1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17" + "a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244" + "ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280" + "a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5" + "de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], encryptedData.size(), true, + &encryptionIv[0], 0, &destBuffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + sleep(kShortSleep); // Should still be valid key. + + memset(outputBuffer, 0, sizeof(outputBuffer)); + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], encryptedData.size(), true, + &encryptionIv[0], 0, &destBuffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + sleep(kLongSleep); // Should be expired key. + + memset(outputBuffer, 0, sizeof(outputBuffer)); + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], encryptedData.size(), true, + &encryptionIv[0], 0, &destBuffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +// +// Certificate Root of Trust Tests +// + +void TestKey(const uint8_t key[], size_t length) { + uint8_t const* p = key; + RSA* rsa = d2i_RSAPrivateKey(0, &p , length); + if (!rsa) { + cout << "d2i_RSAPrivateKey failed. "; + dump_openssl_error(); + ASSERT_TRUE(false); + } + switch (RSA_check_key(rsa)) { + case 1: // valid. + ASSERT_TRUE(true); + return; + case 0: // not valid. + cout << "[TestKey(): rsa key not valid] "; + dump_openssl_error(); + ASSERT_TRUE(false); + default: // -1 == check failed. + cout << "[TestKey(): error checking rsa key] "; + dump_openssl_error(); + ASSERT_TRUE(false); + } +} + +TEST_F(DISABLED_TestKeybox, ValidateRSATestKeys) { + TestKey(kTestPKCS1RSAPrivateKey2_2048, sizeof(kTestPKCS1RSAPrivateKey2_2048)); + TestKey(kTestPKCS1RSAPrivateKey3_2048, sizeof(kTestPKCS1RSAPrivateKey3_2048)); +} + +TEST_F(DISABLED_TestKeybox, CertificateProvision) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + struct RSAPrivateKeyMessage encrypted; + std::vector signature; + s.MakeRSACertificate(&encrypted, &signature); + vector wrapped_key; + s.RewrapRSAKey(encrypted, signature, &wrapped_key); + + vector clear_key(kTestRSAPKCS8PrivateKeyInfo2_2048, + kTestRSAPKCS8PrivateKeyInfo2_2048 + + sizeof(kTestRSAPKCS8PrivateKeyInfo2_2048)); + ASSERT_EQ(NULL, find(wrapped_key, clear_key)); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange1) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + struct RSAPrivateKeyMessage encrypted; + std::vector signature; + s.MakeRSACertificate(&encrypted, &signature); + vector wrapped_key; + + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key.clear(); + wrapped_key.resize(wrapped_key_length); + uint32_t nonce = encrypted.nonce; + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, + & (wrapped_key.front()), + &wrapped_key_length)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange2) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + struct RSAPrivateKeyMessage encrypted; + std::vector signature; + s.MakeRSACertificate(&encrypted, &signature); + vector wrapped_key; + + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key.clear(); + wrapped_key.resize(wrapped_key_length); + vector bad_buffer(encrypted.rsa_key, + encrypted.rsa_key+sizeof(encrypted.rsa_key)); + + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + &bad_buffer[0], + encrypted.rsa_key_length, + encrypted.rsa_key_iv, + & (wrapped_key.front()), + &wrapped_key_length)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRange3) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + struct RSAPrivateKeyMessage encrypted; + std::vector signature; + s.MakeRSACertificate(&encrypted, &signature); + vector wrapped_key; + + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key.clear(); + wrapped_key.resize(wrapped_key_length); + vector bad_buffer(encrypted.rsa_key, + encrypted.rsa_key+sizeof(encrypted.rsa_key)); + + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + &bad_buffer[0], + & (wrapped_key.front()), + &wrapped_key_length)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, CertificateProvisionBadSignature) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + struct RSAPrivateKeyMessage encrypted; + std::vector signature; + s.MakeRSACertificate(&encrypted, &signature); + vector wrapped_key; + + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key.clear(); + wrapped_key.resize(wrapped_key_length); + signature[4] ^= 42; // bad signature. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, + & (wrapped_key.front()), + &wrapped_key_length)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, CertificateProvisionBadNonce) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + struct RSAPrivateKeyMessage encrypted; + std::vector signature; + s.MakeRSACertificate(&encrypted, &signature); + vector wrapped_key; + + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key.clear(); + wrapped_key.resize(wrapped_key_length); + encrypted.nonce ^= 42; // Almost surely a bad nonce. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, + & (wrapped_key.front()), + &wrapped_key_length)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, CertificateProvisionBadRSAKey) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + struct RSAPrivateKeyMessage encrypted; + std::vector signature; + s.MakeRSACertificate(&encrypted, &signature); + vector wrapped_key; + + size_t wrapped_key_length = 0; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, NULL, + &wrapped_key_length)); + wrapped_key.clear(); + wrapped_key.resize(wrapped_key_length); + encrypted.rsa_key[1] ^= 42; // Almost surely a bad key. + ASSERT_NE(OEMCrypto_SUCCESS, + OEMCrypto_RewrapDeviceRSAKey(s.session_id(), message_ptr, + sizeof(encrypted), &signature[0], + signature.size(), &encrypted.nonce, + encrypted.rsa_key, + encrypted.rsa_key_length, + encrypted.rsa_key_iv, + & (wrapped_key.front()), + &wrapped_key_length)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, LoadWrappedRSAKey) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + std::vector wrapped_rsa_key; + CreateWrappedRSAKey(&wrapped_rsa_key); + + Session& s = createSession("ONE"); + s.open(); + sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key[0], + wrapped_rsa_key.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, RSASignature) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + std::vector wrapped_rsa_key; + CreateWrappedRSAKey(&wrapped_rsa_key); + + Session& s = createSession("ONE"); + s.open(); + sts = OEMCrypto_LoadDeviceRSAKey(s.session_id(), &wrapped_rsa_key[0], + wrapped_rsa_key.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Sign a Message + // WHAT: Dummy license request. + // WHY: Test message signing. + vector licenseRequest = wvcdm::a2b_hex( + "ba711a51e0c4c995440c28057f7f5e2f2e9c3a1edeb7549aca21e6050b059ac8" + "6ad64ec1a528eef17b4f5ce781af488d50fb0e60d04b48c78d55847a4e14243c" + "0023c553b46a2f53995870f351295e3aa2237f153f1415e817ad23e662e547b1" + "4708b303473813f93ee192353ff22bee54dd0f558bbe4b61b75b387bc310e9d6" + "8ff2cb3482689c0688570809b756dba4c2697be3132a2da782aa877ed64d8c7d" + "506525a382bad14d7e797c256c3617c22fa4165482b9742e9b54ffb6c52eda1d"); + size_t signature_length = 0; + + sts = OEMCrypto_GenerateRSASignature(s.session_id(), &licenseRequest[0], + licenseRequest.size(), NULL, + &signature_length); + + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_NE(static_cast(0), signature_length); + + uint8_t* signature = new uint8_t[signature_length]; + + sts = OEMCrypto_GenerateRSASignature(s.session_id(), &licenseRequest[0], + licenseRequest.size(), signature, + &signature_length); + + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + // In the real world, the signature above would just have been used to contact + // the license server to get this response. + ASSERT_TRUE(s.PreparePublicKey(kTestRSAPublicKey2_2048, + sizeof(kTestRSAPublicKey2_2048))); + ASSERT_TRUE(s.VerifyRSASignature(&licenseRequest[0], licenseRequest.size(), + signature, &signature_length)); + s.close(); + testTearDown(); + + delete[] signature; +} + +TEST_F(DISABLED_TestKeybox, LoadRSASessionKey) { + testSetUp(); + + InstallKeybox(kDefaultKeybox, true); + std::vector wrapped_rsa_key; + CreateWrappedRSAKey(&wrapped_rsa_key); + Session& s = createSession("ONE"); + s.open(); + s.InstallRSASessionTestKey(wrapped_rsa_key); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_TestKeybox, CertificateDecrypt) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + std::vector wrapped_rsa_key; + CreateWrappedRSAKey(&wrapped_rsa_key); + Session& s = createSession("ONE"); + s.open(); + + s.InstallRSASessionTestKey(wrapped_rsa_key); + s.LoadTestKeys(kDuration, 0, 0); + + // Select the key (from FillSimpleMessage) + vector keyId = wvcdm::a2b_hex("000000000000000000000000"); + sts = OEMCrypto_SelectKey(s.session_id(), &keyId[0], keyId.size()); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + // Set up our expected input and output + // WHAT: Dummy encrypted data. + vector encryptedData = wvcdm::a2b_hex( + "ec261c115f9d5cda1d5cc7d33c4e37362d1397c89efdd1da5f0065c4848b0462" + "337ba14693735203c9b4184e362439c0cea5e5d1a628425eddf8a6bf9ba901ca" + "46f5a9fd973cffbbe3c276af9919e2e8f6f3f420538b7a0d6dc41487874d96b8" + "efaedb45a689b91beb8c20d36140ad467d9d620b19a5fc6f223b57e0e6a7f913" + "00fd899e5e1b89963e83067ca0912aa5b79df683e2530b55a9645be341bc5f07" + "cffc724790af635c959e2644e51ba7f23bae710eb55a1f2f4e060c3c1dd1387c" + "74415dc880492dd1d5b9ecf3f01de48a44baeb4d3ea5cc4f8d561d0865afcabb" + "fc14a9ab9647e6e31adabb72d792f0c9ba99dc3e9205657d28fc7771d64e6d4b"); + vector encryptionIv = wvcdm::a2b_hex( + "719dbcb253b2ec702bb8c1b1bc2f3bc6"); + // WHAT: Expected decrypted data. + vector unencryptedData = wvcdm::a2b_hex( + "19ef4361e16e6825b336e2012ad8ffc9ce176ab2256e1b98aa15b7877bd8c626" + "fa40b2e88373457cbcf4f1b4b9793434a8ac03a708f85974cff01bddcbdd7a8e" + "e33fd160c1d5573bfd8104efd23237edcf28205c3673920553f8dd5e916604b0" + "1082345181dceeae5ea39d829c7f49e1850c460645de33c288723b7ae3d91a17" + "a3f04195cd1945ba7b0f37fef7e82368be30f04365d877766f6d56f67d22a244" + "ef2596d3053f657c1b5d90b64e11797edf1c198a23a7bfc20e4d44c74ae41280" + "a8317f443255f4020eda850ff0954e308f53a634cbce799ae58911bc59ccd6a5" + "de2ac53ee0fa7ea15fc692cc892acc0090865dc57becacddf362a092dfd3040b"); + + // Describe the output + uint8_t outputBuffer[256]; + OEMCrypto_DestBufferDesc destBuffer; + destBuffer.type = OEMCrypto_BufferType_Clear; + destBuffer.buffer.clear.address = outputBuffer; + destBuffer.buffer.clear.max_length = sizeof(outputBuffer); + + // Decrypt the data + sts = OEMCrypto_DecryptCTR( + s.session_id(), &encryptedData[0], encryptedData.size(), true, + &encryptionIv[0], 0, &destBuffer, + OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer, + unencryptedData.size())); + + s.close(); + testTearDown(); +} + +class DISABLED_GenericDRMTest : public DISABLED_TestKeybox { + protected: + MessageData message_data_; + + static const size_t kBufferSize = 160; // multiple of encryption block size. + uint8_t clear_buffer_[kBufferSize]; + uint8_t encrypted_buffer_[kBufferSize]; + uint8_t iv_[wvcdm::KEY_IV_SIZE]; + + + void MakeFourKeys(Session* s) { + s->FillSimpleMessage(&message_data_, kDuration, 0, 0); + message_data_.keys[0].control.control_bits = + htonl(wvoec_mock::kControlAllowEncrypt); + message_data_.keys[1].control.control_bits = + htonl(wvoec_mock::kControlAllowDecrypt); + message_data_.keys[2].control.control_bits = + htonl(wvoec_mock::kControlAllowSign); + message_data_.keys[3].control.control_bits = + htonl(wvoec_mock::kControlAllowVerify); + + message_data_.keys[2].key_data_length = wvcdm::MAC_KEY_SIZE; + message_data_.keys[3].key_data_length = wvcdm::MAC_KEY_SIZE; + + for(size_t i=0; i < kBufferSize; i++) { + clear_buffer_[i] = 1 + i %250; + } + for(size_t i=0; i < wvcdm::KEY_IV_SIZE; i++) { + iv_[i] = i; + } + + } + + void LoadFourKeys(Session* s) { + MessageData encrypted; + s->EncryptMessage(message_data_, &encrypted); + std::vector signature; + s->ServerSignMessage(encrypted, &signature); + OEMCrypto_KeyObject key_array[kNumKeys]; + const uint8_t* message_ptr = reinterpret_cast(&encrypted); + s->FillKeyArray(encrypted, key_array); + + OEMCryptoResult sts = OEMCrypto_LoadKeys(s->session_id(), + message_ptr, sizeof(encrypted), + &signature[0], signature.size(), + encrypted.mac_key_iv, + encrypted.mac_keys, + kNumKeys, key_array); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + } + + void EncryptBuffer(unsigned int key_index, const uint8_t* in_buffer, + uint8_t *out_buffer) { + AES_KEY aes_key; + ASSERT_EQ(0, AES_set_encrypt_key(message_data_.keys[key_index].key_data, + AES_BLOCK_SIZE * 8, &aes_key)); + uint8_t iv_buffer[wvcdm::KEY_IV_SIZE]; + memcpy(iv_buffer, iv_, wvcdm::KEY_IV_SIZE); + AES_cbc_encrypt(in_buffer, out_buffer, kBufferSize, + &aes_key, iv_buffer, AES_ENCRYPT); + } + + // Sign the buffer with the specified key. + void SignBuffer(unsigned int key_index, const uint8_t* in_buffer, + uint8_t signature[SHA256_DIGEST_LENGTH]) { + unsigned int md_len = SHA256_DIGEST_LENGTH; + HMAC(EVP_sha256(), message_data_.keys[key_index].key_data, + SHA256_DIGEST_LENGTH, in_buffer, kBufferSize, signature, &md_len); + } + + void BadEncrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t buffer_length) { + OEMCryptoResult sts; + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + uint8_t expected_encrypted[kBufferSize]; + EncryptBuffer(key_index, clear_buffer_, expected_encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + uint8_t encrypted[kBufferSize]; + sts = OEMCrypto_Generic_Encrypt(s.session_id(), clear_buffer_, + buffer_length, iv_, + algorithm, encrypted); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(encrypted, expected_encrypted, buffer_length)); + s.close(); + } + + void BadDecrypt(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t buffer_length) { + OEMCryptoResult sts; + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + uint8_t encrypted[kBufferSize]; + EncryptBuffer(key_index, clear_buffer_, encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + uint8_t resultant[kBufferSize]; + sts = OEMCrypto_Generic_Decrypt(s.session_id(), encrypted, + buffer_length, iv_, + algorithm, resultant); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(clear_buffer_, resultant, buffer_length)); + s.close(); + } + + void BadSign(unsigned int key_index, OEMCrypto_Algorithm algorithm) { + OEMCryptoResult sts; + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + uint8_t expected_signature[SHA256_DIGEST_LENGTH]; + SignBuffer(key_index, clear_buffer_, expected_signature); + + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + size_t signature_length = (size_t)SHA256_DIGEST_LENGTH; + uint8_t signature[SHA256_DIGEST_LENGTH]; + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_, kBufferSize, + algorithm, signature, &signature_length); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(signature, expected_signature, SHA256_DIGEST_LENGTH)); + s.close(); + } + + void BadVerify(unsigned int key_index, OEMCrypto_Algorithm algorithm, + size_t signature_size, bool alter_data) { + OEMCryptoResult sts; + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + uint8_t signature[SHA256_DIGEST_LENGTH]; + SignBuffer(key_index, clear_buffer_, signature); + if( alter_data ) { + signature[0] = 43; + } + + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = OEMCrypto_Generic_Verify(s.session_id(), clear_buffer_, kBufferSize, + algorithm,signature, + signature_size); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + s.close(); + } +}; + +TEST_F(DISABLED_GenericDRMTest, GenericKeyLoad) { + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericKeyEncrypt) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + unsigned int key_index = 0; + uint8_t expected_encrypted[kBufferSize]; + EncryptBuffer(key_index, clear_buffer_, expected_encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + uint8_t encrypted[kBufferSize]; + sts = OEMCrypto_Generic_Encrypt(s.session_id(), clear_buffer_, + kBufferSize, iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, encrypted); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(encrypted, expected_encrypted, kBufferSize)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericKeyBadEncrypt) { + testSetUp(); + BadEncrypt(0, OEMCrypto_HMAC_SHA256, kBufferSize); + BadEncrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize-10); + BadEncrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + BadEncrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + BadEncrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericKeyDecrypt) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + unsigned int key_index = 1; + uint8_t encrypted[kBufferSize]; + EncryptBuffer(key_index, clear_buffer_, encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + uint8_t resultant[kBufferSize]; + sts = OEMCrypto_Generic_Decrypt(s.session_id(), encrypted, kBufferSize, iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(clear_buffer_, resultant, kBufferSize)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericSecureToClear) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + message_data_.keys[1].control.control_bits + |= htonl(wvoec_mock::kControlObserveDataPath + | wvoec_mock::kControlDataPathSecure); + LoadFourKeys(&s); + unsigned int key_index = 1; + uint8_t encrypted[kBufferSize]; + EncryptBuffer(key_index, clear_buffer_, encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + uint8_t resultant[kBufferSize]; + sts = OEMCrypto_Generic_Decrypt(s.session_id(), encrypted, kBufferSize, iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(clear_buffer_, resultant, kBufferSize)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericKeyBadDecrypt) { + testSetUp(); + BadDecrypt(1, OEMCrypto_HMAC_SHA256, kBufferSize); + BadDecrypt(1, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize-10); + BadDecrypt(0, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + BadDecrypt(2, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + BadDecrypt(3, OEMCrypto_AES_CBC_128_NO_PADDING, kBufferSize); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericKeySign) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + unsigned int key_index = 2; + uint8_t expected_signature[SHA256_DIGEST_LENGTH]; + SignBuffer(key_index, clear_buffer_, expected_signature); + + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + size_t gen_signature_length = 0; + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_, kBufferSize, + OEMCrypto_HMAC_SHA256, NULL, + &gen_signature_length); + ASSERT_EQ(OEMCrypto_ERROR_SHORT_BUFFER, sts); + ASSERT_EQ(static_cast(SHA256_DIGEST_LENGTH), gen_signature_length); + uint8_t signature[SHA256_DIGEST_LENGTH]; + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_, kBufferSize, + OEMCrypto_HMAC_SHA256,signature, + &gen_signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(signature, expected_signature, SHA256_DIGEST_LENGTH)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericKeyBadSign) { + testSetUp(); + BadSign(0, OEMCrypto_HMAC_SHA256); // Can't sign with encrypt key. + BadSign(1, OEMCrypto_HMAC_SHA256); // Can't sign with decrypt key. + BadSign(3, OEMCrypto_HMAC_SHA256); // Can't sign with verify key. + BadSign(2, OEMCrypto_AES_CBC_128_NO_PADDING); // Bad signing algorithm. + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericKeyVerify) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + LoadFourKeys(&s); + unsigned int key_index = 3; + uint8_t signature[SHA256_DIGEST_LENGTH]; + SignBuffer(key_index, clear_buffer_, signature); + + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = OEMCrypto_Generic_Verify(s.session_id(), clear_buffer_, kBufferSize, + OEMCrypto_HMAC_SHA256,signature, + SHA256_DIGEST_LENGTH); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, GenericKeyBadVerify) { + testSetUp(); + BadVerify(0, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); + BadVerify(1, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); + BadVerify(2, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, false); + BadVerify(3, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH, true); + BadVerify(3, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH - 1, false); + BadVerify(3, OEMCrypto_HMAC_SHA256, SHA256_DIGEST_LENGTH + 1, false); + BadVerify(3, OEMCrypto_AES_CBC_128_NO_PADDING, SHA256_DIGEST_LENGTH, false); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, KeyDurationEncrypt) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + message_data_.keys[0].control.duration = htonl(kDuration); + message_data_.keys[1].control.duration = htonl(kDuration); + message_data_.keys[2].control.duration = htonl(kDuration); + message_data_.keys[3].control.duration = htonl(kDuration); + LoadFourKeys(&s); + + uint8_t expected_encrypted[kBufferSize]; + EncryptBuffer(0, clear_buffer_, expected_encrypted); + unsigned int key_index = 0; + uint8_t encrypted[kBufferSize]; + + sleep(kShortSleep); // Should still be valid key. + + memset(encrypted, 0, kBufferSize); + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + sts = OEMCrypto_Generic_Encrypt(s.session_id(), clear_buffer_, + kBufferSize, iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, encrypted); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(encrypted, expected_encrypted, kBufferSize)); + + sleep(kLongSleep); // Should be expired key. + + memset(encrypted, 0, kBufferSize); + sts = OEMCrypto_Generic_Encrypt(s.session_id(), clear_buffer_, + kBufferSize, iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, encrypted); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(encrypted, expected_encrypted, kBufferSize)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, KeyDurationDecrypt) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + message_data_.keys[0].control.duration = htonl(kDuration); + message_data_.keys[1].control.duration = htonl(kDuration); + message_data_.keys[2].control.duration = htonl(kDuration); + message_data_.keys[3].control.duration = htonl(kDuration); + LoadFourKeys(&s); + + unsigned int key_index = 1; + uint8_t encrypted[kBufferSize]; + EncryptBuffer(key_index, clear_buffer_, encrypted); + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + uint8_t resultant[kBufferSize]; + sleep(kShortSleep); // Should still be valid key. + + memset(resultant, 0, kBufferSize); + sts = OEMCrypto_Generic_Decrypt(s.session_id(), encrypted, kBufferSize, iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(clear_buffer_, resultant, kBufferSize)); + + sleep(kLongSleep); // Should be expired key. + + memset(resultant, 0, kBufferSize); + sts = OEMCrypto_Generic_Decrypt(s.session_id(), encrypted, kBufferSize, iv_, + OEMCrypto_AES_CBC_128_NO_PADDING, resultant); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(clear_buffer_, resultant, kBufferSize)); + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, KeyDurationSign) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + + message_data_.keys[0].control.duration = htonl(kDuration); + message_data_.keys[1].control.duration = htonl(kDuration); + message_data_.keys[2].control.duration = htonl(kDuration); + message_data_.keys[3].control.duration = htonl(kDuration); + + LoadFourKeys(&s); + + unsigned int key_index = 2; + uint8_t expected_signature[SHA256_DIGEST_LENGTH]; + uint8_t signature[SHA256_DIGEST_LENGTH]; + size_t signature_length = SHA256_DIGEST_LENGTH; + SignBuffer(key_index, clear_buffer_, expected_signature); + + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + sleep(kShortSleep); // Should still be valid key. + + memset(signature, 0, SHA256_DIGEST_LENGTH); + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_, kBufferSize, + OEMCrypto_HMAC_SHA256,signature, + &signature_length); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + ASSERT_EQ(0, memcmp(signature, expected_signature, SHA256_DIGEST_LENGTH)); + + sleep(kLongSleep); // Should be expired key. + + memset(signature, 0, SHA256_DIGEST_LENGTH); + sts = OEMCrypto_Generic_Sign(s.session_id(), clear_buffer_, kBufferSize, + OEMCrypto_HMAC_SHA256,signature, + &signature_length); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + ASSERT_NE(0, memcmp(signature, expected_signature, SHA256_DIGEST_LENGTH)); + + s.close(); + testTearDown(); +} + +TEST_F(DISABLED_GenericDRMTest, KeyDurationVerify) { + OEMCryptoResult sts; + testSetUp(); + InstallKeybox(kDefaultKeybox, true); + Session& s = createSession("ONE"); + s.open(); + s.GenerateDerivedKeys(); + MakeFourKeys(&s); + message_data_.keys[0].control.duration = htonl(kDuration); + message_data_.keys[1].control.duration = htonl(kDuration); + message_data_.keys[2].control.duration = htonl(kDuration); + message_data_.keys[3].control.duration = htonl(kDuration); + LoadFourKeys(&s); + + unsigned int key_index = 3; + uint8_t signature[SHA256_DIGEST_LENGTH]; + SignBuffer(key_index, clear_buffer_, signature); + + sts = OEMCrypto_SelectKey(s.session_id(), + message_data_.keys[key_index].key_id, + kTestKeyIdLength); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + sleep(kShortSleep); // Should still be valid key. + + sts = OEMCrypto_Generic_Verify(s.session_id(), clear_buffer_, kBufferSize, + OEMCrypto_HMAC_SHA256,signature, + SHA256_DIGEST_LENGTH); + ASSERT_EQ(OEMCrypto_SUCCESS, sts); + + sleep(kLongSleep); // Should be expired key. + + sts = OEMCrypto_Generic_Verify(s.session_id(), clear_buffer_, kBufferSize, + OEMCrypto_HMAC_SHA256,signature, + SHA256_DIGEST_LENGTH); + ASSERT_NE(OEMCrypto_SUCCESS, sts); + + s.close(); + testTearDown(); +} + +} // namespace wvoec diff --git a/run_test.sh b/run_test.sh new file mode 100755 index 00000000..4a1202b9 --- /dev/null +++ b/run_test.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -ex +./build/build.py x86-64 -r +./out/x86-64/Release/wvcdm_shared_api_unittest