Source release v3.0.0-0-g8d3792b-ce + third_party

Change-Id: I399e71ddfffcd436171d1c60283c63ab4658e0b1
This commit is contained in:
Joey Parrish
2015-06-19 15:13:34 -07:00
parent 58aba6b2ec
commit 0546ee6732
965 changed files with 426663 additions and 12897 deletions

15
.gitignore vendored
View File

@@ -1,13 +1,16 @@
# GYP generated makefiles
_auto_*
# GYP-generated files.
Makefile
*.Makefile
*.mk
*.xcodeproj
# Output directory
# Output directory.
out/
# Certificate created by unit tests
cert.bin
# Python object files
# Python object files.
*.pyc
# Ignoring backup files.
*~

252
README
View File

@@ -1,252 +0,0 @@
README for Widevine CDM Partner Kit v2.2
Date: 12/16/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_<kit-version>.tar.xz
cd widevine-cdm_<kit-version>
The remaining instructions will refer to this directory as <cdm-kit-dir>.
The top level directories and files of the kit are as follows:
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
platforms - platform-specific build configs
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.1g)
- 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 <gyp-kit-root>
cd <gyp-kit-root>
# 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 <setuptools-temp>
cd <setuptools-temp>
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 <protobuf-kit-root>
cd <protobuf-kit-root>
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 <gmock_dir>
cd <gmock_dir>
wget https://googlemock.googlecode.com/files/gmock-1.6.0.zip
unzip gmock-1.6.0.zip
cd <cdm-kit-dir>
mkdir third_party
cd third_party
ln -sf <gmock_dir>/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 <stringencoders-dir>
cd <stringencoders-dir>
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 <cdm-kit-dir>/third_party/stringencoders/src
cp modp_b64w_data.h <cdm-kit-dir>/third_party/stringencoders/src
cp src/modp_b64w.c \
<cdm-kit-dir>/third_party/stringencoders/src/modp_b64w.cpp
cp src/modp_b64w.h <cdm-kit-dir>/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
<cdm-kit-dir>/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.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.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
cdm/cdm_unittests.gyp
wvcdm_shared_api_unittest Build CDM unit tests
third_party/gmock.gyp
gmock Provides gmock modules for unit tests
gmock_main Provides main routine for unit tests
gtest Provides gtest modules for unit tests
oemcrypto/oemcrypto.gyp
oec_lib Build 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.

BIN
README.pdf Normal file

Binary file not shown.

View File

@@ -1,143 +0,0 @@
README.upgrading for Widevine CDM Partner Kit v2.2
Date: 12/16/2014
This document provides details on important changes between versions of the
Widevine CDM. Some upgrades require you to make changes to your application,
so please read carefully.
NOTE: All gyp variables have default values in platforms/global_config.gypi.
You may override these defaults in your platform-specific gypi.
New in v2.2.0:
=====
New gyp variables have been introduced to make the build more configurable:
* privacy_crypto_impl
This replaces the variable 'disable_privacy_crypto'. To achieve the same
effect, set the value to 'dummy'. Defaults to 'openssl'.
ContentDecryptionModule_1::GenerateKeyRequest did not previously require the
'type' parameter to be supplied. This is now required, and must be set to the
content's MIME type.
ContentDecryptionModule_1::Decrypt* previously required subsamples. This is no
longer required. WebM content, for example, does not contain subsamples.
ContentDecryptionModule_1::Decrypt* did not previously set the size of a
decrypted Buffer. This has been fixed.
New CDM/Host interfaces, CDM_4/Host_4, have been introduced. CDM_1/Host_1 will
continue to be supported until the end of Q1, 2015. The new interface includes
many fixes and improvements, updates to reflect the evolving EME standard, and
the introduction of offline playback APIs.
New in v2.1.6:
=====
The following methods have been removed from the CDM interface (class
ContentDecryptionModule):
* CancelKeyRequest
The following methods have been added to the CDM interface (class
ContentDecryptionModule):
* CloseSession
Previous versions of the CDM did not dispatch key errors (Host::SendKeyError)
on failure. This has been fixed. If you added workarounds to your Host,
please remove them to avoid duplicate key errors.
New in v2.1.5:
=====
New gyp variables have been introduced to make the build more configurable:
* protobuf_lib_type and protobuf_lib
These, along with 'protoc_dir', form a new protobuf configuration model
and replace the variables 'protobuf_source_dir', 'use_system_protobuf',
and 'protobuf_gyp'. For details, see platforms/global_config.gypi.
New in v2.1.3:
=====
New gyp variables have been introduced to make the build more configurable:
* oemcrypto_v8
The current version of OEMCrypto is v9. Set to 'true' if your system
supplies OEMCrypto v8. Defaults to 'false'.
The CDM interface (class ContentDecryptionModule) has been simplified.
The following methods have been removed:
* InitializeAudioDecoder
* InitializeVideoDecoder
* DeinitializeDecoder
* ResetDecoder
* DecryptAndDecodeFrame
* DecryptAndDecodeSamples
The Host interface (class Host) has been simplified.
The following methods have been removed:
* GetPrivateData
* PersistPlatformString
* GetPlatformByteArray
* SetPlatformByteArray
* PersistPlatformByteArray
The following Host methods have changed:
* GetPlatformString
Now does the job of GetPlatformByteArray as well.
* SetPlatformString
Now does the job of SetPlatformByteArray, PersistPlatformString, and
PersistPlatformByteArray as well.
* SetTimer
This should schedule a single callback, not a repeating timer. Multiple
timer callbacks may be scheduled by the CDM at once. All callbacks should
occur serially on the same thread/queue as all other calls into the CDM.
New in v2.1.2:
=====
The following file locations have changed:
./cdm/cdm_api_internal.gyp => ./cdm/cdm.gyp
./cdm/cdm_api_external.gyp => ./cdm/cdm_unittests.gyp
If you have your own gyp files which depend on CDM targets, please update them
accordingly.
./build.py will use the ninja build tool if available. Otherwise, it will
continue to use make.
The Host is no longer expected to allocate a Buffer before calling
ContentDecryptionModule::Decrypt. The CDM will now call Host::Allocate and
DecryptedBlock::SetBuffer as needed in its Decrypt method. To avoid the
potential for a memory leak, make sure that your Host implementation no
longer allocates a Buffer before calling Decrypt.
For more details on the Host interface, see "Widevine Security Integration
Guide for CENC: EME Supplement".
New in v2.1.1:
=====
The following file locations have changed:
./build/build.py => ./build.py
./build/platforms/ => ./platforms/
./build/global_config.gypi => ./platforms/global_config.gypi
./build/protoc.gypi => ./third_party/protoc.gypi
The file ./build/platforms/cdm_platforms.py no longer exists. Platform
definitions are now based on the folder structure in ./platforms rather than
a python-based config file.
The OEMCrypto interface has been updated to v9, and documentation has been
updated in oemcrypto/include. The following functions were added:
* OEMCrypto_GetHDCPCapability
* OEMCrypto_SupportsUsageTable
* OEMCrypto_UpdateUsageTable
* OEMCrypto_DeactivateUsageEntry
* OEMCrypto_ReportUsage
* OEMCrypto_DeleteUsageEntry
* OEMCrypto_DeleteUsageTable
The file ./platforms/x86-64/x86-64.gypi now contains examples of how settings
can be customized for a given platform.

View File

@@ -3,14 +3,17 @@
"""Generates build files and builds the CDM source release."""
import argparse
import imp
import os
import subprocess
import sys
import gyp
cdm_top = os.path.abspath(os.path.dirname(__file__))
# If gyp has been installed locally in third_party, this will find it.
# Irrelevant if gyp has been installed globally.
sys.path.insert(1, os.path.join(cdm_top, 'third_party'))
import gyp
def IsNinjaInstalled():
"""Determine if ninja is installed."""
@@ -74,16 +77,14 @@ def ImportPlatform(name, gyp_args):
platforms_path = os.path.join(cdm_top, 'platforms')
target_path = os.path.join(platforms_path, name)
platform_gypi_path = os.path.join(target_path, name + '.gypi')
global_gypi_path = os.path.join(platforms_path, 'global_config.gypi')
platform_gypi_path = os.path.join(target_path, 'settings.gypi')
output_path = os.path.join(cdm_top, 'out', name)
gyp_args.append('--include=%s' % platform_gypi_path)
gyp_args.append('--include=%s' % global_gypi_path)
gyp_args.append('-Goutput_dir=%s' % output_path)
sys.path.insert(0, target_path)
target = __import__(name)
platform_environment_path = os.path.join(target_path, 'environment.py')
target = imp.load_source('environment', platform_environment_path)
if hasattr(target, 'export_variables'):
for k, v in target.export_variables.iteritems():

View File

@@ -1,14 +1,49 @@
# Copyright 2013 Google Inc. All Rights Reserved.
# Copyright 2015 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.
{
'variables': {
# Override if you intend to link against a different OEMCrypto API version.
'oemcrypto_version%': 10,
# Override if you can't depend on OpenSSL for privacy features.
# If set to 'dummy', privacy mode in the CDM will fail.
'privacy_crypto_impl%': 'openssl',
# There are three protobuf configurations:
#
# 1) protobuf_config == 'system'
# Use a system-wide installation of protobuf.
# Specify the protobuf library in protobuf_lib.
# Specify the path to protoc in protoc_bin.
#
# 2) protobuf_config == 'target'
# Use an existing protobuf gyp target from your project.
# Specify the protobuf gyp file and target in protobuf_lib_target.
# Specify the protoc gyp file and target in protoc_host_target.
# Specify the path to protoc in protoc_bin.
#
# 3) protobuf_config == 'source' (default)
# Build protobuf and protoc from source.
# Specify the path to the protobuf source in protobuf_source.
# Make sure that a valid config.h for your target is in the source tree.
'protobuf_config%': 'source',
'protobuf_source%': '../third_party/protobuf',
}, # variables
'conditions': [
['protobuf_config=="source"', {
# Include protobuf targets used by protobuf_config=='source'
'includes': ['../third_party/protobuf.gypi'],
}],
], # conditions
'targets': [
{
'target_name': 'license_protocol',
'type': 'static_library',
'standalone_static_library': 1,
'sources': ['../core/src/license_protocol.proto',],
'variables': {
'proto_in_dir': '../core/src',
@@ -18,6 +53,7 @@
{
'target_name': 'device_files',
'type': 'static_library',
'standalone_static_library': 1,
'sources': ['../core/src/device_files.proto',],
'variables': {
'proto_in_dir': '../core/src',
@@ -25,54 +61,49 @@
'includes': ['../third_party/protoc.gypi'],
},
{
'target_name': 'wvcdm_sysdep',
'target_name': 'widevine_cdm_core',
'type': 'static_library',
'defines': ['CDM_IMPLEMENTATION'],
'include_dirs': [
'../cdm/include',
'../core/include',
'../linux/include',
'../third_party/stringencoders/src',
],
'sources': [
'../cdm/src/host_4_file_io_client.cpp',
'../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_static',
'type': 'static_library',
'defines': ['CDM_IMPLEMENTATION'],
'standalone_static_library': 1,
'dependencies': [
'device_files',
'license_protocol',
'wvcdm_sysdep',
'<(oemcrypto_target)',
],
# Without this, library deps do not propagate from the protocol targets
# up to the shared lib or executable above.
'export_dependent_settings': [
'device_files',
'license_protocol',
],
'include_dirs': [
'../cdm/include',
'../core/include',
'../linux/include',
'../oemcrypto/include',
'../third_party/stringencoders/src',
],
'direct_dependent_settings': {
'include_dirs': [
'../core/include',
'../oemcrypto/include',
],
},
'sources': [
# uses common published api
'../cdm/src/cdm_library_init.cpp',
'../cdm/src/clock.cpp',
'../cdm/src/host_event_listener.cpp',
'../cdm/src/wv_content_decryption_module_1.cpp',
'../cdm/src/wv_content_decryption_module_4.cpp',
'../core/include/buffer_reader.h',
'../core/include/cdm_client_property_set.h',
'../core/include/cdm_engine.h',
'../core/include/cdm_session.h',
'../core/include/certificate_provisioning.h',
'../core/include/clock.h',
'../core/include/crypto_key.h',
'../core/include/crypto_session.h',
'../core/include/device_files.h',
'../core/include/file_store.h',
'../core/include/initialization_data.h',
'../core/include/license.h',
'../core/include/lock.h',
'../core/include/log.h',
'../core/include/max_res_engine.h',
'../core/include/oemcrypto_adapter.h',
'../core/include/policy_engine.h',
'../core/include/privacy_crypto.h',
'../core/include/properties.h',
'../core/include/scoped_ptr.h',
'../core/include/string_conversions.h',
'../core/include/wv_cdm_constants.h',
'../core/include/wv_cdm_event_listener.h',
'../core/include/wv_cdm_types.h',
'../core/src/buffer_reader.cpp',
'../core/src/cdm_engine.cpp',
'../core/src/cdm_session.cpp',
@@ -81,32 +112,80 @@
'../core/src/device_files.cpp',
'../core/src/initialization_data.cpp',
'../core/src/license.cpp',
'../core/src/max_res_engine.cpp',
'../core/src/oemcrypto_adapter_static.cpp',
'../core/src/policy_engine.cpp',
'../core/src/privacy_crypto_<(privacy_crypto_impl).cpp',
'../core/src/properties.cpp',
'../core/src/string_conversions.cpp',
'../third_party/stringencoders/src/modp_b64w_data.h',
'../third_party/stringencoders/src/modp_b64w.cpp',
'../third_party/stringencoders/src/modp_b64w.h',
],
'conditions': [
['oemcrypto_v8=="true"', {
'sources!': [ # exclude
'../core/src/oemcrypto_adapter_static.cpp',
['oemcrypto_version < 9', {
'sources': [
# Include APIs introduced in v9.
'../core/src/oemcrypto_adapter_static_v9.cpp',
],
'sources': [ # include
'../core/src/oemcrypto_adapter_static_v8.cpp',
}],
['oemcrypto_version < 10', {
'sources': [
# Include APIs introduced in v10.
'../core/src/oemcrypto_adapter_static_v10.cpp',
],
}],
],
},
{
'target_name': 'wvcdm_shared',
'type': 'shared_library',
'target_name': 'widevine_ce_cdm_static',
'type': 'static_library',
'standalone_static_library': 1,
'dependencies': [
'wvcdm_static',
'widevine_cdm_core',
'device_files',
'license_protocol',
],
# Without this, library deps do not propagate from the protocol targets
# up to the shared lib or executable above.
'export_dependent_settings': [
'device_files',
'license_protocol',
],
'defines': ['CDM_IMPLEMENTATION'],
'include_dirs': [
'include',
],
'direct_dependent_settings': {
'include_dirs': [
'include',
],
'libraries': [
'-lpthread',
],
},
'sources': [
'include/cdm.h',
'include/cdm_version.h',
'include/override.h',
'include/properties_ce.h',
'src/cdm.cpp',
'src/lock.cpp',
'src/log.cpp',
'src/properties_ce.cpp',
],
},
{
'target_name': 'dummy',
'type': 'none',
}
'target_name': 'widevine_ce_cdm_shared',
'type': 'shared_library',
'dependencies': [
'widevine_ce_cdm_static',
],
'direct_dependent_settings': {
'include_dirs': [
'include',
],
},
},
],
}

View File

@@ -1,51 +1,50 @@
# Copyright 2013 Google Inc. All Rights Reserved.
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Builds under the CDM ./build.py (target platform) build system
# Refer to the distribution package's README for details.
#
{
'variables': {
'oemcrypto_lib%': '',
},
'targets': [
{
'target_name': 'wvcdm_shared_api_unittest',
'target_name': 'widevine_ce_cdm_unittest',
'type': 'executable',
'sources': [
'../cdm/test/cdm_api_1_test.cpp',
'../cdm/test/cdm_api_4_test.cpp',
'../cdm/test/cdm_test_main.cpp',
'../cdm/test/device_cert.cpp',
'../cdm/test/device_cert.h',
'../cdm/test/test_host_1.cpp',
'../cdm/test/test_host_4.cpp',
'../cdm/test/test_host_4_file_io.cpp',
'../cdm/test/test_util.cpp',
'../core/test/config_test_env.cpp',
'../core/test/license_request.cpp',
'../core/test/http_socket.cpp',
'../core/test/test_printers.cpp',
'../core/test/url_request.cpp',
# The test runner and the testing device certificate.
'test/cdm_test_main.cpp',
'test/device_cert.cpp',
'test/device_cert.h',
# The test host, which is required for all test suites on CE.
'test/test_host.cpp',
'test/test_host.h',
],
'include_dirs': [
'include',
'../cdm/include',
'../core/include',
'../core/test',
'../oemcrypto/include',
],
'defines': [
'UNIT_TEST',
'includes': [
'oemcrypto_unittests.gypi',
'core_unittests.gypi',
'cdm_unittests.gypi',
],
'libraries': [
'-lssl',
'-lcrypto',
'-lpthread',
'-lcrypto', # oec_mock
'-lssl', # oec_mock
'-lpthread', # gtest
],
'dependencies': [
'cdm.gyp:wvcdm_shared',
'cdm.gyp:widevine_ce_cdm_shared',
'../third_party/gmock.gyp:gmock',
'../third_party/gmock.gyp:gmock_main',
'../third_party/gmock.gyp:gtest',
],
'conditions': [
['oemcrypto_lib==""', {
'dependencies': [
'../oemcrypto/mock/oec_mock.gyp:oec_mock',
],
}, {
'libraries': [
'<(oemcrypto_lib)',
],
}],
],
},
],
}

24
cdm/cdm_unittests.gypi Normal file
View File

@@ -0,0 +1,24 @@
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Include this in any custom unit test targets.
# Does not include the device certificate or the test runner main.
{
'sources': [
'../core/test/http_socket.cpp',
'../core/test/license_request.cpp',
'../core/test/url_request.cpp',
'test/cdm_test.cpp',
'test/cdm_test_printers.cpp',
'test/cdm_test_printers.h',
'test/test_host.cpp',
'test/test_host.h',
],
'include_dirs': [
'../core/test',
],
'defines': [
'UNIT_TEST',
'CDM_TESTS',
],
}

32
cdm/core_unittests.gypi Normal file
View File

@@ -0,0 +1,32 @@
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Include this in any custom unit test targets.
# Does not include the test runner main.
{
'sources': [
'../core/test/base64_test.cpp',
'../core/test/cdm_engine_test.cpp',
'../core/test/cdm_session_unittest.cpp',
'../core/test/config_test_env.cpp',
'../core/test/device_files_unittest.cpp',
'../core/test/http_socket.cpp',
'../core/test/initialization_data_unittest.cpp',
'../core/test/license_request.cpp',
'../core/test/license_unittest.cpp',
'../core/test/max_res_engine_unittest.cpp',
'../core/test/policy_engine_unittest.cpp',
'../core/test/test_printers.cpp',
'../core/test/url_request.cpp',
],
'include_dirs': [
'../core/include',
'../core/test',
],
'defines': [
'UNIT_TEST',
'CORE_TESTS',
],
'dependencies': [
'cdm.gyp:license_protocol',
],
}

434
cdm/include/cdm.h Normal file
View File

@@ -0,0 +1,434 @@
// Copyright 2015 Google Inc. All Rights Reserved.
// Based on the EME draft spec from 2015 June 01.
// https://rawgit.com/w3c/encrypted-media/1cbedad/index.html
// TODO: Verify behavior and update to June 12 draft.
#ifndef WVCDM_CDM_CDM_H_
#define WVCDM_CDM_CDM_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 <stdint.h>
#endif
#include <map>
#include <string>
// Define CDM_EXPORT to export functionality across shared library boundaries.
#if defined(WIN32)
# if defined(CDM_IMPLEMENTATION)
# define CDM_EXPORT __declspec(dllexport)
# else
# define CDM_EXPORT __declspec(dllimport)
# endif // defined(CDM_IMPLEMENTATION)
#else // defined(WIN32)
# if defined(CDM_IMPLEMENTATION)
# define CDM_EXPORT __attribute__((visibility("default")))
# else
# define CDM_EXPORT
# endif
#endif // defined(WIN32)
namespace widevine {
class CDM_EXPORT Cdm {
public:
// Session types defined by EME.
typedef enum {
kTemporary = 0,
kPersistent = 1,
} SessionType;
// Message types defined by EME.
typedef enum {
kLicenseRequest = 0,
kLicenseRenewal = 1,
kLicenseRelease = 2,
kIndividualizationRequest = 3,
} MessageType;
typedef enum {
// These are defined by Widevine:
kSuccess = 0,
kNeedsDeviceCertificate = 1,
kSessionNotFound = 2,
kDecryptError = 3,
kNoKey = 4,
// These are analogous to the errors used by EME:
kInvalidAccess = 5,
kNotSupported = 6,
kInvalidState = 7,
kQuotaExceeded = 8,
// This covers errors that we do not expect (see logs for details):
kUnexpectedError = 99999,
} Status;
// These are the init data types defined by EME.
typedef enum {
kCenc = 0,
kKeyIds = 1, // NOTE: not supported by Widevine at this time
kWebM = 2,
} InitDataType;
// These are key statuses defined by EME.
typedef enum {
kUsable = 0,
kExpired = 1,
kOutputNotAllowed = 2,
kStatusPending = 3,
kInternalError = 4,
} KeyStatus;
// These are defined by Widevine. The CDM can be configured to decrypt in
// three modes (dependent on OEMCrypto support).
typedef enum {
// Data is decrypted to an opaque handle.
// Translates to OEMCrypto's OEMCrypto_BufferType_Secure.
kOpaqueHandle = 0,
// Decrypted data never returned to the caller, but is decoded and rendered
// by OEMCrypto.
// Translates to OEMCrypto's OEMCrypto_BufferType_Direct.
kDirectRender = 1,
// There is no secure output available, so all data is decrypted into a
// clear buffer in main memory.
// Translates to OEMCrypto's OEMCrypto_BufferType_Clear.
kNoSecureOutput = 2,
} SecureOutputType;
// Logging levels defined by Widevine.
// See Cdm::initialize().
typedef enum {
kSilent = -1,
kErrors = 0,
kWarnings = 1,
kInfo = 2,
kDebug = 3,
kVerbose = 4,
} LogLevel;
// A map of key statuses.
// See Cdm::getKeyStatuses().
typedef std::map<std::string, KeyStatus> KeyStatusMap;
// An event listener interface provided by the application and attached to
// each CDM session.
// See Cdm::createSession().
class IEventListener {
public:
// A message (license request, renewal, etc.) to be dispatched to the
// application's license server.
// The response, if successful, should be provided back to the CDM via a
// call to Cdm::update().
virtual void onMessage(const std::string& session_id,
MessageType message_type,
const std::string& message) = 0;
// There has been a change in the keys in the session or their status.
virtual void onKeyStatusesChange(const std::string& session_id) = 0;
// A remove() operation has been completed.
virtual void onRemoveComplete(const std::string& session_id) = 0;
protected:
IEventListener() {}
virtual ~IEventListener() {}
};
// A storage interface provided by the application, independent of CDM
// instances.
// See Cdm::initialize().
// NOTE: It is important for users of your application to be able to clear
// stored data. Also, browsers or other multi-application systems should
// store data separately per-app or per-origin.
// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo.
class IStorage {
public:
virtual bool read(const std::string& name,
std::string* data) = 0;
virtual bool write(const std::string& name,
const std::string& data) = 0;
virtual bool exists(const std::string& name) = 0;
virtual bool remove(const std::string& name) = 0;
virtual int32_t size(const std::string& name) = 0;
protected:
IStorage() {}
virtual ~IStorage() {}
};
// A clock interface provided by the application, independent of CDM
// instances.
// See Cdm::initialize().
class IClock {
public:
// Returns the current time in milliseconds since 1970 UTC.
virtual int64_t now() = 0;
protected:
IClock() {}
virtual ~IClock() {}
};
// A timer interface provided by the application, independent of CDM
// instances.
// See Cdm::initialize().
class ITimer {
public:
class IClient {
public:
// Called by ITimer when a timer expires.
virtual void onTimerExpired(void* context) = 0;
protected:
IClient() {}
virtual ~IClient() {}
};
// Call |client->onTimerExpired(context)| after a delay of |delay_ms| ms.
virtual void setTimeout(int64_t delay_ms,
IClient* client,
void* context) = 0;
protected:
ITimer() {}
virtual ~ITimer() {}
};
// Client information, provided by the application, independent of CDM
// instances.
// See Cdm::initialize().
// These parameters end up as client indentification in license requests.
// All fields may be used by a license server proxy to drive business logic.
// Some fields are required (indicated below), but please fill out as many
// as make sense for your application.
// No user-identifying information may be put in these fields!
struct ClientInfo {
// The name of the product or application, e.g. "TurtleTube"
// Required.
std::string product_name;
// The name of the company who makes the device, e.g. "Kubrick, Inc."
// Required.
std::string company_name;
// The name of the device, e.g. "HAL"
std::string device_name;
// The device model, e.g. "HAL 9000"
// Required.
std::string model_name;
// The architecture of the device, e.g. "x86-64"
std::string arch_name;
// Information about the build of the browser, application, or platform into
// which the CDM is integrated, e.g. "v2.71828, 2038-01-19-03:14:07"
std::string build_info;
};
// Device certificate request information.
// The structure is passed by the application to the library in as an output
// parameter to Cdm::initialize().
// All fields are filled in by the library to instruct the application to
// handle device certificate requests, if needed.
struct DeviceCertificateRequest {
// If false, the library is ready to create and/or load sessions.
// If true, a device certificate is needed first.
// Sessions cannot be created or loaded until the device certificate has
// been provisioned.
bool needed;
// If |needed| is true, this string contains the URL that must be used to
// provision a device certificate. The request must be a POST.
std::string url;
// If |needed| is true, the response from the above-described HTTP POST
// must be provided as an argument to this method.
// Returns kSuccess if the provisioning was successful.
// Any other return value means the provisioning failed and the CDM cannot
// be used yet.
Status acceptReply(const std::string& reply);
};
// Initialize the CDM library and provide access to platform services.
// All platform interfaces are required.
// The |device_certificate_request| parameter will be filled in by
// initialize().
// See documentation for DeviceCertificateRequest for more information.
// Logging is controlled by |verbosity|.
// Must be called and must return kSuccess before create() is called.
static Status initialize(
SecureOutputType secure_output_type,
const ClientInfo& client_info,
IStorage* storage,
IClock* clock,
ITimer* timer,
DeviceCertificateRequest* device_certificate_request,
LogLevel verbosity);
// Query the CDM library version.
static const char* version();
// Constructs a new CDM instance.
// initialize() must be called first and must return kSuccess before a CDM
// instance may be constructed.
// The CDM may notify of events at any time via the provided |listener|,
// which may not be NULL.
// If |privacy_mode| is true, server certificates are required and will be
// used to encrypt messages to the license server.
// By using server certificates to encrypt communication with the license
// server, device-identifying information cannot be extracted from the
// license exchange process by an intermediate layer between the CDM and
// the server.
// This is particularly useful for browser environments, but is recommended
// for use whenever possible.
static Cdm* create(IEventListener* listener,
bool privacy_mode);
virtual ~Cdm() {}
// Provides a server certificate to be used to encrypt messages to the
// license server.
// If |privacy_mode| was true in create() and setServerCertificate() is not
// called, the CDM will attempt to provision a server certificate through
// IEventListener::onMessage() with messageType == kIndividualizationRequest.
// May not be called if |privacy_mode| was false.
virtual Status setServerCertificate(const std::string& certificate) = 0;
// Creates a new session.
// Do not use this to load an existing persistent session.
// If successful, the session_id is returned via |sessionId|.
virtual Status createSession(SessionType session_type,
std::string* session_id) = 0;
// Generates a request based on the initData.
// The request will be provided via a synchronous call to
// IEventListener::onMessage().
// This is done so that license requests and renewals follow the same flow.
virtual Status generateRequest(const std::string& session_id,
InitDataType init_data_type,
const std::string& init_data) = 0;
// Loads an existing persisted session from storage.
virtual Status load(const std::string& session_id) = 0;
// Provides messages, including licenses, to the CDM.
// If the message is a successful response to a release message, stored
// session data will be removed for the session.
virtual Status update(const std::string& session_id,
const std::string& response) = 0;
// The time, in milliseconds since 1970 UTC, after which the key(s) in the
// session will no longer be usable to decrypt media data, or -1 if no such
// time exists.
virtual Status getExpiration(const std::string& session_id,
int64_t* expiration) = 0;
// A map of known key IDs to the current status of the associated key.
virtual Status getKeyStatuses(const std::string& session_id,
KeyStatusMap* key_statuses) = 0;
// Indicates that the application no longer needs the session and the CDM
// should release any resources associated with it and close it.
// Does not generate release messages for persistent sessions.
// Does not remove stored session data for persistent sessions.
virtual Status close(const std::string& session_id) = 0;
// Removes stored session data associated with the session.
// The session must be loaded before it can be removed.
// Generates release messages, which must be delivered to the license server.
// A reply from the license server must be provided via update() before the
// session is fully removed.
virtual Status remove(const std::string& session_id) = 0;
struct InputBuffer {
public:
InputBuffer()
: key_id(NULL),
key_id_length(0),
iv(NULL),
iv_length(0),
data(NULL),
data_length(0),
block_offset(0),
is_encrypted(true),
is_video(true),
first_subsample(true),
last_subsample(true) {}
const uint8_t* key_id;
uint32_t key_id_length;
// The IV is expected to be 16 bytes.
const uint8_t* iv;
uint32_t iv_length;
const uint8_t* data;
uint32_t data_length;
// |data|'s offset within its 16-byte AES block, used for CENC subsamples.
// Should start at 0 for each sample, then go up by |data_length| (mod 16)
// after the |is_encrypted| part of each subsample.
uint32_t block_offset;
// If false, copies the input data directly to the output buffer. Used for
// secure output types, where the output buffer cannot be directly accessed
// above the CDM.
bool is_encrypted;
// Used by secure output type kDirectRender, where the secure hardware must
// decode and render the decrypted content:
bool is_video;
bool first_subsample;
bool last_subsample;
};
struct OutputBuffer {
OutputBuffer()
: data(NULL),
data_length(0),
data_offset(0),
is_secure(false) {}
// If |is_secure| is false or the secure output type is kNoSecureOutput,
// this is a memory address in main memory.
// If |is_secure| is true and the secure output type is kOpaqueHandle,
// this is an opaque handle.
// If |is_secure| is true and the secure output type is kDirectRender,
// this is ignored.
// See also SecureOutputType argument to initialize().
uint8_t* data;
// The maximum amount of data that can be decrypted to the buffer in this
// call, starting from |data|.
// Must be at least as large as the input buffer's |data_length|.
// This size accounts for the bytes that will be skipped by |data_offset|.
uint32_t data_length;
// An offset applied to the output address.
// Useful when |data| is an opaque handle rather than an address.
uint32_t data_offset;
// False for clear buffers, true otherwise.
// Must be false if the secure output type is kNoSecureOutput.
// See also SecureOutputType argument to initialize().
bool is_secure;
};
// Decrypt the input as described by |input| and pass the output as described
// in |output|.
virtual Status decrypt(const InputBuffer& input,
const OutputBuffer& output) = 0;
protected:
Cdm() {}
};
} // namespace widevine
#endif // WVCDM_CDM_CDM_H_

View File

@@ -1,27 +0,0 @@
// 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_

View File

@@ -1,96 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_CDM_HOST_FILE_H_
#define WVCDM_CDM_CDM_HOST_FILE_H_
#include "content_decryption_module.h"
#include "file_store.h"
#include "host_4_file_io_client.h"
#include "scoped_ptr.h"
namespace wvcdm {
class IFileFactory;
class IHostFile {
public:
virtual ~IHostFile() {}
virtual bool Open(const std::string& name) = 0;
virtual ssize_t Read(char* buffer, size_t bytes) = 0;
virtual ssize_t Write(const char* buffer, size_t bytes) = 0;
virtual bool Close() = 0;
virtual bool Remove(const std::string& name) = 0;
virtual ssize_t FileSize(const std::string& name) = 0;
};
class File1Impl : public IHostFile {
public:
explicit File1Impl(cdm::Host_1* const host_1) : host_1_(host_1) {}
virtual ~File1Impl() {}
virtual bool Open(const std::string& name) OVERRIDE;
virtual ssize_t Read(char* buffer, size_t bytes) OVERRIDE;
virtual ssize_t Write(const char* buffer, size_t bytes) OVERRIDE;
virtual bool Close() OVERRIDE;
virtual bool Remove(const std::string& name) OVERRIDE;
virtual ssize_t FileSize(const std::string& name) OVERRIDE;
private:
cdm::Host_1* const host_1_;
std::string fname_;
};
class File4Impl : public IHostFile {
public:
explicit File4Impl(cdm::Host_4* const host_4) :
host_4_(host_4), host_4_file_io_client_(host_4) {}
virtual ~File4Impl() {}
virtual bool Open(const std::string& name) OVERRIDE;
virtual ssize_t Read(char* buffer, size_t bytes) OVERRIDE;
virtual ssize_t Write(const char* buffer, size_t bytes) OVERRIDE;
virtual bool Close() OVERRIDE;
virtual bool Remove(const std::string& name) OVERRIDE;
virtual ssize_t FileSize(const std::string& name) OVERRIDE;
private:
cdm::Host_4* const host_4_;
Host4FileIOClient host_4_file_io_client_;
};
class File::Impl {
public:
explicit Impl(cdm::Host_1* const host_1) :
file_api_(new File1Impl(host_1)) {}
explicit Impl(cdm::Host_4* const host_4) :
file_api_(new File4Impl(host_4)) {}
virtual ~Impl() {}
static void RegisterFileFactory(IFileFactory* factory) { factory_ = factory; }
virtual bool Open(const std::string& name);
virtual ssize_t Read(char* buffer, size_t bytes);
virtual ssize_t Write(const char* buffer, size_t bytes);
virtual bool Close();
virtual bool Exists(const std::string& name);
virtual bool Remove(const std::string& name);
virtual ssize_t FileSize(const std::string& name);
private:
static IFileFactory* factory_;
friend class File;
scoped_ptr<IHostFile> file_api_;
};
class IFileFactory {
protected:
IFileFactory() { File::Impl::RegisterFileFactory(this); }
virtual ~IFileFactory() {}
public:
virtual File::Impl* NewFileImpl() = 0;
};
} // namespace wvcdm
#endif // WVCDM_CDM_CDM_HOST_FILE_H_

View File

@@ -1,45 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_CDM_HOST_TIMER_H_
#define WVCDM_CDM_CDM_HOST_TIMER_H_
#include <cstddef>
#include <string>
#include <vector>
#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_

View File

@@ -0,0 +1,2 @@
// Widevine CE CDM Version
#define CDM_VERSION "v3.0.0-0-g8d3792b-ce"

View File

@@ -1,654 +0,0 @@
// 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 <stdint.h>
#endif
#include <string>
#include <vector>
// 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)
// We maintain this macro for backward compatibility only.
#define INITIALIZE_CDM_MODULE InitializeCdmModule
extern "C" {
CDM_EXPORT void InitializeCdmModule();
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, uint32_t key_system_size,
GetCdmHostFunc get_cdm_host_func, void* user_data);
CDM_EXPORT const char* GetCdmVersion();
}
namespace cdm {
class Host_1;
class Host_4;
enum Status {
kSuccess = 0,
kNoKey = 2, // The required decryption key is not available.
kSessionError = 3, // Session management error.
kDecryptError = 4, // Decryption failed.
kDecodeError = 5, // Error decoding audio or video.
kRetry = 6, // Buffer temporarily cannot be accepted, delay and retry.
kNeedsDeviceCertificate = 7 // A certificate is required for licensing.
};
// This must be consistent with MediaKeyError defined in the
// Encrypted media Extensions (EME) specification: http://goo.gl/IBjNCP
enum MediaKeyError {
kUnknownError = 1,
kClientError = 2,
kOutputError = 4,
};
// The type of session to create. The valid types are defined in the spec:
// http://goo.gl/vmc3pd
enum SessionType {
kTemporary = 0,
kPersistent = 1,
kProvisioning = 2,
};
// The type of stream. Used in DecryptDecodeAndRender.
enum StreamType {
kStreamTypeAudio = 0,
kStreamTypeVideo = 1
};
// 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(uint32_t clear_bytes, uint32_t cipher_bytes)
: clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {}
uint32_t clear_bytes;
uint32_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.
uint32_t data_size; // Size (in bytes) of |data|.
uint32_t data_offset; // Number of bytes to be discarded before decryption.
const uint8_t* key_id; // Key ID to identify the decryption key.
uint32_t key_id_size; // Size (in bytes) of |key_id|.
const uint8_t* iv; // Initialization vector.
uint32_t iv_size; // Size (in bytes) of |iv|.
const struct SubsampleEntry* subsamples;
uint32_t num_subsamples; // Number of subsamples in |subsamples|.
int64_t timestamp; // Presentation timestamp in microseconds.
};
// Represents a buffer created by the Host.
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&);
};
// Represents a key-value map.
// Both created and destroyed by the Host.
// Data is filled in by the CDM.
// Need not be implemented if QueryKeyStatus() is not called.
class KeyValueMap {
public:
virtual void Set(const char* key, void* value, size_t value_size) = 0;
protected:
KeyValueMap() {}
virtual ~KeyValueMap() {}
private:
KeyValueMap(const KeyValueMap&);
void operator=(const KeyValueMap&);
};
// 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() {}
};
// The FileIO interface provides a way for the CDM to store data in a file in
// persistent storage. This interface aims only at providing basic read/write
// capabilities and should not be used as a full fledged file IO API.
//
// All methods that report their result via calling a method on FileIOClient
// (currently, this is Open, Read, and Write) must call into FileIOClient on the
// same thread they were called on and must do so before returning. This
// restriction may be lifted in the future.
//
// Each domain (e.g. "example.com") and each CDM has it's own persistent
// storage. All instances of a given CDM associated with a given domain share
// the same persistent storage.
//
// Note to implementors of this interface:
// Per-origin storage and the ability for users to clear it are important.
// See http://www.w3.org/TR/encrypted-media/#privacy-storedinfo.
class FileIO {
public:
// Opens the file with |file_name| for read and write.
// FileIOClient::OnOpenComplete() will be called after the opening
// operation finishes.
// - When the file is opened by a CDM instance, it will be classified as "in
// use". In this case other CDM instances in the same domain may receive
// kInUse status when trying to open it.
// - |file_name| should not include path separators.
virtual void Open(const char* file_name, uint32_t file_name_size) = 0;
// Reads the contents of the file. FileIOClient::OnReadComplete() will be
// called with the read status. Read() should not be called if a previous
// Read() or Write() call is still pending; otherwise OnReadComplete() will
// be called with kInUse.
virtual void Read() = 0;
// Writes |data_size| bytes of |data| into the file.
// FileIOClient::OnWriteComplete() will be called with the write status.
// All existing contents in the file will be overwritten. Calling Write() with
// NULL |data| will clear all contents in the file. Write() should not be
// called if a previous Write() or Read() call is still pending; otherwise
// OnWriteComplete() will be called with kInUse.
virtual void Write(const uint8_t* data, uint32_t data_size) = 0;
// Closes the file if opened, destroys this FileIO object and releases any
// resources allocated. The CDM must call this method when it finished using
// this object. A FileIO object must not be used after Close() is called.
virtual void Close() = 0;
protected:
FileIO() {}
virtual ~FileIO() {}
};
// Responses to FileIO calls.
class FileIOClient {
public:
enum Status {
kSuccess = 0,
kInUse,
kError
};
// Response to a FileIO::Open() call with the open |status|.
virtual void OnOpenComplete(Status status) = 0;
// Response to a FileIO::Read() call to provide |data_size| bytes of |data|
// read from the file.
// - kSuccess indicates that all contents of the file has been successfully
// read. In this case, 0 |data_size| means that the file is empty.
// - kInUse indicates that there are other read/write operations pending.
// - kError indicates read failure, e.g. the storage isn't open or cannot be
// fully read.
virtual void OnReadComplete(Status status,
const uint8_t* data, uint32_t data_size) = 0;
// Response to a FileIO::Write() call.
// - kSuccess indicates that all the data has been written into the file
// successfully.
// - kInUse indicates that there are other read/write operations pending.
// - kError indicates write failure, e.g. the storage isn't open or cannot be
// fully written. Upon write failure, the contents of the file should be
// regarded as corrupt and should not used.
virtual void OnWriteComplete(Status status) = 0;
protected:
FileIOClient() {}
virtual ~FileIOClient() {}
};
// ContentDecryptionModule interface that all CDMs need to implement.
// CDM interfaces are versioned for backward compatibility.
// Note: ContentDecryptionModule implementations must use the Host
// to allocate any Buffer that needs to be passed back to the caller.
// Host implementations must call Buffer::Destroy() when a Buffer is created
// that will never be returned to the caller.
// Based on chromium's ContentDecryptionModule_1.
class ContentDecryptionModule_1 {
public:
static const int kVersion = 1002;
typedef Host_1 Host;
// 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;
// Tests whether |key_id| is known to any current session.
virtual bool IsKeyValid(const uint8_t* key_id, int key_id_size) = 0;
// Closes the session identified by |session_id| and releases all crypto
// resources related to that session. After calling this, it is invalid to
// refer to this session any more, because the session has been destroyed.
//
// Returns kSuccess if the session |session_id| was successfully closed and
// all resources released, kSessionError otherwise.
virtual Status CloseSession(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;
// Decrypts the |encrypted_buffer|, decodes the decrypted buffer, and passes
// the video frame or audio samples to the rendering FW/HW. No data is
// returned to the caller.
//
// 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 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., 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 Status HandleProvisioningResponse(
std::string& response) = 0;
protected:
ContentDecryptionModule_1() {}
virtual ~ContentDecryptionModule_1() {}
};
// Based on chromium's ContentDecryptionModule_4 and ContentDecryptionModule_5.
class ContentDecryptionModule_4 {
public:
static const int kVersion = 1004;
typedef Host_4 Host;
// The non-decryption methods on this class, such as CreateSession(),
// get passed a |session_id| for a MediaKeySession object. It must be used in
// the reply via Host methods (e.g. Host::OnSessionMessage()).
// Note: |session_id| is different from MediaKeySession's sessionId attribute,
// which is referred to as |web_session_id| in this file.
// Creates a new session and generates a key request given |init_data| and
// |session_type|. OnSessionCreated() will be called with a web session ID
// once the session exists. OnSessionMessage() will subsequently be called
// with the key request. A session represents hardware crypto resources,
// (if any exist for the platform), which may be in limited supply.
// For sessions of type kProvisioning, |mime_type| and |init_data| will be
// ignored and may be NULL.
virtual void CreateSession(uint32_t session_id,
const char* mime_type, uint32_t mime_type_size,
const uint8_t* init_data, uint32_t init_data_size,
SessionType session_type) = 0;
// Creates a new session and loads a previous persistent session into it that
// has a web session ID of |web_session_id|. OnSessionCreated() will be
// called once the session is loaded.
virtual void LoadSession(
uint32_t session_id,
const char* web_session_id, uint32_t web_session_id_length) = 0;
// Updates the session with |response|.
virtual void UpdateSession(
uint32_t session_id,
const uint8_t* response, uint32_t response_size) = 0;
// Tests whether |key_id| is known to any current session.
virtual bool IsKeyValid(const uint8_t* key_id, int key_id_size) = 0;
// Releases the resources for the session |session_id|.
// After calling this, it is invalid to refer to this session any more.
// If any hardware crypto resources were being used by this session, they will
// be released.
// If this session was a persistent session, this will NOT delete the
// persisted data. The persisted data will be preserved so that the session
// can be reloaded later with LoadSession(). To delete the persisted session,
// use RemoveSession().
virtual void ReleaseSession(uint32_t session_id) = 0;
// Creates a new session and generates a key release request for the
// existing persistent session identified by |web_session_id|.
// OnSessionCreated() will be called once the session exists.
// OnSessionMessage() will subsequently be called with the key release
// request.
virtual void RemoveSession(
uint32_t session_id,
const char* web_session_id, uint32_t web_session_id_length) = 0;
// Signals to the CDM that it should use server certificates to protect the
// privacy of the user. The primary use of this is when the application
// driving the CDM is untrusted code, such as when a web browser allows a web
// page's JavaScript to access the CDM. By using server certificates to
// encrypt communication with the license server, device-identifying
// information cannot be extracted from the license exchange process by a
// malicious caller.
// Unless you also call SetServerCertificate() to set a pre-cached server
// certificate, the CDM will perform a certificate exchange with the server
// prior to any key exchanges.
// This method may not be called if any sessions are open. It is typically
// called before any sessions have been opened, but may also be called if all
// open sessions have been released.
// Note that calling SetServerCertificate() implicitly calls this method as
// well.
virtual Status UsePrivacyMode() = 0;
// Provides a server certificate to be used to encrypt messages to the
// license server. Calling this is like calling UsePrivacyMode(), except that
// because the certificate is provided up-front, the CDM does not have to
// perform a certificate exchange with the server.
// This method may not be called if any sessions are open. It is typically
// called before any sessions have been opened, but may also be called if all
// open sessions have been released.
// Note that calling this method also implicitly calls UsePrivacyMode().
virtual Status SetServerCertificate(
const uint8_t* server_certificate_data,
uint32_t server_certificate_data_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;
// Decrypts the |encrypted_buffer|, decodes the decrypted buffer, and passes
// the video or audio frames to the rendering FW/HW. No data is returned to
// the caller.
//
// 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.
virtual Status DecryptDecodeAndRender(const InputBuffer& encrypted_buffer,
StreamType stream_type) = 0;
// Destroys the object in the same context as it was created.
virtual void Destroy() = 0;
protected:
ContentDecryptionModule_4() {}
virtual ~ContentDecryptionModule_4() {}
};
// Host interface that the CDM can call into to access browser side services.
// Host interfaces are versioned for backward compatibility.
// Based on chromium's Host_1.
class Host_1 {
public:
static const int kVersion = 1002;
// 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;
// Version 1.3:
// These virtual member functions extend the cdm::Host interface to allow
// the CDM to query the host for various information.
// Asks the host to persist a name-value pair.
virtual void SetPlatformString(const std::string& name,
const std::string& value) = 0;
// Retrieves a value by name. If there is no such value, the Host should
// set value to an empty string.
virtual void GetPlatformString(const std::string& name,
std::string* value) = 0;
protected:
Host_1() {}
virtual ~Host_1() {}
};
// Based on chromium's Host_4 and Host_5.
class Host_4 {
public:
static const int kVersion = 1004;
// 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(uint32_t capacity) = 0;
// Requests the host to call ContentDecryptionModule::TimerFired() |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;
// Called by the CDM when a session is created or loaded and the value for the
// MediaKeySession's sessionId attribute is available (|web_session_id|).
// This must be called before OnSessionMessage() or OnSessionUpdated() is
// called for |session_id|. |web_session_id_length| should not include null
// termination.
// When called in response to LoadSession(), the |web_session_id| must be the
// same as the |web_session_id| passed in LoadSession().
virtual void OnSessionCreated(
uint32_t session_id,
const char* web_session_id, uint32_t web_session_id_length) = 0;
// Called by the CDM when it has a message for session |session_id|.
// Length parameters should not include null termination.
virtual void OnSessionMessage(
uint32_t session_id,
const char* message, uint32_t message_length,
const char* destination_url, uint32_t destination_url_length) = 0;
// Called by the CDM when session |session_id| has been updated.
virtual void OnSessionUpdated(uint32_t session_id) = 0;
// Called by the CDM when session |session_id| is closed.
virtual void OnSessionClosed(uint32_t session_id) = 0;
// Called by the CDM when an error occurs in session |session_id|.
virtual void OnSessionError(uint32_t session_id,
Status error_code,
uint32_t system_code) = 0;
// Creates a FileIO object from the host to do file IO operation. Returns NULL
// if a FileIO object cannot be obtained. Once a valid FileIO object is
// returned, |client| must be valid until FileIO::Close() is called. The
// CDM can call this method multiple times to operate on different files.
virtual FileIO* CreateFileIO(FileIOClient* client) = 0;
protected:
Host_4() {}
virtual ~Host_4() {}
};
typedef ContentDecryptionModule_1 ContentDecryptionModule;
const int kCdmInterfaceVersion = ContentDecryptionModule::kVersion;
typedef ContentDecryptionModule::Host Host;
const int kHostInterfaceVersion = Host::kVersion;
} // namespace cdm
#endif // WVCDM_CDM_CONTENT_DECRYPTION_MODULE_H_

View File

@@ -1,54 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_HOST_4_FILE_IO_CLIENT_H_
#define WVCDM_HOST_4_FILE_IO_CLIENT_H_
#include "content_decryption_module.h"
#include "wv_cdm_common.h"
#include "wv_cdm_types.h"
namespace wvcdm {
class Host4FileIOClient : public cdm::FileIOClient {
public:
explicit Host4FileIOClient(cdm::Host_4* host)
: host_(host),
file_io_(NULL),
status_(kSuccess),
data_size_(0),
buffer_(NULL),
buffer_size_(0) {}
~Host4FileIOClient();
bool Open(const std::string& name);
bool Read(char* buffer, size_t buffer_size);
bool ReadFileSize() { return Read(NULL, 0); }
bool Write(const char* data, size_t data_size);
bool Close();
// cdm::FileIOClient implementation
virtual void OnOpenComplete(Status status) OVERRIDE;
virtual void OnReadComplete(Status status, const uint8_t* data,
uint32_t data_size) OVERRIDE;
virtual void OnWriteComplete(Status status) OVERRIDE;
// Get the result of the last operation
Status status() const { return status_; }
uint32_t data_size() const { return data_size_; }
private:
cdm::Host_4* host_;
cdm::FileIO* file_io_;
// These hold the result of the last operation
Status status_;
uint32_t data_size_;
char* buffer_;
size_t buffer_size_;
CORE_DISALLOW_COPY_AND_ASSIGN(Host4FileIOClient);
};
} // namespace wvcdm
#endif // WVCDM_HOST_4_FILE_IO_CLIENT_H_

View File

@@ -1,33 +0,0 @@
// 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_

17
cdm/include/override.h Normal file
View File

@@ -0,0 +1,17 @@
// Copyright 2015 Google Inc. All Rights Reserved.
// TODO: Import to core/, use everywhere.
#ifndef WVCDM_CDM_OVERRIDE_H_
#define WVCDM_CDM_OVERRIDE_H_
#define GCC_HAS_OVERRIDE ( \
(__GNUC__ > 4) || \
(__GNUC__ == 4 && __GNUC_MINOR__ >= 7) \
)
#if defined(COMPILER_MSVC) || defined(__clang__) || GCC_HAS_OVERRIDE
#define OVERRIDE override
#else
#define OVERRIDE
#endif
#endif // WVCDM_CDM_OVERRIDE_H_

View File

@@ -0,0 +1,30 @@
// Copyright 2015 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_PROPERTIES_CE_H_
#define WVCDM_CDM_PROPERTIES_CE_H_
#include "cdm.h"
#if defined(UNIT_TEST)
# include <gtest/gtest.h>
#endif
namespace widevine {
class PropertiesCE {
public:
static Cdm::ClientInfo GetClientInfo();
static Cdm::SecureOutputType GetSecureOutputType();
private:
static void SetSecureOutputType(Cdm::SecureOutputType secure_output_type);
static void SetClientInfo(const Cdm::ClientInfo& client_info);
friend class Cdm;
#if defined(UNIT_TEST)
FRIEND_TEST(CdmTest, DeviceCertificateRequest);
#endif
};
} // namespace widevine
#endif // WVCDM_CDM_PROPERTIES_CE_H_

View File

@@ -1,35 +0,0 @@
// 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 {
// Set only one of the two below to true. If secure buffer
// is selected, fallback to userspace buffers may occur
// if L1/L2 OEMCrypto APIs fail.
// Note: This is managed in the build configuration.
const bool kPropertyOemCryptoUseSecureBuffers = PLATFORM_REQUIRES_SECURE_BUFFERS;
const bool kPropertyOemCryptoUseFifo = true;
const bool kPropertyOemCryptoUseUserSpaceBuffers = PLATFORM_USES_CLEAR_BUFFERS;
// If true, the unit tests require OEMCrypto to support usage tables.
const bool kPropertyOemCryptoRequireUsageTable = false;
// 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 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_

View File

@@ -1,12 +0,0 @@
// 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_

View File

@@ -1,3 +0,0 @@
// Widevine CDM Kit Version
#define WV_CDM_VERSION "v2.2.0-0-903"

View File

@@ -1,73 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_WV_CLIENT_PROPERTY_SET_H_
#define WVCDM_CDM_WV_CLIENT_PROPERTY_SET_H_
#include "cdm_client_property_set.h"
#include "wv_cdm_types.h"
namespace wvcdm {
class WVClientPropertySet : public CdmClientPropertySet {
public:
WVClientPropertySet()
: use_privacy_mode_(false) {}
virtual ~WVClientPropertySet() {}
void set_security_level(const std::string& securityLevel) {
security_level_ = securityLevel;
}
virtual const 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::string& serviceCertificate) {
service_certificate_ = serviceCertificate;
}
virtual const std::string& 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::string service_certificate_;
};
} // namespace wvcdm
#endif // WVCDM_CDM_WV_CLIENT_PROPERTY_SET_H_

View File

@@ -1,112 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_1_H_
#define WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_1_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"
#include "wv_client_property_set.h"
namespace wvcdm {
class WvContentDecryptionModule_1 : public cdm::ContentDecryptionModule_1,
public IFileFactory,
public IClock {
public:
explicit WvContentDecryptionModule_1(cdm::Host_1* host);
virtual ~WvContentDecryptionModule_1();
// cdm::ContentDecryptionModule_1 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 bool IsKeyValid(const uint8_t* key_id, int key_id_size) OVERRIDE;
virtual cdm::Status CloseSession(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 DecryptDecodeAndRenderFrame(
const cdm::InputBuffer& encrypted_buffer) OVERRIDE;
virtual cdm::Status DecryptDecodeAndRenderSamples(
const cdm::InputBuffer& encrypted_buffer) OVERRIDE;
virtual void Destroy() OVERRIDE;
virtual cdm::Status GetProvisioningRequest(
std::string* request, std::string* default_url) OVERRIDE;
virtual cdm::Status HandleProvisioningResponse(
std::string& response) OVERRIDE;
private:
void EnablePolicyTimer();
void DisablePolicyTimer();
virtual File::Impl* NewFileImpl() OVERRIDE;
virtual int64_t GetCurrentTimeInSeconds() OVERRIDE;
/* |parameters| is expected to be initialized with anything not related to
* subsample parsing. |iv| is initialized by the caller, but may be modified
* during decryption. |decrypted_block| may be NULL for L1 decrypts, since
* no data is passed back to the caller. */
cdm::Status DoSubsampleDecrypt(CdmDecryptionParameters& parameters,
std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block);
cdm::Status DoDecrypt(CdmDecryptionParameters& parameters,
std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block);
/* |parameters| is expected to be initialized with everything required by
* DoSubsampleDecrypt and DoDecrypt, plus |is_encrypted| and
* |subsample_flags|. Counters and |iv| will be updated to prepare for
* subsequent calls. */
cdm::Status DecryptAndUpdateCounters(CdmDecryptionParameters& parameters,
std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
const size_t bytes,
cdm::DecryptedBlock* decrypted_block,
size_t& offset,
size_t& encrypted_offset,
uint32_t& block_ctr);
void SetSizesAndAllocate(size_t output_size,
CdmDecryptionParameters& parameters,
cdm::DecryptedBlock* decrypted_block);
cdm::Host_1* const host_;
HostEventListener host_event_listener_;
CdmEngine cdm_engine_;
WVClientPropertySet property_set_;
bool timer_enabled_;
CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule_1);
};
} // namespace wvcdm
#endif // WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_1_H_

View File

@@ -1,155 +0,0 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_4_H_
#define WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_4_H_
#include "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 "wv_cdm_common.h"
#include "wv_cdm_event_listener.h"
#include "wv_cdm_types.h"
#include "wv_client_property_set.h"
#if defined(UNIT_TEST)
# include "gtest/gtest_prod.h"
#endif
namespace wvcdm {
class WvContentDecryptionModule_4 : public cdm::ContentDecryptionModule_4,
public IFileFactory,
public IClock,
public WvCdmEventListener {
public:
explicit WvContentDecryptionModule_4(cdm::Host_4* host);
virtual ~WvContentDecryptionModule_4();
virtual void CreateSession(uint32_t session_id,
const char* mime_type, uint32_t mime_type_size,
const uint8_t* init_data, uint32_t init_data_size,
cdm::SessionType session_type) OVERRIDE;
virtual void LoadSession(uint32_t session_id, const char* web_session_id,
uint32_t web_session_id_length) OVERRIDE;
virtual void UpdateSession(uint32_t session_id, const uint8_t* response,
uint32_t response_size) OVERRIDE;
virtual bool IsKeyValid(const uint8_t* key_id, int key_id_size) OVERRIDE;
virtual void ReleaseSession(uint32_t session_id) OVERRIDE;
virtual void RemoveSession(uint32_t session_id, const char* web_session_id,
uint32_t web_session_id_length) OVERRIDE;
virtual cdm::Status UsePrivacyMode() OVERRIDE;
virtual cdm::Status SetServerCertificate(
const uint8_t* server_certificate_data,
uint32_t server_certificate_data_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 DecryptDecodeAndRender(
const cdm::InputBuffer& encrypted_buffer,
cdm::StreamType stream_type) OVERRIDE;
virtual void Destroy() OVERRIDE;
// wvcdm::WvCdmEventListener implementation.
virtual void OnEvent(const CdmSessionId& session_id,
CdmEventType cdm_event) OVERRIDE;
private:
class InternalSession {
public:
enum SessionType {
kDecrypt = 1,
kRelease = 2,
kProvision = 3,
};
InternalSession() : webid_(), session_type_(kDecrypt) {}
InternalSession(const std::string& webid, SessionType session_type)
: webid_(webid), session_type_(session_type) {}
const std::string& webid() const { return webid_; }
bool is_decrypt() const { return session_type_ == kDecrypt; }
bool is_release() const { return session_type_ == kRelease; }
bool is_provision() const { return session_type_ == kProvision; }
private:
std::string webid_;
SessionType session_type_;
};
void EnablePolicyTimer();
void DisablePolicyTimer();
void CreateProvisionSession(uint32_t session_id);
void UpdateProvisionSession(uint32_t session_id, const uint8_t* response,
uint32_t response_size);
bool CallOpenSession(uint32_t session_id);
virtual File::Impl* NewFileImpl() OVERRIDE;
virtual int64_t GetCurrentTimeInSeconds() OVERRIDE;
/* |parameters| is expected to be initialized with anything not related to
* subsample parsing. |iv| is initialized by the caller, but may be modified
* during decryption. |decrypted_block| may be NULL for L1 decrypts, since
* no data is passed back to the caller. */
cdm::Status DoSubsampleDecrypt(CdmDecryptionParameters& parameters,
std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block);
cdm::Status DoDecrypt(CdmDecryptionParameters& parameters,
std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block);
/* |parameters| is expected to be initialized with everything required by
* DoSubsampleDecrypt and DoDecrypt, plus |is_encrypted| and
* |subsample_flags|. Counters and |iv| will be updated to prepare for
* subsequent calls. */
cdm::Status DecryptAndUpdateCounters(CdmDecryptionParameters& parameters,
std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
const size_t bytes,
cdm::DecryptedBlock* decrypted_block,
size_t& offset, size_t& encrypted_offset,
uint32_t& block_ctr);
void SetSizesAndAllocate(size_t output_size,
CdmDecryptionParameters& parameters,
cdm::DecryptedBlock* decrypted_block);
#if defined(UNIT_TEST)
FRIEND_TEST(CdmApi4Test, UsePrivacyMode);
FRIEND_TEST(CdmApi4Test, UsePrivacyModeFailsWithOpenSessions);
FRIEND_TEST(CdmApi4Test, SetExplicitServerCertificate);
FRIEND_TEST(CdmApi4Test, SetServerCertificateFailsWithOpenSessions);
#endif
cdm::Host_4* const host_;
WVClientPropertySet property_set_;
CdmEngine cdm_engine_;
std::map<uint32_t, InternalSession> session_map_;
bool timer_enabled_;
CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule_4);
};
} // namespace wvcdm
#endif // WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_4_H_

View File

@@ -0,0 +1,18 @@
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Include this in any custom unit test targets.
# Does not include the test runner main.
{
'sources': [
'../oemcrypto/test/oemcrypto_test.cpp',
],
'include_dirs': [
'../core/include', # log.h
'../oemcrypto/include',
'../oemcrypto/mock/src', # oemcrypto_key_mock.h
'../oemcrypto/test',
],
'defines': [
'OEMCRYPTO_TESTS',
],
}

889
cdm/src/cdm.cpp Normal file
View File

@@ -0,0 +1,889 @@
// Copyright 2015 Google Inc. All Rights Reserved.
#include "cdm.h"
#include <assert.h>
#include <string.h> // memcpy
#include <vector>
// core:
#include "cdm_client_property_set.h"
#include "cdm_engine.h"
#include "clock.h"
#include "crypto_session.h"
#include "file_store.h"
#include "license.h"
#include "log.h"
#include "properties.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
// CE:
#include "cdm_version.h"
#include "override.h"
#include "properties_ce.h"
namespace widevine {
using namespace wvcdm;
namespace {
const int64_t kPolicyTimerDurationMilliseconds = 5000;
void* const kPolicyTimerContext = NULL;
struct HostType {
Cdm::IStorage* storage;
Cdm::IClock* clock;
Cdm::ITimer* timer;
CdmEngine* provisioning_engine;
bool initialized;
HostType()
: storage(NULL),
clock(NULL),
timer(NULL),
provisioning_engine(NULL),
initialized(false) {}
} host;
class PropertySet : public CdmClientPropertySet {
public:
PropertySet()
: use_privacy_mode_(false) {}
virtual ~PropertySet() {}
virtual const std::string& security_level() const OVERRIDE {
// Unused on CE platforms. Used by Android to switch to L3.
return empty_string_;
}
void set_use_privacy_mode(bool use) {
use_privacy_mode_ = use;
}
virtual bool use_privacy_mode() const OVERRIDE {
return use_privacy_mode_;
}
virtual const std::string& service_certificate() const OVERRIDE {
return service_certificate_;
}
virtual void set_service_certificate(const std::string& cert) OVERRIDE {
service_certificate_ = cert;
}
virtual bool is_session_sharing_enabled() const OVERRIDE {
// Unused on CE platforms.
return true;
}
virtual uint32_t session_sharing_id() const OVERRIDE {
// Unused on CE platforms.
return 1;
}
virtual void set_session_sharing_id(uint32_t id) OVERRIDE {
// Unused on CE platforms.
return;
}
virtual const std::string& app_id() const OVERRIDE {
// Unused on CE platforms.
return empty_string_;
}
private:
bool use_privacy_mode_;
std::string service_certificate_;
// This is empty, but g++ 4.8 will not allow app_id() to return a string
// literal as a const reference to std::string.
const std::string empty_string_;
};
class CdmImpl : public Cdm,
public Cdm::ITimer::IClient,
public WvCdmEventListener {
public:
CdmImpl(IEventListener* listener,
bool privacy_mode);
virtual ~CdmImpl();
// Cdm:
virtual Status setServerCertificate(const std::string& certificate) OVERRIDE;
virtual Status createSession(SessionType session_type,
std::string* session_id) OVERRIDE;
virtual Status generateRequest(const std::string& session_id,
InitDataType init_data_type,
const std::string& init_data) OVERRIDE;
virtual Status load(const std::string& session_id) OVERRIDE;
virtual Status update(const std::string& session_id,
const std::string& response) OVERRIDE;
virtual Status getExpiration(const std::string& session_id,
int64_t* expiration) OVERRIDE;
virtual Status getKeyStatuses(const std::string& session_id,
KeyStatusMap* key_statuses) OVERRIDE;
virtual Status close(const std::string& session_id) OVERRIDE;
virtual Status remove(const std::string& session_id) OVERRIDE;
virtual Status decrypt(const InputBuffer& input,
const OutputBuffer& output) OVERRIDE;
// ITimer::IClient:
virtual void onTimerExpired(void* context) OVERRIDE;
// WvCdmEventListener:
virtual void OnSessionRenewalNeeded(const CdmSessionId& session_id) OVERRIDE;
virtual void OnSessionKeysChange(const CdmSessionId& session_id,
const CdmKeyStatusMap& keys_status,
bool has_new_usable_key) OVERRIDE;
virtual void OnExpirationUpdate(const CdmSessionId& session_id,
int64_t new_expiry_time_seconds) OVERRIDE;
private:
IEventListener* listener_;
bool policy_timer_enabled_;
CdmEngine cdm_engine_;
PropertySet property_set_;
std::map<std::string, SessionType> new_session_types_;
std::map<std::string, int64_t> session_expirations_;
std::map<std::string, KeyStatusMap> session_key_statuses_;
};
CdmImpl::CdmImpl(IEventListener* listener,
bool privacy_mode)
: listener_(listener),
policy_timer_enabled_(false) {
property_set_.set_use_privacy_mode(privacy_mode);
}
CdmImpl::~CdmImpl() {}
Cdm::Status CdmImpl::setServerCertificate(const std::string& certificate) {
if (!property_set_.use_privacy_mode()) {
LOGE("Cannot set server certificate if privacy mode is disabled.");
return kNotSupported;
}
if (certificate.empty()) {
LOGE("An empty server certificate is invalid.");
return kInvalidAccess;
}
if (CdmLicense::VerifySignedServiceCertificate(certificate) != NO_ERROR) {
LOGE("Invalid server certificate!");
return kInvalidAccess;
}
property_set_.set_service_certificate(certificate);
return kSuccess;
}
Cdm::Status CdmImpl::createSession(SessionType session_type,
std::string* session_id) {
if (!session_id) {
LOGE("Missing session ID pointer.");
return kInvalidAccess;
}
// Important! The caller may pass a pre-filled string, which must be cleared
// before being given to CdmEngine.
session_id->clear();
switch (session_type) {
case kTemporary:
case kPersistent:
break;
default:
LOGE("Unsupported session type: %d", session_type);
return kNotSupported;
}
std::string empty_origin;
CdmResponseType result = cdm_engine_.OpenSession(
"com.widevine.alpha", &property_set_, empty_origin, this,
NULL, session_id);
switch (result) {
case NO_ERROR:
new_session_types_[*session_id] = session_type;
return kSuccess;
case NEED_PROVISIONING:
LOGE("A device certificate is needed.");
return kNeedsDeviceCertificate;
default:
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
}
Cdm::Status CdmImpl::generateRequest(const std::string& session_id,
InitDataType init_data_type,
const std::string& init_data) {
if (!cdm_engine_.IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
std::map<std::string, SessionType>::iterator it =
new_session_types_.find(session_id);
if (it == new_session_types_.end()) {
LOGE("Request already generated: %s", session_id.c_str());
return kInvalidState;
}
SessionType session_type = it->second;
CdmLicenseType license_type;
switch (session_type) {
case kTemporary:
license_type = kLicenseTypeStreaming;
break;
case kPersistent:
license_type = kLicenseTypeOffline;
break;
default:
LOGE("Unexpected session type: %d", session_type);
return kUnexpectedError;
}
std::string init_data_type_name;
switch (init_data_type) {
case kCenc:
init_data_type_name = CENC_INIT_DATA_FORMAT;
break;
case kKeyIds:
LOGE("Key IDs init data type is not supported.");
return kNotSupported;
case kWebM:
init_data_type_name = WEBM_INIT_DATA_FORMAT;
break;
default:
LOGE("Invalid init data type: %d", init_data_type);
return kInvalidAccess;
}
InitializationData init_data_obj(init_data_type_name, init_data);
if (init_data_obj.IsEmpty()) {
LOGE("Failed to parse init data.");
return kInvalidAccess;
}
CdmAppParameterMap empty_app_parameters;
std::string key_request;
CdmKeyRequestType key_request_type;
std::string ignored_server_url;
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
session_id, session_id, init_data_obj,
license_type, empty_app_parameters, &key_request, &key_request_type,
&ignored_server_url, NULL);
if (result != KEY_MESSAGE) {
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
new_session_types_.erase(it);
assert(key_request_type == kKeyRequestTypeInitial);
MessageType message_type = kLicenseRequest;
if (property_set_.use_privacy_mode() &&
property_set_.service_certificate().empty()) {
// We can deduce that this is a server cert request, even though CdmEgine
// cannot currently inform us of this.
message_type = kIndividualizationRequest;
LOGI("A server certificate request has been generated.");
} else {
LOGI("A license request has been generated.");
}
listener_->onMessage(session_id, message_type, key_request);
return kSuccess;
}
Cdm::Status CdmImpl::load(const std::string& session_id) {
if (session_id.empty()) {
LOGE("Empty session ID.");
return kInvalidAccess;
}
if (cdm_engine_.IsOpenSession(session_id)) {
LOGE("Session ID already loaded.");
return kQuotaExceeded;
}
std::string empty_origin;
std::string ignored_session_id_output;
CdmResponseType result = cdm_engine_.OpenSession(
"com.widevine.alpha", &property_set_, empty_origin, this,
&session_id, &ignored_session_id_output);
switch (result) {
case NO_ERROR:
break;
case NEED_PROVISIONING:
LOGE("A device certificate is needed.");
return kNeedsDeviceCertificate;
default:
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
result = cdm_engine_.RestoreKey(session_id, session_id);
if (result == GET_LICENSE_ERROR) {
LOGE("Unable to load license: %s", session_id.c_str());
return kSessionNotFound;
} else if (result == GET_RELEASED_LICENSE_ERROR) {
// This was partially removed already.
// The EME spec states that we should send a release message right away.
InitializationData empty_initialization_data;
CdmAppParameterMap empty_app_parameters;
CdmKeyMessage key_request;
std::string ignored_server_url;
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
session_id, session_id, empty_initialization_data,
kLicenseTypeRelease, empty_app_parameters, &key_request, NULL,
&ignored_server_url, NULL);
if (result != KEY_MESSAGE) {
LOGE("Unexpected error %d", result);
cdm_engine_.CloseSession(session_id);
return kUnexpectedError;
}
LOGI("A license release has been generated.");
MessageType message_type = kLicenseRelease;
listener_->onMessage(session_id, message_type, key_request);
} else if (result != KEY_ADDED) {
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::update(const std::string& session_id,
const std::string& response) {
if (!cdm_engine_.IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (new_session_types_.find(session_id) != new_session_types_.end()) {
LOGE("Request not yet generated: %s", session_id.c_str());
return kInvalidState;
}
if (response.empty()) {
LOGE("Empty response.");
return kInvalidAccess;
}
bool predicted_to_be_server_cert_response =
property_set_.use_privacy_mode() &&
property_set_.service_certificate().empty();
// NOTE: If the CdmSession object recognizes that this is not the first
// AddKey(), it will internally delegate to RenewKey().
CdmKeySetId key_set_id = session_id;
CdmResponseType result =
cdm_engine_.AddKey(session_id, response, &key_set_id);
if (result == NEED_KEY) {
// We just provisioned a server certificate.
assert(predicted_to_be_server_cert_response);
// The cert is now available to all sessions in this CDM instance.
// This is consistent with the behavior of the Chrome CDM.
assert(!property_set_.service_certificate().empty());
// The underlying session in CdmEngine has stored a copy of the original
// init data, so we can use an empty one this time.
InitializationData empty_init_data;
CdmAppParameterMap empty_app_parameters;
std::string key_request;
CdmKeyRequestType key_request_type;
std::string ignored_server_url;
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
session_id, session_id, empty_init_data, kLicenseTypeDeferred,
empty_app_parameters, &key_request, &key_request_type,
&ignored_server_url, NULL);
if (result != KEY_MESSAGE) {
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
LOGI("A deferred license request has been generated.");
assert(key_request_type == kKeyRequestTypeInitial);
MessageType message_type = kLicenseRequest;
listener_->onMessage(session_id, message_type, key_request);
return kSuccess;
}
assert(!predicted_to_be_server_cert_response);
if (result != KEY_ADDED) {
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
if (!policy_timer_enabled_) {
policy_timer_enabled_ = true;
host.timer->setTimeout(kPolicyTimerDurationMilliseconds,
this,
kPolicyTimerContext);
}
if (cdm_engine_.IsReleaseSession(session_id)) {
cdm_engine_.CloseSession(session_id);
listener_->onRemoveComplete(session_id);
}
return kSuccess;
}
Cdm::Status CdmImpl::getExpiration(const std::string& session_id,
int64_t* expiration) {
if (!cdm_engine_.IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
*expiration = session_expirations_[session_id];
return kSuccess;
}
Cdm::Status CdmImpl::getKeyStatuses(const std::string& session_id,
KeyStatusMap* key_statuses) {
if (!cdm_engine_.IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
*key_statuses = session_key_statuses_[session_id];
return kSuccess;
}
Cdm::Status CdmImpl::close(const std::string& session_id) {
if (!cdm_engine_.IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
CdmResponseType result = cdm_engine_.CloseSession(session_id);
if (result != NO_ERROR) {
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
return kSuccess;
}
Cdm::Status CdmImpl::remove(const std::string& session_id) {
if (!cdm_engine_.IsOpenSession(session_id)) {
LOGE("No such session: %s", session_id.c_str());
return kSessionNotFound;
}
if (new_session_types_.find(session_id) != new_session_types_.end()) {
LOGE("Request not yet generated: %s", session_id.c_str());
return kInvalidState;
}
if (!cdm_engine_.IsOfflineSession(session_id)) {
LOGE("Not a persistent session: %s", session_id.c_str());
return kInvalidAccess;
}
InitializationData empty_initialization_data;
CdmAppParameterMap empty_app_parameters;
CdmKeyMessage key_request;
std::string ignored_server_url;
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
session_id, session_id, empty_initialization_data,
kLicenseTypeRelease, empty_app_parameters, &key_request, NULL,
&ignored_server_url, NULL);
if (result != KEY_MESSAGE) {
LOGE("Unexpected error %d", result);
cdm_engine_.CloseSession(session_id);
return kUnexpectedError;
}
LOGI("A license release has been generated.");
MessageType message_type = kLicenseRelease;
listener_->onMessage(session_id, message_type, key_request);
return kSuccess;
}
Cdm::Status CdmImpl::decrypt(const InputBuffer& input,
const OutputBuffer& output) {
if (input.is_encrypted && input.iv_length != 16) {
LOGE("The IV must be 16 bytes long.");
return kInvalidAccess;
}
if (PropertiesCE::GetSecureOutputType() == kNoSecureOutput &&
output.is_secure) {
LOGE("The CDM is configured without secure output support.");
return kNotSupported;
}
std::string key_id(reinterpret_cast<const char *>(input.key_id),
input.key_id_length);
std::vector<uint8_t> iv(input.iv, input.iv + input.iv_length);
CdmDecryptionParameters parameters;
parameters.is_encrypted = input.is_encrypted;
parameters.is_secure = output.is_secure;
parameters.key_id = &key_id;
parameters.encrypt_buffer = input.data;
parameters.encrypt_length = input.data_length;
parameters.iv = &iv;
parameters.block_offset = input.block_offset;
parameters.decrypt_buffer = output.data;
parameters.decrypt_buffer_length = output.data_length;
parameters.decrypt_buffer_offset = output.data_offset;
parameters.subsample_flags =
(input.first_subsample ? OEMCrypto_FirstSubsample : 0) |
(input.last_subsample ? OEMCrypto_LastSubsample : 0);
parameters.is_video = input.is_video;
CdmSessionId empty_session_id;
CdmResponseType result = cdm_engine_.Decrypt(empty_session_id, parameters);
if (result == NEED_KEY || result == SESSION_NOT_FOUND_FOR_DECRYPT) {
LOGE("Key not available.");
return kNoKey;
}
if (result == NO_ERROR) {
return kSuccess;
}
LOGE("Decrypt error: %d", result);
return kDecryptError;
}
void CdmImpl::onTimerExpired(void* context) {
if (context == kPolicyTimerContext) {
if (policy_timer_enabled_) {
cdm_engine_.OnTimerEvent();
host.timer->setTimeout(kPolicyTimerDurationMilliseconds,
this,
kPolicyTimerContext);
}
}
}
void CdmImpl::OnSessionRenewalNeeded(const CdmSessionId& session_id) {
CdmKeyMessage message;
std::string server_url;
CdmResponseType result =
cdm_engine_.GenerateRenewalRequest(session_id, &message, &server_url);
if (result != KEY_MESSAGE) {
LOGE("Unexpected error %d", result);
return;
}
LOGI("A license renewal has been generated.");
MessageType message_type = kLicenseRenewal;
listener_->onMessage(session_id, message_type, message);
}
void CdmImpl::OnSessionKeysChange(const CdmSessionId& session_id,
const CdmKeyStatusMap& keys_status,
bool has_new_usable_key) {
KeyStatusMap& map = session_key_statuses_[session_id];
CdmKeyStatusMap::const_iterator it;
for (it = keys_status.begin(); it != keys_status.end(); ++it) {
KeyStatus status;
switch (it->second) {
case kKeyStatusUsable:
map[it->first] = kUsable;
break;
case kKeyStatusExpired:
map[it->first] = kExpired;
break;
case kKeyStatusOutputNotAllowed:
map[it->first] = kOutputNotAllowed;
break;
case kKeyStatusPending:
map[it->first] = kStatusPending;
break;
case kKeyStatusInternalError:
map[it->first] = kInternalError;
break;
default:
LOGE("Unrecognized key status: %d", it->second);
map[it->first] = kInternalError;
break;
}
}
listener_->onKeyStatusesChange(session_id);
}
void CdmImpl::OnExpirationUpdate(const CdmSessionId& session_id,
int64_t new_expiry_time_seconds) {
session_expirations_[session_id] = new_expiry_time_seconds * 1000;
}
bool VerifyL1() {
CryptoSession cs;
return cs.GetSecurityLevel() == kSecurityLevelL1;
}
} // namespace
// static
Cdm::Status Cdm::initialize(
SecureOutputType secure_output_type,
const ClientInfo& client_info,
IStorage* storage,
IClock* clock,
ITimer* timer,
DeviceCertificateRequest* device_certificate_request,
LogLevel verbosity) {
// If you want to direct-render on L3, CryptoSession will pass that request
// along to OEMCrypto. But if you want to use an opaque handle on L3,
// CryptoSession will silently ignore you and tell OEMCrypto to treat the
// address as a clear buffer. :-(
//
// So this logic mirrors that in CryptoSession. Effectively, we are
// detecting at init time the conditions that would prevent CryptoSession (in
// its current form) from passing the desired buffer type constant to
// OEMCrypto.
// TODO: Discuss changes to CryptoSession.
switch (secure_output_type) {
case kOpaqueHandle:
// This output type requires an OEMCrypto that reports L1.
// This requirement comes from CryptoSession::SetDestinationBufferType().
if (!VerifyL1()) {
LOGE("Not an L1 implementation, kOpaqueHandle cannot be used!");
return kNotSupported;
}
break;
case kDirectRender:
case kNoSecureOutput:
break;
default:
LOGE("Invalid output type!");
return kInvalidAccess;
}
if (client_info.product_name.empty() ||
client_info.company_name.empty() ||
client_info.model_name.empty()) {
LOGE("Client info requires product_name, company_name, model_name!");
return kInvalidAccess;
}
if (!storage || !clock || !timer) {
LOGE("All interfaces are required!");
return kInvalidAccess;
}
if (!device_certificate_request) {
LOGE("Device certificate request pointer is required!");
return kInvalidAccess;
}
// Our enum values match those in core/include/log.h
g_cutoff = static_cast<LogPriority>(verbosity);
PropertiesCE::SetSecureOutputType(secure_output_type);
PropertiesCE::SetClientInfo(client_info);
Properties::Init();
host.storage = storage;
host.clock = clock;
host.timer = timer;
device_certificate_request->needed = false;
if (!host.provisioning_engine) {
host.provisioning_engine = new CdmEngine();
}
bool has_cert = host.provisioning_engine->IsProvisioned(
kSecurityLevelL1, "" /* origin */);
if (!has_cert) {
device_certificate_request->needed = true;
std::string empty_authority;
std::string empty_origin;
std::string base_url;
std::string signed_request;
CdmResponseType result = host.provisioning_engine->GetProvisioningRequest(
kCertificateWidevine, empty_authority, empty_origin,
&signed_request, &base_url);
if (result != NO_ERROR) {
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
device_certificate_request->url = base_url;
device_certificate_request->url.append("&signedRequest=");
device_certificate_request->url.append(signed_request);
}
host.initialized = true;
return kSuccess;
}
// static
const char* Cdm::version() {
return CDM_VERSION;
}
// static
Cdm* Cdm::create(IEventListener* listener,
bool privacy_mode) {
if (!host.initialized) {
LOGE("Not initialized!");
return NULL;
}
if (!listener) {
LOGE("No listener!");
return NULL;
}
return new CdmImpl(listener, privacy_mode);
}
Cdm::Status Cdm::DeviceCertificateRequest::acceptReply(
const std::string& reply) {
if (!host.provisioning_engine) {
LOGE("Provisioning reply received while not in a provisioning state!");
return kInvalidAccess;
}
std::string empty_origin;
std::string ignored_cert;
std::string ignored_wrapped_key;
CdmResponseType result =
host.provisioning_engine->HandleProvisioningResponse(
empty_origin, reply, &ignored_cert, &ignored_wrapped_key);
if (result != NO_ERROR) {
LOGE("Unexpected error %d", result);
return kUnexpectedError;
}
return kSuccess;
}
} // namespace widevine
// Missing symbols from core:
namespace wvcdm {
using namespace widevine;
int64_t Clock::GetCurrentTime() {
return host.clock->now() / 1000;
}
struct File::Impl {
std::string name;
bool read_only;
bool truncate;
};
File::File() : impl_(NULL) {}
File::~File() {
Close();
}
bool File::Open(const std::string& file_path, int flags) {
if (!(flags & kCreate) && !host.storage->exists(file_path)) {
return false;
}
impl_ = new Impl;
impl_->name = file_path;
impl_->read_only = (flags & kReadOnly);
impl_->truncate = (flags & kTruncate);
return true;
}
ssize_t File::Read(char* buffer, size_t bytes) {
if (!impl_) {
return -1;
}
std::string data;
if (!host.storage->read(impl_->name, &data)) {
return -1;
}
size_t to_copy = std::min(bytes, data.size());
memcpy(buffer, data.data(), to_copy);
return to_copy;
}
ssize_t File::Write(const char* buffer, size_t bytes) {
if (!impl_) {
return -1;
}
if (!impl_->truncate) {
LOGE("Internal error: files cannot be appended to.");
return -1;
}
std::string data(buffer, bytes);
if (!host.storage->write(impl_->name, data)) {
return -1;
}
return bytes;
}
void File::Close() {
if (impl_) {
delete impl_;
}
impl_ = NULL;
}
bool File::Exists(const std::string& file_path) {
return host.storage->exists(file_path);
}
bool File::Remove(const std::string& file_path) {
return host.storage->remove(file_path);
}
bool File::Copy(const std::string& old_path, const std::string& new_path) {
std::string data;
bool read_ok = host.storage->read(old_path, &data);
if (!read_ok) return false;
return host.storage->write(new_path, data);
}
bool File::List(const std::string& path, std::vector<std::string>* files) {
return false;
}
bool File::CreateDirectory(const std::string dir_path) {
return true;
}
bool File::IsDirectory(const std::string& dir_path) {
return false;
}
bool File::IsRegularFile(const std::string& file_path) {
return host.storage->exists(file_path);
}
ssize_t File::FileSize(const std::string& file_path) {
return host.storage->size(file_path);
}
} // namespace wvcdm

View File

@@ -1,48 +0,0 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#include <assert.h>
#include "wv_content_decryption_module_1.h"
#include "wv_content_decryption_module_4.h"
#include "wv_cdm_version.h"
void InitializeCdmModule() {}
void DeinitializeCdmModule() {}
void* CreateCdmInstance(int cdm_interface_version, const char* key_system,
uint32_t key_system_size,
GetCdmHostFunc get_cdm_host_func, void* user_data) {
void *host = NULL;
switch (cdm_interface_version) {
case cdm::ContentDecryptionModule_1::kVersion:
host = get_cdm_host_func(cdm::Host_1::kVersion, user_data);
break;
case cdm::ContentDecryptionModule_4::kVersion:
host = get_cdm_host_func(cdm::Host_4::kVersion, user_data);
break;
}
if (!host)
return NULL;
switch (cdm_interface_version) {
case cdm::ContentDecryptionModule_1::kVersion:
return new wvcdm::WvContentDecryptionModule_1(
static_cast<cdm::Host_1*>(host));
case cdm::ContentDecryptionModule_4::kVersion:
return new wvcdm::WvContentDecryptionModule_4(
static_cast<cdm::Host_4*>(host));
}
assert(false); // NOT REACHED
return NULL;
}
const char* GetCdmVersion() {
return WV_CDM_VERSION;
}

View File

@@ -1,24 +0,0 @@
#include "clock.h"
#include <cstddef>
#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

View File

@@ -1,222 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "cdm_host_file.h"
#include <string.h>
#include <algorithm>
#include <string>
namespace wvcdm {
IFileFactory* File::Impl::factory_ = NULL;
// File::Impl() Section
// The file handler for cert.bin, aka DeviceCertificate, and usage.bin,
// aka UsageTable, are all we're setting up for now.
namespace {
const char* const kDeviceCertificateKey = "DeviceCertificate";
const char* const kUsageInfoKey = "UsageInfo";
const char* const kUsageTableKey = "MockOemCryptoUsageTable";
const char* const kGenerationNumberKey = "MockOemCryptoGenerationNumber";
const char* GetKeyForFileName(const std::string& name) {
if (name == "cert.bin") {
return kDeviceCertificateKey;
} else if (name == "usage.bin") {
return kUsageInfoKey;
} else if (name == "UsageTable.dat") {
return kUsageTableKey;
} else if (name == "GenerationNumber.dat") {
return kGenerationNumberKey;
} else {
return "";
}
}
} // namespace
// File1 implmentation.
bool File1Impl::Open(const std::string& name) {
if (name.empty())
return false;
fname_ = name;
return true;
}
ssize_t File1Impl::Read(char* buffer, size_t bytes) {
std::string key(GetKeyForFileName(fname_));
if (key.length() > 0) {
std::string value;
host_1_->GetPlatformString(key, &value);
size_t bytes_to_copy = std::min(bytes, value.size());
memcpy(buffer, value.data(), bytes_to_copy);
return value.size() ? bytes_to_copy : -1;
} else {
return -1;
}
}
bool File1Impl::Remove(const std::string& name) {
std::string key(GetKeyForFileName(name));
if (key.length() > 0) {
host_1_->SetPlatformString(key, "");
return true;
} else {
return false;
}
}
ssize_t File1Impl::FileSize(const std::string& name) {
std::string key(GetKeyForFileName(name));
if (key.length() > 0) {
std::string value;
host_1_->GetPlatformString(key, &value);
return value.empty() ? -1 : value.size();
} else {
return -1;
}
}
ssize_t File1Impl::Write(const char* buffer, size_t bytes) {
std::string key(GetKeyForFileName(fname_));
if (key.length() > 0) {
std::string value(buffer, bytes);
host_1_->SetPlatformString(key, value);
return bytes;
} else {
return -1;
}
}
bool File1Impl::Close() {
fname_.clear();
return true;
}
// File 4 Implementation.
bool File4Impl::Open(const std::string& name) {
return host_4_file_io_client_.Open(name);
}
ssize_t File4Impl::Read(char* buffer, size_t bytes) {
if (host_4_file_io_client_.Read(buffer, bytes) &&
host_4_file_io_client_.data_size() > 0) {
return std::min<size_t>(host_4_file_io_client_.data_size(), bytes);
} else {
return -1;
}
}
ssize_t File4Impl::Write(const char* buffer, size_t bytes) {
if (host_4_file_io_client_.Write(buffer, bytes)) {
return bytes;
} else {
return -1;
}
}
bool File4Impl::Close() {
return host_4_file_io_client_.Close();
}
bool File4Impl::Remove(const std::string& name) {
Host4FileIOClient file_io_client(host_4_);
return file_io_client.Open(name) && file_io_client.Write("", 0);
}
ssize_t File4Impl::FileSize(const std::string& name) {
Host4FileIOClient file_io_client(host_4_);
if (file_io_client.Open(name) && file_io_client.ReadFileSize() &&
file_io_client.data_size() > 0) {
return file_io_client.data_size();
} else {
return -1;
}
}
// Common file implementation.
bool File::Impl::Open(const std::string& name) {
return file_api_->Open(name);
}
ssize_t File::Impl::Read(char* buffer, size_t bytes) {
return file_api_->Read(buffer, bytes);
}
ssize_t File::Impl::Write(const char* buffer, size_t bytes) {
return file_api_->Write(buffer, bytes);
}
bool File::Impl::Close() {
return file_api_->Close();
}
bool File::Impl::Exists(const std::string& name) {
return FileSize(name) > 0;
}
bool File::Impl::Remove(const std::string& name) {
return file_api_->Remove(name);
}
ssize_t File::Impl::FileSize(const std::string& name) {
return file_api_->FileSize(name);
}
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(); }
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 API implementation is needed by the CDM
return false;
}
bool File::List(const std::string& path, std::vector<std::string>* files) {
// Required for linkage only - no API implementation is needed by the CDM
return false;
}
bool File::CreateDirectory(std::string path) {
// Required for linkage only - no API implementation is needed by the CDM
return true;
}
bool File::IsDirectory(const std::string& path) {
// Required for linkage only - no API implementation is needed by the CDM
return false;
}
bool File::IsRegularFile(const std::string& path) {
// Required for linkage only - no API implementation is needed by the CDM
return false;
}
ssize_t File::FileSize(const std::string& path) {
return impl_->FileSize(path);
}
} // namespace wvcdm

View File

@@ -1,66 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "host_4_file_io_client.h"
#include <string.h>
#include <algorithm>
namespace wvcdm {
Host4FileIOClient::~Host4FileIOClient() {
if (file_io_ != NULL) Close();
}
bool Host4FileIOClient::Open(const std::string& name) {
if (host_ == NULL) return false;
if (file_io_ != NULL) return false;
file_io_ = host_->CreateFileIO(this);
file_io_->Open(name.data(), name.length());
return status_ == kSuccess;
}
bool Host4FileIOClient::Read(char* buffer, size_t buffer_size) {
if (file_io_ == NULL) return false;
buffer_ = buffer;
buffer_size_ = buffer_size;
file_io_->Read();
return status_ == kSuccess;
}
bool Host4FileIOClient::Write(const char* data, size_t data_size) {
if (file_io_ == NULL) return false;
file_io_->Write(reinterpret_cast<const uint8_t*>(data), data_size);
return status_ == kSuccess;
}
bool Host4FileIOClient::Close() {
if (file_io_ == NULL) return false;
file_io_->Close();
file_io_ = NULL;
status_ = kSuccess;
data_size_ = 0;
return true;
}
void Host4FileIOClient::OnOpenComplete(Status status) {
status_ = status;
data_size_ = 0;
}
void Host4FileIOClient::OnReadComplete(Status status, const uint8_t* data,
uint32_t data_size) {
status_ = status;
data_size_ = data_size;
if (buffer_ != NULL) {
memcpy(buffer_, data, std::min<size_t>(data_size, buffer_size_));
buffer_ = NULL;
buffer_size_ = 0;
}
}
void Host4FileIOClient::OnWriteComplete(Status status) {
status_ = status;
data_size_ = 0;
}
} // namespace wvcdm

View File

@@ -1,38 +0,0 @@
// 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

32
cdm/src/lock.cpp Normal file
View File

@@ -0,0 +1,32 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Lock class - provides a simple mutex implementation.
#include "lock.h"
#include <pthread.h>
namespace wvcdm {
struct Lock::Impl {
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);
}
} // namespace wvcdm

38
cdm/src/log.cpp Normal file
View File

@@ -0,0 +1,38 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Log - implemented using stderr.
#include "log.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
namespace wvcdm {
LogPriority g_cutoff = LOG_WARN;
void InitLogging() {}
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)) {
fprintf(stderr, "[FATAL:%s(%d)] Invalid log priority level: %d\n",
file, line, level);
return;
}
if (level > g_cutoff) return;
fprintf(stderr, "[%s:%s(%d)] ", severities[level], file, line);
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
putc('\n', stderr);
fflush(stderr);
}
} // namespace wvcdm

146
cdm/src/properties_ce.cpp Normal file
View File

@@ -0,0 +1,146 @@
// Copyright 2015 Google Inc. All Rights Reserved.
#include "properties.h"
#include "properties_ce.h"
#include "cdm_version.h"
#include "log.h"
// This anonymous namespace is shared between both the widevine namespace and
// wvcdm namespace objects below.
namespace {
bool use_secure_buffers_ = false;
bool use_fifo_ = false;
bool use_userspace_buffers_ = true;
widevine::Cdm::SecureOutputType secure_output_type_ =
widevine::Cdm::kNoSecureOutput;
widevine::Cdm::ClientInfo client_info_;
bool GetValue(const std::string& source, std::string* output) {
if (!output) {
return false;
}
*output = source;
return source.size() != 0;
}
} // namespace
namespace widevine {
// static
void PropertiesCE::SetSecureOutputType(
Cdm::SecureOutputType secure_output_type) {
secure_output_type_ = secure_output_type;
switch (secure_output_type) {
case Cdm::kOpaqueHandle:
use_secure_buffers_ = true;
use_fifo_ = false;
use_userspace_buffers_ = false;
break;
case Cdm::kDirectRender:
use_secure_buffers_ = false;
use_fifo_ = true;
use_userspace_buffers_ = false;
break;
case Cdm::kNoSecureOutput:
default:
use_secure_buffers_ = false;
use_fifo_ = false;
use_userspace_buffers_ = true;
break;
}
}
// static
Cdm::SecureOutputType PropertiesCE::GetSecureOutputType() {
return secure_output_type_;
}
// static
void PropertiesCE::SetClientInfo(const Cdm::ClientInfo& client_info) {
client_info_ = client_info;
}
// static
Cdm::ClientInfo PropertiesCE::GetClientInfo() {
return client_info_;
}
} // namespace widevine
namespace wvcdm {
// static
void Properties::Init() {
oem_crypto_use_secure_buffers_ = use_secure_buffers_;
oem_crypto_use_fifo_ = use_fifo_;
oem_crypto_use_userspace_buffers_ = use_userspace_buffers_;
use_certificates_as_identification_ = true;
security_level_path_backward_compatibility_support_ = false;
session_property_set_.reset(new CdmClientPropertySetMap());
}
// static
bool Properties::GetCompanyName(std::string* company_name) {
return GetValue(client_info_.company_name, company_name);
}
// static
bool Properties::GetModelName(std::string* model_name) {
return GetValue(client_info_.model_name, model_name);
}
// static
bool Properties::GetArchitectureName(std::string* arch_name) {
return GetValue(client_info_.arch_name, arch_name);
}
// static
bool Properties::GetDeviceName(std::string* device_name) {
return GetValue(client_info_.device_name, device_name);
}
// static
bool Properties::GetProductName(std::string* product_name) {
return GetValue(client_info_.product_name, product_name);
}
// static
bool Properties::GetBuildInfo(std::string* build_info) {
return GetValue(client_info_.build_info, build_info);
}
// static
bool Properties::GetWVCdmVersion(std::string* version) {
return GetValue(CDM_VERSION, version);
}
// static
bool Properties::GetDeviceFilesBasePath(CdmSecurityLevel security_level,
std::string* base_path) {
// A no-op, but successful.
base_path->clear();
return true;
}
// static
bool Properties::GetFactoryKeyboxPath(std::string* keybox) {
// Unused on CE devices.
return false;
}
// static
bool Properties::GetOEMCryptoPath(std::string* library_name) {
// Unused on CE devices.
return false;
}
// static
bool Properties::AlwaysUseKeySetIds() {
return true;
}
} // namespace wvcdm

View File

@@ -1,90 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "properties.h"
#include <string>
#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) {
LOGE("Properties::GetOEMCryptoPath: Unimplemented property");
return false;
}
} // namespace wvcdm

View File

@@ -1,422 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "wv_content_decryption_module_1.h"
#include <assert.h>
#include <string.h>
#include "initialization_data.h"
#include "log.h"
#include "OEMCryptoCENC.h"
#include "properties.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
namespace {
enum {
// individual error codes
kAttachEventListenerError = 0x0001,
// error classes to be OR'd with cdm engine result values
kOpenSessionErrorBase = 0x0100,
kGenerateKeyRequestErrorBase = 0x0200,
kAddKeyErrorBase = 0x0300,
};
const int kCdmPolicyTimerDurationSeconds = 5;
// 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 {
// cdm::ContentDecryptionModule_1 implementation.
WvContentDecryptionModule_1::WvContentDecryptionModule_1(cdm::Host_1* host)
: host_(host),
host_event_listener_(host, &cdm_engine_),
cdm_engine_(),
property_set_(),
timer_enabled_(false) {
HostClock::SetClockInterface(this);
}
WvContentDecryptionModule_1::~WvContentDecryptionModule_1() {
DisablePolicyTimer();
}
cdm::Status WvContentDecryptionModule_1::GenerateKeyRequest(
const char* type, int type_size, const uint8_t* init_data,
int init_data_size) {
LOGI("WvContentDecryptionModule_1::GenerateKeyRequest()");
CdmInitData init_data_internal(reinterpret_cast<const char*>(init_data),
init_data_size);
std::string type_string(type, type_size);
InitializationData initialization_data(type_string,
init_data_internal);
CdmKeyMessage key_request;
CdmSessionId session_id;
std::string security_level;
std::string privacy_mode;
std::string service_certificate;
host_->GetPlatformString("SecurityLevel", &security_level);
host_->GetPlatformString("PrivacyOn", &privacy_mode);
host_->GetPlatformString("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 (result == NEED_PROVISIONING) {
LOGI("Need to aquire a Device Certificate from the Provisioning Server");
return cdm::kNeedsDeviceCertificate;
}
if (result != NO_ERROR) {
host_->SendKeyError("", 0, cdm::kClientError,
kOpenSessionErrorBase | result);
return cdm::kSessionError;
}
if (!cdm_engine_.AttachEventListener(session_id, &host_event_listener_)) {
cdm_engine_.CloseSession(session_id);
host_->SendKeyError("", 0, cdm::kClientError, kAttachEventListenerError);
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, initialization_data, kLicenseTypeStreaming,
app_parameters, &key_request, &server_url, NULL);
if (KEY_MESSAGE != result) {
cdm_engine_.CloseSession(session_id);
host_->SendKeyError("", 0, cdm::kClientError,
kGenerateKeyRequestErrorBase | result);
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_1::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("WvContentDecryptionModule_1::AddKey()");
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;
}
host_->SendKeyError(session_id, session_id_size, cdm::kClientError,
kAddKeyErrorBase | response);
return cdm::kSessionError;
}
bool WvContentDecryptionModule_1::IsKeyValid(const uint8_t* key_id,
int key_id_size) {
KeyId key(reinterpret_cast<const char*>(key_id), key_id_size);
return cdm_engine_.IsKeyLoaded(key);
}
cdm::Status WvContentDecryptionModule_1::CloseSession(const char* session_id,
int session_id_size) {
LOGI("WvContentDecryptionModule_1::CloseSession()");
CdmSessionId session_id_internal(session_id, session_id_size);
return cdm_engine_.CloseSession(session_id_internal) == NO_ERROR
? cdm::kSuccess
: cdm::kSessionError;
}
void WvContentDecryptionModule_1::TimerExpired(void* context) {
LOGI("WvContentDecryptionModule_1::TimerExpired()");
if (timer_enabled_) {
cdm_engine_.OnTimerEvent();
host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, NULL);
}
}
cdm::Status WvContentDecryptionModule_1::Decrypt(
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block) {
LOGI("WvContentDecryptionModule_1::Decrypt()");
if (static_cast<size_t>(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<const char*>(encrypted_buffer.key_id),
encrypted_buffer.key_id_size);
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
NULL);
parameters.is_secure = Properties::oem_crypto_use_secure_buffers();
if (encrypted_buffer.num_subsamples)
return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, decrypted_block);
else
return DoDecrypt(parameters, iv, encrypted_buffer, decrypted_block);
}
// 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_1::DecryptDecodeAndRenderFrame(
const cdm::InputBuffer& encrypted_buffer) {
LOGI("WvContentDecryptionModule_1::DecryptDecodeAndRenderFrame()");
if (static_cast<size_t>(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<const char*>(encrypted_buffer.key_id),
encrypted_buffer.key_id_size);
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
NULL);
if (encrypted_buffer.num_subsamples)
return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, NULL);
else
return DoDecrypt(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_1::DecryptDecodeAndRenderSamples(
const cdm::InputBuffer& encrypted_buffer) {
LOGI("WvContentDecryptionModule_1::DecryptDecodeAndRenderSamples()");
if (static_cast<size_t>(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<const char*>(encrypted_buffer.key_id),
encrypted_buffer.key_id_size);
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
NULL);
parameters.is_video = false; // override the default true value for audio.
if (encrypted_buffer.num_subsamples)
return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, NULL);
else
return DoDecrypt(parameters, iv, encrypted_buffer, NULL);
}
void WvContentDecryptionModule_1::Destroy() { delete this; }
// Provisioning related methods
cdm::Status WvContentDecryptionModule_1::GetProvisioningRequest(
std::string* request, std::string* provisioning_server_url) {
CdmCertificateType cert_type = kCertificateWidevine;
std::string cert_authority;
if (cdm_engine_.GetProvisioningRequest(
cert_type, cert_authority,
static_cast<CdmProvisioningRequest*>(request),
provisioning_server_url) == NO_ERROR) {
return cdm::kSuccess;
}
return cdm::kSessionError;
}
cdm::Status WvContentDecryptionModule_1::HandleProvisioningResponse(
std::string& response) {
std::string cert, wrapped_key;
if (cdm_engine_.HandleProvisioningResponse(
static_cast<CdmProvisioningRequest&>(response), &cert,
&wrapped_key) == NO_ERROR) {
return cdm::kSuccess;
}
return cdm::kSessionError;
}
void WvContentDecryptionModule_1::EnablePolicyTimer() {
LOGI("WvContentDecryptionModule_1::EnablePolicyTimer()");
if (!timer_enabled_) {
timer_enabled_ = true;
host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, NULL);
}
}
void WvContentDecryptionModule_1::DisablePolicyTimer() {
LOGI("WvContentDecryptionModule_1::DisablePolicyTimer()");
timer_enabled_ = false;
}
cdm::Status WvContentDecryptionModule_1::DoSubsampleDecrypt(
CdmDecryptionParameters& parameters, std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block) {
if (!encrypted_buffer.subsamples)
return cdm::kDecryptError;
size_t output_size = 0;
for (uint32_t i = 0; i < encrypted_buffer.num_subsamples; ++i) {
const cdm::SubsampleEntry& subsample = encrypted_buffer.subsamples[i];
output_size += subsample.cipher_bytes + subsample.clear_bytes;
}
assert(output_size ==
encrypted_buffer.data_size - encrypted_buffer.data_offset);
SetSizesAndAllocate(output_size, parameters, decrypted_block);
size_t offset = 0;
size_t encrypted_offset = 0;
uint32_t block_ctr = 0;
const cdm::SubsampleEntry* subsamples = encrypted_buffer.subsamples;
for (uint32_t 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 (bytes == 0) continue;
parameters.is_encrypted = is_encrypted;
parameters.subsample_flags =
((offset == 0) ? OEMCrypto_FirstSubsample : 0) |
((offset + bytes == output_size) ? OEMCrypto_LastSubsample : 0);
cdm::Status status =
DecryptAndUpdateCounters(parameters, iv, encrypted_buffer,
bytes, decrypted_block, offset,
encrypted_offset, block_ctr);
if (status != cdm::kSuccess)
return status;
}
}
return cdm::kSuccess;
}
cdm::Status WvContentDecryptionModule_1::DoDecrypt(
CdmDecryptionParameters& parameters, std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block) {
assert(!encrypted_buffer.subsamples);
size_t output_size =
encrypted_buffer.data_size - encrypted_buffer.data_offset;
SetSizesAndAllocate(output_size, parameters, decrypted_block);
size_t offset = 0;
size_t encrypted_offset = 0;
uint32_t block_ctr = 0;
parameters.is_encrypted = true;
parameters.subsample_flags =
OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample;
return DecryptAndUpdateCounters(parameters, iv, encrypted_buffer,
output_size, decrypted_block, offset,
encrypted_offset, block_ctr);
}
void WvContentDecryptionModule_1::SetSizesAndAllocate(
size_t output_size,
CdmDecryptionParameters& parameters,
cdm::DecryptedBlock* decrypted_block) {
parameters.decrypt_buffer_length = output_size;
if (decrypted_block) {
cdm::Buffer* buffer = host_->Allocate(output_size);
buffer->SetSize(output_size);
decrypted_block->SetDecryptedBuffer(buffer);
parameters.decrypt_buffer = buffer->Data();
} else {
parameters.decrypt_buffer = NULL;
}
}
cdm::Status WvContentDecryptionModule_1::DecryptAndUpdateCounters(
CdmDecryptionParameters& parameters,
std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
const size_t bytes,
cdm::DecryptedBlock* decrypted_block,
size_t& offset,
size_t& encrypted_offset,
uint32_t& block_ctr) {
if (parameters.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];
parameters.decrypt_buffer_offset = offset;
parameters.encrypt_length = bytes;
parameters.block_offset = encrypted_offset % kIvSize;
offset += bytes;
if (parameters.is_encrypted) encrypted_offset += bytes;
CdmSessionId session_id; // cdm_engine will locate via key_id.
CdmResponseType status = cdm_engine_.Decrypt(session_id, parameters);
switch (status) {
case wvcdm::NEED_KEY:
return cdm::kNoKey;
case wvcdm::NO_ERROR:
return cdm::kSuccess;
default:
return cdm::kDecryptError;
}
}
int64_t WvContentDecryptionModule_1::GetCurrentTimeInSeconds() {
return host_->GetCurrentWallTimeInSeconds();
}
File::Impl* WvContentDecryptionModule_1::NewFileImpl() {
return new File::Impl(host_);
}
} // namespace wvcdm

View File

@@ -1,597 +0,0 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#include "wv_content_decryption_module_4.h"
#include <assert.h>
#include "log.h"
#include "properties.h"
#include "wv_cdm_constants.h"
namespace {
enum {
// individual error codes
kAttachEventListenerError = 0x0001,
kLicenseExpiredNotification = 0x0002,
kSessionNotFoundError = 0x0003,
kSessionAlreadyExistsError = 0x0004,
kInvalidParameter = 0x0005,
kNotImplemented = 0x0006,
// error classes to be OR'd with cdm engine result values
kOpenSessionErrorBase = 0x0100,
kGenerateKeyRequestErrorBase = 0x0200,
kGenerateRenewalRequestErrorBase = 0x0300,
kUpdateSessionErrorBase = 0x0400,
kRestoreKeyErrorBase = 0x0500,
kOpenKeySetSessionErrorBase = 0x0600,
kGetProvisioningRequestErrorBase = 0x0700,
kHandleProvisioningResponseErrorBase = 0x0800,
};
const int kCdmPolicyTimerDurationSeconds = 5;
// 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;
void Ctr128Add(size_t block_count, uint8_t* counter) {
assert(NULL != counter);
if (0 == block_count) return;
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;
}
}
}
} // namespace
namespace wvcdm {
// cdm::ContentDecryptionModule_4 implementation.
WvContentDecryptionModule_4::WvContentDecryptionModule_4(cdm::Host_4* host)
: host_(host), timer_enabled_(false) {
HostClock::SetClockInterface(this);
}
WvContentDecryptionModule_4::~WvContentDecryptionModule_4() {
DisablePolicyTimer();
}
void WvContentDecryptionModule_4::CreateSession(uint32_t session_id,
const char* mime_type,
uint32_t mime_type_size,
const uint8_t* init_data,
uint32_t init_data_size,
cdm::SessionType session_type) {
if (session_type == cdm::kProvisioning) {
// CreateProvisionSession() dispatches its own errors to the host.
CreateProvisionSession(session_id);
} else {
CdmLicenseType license_type;
switch (session_type) {
case cdm::kTemporary:
license_type = kLicenseTypeStreaming;
break;
case cdm::kPersistent:
license_type = kLicenseTypeOffline;
break;
default:
LOGE(
"WvContentDecryptionModule_4::CreateSession: Unsupported session "
"type ",
session_type);
host_->OnSessionError(session_id, cdm::kSessionError,
kInvalidParameter);
return;
}
if (!CallOpenSession(session_id)) {
// CallOpenSession() dispatches its own errors to the host.
return;
}
CdmSessionId internal_session_id = session_map_[session_id].webid();
CdmKeySetId empty_key_set_id;
std::string mime_type_string(mime_type, mime_type_size);
CdmInitData init_data_internal(reinterpret_cast<const char*>(init_data),
init_data_size);
InitializationData initialization_data(mime_type_string,
init_data_internal);
CdmAppParameterMap empty_app_parameters;
CdmKeyMessage key_request;
std::string server_url;
CdmKeySetId key_set_id;
CdmResponseType result = cdm_engine_.GenerateKeyRequest(
internal_session_id, empty_key_set_id, initialization_data,
license_type, empty_app_parameters, &key_request, &server_url,
&key_set_id);
if (KEY_MESSAGE == result) {
if (session_type == cdm::kPersistent) {
host_->OnSessionCreated(session_id, key_set_id.c_str(),
key_set_id.length());
} else {
host_->OnSessionCreated(session_id, internal_session_id.c_str(),
internal_session_id.length());
}
host_->OnSessionMessage(session_id, key_request.c_str(),
key_request.length(), server_url.c_str(),
server_url.length());
} else {
cdm_engine_.CloseSession(internal_session_id);
session_map_.erase(session_id);
host_->OnSessionError(session_id, cdm::kSessionError,
kGenerateKeyRequestErrorBase | result);
}
}
}
void WvContentDecryptionModule_4::LoadSession(uint32_t session_id,
const char* web_session_id,
uint32_t web_session_id_length) {
if (!CallOpenSession(session_id)) {
// CallOpenSession() dispatches its own errors to the host.
return;
}
CdmSessionId internal_session_id = session_map_[session_id].webid();
CdmKeySetId key_set_id(web_session_id, web_session_id_length);
CdmResponseType result =
cdm_engine_.RestoreKey(internal_session_id, key_set_id);
if (result != KEY_ADDED) {
cdm_engine_.CloseSession(internal_session_id);
session_map_.erase(session_id);
host_->OnSessionError(session_id, cdm::kSessionError,
kRestoreKeyErrorBase | result);
return;
}
host_->OnSessionCreated(session_id, web_session_id, web_session_id_length);
}
void WvContentDecryptionModule_4::UpdateSession(uint32_t session_id,
const uint8_t* response,
uint32_t response_size) {
LOGI("WvContentDecryptionModule_4::UpdateSession()");
if (session_map_.count(session_id) == 0) {
host_->OnSessionError(session_id, cdm::kSessionError,
kSessionNotFoundError);
return;
}
const InternalSession& session = session_map_[session_id];
if (session.is_provision()) {
// UpdateProvisionSession() dispatches its own errors to the host.
UpdateProvisionSession(session_id, response, response_size);
} else {
bool is_release = session.is_release();
CdmSessionId session_id_internal;
CdmKeySetId key_set_id;
if (!is_release) {
session_id_internal = session.webid();
} else {
key_set_id = session.webid();
}
CdmKeyResponse key_data((const char*)response, response_size);
CdmResponseType status =
cdm_engine_.AddKey(session_id_internal, key_data, &key_set_id);
if (status != KEY_ADDED) {
host_->OnSessionError(session_id, cdm::kSessionError,
status | kUpdateSessionErrorBase);
return;
}
if (!is_release) EnablePolicyTimer();
host_->OnSessionUpdated(session_id);
}
}
bool WvContentDecryptionModule_4::IsKeyValid(const uint8_t* key_id,
int key_id_size) {
KeyId cdm_key_id(reinterpret_cast<const char*>(key_id), key_id_size);
return cdm_engine_.IsKeyLoaded(cdm_key_id);
}
void WvContentDecryptionModule_4::ReleaseSession(uint32_t session_id) {
if (session_map_.count(session_id) == 0) {
host_->OnSessionError(session_id, cdm::kSessionError,
kSessionNotFoundError);
return;
}
if (session_map_[session_id].is_release()) {
cdm_engine_.CloseKeySetSession(session_map_[session_id].webid());
} else if (session_map_[session_id].is_decrypt()) {
cdm_engine_.CloseSession(session_map_[session_id].webid());
}
host_->OnSessionClosed(session_id);
session_map_.erase(session_id);
}
void WvContentDecryptionModule_4::RemoveSession(
uint32_t session_id, const char* web_session_id,
uint32_t web_session_id_length) {
if (session_map_.count(session_id) != 0) {
host_->OnSessionError(session_id, cdm::kSessionError,
kSessionAlreadyExistsError);
return;
}
CdmKeySetId key_set_id(web_session_id, web_session_id_length);
CdmResponseType result = cdm_engine_.OpenKeySetSession(key_set_id);
if (result != NO_ERROR) {
host_->OnSessionError(session_id, cdm::kSessionError,
kOpenKeySetSessionErrorBase | result);
return;
}
session_map_[session_id] =
InternalSession(key_set_id, InternalSession::kRelease);
CdmSessionId empty_session_id;
InitializationData empty_initialization_data;
CdmAppParameterMap empty_app_parameters;
CdmKeyMessage key_request;
std::string server_url;
result = cdm_engine_.GenerateKeyRequest(
empty_session_id, key_set_id, empty_initialization_data,
kLicenseTypeRelease, empty_app_parameters, &key_request, &server_url,
NULL);
if (KEY_MESSAGE == result) {
host_->OnSessionCreated(session_id, key_set_id.c_str(),
key_set_id.length());
host_->OnSessionMessage(session_id, key_request.c_str(),
key_request.length(), server_url.c_str(),
server_url.length());
} else {
cdm_engine_.CloseKeySetSession(key_set_id);
host_->OnSessionError(session_id, cdm::kSessionError,
kGenerateKeyRequestErrorBase | result);
}
}
cdm::Status WvContentDecryptionModule_4::UsePrivacyMode() {
if (!session_map_.empty()) return cdm::kSessionError;
property_set_.set_use_privacy_mode(true);
return cdm::kSuccess;
}
cdm::Status WvContentDecryptionModule_4::SetServerCertificate(
const uint8_t* server_certificate_data,
uint32_t server_certificate_data_size) {
if (!session_map_.empty()) return cdm::kSessionError;
cdm::Status result = UsePrivacyMode();
if (result != cdm::kSuccess) return result;
std::string server_certificate(
reinterpret_cast<const char*>(server_certificate_data),
server_certificate_data_size);
property_set_.set_service_certificate(server_certificate);
return cdm::kSuccess;
}
void WvContentDecryptionModule_4::TimerExpired(void* context) {
LOGI("WvContentDecryptionModule_4::TimerExpired()");
if (timer_enabled_) {
cdm_engine_.OnTimerEvent();
host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, NULL);
}
}
cdm::Status WvContentDecryptionModule_4::Decrypt(
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block) {
LOGI("WvContentDecryptionModule_4::Decrypt()");
if (static_cast<size_t>(encrypted_buffer.iv_size) != KEY_IV_SIZE)
return cdm::kDecryptError;
std::vector<uint8_t> iv(encrypted_buffer.iv,
encrypted_buffer.iv + encrypted_buffer.iv_size);
KeyId key_id(reinterpret_cast<const char*>(encrypted_buffer.key_id),
encrypted_buffer.key_id_size);
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
NULL);
parameters.is_secure = Properties::oem_crypto_use_secure_buffers();
if (encrypted_buffer.num_subsamples)
return DoSubsampleDecrypt(parameters, iv, encrypted_buffer,
decrypted_block);
else
return DoDecrypt(parameters, iv, encrypted_buffer, decrypted_block);
}
cdm::Status WvContentDecryptionModule_4::DecryptDecodeAndRender(
const cdm::InputBuffer& encrypted_buffer, cdm::StreamType stream_type) {
LOGI("WvContentDecryptionModule_4::DecryptDecodeAndRender()");
if (static_cast<size_t>(encrypted_buffer.iv_size) != KEY_IV_SIZE)
return cdm::kDecryptError;
std::vector<uint8_t> iv(encrypted_buffer.iv,
encrypted_buffer.iv + encrypted_buffer.iv_size);
KeyId key_id(reinterpret_cast<const char*>(encrypted_buffer.key_id),
encrypted_buffer.key_id_size);
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
NULL);
parameters.is_video = (stream_type == cdm::kStreamTypeVideo);
if (encrypted_buffer.num_subsamples)
return DoSubsampleDecrypt(parameters, iv, encrypted_buffer, NULL);
else
return DoDecrypt(parameters, iv, encrypted_buffer, NULL);
}
void WvContentDecryptionModule_4::Destroy() { delete this; }
File::Impl* WvContentDecryptionModule_4::NewFileImpl() {
return new File::Impl(host_);
}
cdm::Status WvContentDecryptionModule_4::DoSubsampleDecrypt(
CdmDecryptionParameters& parameters, std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block) {
if (!encrypted_buffer.subsamples) return cdm::kDecryptError;
size_t output_size = 0;
for (uint32_t i = 0; i < encrypted_buffer.num_subsamples; ++i) {
const cdm::SubsampleEntry& subsample = encrypted_buffer.subsamples[i];
output_size += subsample.cipher_bytes + subsample.clear_bytes;
}
assert(output_size ==
encrypted_buffer.data_size - encrypted_buffer.data_offset);
SetSizesAndAllocate(output_size, parameters, decrypted_block);
size_t offset = 0;
size_t encrypted_offset = 0;
uint32_t block_ctr = 0;
const cdm::SubsampleEntry* subsamples = encrypted_buffer.subsamples;
for (uint32_t 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 (bytes == 0) continue;
parameters.is_encrypted = is_encrypted;
parameters.subsample_flags =
((offset == 0) ? OEMCrypto_FirstSubsample : 0) |
((offset + bytes == output_size) ? OEMCrypto_LastSubsample : 0);
cdm::Status status = DecryptAndUpdateCounters(
parameters, iv, encrypted_buffer, bytes, decrypted_block, offset,
encrypted_offset, block_ctr);
if (status != cdm::kSuccess) return status;
}
}
return cdm::kSuccess;
}
cdm::Status WvContentDecryptionModule_4::DoDecrypt(
CdmDecryptionParameters& parameters, std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer,
cdm::DecryptedBlock* decrypted_block) {
assert(!encrypted_buffer.subsamples);
size_t output_size =
encrypted_buffer.data_size - encrypted_buffer.data_offset;
SetSizesAndAllocate(output_size, parameters, decrypted_block);
size_t offset = 0;
size_t encrypted_offset = 0;
uint32_t block_ctr = 0;
parameters.is_encrypted = true;
parameters.subsample_flags =
OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample;
return DecryptAndUpdateCounters(parameters, iv, encrypted_buffer, output_size,
decrypted_block, offset, encrypted_offset,
block_ctr);
}
void WvContentDecryptionModule_4::SetSizesAndAllocate(
size_t output_size, CdmDecryptionParameters& parameters,
cdm::DecryptedBlock* decrypted_block) {
parameters.decrypt_buffer_length = output_size;
if (decrypted_block) {
cdm::Buffer* buffer = host_->Allocate(output_size);
buffer->SetSize(output_size);
decrypted_block->SetDecryptedBuffer(buffer);
parameters.decrypt_buffer = buffer->Data();
} else {
parameters.decrypt_buffer = NULL;
}
}
cdm::Status WvContentDecryptionModule_4::DecryptAndUpdateCounters(
CdmDecryptionParameters& parameters, std::vector<uint8_t>& iv,
const cdm::InputBuffer& encrypted_buffer, const size_t bytes,
cdm::DecryptedBlock* decrypted_block, size_t& offset,
size_t& encrypted_offset, uint32_t& block_ctr) {
if (parameters.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];
parameters.decrypt_buffer_offset = offset;
parameters.encrypt_length = bytes;
parameters.block_offset = encrypted_offset % kIvSize;
offset += bytes;
if (parameters.is_encrypted) encrypted_offset += bytes;
CdmSessionId session_id; // cdm_engine will locate via key_id.
CdmResponseType status = cdm_engine_.Decrypt(session_id, parameters);
switch (status) {
case wvcdm::NEED_KEY:
return cdm::kNoKey;
case wvcdm::NO_ERROR:
return cdm::kSuccess;
default:
return cdm::kDecryptError;
}
}
void WvContentDecryptionModule_4::EnablePolicyTimer() {
LOGI("WvContentDecryptionModule_4::EnablePolicyTimer()");
if (!timer_enabled_) {
timer_enabled_ = true;
host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, NULL);
}
}
void WvContentDecryptionModule_4::DisablePolicyTimer() {
LOGI("WvContentDecryptionModule_4::DisablePolicyTimer()");
timer_enabled_ = false;
}
void WvContentDecryptionModule_4::CreateProvisionSession(uint32_t session_id) {
CdmCertificateType cert_type = kCertificateWidevine;
std::string cert_authority;
CdmProvisioningRequest request;
std::string default_url;
CdmResponseType status = cdm_engine_.GetProvisioningRequest(
cert_type, cert_authority, &request, &default_url);
if (status != NO_ERROR) {
host_->OnSessionError(session_id, cdm::kSessionError,
kGetProvisioningRequestErrorBase | status);
return;
}
std::string blank_web_id;
session_map_[session_id] =
InternalSession(blank_web_id, InternalSession::kProvision);
host_->OnSessionCreated(session_id, blank_web_id.c_str(),
blank_web_id.length());
host_->OnSessionMessage(session_id, request.c_str(), request.length(),
default_url.c_str(), default_url.length());
}
void WvContentDecryptionModule_4::UpdateProvisionSession(
uint32_t session_id, const uint8_t* response, uint32_t response_size) {
CdmProvisioningResponse cdm_response(reinterpret_cast<const char*>(response),
response_size);
std::string cert;
std::string wrapped_key;
CdmResponseType status =
cdm_engine_.HandleProvisioningResponse(cdm_response, &cert, &wrapped_key);
if (status != NO_ERROR) {
host_->OnSessionError(session_id, cdm::kSessionError,
kHandleProvisioningResponseErrorBase | status);
return;
}
host_->OnSessionUpdated(session_id);
}
bool WvContentDecryptionModule_4::CallOpenSession(uint32_t session_id) {
CdmSessionId internal_session_id;
if (session_map_.count(session_id) != 0) {
host_->OnSessionError(session_id, cdm::kSessionError,
kSessionAlreadyExistsError);
return false;
}
CdmResponseType result = cdm_engine_.OpenSession(
"com.widevine.alpha", &property_set_, &internal_session_id);
if (result != NO_ERROR) {
if (result == NEED_PROVISIONING) {
host_->OnSessionError(session_id, cdm::kNeedsDeviceCertificate,
kOpenSessionErrorBase | result);
} else {
host_->OnSessionError(session_id, cdm::kSessionError,
kOpenSessionErrorBase | result);
}
return false;
}
if (!cdm_engine_.AttachEventListener(internal_session_id, this)) {
cdm_engine_.CloseSession(internal_session_id);
host_->OnSessionError(session_id, cdm::kSessionError,
kAttachEventListenerError);
return false;
}
session_map_[session_id] =
InternalSession(internal_session_id, InternalSession::kDecrypt);
return true;
}
int64_t WvContentDecryptionModule_4::GetCurrentTimeInSeconds() {
return host_->GetCurrentWallTimeInSeconds();
}
void WvContentDecryptionModule_4::OnEvent(const CdmSessionId& session_id,
CdmEventType cdm_event) {
bool found = false;
std::map<uint32_t, InternalSession>::iterator session_pair_iterator;
uint32_t external_session_id = 0;
for (session_pair_iterator = session_map_.begin();
session_pair_iterator != session_map_.end(); ++session_pair_iterator) {
if (session_pair_iterator->second.webid() == session_id) {
found = true;
external_session_id = session_pair_iterator->first;
break;
}
}
if (!found) {
LOGE("Unmapped Session Event on Session %s", session_id.c_str());
host_->OnSessionError(0, cdm::kSessionError, kSessionNotFoundError);
return;
}
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) {
LOGI("Renewal created");
host_->OnSessionMessage(external_session_id, cdm_message.c_str(),
cdm_message.length(), server_url.c_str(),
server_url.length());
} else {
LOGD("Error on Generating a Renewal Request!");
host_->OnSessionError(external_session_id, cdm::kSessionError,
kGenerateRenewalRequestErrorBase | result);
}
break;
}
case LICENSE_EXPIRED_EVENT: {
LOGD("License Expired Event");
host_->OnSessionError(external_session_id, cdm::kSessionError,
kLicenseExpiredNotification);
break;
}
}
}
} // namespace wvcdm

View File

@@ -1,709 +0,0 @@
// 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.
#include "cdm_test_config.h"
#include "test_host_1.h"
#include "test_util.h"
#include <getopt.h>
#include <gtest/gtest.h>
#include "clock.h"
#include "config_test_env.h"
#include "content_decryption_module.h"
#include "device_cert.h"
#include "license_request.h"
#include "log.h"
#include "properties.h"
#include "scoped_ptr.h"
#include "string_conversions.h"
#include "url_request.h"
static const int kTestPolicyRenewalDelaySeconds = 180;
static const int kDelayWaitToForRenewalMessageSeconds = 2;
static const int kHttpOk = 200;
static const int kHttpBadGateway = 502;
static const int kNumRetries = 5;
static const int kRetryBaseDelaySeconds = 3;
namespace {
// Default key system identifier.
const char kKeySystemWidevine[] = "com.widevine.alpha";
// Default mime type for key request generation.
const char kMimeType[] = "video/mp4";
// Key ID of key used to encrypt the test content.
// This is used to look up the content key.
const std::vector<uint8_t> kTestKeyId =
wvcdm::a2b_hex("371ea35e1a985d75d198a7f41020dc23");
// Dummy encrypted data.
const std::vector<uint8_t> kInputVector1 = wvcdm::a2b_hex(
"64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36"
"17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d"
"7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab"
"ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c"
"2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4"
"9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4"
"6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012"
"c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685");
const std::vector<uint8_t> kIv1 =
wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f");
// Expected output for kInputVector1.
const std::vector<uint8_t> kOutputVector1 = wvcdm::a2b_hex(
"217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c"
"942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca"
"595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747"
"8df21fdc42f166880d97a2225cd5c9ea5e7b752f4cf81bbdbe98e542ee10e1c6"
"ad868a6ac55c10d564fc23b8acff407daaf4ed2743520e02cda9680d9ea88e91"
"029359c4cf5906b6ab5bf60fbb3f1a1c7c59acfc7e4fb4ad8e623c04d503a3dd"
"4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed"
"08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659");
// Dummy encrypted data. This is a combination of clear and encrypted data.
const std::vector<uint8_t> kInputVector2 = wvcdm::a2b_hex(
// subsample 0
"abcdef"
"53cc758763904ea5870458e6b23d36db1e6d7f7aaa2f3eeebb5393a7264991e7"
"ce4f57b198326e1a208a821799b2a29c90567ab57321b06e51fc20dc9bc5fc55"
"10720a8bb1f5e002c3e50ff70d2d806a9432cad237050d09581f5b0d59b00090"
"b3ad69b4087f5a155b17e13c44d33fa007475d207fc4ac2ef3b571ecb9"
// subsample 1
"0123456789"
"f3c852"
"ce00dc4806f0c6856ae1732e20308096478e1d822d75c2bb768119565d3bd6e6"
"901e36164f4802355ee758fc46ef6cf5f852dd5256c7b1e5f96d29"
// subsample 2
"deadbeefbaadf00d"
"3b20525d5e"
"78b8e5aa344d5c4e425e67ddf889ea7c4bb1d49af67eba67718b765e0a940402"
"8d306f4ce693ad6dc0a931d507fa14fff4d293d4170280b3e0fca2d628f722e8"
);
const std::vector<uint8_t> kIv2 =
wvcdm::a2b_hex("6ba18dd40f49da7f64c368e4db43fc88");
// Expected output for kInputVector2.
const std::vector<uint8_t> kOutputVector2 = wvcdm::a2b_hex(
// subsample 0
"abcdef"
"52e65334501acadf78e2b26460def3ac973771ed7c64001a2e82917342a7eab3"
"047f5e85449692fae8f677be425a47bdea850df5a3ffff17043afb1f2b437ab2"
"b1d5e0784c4ed8f97fc24b8f565e85ed63fb7d1365980d9aea7b8b58f488f83c"
"1ce80b6096c60f3b113c988ff185b26e798da8fc6f327e4ff00e4b3fbf"
// subsample 1
"0123456789"
"b1ed0a"
"a054bce40ccb0ebc70b181d1a12055f46ac55e29c7c2473a29d2a366d240ec48"
"7cede274f012813a877f99159e7062b6a37cfc9327a7bc2195814e"
// subsample 2
"deadbeefbaadf00d"
"653b818d1d"
"4ab9a9128361d8ca6a9d2766df5c096ee29f4f5204febdf217a94a5b560cd692"
"cc36d3e071df789fdeac2fb7ec6dcd7af94bb1f85c22025b25e702e38212b927"
);
// Dummy encrypted data. This will be decrypted with a data_offset
// instead of subsamples.
const std::vector<uint8_t> kInputVector3 = wvcdm::a2b_hex(
"64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36"
"17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d"
"7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab"
"ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c"
"2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4"
"9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4"
"6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012"
"c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685");
const std::vector<uint8_t> kIv3 =
wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f");
// The data_offset for kInputVector3.
const uint32_t kInputOffset3 = 9;
// Expected output for kInputVector3 offset by kInputOffset3.
const std::vector<uint8_t> kOutputVector3 = wvcdm::a2b_hex(
"19ab304b49908e2395b32f26bf471adf4a4bc92f9e999cca8476d24a257931b4"
"c5fd177693ed55e31cd2b85dc196b2b722cd8854eb9334f3dab0b5bd26aa5e66"
"a9d1cfbba877c9456b11dc99a6bdc7015ca1544f7ce66171a8179eca19efe515"
"8c4c1d0612dff64100387065da108fdbfcc14738202ac3d27520eb48c020ddb7"
"714dca22e5e2241aff6932dba1587a97ac1a952827d411d8582dfecc2e9e1494"
"644046ca7044bc41c3c5e0a3a405d5551f3f5bdd6f36042e2f0f3693778b9277"
"6ed8d106647a7539df7d30288803cd9ca1c274bebe688151c72b451f571a441f"
"83d0ff77d8d57dcb395122e175f4944569917627d6c3dc");
void* GetCdmHost(int host_interface_version, void* user_data) {
if (host_interface_version != cdm::Host_1::kVersion) return NULL;
return user_data;
}
} // namespace
namespace wvcdm {
class CdmApi1Test : public testing::Test {
public:
CdmApi1Test() : cdm_(NULL) {}
~CdmApi1Test() {}
protected:
virtual void SetUp() {
// Create the Host.
host_.reset(new TestHost_1());
// Set various parameters that the CDM will query.
host_->SetPlatformString("SecurityLevel", "L1");
host_->SetPlatformString("PrivacyOn", "False");
std::string cert(reinterpret_cast<const char*>(kDeviceCert),
kDeviceCertSize);
host_->SetPlatformString("DeviceCertificate", cert);
// Initialize the CDM module before creating a CDM instance.
InitializeCdmModule();
// Create the CDM.
cdm_ =
reinterpret_cast<cdm::ContentDecryptionModule_1*>(::CreateCdmInstance(
cdm::ContentDecryptionModule_1::kVersion, kKeySystemWidevine,
strlen(kKeySystemWidevine), GetCdmHost, host_.get()));
// Tell the Host about the CDM.
host_->SetCdmPtr(cdm_);
}
cdm::Status GenerateKeyRequest(const std::string& init_data) {
cdm::Status status = cdm_->GenerateKeyRequest(
kMimeType, strlen(kMimeType),
(const uint8_t*)init_data.data(), init_data.length());
return status;
}
cdm::Status GenerateKeyRequestWithMimeType(const std::string& mime_type) {
cdm::Status status = cdm_->GenerateKeyRequest(
mime_type.c_str(), mime_type.length(),
(const uint8_t*)g_key_id.data(), g_key_id.length());
return status;
}
// posts a request and extracts the drm message from the response
std::string GetKeyRequestResponse(const TestHost_1::KeyMessage& key_msg) {
std::string url;
if (key_msg.default_url.empty()) {
url = g_license_server + g_client_auth;
} else {
// Note that the client auth string is not appended when the CDM tells
// us what URL to use.
url = key_msg.default_url;
}
int status_code;
std::string response;
UrlRequest url_request(url);
for (int retries = 0; retries < kNumRetries; ++retries) {
EXPECT_TRUE(url_request.is_connected());
if (!url_request.is_connected()) {
return "";
}
url_request.PostRequest(key_msg.message);
int resp_bytes = url_request.GetResponse(&response);
status_code = url_request.GetStatusCode(response);
// Sometimes, the server returns "HTTP 502 bad gateway".
// If we treat this as a non-fatal error, we reduce test flakiness.
if (status_code != kHttpBadGateway) {
// Move on with normal processing.
break;
}
// Reconnect to the server and try again. Since the server's 502
// response could indicate a temporary failure due to load, we use
// an exponential backoff. Each time we reconnect, we delay by
// exactly twice as long as the last time. This is a simplified
// version of the delay strategy recommended by Google in the
// internal document "Rate Limiting in Google Applications" under
// the heading "Settings for client exponential backoff". We do
// not bother to fuzz the delay, since unit tests are not running
// simultaneously in large numbers like real clients would be.
LOGE("Bad gateway, retrying.");
sleep(kRetryBaseDelaySeconds << retries);
url_request.Reconnect();
}
// Some license servers return 400 for invalid message, some
// return 500; treat anything other than 200 as an invalid message.
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\n%s", drm_msg.size(),
HexEncode(reinterpret_cast<const uint8_t*>(drm_msg.data()),
drm_msg.size()).c_str());
return drm_msg;
}
}
void ProcessKeyResponse() {
TestHost_1::KeyMessage key_msg = host_->GetLastKeyMessage();
ASSERT_FALSE(key_msg.message.empty());
EXPECT_TRUE(key_msg.default_url.empty());
std::string drm_msg = GetKeyRequestResponse(key_msg);
EXPECT_EQ(cdm::kSuccess, AddKey(key_msg.session_id, drm_msg));
}
void ProcessKeyRenewalResponse() {
TestHost_1::KeyMessage key_msg = host_->GetLastKeyMessage();
ASSERT_FALSE(key_msg.message.empty());
EXPECT_FALSE(key_msg.default_url.empty());
std::string drm_msg = GetKeyRequestResponse(key_msg);
EXPECT_EQ(cdm::kSuccess, AddKey(key_msg.session_id, drm_msg));
}
void CloseSession(const std::string& session_id) {
cdm::Status status =
cdm_->CloseSession(session_id.data(), session_id.length());
EXPECT_EQ(cdm::kSuccess, status);
}
cdm::Status AddKey(const std::string& session_id,
const 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);
return status;
}
cdm::InputBuffer BuildInputBuffer(const std::vector<uint8_t>& encrypted,
const std::vector<uint8_t>& iv) {
cdm::InputBuffer buf;
buf.data = &encrypted[0];
buf.data_size = encrypted.size();
buf.key_id = &kTestKeyId[0];
buf.key_id_size = kTestKeyId.size();
buf.iv = &iv[0];
buf.iv_size = iv.size();
buf.data_offset = 0;
buf.timestamp = 10;
return buf;
}
cdm::InputBuffer BuildInputBuffer(
const std::vector<uint8_t>& encrypted,
const std::vector<uint8_t>& iv,
const std::vector<cdm::SubsampleEntry>& sub) {
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv);
buf.subsamples = &sub[0];
buf.num_subsamples = sub.size();
return buf;
}
cdm::InputBuffer BuildInputBuffer(
const std::vector<uint8_t>& encrypted,
const std::vector<uint8_t>& iv,
const uint32_t offset) {
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv);
buf.data_offset = offset;
return buf;
}
std::vector<cdm::SubsampleEntry> BuildMultipleSubsamples() {
std::vector<cdm::SubsampleEntry> sub;
sub.push_back(cdm::SubsampleEntry(3, 125));
sub.push_back(cdm::SubsampleEntry(5, 62));
sub.push_back(cdm::SubsampleEntry(8, 69));
return sub;
}
std::vector<cdm::SubsampleEntry> BuildSingleSubsample(size_t size) {
std::vector<cdm::SubsampleEntry> sub;
sub.push_back(cdm::SubsampleEntry(0, size));
return sub;
}
cdm::ContentDecryptionModule_1* cdm_; // owned by host_
wvcdm::scoped_ptr<TestHost_1> host_;
};
namespace {
class DummyCDM : public cdm::ContentDecryptionModule_1 {
public:
DummyCDM() : timer_fired_(false), last_context_(NULL) {}
virtual cdm::Status GenerateKeyRequest(const char*, int, const uint8_t*,
int) OVERRIDE {
return cdm::kSessionError;
}
virtual cdm::Status AddKey(const char*, int, const uint8_t*, int,
const uint8_t*, int) OVERRIDE {
return cdm::kSessionError;
}
virtual bool IsKeyValid(const uint8_t*, int) OVERRIDE { return false; }
virtual cdm::Status CloseSession(const char*, int) OVERRIDE {
return cdm::kSessionError;
}
virtual void TimerExpired(void* context) OVERRIDE {
timer_fired_ = true;
last_context_ = context;
}
virtual cdm::Status Decrypt(const cdm::InputBuffer&,
cdm::DecryptedBlock*) OVERRIDE {
return cdm::kSessionError;
}
virtual cdm::Status DecryptDecodeAndRenderFrame(
const cdm::InputBuffer&) OVERRIDE {
return cdm::kSessionError;
}
virtual cdm::Status DecryptDecodeAndRenderSamples(
const cdm::InputBuffer&) OVERRIDE {
return cdm::kSessionError;
}
virtual void Destroy() OVERRIDE { delete this; }
virtual cdm::Status GetProvisioningRequest(std::string*,
std::string*) OVERRIDE {
return cdm::kSessionError;
}
virtual cdm::Status HandleProvisioningResponse(std::string&) OVERRIDE {
return cdm::kSessionError;
}
bool TimerFired() const { return timer_fired_; }
void* LastTimerContext() const { return last_context_; }
void ResetTimerStatus() {
timer_fired_ = false;
last_context_ = NULL;
}
private:
bool timer_fired_;
void* last_context_;
};
} // namespace
TEST_F(CdmApi1Test, TestHostTimer) {
// Validate that the TestHost timers are processed in the correct order.
// To do this, we replace the cdm with a dummy that only tracks timers.
DummyCDM* cdm = new DummyCDM();
// The old CDM is destroyed by SetCdmPtr.
cdm_ = cdm;
host_->SetCdmPtr(cdm);
const double kTimerDelaySeconds = 1.0;
const int64_t kTimerDelayMs = kTimerDelaySeconds * 1000;
void* kCtx1 = reinterpret_cast<void*>(0x1);
void* kCtx2 = reinterpret_cast<void*>(0x2);
host_->SetTimer(kTimerDelayMs * 1, kCtx1);
host_->SetTimer(kTimerDelayMs * 2, kCtx2);
host_->FastForwardTime(kTimerDelaySeconds);
EXPECT_TRUE(cdm->TimerFired());
EXPECT_EQ(kCtx1, cdm->LastTimerContext());
cdm->ResetTimerStatus();
host_->FastForwardTime(kTimerDelaySeconds);
EXPECT_TRUE(cdm->TimerFired());
EXPECT_EQ(kCtx2, cdm->LastTimerContext());
cdm->ResetTimerStatus();
host_->FastForwardTime(kTimerDelaySeconds);
EXPECT_FALSE(cdm->TimerFired());
}
TEST_F(CdmApi1Test, DeviceCertificateTest) {
if (Properties::use_certificates_as_identification()) {
// Clear any existing device cert.
host_->SetPlatformString("DeviceCertificate", "");
ASSERT_EQ(cdm::kNeedsDeviceCertificate, GenerateKeyRequest(g_key_id));
// The Host must handle the certificate provisioning request.
std::string server_url;
std::string request;
cdm::Status status = cdm_->GetProvisioningRequest(&request, &server_url);
ASSERT_EQ(cdm::kSuccess, status);
UrlRequest url_request(server_url);
url_request.PostCertRequestInQueryString(request);
std::string message;
bool ok = url_request.GetResponse(&message);
ASSERT_TRUE(ok);
status = cdm_->HandleProvisioningResponse(message);
ASSERT_EQ(cdm::kSuccess, status);
// Now we are provisioned, so GKR should succeed.
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
} else {
LOGI(
"Skipping CdmApi1Test::DeviceCertificateTest because this platform "
"does not support device certificates.");
}
}
// Note that these tests, BaseMessageTest, NormalDecryption and RenewalTest,
// 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(CdmApi1Test, BaseMessageTest) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
}
TEST_F(CdmApi1Test, NormalDecryption) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
// Level 1 / Level 2 payload comes back in the cpu memory as cleartext.
std::vector<uint8_t> encrypted = kInputVector1;
std::vector<uint8_t> iv = kIv1;
std::vector<uint8_t> expected = kOutputVector1;
std::vector<cdm::SubsampleEntry> sub = BuildSingleSubsample(encrypted.size());
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(
0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size));
}
TEST_F(CdmApi1Test, NormalSubSampleDecryptionWithSubsampleInfo) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
// Level 1 / Level 2 payload comes back in the cpu memory as cleartext.
std::vector<uint8_t> encrypted = kInputVector2;
std::vector<uint8_t> iv = kIv2;
std::vector<uint8_t> expected = kOutputVector2;
std::vector<cdm::SubsampleEntry> sub = BuildMultipleSubsamples();
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(
0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size));
}
TEST_F(CdmApi1Test, NormalSubSampleDecryptionWithMissingSubsampleInfo) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
std::vector<uint8_t> encrypted = kInputVector2;
std::vector<uint8_t> iv = kIv2;
std::vector<uint8_t> expected = kOutputVector2;
std::vector<cdm::SubsampleEntry> sub = BuildMultipleSubsamples();
// Don't add these subsamples yet!
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv);
buf.num_subsamples = sub.size();
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kDecryptError, status);
// Add the subsamples pointer and expect success.
buf.subsamples = &sub[0];
status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(
0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size));
}
TEST_F(CdmApi1Test, DecryptWithDataOffset) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
std::vector<uint8_t> encrypted = kInputVector3;
std::vector<uint8_t> iv = kIv3;
std::vector<uint8_t> expected = kOutputVector3;
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, kInputOffset3);
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
cdm::Buffer *output_buf = output.DecryptedBuffer();
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(buf.data_size - buf.data_offset, output_buf->Size());
EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &expected[0],
output_buf->Size()));
}
TEST_F(CdmApi1Test, DecryptReturnsSizedBuffer) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
std::vector<uint8_t> encrypted = kInputVector1;
std::vector<uint8_t> iv = kIv1;
std::vector<uint8_t> expected = kOutputVector1;
std::vector<cdm::SubsampleEntry> sub = BuildSingleSubsample(encrypted.size());
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
cdm::Buffer* buffer = output.DecryptedBuffer();
EXPECT_NE((void*)NULL, buffer);
if (buffer) {
EXPECT_EQ(expected.size(), output.DecryptedBuffer()->Size());
}
}
TEST_F(CdmApi1Test, RenewalTest) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
// We expect that by the time we've added a key, the CDM has set a timer.
// Otherwise, it couldn't correctly handle renewal.
EXPECT_NE(0, host_->NumTimers());
host_->FastForwardTime(kTestPolicyRenewalDelaySeconds +
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.
ProcessKeyRenewalResponse();
}
TEST_F(CdmApi1Test, SecureDecryptionLevel1) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
// 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.
std::vector<uint8_t> encrypted = kInputVector1;
std::vector<uint8_t> iv = kIv1;
std::vector<cdm::SubsampleEntry> sub = BuildSingleSubsample(encrypted.size());
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
cdm::Status status;
status = cdm_->DecryptDecodeAndRenderSamples(buf);
EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kSuccess, status);
}
TEST_F(CdmApi1Test, SecureDecryptionLevel1WithSubsampleInfo) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
// 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.
std::vector<uint8_t> encrypted = kInputVector2;
std::vector<uint8_t> iv = kIv2;
std::vector<cdm::SubsampleEntry> sub = BuildMultipleSubsamples();
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
cdm::Status status;
status = cdm_->DecryptDecodeAndRenderSamples(buf);
EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kSuccess, status);
}
TEST_F(CdmApi1Test, SecureDecryptionLevel1WithMissingSubsampleInfo) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
ProcessKeyResponse();
std::vector<uint8_t> encrypted = kInputVector2;
std::vector<uint8_t> iv = kIv2;
std::vector<cdm::SubsampleEntry> sub = BuildMultipleSubsamples();
// Don't add these subsamples yet!
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv);
buf.num_subsamples = sub.size();
cdm::Status status;
status = cdm_->DecryptDecodeAndRenderSamples(buf);
EXPECT_EQ(cdm::kDecryptError, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kDecryptError, status);
// Add the subsamples pointer and expect success.
buf.subsamples = &sub[0];
status = cdm_->DecryptDecodeAndRenderSamples(buf);
EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kSuccess, status);
}
TEST_F(CdmApi1Test, GenerateKeyRequestFailureSendsKeyError) {
// Pass a bogus key id and expect failure.
EXPECT_EQ(cdm::kSessionError, GenerateKeyRequest(""));
// Expect the CDM to pass a key error back to the host.
EXPECT_EQ(1, host_->KeyErrorsSize());
}
TEST_F(CdmApi1Test, AddKeyFailureSendsKeyError) {
EXPECT_EQ(cdm::kSuccess, GenerateKeyRequest(g_key_id));
// Get the message and response.
TestHost_1::KeyMessage key_msg = host_->GetLastKeyMessage();
EXPECT_TRUE(key_msg.default_url.empty());
std::string drm_msg = GetKeyRequestResponse(key_msg);
// Call AddKey with a bad session id and expect failure.
EXPECT_EQ(cdm::kSessionError, AddKey("BLAH", drm_msg));
// Expect the CDM to pass a key error back to the host.
EXPECT_EQ(1, host_->KeyErrorsSize());
// Call AddKey with a bad license and expect failure.
EXPECT_EQ(cdm::kSessionError, AddKey(key_msg.session_id, "BLAH"));
// Expect the CDM to pass one more key error back to the host.
EXPECT_EQ(2, host_->KeyErrorsSize());
}
TEST_F(CdmApi1Test, MimeTypeMatters) {
cdm::Status status;
status = GenerateKeyRequestWithMimeType("video/mp4");
ASSERT_EQ(cdm::kSuccess, status);
status = GenerateKeyRequestWithMimeType("video/webm");
ASSERT_EQ(cdm::kSuccess, status);
status = GenerateKeyRequestWithMimeType("video/blah");
ASSERT_EQ(cdm::kSessionError, status);
}
} // namespace wvcdm

View File

@@ -1,925 +0,0 @@
// 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.
#include "cdm_test_config.h"
#include "test_host_4.h"
#include "test_util.h"
#include <getopt.h>
#include <gtest/gtest.h>
#include "cdm_client_property_set.h"
#include "clock.h"
#include "config_test_env.h"
#include "content_decryption_module.h"
#include "device_cert.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_content_decryption_module_4.h"
static const int kTestPolicyRenewalDelaySeconds = 180;
static const int kDelayWaitToForRenewalMessageSeconds = 2;
static const int kHttpOk = 200;
static const int kSessionId1 = 1;
static const int kSessionId2 = 2;
static const int kSessionId3 = 3;
namespace {
// Default key system identifier.
const char kKeySystemWidevine[] = "com.widevine.alpha";
// Default mime type for session creation.
const char kMimeType[] = "video/mp4";
// Known filename for certificate manipulation.
const std::string kCertFilename = "cert.bin";
// Key ID of key used to encrypt the test content.
// This is used to look up the content key.
const std::vector<uint8_t> kTestKeyId =
wvcdm::a2b_hex("371ea35e1a985d75d198a7f41020dc23");
// Dummy encrypted data.
const std::vector<uint8_t> kInputVector1 = wvcdm::a2b_hex(
"64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36"
"17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d"
"7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab"
"ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c"
"2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4"
"9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4"
"6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012"
"c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685");
const std::vector<uint8_t> kIv1 =
wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f");
// Expected output for kInputVector1.
const std::vector<uint8_t> kOutputVector1 = wvcdm::a2b_hex(
"217ce9bde99bd91e9733a1a00b9b557ac3a433dc92633546156817fae26b6e1c"
"942ac20a89ff79f4c2f25fba99d6a44618a8c0420b27d54e3da17b77c9d43cca"
"595d259a1e4a8b6d7744cd98c5d3f921adc252eb7d8af6b916044b676a574747"
"8df21fdc42f166880d97a2225cd5c9ea5e7b752f4cf81bbdbe98e542ee10e1c6"
"ad868a6ac55c10d564fc23b8acff407daaf4ed2743520e02cda9680d9ea88e91"
"029359c4cf5906b6ab5bf60fbb3f1a1c7c59acfc7e4fb4ad8e623c04d503a3dd"
"4884604c8da8a53ce33db9ff8f1c5bb6bb97f37b39906bf41596555c1bcce9ed"
"08a899cd760ff0899a1170c2f224b9c52997a0785b7fe170805fd3e8b1127659");
// Dummy encrypted data. This is a combination of clear and encrypted data.
const std::vector<uint8_t> kInputVector2 = wvcdm::a2b_hex(
// subsample 0
"abcdef"
"53cc758763904ea5870458e6b23d36db1e6d7f7aaa2f3eeebb5393a7264991e7"
"ce4f57b198326e1a208a821799b2a29c90567ab57321b06e51fc20dc9bc5fc55"
"10720a8bb1f5e002c3e50ff70d2d806a9432cad237050d09581f5b0d59b00090"
"b3ad69b4087f5a155b17e13c44d33fa007475d207fc4ac2ef3b571ecb9"
// subsample 1
"0123456789"
"f3c852"
"ce00dc4806f0c6856ae1732e20308096478e1d822d75c2bb768119565d3bd6e6"
"901e36164f4802355ee758fc46ef6cf5f852dd5256c7b1e5f96d29"
// subsample 2
"deadbeefbaadf00d"
"3b20525d5e"
"78b8e5aa344d5c4e425e67ddf889ea7c4bb1d49af67eba67718b765e0a940402"
"8d306f4ce693ad6dc0a931d507fa14fff4d293d4170280b3e0fca2d628f722e8"
);
const std::vector<uint8_t> kIv2 =
wvcdm::a2b_hex("6ba18dd40f49da7f64c368e4db43fc88");
// Expected output for kInputVector2.
const std::vector<uint8_t> kOutputVector2 = wvcdm::a2b_hex(
// subsample 0
"abcdef"
"52e65334501acadf78e2b26460def3ac973771ed7c64001a2e82917342a7eab3"
"047f5e85449692fae8f677be425a47bdea850df5a3ffff17043afb1f2b437ab2"
"b1d5e0784c4ed8f97fc24b8f565e85ed63fb7d1365980d9aea7b8b58f488f83c"
"1ce80b6096c60f3b113c988ff185b26e798da8fc6f327e4ff00e4b3fbf"
// subsample 1
"0123456789"
"b1ed0a"
"a054bce40ccb0ebc70b181d1a12055f46ac55e29c7c2473a29d2a366d240ec48"
"7cede274f012813a877f99159e7062b6a37cfc9327a7bc2195814e"
// subsample 2
"deadbeefbaadf00d"
"653b818d1d"
"4ab9a9128361d8ca6a9d2766df5c096ee29f4f5204febdf217a94a5b560cd692"
"cc36d3e071df789fdeac2fb7ec6dcd7af94bb1f85c22025b25e702e38212b927"
);
// Dummy encrypted data. This will be decrypted with a data_offset
// instead of subsamples.
const std::vector<uint8_t> kInputVector3 = wvcdm::a2b_hex(
"64ab17b3e3dfab47245c7cce4543d4fc7a26dcf248f19f9b59f3c92601440b36"
"17c8ed0c96c656549e461f38708cd47a434066f8df28ccc28b79252eee3f9c2d"
"7f6c68ebe40141fe818fe082ca523c03d69ddaf183a93c022327fedc5582c5ab"
"ca9d342b71263a67f9cb2336f12108aaaef464f17177e44e9b0c4e56e61da53c"
"2150b4405cc82d994dfd9bf4087c761956d6688a9705db4cf350381085f383c4"
"9666d4aed135c519c1f0b5cba06e287feea96ea367bf54e7368dcf998276c6e4"
"6497e0c50e20fef74e42cb518fe7f22ef27202428688f86404e8278587017012"
"c1d65537c6cbd7dde04aae338d68115a9f430afc100ab83cdadf45dca39db685");
const std::vector<uint8_t> kIv3 =
wvcdm::a2b_hex("f6f4b1e600a5b67813ed2bded913ba9f");
// The data_offset for kInputVector3.
const uint32_t kInputOffset3 = 9;
// Expected output for kInputVector3 offset by kInputOffset3.
const std::vector<uint8_t> kOutputVector3 = wvcdm::a2b_hex(
"19ab304b49908e2395b32f26bf471adf4a4bc92f9e999cca8476d24a257931b4"
"c5fd177693ed55e31cd2b85dc196b2b722cd8854eb9334f3dab0b5bd26aa5e66"
"a9d1cfbba877c9456b11dc99a6bdc7015ca1544f7ce66171a8179eca19efe515"
"8c4c1d0612dff64100387065da108fdbfcc14738202ac3d27520eb48c020ddb7"
"714dca22e5e2241aff6932dba1587a97ac1a952827d411d8582dfecc2e9e1494"
"644046ca7044bc41c3c5e0a3a405d5551f3f5bdd6f36042e2f0f3693778b9277"
"6ed8d106647a7539df7d30288803cd9ca1c274bebe688151c72b451f571a441f"
"83d0ff77d8d57dcb395122e175f4944569917627d6c3dc");
// Dummy data used as a server certificate. Not a real cert.
const std::vector<uint8_t> kFakeServerCertficiate = wvcdm::a2b_hex(
"65393939a0e4a28fb07aa8237bdc9e2fd30fdf763061ed9ed91b368a2b78fc2d"
"15f0b6add5d56d05f7e5852aeae67de6127d6b61b39bb5d6e9657f352ba75e72"
"c436f334878568504f697ad01aa329efbadebc3b9aaf502ada9b8e6fcf066252"
"76690a0f50cd3852dd39f7c5444402f86831d8bb2cbbd7cba11ab1caa35445fe"
"35a332529845f2f4b5d5a0b1e2c0855fc2d644443eb967e1f9030fd0d9e6375b"
"a5100a997ff8a958606d59a00151d251eaf69f9e00465b5aa4c23ef33a11b05b"
"212c1ff830fcd095c681ef25a18db08d7bddfeee16763e9fec06daa8275de2b0"
"554a0bec821fb7bb6fbda081d8cecce6c51195e6b6a1c0fcbb2470bcff2a962e"
"57e767f83cc8b6c31d41d7c59526128f19cbf625fa4f5f382393a3bd2b76463a"
"fe97e4a6f30f631c83308aa5fdccc2d1765c113d474bf2496e03c030c18e5ca8"
"84cb98fa120804baa245682966926ccbf555450437de10e549afc088f8c36f63"
"8a943178bdd58e4ef1f7d501e2296bbe2df57ce8816d6ae9e7ab18b1e01dac9b"
"8c312298356cad58c6ed46b1cd3a895e496c66f5229da39e260a7c1f782653bd"
"ade5f6132fc4771bb8caca80eb063abb47144abc44aaba8d23fcc721199291b3"
"0b95bc7d310c3f90756916552151a6008feba73ddab87454822914732d6ee78a"
"3587f33c698b0ab90f01b38a71abd01660a5ef1473e4556aa8c17d34679065c5"
"689dd21026601e94146254346f4ccd979bc378046473b3de64b8654e18406e94"
"b673dc13fdcc70213365bd098f212ed7a973ef35da18e16b1c118f8d4eda0b39"
"ffb8084ca93a44923df8475e3feec6c0941a2a37d5df514e686dd182dc9ebbff"
"41d8d80aa39d059de3b7849d4e5946b09937c6e7a6f6392ea5e9370ba1867553"
"b716e31433c2e3ca3eff1e7c08a31b201fd18c363b8daeed59df7afb68ad5166"
"659914a2e137c9d2e5940aaa921694c8d84d5ea1c83486e473e3021cb825c0ed"
"f778c621598d35e44843cd53e32f90925e74bc8eb469889fb221a2c592b43d94"
"2079d6393ab76e47d30dbf837fa5ca07d7186710973c5bb17c04b3207a85a166"
"153053f6b0ec35c6b124efd43be274cdf8300234806a72231272d13b331cafd3"
"dab01002b5e1961a5f3221d4c589fac3e42c1d1de9f244090e31a08999d3434d"
"15a5159f09fa3307e0d9466ac40af63c0a62f8d2719e1df80bb24d4d7ab256f7"
"0906ee342a47a9bd6805d796cf928f0d5251701ba8e6888675b1b6fd03e77df0"
"8150495c778cb7942d8c060ace4b080ad22e44854988d5e2232f5dbcf2db559f"
"24bae8667c33cea77f1c6a58d9dd010363c233cff6d5d26f5f77230ee681456c"
"35a8f90d37c2fc0ab07a2a795431f829cca574fd37d8822e04fc500ba468f08a"
"556e53ba8992dd1ccbd44559a7b93bebc27cec81a834755cf110bce183481e42");
void* GetCdmHost(int host_interface_version, void* user_data) {
if (host_interface_version != cdm::Host_4::kVersion) return NULL;
return user_data;
}
} // namespace
namespace wvcdm {
class CdmApi4Test : public testing::Test {
public:
CdmApi4Test() : cdm_(NULL) {}
~CdmApi4Test() {}
protected:
virtual void SetUp() {
// Create the Host.
host_.reset(new TestHost_4());
// Load the device cert that was already "saved" to the device.
std::string cert(reinterpret_cast<const char*>(kDeviceCert),
kDeviceCertSize);
host_->file_store[kCertFilename] = cert;
// Initialize the CDM module before creating a CDM instance.
InitializeCdmModule();
// Create the CDM.
cdm_ =
reinterpret_cast<cdm::ContentDecryptionModule_4*>(::CreateCdmInstance(
cdm::ContentDecryptionModule_4::kVersion, kKeySystemWidevine,
strlen(kKeySystemWidevine), GetCdmHost, host_.get()));
if (cdm_ == NULL) {
FAIL() << "Fatal CDM creation error!";
}
// Tell the Host about the CDM.
host_->SetCdmPtr(cdm_);
}
void CreateSession(const uint32_t session_id, const std::string& init_data,
cdm::SessionType session_type) {
cdm_->CreateSession(session_id, kMimeType, strlen(kMimeType),
reinterpret_cast<const uint8_t*>(init_data.data()),
init_data.length(), session_type);
}
void CreateSessionWithMimeType(const uint32_t session_id,
const std::string& mime_type) {
cdm_->CreateSession(session_id, mime_type.c_str(), mime_type.length(),
reinterpret_cast<const uint8_t*>(g_key_id.data()),
g_key_id.length(), cdm::kTemporary);
}
void LoadSession(uint32_t session_id, const std::string& web_session_id) {
cdm_->LoadSession(session_id, web_session_id.data(),
web_session_id.length());
}
// posts a request and extracts the drm message from the response
std::string GetKeyRequestResponse(
const TestHost_4::SessionMessage& session_msg,
const std::string& default_url, bool is_provision) {
std::string url;
if (session_msg.default_url.empty()) {
url = default_url;
} else {
// Note that the client auth string is assumed to already be appended when
// the CDM tells us what URL to use.
url = session_msg.default_url;
}
UrlRequest url_request(url);
EXPECT_TRUE(url_request.is_connected());
if (!url_request.is_connected()) {
return "";
}
if (!is_provision) {
url_request.PostRequest(session_msg.message);
} else {
url_request.PostCertRequestInQueryString(session_msg.message);
}
std::string response;
int resp_bytes = url_request.GetResponse(&response);
// 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);
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\n%s", drm_msg.size(),
HexEncode(reinterpret_cast<const uint8_t*>(drm_msg.data()),
drm_msg.size()).c_str());
return drm_msg;
}
}
void ProcessKeyResponse(bool expect_url_in_message) {
ProcessKeyResponse(expect_url_in_message, g_license_server + g_client_auth);
}
void ProcessKeyResponse(bool expect_url_in_message,
const std::string& default_url) {
ProcessServerResponse(expect_url_in_message, false, default_url);
}
void ProcessProvisionResponse() { ProcessServerResponse(true, true, ""); }
void ProcessServerResponse(bool expect_url_in_message, bool is_provision,
const std::string& default_url) {
TestHost_4::SessionMessage session_msg = host_->GetLastSessionMessage();
ASSERT_FALSE(session_msg.message.empty());
// Use EXPECT_bool instead of EXPECT_EQ to get better error printing.
if (expect_url_in_message) {
EXPECT_FALSE(session_msg.default_url.empty());
} else {
EXPECT_TRUE(session_msg.default_url.empty());
}
std::string drm_msg =
GetKeyRequestResponse(session_msg, default_url, is_provision);
UpdateSession(session_msg.session_id, (const uint8_t*)drm_msg.c_str(),
drm_msg.length());
}
void ReleaseSession(uint32_t session_id) { cdm_->ReleaseSession(session_id); }
void RemoveSession(uint32_t session_id, const std::string& web_session_id) {
cdm_->RemoveSession(session_id, web_session_id.data(),
web_session_id.length());
}
void UpdateSession(uint32_t session_id, const uint8_t* response,
uint32_t response_size) {
cdm_->UpdateSession(session_id, response, response_size);
}
cdm::Status UsePrivacyMode() { return cdm_->UsePrivacyMode(); }
cdm::Status SetServerCertificate(const std::vector<uint8_t>& cert) {
return cdm_->SetServerCertificate(&cert[0], cert.size());
}
bool SessionErrorPresent(uint32_t session_id) {
TestHost_4::SessionError serr = host_->GetLastSessionError();
if (serr.session_id == session_id) {
return true;
}
return false;
}
bool SessionErrorPresent(uint32_t session_id, cdm::Status error_code) {
TestHost_4::SessionError serr = host_->GetLastSessionError();
if (serr.session_id == session_id && serr.error_code == error_code) {
return true;
}
return false;
}
cdm::InputBuffer BuildInputBuffer(const std::vector<uint8_t>& encrypted,
const std::vector<uint8_t>& iv) {
cdm::InputBuffer buf;
buf.data = &encrypted[0];
buf.data_size = encrypted.size();
buf.key_id = &kTestKeyId[0];
buf.key_id_size = kTestKeyId.size();
buf.iv = &iv[0];
buf.iv_size = iv.size();
buf.data_offset = 0;
buf.timestamp = 10;
return buf;
}
cdm::InputBuffer BuildInputBuffer(
const std::vector<uint8_t>& encrypted, const std::vector<uint8_t>& iv,
const std::vector<cdm::SubsampleEntry>& sub) {
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv);
buf.subsamples = &sub[0];
buf.num_subsamples = sub.size();
return buf;
}
cdm::InputBuffer BuildInputBuffer(const std::vector<uint8_t>& encrypted,
const std::vector<uint8_t>& iv,
const uint32_t data_offset) {
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv);
buf.data_offset = data_offset;
return buf;
}
std::vector<cdm::SubsampleEntry> BuildMultipleSubsamples() {
std::vector<cdm::SubsampleEntry> sub;
sub.push_back(cdm::SubsampleEntry(3, 125));
sub.push_back(cdm::SubsampleEntry(5, 62));
sub.push_back(cdm::SubsampleEntry(8, 69));
return sub;
}
std::vector<cdm::SubsampleEntry> BuildSingleSubsample(size_t size) {
std::vector<cdm::SubsampleEntry> sub;
sub.push_back(cdm::SubsampleEntry(0, size));
return sub;
}
void GetOfflineConfiguration(std::string* key_id, std::string* license_server,
std::string* client_auth) {
// This method compares three different places settings could be found:
// 1) A configuration object using the default settings.
// 2) The global variables prefixed with g_.
// 3) A configuration object configured for offline playback.
// The desire is to use 3 unless the user customized the settings on the
// command line, in which case we want to defer to them and use 2. The "if"
// statements check if 1 and 2 are the same. If so, we know the user
// did not customize the settings on the command line, and therefore we can
// use 3. Otherwise, we use 2. This will never return the values from 1;
// 1 is only used to check if 2 has been altered by the user.
ConfigTestEnv std_config(kLicenseServerId);
// TODO (juce): Switch this to kLicenseServerId once
// kContentProtectionServer is the default.
ConfigTestEnv config(wvcdm::kContentProtectionServer, false);
if (g_key_id.compare(a2bs_hex(std_config.key_id())) == 0)
key_id->assign(wvcdm::a2bs_hex(config.key_id()));
else
key_id->assign(g_key_id);
if (g_license_server.compare(std_config.license_server()) == 0)
license_server->assign(config.license_server());
else
license_server->assign(g_license_server);
if (g_client_auth.compare(std_config.client_auth()) == 0)
client_auth->assign(config.client_auth());
else
client_auth->assign(g_client_auth);
}
cdm::ContentDecryptionModule_4* cdm_; // owned by host_
wvcdm::scoped_ptr<TestHost_4> host_;
};
namespace {
class DummyCDM : public cdm::ContentDecryptionModule_4 {
public:
DummyCDM() : timer_fired_(false), last_context_(NULL) {}
virtual void CreateSession(uint32_t session_id,
const char* mime_type, uint32_t mime_type_size,
const uint8_t* init_data, uint32_t init_data_size,
cdm::SessionType session_type) OVERRIDE {}
virtual void LoadSession(uint32_t session_id, const char* web_session_id,
uint32_t web_session_id_length) OVERRIDE {}
virtual void UpdateSession(uint32_t session_id, const uint8_t* response,
uint32_t response_size) OVERRIDE {}
virtual bool IsKeyValid(const uint8_t*, int) OVERRIDE { return false; }
virtual void ReleaseSession(uint32_t session_id) OVERRIDE {}
virtual void RemoveSession(uint32_t session_id, const char* web_session_id,
uint32_t web_session_id_length) {}
virtual cdm::Status UsePrivacyMode() OVERRIDE { return cdm::kSuccess; }
virtual cdm::Status SetServerCertificate(
const uint8_t* server_certificate_data,
uint32_t server_certificate_data_size) OVERRIDE {
return cdm::kSuccess;
}
virtual void TimerExpired(void* context) OVERRIDE {
timer_fired_ = true;
last_context_ = context;
}
virtual cdm::Status Decrypt(const cdm::InputBuffer&,
cdm::DecryptedBlock*) OVERRIDE {
return cdm::kSessionError;
}
virtual cdm::Status DecryptDecodeAndRender(
const cdm::InputBuffer& encrypted_buffer,
cdm::StreamType stream_type) OVERRIDE {
return cdm::kDecryptError;
}
virtual void Destroy() OVERRIDE { delete this; }
bool TimerFired() const { return timer_fired_; }
void* LastTimerContext() const { return last_context_; }
void ResetTimerStatus() {
timer_fired_ = false;
last_context_ = NULL;
}
private:
bool timer_fired_;
void* last_context_;
};
} // namespace
TEST_F(CdmApi4Test, TestHostTimer) {
// Validate that the TestHost timers are processed in the correct order.
// To do this, we replace the cdm with a dummy that only tracks timers.
DummyCDM* cdm = new DummyCDM();
// The old CDM is destroyed by SetCdmPtr.
cdm_ = cdm;
host_->SetCdmPtr(cdm);
const double kTimerDelaySeconds = 1.0;
const int64_t kTimerDelayMs = kTimerDelaySeconds * 1000;
void* kCtx1 = reinterpret_cast<void*>(0x1);
void* kCtx2 = reinterpret_cast<void*>(0x2);
host_->SetTimer(kTimerDelayMs * 1, kCtx1);
host_->SetTimer(kTimerDelayMs * 2, kCtx2);
host_->FastForwardTime(kTimerDelaySeconds);
EXPECT_TRUE(cdm->TimerFired());
EXPECT_EQ(kCtx1, cdm->LastTimerContext());
cdm->ResetTimerStatus();
host_->FastForwardTime(kTimerDelaySeconds);
EXPECT_TRUE(cdm->TimerFired());
EXPECT_EQ(kCtx2, cdm->LastTimerContext());
cdm->ResetTimerStatus();
host_->FastForwardTime(kTimerDelaySeconds);
EXPECT_FALSE(cdm->TimerFired());
}
TEST_F(CdmApi4Test, DeviceCertificateTest) {
if (Properties::use_certificates_as_identification()) {
// Clear any existing certificates
host_->file_store.erase(kCertFilename);
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ASSERT_TRUE(SessionErrorPresent(kSessionId1, cdm::kNeedsDeviceCertificate));
// The Host must handle the certificate provisioning request.
CreateSession(kSessionId2, "", cdm::kProvisioning);
ASSERT_FALSE(SessionErrorPresent(kSessionId2));
ProcessProvisionResponse();
ASSERT_FALSE(SessionErrorPresent(kSessionId2));
CreateSession(kSessionId3, g_key_id, cdm::kTemporary);
ASSERT_FALSE(SessionErrorPresent(kSessionId3));
} else {
LOGI(
"Skipping CdmApi4Test::DeviceCertificateTest because this platform "
"does not support device certificates.");
}
}
// Note that these tests, BaseMessageTest, NormalDecryption, TimeTest, and
// others 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(CdmApi4Test, BaseMessageTest) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, NormalDecryption) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
// Level 1 / Level 2 payload comes back in the cpu memory as cleartext.
std::vector<uint8_t> encrypted = kInputVector1;
std::vector<uint8_t> iv = kIv1;
std::vector<uint8_t> expected = kOutputVector1;
std::vector<cdm::SubsampleEntry> sub = BuildSingleSubsample(encrypted.size());
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(
0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size));
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, NormalSubSampleDecryptionWithSubsampleInfo) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
// Level 1 / Level 2 payload comes back in the cpu memory as cleartext.
std::vector<uint8_t> encrypted = kInputVector2;
std::vector<uint8_t> iv = kIv2;
std::vector<uint8_t> expected = kOutputVector2;
std::vector<cdm::SubsampleEntry> sub = BuildMultipleSubsamples();
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(
0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size));
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, NormalSubSampleDecryptionWithMissingSubsampleInfo) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
std::vector<uint8_t> encrypted = kInputVector2;
std::vector<uint8_t> iv = kIv2;
std::vector<uint8_t> expected = kOutputVector2;
std::vector<cdm::SubsampleEntry> sub = BuildMultipleSubsamples();
// Don't add these subsamples yet!
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv);
buf.num_subsamples = sub.size();
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kDecryptError, status);
// Add the subsamples pointer and expect success.
buf.subsamples = &sub[0];
status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(
0, memcmp(output.DecryptedBuffer()->Data(), &expected[0], buf.data_size));
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, DecryptWithDataOffset) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
std::vector<uint8_t> encrypted = kInputVector3;
std::vector<uint8_t> iv = kIv3;
std::vector<uint8_t> expected = kOutputVector3;
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, kInputOffset3);
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
cdm::Buffer* output_buf = output.DecryptedBuffer();
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(buf.data_size - buf.data_offset, output_buf->Size());
EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &expected[0],
output_buf->Size()));
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, DecryptReturnsSizedBuffer) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
std::vector<uint8_t> encrypted = kInputVector1;
std::vector<uint8_t> iv = kIv1;
std::vector<uint8_t> expected = kOutputVector1;
std::vector<cdm::SubsampleEntry> sub = BuildSingleSubsample(encrypted.size());
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
TestDecryptedBlock output;
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
cdm::Buffer* buffer = output.DecryptedBuffer();
EXPECT_NE((void*)NULL, buffer);
if (buffer) {
EXPECT_EQ(expected.size(), output.DecryptedBuffer()->Size());
}
ReleaseSession(kSessionId1);
}
// This exercises both timers and renewal.
TEST_F(CdmApi4Test, RenewalTest) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
// We expect that by the time we've added a key, the CDM has set a timer.
// Otherwise, it couldn't correctly handle renewal.
EXPECT_NE(0, host_->NumTimers());
host_->FastForwardTime(kTestPolicyRenewalDelaySeconds +
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.
ProcessKeyResponse(true);
// And the key should have been added.
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, SecureDecryptionLevel1) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
// Level 1 passes encrypted payload straight through. By calling the CDM's
// DecryptDecodeAndRender, OEMCrypto_DecryptCTR will be told to use Direct
// Rendering.
std::vector<uint8_t> encrypted = kInputVector1;
std::vector<uint8_t> iv = kIv1;
std::vector<cdm::SubsampleEntry> sub = BuildSingleSubsample(encrypted.size());
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
cdm::Status status;
status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeVideo);
EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeAudio);
EXPECT_EQ(cdm::kSuccess, status);
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, SecureDecryptionLevel1WithSubsampleInfo) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
// Level 1 passes encrypted payload straight through. By calling the
// CDM's DecryptDecodeAndRender, OEMCrypto_DecryptCTR will be told
// to use Direct Rendering.
std::vector<uint8_t> encrypted = kInputVector2;
std::vector<uint8_t> iv = kIv2;
std::vector<cdm::SubsampleEntry> sub = BuildMultipleSubsamples();
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv, sub);
cdm::Status status;
status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeVideo);
EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeAudio);
EXPECT_EQ(cdm::kSuccess, status);
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, SecureDecryptionLevel1WithMissingSubsampleInfo) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
std::vector<uint8_t> encrypted = kInputVector2;
std::vector<uint8_t> iv = kIv2;
std::vector<cdm::SubsampleEntry> sub = BuildMultipleSubsamples();
// Don't add these subsamples yet!
cdm::InputBuffer buf = BuildInputBuffer(encrypted, iv);
buf.num_subsamples = sub.size();
cdm::Status status;
status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeVideo);
EXPECT_EQ(cdm::kDecryptError, status);
status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeAudio);
EXPECT_EQ(cdm::kDecryptError, status);
// Add the subsamples pointer and expect success.
buf.subsamples = &sub[0];
status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeVideo);
EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRender(buf, cdm::kStreamTypeAudio);
EXPECT_EQ(cdm::kSuccess, status);
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, GenerateKeyRequestFailureSendsKeyError) {
// Pass a bogus key id and expect failure.
// Expect the CDM to pass a key error back to the host.
CreateSession(kSessionId1, g_wrong_key_id, cdm::kTemporary);
EXPECT_EQ(1, host_->SessionErrorsSize());
EXPECT_EQ(0, host_->SessionMessagesSize());
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, UpdateSessionFailureSendsKeyError) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
TestHost_4::SessionMessage session_msg = host_->GetLastSessionMessage();
EXPECT_TRUE(session_msg.default_url.empty());
std::string drm_msg = GetKeyRequestResponse(
session_msg, g_license_server + g_client_auth, false);
// Call UpdateSession with a bad session id, and expect the CDM to pass a key
// error back to the host.
UpdateSession(kSessionId2, reinterpret_cast<const uint8_t*>(drm_msg.c_str()),
drm_msg.length());
EXPECT_EQ(1, host_->SessionErrorsSize());
// Call UpdateSession with a bad license, and expect the CDM to pass a key
// error back to the host.
static const std::string kBadLicense = "!kGoodLicense";
UpdateSession(kSessionId1,
reinterpret_cast<const uint8_t*>(kBadLicense.c_str()),
kBadLicense.length());
EXPECT_EQ(2, host_->SessionErrorsSize());
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, IsKeyValidDetectsValidKey) {
EXPECT_FALSE(cdm_->IsKeyValid(
reinterpret_cast<const uint8_t*>(kTestKeyId.data()), kTestKeyId.size()));
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ProcessKeyResponse(false);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
EXPECT_TRUE(cdm_->IsKeyValid(
reinterpret_cast<const uint8_t*>(kTestKeyId.data()), kTestKeyId.size()));
ReleaseSession(kSessionId1);
EXPECT_FALSE(cdm_->IsKeyValid(
reinterpret_cast<const uint8_t*>(kTestKeyId.data()), kTestKeyId.size()));
}
TEST_F(CdmApi4Test, OfflineLicense) {
// override default settings unless configured through the command line
std::string key_id;
std::string license_server;
std::string client_auth;
GetOfflineConfiguration(&key_id, &license_server, &client_auth);
CreateSession(kSessionId1, key_id, cdm::kPersistent);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
ProcessKeyResponse(false, license_server + client_auth);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, RestoreOfflineLicense) {
// override default settings unless configured through the command line
std::string key_id;
std::string license_server;
std::string client_auth;
GetOfflineConfiguration(&key_id, &license_server, &client_auth);
CreateSession(kSessionId1, key_id, cdm::kPersistent);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
ProcessKeyResponse(false, license_server + client_auth);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
std::string web_session_id = host_->session_map[kSessionId1];
EXPECT_FALSE(web_session_id.empty());
ReleaseSession(kSessionId1);
LoadSession(kSessionId2, web_session_id);
ASSERT_FALSE(SessionErrorPresent(kSessionId2));
ReleaseSession(kSessionId2);
}
TEST_F(CdmApi4Test, ReleaseOfflineLicense) {
// override default settings unless configured through the command line
std::string key_id;
std::string license_server;
std::string client_auth;
GetOfflineConfiguration(&key_id, &license_server, &client_auth);
CreateSession(kSessionId1, key_id, cdm::kPersistent);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
ProcessKeyResponse(false, license_server + client_auth);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
std::string web_session_id = host_->session_map[kSessionId1];
EXPECT_FALSE(web_session_id.empty());
ReleaseSession(kSessionId1);
RemoveSession(kSessionId2, web_session_id);
ProcessKeyResponse(true, license_server + client_auth);
ASSERT_FALSE(SessionErrorPresent(kSessionId2));
}
TEST_F(CdmApi4Test, MimeTypeMatters) {
CreateSessionWithMimeType(kSessionId1, "video/mp4");
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
ReleaseSession(kSessionId1);
CreateSessionWithMimeType(kSessionId1, "video/webm");
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
ReleaseSession(kSessionId1);
CreateSessionWithMimeType(kSessionId1, "video/blah");
ASSERT_TRUE(SessionErrorPresent(kSessionId1));
}
TEST_F(CdmApi4Test, UsePrivacyMode) {
ASSERT_EQ(cdm::kSuccess, UsePrivacyMode());
const CdmClientPropertySet& property_set =
reinterpret_cast<WvContentDecryptionModule_4*>(cdm_)->property_set_;
EXPECT_TRUE(property_set.use_privacy_mode());
}
TEST_F(CdmApi4Test, UsePrivacyModeFailsWithOpenSessions) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
EXPECT_NE(cdm::kSuccess, UsePrivacyMode());
const CdmClientPropertySet& property_set =
reinterpret_cast<WvContentDecryptionModule_4*>(cdm_)->property_set_;
EXPECT_FALSE(property_set.use_privacy_mode());
ReleaseSession(kSessionId1);
}
TEST_F(CdmApi4Test, SetExplicitServerCertificate) {
ASSERT_EQ(cdm::kSuccess, SetServerCertificate(kFakeServerCertficiate));
const CdmClientPropertySet& property_set =
reinterpret_cast<WvContentDecryptionModule_4*>(cdm_)->property_set_;
EXPECT_TRUE(property_set.use_privacy_mode());
const std::string& set_cert = property_set.service_certificate();
ASSERT_EQ(kFakeServerCertficiate.size(), set_cert.size());
EXPECT_EQ(0,
memcmp(&kFakeServerCertficiate[0], &set_cert[0], set_cert.size()));
}
TEST_F(CdmApi4Test, SetServerCertificateFailsWithOpenSessions) {
CreateSession(kSessionId1, g_key_id, cdm::kTemporary);
ASSERT_FALSE(SessionErrorPresent(kSessionId1));
EXPECT_NE(cdm::kSuccess, SetServerCertificate(kFakeServerCertficiate));
const CdmClientPropertySet& property_set =
reinterpret_cast<WvContentDecryptionModule_4*>(cdm_)->property_set_;
EXPECT_FALSE(property_set.use_privacy_mode());
ReleaseSession(kSessionId1);
}
} // namespace wvcdm

1078
cdm/test/cdm_test.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_TEST_CDM_TEST_CONFIG_H_
#define WVCDM_CDM_TEST_CDM_TEST_CONFIG_H_
#include <string>
#include "config_test_env.h"
extern std::string g_client_auth;
extern std::string g_key_id;
extern std::string g_license_server;
extern std::string g_wrong_key_id;
static const wvcdm::LicenseServerId kLicenseServerId =
wvcdm::kContentProtectionServer;
#endif // WVCDM_CDM_TEST_CDM_TEST_CONFIG_H_

View File

@@ -1,96 +1,87 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#include "cdm_test_config.h"
// Copyright 2015 Google Inc. All Rights Reserved.
#include <assert.h>
#include <getopt.h>
#include <gtest/gtest.h>
#include <time.h>
#include "log.h"
#include "string_conversions.h"
#if defined(__linux__)
#include <sys/utsname.h>
#endif
// 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;
std::string g_key_id;
std::string g_license_server;
std::string g_wrong_key_id;
#include "cdm.h"
#include "device_cert.h"
#include "override.h"
#include "test_host.h"
#if defined(OEMCRYPTO_TESTS)
# include "oemcrypto_test.h"
#endif
using namespace widevine;
TestHost* g_host = NULL;
int main(int argc, char** argv) {
// Init gtest and let it consume arguments.
::testing::InitGoogleTest(&argc, argv);
wvcdm::InitLogging(argc, argv);
wvcdm::ConfigTestEnv config(kLicenseServerId);
g_client_auth.assign(config.client_auth());
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());
std::string license_server(g_license_server);
// Parse arguments.
int show_usage = 0;
static const struct option long_options[] = {
{"keyid", required_argument, NULL, 'k'},
{"server", required_argument, NULL, 's'},
{NULL, 0, NULL, '\0'}};
int option_index = 0;
int opt = 0;
while ((opt = getopt_long(argc, argv, "k:s:v", long_options,
&option_index)) != -1) {
int opt;
int verbosity = 0;
while ((opt = getopt_long(argc, argv, "v", NULL, NULL)) != -1) {
switch (opt) {
case 'k': {
g_key_id.clear();
g_key_id.assign(optarg);
case 'v':
++verbosity;
break;
}
case 's': {
g_license_server.clear();
g_license_server.assign(optarg);
break;
}
case 'v': {
// This option has already been consumed by wvcdm::InitLogging() above.
// We only tell getopt about it so that it is not an error. We ignore
// the option here when seen.
// TODO: Stop passing argv to InitLogging, and instead set the log
// level here through the logging API. We should keep all command-line
// parsing at the application level, rather than split between various
// apps and various platform-specific logging implementations.
break;
}
case '?': {
case '?':
show_usage = 1;
break;
}
}
}
if (show_usage) {
std::cout << std::endl;
std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl;
std::cout << std::setw(30) << std::left << " --server=<server_url>";
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=<key_id>";
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;
fprintf(stderr, "Usage: %s [-v|-vv|-vvv]\n\n", argv[0]);
return 0;
}
std::cout << std::endl;
std::cout << "Server: " << g_license_server << std::endl;
std::cout << "KeyID: " << g_key_id << std::endl << std::endl;
// Set up a Host so that tests and initialize the library. This makes these
// services available to the tests. We would do this in the test suite
// itself, but the core & OEMCrypto tests don't know they depend on this
// for storage.
g_host = new TestHost();
Cdm::ClientInfo client_info;
// Set client info that denotes this as the test suite:
client_info.product_name = "CE cdm tests";
client_info.company_name = "www";
client_info.model_name = "www";
#if defined(__linux__)
client_info.device_name = "Linux";
{
struct utsname name;
if (uname(&name) == 0) {
client_info.arch_name = name.machine;
}
}
#else
client_info.device_name = "unknown";
#endif
client_info.build_info = __DATE__;
g_key_id = wvcdm::a2bs_hex(g_key_id);
config.set_license_server(g_license_server);
config.set_key_id(g_key_id);
Cdm::DeviceCertificateRequest cert_request;
Cdm::Status status = Cdm::initialize(
Cdm::kNoSecureOutput, client_info, g_host, g_host, g_host, &cert_request,
static_cast<Cdm::LogLevel>(verbosity));
assert(status == Cdm::kSuccess);
assert(cert_request.needed == false);
#if defined(OEMCRYPTO_TESTS)
// Set up the OEMCrypto test harness.
wvoec::global_features.Initialize(false /* is_cast_receiver */,
false /* force_load_test_keybox */);
::testing::GTEST_FLAG(filter)
= wvoec::global_features.RestrictFilter(::testing::GTEST_FLAG(filter));
#endif
return RUN_ALL_TESTS();
}

View File

@@ -0,0 +1,67 @@
// Copyright 2015 Google Inc. All Rights Reserved.
#include "cdm_test_printers.h"
namespace widevine {
void PrintTo(const Cdm::MessageType& value, ::std::ostream* os) {
switch (value) {
case Cdm::kLicenseRequest: *os << "Cdm::kLicenseRequest";
break;
case Cdm::kLicenseRenewal: *os << "Cdm::kLicenseRenewal";
break;
case Cdm::kLicenseRelease: *os << "Cdm::kLicenseRelease";
break;
case Cdm::kIndividualizationRequest:
*os << "Cdm::kIndividualizationRequest";
break;
default: *os << "Unknown Cdm::MessageType value " << value;
break;
}
}
void PrintTo(const Cdm::Status& value, ::std::ostream* os) {
switch (value) {
case Cdm::kSuccess: *os << "Cdm::kSuccess";
break;
case Cdm::kNeedsDeviceCertificate: *os << "Cdm::kNeedsDeviceCertificate";
break;
case Cdm::kSessionNotFound: *os << "Cdm::kSessionNotFound";
break;
case Cdm::kDecryptError: *os << "Cdm::kDecryptError";
break;
case Cdm::kNoKey: *os << "Cdm::kNoKey";
break;
case Cdm::kInvalidAccess: *os << "Cdm::kInvalidAccess";
break;
case Cdm::kNotSupported: *os << "Cdm::kNotSupported";
break;
case Cdm::kInvalidState: *os << "Cdm::kInvalidState";
break;
case Cdm::kQuotaExceeded: *os << "Cdm::kQuotaExceeded";
break;
case Cdm::kUnexpectedError: *os << "Cdm::kUnexpectedError";
break;
default: *os << "Unknown Cdm::Status value " << value;
break;
}
}
void PrintTo(const Cdm::KeyStatus& value, ::std::ostream* os) {
switch (value) {
case Cdm::kUsable: *os << "Cdm::kUsable";
break;
case Cdm::kExpired: *os << "Cdm::kExpired";
break;
case Cdm::kOutputNotAllowed: *os << "Cdm::kOutputNotAllowed";
break;
case Cdm::kStatusPending: *os << "Cdm::kStatusPending";
break;
case Cdm::kInternalError: *os << "Cdm::kInternalError";
break;
default: *os << "Unknown Cdm::KeyStatus value " << value;
break;
}
}
} // namespace widevine

View File

@@ -0,0 +1,19 @@
// Copyright 2015 Google Inc. All Rights Reserved.
// This file adds some print methods so that when unit tests fail, the
// will print the name of an enumeration instead of the numeric value.
#ifndef WVCDM_CDM_TEST_CDM_TEST_PRINTERS_H_
#define WVCDM_CDM_TEST_CDM_TEST_PRINTERS_H_
#include <iostream>
#include "cdm.h"
namespace widevine {
void PrintTo(const Cdm::MessageType& value, ::std::ostream* os);
void PrintTo(const Cdm::Status& value, ::std::ostream* os);
void PrintTo(const Cdm::KeyStatus& value, ::std::ostream* os);
} // namespace widevine
#endif // WVCDM_CDM_TEST_CDM_TEST_PRINTERS_H_

View File

@@ -5,77 +5,77 @@
const uint8_t kDeviceCert[] = {
0x0A, 0x9A, 0x14, 0x08, 0x01, 0x10, 0x01, 0x1A,
0x93, 0x14, 0x0A, 0xED, 0x09, 0x0A, 0xAE, 0x02,
0x08, 0x02, 0x12, 0x10, 0x19, 0x77, 0x50, 0x4D,
0x66, 0x1C, 0x28, 0xBE, 0x41, 0xD3, 0xAA, 0x33,
0x98, 0x3D, 0xB5, 0x46, 0x18, 0x8D, 0xA3, 0x81,
0x9E, 0x05, 0x22, 0x8E, 0x02, 0x30, 0x82, 0x01,
0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xB2, 0xA8,
0x6A, 0x68, 0x01, 0x84, 0xC2, 0x62, 0x08, 0x8D,
0xFB, 0x5E, 0xA7, 0x5B, 0xC2, 0x70, 0x6E, 0xF0,
0xE1, 0xF7, 0xCE, 0x0D, 0x06, 0x29, 0x8A, 0x8C,
0x11, 0x6C, 0xB8, 0xBD, 0xEC, 0xA5, 0x9F, 0x60,
0x65, 0x9F, 0x89, 0x52, 0xC0, 0x72, 0xF8, 0x2A,
0x30, 0xBD, 0xB8, 0x52, 0xFF, 0x2F, 0x6C, 0xAE,
0x36, 0x1E, 0xED, 0xC2, 0x0E, 0x00, 0x64, 0x57,
0x0F, 0xFD, 0xA9, 0xEE, 0xCE, 0x62, 0xB0, 0x23,
0xB8, 0x68, 0x2B, 0xED, 0xDE, 0x3B, 0xCA, 0xCD,
0x07, 0x55, 0xDB, 0xF7, 0x77, 0xCE, 0x6B, 0x16,
0xE0, 0x17, 0x6C, 0xE6, 0xBE, 0x76, 0xBE, 0x31,
0xC8, 0x62, 0x98, 0xF3, 0x62, 0x3B, 0x24, 0x73,
0x43, 0x19, 0x0A, 0x0A, 0xE9, 0xA4, 0x76, 0xA8,
0x27, 0x12, 0xBD, 0xF5, 0x8A, 0x28, 0x99, 0x54,
0x8A, 0x55, 0x56, 0x6B, 0x92, 0x57, 0xA2, 0x6C,
0x7E, 0xD9, 0xEE, 0xF7, 0xCE, 0xEC, 0xB6, 0xA4,
0xC2, 0xC2, 0x4F, 0x60, 0xF9, 0x6C, 0x96, 0x1E,
0x5A, 0x1C, 0xC6, 0x14, 0x2D, 0x6D, 0xA8, 0x83,
0x8C, 0x80, 0x07, 0x5F, 0x20, 0xFC, 0xAF, 0x08,
0x98, 0xAE, 0x94, 0x10, 0x13, 0x49, 0xD3, 0xDF,
0x94, 0x8A, 0x51, 0xE2, 0x1D, 0xD6, 0x9E, 0xA6,
0xE1, 0x56, 0x23, 0x0D, 0x69, 0x4D, 0x1A, 0x14,
0xEF, 0x5D, 0x33, 0xF8, 0x5D, 0x7A, 0xEF, 0xD8,
0x02, 0x9E, 0xF2, 0xFB, 0xB3, 0x75, 0x04, 0xAF,
0x9C, 0x19, 0x73, 0xEC, 0xA3, 0x01, 0x8F, 0xD7,
0x03, 0xB7, 0x58, 0xD4, 0x2A, 0xD4, 0xA6, 0x21,
0xC8, 0x39, 0x58, 0xCA, 0x2F, 0x40, 0x19, 0x12,
0x35, 0x58, 0x4D, 0xA8, 0x60, 0x06, 0xEE, 0x90,
0x17, 0xE3, 0xB8, 0xA2, 0xE2, 0xDC, 0xEB, 0x74,
0x50, 0x0E, 0xCD, 0x34, 0x44, 0x04, 0x09, 0xC1,
0xC6, 0xE5, 0x55, 0xAF, 0xFD, 0x14, 0xB6, 0x27,
0x4F, 0xA7, 0x2C, 0xB4, 0xE1, 0x3D, 0x02, 0x03,
0x08, 0x02, 0x12, 0x10, 0x7F, 0x6F, 0x31, 0x50,
0xB3, 0x73, 0x26, 0x2C, 0xBD, 0xC3, 0xB2, 0xDA,
0xDE, 0x07, 0x5D, 0xBB, 0x18, 0xF3, 0xFA, 0xB0,
0xA9, 0x05, 0x22, 0x8E, 0x02, 0x30, 0x82, 0x01,
0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xBF, 0xAF,
0x84, 0xAC, 0x9C, 0x2D, 0x57, 0x4C, 0xDE, 0x10,
0xB9, 0x94, 0xF0, 0x51, 0x4F, 0xBE, 0xA0, 0x0D,
0xDC, 0x7E, 0x44, 0x38, 0x76, 0xD7, 0xB7, 0xBF,
0x0B, 0x7C, 0x1A, 0x7A, 0x15, 0x9B, 0x66, 0xD1,
0xE4, 0x67, 0x83, 0xA8, 0xE5, 0xFA, 0x11, 0xE3,
0xEA, 0xB8, 0x84, 0xD8, 0x32, 0x9C, 0x8B, 0xA7,
0xCC, 0x60, 0xBE, 0x68, 0x7C, 0x13, 0x29, 0xBD,
0xF5, 0x8A, 0x6E, 0xFC, 0x7B, 0xAA, 0x4A, 0x72,
0xB8, 0xB2, 0x52, 0xD8, 0xEA, 0x03, 0x87, 0x75,
0xE4, 0xE4, 0x52, 0xD9, 0x83, 0x72, 0x7A, 0xA5,
0x9E, 0x0B, 0x27, 0xD8, 0xA1, 0x6A, 0x23, 0xF4,
0x59, 0xAB, 0xE8, 0xD5, 0x5B, 0xDD, 0x64, 0xC8,
0xFD, 0x30, 0x28, 0x5B, 0x0D, 0x0E, 0xCE, 0x7F,
0x1E, 0x4C, 0xC0, 0x36, 0x17, 0xC5, 0xC1, 0xD0,
0x19, 0x0C, 0x5A, 0x71, 0xCA, 0x3B, 0xCA, 0x41,
0xD9, 0x67, 0x36, 0x0A, 0x23, 0xA3, 0xDC, 0x9B,
0x86, 0x53, 0xF6, 0xAC, 0x1E, 0xD9, 0x41, 0x34,
0x73, 0x04, 0xE1, 0x68, 0x1C, 0x38, 0xA8, 0x2A,
0xE8, 0x0D, 0x88, 0xF9, 0xA9, 0xD4, 0x64, 0x4E,
0xFF, 0xF8, 0x94, 0x56, 0x99, 0xDB, 0xA2, 0x6C,
0x15, 0xF2, 0xA1, 0x34, 0xF6, 0x51, 0xC0, 0xAB,
0x2A, 0x99, 0xB9, 0xF3, 0x3D, 0x2D, 0x13, 0xCD,
0x8B, 0x6B, 0xAD, 0x82, 0x10, 0x73, 0x4E, 0x42,
0x61, 0x78, 0x69, 0x2F, 0x19, 0x09, 0x06, 0x65,
0x0F, 0x7E, 0xA9, 0xB1, 0xB3, 0xC2, 0x30, 0x86,
0xE9, 0x0B, 0x29, 0xC0, 0xB6, 0x08, 0x73, 0x7C,
0x3B, 0xFA, 0x39, 0x5A, 0x5A, 0xB1, 0xD5, 0x6E,
0xFF, 0x85, 0x41, 0xF3, 0xBF, 0xE9, 0xD9, 0x45,
0xE6, 0xDD, 0xB1, 0x35, 0x39, 0x56, 0xA5, 0xCE,
0xBD, 0x6C, 0xC3, 0xFD, 0xEA, 0x47, 0xE9, 0x65,
0x58, 0x21, 0x62, 0xFF, 0x00, 0x9A, 0xFE, 0xD3,
0x5B, 0x15, 0xC2, 0xC9, 0x64, 0x3F, 0x02, 0x03,
0x01, 0x00, 0x01, 0x28, 0x99, 0x20, 0x12, 0x80,
0x02, 0x6B, 0x7E, 0x26, 0xBC, 0x2C, 0x1F, 0x19,
0x55, 0x2B, 0xBF, 0x88, 0x3F, 0x48, 0xDE, 0xE2,
0x8E, 0x82, 0xCC, 0x77, 0x72, 0x31, 0x34, 0x2B,
0x4E, 0x2C, 0xDF, 0xFB, 0xF6, 0xA1, 0x6F, 0x94,
0xCA, 0x01, 0x45, 0xAE, 0x42, 0x0A, 0xFF, 0x51,
0xE9, 0xB8, 0x6C, 0xDF, 0xB1, 0x1E, 0x35, 0x81,
0x1D, 0xF9, 0xB7, 0x51, 0x75, 0xE4, 0xF4, 0xD6,
0xEA, 0x7D, 0xBC, 0x96, 0x2F, 0x29, 0x3D, 0xC1,
0x89, 0x1C, 0x5C, 0xF9, 0x08, 0x67, 0x7C, 0x0E,
0xBB, 0xC2, 0x9D, 0x28, 0x6F, 0x38, 0x2E, 0x57,
0x41, 0xA1, 0x96, 0x37, 0xCE, 0x7D, 0xD4, 0x24,
0xFC, 0x26, 0xB2, 0x5E, 0xDF, 0x63, 0x33, 0xBA,
0x3E, 0x3F, 0x92, 0xB6, 0x79, 0xC7, 0xE7, 0x14,
0x86, 0xC5, 0xEE, 0x96, 0xF3, 0x2F, 0xFB, 0x4B,
0xF3, 0xC4, 0xCA, 0xF3, 0x54, 0xDB, 0x56, 0x6C,
0x7F, 0x4E, 0xC7, 0x81, 0x42, 0x87, 0x5D, 0x22,
0xDF, 0x80, 0x9C, 0xE0, 0x9C, 0xE1, 0x9F, 0x0C,
0xBC, 0xB7, 0x58, 0x44, 0x84, 0xE8, 0x3F, 0x97,
0x0C, 0x79, 0x5A, 0x89, 0x09, 0x3D, 0x77, 0xB7,
0xEF, 0xCC, 0x45, 0xB9, 0xC8, 0x20, 0xBE, 0x0A,
0x2F, 0x7A, 0x30, 0x3F, 0x7D, 0x07, 0x33, 0xF9,
0x5D, 0xD0, 0x23, 0x11, 0xD8, 0x21, 0xC9, 0x42,
0xCC, 0x7A, 0x25, 0xD1, 0xEA, 0x22, 0x6B, 0x65,
0x34, 0xF2, 0x66, 0xE7, 0xB9, 0x1B, 0xF7, 0x19,
0xCA, 0xA3, 0x28, 0x8B, 0x06, 0x99, 0x88, 0xFB,
0xD4, 0xA5, 0x24, 0xDC, 0x5D, 0x1F, 0x57, 0xC6,
0xBB, 0xB4, 0x1B, 0x16, 0x31, 0x8E, 0xC2, 0x4E,
0xA1, 0x54, 0xDE, 0x19, 0x77, 0x3A, 0x1B, 0xE6,
0x3F, 0xAF, 0x91, 0x47, 0x0B, 0xED, 0x59, 0xD8,
0xCD, 0x47, 0xF0, 0x62, 0xE0, 0xFA, 0xF3, 0xB1,
0x60, 0xE5, 0xAD, 0x98, 0x12, 0x71, 0x90, 0x58,
0xA9, 0xD7, 0x55, 0x92, 0x62, 0xD3, 0xEA, 0x5B,
0xF8, 0x1A, 0xB6, 0x05, 0x0A, 0xB0, 0x02, 0x08,
0x02, 0x72, 0xAA, 0xE9, 0xCD, 0xE2, 0xF8, 0xFC,
0x8B, 0x85, 0x52, 0xFE, 0x30, 0xAD, 0x49, 0x94,
0x3F, 0x56, 0x5A, 0x60, 0xFD, 0xDD, 0x2D, 0x79,
0xDA, 0xB4, 0x5F, 0x3A, 0x87, 0x10, 0x82, 0x5F,
0x0B, 0xBD, 0x20, 0x17, 0xF1, 0x59, 0xB0, 0x3F,
0xDE, 0x24, 0x3A, 0x13, 0x43, 0xBC, 0xC6, 0xBC,
0xC0, 0xFD, 0xEF, 0xCF, 0x3B, 0x19, 0xBF, 0x07,
0xD7, 0xD9, 0xF0, 0x1E, 0x1B, 0xDF, 0xF9, 0x35,
0x82, 0x69, 0x3F, 0x3F, 0xF3, 0x71, 0x65, 0xB5,
0x86, 0xF8, 0xCB, 0x44, 0x2E, 0xEE, 0x80, 0xE3,
0x53, 0x6D, 0xA8, 0x50, 0xDB, 0x87, 0x1A, 0x3B,
0x49, 0x78, 0x6E, 0x61, 0x3D, 0x60, 0x61, 0xFD,
0xE8, 0xBE, 0x05, 0x77, 0xFF, 0x2B, 0x0F, 0x0D,
0xE4, 0x53, 0x29, 0xB8, 0x55, 0x52, 0x2E, 0x7F,
0xA3, 0x54, 0x0F, 0x66, 0x93, 0x4B, 0x17, 0xFB,
0xE1, 0x82, 0x33, 0x59, 0x21, 0x9E, 0x44, 0x02,
0x27, 0x4E, 0xEC, 0x86, 0xC4, 0x6C, 0x5E, 0xF9,
0xD0, 0x31, 0xDD, 0xFA, 0xB8, 0x5D, 0x05, 0x2E,
0xBC, 0xE9, 0x48, 0xB8, 0xC2, 0xF4, 0x15, 0x2C,
0xDF, 0x51, 0x6B, 0x75, 0x62, 0x7D, 0x99, 0x67,
0x25, 0x6F, 0x0D, 0xA9, 0x5A, 0x96, 0x01, 0x3D,
0x8F, 0x14, 0x13, 0x36, 0x9D, 0x8D, 0x34, 0xF1,
0xEC, 0xFC, 0xFE, 0xEA, 0xBA, 0xAD, 0xFC, 0x7C,
0xCE, 0xA3, 0x36, 0x80, 0xFF, 0xB8, 0x9D, 0x60,
0x70, 0x78, 0xE1, 0x42, 0x7F, 0xD7, 0x99, 0x61,
0xBB, 0x51, 0x70, 0xC7, 0xE5, 0xA3, 0x74, 0xFA,
0x18, 0x02, 0x4B, 0x2A, 0x5D, 0xC4, 0x40, 0x1E,
0x6A, 0x22, 0xA9, 0xC5, 0x37, 0xF3, 0xD9, 0x7F,
0x8B, 0x1A, 0xD4, 0x8B, 0x3B, 0x00, 0x78, 0xB3,
0xAA, 0x7C, 0x04, 0xBB, 0x96, 0x31, 0x33, 0x65,
0xFA, 0xF2, 0xEB, 0x87, 0x2D, 0xF1, 0x8A, 0xFA,
0xEE, 0x7B, 0x1F, 0xD4, 0x67, 0xBD, 0x3F, 0x0F,
0x61, 0x1A, 0xB6, 0x05, 0x0A, 0xB0, 0x02, 0x08,
0x01, 0x12, 0x10, 0x08, 0x4D, 0x60, 0xBC, 0x65,
0x93, 0x7D, 0xB6, 0xAC, 0x7F, 0x99, 0x2B, 0x41,
0xEC, 0xF5, 0xF2, 0x18, 0xA2, 0xD3, 0x8A, 0x8C,
@@ -162,175 +162,175 @@ const uint8_t kDeviceCert[] = {
0xD2, 0xB9, 0x60, 0x79, 0x2D, 0x11, 0xDA, 0x50,
0x86, 0xAB, 0x8C, 0xCF, 0xF4, 0x61, 0x80, 0xC2,
0x43, 0xC3, 0x95, 0xD2, 0x2F, 0x1A, 0x9E, 0x4F,
0x6B, 0x02, 0x12, 0xA0, 0x0A, 0x2D, 0x2C, 0x09,
0x70, 0xC7, 0xEE, 0xCF, 0x10, 0x8A, 0x67, 0x80,
0x6B, 0xC0, 0x7D, 0x66, 0x49, 0x90, 0x15, 0xFC,
0x96, 0xC1, 0x9E, 0xB9, 0x33, 0x15, 0xAA, 0x16,
0x29, 0x79, 0x3C, 0x79, 0x15, 0x49, 0x8B, 0x6C,
0x1F, 0x76, 0x65, 0xFB, 0xFC, 0x7F, 0xFE, 0x34,
0x1D, 0x97, 0xDB, 0x80, 0xD2, 0x15, 0x20, 0x35,
0x85, 0x45, 0x51, 0x2F, 0x37, 0x87, 0xB0, 0xA6,
0xFC, 0xF4, 0x35, 0x7B, 0x05, 0xB8, 0xC0, 0x1B,
0x30, 0x21, 0x5E, 0xE0, 0x1B, 0x79, 0x66, 0xF8,
0x77, 0x33, 0x11, 0x8D, 0x23, 0xD4, 0x4E, 0xB3,
0x06, 0xAE, 0x09, 0xF6, 0x72, 0x32, 0xDF, 0xA3,
0x99, 0x78, 0x19, 0xD8, 0xFC, 0xB6, 0xB3, 0xAF,
0x46, 0xBB, 0xB2, 0x52, 0x50, 0x4D, 0xDE, 0xF7,
0x9D, 0xD4, 0xB8, 0x7C, 0xEB, 0xD9, 0x94, 0x75,
0x90, 0x4E, 0x83, 0x06, 0x17, 0x0C, 0x33, 0x31,
0x09, 0x73, 0x7B, 0xED, 0x05, 0xFE, 0xE3, 0xC1,
0x4C, 0xA9, 0x25, 0x3F, 0x33, 0x27, 0x17, 0xD0,
0x03, 0x4F, 0x8A, 0x0E, 0x03, 0xBF, 0xE1, 0xD6,
0x5D, 0x0E, 0xBA, 0x51, 0x61, 0x7F, 0x43, 0xA5,
0x46, 0xC7, 0x9E, 0xA3, 0x62, 0xAC, 0xCA, 0xDA,
0x20, 0xE5, 0x4C, 0xB3, 0x3D, 0x9D, 0xF2, 0x0E,
0xF4, 0x08, 0xDF, 0x9C, 0xC0, 0x99, 0xC6, 0xFF,
0xD0, 0x8E, 0x5F, 0x81, 0x72, 0x73, 0x6A, 0xEF,
0xE1, 0xEE, 0x4C, 0x03, 0x77, 0xFF, 0x28, 0xD3,
0x66, 0x19, 0x7D, 0x08, 0xD4, 0x6E, 0x61, 0xC7,
0x59, 0x69, 0x9A, 0xF8, 0xC8, 0xB2, 0xAB, 0xE0,
0xB4, 0x05, 0x45, 0xC2, 0x01, 0xDF, 0x94, 0x7D,
0xC2, 0xA8, 0xC9, 0x9B, 0x30, 0x1A, 0x4E, 0x1B,
0xB2, 0xB2, 0x1C, 0xCE, 0x06, 0x47, 0x9B, 0xCF,
0xA2, 0xF5, 0x56, 0x97, 0x18, 0x96, 0xC0, 0xFE,
0x08, 0x63, 0x75, 0x9C, 0xC0, 0xE6, 0xBC, 0x37,
0x36, 0x13, 0x15, 0x34, 0x60, 0xDB, 0xAF, 0xD1,
0x9F, 0xA7, 0xDD, 0x1D, 0xD2, 0x25, 0x2F, 0xB6,
0x72, 0xC2, 0x13, 0x65, 0x12, 0x08, 0x0F, 0x20,
0x54, 0x2B, 0x7F, 0x33, 0xC2, 0xE6, 0xA9, 0x1D,
0xB6, 0x6C, 0xC5, 0xCA, 0xD6, 0xCC, 0x1B, 0x90,
0xF4, 0x81, 0xD6, 0xDE, 0xB1, 0x87, 0x52, 0xF7,
0x24, 0xD3, 0x46, 0xA2, 0xB4, 0x8F, 0xF9, 0xD3,
0x81, 0xA3, 0x24, 0x79, 0xC0, 0x28, 0xE9, 0xF7,
0x67, 0x2D, 0x8A, 0x6B, 0x05, 0x2D, 0xD9, 0xEE,
0xD2, 0x54, 0x5B, 0x30, 0xA5, 0xC0, 0xC7, 0x85,
0x94, 0x1B, 0xF5, 0xD0, 0xDC, 0x0D, 0x69, 0x10,
0xE7, 0x3A, 0xA3, 0xDB, 0x0F, 0x9B, 0x77, 0x10,
0x75, 0x11, 0x57, 0x64, 0xBB, 0x69, 0x4A, 0x87,
0xC7, 0x3F, 0xD7, 0xB4, 0x74, 0xE4, 0x96, 0xFC,
0xB7, 0x6A, 0xE5, 0x99, 0xBE, 0xD4, 0x36, 0x9D,
0xF4, 0x51, 0xDE, 0xDE, 0xF6, 0x71, 0x72, 0xE4,
0x46, 0x79, 0x4C, 0x04, 0x0F, 0x4D, 0x8C, 0xCB,
0x21, 0xA4, 0x9C, 0xE3, 0xF1, 0x4D, 0x4A, 0xE7,
0xF7, 0x37, 0x1E, 0x91, 0xC5, 0x97, 0x1D, 0xC0,
0x75, 0x21, 0xB0, 0xA1, 0xB5, 0xCF, 0xD5, 0x0A,
0x54, 0x00, 0x81, 0x61, 0x72, 0xC8, 0x1D, 0xBF,
0x0B, 0x16, 0xE7, 0xE0, 0x7D, 0x7E, 0x91, 0xB8,
0x8C, 0x1D, 0xCF, 0x32, 0xB7, 0xCE, 0xB3, 0x4C,
0xE7, 0xD0, 0x53, 0xC5, 0x52, 0x91, 0xDC, 0x04,
0xEC, 0x39, 0xE5, 0x9F, 0x21, 0x0B, 0x36, 0xD7,
0x11, 0x30, 0x89, 0x96, 0x72, 0x3F, 0x9D, 0x3A,
0x7F, 0xC9, 0x9C, 0x51, 0x38, 0x1C, 0xC4, 0x12,
0x9E, 0x8C, 0x59, 0x3D, 0x1F, 0x37, 0x66, 0xF8,
0x42, 0x61, 0xB1, 0xC0, 0x3D, 0xA8, 0xEB, 0xF5,
0xBD, 0x83, 0x31, 0x8F, 0xF9, 0x17, 0x6B, 0xAE,
0xD3, 0x6E, 0x5A, 0x0D, 0x7E, 0x40, 0x82, 0xA1,
0x5B, 0x52, 0xA4, 0x94, 0x76, 0x2E, 0x9D, 0x27,
0x51, 0x0E, 0xEA, 0xD5, 0xC6, 0x65, 0x61, 0x47,
0x8F, 0x67, 0x55, 0x4B, 0x9A, 0x35, 0xBA, 0x40,
0x31, 0x52, 0xFD, 0x7E, 0xE1, 0xE6, 0x7D, 0xFB,
0xBB, 0xCA, 0x33, 0xF3, 0xFA, 0x23, 0xB7, 0x17,
0x5B, 0xE7, 0xD2, 0x93, 0xEB, 0x25, 0x60, 0x73,
0x0B, 0x09, 0x27, 0x48, 0x6C, 0x8E, 0xF6, 0xE7,
0xDF, 0x6D, 0x0D, 0xD7, 0x04, 0xAB, 0x04, 0x64,
0x56, 0x86, 0x79, 0x49, 0x8B, 0xF8, 0xD2, 0xE7,
0x7F, 0x59, 0xAB, 0x90, 0xDE, 0xA1, 0x04, 0x8C,
0x15, 0x24, 0x4C, 0xFA, 0x87, 0x51, 0x1F, 0x12,
0xA2, 0xB3, 0x66, 0x3A, 0x8F, 0x31, 0x9A, 0x08,
0xF4, 0x2C, 0xE2, 0x04, 0x02, 0x12, 0xB3, 0xFF,
0xBF, 0x7E, 0xE0, 0x04, 0x4E, 0x85, 0x51, 0x52,
0xF3, 0x08, 0x09, 0x77, 0x22, 0x54, 0x1E, 0xEC,
0xE8, 0x5B, 0x41, 0x52, 0x43, 0x4D, 0xBF, 0x24,
0x03, 0xAF, 0x4F, 0x3D, 0x58, 0xEE, 0xCD, 0x0E,
0x91, 0x1F, 0x03, 0x55, 0xCA, 0xF5, 0x28, 0x40,
0xB3, 0xA8, 0xAA, 0x7F, 0x5E, 0xD1, 0x67, 0x3D,
0x68, 0xD1, 0xB7, 0x4E, 0xF7, 0x9E, 0xB4, 0x96,
0xED, 0xDF, 0xCA, 0x50, 0x4D, 0x4C, 0xD2, 0xC2,
0xC7, 0x87, 0x5D, 0x1D, 0x85, 0x76, 0xCA, 0x34,
0x1C, 0x47, 0x8B, 0x8F, 0xB2, 0x63, 0x74, 0xDB,
0xB8, 0x99, 0x8B, 0x96, 0x47, 0x87, 0x8A, 0xA7,
0x3A, 0xEB, 0x17, 0x17, 0xEF, 0x59, 0xC1, 0xF6,
0x15, 0x00, 0x78, 0x09, 0x98, 0xB5, 0x52, 0xC1,
0x78, 0x4C, 0xA3, 0x0B, 0xAE, 0xA8, 0xF9, 0xEA,
0xC1, 0xC8, 0x0D, 0x75, 0x91, 0xE5, 0xB4, 0xF7,
0x9D, 0xA8, 0x6B, 0xDA, 0xFA, 0x63, 0xC3, 0xB6,
0x36, 0x9F, 0xB3, 0x78, 0x7D, 0xF0, 0x27, 0xED,
0x45, 0x3F, 0xD9, 0x5D, 0xB3, 0xE7, 0xB3, 0xB7,
0xB0, 0xE6, 0x9E, 0x4F, 0x08, 0xA0, 0xF7, 0x09,
0x22, 0x7D, 0x7D, 0x9F, 0x9C, 0x61, 0xB8, 0xAE,
0x9B, 0x9C, 0xCD, 0x84, 0x9F, 0xBF, 0xEE, 0x8D,
0x40, 0xF4, 0x9A, 0x72, 0x8F, 0xBF, 0xC2, 0xEF,
0xC8, 0x70, 0x8E, 0xAB, 0x10, 0xAC, 0x5B, 0xDA,
0xDC, 0x9F, 0x4E, 0xA0, 0xBA, 0x78, 0xA3, 0x2C,
0x81, 0xF4, 0x2D, 0xCD, 0x35, 0x1E, 0x46, 0xEF,
0x05, 0x24, 0x3A, 0x46, 0xE7, 0x1B, 0xD0, 0xF4,
0x94, 0x5C, 0x58, 0x7B, 0xAC, 0xB4, 0x9E, 0xFF,
0xBD, 0x8D, 0xB0, 0xFB, 0xEB, 0xAC, 0x02, 0x2D,
0xFA, 0x17, 0x6D, 0x92, 0x54, 0x9F, 0x70, 0x4E,
0x7E, 0x30, 0xED, 0x70, 0x42, 0x4B, 0x38, 0x76,
0x82, 0x63, 0x18, 0x77, 0x5F, 0x10, 0x94, 0x6F,
0xD6, 0xA2, 0x3F, 0x27, 0xC0, 0xDC, 0xC9, 0xAA,
0x64, 0xE5, 0x8C, 0x11, 0x31, 0x31, 0x22, 0x40,
0xEB, 0x1D, 0x4E, 0xA6, 0x8D, 0xCF, 0x68, 0x78,
0x8F, 0x84, 0xEF, 0xCB, 0x3D, 0x96, 0x56, 0x9F,
0x1D, 0xFC, 0xF4, 0xFB, 0xBF, 0xB4, 0x9E, 0x39,
0xAB, 0x1B, 0x7F, 0x62, 0xDD, 0x7E, 0x03, 0x8E,
0x3A, 0x16, 0x45, 0xF0, 0x56, 0x9B, 0xAE, 0x9A,
0xFB, 0x3C, 0xC4, 0x81, 0x2B, 0xBA, 0x35, 0xE8,
0x85, 0x20, 0x48, 0x8B, 0xB0, 0x4C, 0x44, 0x85,
0xFA, 0xDB, 0xC2, 0xAE, 0x4E, 0x2D, 0xE5, 0x30,
0x62, 0x6F, 0xC6, 0xEC, 0xD2, 0x1C, 0xE4, 0xF0,
0x1E, 0x71, 0xA3, 0x35, 0x41, 0xF4, 0x3E, 0x2F,
0xDD, 0x36, 0x14, 0x4F, 0x8C, 0xD5, 0x3E, 0x46,
0x81, 0x24, 0x51, 0x93, 0x2B, 0xB0, 0x00, 0x03,
0x93, 0xFE, 0x45, 0x0B, 0x11, 0x6C, 0x36, 0xEA,
0xF3, 0x06, 0xB5, 0xC7, 0xD5, 0x49, 0x1A, 0xB5,
0xD4, 0xBD, 0x97, 0x38, 0x35, 0xE2, 0x84, 0xAC,
0xFC, 0xDE, 0x8C, 0x50, 0xB6, 0x2D, 0x37, 0x21,
0xA8, 0xAE, 0x06, 0x31, 0x70, 0x87, 0x1E, 0xB6,
0x55, 0xB7, 0x5B, 0x28, 0x07, 0x00, 0x00, 0xC7,
0x55, 0xAD, 0xE7, 0xF5, 0xB6, 0xA6, 0x0C, 0xA3,
0x1A, 0x36, 0xAA, 0xC5, 0x37, 0x55, 0x24, 0x9F,
0xD9, 0x1A, 0x75, 0xD3, 0x5C, 0x5E, 0x9A, 0x43,
0xE9, 0x1A, 0xA2, 0x93, 0xAD, 0xD6, 0x3B, 0xD2,
0x2E, 0x82, 0x09, 0xE4, 0x1C, 0x0A, 0x7B, 0x0E,
0x50, 0xCD, 0xAF, 0x00, 0xE6, 0xF4, 0xCF, 0x54,
0xE0, 0xE9, 0xB8, 0x93, 0xF7, 0xD7, 0x64, 0x1A,
0x76, 0x0C, 0x2F, 0x85, 0x67, 0xE6, 0x7C, 0xFA,
0x3C, 0xB8, 0xC7, 0x42, 0xF6, 0x3D, 0x40, 0xDD,
0xBD, 0x2D, 0xC1, 0x0C, 0x2D, 0x17, 0x98, 0xDC,
0x37, 0x7D, 0x02, 0xD7, 0x23, 0x72, 0x09, 0xA5,
0xF2, 0x4C, 0xA3, 0xC8, 0xC9, 0x87, 0x89, 0xE4,
0xCF, 0x88, 0xD8, 0x6F, 0x8F, 0xD7, 0x1E, 0x83,
0x8B, 0x80, 0x11, 0xFE, 0xA8, 0x93, 0xE2, 0xCD,
0x38, 0x8A, 0x88, 0x1E, 0x66, 0x69, 0x3C, 0x98,
0x43, 0xC1, 0x0D, 0x98, 0x8F, 0x34, 0x33, 0x29,
0xC0, 0x33, 0xD0, 0xDD, 0xC1, 0x99, 0x80, 0x47,
0x5B, 0x02, 0xC3, 0x59, 0x04, 0x3F, 0x96, 0xC4,
0x84, 0x8D, 0xE2, 0xF7, 0x04, 0xCD, 0x94, 0xAB,
0x38, 0x82, 0x19, 0x45, 0x69, 0x21, 0x4E, 0x39,
0xB6, 0xF7, 0x78, 0xD5, 0x37, 0x0E, 0x8B, 0x07,
0xD3, 0xFA, 0x60, 0xE7, 0x41, 0x4B, 0xD0, 0x1A,
0xB6, 0x76, 0x61, 0xC0, 0x94, 0x21, 0xD1, 0x47,
0x4D, 0xCC, 0x7E, 0x50, 0x67, 0xA4, 0x2E, 0x93,
0x4D, 0xC5, 0xFE, 0x08, 0x51, 0x60, 0x98, 0x91,
0x65, 0x11, 0x7E, 0x32, 0xA4, 0x15, 0xA0, 0xC6,
0x35, 0xE8, 0xDA, 0xE2, 0xDB, 0x62, 0x8F, 0x9C,
0x5F, 0xF3, 0xF4, 0x27, 0xC0, 0x25, 0x10, 0x74,
0x76, 0x9F, 0x16, 0x13, 0xEC, 0xBF, 0x4A, 0xCE,
0x5D, 0x60, 0xFB, 0xFE, 0x14, 0xF8, 0x18, 0x70,
0xC2, 0x2D, 0xBD, 0x92, 0xAF, 0xEC, 0xAF, 0xE3,
0xAB, 0xA4, 0xBD, 0x7B, 0xDE, 0x47, 0x12, 0x3D,
0x1F, 0x8C, 0x1D, 0x74, 0x66, 0x2B, 0x2E, 0x7B,
0x0C, 0x08, 0xF3, 0x29, 0xCB, 0x89, 0x8D, 0x22,
0x38, 0x70, 0x7A, 0x24, 0xC5, 0x44, 0x28, 0xF7,
0x90, 0x9E, 0xD3, 0xFA, 0x6B, 0x4B, 0xDB, 0x32,
0xE0, 0xBE, 0x65, 0xBF, 0x37, 0x87, 0xF2, 0xA7,
0x6F, 0xB5, 0x48, 0xC4, 0xDE, 0x12, 0x20, 0x2C,
0x88, 0x46, 0x18, 0x74, 0xC2, 0x4A, 0x8A, 0x9F,
0xDF, 0x09, 0x4E, 0xBE, 0x94, 0xB8, 0x94, 0xED,
0xA6, 0x95, 0xCC, 0x54, 0x82, 0xB5, 0x8B, 0xF3,
0xE6, 0xBD, 0xA1, 0xD8, 0xE8, 0x19, 0xB8,
0x6B, 0x02, 0x12, 0xA0, 0x0A, 0x92, 0x1C, 0xDD,
0x85, 0x4C, 0xEB, 0x5E, 0x8E, 0xF0, 0x48, 0x26,
0x1F, 0xB8, 0x9B, 0xE1, 0x79, 0x96, 0xBF, 0x44,
0x0D, 0x88, 0x67, 0xF6, 0x48, 0x65, 0xD8, 0xE5,
0x33, 0x93, 0x05, 0x07, 0xAC, 0x0C, 0x0B, 0x60,
0xE5, 0xDB, 0x60, 0xD0, 0x3F, 0x42, 0x58, 0x8E,
0x73, 0x9D, 0x91, 0x6F, 0xD7, 0xD3, 0x14, 0x97,
0x75, 0xCA, 0xDC, 0xF2, 0xBA, 0x8C, 0x20, 0x13,
0xD8, 0xD2, 0x07, 0x74, 0xAA, 0xC2, 0x07, 0x89,
0xFF, 0x95, 0x27, 0x52, 0x63, 0x94, 0xA5, 0xF1,
0xA9, 0x20, 0x61, 0xEE, 0x56, 0x81, 0xA0, 0xDD,
0xF7, 0xF0, 0xAF, 0xD6, 0xF3, 0x83, 0x88, 0x9C,
0xD2, 0x50, 0xE5, 0x43, 0xB8, 0xD6, 0xF0, 0x93,
0x7D, 0x11, 0x52, 0x82, 0xFD, 0xD8, 0xCA, 0xF5,
0xC4, 0xD1, 0xE7, 0x78, 0x1C, 0xEC, 0x5C, 0x34,
0x82, 0x8D, 0x82, 0x88, 0xB3, 0x84, 0xBF, 0xCD,
0x82, 0xA5, 0x8D, 0xE7, 0xCA, 0xAD, 0x39, 0x9F,
0x98, 0x03, 0xD1, 0x05, 0xE0, 0xA3, 0x0A, 0x88,
0xA6, 0x5E, 0xBA, 0x8D, 0xC3, 0xF7, 0xB9, 0x07,
0x75, 0x14, 0x54, 0xE8, 0x3A, 0x96, 0x02, 0x94,
0xB5, 0x21, 0x7B, 0xDA, 0x30, 0xE8, 0xC6, 0x25,
0x1F, 0xE2, 0x53, 0x1D, 0xDA, 0xE5, 0xE1, 0x2A,
0xDD, 0x74, 0xDE, 0x03, 0xDA, 0x73, 0xCB, 0x1D,
0x1B, 0x1F, 0xCA, 0xFE, 0x23, 0x42, 0xFA, 0x47,
0x1E, 0xB3, 0x1E, 0x43, 0x55, 0xB4, 0x1B, 0x67,
0x51, 0x3C, 0x20, 0x42, 0x36, 0x57, 0xCA, 0x83,
0x5F, 0xD5, 0x1F, 0x7E, 0xC5, 0x80, 0x1E, 0x04,
0xCE, 0xD0, 0xDA, 0x72, 0xAF, 0xEA, 0x91, 0x60,
0xC2, 0xC5, 0x4E, 0xA1, 0x99, 0x55, 0x93, 0x09,
0xA8, 0x6F, 0xCE, 0x78, 0x71, 0xE1, 0x56, 0x01,
0x17, 0x76, 0xA1, 0x37, 0x20, 0x21, 0x42, 0xE9,
0xF5, 0xB7, 0x77, 0xC9, 0xF7, 0x18, 0x2A, 0xB4,
0x12, 0xD0, 0x0A, 0x60, 0x14, 0x02, 0x6A, 0xE5,
0x63, 0x39, 0x5B, 0xD4, 0xB9, 0xBE, 0xA9, 0x4D,
0x28, 0x86, 0xA3, 0x87, 0xB6, 0x91, 0xCF, 0x61,
0xEB, 0x83, 0x0F, 0xE7, 0x4E, 0x42, 0xC2, 0xDC,
0xDC, 0xF3, 0x2F, 0x31, 0x78, 0xC7, 0x73, 0x5A,
0xB2, 0x5C, 0x69, 0xDC, 0x89, 0x82, 0xDF, 0x2A,
0xEF, 0xC5, 0x36, 0x89, 0x8F, 0xF1, 0x1F, 0x2B,
0x8B, 0x56, 0x28, 0x2A, 0x68, 0x34, 0xBE, 0x69,
0x58, 0x89, 0x09, 0x19, 0x3D, 0xDE, 0xBC, 0xD3,
0x8F, 0x73, 0x30, 0xAA, 0xA5, 0x5F, 0xDF, 0x65,
0x91, 0xC2, 0xC3, 0x70, 0xE5, 0xF8, 0x9D, 0x26,
0x47, 0xBA, 0xAB, 0x90, 0x41, 0xD6, 0xE5, 0x84,
0xF7, 0xF8, 0x10, 0x0E, 0x09, 0x75, 0x4D, 0xCC,
0x45, 0x8B, 0x9A, 0x58, 0xB3, 0xD4, 0x58, 0xC1,
0x7C, 0x8E, 0x78, 0x11, 0xF9, 0x4A, 0xAA, 0xB5,
0xB5, 0x32, 0x04, 0x2E, 0x63, 0x78, 0x5A, 0x0C,
0xD0, 0xB2, 0xE7, 0x1F, 0x16, 0xD0, 0xDB, 0x21,
0x3A, 0xC5, 0xEE, 0xBF, 0x8F, 0xE8, 0x50, 0xF0,
0x93, 0x9B, 0xAF, 0x7C, 0x95, 0x5A, 0x9F, 0x61,
0xB0, 0xFC, 0x98, 0xE8, 0x9D, 0x99, 0xB8, 0xEA,
0x86, 0xDD, 0x4B, 0x84, 0x79, 0xD8, 0xEC, 0xCE,
0xE9, 0x66, 0xDC, 0x4F, 0xF8, 0x4E, 0x3D, 0x64,
0x28, 0xCB, 0xE0, 0x3E, 0x28, 0x81, 0x11, 0xB6,
0x4D, 0xFE, 0x20, 0x2C, 0xF5, 0xC4, 0xD0, 0x5E,
0x12, 0x05, 0x25, 0xEE, 0x6D, 0x81, 0xDE, 0x87,
0x5F, 0x45, 0xC8, 0x1B, 0x68, 0x33, 0xE2, 0xC8,
0xF9, 0x13, 0x0F, 0xF4, 0x6B, 0xFF, 0x75, 0x02,
0xF7, 0xAC, 0x25, 0xC3, 0xB7, 0x24, 0xC3, 0x88,
0xEF, 0xD1, 0xDC, 0xAC, 0xF6, 0x57, 0x5B, 0x4E,
0xC4, 0x74, 0x21, 0x89, 0x04, 0x44, 0xCA, 0x11,
0x74, 0xA7, 0xB2, 0x29, 0x43, 0x45, 0x2D, 0xBD,
0x2E, 0xB0, 0xA7, 0x81, 0x70, 0x78, 0xD4, 0x7C,
0xE9, 0x22, 0x30, 0x48, 0xF2, 0xF0, 0x4B, 0xD4,
0x9F, 0x49, 0x18, 0xF2, 0x82, 0xC9, 0x2C, 0xF0,
0xCD, 0xB6, 0x94, 0x86, 0x24, 0x14, 0xBC, 0x3F,
0x07, 0x9A, 0x54, 0x76, 0xDE, 0x53, 0x10, 0x2B,
0xAD, 0x54, 0x06, 0xD8, 0xFA, 0xE6, 0x27, 0x49,
0x5E, 0xB7, 0x9C, 0x68, 0xAE, 0x3C, 0x1E, 0x66,
0x7E, 0x88, 0xAD, 0xF2, 0x2E, 0xAE, 0x5E, 0xDE,
0xAC, 0x89, 0xCE, 0xED, 0x7D, 0x8C, 0x7D, 0x7E,
0x68, 0x49, 0x7F, 0x41, 0x79, 0xF4, 0x34, 0x28,
0x1E, 0xCC, 0x83, 0xE1, 0xE5, 0xCB, 0x29, 0xCA,
0x41, 0x36, 0xCD, 0x35, 0x84, 0x34, 0x63, 0x15,
0xFF, 0x7E, 0x3E, 0xEE, 0x9A, 0x3B, 0x52, 0x16,
0x3B, 0xAF, 0x4B, 0xE7, 0x84, 0xF2, 0x5B, 0x0A,
0x9B, 0x9F, 0x35, 0x4D, 0xCE, 0xA6, 0xBE, 0xB4,
0xF5, 0xAE, 0xBF, 0xA8, 0x9E, 0xEF, 0xA8, 0x2F,
0x58, 0x22, 0x74, 0xC1, 0x02, 0xB4, 0x34, 0x96,
0x57, 0xC4, 0x96, 0x88, 0x24, 0xA1, 0x81, 0xB5,
0x33, 0xA6, 0x9E, 0x18, 0x43, 0x3F, 0x35, 0xD3,
0x66, 0x03, 0x53, 0x47, 0xF7, 0x4B, 0x10, 0x61,
0x58, 0x06, 0xEE, 0x3B, 0xB1, 0x0F, 0x29, 0x4F,
0xFD, 0x62, 0x5C, 0x4B, 0x23, 0x52, 0x4E, 0x16,
0x5A, 0x2E, 0x06, 0x4E, 0xCB, 0x00, 0xCA, 0xD6,
0xF1, 0x06, 0x9D, 0x6D, 0x93, 0x72, 0x1D, 0x1D,
0x2F, 0x09, 0xBC, 0x66, 0xC6, 0x87, 0xC6, 0xD4,
0xF3, 0xB7, 0xD6, 0xFA, 0xFA, 0x67, 0xFD, 0xFF,
0x9A, 0x20, 0x1D, 0x24, 0xF5, 0xCD, 0xC3, 0xD6,
0xEC, 0x28, 0xB9, 0x9A, 0x93, 0x67, 0xFF, 0x9E,
0x19, 0x96, 0x70, 0xB9, 0x61, 0x26, 0x75, 0x58,
0x29, 0x52, 0x52, 0xDB, 0x75, 0xCC, 0x2E, 0xB2,
0x2F, 0xEB, 0x34, 0x7D, 0x83, 0x82, 0x23, 0xA2,
0x61, 0x4F, 0xED, 0x19, 0x64, 0xDC, 0xCD, 0x1C,
0x0E, 0xB8, 0x32, 0xF6, 0x5C, 0x68, 0x94, 0xA9,
0xDF, 0xBC, 0x23, 0xB4, 0x95, 0x3D, 0x75, 0x18,
0x14, 0xBE, 0xE1, 0x67, 0x44, 0xDD, 0x39, 0x7E,
0x10, 0x00, 0xFE, 0x90, 0xF4, 0x56, 0x15, 0x40,
0xCF, 0x8E, 0xBC, 0x95, 0xF9, 0x53, 0xA9, 0xD3,
0xFA, 0xB3, 0xCD, 0xF2, 0x82, 0xEC, 0xC5, 0xA3,
0xE2, 0xDD, 0xDD, 0xCD, 0xA3, 0xC7, 0x1D, 0x55,
0xEA, 0x7B, 0x4E, 0x93, 0xE9, 0xBE, 0x15, 0x34,
0xF2, 0x77, 0xA8, 0x44, 0x12, 0x22, 0x81, 0xF0,
0x82, 0xE0, 0x89, 0x71, 0xBE, 0xD3, 0x12, 0x04,
0x27, 0xFC, 0xB1, 0xC6, 0x16, 0x7C, 0x3C, 0x6D,
0xA4, 0xD5, 0x6C, 0xBA, 0x6B, 0x78, 0x03, 0xA9,
0x77, 0xCB, 0xD6, 0x69, 0xB4, 0x8F, 0xB6, 0xEA,
0xE9, 0xAA, 0x54, 0xA6, 0x8A, 0x2B, 0x75, 0x8A,
0x54, 0x3C, 0xE0, 0x24, 0xBD, 0x2D, 0xF4, 0xA7,
0x88, 0x84, 0x72, 0x2E, 0x3D, 0x49, 0x01, 0x6E,
0xBB, 0x01, 0x4C, 0x0D, 0x17, 0xDB, 0x7F, 0xF0,
0xF7, 0x67, 0x0F, 0xA3, 0x2F, 0x75, 0x39, 0x5D,
0x59, 0xEE, 0xD4, 0x0D, 0x12, 0x5A, 0xCA, 0x7A,
0x2B, 0xD7, 0xBF, 0x5D, 0xB7, 0xF4, 0xE0, 0x6F,
0x18, 0x29, 0x75, 0x13, 0xC7, 0x97, 0x2E, 0xEE,
0xF3, 0x28, 0x3B, 0x13, 0xC1, 0x34, 0x70, 0xA1,
0x94, 0x00, 0x5C, 0xA5, 0x5C, 0x41, 0x81, 0xB5,
0xC9, 0x6D, 0xD4, 0xE7, 0x1C, 0xC0, 0xC0, 0x4C,
0x23, 0x44, 0x9B, 0xDC, 0x24, 0xCB, 0x72, 0x9D,
0xE7, 0xDD, 0x80, 0x34, 0xFD, 0x55, 0xE7, 0xA6,
0xD0, 0x78, 0x3C, 0x0D, 0x00, 0x96, 0xC6, 0xBB,
0x33, 0x95, 0x35, 0xD8, 0x8D, 0x78, 0x8B, 0x64,
0x76, 0x55, 0x2B, 0xE0, 0x0E, 0xD7, 0x59, 0xC0,
0x21, 0x41, 0xF5, 0x66, 0x83, 0x56, 0xF4, 0x56,
0xB2, 0x07, 0x70, 0xE7, 0x71, 0x49, 0x42, 0xF5,
0xED, 0x09, 0x30, 0x88, 0x6F, 0xC3, 0x0D, 0x21,
0xD1, 0xF8, 0xC3, 0xD7, 0x2C, 0xCE, 0x40, 0x96,
0x5B, 0x30, 0xB3, 0x79, 0xF9, 0x0F, 0x0F, 0x07,
0xA7, 0x73, 0xC1, 0x96, 0x0E, 0xD8, 0x3E, 0x2D,
0xF6, 0x04, 0xF4, 0xDE, 0x50, 0x50, 0xC8, 0x4F,
0x8A, 0xE2, 0xDC, 0xED, 0x89, 0x5D, 0x2A, 0x07,
0xFE, 0xD9, 0x6E, 0xDE, 0xED, 0x2F, 0xDE, 0xC5,
0xE9, 0x18, 0x48, 0xE1, 0xAD, 0xC0, 0x27, 0xE7,
0xC5, 0xB2, 0xE8, 0xC8, 0x58, 0x2C, 0x52, 0x23,
0x78, 0x1B, 0x9D, 0xC4, 0xA6, 0x01, 0x07, 0xE6,
0xBE, 0xE2, 0x26, 0x17, 0x81, 0x5F, 0x49, 0x3A,
0xE6, 0xDB, 0x51, 0xC6, 0x06, 0xD7, 0x2C, 0x36,
0x13, 0xB0, 0x2B, 0x04, 0xE4, 0x29, 0xDD, 0xFE,
0x6F, 0x9B, 0xA3, 0xFD, 0x68, 0x79, 0x56, 0x24,
0x8A, 0x28, 0x6D, 0x69, 0xD6, 0x54, 0x5C, 0xD7,
0xF3, 0x28, 0x45, 0x77, 0x78, 0x27, 0x23, 0x35,
0xCA, 0x68, 0xBB, 0x1A, 0x47, 0x41, 0x51, 0x8E,
0x9A, 0x01, 0x49, 0xBD, 0xE8, 0xB6, 0x3A, 0x26,
0xFE, 0x4A, 0x43, 0xE5, 0xF7, 0xCB, 0x3B, 0x21,
0xBC, 0xF3, 0xD4, 0xEF, 0x38, 0x89, 0x06, 0x0A,
0xA2, 0x3D, 0xA1, 0xF6, 0xEC, 0x84, 0x0E, 0xF4,
0xAA, 0x1E, 0xD3, 0x4B, 0x5E, 0x91, 0x6C, 0xD9,
0x83, 0x5F, 0xB3, 0x0B, 0x47, 0x3E, 0x85, 0x18,
0xE9, 0x2D, 0x8A, 0x33, 0xAE, 0x34, 0xF6, 0x58,
0x54, 0x11, 0xDC, 0xD8, 0xC4, 0x6D, 0x7C, 0xAA,
0x15, 0xCD, 0xCC, 0x17, 0x7A, 0xF2, 0x77, 0x57,
0x5F, 0x40, 0xF8, 0x58, 0xF4, 0x96, 0xDF, 0x6E,
0xFC, 0xB9, 0x70, 0x17, 0x08, 0x5B, 0x43, 0x29,
0x4D, 0xD4, 0xA7, 0x6C, 0xDD, 0x8E, 0xC7, 0xFD,
0x8D, 0xE9, 0xE1, 0xBA, 0x7E, 0xFF, 0x39, 0xE5,
0x74, 0x79, 0x5E, 0x9A, 0x29, 0x2F, 0xC3, 0x0D,
0xDD, 0x65, 0x1C, 0x2F, 0xBB, 0x3C, 0x50, 0x8F,
0x5B, 0x47, 0x9A, 0x91, 0xEF, 0xC4, 0x0F, 0x7F,
0xCD, 0x85, 0xBC, 0x7C, 0x41, 0x9C, 0x96, 0x59,
0x17, 0x9B, 0x95, 0x3E, 0x26, 0x41, 0xDD, 0xE5,
0xD1, 0x72, 0x85, 0x10, 0x05, 0x9C, 0xBF, 0x88,
0x26, 0x29, 0xBE, 0x96, 0x6C, 0x0C, 0x36, 0x76,
0xE8, 0x06, 0xC5, 0x04, 0xD9, 0x06, 0xA8, 0x79,
0xF8, 0xEA, 0xF0, 0x60, 0xAF, 0x12, 0x20, 0xA8,
0x98, 0xAD, 0x74, 0xB9, 0x6F, 0x94, 0xCA, 0xCB,
0xDD, 0x9C, 0x4E, 0x62, 0xDF, 0xD1, 0x59, 0x3E,
0x58, 0xEE, 0x82, 0xB9, 0xAD, 0x9E, 0xB7, 0x45,
0x2C, 0x1F, 0x8C, 0x15, 0x77, 0xCC, 0xD2
};
const size_t kDeviceCertSize = sizeof(kDeviceCert);

93
cdm/test/test_host.cpp Normal file
View File

@@ -0,0 +1,93 @@
// Copyright 2015 Google Inc. All Rights Reserved.
#include "test_host.h"
#include <gtest/gtest.h>
#include <sys/time.h>
#include "device_cert.h"
#include "log.h"
using namespace widevine;
TestHost::TestHost() {
Reset();
}
void TestHost::Reset() {
struct timeval tv;
tv.tv_sec = tv.tv_usec = 0;
gettimeofday(&tv, NULL);
now_ = (tv.tv_sec * 1000LL) + (tv.tv_usec / 1000LL);
// Surprisingly, std::priority_queue has no clear().
while (!timers_.empty()) {
timers_.pop();
}
files_.clear();
files_["cert.bin"] =
std::string((const char*)kDeviceCert, kDeviceCertSize);
}
void TestHost::ElapseTime(int64_t milliseconds) {
int64_t goal_time = now_ + milliseconds;
while (now_ < goal_time) {
if (timers_.empty()) {
now_ = goal_time;
} else {
Timer t = timers_.top();
timers_.pop();
ASSERT_GE(t.expiry_time, now_);
now_ = t.expiry_time;
t.client->onTimerExpired(t.context);
}
}
}
int TestHost::NumTimers() const { return timers_.size(); }
bool TestHost::read(const std::string& name,
std::string* data) {
StorageMap::iterator it = files_.find(name);
bool ok = it != files_.end();
LOGD("read file: %s: %s", name.c_str(), ok ? "ok" : "fail");
if (!ok) return false;
*data = it->second;
return true;
}
bool TestHost::write(const std::string& name,
const std::string& data) {
LOGD("write file: %s", name.c_str());
files_[name] = data;
return true;
}
bool TestHost::exists(const std::string& name) {
StorageMap::iterator it = files_.find(name);
bool ok = it != files_.end();
LOGD("exists? %s: %s", name.c_str(), ok ? "true" : "false");
return ok;
}
bool TestHost::remove(const std::string& name) {
files_.erase(name);
return true;
}
int32_t TestHost::size(const std::string& name) {
StorageMap::iterator it = files_.find(name);
if (it == files_.end()) return -1;
return it->second.size();
}
int64_t TestHost::now() {
return now_;
}
void TestHost::setTimeout(int64_t delay_ms,
IClient* client,
void* context) {
int64_t expiry_time = now_ + delay_ms;
timers_.push(Timer(expiry_time, client, context));
}

60
cdm/test/test_host.h Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2015 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_TEST_TEST_HOST_H_
#define WVCDM_CDM_TEST_TEST_HOST_H_
#include <queue>
#include "cdm.h"
#include "override.h"
class TestHost : public widevine::Cdm::IStorage,
public widevine::Cdm::IClock,
public widevine::Cdm::ITimer {
public:
TestHost();
void Reset();
void ElapseTime(int64_t milliseconds);
int NumTimers() const;
virtual bool read(const std::string& name,
std::string* data) OVERRIDE;
virtual bool write(const std::string& name,
const std::string& data) OVERRIDE;
virtual bool exists(const std::string& name) OVERRIDE;
virtual bool remove(const std::string& name) OVERRIDE;
virtual int32_t size(const std::string& name) OVERRIDE;
virtual int64_t now() OVERRIDE;
virtual void setTimeout(int64_t delay_ms,
IClient* client,
void* context) OVERRIDE;
private:
struct Timer {
Timer(int64_t expiry_time, IClient* client, void* context)
: expiry_time(expiry_time), client(client), context(context) {}
bool operator<(const Timer& other) const {
// We want to reverse the order so that the smallest expiry times go to
// the top of the priority queue.
return expiry_time > other.expiry_time;
}
int64_t expiry_time;
IClient* client;
void* context;
};
int64_t now_;
std::priority_queue<Timer> timers_;
typedef std::map<std::string, std::string> StorageMap;
StorageMap files_;
};
// Owned and managed by the test runner.
extern TestHost* g_host;
#endif // WVCDM_CDM_TEST_TEST_HOST_H_

View File

@@ -1,134 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Review the TestHost_1 class below to observe how the CDM interfaces with
// the host application.
#include "test_host_1.h"
#include <gtest/gtest.h>
#include <sys/time.h>
#include "test_util.h"
static double GetCurrentTime() {
struct timeval tv;
tv.tv_sec = tv.tv_usec = 0;
gettimeofday(&tv, NULL);
return tv.tv_sec + (tv.tv_usec / (1000.0 * 1000.0));
}
TestHost_1::TestHost_1()
: current_time_(GetCurrentTime()),
has_new_key_message_(false),
has_new_key_error_(false),
cdm_(NULL) {
}
TestHost_1::~TestHost_1() {
if (cdm_)
cdm_->Destroy();
}
cdm::Buffer* TestHost_1::Allocate(int32_t capacity) {
return TestBuffer::Create(capacity);
}
void TestHost_1::SetTimer(int64_t delay_ms, void* context) {
double expiry_time = current_time_ + (delay_ms / 1000.0);
timers_.push(Timer(expiry_time, context));
}
double TestHost_1::GetCurrentWallTimeInSeconds() {
return current_time_;
}
void TestHost_1::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_1::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_1::FastForwardTime(double seconds) {
double goal_time = current_time_ + seconds;
while (current_time_ < goal_time) {
if (timers_.empty()) {
current_time_ = goal_time;
} else {
Timer t = timers_.top();
timers_.pop();
ASSERT_GE(t.expiry_time, current_time_);
current_time_ = t.expiry_time;
cdm_->TimerExpired(t.context);
}
}
}
void TestHost_1::GetPlatformString(const std::string& name,
std::string* value) {
*value = platform_strings_[name];
}
void TestHost_1::SetPlatformString(const std::string& name,
const std::string& value) {
platform_strings_[name] = value;
}
int TestHost_1::KeyMessagesSize() const { return key_messages_.size(); }
int TestHost_1::KeyErrorsSize() const { return key_errors_.size(); }
int TestHost_1::NumTimers() const { return timers_.size(); }
TestHost_1::KeyMessage TestHost_1::GetLastKeyMessage() {
if (!has_new_key_message_) {
return KeyMessage();
}
if (key_messages_.empty()) {
return KeyMessage();
}
has_new_key_message_ = false;
return key_messages_.back();
}
TestHost_1::KeyError TestHost_1::GetLastKeyError() {
if (!has_new_key_error_) return KeyError();
if (key_errors_.empty()) return KeyError();
has_new_key_error_ = false;
return key_errors_.back();
}
TestHost_1::KeyMessage TestHost_1::GetKeyMessage(int index) const {
return key_messages_[index];
}
TestHost_1::KeyError TestHost_1::GetKeyError(int index) const {
return key_errors_[index];
}
void TestHost_1::SetCdmPtr(cdm::ContentDecryptionModule_1* cdm) {
if (cdm_) {
cdm_->Destroy();
}
cdm_ = cdm;
}

View File

@@ -1,105 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_TEST_TEST_HOST_1_H_
#define WVCDM_CDM_TEST_TEST_HOST_1_H_
#include "content_decryption_module.h"
#include <map>
#include <queue>
#include "wv_cdm_common.h"
#include "wv_cdm_types.h"
class TestHost_1 : public cdm::Host_1 {
public:
// These structs are used to store the KeyMessages and KeyErrors passed to
// this class' objects.
struct 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_1();
virtual ~TestHost_1();
// cdm::Host implementation.
virtual cdm::Buffer* Allocate(int32_t capacity) OVERRIDE;
virtual void SetTimer(int64_t delay_ms, void* context) OVERRIDE;
virtual double GetCurrentWallTimeInSeconds() OVERRIDE;
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 GetPlatformString(const std::string& name,
std::string* value) OVERRIDE;
virtual void SetPlatformString(const std::string& name,
const std::string& value) OVERRIDE;
// Methods only for this test.
void FastForwardTime(double seconds);
int KeyMessagesSize() const;
int KeyErrorsSize() const;
int NumTimers() 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 SetCdmPtr(cdm::ContentDecryptionModule_1* cdm);
private:
struct Timer {
Timer(double expiry_time, void* context)
: expiry_time(expiry_time), context(context) {}
bool operator<(const Timer& other) const {
// We want to reverse the order so that the smallest expiry times go to
// the top of the priority queue.
return expiry_time > other.expiry_time;
}
double expiry_time;
void* context;
};
double current_time_;
std::priority_queue<Timer> timers_;
std::vector<KeyMessage> key_messages_;
std::vector<KeyError> key_errors_;
bool has_new_key_message_;
bool has_new_key_error_;
std::map<std::string, std::string> platform_strings_;
cdm::ContentDecryptionModule_1* cdm_;
CORE_DISALLOW_COPY_AND_ASSIGN(TestHost_1);
};
#endif // WVCDM_CDM_TEST_TEST_HOST_1_H_

View File

@@ -1,137 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Review the TestHost_4 class below to observe how the CDM interfaces with
// the host application.
#include "test_host_4.h"
#include <gtest/gtest.h>
#include <sys/time.h>
#include "test_host_4_file_io.h"
#include "test_util.h"
static double GetCurrentTime() {
struct timeval tv;
tv.tv_sec = tv.tv_usec = 0;
gettimeofday(&tv, NULL);
return tv.tv_sec + (tv.tv_usec / (1000.0 * 1000.0));
}
TestHost_4::TestHost_4()
: current_time_(GetCurrentTime()),
has_new_session_message_(false),
has_new_session_error_(false),
cdm_(NULL) {}
TestHost_4::~TestHost_4() {
if (cdm_) cdm_->Destroy();
}
cdm::Buffer* TestHost_4::Allocate(uint32_t capacity) {
return TestBuffer::Create(capacity);
}
void TestHost_4::SetTimer(int64_t delay_ms, void* context) {
double expiry_time = current_time_ + (delay_ms / 1000.0);
timers_.push(Timer(expiry_time, context));
}
double TestHost_4::GetCurrentWallTimeInSeconds() { return current_time_; }
void TestHost_4::FastForwardTime(double seconds) {
double goal_time = current_time_ + seconds;
while (current_time_ < goal_time) {
if (timers_.empty()) {
current_time_ = goal_time;
} else {
Timer t = timers_.top();
timers_.pop();
ASSERT_GE(t.expiry_time, current_time_);
current_time_ = t.expiry_time;
cdm_->TimerExpired(t.context);
}
}
}
int TestHost_4::SessionMessagesSize() const { return session_messages_.size(); }
int TestHost_4::SessionErrorsSize() const { return session_errors_.size(); }
int TestHost_4::NumTimers() const { return timers_.size(); }
TestHost_4::SessionMessage TestHost_4::GetLastSessionMessage() {
if (!has_new_session_message_) {
return SessionMessage();
}
if (session_messages_.empty()) {
return SessionMessage();
}
has_new_session_message_ = false;
return session_messages_.back();
}
TestHost_4::SessionError TestHost_4::GetLastSessionError() {
if (!has_new_session_error_) return SessionError();
if (session_errors_.empty()) return SessionError();
has_new_session_error_ = false;
return session_errors_.back();
}
TestHost_4::SessionMessage TestHost_4::GetSessionMessage(int index) const {
return session_messages_[index];
}
TestHost_4::SessionError TestHost_4::GetSessionError(int index) const {
return session_errors_[index];
}
void TestHost_4::SetCdmPtr(cdm::ContentDecryptionModule_4* cdm) {
if (cdm_) {
cdm_->Destroy();
}
cdm_ = cdm;
}
void TestHost_4::OnSessionCreated(uint32_t session_id,
const char* web_session_id,
uint32_t web_session_id_length) {
std::string webid(web_session_id, web_session_id_length);
session_map[session_id] = webid; // keep a parallel map with cdm.
}
void TestHost_4::OnSessionMessage(uint32_t session_id, const char* message,
uint32_t message_length,
const char* destination_url,
uint32_t destination_url_length) {
SessionMessage session_message;
session_message.session_id = session_id;
session_message.message.assign(message, message_length);
session_message.default_url.assign(destination_url, destination_url_length);
session_messages_.push_back(session_message);
has_new_session_message_ = true;
}
void TestHost_4::OnSessionUpdated(uint32_t session_id) {}
void TestHost_4::OnSessionClosed(uint32_t session_id) {
session_map.erase(session_id);
}
void TestHost_4::OnSessionError(uint32_t session_id, cdm::Status error_code,
uint32_t system_code) {
SessionError session_error;
session_error.session_id = session_id;
session_error.error_code = error_code;
session_error.system_code = system_code;
session_errors_.push_back(session_error);
has_new_session_error_ = true;
}
cdm::FileIO* TestHost_4::CreateFileIO(cdm::FileIOClient* client) {
return new TestHost_4_FileIO(this, client);
}

View File

@@ -1,108 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_TEST_TEST_HOST_4_H_
#define WVCDM_CDM_TEST_TEST_HOST_4_H_
#include "content_decryption_module.h"
#include "gmock/gmock.h"
#include <queue>
#include "wv_cdm_common.h"
#include "wv_cdm_types.h"
class TestHost_4 : public cdm::Host_4 {
public:
// These structs are used to store the SessionMessages and SessionErrors
// passed to this class' objects.
struct SessionMessage {
uint32_t session_id;
std::string message;
std::string default_url;
};
struct SessionError {
uint32_t session_id;
cdm::Status error_code;
uint32_t system_code;
};
TestHost_4();
virtual ~TestHost_4();
// cdm::Host implementation.
virtual cdm::Buffer* Allocate(uint32_t capacity) OVERRIDE;
virtual void SetTimer(int64_t delay_ms, void* context) OVERRIDE;
virtual double GetCurrentWallTimeInSeconds() OVERRIDE;
virtual void OnSessionCreated(uint32_t session_id, const char* web_session_id,
uint32_t web_session_id_length) OVERRIDE;
virtual void OnSessionMessage(uint32_t session_id, const char* message,
uint32_t message_length,
const char* destination_url,
uint32_t destination_url_length) OVERRIDE;
virtual void OnSessionUpdated(uint32_t session_id) OVERRIDE;
virtual void OnSessionClosed(uint32_t session_id) OVERRIDE;
virtual void OnSessionError(uint32_t session_id, cdm::Status error_code,
uint32_t system_code) OVERRIDE;
virtual cdm::FileIO* CreateFileIO(cdm::FileIOClient* client) OVERRIDE;
// Methods only for this test.
void FastForwardTime(double seconds);
int SessionMessagesSize() const;
int SessionErrorsSize() const;
int NumTimers() const;
// Returns Key{Message,Error} (replace Message with Error for SessionError).
// It returns the most recent message passed to SendSessionMessage(). Another
// call to this method without a new SendSessionMessage() call will return an
// empty SessionMessage struct.
SessionMessage GetLastSessionMessage();
SessionError GetLastSessionError();
SessionMessage GetSessionMessage(int index) const;
SessionError GetSessionError(int index) const;
void SetCdmPtr(cdm::ContentDecryptionModule_4* cdm);
std::map<uint32_t, std::string> session_map;
// Accessed by all FileIO objects.
std::map<std::string, std::string> file_store;
private:
struct Timer {
Timer(double expiry_time, void* context)
: expiry_time(expiry_time), context(context) {}
bool operator<(const Timer& other) const {
// We want to reverse the order so that the smallest expiry times go to
// the top of the priority queue.
return expiry_time > other.expiry_time;
}
double expiry_time;
void* context;
};
double current_time_;
std::priority_queue<Timer> timers_;
std::vector<SessionMessage> session_messages_;
std::vector<SessionError> session_errors_;
bool has_new_session_message_;
bool has_new_session_error_;
cdm::ContentDecryptionModule_4* cdm_;
CORE_DISALLOW_COPY_AND_ASSIGN(TestHost_4);
};
#endif // WVCDM_CDM_TEST_TEST_HOST_4_H_

View File

@@ -1,31 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Review the TestHost_4 class below to observe how the CDM interfaces with
// the host application.
#include "test_host_4_file_io.h"
#include <gtest/gtest.h>
void TestHost_4_FileIO::Open(const char* file_name, uint32_t file_name_size) {
ASSERT_EQ(0, file_name_.size());
file_name_.assign(file_name, file_name_size);
client_->OnOpenComplete(cdm::FileIOClient::kSuccess);
}
void TestHost_4_FileIO::Read() {
ASSERT_NE(0, file_name_.size());
const std::string& data = host_->file_store[file_name_];
client_->OnReadComplete(cdm::FileIOClient::kSuccess,
reinterpret_cast<const uint8_t*>(data.data()),
data.size());
}
void TestHost_4_FileIO::Write(const uint8_t* data, uint32_t data_size) {
ASSERT_NE(0, file_name_.size());
host_->file_store[file_name_].assign(reinterpret_cast<const char*>(data),
data_size);
client_->OnWriteComplete(cdm::FileIOClient::kSuccess);
}
void TestHost_4_FileIO::Close() { delete this; }

View File

@@ -1,34 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_TEST_TEST_HOST_4_FILE_IO_H_
#define WVCDM_CDM_TEST_TEST_HOST_4_FILE_IO_H_
#include <string>
#include "content_decryption_module.h"
#include "test_host_4.h"
#include "wv_cdm_common.h"
#include "wv_cdm_types.h"
class TestHost_4_FileIO : public cdm::FileIO {
public:
TestHost_4_FileIO(TestHost_4* host, cdm::FileIOClient* client)
: host_(host), client_(client) {}
virtual ~TestHost_4_FileIO() {}
// cdm::FileIO implementation.
virtual void Open(const char* file_name, uint32_t file_name_size) OVERRIDE;
virtual void Read() OVERRIDE;
virtual void Write(const uint8_t* data, uint32_t data_size) OVERRIDE;
virtual void Close() OVERRIDE;
private:
TestHost_4* host_;
cdm::FileIOClient* client_;
std::string file_name_;
CORE_DISALLOW_COPY_AND_ASSIGN(TestHost_4_FileIO);
};
#endif // WVCDM_CDM_TEST_TEST_HOST_4_FILE_IO_H_

View File

@@ -1,52 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "test_util.h"
TestBuffer* TestBuffer::Create(uint32_t capacity) {
return new TestBuffer(capacity);
}
void TestBuffer::Destroy() {
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() {
if (buffer_) {
delete[] buffer_;
buffer_ = NULL;
}
}
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_; }

View File

@@ -1,58 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#ifndef WVCDM_CDM_TEST_TEST_UTIL_H_
#define WVCDM_CDM_TEST_TEST_UTIL_H_
#include "content_decryption_module.h"
#include "wv_cdm_common.h"
#include "wv_cdm_types.h"
// 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 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);
};
#endif // WVCDM_CDM_TEST_TEST_UTIL_H_

View File

@@ -3,9 +3,9 @@
#ifndef WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_
#define WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_
#include <stdint.h>
#include <string>
#include <vector>
#include <stdint.h>
namespace wvcdm {
@@ -16,9 +16,11 @@ class CdmClientPropertySet {
virtual const std::string& security_level() const = 0;
virtual bool use_privacy_mode() const = 0;
virtual const std::string& service_certificate() const = 0;
virtual void set_service_certificate(const std::string& cert) = 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;
virtual const std::string& app_id() const = 0;
};
} // namespace wvcdm

View File

@@ -3,8 +3,12 @@
#ifndef WVCDM_CORE_CDM_ENGINE_H_
#define WVCDM_CORE_CDM_ENGINE_H_
#include <string>
#include "certificate_provisioning.h"
#include "crypto_session.h"
#include "initialization_data.h"
#include "lock.h"
#include "oemcrypto_adapter.h"
#include "scoped_ptr.h"
#include "wv_cdm_types.h"
@@ -14,6 +18,7 @@ namespace wvcdm {
class CdmClientPropertySet;
class CdmSession;
class CryptoEngine;
class UsagePropertySet;
class WvCdmEventListener;
typedef std::map<CdmSessionId, CdmSession*> CdmSessionMap;
@@ -26,11 +31,17 @@ class CdmEngine {
// Session related methods
virtual CdmResponseType OpenSession(const CdmKeySystem& key_system,
const CdmClientPropertySet* property_set,
CdmClientPropertySet* property_set,
const std::string& origin,
WvCdmEventListener* event_listener,
const CdmSessionId* forced_session_id,
CdmSessionId* session_id);
virtual CdmResponseType CloseSession(const CdmSessionId& session_id);
virtual bool IsOpenSession(const CdmSessionId& session_id);
virtual CdmResponseType OpenKeySetSession(const CdmKeySetId& key_set_id);
virtual CdmResponseType OpenKeySetSession(
const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set,
const std::string& origin, WvCdmEventListener* event_listener);
virtual CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id);
// License related methods
@@ -49,6 +60,9 @@ class CdmEngine {
// and renewal requests.
// key_request: This must be non-null and point to a CdmKeyMessage. The buffer
// will have its contents replaced with the key request.
// key_request_type: May be null. If it is non-null, it will be filled with
// key request type, whether it is an initial request,
// renewal request or release request etc.
// server_url: This must be non-null and point to a string. The string will
// have its contents replaced with the default URL (if one is
// known) to send this key request to.
@@ -56,11 +70,14 @@ class CdmEngine {
// will have its contents replaced with the key set ID of the
// session. Note that for non-offline license requests, the
// key set ID is empty, so the CdmKeySetId will be cleared.
// TODO(kqyang): Consider refactor GenerateKeyRequest to reduce the number of
// parameters.
virtual CdmResponseType GenerateKeyRequest(
const CdmSessionId& session_id, const CdmKeySetId& key_set_id,
const InitializationData& init_data, const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
std::string* server_url, CdmKeySetId* key_set_id_out);
CdmKeyRequestType* key_request_type, std::string* server_url,
CdmKeySetId* key_set_id_out);
// Accept license response and extract key info.
virtual CdmResponseType AddKey(const CdmSessionId& session_id,
@@ -82,33 +99,50 @@ class CdmEngine {
const CdmKeyResponse& key_data);
// Query system information
virtual CdmResponseType QueryStatus(CdmQueryMap* info);
virtual CdmResponseType QueryStatus(SecurityLevel security_level,
CdmQueryMap* info);
// Query session information
virtual CdmResponseType QuerySessionStatus(const CdmSessionId& session_id,
CdmQueryMap* key_info);
virtual bool IsReleaseSession(const CdmSessionId& session_id);
virtual bool IsOfflineSession(const CdmSessionId& session_id);
// Query license information
virtual CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
CdmQueryMap* key_info);
// Query seesion control information
// Query session control information
virtual CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id,
CdmQueryMap* key_info);
// Provisioning related methods
virtual CdmResponseType GetProvisioningRequest(
CdmCertificateType cert_type, const std::string& cert_authority,
CdmProvisioningRequest* request, std::string* default_url);
const std::string& origin, CdmProvisioningRequest* request,
std::string* default_url);
virtual CdmResponseType HandleProvisioningResponse(
CdmProvisioningResponse& response, std::string* cert,
std::string* wrapped_key);
const std::string& origin, const CdmProvisioningResponse& response,
std::string* cert, std::string* wrapped_key);
virtual CdmResponseType Unprovision(CdmSecurityLevel security_level);
virtual bool IsProvisioned(CdmSecurityLevel security_level,
const std::string& origin);
virtual CdmResponseType Unprovision(CdmSecurityLevel security_level,
const std::string& origin);
// Usage related methods for streaming licenses
virtual CdmResponseType GetUsageInfo(CdmUsageInfo* usage_info);
// Retrieve a random usage info from the list of all usage infos for this app
// id.
virtual CdmResponseType GetUsageInfo(const std::string& app_id,
CdmUsageInfo* usage_info);
// Retrieve the usage info for the specified pst.
// Returns UNKNOWN_ERROR if no usage info was found.
virtual CdmResponseType GetUsageInfo(const std::string& app_id,
const CdmSecureStopId& ssid,
CdmUsageInfo* usage_info);
virtual CdmResponseType ReleaseAllUsageInfo(const std::string& app_id);
virtual CdmResponseType ReleaseUsageInfo(
const CdmUsageInfoReleaseMessage& message);
@@ -123,32 +157,50 @@ class CdmEngine {
virtual bool IsKeyLoaded(const KeyId& key_id);
virtual bool FindSessionForKey(const KeyId& key_id, CdmSessionId* sessionId);
// Event listener related methods
virtual bool AttachEventListener(const CdmSessionId& session_id,
WvCdmEventListener* listener);
virtual bool DetachEventListener(const CdmSessionId& session_id,
WvCdmEventListener* listener);
// Used for notifying the Max-Res Engine of resolution changes
virtual void NotifyResolution(const CdmSessionId& session_id, uint32_t width,
uint32_t height);
// Timer expiration method
// Timer expiration method. This method is not re-entrant -- there can be
// only one timer.
// This method triggers appropriate event callbacks from |event_listener_|,
// which is assumed to be asynchronous -- i.e. an event should be dispatched
// to another thread which does the actual work. In particular, if a
// synchronous listener calls OpenSession or CloseSession, the thread will
// dead lock.
virtual void OnTimerEvent();
private:
// private methods
bool ValidateKeySystem(const CdmKeySystem& key_system);
CdmResponseType GetUsageInfo(const std::string& app_id,
SecurityLevel requested_security_level,
CdmUsageInfo* usage_info);
void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
std::string MapHdcpVersion(CryptoSession::HdcpCapability version);
// instance variables
CdmSessionMap sessions_;
CdmReleaseKeySetMap release_key_sets_;
CertificateProvisioning cert_provisioning_;
scoped_ptr<CertificateProvisioning> cert_provisioning_;
SecurityLevel cert_provisioning_requested_security_level_;
static bool seeded_;
// usage related variables
scoped_ptr<CdmSession> usage_session_;
int64_t last_usage_information_update_time;
scoped_ptr<UsagePropertySet> usage_property_set_;
int64_t last_usage_information_update_time_;
// Locks the list of sessions, |sessions_|, for the event timer. It will be
// locked in OpenSession, CloseSession. It is also locked in OnTimerEvent and
// OnKeyReleaseEvent while the list of event listeners is being generated.
// The layer above the CDM implementation is expected to handle thread
// synchronization to make sure other functions that access sessions do not
// occur simultaneously with OpenSession or CloseSession.
Lock session_list_lock_;
CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine);
};

View File

@@ -4,6 +4,7 @@
#define WVCDM_CORE_CDM_SESSION_H_
#include <set>
#include <string>
#include "crypto_session.h"
#include "device_files.h"
@@ -21,7 +22,9 @@ class WvCdmEventListener;
class CdmSession {
public:
explicit CdmSession(const CdmClientPropertySet* cdm_client_property_set);
CdmSession(CdmClientPropertySet* cdm_client_property_set,
const std::string& origin, WvCdmEventListener* event_listener,
const CdmSessionId* forced_session_id);
virtual ~CdmSession();
virtual CdmResponseType Init();
@@ -34,9 +37,10 @@ class CdmSession {
virtual const CdmSessionId& session_id() { return session_id_; }
virtual CdmResponseType GenerateKeyRequest(
const InitializationData& init_data, const CdmLicenseType license_type,
const InitializationData& init_data, CdmLicenseType license_type,
const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
std::string* server_url, CdmKeySetId* key_set_id);
CdmKeyRequestType* key_request_type, std::string* server_url,
CdmKeySetId* key_set_id);
// AddKey() - Accept license response and extract key info.
virtual CdmResponseType AddKey(const CdmKeyResponse& key_response,
@@ -74,24 +78,33 @@ class CdmSession {
virtual bool IsKeyLoaded(const KeyId& key_id);
virtual bool AttachEventListener(WvCdmEventListener* listener);
virtual bool DetachEventListener(WvCdmEventListener* listener);
// Used for notifying the Policy Engine of resolution changes
virtual void NotifyResolution(uint32_t width, uint32_t height);
virtual void OnTimerEvent();
virtual void OnTimerEvent(bool update_usage);
virtual void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
virtual void GetApplicationId(std::string* app_id);
virtual SecurityLevel GetRequestedSecurityLevel() {
return requested_security_level_;
}
virtual CdmSecurityLevel GetSecurityLevel() { return security_level_; }
// Delete usage information for the list of tokens, |provider_session_tokens|.
virtual CdmResponseType DeleteMultipleUsageInformation(
const std::vector<std::string>& provider_session_tokens);
virtual CdmResponseType UpdateUsageInformation();
virtual bool is_initial_usage_update() { return is_initial_usage_update_; }
virtual bool is_usage_update_needed() { return is_usage_update_needed_; }
virtual void reset_is_usage_update_needed() {
virtual void reset_usage_flags() {
is_initial_usage_update_ = false;
is_usage_update_needed_ = false;
}
virtual bool is_release() { return is_release_; }
virtual bool is_offline() { return is_offline_; }
// ReleaseCrypto() - Closes the underlying crypto session but leaves this
// object alive. It is invalid to call any method that requires a crypto
// session after calling this. Since calling this renders this object mostly
@@ -99,11 +112,10 @@ class CdmSession {
// release the underlying crypto session) rather than call this method.
virtual CdmResponseType ReleaseCrypto();
bool DeleteLicense();
private:
// Internal constructor
void Create(CdmLicense* license_parser, CryptoSession* crypto_session,
PolicyEngine* policy_engine, DeviceFiles* file_handle,
const CdmClientPropertySet* cdm_client_property_set);
friend class CdmSessionTest;
// Generate unique ID for each new session.
CdmSessionId GenerateSessionId();
@@ -111,11 +123,17 @@ class CdmSession {
CdmResponseType StoreLicense();
bool StoreLicense(DeviceFiles::LicenseState state);
bool DeleteLicense();
// These setters are for testing only. Takes ownership of the pointers.
void set_license_parser(CdmLicense* license_parser);
void set_crypto_session(CryptoSession* crypto_session);
void set_policy_engine(PolicyEngine* policy_engine);
void set_file_handle(DeviceFiles* file_handle);
// instance variables
bool initialized_;
CdmSessionId session_id_;
const std::string origin_;
scoped_ptr<CdmLicense> license_parser_;
scoped_ptr<CryptoSession> crypto_session_;
scoped_ptr<PolicyEngine> policy_engine_;
@@ -123,11 +141,15 @@ class CdmSession {
bool license_received_;
bool is_offline_;
bool is_release_;
bool is_usage_update_needed_;
bool is_initial_decryption_;
CdmSecurityLevel security_level_;
SecurityLevel requested_security_level_;
CdmAppParameterMap app_parameters_;
// decryption and usage flags
bool is_initial_decryption_;
bool has_decrypted_since_last_report_; // ... last report to policy engine.
bool is_initial_usage_update_;
bool is_usage_update_needed_;
// information useful for offline and usage scenarios
CdmKeyMessage key_request_;
@@ -142,18 +164,6 @@ class CdmSession {
// license type release and offline related information
CdmKeySetId key_set_id_;
std::set<WvCdmEventListener*> listeners_;
// For testing only
// Takes ownership of license_parser, crypto_session, policy_engine
// and device_files
CdmSession(CdmLicense* license_parser, CryptoSession* crypto_session,
PolicyEngine* policy_engine, DeviceFiles* file_handle,
const CdmClientPropertySet* cdm_client_property_set);
#if defined(UNIT_TEST)
friend class CdmSessionTest;
#endif
CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession);
};

View File

@@ -3,6 +3,8 @@
#ifndef WVCDM_CORE_CERTIFICATE_PROVISIONING_H_
#define WVCDM_CORE_CERTIFICATE_PROVISIONING_H_
#include <string>
#include "crypto_session.h"
#include "oemcrypto_adapter.h"
#include "wv_cdm_types.h"
@@ -20,11 +22,14 @@ class CertificateProvisioning {
CdmResponseType GetProvisioningRequest(SecurityLevel requested_security_level,
CdmCertificateType cert_type,
const std::string& cert_authority,
const std::string& origin,
CdmProvisioningRequest* request,
std::string* default_url);
CdmResponseType HandleProvisioningResponse(CdmProvisioningResponse& response,
std::string* cert,
std::string* wrapped_key);
CdmResponseType HandleProvisioningResponse(
const std::string& origin,
const CdmProvisioningResponse& response,
std::string* cert,
std::string* wrapped_key);
private:
void ComposeJsonRequestAsQueryString(const std::string& message,

View File

@@ -19,6 +19,6 @@ class Clock {
virtual int64_t GetCurrentTime();
};
}; // namespace wvcdm
} // namespace wvcdm
#endif // WVCDM_CORE_CLOCK_H_

View File

@@ -35,6 +35,6 @@ class CryptoKey {
std::string key_control_iv_;
};
}; // namespace wvcdm
} // namespace wvcdm
#endif // WVCDM_CORE_CRYPTO_KEY_H_

View File

@@ -3,8 +3,8 @@
#ifndef WVCDM_CORE_CRYPTO_SESSSION_H_
#define WVCDM_CORE_CRYPTO_SESSSION_H_
#include <string>
#include <map>
#include <string>
#include "lock.h"
#include "oemcrypto_adapter.h"
@@ -18,17 +18,13 @@ typedef std::map<CryptoKeyId, CryptoKey*> CryptoKeyMap;
class CryptoSession {
public:
// This enum should be kept in sync with the values specified for
// HDCP capabilities in OEMCryptoCENC.h. (See comments for
// OEMCrypto_GetHDCPCapability)
typedef OEMCrypto_HDCP_Capability HdcpCapability;
typedef enum {
kOemCryptoHdcpNotSupported = 0,
kOemCryptoHdcpVersion1 = 1,
kOemCryptoHdcpVersion2 = 2,
kOemCryptoHdcpVersion2_1 = 3,
kOemCryptoHdcpVersion2_2 = 4,
kOemCryptoNoHdcpDeviceAttached = 0xff,
} OemCryptoHdcpVersion;
kUsageDurationsInvalid = 0,
kUsageDurationPlaybackNotBegun = 1,
kUsageDurationsValid = 2,
} UsageDurationStatus;
CryptoSession();
virtual ~CryptoSession();
@@ -77,17 +73,34 @@ class CryptoSession {
// Media data path
virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
// Usage related methods
virtual bool UsageInformationSupport(bool* has_support);
virtual CdmResponseType UpdateUsageInformation();
virtual CdmResponseType DeactivateUsageInformation(
const std::string& provider_session_token);
virtual CdmResponseType GenerateUsageReport(
const std::string& provider_session_token, std::string* usage_report);
const std::string& provider_session_token, std::string* usage_report,
UsageDurationStatus* usage_duration_status,
int64_t* seconds_since_started, int64_t* seconds_since_last_played);
virtual CdmResponseType ReleaseUsageInformation(
const std::string& message, const std::string& signature,
const std::string& provider_session_token);
// Delete a usage information for a single token. This does not require
// a signed message from the server.
virtual CdmResponseType DeleteUsageInformation(
const std::string& provider_session_token);
// Delete usage information for a list of tokens. This does not require
// a signed message from the server.
virtual CdmResponseType DeleteMultipleUsageInformation(
const std::vector<std::string>& provider_session_tokens);
virtual CdmResponseType DeleteAllUsageReports();
virtual bool IsAntiRollbackHwPresent();
virtual bool GetHdcpCapabilities(OemCryptoHdcpVersion* current,
OemCryptoHdcpVersion* max);
virtual bool GetHdcpCapabilities(HdcpCapability* current,
HdcpCapability* max);
virtual bool GetRandom(size_t data_length, uint8_t* random_data);
virtual bool GetNumberOfOpenSessions(size_t* count);
virtual bool GetMaxNumberOfSessions(size_t* max);
private:
void Init();
@@ -109,6 +122,7 @@ class CryptoSession {
static int session_count_;
bool open_;
bool update_usage_table_after_close_session_;
CryptoSessionId oec_session_id_;
OEMCryptoBufferType destination_buffer_type_;
@@ -123,6 +137,6 @@ class CryptoSession {
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession);
};
}; // namespace wvcdm
} // namespace wvcdm
#endif // WVCDM_CORE_CRYPTO_SESSSION_H_

View File

@@ -3,6 +3,11 @@
#ifndef WVCDM_CORE_DEVICE_FILES_H_
#define WVCDM_CORE_DEVICE_FILES_H_
#include <unistd.h>
#include <set>
#include <string>
#include "scoped_ptr.h"
#include "wv_cdm_types.h"
@@ -30,10 +35,14 @@ class DeviceFiles {
return Init(security_level);
}
virtual bool StoreCertificate(const std::string& certificate,
virtual bool StoreCertificate(const std::string& origin,
const std::string& certificate,
const std::string& wrapped_private_key);
virtual bool RetrieveCertificate(std::string* certificate,
virtual bool RetrieveCertificate(const std::string& origin,
std::string* certificate,
std::string* wrapped_private_key);
virtual bool HasCertificate(const std::string& origin);
virtual bool RemoveCertificate(const std::string& origin);
virtual bool StoreLicense(const std::string& key_set_id,
const LicenseState state,
@@ -42,14 +51,17 @@ class DeviceFiles {
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);
const std::string& release_server_url,
int64_t playback_start_time,
int64_t last_playback_time,
const CdmAppParameterMap& app_parameters);
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,
int64_t* playback_start_time, int64_t* last_playback_time,
CdmAppParameterMap* app_parameters);
virtual bool DeleteLicense(const std::string& key_set_id);
virtual bool DeleteAllFiles();
virtual bool DeleteAllLicenses();
@@ -58,47 +70,78 @@ class DeviceFiles {
virtual bool StoreUsageInfo(const std::string& provider_session_token,
const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response);
virtual bool DeleteUsageInfo(const std::string& provider_session_token);
virtual bool DeleteUsageInfo();
const CdmKeyResponse& key_response,
const std::string& app_id);
virtual bool DeleteUsageInfo(const std::string& app_id,
const std::string& provider_session_token);
// Delete usage information from the file system. Puts a list of all the
// psts that were deleted from the file into |provider_session_tokens|.
virtual bool DeleteAllUsageInfoForApp(
const std::string& app_id,
std::vector<std::string>* provider_session_tokens);
// Retrieve one usage info from the file. Subsequent calls will retrieve
// subsequent entries in the table for this app_id.
virtual bool RetrieveUsageInfo(
const std::string& app_id,
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info);
// Retrieve the usage info entry specified by |provider_session_token|.
// Returns false if the entry could not be found.
virtual bool RetrieveUsageInfo(const std::string& app_id,
const std::string& provider_session_token,
CdmKeyMessage* license_request,
CdmKeyResponse* license_response);
private:
bool StoreFileWithHash(const char* name, const std::string& serialized_file);
bool StoreFileRaw(const char* name, const std::string& serialized_file);
bool RetrieveHashedFile(const char* name, std::string* serialized_file);
// Helpers that wrap the File interface and automatically handle hashing, as
// well as adding the device files base path to to the file name.
bool StoreFileWithHash(const std::string& name,
const std::string& serialized_file);
bool StoreFileRaw(const std::string& name,
const std::string& serialized_file);
bool RetrieveHashedFile(const std::string& name,
std::string* serialized_file);
bool FileExists(const std::string& name);
bool RemoveFile(const std::string& name);
ssize_t GetFileSize(const std::string& name);
// 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();
// For testing only:
static std::string GetCertificateFileName();
static std::string GetCertificateFileName(const std::string& origin);
static std::string GetLicenseFileNameExtension();
static std::string GetUsageInfoFileName();
static std::string GetBlankFileData();
static std::string GetUsageInfoFileName(const std::string& app_id);
static std::string GetFileNameSafeHash(const std::string& input);
// For testing only:
void SetTestFile(File* file);
#if defined(UNIT_TEST)
FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel);
FRIEND_TEST(DeviceFilesStoreTest, StoreCertificate);
FRIEND_TEST(DeviceCertificateStoreTest, StoreCertificate);
FRIEND_TEST(DeviceCertificateTest, ReadCertificate);
FRIEND_TEST(DeviceCertificateTest, HasCertificate);
FRIEND_TEST(DeviceFilesStoreTest, StoreLicense);
FRIEND_TEST(DeviceFilesTest, DeleteLicense);
FRIEND_TEST(DeviceFilesTest, ReadCertificate);
FRIEND_TEST(DeviceFilesTest, ReserveLicenseIds);
FRIEND_TEST(DeviceFilesTest, ReserveLicenseIdsDoesNotUseFileSystem);
FRIEND_TEST(DeviceFilesTest, RetrieveLicenses);
FRIEND_TEST(DeviceFilesTest, AppParametersBackwardCompatibility);
FRIEND_TEST(DeviceFilesTest, SecurityLevelPathBackwardCompatibility);
FRIEND_TEST(DeviceFilesTest, StoreLicenses);
FRIEND_TEST(DeviceFilesTest, UpdateLicenseState);
FRIEND_TEST(DeviceFilesUsageInfoTest, Delete);
FRIEND_TEST(DeviceFilesUsageInfoTest, DeleteAll);
FRIEND_TEST(DeviceFilesUsageInfoTest, Read);
FRIEND_TEST(DeviceFilesUsageInfoTest, Store);
FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest);
FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test);
FRIEND_TEST(WvCdmUsageInfoTest, DISABLED_UsageInfo);
FRIEND_TEST(WvCdmRequestLicenseTest, UsageInfoRetryTest);
FRIEND_TEST(WvCdmUsageInfoTest, UsageInfo);
FRIEND_TEST(WvCdmExtendedDurationTest, UsageOverflowTest);
#endif
static std::set<std::string> reserved_license_ids_;
scoped_ptr<File> file_;
CdmSecurityLevel security_level_;
bool initialized_;

View File

@@ -5,10 +5,11 @@
#ifndef WVCDM_CORE_FILE_STORE_H_
#define WVCDM_CORE_FILE_STORE_H_
#include "wv_cdm_types.h"
#include <unistd.h>
#include <string>
#include <vector>
#include <stddef.h>
#include <stdlib.h>
#include "wv_cdm_types.h"
namespace wvcdm {

View File

@@ -34,7 +34,6 @@ class InitializationData {
CdmInitData data_;
bool is_cenc_;
bool is_webm_;
CORE_DISALLOW_COPY_AND_ASSIGN(InitializationData);
};
} // namespace wvcdm

View File

@@ -12,6 +12,7 @@
namespace video_widevine_server {
namespace sdk {
class SignedMessage;
class LicenseRequest;
}
} // namespace video_widevine_server
@@ -23,21 +24,19 @@ class PolicyEngine;
class CdmLicense {
public:
CdmLicense();
CdmLicense(const CdmSessionId& session_id);
virtual ~CdmLicense();
virtual bool Init(const std::string& token, CryptoSession* session,
PolicyEngine* policy_engine);
virtual bool PrepareKeyRequest(const InitializationData& init_data,
const CdmLicenseType license_type,
const CdmAppParameterMap& app_parameters,
const CdmSessionId& session_id,
CdmKeyMessage* signed_request,
std::string* server_url);
virtual bool PrepareKeyUpdateRequest(bool is_renewal,
CdmKeyMessage* signed_request,
std::string* server_url);
virtual CdmResponseType PrepareKeyRequest(
const InitializationData& init_data, const CdmLicenseType license_type,
const CdmAppParameterMap& app_parameters, CdmKeyMessage* signed_request,
std::string* server_url);
virtual CdmResponseType PrepareKeyUpdateRequest(
bool is_renewal, const CdmAppParameterMap& app_parameters,
CdmKeyMessage* signed_request, std::string* server_url);
virtual CdmResponseType HandleKeyResponse(
const CdmKeyResponse& license_response);
virtual CdmResponseType HandleKeyUpdateResponse(
@@ -46,38 +45,50 @@ class CdmLicense {
virtual bool RestoreOfflineLicense(
const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response,
const CdmKeyResponse& license_renewal_response);
virtual bool RestoreUsageLicense(const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response);
virtual bool HasInitData() { return !stored_init_data_.empty(); }
const CdmKeyResponse& license_renewal_response,
int64_t playback_start_time, int64_t last_playback_time);
virtual bool RestoreLicenseForRelease(const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response);
virtual bool HasInitData() { return stored_init_data_.get(); }
virtual bool IsKeyLoaded(const KeyId& key_id);
virtual std::string provider_session_token() {
return provider_session_token_;
}
static CdmResponseType VerifySignedServiceCertificate(
const std::string& signed_service_certificate);
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);
CdmResponseType PrepareClientId(
bool encrypt, const std::string& certificate,
const CdmAppParameterMap& app_parameters,
video_widevine_server::sdk::LicenseRequest* license_request);
template <typename T>
bool PrepareContentId(const CdmLicenseType license_type,
const std::string& request_id, T* content_id);
static CdmResponseType VerifyAndExtractSignedServiceCertificate(
const std::string& signed_service_certificate,
std::string* service_certificate);
bool GetServiceCertificate(std::string* service_certificate);
CryptoSession* session_;
PolicyEngine* policy_engine_;
std::string server_url_;
std::string token_;
std::string service_certificate_;
std::string stored_init_data_;
const CdmSessionId session_id_;
scoped_ptr<InitializationData> stored_init_data_;
bool initialized_;
std::set<KeyId> loaded_keys_;
std::string provider_session_token_;
bool renew_with_client_id_;
// Used for certificate based licensing
CdmKeyMessage key_request_;
@@ -85,7 +96,8 @@ class CdmLicense {
scoped_ptr<Clock> clock_;
// For testing
CdmLicense(Clock* clock); // CdmLicense takes ownership of the clock.
// CdmLicense takes ownership of the clock.
CdmLicense(const CdmSessionId& session_id, Clock* clock);
#if defined(UNIT_TEST)
friend class CdmLicenseTest;
#endif

View File

@@ -21,10 +21,6 @@ class 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:
@@ -50,6 +46,6 @@ class AutoLock {
CORE_DISALLOW_COPY_AND_ASSIGN(AutoLock);
};
}; // namespace wvcdm
} // namespace wvcdm
#endif // WVCDM_CORE_LOCK_H_

View File

@@ -23,7 +23,7 @@ extern LogPriority g_cutoff;
// 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 InitLogging();
void Log(const char* file, int line, LogPriority level, const char* fmt, ...);
@@ -34,6 +34,6 @@ void Log(const char* file, int line, LogPriority level, const char* fmt, ...);
#define LOGD(...) Log(__FILE__, __LINE__, wvcdm::LOG_DEBUG, __VA_ARGS__)
#define LOGV(...) Log(__FILE__, __LINE__, wvcdm::LOG_VERBOSE, __VA_ARGS__)
}; // namespace wvcdm
} // namespace wvcdm
#endif // WVCDM_CORE_LOG_H_

View File

@@ -0,0 +1,105 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#ifndef WVCDM_CORE_MAX_RES_ENGINE_H_
#define WVCDM_CORE_MAX_RES_ENGINE_H_
#include <map>
#include "crypto_session.h"
#include "license_protocol.pb.h"
#include "lock.h"
#include "scoped_ptr.h"
#include "wv_cdm_types.h"
namespace wvcdm {
class Clock;
class MaxResEngineTest;
// Similar to the Policy Engine, 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 MaxResEngine {
public:
explicit MaxResEngine(CryptoSession* crypto_session);
virtual ~MaxResEngine();
// The value returned is computed during the last call to SetLicense/
// SetResolution/OnTimerEvent and may be out of sync depending on the amount
// of time elapsed. The current decryption status is not calculated when this
// function is called to avoid overhead in the decryption path.
virtual bool CanDecrypt(const KeyId& key_id);
// SetLicense is used in handling the initial license response. It stores
// an exact copy of the key constraints from the license.
virtual void SetLicense(const video_widevine_server::sdk::License& license);
// SetResolution is called when the current output resolution is updated by
// the decoder. The max-res engine will recalculate the current resolution
// constraints, (if any) which may affect the results for CanDecrypt().
virtual void SetResolution(uint32_t width, uint32_t height);
// OnTimerEvent is called when a timer fires. The max-res engine may check the
// current HDCP level using the crypto session, which may affect the results
// for CanDecrypt().
virtual void OnTimerEvent();
private:
typedef ::video_widevine_server::sdk::License::KeyContainer KeyContainer;
typedef ::video_widevine_server::sdk::License::KeyContainer::OutputProtection
OutputProtection;
typedef ::video_widevine_server::sdk::License::KeyContainer::
VideoResolutionConstraint VideoResolutionConstraint;
typedef ::google::protobuf::RepeatedPtrField<VideoResolutionConstraint>
ConstraintList;
class KeyStatus {
public:
explicit KeyStatus(const ConstraintList& constraints);
KeyStatus(const ConstraintList& constraints,
const OutputProtection::HDCP& default_hdcp_level);
bool can_decrypt() const { return can_decrypt_; }
void Update(uint32_t res,
CryptoSession::HdcpCapability current_hdcp_level);
private:
void Init(const ConstraintList& constraints);
VideoResolutionConstraint* GetConstraintForRes(uint32_t res);
static CryptoSession::HdcpCapability ProtobufHdcpToOemCryptoHdcp(
const OutputProtection::HDCP& input);
bool can_decrypt_;
CryptoSession::HdcpCapability default_hdcp_level_;
ConstraintList constraints_;
};
typedef std::map<wvcdm::KeyId,
wvcdm::MaxResEngine::KeyStatus*>::const_iterator KeyIterator;
void Init(CryptoSession* crypto_session, Clock* clock);
void DeleteAllKeys();
Lock status_lock_;
std::map<KeyId, KeyStatus*> keys_;
uint32_t current_resolution_;
int64_t next_check_time_;
scoped_ptr<Clock> clock_;
CryptoSession* crypto_session_;
// For testing
friend class MaxResEngineTest;
MaxResEngine(CryptoSession* crypto_session, Clock* clock);
CORE_DISALLOW_COPY_AND_ASSIGN(MaxResEngine);
};
} // wvcdm
#endif // WVCDM_CORE_MAX_RES_ENGINE_H_

View File

@@ -4,26 +4,36 @@
#define WVCDM_CORE_OEMCRYPTO_ADAPTER_H_
#include "OEMCryptoCENC.h"
#include "wv_cdm_types.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. */
// 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_CopyBuffer(
SecurityLevel level, const uint8_t* data_addr, size_t data_length,
OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags);
OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox,
size_t keyBoxLength,
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);
OEMCryptoResult OEMCrypto_GetHDCPCapability(SecurityLevel level,
OEMCrypto_HDCP_Capability* current,
OEMCrypto_HDCP_Capability* maximum);
bool OEMCrypto_SupportsUsageTable(SecurityLevel level);
bool OEMCrypto_IsAntiRollbackHwPresent(SecurityLevel level);
OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(SecurityLevel level,
size_t* count);
OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(SecurityLevel level,
size_t* maximum);
} // namespace wvcdm
#endif // WVCDM_CORE_OEMCRYPTO_ADAPTER_H_

View File

@@ -3,9 +3,12 @@
#ifndef WVCDM_CORE_POLICY_ENGINE_H_
#define WVCDM_CORE_POLICY_ENGINE_H_
#include <map>
#include <string>
#include "license_protocol.pb.h"
#include "max_res_engine.h"
#include "scoped_ptr.h"
#include "wv_cdm_types.h"
namespace wvcdm {
@@ -13,13 +16,15 @@ namespace wvcdm {
using video_widevine_server::sdk::LicenseIdentification;
class Clock;
class PolicyEngineTest;
class CryptoSession;
class WvCdmEventListener;
// 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(CdmSessionId session_id, WvCdmEventListener* event_listener,
CryptoSession* crypto_session);
virtual ~PolicyEngine();
// The value returned should be taken as a hint rather than an absolute
@@ -27,14 +32,12 @@ class PolicyEngine {
// 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.
virtual bool can_decrypt() { return can_decrypt_; }
virtual bool CanDecrypt(const KeyId& key_id);
// 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.
virtual void OnTimerEvent(bool* event_occurred, CdmEventType* event);
// that the timer has fired and dispatches the relevant events through
// |event_listener_|.
virtual void OnTimerEvent();
// SetLicense is used in handling the initial license response. It stores
// an exact copy of the policy information stored in the license.
@@ -42,8 +45,14 @@ class PolicyEngine {
// permits playback.
virtual void SetLicense(const video_widevine_server::sdk::License& license);
// SetLicenseForRelease is used when releasing a license. The keys in this
// license will be ignored, and any old keys will be expired.
virtual void SetLicenseForRelease(
const video_widevine_server::sdk::License& license);
// Call this on first decrypt to set the start of playback.
virtual void BeginDecryption(void);
virtual void DecryptionEvent(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
@@ -53,11 +62,32 @@ class PolicyEngine {
virtual void UpdateLicense(
const video_widevine_server::sdk::License& license);
// Used for notifying the Policy Engine of resolution changes
virtual void NotifyResolution(uint32_t width, uint32_t height);
virtual void NotifySessionExpiration();
virtual CdmResponseType Query(CdmQueryMap* key_info);
virtual const LicenseIdentification& license_id() { return license_id_; }
bool GetSecondsSinceStarted(int64_t* seconds_since_started);
bool GetSecondsSinceLastPlayed(int64_t* seconds_since_started);
// for offline save and restore
int64_t GetPlaybackStartTime() { return playback_start_time_; }
int64_t GetLastPlaybackTime() { return last_playback_time_; }
void RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time);
bool IsLicenseForFuture() { return license_state_ == kLicenseStatePending; }
bool IsPlaybackStarted() { return playback_start_time_ > 0; }
bool IsLicenseOrPlaybackDurationExpired(int64_t current_time);
private:
friend class PolicyEngineTest;
typedef enum {
kLicenseStateInitial,
kLicenseStatePending, // if license is issued for sometime in the future
@@ -67,11 +97,10 @@ class PolicyEngine {
kLicenseStateExpired
} LicenseState;
void Init(Clock* clock);
int64_t GetLicenseExpiryTime();
int64_t GetPlaybackExpiryTime();
bool IsLicenseDurationExpired(int64_t current_time);
int64_t GetLicenseDurationRemaining(int64_t current_time);
bool IsPlaybackDurationExpired(int64_t current_time);
int64_t GetPlaybackDurationRemaining(int64_t current_time);
bool IsRenewalDelayExpired(int64_t current_time);
@@ -80,8 +109,19 @@ class PolicyEngine {
void UpdateRenewalRequest(int64_t current_time);
// Notifies updates in keys information and fire OnKeysChange event if
// key changes.
void NotifyKeysChange(CdmKeyStatus new_status);
// Notifies updates in expiry time and fire OnExpirationUpdate event if
// expiry time changes.
void NotifyExpirationUpdate();
// These setters are for testing only. Takes ownership of the pointers.
void set_clock(Clock* clock);
void set_max_res_engine(MaxResEngine* max_res_engine);
LicenseState license_state_;
bool can_decrypt_;
// This is the current policy information for this license. This gets updated
// as license renewals occur.
@@ -97,6 +137,9 @@ class PolicyEngine {
// license request/renewal
int64_t license_start_time_;
int64_t playback_start_time_;
int64_t last_playback_time_;
int64_t last_expiry_time_;
bool last_expiry_time_set_;
// This is used as a reference point for policy management. This value
// represents an offset from license_start_time_. This is used to
@@ -104,11 +147,15 @@ class PolicyEngine {
int64_t next_renewal_time_;
int64_t policy_max_duration_seconds_;
Clock* clock_;
// Used to dispatch CDM events.
CdmSessionId session_id_;
WvCdmEventListener* event_listener_;
// For testing
friend class PolicyEngineTest;
PolicyEngine(Clock* clock);
scoped_ptr<MaxResEngine> max_res_engine_;
std::map<KeyId, CdmKeyStatus> keys_status_;
scoped_ptr<Clock> clock_;
CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine);
};

View File

@@ -12,12 +12,12 @@
#include "wv_cdm_types.h"
#if defined(UNIT_TEST)
# include "gtest/gtest_prod.h"
# include <gtest/gtest_prod.h>
#endif
namespace wvcdm {
typedef std::map<CdmSessionId, const CdmClientPropertySet*>
typedef std::map<CdmSessionId, CdmClientPropertySet*>
CdmClientPropertySetMap;
// This class saves information about features and properties enabled
@@ -37,9 +37,6 @@ class Properties {
static inline bool oem_crypto_use_userspace_buffers() {
return oem_crypto_use_userspace_buffers_;
}
static inline bool oem_crypto_require_usage_tables() {
return oem_crypto_require_usage_tables_;
}
static inline bool use_certificates_as_identification() {
return use_certificates_as_identification_;
}
@@ -52,24 +49,29 @@ class Properties {
static bool GetDeviceName(std::string* device_name);
static bool GetProductName(std::string* product_name);
static bool GetBuildInfo(std::string* build_info);
static bool GetWVCdmVersion(std::string* version);
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 AlwaysUseKeySetIds();
static bool GetSecurityLevelDirectories(std::vector<std::string>* dirs);
static bool GetSecurityLevel(const CdmSessionId& session_id,
std::string* security_level);
static bool GetApplicationId(const CdmSessionId& session_id,
std::string* app_id);
static bool GetServiceCertificate(const CdmSessionId& session_id,
std::string* service_certificate);
static bool SetServiceCertificate(const CdmSessionId& session_id,
const std::string& service_certificate);
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);
CdmClientPropertySet* property_set);
static bool RemoveSessionPropertySet(const CdmSessionId& session_id);
private:
static const CdmClientPropertySet* GetCdmClientPropertySet(
static CdmClientPropertySet* GetCdmClientPropertySet(
const CdmSessionId& session_id);
static void set_oem_crypto_use_secure_buffers(bool flag) {
oem_crypto_use_secure_buffers_ = flag;
@@ -80,9 +82,6 @@ class Properties {
static void set_oem_crypto_use_userspace_buffers(bool flag) {
oem_crypto_use_userspace_buffers_ = flag;
}
static void set_oem_crypto_require_usage_tables(bool flag) {
oem_crypto_require_usage_tables_ = flag;
}
static void set_use_certificates_as_identification(bool flag) {
use_certificates_as_identification_ = flag;
}
@@ -104,7 +103,6 @@ class Properties {
static bool oem_crypto_use_secure_buffers_;
static bool oem_crypto_use_fifo_;
static bool oem_crypto_use_userspace_buffers_;
static bool oem_crypto_require_usage_tables_;
static bool use_certificates_as_identification_;
static bool security_level_path_backward_compatibility_support_;
static scoped_ptr<CdmClientPropertySetMap> session_property_set_;

View File

@@ -60,6 +60,6 @@ class scoped_ptr {
CORE_DISALLOW_COPY_AND_ASSIGN(scoped_ptr);
};
}; // namespace wvcdm
} // namespace wvcdm
#endif // WVCDM_CORE_SCOPED_PTR_H_

View File

@@ -22,7 +22,8 @@ std::string HexEncode(const uint8_t* bytes, unsigned size);
std::string IntToString(int value);
std::string UintToString(unsigned int value);
int64_t htonll64(int64_t x);
inline int64_t ntohll64(int64_t x) { return htonll64(x); }
}; // namespace wvcdm
} // namespace wvcdm
#endif // WVCDM_CORE_STRING_CONVERSIONS_H_

View File

@@ -1,51 +0,0 @@
// 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_

View File

@@ -14,6 +14,10 @@ static const size_t KEY_SIZE = 16;
static const size_t MAC_KEY_SIZE = 32;
static const size_t KEYBOX_KEY_DATA_SIZE = 72;
// Use 0 to represent never expired license as specified in EME spec
// (NaN in JS translates to 0 in unix timestamp).
static const int64_t NEVER_EXPIRES = 0;
static const char SESSION_ID_PREFIX[] = "sid";
static const char KEY_SET_ID_PREFIX[] = "ksid";
static const char KEY_SYSTEM[] = "com.widevine";
@@ -43,6 +47,16 @@ 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_KEY_CURRENT_HDCP_LEVEL = "HdcpLevel";
// current HDCP level
static const std::string QUERY_KEY_MAX_HDCP_LEVEL = "MaxHdcpLevel";
// maximum supported HDCP level
static const std::string QUERY_KEY_USAGE_SUPPORT = "UsageSupport";
// whether usage reporting is supported
static const std::string QUERY_KEY_NUMBER_OF_OPEN_SESSIONS =
"NumberOfOpenSessions";
static const std::string QUERY_KEY_MAX_NUMBER_OF_SESSIONS =
"MaxNumberOfSessions";
static const std::string QUERY_VALUE_TRUE = "True";
static const std::string QUERY_VALUE_FALSE = "False";
@@ -52,6 +66,12 @@ 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";
static const std::string QUERY_VALUE_DISCONNECTED = "Disconnected";
static const std::string QUERY_VALUE_UNPROTECTED = "Unprotected";
static const std::string QUERY_VALUE_HDCP_V1 = "HDCP-1.x";
static const std::string QUERY_VALUE_HDCP_V2_0 = "HDCP-2.0";
static const std::string QUERY_VALUE_HDCP_V2_1 = "HDCP-2.1";
static const std::string QUERY_VALUE_HDCP_V2_2 = "HDCP-2.2";
static const std::string ISO_BMFF_VIDEO_MIME_TYPE = "video/mp4";
static const std::string ISO_BMFF_AUDIO_MIME_TYPE = "audio/mp4";
@@ -59,6 +79,8 @@ static const std::string WEBM_VIDEO_MIME_TYPE = "video/webm";
static const std::string WEBM_AUDIO_MIME_TYPE = "audio/webm";
static const std::string CENC_INIT_DATA_FORMAT = "cenc";
static const std::string WEBM_INIT_DATA_FORMAT = "webm";
static const char EMPTY_ORIGIN[] = "";
} // namespace wvcdm
#endif // WVCDM_CORE_WV_CDM_CONSTANTS_H_

View File

@@ -8,16 +8,19 @@
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;
virtual void OnSessionRenewalNeeded(const CdmSessionId& session_id) = 0;
virtual void OnSessionKeysChange(const CdmSessionId& session_id,
const CdmKeyStatusMap& keys_status,
bool has_new_usable_key) = 0;
// Note that a |new_expiry_time_seconds| of 0 represents never expired
// license.
virtual void OnExpirationUpdate(const CdmSessionId& session_id,
int64_t new_expiry_time_seconds) = 0;
private:
CORE_DISALLOW_COPY_AND_ASSIGN(WvCdmEventListener);

View File

@@ -3,8 +3,8 @@
#ifndef WVCDM_CORE_WV_CDM_TYPES_H_
#define WVCDM_CORE_WV_CDM_TYPES_H_
#include <map>
#include <stdint.h>
#include <map>
#include <string>
#include <vector>
@@ -15,6 +15,7 @@ typedef std::string CdmInitData;
typedef std::string CdmKeyMessage;
typedef std::string CdmKeyResponse;
typedef std::string KeyId;
typedef std::string CdmSecureStopId;
typedef std::string CdmSessionId;
typedef std::string CdmKeySetId;
typedef std::string RequestId;
@@ -28,6 +29,13 @@ typedef std::string CdmUsageInfoReleaseMessage;
typedef std::string CdmProvisioningRequest;
typedef std::string CdmProvisioningResponse;
enum CdmKeyRequestType {
kKeyRequestTypeUnknown,
kKeyRequestTypeInitial,
kKeyRequestTypeRenewal,
kKeyRequestTypeRelease,
};
enum CdmResponseType {
NO_ERROR,
UNKNOWN_ERROR,
@@ -39,21 +47,189 @@ enum CdmResponseType {
NEED_PROVISIONING,
DEVICE_REVOKED,
INSUFFICIENT_CRYPTO_RESOURCES,
ADD_KEY_ERROR,
CERT_PROVISIONING_GET_KEYBOX_ERROR_1,
CERT_PROVISIONING_GET_KEYBOX_ERROR_2,
CERT_PROVISIONING_INVALID_CERT_TYPE,
CERT_PROVISIONING_REQUEST_ERROR_1,
CERT_PROVISIONING_REQUEST_ERROR_2,
CERT_PROVISIONING_REQUEST_ERROR_3,
CERT_PROVISIONING_REQUEST_ERROR_4,
CERT_PROVISIONING_RESPONSE_ERROR_1,
CERT_PROVISIONING_RESPONSE_ERROR_2,
CERT_PROVISIONING_RESPONSE_ERROR_3,
CERT_PROVISIONING_RESPONSE_ERROR_4,
CERT_PROVISIONING_RESPONSE_ERROR_5,
CERT_PROVISIONING_RESPONSE_ERROR_6,
CERT_PROVISIONING_RESPONSE_ERROR_7,
CERT_PROVISIONING_RESPONSE_ERROR_8,
CRYPTO_SESSION_OPEN_ERROR_1,
CRYPTO_SESSION_OPEN_ERROR_2,
CRYPTO_SESSION_OPEN_ERROR_3,
CRYPTO_SESSION_OPEN_ERROR_4,
CRYPTO_SESSION_OPEN_ERROR_5,
DECRYPT_NOT_READY,
DEVICE_CERTIFICATE_ERROR_1,
DEVICE_CERTIFICATE_ERROR_2,
DEVICE_CERTIFICATE_ERROR_3,
DEVICE_CERTIFICATE_ERROR_4,
EMPTY_KEY_DATA_1,
EMPTY_KEY_DATA_2,
EMPTY_KEYSET_ID,
EMPTY_KEYSET_ID_ENG_1,
EMPTY_KEYSET_ID_ENG_2,
EMPTY_KEYSET_ID_ENG_3,
EMPTY_KEYSET_ID_ENG_4,
EMPTY_LICENSE_RENEWAL,
EMPTY_LICENSE_RESPONSE_1,
EMPTY_LICENSE_RESPONSE_2,
EMPTY_PROVISIONING_CERTIFICATE,
EMPTY_PROVISIONING_RESPONSE,
EMPTY_SESSION_ID,
GENERATE_DERIVED_KEYS_ERROR,
LICENSE_RENEWAL_NONCE_GENERATION_ERROR,
GENERATE_USAGE_REPORT_ERROR,
GET_LICENSE_ERROR,
GET_RELEASED_LICENSE_ERROR,
GET_USAGE_INFO_ERROR_1,
GET_USAGE_INFO_ERROR_2,
GET_USAGE_INFO_ERROR_3,
GET_USAGE_INFO_ERROR_4,
INIT_DATA_NOT_FOUND,
INVALID_CRYPTO_SESSION_1,
INVALID_CRYPTO_SESSION_2,
INVALID_CRYPTO_SESSION_3,
INVALID_CRYPTO_SESSION_4,
INVALID_CRYPTO_SESSION_5,
INVALID_DECRYPT_PARAMETERS_ENG_1,
INVALID_DECRYPT_PARAMETERS_ENG_2,
INVALID_DECRYPT_PARAMETERS_ENG_3,
INVALID_DECRYPT_PARAMETERS_ENG_4,
INVALID_DEVICE_CERTIFICATE_TYPE,
INVALID_KEY_SYSTEM,
INVALID_LICENSE_RESPONSE,
INVALID_LICENSE_TYPE,
INVALID_PARAMETERS_ENG_1,
INVALID_PARAMETERS_ENG_2,
INVALID_PARAMETERS_ENG_3,
INVALID_PARAMETERS_ENG_4,
INVALID_PARAMETERS_LIC_1,
INVALID_PARAMETERS_LIC_2,
INVALID_PROVISIONING_PARAMETERS_1,
INVALID_PROVISIONING_PARAMETERS_2,
INVALID_PROVISIONING_REQUEST_PARAM_1,
INVALID_PROVISIONING_REQUEST_PARAM_2,
INVALID_QUERY_KEY,
INVALID_SESSION_ID,
KEY_REQUEST_ERROR_1,
UNUSED_1, /* previously KEY_REQUEST_ERROR_2 */
KEY_SIZE_ERROR,
KEYSET_ID_NOT_FOUND_1,
KEYSET_ID_NOT_FOUND_2,
KEYSET_ID_NOT_FOUND_3,
LICENSE_ID_NOT_FOUND,
LICENSE_PARSER_INIT_ERROR,
LICENSE_PARSER_NOT_INITIALIZED_1,
LICENSE_PARSER_NOT_INITIALIZED_2,
LICENSE_PARSER_NOT_INITIALIZED_3,
LICENSE_RESPONSE_NOT_SIGNED,
LICENSE_RESPONSE_PARSE_ERROR_1,
LICENSE_RESPONSE_PARSE_ERROR_2,
LICENSE_RESPONSE_PARSE_ERROR_3,
LOAD_KEY_ERROR,
NO_CONTENT_KEY,
REFRESH_KEYS_ERROR,
RELEASE_ALL_USAGE_INFO_ERROR_1,
RELEASE_ALL_USAGE_INFO_ERROR_2,
RELEASE_KEY_ERROR,
RELEASE_KEY_REQUEST_ERROR,
RELEASE_LICENSE_ERROR_1,
RELEASE_LICENSE_ERROR_2,
RELEASE_USAGE_INFO_ERROR,
RENEW_KEY_ERROR_1,
RENEW_KEY_ERROR_2,
LICENSE_RENEWAL_SIGNING_ERROR,
RESTORE_OFFLINE_LICENSE_ERROR_1,
RESTORE_OFFLINE_LICENSE_ERROR_2,
SESSION_INIT_ERROR_1,
SESSION_INIT_ERROR_2,
SESSION_INIT_GET_KEYBOX_ERROR,
SESSION_NOT_FOUND_1,
SESSION_NOT_FOUND_2,
SESSION_NOT_FOUND_3,
SESSION_NOT_FOUND_4,
SESSION_NOT_FOUND_5,
SESSION_NOT_FOUND_6,
SESSION_NOT_FOUND_7,
SESSION_NOT_FOUND_8,
SESSION_NOT_FOUND_9,
SESSION_NOT_FOUND_10,
SESSION_NOT_FOUND_FOR_DECRYPT,
SESSION_KEYS_NOT_FOUND,
SIGNATURE_NOT_FOUND,
STORE_LICENSE_ERROR_1,
STORE_LICENSE_ERROR_2,
STORE_LICENSE_ERROR_3,
STORE_USAGE_INFO_ERROR,
UNPROVISION_ERROR_1,
UNPROVISION_ERROR_2,
UNPROVISION_ERROR_3,
UNPROVISION_ERROR_4,
UNSUPPORTED_INIT_DATA,
USAGE_INFO_NOT_FOUND,
LICENSE_RENEWAL_SERVICE_CERTIFICATE_GENERATION_ERROR,
PARSE_SERVICE_CERTIFICATE_ERROR,
SERVICE_CERTIFICATE_TYPE_ERROR,
CLIENT_ID_GENERATE_RANDOM_ERROR,
CLIENT_ID_AES_INIT_ERROR,
CLIENT_ID_AES_ENCRYPT_ERROR,
CLIENT_ID_RSA_INIT_ERROR,
CLIENT_ID_RSA_ENCRYPT_ERROR,
INVALID_QUERY_STATUS,
DUPLICATE_SESSION_ID_SPECIFIED,
EMPTY_PROVISIONING_CERTIFICATE_2,
LICENSE_PARSER_NOT_INITIALIZED_4,
INVALID_PARAMETERS_LIC_3,
INVALID_PARAMETERS_LIC_4,
UNUSED_2, /* previously INVALID_PARAMETERS_LIC_5 */
INVALID_PARAMETERS_LIC_6,
INVALID_PARAMETERS_LIC_7,
LICENSE_REQUEST_SERVICE_CERTIFICATE_GENERATION_ERROR,
CENC_INIT_DATA_UNAVAILABLE,
PREPARE_CENC_CONTENT_ID_FAILED,
WEBM_INIT_DATA_UNAVAILABLE,
PREPARE_WEBM_CONTENT_ID_FAILED,
UNSUPPORTED_INIT_DATA_FORMAT,
LICENSE_REQUEST_NONCE_GENERATION_ERROR,
LICENSE_REQUEST_SIGNING_ERROR,
EMPTY_LICENSE_REQUEST,
};
enum CdmKeyStatus {
kKeyStatusUsable,
kKeyStatusExpired,
kKeyStatusOutputNotAllowed,
kKeyStatusPending,
kKeyStatusInternalError,
};
typedef std::map<KeyId, CdmKeyStatus> CdmKeyStatusMap;
#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
kLicenseTypeRelease,
// If the original request was saved to make a service certificate request,
// use Deferred for the license type in the subsequent request.
kLicenseTypeDeferred,
};
enum SecurityLevel {
kLevelDefault,
kLevel3
};
enum CdmSecurityLevel {

View File

@@ -21,16 +21,43 @@
namespace {
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
const size_t kUsageReportsPerRequest = 1;
} // unnamed namespace
} // namespace
namespace wvcdm {
class UsagePropertySet : public CdmClientPropertySet {
public:
UsagePropertySet() {}
virtual ~UsagePropertySet() {}
void set_security_level(SecurityLevel security_level) {
if (kLevel3 == security_level)
security_level_ = QUERY_VALUE_SECURITY_LEVEL_L3;
else
security_level_.clear();
}
virtual const std::string& security_level() const { return security_level_; }
virtual bool use_privacy_mode() const { return false; }
virtual const std::string& service_certificate() const { return empty_; }
virtual void set_service_certificate(const std::string&) {}
virtual bool is_session_sharing_enabled() const { return false; }
virtual uint32_t session_sharing_id() const { return 0; }
virtual void set_session_sharing_id(uint32_t /* id */) {}
virtual const std::string& app_id() const { return app_id_; }
void set_app_id(const std::string& appId) { app_id_ = appId; }
private:
std::string app_id_;
std::string security_level_;
const std::string empty_;
};
bool CdmEngine::seeded_ = false;
CdmEngine::CdmEngine()
: cert_provisioning_requested_security_level_(kLevelDefault),
: cert_provisioning_(NULL),
cert_provisioning_requested_security_level_(kLevelDefault),
usage_session_(NULL),
last_usage_information_update_time(0) {
last_usage_information_update_time_(0) {
Properties::Init();
if (!seeded_) {
Clock clock;
@@ -40,6 +67,7 @@ CdmEngine::CdmEngine()
}
CdmEngine::~CdmEngine() {
AutoLock lock(session_list_lock_);
CdmSessionMap::iterator i(sessions_.begin());
for (; i != sessions_.end(); ++i) {
delete i->second;
@@ -48,24 +76,34 @@ CdmEngine::~CdmEngine() {
}
CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
const CdmClientPropertySet* property_set,
CdmClientPropertySet* property_set,
const std::string& origin,
WvCdmEventListener* event_listener,
const CdmSessionId* forced_session_id,
CdmSessionId* session_id) {
LOGI("CdmEngine::OpenSession");
if (!ValidateKeySystem(key_system)) {
LOGI("CdmEngine::OpenSession: invalid key_system = %s", key_system.c_str());
return KEY_ERROR;
return INVALID_KEY_SYSTEM;
}
if (!session_id) {
LOGE("CdmEngine::OpenSession: no session ID destination provided");
return KEY_ERROR;
return INVALID_PARAMETERS_ENG_1;
}
scoped_ptr<CdmSession> new_session(new CdmSession(property_set));
if (forced_session_id) {
if (sessions_.find(*forced_session_id) != sessions_.end()) {
return DUPLICATE_SESSION_ID_SPECIFIED;
}
}
scoped_ptr<CdmSession> new_session(
new CdmSession(property_set, origin, event_listener, forced_session_id));
if (new_session->session_id().empty()) {
LOGE("CdmEngine::OpenSession: failure to generate session ID");
return UNKNOWN_ERROR;
return EMPTY_SESSION_ID;
}
CdmResponseType sts = new_session->Init();
@@ -74,25 +112,30 @@ CdmResponseType CdmEngine::OpenSession(const CdmKeySystem& key_system,
cert_provisioning_requested_security_level_ =
new_session->GetRequestedSecurityLevel();
} else {
LOGE("CdmEngine::OpenSession: bad session init: %u", sts);
LOGE("CdmEngine::OpenSession: bad session init: %d", sts);
}
return sts;
}
*session_id = new_session->session_id();
AutoLock lock(session_list_lock_);
sessions_[*session_id] = new_session.release();
return NO_ERROR;
}
CdmResponseType CdmEngine::OpenKeySetSession(const CdmKeySetId& key_set_id) {
CdmResponseType CdmEngine::OpenKeySetSession(
const CdmKeySetId& key_set_id, CdmClientPropertySet* property_set,
const std::string& origin, WvCdmEventListener* event_listener) {
LOGI("CdmEngine::OpenKeySetSession");
if (key_set_id.empty()) {
LOGE("CdmEngine::OpenKeySetSession: invalid key set id");
return KEY_ERROR;
return EMPTY_KEYSET_ID_ENG_1;
}
CdmSessionId session_id;
CdmResponseType sts = OpenSession(KEY_SYSTEM, NULL, &session_id);
CdmResponseType sts =
OpenSession(KEY_SYSTEM, property_set, origin, event_listener,
NULL /* forced_session_id */, &session_id);
if (sts != NO_ERROR) return sts;
@@ -102,13 +145,12 @@ CdmResponseType CdmEngine::OpenKeySetSession(const CdmKeySetId& key_set_id) {
CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) {
LOGI("CdmEngine::CloseSession");
AutoLock lock(session_list_lock_);
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;
return SESSION_NOT_FOUND_1;
}
CdmSession* session = iter->second;
sessions_.erase(session_id);
delete session;
@@ -122,7 +164,7 @@ CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& 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;
return KEYSET_ID_NOT_FOUND_1;
}
CdmResponseType sts = CloseSession(iter->second);
@@ -130,33 +172,42 @@ CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) {
return sts;
}
bool CdmEngine::IsOpenSession(const CdmSessionId& session_id) {
CdmSessionMap::iterator iter = sessions_.find(session_id);
return iter != sessions_.end();
}
CdmResponseType CdmEngine::GenerateKeyRequest(
const CdmSessionId& session_id, const CdmKeySetId& key_set_id,
const InitializationData& init_data, const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
std::string* server_url, CdmKeySetId* key_set_id_out) {
CdmKeyRequestType* key_request_type, std::string* server_url,
CdmKeySetId* key_set_id_out) {
LOGI("CdmEngine::GenerateKeyRequest");
CdmSessionId id = session_id;
CdmResponseType sts;
if (license_type == kLicenseTypeRelease) {
// NOTE: If AlwaysUseKeySetIds() is true, there is no need to consult the
// release_key_sets_ map for release licenses.
if (license_type == kLicenseTypeRelease &&
!Properties::AlwaysUseKeySetIds()) {
if (key_set_id.empty()) {
LOGE("CdmEngine::GenerateKeyRequest: invalid key set ID");
return UNKNOWN_ERROR;
return EMPTY_KEYSET_ID_ENG_2;
}
if (!session_id.empty()) {
LOGE("CdmEngine::GenerateKeyRequest: invalid session ID = %s",
session_id.c_str());
return UNKNOWN_ERROR;
return INVALID_SESSION_ID;
}
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;
return KEYSET_ID_NOT_FOUND_2;
}
id = iter->second;
@@ -166,12 +217,12 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
if (iter == sessions_.end()) {
LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s",
id.c_str());
return KEY_ERROR;
return SESSION_NOT_FOUND_2;
}
if (!key_request) {
LOGE("CdmEngine::GenerateKeyRequest: no key request destination provided");
return KEY_ERROR;
return INVALID_PARAMETERS_ENG_2;
}
key_request->clear();
@@ -179,27 +230,23 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
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);
LOGE("CdmEngine::GenerateKeyRequest: key release restoration failed,"
"sts = %d", static_cast<int>(sts));
return sts;
}
}
sts = iter->second->GenerateKeyRequest(
init_data, license_type, app_parameters, key_request, server_url,
key_set_id_out);
init_data, license_type, app_parameters, key_request, key_request_type,
server_url, key_set_id_out);
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);
LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, "
"sts = %d", static_cast<int>(sts));
return sts;
}
@@ -221,18 +268,18 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id,
if (license_type_release) {
if (!key_set_id) {
LOGE("CdmEngine::AddKey: no key set id provided");
return KEY_ERROR;
return INVALID_PARAMETERS_ENG_3;
}
if (key_set_id->empty()) {
LOGE("CdmEngine::AddKey: invalid key set id");
return KEY_ERROR;
return EMPTY_KEYSET_ID_ENG_3;
}
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;
return KEYSET_ID_NOT_FOUND_3;
}
id = iter->second;
@@ -242,18 +289,18 @@ CdmResponseType CdmEngine::AddKey(const CdmSessionId& session_id,
if (iter == sessions_.end()) {
LOGE("CdmEngine::AddKey: session id not found = %s", id.c_str());
return KEY_ERROR;
return SESSION_NOT_FOUND_3;
}
if (key_data.empty()) {
LOGE("CdmEngine::AddKey: no key_data");
return KEY_ERROR;
return EMPTY_KEY_DATA_1;
}
CdmResponseType sts = iter->second->AddKey(key_data, key_set_id);
if (KEY_ADDED != sts) {
LOGE("CdmEngine::AddKey: keys not added, result = %d", (int)sts);
LOGE("CdmEngine::AddKey: keys not added, result = %d", sts);
return sts;
}
@@ -266,14 +313,14 @@ CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id,
if (key_set_id.empty()) {
LOGI("CdmEngine::RestoreKey: invalid key set id");
return KEY_ERROR;
return EMPTY_KEYSET_ID_ENG_4;
}
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;
return SESSION_NOT_FOUND_4;
}
CdmResponseType sts =
@@ -282,10 +329,10 @@ CdmResponseType CdmEngine::RestoreKey(const CdmSessionId& session_id,
cert_provisioning_requested_security_level_ =
iter->second->GetRequestedSecurityLevel();
}
if (sts != KEY_ADDED) {
if (sts != KEY_ADDED && sts != GET_RELEASED_LICENSE_ERROR) {
LOGE("CdmEngine::RestoreKey: restore offline session failed = %d", sts);
}
return sts;
return sts; // TODO ewew
}
CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) {
@@ -295,7 +342,7 @@ CdmResponseType CdmEngine::RemoveKeys(const CdmSessionId& session_id) {
if (iter == sessions_.end()) {
LOGE("CdmEngine::RemoveKeys: session_id not found = %s",
session_id.c_str());
return KEY_ERROR;
return SESSION_NOT_FOUND_5;
}
iter->second->ReleaseCrypto();
@@ -311,12 +358,12 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
if (iter == sessions_.end()) {
LOGE("CdmEngine::GenerateRenewalRequest: session_id not found = %s",
session_id.c_str());
return KEY_ERROR;
return SESSION_NOT_FOUND_6;
}
if (!key_request) {
LOGE("CdmEngine::GenerateRenewalRequest: no key request destination");
return KEY_ERROR;
return INVALID_PARAMETERS_ENG_4;
}
key_request->clear();
@@ -326,7 +373,7 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
if (KEY_MESSAGE != sts) {
LOGE("CdmEngine::GenerateRenewalRequest: key request gen. failed, sts=%d",
(int)sts);
sts);
return sts;
}
@@ -340,26 +387,31 @@ CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id,
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;
return SESSION_NOT_FOUND_7;
}
if (key_data.empty()) {
LOGE("CdmEngine::RenewKey: no key_data");
return KEY_ERROR;
return EMPTY_KEY_DATA_2;
}
CdmResponseType sts = iter->second->RenewKey(key_data);
if (KEY_ADDED != sts) {
LOGE("CdmEngine::RenewKey: keys not added, sts=%d", (int)sts);
LOGE("CdmEngine::RenewKey: keys not added, sts=%d", static_cast<int>(sts));
return sts;
}
return KEY_ADDED;
}
CdmResponseType CdmEngine::QueryStatus(CdmQueryMap* key_info) {
CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
CdmQueryMap* key_info) {
LOGI("CdmEngine::QueryStatus");
CryptoSession crypto_session;
if (security_level == kLevel3) {
CdmResponseType status = crypto_session.Open(kLevel3);
if (NO_ERROR != status) return INVALID_QUERY_STATUS;
}
switch (crypto_session.GetSecurityLevel()) {
case kSecurityLevelL1:
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
@@ -376,7 +428,7 @@ CdmResponseType CdmEngine::QueryStatus(CdmQueryMap* key_info) {
QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
break;
default:
return KEY_ERROR;
return INVALID_QUERY_KEY;
}
std::string deviceId;
@@ -399,6 +451,39 @@ CdmResponseType CdmEngine::QueryStatus(CdmQueryMap* key_info) {
(*key_info)[QUERY_KEY_PROVISIONING_ID] = provisioning_id;
}
CryptoSession::HdcpCapability current_hdcp;
CryptoSession::HdcpCapability max_hdcp;
success = crypto_session.GetHdcpCapabilities(&current_hdcp, &max_hdcp);
if (success) {
(*key_info)[QUERY_KEY_CURRENT_HDCP_LEVEL] = MapHdcpVersion(current_hdcp);
(*key_info)[QUERY_KEY_MAX_HDCP_LEVEL] = MapHdcpVersion(max_hdcp);
}
bool supports_usage_reporting;
success = crypto_session.UsageInformationSupport(&supports_usage_reporting);
if (success) {
(*key_info)[QUERY_KEY_USAGE_SUPPORT] =
supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
}
size_t number_of_open_sessions;
success = crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions);
if (success) {
std::ostringstream open_sessions_stream;
open_sessions_stream << number_of_open_sessions;
(*key_info)[QUERY_KEY_NUMBER_OF_OPEN_SESSIONS] =
open_sessions_stream.str();
}
size_t maximum_number_of_sessions;
success = crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions);
if (success) {
std::ostringstream max_sessions_stream;
max_sessions_stream << maximum_number_of_sessions;
(*key_info)[QUERY_KEY_MAX_NUMBER_OF_SESSIONS] =
max_sessions_stream.str();
}
return NO_ERROR;
}
@@ -409,11 +494,33 @@ CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id,
if (iter == sessions_.end()) {
LOGE("CdmEngine::QuerySessionStatus: session_id not found = %s",
session_id.c_str());
return KEY_ERROR;
return SESSION_NOT_FOUND_8;
}
return iter->second->QueryStatus(key_info);
}
bool CdmEngine::IsReleaseSession(const CdmSessionId& session_id) {
LOGI("CdmEngine::IsReleaseSession");
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::IsReleaseSession: session_id not found = %s",
session_id.c_str());
return false;
}
return iter->second->is_release();
}
bool CdmEngine::IsOfflineSession(const CdmSessionId& session_id) {
LOGI("CdmEngine::IsOfflineSession");
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::IsOfflineSession: session_id not found = %s",
session_id.c_str());
return false;
}
return iter->second->is_offline();
}
CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id,
CdmQueryMap* key_info) {
LOGI("CdmEngine::QueryKeyStatus");
@@ -421,7 +528,7 @@ CdmResponseType CdmEngine::QueryKeyStatus(const CdmSessionId& session_id,
if (iter == sessions_.end()) {
LOGE("CdmEngine::QueryKeyStatus: session_id not found = %s",
session_id.c_str());
return KEY_ERROR;
return SESSION_NOT_FOUND_9;
}
return iter->second->QueryKeyStatus(key_info);
}
@@ -433,7 +540,7 @@ CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id,
if (iter == sessions_.end()) {
LOGE("CdmEngine::QueryKeyControlInfo: session_id not found = %s",
session_id.c_str());
return KEY_ERROR;
return SESSION_NOT_FOUND_10;
}
return iter->second->QueryKeyControlInfo(key_info);
}
@@ -443,18 +550,30 @@ CdmResponseType CdmEngine::QueryKeyControlInfo(const CdmSessionId& session_id,
* 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.
* Returns NO_ERROR for success and CdmResponseType error code if fails.
*/
CdmResponseType CdmEngine::GetProvisioningRequest(
CdmCertificateType cert_type, const std::string& cert_authority,
CdmProvisioningRequest* request, std::string* default_url) {
if (!request || !default_url) {
LOGE("CdmEngine::GetProvisioningRequest: invalid input parameters");
return UNKNOWN_ERROR;
const std::string& origin, CdmProvisioningRequest* request,
std::string* default_url) {
if (!request) {
LOGE("CdmEngine::GetProvisioningRequest: invalid output parameters");
return INVALID_PROVISIONING_REQUEST_PARAM_1;
}
return cert_provisioning_.GetProvisioningRequest(
if (!default_url) {
LOGE("CdmEngine::GetProvisioningRequest: invalid output parameters");
return INVALID_PROVISIONING_REQUEST_PARAM_2;
}
if (NULL == cert_provisioning_.get()) {
cert_provisioning_.reset(new CertificateProvisioning());
}
CdmResponseType ret = cert_provisioning_->GetProvisioningRequest(
cert_provisioning_requested_security_level_, cert_type, cert_authority,
request, default_url);
origin, request, default_url);
if (ret != NO_ERROR) {
cert_provisioning_.reset(NULL); // Release resources.
}
return ret;
}
/*
@@ -462,47 +581,183 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
* 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.
* Returns NO_ERROR for success and CdmResponseType error code if fails.
*/
CdmResponseType CdmEngine::HandleProvisioningResponse(
CdmProvisioningResponse& response, std::string* cert,
std::string* wrapped_key) {
const std::string& origin, const CdmProvisioningResponse& response,
std::string* cert, std::string* wrapped_key) {
if (response.empty()) {
LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response.");
return UNKNOWN_ERROR;
cert_provisioning_.reset(NULL);
return EMPTY_PROVISIONING_RESPONSE;
}
if (NULL == cert) {
LOGE(
"CdmEngine::HandleProvisioningResponse: invalid certificate "
"destination");
return UNKNOWN_ERROR;
cert_provisioning_.reset(NULL);
return INVALID_PROVISIONING_PARAMETERS_1;
}
if (NULL == wrapped_key) {
LOGE(
"CdmEngine::HandleProvisioningResponse: invalid wrapped key "
"destination");
return UNKNOWN_ERROR;
LOGE("CdmEngine::HandleProvisioningResponse: invalid wrapped key "
"destination");
cert_provisioning_.reset(NULL);
return INVALID_PROVISIONING_PARAMETERS_2;
}
return cert_provisioning_.HandleProvisioningResponse(response, cert,
wrapped_key);
if (NULL == cert_provisioning_.get()) {
LOGE("CdmEngine::HandleProvisioningResponse: provisioning object missing.");
return EMPTY_PROVISIONING_CERTIFICATE;
}
CdmResponseType ret = cert_provisioning_->HandleProvisioningResponse(
origin, response, cert, wrapped_key);
cert_provisioning_.reset(NULL); // Release resources.
return ret;
}
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
bool CdmEngine::IsProvisioned(CdmSecurityLevel security_level,
const std::string& origin) {
DeviceFiles handle;
if (!handle.Init(security_level)) {
LOGE("CdmEngine::IsProvisioned: unable to initialize device files");
return false;
}
return handle.HasCertificate(origin);
}
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level,
const std::string& origin) {
DeviceFiles handle;
if (!handle.Init(security_level)) {
LOGE("CdmEngine::Unprovision: unable to initialize device files");
return UNKNOWN_ERROR;
return UNPROVISION_ERROR_1;
}
if (!handle.DeleteAllFiles()) {
LOGE("CdmEngine::Unprovision: unable to delete files");
return UNKNOWN_ERROR;
if (origin != EMPTY_ORIGIN) {
if (!handle.RemoveCertificate(origin)) {
LOGE("CdmEngine::Unprovision: unable to delete certificate for origin %s",
origin.c_str());
return UNPROVISION_ERROR_2;
}
return NO_ERROR;
} else {
if (!handle.DeleteAllFiles()) {
LOGE("CdmEngine::Unprovision: unable to delete files");
return UNPROVISION_ERROR_3;
}
scoped_ptr<CryptoSession> crypto_session(new CryptoSession());
CdmResponseType status = crypto_session->Open(
security_level == kSecurityLevelL3 ? kLevel3 : kLevelDefault);
if (NO_ERROR != status) {
LOGE("CdmEngine::Unprovision: error opening crypto session: %d", status);
return UNPROVISION_ERROR_4;
}
status = crypto_session->DeleteAllUsageReports();
if (status != NO_ERROR) {
LOGE("CdmEngine::Unprovision: error deleteing usage reports: %d", status);
}
return status;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
usage_session_.reset(new CdmSession(NULL));
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
const CdmSecureStopId& ssid,
CdmUsageInfo* usage_info) {
if (NULL == usage_property_set_.get()) {
usage_property_set_.reset(new UsagePropertySet());
}
usage_property_set_->set_security_level(kLevelDefault);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(
new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL));
CdmResponseType status = usage_session_->Init();
if (NO_ERROR != status) {
LOGE("CdmEngine::GetUsageInfo: session init error");
return status;
}
DeviceFiles handle;
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("CdmEngine::GetUsageInfo: device file init error");
return GET_USAGE_INFO_ERROR_1;
}
CdmKeyMessage license_request;
CdmKeyResponse license_response;
if (!handle.RetrieveUsageInfo(app_id, ssid, &license_request,
&license_response)) {
usage_property_set_->set_security_level(kLevel3);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(
new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL));
status = usage_session_->Init();
if (NO_ERROR != status) {
LOGE("CdmEngine::GetUsageInfo: session init error");
return status;
}
if (!handle.Reset(usage_session_->GetSecurityLevel())) {
LOGE("CdmEngine::GetUsageInfo: device file init error");
return GET_USAGE_INFO_ERROR_2;
}
if (!handle.RetrieveUsageInfo(app_id, ssid, &license_request,
&license_response)) {
// No entry found for that ssid.
return USAGE_INFO_NOT_FOUND;
}
}
std::string server_url;
usage_info->resize(1);
status =
usage_session_->RestoreUsageSession(license_request, license_response);
if (KEY_ADDED != status) {
LOGE("CdmEngine::GetUsageInfo: restore usage session error %d", status);
usage_info->clear();
return status;
}
status =
usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url);
if (KEY_MESSAGE != status) {
LOGE("CdmEngine::GetUsageInfo: generate release request error: %d", status);
usage_info->clear();
return status;
}
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
CdmUsageInfo* usage_info) {
// Return a random usage report from a random security level
SecurityLevel security_level = ((rand() % 2) == 0) ? kLevelDefault : kLevel3;
CdmResponseType status = UNKNOWN_ERROR;
do {
status = GetUsageInfo(app_id, security_level, usage_info);
if (KEY_MESSAGE == status && !usage_info->empty()) return status;
} while (KEY_CANCELED == status);
security_level = (kLevel3 == security_level) ? kLevelDefault : kLevel3;
do {
status = GetUsageInfo(app_id, security_level, usage_info);
if (NEED_PROVISIONING == status)
return NO_ERROR; // Valid scenario that one of the security
// levels has not been provisioned
} while (KEY_CANCELED == status);
return status;
}
CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
SecurityLevel requested_security_level,
CdmUsageInfo* usage_info) {
if (NULL == usage_property_set_.get()) {
usage_property_set_.reset(new UsagePropertySet());
}
usage_property_set_->set_security_level(requested_security_level);
usage_property_set_->set_app_id(app_id);
usage_session_.reset(
new CdmSession(usage_property_set_.get(), EMPTY_ORIGIN, NULL, NULL));
CdmResponseType status = usage_session_->Init();
if (NO_ERROR != status) {
@@ -513,13 +768,13 @@ CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
DeviceFiles handle;
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("CdmEngine::GetUsageInfo: unable to initialize device files");
return status;
return GET_USAGE_INFO_ERROR_3;
}
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> > license_info;
if (!handle.RetrieveUsageInfo(&license_info)) {
if (!handle.RetrieveUsageInfo(app_id, &license_info)) {
LOGE("CdmEngine::GetUsageInfo: unable to read usage information");
return UNKNOWN_ERROR;
return GET_USAGE_INFO_ERROR_4;
}
if (0 == license_info.size()) {
@@ -543,25 +798,59 @@ CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
status =
usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url);
if (KEY_MESSAGE != status) {
LOGE("CdmEngine::GetUsageInfo: generate release request error: %ld",
status);
usage_info->clear();
return status;
switch (status) {
case KEY_MESSAGE:
break;
case KEY_CANCELED: // usage information not present in
usage_session_->DeleteLicense(); // OEMCrypto, delete and try again
usage_info->clear();
break;
default:
LOGE("CdmEngine::GetUsageInfo: generate release request error: %d",
status);
usage_info->clear();
break;
}
return KEY_MESSAGE;
return status;
}
CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
CdmResponseType status = NO_ERROR;
for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) {
DeviceFiles handle;
if (handle.Init(static_cast<CdmSecurityLevel>(j))) {
std::vector<std::string> provider_session_tokens;
if (!handle.DeleteAllUsageInfoForApp(app_id, &provider_session_tokens)) {
LOGE("CdmEngine::ReleaseAllUsageInfo: failed to delete L%d secure"
"stops", j);
status = RELEASE_ALL_USAGE_INFO_ERROR_1;
} else {
CdmResponseType status2 = usage_session_->
DeleteMultipleUsageInformation(provider_session_tokens);
if (status2 != NO_ERROR) {
status = status2;
}
}
} else {
LOGE("CdmEngine::ReleaseAllUsageInfo: failed to initialize L%d device"
"files", j);
status = RELEASE_ALL_USAGE_INFO_ERROR_2;
}
}
return status;
}
CdmResponseType CdmEngine::ReleaseUsageInfo(
const CdmUsageInfoReleaseMessage& message) {
if (NULL == usage_session_.get()) {
LOGE("CdmEngine::ReleaseUsageInfo: cdm session not initialized");
return UNKNOWN_ERROR;
return RELEASE_USAGE_INFO_ERROR;
}
CdmResponseType status = usage_session_->ReleaseKey(message);
usage_session_.reset(NULL);
if (NO_ERROR != status) {
LOGE("CdmEngine::ReleaseUsageInfo: release key error: %ld", status);
LOGE("CdmEngine::ReleaseUsageInfo: release key error: %d", status);
}
return status;
}
@@ -570,25 +859,26 @@ CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id,
const CdmDecryptionParameters& parameters) {
if (parameters.key_id == NULL) {
LOGE("CdmEngine::Decrypt: no key_id");
return KEY_ERROR;
return INVALID_DECRYPT_PARAMETERS_ENG_1;
}
if (parameters.encrypt_buffer == NULL) {
LOGE("CdmEngine::Decrypt: no src encrypt buffer");
return KEY_ERROR;
return INVALID_DECRYPT_PARAMETERS_ENG_2;
}
if (parameters.iv == NULL) {
LOGE("CdmEngine::Decrypt: no iv");
return KEY_ERROR;
return INVALID_DECRYPT_PARAMETERS_ENG_3;
}
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.
return INVALID_DECRYPT_PARAMETERS_ENG_4;
}
// else we must be level 1 direct and we don't need to return a buffer.
}
CdmSessionMap::iterator iter;
@@ -605,7 +895,7 @@ CdmResponseType CdmEngine::Decrypt(const CdmSessionId& session_id,
if (iter == sessions_.end()) {
LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d",
session_id.c_str(), session_id.size());
return KEY_ERROR;
return SESSION_NOT_FOUND_FOR_DECRYPT;
}
return iter->second->Decrypt(parameters);
@@ -650,24 +940,12 @@ bool CdmEngine::FindSessionForKey(const KeyId& key_id,
return false;
}
bool CdmEngine::AttachEventListener(const CdmSessionId& session_id,
WvCdmEventListener* listener) {
void CdmEngine::NotifyResolution(const CdmSessionId& session_id, uint32_t width,
uint32_t height) {
CdmSessionMap::iterator iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
return false;
if (iter != sessions_.end()) {
iter->second->NotifyResolution(width, height);
}
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) {
@@ -677,37 +955,73 @@ bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
void CdmEngine::OnTimerEvent() {
Clock clock;
uint64_t current_time = clock.GetCurrentTime();
bool update_usage_information = false;
bool usage_update_period_expired = false;
if (current_time - last_usage_information_update_time >
if (current_time - last_usage_information_update_time_ >
kUpdateUsageInformationPeriod) {
update_usage_information = true;
last_usage_information_update_time = current_time;
usage_update_period_expired = true;
last_usage_information_update_time_ = current_time;
}
bool is_initial_usage_update = false;
bool is_usage_update_needed = false;
AutoLock lock(session_list_lock_);
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
iter->second->OnTimerEvent();
is_initial_usage_update =
is_initial_usage_update || iter->second->is_initial_usage_update();
is_usage_update_needed =
is_usage_update_needed || iter->second->is_usage_update_needed();
if (update_usage_information && iter->second->is_usage_update_needed()) {
// usage is updated for all sessions so this needs to be
// called only once per update usage information period
CdmResponseType status = iter->second->UpdateUsageInformation();
if (NO_ERROR != status) {
LOGW("Update usage information failed: %u", status);
} else {
update_usage_information = false;
iter->second->OnTimerEvent(usage_update_period_expired);
}
if (is_usage_update_needed &&
(usage_update_period_expired || is_initial_usage_update)) {
bool has_usage_been_updated = false;
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
iter->second->reset_usage_flags();
if (!has_usage_been_updated) {
// usage is updated for all sessions so this needs to be
// called only once per update usage information period
CdmResponseType status = iter->second->UpdateUsageInformation();
if (NO_ERROR != status) {
LOGW("Update usage information failed: %d", status);
} else {
has_usage_been_updated = true;
}
}
}
iter->second->reset_is_usage_update_needed();
}
}
void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) {
AutoLock lock(session_list_lock_);
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
iter->second->OnKeyReleaseEvent(key_set_id);
}
}
std::string CdmEngine::MapHdcpVersion(
CryptoSession::HdcpCapability version) {
switch (version) {
case HDCP_NONE:
return QUERY_VALUE_UNPROTECTED;
case HDCP_V1:
return QUERY_VALUE_HDCP_V1;
case HDCP_V2:
return QUERY_VALUE_HDCP_V2_0;
case HDCP_V2_1:
return QUERY_VALUE_HDCP_V2_1;
case HDCP_V2_2:
return QUERY_VALUE_HDCP_V2_2;
case HDCP_NO_DIGITAL_OUTPUT:
return QUERY_VALUE_DISCONNECTED;
}
return "";
}
} // namespace wvcdm

View File

@@ -2,11 +2,14 @@
#include "cdm_session.h"
#include <iostream>
#include <sstream>
#include <assert.h>
#include <stdlib.h>
#include <iostream>
#include <sstream>
#include "cdm_engine.h"
#include "clock.h"
#include "crypto_session.h"
#include "device_files.h"
#include "file_store.h"
@@ -22,58 +25,41 @@ const size_t kKeySetIdLength = 14;
namespace wvcdm {
typedef std::set<WvCdmEventListener*>::iterator CdmEventListenerIter;
CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set) {
Create(new CdmLicense(), new CryptoSession(), new PolicyEngine(),
new DeviceFiles(), cdm_client_property_set);
}
CdmSession::CdmSession(CdmLicense* license_parser,
CryptoSession* crypto_session,
PolicyEngine* policy_engine, DeviceFiles* file_handle,
const CdmClientPropertySet* cdm_client_property_set) {
Create(license_parser, crypto_session, policy_engine, file_handle,
cdm_client_property_set);
}
void CdmSession::Create(CdmLicense* license_parser,
CryptoSession* crypto_session,
PolicyEngine* policy_engine, DeviceFiles* file_handle,
const CdmClientPropertySet* cdm_client_property_set) {
// Just return on failures. Failures will be signaled in Init.
if (NULL == license_parser) {
LOGE("CdmSession::Create: License parser not provided");
return;
CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set,
const std::string& origin,
WvCdmEventListener* event_listener,
const CdmSessionId* forced_session_id)
: initialized_(false),
session_id_(GenerateSessionId()),
origin_(origin),
crypto_session_(new CryptoSession),
file_handle_(new DeviceFiles),
license_received_(false),
is_offline_(false),
is_release_(false),
security_level_(kSecurityLevelUninitialized),
requested_security_level_(kLevelDefault),
is_initial_decryption_(true),
has_decrypted_since_last_report_(false),
is_initial_usage_update_(true),
is_usage_update_needed_(false) {
if (Properties::AlwaysUseKeySetIds()) {
if (forced_session_id) {
key_set_id_ = *forced_session_id;
} else {
bool ok = GenerateKeySetId(&key_set_id_);
assert(ok);
}
session_id_ = key_set_id_;
}
if (NULL == crypto_session) {
LOGE("CdmSession::Create: Crypto session not provided");
return;
}
if (NULL == policy_engine) {
LOGE("CdmSession::Create: Policy engine not provided");
return;
}
if (NULL == file_handle) {
LOGE("CdmSession::Create: Device files not provided");
return;
}
initialized_ = false;
session_id_ = GenerateSessionId();
license_parser_.reset(license_parser);
crypto_session_.reset(crypto_session);
file_handle_.reset(file_handle);
policy_engine_.reset(policy_engine);
license_received_ = false;
is_offline_ = false;
is_release_ = false;
is_usage_update_needed_ = false;
is_initial_decryption_ = true;
requested_security_level_ = kLevelDefault;
if (NULL != cdm_client_property_set) {
if (QUERY_VALUE_SECURITY_LEVEL_L3.compare(
cdm_client_property_set->security_level()) == 0) {
license_parser_.reset(new CdmLicense(session_id_));
policy_engine_.reset(new PolicyEngine(
session_id_, event_listener, crypto_session_.get()));
if (cdm_client_property_set) {
if (cdm_client_property_set->security_level() ==
QUERY_VALUE_SECURITY_LEVEL_L3) {
requested_security_level_ = kLevel3;
security_level_ = kSecurityLevelL3;
}
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
}
@@ -84,11 +70,11 @@ CdmSession::~CdmSession() { Properties::RemoveSessionPropertySet(session_id_); }
CdmResponseType CdmSession::Init() {
if (session_id_.empty()) {
LOGE("CdmSession::Init: Failed, session not properly constructed");
return UNKNOWN_ERROR;
return SESSION_INIT_ERROR_1;
}
if (initialized_) {
LOGE("CdmSession::Init: Failed due to previous initialization");
return UNKNOWN_ERROR;
return SESSION_INIT_ERROR_2;
}
CdmResponseType sts = crypto_session_->Open(requested_security_level_);
if (NO_ERROR != sts) return sts;
@@ -98,17 +84,18 @@ CdmResponseType CdmSession::Init() {
if (Properties::use_certificates_as_identification()) {
std::string wrapped_key;
if (!file_handle_->Init(security_level_) ||
!file_handle_->RetrieveCertificate(&token, &wrapped_key) ||
!file_handle_->RetrieveCertificate(origin_, &token, &wrapped_key) ||
!crypto_session_->LoadCertificatePrivateKey(wrapped_key)) {
return NEED_PROVISIONING;
}
} else {
if (!crypto_session_->GetToken(&token)) return UNKNOWN_ERROR;
if (!crypto_session_->GetToken(&token))
return SESSION_INIT_GET_KEYBOX_ERROR;
}
if (!license_parser_->Init(token, crypto_session_.get(),
policy_engine_.get()))
return UNKNOWN_ERROR;
return LICENSE_PARSER_INIT_ERROR;
license_received_ = false;
is_initial_decryption_ = true;
@@ -121,27 +108,42 @@ CdmResponseType CdmSession::RestoreOfflineSession(
key_set_id_ = key_set_id;
// Retrieve license information from persistent store
if (!file_handle_->Reset(security_level_)) return UNKNOWN_ERROR;
if (!file_handle_->Reset(security_level_))
return RESTORE_OFFLINE_LICENSE_ERROR_1;
DeviceFiles::LicenseState license_state;
int64_t playback_start_time;
int64_t last_playback_time;
if (!file_handle_->RetrieveLicense(
key_set_id, &license_state, &offline_init_data_, &key_request_,
&key_response_, &offline_key_renewal_request_,
&offline_key_renewal_response_, &offline_release_server_url_)) {
&offline_key_renewal_response_, &offline_release_server_url_,
&playback_start_time, &last_playback_time, &app_parameters_)) {
LOGE("CdmSession::Init failed to retrieve license. key set id = %s",
key_set_id.c_str());
return UNKNOWN_ERROR;
return GET_LICENSE_ERROR;
}
if (license_state != DeviceFiles::kLicenseStateActive) {
LOGE("CdmSession::Init invalid offline license state = %s", license_state);
return UNKNOWN_ERROR;
// Do not restore a released offline license, unless a release retry
if (!(license_type == kLicenseTypeRelease ||
license_state == DeviceFiles::kLicenseStateActive)) {
LOGE("CdmSession::Init invalid offline license state = %d, type = %d",
license_state, license_type);
return GET_RELEASED_LICENSE_ERROR;
}
if (!license_parser_->RestoreOfflineLicense(key_request_, key_response_,
offline_key_renewal_response_)) {
return UNKNOWN_ERROR;
if (license_type == kLicenseTypeRelease) {
if (!license_parser_->RestoreLicenseForRelease(key_request_,
key_response_)) {
return RELEASE_LICENSE_ERROR_1;
}
} else {
if (!license_parser_->RestoreOfflineLicense(
key_request_, key_response_, offline_key_renewal_response_,
playback_start_time, last_playback_time)) {
return RESTORE_OFFLINE_LICENSE_ERROR_2;
}
}
license_received_ = true;
@@ -155,8 +157,8 @@ CdmResponseType CdmSession::RestoreUsageSession(
key_request_ = key_request;
key_response_ = key_response;
if (!license_parser_->RestoreUsageLicense(key_request_, key_response_)) {
return UNKNOWN_ERROR;
if (!license_parser_->RestoreLicenseForRelease(key_request_, key_response_)) {
return RELEASE_LICENSE_ERROR_2;
}
license_received_ = true;
@@ -166,17 +168,18 @@ CdmResponseType CdmSession::RestoreUsageSession(
}
CdmResponseType CdmSession::GenerateKeyRequest(
const InitializationData& init_data, const CdmLicenseType license_type,
const InitializationData& init_data, CdmLicenseType license_type,
const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
std::string* server_url, CdmKeySetId* key_set_id) {
CdmKeyRequestType* key_request_type, std::string* server_url,
CdmKeySetId* key_set_id) {
if (crypto_session_.get() == NULL) {
LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session");
return UNKNOWN_ERROR;
return INVALID_CRYPTO_SESSION_1;
}
if (!crypto_session_->IsOpen()) {
LOGW("CdmSession::GenerateKeyRequest: Crypto session not open");
return UNKNOWN_ERROR;
return CRYPTO_SESSION_OPEN_ERROR_1;
}
switch (license_type) {
@@ -189,36 +192,61 @@ CdmResponseType CdmSession::GenerateKeyRequest(
case kLicenseTypeRelease:
is_release_ = true;
break;
case kLicenseTypeDeferred:
// If you're going to pass Deferred, you must have empty init data in
// this call and stored init data from the previous call.
if (!init_data.IsEmpty() || !license_parser_->HasInitData()) {
return INVALID_LICENSE_TYPE;
}
// The arguments check out.
// The is_release_ and is_offline_ flags were already set last time based
// on the original license type. Do not change them, and use them to
// re-derive the original license type.
if (is_release_) {
license_type = kLicenseTypeRelease;
} else if (is_offline_) {
license_type = kLicenseTypeOffline;
} else {
license_type = kLicenseTypeStreaming;
}
break;
default:
LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld",
license_type);
return UNKNOWN_ERROR;
return INVALID_LICENSE_TYPE;
}
if (is_release_) {
if (key_request_type) *key_request_type = kKeyRequestTypeRelease;
return GenerateReleaseRequest(key_request, server_url);
} else if (license_received_) { // renewal
if (key_request_type) *key_request_type = kKeyRequestTypeRenewal;
return GenerateRenewalRequest(key_request, server_url);
} else {
if (!init_data.is_supported()) {
LOGW("CdmSession::GenerateKeyRequest: unsupported init data type (%s)",
init_data.type().c_str());
return KEY_ERROR;
if (key_request_type) *key_request_type = kKeyRequestTypeInitial;
if (!license_parser_->HasInitData()) {
if (!init_data.is_supported()) {
LOGW("CdmSession::GenerateKeyRequest: unsupported init data type (%s)",
init_data.type().c_str());
return UNSUPPORTED_INIT_DATA;
}
if (init_data.IsEmpty()) {
LOGW("CdmSession::GenerateKeyRequest: init data absent");
return INIT_DATA_NOT_FOUND;
}
}
if (init_data.IsEmpty() && !license_parser_->HasInitData()) {
LOGW("CdmSession::GenerateKeyRequest: init data absent");
return KEY_ERROR;
}
if (is_offline_ && !GenerateKeySetId(&key_set_id_)) {
if (is_offline_ && key_set_id_.empty() &&
!GenerateKeySetId(&key_set_id_)) {
LOGE("CdmSession::GenerateKeyRequest: Unable to generate key set ID");
return UNKNOWN_ERROR;
return KEY_REQUEST_ERROR_1;
}
if (!license_parser_->PrepareKeyRequest(init_data, license_type,
app_parameters, session_id_,
key_request, server_url)) {
return KEY_ERROR;
}
app_parameters_ = app_parameters;
CdmResponseType status = license_parser_->PrepareKeyRequest(
init_data, license_type,
app_parameters, key_request, server_url);
if (KEY_MESSAGE != status) return status;
key_request_ = *key_request;
if (is_offline_) {
@@ -236,12 +264,12 @@ 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;
return INVALID_CRYPTO_SESSION_2;
}
if (!crypto_session_->IsOpen()) {
LOGW("CdmSession::AddKey: Crypto session not open");
return UNKNOWN_ERROR;
return CRYPTO_SESSION_OPEN_ERROR_2;
}
if (is_release_) {
@@ -252,7 +280,7 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
} else {
CdmResponseType sts = license_parser_->HandleKeyResponse(key_response);
if (sts != KEY_ADDED) return sts;
if (sts != KEY_ADDED) return (KEY_ERROR == sts) ? ADD_KEY_ERROR : sts;
license_received_ = true;
key_response_ = key_response;
@@ -270,12 +298,12 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) {
if (crypto_session_.get() == NULL) {
LOGE("CdmSession::QueryStatus: Invalid crypto session");
return UNKNOWN_ERROR;
return INVALID_CRYPTO_SESSION_3;
}
if (!crypto_session_->IsOpen()) {
LOGE("CdmSession::QueryStatus: Crypto session not open");
return UNKNOWN_ERROR;
return CRYPTO_SESSION_OPEN_ERROR_3;
}
switch (security_level_) {
@@ -294,7 +322,7 @@ CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) {
QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
break;
default:
return KEY_ERROR;
return INVALID_QUERY_KEY;
}
return NO_ERROR;
}
@@ -306,12 +334,12 @@ CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) {
CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) {
if (crypto_session_.get() == NULL) {
LOGW("CdmSession::QueryKeyControlInfo: Invalid crypto session");
return UNKNOWN_ERROR;
return INVALID_CRYPTO_SESSION_4;
}
if (!crypto_session_->IsOpen()) {
LOGW("CdmSession::QueryKeyControlInfo: Crypto session not open");
return UNKNOWN_ERROR;
return CRYPTO_SESSION_OPEN_ERROR_4;
}
std::stringstream ss;
@@ -322,20 +350,40 @@ CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) {
// 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;
if (crypto_session_.get() == NULL) {
LOGW("CdmSession::Decrypt: Invalid crypto session");
return INVALID_CRYPTO_SESSION_5;
}
if (!crypto_session_->IsOpen()) {
LOGW("CdmSession::Decrypt: Crypto session not open");
return CRYPTO_SESSION_OPEN_ERROR_5;
}
// Playback may not begin until either the start time passes or the license
// is updated, so we treat this Decrypt call as invalid.
if (params.is_encrypted && !policy_engine_->CanDecrypt(*params.key_id)) {
return policy_engine_->IsLicenseForFuture() ? DECRYPT_NOT_READY : NEED_KEY;
}
CdmResponseType status = crypto_session_->Decrypt(params);
if (NO_ERROR == status) {
if (status == NO_ERROR) {
if (is_initial_decryption_) {
policy_engine_->BeginDecryption();
is_initial_decryption_ = false;
}
has_decrypted_since_last_report_ = true;
if (!is_usage_update_needed_) {
is_usage_update_needed_ =
!license_parser_->provider_session_token().empty();
}
} else {
Clock clock;
int64_t current_time = clock.GetCurrentTime();
if (policy_engine_->IsLicenseOrPlaybackDurationExpired(current_time)) {
return NEED_KEY;
}
}
return status;
@@ -346,11 +394,10 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
// 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;
}
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
true, app_parameters_, key_request, server_url);
if (KEY_MESSAGE != status) return status;
if (is_offline_) {
offline_key_renewal_request_ = *key_request;
@@ -362,11 +409,12 @@ CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request,
CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
CdmResponseType sts =
license_parser_->HandleKeyUpdateResponse(true, key_response);
if (sts != KEY_ADDED) return sts;
if (sts != KEY_ADDED) return (KEY_ERROR == sts) ? RENEW_KEY_ERROR_1 : sts;
if (is_offline_) {
offline_key_renewal_response_ = key_response;
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) return UNKNOWN_ERROR;
if (!StoreLicense(DeviceFiles::kLicenseStateActive))
return RENEW_KEY_ERROR_2;
}
return KEY_ADDED;
}
@@ -374,12 +422,14 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request,
std::string* server_url) {
is_release_ = true;
if (!license_parser_->PrepareKeyUpdateRequest(false, key_request, server_url))
return UNKNOWN_ERROR;
CdmResponseType status = license_parser_->PrepareKeyUpdateRequest(
false, app_parameters_, key_request, server_url);
if (KEY_MESSAGE != status) return status;
if (is_offline_) { // Mark license as being released
if (!StoreLicense(DeviceFiles::kLicenseStateReleasing))
return UNKNOWN_ERROR;
return RELEASE_KEY_REQUEST_ERROR;
}
return KEY_MESSAGE;
}
@@ -388,7 +438,7 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request,
CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
CdmResponseType sts =
license_parser_->HandleKeyUpdateResponse(false, key_response);
if (KEY_ADDED != sts) return sts;
if (KEY_ADDED != sts) return (KEY_ERROR == sts) ? RELEASE_KEY_ERROR : sts;
if (is_offline_ || !license_parser_->provider_session_token().empty()) {
DeleteLicense();
@@ -427,6 +477,7 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
key_set_id->clear();
}
}
// Reserve the license ID to avoid collisions.
file_handle_->ReserveLicenseId(*key_set_id);
return true;
}
@@ -435,7 +486,7 @@ CdmResponseType CdmSession::StoreLicense() {
if (is_offline_) {
if (key_set_id_.empty()) {
LOGE("CdmSession::StoreLicense: No key set ID");
return UNKNOWN_ERROR;
return EMPTY_KEYSET_ID;
}
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
@@ -447,7 +498,7 @@ CdmResponseType CdmSession::StoreLicense() {
}
key_set_id_.clear();
return UNKNOWN_ERROR;
return STORE_LICENSE_ERROR_1;
}
return NO_ERROR;
}
@@ -456,18 +507,20 @@ CdmResponseType CdmSession::StoreLicense() {
license_parser_->provider_session_token();
if (provider_session_token.empty()) {
LOGE("CdmSession::StoreLicense: No provider session token and not offline");
return UNKNOWN_ERROR;
return STORE_LICENSE_ERROR_2;
}
if (!file_handle_->Reset(security_level_)) {
LOGE("CdmSession::StoreLicense: Unable to initialize device files");
return UNKNOWN_ERROR;
return STORE_LICENSE_ERROR_3;
}
std::string app_id;
GetApplicationId(&app_id);
if (!file_handle_->StoreUsageInfo(provider_session_token, key_request_,
key_response_)) {
key_response_, app_id)) {
LOGE("CdmSession::StoreLicense: Unable to store usage info");
return UNKNOWN_ERROR;
return STORE_USAGE_INFO_ERROR;
}
return NO_ERROR;
}
@@ -478,57 +531,67 @@ bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
return file_handle_->StoreLicense(
key_set_id_, state, offline_init_data_, key_request_, key_response_,
offline_key_renewal_request_, offline_key_renewal_response_,
offline_release_server_url_);
offline_release_server_url_, policy_engine_->GetPlaybackStartTime(),
policy_engine_->GetLastPlaybackTime(), app_parameters_);
}
bool CdmSession::DeleteLicense() {
if (!is_offline_ && license_parser_->provider_session_token().empty())
return false;
if (!license_parser_->provider_session_token().empty()) {
if (crypto_session_->DeleteUsageInformation(
license_parser_->provider_session_token()) != NO_ERROR) {
LOGE("CdmSession::DeleteLicense: error deleting usage info");
}
}
if (!file_handle_->Reset(security_level_)) {
LOGE("CdmSession::DeleteLicense: Unable to initialize device files");
return false;
}
if (is_offline_)
if (is_offline_) {
return file_handle_->DeleteLicense(key_set_id_);
else
} else {
std::string app_id;
GetApplicationId(&app_id);
return file_handle_->DeleteUsageInfo(
license_parser_->provider_session_token());
app_id, license_parser_->provider_session_token());
}
}
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {
std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener);
return result.second;
void CdmSession::NotifyResolution(uint32_t width, uint32_t height) {
policy_engine_->NotifyResolution(width, height);
}
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::OnTimerEvent(bool update_usage) {
if (update_usage && has_decrypted_since_last_report_) {
policy_engine_->DecryptionEvent();
has_decrypted_since_last_report_ = false;
if (is_offline_ && !is_release_) {
StoreLicense(DeviceFiles::kLicenseStateActive);
}
}
policy_engine_->OnTimerEvent();
}
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);
}
policy_engine_->NotifySessionExpiration();
}
}
void CdmSession::GetApplicationId(std::string* app_id) {
if (app_id && !Properties::GetApplicationId(session_id_, app_id)) {
*app_id = "";
}
}
CdmResponseType CdmSession::DeleteMultipleUsageInformation(
const std::vector<std::string>& provider_session_tokens) {
return crypto_session_->DeleteMultipleUsageInformation(
provider_session_tokens);
}
CdmResponseType CdmSession::UpdateUsageInformation() {
return crypto_session_->UpdateUsageInformation();
}
@@ -538,4 +601,20 @@ CdmResponseType CdmSession::ReleaseCrypto() {
return NO_ERROR;
}
void CdmSession::set_license_parser(CdmLicense* license_parser) {
license_parser_.reset(license_parser);
}
void CdmSession::set_crypto_session(CryptoSession* crypto_session) {
crypto_session_.reset(crypto_session);
}
void CdmSession::set_policy_engine(PolicyEngine* policy_engine) {
policy_engine_.reset(policy_engine);
}
void CdmSession::set_file_handle(DeviceFiles* file_handle) {
file_handle_.reset(file_handle);
}
} // namespace wvcdm

View File

@@ -53,15 +53,15 @@ void CertificateProvisioning::ComposeJsonRequestAsQueryString(
* 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.
* Returns NO_ERROR for success and CERT_PROVISIONING_REQUEST_ERROR_? if fails.
*/
CdmResponseType CertificateProvisioning::GetProvisioningRequest(
SecurityLevel requested_security_level, CdmCertificateType cert_type,
const std::string& cert_authority, CdmProvisioningRequest* request,
std::string* default_url) {
const std::string& cert_authority, const std::string& origin,
CdmProvisioningRequest* request, std::string* default_url) {
if (!default_url) {
LOGE("GetProvisioningRequest: pointer for returning URL is NULL");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_REQUEST_ERROR_1;
}
default_url->assign(kProvisioningServerUrl);
@@ -79,14 +79,14 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
std::string token;
if (!crypto_session_.GetToken(&token)) {
LOGE("GetProvisioningRequest: fails to get token");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_GET_KEYBOX_ERROR_1;
}
client_id->set_token(token);
uint32_t nonce;
if (!crypto_session_.GenerateNonce(&nonce)) {
LOGE("GetProvisioningRequest: fails to generate a nonce");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_REQUEST_ERROR_2;
}
// The provisioning server does not convert the nonce to uint32_t, it just
@@ -107,12 +107,21 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
break;
default:
LOGE("GetProvisioningRequest: unknown certificate type %ld", cert_type);
return UNKNOWN_ERROR;
return CERT_PROVISIONING_INVALID_CERT_TYPE;
}
cert_type_ = cert_type;
options->set_certificate_authority(cert_authority);
if (origin != EMPTY_ORIGIN) {
std::string device_unique_id;
if (!crypto_session_.GetDeviceUniqueId(&device_unique_id)) {
LOGE("GetProvisioningRequest: fails to get device unique ID");
return CERT_PROVISIONING_GET_KEYBOX_ERROR_2;
}
provisioning_request.set_stable_id(device_unique_id + origin);
}
std::string serialized_message;
provisioning_request.SerializeToString(&serialized_message);
@@ -121,11 +130,11 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
if (!crypto_session_.PrepareRequest(serialized_message, true,
&request_signature)) {
LOGE("GetProvisioningRequest: fails to prepare request");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_REQUEST_ERROR_3;
}
if (request_signature.empty()) {
LOGE("GetProvisioningRequest: request signature is empty");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_REQUEST_ERROR_4;
}
SignedProvisioningMessage signed_provisioning_msg;
@@ -177,11 +186,11 @@ bool CertificateProvisioning::ParseJsonResponse(
* 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.
* Returns NO_ERROR for success and CERT_PROVISIONING_RESPONSE_ERROR_? if fails.
*/
CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
CdmProvisioningResponse& response, std::string* cert,
std::string* wrapped_key) {
const std::string& origin, const CdmProvisioningResponse& response,
std::string* cert, std::string* wrapped_key) {
// Extracts signed response from JSON string, decodes base64 signed response
const std::string kMessageStart = "\"signedResponse\": \"";
const std::string kMessageEnd = "\"";
@@ -189,7 +198,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
if (!ParseJsonResponse(response, kMessageStart, kMessageEnd,
&serialized_signed_response)) {
LOGE("Fails to extract signed serialized response from JSON response");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_RESPONSE_ERROR_1;
}
// Authenticates provisioning response using D1s (server key derived from
@@ -198,7 +207,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
SignedProvisioningMessage signed_response;
if (!signed_response.ParseFromString(serialized_signed_response)) {
LOGE("HandleProvisioningResponse: fails to parse signed response");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_RESPONSE_ERROR_2;
}
bool error = false;
@@ -212,19 +221,19 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
error = true;
}
if (error) return UNKNOWN_ERROR;
if (error) return CERT_PROVISIONING_RESPONSE_ERROR_3;
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;
return CERT_PROVISIONING_RESPONSE_ERROR_4;
}
if (!provisioning_response.has_device_rsa_key()) {
LOGE("HandleProvisioningResponse: key not found");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_RESPONSE_ERROR_5;
}
const std::string& enc_rsa_key = provisioning_response.device_rsa_key();
@@ -236,7 +245,7 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
enc_rsa_key, rsa_key_iv,
&wrapped_rsa_key)) {
LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_RESPONSE_ERROR_6;
}
crypto_session_.Close();
@@ -253,11 +262,11 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
DeviceFiles handle;
if (!handle.Init(crypto_session_.GetSecurityLevel())) {
LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_RESPONSE_ERROR_7;
}
if (!handle.StoreCertificate(device_certificate, wrapped_rsa_key)) {
if (!handle.StoreCertificate(origin, device_certificate, wrapped_rsa_key)) {
LOGE("HandleProvisioningResponse: failed to save provisioning certificate");
return UNKNOWN_ERROR;
return CERT_PROVISIONING_RESPONSE_ERROR_8;
}
handle.DeleteAllLicenses();

View File

@@ -6,6 +6,7 @@
#include "crypto_session.h"
#include <arpa/inet.h> // needed for ntoh()
#include <string.h>
#include <iostream>
#include "crypto_key.h"
@@ -24,6 +25,7 @@ std::string EncodeUint32(unsigned int u) {
s.append(1, (u >> 0) & 0xFF);
return s;
}
const uint32_t kRsaSignatureLength = 256;
}
namespace wvcdm {
@@ -35,6 +37,7 @@ uint64_t CryptoSession::request_id_index_ = 0;
CryptoSession::CryptoSession()
: open_(false),
update_usage_table_after_close_session_(false),
is_destination_buffer_type_valid_(false),
requested_security_level_(kLevelDefault),
request_id_base_(0) {
@@ -64,7 +67,11 @@ void CryptoSession::Init() {
void CryptoSession::Terminate() {
LOGV("CryptoSession::Terminate");
AutoLock auto_lock(crypto_lock_);
session_count_ -= 1;
if (session_count_ > 0) {
session_count_ -= 1;
} else {
LOGE("CryptoSession::Terminate error, session count: %d", session_count_);
}
if (session_count_ > 0 || !initialized_) return;
OEMCryptoResult sts = OEMCrypto_Terminate();
if (OEMCrypto_SUCCESS != sts) {
@@ -168,8 +175,6 @@ bool CryptoSession::GetApiVersion(uint32_t* version) {
if (!initialized_) {
return false;
}
LOGV("CryptoSession::GetApiVersion: Lock");
*version = OEMCrypto_APIVersion(requested_security_level_);
return true;
}
@@ -242,9 +247,15 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
LOGV("OpenSession: id= %ld", (uint32_t)oec_session_id_);
open_ = true;
} else if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", sts,
session_count_, (int)initialized_);
return INSUFFICIENT_CRYPTO_RESOURCES;
}
if (!open_) return UNKNOWN_ERROR;
if (!open_) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d", sts,
session_count_, (int)initialized_);
return UNKNOWN_ERROR;
}
OEMCrypto_GetRandom(reinterpret_cast<uint8_t*>(&request_id_base_),
sizeof(request_id_base_));
@@ -259,6 +270,11 @@ void CryptoSession::Close() {
if (!open_) return;
if (OEMCrypto_SUCCESS == OEMCrypto_CloseSession(oec_session_id_)) {
open_ = false;
if (update_usage_table_after_close_session_) {
OEMCryptoResult sts = OEMCrypto_UpdateUsageTable();
if (sts != OEMCrypto_SUCCESS)
LOGW("CryptoSession::Close: OEMCrypto_UpdateUsageTable error=%ld", sts);
}
}
}
@@ -410,13 +426,21 @@ CdmResponseType CryptoSession::LoadKeys(
provider_session_token.length());
if (OEMCrypto_SUCCESS == sts) {
if (!provider_session_token.empty()) {
update_usage_table_after_close_session_ = true;
sts = OEMCrypto_UpdateUsageTable();
if (sts != OEMCrypto_SUCCESS) {
LOGW("CryptoSession::LoadKeys: OEMCrypto_UpdateUsageTable error=%ld",
sts);
}
}
return KEY_ADDED;
} else if (OEMCrypto_ERROR_TOO_MANY_KEYS == sts) {
LOGE("LoadKeys: OEMCrypto_LoadKeys error=%d", sts);
LOGE("CryptoSession::LoadKeys: OEMCrypto_LoadKeys error=%d", sts);
return INSUFFICIENT_CRYPTO_RESOURCES;
} else {
LOGE("LoadKeys: OEMCrypto_LoadKeys error=%d", sts);
return KEY_ERROR;
LOGE("CryptoSession::LoadKeys: OEMCrypto_LoadKeys error=%d", sts);
return LOAD_KEY_ERROR;
}
}
@@ -430,7 +454,7 @@ bool CryptoSession::LoadCertificatePrivateKey(std::string& wrapped_key) {
wrapped_key.size());
if (OEMCrypto_SUCCESS != sts) {
LOGD("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts);
LOGE("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts);
return false;
}
@@ -500,7 +524,7 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message) {
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGD("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
LOGE("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
return false;
}
@@ -524,7 +548,7 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message,
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGD("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts);
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts);
return false;
}
@@ -545,7 +569,7 @@ bool CryptoSession::GenerateSignature(const std::string& message,
if (OEMCrypto_SUCCESS != sts) {
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
return false;
}
@@ -558,7 +582,7 @@ bool CryptoSession::GenerateSignature(const std::string& message,
&length);
if (OEMCrypto_SUCCESS != sts) {
LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
LOGE("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
return false;
}
}
@@ -574,6 +598,7 @@ bool CryptoSession::GenerateRsaSignature(const std::string& message,
LOGV("GenerateRsaSignature: id=%ld", (uint32_t)oec_session_id_);
if (!signature) return false;
signature->resize(kRsaSignatureLength);
size_t length = signature->size();
OEMCryptoResult sts = OEMCrypto_GenerateRSASignature(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
@@ -583,7 +608,7 @@ bool CryptoSession::GenerateRsaSignature(const std::string& message,
if (OEMCrypto_SUCCESS != sts) {
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
LOGD("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
return false;
}
@@ -596,7 +621,7 @@ bool CryptoSession::GenerateRsaSignature(const std::string& message,
&length, kSign_RSASSA_PSS);
if (OEMCrypto_SUCCESS != sts) {
LOGD("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
LOGE("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
return false;
}
}
@@ -612,18 +637,6 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
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;
@@ -647,10 +660,29 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
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);
OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (!params.is_encrypted) {
sts = OEMCrypto_CopyBuffer(requested_security_level_,
params.encrypt_buffer, params.encrypt_length,
&buffer_descriptor, params.subsample_flags);
}
if (params.is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) {
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;
}
}
}
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:
@@ -668,8 +700,7 @@ bool CryptoSession::UsageInformationSupport(bool* has_support) {
LOGV("UsageInformationSupport: id=%ld", (uint32_t)oec_session_id_);
if (!initialized_) return false;
*has_support = OEMCrypto_SupportsUsageTable(
kSecurityLevelL3 == GetSecurityLevel() ? kLevel3 : kLevelDefault);
*has_support = OEMCrypto_SupportsUsageTable(requested_security_level_);
return true;
}
@@ -686,14 +717,9 @@ CdmResponseType CryptoSession::UpdateUsageInformation() {
return NO_ERROR;
}
CdmResponseType CryptoSession::GenerateUsageReport(
const std::string& provider_session_token, std::string* usage_report) {
LOGV("GenerateUsageReport: id=%ld", (uint32_t)oec_session_id_);
if (NULL == usage_report) {
LOGE("usage_report parameter is null");
return UNKNOWN_ERROR;
}
CdmResponseType CryptoSession::DeactivateUsageInformation(
const std::string& provider_session_token) {
LOGV("DeactivateUsageInformation: id=%ld", (uint32_t)oec_session_id_);
AutoLock auto_lock(crypto_lock_);
uint8_t* pst = reinterpret_cast<uint8_t*>(
@@ -702,20 +728,44 @@ CdmResponseType CryptoSession::GenerateUsageReport(
OEMCryptoResult status =
OEMCrypto_DeactivateUsageEntry(pst, provider_session_token.length());
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::GenerateUsageReport: Deactivate Usage Entry error=%ld",
status);
switch (status) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_INVALID_CONTEXT:
LOGE("CryptoSession::DeactivateUsageInformation: invalid context error");
return KEY_CANCELED;
default:
LOGE("CryptoSession::DeactivateUsageInformation: error=%ld", status);
return UNKNOWN_ERROR;
}
}
CdmResponseType CryptoSession::GenerateUsageReport(
const std::string& provider_session_token, std::string* usage_report,
UsageDurationStatus* usage_duration_status, int64_t* seconds_since_started,
int64_t* seconds_since_last_played) {
LOGV("GenerateUsageReport: id=%ld", (uint32_t)oec_session_id_);
if (NULL == usage_report) {
LOGE("CryptoSession::GenerateUsageReport: usage_report parameter is null");
return UNKNOWN_ERROR;
}
size_t usage_length = 0;
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), NULL,
&usage_length);
AutoLock auto_lock(crypto_lock_);
uint8_t* pst = reinterpret_cast<uint8_t*>(
const_cast<char*>(provider_session_token.data()));
if (OEMCrypto_ERROR_SHORT_BUFFER != status) {
LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld", status);
return UNKNOWN_ERROR;
size_t usage_length = 0;
OEMCryptoResult status = OEMCrypto_ReportUsage(
oec_session_id_, pst, provider_session_token.length(), NULL,
&usage_length);
if (OEMCrypto_SUCCESS != status) {
if (OEMCrypto_ERROR_SHORT_BUFFER != status) {
LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld",
status);
return UNKNOWN_ERROR;
}
}
usage_report->resize(usage_length);
@@ -730,10 +780,49 @@ CdmResponseType CryptoSession::GenerateUsageReport(
return UNKNOWN_ERROR;
}
if (usage_length < usage_report->length()) {
if (usage_length != usage_report->length()) {
usage_report->resize(usage_length);
}
OEMCrypto_PST_Report pst_report;
*usage_duration_status = kUsageDurationsInvalid;
if (usage_length < sizeof(pst_report)) {
LOGE("CryptoSession::GenerateUsageReport: usage report too small=%ld",
usage_length);
return NO_ERROR; // usage report available but no duration information
}
memcpy(&pst_report, usage_report->data(), sizeof(pst_report));
if (kUnused == pst_report.status) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return NO_ERROR;
}
LOGV("OEMCrypto_PST_Report.status: %d\n", pst_report.status);
LOGV("OEMCrypto_PST_Report.clock_security_level: %d\n",
pst_report.clock_security_level);
LOGV("OEMCrypto_PST_Report.pst_length: %d\n", pst_report.pst_length);
LOGV("OEMCrypto_PST_Report.padding: %d\n", pst_report.padding);
LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %lld\n",
ntohll64(pst_report.seconds_since_license_received));
LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %lld\n",
ntohll64(pst_report.seconds_since_first_decrypt));
LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %lld\n",
ntohll64(pst_report.seconds_since_last_decrypt));
LOGV("OEMCrypto_PST_Report: %s\n", b2a_hex(*usage_report).c_str());
// When usage report state is inactive, we have to deduce whether the
// license was ever used.
if (kInactive == pst_report.status &&
(0 > ntohll64(pst_report.seconds_since_first_decrypt) ||
ntohll64(pst_report.seconds_since_license_received) <
ntohll64(pst_report.seconds_since_first_decrypt))) {
*usage_duration_status = kUsageDurationPlaybackNotBegun;
return NO_ERROR;
}
*usage_duration_status = kUsageDurationsValid;
*seconds_since_started = ntohll64(pst_report.seconds_since_first_decrypt);
*seconds_since_last_played = ntohll64(pst_report.seconds_since_last_decrypt);
return NO_ERROR;
}
@@ -755,10 +844,81 @@ CdmResponseType CryptoSession::ReleaseUsageInformation(
status);
return UNKNOWN_ERROR;
}
status = OEMCrypto_UpdateUsageTable();
if (status != OEMCrypto_SUCCESS) {
LOGW("CryptoSession::ReleaseUsageInformation: update table error=%ld",
status);
}
return NO_ERROR;
}
CdmResponseType CryptoSession::DeleteUsageInformation(
const std::string& provider_session_token) {
CdmResponseType response = NO_ERROR;
LOGV("CryptoSession::DeleteUsageInformation");
OEMCryptoResult status = OEMCrypto_ForceDeleteUsageEntry(
reinterpret_cast<const uint8_t*>(provider_session_token.c_str()),
provider_session_token.length());
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::DeleteUsageInformation: Delete Usage Table error =%ld",
status);
response = UNKNOWN_ERROR;
}
status = OEMCrypto_UpdateUsageTable();
if (status != OEMCrypto_SUCCESS) {
LOGE("CryptoSession::DeleteUsageInformation: update table error=%ld",
status);
response = UNKNOWN_ERROR;
}
return response;
}
CdmResponseType CryptoSession::DeleteMultipleUsageInformation(
const std::vector<std::string>& provider_session_tokens) {
LOGV("CryptoSession::DeleteMultipleUsageInformation");
CdmResponseType response = NO_ERROR;
for (size_t i=0; i < provider_session_tokens.size(); ++i) {
OEMCryptoResult status = OEMCrypto_ForceDeleteUsageEntry(
reinterpret_cast<const uint8_t*>(provider_session_tokens[i].c_str()),
provider_session_tokens[i].length());
if (OEMCrypto_SUCCESS != status) {
LOGW("CryptoSession::DeleteMultipleUsageInformation: "
"Delete Usage Table error =%ld", status);
response = UNKNOWN_ERROR;
}
}
OEMCryptoResult status = OEMCrypto_UpdateUsageTable();
if (status != OEMCrypto_SUCCESS) {
LOGE("CryptoSession::DeleteMultipleUsageInformation: update table error=%ld",
status);
response = UNKNOWN_ERROR;
}
return response;
}
CdmResponseType CryptoSession::DeleteAllUsageReports() {
LOGV("DeleteAllUsageReports");
OEMCryptoResult status = OEMCrypto_DeleteUsageTable();
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::DeleteAllUsageReports: Delete Usage Table error =%ld",
status);
}
status = OEMCrypto_UpdateUsageTable();
if (status != OEMCrypto_SUCCESS) {
LOGE("CryptoSession::DeletaAllUsageReports: update table error=%ld",
status);
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
bool CryptoSession::IsAntiRollbackHwPresent() {
return OEMCrypto_IsAntiRollbackHwPresent(requested_security_level_);
}
bool CryptoSession::GenerateNonce(uint32_t* nonce) {
if (!nonce) {
LOGE("input parameter is null");
@@ -796,7 +956,7 @@ bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
const std::string& enc_rsa_key,
const std::string& rsa_key_iv,
std::string* wrapped_rsa_key) {
LOGD("CryptoSession::RewrapDeviceRSAKey, session id=%ld",
LOGV("CryptoSession::RewrapDeviceRSAKey, session id=%ld",
static_cast<uint32_t>(oec_session_id_));
const uint8_t* signed_msg = reinterpret_cast<const uint8_t*>(message.data());
@@ -841,20 +1001,20 @@ bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
return true;
}
bool CryptoSession::GetHdcpCapabilities(OemCryptoHdcpVersion* current_version,
OemCryptoHdcpVersion* max_version) {
bool CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
HdcpCapability* max) {
LOGV("GetHdcpCapabilities: id=%ld", (uint32_t)oec_session_id_);
if (!initialized_) return UNKNOWN_ERROR;
OEMCrypto_HDCP_Capability current, max;
OEMCryptoResult status = OEMCrypto_GetHDCPCapability(&current, &max);
if (!initialized_) return false;
if (current == NULL || max == NULL) {
LOGE("CryptoSession::GetHdcpCapabilities: |current|, |max| cannot be NULL");
return false;
}
OEMCryptoResult status = OEMCrypto_GetHDCPCapability(
requested_security_level_, current, max);
if (OEMCrypto_SUCCESS != status) {
LOGW("OEMCrypto_GetHDCPCapability fails with %d", status);
return false;
}
*current_version = static_cast<OemCryptoHdcpVersion>(current);
*max_version = static_cast<OemCryptoHdcpVersion>(max);
return true;
}
@@ -873,4 +1033,42 @@ bool CryptoSession::GetRandom(size_t data_length, uint8_t* random_data) {
return true;
}
}; // namespace wvcdm
bool CryptoSession::GetNumberOfOpenSessions(size_t* count) {
LOGV("GetNumberOfOpenSessions");
if (!initialized_) return false;
if (count == NULL) {
LOGE("CryptoSession::GetNumberOfOpenSessions: |count| cannot be NULL");
return false;
}
size_t sessions_count;
OEMCryptoResult status = OEMCrypto_GetNumberOfOpenSessions(
requested_security_level_, &sessions_count);
if (OEMCrypto_SUCCESS != status) {
LOGW("OEMCrypto_GetNumberOfOpenSessions fails with %d", status);
return false;
}
*count = sessions_count;
return true;
}
bool CryptoSession::GetMaxNumberOfSessions(size_t* max) {
LOGV("GetMaxNumberOfSessions");
if (!initialized_) return false;
if (max == NULL) {
LOGE("CryptoSession::GetMaxNumberOfSessions: |max| cannot be NULL");
return false;
}
size_t max_sessions;
OEMCryptoResult status = OEMCrypto_GetMaxNumberOfSessions(
requested_security_level_, &max_sessions);
if (OEMCrypto_SUCCESS != status) {
LOGW("OEMCrypto_GetMaxNumberOfSessions fails with %d", status);
return false;
}
*max = max_sessions;
return true;
}
} // namespace wvcdm

View File

@@ -2,15 +2,7 @@
#include "device_files.h"
#if defined(__APPLE__)
#include <CommonCrypto/CommonDigest.h>
#define SHA256 CC_SHA256
#define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH
#else
#include <openssl/sha.h>
#endif
#include <cstring>
#include <string.h>
#include <string>
#include "device_files.pb.h"
@@ -18,6 +10,16 @@
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#if defined(__APPLE__)
#include <CommonCrypto/CommonDigest.h>
#define SHA256 CC_SHA256
#define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH
#else
#include <openssl/sha.h>
#include <openssl/md5.h>
#endif
// Protobuf generated classes.
using video_widevine_client::sdk::DeviceCertificate;
@@ -25,22 +27,25 @@ 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;
using video_widevine_client::sdk::NameValue;
using video_widevine_client::sdk::UsageInfo;
using video_widevine_client::sdk::UsageInfo_ProviderSession;
namespace {
const char kCertificateFileName[] = "cert.bin";
const char kUsageInfoFileName[] = "usage.bin";
const char kCertificateFileNamePrefix[] = "cert";
const char kCertificateFileNameExt[] = ".bin";
const char kUsageInfoFileNamePrefix[] = "usage";
const char kUsageInfoFileNameExt[] = ".bin";
const char kLicenseFileNameExt[] = ".lic";
const char kEmptyFileName[] = "";
const char kWildcard[] = "*";
const char kDirectoryDelimiter = '/';
const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"};
const char* kSecurityLevelPathCompatibilityExclusionList[] = {
"ay64.dat", "ay64.dat2", "ay64.dat3"};
size_t kSecurityLevelPathCompatibilityExclusionListSize =
sizeof(kSecurityLevelPathCompatibilityExclusionList) /
sizeof(*kSecurityLevelPathCompatibilityExclusionList);
// Some platforms cannot store a truly blank file, so we use a W for Widevine.
const char kBlankFileData[] = "W";
bool Hash(const std::string& data, std::string* hash) {
if (!hash) return false;
@@ -53,10 +58,13 @@ bool Hash(const std::string& data, std::string* hash) {
return true;
}
} // unnamed namespace
} // namespace
namespace wvcdm {
// static
std::set<std::string> DeviceFiles::reserved_license_ids_;
DeviceFiles::DeviceFiles()
: file_(NULL),
security_level_(kSecurityLevelUninitialized),
@@ -68,14 +76,10 @@ DeviceFiles::~DeviceFiles() {
}
bool DeviceFiles::Init(CdmSecurityLevel security_level) {
switch (security_level) {
case kSecurityLevelL1:
case kSecurityLevelL2:
case kSecurityLevelL3:
break;
default:
LOGW("DeviceFiles::Init: Unsupported security level %d", security_level);
return false;
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level, &path)) {
LOGW("DeviceFiles::Init: Unsupported security level %d", security_level);
return false;
}
if (!test_file_) file_.reset(new File());
security_level_ = security_level;
@@ -83,7 +87,8 @@ bool DeviceFiles::Init(CdmSecurityLevel security_level) {
return true;
}
bool DeviceFiles::StoreCertificate(const std::string& certificate,
bool DeviceFiles::StoreCertificate(const std::string& origin,
const std::string& certificate,
const std::string& wrapped_private_key) {
if (!initialized_) {
LOGW("DeviceFiles::StoreCertificate: not initialized");
@@ -103,10 +108,11 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate,
std::string serialized_file;
file.SerializeToString(&serialized_file);
return StoreFileWithHash(kCertificateFileName, serialized_file);
return StoreFileWithHash(GetCertificateFileName(origin), serialized_file);
}
bool DeviceFiles::RetrieveCertificate(std::string* certificate,
bool DeviceFiles::RetrieveCertificate(const std::string& origin,
std::string* certificate,
std::string* wrapped_private_key) {
if (!initialized_) {
LOGW("DeviceFiles::RetrieveCertificate: not initialized");
@@ -118,7 +124,9 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate,
}
std::string serialized_file;
if (!RetrieveHashedFile(kCertificateFileName, &serialized_file)) return false;
if (!RetrieveHashedFile(GetCertificateFileName(origin), &serialized_file)) {
return false;
}
video_widevine_client::sdk::File file;
if (!file.ParseFromString(serialized_file)) {
@@ -148,14 +156,32 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate,
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) {
bool DeviceFiles::HasCertificate(const std::string& origin) {
if (!initialized_) {
LOGW("DeviceFiles::HasCertificate: not initialized");
return false;
}
return FileExists(GetCertificateFileName(origin));
}
bool DeviceFiles::RemoveCertificate(const std::string& origin) {
if (!initialized_) {
LOGW("DeviceFiles::RemoveCertificate: not initialized");
return false;
}
return RemoveFile(GetCertificateFileName(origin));
}
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, int64_t playback_start_time,
int64_t last_playback_time, const CdmAppParameterMap& app_parameters) {
if (!initialized_) {
LOGW("DeviceFiles::StoreLicense: not initialized");
return false;
@@ -186,29 +212,38 @@ bool DeviceFiles::StoreLicense(const std::string& key_set_id,
license->set_renewal_request(license_renewal_request);
license->set_renewal(license_renewal);
license->set_release_server_url(release_server_url);
license->set_playback_start_time(playback_start_time);
license->set_last_playback_time(last_playback_time);
NameValue* app_params;
for (CdmAppParameterMap::const_iterator iter = app_parameters.begin();
iter != app_parameters.end(); ++iter) {
app_params = license->add_app_parameters();
app_params->set_name(iter->first);
app_params->set_value(iter->second);
}
std::string serialized_file;
file.SerializeToString(&serialized_file);
std::string file_name = key_set_id + kLicenseFileNameExt;
return StoreFileWithHash(file_name.c_str(), serialized_file);
reserved_license_ids_.erase(key_set_id);
return StoreFileWithHash(key_set_id + kLicenseFileNameExt, serialized_file);
}
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) {
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, int64_t* playback_start_time,
int64_t* last_playback_time, CdmAppParameterMap* app_parameters) {
if (!initialized_) {
LOGW("DeviceFiles::RetrieveLicense: not initialized");
return false;
}
std::string serialized_file;
std::string file_name = key_set_id + kLicenseFileNameExt;
if (!RetrieveHashedFile(file_name.c_str(), &serialized_file)) return false;
if (!RetrieveHashedFile(key_set_id + kLicenseFileNameExt, &serialized_file)) {
return false;
}
video_widevine_client::sdk::File file;
if (!file.ParseFromString(serialized_file)) {
@@ -252,6 +287,12 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
*license_renewal_request = license.renewal_request();
*license_renewal = license.renewal();
*release_server_url = license.release_server_url();
*playback_start_time = license.playback_start_time();
*last_playback_time = license.last_playback_time();
for (int i = 0; i < license.app_parameters_size(); ++i) {
(*app_parameters)[license.app_parameters(i).name()] =
license.app_parameters(i).value();
}
return true;
}
@@ -260,16 +301,7 @@ bool DeviceFiles::DeleteLicense(const std::string& key_set_id) {
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);
return RemoveFile(key_set_id + kLicenseFileNameExt);
}
bool DeviceFiles::DeleteAllLicenses() {
@@ -277,16 +309,7 @@ bool DeviceFiles::DeleteAllLicenses() {
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);
return RemoveFile(std::string(kWildcard) + kLicenseFileNameExt);
}
bool DeviceFiles::DeleteAllFiles() {
@@ -295,13 +318,9 @@ bool DeviceFiles::DeleteAllFiles() {
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);
// We pass an empty string to RemoveFile to delete the device files base
// directory itself.
return RemoveFile(kEmptyFileName);
}
bool DeviceFiles::LicenseExists(const std::string& key_set_id) {
@@ -309,16 +328,8 @@ bool DeviceFiles::LicenseExists(const std::string& key_set_id) {
LOGW("DeviceFiles::LicenseExists: not initialized");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::LicenseExists: Unable to get base path");
return false;
}
path.append(key_set_id);
path.append(kLicenseFileNameExt);
return file_->Exists(path);
return reserved_license_ids_.count(key_set_id) ||
FileExists(key_set_id + kLicenseFileNameExt);
}
bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) {
@@ -326,14 +337,14 @@ bool DeviceFiles::ReserveLicenseId(const std::string& key_set_id) {
LOGW("DeviceFiles::ReserveLicenseId: not initialized");
return false;
}
std::string file_name = key_set_id + kLicenseFileNameExt;
return StoreFileRaw(file_name.c_str(), kBlankFileData);
reserved_license_ids_.insert(key_set_id);
return true;
}
bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response) {
const CdmKeyResponse& key_response,
const std::string& app_id) {
if (!initialized_) {
LOGW("DeviceFiles::StoreUsageInfo: not initialized");
return false;
@@ -341,7 +352,8 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
std::string serialized_file;
video_widevine_client::sdk::File file;
if (!RetrieveHashedFile(kUsageInfoFileName, &serialized_file)) {
std::string file_name = GetUsageInfoFileName(app_id);
if (!RetrieveHashedFile(file_name, &serialized_file)) {
file.set_type(video_widevine_client::sdk::File::USAGE_INFO);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
} else {
@@ -360,17 +372,18 @@ bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
provider_session->set_license(key_response.data(), key_response.size());
file.SerializeToString(&serialized_file);
return StoreFileWithHash(kUsageInfoFileName, serialized_file);
return StoreFileWithHash(file_name, serialized_file);
}
bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) {
bool DeviceFiles::DeleteUsageInfo(const std::string& app_id,
const std::string& provider_session_token) {
if (!initialized_) {
LOGW("DeviceFiles::DeleteUsageInfo: not initialized");
return false;
}
std::string serialized_file;
if (!RetrieveHashedFile(kUsageInfoFileName, &serialized_file)) return false;
std::string file_name = GetUsageInfoFileName(app_id);
if (!RetrieveHashedFile(file_name, &serialized_file)) return false;
video_widevine_client::sdk::File file;
if (!file.ParseFromString(serialized_file)) {
@@ -382,8 +395,7 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) {
int index = 0;
bool found = false;
for (; index < usage_info->sessions_size(); ++index) {
if (usage_info->sessions(index).token().compare(provider_session_token) ==
0) {
if (usage_info->sessions(index).token() == provider_session_token) {
found = true;
break;
}
@@ -405,26 +417,43 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) {
sessions->RemoveLast();
file.SerializeToString(&serialized_file);
return StoreFileWithHash(kUsageInfoFileName, serialized_file);
return StoreFileWithHash(file_name, serialized_file);
}
bool DeviceFiles::DeleteUsageInfo() {
bool DeviceFiles::DeleteAllUsageInfoForApp(
const std::string& app_id,
std::vector<std::string>* provider_session_tokens) {
if (!initialized_) {
LOGW("DeviceFiles::DeleteUsageInfo: not initialized");
LOGW("DeviceFiles::DeleteAllUsageInfoForApp: not initialized");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::DeleteUsageInfo: Unable to get base path");
if (NULL == provider_session_tokens) {
LOGW("DeviceFiles::DeleteAllUsageInfoForApp: pst destination not provided");
return false;
}
path.append(kUsageInfoFileName);
provider_session_tokens->clear();
return file_->Remove(path);
std::string file_name = GetUsageInfoFileName(app_id);
if (!FileExists(file_name)) return true;
std::string serialized_file;
if (RetrieveHashedFile(file_name, &serialized_file)) {
video_widevine_client::sdk::File file_proto;
if (!file_proto.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::DeleteAllUsageInfoForApp: Unable to parse file");
} else {
for (int i = 0; i < file_proto.usage_info().sessions_size(); ++i) {
provider_session_tokens->push_back(
file_proto.usage_info().sessions(i).token());
}
}
} else {
LOGW("DeviceFiles::DeleteAllUsageInfoForApp: Unable to retrieve file");
}
return RemoveFile(file_name);
}
bool DeviceFiles::RetrieveUsageInfo(
const std::string& app_id,
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info) {
if (!initialized_) {
LOGW("DeviceFiles::RetrieveUsageInfo: not initialized");
@@ -439,15 +468,9 @@ bool DeviceFiles::RetrieveUsageInfo(
}
std::string serialized_file;
if (!RetrieveHashedFile(kUsageInfoFileName, &serialized_file)) {
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
return false;
}
path += kUsageInfoFileName;
if (!file_->Exists(path) || 0 == file_->FileSize(path)) {
std::string file_name = GetUsageInfoFileName(app_id);
if (!RetrieveHashedFile(file_name, &serialized_file)) {
if (!FileExists(file_name) || GetFileSize(file_name) == 0) {
usage_info->resize(0);
return true;
}
@@ -471,15 +494,45 @@ bool DeviceFiles::RetrieveUsageInfo(
return true;
}
bool DeviceFiles::StoreFileWithHash(const char* name,
const std::string& serialized_file) {
if (!file_.get()) {
LOGW("DeviceFiles::StoreFileWithHash: Invalid file handle");
bool DeviceFiles::RetrieveUsageInfo(const std::string& app_id,
const std::string& provider_session_token,
CdmKeyMessage* license_request,
CdmKeyResponse* license_response) {
if (!initialized_) {
LOGW("DeviceFiles::RetrieveUsageInfo: not initialized");
return false;
}
std::string serialized_file;
std::string file_name = GetUsageInfoFileName(app_id);
if (!RetrieveHashedFile(file_name, &serialized_file)) return false;
video_widevine_client::sdk::File file;
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse file");
return false;
}
int index = 0;
bool found = false;
for (; index < file.usage_info().sessions_size(); ++index) {
if (file.usage_info().sessions(index).token() == provider_session_token) {
found = true;
break;
}
}
if (!found) {
return false;
}
if (!name) {
LOGW("DeviceFiles::StoreFileWithHash: Unspecified file name parameter");
*license_request = file.usage_info().sessions(index).license_request();
*license_response = file.usage_info().sessions(index).license();
return true;
}
bool DeviceFiles::StoreFileWithHash(const std::string& name,
const std::string& serialized_file) {
if (!file_.get()) {
LOGW("DeviceFiles::StoreFileWithHash: Invalid file handle");
return false;
}
@@ -501,18 +554,13 @@ bool DeviceFiles::StoreFileWithHash(const char* name,
return StoreFileRaw(name, serialized_hash_file);
}
bool DeviceFiles::StoreFileRaw(const char* name,
bool DeviceFiles::StoreFileRaw(const std::string& name,
const std::string& serialized_file) {
if (!file_.get()) {
LOGW("DeviceFiles::StoreFileRaw: Invalid file handle");
return false;
}
if (!name) {
LOGW("DeviceFiles::StoreFileRaw: Unspecified file name parameter");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::StoreFileRaw: Unable to get base path");
@@ -546,18 +594,13 @@ bool DeviceFiles::StoreFileRaw(const char* name,
return true;
}
bool DeviceFiles::RetrieveHashedFile(const char* name,
bool DeviceFiles::RetrieveHashedFile(const std::string& name,
std::string* serialized_file) {
if (!file_.get()) {
LOGW("DeviceFiles::RetrieveHashedFile: Invalid file handle");
return false;
}
if (!name) {
LOGW("DeviceFiles::RetrieveHashedFile: Unspecified file name parameter");
return false;
}
if (!serialized_file) {
LOGW(
"DeviceFiles::RetrieveHashedFile: Unspecified serialized_file "
@@ -582,6 +625,9 @@ bool DeviceFiles::RetrieveHashedFile(const char* name,
if (bytes <= 0) {
LOGW("DeviceFiles::RetrieveHashedFile: File size invalid: %s",
path.c_str());
// Remove the corrupted file so the caller will not get the same error
// when trying to access the file repeatedly, causing the system to stall.
file_->Remove(path);
return false;
}
@@ -614,8 +660,11 @@ bool DeviceFiles::RetrieveHashedFile(const char* name,
return false;
}
if (hash.compare(hash_file.hash())) {
if (hash != hash_file.hash()) {
LOGW("DeviceFiles::RetrieveHashedFile: Hash mismatch");
// Remove the corrupted file so the caller will not get the same error
// when trying to access the file repeatedly, causing the system to stall.
file_->Remove(path);
return false;
}
@@ -623,6 +672,54 @@ bool DeviceFiles::RetrieveHashedFile(const char* name,
return true;
}
bool DeviceFiles::FileExists(const std::string& name) {
if (!file_.get()) {
LOGW("DeviceFiles::FileExists: Invalid file handle");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::FileExists: Unable to get base path");
return false;
}
path += name;
return file_->Exists(path);
}
bool DeviceFiles::RemoveFile(const std::string& name) {
if (!file_.get()) {
LOGW("DeviceFiles::RemoveFile: Invalid file handle");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::RemoveFile: Unable to get base path");
return false;
}
path += name;
return file_->Remove(path);
}
ssize_t DeviceFiles::GetFileSize(const std::string& name) {
if (!file_.get()) {
LOGW("DeviceFiles::GetFileSize: Invalid file handle");
return -1;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::GetFileSize: Unable to get base path");
return -1;
}
path += name;
return file_->FileSize(path);
}
void DeviceFiles::SecurityLevelPathBackwardCompatibility() {
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
@@ -687,17 +784,33 @@ void DeviceFiles::SecurityLevelPathBackwardCompatibility() {
}
}
std::string DeviceFiles::GetCertificateFileName() {
return kCertificateFileName;
std::string DeviceFiles::GetCertificateFileName(const std::string& origin) {
std::string hash;
if (origin != EMPTY_ORIGIN) {
hash = GetFileNameSafeHash(origin);
}
return kCertificateFileNamePrefix + hash + kCertificateFileNameExt;
}
std::string DeviceFiles::GetLicenseFileNameExtension() {
return kLicenseFileNameExt;
}
std::string DeviceFiles::GetUsageInfoFileName() { return kUsageInfoFileName; }
std::string DeviceFiles::GetUsageInfoFileName(const std::string& app_id) {
std::string hash;
if (app_id != "") {
hash = GetFileNameSafeHash(app_id);
}
return kUsageInfoFileNamePrefix + hash + kUsageInfoFileNameExt;
}
std::string DeviceFiles::GetBlankFileData() { return kBlankFileData; }
std::string DeviceFiles::GetFileNameSafeHash(const std::string& input) {
std::vector<uint8_t> hash(MD5_DIGEST_LENGTH);
const unsigned char* input_ptr =
reinterpret_cast<const unsigned char*>(input.data());
MD5(input_ptr, input.size(), &hash[0]);
return wvcdm::Base64SafeEncode(hash);
}
void DeviceFiles::SetTestFile(File* file) {
file_.reset(file);

View File

@@ -13,6 +13,11 @@ package video_widevine_client.sdk;
// need this if we are using libprotobuf-cpp-2.3.0-lite
option optimize_for = LITE_RUNTIME;
message NameValue {
optional string name = 1;
optional string value = 2;
}
message DeviceCertificate {
optional bytes certificate = 1;
optional bytes wrapped_private_key = 2;
@@ -31,6 +36,9 @@ message License {
optional bytes renewal_request = 5;
optional bytes renewal = 6;
optional bytes release_server_url = 7;
optional int64 playback_start_time = 8 [default = 0];
optional int64 last_playback_time = 9 [default = 0];
repeated NameValue app_parameters = 10;
}
message UsageInfo {

View File

@@ -38,76 +38,122 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
BufferReader reader(reinterpret_cast<const uint8_t*>(init_data.data()),
init_data.length());
// TODO(kqyang): Extracted from an actual init_data;
// Need to find out where it comes from.
// Widevine's registered system ID.
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
// one PSSH box consists of:
// 4 byte size of the atom, inclusive. (0 means the rest of the buffer.)
// 4 byte atom type, "pssh".
// (optional, if size == 1) 8 byte size of the atom, inclusive.
// 1 byte version, value 0 or 1. (skip if larger.)
// 3 byte flags, value 0. (ignored.)
// 16 byte system id.
// (optional, if version == 1) 4 byte key ID count. (K)
// (optional, if version == 1) K * 16 byte key ID.
// 4 byte size of PSSH data, exclusive. (N)
// N byte PSSH data.
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");
size_t start_pos = reader.pos();
// atom size, used for skipping.
uint64_t size;
if (!reader.Read4Into8(&size)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read atom size.");
return false;
}
std::vector<uint8_t> atom_type;
if (!reader.ReadVec(&atom_type, 4)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read atom type.");
return false;
}
if (size == 1) {
if (!reader.Read8(&size)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read 64-bit atom "
"size.");
return false;
}
} else if (size == 0) {
size = reader.size() - start_pos;
}
// "pssh"
std::vector<uint8_t> pssh;
if (!reader.ReadVec(&pssh, 4)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH literal");
return false;
if (memcmp(&atom_type[0], "pssh", 4)) {
LOGW("CdmEngine::ExtractWidevinePssh: PSSH literal not present.");
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of the "
"atom.");
return false;
}
continue;
}
if (memcmp(&pssh[0], "pssh", 4)) {
LOGW("CdmEngine::ExtractWidevinePssh: PSSH literal not present");
// version
uint8_t version;
if (!reader.Read1(&version)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH version.");
return false;
}
// flags
uint32_t flags;
if (!reader.Read4(&flags)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH flags");
return false;
if (version > 1) {
// unrecognized version - skip.
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of the "
"atom.");
return false;
}
continue;
}
if (flags != 0) {
LOGW("CdmEngine::ExtractWidevinePssh: PSSH flags not zero");
// flags
if (!reader.SkipBytes(3)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the PSSH flags.");
return false;
}
// system id
std::vector<uint8_t> system_id;
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read system ID");
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");
// skip non-Widevine PSSH boxes.
if (!reader.SkipBytes(size - (reader.pos() - start_pos))) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip the rest of the "
"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");
if (version == 1) {
// v1 has additional fields for key IDs. We can skip them.
uint32_t num_key_ids;
if (!reader.Read4(&num_key_ids)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read num key IDs.");
return false;
}
if (!reader.SkipBytes(num_key_ids * 16)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to skip key IDs.");
return false;
}
}
// size of PSSH data
uint32_t data_length;
if (!reader.Read4(&data_length)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data size.");
return false;
}
output->clear();
if (!reader.ReadString(output, pssh_length)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH");
if (!reader.ReadString(output, data_length)) {
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH data.");
return false;
}

File diff suppressed because it is too large Load Diff

View File

@@ -78,6 +78,10 @@ message License {
// Indicates that the license shall be sent for renewal when usage is
// started.
optional bool renew_with_usage = 11 [default = false];
// Indicates to client that license renewal and release requests ought to
// include ClientIdentification (client_id).
optional bool renew_with_client_id = 12 [default = false];
}
message KeyContainer {
@@ -167,6 +171,8 @@ message License {
optional KeyType type = 4;
optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO];
optional OutputProtection required_protection = 6;
// NOTE: Use of requested_protection is not recommended as it is only
// supported on a small number of platforms.
optional OutputProtection requested_protection = 7;
optional KeyControl key_control = 8;
optional OperatorSessionKeyPermissions operator_session_key_permissions = 9;
@@ -174,7 +180,13 @@ message License {
// content being decrypted/decoded falls within one of the specified ranges,
// the optional required_protections may be applied. Otherwise an error will
// be reported.
// NOTE: Use of this feature is not recommended, as it is only supported on
// a small number of platforms.
repeated VideoResolutionConstraint video_resolution_constraints = 10;
// Optional flag to indicate the key must only be used if the client
// supports anti rollback of the user table. Content provider can query the
// client capabilities to determine if the client support this feature.
optional bool anti_rollback_usage_table = 11 [default = false];
}
optional LicenseIdentification id = 1;
@@ -286,36 +298,6 @@ message SignedMessage {
optional RemoteAttestation remote_attestation = 5;
}
// 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;
// Client token for the session. This session is for use by the license
// provider, and is akin to a client cookie. It will be copied to
// License::provider_client_token, and sent back by the client in
// ClientIdentification::provider_client_token in all license requests
// thereafter.
optional bytes provider_client_token = 6;
// Session token for the session. This token is for use by the license
// provider, and is akin to a session cookie. It will be copied to
// LicenseIdentfication::provider_session_token, and sent back in all
// license renewal and release requests for the session thereafter.
optional bytes provider_session_token = 7;
}
// 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
// ----------------------------------------------------------------------------
@@ -348,6 +330,9 @@ message ProvisioningRequest {
optional bytes nonce = 2;
// Options for type of certificate to generate. Optional.
optional ProvisioningOptions options = 3;
// Stable identifier, unique for each device + application (or origin).
// Required if doing per-origin provisioning.
optional bytes stable_id = 4;
}
// Provisioning response sent by the provisioning server to client devices.
@@ -410,6 +395,7 @@ message ClientIdentification {
optional bool video_resolution_constraints = 3 [default = false];
optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE];
optional uint32 oem_crypto_api_version = 5;
optional bool anti_rollback_usage_table = 6 [default = false];
}
// Type of factory-provisioned device root of trust. Optional.

173
core/src/max_res_engine.cpp Normal file
View File

@@ -0,0 +1,173 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#include "max_res_engine.h"
#include "clock.h"
#include "log.h"
namespace {
const int64_t kHdcpCheckInterval = 10;
const uint32_t kNoResolution = 0;
} // namespace
namespace wvcdm {
MaxResEngine::MaxResEngine(CryptoSession* crypto_session) {
Init(crypto_session, new Clock());
}
MaxResEngine::MaxResEngine(CryptoSession* crypto_session, Clock* clock) {
Init(crypto_session, clock);
}
MaxResEngine::~MaxResEngine() {
AutoLock lock(status_lock_);
DeleteAllKeys();
}
bool MaxResEngine::CanDecrypt(const KeyId& key_id) {
AutoLock lock(status_lock_);
if (keys_.count(key_id) > 0) {
return keys_[key_id]->can_decrypt();
} else {
// If a Key ID is unknown to us, we don't know of any constraints for it,
// so never block decryption.
return true;
}
}
void MaxResEngine::Init(CryptoSession* crypto_session, Clock* clock) {
AutoLock lock(status_lock_);
current_resolution_ = kNoResolution;
clock_.reset(clock);
next_check_time_ = clock_->GetCurrentTime();
crypto_session_ = crypto_session;
}
void MaxResEngine::SetLicense(
const video_widevine_server::sdk::License& license) {
AutoLock lock(status_lock_);
DeleteAllKeys();
for (int32_t key_index = 0; key_index < license.key_size(); ++key_index) {
const KeyContainer& key = license.key(key_index);
if (key.type() == KeyContainer::CONTENT && key.has_id() &&
key.video_resolution_constraints_size() > 0) {
const ConstraintList& constraints = key.video_resolution_constraints();
const KeyId& key_id = key.id();
if (key.has_required_protection()) {
keys_[key_id] =
new KeyStatus(constraints, key.required_protection().hdcp());
} else {
keys_[key_id] = new KeyStatus(constraints);
}
}
}
}
void MaxResEngine::SetResolution(uint32_t width, uint32_t height) {
AutoLock lock(status_lock_);
current_resolution_ = width * height;
}
void MaxResEngine::OnTimerEvent() {
AutoLock lock(status_lock_);
int64_t current_time = clock_->GetCurrentTime();
if (!keys_.empty() && current_resolution_ != kNoResolution &&
current_time >= next_check_time_) {
CryptoSession::HdcpCapability current_hdcp_level;
CryptoSession::HdcpCapability ignored;
if (!crypto_session_->GetHdcpCapabilities(&current_hdcp_level, &ignored)) {
current_hdcp_level = HDCP_NONE;
}
for (KeyIterator i = keys_.begin(); i != keys_.end(); ++i) {
i->second->Update(current_resolution_, current_hdcp_level);
}
next_check_time_ = current_time + kHdcpCheckInterval;
}
}
void MaxResEngine::DeleteAllKeys() {
// This helper method assumes that status_lock_ is already held.
for (KeyIterator i = keys_.begin(); i != keys_.end(); ++i) delete i->second;
keys_.clear();
}
MaxResEngine::KeyStatus::KeyStatus(const ConstraintList& constraints)
: default_hdcp_level_(HDCP_NONE) {
Init(constraints);
}
MaxResEngine::KeyStatus::KeyStatus(
const ConstraintList& constraints,
const OutputProtection::HDCP& default_hdcp_level)
: default_hdcp_level_(ProtobufHdcpToOemCryptoHdcp(default_hdcp_level)) {
Init(constraints);
}
void MaxResEngine::KeyStatus::Init(const ConstraintList& constraints) {
constraints_.Clear();
constraints_.MergeFrom(constraints);
can_decrypt_ = true;
}
void MaxResEngine::KeyStatus::Update(
uint32_t res, CryptoSession::HdcpCapability current_hdcp_level) {
VideoResolutionConstraint* current_constraint = GetConstraintForRes(res);
if (current_constraint == NULL) {
can_decrypt_ = false;
return;
}
CryptoSession::HdcpCapability desired_hdcp_level;
if (current_constraint->has_required_protection()) {
desired_hdcp_level = ProtobufHdcpToOemCryptoHdcp(
current_constraint->required_protection().hdcp());
} else {
desired_hdcp_level = default_hdcp_level_;
}
can_decrypt_ = (current_hdcp_level >= desired_hdcp_level);
}
MaxResEngine::VideoResolutionConstraint*
MaxResEngine::KeyStatus::GetConstraintForRes(uint32_t res) {
typedef ConstraintList::pointer_iterator Iterator;
for (Iterator i = constraints_.pointer_begin();
i != constraints_.pointer_end(); ++i) {
VideoResolutionConstraint* constraint = *i;
if (constraint->has_min_resolution_pixels() &&
constraint->has_max_resolution_pixels() &&
res >= constraint->min_resolution_pixels() &&
res <= constraint->max_resolution_pixels()) {
return constraint;
}
}
return NULL;
}
CryptoSession::HdcpCapability
MaxResEngine::KeyStatus::ProtobufHdcpToOemCryptoHdcp(
const OutputProtection::HDCP& input) {
switch (input) {
case OutputProtection::HDCP_NONE:
return HDCP_NONE;
case OutputProtection::HDCP_V1:
return HDCP_V1;
case OutputProtection::HDCP_V2:
return HDCP_V2;
case OutputProtection::HDCP_V2_1:
return HDCP_V2_1;
case OutputProtection::HDCP_V2_2:
return HDCP_V2_2;
case OutputProtection::HDCP_NO_DIGITAL_OUTPUT:
return HDCP_NO_DIGITAL_OUTPUT;
default:
LOGE("MaxResEngine::KeyStatus::ProtobufHdcpToOemCryptoHdcp: "
"Unknown HDCP Level");
return HDCP_NO_DIGITAL_OUTPUT;
}
}
} // wvcdm

View File

@@ -5,7 +5,6 @@
// compile time.
//
#include "OEMCryptoCENC.h"
#include "oemcrypto_adapter.h"
namespace wvcdm {
@@ -15,6 +14,12 @@ OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session,
return ::OEMCrypto_OpenSession(session);
}
OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox,
size_t keyBoxLength,
SecurityLevel level) {
return ::OEMCrypto_InstallKeybox(keybox, keyBoxLength);
}
OEMCryptoResult OEMCrypto_IsKeyboxValid(SecurityLevel level) {
return ::OEMCrypto_IsKeyboxValid();
}
@@ -29,12 +34,6 @@ OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength,
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();
}
@@ -43,8 +42,34 @@ const char* OEMCrypto_SecurityLevel(SecurityLevel level) {
return ::OEMCrypto_SecurityLevel();
}
OEMCryptoResult OEMCrypto_GetHDCPCapability(
SecurityLevel level, OEMCrypto_HDCP_Capability* current,
OEMCrypto_HDCP_Capability* maximum) {
return ::OEMCrypto_GetHDCPCapability(current, maximum);
}
bool OEMCrypto_SupportsUsageTable(SecurityLevel level) {
return ::OEMCrypto_SupportsUsageTable();
}
}; // namespace wvcdm
bool OEMCrypto_IsAntiRollbackHwPresent(SecurityLevel level) {
return ::OEMCrypto_IsAntiRollbackHwPresent();
}
OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(SecurityLevel level,
size_t* count) {
return ::OEMCrypto_GetNumberOfOpenSessions(count);
}
OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(SecurityLevel level,
size_t* maximum) {
return ::OEMCrypto_GetMaxNumberOfSessions(maximum);
}
OEMCryptoResult OEMCrypto_CopyBuffer(
SecurityLevel level, const uint8_t* data_addr, size_t data_length,
OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags) {
return ::OEMCrypto_CopyBuffer(data_addr, data_length, out_buffer,
subsample_flags);
}
} // namespace wvcdm

View File

@@ -0,0 +1,62 @@
// 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.
//
// Defines APIs introduced in newer version (v10) which is not available in v9
// to allow an older oemcrypto implementation to be linked with CDM.
#include "OEMCryptoCENC.h"
extern "C" OEMCryptoResult OEMCrypto_GetHDCPCapability_V9(uint8_t* current,
uint8_t* maximum);
extern "C" OEMCryptoResult OEMCrypto_LoadTestKeybox() {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_LoadTestRSAKey() {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_QueryKeyControl(
OEMCrypto_SESSION session, const uint8_t* key_id, size_t key_id_length,
uint8_t* key_control_block, size_t* key_control_block_length) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_CopyBuffer(
SecurityLevel level, const uint8_t* data_addr, size_t data_length,
OEMCrypto_DestBufferDesc* out_buffer, uint8_t subsample_flags) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_GetHDCPCapability(
OEMCrypto_HDCP_Capability* current, OEMCrypto_HDCP_Capability* maximum) {
if (current == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
if (maximum == NULL) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
uint8_t current_byte, maximum_byte;
OEMCryptoResult sts = OEMCrypto_GetHDCPCapability_V9(&current_byte,
&maximum_byte);
*current = static_cast<OEMCrypto_HDCP_Capability>(current_byte);
*maximum = static_cast<OEMCrypto_HDCP_Capability>(maximum_byte);
return sts;
}
extern "C" bool OEMCrypto_IsAntiRollbackHwPresent() {
return false;
}
extern "C" OEMCryptoResult OEMCrypto_GetNumberOfOpenSessions(size_t* count) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_GetMaxNumberOfSessions(size_t* max) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_ForceDeleteUsageEntry(
const uint8_t* pst, size_t pst_length) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}

View File

@@ -1,19 +1,16 @@
/*******************************************************************************
*
* 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.
* An implementation should compile either oemcrypto_adapter_dynamic.cpp or
* oemcrypto_adapter_static.cpp, but not both.
* This version contains shim code to allow an older, version 8 API, oemcrypto,
* to be linked with CDM.
*
******************************************************************************/
// 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.
//
// An implementation should compile either oemcrypto_adapter_dynamic.cpp or
// oemcrypto_adapter_static.cpp, but not both.
//
// Defines APIs introduced in newer version (v9) which is not available in v8
// to allow an older oemcrypto implementation to be linked with CDM.
#include "OEMCryptoCENC.h"
#include "oemcrypto_adapter.h"
extern "C" OEMCryptoResult OEMCrypto_LoadKeys_V8(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
@@ -25,45 +22,6 @@ extern "C" OEMCryptoResult OEMCrypto_GenerateRSASignature_V8(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
uint8_t* signature, size_t* signature_length);
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();
}
bool OEMCrypto_SupportsUsageTable(SecurityLevel level) {
return ::OEMCrypto_SupportsUsageTable();
}
extern "C" OEMCryptoResult OEMCrypto_LoadKeys(
OEMCrypto_SESSION session, const uint8_t* message, size_t message_length,
const uint8_t* signature, size_t signature_length,
@@ -113,4 +71,3 @@ extern "C" OEMCryptoResult OEMCrypto_DeleteUsageEntry(
size_t signature_length) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
}; // namespace wvcdm

View File

@@ -13,48 +13,60 @@
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
using video_widevine_server::sdk::License;
namespace wvcdm {
PolicyEngine::PolicyEngine() { Init(new Clock()); }
PolicyEngine::PolicyEngine(CdmSessionId session_id,
WvCdmEventListener* event_listener,
CryptoSession* crypto_session)
: license_state_(kLicenseStateInitial),
license_start_time_(0),
playback_start_time_(0),
last_playback_time_(0),
last_expiry_time_(0),
last_expiry_time_set_(false),
next_renewal_time_(0),
policy_max_duration_seconds_(0),
session_id_(session_id),
event_listener_(event_listener),
max_res_engine_(new MaxResEngine(crypto_session)),
clock_(new Clock) {}
PolicyEngine::PolicyEngine(Clock* clock) { Init(clock); }
PolicyEngine::~PolicyEngine() {}
PolicyEngine::~PolicyEngine() {
if (clock_) delete clock_;
bool PolicyEngine::CanDecrypt(const KeyId& key_id) {
if (keys_status_.find(key_id) == keys_status_.end()) {
LOGE("PolicyEngine::CanDecrypt Key '%s' not in license.",
b2a_hex(key_id).c_str());
return false;
}
return keys_status_[key_id] == kKeyStatusUsable;
}
void PolicyEngine::Init(Clock* clock) {
license_state_ = kLicenseStateInitial;
can_decrypt_ = false;
license_start_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;
void PolicyEngine::OnTimerEvent() {
int64_t current_time = clock_->GetCurrentTime();
// License expiration trumps all.
if ((IsLicenseDurationExpired(current_time) ||
IsPlaybackDurationExpired(current_time)) &&
if (IsLicenseOrPlaybackDurationExpired(current_time) &&
license_state_ != kLicenseStateExpired) {
license_state_ = kLicenseStateExpired;
can_decrypt_ = false;
*event = LICENSE_EXPIRED_EVENT;
*event_occurred = true;
NotifyKeysChange(kKeyStatusExpired);
return;
}
max_res_engine_->OnTimerEvent();
bool renewal_needed = false;
// Test to determine if renewal should be attempted.
switch (license_state_) {
case kLicenseStateCanPlay: {
if (IsRenewalDelayExpired(current_time)) renewal_needed = true;
// HDCP may change, so force a check.
NotifyKeysChange(kKeyStatusUsable);
break;
}
@@ -71,7 +83,7 @@ void PolicyEngine::OnTimerEvent(bool* event_occurred, CdmEventType* event) {
case kLicenseStatePending: {
if (current_time >= license_start_time_) {
license_state_ = kLicenseStateCanPlay;
can_decrypt_ = true;
NotifyKeysChange(kKeyStatusUsable);
}
break;
}
@@ -83,28 +95,46 @@ void PolicyEngine::OnTimerEvent(bool* event_occurred, CdmEventType* event) {
default: {
license_state_ = kLicenseStateExpired;
can_decrypt_ = false;
NotifyKeysChange(kKeyStatusInternalError);
break;
}
}
if (renewal_needed) {
UpdateRenewalRequest(current_time);
*event = LICENSE_RENEWAL_NEEDED_EVENT;
*event_occurred = true;
if (event_listener_) event_listener_->OnSessionRenewalNeeded(session_id_);
}
}
void PolicyEngine::SetLicense(
const video_widevine_server::sdk::License& license) {
void PolicyEngine::SetLicense(const License& license) {
license_id_.Clear();
license_id_.CopyFrom(license.id());
policy_.Clear();
// Extract content key ids.
keys_status_.clear();
for (int key_index = 0; key_index < license.key_size(); ++key_index) {
const License::KeyContainer& key = license.key(key_index);
if (key.type() == License::KeyContainer::CONTENT && key.has_id())
keys_status_[key.id()] = kKeyStatusInternalError;
}
UpdateLicense(license);
max_res_engine_->SetLicense(license);
}
void PolicyEngine::SetLicenseForRelease(const License& license) {
license_id_.Clear();
license_id_.CopyFrom(license.id());
policy_.Clear();
// Expire any old keys.
NotifyKeysChange(kKeyStatusExpired);
UpdateLicense(license);
}
void PolicyEngine::UpdateLicense(
const video_widevine_server::sdk::License& license) {
void PolicyEngine::UpdateLicense(const License& license) {
if (!license.has_policy()) return;
if (kLicenseStateExpired == license_state_) {
@@ -144,23 +174,22 @@ void PolicyEngine::UpdateLicense(
policy_max_duration_seconds_ = policy_.license_duration_seconds();
}
if (!policy_.can_play()) {
int64_t current_time = clock_->GetCurrentTime();
if (!policy_.can_play() || IsLicenseOrPlaybackDurationExpired(current_time)) {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusExpired);
return;
}
int64_t current_time = clock_->GetCurrentTime();
if (IsLicenseDurationExpired(current_time)) return;
if (IsPlaybackDurationExpired(current_time)) return;
// Update state
if (current_time >= license_start_time_) {
license_state_ = kLicenseStateCanPlay;
can_decrypt_ = true;
NotifyKeysChange(kKeyStatusUsable);
} else {
license_state_ = kLicenseStatePending;
can_decrypt_ = false;
NotifyKeysChange(kKeyStatusPending);
}
NotifyExpirationUpdate();
}
void PolicyEngine::BeginDecryption() {
@@ -170,10 +199,12 @@ void PolicyEngine::BeginDecryption() {
case kLicenseStateNeedRenewal:
case kLicenseStateWaitingLicenseUpdate:
playback_start_time_ = clock_->GetCurrentTime();
last_playback_time_ = playback_start_time_;
if (policy_.renew_with_usage()) {
license_state_ = kLicenseStateNeedRenewal;
}
NotifyExpirationUpdate();
break;
case kLicenseStateInitial:
case kLicenseStatePending:
@@ -184,11 +215,27 @@ void PolicyEngine::BeginDecryption() {
}
}
void PolicyEngine::DecryptionEvent() {
last_playback_time_ = clock_->GetCurrentTime();
}
void PolicyEngine::NotifyResolution(uint32_t width, uint32_t height) {
max_res_engine_->SetResolution(width, height);
}
void PolicyEngine::NotifySessionExpiration() {
license_state_ = kLicenseStateExpired;
NotifyKeysChange(kKeyStatusExpired);
}
CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) {
std::stringstream ss;
int64_t current_time = clock_->GetCurrentTime();
if (license_state_ == kLicenseStateInitial) return UNKNOWN_ERROR;
if (license_state_ == kLicenseStateInitial) {
key_info->clear();
return NO_ERROR;
}
(*key_info)[QUERY_KEY_LICENSE_TYPE] =
license_id_.type() == video_widevine_server::sdk::STREAMING
@@ -210,47 +257,73 @@ CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) {
return NO_ERROR;
}
bool PolicyEngine::GetSecondsSinceStarted(int64_t* seconds_since_started) {
if (playback_start_time_ == 0) return false;
*seconds_since_started = clock_->GetCurrentTime() - playback_start_time_;
return (*seconds_since_started >= 0) ? true : false;
}
bool PolicyEngine::GetSecondsSinceLastPlayed(
int64_t* seconds_since_last_played) {
if (last_playback_time_ == 0) return false;
*seconds_since_last_played = clock_->GetCurrentTime() - last_playback_time_;
return (*seconds_since_last_played >= 0) ? true : false;
}
void PolicyEngine::RestorePlaybackTimes(int64_t playback_start_time,
int64_t last_playback_time) {
playback_start_time_ = (playback_start_time > 0) ? playback_start_time : 0;
last_playback_time_ = (last_playback_time > 0) ? last_playback_time : 0;
NotifyExpirationUpdate();
}
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
license_state_ = kLicenseStateWaitingLicenseUpdate;
next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds();
}
bool PolicyEngine::IsLicenseOrPlaybackDurationExpired(int64_t current_time) {
int64_t expiry_time =
IsPlaybackStarted() ? GetPlaybackExpiryTime() : GetLicenseExpiryTime();
return (expiry_time == NEVER_EXPIRES) ? false : (expiry_time <= current_time);
}
// 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_start_time_ + policy_max_duration_seconds_ <= current_time;
int64_t PolicyEngine::GetLicenseExpiryTime() {
return policy_max_duration_seconds_ > 0
? license_start_time_ + policy_max_duration_seconds_
: NEVER_EXPIRES;
}
int64_t PolicyEngine::GetPlaybackExpiryTime() {
return (playback_start_time_ > 0 && policy_.playback_duration_seconds() > 0)
? (playback_start_time_ + policy_.playback_duration_seconds())
: NEVER_EXPIRES;
}
int64_t PolicyEngine::GetLicenseDurationRemaining(int64_t current_time) {
if (0 == policy_max_duration_seconds_) return LLONG_MAX;
int64_t remaining_time =
policy_max_duration_seconds_ + license_start_time_ - current_time;
if (remaining_time < 0)
remaining_time = 0;
else if (remaining_time > policy_max_duration_seconds_)
remaining_time = policy_max_duration_seconds_;
return remaining_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;
int64_t license_expiry_time = GetLicenseExpiryTime();
if (license_expiry_time == NEVER_EXPIRES) return LLONG_MAX;
if (license_expiry_time < current_time) return 0;
return std::min(license_expiry_time - current_time,
policy_max_duration_seconds_);
}
int64_t PolicyEngine::GetPlaybackDurationRemaining(int64_t current_time) {
if (0 == policy_.playback_duration_seconds()) return LLONG_MAX;
if (0 == playback_start_time_) return policy_.playback_duration_seconds();
int64_t playback_expiry_time = GetPlaybackExpiryTime();
if (playback_expiry_time == NEVER_EXPIRES) {
return (policy_.playback_duration_seconds() != 0)
? policy_.playback_duration_seconds()
: LLONG_MAX;
}
int64_t remaining_time =
policy_.playback_duration_seconds() + playback_start_time_ - current_time;
if (remaining_time < 0) remaining_time = 0;
return remaining_time;
if (playback_expiry_time < current_time) return 0;
return std::min(playback_expiry_time - current_time,
policy_.playback_duration_seconds());
}
bool PolicyEngine::IsRenewalDelayExpired(int64_t current_time) {
@@ -271,4 +344,45 @@ bool PolicyEngine::IsRenewalRetryIntervalExpired(int64_t current_time) {
next_renewal_time_ <= current_time;
}
void PolicyEngine::NotifyKeysChange(CdmKeyStatus new_status) {
bool keys_changed = false;
bool has_new_usable_key = false;
for (std::map<KeyId, CdmKeyStatus>::iterator it = keys_status_.begin();
it != keys_status_.end(); ++it) {
const KeyId key_id = it->first;
CdmKeyStatus& key_status = it->second;
CdmKeyStatus updated_status = new_status;
if (updated_status == kKeyStatusUsable) {
if (!max_res_engine_->CanDecrypt(key_id))
updated_status = kKeyStatusOutputNotAllowed;
}
if (key_status != updated_status) {
key_status = updated_status;
if (updated_status == kKeyStatusUsable) has_new_usable_key = true;
keys_changed = true;
}
}
if (keys_changed && event_listener_) {
event_listener_->OnSessionKeysChange(session_id_, keys_status_,
has_new_usable_key);
}
}
void PolicyEngine::NotifyExpirationUpdate() {
int64_t expiry_time =
IsPlaybackStarted() ? GetPlaybackExpiryTime() : GetLicenseExpiryTime();
if (!last_expiry_time_set_ || expiry_time != last_expiry_time_) {
last_expiry_time_ = expiry_time;
if (event_listener_)
event_listener_->OnExpirationUpdate(session_id_, expiry_time);
}
last_expiry_time_set_ = true;
}
void PolicyEngine::set_clock(Clock* clock) { clock_.reset(clock); }
void PolicyEngine::set_max_res_engine(MaxResEngine* max_res_engine) {
max_res_engine_.reset(max_res_engine);
}
} // wvcdm

View File

@@ -108,8 +108,8 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
}
int padding = 0;
if (EVP_EncryptFinal(&ctx, reinterpret_cast<uint8_t*>(&(*out)[out_length]),
&padding) == 0) {
if (EVP_EncryptFinal_ex(&ctx, reinterpret_cast<uint8_t*>(&(*out)[out_length]),
&padding) == 0) {
LOGE("AesCbcKey::Encrypt: PKCS7 padding failure: %s",
ERR_error_string(ERR_get_error(), NULL));
return false;
@@ -180,6 +180,64 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message,
return true;
}
// LogOpenSSLError is a callback from OpenSSL which is called with each error
// in the thread's error queue.
static int LogOpenSSLError(const char *msg, size_t /* len */, void */* ctx */) {
LOGE(" %s", msg);
return 1;
}
static bool VerifyPSSSignature(EVP_PKEY *pkey, const std::string &message,
const std::string &signature) {
EVP_MD_CTX ctx;
EVP_MD_CTX_init(&ctx);
EVP_PKEY_CTX *pctx = NULL;
if (EVP_DigestVerifyInit(&ctx, &pctx, EVP_sha1(), NULL /* no ENGINE */,
pkey) != 1) {
LOGE("EVP_DigestVerifyInit failed in VerifyPSSSignature");
goto err;
}
if (EVP_PKEY_CTX_set_signature_md(pctx, EVP_sha1()) != 1) {
LOGE("EVP_PKEY_CTX_set_signature_md failed in VerifyPSSSignature");
goto err;
}
if (EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) != 1) {
LOGE("EVP_PKEY_CTX_set_rsa_padding failed in VerifyPSSSignature");
goto err;
}
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, kPssSaltLength) != 1) {
LOGE("EVP_PKEY_CTX_set_rsa_pss_saltlen failed in VerifyPSSSignature");
goto err;
}
if (EVP_DigestVerifyUpdate(&ctx, message.data(), message.size()) != 1) {
LOGE("EVP_DigestVerifyUpdate failed in VerifyPSSSignature");
goto err;
}
if (EVP_DigestVerifyFinal(
&ctx, const_cast<uint8_t *>(
reinterpret_cast<const uint8_t *>(signature.data())),
signature.size()) != 1) {
LOGE(
"EVP_DigestVerifyFinal failed in VerifyPSSSignature. (Probably a bad "
"signature.)");
goto err;
}
EVP_MD_CTX_cleanup(&ctx);
return true;
err:
ERR_print_errors_cb(LogOpenSSLError, NULL);
EVP_MD_CTX_cleanup(&ctx);
return false;
}
bool RsaPublicKey::VerifySignature(const std::string& message,
const std::string& signature) {
if (serialized_key_.empty()) {
@@ -190,50 +248,25 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
LOGE("RsaPublicKey::VerifySignature: signed message is empty");
return false;
}
RSA* key = GetKey(serialized_key_);
if (key == NULL) {
RSA* rsa_key = GetKey(serialized_key_);
if (rsa_key == NULL) {
// Error already logged by GetKey.
return false;
}
int rsa_size = RSA_size(key);
if (static_cast<int>(signature.size()) != rsa_size) {
LOGE(
"RsaPublicKey::VerifySignature: message signature is of the wrong "
"size (expected %d, actual %d)",
rsa_size, signature.size());
FreeKey(key);
EVP_PKEY *pkey = EVP_PKEY_new();
if (pkey == NULL ||
EVP_PKEY_set1_RSA(pkey, rsa_key) != 1) {
FreeKey(rsa_key);
LOGE("RsaPublicKey::VerifySignature: failed to wrap key in an EVP_PKEY");
return false;
}
FreeKey(rsa_key);
// Decrypt the signature.
std::string padded_digest(signature.size(), 0);
if (RSA_public_decrypt(
signature.size(),
const_cast<unsigned char*>(
reinterpret_cast<const unsigned char*>(signature.data())),
reinterpret_cast<unsigned char*>(&padded_digest[0]), key,
RSA_NO_PADDING) != rsa_size) {
LOGE("RsaPublicKey::VerifySignature: RSA public decrypt failure: %s",
ERR_error_string(ERR_get_error(), NULL));
FreeKey(key);
return false;
}
const bool ok = VerifyPSSSignature(pkey, message, signature);
EVP_PKEY_free(pkey);
// Hash the message using SHA1.
std::string message_digest(SHA_DIGEST_LENGTH, 0);
SHA1(reinterpret_cast<const unsigned char*>(message.data()), message.size(),
reinterpret_cast<unsigned char*>(&message_digest[0]));
// Verify PSS padding.
if (RSA_verify_PKCS1_PSS(
key, reinterpret_cast<const unsigned char*>(message_digest.data()),
EVP_sha1(),
reinterpret_cast<const unsigned char*>(padded_digest.data()),
kPssSaltLength) == 0) {
LOGE("RsaPublicKey::VerifySignature: RSA verify failure: %s",
ERR_error_string(ERR_get_error(), NULL));
FreeKey(key);
if (!ok) {
LOGE("RsaPublicKey::VerifySignature: RSA verify failure");
return false;
}

View File

@@ -1,7 +1,7 @@
// Copyright 2013 Google Inc. All Rights Reserved.
#include "log.h"
#include "properties_configuration.h"
#include "properties.h"
#include "wv_cdm_constants.h"
namespace {
@@ -12,31 +12,18 @@ namespace wvcdm {
bool Properties::oem_crypto_use_secure_buffers_;
bool Properties::oem_crypto_use_fifo_;
bool Properties::oem_crypto_use_userspace_buffers_;
bool Properties::oem_crypto_require_usage_tables_;
bool Properties::use_certificates_as_identification_;
bool Properties::security_level_path_backward_compatibility_support_;
scoped_ptr<CdmClientPropertySetMap> Properties::session_property_set_;
void Properties::Init() {
oem_crypto_use_secure_buffers_ = kPropertyOemCryptoUseSecureBuffers;
oem_crypto_use_fifo_ = kPropertyOemCryptoUseFifo;
oem_crypto_use_userspace_buffers_ = kPropertyOemCryptoUseUserSpaceBuffers;
oem_crypto_require_usage_tables_ = kPropertyOemCryptoRequireUsageTable;
use_certificates_as_identification_ =
kPropertyUseCertificatesAsIdentification;
security_level_path_backward_compatibility_support_ =
kSecurityLevelPathBackwardCompatibilitySupport;
session_property_set_.reset(new CdmClientPropertySetMap());
}
bool Properties::AddSessionPropertySet(
const CdmSessionId& session_id, const CdmClientPropertySet* property_set) {
const CdmSessionId& session_id, CdmClientPropertySet* property_set) {
if (NULL == session_property_set_.get()) {
return false;
}
std::pair<CdmClientPropertySetMap::iterator, bool> result =
session_property_set_->insert(
std::pair<const CdmSessionId, const CdmClientPropertySet*>(
std::pair<const CdmSessionId, CdmClientPropertySet*>(
session_id, property_set));
return result.second;
}
@@ -48,10 +35,10 @@ bool Properties::RemoveSessionPropertySet(const CdmSessionId& session_id) {
return (1 == session_property_set_->erase(session_id));
}
const CdmClientPropertySet* Properties::GetCdmClientPropertySet(
CdmClientPropertySet* Properties::GetCdmClientPropertySet(
const CdmSessionId& session_id) {
if (NULL != session_property_set_.get()) {
CdmClientPropertySetMap::const_iterator it =
CdmClientPropertySetMap::iterator it =
session_property_set_->find(session_id);
if (it != session_property_set_->end()) {
return it->second;
@@ -60,14 +47,14 @@ const CdmClientPropertySet* Properties::GetCdmClientPropertySet(
return NULL;
}
bool Properties::GetSecurityLevel(const CdmSessionId& session_id,
std::string* security_level) {
bool Properties::GetApplicationId(const CdmSessionId& session_id,
std::string* app_id) {
const CdmClientPropertySet* property_set =
GetCdmClientPropertySet(session_id);
if (NULL == property_set) {
return false;
}
*security_level = property_set->security_level();
*app_id = property_set->app_id();
return true;
}
@@ -82,6 +69,17 @@ bool Properties::GetServiceCertificate(const CdmSessionId& session_id,
return true;
}
bool Properties::SetServiceCertificate(const CdmSessionId& session_id,
const std::string& service_certificate) {
CdmClientPropertySet* property_set =
GetCdmClientPropertySet(session_id);
if (NULL == property_set) {
return false;
}
property_set->set_service_certificate(service_certificate);
return true;
}
bool Properties::UsePrivacyMode(const CdmSessionId& session_id) {
const CdmClientPropertySet* property_set =
GetCdmClientPropertySet(session_id);

View File

@@ -4,14 +4,15 @@
#include <arpa/inet.h>
#include <ctype.h>
#include <iostream>
#include <stdio.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <modp_b64w.h>
#include "log.h"
#include "modp_b64w.h"
namespace wvcdm {
@@ -43,8 +44,8 @@ std::vector<uint8_t> a2b_hex(const std::string& byte) {
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);
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);
@@ -56,8 +57,9 @@ std::vector<uint8_t> a2b_hex(const std::string& byte) {
// dump the string with the label.
std::vector<uint8_t> a2b_hex(const std::string& label,
const std::string& byte) {
std::cout << std::endl << "[[DUMP: " << label << " ]= \"" << byte << "\"]"
<< std::endl << std::endl;
std::cout << std::endl
<< "[[DUMP: " << label << " ]= \"" << byte << "\"]" << std::endl
<< std::endl;
return a2b_hex(byte);
}
@@ -173,15 +175,15 @@ int64_t htonll64(int64_t x) { // Convert to big endian (network-byte-order)
int64_t number;
} mixed;
mixed.number = 1;
if (mixed.array[0] == 1) {
mixed.number = x; // Little Endian.
if (mixed.array[0] == 1) { // Little Endian.
mixed.number = x;
uint32_t temp = mixed.array[0];
mixed.array[0] = htonl(mixed.array[1]);
mixed.array[1] = htonl(temp);
return mixed.number;
} else {
return x; // Big Endian.
} else { // Big Endian.
return x;
}
}
}; // namespace wvcdm
} // namespace wvcdm

View File

@@ -2,10 +2,13 @@
#include <utility>
#include "gtest/gtest.h"
#include <gtest/gtest.h>
#include "log.h"
#include "string_conversions.h"
namespace wvcdm {
namespace {
// Test vectors as suggested by http://tools.ietf.org/html/rfc4648#section-10
@@ -50,9 +53,7 @@ const std::pair<const std::string*, const std::string*> kBase64TestVectors[] = {
make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data),
make_pair(&kTestData, &kB64TestData)};
} // unnamed namespace
namespace wvcdm {
} // namespace
class Base64EncodeDecodeTest
: public ::testing::TestWithParam<

View File

@@ -4,14 +4,12 @@
// This is because we need a valid RSA certificate, and will attempt to connect
// to the provisioning server to request one if we don't.
#include <errno.h>
#include <getopt.h>
#include <gtest/gtest.h>
#include <string>
#include "cdm_engine.h"
#include "config_test_env.h"
#include "gtest/gtest.h"
#include "initialization_data.h"
#include "license_request.h"
#include "log.h"
@@ -23,6 +21,8 @@
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
namespace wvcdm {
namespace {
// Http OK response code.
const int kHttpOk = 200;
@@ -30,29 +30,45 @@ 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_pssh;
wvcdm::KeyId g_key_id_unwrapped;
wvcdm::CdmKeySystem g_key_system;
KeyId g_key_id_pssh;
KeyId g_key_id_unwrapped;
CdmKeySystem g_key_system;
std::string g_license_server;
wvcdm::KeyId g_wrong_key_id;
KeyId g_wrong_key_id;
const std::string kCencMimeType = "video/mp4";
const std::string kWebmMimeType = "video/webm";
} // namespace
namespace wvcdm {
class WvCdmEngineTest : public testing::Test {
public:
static void SetUpTestCase() {
ConfigTestEnv config(kContentProtectionServer);
g_client_auth.assign(config.client_auth());
g_key_system.assign(config.key_system());
g_wrong_key_id.assign(config.wrong_key_id());
g_license_server.assign(config.license_server());
g_key_id_pssh.assign(a2bs_hex(config.key_id()));
// Extract the key ID from the PSSH box.
InitializationData extractor(CENC_INIT_DATA_FORMAT,
g_key_id_pssh);
g_key_id_unwrapped = extractor.data();
}
virtual void SetUp() {
CdmResponseType status = cdm_engine_.OpenSession(g_key_system, NULL, &session_id_);
CdmResponseType status =
cdm_engine_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL,
NULL /* forced_session_id */, &session_id_);
if (status == NEED_PROVISIONING) {
Provision();
status = cdm_engine_.OpenSession(g_key_system, NULL, &session_id_);
status = cdm_engine_.OpenSession(g_key_system, NULL, EMPTY_ORIGIN, NULL,
NULL /* forced_session_id */,
&session_id_);
}
ASSERT_EQ(NO_ERROR, status);
ASSERT_NE("", session_id_) << "Could not open CDM session.";
ASSERT_TRUE(cdm_engine_.IsOpenSession(session_id_));
}
virtual void TearDown() { cdm_engine_.CloseSession(session_id_); }
@@ -64,19 +80,17 @@ class WvCdmEngineTest : public testing::Test {
CdmCertificateType cert_type = kCertificateWidevine;
std::string cert_authority;
std::string cert, wrapped_key;
ASSERT_EQ(NO_ERROR,
cdm_engine_.GetProvisioningRequest(cert_type,
cert_authority,
&prov_request,
&provisioning_server_url));
ASSERT_EQ(NO_ERROR, cdm_engine_.GetProvisioningRequest(
cert_type, cert_authority, EMPTY_ORIGIN,
&prov_request, &provisioning_server_url));
UrlRequest url_request(provisioning_server_url);
url_request.PostCertRequestInQueryString(prov_request);
std::string message;
bool ok = url_request.GetResponse(&message);
EXPECT_TRUE(ok);
ASSERT_EQ(NO_ERROR,
cdm_engine_.HandleProvisioningResponse(message,
&cert, &wrapped_key));
ASSERT_EQ(NO_ERROR, cdm_engine_.HandleProvisioningResponse(EMPTY_ORIGIN,
message, &cert,
&wrapped_key));
}
void GenerateKeyRequest(const std::string& key_id,
@@ -87,10 +101,12 @@ class WvCdmEngineTest : public testing::Test {
InitializationData init_data(init_data_type_string, key_id);
EXPECT_EQ(KEY_MESSAGE,
cdm_engine_.GenerateKeyRequest(
session_id_, key_set_id, init_data, kLicenseTypeStreaming,
app_parameters, &key_msg_, &server_url, NULL));
CdmKeyRequestType key_request_type;
EXPECT_EQ(KEY_MESSAGE, cdm_engine_.GenerateKeyRequest(
session_id_, key_set_id, init_data,
kLicenseTypeStreaming, app_parameters, &key_msg_,
&key_request_type, &server_url, NULL));
EXPECT_EQ(kKeyRequestTypeInitial, key_request_type);
}
void GenerateRenewalRequest() {
@@ -144,14 +160,14 @@ class WvCdmEngineTest : public testing::Test {
const std::string& client_auth) {
std::string resp = GetKeyRequestResponse(server_url, client_auth);
CdmKeySetId key_set_id;
EXPECT_EQ(wvcdm::KEY_ADDED,
EXPECT_EQ(KEY_ADDED,
cdm_engine_.AddKey(session_id_, resp, &key_set_id));
}
void VerifyRenewalKeyResponse(const std::string& server_url,
const std::string& client_auth) {
std::string resp = GetKeyRequestResponse(server_url, client_auth);
EXPECT_EQ(wvcdm::KEY_ADDED, cdm_engine_.RenewKey(session_id_, resp));
EXPECT_EQ(KEY_ADDED, cdm_engine_.RenewKey(session_id_, resp));
}
CdmEngine cdm_engine_;
@@ -206,94 +222,3 @@ TEST_F(WvCdmEngineTest, LicenseRenewal) {
}
} // namespace wvcdm
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
wvcdm::InitLogging(argc, argv);
wvcdm::ConfigTestEnv config(wvcdm::kContentProtectionServer);
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_pssh.assign(config.key_id());
std::string license_server(g_license_server);
int show_usage = 0;
static const struct option long_options[] = {
{"keyid", required_argument, NULL, 'k'},
{"server", required_argument, NULL, 's'},
{NULL, 0, NULL, '\0'}};
int option_index = 0;
int opt = 0;
while ((opt = getopt_long(argc, argv, "k:s:v", long_options,
&option_index)) != -1) {
switch (opt) {
case 'k': {
g_key_id_pssh.clear();
g_key_id_pssh.assign(optarg);
break;
}
case 's': {
g_license_server.clear();
g_license_server.assign(optarg);
break;
}
case 'v': {
// This option _may_ have already been consumed by wvcdm::InitLogging()
// above, depending on the platform-specific logging implementation.
// We only tell getopt about it so that it is not an error. We ignore
// the option here when seen.
// TODO: Stop passing argv to InitLogging, and instead set the log
// level here through the logging API. We should keep all command-line
// parsing at the application level, rather than split between various
// apps and various platform-specific logging implementations.
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 << " --server=<server_url>";
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=<key_id>";
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_pssh << std::endl;
return 0;
}
std::cout << std::endl;
std::cout << "Server: " << g_license_server << std::endl;
std::cout << "KeyID: " << g_key_id_pssh << std::endl << std::endl;
g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh);
config.set_license_server(g_license_server);
config.set_key_id(g_key_id_pssh);
// Extract the key ID from the PSSH box.
wvcdm::InitializationData extractor(wvcdm::CENC_INIT_DATA_FORMAT,
g_key_id_pssh);
g_key_id_unwrapped = extractor.data();
return RUN_ALL_TESTS();
}

View File

@@ -1,15 +1,20 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "cdm_session.h"
#include "crypto_key.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "properties.h"
#include "scoped_ptr.h"
#include "string_conversions.h"
#include "test_printers.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
namespace {
const std::string kToken = wvcdm::a2bs_hex(
const std::string kToken = a2bs_hex(
"0AAE02080212107E0A892DEEB021E7AF696B938BB1D5B1188B85AD9D05228E023082010A02"
"82010100DBEDF2BFB0EC98213766E65049B9AB176FA4B1FBFBB2A0C96C87D9F2B895E0ED77"
"93BDA057E6BC3E0CA2348BC6831E03609445CA4D418CB98EAC98FFC87AB2364CE76BA26BEE"
@@ -45,7 +50,7 @@ const std::string kToken = wvcdm::a2bs_hex(
"8CD5A9DF6E3D3A99B806F6D60991358C5BE77117D4F3168F3348E9A048539F892F4D783152"
"C7A8095224AA56B78C5CF7BD1AB1B179C0C0D11E3C3BAC84C141A00191321E3ACC17242E68"
"3C");
const std::string kWrappedKey = wvcdm::a2bs_hex(
const std::string kWrappedKey = a2bs_hex(
"3B84252DD84F1A710365014A114507FFFA3DD404625D61D1EEC7C3A39D72CB8D9318ADE9DA"
"05D69F9776DAFDA49A97BC30E84CA275925DFD98CA04F7DB23465103A224852192DE232902"
"99FF82024F5CCA7716ACA9BE0B56348BA16B9E3136D73789C842CB2ECA4820DDAAF59CCB9B"
@@ -82,9 +87,42 @@ const std::string kWrappedKey = wvcdm::a2bs_hex(
"33EF70621A98184DDAB5E14BC971CF98CF6C91A37FFA83B00AD3BCABBAAB2DEF1C52F43003"
"E74C92B44F9205D22262FB47948654229DE1920F8EDF96A19A88A1CA1552F8856FB4CBF83B"
"AA3348419159D207F65FCE9C1A500C6818");
} // namespace
namespace wvcdm {
const std::string kTestOrigin = "com.google";
class MockDeviceFiles : public DeviceFiles {
public:
MOCK_METHOD1(Init, bool(CdmSecurityLevel));
MOCK_METHOD3(RetrieveCertificate, bool(const std::string&, std::string*,
std::string*));
};
class MockCryptoSession : public CryptoSession {
public:
MOCK_METHOD1(GetToken, bool(std::string*));
MOCK_METHOD0(GetSecurityLevel, CdmSecurityLevel());
MOCK_METHOD0(Open, CdmResponseType());
MOCK_METHOD1(Open, CdmResponseType(SecurityLevel));
MOCK_METHOD1(LoadCertificatePrivateKey, bool(std::string&));
MOCK_METHOD0(DeleteAllUsageReports, CdmResponseType());
};
class MockPolicyEngine : public PolicyEngine {
public:
MockPolicyEngine() : PolicyEngine("mock_session_id", NULL, NULL) {}
// Leaving a place-holder for when PolicyEngine methods need to be mocked
};
class MockCdmLicense : public CdmLicense {
public:
MockCdmLicense(const CdmSessionId& session_id)
: CdmLicense(session_id) {}
MOCK_METHOD3(Init, bool(const std::string&, CryptoSession*, PolicyEngine*));
};
} // namespace
// gmock methods
using ::testing::_;
@@ -95,53 +133,22 @@ using ::testing::SetArgPointee;
using ::testing::Sequence;
using ::testing::StrEq;
class MockDeviceFiles : public DeviceFiles {
public:
MOCK_METHOD1(Init, bool(CdmSecurityLevel));
MOCK_METHOD2(RetrieveCertificate, bool(std::string*, std::string*));
};
class MockCryptoSession : public CryptoSession {
public:
MOCK_METHOD1(GetToken, bool(std::string*));
MOCK_METHOD0(GetSecurityLevel, CdmSecurityLevel());
MOCK_METHOD0(Open, CdmResponseType());
MOCK_METHOD1(Open, CdmResponseType(SecurityLevel));
MOCK_METHOD1(LoadCertificatePrivateKey, bool(std::string&));
};
class MockPolicyEngine : public PolicyEngine {
public:
// Leaving a place holder for when PolicyEngine methods need to be mocked
};
class MockCdmLicense : public CdmLicense {
public:
MOCK_METHOD3(Init, bool(const std::string&, CryptoSession*, PolicyEngine*));
};
class CdmSessionTest : public ::testing::Test {
protected:
virtual void SetUp() {
license_parser_ = new MockCdmLicense();
cdm_session_.reset(new CdmSession(NULL, kTestOrigin, NULL, NULL));
// Inject testing mocks.
license_parser_ = new MockCdmLicense(cdm_session_->session_id());
cdm_session_->set_license_parser(license_parser_);
crypto_session_ = new MockCryptoSession();
cdm_session_->set_crypto_session(crypto_session_);
policy_engine_ = new MockPolicyEngine();
cdm_session_->set_policy_engine(policy_engine_);
file_handle_ = new MockDeviceFiles();
cdm_session_->set_file_handle(file_handle_);
}
virtual void TearDown() {
if (cdm_session_) delete cdm_session_;
}
void CreateSession() { CreateSession(NULL); }
void CreateSession(const CdmClientPropertySet* cdm_client_property_set) {
cdm_session_ =
new CdmSession(license_parser_, crypto_session_, policy_engine_,
file_handle_, cdm_client_property_set);
}
CdmSession* cdm_session_;
scoped_ptr<CdmSession> cdm_session_;
MockCdmLicense* license_parser_;
MockCryptoSession* crypto_session_;
MockPolicyEngine* policy_engine_;
@@ -158,8 +165,9 @@ TEST_F(CdmSessionTest, InitWithCertificate) {
.InSequence(crypto_session_seq)
.WillOnce(Return(level));
EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true));
EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey),
EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(),
NotNull()))
.WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey),
Return(true)));
EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey)))
.InSequence(crypto_session_seq)
@@ -170,7 +178,6 @@ TEST_F(CdmSessionTest, InitWithCertificate) {
Properties::set_use_certificates_as_identification(true);
CreateSession();
ASSERT_EQ(NO_ERROR, cdm_session_->Init());
}
@@ -192,7 +199,6 @@ TEST_F(CdmSessionTest, InitWithKeybox) {
Properties::set_use_certificates_as_identification(false);
CreateSession();
ASSERT_EQ(NO_ERROR, cdm_session_->Init());
}
@@ -206,8 +212,9 @@ TEST_F(CdmSessionTest, ReInitFail) {
.InSequence(crypto_session_seq)
.WillOnce(Return(level));
EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true));
EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kToken), SetArgPointee<1>(kWrappedKey),
EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(),
NotNull()))
.WillOnce(DoAll(SetArgPointee<1>(kToken), SetArgPointee<2>(kWrappedKey),
Return(true)));
EXPECT_CALL(*crypto_session_, LoadCertificatePrivateKey(StrEq(kWrappedKey)))
.InSequence(crypto_session_seq)
@@ -218,9 +225,8 @@ TEST_F(CdmSessionTest, ReInitFail) {
Properties::set_use_certificates_as_identification(true);
CreateSession();
ASSERT_EQ(NO_ERROR, cdm_session_->Init());
ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init());
ASSERT_NE(NO_ERROR, cdm_session_->Init());
}
TEST_F(CdmSessionTest, InitFailCryptoError) {
@@ -230,7 +236,6 @@ TEST_F(CdmSessionTest, InitFailCryptoError) {
Properties::set_use_certificates_as_identification(true);
CreateSession();
ASSERT_EQ(UNKNOWN_ERROR, cdm_session_->Init());
}
@@ -244,13 +249,13 @@ TEST_F(CdmSessionTest, InitNeedsProvisioning) {
.InSequence(crypto_session_seq)
.WillOnce(Return(level));
EXPECT_CALL(*file_handle_, Init(Eq(level))).WillOnce(Return(true));
EXPECT_CALL(*file_handle_, RetrieveCertificate(NotNull(), NotNull()))
EXPECT_CALL(*file_handle_, RetrieveCertificate(StrEq(kTestOrigin), NotNull(),
NotNull()))
.WillOnce(Return(false));
Properties::set_use_certificates_as_identification(true);
CreateSession();
ASSERT_EQ(NEED_PROVISIONING, cdm_session_->Init());
}
} // wvcdm
} // namespace wvcdm

View File

@@ -2,10 +2,14 @@
#include "config_test_env.h"
namespace wvcdm {
namespace {
const std::string kWidevineKeySystem = "com.widevine.alpha";
// Content Protection license server data
// Content Protection license server (UAT) data
// For staging server replace url with http://wv-staging-proxy.appspot.com/proxy
const std::string kCpLicenseServer = "http://widevine-proxy.appspot.com/proxy";
const std::string kCpClientAuth = "";
const std::string kCpKeyId =
@@ -26,7 +30,7 @@ const std::string kCpOfflineKeyId =
"00000020" // pssh data size
// pssh data:
"08011a0d7769646576696e655f746573"
"74220d6f66666c696e655f636c697031";
"74220d6f66666c696e655f636c697032";
// Google Play license server data
const std::string kGpLicenseServer =
@@ -72,17 +76,15 @@ const std::string kProductionProvisioningServerUrl =
"certificateprovisioning/v1/devicecertificates/create"
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = {
{wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId,
const ConfigTestEnv::LicenseServerConfiguration license_servers[] = {
{kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId,
kGpOfflineKeyId},
{wvcdm::kContentProtectionServer, kCpLicenseServer, kCpClientAuth, kCpKeyId,
{kContentProtectionServer, kCpLicenseServer, kCpClientAuth, kCpKeyId,
kCpOfflineKeyId},
};
} // namespace
namespace wvcdm {
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) { Init(server_id); }
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming) {

Some files were not shown because too many files have changed in this diff Show More