Initial source release: v2.0.8-0-679
Change-Id: Idf6316a8faf4b4fdc54265aad12084e5aa60707a
This commit is contained in:
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
# GYP generated makefiles
|
||||
Makefile
|
||||
*.Makefile
|
||||
*.mk
|
||||
|
||||
# Output directory
|
||||
out/
|
||||
|
||||
# Certificate created by unit tests
|
||||
cert.bin
|
||||
|
||||
# Python object files
|
||||
*.pyc
|
||||
255
README
Normal file
255
README
Normal file
@@ -0,0 +1,255 @@
|
||||
README for Widevine CDM Partner Kit v2.0
|
||||
Date: 3/28/2014
|
||||
|
||||
This document provides additional details on installation, system
|
||||
setup, building, and testing components of the Widevine Content
|
||||
Decryption Module supplied in the Widevine CDM Partner Kit.
|
||||
This document supplements the information found in the "WV Modular DRM
|
||||
Security Integration Guide for Common Encryption (CENC): EME/Embedded
|
||||
Device Supplement v2.1", which is the EME-specific portion of the
|
||||
dcoeument "WV Modular DRM Security Integration Guide for Common
|
||||
Encryption (CENC)".
|
||||
|
||||
Kit Contents
|
||||
|
||||
The Widevine CDM Partner Kit is distributed as an XZipped tar file
|
||||
(.tar.xz). To unpack the kit into your current working directory:
|
||||
|
||||
tar xf widevine-cdm_<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:
|
||||
build - build scripts
|
||||
cdm - the CDM interface declarations and definitions, and tests
|
||||
core - the CDM implementation, and tests
|
||||
linux - platform-specific files for the CDM implementation
|
||||
oemcrypto - OEMCrypto declarations, a mock implementation, and tests
|
||||
README - this file
|
||||
run_test.sh - builds and tests the CDM with mock OEMCrypto
|
||||
|
||||
System Setup
|
||||
|
||||
Some third-party software packages are required to build the CDM, OEMCrypto,
|
||||
and various test modules. The actual versions that have been used and
|
||||
verified are listed here, but earlier or later versions may also work. If
|
||||
you already have a different version of a particular package installed
|
||||
on your system, then you should try the build and unit tests to see if
|
||||
there are any issues.
|
||||
|
||||
The current set of third-party packages includes:
|
||||
|
||||
- gTest and gMock (version 1.6.0)
|
||||
- protobufs (Google Protocol Buffers) (version 2.5)
|
||||
- OpenSSL (libssl and libcrypto) (version 1.0.1e)
|
||||
- stringencoders (https://code.google.com/p/stringencoders)
|
||||
- python (version 2.7 or better)
|
||||
- GYP (python-based build tool) (https://code.google.com/p/gyp,
|
||||
svn revision 1846)
|
||||
|
||||
The following sections provide information about acquiring
|
||||
and installing some of these packages. This is not a complete list, but
|
||||
it tries to cover the items that do not follow the typical methodologies.
|
||||
Also, the instructions apply to and were tested on an up-to-date Ubuntu
|
||||
system. They may not work on your system exactly as shown.
|
||||
|
||||
GYP
|
||||
|
||||
The kit uses GYP (Generate Your Projects) to create makefiles.
|
||||
|
||||
The standard GYP source release is through a subversion (svn) checkout
|
||||
of trunk. Most of this kit's development and testing was performed with
|
||||
GYP revision 1846. The newest revision at the time of this document
|
||||
is 1884. You may already have GYP installed on your system, or you may
|
||||
try "apt-get install gyp" to install the version packaged for your
|
||||
system. If you choose to install the latest revision (option 1) or install
|
||||
revision 1846 (option 2), follow the instructions below.
|
||||
|
||||
GYP requires Python. You should already have python 2.7 or better on your
|
||||
system. There is also a python package called setuptools that GYP requires.
|
||||
If the GYP build fails, see the section below to install setuptools.
|
||||
|
||||
python --version # will return "Python 2.7.3" or something similar
|
||||
|
||||
mkdir <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/build.py. It configures the build
|
||||
environment, runs GYP to generate a series of make files. and starts the
|
||||
top level make. The build scripts are parameterized to build for multiple
|
||||
targets. All the pieces are provided to build for x86-64 (linux). You
|
||||
can use the x86-64 build as a template for building for your embedded targets.
|
||||
The "CDM Porting Guide" supplied with this kit discusses the steps involved.
|
||||
|
||||
Configuring for a new target device
|
||||
|
||||
The kit "out of the box" builds a CDM module and links it into a unit
|
||||
test executable image that can be run on your linux system. To build
|
||||
the CDM for a different target device, you will have to install an appropriate
|
||||
toolchain and SDK for your targets. Then you will have to extend
|
||||
the build scripts to utilize these resources. You will also have to modify
|
||||
or add certain source modules to integrate the CDM into your target's
|
||||
media and/or browser environment. This process is documented in the
|
||||
"CDM Porting Guide".
|
||||
|
||||
Tests
|
||||
|
||||
Read the sections related to testing in the "WV Modular DRM Security
|
||||
Integration Guide for Common Encryption (CENC): EME/Embedded Device
|
||||
Supplement v2.1"
|
||||
|
||||
Test directories can be found in many of the kit's directories. The GYP files
|
||||
for building the test images are found in the test directory or one level
|
||||
above the test directory. The test-releated GYP targets will have "test" or
|
||||
"unittest" in the name.
|
||||
|
||||
Here's a current list of GYP files and test-related targets:
|
||||
|
||||
File - Targets Purpose
|
||||
cdm/cdm_api_internal.gyp
|
||||
license_protocol Create protobuf sources for license protocol
|
||||
device_files Create protobuf sources for license storage
|
||||
wvcdm_sysdep Build system-dependent layer of CDM
|
||||
wvcdm_shared Build CDM shared library
|
||||
wvcdm_shared_api_unittest Build CDM unit tests
|
||||
cdm/test/gtest.gyp
|
||||
gtest Provides gtest modules for unit tests
|
||||
cdm/test/gmock.gyp
|
||||
gmock Provides gmock modules for unit tests
|
||||
gmock_main Provides main routine for unit tests
|
||||
build/platforms/x86-64/oemcrypto/oec_build.gyp
|
||||
oemcrypto Top level target to build/link OEMCrypto
|
||||
oemcrypto/oemcrypto.gyp
|
||||
oec_lib Build OEMCrypto library
|
||||
oec_mock Build mock OEMCrypto library
|
||||
oec_unittest Build OEMCrypto unit tests
|
||||
oemcrypto/mock/oec_mock.gyp
|
||||
oec_mock Build mock OEMCrypto sources
|
||||
oemcrypto/prebuilt/oec_prebuilt.gyp
|
||||
oec_prebuilt Integrate prebuilt OEMCrypto library
|
||||
|
||||
The "CDM Porting Guide" discusses how these targets are used and how you
|
||||
will modify them to build libraries and test binaries for your target devices.
|
||||
|
||||
The "CDM Porting Guide", the "WV Modular DRM Security Integration Guide for
|
||||
Common Encryption (CENC): EME/Embedded Device Supplement v2.1", and the
|
||||
"WV Modular DRM Security Integration Guide for Common Encryption (CENC)"
|
||||
should provide the information you need to create your OEMCrypto and CDM
|
||||
implementations for your target devices.
|
||||
117
build/build.py
Executable file
117
build/build.py
Executable file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/python2
|
||||
# Copyright 2013 Google Inc. All rights reserved.
|
||||
|
||||
import gyp
|
||||
import os
|
||||
from os import path
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
toolchain = {}
|
||||
starting_dir = os.getcwd()
|
||||
|
||||
toolset_variables = ['CC', 'CXX', 'AR']
|
||||
env_variables = ['CFLAGS', 'CXXFLAGS', 'LDFLAGS']
|
||||
|
||||
gyp_args = string.split('--format=make --depth=.')
|
||||
|
||||
parser = argparse.ArgumentParser(description='Process build options.')
|
||||
parser_arguments = []
|
||||
build_config = '--build=Debug'
|
||||
global_include_file = '--include=build/global_config.gypi'
|
||||
|
||||
print ' Gyp Args: %s' % (gyp_args)
|
||||
|
||||
def ImportPlatform(name):
|
||||
os.chdir('build/platforms')
|
||||
sys.path.insert(0, os.getcwd())
|
||||
platforms_list = __import__('cdm_platforms')
|
||||
platforms = platforms_list.platforms
|
||||
target_platform = platforms[name]
|
||||
print ' Target Platform: %s' % (target_platform)
|
||||
target_folder = target_platform['folder']
|
||||
print ' Target Folder: %s' % (target_folder)
|
||||
target_file = target_platform['file']
|
||||
print ' Target File: %s' % (target_file)
|
||||
global target_env
|
||||
target_env = target_file
|
||||
|
||||
os.chdir(target_folder)
|
||||
sys.path.insert(0, os.getcwd())
|
||||
target = __import__(target_file)
|
||||
tooldir = ''
|
||||
if 'tooldir' in target.toolchain:
|
||||
tooldir = target.toolchain['tooldir']
|
||||
print ' tooldir: %s' % (tooldir)
|
||||
for i, v in enumerate(toolset_variables):
|
||||
if v in target.toolchain:
|
||||
os.environ[v] = path.join(tooldir, target.toolchain[v])
|
||||
print ' tool: %s' % (os.environ[v])
|
||||
if 'tooldir' in target.toolchain:
|
||||
tooldir = target.toolchain['tooldir']
|
||||
|
||||
for i, v in enumerate(env_variables):
|
||||
if v in target.toolchain:
|
||||
os.environ[v] = target.toolchain[v]
|
||||
if hasattr(target, 'gyp_variables'):
|
||||
global gyp_args
|
||||
gyp_args += target.gyp_variables
|
||||
|
||||
if hasattr(target, 'export_variables'):
|
||||
print '---------------has export variables'
|
||||
|
||||
for v in target.export_variables:
|
||||
first = v.split(':')
|
||||
x = first[0]
|
||||
y = target.export_variables[x]
|
||||
if x=="ADD_PATH":
|
||||
os.environ["PATH"] = os.environ["PATH"] + y
|
||||
print ' just updated to PATH'
|
||||
print os.environ["PATH"]
|
||||
else:
|
||||
os.environ[x] = y
|
||||
print ' just set Env variable %s to %s' % (x, y)
|
||||
else:
|
||||
print '-------has NO export variables'
|
||||
|
||||
os.chdir(starting_dir)
|
||||
os.environ['CDM_TOP'] = starting_dir
|
||||
|
||||
#print ' Actual: %s' % (starting_dir)
|
||||
|
||||
def RunGyp(args):
|
||||
print ' Args: %s' % (args)
|
||||
global gyp_args
|
||||
global build_config
|
||||
if parser_arguments.release:
|
||||
build_config = '--build=Release'
|
||||
gyp_args.append(build_config)
|
||||
# Append the global include file last.
|
||||
# This allows a platform definition to exist
|
||||
# in a gypi file adding the ability for the
|
||||
# platform definition to use the features of gyp.
|
||||
gyp_args.append(global_include_file)
|
||||
gyp.main(gyp_args)
|
||||
|
||||
def ParseOptions(args):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("platform", help="The platform configuration you want to build")
|
||||
parser.add_argument("-r", "--release", help='build release build', action='store_true')
|
||||
global parser_arguments
|
||||
parser_arguments = parser.parse_args()
|
||||
|
||||
def main(args):
|
||||
def exitOnFailure(result):
|
||||
if not result == 0:
|
||||
sys.exit(result)
|
||||
|
||||
ParseOptions(args)
|
||||
ImportPlatform(parser_arguments.platform)
|
||||
print ' Running Gyp with: %s' % args
|
||||
|
||||
RunGyp(args)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1:])
|
||||
43
build/global_config.gypi
Normal file
43
build/global_config.gypi
Normal file
@@ -0,0 +1,43 @@
|
||||
# Copyright 2013 Google Inc. All rights reserved.
|
||||
{
|
||||
# Global place to define variables and defines.
|
||||
# This file is passed to gyp and the contents included in
|
||||
# every gyp referenced.
|
||||
|
||||
'variables': {
|
||||
'target_build%':"x86-64",
|
||||
'company_name%':'"UndefinedCompanyName"',
|
||||
'model_name%':'"UndefinedModelName"',
|
||||
'architecture_name%':'"UndefinedArchitectureName"',
|
||||
'device_name%':'"UndefinedDeviceName"',
|
||||
'product_name%':'"UndefinedProductName"',
|
||||
'buildinfo_data%':'"UndefinedBuildInfo"',
|
||||
'target_oemcrypto%': 'oec_mock',
|
||||
'oemcrypto_target%':'../oemcrypto/mock/oec_mock.gyp:oec_mock',
|
||||
'protoc_dir%':'/usr/bin',
|
||||
'certificate_provision%':'false',
|
||||
},
|
||||
'target_defaults': {
|
||||
'default_configuration': 'Release',
|
||||
'configurations': {
|
||||
'Debug': {
|
||||
'cflags': ['-g'],
|
||||
},
|
||||
'Release': {},
|
||||
}, # end configurations
|
||||
'target_conditions': [
|
||||
['_type=="static_library"', {
|
||||
'standalone_static_library': 1,
|
||||
}]
|
||||
],
|
||||
'defines': [
|
||||
'PLATFORM_COMPANY_NAME_WV=<(company_name)',
|
||||
'PLATFORM_MODEL_NAME_WV=<(model_name)',
|
||||
'PLATFORM_ARCHITECTURE_NAME_WV=<(architecture_name)',
|
||||
'PLATFORM_DEVICE_NAME_WV=<(device_name)',
|
||||
'PLATFORM_PRODUCT_NAME_WV=<(product_name)',
|
||||
'PLATFORM_BUILDINFO_WV=<(buildinfo_data)',
|
||||
'PLATFORM_CERTIFICATE_PROV=<(certificate_provision)',
|
||||
],
|
||||
} # end target_defaults
|
||||
}
|
||||
12
build/platforms/cdm_platforms.py
Normal file
12
build/platforms/cdm_platforms.py
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/python2
|
||||
# Copyright 2013 Google Inc. All rights reserved.
|
||||
|
||||
# cdm_platforms.py --- Supported platforms
|
||||
# Modify this file to extend the platforms that can be built by build.py
|
||||
|
||||
platforms = {
|
||||
'x86-64': {
|
||||
'folder': 'x86-64',
|
||||
'file': 'x86-64',
|
||||
},
|
||||
}
|
||||
24
build/platforms/x86-64/oemcrypto/oec_build.gyp
Normal file
24
build/platforms/x86-64/oemcrypto/oec_build.gyp
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright 2013 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Build the vendor's oemcrypto (simulated with mock for x86-64)
|
||||
#
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'oemcrypto',
|
||||
'type': 'static_library',
|
||||
'include_dirs': [
|
||||
'../../../../core/include',
|
||||
'../../../../oemcrypto/include',
|
||||
'../../../../',
|
||||
],
|
||||
'sources': [
|
||||
'../../../../oemcrypto/mock/src/oemcrypto_engine_mock.cpp',
|
||||
'../../../../oemcrypto/mock/src/oemcrypto_key_mock.cpp',
|
||||
'../../../../oemcrypto/mock/src/oemcrypto_keybox_mock.cpp',
|
||||
'../../../../oemcrypto/mock/src/oemcrypto_mock.cpp',
|
||||
'../../../../oemcrypto/mock/src/wvcrc.cpp',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
6
build/platforms/x86-64/x86-64.gypi
Normal file
6
build/platforms/x86-64/x86-64.gypi
Normal file
@@ -0,0 +1,6 @@
|
||||
# Copyright 2013 Google Inc. All rights reserved.
|
||||
{
|
||||
'variables': {
|
||||
'certificate_provision':'true',
|
||||
},
|
||||
}
|
||||
50
build/platforms/x86-64/x86-64.py
Normal file
50
build/platforms/x86-64/x86-64.py
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/python2
|
||||
# Copyright 2013 Google Inc. All rights reserved.
|
||||
#
|
||||
# x86-64 baseline
|
||||
# Definitions:
|
||||
# external_build - boolean if 0, we will build libwvcdm_shared from source.
|
||||
# Comment out "V:1" for non-verbose make, it's not based on bool value.
|
||||
#
|
||||
toolchain = {
|
||||
'tooldir': '/usr/bin/',
|
||||
'CC' : 'gcc',
|
||||
'CXX' : 'g++',
|
||||
'AR' : 'ar',
|
||||
'LD' : 'ld',
|
||||
'CFLAGS': '-fno-exceptions -fPIC \
|
||||
-I/usr/local/include \
|
||||
-I./build/platforms/x86-64',
|
||||
'CXXFLAGS': '-fno-exceptions -fPIC \
|
||||
-I/usr/local/include \
|
||||
-I$(CDM_TOP)/build/platforms/x86-64',
|
||||
'LDFLAGS': '-L$(CDM_TOP)/prebuilt/gtest/out_x86_linux/Release/lib',
|
||||
}
|
||||
|
||||
# gyp_variables
|
||||
# Definitions:
|
||||
# external_build = vendor can only get binary libwvcdm, no source.
|
||||
# oemcrypto_lib = where the vendor's oemcrypto lib is if not a mock.
|
||||
# target_oemcrypto = oemcrypto_lib or comment out for default of oec_mock.
|
||||
gyp_variables = [
|
||||
'-Duse_system_protobuf=1',
|
||||
'-Dexternal_build=0',
|
||||
'-Dtarget_build=x86-64',
|
||||
'-Doemcrypto_target=../build/platforms/x86-64/oemcrypto/oec_build.gyp:oemcrypto',
|
||||
'-Doemcrypto_lib=',
|
||||
'cdm/cdm_api_external.gyp',
|
||||
'-Dcompany_name="www"',
|
||||
'-Dmodel_name="www"',
|
||||
'-Darchitecture_name="x86-64"',
|
||||
'-Ddevice_name="x86-64 Linux"',
|
||||
'-Dproduct_name="x86-64 cdm"',
|
||||
'-Dbuildinfo_data="cdm_partner_2.0"',
|
||||
'--include=build/platforms/x86-64/x86-64.gypi',
|
||||
]
|
||||
|
||||
# Comment out "V:1" if you want non-verbose make, it's not based on bool value.
|
||||
|
||||
export_variables = {
|
||||
'V':'1', \
|
||||
'builddir_name':'$(CDM_TOP)/out/x86-64', \
|
||||
}
|
||||
40
build/protoc.gypi
Normal file
40
build/protoc.gypi
Normal file
@@ -0,0 +1,40 @@
|
||||
# Copyright 2014 Google Inc. All rights reserved.
|
||||
{
|
||||
'variables': {
|
||||
'cc_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out/<(proto_out_dir)',
|
||||
'proto_in_dir%': '.',
|
||||
},
|
||||
'rules': [
|
||||
{
|
||||
'rule_name': 'genproto',
|
||||
'extension': 'proto',
|
||||
'outputs': [
|
||||
'<(cc_dir)/<(RULE_INPUT_ROOT).pb.cc',
|
||||
'<(cc_dir)/<(RULE_INPUT_ROOT).pb.h',
|
||||
],
|
||||
'action': [
|
||||
'<(protoc_dir)/protoc',
|
||||
'--proto_path=<(proto_in_dir)',
|
||||
# Naively you'd use <(RULE_INPUT_PATH) here, but protoc requires
|
||||
# --proto_path is a strict prefix of the path given as an argument.
|
||||
'<(proto_in_dir)/<(RULE_INPUT_ROOT)<(RULE_INPUT_EXT)',
|
||||
'--cpp_out=<(cc_dir)',
|
||||
],
|
||||
'message': 'Generating C++ code from <(RULE_INPUT_PATH) ccdir=<(cc_dir)',
|
||||
'process_outputs_as_sources': 1,
|
||||
},
|
||||
],
|
||||
'include_dirs': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)/protoc_out',
|
||||
'<(proto_out_dir)',
|
||||
],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [
|
||||
'<(SHARED_INTERMEDIATE_DIR)/protoc_out',
|
||||
'<(cc_dir)',
|
||||
]
|
||||
},
|
||||
# This target exports a hard dependency because it generates header
|
||||
# files.
|
||||
'hard_dependency': 1,
|
||||
}
|
||||
54
cdm/cdm_api_external.gyp
Normal file
54
cdm/cdm_api_external.gyp
Normal file
@@ -0,0 +1,54 @@
|
||||
# Copyright 2013 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Builds under the CDM ./build/build.py (target platform) build system
|
||||
# Refer to the distribution package's README for details.
|
||||
#
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'wvcdm_shared_api_unittest',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'../cdm/test/cdm_api_test.cpp',
|
||||
'../core/test/config_test_env.cpp',
|
||||
'../core/test/license_request.cpp',
|
||||
'../core/test/http_socket.cpp',
|
||||
'../core/test/url_request.cpp',
|
||||
],
|
||||
'include_dirs': [
|
||||
'include',
|
||||
'../cdm/include',
|
||||
'../core/include',
|
||||
'../core/test',
|
||||
'../oemcrypto/include',
|
||||
],
|
||||
'ldflags': [
|
||||
'-L$(builddir_name)/$(BUILDTYPE)/lib.target',
|
||||
'-L$(builddir_name)/$(BUILDTYPE)',
|
||||
'-Wl,-rpath=$(builddir_name)/$(BUILDTYPE)/lib.target/',
|
||||
],
|
||||
'libraries': [
|
||||
'$(builddir_name)/$(BUILDTYPE)/lib.target/libwvcdm_shared.so',
|
||||
'-lssl',
|
||||
'-lcrypto',
|
||||
'-lpthread',
|
||||
'-lprotobuf',
|
||||
],
|
||||
'dependencies': [
|
||||
'test/gmock.gyp:gmock',
|
||||
'test/gmock.gyp:gmock_main',
|
||||
'test/gtest.gyp:gtest',
|
||||
],
|
||||
'conditions': [
|
||||
[ 'external_build==0',
|
||||
{
|
||||
'dependencies': [
|
||||
'../cdm/cdm_api_internal.gyp:wvcdm_shared',
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
89
cdm/cdm_api_internal.gyp
Normal file
89
cdm/cdm_api_internal.gyp
Normal file
@@ -0,0 +1,89 @@
|
||||
# Copyright 2013 Google Inc. All Rights Reserved.
|
||||
#
|
||||
# Refer to the distribution package's README for information about
|
||||
# setting up your system, performing the build, and using/testing
|
||||
# the build targets.
|
||||
#
|
||||
# Set external_build=0 via GYP_DEFINES if debug information in
|
||||
# libwvcdm_shared.so is desirable.
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'license_protocol',
|
||||
'type': 'static_library',
|
||||
'sources': ['../core/src/license_protocol.proto',],
|
||||
'variables': {
|
||||
'proto_in_dir': '../core/src',
|
||||
'proto_out_dir': 'license_protocol',
|
||||
},
|
||||
'includes': ['../build/protoc.gypi'],
|
||||
},
|
||||
{
|
||||
'target_name': 'device_files',
|
||||
'type': 'static_library',
|
||||
'sources': ['../core/src/device_files.proto',],
|
||||
'variables': {
|
||||
'proto_in_dir': '../core/src',
|
||||
'proto_out_dir': 'device_files',
|
||||
},
|
||||
'includes': ['../build/protoc.gypi'],
|
||||
},
|
||||
{
|
||||
'target_name': 'wvcdm_sysdep',
|
||||
'type': 'static_library',
|
||||
'defines': ['CDM_IMPLEMENTATION'],
|
||||
'include_dirs': [
|
||||
'../cdm/include',
|
||||
'../core/include',
|
||||
'../linux/include',
|
||||
'../third_party/stringencoders/src',
|
||||
],
|
||||
'sources': [
|
||||
'../cdm/src/file_store.cpp',
|
||||
'../cdm/src/properties_common.cpp',
|
||||
'../core/src/string_conversions.cpp',
|
||||
'../linux/src/lock.cpp',
|
||||
'../linux/src/log.cpp',
|
||||
'../third_party/stringencoders/src/modp_b64w.cpp',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'wvcdm_shared',
|
||||
'type': 'shared_library',
|
||||
'defines': ['CDM_IMPLEMENTATION'],
|
||||
'dependencies': [
|
||||
'license_protocol',
|
||||
'wvcdm_sysdep',
|
||||
'device_files',
|
||||
'<(oemcrypto_target)',
|
||||
],
|
||||
'include_dirs': [
|
||||
'../cdm/include',
|
||||
'../core/include',
|
||||
'../third_party/gmock/include',
|
||||
'../linux/include',
|
||||
'../oemcrypto/include',
|
||||
],
|
||||
'sources': [
|
||||
# uses common published api
|
||||
'../cdm/src/clock.cpp',
|
||||
'../cdm/src/host_event_listener.cpp',
|
||||
'../cdm/src/wv_content_decryption_module.cpp',
|
||||
'../core/src/buffer_reader.cpp',
|
||||
'../core/src/cdm_engine.cpp',
|
||||
'../core/src/cdm_session.cpp',
|
||||
'../core/src/certificate_provisioning.cpp',
|
||||
'../core/src/crypto_session.cpp',
|
||||
'../core/src/device_files.cpp',
|
||||
'../core/src/license.cpp',
|
||||
'../core/src/oemcrypto_adapter_static.cpp',
|
||||
'../core/src/policy_engine.cpp',
|
||||
'../core/src/privacy_crypto.cpp',
|
||||
'../core/src/properties.cpp',
|
||||
],
|
||||
'direct_dependencies': [
|
||||
'license_protocol',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
30
cdm/include/LICENSE
Normal file
30
cdm/include/LICENSE
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright for the following files derived from the Chromium project
|
||||
// content_decryption_module.h
|
||||
//
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
26
cdm/include/cdm_host_clock.h
Normal file
26
cdm/include/cdm_host_clock.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CDM_CDM_HOST_CLOCK_H_
|
||||
#define WVCDM_CDM_CDM_HOST_CLOCK_H_
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class IClock {
|
||||
public:
|
||||
IClock(){}
|
||||
virtual ~IClock();
|
||||
virtual int64_t GetCurrentTimeInSeconds() = 0;
|
||||
};
|
||||
|
||||
class HostClock {
|
||||
friend class Clock;
|
||||
friend class IClock;
|
||||
public:
|
||||
static void SetClockInterface(IClock* iclock);
|
||||
int64_t GetCurrentTimeInSeconds();
|
||||
private:
|
||||
static IClock* impl_;
|
||||
};
|
||||
} // namspace wvcdm
|
||||
|
||||
#endif // WVCDM_CDM_CDM_HOST_CLOCK_H_
|
||||
43
cdm/include/cdm_host_file.h
Normal file
43
cdm/include/cdm_host_file.h
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CDM_CDM_HOST_FILE_H_
|
||||
#define WVCDM_CDM_CDM_HOST_FILE_H_
|
||||
|
||||
#include "file_store.h"
|
||||
#include "content_decryption_module.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class IFileFactory {
|
||||
protected:
|
||||
IFileFactory(){}
|
||||
virtual ~IFileFactory(){}
|
||||
public:
|
||||
virtual File::Impl* NewFileImpl () = 0;
|
||||
};
|
||||
|
||||
class File::Impl {
|
||||
public:
|
||||
explicit Impl(cdm::Host* const host) : host_(host) {}
|
||||
FILE* file_;
|
||||
static void RegisterFileFactory(IFileFactory* factory) {
|
||||
factory_ = factory;
|
||||
}
|
||||
static IFileFactory* factory_;
|
||||
|
||||
virtual bool Exists(const std::string& name);
|
||||
virtual bool Open(const std::string& name);
|
||||
virtual bool Close();
|
||||
virtual bool Remove(const std::string& name);
|
||||
virtual size_t Read(char* buffer, size_t bytes);
|
||||
virtual size_t Write(const char* buffer, size_t bytes);
|
||||
virtual size_t FileSize(const std::string& name);
|
||||
|
||||
private:
|
||||
cdm::Host* const host_;
|
||||
std::string fname_;
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CDM_CDM_HOST_FILE_H_
|
||||
45
cdm/include/cdm_host_timer.h
Normal file
45
cdm/include/cdm_host_timer.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CDM_CDM_HOST_TIMER_H_
|
||||
#define WVCDM_CDM_CDM_HOST_TIMER_H_
|
||||
|
||||
#include <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_
|
||||
573
cdm/include/content_decryption_module.h
Normal file
573
cdm/include/content_decryption_module.h
Normal file
@@ -0,0 +1,573 @@
|
||||
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef WVCDM_CDM_CONTENT_DECRYPTION_MODULE_H_
|
||||
#define WVCDM_CDM_CONTENT_DECRYPTION_MODULE_H_
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef int int32_t;
|
||||
typedef __int64 int64_t;
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
// Define CDM_EXPORT so that functionality implemented by the CDM module
|
||||
// can be exported to consumers.
|
||||
#if defined(WIN32)
|
||||
|
||||
#if defined(CDM_IMPLEMENTATION)
|
||||
#define CDM_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
#define CDM_EXPORT __declspec(dllimport)
|
||||
#endif // defined(CDM_IMPLEMENTATION)
|
||||
|
||||
#else // defined(WIN32)
|
||||
|
||||
#if defined(CDM_IMPLEMENTATION)
|
||||
#define CDM_EXPORT __attribute__((visibility("default")))
|
||||
#else
|
||||
#define CDM_EXPORT
|
||||
#endif
|
||||
|
||||
#endif // defined(WIN32)
|
||||
|
||||
// The version number must be rolled when the exported functions are updated!
|
||||
// If the CDM and the adapter use different versions of these functions, the
|
||||
// adapter will fail to load or crash!
|
||||
#define INITIALIZE_CDM_MODULE InitializeCdmModule_1
|
||||
|
||||
extern "C" {
|
||||
CDM_EXPORT void INITIALIZE_CDM_MODULE();
|
||||
|
||||
CDM_EXPORT void DeinitializeCdmModule();
|
||||
|
||||
// Returns a pointer to the requested CDM Host interface upon success.
|
||||
// Returns NULL if the requested CDM Host interface is not supported.
|
||||
// The caller should cast the returned pointer to the type matching
|
||||
// |host_interface_version|.
|
||||
typedef void* (*GetCdmHostFunc)(int host_interface_version, void* user_data);
|
||||
|
||||
// Returns a pointer to the requested CDM upon success.
|
||||
// Returns NULL if an error occurs or the requested |cdm_interface_version| or
|
||||
// |key_system| is not supported or another error occurs.
|
||||
// The caller should cast the returned pointer to the type matching
|
||||
// |cdm_interface_version|.
|
||||
// Caller retains ownership of arguments and must call Destroy() on the returned
|
||||
// object.
|
||||
CDM_EXPORT void* CreateCdmInstance(
|
||||
int cdm_interface_version,
|
||||
const char* key_system, int key_system_size,
|
||||
GetCdmHostFunc get_cdm_host_func, void* user_data);
|
||||
|
||||
CDM_EXPORT int GetCdmVersion();
|
||||
}
|
||||
|
||||
namespace cdm {
|
||||
|
||||
class AudioFrames;
|
||||
class DecryptedBlock;
|
||||
class VideoFrame;
|
||||
|
||||
enum Status {
|
||||
kSuccess = 0,
|
||||
kNeedMoreData, // Decoder needs more data to produce a decoded frame/sample.
|
||||
kNoKey, // The required decryption key is not available.
|
||||
kSessionError, // Session management error.
|
||||
kDecryptError, // Decryption failed.
|
||||
kDecodeError, // Error decoding audio or video.
|
||||
kRetry, // Buffer temporarily cannot be accepted. Retry after a short delay.
|
||||
kNeedsDeviceCertificate // Requires Device Certificate for content licensing
|
||||
};
|
||||
|
||||
// This must be consistent with MediaKeyError defined in the
|
||||
// Encrypted media Extensions (EME) specification: http://goo.gl/3Df8h
|
||||
enum MediaKeyError {
|
||||
kUnknownError = 1,
|
||||
kClientError,
|
||||
kServiceError,
|
||||
kOutputError,
|
||||
kHardwareChangeError,
|
||||
kDomainError
|
||||
};
|
||||
|
||||
// An input buffer can be split into several continuous subsamples.
|
||||
// A SubsampleEntry specifies the number of clear and cipher bytes in each
|
||||
// subsample. For example, the following buffer has three subsamples:
|
||||
//
|
||||
// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
|
||||
// | clear1 | cipher1 | clear2 | cipher2 | clear3 | cipher3 |
|
||||
//
|
||||
// For decryption, all of the cipher bytes in a buffer should be concatenated
|
||||
// (in the subsample order) into a single logical stream. The clear bytes should
|
||||
// not be considered as part of decryption.
|
||||
//
|
||||
// Stream to decrypt: | cipher1 | cipher2 | cipher3 |
|
||||
// Decrypted stream: | decrypted1| decrypted2 | decrypted3 |
|
||||
//
|
||||
// After decryption, the decrypted bytes should be copied over the position
|
||||
// of the corresponding cipher bytes in the original buffer to form the output
|
||||
// buffer. Following the above example, the decrypted buffer should be:
|
||||
//
|
||||
// |<----- subsample1 ----->|<----- subsample2 ----->|<----- subsample3 ----->|
|
||||
// | clear1 | decrypted1| clear2 | decrypted2 | clear3 | decrypted3 |
|
||||
//
|
||||
struct SubsampleEntry {
|
||||
SubsampleEntry(int32_t clear_bytes, int32_t cipher_bytes)
|
||||
: clear_bytes(clear_bytes), cipher_bytes(cipher_bytes) {}
|
||||
|
||||
int32_t clear_bytes;
|
||||
int32_t cipher_bytes;
|
||||
};
|
||||
|
||||
// Represents an input buffer to be decrypted (and possibly decoded). It
|
||||
// does not own any pointers in this struct.
|
||||
struct InputBuffer {
|
||||
InputBuffer()
|
||||
: data(NULL),
|
||||
data_size(0),
|
||||
data_offset(0),
|
||||
key_id(NULL),
|
||||
key_id_size(0),
|
||||
iv(NULL),
|
||||
iv_size(0),
|
||||
subsamples(NULL),
|
||||
num_subsamples(0),
|
||||
timestamp(0) {}
|
||||
|
||||
const uint8_t* data; // Pointer to the beginning of the input data.
|
||||
int32_t data_size; // Size (in bytes) of |data|.
|
||||
|
||||
int32_t data_offset; // Number of bytes to be discarded before decryption.
|
||||
|
||||
const uint8_t* key_id; // Key ID to identify the decryption key.
|
||||
int32_t key_id_size; // Size (in bytes) of |key_id|.
|
||||
|
||||
const uint8_t* iv; // Initialization vector.
|
||||
int32_t iv_size; // Size (in bytes) of |iv|.
|
||||
|
||||
const struct SubsampleEntry* subsamples;
|
||||
int32_t num_subsamples; // Number of subsamples in |subsamples|.
|
||||
|
||||
int64_t timestamp; // Presentation timestamp in microseconds.
|
||||
};
|
||||
|
||||
struct AudioDecoderConfig {
|
||||
enum AudioCodec {
|
||||
kUnknownAudioCodec = 0,
|
||||
kCodecVorbis,
|
||||
kCodecAac
|
||||
};
|
||||
|
||||
AudioDecoderConfig()
|
||||
: codec(kUnknownAudioCodec),
|
||||
channel_count(0),
|
||||
bits_per_channel(0),
|
||||
samples_per_second(0),
|
||||
extra_data(NULL),
|
||||
extra_data_size(0) {}
|
||||
|
||||
AudioCodec codec;
|
||||
int32_t channel_count;
|
||||
int32_t bits_per_channel;
|
||||
int32_t samples_per_second;
|
||||
|
||||
// Optional byte data required to initialize audio decoders, such as the
|
||||
// vorbis setup header.
|
||||
uint8_t* extra_data;
|
||||
int32_t extra_data_size;
|
||||
};
|
||||
|
||||
// Surface formats based on FOURCC labels, see: http://www.fourcc.org/yuv.php
|
||||
enum VideoFormat {
|
||||
kUnknownVideoFormat = 0, // Unknown format value. Used for error reporting.
|
||||
kYv12, // 12bpp YVU planar 1x1 Y, 2x2 VU samples.
|
||||
kI420 // 12bpp YVU planar 1x1 Y, 2x2 UV samples.
|
||||
};
|
||||
|
||||
struct Size {
|
||||
Size() : width(0), height(0) {}
|
||||
Size(int32_t width, int32_t height) : width(width), height(height) {}
|
||||
|
||||
int32_t width;
|
||||
int32_t height;
|
||||
};
|
||||
|
||||
struct VideoDecoderConfig {
|
||||
enum VideoCodec {
|
||||
kUnknownVideoCodec = 0,
|
||||
kCodecVp8,
|
||||
kCodecH264
|
||||
};
|
||||
|
||||
enum VideoCodecProfile {
|
||||
kUnknownVideoCodecProfile = 0,
|
||||
kVp8ProfileMain,
|
||||
kH264ProfileBaseline,
|
||||
kH264ProfileMain,
|
||||
kH264ProfileExtended,
|
||||
kH264ProfileHigh,
|
||||
kH264ProfileHigh10,
|
||||
kH264ProfileHigh422,
|
||||
kH264ProfileHigh444Predictive
|
||||
};
|
||||
|
||||
VideoDecoderConfig()
|
||||
: codec(kUnknownVideoCodec),
|
||||
profile(kUnknownVideoCodecProfile),
|
||||
format(kUnknownVideoFormat),
|
||||
extra_data(NULL),
|
||||
extra_data_size(0) {}
|
||||
|
||||
VideoCodec codec;
|
||||
VideoCodecProfile profile;
|
||||
VideoFormat format;
|
||||
|
||||
// Width and height of video frame immediately post-decode. Not all pixels
|
||||
// in this region are valid.
|
||||
Size coded_size;
|
||||
|
||||
// Optional byte data required to initialize video decoders, such as H.264
|
||||
// AAVC data.
|
||||
uint8_t* extra_data;
|
||||
int32_t extra_data_size;
|
||||
};
|
||||
|
||||
enum StreamType {
|
||||
kStreamTypeAudio = 0,
|
||||
kStreamTypeVideo = 1
|
||||
};
|
||||
|
||||
// ContentDecryptionModule interface that all CDMs need to implement.
|
||||
// The interface is versioned for backward compatibility.
|
||||
// Note: ContentDecryptionModule implementations must use the allocator
|
||||
// provided in CreateCdmInstance() to allocate any Buffer that needs to
|
||||
// be passed back to the caller. Implementations must call Buffer::Destroy()
|
||||
// when a Buffer is created that will never be returned to the caller.
|
||||
class ContentDecryptionModule_1 {
|
||||
public:
|
||||
// Generates a |key_request| given |type| and |init_data|.
|
||||
//
|
||||
// Returns kSuccess if the key request was successfully generated, in which
|
||||
// case the CDM must send the key message by calling Host::SendKeyMessage().
|
||||
// Returns kSessionError if any error happened, in which case the CDM must
|
||||
// send a key error by calling Host::SendKeyError().
|
||||
virtual Status GenerateKeyRequest(
|
||||
const char* type, int type_size,
|
||||
const uint8_t* init_data, int init_data_size) = 0;
|
||||
|
||||
// Adds the |key| to the CDM to be associated with |key_id|.
|
||||
//
|
||||
// Returns kSuccess if the key was successfully added, kSessionError
|
||||
// otherwise.
|
||||
virtual Status AddKey(const char* session_id, int session_id_size,
|
||||
const uint8_t* key, int key_size,
|
||||
const uint8_t* key_id, int key_id_size) = 0;
|
||||
|
||||
// Cancels any pending key request made to the CDM for |session_id|.
|
||||
//
|
||||
// Returns kSuccess if all pending key requests for |session_id| were
|
||||
// successfully canceled or there was no key request to be canceled,
|
||||
// kSessionError otherwise.
|
||||
virtual Status CancelKeyRequest(
|
||||
const char* session_id, int session_id_size) = 0;
|
||||
|
||||
// Performs scheduled operation with |context| when the timer fires.
|
||||
virtual void TimerExpired(void* context) = 0;
|
||||
|
||||
// Decrypts the |encrypted_buffer|.
|
||||
//
|
||||
// Returns kSuccess if decryption succeeded, in which case the callee
|
||||
// should have filled the |decrypted_buffer| and passed the ownership of
|
||||
// |data| in |decrypted_buffer| to the caller.
|
||||
// Returns kNoKey if the CDM did not have the necessary decryption key
|
||||
// to decrypt.
|
||||
// Returns kDecryptError if any other error happened.
|
||||
// If the return value is not kSuccess, |decrypted_buffer| should be ignored
|
||||
// by the caller.
|
||||
virtual Status Decrypt(const InputBuffer& encrypted_buffer,
|
||||
DecryptedBlock* decrypted_buffer) = 0;
|
||||
|
||||
// Initializes the CDM audio decoder with |audio_decoder_config|.
|
||||
// This function is only needed if DecryptAndDecodeSamples() or
|
||||
// DecryptDecodeAndRenderSamples() is used. It must be called before either
|
||||
// of these routines is called.
|
||||
//
|
||||
// Returns kSuccess if the |audio_decoder_config| is supported and the CDM
|
||||
// audio decoder is successfully initialized.
|
||||
// Returns kSessionError if |audio_decoder_config| is not supported. The CDM
|
||||
// may still be able to do Decrypt().
|
||||
//
|
||||
virtual Status InitializeAudioDecoder(
|
||||
const AudioDecoderConfig& audio_decoder_config) = 0;
|
||||
|
||||
// Initializes the CDM video decoder with |video_decoder_config|.
|
||||
// This function is only needed if DecryptAndDecodeFrame() or
|
||||
// DecryptDecodeAndRenderFrame() is used. It must be called before either
|
||||
// of these routines is called.
|
||||
//
|
||||
// Returns kSuccess if the |video_decoder_config| is supported and the CDM
|
||||
// video decoder is successfully initialized.
|
||||
// Returns kSessionError if |video_decoder_config| is not supported. The CDM
|
||||
// may still be able to do Decrypt().
|
||||
//
|
||||
virtual Status InitializeVideoDecoder(
|
||||
const VideoDecoderConfig& video_decoder_config) = 0;
|
||||
|
||||
// De-initializes the CDM decoder and sets it to an uninitialized state. The
|
||||
// caller can initialize the decoder again after this call to re-initialize
|
||||
// it. This can be used to reconfigure the decoder if the configuration
|
||||
// changes.
|
||||
virtual void DeinitializeDecoder(StreamType decoder_type) = 0;
|
||||
|
||||
// Resets the CDM decoder to an initialized clean state. All internal buffers
|
||||
// MUST be flushed.
|
||||
virtual void ResetDecoder(StreamType decoder_type) = 0;
|
||||
|
||||
// Decrypts the |encrypted_buffer| and decodes the decrypted buffer into a
|
||||
// |video_frame|. Upon end-of-stream, the caller should call this function
|
||||
// repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
|
||||
// |video_frame| (|format| == kEmptyVideoFrame) is produced.
|
||||
//
|
||||
// Returns kSuccess if decryption and decoding both succeeded, in which case
|
||||
// the callee will have filled the |video_frame| and passed the ownership of
|
||||
// |frame_buffer| in |video_frame| to the caller.
|
||||
// Returns kNoKey if the CDM did not have the necessary decryption key
|
||||
// to decrypt.
|
||||
// Returns kNeedMoreData if more data was needed by the decoder to generate
|
||||
// a decoded frame (e.g. during initialization and end-of-stream).
|
||||
// Returns kDecryptError if any decryption error happened.
|
||||
// Returns kDecodeError if any decoding error happened.
|
||||
// If the return value is not kSuccess, |video_frame| should be ignored by
|
||||
// the caller.
|
||||
virtual Status DecryptAndDecodeFrame(const InputBuffer& encrypted_buffer,
|
||||
VideoFrame* video_frame) = 0;
|
||||
|
||||
// Decrypts the |encrypted_buffer| and decodes the decrypted buffer into
|
||||
// |audio_frames|. Upon end-of-stream, the caller should call this function
|
||||
// repeatedly with empty |encrypted_buffer| (|data| == NULL) until only empty
|
||||
// |audio_frames| is produced.
|
||||
//
|
||||
// Returns kSuccess if decryption and decoding both succeeded, in which case
|
||||
// the callee will have filled |audio_frames| and passed the ownership of
|
||||
// |data| in |audio_frames| to the caller.
|
||||
// Returns kNoKey if the CDM did not have the necessary decryption key
|
||||
// to decrypt.
|
||||
// Returns kNeedMoreData if more data was needed by the decoder to generate
|
||||
// audio samples (e.g. during initialization and end-of-stream).
|
||||
// Returns kDecryptError if any decryption error happened.
|
||||
// Returns kDecodeError if any decoding error happened.
|
||||
// If the return value is not kSuccess, |audio_frames| should be ignored by
|
||||
// the caller.
|
||||
virtual Status DecryptAndDecodeSamples(const InputBuffer& encrypted_buffer,
|
||||
AudioFrames* audio_frames) = 0;
|
||||
|
||||
// Decrypts the |encrypted_buffer|, decodes the decrypted buffer into a
|
||||
// video frame, and passes the frame to the rendering FW/HW. No data
|
||||
// is returned.
|
||||
//
|
||||
// Returns kSuccess if decryption, decoding, and rendering all succeeded.
|
||||
// Returns kNoKey if the CDM did not have the necessary decryption key
|
||||
// to decrypt.
|
||||
// Returns kRetry if |encrypted_buffer| cannot be accepted (e.g, video
|
||||
// pipeline is full). Caller should retry after a short delay.
|
||||
// Returns kDecryptError if any decryption error happened.
|
||||
// Returns kDecodeError if any decoding error happened.
|
||||
// If the return value is not kSuccess, |video_frame| should be ignored by
|
||||
// the caller.
|
||||
virtual Status DecryptDecodeAndRenderFrame(
|
||||
const InputBuffer& encrypted_buffer) = 0;
|
||||
|
||||
// Decrypts the |encrypted_buffer|, decodes the decrypted buffer into
|
||||
// audio frames, and passes the samples to the rendering FW/HW. No
|
||||
// data is returned.
|
||||
//
|
||||
// Returns kSuccess if decryption, decoding, and rendering all succeeded.
|
||||
// Returns kNoKey if the CDM did not have the necessary decryption key
|
||||
// to decrypt.
|
||||
// Returns kRetry if |encrypted_buffer| cannot be accepted (e.g., audio
|
||||
// pipeline is full). Caller should retry after a short delay.
|
||||
// Returns kDecryptError if any decryption error happened.
|
||||
// Returns kDecodeError if any decoding error happened.
|
||||
// If the return value is not kSuccess or kRetry, the audiostream has failed
|
||||
// and should be reset.
|
||||
virtual Status DecryptDecodeAndRenderSamples(
|
||||
const InputBuffer& encrypted_buffer) = 0;
|
||||
|
||||
// Destroys the object in the same context as it was created.
|
||||
virtual void Destroy() = 0;
|
||||
|
||||
// Provisioning related methods
|
||||
virtual Status GetProvisioningRequest(
|
||||
std::string* request, std::string* default_url) = 0;
|
||||
|
||||
virtual cdm::Status HandleProvisioningResponse(
|
||||
std::string& response) = 0;
|
||||
|
||||
protected:
|
||||
ContentDecryptionModule_1() {}
|
||||
virtual ~ContentDecryptionModule_1() {}
|
||||
};
|
||||
|
||||
const int kWidevineCdmInterfaceVersion_1 = 1002;
|
||||
|
||||
typedef ContentDecryptionModule_1 ContentDecryptionModule;
|
||||
const int kCdmInterfaceVersion = kWidevineCdmInterfaceVersion_1;
|
||||
|
||||
// Represents a buffer created by Allocator implementations.
|
||||
class Buffer {
|
||||
public:
|
||||
// Destroys the buffer in the same context as it was created.
|
||||
virtual void Destroy() = 0;
|
||||
|
||||
virtual int32_t Capacity() const = 0;
|
||||
virtual uint8_t* Data() = 0;
|
||||
virtual void SetSize(int32_t size) = 0;
|
||||
virtual int32_t Size() const = 0;
|
||||
|
||||
protected:
|
||||
Buffer() {}
|
||||
virtual ~Buffer() {}
|
||||
|
||||
private:
|
||||
Buffer(const Buffer&);
|
||||
void operator=(const Buffer&);
|
||||
};
|
||||
|
||||
// Host interface that the CDM can call into to access browser side services.
|
||||
// Host interfaces are versioned for backward compatibility. CDM should use
|
||||
// HostFactory object to request a Host interface of a particular version.
|
||||
class Host_1 {
|
||||
public:
|
||||
// Returns a Buffer* containing non-zero members upon success, or NULL on
|
||||
// failure. The caller owns the Buffer* after this call. The buffer is not
|
||||
// guaranteed to be zero initialized. The capacity of the allocated Buffer
|
||||
// is guaranteed to be not less than |capacity|.
|
||||
virtual Buffer* Allocate(int32_t capacity) = 0;
|
||||
|
||||
// Requests the host to call ContentDecryptionModule::TimerExpired() in
|
||||
// |delay_ms| from now with |context|.
|
||||
virtual void SetTimer(int64_t delay_ms, void* context) = 0;
|
||||
|
||||
// Returns the current epoch wall time in seconds.
|
||||
virtual double GetCurrentWallTimeInSeconds() = 0;
|
||||
|
||||
// Sends a keymessage event to the application.
|
||||
// Length parameters should not include null termination.
|
||||
virtual void SendKeyMessage(
|
||||
const char* session_id, int32_t session_id_length,
|
||||
const char* message, int32_t message_length,
|
||||
const char* default_url, int32_t default_url_length) = 0;
|
||||
|
||||
// Sends a keyerror event to the application.
|
||||
// |session_id_length| should not include null termination.
|
||||
virtual void SendKeyError(const char* session_id,
|
||||
int32_t session_id_length,
|
||||
MediaKeyError error_code,
|
||||
uint32_t system_code) = 0;
|
||||
|
||||
// Get private data from the host. This function is limited to internal use.
|
||||
typedef const void* (*GetPrivateInterface)(const char* interface_name);
|
||||
virtual void GetPrivateData(int32_t* instance,
|
||||
GetPrivateInterface* get_interface) = 0;
|
||||
|
||||
//Version 1.3
|
||||
// These Virtual member functions extend the cdm::Host interface to allow
|
||||
// the sharing for platform specific information, between the cdm::Host and
|
||||
// the CDM.
|
||||
|
||||
virtual int GetPlatformString(const std::string& name,
|
||||
std::string* value) = 0;
|
||||
virtual int SetPlatformString(const std::string& name,
|
||||
const std::string& value) = 0;
|
||||
virtual int PersistPlatformString(const std::string& name,
|
||||
const std::string& value) = 0;
|
||||
virtual int GetPlatformByteArray(const std::string& name,
|
||||
std::vector<uint8_t>* value) = 0;
|
||||
virtual int SetPlatformByteArray(const std::string& name,
|
||||
const std::vector<uint8_t>& value) = 0;
|
||||
virtual int PersistPlatformByteArray(const std::string& name,
|
||||
const std::vector<uint8_t>& value) = 0;
|
||||
|
||||
protected:
|
||||
Host_1() {}
|
||||
virtual ~Host_1() {}
|
||||
};
|
||||
|
||||
const int kWidevineHostInterfaceVersion_1 = 1002;
|
||||
|
||||
typedef Host_1 Host;
|
||||
const int kHostInterfaceVersion = kWidevineHostInterfaceVersion_1;
|
||||
|
||||
// Represents a decrypted block that has not been decoded.
|
||||
class DecryptedBlock {
|
||||
public:
|
||||
virtual void SetDecryptedBuffer(Buffer* buffer) = 0;
|
||||
virtual Buffer* DecryptedBuffer() = 0;
|
||||
|
||||
virtual void SetTimestamp(int64_t timestamp) = 0;
|
||||
virtual int64_t Timestamp() const = 0;
|
||||
|
||||
protected:
|
||||
DecryptedBlock() {}
|
||||
virtual ~DecryptedBlock() {}
|
||||
};
|
||||
|
||||
class VideoFrame {
|
||||
public:
|
||||
enum VideoPlane {
|
||||
kYPlane = 0,
|
||||
kUPlane = 1,
|
||||
kVPlane = 2,
|
||||
kMaxPlanes = 3,
|
||||
};
|
||||
|
||||
virtual void SetFormat(VideoFormat format) = 0;
|
||||
virtual VideoFormat Format() const = 0;
|
||||
|
||||
virtual void SetSize(cdm::Size size) = 0;
|
||||
virtual cdm::Size Size() const = 0;
|
||||
|
||||
virtual void SetFrameBuffer(Buffer* frame_buffer) = 0;
|
||||
virtual Buffer* FrameBuffer() = 0;
|
||||
|
||||
virtual void SetPlaneOffset(VideoPlane plane, int32_t offset) = 0;
|
||||
virtual int32_t PlaneOffset(VideoPlane plane) = 0;
|
||||
|
||||
virtual void SetStride(VideoPlane plane, int32_t stride) = 0;
|
||||
virtual int32_t Stride(VideoPlane plane) = 0;
|
||||
|
||||
virtual void SetTimestamp(int64_t timestamp) = 0;
|
||||
virtual int64_t Timestamp() const = 0;
|
||||
|
||||
protected:
|
||||
VideoFrame() {}
|
||||
virtual ~VideoFrame() {}
|
||||
};
|
||||
|
||||
// Represents decrypted and decoded audio frames. AudioFrames can contain
|
||||
// multiple audio output buffers, which are serialized into this format:
|
||||
//
|
||||
// |<------------------- serialized audio buffer ------------------->|
|
||||
// | int64_t timestamp | int64_t length | length bytes of audio data |
|
||||
//
|
||||
// For example, with three audio output buffers, the AudioFrames will look
|
||||
// like this:
|
||||
//
|
||||
// |<----------------- AudioFrames ------------------>|
|
||||
// | audio buffer 0 | audio buffer 1 | audio buffer 2 |
|
||||
class AudioFrames {
|
||||
public:
|
||||
virtual void SetFrameBuffer(Buffer* buffer) = 0;
|
||||
virtual Buffer* FrameBuffer() = 0;
|
||||
|
||||
protected:
|
||||
AudioFrames() {}
|
||||
virtual ~AudioFrames() {}
|
||||
};
|
||||
|
||||
} // namespace cdm
|
||||
|
||||
#endif // WVCDM_CDM_CONTENT_DECRYPTION_MODULE_H_
|
||||
33
cdm/include/host_event_listener.h
Normal file
33
cdm/include/host_event_listener.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CDM_HOST_EVENT_LISTENER_H_
|
||||
#define WVCDM_CDM_HOST_EVENT_LISTENER_H_
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "wv_cdm_common.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class HostEventListener : public WvCdmEventListener {
|
||||
public:
|
||||
HostEventListener(cdm::Host* host, CdmEngine* cdm_engine)
|
||||
: host_(host), cdm_engine_(cdm_engine) {}
|
||||
virtual ~HostEventListener() {}
|
||||
|
||||
// wvcdm::WvCdmEventListener implementation.
|
||||
virtual void OnEvent(const CdmSessionId& session_id,
|
||||
CdmEventType cdm_event) OVERRIDE;
|
||||
|
||||
private:
|
||||
cdm::Host* const host_;
|
||||
CdmEngine* const cdm_engine_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(HostEventListener);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CDM_HOST_EVENT_LISTENER_H_
|
||||
47
cdm/include/properties_configuration.h
Normal file
47
cdm/include/properties_configuration.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CDM_PROPERTIES_CONFIGURATION_H_
|
||||
#define WVCDM_CDM_PROPERTIES_CONFIGURATION_H_
|
||||
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "properties.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// If false begin license usage on first playback
|
||||
const bool kPropertyBeginLicenseUsageWhenReceived = false;
|
||||
|
||||
// If false, calls to Generate Key request, after the first one,
|
||||
// will result in a renewal request being generated
|
||||
const bool kPropertyRequireExplicitRenewRequest = false;
|
||||
|
||||
// Set only one of the three below to true. If secure buffer
|
||||
// is selected, fallback to userspace buffers may occur
|
||||
// if L1/L2 OEMCrypto APIs fail
|
||||
const bool kPropertyOemCryptoUseSecureBuffers = false;
|
||||
const bool kPropertyOemCryptoUseFifo = true;
|
||||
const bool kPropertyOemCryptoUseUserSpaceBuffers = true;
|
||||
|
||||
// If false, keyboxes will be used as client identification
|
||||
// and passed as the token in the license request.
|
||||
// The default value of false for PLATFORM_CERTIFICATE_PROV is set in
|
||||
// global_config.gypi. It can be overridden to true in the platform specific
|
||||
// .gypi files if you want your device to use certificates for provisioning.
|
||||
const bool kPropertyUseCertificatesAsIdentification = PLATFORM_CERTIFICATE_PROV;
|
||||
|
||||
// If false, extraction of widevine PSSH information from the PSSH box
|
||||
// takes place external to the CDM. This will become the default behaviour
|
||||
// once all platforms support it.
|
||||
const bool kExtractPsshData = true;
|
||||
|
||||
// If true, session_id parameter to CdmEngine::Decrypt can be empty; the
|
||||
// function will try to find out the session_id from the key_id.
|
||||
const bool kDecryptWithEmptySessionSupport = true;
|
||||
|
||||
// If true, device files will be moved to the directory specified by
|
||||
// Properties::GetDeviceFilesBasePath
|
||||
const bool kSecurityLevelPathBackwardCompatibilitySupport = false;
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CDM_PROPERTIES_CONFIGURATION_H_
|
||||
12
cdm/include/wv_cdm_common.h
Normal file
12
cdm/include/wv_cdm_common.h
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CDM_WV_CDM_COMMON_H_
|
||||
#define WVCDM_CDM_WV_CDM_COMMON_H_
|
||||
|
||||
#if defined(COMPILER_MSVC) || defined(__clang__)
|
||||
#define OVERRIDE override
|
||||
#else
|
||||
#define OVERRIDE
|
||||
#endif
|
||||
|
||||
#endif // WVCDM_CDM_WV_CDM_COMMON_H_
|
||||
3
cdm/include/wv_cdm_version.h
Normal file
3
cdm/include/wv_cdm_version.h
Normal file
@@ -0,0 +1,3 @@
|
||||
// Widevine CDM Kit Version
|
||||
#define WV_CDM_VERSION "v2.0.8-0-679"
|
||||
|
||||
162
cdm/include/wv_content_decryption_module.h
Normal file
162
cdm/include/wv_content_decryption_module.h
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
#define WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
|
||||
#include "cdm_client_property_set.h"
|
||||
#include "cdm_engine.h"
|
||||
#include "cdm_host_clock.h"
|
||||
#include "cdm_host_file.h"
|
||||
|
||||
#include "clock.h"
|
||||
#include "content_decryption_module.h"
|
||||
#include "host_event_listener.h"
|
||||
|
||||
#include "wv_cdm_common.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class WVClientPropertySet : public wvcdm::CdmClientPropertySet {
|
||||
public:
|
||||
WVClientPropertySet()
|
||||
: use_privacy_mode_(false) {}
|
||||
|
||||
virtual ~WVClientPropertySet() {}
|
||||
|
||||
void set_security_level(const std::string& securityLevel) {
|
||||
security_level_ = securityLevel;
|
||||
}
|
||||
|
||||
virtual std::string security_level() const {
|
||||
return security_level_;
|
||||
}
|
||||
|
||||
void set_use_privacy_mode(bool usePrivacyMode) {
|
||||
use_privacy_mode_ = usePrivacyMode;
|
||||
}
|
||||
|
||||
virtual bool use_privacy_mode() const {
|
||||
return use_privacy_mode_;
|
||||
}
|
||||
|
||||
void set_service_certificate(const std::vector<uint8_t>& serviceCertificate) {
|
||||
service_certificate_ = serviceCertificate;
|
||||
}
|
||||
|
||||
virtual std::vector<uint8_t> service_certificate() const {
|
||||
return service_certificate_;
|
||||
}
|
||||
|
||||
virtual bool is_session_sharing_enabled() const {
|
||||
return true; // This is unused by common cdm but we need a definition
|
||||
// for the pure virtual methods.
|
||||
}
|
||||
|
||||
void set_is_session_sharing_enabled(bool shareKeys) {
|
||||
return; // This is unused by common cdm but we need a definition
|
||||
// for the pure virtual methods.
|
||||
}
|
||||
|
||||
virtual uint32_t session_sharing_id() const {
|
||||
return 1; // This is unused by common cdm but we need a
|
||||
// definition for the pure virtual methods.
|
||||
}
|
||||
|
||||
virtual void set_session_sharing_id(uint32_t id) {
|
||||
return; // This is unused by common cdm but we need a
|
||||
// definition for the pure virtual methods.
|
||||
}
|
||||
private:
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(WVClientPropertySet);
|
||||
|
||||
std::string security_level_;
|
||||
bool use_privacy_mode_;
|
||||
std::vector<uint8_t> service_certificate_;
|
||||
};
|
||||
|
||||
class WvContentDecryptionModule : public cdm::ContentDecryptionModule,
|
||||
public IFileFactory,
|
||||
public IClock {
|
||||
File::Impl* NewFileImpl() { return new File::Impl(host_); }
|
||||
public:
|
||||
explicit WvContentDecryptionModule(cdm::Host* host)
|
||||
: host_(host), host_event_listener_(host, &cdm_engine_) {
|
||||
File::Impl::RegisterFileFactory(this);
|
||||
HostClock::SetClockInterface(this);
|
||||
}
|
||||
virtual ~WvContentDecryptionModule();
|
||||
// cdm::ContentDecryptionModule implementation.
|
||||
virtual cdm::Status GenerateKeyRequest(const char* type, int type_size,
|
||||
const uint8_t* init_data,
|
||||
int init_data_size) OVERRIDE;
|
||||
|
||||
virtual cdm::Status AddKey(const char* session_id, int session_id_size,
|
||||
const uint8_t* key, int key_size,
|
||||
const uint8_t* key_id, int key_id_size) OVERRIDE;
|
||||
|
||||
virtual cdm::Status CancelKeyRequest(const char* session_id,
|
||||
int session_id_size) OVERRIDE;
|
||||
|
||||
virtual void TimerExpired(void* context) OVERRIDE;
|
||||
|
||||
virtual cdm::Status Decrypt(const cdm::InputBuffer& encrypted_buffer,
|
||||
cdm::DecryptedBlock* decrypted_buffer) OVERRIDE;
|
||||
|
||||
virtual cdm::Status InitializeAudioDecoder(
|
||||
const cdm::AudioDecoderConfig& audio_decoder_config) OVERRIDE;
|
||||
|
||||
virtual cdm::Status InitializeVideoDecoder(
|
||||
const cdm::VideoDecoderConfig& video_decoder_config) OVERRIDE;
|
||||
|
||||
virtual void DeinitializeDecoder(cdm::StreamType decoder_type) OVERRIDE;
|
||||
|
||||
virtual void ResetDecoder(cdm::StreamType decoder_type) OVERRIDE;
|
||||
|
||||
virtual cdm::Status DecryptAndDecodeFrame(
|
||||
const cdm::InputBuffer& encrypted_buffer,
|
||||
cdm::VideoFrame* video_frame) OVERRIDE;
|
||||
|
||||
virtual cdm::Status DecryptAndDecodeSamples(
|
||||
const cdm::InputBuffer& encrypted_buffer,
|
||||
cdm::AudioFrames* audio_frames) OVERRIDE;
|
||||
|
||||
virtual cdm::Status DecryptDecodeAndRenderFrame(
|
||||
const cdm::InputBuffer& encrypted_buffer) OVERRIDE;
|
||||
|
||||
virtual cdm::Status DecryptDecodeAndRenderSamples(
|
||||
const cdm::InputBuffer& encrypted_buffer) OVERRIDE;
|
||||
|
||||
virtual void Destroy() OVERRIDE;
|
||||
|
||||
// Provisioning related methods
|
||||
virtual cdm::Status GetProvisioningRequest(
|
||||
std::string* request, std::string* default_url);
|
||||
|
||||
virtual cdm::Status HandleProvisioningResponse(
|
||||
std::string& response);
|
||||
|
||||
void EnablePolicyTimer();
|
||||
void DisablePolicyTimer();
|
||||
void OnTimerEvent();
|
||||
|
||||
private:
|
||||
virtual int64_t GetCurrentTimeInSeconds() {
|
||||
return host_->GetCurrentWallTimeInSeconds();
|
||||
}
|
||||
cdm::Status DoSubsampleDecrypt(CdmSessionId& session_id,
|
||||
CdmDecryptionParameters& parameters,
|
||||
std::vector<uint8_t>& iv,
|
||||
const cdm::InputBuffer& encrypted_buffer,
|
||||
cdm::DecryptedBlock* decrypted_block);
|
||||
CdmEngine cdm_engine_;
|
||||
cdm::Host* const host_;
|
||||
HostEventListener host_event_listener_;
|
||||
WVClientPropertySet property_set_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CDM_WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
24
cdm/src/clock.cpp
Normal file
24
cdm/src/clock.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#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
|
||||
146
cdm/src/file_store.cpp
Normal file
146
cdm/src/file_store.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include "cdm_host_file.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
IFileFactory* File::Impl::factory_ = NULL;
|
||||
|
||||
// File::Impl() Section
|
||||
// The file handler for cert.bin, aka DeviceCertificate is all we're
|
||||
// setting up for now.
|
||||
|
||||
bool File::Impl::Exists(const std::string& name) {
|
||||
if (name == "cert.bin") {
|
||||
std::vector<uint8_t> value;
|
||||
if (host_->GetPlatformByteArray("DeviceCertificate", &value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::Impl::Open(const std::string& name) {
|
||||
if (name == "cert.bin") {
|
||||
fname_= name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::Impl::Close() {
|
||||
fname_ = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::Impl::Remove(const std::string& name) {
|
||||
if (Exists(name)) {
|
||||
std::vector<uint8_t> value(0);
|
||||
if (host_->SetPlatformByteArray("DeviceCertificate", value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t File::Impl::Read(char* buffer, size_t bytes) {
|
||||
if (fname_ == "cert.bin") {
|
||||
std::vector<uint8_t> value;
|
||||
if (host_->GetPlatformByteArray("DeviceCertificate", &value)) {
|
||||
memcpy(buffer, &value[0], bytes);
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t File::Impl::Write(const char* buffer, size_t bytes) {
|
||||
if (fname_ == "cert.bin") {
|
||||
std::vector<uint8_t> value;
|
||||
value.resize(bytes);
|
||||
memcpy(&value[0], buffer, bytes);
|
||||
if (host_->PersistPlatformByteArray("DeviceCertificate", value)) {
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t File::Impl::FileSize(const std::string& name) {
|
||||
if (name == "cert.bin") {
|
||||
std::vector<uint8_t> value;
|
||||
if (host_->GetPlatformByteArray("DeviceCertificate", &value)) {
|
||||
return value.size();
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
File::File() : impl_(File::Impl::factory_->NewFileImpl()) {}
|
||||
|
||||
File::~File() {
|
||||
Close();
|
||||
delete impl_;
|
||||
}
|
||||
|
||||
bool File::Open(const std::string& name, int flags) {
|
||||
return impl_->Open(name);
|
||||
}
|
||||
|
||||
void File::Close() {
|
||||
impl_->Close();
|
||||
return;
|
||||
}
|
||||
|
||||
ssize_t File::Read(char* buffer, size_t bytes) {
|
||||
return impl_->Read(buffer, bytes);
|
||||
}
|
||||
|
||||
ssize_t File::Write(const char* buffer, size_t bytes) {
|
||||
return impl_->Write(buffer, bytes);
|
||||
}
|
||||
|
||||
bool File::Exists(const std::string& path) {
|
||||
return impl_->Exists(path);
|
||||
}
|
||||
|
||||
bool File::Remove(const std::string& path) {
|
||||
return impl_->Remove(path);
|
||||
}
|
||||
|
||||
bool File::Copy(const std::string& from, const std::string& to) {
|
||||
// Required for linkage only - no current API implementation in the partner CDM is required.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::List(const std::string& path, std::vector<std::string>* files) {
|
||||
// Required for linkage only - no current API implementation in the partner CDM is required.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(std::string path) {
|
||||
// Required for linkage only - no current API implementation in the partner CDM is required.
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::IsDirectory(const std::string& path) {
|
||||
// Required for linkage only - no current API implementation in the partner CDM is required.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::IsRegularFile(const std::string& path) {
|
||||
// Required for linkage only - no current API implementation in the partner CDM is required.
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t File::FileSize(const std::string& path) {
|
||||
size_t size = impl_->FileSize(path);
|
||||
if (size > 0) {
|
||||
return size;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
38
cdm/src/host_event_listener.cpp
Normal file
38
cdm/src/host_event_listener.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "host_event_listener.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
void HostEventListener::OnEvent(const CdmSessionId& session_id,
|
||||
CdmEventType cdm_event) {
|
||||
switch (cdm_event) {
|
||||
case LICENSE_RENEWAL_NEEDED_EVENT: {
|
||||
wvcdm::CdmKeyMessage cdm_message;
|
||||
std::string server_url;
|
||||
CdmResponseType result = cdm_engine_->GenerateRenewalRequest(
|
||||
session_id, &cdm_message, &server_url);
|
||||
if (result == wvcdm::KEY_MESSAGE) {
|
||||
host_->SendKeyMessage(session_id.data(), session_id.length(),
|
||||
cdm_message.data(), cdm_message.length(),
|
||||
server_url.data(), server_url.length());
|
||||
} else {
|
||||
LOGD("Error on Generating a Renewal Request!");
|
||||
host_->SendKeyError(session_id.data(), session_id.size(),
|
||||
cdm::kUnknownError, 0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LICENSE_EXPIRED_EVENT: {
|
||||
host_->SendKeyError(session_id.data(), session_id.size(),
|
||||
cdm::kUnknownError, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
95
cdm/src/properties_common.cpp
Normal file
95
cdm/src/properties_common.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "properties.h"
|
||||
|
||||
#include <string>
|
||||
#include <sys/utsname.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
bool Properties::GetCompanyName(std::string* company_name) {
|
||||
if (!company_name) {
|
||||
LOGW("Properties::GetCompanyName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*company_name = PLATFORM_COMPANY_NAME_WV;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetModelName(std::string* model_name) {
|
||||
if (!model_name) {
|
||||
LOGW("Properties::GetModelName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*model_name = PLATFORM_MODEL_NAME_WV;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool Properties::GetArchitectureName(std::string* arch_name) {
|
||||
if (!arch_name) {
|
||||
LOGW("Properties::GetArchitectureName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*arch_name = PLATFORM_ARCHITECTURE_NAME_WV;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool Properties::GetDeviceName(std::string* device_name) {
|
||||
if (!device_name) {
|
||||
LOGW("Properties::GetDeviceName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*device_name = PLATFORM_DEVICE_NAME_WV;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool Properties::GetProductName(std::string* product_name) {
|
||||
if (!product_name) {
|
||||
LOGW("Properties::GetProductName: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*product_name = PLATFORM_PRODUCT_NAME_WV;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool Properties::GetBuildInfo(std::string* build_info) {
|
||||
if (!build_info) {
|
||||
LOGW("Properties::GetBuildInfo: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*build_info = PLATFORM_BUILDINFO_WV;
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
bool Properties::GetDeviceFilesBasePath(CdmSecurityLevel security_level,
|
||||
std::string* base_path) {
|
||||
// no-op
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetFactoryKeyboxPath(std::string* keybox) {
|
||||
if (!keybox) {
|
||||
LOGW("Properties::GetFactoryKeyboxPath: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::GetOEMCryptoPath(std::string* library_name) {
|
||||
if (!library_name) {
|
||||
LOGW("Properties::GetOEMCryptoPath: Invalid parameter");
|
||||
return false;
|
||||
}
|
||||
*library_name = "out/Default/lib.target/liboemcrypto.so";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
76
cdm/src/timer.cpp
Normal file
76
cdm/src/timer.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Timer class - provides a simple timer implementation
|
||||
//
|
||||
#include "cdm_host_timer.h"
|
||||
#include "scoped_ptr.h"
|
||||
#include "timer.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
ITimerFactory* Timer::Impl::factory_ = NULL;
|
||||
|
||||
Timer::Impl::Impl(cdm::Host* const host)
|
||||
: host_(host), handler_(NULL), delay_ms_(0),
|
||||
state_(kIdle) {
|
||||
}
|
||||
|
||||
void Timer::Impl::RegisterTimerFactory(ITimerFactory* factory) {
|
||||
factory_ = factory;
|
||||
}
|
||||
|
||||
void Timer::Impl::Start(TimerHandler* handler, uint32_t time_in_secs) {
|
||||
handler_ = handler;
|
||||
delay_ms_ = time_in_secs * 1000;
|
||||
state_ = kRunning;
|
||||
host_->SetTimer(delay_ms_, this);
|
||||
}
|
||||
|
||||
void Timer::Impl::OnTimerEvent() {
|
||||
if (kRunning == state_) {
|
||||
handler_->OnTimerEvent();
|
||||
host_->SetTimer(delay_ms_, this);
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::Impl::Stop() {
|
||||
state_ = kIdle;
|
||||
}
|
||||
|
||||
Timer::Timer() : impl_(NULL) {}
|
||||
|
||||
Timer::~Timer() {
|
||||
if (impl_)
|
||||
delete impl_;
|
||||
}
|
||||
|
||||
bool Timer::Start(TimerHandler* handler, uint32_t time_in_secs) {
|
||||
if (!handler || 0 == time_in_secs)
|
||||
return false;
|
||||
if (!impl_ && Impl::factory_)
|
||||
impl_ = Impl::factory_->NewTimerImpl();
|
||||
if(!impl_)
|
||||
return false;
|
||||
|
||||
impl_->Start(handler, time_in_secs);
|
||||
return IsRunning();
|
||||
}
|
||||
|
||||
void Timer::Stop() {
|
||||
if (impl_)
|
||||
impl_->Stop();
|
||||
}
|
||||
|
||||
bool Timer::IsRunning() {
|
||||
if (!impl_)
|
||||
return false;
|
||||
return impl_->IsRunning();
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
410
cdm/src/wv_content_decryption_module.cpp
Normal file
410
cdm/src/wv_content_decryption_module.cpp
Normal file
@@ -0,0 +1,410 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
|
||||
#include "cdm_client_property_set.h"
|
||||
#include "content_decryption_module.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "properties.h"
|
||||
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_types.h"
|
||||
#include "wv_cdm_version.h"
|
||||
|
||||
void INITIALIZE_CDM_MODULE() {}
|
||||
|
||||
void DeinitializeCdmModule() {}
|
||||
|
||||
void* CreateCdmInstance(int cdm_interface_version, const char* key_system,
|
||||
int key_system_size, GetCdmHostFunc get_cdm_host_func,
|
||||
void* user_data) {
|
||||
if (cdm_interface_version != cdm::kCdmInterfaceVersion) return NULL;
|
||||
|
||||
cdm::Host* host = static_cast<cdm::Host*>(
|
||||
get_cdm_host_func(cdm::kHostInterfaceVersion, user_data));
|
||||
|
||||
if (!host) return NULL;
|
||||
|
||||
return static_cast<cdm::ContentDecryptionModule*>(
|
||||
new wvcdm::WvContentDecryptionModule(host));
|
||||
}
|
||||
|
||||
int GetCdmVersion() { return cdm::kCdmInterfaceVersion; }
|
||||
|
||||
namespace {
|
||||
static const std::string kWvCdmVersionString(WV_CDM_VERSION);
|
||||
|
||||
const int kCdmPolicyTimerDurationSeconds = 1;
|
||||
const int kCdmPolicyTimerCancel = 0;
|
||||
|
||||
// The iso spec only uses the lower 8 bytes of the iv as
|
||||
// the counter.
|
||||
const uint32_t kCencIvSize = 8;
|
||||
const uint32_t kIvSize = 16;
|
||||
|
||||
bool Ctr128Add(size_t block_count, uint8_t* counter) {
|
||||
if (NULL == counter)
|
||||
return false;
|
||||
if (0 == block_count)
|
||||
return true;
|
||||
uint8_t carry = 0;
|
||||
uint8_t n = kIvSize - 1;
|
||||
while (n >= kCencIvSize) {
|
||||
uint32_t temp = block_count & 0xff;
|
||||
temp += counter[n];
|
||||
temp += carry;
|
||||
counter[n] = temp & 0xff;
|
||||
carry = (temp & 0x100) ? 1 : 0;
|
||||
block_count = block_count >> 8;
|
||||
n--;
|
||||
if (!block_count && !carry) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// An empty iv string signals that the frame is unencrypted.
|
||||
bool IsBufferEncrypted(const cdm::InputBuffer& input_buffer) {
|
||||
return input_buffer.iv_size != 0;
|
||||
}
|
||||
|
||||
// cdm::ContentDecryptionModule implementation.
|
||||
|
||||
WvContentDecryptionModule::~WvContentDecryptionModule() {
|
||||
DisablePolicyTimer();
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::GenerateKeyRequest(
|
||||
const char* type, int type_size, const uint8_t* init_data,
|
||||
int init_data_size) {
|
||||
LOGI("Enter WvContentDecryptionModule::GenerateKeyRequest()");
|
||||
CdmInitData init_data_internal(reinterpret_cast<const char*>(init_data),
|
||||
init_data_size);
|
||||
CdmKeyMessage key_request;
|
||||
CdmSessionId session_id;
|
||||
|
||||
std::string security_level;
|
||||
std::string privacy_mode;
|
||||
kVectorBytes service_certificate;
|
||||
|
||||
host_->GetPlatformString("SecurityLevel", &security_level);
|
||||
host_->GetPlatformString("PrivacyOn", &privacy_mode);
|
||||
host_->GetPlatformByteArray("ServiceCertificate", &service_certificate);
|
||||
|
||||
property_set_.set_security_level(security_level);
|
||||
property_set_.set_use_privacy_mode(privacy_mode == "True" ? 1 : 0 );
|
||||
property_set_.set_service_certificate(service_certificate);
|
||||
|
||||
CdmResponseType result =
|
||||
cdm_engine_.OpenSession("com.widevine.alpha", &property_set_, &session_id);
|
||||
|
||||
if (NEED_PROVISIONING == result) {
|
||||
LOGI("Need to aquire a Device Certificate from the Provisioning Server");
|
||||
return cdm::kNeedsDeviceCertificate;
|
||||
}
|
||||
|
||||
if (NO_ERROR != result) return cdm::kSessionError;
|
||||
|
||||
if (!cdm_engine_.AttachEventListener(session_id, &host_event_listener_)) {
|
||||
cdm_engine_.CloseSession(session_id);
|
||||
return cdm::kSessionError;
|
||||
}
|
||||
|
||||
CdmAppParameterMap app_parameters; // empty
|
||||
CdmKeySetId key_set_id; // empty
|
||||
std::string server_url;
|
||||
|
||||
result = cdm_engine_.GenerateKeyRequest(
|
||||
session_id, key_set_id, init_data_internal, kLicenseTypeStreaming,
|
||||
app_parameters, &key_request, &server_url);
|
||||
if (KEY_MESSAGE != result) {
|
||||
cdm_engine_.CloseSession(session_id);
|
||||
return cdm::kSessionError;
|
||||
}
|
||||
|
||||
host_->SendKeyMessage(session_id.data(), session_id.length(),
|
||||
key_request.data(), key_request.length(),
|
||||
server_url.data(), server_url.length());
|
||||
|
||||
return cdm::kSuccess;
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::AddKey(const char* session_id,
|
||||
int session_id_size,
|
||||
const uint8_t* key, int key_size,
|
||||
const uint8_t* key_id,
|
||||
int key_id_size) {
|
||||
LOGI("Enter WvContentDecryptionModule::AddKey()\n");
|
||||
CdmSessionId session_id_internal(session_id, session_id_size);
|
||||
CdmKeyResponse key_data((const char*)key, key_size);
|
||||
CdmKeySetId key_set_id;
|
||||
|
||||
CdmResponseType response = cdm_engine_.AddKey(session_id_internal,
|
||||
key_data, &key_set_id);
|
||||
|
||||
if (response == KEY_ADDED) {
|
||||
EnablePolicyTimer();
|
||||
return cdm::kSuccess;
|
||||
} else {
|
||||
return cdm::kSessionError;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::CancelKeyRequest(const char* session_id,
|
||||
int session_id_size) {
|
||||
LOGI("Enter WvContentDecryptionModule::CancelKeyRequest()\n");
|
||||
CdmSessionId session_id_internal(session_id, session_id_size);
|
||||
return cdm_engine_.CancelKeyRequest(session_id_internal) == NO_ERROR
|
||||
? cdm::kSuccess
|
||||
: cdm::kSessionError;
|
||||
}
|
||||
|
||||
void WvContentDecryptionModule::TimerExpired(void* context) {
|
||||
LOGI("Timer expired, send cdm_engine OnTimerEvent");
|
||||
if (this != context) {
|
||||
LOGD("Context should have been set, Timer Expired Error\n");
|
||||
return;
|
||||
}
|
||||
OnTimerEvent();
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::Decrypt(
|
||||
const cdm::InputBuffer& encrypted_buffer,
|
||||
cdm::DecryptedBlock* decrypted_block) {
|
||||
LOGI("=>Enter WvContentDecryptionModule::Decrypt()\n");
|
||||
if (encrypted_buffer.iv_size != KEY_IV_SIZE)
|
||||
return cdm::kDecryptError;
|
||||
std::vector < uint8_t > iv(KEY_IV_SIZE);
|
||||
memcpy(&iv[0], encrypted_buffer.iv, encrypted_buffer.iv_size);
|
||||
|
||||
KeyId key_id(reinterpret_cast<const char*>(encrypted_buffer.key_id),
|
||||
encrypted_buffer.key_id_size);
|
||||
|
||||
CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id?
|
||||
|
||||
if (NULL == encrypted_buffer.subsamples
|
||||
|| encrypted_buffer.num_subsamples <= 0)
|
||||
return cdm::kDecryptError;
|
||||
|
||||
CdmDecryptionParameters parameters(&key_id,
|
||||
encrypted_buffer.data, 0, &iv, 0,
|
||||
NULL);
|
||||
parameters.is_secure = false;
|
||||
return DoSubsampleDecrypt(session_id,
|
||||
parameters,
|
||||
iv,
|
||||
encrypted_buffer,
|
||||
decrypted_block);
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::InitializeAudioDecoder(
|
||||
const cdm::AudioDecoderConfig& audio_decoder_config) {
|
||||
LOGI("WvContentDecryptionModule::InitializeAudioDecoder() Not implemented\n");
|
||||
return cdm::kDecodeError;
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::InitializeVideoDecoder(
|
||||
const cdm::VideoDecoderConfig& video_decoder_config) {
|
||||
LOGI("WvContentDecryptionModule::InitializeVideoDecoder() Not implemented\n");
|
||||
return cdm::kDecodeError;
|
||||
}
|
||||
|
||||
void WvContentDecryptionModule::DeinitializeDecoder(
|
||||
cdm::StreamType decoder_type) {
|
||||
LOGI("WvContentDecryptionModule::DeInitializeDecoder() Not implemented\n");
|
||||
}
|
||||
|
||||
void WvContentDecryptionModule::ResetDecoder(cdm::StreamType decoder_type) {
|
||||
LOGI("WvContentDecryptionModule::ResetDecoder() Not implemented\n");
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::DecryptAndDecodeFrame(
|
||||
const cdm::InputBuffer& encrypted_buffer, cdm::VideoFrame* video_frame) {
|
||||
LOGI("WvContentDecryptionModule::DecryptAndDecodeFrame() Not implemented\n");
|
||||
return cdm::kDecodeError;
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::DecryptAndDecodeSamples(
|
||||
const cdm::InputBuffer& encrypted_buffer, cdm::AudioFrames* audio_frames) {
|
||||
LOGI(
|
||||
"WvContentDecryptionModule::DecryptAndDecodeSamples() Not implemented\n");
|
||||
return cdm::kDecodeError;
|
||||
}
|
||||
|
||||
// This is the Level 1 API. When the host application calls the CDM's
|
||||
// DecryptDecodeAndRenderFrame(), rather than the CDM's Decrypt(),
|
||||
// OEMCrypto_DecryptCTR() will be told to use direct rendering with no
|
||||
// cleartext in the return.
|
||||
cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderFrame(
|
||||
const cdm::InputBuffer& encrypted_buffer) {
|
||||
LOGI("WvContentDecryptionModule::DecryptDecodeAndRenderFrame()\n");
|
||||
|
||||
if (encrypted_buffer.iv_size != KEY_IV_SIZE) return cdm::kDecryptError;
|
||||
|
||||
std::vector<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);
|
||||
CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id.
|
||||
|
||||
if (NULL == encrypted_buffer.subsamples
|
||||
|| encrypted_buffer.num_subsamples <= 0)
|
||||
return cdm::kDecryptError;
|
||||
|
||||
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
|
||||
NULL);
|
||||
return DoSubsampleDecrypt(session_id, parameters, iv, encrypted_buffer,
|
||||
NULL);
|
||||
}
|
||||
|
||||
// This is the Level 1 API. When the host application calls the CDM's
|
||||
// DecryptDecodeAndRenderSamples(), rather than the CDM's Decrypt(),
|
||||
// OEMCrypto_DecryptCTR() will be told to use direct rendering with no cleartext
|
||||
// in the return.
|
||||
cdm::Status WvContentDecryptionModule::DecryptDecodeAndRenderSamples(
|
||||
const cdm::InputBuffer& encrypted_buffer) {
|
||||
LOGI("WvContentDecryptionModule::DecryptDecodeAndRenderSamples()\n");
|
||||
|
||||
if (encrypted_buffer.iv_size != KEY_IV_SIZE) return cdm::kDecryptError;
|
||||
|
||||
std::vector<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);
|
||||
CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id.
|
||||
|
||||
if (NULL == encrypted_buffer.subsamples ||
|
||||
encrypted_buffer.num_subsamples <= 0)
|
||||
return cdm::kDecryptError;
|
||||
|
||||
CdmDecryptionParameters parameters(&key_id, encrypted_buffer.data, 0, &iv, 0,
|
||||
NULL);
|
||||
parameters.is_video = false; // override the default true value for audio.
|
||||
return DoSubsampleDecrypt(session_id, parameters, iv, encrypted_buffer,
|
||||
NULL);
|
||||
}
|
||||
|
||||
void WvContentDecryptionModule::Destroy() { delete this; }
|
||||
|
||||
// Provisioning related methods
|
||||
cdm::Status WvContentDecryptionModule::GetProvisioningRequest(
|
||||
std::string* request, std::string* provisioning_server_url) {
|
||||
if (cdm_engine_.GetProvisioningRequest(
|
||||
static_cast<CdmProvisioningRequest*>(request),
|
||||
provisioning_server_url) == NO_ERROR) {
|
||||
return cdm::kSuccess;
|
||||
}
|
||||
return cdm::kSessionError;
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::HandleProvisioningResponse(
|
||||
std::string& response) {
|
||||
if (cdm_engine_.HandleProvisioningResponse(
|
||||
static_cast<CdmProvisioningRequest&>(response)) == NO_ERROR) {
|
||||
return cdm::kSuccess;
|
||||
}
|
||||
return cdm::kSessionError;
|
||||
}
|
||||
|
||||
void WvContentDecryptionModule::EnablePolicyTimer() {
|
||||
LOGI("WvContentDecryptionModule::EnablePolicyTimer()\n");
|
||||
host_->SetTimer(kCdmPolicyTimerDurationSeconds * 1000, this);
|
||||
}
|
||||
|
||||
void WvContentDecryptionModule::DisablePolicyTimer() {
|
||||
LOGI("WvContentDecryptionModule::DisablePolicyTimer()\n");
|
||||
host_->SetTimer(kCdmPolicyTimerCancel, NULL);
|
||||
}
|
||||
|
||||
void WvContentDecryptionModule::OnTimerEvent() {
|
||||
|
||||
LOGI("WvContentDecryptionModule::OnTimerEvent()\n");
|
||||
cdm_engine_.OnTimerEvent();
|
||||
}
|
||||
|
||||
cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt(
|
||||
CdmSessionId& session_id,
|
||||
CdmDecryptionParameters& parameters,
|
||||
std::vector < uint8_t >& iv,
|
||||
const cdm::InputBuffer& encrypted_buffer,
|
||||
cdm::DecryptedBlock* decrypted_block) {
|
||||
|
||||
/* This routine assumes session_id and iv have already
|
||||
been initialized by the caller and encrypted_buffer contains subsample
|
||||
information. Also, parameters is expected to be pre-initialized with any
|
||||
needed parameters not related to subsample parsing.
|
||||
decrypted_block may be NULL. */
|
||||
CdmResponseType status = NO_ERROR;
|
||||
uint8_t* output_buffer = decrypted_block
|
||||
? reinterpret_cast<uint8_t*>(
|
||||
decrypted_block->DecryptedBuffer()->Data())
|
||||
: NULL;
|
||||
size_t offset = 0;
|
||||
size_t encrypted_offset = 0;
|
||||
uint32_t block_ctr = 0;
|
||||
const cdm::SubsampleEntry *subsamples = encrypted_buffer.subsamples;
|
||||
bool first = true;
|
||||
|
||||
for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) {
|
||||
const cdm::SubsampleEntry& subsample = subsamples[i];
|
||||
|
||||
for (int is_encrypted = 0; is_encrypted < 2; ++is_encrypted) {
|
||||
size_t bytes =
|
||||
is_encrypted ? subsample.cipher_bytes : subsample.clear_bytes;
|
||||
if (0 == bytes)
|
||||
continue;
|
||||
if (is_encrypted) {
|
||||
uint32_t counter = encrypted_offset / kIvSize;
|
||||
::Ctr128Add(counter - block_ctr, &iv[0]);
|
||||
block_ctr = counter;
|
||||
}
|
||||
|
||||
parameters.encrypt_buffer = &encrypted_buffer.data[encrypted_buffer
|
||||
.data_offset + offset];
|
||||
if (output_buffer)
|
||||
parameters.decrypt_buffer = &output_buffer[offset];
|
||||
|
||||
parameters.encrypt_length = bytes;
|
||||
parameters.decrypt_buffer_length = encrypted_buffer.data_size - offset;
|
||||
parameters.block_offset = encrypted_offset % kIvSize;
|
||||
|
||||
offset += bytes;
|
||||
if (is_encrypted)
|
||||
encrypted_offset += bytes;
|
||||
|
||||
parameters.is_encrypted = is_encrypted;
|
||||
parameters.subsample_flags =
|
||||
(true == first) ? OEMCrypto_FirstSubsample : 0;
|
||||
parameters.subsample_flags |= (
|
||||
offset == encrypted_buffer.data_size ? OEMCrypto_LastSubsample : 0);
|
||||
|
||||
first = false;
|
||||
|
||||
status = cdm_engine_.Decrypt(session_id, parameters);
|
||||
|
||||
switch (status) {
|
||||
case wvcdm::NEED_KEY:
|
||||
return cdm::kNoKey;
|
||||
break;
|
||||
case wvcdm::NO_ERROR:
|
||||
break;
|
||||
default:
|
||||
return cdm::kDecryptError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cdm::kSuccess;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
1595
cdm/test/cdm_api_test.cpp
Normal file
1595
cdm/test/cdm_api_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
31
cdm/test/gmock.gyp
Normal file
31
cdm/test/gmock.gyp
Normal file
@@ -0,0 +1,31 @@
|
||||
# Copyright 2013 Google Inc. All Rights Reserved.
|
||||
{
|
||||
'target_defaults': {
|
||||
'type': 'static_library',
|
||||
'include_dirs': [
|
||||
'../../third_party/gmock',
|
||||
'../../third_party/gmock/include',
|
||||
'../../third_party/gmock/gtest/include',
|
||||
],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [
|
||||
'../../third_party/gmock/include',
|
||||
'../../third_party/gmock/gtest/include',
|
||||
],
|
||||
},
|
||||
},
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'gmock',
|
||||
'sources': [
|
||||
'../../third_party/gmock/src/gmock-all.cc',
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'gmock_main',
|
||||
'sources': [
|
||||
'../../third_party/gmock/src/gmock_main.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
16
cdm/test/gtest.gyp
Normal file
16
cdm/test/gtest.gyp
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright 2013 Google Inc. All Rights Reserved.
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'gtest',
|
||||
'type': 'static_library',
|
||||
'include_dirs': [
|
||||
'../../third_party/gmock/gtest',
|
||||
'../../third_party/gmock/gtest/include',
|
||||
],
|
||||
'sources': [
|
||||
'../../third_party/gmock/gtest/src/gtest-all.cc',
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
68
core/include/buffer_reader.h
Normal file
68
core/include/buffer_reader.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_BUFFER_READER_H_
|
||||
#define WVCDM_CORE_BUFFER_READER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Annotate a function indicating the caller must examine the return value.
|
||||
// Use like:
|
||||
// int foo() WARN_UNUSED_RESULT;
|
||||
// To explicitly ignore a result, see |ignore_result()| in <base/basictypes.h>.
|
||||
#if defined(COMPILER_GCC)
|
||||
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#else
|
||||
#define WARN_UNUSED_RESULT
|
||||
#endif
|
||||
|
||||
class BufferReader {
|
||||
public:
|
||||
BufferReader(const uint8_t* buf, size_t size)
|
||||
: buf_(buf), size_(size), pos_(0) {}
|
||||
|
||||
bool HasBytes(int count) { return (pos() + count <= size()); }
|
||||
|
||||
// Read a value from the stream, performing endian correction,
|
||||
// and advance the stream pointer.
|
||||
bool Read1(uint8_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read2(uint16_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read2s(int16_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4(uint32_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4s(int32_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read8(uint64_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read8s(int64_t* v) WARN_UNUSED_RESULT;
|
||||
|
||||
bool ReadString(std::string* str, int count) WARN_UNUSED_RESULT;
|
||||
bool ReadVec(std::vector<uint8_t>* t, int count) WARN_UNUSED_RESULT;
|
||||
|
||||
// These variants read a 4-byte integer of the corresponding signedness and
|
||||
// store it in the 8-byte return type.
|
||||
bool Read4Into8(uint64_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4sInto8s(int64_t* v) WARN_UNUSED_RESULT;
|
||||
|
||||
// Advance the stream by this many bytes.
|
||||
bool SkipBytes(int nbytes) WARN_UNUSED_RESULT;
|
||||
|
||||
const uint8_t* data() const { return buf_; }
|
||||
size_t size() const { return size_; }
|
||||
size_t pos() const { return pos_; }
|
||||
|
||||
protected:
|
||||
const uint8_t* buf_;
|
||||
size_t size_;
|
||||
size_t pos_;
|
||||
|
||||
template<typename T> bool Read(T* t) WARN_UNUSED_RESULT;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(BufferReader);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_BUFFER_READER_H_
|
||||
26
core/include/cdm_client_property_set.h
Normal file
26
core/include/cdm_client_property_set.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_
|
||||
#define WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmClientPropertySet {
|
||||
public:
|
||||
virtual ~CdmClientPropertySet() {}
|
||||
|
||||
virtual std::string security_level() const = 0;
|
||||
virtual bool use_privacy_mode() const = 0;
|
||||
virtual std::vector<uint8_t> service_certificate() const = 0;
|
||||
virtual bool is_session_sharing_enabled() const = 0;
|
||||
virtual uint32_t session_sharing_id() const = 0;
|
||||
virtual void set_session_sharing_id(uint32_t id) = 0;
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_CDM_CLIENT_PROPERTY_SET_H_
|
||||
124
core/include/cdm_engine.h
Normal file
124
core/include/cdm_engine.h
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_CDM_ENGINE_H_
|
||||
#define WVCDM_CORE_CDM_ENGINE_H_
|
||||
|
||||
#include "certificate_provisioning.h"
|
||||
#include "oemcrypto_adapter.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmClientPropertySet;
|
||||
class CdmSession;
|
||||
class CryptoEngine;
|
||||
class WvCdmEventListener;
|
||||
|
||||
typedef std::map<CdmSessionId, CdmSession*> CdmSessionMap;
|
||||
typedef std::map<CdmKeySetId, CdmSessionId> CdmReleaseKeySetMap;
|
||||
|
||||
class CdmEngine {
|
||||
public:
|
||||
CdmEngine();
|
||||
virtual ~CdmEngine();
|
||||
|
||||
// Session related methods
|
||||
CdmResponseType OpenSession(const CdmKeySystem& key_system,
|
||||
const CdmClientPropertySet* property_set,
|
||||
CdmSessionId* session_id);
|
||||
CdmResponseType CloseSession(const CdmSessionId& session_id);
|
||||
|
||||
CdmResponseType OpenKeySetSession(const CdmKeySetId& key_set_id);
|
||||
CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id);
|
||||
|
||||
// License related methods
|
||||
// Construct a valid license request
|
||||
CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id,
|
||||
const CdmKeySetId& key_set_id,
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// Accept license response and extract key info.
|
||||
CdmResponseType AddKey(const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data,
|
||||
CdmKeySetId* key_set_id);
|
||||
|
||||
CdmResponseType RestoreKey(const CdmSessionId& session_id,
|
||||
const CdmKeySetId& key_set_id);
|
||||
|
||||
CdmResponseType CancelKeyRequest(const CdmSessionId& session_id);
|
||||
|
||||
// Construct valid renewal request for the current session keys.
|
||||
CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// Accept renewal response and update key info.
|
||||
CdmResponseType RenewKey(const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data);
|
||||
|
||||
// Query system information
|
||||
CdmResponseType QueryStatus(CdmQueryMap* info);
|
||||
|
||||
// Query session information
|
||||
virtual CdmResponseType QuerySessionStatus(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info);
|
||||
|
||||
// Query license information
|
||||
CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info);
|
||||
|
||||
// Query seesion control information
|
||||
CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info);
|
||||
|
||||
// Provisioning related methods
|
||||
CdmResponseType GetProvisioningRequest(CdmProvisioningRequest* request,
|
||||
std::string* default_url);
|
||||
|
||||
CdmResponseType HandleProvisioningResponse(CdmProvisioningResponse& response);
|
||||
|
||||
// Decryption and key related methods
|
||||
// Accept encrypted buffer and return decrypted data.
|
||||
CdmResponseType Decrypt(const CdmSessionId& session_id,
|
||||
const CdmDecryptionParameters& parameters);
|
||||
|
||||
// Is the key known to any session?
|
||||
bool IsKeyLoaded(const KeyId& key_id);
|
||||
bool FindSessionForKey(const KeyId& key_id, CdmSessionId* sessionId);
|
||||
|
||||
// Event listener related methods
|
||||
bool AttachEventListener(const CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
bool DetachEventListener(const CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
|
||||
// Parse a blob of multiple concatenated PSSH atoms to extract the first
|
||||
// widevine pssh
|
||||
static bool ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
CdmInitData* output);
|
||||
|
||||
// Timer expiration method
|
||||
void OnTimerEvent();
|
||||
|
||||
private:
|
||||
// private methods
|
||||
bool ValidateKeySystem(const CdmKeySystem& key_system);
|
||||
|
||||
void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
|
||||
|
||||
// instance variables
|
||||
CdmSessionMap sessions_;
|
||||
CdmReleaseKeySetMap release_key_sets_;
|
||||
CertificateProvisioning cert_provisioning_;
|
||||
SecurityLevel cert_provisioning_requested_security_level_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_CDM_ENGINE_H_
|
||||
130
core/include/cdm_session.h
Normal file
130
core/include/cdm_session.h
Normal file
@@ -0,0 +1,130 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_CDM_SESSION_H_
|
||||
#define WVCDM_CORE_CDM_SESSION_H_
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "crypto_session.h"
|
||||
#include "device_files.h"
|
||||
#include "license.h"
|
||||
#include "oemcrypto_adapter.h"
|
||||
#include "policy_engine.h"
|
||||
#include "scoped_ptr.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmClientPropertySet;
|
||||
class WvCdmEventListener;
|
||||
|
||||
class CdmSession {
|
||||
public:
|
||||
explicit CdmSession(const CdmClientPropertySet* cdm_client_property_set);
|
||||
~CdmSession();
|
||||
|
||||
CdmResponseType Init();
|
||||
|
||||
CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id,
|
||||
const CdmLicenseType license_type);
|
||||
|
||||
void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; }
|
||||
const CdmKeySystem& key_system() { return key_system_; }
|
||||
|
||||
const CdmSessionId& session_id() { return session_id_; }
|
||||
|
||||
CdmResponseType GenerateKeyRequest(const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
const CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// AddKey() - Accept license response and extract key info.
|
||||
CdmResponseType AddKey(const CdmKeyResponse& key_response,
|
||||
CdmKeySetId* key_set_id);
|
||||
|
||||
// CancelKeyRequest() - Cancel session.
|
||||
CdmResponseType CancelKeyRequest();
|
||||
|
||||
// Query session status
|
||||
CdmResponseType QueryStatus(CdmQueryMap* key_info);
|
||||
|
||||
// Query license information
|
||||
CdmResponseType QueryKeyStatus(CdmQueryMap* key_info);
|
||||
|
||||
// Query session control info
|
||||
CdmResponseType QueryKeyControlInfo(CdmQueryMap* key_info);
|
||||
|
||||
// Decrypt() - Accept encrypted buffer and return decrypted data.
|
||||
CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
|
||||
|
||||
// License renewal
|
||||
// GenerateRenewalRequest() - Construct valid renewal request for the current
|
||||
// session keys.
|
||||
CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// RenewKey() - Accept renewal response and update key info.
|
||||
CdmResponseType RenewKey(const CdmKeyResponse& key_response);
|
||||
|
||||
// License release
|
||||
// GenerateReleaseRequest() - Construct valid release request for the current
|
||||
// session keys.
|
||||
CdmResponseType GenerateReleaseRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// ReleaseKey() - Accept response and release key.
|
||||
CdmResponseType ReleaseKey(const CdmKeyResponse& key_response);
|
||||
|
||||
bool IsKeyLoaded(const KeyId& key_id);
|
||||
|
||||
bool AttachEventListener(WvCdmEventListener* listener);
|
||||
bool DetachEventListener(WvCdmEventListener* listener);
|
||||
|
||||
void OnTimerEvent();
|
||||
void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
|
||||
|
||||
SecurityLevel GetRequestedSecurityLevel();
|
||||
|
||||
private:
|
||||
|
||||
// Generate unique ID for each new session.
|
||||
CdmSessionId GenerateSessionId();
|
||||
bool GenerateKeySetId(CdmKeySetId* key_set_id);
|
||||
|
||||
bool StoreLicense(DeviceFiles::LicenseState state);
|
||||
|
||||
// instance variables
|
||||
const CdmSessionId session_id_;
|
||||
CdmKeySystem key_system_;
|
||||
CdmLicense license_parser_;
|
||||
scoped_ptr<CryptoSession> crypto_session_;
|
||||
PolicyEngine policy_engine_;
|
||||
bool license_received_;
|
||||
bool reinitialize_session_;
|
||||
|
||||
CdmLicenseType license_type_;
|
||||
|
||||
// license type offline related information
|
||||
CdmInitData offline_pssh_data_;
|
||||
CdmKeyMessage offline_key_request_;
|
||||
CdmKeyResponse offline_key_response_;
|
||||
CdmKeyMessage offline_key_renewal_request_;
|
||||
CdmKeyResponse offline_key_renewal_response_;
|
||||
std::string offline_release_server_url_;
|
||||
|
||||
// license type release and offline related information
|
||||
CdmKeySetId key_set_id_;
|
||||
|
||||
// Used for certificate based licensing
|
||||
std::string wrapped_key_;
|
||||
bool is_certificate_loaded_;
|
||||
|
||||
std::set<WvCdmEventListener*> listeners_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_CDM_SESSION_H_
|
||||
38
core/include/certificate_provisioning.h
Normal file
38
core/include/certificate_provisioning.h
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_CERTIFICATE_PROVISIONING_H_
|
||||
#define WVCDM_CORE_CERTIFICATE_PROVISIONING_H_
|
||||
|
||||
#include "crypto_session.h"
|
||||
#include "oemcrypto_adapter.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmSession;
|
||||
|
||||
class CertificateProvisioning {
|
||||
public:
|
||||
CertificateProvisioning() {};
|
||||
~CertificateProvisioning() {};
|
||||
|
||||
// Provisioning related methods
|
||||
CdmResponseType GetProvisioningRequest(SecurityLevel requested_security_level,
|
||||
CdmProvisioningRequest* request,
|
||||
std::string* default_url);
|
||||
CdmResponseType HandleProvisioningResponse(CdmProvisioningResponse& response);
|
||||
|
||||
private:
|
||||
void ComposeJsonRequestAsQueryString(const std::string& message,
|
||||
CdmProvisioningRequest* request);
|
||||
bool ParseJsonResponse(const CdmProvisioningResponse& json_str,
|
||||
const std::string& start_substr,
|
||||
const std::string& end_substr,
|
||||
std::string* result);
|
||||
CryptoSession crypto_session_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CertificateProvisioning);
|
||||
};
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_CERTIFICATE_PROVISIONING_H_
|
||||
25
core/include/clock.h
Normal file
25
core/include/clock.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Clock - Platform independent interface for a time library
|
||||
//
|
||||
#ifndef WVCDM_CORE_CLOCK_H_
|
||||
#define WVCDM_CORE_CLOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides time related information. The implementation is platform dependent.
|
||||
class Clock {
|
||||
|
||||
public:
|
||||
Clock() {}
|
||||
virtual ~Clock() {}
|
||||
|
||||
// Provides the number of seconds since an epoch - 01/01/1970 00:00 UTC
|
||||
virtual int64_t GetCurrentTime();
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_CLOCK_H_
|
||||
40
core/include/crypto_key.h
Normal file
40
core/include/crypto_key.h
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_CRYPTO_KEY_H_
|
||||
#define WVCDM_CORE_CRYPTO_KEY_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CryptoKey {
|
||||
public:
|
||||
CryptoKey() {};
|
||||
~CryptoKey() {};
|
||||
|
||||
const std::string& key_id() const { return key_id_; }
|
||||
const std::string& key_data() const { return key_data_; }
|
||||
const std::string& key_data_iv() const { return key_data_iv_; }
|
||||
const std::string& key_control() const { return key_control_; }
|
||||
const std::string& key_control_iv() const { return key_control_iv_; }
|
||||
void set_key_id(const std::string& key_id) { key_id_ = key_id; }
|
||||
void set_key_data(const std::string& key_data) { key_data_ = key_data; }
|
||||
void set_key_data_iv(const std::string& iv) { key_data_iv_ = iv; }
|
||||
void set_key_control(const std::string& ctl) { key_control_ = ctl; }
|
||||
void set_key_control_iv(const std::string& ctl_iv) {
|
||||
key_control_iv_ = ctl_iv;
|
||||
}
|
||||
|
||||
bool HasKeyControl() const { return key_control_.size() >= 16; }
|
||||
|
||||
private:
|
||||
std::string key_id_;
|
||||
std::string key_data_iv_;
|
||||
std::string key_data_;
|
||||
std::string key_control_;
|
||||
std::string key_control_iv_;
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_CRYPTO_KEY_H_
|
||||
104
core/include/crypto_session.h
Normal file
104
core/include/crypto_session.h
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_CRYPTO_SESSSION_H_
|
||||
#define WVCDM_CORE_CRYPTO_SESSSION_H_
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "lock.h"
|
||||
#include "oemcrypto_adapter.h"
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CryptoKey;
|
||||
typedef std::map<CryptoKeyId, CryptoKey*> CryptoKeyMap;
|
||||
|
||||
class CryptoSession {
|
||||
public:
|
||||
CryptoSession();
|
||||
~CryptoSession();
|
||||
|
||||
bool ValidateKeybox();
|
||||
bool GetToken(std::string* token);
|
||||
CdmSecurityLevel GetSecurityLevel();
|
||||
bool GetDeviceUniqueId(std::string* device_id);
|
||||
bool GetSystemId(uint32_t* system_id);
|
||||
bool GetProvisioningId(std::string* provisioning_id);
|
||||
|
||||
CdmResponseType Open() { return Open(kLevelDefault); }
|
||||
CdmResponseType Open(SecurityLevel requested_security_level);
|
||||
void Close();
|
||||
|
||||
bool IsOpen() { return open_; }
|
||||
CryptoSessionId oec_session_id() { return oec_session_id_; }
|
||||
|
||||
// Key request/response
|
||||
void GenerateRequestId(std::string& req_id_str);
|
||||
bool PrepareRequest(const std::string& key_deriv_message,
|
||||
bool is_provisioning, std::string* signature);
|
||||
bool PrepareRenewalRequest(const std::string& message,
|
||||
std::string* signature);
|
||||
CdmResponseType LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
int num_keys, const CryptoKey* key_array);
|
||||
bool LoadCertificatePrivateKey(std::string& wrapped_key);
|
||||
bool RefreshKeys(const std::string& message, const std::string& signature,
|
||||
int num_keys, const CryptoKey* key_array);
|
||||
bool GenerateNonce(uint32_t* nonce);
|
||||
bool GenerateDerivedKeys(const std::string& message);
|
||||
bool GenerateDerivedKeys(const std::string& message,
|
||||
const std::string& session_key);
|
||||
bool RewrapDeviceRSAKey(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& nonce,
|
||||
const std::string& enc_rsa_key,
|
||||
const std::string& rsa_key_iv,
|
||||
std::string* wrapped_rsa_key);
|
||||
|
||||
// Media data path
|
||||
CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
|
||||
|
||||
bool GetRandom(size_t data_length, uint8_t* random_data);
|
||||
|
||||
private:
|
||||
void Init();
|
||||
void Terminate();
|
||||
void GenerateMacContext(const std::string& input_context,
|
||||
std::string* deriv_context);
|
||||
void GenerateEncryptContext(const std::string& input_context,
|
||||
std::string* deriv_context);
|
||||
bool GenerateSignature(const std::string& message, bool use_rsa,
|
||||
std::string* signature);
|
||||
size_t GetOffset(std::string message, std::string field);
|
||||
bool SetDestinationBufferType();
|
||||
|
||||
bool SelectKey(const std::string& key_id);
|
||||
|
||||
static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature
|
||||
static Lock crypto_lock_;
|
||||
static bool initialized_;
|
||||
static int session_count_;
|
||||
|
||||
bool open_;
|
||||
CryptoSessionId oec_session_id_;
|
||||
|
||||
OEMCryptoBufferType destination_buffer_type_;
|
||||
bool is_destination_buffer_type_valid_;
|
||||
SecurityLevel requested_security_level_;
|
||||
|
||||
KeyId key_id_;
|
||||
|
||||
uint64_t request_id_base_;
|
||||
static uint64_t request_id_index_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_CRYPTO_SESSSION_H_
|
||||
76
core/include/device_files.h
Normal file
76
core/include/device_files.h
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
#ifndef WVCDM_CORE_DEVICE_FILES_H_
|
||||
#define WVCDM_CORE_DEVICE_FILES_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class File;
|
||||
|
||||
class DeviceFiles {
|
||||
public:
|
||||
typedef enum {
|
||||
kLicenseStateActive,
|
||||
kLicenseStateReleasing,
|
||||
kLicenseStateUnknown,
|
||||
} LicenseState;
|
||||
|
||||
DeviceFiles(): file_(NULL), security_level_(kSecurityLevelUninitialized),
|
||||
initialized_(false) {}
|
||||
virtual ~DeviceFiles() {}
|
||||
|
||||
virtual bool Init(const File* handle, CdmSecurityLevel security_level);
|
||||
|
||||
virtual bool StoreCertificate(const std::string& certificate,
|
||||
const std::string& wrapped_private_key);
|
||||
virtual bool RetrieveCertificate(std::string* certificate,
|
||||
std::string* wrapped_private_key);
|
||||
|
||||
virtual bool StoreLicense(const std::string& key_set_id,
|
||||
const LicenseState state,
|
||||
const CdmInitData& pssh_data,
|
||||
const CdmKeyMessage& key_request,
|
||||
const CdmKeyResponse& key_response,
|
||||
const CdmKeyMessage& key_renewal_request,
|
||||
const CdmKeyResponse& key_renewal_response,
|
||||
const std::string& release_server_url);
|
||||
virtual bool RetrieveLicense(const std::string& key_set_id,
|
||||
LicenseState* state,
|
||||
CdmInitData* pssh_data,
|
||||
CdmKeyMessage* key_request,
|
||||
CdmKeyResponse* key_response,
|
||||
CdmKeyMessage* key_renewal_request,
|
||||
CdmKeyResponse* key_renewal_response,
|
||||
std::string* release_server_url);
|
||||
virtual bool DeleteLicense(const std::string& key_set_id);
|
||||
virtual bool DeleteAllFiles();
|
||||
virtual bool DeleteAllLicenses();
|
||||
virtual bool LicenseExists(const std::string& key_set_id);
|
||||
|
||||
// For testing only
|
||||
static std::string GetCertificateFileName();
|
||||
static std::string GetLicenseFileNameExtension();
|
||||
|
||||
protected:
|
||||
bool Hash(const std::string& data, std::string* hash);
|
||||
bool StoreFile(const char* name, const std::string& data);
|
||||
bool RetrieveFile(const char* name, std::string* data);
|
||||
|
||||
private:
|
||||
// Certificate and offline licenses are now stored in security
|
||||
// level specific directories. In an earlier version they were
|
||||
// stored in a common directory and need to be copied over.
|
||||
virtual void SecurityLevelPathBackwardCompatibility();
|
||||
|
||||
File* file_;
|
||||
CdmSecurityLevel security_level_;
|
||||
bool initialized_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_DEVICE_FILES_H_
|
||||
51
core/include/file_store.h
Normal file
51
core/include/file_store.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// File - Platform independent interface for a File class
|
||||
//
|
||||
#ifndef WVCDM_CORE_FILE_STORE_H_
|
||||
#define WVCDM_CORE_FILE_STORE_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// File class. The implementation is platform dependent.
|
||||
class File {
|
||||
public:
|
||||
class Impl;
|
||||
|
||||
// defines as bit flag
|
||||
enum OpenFlags {
|
||||
kNoFlags = 0,
|
||||
kBinary = 1,
|
||||
kCreate = 2,
|
||||
kReadOnly = 4, // defaults to read and write access
|
||||
kTruncate = 8
|
||||
};
|
||||
|
||||
File();
|
||||
virtual ~File();
|
||||
|
||||
virtual bool Open(const std::string& file_path, int flags);
|
||||
virtual ssize_t Read(char* buffer, size_t bytes);
|
||||
virtual ssize_t Write(const char* buffer, size_t bytes);
|
||||
virtual void Close();
|
||||
|
||||
virtual bool Exists(const std::string& file_path);
|
||||
virtual bool Remove(const std::string& file_path);
|
||||
virtual bool Copy(const std::string& old_path, const std::string& new_path);
|
||||
virtual bool List(const std::string& path, std::vector<std::string>* files);
|
||||
virtual bool CreateDirectory(const std::string dir_path);
|
||||
virtual bool IsDirectory(const std::string& dir_path);
|
||||
virtual bool IsRegularFile(const std::string& file_path);
|
||||
virtual ssize_t FileSize(const std::string& file_path);
|
||||
|
||||
private:
|
||||
Impl *impl_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(File);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_FILE_STORE_H_
|
||||
74
core/include/license.h
Normal file
74
core/include/license.h
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_LICENSE_H_
|
||||
#define WVCDM_CORE_LICENSE_H_
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace video_widevine_server {
|
||||
namespace sdk {
|
||||
class SignedMessage;
|
||||
}
|
||||
} // namespace video_widevine_server
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CryptoSession;
|
||||
class PolicyEngine;
|
||||
|
||||
class CdmLicense {
|
||||
public:
|
||||
|
||||
CdmLicense() : session_(NULL), initialized_(false) {}
|
||||
~CdmLicense() {}
|
||||
|
||||
bool Init(const std::string& token, CryptoSession* session,
|
||||
PolicyEngine* policy_engine);
|
||||
|
||||
bool PrepareKeyRequest(const CdmInitData& pssh_data,
|
||||
const CdmLicenseType license_type,
|
||||
const CdmAppParameterMap& app_parameters,
|
||||
const CdmSessionId& session_id,
|
||||
CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
bool PrepareKeyUpdateRequest(bool is_renewal, CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response);
|
||||
CdmResponseType HandleKeyUpdateResponse(
|
||||
bool is_renewal, const CdmKeyResponse& license_response);
|
||||
|
||||
bool RestoreOfflineLicense(CdmKeyMessage& license_request,
|
||||
CdmKeyResponse& license_response,
|
||||
CdmKeyResponse& license_renewal_response);
|
||||
bool HasInitData() { return !init_data_.empty(); }
|
||||
bool IsKeyLoaded(const KeyId& key_id);
|
||||
|
||||
private:
|
||||
bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
CdmResponseType HandleServiceCertificateResponse(
|
||||
const video_widevine_server::sdk::SignedMessage& signed_message);
|
||||
|
||||
CdmResponseType HandleKeyErrorResponse(
|
||||
const video_widevine_server::sdk::SignedMessage& signed_message);
|
||||
|
||||
CryptoSession* session_;
|
||||
PolicyEngine* policy_engine_;
|
||||
std::string server_url_;
|
||||
std::string token_;
|
||||
std::string service_certificate_;
|
||||
std::string init_data_;
|
||||
bool initialized_;
|
||||
std::set<KeyId> loaded_keys_;
|
||||
|
||||
// Used for certificate based licensing
|
||||
CdmKeyMessage key_request_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmLicense);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_LICENSE_H_
|
||||
61
core/include/lock.h
Normal file
61
core/include/lock.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Lock - Platform independent interface for a Mutex class
|
||||
//
|
||||
#ifndef WVCDM_CORE_LOCK_H_
|
||||
#define WVCDM_CORE_LOCK_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Simple lock class. The implementation is platform dependent.
|
||||
//
|
||||
// The lock must be unlocked by the thread that locked it.
|
||||
// The lock is also not recursive (ie. cannot be taken multiple times).
|
||||
class Lock {
|
||||
public:
|
||||
Lock();
|
||||
~Lock();
|
||||
|
||||
void Acquire();
|
||||
void Release();
|
||||
|
||||
// Acquires a lock if not held and returns true.
|
||||
// Returns false if the lock is held by another thread.
|
||||
bool Try();
|
||||
|
||||
friend class AutoLock;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl *impl_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(Lock);
|
||||
};
|
||||
|
||||
// Manages the lock automatically. It will be locked when AutoLock
|
||||
// is constructed and release when AutoLock goes out of scope.
|
||||
class AutoLock {
|
||||
public:
|
||||
explicit AutoLock(Lock& lock) : lock_(&lock) {
|
||||
lock_->Acquire();
|
||||
}
|
||||
|
||||
explicit AutoLock(Lock* lock) : lock_(lock) {
|
||||
lock_->Acquire();
|
||||
}
|
||||
|
||||
~AutoLock() {
|
||||
lock_->Release();
|
||||
}
|
||||
|
||||
private:
|
||||
Lock *lock_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(AutoLock);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_LOCK_H_
|
||||
37
core/include/log.h
Normal file
37
core/include/log.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Log - Platform independent interface for a Logging class
|
||||
//
|
||||
#ifndef WVCDM_CORE_LOG_H_
|
||||
#define WVCDM_CORE_LOG_H_
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Simple logging class. The implementation is platform dependent.
|
||||
|
||||
typedef enum {
|
||||
LOG_ERROR,
|
||||
LOG_WARN,
|
||||
LOG_INFO,
|
||||
LOG_DEBUG,
|
||||
LOG_VERBOSE
|
||||
} LogPriority;
|
||||
|
||||
// Enable/disable verbose logging (LOGV).
|
||||
// This function is supplied for cases where the system layer does not
|
||||
// initialize logging. This is also needed to initialize logging in
|
||||
// unit tests.
|
||||
void InitLogging(int argc, const char* const* argv);
|
||||
|
||||
void Log(const char* file, int line, LogPriority level, const char* fmt, ...);
|
||||
|
||||
// Log APIs
|
||||
#define LOGE(...) Log(__FILE__, __LINE__, wvcdm::LOG_ERROR, __VA_ARGS__)
|
||||
#define LOGW(...) Log(__FILE__, __LINE__, wvcdm::LOG_WARN, __VA_ARGS__)
|
||||
#define LOGI(...) Log(__FILE__, __LINE__, wvcdm::LOG_INFO, __VA_ARGS__)
|
||||
#define LOGD(...) Log(__FILE__, __LINE__, wvcdm::LOG_DEBUG, __VA_ARGS__)
|
||||
#define LOGV(...) Log(__FILE__, __LINE__, wvcdm::LOG_VERBOSE, __VA_ARGS__)
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_LOG_H_
|
||||
31
core/include/oemcrypto_adapter.h
Normal file
31
core/include/oemcrypto_adapter.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
#ifndef WVCDM_CORE_OEMCRYPTO_ADAPTER_H_
|
||||
#define WVCDM_CORE_OEMCRYPTO_ADAPTER_H_
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
enum SecurityLevel {
|
||||
kLevelDefault,
|
||||
kLevel3
|
||||
};
|
||||
|
||||
/* This attempts to open a session at the desired security level.
|
||||
If one level is not available, the other will be used instead. */
|
||||
OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session,
|
||||
SecurityLevel level);
|
||||
OEMCryptoResult OEMCrypto_IsKeyboxValid(SecurityLevel level);
|
||||
OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength,
|
||||
SecurityLevel level);
|
||||
OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength,
|
||||
SecurityLevel level);
|
||||
OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox,
|
||||
size_t keyBoxLength,
|
||||
SecurityLevel level);
|
||||
uint32_t OEMCrypto_APIVersion(SecurityLevel level);
|
||||
const char* OEMCrypto_SecurityLevel(SecurityLevel level);
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_OEMCRYPTO_ADAPTER_H_
|
||||
121
core/include/policy_engine.h
Normal file
121
core/include/policy_engine.h
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_POLICY_ENGINE_H_
|
||||
#define WVCDM_CORE_POLICY_ENGINE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "license_protocol.pb.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class Clock;
|
||||
class PolicyEngineTest;
|
||||
|
||||
// This acts as an oracle that basically says "Yes(true) you may still decrypt
|
||||
// or no(false) you may not decrypt this data anymore."
|
||||
class PolicyEngine {
|
||||
public:
|
||||
PolicyEngine();
|
||||
~PolicyEngine();
|
||||
|
||||
// The value returned should be taken as a hint rather than an absolute
|
||||
// status. It is computed during the last call to either SetLicense/
|
||||
// UpdateLicense/OnTimerEvent/BeginDecryption and may be out of sync
|
||||
// depending on the amount of time elapsed. The current decryption
|
||||
// status is not calculated to avoid overhead in the decryption path.
|
||||
inline bool can_decrypt() { return can_decrypt_; }
|
||||
|
||||
// OnTimerEvent is called when a timer fires. It notifies the Policy Engine
|
||||
// that the timer has fired and that it should check whether any events have
|
||||
// occurred since the last timer event. If so, it sets event_occurred to true
|
||||
// and sets event to point to the event that occurred. If not, it sets
|
||||
// event_occurred to false.
|
||||
void OnTimerEvent(bool* event_occurred, CdmEventType* event);
|
||||
|
||||
// SetLicense is used in handling the initial license response. It stores
|
||||
// an exact copy of the policy information stored in the license.
|
||||
// The license state transitions to kLicenseStateCanPlay if the license
|
||||
// permits playback.
|
||||
void SetLicense(const video_widevine_server::sdk::License& license);
|
||||
|
||||
// Call this on first decrypt to set the start of playback. This is
|
||||
// for cases where usage begins not when the license is received,
|
||||
// but at the start of playback
|
||||
void BeginDecryption(void);
|
||||
|
||||
// UpdateLicense is used in handling a license response for a renewal request.
|
||||
// The response may only contain any policy fields that have changed. In this
|
||||
// case an exact copy is not what we want to happen. We also will receive an
|
||||
// updated license_start_time from the server. The license will transition to
|
||||
// kLicenseStateCanPlay if the license permits playback.
|
||||
void UpdateLicense(const video_widevine_server::sdk::License& license);
|
||||
|
||||
CdmResponseType Query(CdmQueryMap* key_info);
|
||||
|
||||
const video_widevine_server::sdk::LicenseIdentification& license_id() {
|
||||
return license_id_;
|
||||
}
|
||||
|
||||
bool IsLicenseDurationExpired(int64_t current_time);
|
||||
bool IsPlaybackDurationExpired(int64_t current_time);
|
||||
|
||||
private:
|
||||
typedef enum {
|
||||
kLicenseStateInitial,
|
||||
kLicenseStateInitialPendingUsage,
|
||||
kLicenseStateCanPlay,
|
||||
kLicenseStateNeedRenewal,
|
||||
kLicenseStateWaitingLicenseUpdate,
|
||||
kLicenseStateExpired
|
||||
} LicenseState;
|
||||
|
||||
void Init(Clock* clock);
|
||||
|
||||
bool IsRenewalDelayExpired(int64_t current_time);
|
||||
bool IsRenewalRecoveryDurationExpired(int64_t current_time);
|
||||
bool IsRenewalRetryIntervalExpired(int64_t current_time);
|
||||
|
||||
void UpdateRenewalRequest(int64_t current_time);
|
||||
|
||||
LicenseState license_state_;
|
||||
bool can_decrypt_;
|
||||
|
||||
// This is the current policy information for this license. This gets updated
|
||||
// as license renewals occur.
|
||||
video_widevine_server::sdk::License::Policy policy_;
|
||||
|
||||
// This is the license id field from server response. This data gets passed
|
||||
// back to the server in each renewal request. When we get a renewal response
|
||||
// from the license server we will get an updated id field.
|
||||
video_widevine_server::sdk::LicenseIdentification license_id_;
|
||||
|
||||
// This is the license start time that gets sent from the server in each
|
||||
// license request or renewal.
|
||||
int64_t license_start_time_;
|
||||
|
||||
// This is the time at which the license was received and playback was
|
||||
// started. These times are based off the local clock in case there is a
|
||||
// discrepency between local and server time.
|
||||
int64_t license_received_time_;
|
||||
int64_t playback_start_time_;
|
||||
|
||||
// This is used as a reference point for policy management. This value
|
||||
// represents an offset from license_received_time_. This is used to
|
||||
// calculate the time where renewal retries should occur.
|
||||
int64_t next_renewal_time_;
|
||||
int64_t policy_max_duration_seconds_;
|
||||
|
||||
Clock* clock_;
|
||||
|
||||
// For testing
|
||||
friend class PolicyEngineTest;
|
||||
PolicyEngine(Clock* clock);
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine);
|
||||
};
|
||||
|
||||
} // wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_POLICY_ENGINE_H_
|
||||
74
core/include/privacy_crypto.h
Normal file
74
core/include/privacy_crypto.h
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Declaration of classes representing AES and RSA public keys used
|
||||
// for signature verification and encryption.
|
||||
//
|
||||
// AES encryption details:
|
||||
// Algorithm: AES-CBC
|
||||
//
|
||||
// RSA signature details:
|
||||
// Algorithm: RSASSA-PSS
|
||||
// Hash algorithm: SHA1
|
||||
// Mask generation function: mgf1SHA1
|
||||
// Salt length: 20 bytes
|
||||
// Trailer field: 0xbc
|
||||
//
|
||||
// RSA encryption details:
|
||||
// Algorithm: RSA-OAEP
|
||||
// Mask generation function: mgf1SHA1
|
||||
// Label (encoding paramter): empty string
|
||||
//
|
||||
#ifndef WVCDM_CORE_PRIVACY_CRYPTO_H_
|
||||
#define WVCDM_CORE_PRIVACY_CRYPTO_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "openssl/evp.h"
|
||||
#include "openssl/rsa.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class AesCbcKey {
|
||||
public:
|
||||
AesCbcKey() : initialized_(false) {};
|
||||
~AesCbcKey() {};
|
||||
|
||||
bool Init(const std::string& key);
|
||||
bool Encrypt(const std::string& in, std::string* out, std::string* iv);
|
||||
|
||||
private:
|
||||
EVP_CIPHER_CTX ctx_;
|
||||
bool initialized_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey);
|
||||
};
|
||||
|
||||
class RsaPublicKey {
|
||||
public:
|
||||
RsaPublicKey() : key_(NULL) {}
|
||||
~RsaPublicKey();
|
||||
|
||||
// Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey
|
||||
bool Init(const std::string& serialized_key);
|
||||
|
||||
// Encrypt a message using RSA-OAEP. Caller retains ownership of all
|
||||
// parameters. Returns true if successful, false otherwise.
|
||||
bool Encrypt(const std::string& plaintext,
|
||||
std::string* ciphertext);
|
||||
|
||||
// Verify RSSASSA-PSS signature. Caller retains ownership of all parameters.
|
||||
// Returns true if validation succeeds, false otherwise.
|
||||
bool VerifySignature(const std::string& message,
|
||||
const std::string& signature);
|
||||
|
||||
private:
|
||||
RSA* key_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_PRIVACY_CRYPTO_H_
|
||||
121
core/include/properties.h
Normal file
121
core/include/properties.h
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_PROPERTIES_H_
|
||||
#define WVCDM_CORE_PROPERTIES_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "cdm_client_property_set.h"
|
||||
#include "lock.h"
|
||||
#include "scoped_ptr.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::map<CdmSessionId, const CdmClientPropertySet*>
|
||||
CdmClientPropertySetMap;
|
||||
|
||||
// This class saves information about features and properties enabled
|
||||
// for a given platform. At initialization it initializes properties from
|
||||
// property_configuration.h. That file specifies features selected for each
|
||||
// platform. Core CDM can then query enabled features though specific getter
|
||||
// methods.
|
||||
// Setter methods are provided but their only planned use is for testing.
|
||||
class Properties {
|
||||
public:
|
||||
static void Init();
|
||||
|
||||
static inline bool begin_license_usage_when_received() {
|
||||
return begin_license_usage_when_received_;
|
||||
}
|
||||
static inline bool require_explicit_renew_request() {
|
||||
return require_explicit_renew_request_;
|
||||
}
|
||||
static inline bool oem_crypto_use_secure_buffers() {
|
||||
return oem_crypto_use_secure_buffers_;
|
||||
}
|
||||
static inline bool oem_crypto_use_fifo() { return oem_crypto_use_fifo_; }
|
||||
static inline bool oem_crypto_use_userspace_buffers() {
|
||||
return oem_crypto_use_userspace_buffers_;
|
||||
}
|
||||
static inline bool use_certificates_as_identification() {
|
||||
return use_certificates_as_identification_;
|
||||
}
|
||||
static inline bool extract_pssh_data() { return extract_pssh_data_; }
|
||||
static inline bool decrypt_with_empty_session_support() {
|
||||
return decrypt_with_empty_session_support_;
|
||||
}
|
||||
static inline bool security_level_path_backward_compatibility_support() {
|
||||
return security_level_path_backward_compatibility_support_;
|
||||
}
|
||||
static bool GetCompanyName(std::string* company_name);
|
||||
static bool GetModelName(std::string* model_name);
|
||||
static bool GetArchitectureName(std::string* arch_name);
|
||||
static bool GetDeviceName(std::string* device_name);
|
||||
static bool GetProductName(std::string* product_name);
|
||||
static bool GetBuildInfo(std::string* build_info);
|
||||
static bool GetDeviceFilesBasePath(CdmSecurityLevel security_level,
|
||||
std::string* base_path);
|
||||
static bool GetFactoryKeyboxPath(std::string* keybox);
|
||||
static bool GetOEMCryptoPath(std::string* library_name);
|
||||
static bool GetSecurityLevelDirectories(std::vector<std::string>* dirs);
|
||||
static const std::string GetSecurityLevel(const CdmSessionId& session_id);
|
||||
static const std::vector<uint8_t> GetServiceCertificate(
|
||||
const CdmSessionId& session_id);
|
||||
static bool UsePrivacyMode(const CdmSessionId& session_id);
|
||||
static uint32_t GetSessionSharingId(const CdmSessionId& session_id);
|
||||
|
||||
static bool AddSessionPropertySet(const CdmSessionId& session_id,
|
||||
const CdmClientPropertySet* property_set);
|
||||
static bool RemoveSessionPropertySet(const CdmSessionId& session_id);
|
||||
|
||||
private:
|
||||
static const CdmClientPropertySet* GetCdmClientPropertySet(
|
||||
const CdmSessionId& session_id);
|
||||
static void set_begin_license_usage_when_received(bool flag) {
|
||||
begin_license_usage_when_received_ = flag;
|
||||
}
|
||||
static void set_require_explicit_renew_request(bool flag) {
|
||||
require_explicit_renew_request_ = flag;
|
||||
}
|
||||
static void set_oem_crypto_use_secure_buffers(bool flag) {
|
||||
oem_crypto_use_secure_buffers_ = flag;
|
||||
}
|
||||
static void set_oem_crypto_use_fifo(bool flag) {
|
||||
oem_crypto_use_fifo_ = flag;
|
||||
}
|
||||
static void set_oem_crypto_use_userspace_buffers(bool flag) {
|
||||
oem_crypto_use_userspace_buffers_ = flag;
|
||||
}
|
||||
static void set_use_certificates_as_identification(bool flag) {
|
||||
use_certificates_as_identification_ = flag;
|
||||
}
|
||||
static void set_extract_pssh_data(bool flag) { extract_pssh_data_ = flag; }
|
||||
|
||||
static void set_decrypt_with_empty_session_support(bool flag) {
|
||||
decrypt_with_empty_session_support_ = flag;
|
||||
}
|
||||
static void set_security_level_path_backward_compatibility_support(
|
||||
bool flag) {
|
||||
security_level_path_backward_compatibility_support_ = flag;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool begin_license_usage_when_received_;
|
||||
static bool require_explicit_renew_request_;
|
||||
static bool oem_crypto_use_secure_buffers_;
|
||||
static bool oem_crypto_use_fifo_;
|
||||
static bool oem_crypto_use_userspace_buffers_;
|
||||
static bool use_certificates_as_identification_;
|
||||
static bool extract_pssh_data_;
|
||||
static bool decrypt_with_empty_session_support_;
|
||||
static bool security_level_path_backward_compatibility_support_;
|
||||
static scoped_ptr<CdmClientPropertySetMap> session_property_set_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(Properties);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_PROPERTIES_H_
|
||||
64
core/include/scoped_ptr.h
Normal file
64
core/include/scoped_ptr.h
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// A simple and partial implementation of scoped_ptr class.
|
||||
// The implementation is copied from gtest/include/gtest/internal/gtest-port.h.
|
||||
//
|
||||
#ifndef WVCDM_CORE_SCOPED_PTR_H_
|
||||
#define WVCDM_CORE_SCOPED_PTR_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// A scoped_ptr<T> is like a T*, except that the destructor of scoped_ptr<T>
|
||||
// automatically deletes the pointer it holds (if any).
|
||||
// That is, scoped_ptr<T> owns the T object that it points to.
|
||||
// Like a T*, a scoped_ptr<T> may hold either NULL or a pointer to a T object.
|
||||
// Also like T*, scoped_ptr<T> is thread-compatible, and once you
|
||||
// dereference it, you get the thread safety guarantees of T.
|
||||
//
|
||||
// The size of scoped_ptr is small. On most compilers, sizeof(scoped_ptr<T>)
|
||||
// == sizeof(T*).
|
||||
//
|
||||
// Current implementation targets having a strict subset of C++11's
|
||||
// unique_ptr<> features. Known deficiencies include not supporting move-only
|
||||
// deleteres, function pointers as deleters, and deleters with reference
|
||||
// types.
|
||||
|
||||
// This implementation of scoped_ptr is PARTIAL, e.g. it does not support move,
|
||||
// custom deleter etc.
|
||||
template <typename T>
|
||||
class scoped_ptr {
|
||||
public:
|
||||
typedef T element_type;
|
||||
|
||||
explicit scoped_ptr(T* p = NULL) : ptr_(p) {}
|
||||
~scoped_ptr() { reset(); }
|
||||
|
||||
T& operator*() const { return *ptr_; }
|
||||
T* operator->() const { return ptr_; }
|
||||
T* get() const { return ptr_; }
|
||||
|
||||
T* release() {
|
||||
T* const ptr = ptr_;
|
||||
ptr_ = NULL;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void reset(T* p = NULL) {
|
||||
if (p != ptr_) {
|
||||
if (sizeof(T) > 0) { // Makes sure T is a complete type.
|
||||
delete ptr_;
|
||||
}
|
||||
ptr_ = p;
|
||||
}
|
||||
}
|
||||
private:
|
||||
T* ptr_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(scoped_ptr);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_SCOPED_PTR_H_
|
||||
26
core/include/string_conversions.h
Normal file
26
core/include/string_conversions.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_STRING_CONVERSIONS_H_
|
||||
#define WVCDM_CORE_STRING_CONVERSIONS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
std::vector<uint8_t> a2b_hex(const std::string& b);
|
||||
std::string a2bs_hex(const std::string& b);
|
||||
std::string b2a_hex(const std::vector<uint8_t>& b);
|
||||
std::string b2a_hex(const std::string& b);
|
||||
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input);
|
||||
std::string Base64SafeEncodeNoPad(const std::vector<uint8_t>& bin_input);
|
||||
std::vector<uint8_t> Base64SafeDecode(const std::string& bin_input);
|
||||
std::string HexEncode(const uint8_t* bytes, unsigned size);
|
||||
std::string IntToString(int value);
|
||||
std::string UintToString(unsigned int value);
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_STRING_CONVERSIONS_H_
|
||||
51
core/include/timer.h
Normal file
51
core/include/timer.h
Normal file
@@ -0,0 +1,51 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Timer - Platform independent interface for a Timer class
|
||||
//
|
||||
#ifndef WVCDM_CORE_TIMER_H_
|
||||
#define WVCDM_CORE_TIMER_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Timer Handler class.
|
||||
//
|
||||
// Derive from this class if you wish to receive events when the timer
|
||||
// expires. Provide the handler when setting up a new Timer.
|
||||
|
||||
class TimerHandler {
|
||||
public:
|
||||
TimerHandler() {};
|
||||
virtual ~TimerHandler() {};
|
||||
|
||||
virtual void OnTimerEvent() = 0;
|
||||
};
|
||||
|
||||
// Timer class. The implementation is platform dependent.
|
||||
//
|
||||
// This class provides a simple recurring timer API. The class receiving
|
||||
// timer expiry events should derive from TimerHandler.
|
||||
// Specify the receiver class and the periodicty of timer events when
|
||||
// the timer is initiated by calling Start.
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
class Impl;
|
||||
|
||||
Timer();
|
||||
~Timer();
|
||||
|
||||
bool Start(TimerHandler *handler, uint32_t time_in_secs);
|
||||
void Stop();
|
||||
bool IsRunning();
|
||||
|
||||
private:
|
||||
Impl *impl_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(Timer);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_TIMER_H_
|
||||
58
core/include/wv_cdm_constants.h
Normal file
58
core/include/wv_cdm_constants.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_WV_CDM_CONSTANTS_H_
|
||||
#define WVCDM_CORE_WV_CDM_CONSTANTS_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace wvcdm {
|
||||
static const size_t KEY_CONTROL_SIZE = 16;
|
||||
static const size_t KEY_ID_SIZE = 16;
|
||||
static const size_t KEY_IV_SIZE = 16;
|
||||
static const size_t KEY_PAD_SIZE = 16;
|
||||
static const size_t KEY_SIZE = 16;
|
||||
static const size_t MAC_KEY_SIZE = 32;
|
||||
static const size_t KEYBOX_KEY_DATA_SIZE = 72;
|
||||
|
||||
static const char SESSION_ID_PREFIX[] = "sid";
|
||||
static const char KEY_SET_ID_PREFIX[] = "ksid";
|
||||
static const char KEY_SYSTEM[] = "com.widevine";
|
||||
|
||||
// define query keys, values here
|
||||
static const std::string QUERY_KEY_LICENSE_TYPE = "LicenseType";
|
||||
// "Streaming", "Offline"
|
||||
static const std::string QUERY_KEY_PLAY_ALLOWED = "PlayAllowed";
|
||||
// "True", "False"
|
||||
static const std::string QUERY_KEY_PERSIST_ALLOWED = "PersistAllowed";
|
||||
// "True", "False"
|
||||
static const std::string QUERY_KEY_RENEW_ALLOWED = "RenewAllowed";
|
||||
// "True", "False"
|
||||
static const std::string QUERY_KEY_LICENSE_DURATION_REMAINING =
|
||||
"LicenseDurationRemaining"; // non-negative integer
|
||||
static const std::string QUERY_KEY_PLAYBACK_DURATION_REMAINING =
|
||||
"PlaybackDurationRemaining"; // non-negative integer
|
||||
static const std::string QUERY_KEY_RENEWAL_SERVER_URL = "RenewalServerUrl";
|
||||
// url
|
||||
static const std::string QUERY_KEY_OEMCRYPTO_SESSION_ID = "OemCryptoSessionId";
|
||||
// session id
|
||||
static const std::string QUERY_KEY_SECURITY_LEVEL = "SecurityLevel";
|
||||
// "L1", "L3"
|
||||
static const std::string QUERY_KEY_DEVICE_ID = "DeviceID";
|
||||
// device unique id
|
||||
static const std::string QUERY_KEY_SYSTEM_ID = "SystemID";
|
||||
// system id
|
||||
static const std::string QUERY_KEY_PROVISIONING_ID = "ProvisioningID";
|
||||
// provisioning unique id
|
||||
|
||||
static const std::string QUERY_VALUE_TRUE = "True";
|
||||
static const std::string QUERY_VALUE_FALSE = "False";
|
||||
static const std::string QUERY_VALUE_STREAMING = "Streaming";
|
||||
static const std::string QUERY_VALUE_OFFLINE = "Offline";
|
||||
static const std::string QUERY_VALUE_SECURITY_LEVEL_L1 = "L1";
|
||||
static const std::string QUERY_VALUE_SECURITY_LEVEL_L2 = "L2";
|
||||
static const std::string QUERY_VALUE_SECURITY_LEVEL_L3 = "L3";
|
||||
static const std::string QUERY_VALUE_SECURITY_LEVEL_UNKNOWN = "Unknown";
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_WV_CDM_CONSTANTS_H_
|
||||
28
core/include/wv_cdm_event_listener.h
Normal file
28
core/include/wv_cdm_event_listener.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_
|
||||
#define WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Listener for events from the Content Decryption Module.
|
||||
// The caller of the CDM API must provide an implementation for OnEvent
|
||||
// and signal its intent by using the Attach/DetachEventListener methods
|
||||
// in the WvContentDecryptionModule class.
|
||||
class WvCdmEventListener {
|
||||
public:
|
||||
WvCdmEventListener() {}
|
||||
virtual ~WvCdmEventListener() {}
|
||||
|
||||
virtual void OnEvent(const CdmSessionId& session_id,
|
||||
CdmEventType cdm_event) = 0;
|
||||
|
||||
private:
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(WvCdmEventListener);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_WV_CDM_EVENT_LISTENER_H_
|
||||
121
core/include/wv_cdm_types.h
Normal file
121
core/include/wv_cdm_types.h
Normal file
@@ -0,0 +1,121 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WVCDM_CORE_WV_CDM_TYPES_H_
|
||||
#define WVCDM_CORE_WV_CDM_TYPES_H_
|
||||
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::string CdmKeySystem;
|
||||
typedef std::string CdmInitData;
|
||||
typedef std::string CdmKeyMessage;
|
||||
typedef std::string CdmKeyResponse;
|
||||
typedef std::string KeyId;
|
||||
typedef std::string CdmSessionId;
|
||||
typedef std::string CdmKeySetId;
|
||||
typedef std::string RequestId;
|
||||
typedef uint32_t CryptoResult;
|
||||
typedef uint32_t CryptoSessionId;
|
||||
typedef std::string CryptoKeyId;
|
||||
typedef std::map<std::string, std::string> CdmAppParameterMap;
|
||||
typedef std::map<std::string, std::string> CdmQueryMap;
|
||||
typedef std::string CdmProvisioningRequest;
|
||||
typedef std::string CdmProvisioningResponse;
|
||||
|
||||
// Types for shared host/cdm interface pairs used to shared vendor data.
|
||||
typedef std::pair<std::string, std::string> kStringPairs;
|
||||
typedef std::vector<uint8_t> kVectorBytes;
|
||||
typedef std::pair<std::string, kVectorBytes> kVectorPairs;
|
||||
|
||||
enum CdmResponseType {
|
||||
NO_ERROR,
|
||||
UNKNOWN_ERROR,
|
||||
KEY_ADDED,
|
||||
KEY_ERROR,
|
||||
KEY_MESSAGE,
|
||||
NEED_KEY,
|
||||
KEY_CANCELED,
|
||||
NEED_PROVISIONING,
|
||||
DEVICE_REVOKED,
|
||||
INSUFFICIENT_CRYPTO_RESOURCES,
|
||||
};
|
||||
|
||||
#define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
enum CdmEventType {
|
||||
LICENSE_EXPIRED_EVENT,
|
||||
LICENSE_RENEWAL_NEEDED_EVENT
|
||||
};
|
||||
|
||||
enum CdmLicenseType {
|
||||
kLicenseTypeOffline,
|
||||
kLicenseTypeStreaming,
|
||||
kLicenseTypeRelease
|
||||
};
|
||||
|
||||
enum CdmSecurityLevel {
|
||||
kSecurityLevelUninitialized,
|
||||
kSecurityLevelL1,
|
||||
kSecurityLevelL2,
|
||||
kSecurityLevelL3,
|
||||
kSecurityLevelUnknown
|
||||
};
|
||||
|
||||
struct CdmDecryptionParameters {
|
||||
bool is_encrypted;
|
||||
bool is_secure;
|
||||
const KeyId* key_id;
|
||||
const uint8_t* encrypt_buffer;
|
||||
size_t encrypt_length;
|
||||
const std::vector<uint8_t>* iv;
|
||||
size_t block_offset;
|
||||
void* decrypt_buffer;
|
||||
size_t decrypt_buffer_length;
|
||||
size_t decrypt_buffer_offset;
|
||||
uint8_t subsample_flags;
|
||||
bool is_video;
|
||||
CdmDecryptionParameters()
|
||||
: is_encrypted(true),
|
||||
is_secure(true),
|
||||
key_id(NULL),
|
||||
encrypt_buffer(NULL),
|
||||
encrypt_length(0),
|
||||
iv(NULL),
|
||||
block_offset(0),
|
||||
decrypt_buffer(NULL),
|
||||
decrypt_buffer_length(0),
|
||||
decrypt_buffer_offset(0),
|
||||
subsample_flags(0),
|
||||
is_video(true) {}
|
||||
CdmDecryptionParameters(const KeyId* key, const uint8_t* encrypted_buffer,
|
||||
size_t encrypted_length,
|
||||
const std::vector<uint8_t>* initialization_vector,
|
||||
size_t offset, void* decrypted_buffer)
|
||||
: is_encrypted(true),
|
||||
is_secure(true),
|
||||
key_id(key),
|
||||
encrypt_buffer(encrypted_buffer),
|
||||
encrypt_length(encrypted_length),
|
||||
iv(initialization_vector),
|
||||
block_offset(offset),
|
||||
decrypt_buffer(decrypted_buffer),
|
||||
decrypt_buffer_length(encrypted_length),
|
||||
decrypt_buffer_offset(0),
|
||||
subsample_flags(0),
|
||||
is_video(true) {}
|
||||
};
|
||||
|
||||
// forward class references
|
||||
class KeyMessage;
|
||||
class Request;
|
||||
class Key;
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WVCDM_CORE_WV_CDM_TYPES_H_
|
||||
97
core/src/buffer_reader.cpp
Normal file
97
core/src/buffer_reader.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "buffer_reader.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
bool BufferReader::Read1(uint8_t* v) {
|
||||
if (!HasBytes(1)) {
|
||||
LOGE("BufferReader::Read1 : Failure while parsing: Not enough bytes (1)");
|
||||
return false;
|
||||
}
|
||||
|
||||
*v = buf_[pos_++];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Internal implementation of multi-byte reads
|
||||
template<typename T> bool BufferReader::Read(T* v) {
|
||||
if (!HasBytes(sizeof(T))) {
|
||||
LOGE("BufferReader::Read<T> : Failure during parse: Not enough bytes (%u)",
|
||||
sizeof(T));
|
||||
return false;
|
||||
}
|
||||
|
||||
T tmp = 0;
|
||||
for (size_t i = 0; i < sizeof(T); i++) {
|
||||
tmp <<= 8;
|
||||
tmp += buf_[pos_++];
|
||||
}
|
||||
*v = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::Read2(uint16_t* v) { return Read(v); }
|
||||
bool BufferReader::Read2s(int16_t* v) { return Read(v); }
|
||||
bool BufferReader::Read4(uint32_t* v) { return Read(v); }
|
||||
bool BufferReader::Read4s(int32_t* v) { return Read(v); }
|
||||
bool BufferReader::Read8(uint64_t* v) { return Read(v); }
|
||||
bool BufferReader::Read8s(int64_t* v) { return Read(v); }
|
||||
|
||||
bool BufferReader::ReadString(std::string* str, int count) {
|
||||
if (!HasBytes(count)) {
|
||||
LOGE("BufferReader::ReadString : Parse Failure: Not enough bytes (%d)",
|
||||
count);
|
||||
return false;
|
||||
}
|
||||
|
||||
str->assign(buf_ + pos_, buf_ + pos_ + count);
|
||||
pos_ += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::ReadVec(std::vector<uint8_t>* vec, int count) {
|
||||
if (!HasBytes(count)) {
|
||||
LOGE("BufferReader::ReadVec : Parse Failure: Not enough bytes (%d)", count);
|
||||
return false;
|
||||
}
|
||||
|
||||
vec->clear();
|
||||
vec->insert(vec->end(), buf_ + pos_, buf_ + pos_ + count);
|
||||
pos_ += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::SkipBytes(int bytes) {
|
||||
if (!HasBytes(bytes)) {
|
||||
LOGE("BufferReader::SkipBytes : Parse Failure: Not enough bytes (%d)",
|
||||
bytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
pos_ += bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::Read4Into8(uint64_t* v) {
|
||||
uint32_t tmp;
|
||||
if (!Read4(&tmp)) {
|
||||
return false;
|
||||
}
|
||||
*v = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::Read4sInto8s(int64_t* v) {
|
||||
// Beware of the need for sign extension.
|
||||
int32_t tmp;
|
||||
if (!Read4s(&tmp)) {
|
||||
return false;
|
||||
}
|
||||
*v = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
684
core/src/cdm_engine.cpp
Normal file
684
core/src/cdm_engine.cpp
Normal file
@@ -0,0 +1,684 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "cdm_engine.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "buffer_reader.h"
|
||||
#include "cdm_session.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "scoped_ptr.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
CdmEngine::CdmEngine()
|
||||
: cert_provisioning_requested_security_level_(kLevelDefault) {
|
||||
Properties::Init();
|
||||
}
|
||||
|
||||
CdmEngine::~CdmEngine() {
|
||||
CdmSessionMap::iterator i(sessions_.begin());
|
||||
for (; i != sessions_.end(); ++i) {
|
||||
delete i->second;
|
||||
}
|
||||
sessions_.clear();
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::OpenSession(
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmClientPropertySet* property_set,
|
||||
CdmSessionId* session_id) {
|
||||
LOGI("CdmEngine::OpenSession");
|
||||
|
||||
if (!ValidateKeySystem(key_system)) {
|
||||
LOGI("CdmEngine::OpenSession: invalid key_system = %s", key_system.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!session_id) {
|
||||
LOGE("CdmEngine::OpenSession: no session ID destination provided");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
scoped_ptr<CdmSession> new_session(new CdmSession(property_set));
|
||||
if (new_session->session_id().empty()) {
|
||||
LOGE("CdmEngine::OpenSession: failure to generate session ID");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType sts = new_session->Init();
|
||||
if (sts != NO_ERROR) {
|
||||
if (sts == NEED_PROVISIONING) {
|
||||
cert_provisioning_requested_security_level_ =
|
||||
new_session->GetRequestedSecurityLevel();
|
||||
} else {
|
||||
LOGE("CdmEngine::OpenSession: bad session init: %u", sts);
|
||||
}
|
||||
return sts;
|
||||
}
|
||||
*session_id = new_session->session_id();
|
||||
sessions_[*session_id] = new_session.release();
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::OpenKeySetSession(const CdmKeySetId& key_set_id) {
|
||||
LOGI("CdmEngine::OpenKeySetSession");
|
||||
|
||||
if (key_set_id.empty()) {
|
||||
LOGE("CdmEngine::OpenKeySetSession: invalid key set id");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmSessionId session_id;
|
||||
CdmResponseType sts = OpenSession(KEY_SYSTEM, NULL, &session_id);
|
||||
|
||||
if (sts != NO_ERROR)
|
||||
return sts;
|
||||
|
||||
release_key_sets_[key_set_id] = session_id;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) {
|
||||
LOGI("CdmEngine::CloseSession");
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::CloseSession: session not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
delete iter->second;
|
||||
sessions_.erase(session_id);
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::CloseKeySetSession(const CdmKeySetId& key_set_id) {
|
||||
LOGI("CdmEngine::CloseKeySetSession");
|
||||
|
||||
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
|
||||
if (iter == release_key_sets_.end()) {
|
||||
LOGE("CdmEngine::CloseKeySetSession: key set id not found = %s",
|
||||
key_set_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType sts = CloseSession(iter->second);
|
||||
release_key_sets_.erase(iter);
|
||||
return sts;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GenerateKeyRequest(
|
||||
const CdmSessionId& session_id,
|
||||
const CdmKeySetId& key_set_id,
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
LOGI("CdmEngine::GenerateKeyRequest");
|
||||
|
||||
CdmSessionId id = session_id;
|
||||
CdmResponseType sts;
|
||||
|
||||
if (license_type == kLicenseTypeRelease) {
|
||||
if (key_set_id.empty()) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: invalid key set ID");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!session_id.empty()) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: invalid session ID = %s",
|
||||
session_id.c_str());
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(key_set_id);
|
||||
if (iter == release_key_sets_.end()) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: key set ID not found = %s",
|
||||
key_set_id.c_str());
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
id = iter->second;
|
||||
}
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s",
|
||||
id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!key_request) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: no key request destination provided");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
key_request->clear();
|
||||
|
||||
if (license_type == kLicenseTypeRelease) {
|
||||
sts = iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeRelease);
|
||||
if (sts != KEY_ADDED) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: key release restoration failed,"
|
||||
"sts = %d", (int)sts);
|
||||
return sts;
|
||||
}
|
||||
}
|
||||
|
||||
sts = iter->second->GenerateKeyRequest(init_data, license_type,
|
||||
app_parameters, key_request,
|
||||
server_url);
|
||||
|
||||
if (KEY_MESSAGE != sts) {
|
||||
if (sts == NEED_PROVISIONING) {
|
||||
cert_provisioning_requested_security_level_ =
|
||||
iter->second->GetRequestedSecurityLevel();
|
||||
}
|
||||
LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, "
|
||||
"sts = %d", (int)sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
if (license_type == kLicenseTypeRelease) {
|
||||
OnKeyReleaseEvent(key_set_id);
|
||||
}
|
||||
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::AddKey(
|
||||
const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data,
|
||||
CdmKeySetId* key_set_id) {
|
||||
LOGI("CdmEngine::AddKey");
|
||||
|
||||
CdmSessionId id = session_id;
|
||||
bool license_type_release = session_id.empty();
|
||||
|
||||
if (license_type_release) {
|
||||
if (!key_set_id) {
|
||||
LOGE("CdmEngine::AddKey: no key set id provided");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (key_set_id->empty()) {
|
||||
LOGE("CdmEngine::AddKey: invalid key set id");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmReleaseKeySetMap::iterator iter = release_key_sets_.find(*key_set_id);
|
||||
if (iter == release_key_sets_.end()) {
|
||||
LOGE("CdmEngine::AddKey: key set id not found = %s", key_set_id->c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
id = iter->second;
|
||||
}
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(id);
|
||||
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::AddKey: session id not found = %s", id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (key_data.empty()) {
|
||||
LOGE("CdmEngine::AddKey: no key_data");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType sts = iter->second->AddKey(key_data, key_set_id);
|
||||
|
||||
if (KEY_ADDED != sts) {
|
||||
LOGE("CdmEngine::AddKey: keys not added, result = %d", (int)sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
return KEY_ADDED;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::RestoreKey(
|
||||
const CdmSessionId& session_id,
|
||||
const CdmKeySetId& key_set_id) {
|
||||
LOGI("CdmEngine::RestoreKey");
|
||||
|
||||
if (key_set_id.empty()) {
|
||||
LOGI("CdmEngine::RestoreKey: invalid key set id");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::RestoreKey: session_id not found = %s ",
|
||||
session_id.c_str());
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType sts =
|
||||
iter->second->RestoreOfflineSession(key_set_id, kLicenseTypeOffline);
|
||||
if (sts == NEED_PROVISIONING) {
|
||||
cert_provisioning_requested_security_level_ =
|
||||
iter->second->GetRequestedSecurityLevel();
|
||||
}
|
||||
return sts;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::CancelKeyRequest(const CdmSessionId& session_id) {
|
||||
LOGI("CdmEngine::CancelKeyRequest");
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::CancelKeyRequest: session_id not found = %s",
|
||||
session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
// Re-initialize to release crypto session/keys without closing session
|
||||
iter->second->Init();
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GenerateRenewalRequest(
|
||||
const CdmSessionId& session_id,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
LOGI("CdmEngine::GenerateRenewalRequest");
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::GenerateRenewalRequest: session_id not found = %s",
|
||||
session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!key_request) {
|
||||
LOGE("CdmEngine::GenerateRenewalRequest: no key request destination");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
key_request->clear();
|
||||
|
||||
CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request,
|
||||
server_url);
|
||||
|
||||
if (KEY_MESSAGE != sts) {
|
||||
LOGE("CdmEngine::GenerateRenewalRequest: key request gen. failed, sts=%d",
|
||||
(int)sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::RenewKey(
|
||||
const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data) {
|
||||
LOGI("CdmEngine::RenewKey");
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::RenewKey: session_id not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (key_data.empty()) {
|
||||
LOGE("CdmEngine::RenewKey: no key_data");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType sts = iter->second->RenewKey(key_data);
|
||||
if (KEY_ADDED != sts) {
|
||||
LOGE("CdmEngine::RenewKey: keys not added, sts=%d", (int)sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
return KEY_ADDED;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::QueryStatus(CdmQueryMap* key_info) {
|
||||
LOGI("CdmEngine::QueryStatus");
|
||||
CryptoSession crypto_session;
|
||||
switch (crypto_session.GetSecurityLevel()) {
|
||||
case kSecurityLevelL1:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
|
||||
break;
|
||||
case kSecurityLevelL2:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2;
|
||||
break;
|
||||
case kSecurityLevelL3:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
|
||||
break;
|
||||
case kSecurityLevelUninitialized:
|
||||
case kSecurityLevelUnknown:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] =
|
||||
QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
|
||||
break;
|
||||
default:
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
std::string deviceId;
|
||||
bool success = crypto_session.GetDeviceUniqueId(&deviceId);
|
||||
if (success) {
|
||||
(*key_info)[QUERY_KEY_DEVICE_ID] = deviceId;
|
||||
}
|
||||
|
||||
uint32_t system_id;
|
||||
success = crypto_session.GetSystemId(&system_id);
|
||||
if (success) {
|
||||
std::ostringstream system_id_stream;
|
||||
system_id_stream << system_id;
|
||||
(*key_info)[QUERY_KEY_SYSTEM_ID] = system_id_stream.str();
|
||||
}
|
||||
|
||||
std::string provisioning_id;
|
||||
success = crypto_session.GetProvisioningId(&provisioning_id);
|
||||
if (success) {
|
||||
(*key_info)[QUERY_KEY_PROVISIONING_ID] = provisioning_id;
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::QuerySessionStatus(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info) {
|
||||
LOGI("CdmEngine::QuerySessionStatus");
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::QuerySessionStatus: session_id not found = %s",
|
||||
session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
return iter->second->QueryStatus(key_info);
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::QueryKeyStatus(
|
||||
const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info) {
|
||||
LOGI("CdmEngine::QueryKeyStatus");
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::QueryKeyStatus: session_id not found = %s",
|
||||
session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
return iter->second->QueryKeyStatus(key_info);
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::QueryKeyControlInfo(
|
||||
const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info) {
|
||||
LOGI("CdmEngine::QueryKeyControlInfo");
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::QueryKeyControlInfo: session_id not found = %s",
|
||||
session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
return iter->second->QueryKeyControlInfo(key_info);
|
||||
}
|
||||
|
||||
/*
|
||||
* Composes a device provisioning request and output the request in JSON format
|
||||
* in *request. It also returns the default url for the provisioning server
|
||||
* in *default_url.
|
||||
*
|
||||
* Returns NO_ERROR for success and UNKNOWN_ERROR if fails.
|
||||
*/
|
||||
CdmResponseType CdmEngine::GetProvisioningRequest(
|
||||
CdmProvisioningRequest* request,
|
||||
std::string* default_url) {
|
||||
if (!request || !default_url) {
|
||||
LOGE("CdmEngine::GetProvisioningRequest: invalid input parameters");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
return cert_provisioning_.GetProvisioningRequest(
|
||||
cert_provisioning_requested_security_level_,
|
||||
request,
|
||||
default_url);
|
||||
}
|
||||
|
||||
/*
|
||||
* The response message consists of a device certificate and the device RSA key.
|
||||
* The device RSA key is stored in the T.E.E. The device certificate is stored
|
||||
* in the device.
|
||||
*
|
||||
* Returns NO_ERROR for success and UNKNOWN_ERROR if fails.
|
||||
*/
|
||||
CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
CdmProvisioningResponse& response) {
|
||||
if (response.empty()) {
|
||||
LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response.");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
return cert_provisioning_.HandleProvisioningResponse(response);
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::Decrypt(
|
||||
const CdmSessionId& session_id,
|
||||
const CdmDecryptionParameters& parameters) {
|
||||
if (parameters.key_id == NULL) {
|
||||
LOGE("CdmEngine::Decrypt: no key_id");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (parameters.encrypt_buffer == NULL) {
|
||||
LOGE("CdmEngine::Decrypt: no src encrypt buffer");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (parameters.iv == NULL) {
|
||||
LOGE("CdmEngine::Decrypt: no iv");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (parameters.decrypt_buffer == NULL) {
|
||||
if (!parameters.is_secure &&
|
||||
!Properties::Properties::oem_crypto_use_fifo()) {
|
||||
LOGE("CdmEngine::Decrypt: no dest decrypt buffer");
|
||||
return KEY_ERROR;
|
||||
} // else we must be level 1 direct and we don't need to return a buffer.
|
||||
}
|
||||
|
||||
CdmSessionMap::iterator iter;
|
||||
if (session_id.empty()) {
|
||||
if (!Properties::decrypt_with_empty_session_support()) return KEY_ERROR;
|
||||
|
||||
// Loop through the sessions to find the session containing the key_id.
|
||||
for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
|
||||
if (iter->second->IsKeyLoaded(*parameters.key_id)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iter = sessions_.find(session_id);
|
||||
}
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::Decrypt: session_id not found[%d] = %s",
|
||||
session_id.size(),
|
||||
session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
return iter->second->Decrypt(parameters);
|
||||
}
|
||||
|
||||
bool CdmEngine::IsKeyLoaded(const KeyId& key_id) {
|
||||
for (CdmSessionMap::iterator iter = sessions_.begin();
|
||||
iter != sessions_.end(); ++iter) {
|
||||
if (iter->second->IsKeyLoaded(key_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CdmEngine::FindSessionForKey(
|
||||
const KeyId& key_id,
|
||||
CdmSessionId* session_id) {
|
||||
if (NULL == session_id) {
|
||||
LOGE("CdmEngine::FindSessionForKey: session id not provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(*session_id);
|
||||
if (iter != sessions_.end()) {
|
||||
if (iter->second->IsKeyLoaded(key_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t session_sharing_id = Properties::GetSessionSharingId(*session_id);
|
||||
|
||||
for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
|
||||
CdmSessionId local_session_id = iter->second->session_id();
|
||||
if (Properties::GetSessionSharingId(local_session_id) ==
|
||||
session_sharing_id) {
|
||||
if (iter->second->IsKeyLoaded(key_id)) {
|
||||
*session_id = local_session_id;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CdmEngine::AttachEventListener(
|
||||
const CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener) {
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return iter->second->AttachEventListener(listener);
|
||||
}
|
||||
|
||||
bool CdmEngine::DetachEventListener(
|
||||
const CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener) {
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return iter->second->DetachEventListener(listener);
|
||||
}
|
||||
|
||||
bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
|
||||
return (key_system.find("widevine") != std::string::npos);
|
||||
}
|
||||
|
||||
// Parse a blob of multiple concatenated PSSH atoms to extract the first
|
||||
// widevine pssh
|
||||
bool CdmEngine::ExtractWidevinePssh(
|
||||
const CdmInitData& init_data, CdmInitData* output) {
|
||||
|
||||
BufferReader reader(
|
||||
reinterpret_cast<const uint8_t*>(init_data.data()), init_data.length());
|
||||
|
||||
static const uint8_t kWidevineSystemId[] = {
|
||||
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
|
||||
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
||||
};
|
||||
|
||||
// one PSSH blob consists of:
|
||||
// 4 byte size of the PSSH atom, inclusive
|
||||
// "pssh"
|
||||
// 4 byte flags, value 0
|
||||
// 16 byte system id
|
||||
// 4 byte size of PSSH data, exclusive
|
||||
while (1) {
|
||||
// size of PSSH atom, used for skipping
|
||||
uint32_t size;
|
||||
if (!reader.Read4(&size)) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH atom size");
|
||||
return false;
|
||||
}
|
||||
|
||||
// "pssh"
|
||||
std::vector<uint8_t> pssh;
|
||||
if (!reader.ReadVec(&pssh, 4)) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH literal");
|
||||
return false;
|
||||
}
|
||||
if (memcmp(&pssh[0], "pssh", 4)) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: PSSH literal not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
// flags
|
||||
uint32_t flags;
|
||||
if (!reader.Read4(&flags)) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH flags");
|
||||
return false;
|
||||
}
|
||||
if (flags != 0) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: PSSH flags not zero");
|
||||
return false;
|
||||
}
|
||||
|
||||
// system id
|
||||
std::vector<uint8_t> system_id;
|
||||
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read system ID");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(&system_id[0], kWidevineSystemId,
|
||||
sizeof(kWidevineSystemId))) {
|
||||
// skip the remaining contents of the atom,
|
||||
// after size field, atom name, flags and system id
|
||||
if (!reader.SkipBytes(
|
||||
size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: Unable to rest of PSSH atom");
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// size of PSSH box
|
||||
uint32_t pssh_length;
|
||||
if (!reader.Read4(&pssh_length)) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH box size");
|
||||
return false;
|
||||
}
|
||||
|
||||
output->clear();
|
||||
if (!reader.ReadString(output, pssh_length)) {
|
||||
LOGW("CdmEngine::ExtractWidevinePssh: Unable to read PSSH");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// we did not find a matching record
|
||||
return false;
|
||||
}
|
||||
|
||||
void CdmEngine::OnTimerEvent() {
|
||||
for (CdmSessionMap::iterator iter = sessions_.begin();
|
||||
iter != sessions_.end(); ++iter) {
|
||||
iter->second->OnTimerEvent();
|
||||
}
|
||||
}
|
||||
|
||||
void CdmEngine::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) {
|
||||
|
||||
for (CdmSessionMap::iterator iter = sessions_.begin();
|
||||
iter != sessions_.end(); ++iter) {
|
||||
iter->second->OnKeyReleaseEvent(key_set_id);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
453
core/src/cdm_session.cpp
Normal file
453
core/src/cdm_session.cpp
Normal file
@@ -0,0 +1,453 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "cdm_session.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "clock.h"
|
||||
#include "crypto_session.h"
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
|
||||
namespace {
|
||||
const size_t kKeySetIdLength = 14;
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::set<WvCdmEventListener*>::iterator CdmEventListenerIter;
|
||||
|
||||
CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set)
|
||||
: session_id_(GenerateSessionId()),
|
||||
crypto_session_(NULL),
|
||||
license_received_(false),
|
||||
reinitialize_session_(false),
|
||||
license_type_(kLicenseTypeStreaming),
|
||||
is_certificate_loaded_(false) {
|
||||
if (cdm_client_property_set) {
|
||||
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
|
||||
}
|
||||
}
|
||||
|
||||
CdmSession::~CdmSession() { Properties::RemoveSessionPropertySet(session_id_); }
|
||||
|
||||
CdmResponseType CdmSession::Init() {
|
||||
scoped_ptr<CryptoSession> session(new CryptoSession());
|
||||
|
||||
CdmResponseType sts = session->Open(GetRequestedSecurityLevel());
|
||||
if (NO_ERROR != sts) return sts;
|
||||
|
||||
std::string token;
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, session.get()->GetSecurityLevel()) ||
|
||||
!handle.RetrieveCertificate(&token, &wrapped_key_)) {
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
} else {
|
||||
if (!session->GetToken(&token)) return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!license_parser_.Init(token, session.get(), &policy_engine_))
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
crypto_session_.reset(session.release());
|
||||
license_received_ = false;
|
||||
reinitialize_session_ = false;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::RestoreOfflineSession(
|
||||
const CdmKeySetId& key_set_id, const CdmLicenseType license_type) {
|
||||
key_set_id_ = key_set_id;
|
||||
|
||||
// Retrieve license information from persistent store
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, crypto_session_->GetSecurityLevel()))
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
DeviceFiles::LicenseState license_state;
|
||||
|
||||
if (!handle.RetrieveLicense(key_set_id, &license_state, &offline_pssh_data_,
|
||||
&offline_key_request_, &offline_key_response_,
|
||||
&offline_key_renewal_request_,
|
||||
&offline_key_renewal_response_,
|
||||
&offline_release_server_url_)) {
|
||||
LOGE("CdmSession::Init failed to retrieve license. key set id = %s",
|
||||
key_set_id.c_str());
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (license_state != DeviceFiles::kLicenseStateActive) {
|
||||
LOGE("CdmSession::Init invalid offline license state = %s", license_state);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
if (is_certificate_loaded_ ||
|
||||
crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) {
|
||||
is_certificate_loaded_ = true;
|
||||
} else {
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!license_parser_.RestoreOfflineLicense(offline_key_request_,
|
||||
offline_key_response_,
|
||||
offline_key_renewal_response_)) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
license_received_ = true;
|
||||
license_type_ = license_type;
|
||||
return KEY_ADDED;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
const CdmInitData& init_data, const CdmLicenseType license_type,
|
||||
const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
if (reinitialize_session_) {
|
||||
CdmResponseType sts = Init();
|
||||
if (sts != NO_ERROR) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: Reinitialization failed");
|
||||
return sts;
|
||||
}
|
||||
}
|
||||
|
||||
if (crypto_session_.get() == NULL) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!crypto_session_->IsOpen()) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: Crypto session not open");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
license_type_ = license_type;
|
||||
|
||||
if (license_type_ == kLicenseTypeRelease) {
|
||||
return GenerateReleaseRequest(key_request, server_url);
|
||||
} else if (license_received_) { // renewal
|
||||
return Properties::require_explicit_renew_request()
|
||||
? UNKNOWN_ERROR
|
||||
: GenerateRenewalRequest(key_request, server_url);
|
||||
} else {
|
||||
if (init_data.empty() && !license_parser_.HasInitData()) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: init data absent");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmInitData pssh_data = init_data;
|
||||
if (Properties::extract_pssh_data()) {
|
||||
if (!CdmEngine::ExtractWidevinePssh(init_data, &pssh_data)) {
|
||||
return KEY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
if (is_certificate_loaded_ ||
|
||||
crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) {
|
||||
is_certificate_loaded_ = true;
|
||||
} else {
|
||||
reinitialize_session_ = true;
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!license_parser_.PrepareKeyRequest(pssh_data, license_type,
|
||||
app_parameters, session_id_,
|
||||
key_request, server_url)) {
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (license_type_ == kLicenseTypeOffline) {
|
||||
offline_pssh_data_ = pssh_data;
|
||||
offline_key_request_ = *key_request;
|
||||
offline_release_server_url_ = *server_url;
|
||||
}
|
||||
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// AddKey() - Accept license response and extract key info.
|
||||
CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
|
||||
CdmKeySetId* key_set_id) {
|
||||
if (crypto_session_.get() == NULL) {
|
||||
LOGW("CdmSession::AddKey: Invalid crypto session");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!crypto_session_->IsOpen()) {
|
||||
LOGW("CdmSession::AddKey: Crypto session not open");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (license_type_ == kLicenseTypeRelease) {
|
||||
return ReleaseKey(key_response);
|
||||
} else if (license_received_) { // renewal
|
||||
return Properties::require_explicit_renew_request()
|
||||
? UNKNOWN_ERROR
|
||||
: RenewKey(key_response);
|
||||
} else {
|
||||
CdmResponseType sts = license_parser_.HandleKeyResponse(key_response);
|
||||
|
||||
if (sts != KEY_ADDED) return sts;
|
||||
|
||||
license_received_ = true;
|
||||
|
||||
if (license_type_ == kLicenseTypeOffline) {
|
||||
offline_key_response_ = key_response;
|
||||
if (!GenerateKeySetId(&key_set_id_)) {
|
||||
LOGE("CdmSession::AddKey: Unable to generate key set Id");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
|
||||
LOGE("CdmSession::AddKey: Unable to store license");
|
||||
CdmResponseType sts = Init();
|
||||
if (sts != NO_ERROR) {
|
||||
LOGW("CdmSession::AddKey: Reinitialization failed");
|
||||
return sts;
|
||||
}
|
||||
|
||||
key_set_id_.clear();
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
*key_set_id = key_set_id_;
|
||||
return KEY_ADDED;
|
||||
}
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::QueryStatus(CdmQueryMap* key_info) {
|
||||
if (crypto_session_.get() == NULL) {
|
||||
LOGE("CdmSession::QueryStatus: Invalid crypto session");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!crypto_session_->IsOpen()) {
|
||||
LOGE("CdmSession::QueryStatus: Crypto session not open");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
switch (crypto_session_->GetSecurityLevel()) {
|
||||
case kSecurityLevelL1:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
|
||||
break;
|
||||
case kSecurityLevelL2:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2;
|
||||
break;
|
||||
case kSecurityLevelL3:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
|
||||
break;
|
||||
case kSecurityLevelUninitialized:
|
||||
case kSecurityLevelUnknown:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] =
|
||||
QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
|
||||
break;
|
||||
default:
|
||||
return KEY_ERROR;
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) {
|
||||
return policy_engine_.Query(key_info);
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) {
|
||||
if (crypto_session_.get() == NULL) {
|
||||
LOGW("CdmSession::QueryKeyControlInfo: Invalid crypto session");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!crypto_session_->IsOpen()) {
|
||||
LOGW("CdmSession::QueryKeyControlInfo: Crypto session not open");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << crypto_session_->oec_session_id();
|
||||
(*key_info)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str();
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// CancelKeyRequest() - Cancel session.
|
||||
CdmResponseType CdmSession::CancelKeyRequest() {
|
||||
crypto_session_->Close();
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// Decrypt() - Accept encrypted buffer and return decrypted data.
|
||||
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
if (crypto_session_.get() == NULL || !crypto_session_->IsOpen())
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
CdmResponseType status = crypto_session_->Decrypt(params);
|
||||
if (UNKNOWN_ERROR == status) {
|
||||
// Decrypt failed - check status of license and keys.
|
||||
Clock clock;
|
||||
int64_t current_time = clock.GetCurrentTime();
|
||||
if (policy_engine_.IsLicenseDurationExpired(current_time) ||
|
||||
policy_engine_.IsPlaybackDurationExpired(current_time)) {
|
||||
return NEED_KEY;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
// License renewal
|
||||
// GenerateRenewalRequest() - Construct valid renewal request for the current
|
||||
// session keys.
|
||||
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
if (!license_parser_.PrepareKeyUpdateRequest(true, key_request, server_url)) {
|
||||
LOGE("CdmSession::GenerateRenewalRequest: ERROR on prepare");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (license_type_ == kLicenseTypeOffline) {
|
||||
offline_key_renewal_request_ = *key_request;
|
||||
}
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
|
||||
// RenewKey() - Accept renewal response and update key info.
|
||||
CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
|
||||
CdmResponseType sts =
|
||||
license_parser_.HandleKeyUpdateResponse(true, key_response);
|
||||
if (sts != KEY_ADDED) return sts;
|
||||
|
||||
if (license_type_ == kLicenseTypeOffline) {
|
||||
offline_key_renewal_response_ = key_response;
|
||||
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) return UNKNOWN_ERROR;
|
||||
}
|
||||
return KEY_ADDED;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
if (license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url)) {
|
||||
// Mark license as being released
|
||||
if (StoreLicense(DeviceFiles::kLicenseStateReleasing)) return KEY_MESSAGE;
|
||||
}
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// ReleaseKey() - Accept release response and release license.
|
||||
CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
|
||||
CdmResponseType sts =
|
||||
license_parser_.HandleKeyUpdateResponse(false, key_response);
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (handle.Init(&file, crypto_session_->GetSecurityLevel()))
|
||||
handle.DeleteLicense(key_set_id_);
|
||||
|
||||
return sts;
|
||||
}
|
||||
|
||||
bool CdmSession::IsKeyLoaded(const KeyId& key_id) {
|
||||
return license_parser_.IsKeyLoaded(key_id);
|
||||
}
|
||||
|
||||
CdmSessionId CdmSession::GenerateSessionId() {
|
||||
static int session_num = 1;
|
||||
return SESSION_ID_PREFIX + IntToString(++session_num);
|
||||
}
|
||||
|
||||
bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
|
||||
if (!key_set_id) {
|
||||
LOGW("CdmSession::GenerateKeySetId: key set id destination not provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> random_data(
|
||||
(kKeySetIdLength - sizeof(KEY_SET_ID_PREFIX)) / 2, 0);
|
||||
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, crypto_session_->GetSecurityLevel()))
|
||||
return false;
|
||||
|
||||
while (key_set_id->empty()) {
|
||||
if (!crypto_session_->GetRandom(random_data.size(), &random_data[0]))
|
||||
return false;
|
||||
|
||||
*key_set_id = KEY_SET_ID_PREFIX + b2a_hex(random_data);
|
||||
|
||||
// key set collision
|
||||
if (handle.LicenseExists(*key_set_id)) {
|
||||
key_set_id->clear();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, crypto_session_->GetSecurityLevel()))
|
||||
return false;
|
||||
|
||||
return handle.StoreLicense(
|
||||
key_set_id_, state, offline_pssh_data_, offline_key_request_,
|
||||
offline_key_response_, offline_key_renewal_request_,
|
||||
offline_key_renewal_response_, offline_release_server_url_);
|
||||
}
|
||||
|
||||
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {
|
||||
std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener);
|
||||
return result.second;
|
||||
}
|
||||
|
||||
bool CdmSession::DetachEventListener(WvCdmEventListener* listener) {
|
||||
return (listeners_.erase(listener) == 1);
|
||||
}
|
||||
|
||||
void CdmSession::OnTimerEvent() {
|
||||
bool event_occurred = false;
|
||||
CdmEventType event;
|
||||
|
||||
policy_engine_.OnTimerEvent(&event_occurred, &event);
|
||||
|
||||
if (event_occurred) {
|
||||
for (CdmEventListenerIter iter = listeners_.begin();
|
||||
iter != listeners_.end(); ++iter) {
|
||||
(*iter)->OnEvent(session_id_, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CdmSession::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) {
|
||||
if (key_set_id_ == key_set_id) {
|
||||
for (CdmEventListenerIter iter = listeners_.begin();
|
||||
iter != listeners_.end(); ++iter) {
|
||||
(*iter)->OnEvent(session_id_, LICENSE_EXPIRED_EVENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SecurityLevel CdmSession::GetRequestedSecurityLevel() {
|
||||
if (Properties::GetSecurityLevel(session_id_)
|
||||
.compare(QUERY_VALUE_SECURITY_LEVEL_L3) == 0) {
|
||||
return kLevel3;
|
||||
}
|
||||
|
||||
return kLevelDefault;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
250
core/src/certificate_provisioning.cpp
Normal file
250
core/src/certificate_provisioning.cpp
Normal file
@@ -0,0 +1,250 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "certificate_provisioning.h"
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// WHAT: URL for Google Provisioning Server.
|
||||
// WHY: The provisioning server supplies the certificate that is needed
|
||||
// to communicate with the License Server.
|
||||
const std::string kProvisioningServerUrl =
|
||||
"https://www.googleapis.com/"
|
||||
"certificateprovisioning/v1/devicecertificates/create"
|
||||
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
// Protobuf generated classes.
|
||||
using video_widevine_server::sdk::ClientIdentification;
|
||||
using video_widevine_server::sdk::ProvisioningRequest;
|
||||
using video_widevine_server::sdk::ProvisioningResponse;
|
||||
using video_widevine_server::sdk::SignedProvisioningMessage;
|
||||
|
||||
/*
|
||||
* This function converts SignedProvisioningRequest into base64 string.
|
||||
* It then wraps it in JSON format expected by the Apiary frontend.
|
||||
* Apiary requires the base64 encoding to replace '+' with minus '-',
|
||||
* and '/' with underscore '_'; opposite to stubby's.
|
||||
*
|
||||
* Returns the JSON formated string in *request. The JSON string will be
|
||||
* appended as a query parameter, i.e. signedRequest=<base 64 encoded
|
||||
* SignedProvisioningRequest>. All base64 '=' padding chars must be removed.
|
||||
*
|
||||
* The JSON formated request takes the following format:
|
||||
*
|
||||
* base64 encoded message
|
||||
*/
|
||||
void CertificateProvisioning::ComposeJsonRequestAsQueryString(
|
||||
const std::string& message,
|
||||
CdmProvisioningRequest* request) {
|
||||
|
||||
// Performs base64 encoding for message
|
||||
std::vector<uint8_t> message_vector(message.begin(), message.end());
|
||||
std::string message_b64 = Base64SafeEncodeNoPad(message_vector);
|
||||
request->assign(message_b64);
|
||||
}
|
||||
|
||||
/*
|
||||
* Composes a device provisioning request and output the request in JSON format
|
||||
* in *request. It also returns the default url for the provisioning server
|
||||
* in *default_url.
|
||||
*
|
||||
* Returns NO_ERROR for success and UNKNOWN_ERROR if fails.
|
||||
*/
|
||||
CdmResponseType CertificateProvisioning::GetProvisioningRequest(
|
||||
SecurityLevel requested_security_level,
|
||||
CdmProvisioningRequest* request,
|
||||
std::string* default_url) {
|
||||
if (!default_url) {
|
||||
LOGE("GetProvisioningRequest: pointer for returning URL is NULL");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
default_url->assign(kProvisioningServerUrl);
|
||||
|
||||
CdmResponseType sts = crypto_session_.Open(requested_security_level);
|
||||
if (NO_ERROR != sts) {
|
||||
LOGE("GetProvisioningRequest: fails to create a crypto session");
|
||||
return sts;
|
||||
}
|
||||
|
||||
// Prepares device provisioning request.
|
||||
ProvisioningRequest provisioning_request;
|
||||
ClientIdentification* client_id = provisioning_request.mutable_client_id();
|
||||
client_id->set_type(ClientIdentification::KEYBOX);
|
||||
std::string token;
|
||||
if (!crypto_session_.GetToken(&token)) {
|
||||
LOGE("GetProvisioningRequest: fails to get token");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
client_id->set_token(token);
|
||||
|
||||
uint32_t nonce;
|
||||
if (!crypto_session_.GenerateNonce(&nonce)) {
|
||||
LOGE("GetProvisioningRequest: fails to generate a nonce");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// The provisioning server does not convert the nonce to uint32_t, it just
|
||||
// passes the binary data to the response message.
|
||||
std::string the_nonce(reinterpret_cast<char*>(&nonce), sizeof(nonce));
|
||||
provisioning_request.set_nonce(the_nonce);
|
||||
|
||||
std::string serialized_message;
|
||||
provisioning_request.SerializeToString(&serialized_message);
|
||||
|
||||
// Derives signing and encryption keys and constructs signature.
|
||||
std::string request_signature;
|
||||
if (!crypto_session_.PrepareRequest(serialized_message, true,
|
||||
&request_signature)) {
|
||||
LOGE("GetProvisioningRequest: fails to prepare request");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
if (request_signature.empty()) {
|
||||
LOGE("GetProvisioningRequest: request signature is empty");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
SignedProvisioningMessage signed_provisioning_msg;
|
||||
signed_provisioning_msg.set_message(serialized_message);
|
||||
signed_provisioning_msg.set_signature(request_signature);
|
||||
|
||||
std::string serialized_request;
|
||||
signed_provisioning_msg.SerializeToString(&serialized_request);
|
||||
|
||||
// Converts request into JSON string
|
||||
ComposeJsonRequestAsQueryString(serialized_request, request);
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parses the input json_str and locates substring using start_substr and
|
||||
* end_stubstr. The found base64 substring is then decoded and returns
|
||||
* in *result.
|
||||
*
|
||||
* Returns true for success and false if fails.
|
||||
*/
|
||||
bool CertificateProvisioning::ParseJsonResponse(
|
||||
const CdmProvisioningResponse& json_str,
|
||||
const std::string& start_substr,
|
||||
const std::string& end_substr,
|
||||
std::string* result) {
|
||||
std::string b64_string;
|
||||
size_t start = json_str.find(start_substr);
|
||||
if (start == json_str.npos) {
|
||||
LOGE("ParseJsonResponse: cannot find start substring");
|
||||
return false;
|
||||
}
|
||||
size_t end = json_str.find(end_substr, start + start_substr.length());
|
||||
if (end == json_str.npos) {
|
||||
LOGE("ParseJsonResponse cannot locate end substring");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t b64_string_size = end - start - start_substr.length();
|
||||
b64_string.assign(json_str, start + start_substr.length(), b64_string_size);
|
||||
|
||||
// Decodes base64 substring and returns it in *result
|
||||
std::vector<uint8_t> result_vector = Base64SafeDecode(b64_string);
|
||||
result->assign(result_vector.begin(), result_vector.end());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* The response message consists of a device certificate and the device RSA key.
|
||||
* The device RSA key is stored in the T.E.E. The device certificate is stored
|
||||
* in the device.
|
||||
*
|
||||
* Returns NO_ERROR for success and UNKNOWN_ERROR if fails.
|
||||
*/
|
||||
CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
|
||||
CdmProvisioningResponse& response) {
|
||||
|
||||
// Extracts signed response from JSON string, decodes base64 signed response
|
||||
const std::string kMessageStart = "\"signedResponse\": \"";
|
||||
const std::string kMessageEnd = "\"";
|
||||
std::string serialized_signed_response;
|
||||
if (!ParseJsonResponse(response, kMessageStart, kMessageEnd,
|
||||
&serialized_signed_response)) {
|
||||
LOGE("Fails to extract signed serialized response from JSON response");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// Authenticates provisioning response using D1s (server key derived from
|
||||
// the provisioing request's input). Validate provisioning response and
|
||||
// stores private device RSA key and certificate.
|
||||
SignedProvisioningMessage signed_response;
|
||||
if (!signed_response.ParseFromString(serialized_signed_response)) {
|
||||
LOGE("HandleProvisioningResponse: fails to parse signed response");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
bool error = false;
|
||||
if (!signed_response.has_signature()) {
|
||||
LOGE("HandleProvisioningResponse: signature not found");
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (!signed_response.has_message()) {
|
||||
LOGE("HandleProvisioningResponse: message not found");
|
||||
error = true;
|
||||
}
|
||||
|
||||
if (error)
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
const std::string& signed_message = signed_response.message();
|
||||
ProvisioningResponse provisioning_response;
|
||||
|
||||
if (!provisioning_response.ParseFromString(signed_message)) {
|
||||
LOGE("HandleProvisioningResponse: Fails to parse signed message");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!provisioning_response.has_device_rsa_key()) {
|
||||
LOGE("HandleProvisioningResponse: key not found");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
const std::string& enc_rsa_key = provisioning_response.device_rsa_key();
|
||||
const std::string& nonce = provisioning_response.nonce();
|
||||
const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv();
|
||||
const std::string& signature = signed_response.signature();
|
||||
std::string wrapped_rsa_key;
|
||||
if (!crypto_session_.RewrapDeviceRSAKey(signed_message,
|
||||
signature,
|
||||
nonce,
|
||||
enc_rsa_key,
|
||||
rsa_key_iv,
|
||||
&wrapped_rsa_key)){
|
||||
LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
crypto_session_.Close();
|
||||
|
||||
const std::string& device_certificate =
|
||||
provisioning_response.device_certificate();
|
||||
|
||||
File file;
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(&file, crypto_session_.GetSecurityLevel())) {
|
||||
LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
if (!handle.StoreCertificate(device_certificate, wrapped_rsa_key)) {
|
||||
LOGE("HandleProvisioningResponse: failed to save provisioning certificate");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
handle.DeleteAllLicenses();
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
705
core/src/crypto_session.cpp
Normal file
705
core/src/crypto_session.cpp
Normal file
@@ -0,0 +1,705 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Crypto - wrapper classes for OEMCrypto interface
|
||||
//
|
||||
|
||||
#include "crypto_session.h"
|
||||
|
||||
#include <arpa/inet.h> // needed for ntoh()
|
||||
#include <iostream>
|
||||
|
||||
#include "crypto_key.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
// Encode unsigned integer into a big endian formatted string
|
||||
std::string EncodeUint32(unsigned int u) {
|
||||
std::string s;
|
||||
s.append(1, (u >> 24) & 0xFF);
|
||||
s.append(1, (u >> 16) & 0xFF);
|
||||
s.append(1, (u >> 8) & 0xFF);
|
||||
s.append(1, (u >> 0) & 0xFF);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
Lock CryptoSession::crypto_lock_;
|
||||
bool CryptoSession::initialized_ = false;
|
||||
int CryptoSession::session_count_ = 0;
|
||||
uint64_t CryptoSession::request_id_index_ = 0;
|
||||
|
||||
CryptoSession::CryptoSession()
|
||||
: open_(false),
|
||||
is_destination_buffer_type_valid_(false),
|
||||
requested_security_level_(kLevelDefault),
|
||||
request_id_base_(0) {
|
||||
Init();
|
||||
}
|
||||
|
||||
CryptoSession::~CryptoSession() {
|
||||
if (open_) {
|
||||
Close();
|
||||
}
|
||||
Terminate();
|
||||
}
|
||||
|
||||
void CryptoSession::Init() {
|
||||
LOGV("CryptoSession::Init");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
session_count_ += 1;
|
||||
if (initialized_) return;
|
||||
OEMCryptoResult sts = OEMCrypto_Initialize();
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("OEMCrypto_Initialize failed: %d", sts);
|
||||
return;
|
||||
}
|
||||
initialized_ = true;
|
||||
}
|
||||
|
||||
void CryptoSession::Terminate() {
|
||||
LOGV("CryptoSession::Terminate");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
session_count_ -= 1;
|
||||
if (session_count_ > 0 || !initialized_) return;
|
||||
OEMCryptoResult sts = OEMCrypto_Terminate();
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGE("OEMCrypto_Terminate failed: %d", sts);
|
||||
}
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
bool CryptoSession::ValidateKeybox() {
|
||||
LOGV("CryptoSession::ValidateKeybox: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
OEMCryptoResult result = OEMCrypto_IsKeyboxValid(requested_security_level_);
|
||||
return (OEMCrypto_SUCCESS == result);
|
||||
}
|
||||
|
||||
bool CryptoSession::GetToken(std::string* token) {
|
||||
if (!token) {
|
||||
LOGE("CryptoSession::GetToken : No token passed to method.");
|
||||
return false;
|
||||
}
|
||||
uint8_t buf[KEYBOX_KEY_DATA_SIZE];
|
||||
size_t bufSize = sizeof(buf);
|
||||
LOGV("CryptoSession::GetToken: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
OEMCryptoResult sts =
|
||||
OEMCrypto_GetKeyData(buf, &bufSize, requested_security_level_);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
token->assign((const char*)buf, (size_t)bufSize);
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmSecurityLevel CryptoSession::GetSecurityLevel() {
|
||||
LOGV("CryptoSession::GetSecurityLevel: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!initialized_) {
|
||||
return kSecurityLevelUninitialized;
|
||||
}
|
||||
|
||||
std::string security_level =
|
||||
OEMCrypto_SecurityLevel(requested_security_level_);
|
||||
|
||||
if ((security_level.size() != 2) || (security_level.at(0) != 'L')) {
|
||||
return kSecurityLevelUnknown;
|
||||
}
|
||||
|
||||
switch (security_level.at(1)) {
|
||||
case '1':
|
||||
return kSecurityLevelL1;
|
||||
case '2':
|
||||
return kSecurityLevelL2;
|
||||
case '3':
|
||||
return kSecurityLevelL3;
|
||||
default:
|
||||
return kSecurityLevelUnknown;
|
||||
}
|
||||
|
||||
return kSecurityLevelUnknown;
|
||||
}
|
||||
|
||||
bool CryptoSession::GetDeviceUniqueId(std::string* device_id) {
|
||||
if (!device_id) {
|
||||
LOGE("CryptoSession::GetDeviceUniqueId : No buffer passed to method.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> id;
|
||||
size_t id_length = 32;
|
||||
|
||||
id.resize(id_length);
|
||||
|
||||
LOGV("CryptoSession::GetDeviceUniqueId: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
OEMCryptoResult sts =
|
||||
OEMCrypto_GetDeviceID(&id[0], &id_length, requested_security_level_);
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
device_id->assign(reinterpret_cast<char *>(&id[0]), id_length);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GetSystemId(uint32_t* system_id) {
|
||||
if (!system_id) {
|
||||
LOGE("CryptoSession::GetSystemId : No buffer passed to method.");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buf[KEYBOX_KEY_DATA_SIZE];
|
||||
size_t buf_size = sizeof(buf);
|
||||
|
||||
LOGV("CryptoSession::GetSystemId: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
OEMCryptoResult sts =
|
||||
OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_);
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode 32-bit int encoded as network-byte-order byte array starting at
|
||||
// index 4.
|
||||
uint32_t* id = reinterpret_cast<uint32_t*>(&buf[4]);
|
||||
|
||||
*system_id = ntohl(*id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GetProvisioningId(std::string* provisioning_id) {
|
||||
if (!provisioning_id) {
|
||||
LOGE("CryptoSession::GetProvisioningId : No buffer passed to method.");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t buf[KEYBOX_KEY_DATA_SIZE];
|
||||
size_t buf_size = sizeof(buf);
|
||||
|
||||
LOGV("CryptoSession::GetProvisioningId: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
OEMCryptoResult sts =
|
||||
OEMCrypto_GetKeyData(buf, &buf_size, requested_security_level_);
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
provisioning_id->assign(reinterpret_cast<char*>(&buf[8]), 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
|
||||
LOGV("CryptoSession::Open: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!initialized_) return UNKNOWN_ERROR;
|
||||
if (open_) return NO_ERROR;
|
||||
|
||||
OEMCrypto_SESSION sid;
|
||||
requested_security_level_ = requested_security_level;
|
||||
OEMCryptoResult sts = OEMCrypto_OpenSession(&sid, requested_security_level);
|
||||
if (OEMCrypto_SUCCESS == sts) {
|
||||
oec_session_id_ = static_cast<CryptoSessionId>(sid);
|
||||
LOGV("OpenSession: id= %ld", (uint32_t)oec_session_id_);
|
||||
open_ = true;
|
||||
} else if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) {
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
}
|
||||
if (!open_) return UNKNOWN_ERROR;
|
||||
|
||||
OEMCrypto_GetRandom(reinterpret_cast<uint8_t*>(&request_id_base_),
|
||||
sizeof(request_id_base_));
|
||||
++request_id_index_;
|
||||
return NO_ERROR;
|
||||
|
||||
}
|
||||
|
||||
void CryptoSession::Close() {
|
||||
LOGV("CloseSession: id=%ld open=%s", (uint32_t)oec_session_id_,
|
||||
open_ ? "true" : "false");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!open_) return;
|
||||
if (OEMCrypto_SUCCESS == OEMCrypto_CloseSession(oec_session_id_)) {
|
||||
open_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CryptoSession::GenerateRequestId(std::string& req_id_str) {
|
||||
LOGV("CryptoSession::GenerateRequestId: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
req_id_str = HexEncode(reinterpret_cast<uint8_t*>(&request_id_base_),
|
||||
sizeof(request_id_base_)) +
|
||||
HexEncode(reinterpret_cast<uint8_t*>(&request_id_index_),
|
||||
sizeof(request_id_index_));
|
||||
}
|
||||
|
||||
bool CryptoSession::PrepareRequest(const std::string& message,
|
||||
bool is_provisioning,
|
||||
std::string* signature) {
|
||||
LOGV("CryptoSession::PrepareRequest: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
if (!signature) {
|
||||
LOGE("CryptoSession::PrepareRequest : No output destination provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Properties::use_certificates_as_identification() || is_provisioning) {
|
||||
if (!GenerateDerivedKeys(message)) return false;
|
||||
|
||||
if (!GenerateSignature(message, false, signature)) return false;
|
||||
} else {
|
||||
if (!GenerateSignature(message, true, signature)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::PrepareRenewalRequest(const std::string& message,
|
||||
std::string* signature) {
|
||||
LOGV("CryptoSession::PrepareRenewalRequest: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
if (!signature) {
|
||||
LOGE("CryptoSession::PrepareRenewalRequest : No output destination "
|
||||
"provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GenerateSignature(message, false, signature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CryptoSession::GenerateMacContext(const std::string& input_context,
|
||||
std::string* deriv_context) {
|
||||
if (!deriv_context) {
|
||||
LOGE("CryptoSession::GenerateMacContext : No output destination provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string kSigningKeyLabel = "AUTHENTICATION";
|
||||
const size_t kSigningKeySizeBits = MAC_KEY_SIZE * 8;
|
||||
|
||||
deriv_context->assign(kSigningKeyLabel);
|
||||
deriv_context->append(1, '\0');
|
||||
deriv_context->append(input_context);
|
||||
deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2));
|
||||
}
|
||||
|
||||
void CryptoSession::GenerateEncryptContext(const std::string& input_context,
|
||||
std::string* deriv_context) {
|
||||
if (!deriv_context) {
|
||||
LOGE("CryptoSession::GenerateEncryptContext : No output destination "
|
||||
"provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string kEncryptionKeyLabel = "ENCRYPTION";
|
||||
const size_t kEncryptionKeySizeBits = KEY_SIZE * 8;
|
||||
|
||||
deriv_context->assign(kEncryptionKeyLabel);
|
||||
deriv_context->append(1, '\0');
|
||||
deriv_context->append(input_context);
|
||||
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
|
||||
}
|
||||
|
||||
size_t CryptoSession::GetOffset(std::string message, std::string field) {
|
||||
size_t pos = message.find(field);
|
||||
if (pos == std::string::npos) {
|
||||
LOGE("CryptoSession::GetOffset : Cannot find offset for %s", field.c_str());
|
||||
pos = 0;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
int num_keys,
|
||||
const CryptoKey* key_array) {
|
||||
LOGV("CryptoSession::LoadKeys: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
const uint8_t* enc_mac_key = NULL;
|
||||
const uint8_t* enc_mac_key_iv = NULL;
|
||||
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
|
||||
enc_mac_key = msg + GetOffset(message, mac_key);
|
||||
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
|
||||
} else {
|
||||
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
|
||||
}
|
||||
std::vector<OEMCrypto_KeyObject> load_key_array(num_keys);
|
||||
for (int i = 0; i < num_keys; ++i) {
|
||||
const CryptoKey* ki = &key_array[i];
|
||||
OEMCrypto_KeyObject* ko = &load_key_array[i];
|
||||
ko->key_id = msg + GetOffset(message, ki->key_id());
|
||||
ko->key_id_length = ki->key_id().length();
|
||||
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
|
||||
ko->key_data = msg + GetOffset(message, ki->key_data());
|
||||
ko->key_data_length = ki->key_data().length();
|
||||
if (ki->HasKeyControl()) {
|
||||
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
|
||||
ko->key_control = msg + GetOffset(message, ki->key_control());
|
||||
} else {
|
||||
LOGE("For key %d: XXX key has no control block. size=%d", i,
|
||||
ki->key_control().size());
|
||||
ko->key_control_iv = NULL;
|
||||
ko->key_control = NULL;
|
||||
}
|
||||
}
|
||||
LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_LoadKeys(
|
||||
oec_session_id_, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
|
||||
enc_mac_key_iv, enc_mac_key, num_keys, &load_key_array[0]);
|
||||
|
||||
if (OEMCrypto_SUCCESS == sts) {
|
||||
return KEY_ADDED;
|
||||
} else if (OEMCrypto_ERROR_TOO_MANY_KEYS == sts) {
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
} else {
|
||||
return KEY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool CryptoSession::LoadCertificatePrivateKey(std::string& wrapped_key) {
|
||||
LOGV("CryptoSession::LoadCertificatePrivateKey: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
LOGV("LoadDeviceRSAKey: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_LoadDeviceRSAKey(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(wrapped_key.data()),
|
||||
wrapped_key.size());
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGD("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::RefreshKeys(const std::string& message,
|
||||
const std::string& signature, int num_keys,
|
||||
const CryptoKey* key_array) {
|
||||
LOGV("CryptoSession::RefreshKeys: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
std::vector<OEMCrypto_KeyRefreshObject> load_key_array(num_keys);
|
||||
for (int i = 0; i < num_keys; ++i) {
|
||||
const CryptoKey* ki = &key_array[i];
|
||||
OEMCrypto_KeyRefreshObject* ko = &load_key_array[i];
|
||||
if (ki->key_id().empty()) {
|
||||
ko->key_id = NULL;
|
||||
} else {
|
||||
ko->key_id = msg + GetOffset(message, ki->key_id());
|
||||
}
|
||||
if (ki->HasKeyControl()) {
|
||||
if (ki->key_control_iv().empty()) {
|
||||
ko->key_control_iv = NULL;
|
||||
} else {
|
||||
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
|
||||
}
|
||||
ko->key_control = msg + GetOffset(message, ki->key_control());
|
||||
} else {
|
||||
ko->key_control_iv = NULL;
|
||||
ko->key_control = NULL;
|
||||
}
|
||||
}
|
||||
LOGV("RefreshKeys: id=%ld", static_cast<uint32_t>(oec_session_id_));
|
||||
return (
|
||||
OEMCrypto_SUCCESS ==
|
||||
OEMCrypto_RefreshKeys(oec_session_id_, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()),
|
||||
signature.size(), num_keys, &load_key_array[0]));
|
||||
}
|
||||
|
||||
bool CryptoSession::SelectKey(const std::string& key_id) {
|
||||
const uint8_t* key_id_string =
|
||||
reinterpret_cast<const uint8_t*>(key_id.data());
|
||||
|
||||
OEMCryptoResult sts =
|
||||
OEMCrypto_SelectKey(oec_session_id_, key_id_string, key_id.size());
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateDerivedKeys(const std::string& message) {
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(message, &mac_deriv_message);
|
||||
GenerateEncryptContext(message, &enc_deriv_message);
|
||||
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_GenerateDerivedKeys(
|
||||
oec_session_id_,
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGD("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateDerivedKeys(const std::string& message,
|
||||
const std::string& session_key) {
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(message, &mac_deriv_message);
|
||||
GenerateEncryptContext(message, &enc_deriv_message);
|
||||
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
|
||||
OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(session_key.data()),
|
||||
session_key.size(),
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGD("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateSignature(const std::string& message, bool use_rsa,
|
||||
std::string* signature) {
|
||||
LOGV("GenerateSignature: id=%ld", (uint32_t)oec_session_id_);
|
||||
if (!signature) return false;
|
||||
|
||||
size_t length = 0;
|
||||
OEMCryptoResult sts = OEMCrypto_SUCCESS;
|
||||
if (use_rsa) {
|
||||
sts = OEMCrypto_GenerateRSASignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(), NULL, &length);
|
||||
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
||||
LOGD("GenerateSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
sts = OEMCrypto_GenerateSignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(), NULL, &length);
|
||||
}
|
||||
|
||||
signature->resize(length);
|
||||
|
||||
if (use_rsa) {
|
||||
sts = OEMCrypto_GenerateRSASignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
||||
&length);
|
||||
} else {
|
||||
sts = OEMCrypto_GenerateSignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
||||
&length);
|
||||
}
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
signature->resize(length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
if (!is_destination_buffer_type_valid_) {
|
||||
if (!SetDestinationBufferType()) return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
// Check if key needs to be selected
|
||||
if (params.is_encrypted) {
|
||||
if (key_id_ != *params.key_id) {
|
||||
if (SelectKey(*params.key_id)) {
|
||||
key_id_ = *params.key_id;
|
||||
} else {
|
||||
return NEED_KEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OEMCrypto_DestBufferDesc buffer_descriptor;
|
||||
buffer_descriptor.type =
|
||||
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
|
||||
|
||||
switch (buffer_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
buffer_descriptor.buffer.clear.address =
|
||||
static_cast<uint8_t*>(params.decrypt_buffer) +
|
||||
params.decrypt_buffer_offset;
|
||||
buffer_descriptor.buffer.clear.max_length = params.decrypt_buffer_length;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
buffer_descriptor.buffer.secure.handle = params.decrypt_buffer;
|
||||
buffer_descriptor.buffer.secure.offset = params.decrypt_buffer_offset;
|
||||
buffer_descriptor.buffer.secure.max_length = params.decrypt_buffer_length;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
buffer_descriptor.type = OEMCrypto_BufferType_Direct;
|
||||
buffer_descriptor.buffer.direct.is_video = params.is_video;
|
||||
break;
|
||||
}
|
||||
|
||||
OEMCryptoResult sts = OEMCrypto_DecryptCTR(
|
||||
oec_session_id_, params.encrypt_buffer, params.encrypt_length,
|
||||
params.is_encrypted, &(*params.iv).front(), params.block_offset,
|
||||
&buffer_descriptor, params.subsample_flags);
|
||||
|
||||
switch (sts) {
|
||||
case OEMCrypto_SUCCESS:
|
||||
return NO_ERROR;
|
||||
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
|
||||
return INSUFFICIENT_CRYPTO_RESOURCES;
|
||||
case OEMCrypto_ERROR_KEY_EXPIRED:
|
||||
return NEED_KEY;
|
||||
default:
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateNonce(uint32_t* nonce) {
|
||||
if (!nonce) {
|
||||
LOGE("input parameter is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("CryptoSession::GenerateNonce: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
|
||||
return (OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(oec_session_id_, nonce));
|
||||
}
|
||||
|
||||
bool CryptoSession::SetDestinationBufferType() {
|
||||
if (Properties::oem_crypto_use_secure_buffers()) {
|
||||
if (GetSecurityLevel() == kSecurityLevelL1) {
|
||||
destination_buffer_type_ = OEMCrypto_BufferType_Secure;
|
||||
} else {
|
||||
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
|
||||
}
|
||||
} else if (Properties::oem_crypto_use_fifo()) {
|
||||
destination_buffer_type_ = OEMCrypto_BufferType_Direct;
|
||||
} else if (Properties::oem_crypto_use_userspace_buffers()) {
|
||||
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
is_destination_buffer_type_valid_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& nonce,
|
||||
const std::string& enc_rsa_key,
|
||||
const std::string& rsa_key_iv,
|
||||
std::string* wrapped_rsa_key) {
|
||||
LOGD("CryptoSession::RewrapDeviceRSAKey, session id=%ld",
|
||||
static_cast<uint32_t>(oec_session_id_));
|
||||
|
||||
const uint8_t* signed_msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
const uint8_t* msg_rsa_key = NULL;
|
||||
const uint8_t* msg_rsa_key_iv = NULL;
|
||||
const uint32_t* msg_nonce = NULL;
|
||||
if (enc_rsa_key.size() >= MAC_KEY_SIZE && rsa_key_iv.size() >= KEY_IV_SIZE) {
|
||||
msg_rsa_key = signed_msg + GetOffset(message, enc_rsa_key);
|
||||
msg_rsa_key_iv = signed_msg + GetOffset(message, rsa_key_iv);
|
||||
msg_nonce = reinterpret_cast<const uint32_t*>(signed_msg +
|
||||
GetOffset(message, nonce));
|
||||
}
|
||||
|
||||
// Gets wrapped_rsa_key_length by passing NULL as uint8_t* wrapped_rsa_key
|
||||
// and 0 as wrapped_rsa_key_length.
|
||||
size_t wrapped_rsa_key_length = 0;
|
||||
OEMCryptoResult status = OEMCrypto_RewrapDeviceRSAKey(
|
||||
oec_session_id_, signed_msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
|
||||
msg_nonce, msg_rsa_key, enc_rsa_key.size(), msg_rsa_key_iv, NULL,
|
||||
&wrapped_rsa_key_length);
|
||||
if (status != OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||
LOGE("OEMCrypto_RewrapDeviceRSAKey fails to get wrapped_rsa_key_length");
|
||||
return false;
|
||||
}
|
||||
|
||||
wrapped_rsa_key->resize(wrapped_rsa_key_length);
|
||||
status = OEMCrypto_RewrapDeviceRSAKey(
|
||||
oec_session_id_, signed_msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
|
||||
msg_nonce, msg_rsa_key, enc_rsa_key.size(), msg_rsa_key_iv,
|
||||
reinterpret_cast<uint8_t*>(&(*wrapped_rsa_key)[0]),
|
||||
&wrapped_rsa_key_length);
|
||||
|
||||
wrapped_rsa_key->resize(wrapped_rsa_key_length);
|
||||
|
||||
if (OEMCrypto_SUCCESS != status) {
|
||||
LOGE("OEMCrypto_RewrapDeviceRSAKey fails with %d", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GetRandom(size_t data_length, uint8_t* random_data) {
|
||||
if (random_data == NULL) {
|
||||
LOGE("CryptoSession::GetRandom: random data destination not provided");
|
||||
return false;
|
||||
}
|
||||
OEMCryptoResult sts = OEMCrypto_GetRandom(random_data, data_length);
|
||||
|
||||
if (sts != OEMCrypto_SUCCESS) {
|
||||
LOGE("OEMCrypto_GetRandom fails with %d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
528
core/src/device_files.cpp
Normal file
528
core/src/device_files.cpp
Normal file
@@ -0,0 +1,528 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "device_files.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "device_files.pb.h"
|
||||
#include "file_store.h"
|
||||
#include "log.h"
|
||||
#include "openssl/sha.h"
|
||||
#include "properties.h"
|
||||
|
||||
// Protobuf generated classes.
|
||||
using video_widevine_client::sdk::DeviceCertificate;
|
||||
using video_widevine_client::sdk::HashedFile;
|
||||
using video_widevine_client::sdk::License;
|
||||
using video_widevine_client::sdk::License_LicenseState_ACTIVE;
|
||||
using video_widevine_client::sdk::License_LicenseState_RELEASING;
|
||||
|
||||
namespace {
|
||||
const char kCertificateFileName[] = "cert.bin";
|
||||
const char kLicenseFileNameExt[] = ".lic";
|
||||
const char kWildcard[] = "*";
|
||||
const char kDirectoryDelimiter = '/';
|
||||
const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"};
|
||||
size_t kSecurityLevelPathCompatibilityExclusionListSize =
|
||||
sizeof(kSecurityLevelPathCompatibilityExclusionList) /
|
||||
sizeof(*kSecurityLevelPathCompatibilityExclusionList);
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) {
|
||||
if (handle == NULL) {
|
||||
LOGW("DeviceFiles::Init: Invalid file handle parameter");
|
||||
return false;
|
||||
}
|
||||
switch (security_level) {
|
||||
case kSecurityLevelL1:
|
||||
case kSecurityLevelL2:
|
||||
case kSecurityLevelL3:
|
||||
break;
|
||||
default:
|
||||
LOGW("DeviceFiles::Init: Unsupported security level %d", security_level);
|
||||
return false;
|
||||
}
|
||||
file_ = const_cast<File*>(handle);
|
||||
security_level_ = security_level;
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreCertificate(const std::string& certificate,
|
||||
const std::string& wrapped_private_key) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::StoreCertificate: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill in file information
|
||||
video_widevine_client::sdk::File file;
|
||||
|
||||
file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE);
|
||||
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||
|
||||
DeviceCertificate* device_certificate = file.mutable_device_certificate();
|
||||
device_certificate->set_certificate(certificate);
|
||||
device_certificate->set_wrapped_private_key(wrapped_private_key);
|
||||
|
||||
std::string serialized_string;
|
||||
file.SerializeToString(&serialized_string);
|
||||
|
||||
// calculate SHA hash
|
||||
std::string hash;
|
||||
if (!Hash(serialized_string, &hash)) {
|
||||
LOGW("DeviceFiles::StoreCertificate: Hash computation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill in hashed file data
|
||||
HashedFile hashed_file;
|
||||
hashed_file.set_file(serialized_string);
|
||||
hashed_file.set_hash(hash);
|
||||
|
||||
hashed_file.SerializeToString(&serialized_string);
|
||||
|
||||
return StoreFile(kCertificateFileName, serialized_string);
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveCertificate(std::string* certificate,
|
||||
std::string* wrapped_private_key) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Properties::security_level_path_backward_compatibility_support()) {
|
||||
SecurityLevelPathBackwardCompatibility();
|
||||
}
|
||||
|
||||
std::string serialized_hashed_file;
|
||||
if (!RetrieveFile(kCertificateFileName, &serialized_hashed_file))
|
||||
return false;
|
||||
|
||||
HashedFile hashed_file;
|
||||
if (!hashed_file.ParseFromString(serialized_hashed_file)) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse hash file");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string hash;
|
||||
if (!Hash(hashed_file.file(), &hash)) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Hash computation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hash.compare(hashed_file.hash())) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Hash mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!file.ParseFromString(hashed_file.file())) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Incorrect file type");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Incorrect file version");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.has_device_certificate()) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Certificate not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceCertificate device_certificate = file.device_certificate();
|
||||
|
||||
*certificate = device_certificate.certificate();
|
||||
*wrapped_private_key = device_certificate.wrapped_private_key();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreLicense(const std::string& key_set_id,
|
||||
const LicenseState state,
|
||||
const CdmInitData& pssh_data,
|
||||
const CdmKeyMessage& license_request,
|
||||
const CdmKeyResponse& license_message,
|
||||
const CdmKeyMessage& license_renewal_request,
|
||||
const CdmKeyResponse& license_renewal,
|
||||
const std::string& release_server_url) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::StoreLicense: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill in file information
|
||||
video_widevine_client::sdk::File file;
|
||||
|
||||
file.set_type(video_widevine_client::sdk::File::LICENSE);
|
||||
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||
|
||||
License* license = file.mutable_license();
|
||||
switch (state) {
|
||||
case kLicenseStateActive:
|
||||
license->set_state(License_LicenseState_ACTIVE);
|
||||
break;
|
||||
case kLicenseStateReleasing:
|
||||
license->set_state(License_LicenseState_RELEASING);
|
||||
break;
|
||||
default:
|
||||
LOGW("DeviceFiles::StoreLicense: Unknown license state: %u", state);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
license->set_pssh_data(pssh_data);
|
||||
license->set_license_request(license_request);
|
||||
license->set_license(license_message);
|
||||
license->set_renewal_request(license_renewal_request);
|
||||
license->set_renewal(license_renewal);
|
||||
license->set_release_server_url(release_server_url);
|
||||
|
||||
std::string serialized_string;
|
||||
file.SerializeToString(&serialized_string);
|
||||
|
||||
// calculate SHA hash
|
||||
std::string hash;
|
||||
if (!Hash(serialized_string, &hash)) {
|
||||
LOGW("DeviceFiles::StoreLicense: Hash computation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// File in hashed file data
|
||||
HashedFile hashed_file;
|
||||
hashed_file.set_file(serialized_string);
|
||||
hashed_file.set_hash(hash);
|
||||
|
||||
hashed_file.SerializeToString(&serialized_string);
|
||||
|
||||
std::string file_name = key_set_id + kLicenseFileNameExt;
|
||||
return StoreFile(file_name.c_str(), serialized_string);
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
|
||||
LicenseState* state, CdmInitData* pssh_data,
|
||||
CdmKeyMessage* license_request,
|
||||
CdmKeyResponse* license_message,
|
||||
CdmKeyMessage* license_renewal_request,
|
||||
CdmKeyResponse* license_renewal,
|
||||
std::string* release_server_url) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string serialized_hashed_file;
|
||||
std::string file_name = key_set_id + kLicenseFileNameExt;
|
||||
if (!RetrieveFile(file_name.c_str(), &serialized_hashed_file)) return false;
|
||||
|
||||
HashedFile hashed_file;
|
||||
if (!hashed_file.ParseFromString(serialized_hashed_file)) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: Unable to parse hash file");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string hash;
|
||||
if (!Hash(hashed_file.file(), &hash)) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: Hash computation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hash.compare(hashed_file.hash())) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: Hash mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!file.ParseFromString(hashed_file.file())) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.type() != video_widevine_client::sdk::File::LICENSE) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: Incorrect file type");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: Incorrect file version");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.has_license()) {
|
||||
LOGW("DeviceFiles::RetrieveLicense: License not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
License license = file.license();
|
||||
|
||||
switch (license.state()) {
|
||||
case License_LicenseState_ACTIVE:
|
||||
*state = kLicenseStateActive;
|
||||
break;
|
||||
case License_LicenseState_RELEASING:
|
||||
*state = kLicenseStateReleasing;
|
||||
break;
|
||||
default:
|
||||
LOGW("DeviceFiles::RetrieveLicense: Unrecognized license state: %u",
|
||||
kLicenseStateUnknown);
|
||||
*state = kLicenseStateUnknown;
|
||||
break;
|
||||
}
|
||||
*pssh_data = license.pssh_data();
|
||||
*license_request = license.license_request();
|
||||
*license_message = license.license();
|
||||
*license_renewal_request = license.renewal_request();
|
||||
*license_renewal = license.renewal();
|
||||
*release_server_url = license.release_server_url();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::DeleteLicense(const std::string& key_set_id) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::DeleteLicense: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
|
||||
LOGW("DeviceFiles::DeleteLicense: Unable to get base path");
|
||||
return false;
|
||||
}
|
||||
path.append(key_set_id);
|
||||
path.append(kLicenseFileNameExt);
|
||||
|
||||
return file_->Remove(path);
|
||||
}
|
||||
|
||||
bool DeviceFiles::DeleteAllLicenses() {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::DeleteAllLicenses: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
|
||||
LOGW("DeviceFiles::DeleteAllLicenses: Unable to get base path");
|
||||
return false;
|
||||
}
|
||||
path.append(kWildcard);
|
||||
path.append(kLicenseFileNameExt);
|
||||
|
||||
return file_->Remove(path);
|
||||
}
|
||||
|
||||
bool DeviceFiles::DeleteAllFiles() {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::DeleteAllFiles: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
|
||||
LOGW("DeviceFiles::DeleteAllFiles: Unable to get base path");
|
||||
return false;
|
||||
}
|
||||
|
||||
return file_->Remove(path);
|
||||
}
|
||||
|
||||
bool DeviceFiles::LicenseExists(const std::string& key_set_id) {
|
||||
if (!initialized_) {
|
||||
LOGW("DeviceFiles::LicenseExists: not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
|
||||
LOGW("DeviceFiles::StoreFile: Unable to get base path");
|
||||
return false;
|
||||
}
|
||||
path.append(key_set_id);
|
||||
path.append(kLicenseFileNameExt);
|
||||
|
||||
return file_->Exists(path);
|
||||
}
|
||||
|
||||
bool DeviceFiles::Hash(const std::string& data, std::string* hash) {
|
||||
if (!hash) return false;
|
||||
|
||||
hash->resize(SHA256_DIGEST_LENGTH);
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
SHA256_Update(&sha256, data.data(), data.size());
|
||||
SHA256_Final(reinterpret_cast<unsigned char*>(&(*hash)[0]), &sha256);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
|
||||
if (!file_) {
|
||||
LOGW("DeviceFiles::StoreFile: Invalid file handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
LOGW("DeviceFiles::StoreFile: Unspecified file name parameter");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
|
||||
LOGW("DeviceFiles::StoreFile: Unable to get base path");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_->IsDirectory(path)) {
|
||||
if (!file_->CreateDirectory(path)) return false;
|
||||
}
|
||||
|
||||
path += name;
|
||||
|
||||
if (!file_->Open(path, File::kCreate | File::kTruncate | File::kBinary)) {
|
||||
LOGW("DeviceFiles::StoreFile: File open failed: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t bytes = file_->Write(data.data(), data.size());
|
||||
file_->Close();
|
||||
|
||||
if (bytes != static_cast<ssize_t>(data.size())) {
|
||||
LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("DeviceFiles::StoreFile: success: %s (%db)", path.c_str(), data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
|
||||
if (!file_) {
|
||||
LOGW("DeviceFiles::RetrieveFile: Invalid file handle");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
LOGW("DeviceFiles::RetrieveFile: Unspecified file name parameter");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
LOGW("DeviceFiles::RetrieveFile: Unspecified data parameter");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path;
|
||||
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
|
||||
LOGW("DeviceFiles::StoreFile: Unable to get base path");
|
||||
return false;
|
||||
}
|
||||
|
||||
path += name;
|
||||
|
||||
if (!file_->Exists(path)) {
|
||||
LOGW("DeviceFiles::RetrieveFile: %s does not exist", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t bytes = file_->FileSize(path);
|
||||
if (bytes <= 0) {
|
||||
LOGW("DeviceFiles::RetrieveFile: File size invalid: %d", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file_->Open(path, File::kReadOnly | File::kBinary)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data->resize(bytes);
|
||||
bytes = file_->Read(&(*data)[0], data->size());
|
||||
file_->Close();
|
||||
|
||||
if (bytes != static_cast<ssize_t>(data->size())) {
|
||||
LOGW("DeviceFiles::RetrieveFile: read failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("DeviceFiles::RetrieveFile: success: %s (%db)", path.c_str(),
|
||||
data->size());
|
||||
return true;
|
||||
}
|
||||
|
||||
void DeviceFiles::SecurityLevelPathBackwardCompatibility() {
|
||||
std::string path;
|
||||
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
|
||||
LOGW("DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to "
|
||||
"get base path");
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> security_dirs;
|
||||
if (!Properties::GetSecurityLevelDirectories(&security_dirs)) {
|
||||
LOGW("DeviceFiles::SecurityLevelPathBackwardCompatibility: Unable to "
|
||||
"get security directories");
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pos = std::string::npos;
|
||||
for (size_t i = 0; i < security_dirs.size(); ++i) {
|
||||
pos = path.find(security_dirs[i]);
|
||||
if (pos != std::string::npos && pos > 0 &&
|
||||
pos == path.size() - security_dirs[i].size() &&
|
||||
path[pos - 1] == kDirectoryDelimiter) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos == std::string::npos) {
|
||||
LOGV("DeviceFiles::SecurityLevelPathBackwardCompatibility: Security level "
|
||||
"specific path not found. Check properties?");
|
||||
return;
|
||||
}
|
||||
|
||||
std::string from_dir(path, 0, pos);
|
||||
|
||||
std::vector<std::string> files;
|
||||
if (!file_->List(from_dir, &files)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < files.size(); ++i) {
|
||||
std::string from = from_dir + files[i];
|
||||
bool exclude = false;
|
||||
for (size_t j = 0; j < kSecurityLevelPathCompatibilityExclusionListSize;
|
||||
++j) {
|
||||
if (files[i] == kSecurityLevelPathCompatibilityExclusionList[j]) {
|
||||
exclude = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (exclude) continue;
|
||||
if (!file_->IsRegularFile(from)) continue;
|
||||
|
||||
for (size_t j = 0; j < security_dirs.size(); ++j) {
|
||||
std::string to_dir = from_dir + security_dirs[j];
|
||||
if (!file_->Exists(to_dir)) file_->CreateDirectory(to_dir);
|
||||
std::string to = to_dir + files[i];
|
||||
file_->Copy(from, to);
|
||||
}
|
||||
file_->Remove(from);
|
||||
}
|
||||
}
|
||||
|
||||
std::string DeviceFiles::GetCertificateFileName() {
|
||||
return kCertificateFileName;
|
||||
}
|
||||
|
||||
std::string DeviceFiles::GetLicenseFileNameExtension() {
|
||||
return kLicenseFileNameExt;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
55
core/src/device_files.proto
Normal file
55
core/src/device_files.proto
Normal file
@@ -0,0 +1,55 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// device_files.proto
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Format of various files stored at the device.
|
||||
//
|
||||
syntax = "proto2";
|
||||
|
||||
package video_widevine_client.sdk;
|
||||
|
||||
// need this if we are using libprotobuf-cpp-2.3.0-lite
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message DeviceCertificate {
|
||||
optional bytes certificate = 1;
|
||||
optional bytes wrapped_private_key = 2;
|
||||
}
|
||||
|
||||
message License {
|
||||
enum LicenseState {
|
||||
ACTIVE = 1;
|
||||
RELEASING = 2;
|
||||
}
|
||||
|
||||
optional LicenseState state = 1;
|
||||
optional bytes pssh_data = 2;
|
||||
optional bytes license_request = 3;
|
||||
optional bytes license = 4;
|
||||
optional bytes renewal_request = 5;
|
||||
optional bytes renewal = 6;
|
||||
optional bytes release_server_url = 7;
|
||||
}
|
||||
|
||||
message File {
|
||||
enum FileType {
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
LICENSE = 2;
|
||||
}
|
||||
|
||||
enum FileVersion {
|
||||
VERSION_1 = 1;
|
||||
}
|
||||
|
||||
optional FileType type = 1;
|
||||
optional FileVersion version = 2 [default = VERSION_1];
|
||||
optional DeviceCertificate device_certificate = 3;
|
||||
optional License license = 4;
|
||||
}
|
||||
|
||||
message HashedFile {
|
||||
optional bytes file = 1;
|
||||
optional bytes hash = 2;
|
||||
}
|
||||
750
core/src/license.cpp
Normal file
750
core/src/license.cpp
Normal file
@@ -0,0 +1,750 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "license.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "crypto_key.h"
|
||||
#include "crypto_session.h"
|
||||
#include "log.h"
|
||||
#include "policy_engine.h"
|
||||
#include "properties.h"
|
||||
#include "privacy_crypto.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
std::string kCompanyNameKey = "company_name";
|
||||
std::string kModelNameKey = "model_name";
|
||||
std::string kArchitectureNameKey = "architecture_name";
|
||||
std::string kDeviceNameKey = "device_name";
|
||||
std::string kProductNameKey = "product_name";
|
||||
std::string kBuildInfoKey = "build_info";
|
||||
std::string kDeviceIdKey = "device_id";
|
||||
const unsigned char kServiceCertificateCAPublicKey[] = {
|
||||
0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81,
|
||||
0x00, 0xb4, 0xfe, 0x39, 0xc3, 0x65, 0x90, 0x03,
|
||||
0xdb, 0x3c, 0x11, 0x97, 0x09, 0xe8, 0x68, 0xcd,
|
||||
0xf2, 0xc3, 0x5e, 0x9b, 0xf2, 0xe7, 0x4d, 0x23,
|
||||
0xb1, 0x10, 0xdb, 0x87, 0x65, 0xdf, 0xdc, 0xfb,
|
||||
0x9f, 0x35, 0xa0, 0x57, 0x03, 0x53, 0x4c, 0xf6,
|
||||
0x6d, 0x35, 0x7d, 0xa6, 0x78, 0xdb, 0xb3, 0x36,
|
||||
0xd2, 0x3f, 0x9c, 0x40, 0xa9, 0x95, 0x26, 0x72,
|
||||
0x7f, 0xb8, 0xbe, 0x66, 0xdf, 0xc5, 0x21, 0x98,
|
||||
0x78, 0x15, 0x16, 0x68, 0x5d, 0x2f, 0x46, 0x0e,
|
||||
0x43, 0xcb, 0x8a, 0x84, 0x39, 0xab, 0xfb, 0xb0,
|
||||
0x35, 0x80, 0x22, 0xbe, 0x34, 0x23, 0x8b, 0xab,
|
||||
0x53, 0x5b, 0x72, 0xec, 0x4b, 0xb5, 0x48, 0x69,
|
||||
0x53, 0x3e, 0x47, 0x5f, 0xfd, 0x09, 0xfd, 0xa7,
|
||||
0x76, 0x13, 0x8f, 0x0f, 0x92, 0xd6, 0x4c, 0xdf,
|
||||
0xae, 0x76, 0xa9, 0xba, 0xd9, 0x22, 0x10, 0xa9,
|
||||
0x9d, 0x71, 0x45, 0xd6, 0xd7, 0xe1, 0x19, 0x25,
|
||||
0x85, 0x9c, 0x53, 0x9a, 0x97, 0xeb, 0x84, 0xd7,
|
||||
0xcc, 0xa8, 0x88, 0x82, 0x20, 0x70, 0x26, 0x20,
|
||||
0xfd, 0x7e, 0x40, 0x50, 0x27, 0xe2, 0x25, 0x93,
|
||||
0x6f, 0xbc, 0x3e, 0x72, 0xa0, 0xfa, 0xc1, 0xbd,
|
||||
0x29, 0xb4, 0x4d, 0x82, 0x5c, 0xc1, 0xb4, 0xcb,
|
||||
0x9c, 0x72, 0x7e, 0xb0, 0xe9, 0x8a, 0x17, 0x3e,
|
||||
0x19, 0x63, 0xfc, 0xfd, 0x82, 0x48, 0x2b, 0xb7,
|
||||
0xb2, 0x33, 0xb9, 0x7d, 0xec, 0x4b, 0xba, 0x89,
|
||||
0x1f, 0x27, 0xb8, 0x9b, 0x88, 0x48, 0x84, 0xaa,
|
||||
0x18, 0x92, 0x0e, 0x65, 0xf5, 0xc8, 0x6c, 0x11,
|
||||
0xff, 0x6b, 0x36, 0xe4, 0x74, 0x34, 0xca, 0x8c,
|
||||
0x33, 0xb1, 0xf9, 0xb8, 0x8e, 0xb4, 0xe6, 0x12,
|
||||
0xe0, 0x02, 0x98, 0x79, 0x52, 0x5e, 0x45, 0x33,
|
||||
0xff, 0x11, 0xdc, 0xeb, 0xc3, 0x53, 0xba, 0x7c,
|
||||
0x60, 0x1a, 0x11, 0x3d, 0x00, 0xfb, 0xd2, 0xb7,
|
||||
0xaa, 0x30, 0xfa, 0x4f, 0x5e, 0x48, 0x77, 0x5b,
|
||||
0x17, 0xdc, 0x75, 0xef, 0x6f, 0xd2, 0x19, 0x6d,
|
||||
0xdc, 0xbe, 0x7f, 0xb0, 0x78, 0x8f, 0xdc, 0x82,
|
||||
0x60, 0x4c, 0xbf, 0xe4, 0x29, 0x06, 0x5e, 0x69,
|
||||
0x8c, 0x39, 0x13, 0xad, 0x14, 0x25, 0xed, 0x19,
|
||||
0xb2, 0xf2, 0x9f, 0x01, 0x82, 0x0d, 0x56, 0x44,
|
||||
0x88, 0xc8, 0x35, 0xec, 0x1f, 0x11, 0xb3, 0x24,
|
||||
0xe0, 0x59, 0x0d, 0x37, 0xe4, 0x47, 0x3c, 0xea,
|
||||
0x4b, 0x7f, 0x97, 0x31, 0x1c, 0x81, 0x7c, 0x94,
|
||||
0x8a, 0x4c, 0x7d, 0x68, 0x15, 0x84, 0xff, 0xa5,
|
||||
0x08, 0xfd, 0x18, 0xe7, 0xe7, 0x2b, 0xe4, 0x47,
|
||||
0x27, 0x12, 0x11, 0xb8, 0x23, 0xec, 0x58, 0x93,
|
||||
0x3c, 0xac, 0x12, 0xd2, 0x88, 0x6d, 0x41, 0x3d,
|
||||
0xc5, 0xfe, 0x1c, 0xdc, 0xb9, 0xf8, 0xd4, 0x51,
|
||||
0x3e, 0x07, 0xe5, 0x03, 0x6f, 0xa7, 0x12, 0xe8,
|
||||
0x12, 0xf7, 0xb5, 0xce, 0xa6, 0x96, 0x55, 0x3f,
|
||||
0x78, 0xb4, 0x64, 0x82, 0x50, 0xd2, 0x33, 0x5f,
|
||||
0x91, 0x02, 0x03, 0x01, 0x00, 0x01};
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Protobuf generated classes.
|
||||
using video_widevine_server::sdk::ClientIdentification;
|
||||
using video_widevine_server::sdk::ClientIdentification_NameValue;
|
||||
using video_widevine_server::sdk::DeviceCertificate;
|
||||
using video_widevine_server::sdk::EncryptedClientIdentification;
|
||||
using video_widevine_server::sdk::LicenseRequest;
|
||||
using video_widevine_server::sdk::LicenseRequest_ContentIdentification;
|
||||
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC;
|
||||
using video_widevine_server::sdk::
|
||||
LicenseRequest_ContentIdentification_ExistingLicense;
|
||||
using video_widevine_server::sdk::License;
|
||||
using video_widevine_server::sdk::License_KeyContainer;
|
||||
using video_widevine_server::sdk::LicenseError;
|
||||
using video_widevine_server::sdk::SignedDeviceCertificate;
|
||||
using video_widevine_server::sdk::SignedMessage;
|
||||
|
||||
static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
|
||||
std::vector<CryptoKey> key_array;
|
||||
|
||||
// Extract content key(s)
|
||||
for (int i = 0; i < license.key_size(); ++i) {
|
||||
CryptoKey key;
|
||||
size_t length;
|
||||
switch (license.key(i).type()) {
|
||||
case License_KeyContainer::CONTENT:
|
||||
case License_KeyContainer::OPERATOR_SESSION:
|
||||
key.set_key_id(license.key(i).id());
|
||||
// Strip off PKCS#5 padding - since we know the key is 16 or 32 bytes,
|
||||
// the padding will always be 16 bytes.
|
||||
if (license.key(i).key().size() > 16) {
|
||||
length = license.key(i).key().size() - 16;
|
||||
} else {
|
||||
length = 0;
|
||||
}
|
||||
key.set_key_data(license.key(i).key().substr(0, length));
|
||||
key.set_key_data_iv(license.key(i).iv());
|
||||
if (license.key(i).has_key_control()) {
|
||||
key.set_key_control(license.key(i).key_control().key_control_block());
|
||||
key.set_key_control_iv(license.key(i).key_control().iv());
|
||||
}
|
||||
key_array.push_back(key);
|
||||
break;
|
||||
case License_KeyContainer::KEY_CONTROL:
|
||||
if (license.key(i).has_key_control()) {
|
||||
key.set_key_control(license.key(i).key_control().key_control_block());
|
||||
if (license.key(i).key_control().has_iv()) {
|
||||
key.set_key_control_iv(license.key(i).key_control().iv());
|
||||
}
|
||||
key_array.push_back(key);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Ignore SIGNING key types as they are not content related
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return key_array;
|
||||
}
|
||||
|
||||
bool CdmLicense::Init(const std::string& token, CryptoSession* session,
|
||||
PolicyEngine* policy_engine) {
|
||||
if (token.size() == 0) {
|
||||
LOGE("CdmLicense::Init: empty token provided");
|
||||
return false;
|
||||
}
|
||||
if (session == NULL || !session->IsOpen()) {
|
||||
LOGE("CdmLicense::Init: crypto session not provided or not open");
|
||||
return false;
|
||||
}
|
||||
if (policy_engine == NULL) {
|
||||
LOGE("CdmLicense::Init: no policy engine provided");
|
||||
return false;
|
||||
}
|
||||
token_ = token;
|
||||
session_ = session;
|
||||
policy_engine_ = policy_engine;
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
const CdmAppParameterMap& app_parameters,
|
||||
const CdmSessionId& session_id,
|
||||
CdmKeyMessage* signed_request,
|
||||
std::string* server_url) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest: not initialized");
|
||||
return false;
|
||||
}
|
||||
if (init_data.empty() && init_data_.empty()) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest: empty init data provided");
|
||||
return false;
|
||||
}
|
||||
if (session_id.empty()) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest: empty session id provided");
|
||||
return false;
|
||||
}
|
||||
if (!signed_request) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest: no signed request provided");
|
||||
return false;
|
||||
}
|
||||
if (!server_url) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest: no server url provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool privacy_mode_enabled = Properties::UsePrivacyMode(session_id);
|
||||
std::vector<uint8_t> cert = Properties::GetServiceCertificate(session_id);
|
||||
std::string serialized_service_certificate(cert.begin(), cert.end());
|
||||
|
||||
if (serialized_service_certificate.empty())
|
||||
serialized_service_certificate = service_certificate_;
|
||||
|
||||
if (privacy_mode_enabled && serialized_service_certificate.empty()) {
|
||||
init_data_ = init_data;
|
||||
return PrepareServiceCertificateRequest(signed_request, server_url);
|
||||
}
|
||||
|
||||
std::string request_id;
|
||||
session_->GenerateRequestId(request_id);
|
||||
|
||||
LicenseRequest license_request;
|
||||
ClientIdentification* client_id = license_request.mutable_client_id();
|
||||
|
||||
if (Properties::use_certificates_as_identification())
|
||||
client_id->set_type(ClientIdentification::DEVICE_CERTIFICATE);
|
||||
else
|
||||
client_id->set_type(ClientIdentification::KEYBOX);
|
||||
client_id->set_token(token_);
|
||||
|
||||
ClientIdentification_NameValue* client_info;
|
||||
CdmAppParameterMap::const_iterator iter;
|
||||
for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(iter->first);
|
||||
client_info->set_value(iter->second);
|
||||
}
|
||||
std::string value;
|
||||
if (Properties::GetCompanyName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kCompanyNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetModelName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kModelNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetArchitectureName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kArchitectureNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetDeviceName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kDeviceNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetProductName(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kProductNameKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
if (Properties::GetBuildInfo(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kBuildInfoKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
|
||||
if (session_->GetDeviceUniqueId(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kDeviceIdKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
|
||||
if (privacy_mode_enabled) {
|
||||
EncryptedClientIdentification* encrypted_client_id =
|
||||
license_request.mutable_encrypted_client_id();
|
||||
DeviceCertificate service_certificate;
|
||||
|
||||
if (!service_certificate.ParseFromString(serialized_service_certificate)) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest: unable to parse retrieved "
|
||||
"service certificate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (service_certificate.type() !=
|
||||
video_widevine_server::sdk::DeviceCertificate_CertificateType_SERVICE) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest: retrieved certificate not of type"
|
||||
" service, %d", service_certificate.type());
|
||||
return false;
|
||||
}
|
||||
encrypted_client_id->set_service_id(service_certificate.service_id());
|
||||
encrypted_client_id->set_service_certificate_serial_number(
|
||||
service_certificate.serial_number());
|
||||
|
||||
std::string iv(KEY_IV_SIZE, 0);
|
||||
std::string key(KEY_SIZE, 0);
|
||||
|
||||
if (!session_->GetRandom(key.size(), reinterpret_cast<uint8_t*>(&key[0]))) {
|
||||
return false;
|
||||
}
|
||||
if (!session_->GetRandom(iv.size(), reinterpret_cast<uint8_t*>(&iv[0]))) {
|
||||
return false;
|
||||
}
|
||||
std::string id, enc_id, enc_key;
|
||||
client_id->SerializeToString(&id);
|
||||
|
||||
AesCbcKey aes;
|
||||
if (!aes.Init(key)) return false;
|
||||
if (!aes.Encrypt(id, &enc_id, &iv)) return false;
|
||||
|
||||
RsaPublicKey rsa;
|
||||
if (!rsa.Init(service_certificate.public_key())) return false;
|
||||
if (!rsa.Encrypt(key, &enc_key)) return false;
|
||||
|
||||
encrypted_client_id->set_encrypted_client_id_iv(iv);
|
||||
encrypted_client_id->set_encrypted_privacy_key(enc_key);
|
||||
encrypted_client_id->set_encrypted_client_id(enc_id);
|
||||
license_request.clear_client_id();
|
||||
}
|
||||
|
||||
// Content Identification may be a cenc_id, a webm_id or a license_id
|
||||
LicenseRequest_ContentIdentification* content_id =
|
||||
license_request.mutable_content_id();
|
||||
|
||||
LicenseRequest_ContentIdentification_CENC* cenc_content_id =
|
||||
content_id->mutable_cenc_id();
|
||||
|
||||
if (!init_data.empty()) {
|
||||
cenc_content_id->add_pssh(init_data);
|
||||
} else if (privacy_mode_enabled && !init_data_.empty()) {
|
||||
cenc_content_id->add_pssh(init_data_);
|
||||
} else {
|
||||
LOGD("CdmLicense::PrepareKeyRequest: init data not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (license_type) {
|
||||
case kLicenseTypeOffline:
|
||||
cenc_content_id->set_license_type(video_widevine_server::sdk::OFFLINE);
|
||||
break;
|
||||
case kLicenseTypeStreaming:
|
||||
cenc_content_id->set_license_type(video_widevine_server::sdk::STREAMING);
|
||||
break;
|
||||
default:
|
||||
LOGD("CdmLicense::PrepareKeyRequest: Unknown license type = %d",
|
||||
license_type);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
cenc_content_id->set_request_id(request_id);
|
||||
|
||||
// The time field will be updated once the cdm wrapper
|
||||
// has been updated to pass us in the time.
|
||||
license_request.set_request_time(0);
|
||||
|
||||
license_request.set_type(LicenseRequest::NEW);
|
||||
|
||||
// Get/set the nonce. This value will be reflected in the Key Control Block
|
||||
// of the license response.
|
||||
uint32_t nonce;
|
||||
if (!session_->GenerateNonce(&nonce)) {
|
||||
return false;
|
||||
}
|
||||
license_request.set_key_control_nonce(nonce);
|
||||
LOGD("PrepareKeyRequest: nonce=%u", nonce);
|
||||
license_request.set_protocol_version(video_widevine_server::sdk::VERSION_2_1);
|
||||
|
||||
// License request is complete. Serialize it.
|
||||
std::string serialized_license_req;
|
||||
license_request.SerializeToString(&serialized_license_req);
|
||||
|
||||
if (Properties::use_certificates_as_identification())
|
||||
key_request_ = serialized_license_req;
|
||||
|
||||
// Derive signing and encryption keys and construct signature.
|
||||
std::string license_request_signature;
|
||||
if (!session_->PrepareRequest(serialized_license_req, false,
|
||||
&license_request_signature)) {
|
||||
signed_request->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (license_request_signature.empty()) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest: License request signature empty");
|
||||
signed_request->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Put serialize license request and signature together
|
||||
SignedMessage signed_message;
|
||||
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
||||
signed_message.set_signature(license_request_signature);
|
||||
signed_message.set_msg(serialized_license_req);
|
||||
|
||||
signed_message.SerializeToString(signed_request);
|
||||
|
||||
*server_url = server_url_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal,
|
||||
CdmKeyMessage* signed_request,
|
||||
std::string* server_url) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense::PrepareKeyUpdateRequest: not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!signed_request) {
|
||||
LOGE("CdmLicense::PrepareKeyUpdateRequest: No signed request provided");
|
||||
return false;
|
||||
}
|
||||
if (!server_url) {
|
||||
LOGE("CdmLicense::PrepareKeyUpdateRequest: No server url provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
LicenseRequest license_request;
|
||||
if (is_renewal)
|
||||
license_request.set_type(LicenseRequest::RENEWAL);
|
||||
else
|
||||
license_request.set_type(LicenseRequest::RELEASE);
|
||||
|
||||
LicenseRequest_ContentIdentification_ExistingLicense* current_license =
|
||||
license_request.mutable_content_id()->mutable_license();
|
||||
current_license->mutable_license_id()->CopyFrom(policy_engine_->license_id());
|
||||
|
||||
// Get/set the nonce. This value will be reflected in the Key Control Block
|
||||
// of the license response.
|
||||
uint32_t nonce;
|
||||
if (!session_->GenerateNonce(&nonce)) {
|
||||
return false;
|
||||
}
|
||||
license_request.set_key_control_nonce(nonce);
|
||||
LOGD("PrepareKeyUpdateRequest: nonce=%u", nonce);
|
||||
license_request.set_protocol_version(video_widevine_server::sdk::VERSION_2_1);
|
||||
|
||||
// License request is complete. Serialize it.
|
||||
std::string serialized_license_req;
|
||||
license_request.SerializeToString(&serialized_license_req);
|
||||
|
||||
// Construct signature.
|
||||
std::string license_request_signature;
|
||||
if (!session_->PrepareRenewalRequest(serialized_license_req,
|
||||
&license_request_signature))
|
||||
return false;
|
||||
|
||||
if (license_request_signature.empty()) {
|
||||
LOGE("CdmLicense::PrepareKeyUpdateRequest: empty license request"
|
||||
" signature");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Put serialize license request and signature together
|
||||
SignedMessage signed_message;
|
||||
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
||||
signed_message.set_signature(license_request_signature);
|
||||
signed_message.set_msg(serialized_license_req);
|
||||
|
||||
signed_message.SerializeToString(signed_request);
|
||||
*server_url = server_url_;
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
const CdmKeyResponse& license_response) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense::HandleKeyResponse: not initialized");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
if (license_response.empty()) {
|
||||
LOGE("CdmLicense::HandleKeyResponse: empty license response");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
SignedMessage signed_response;
|
||||
if (!signed_response.ParseFromString(license_response)) {
|
||||
LOGE("CdmLicense::HandleKeyResponse: unable to parse signed license"
|
||||
" response");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
switch (signed_response.type()) {
|
||||
case SignedMessage::LICENSE:
|
||||
break;
|
||||
case SignedMessage::SERVICE_CERTIFICATE:
|
||||
return CdmLicense::HandleServiceCertificateResponse(signed_response);
|
||||
case SignedMessage::ERROR:
|
||||
return HandleKeyErrorResponse(signed_response);
|
||||
default:
|
||||
LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d"
|
||||
, signed_response.type());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!signed_response.has_signature()) {
|
||||
LOGE("CdmLicense::HandleKeyResponse: license response is not signed");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
License license;
|
||||
if (!license.ParseFromString(signed_response.msg())) {
|
||||
LOGE("CdmLicense::HandleKeyResponse: unable to parse license response");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
if (!signed_response.has_session_key()) {
|
||||
LOGE("CdmLicense::HandleKeyResponse: no session keys present");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!session_->GenerateDerivedKeys(key_request_,
|
||||
signed_response.session_key()))
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
// Extract mac key
|
||||
std::string mac_key_iv;
|
||||
std::string mac_key;
|
||||
if (license.policy().can_renew()) {
|
||||
for (int i = 0; i < license.key_size(); ++i) {
|
||||
if (license.key(i).type() == License_KeyContainer::SIGNING) {
|
||||
mac_key_iv.assign(license.key(i).iv());
|
||||
|
||||
// Strip off PKCS#5 padding
|
||||
mac_key.assign(license.key(i).key().data(), MAC_KEY_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (mac_key_iv.size() != KEY_IV_SIZE || mac_key.size() != MAC_KEY_SIZE) {
|
||||
LOGE("CdmLicense::HandleKeyResponse: mac key/iv size error"
|
||||
"(key/iv size expected: %d/%d, actual: %d/%d",
|
||||
MAC_KEY_SIZE, KEY_IV_SIZE, mac_key.size(), mac_key_iv.size());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
||||
if (!key_array.size()) {
|
||||
LOGE("CdmLicense::HandleKeyResponse : No content keys.");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (license.policy().has_renewal_server_url()) {
|
||||
server_url_ = license.policy().renewal_server_url();
|
||||
}
|
||||
|
||||
policy_engine_->SetLicense(license);
|
||||
|
||||
CdmResponseType resp = session_->LoadKeys(signed_response.msg(),
|
||||
signed_response.signature(),
|
||||
mac_key_iv,
|
||||
mac_key,
|
||||
key_array.size(),
|
||||
&key_array[0]);
|
||||
|
||||
if (KEY_ADDED == resp) {
|
||||
loaded_keys_.clear();
|
||||
for (std::vector<CryptoKey>::iterator it = key_array.begin();
|
||||
it != key_array.end();
|
||||
++it) {
|
||||
loaded_keys_.insert(it->key_id());
|
||||
}
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
bool is_renewal, const CdmKeyResponse& license_response) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense::HandleKeyUpdateResponse: not initialized");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
if (license_response.empty()) {
|
||||
LOGE("CdmLicense::HandleKeyUpdateResponse : Empty license response.");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
SignedMessage signed_response;
|
||||
if (!signed_response.ParseFromString(license_response)) {
|
||||
LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse signed message");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (signed_response.type() == SignedMessage::ERROR) {
|
||||
return HandleKeyErrorResponse(signed_response);
|
||||
}
|
||||
|
||||
if (!signed_response.has_signature()) {
|
||||
LOGE("CdmLicense::HandleKeyUpdateResponse: signature missing");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
License license;
|
||||
if (!license.ParseFromString(signed_response.msg())) {
|
||||
LOGE("CdmLicense::HandleKeyUpdateResponse: Unable to parse license"
|
||||
" from signed message");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!license.has_id()) {
|
||||
LOGE("CdmLicense::HandleKeyUpdateResponse: license id not present");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_renewal) {
|
||||
if (license.policy().has_renewal_server_url() &&
|
||||
license.policy().renewal_server_url().size() > 0) {
|
||||
server_url_ = license.policy().renewal_server_url();
|
||||
}
|
||||
}
|
||||
|
||||
policy_engine_->UpdateLicense(license);
|
||||
|
||||
if (!is_renewal) return KEY_ADDED;
|
||||
|
||||
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
||||
|
||||
if (session_->RefreshKeys(signed_response.msg(), signed_response.signature(),
|
||||
key_array.size(), &key_array[0])) {
|
||||
return KEY_ADDED;
|
||||
} else {
|
||||
return KEY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool CdmLicense::RestoreOfflineLicense(
|
||||
CdmKeyMessage& license_request, CdmKeyResponse& license_response,
|
||||
CdmKeyResponse& license_renewal_response) {
|
||||
|
||||
if (license_request.empty() || license_response.empty()) {
|
||||
LOGE("CdmLicense::RestoreOfflineLicense: key_request or response empty: "
|
||||
"%u %u",
|
||||
license_request.size(), license_response.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
SignedMessage signed_request;
|
||||
if (!signed_request.ParseFromString(license_request)) {
|
||||
LOGE("CdmLicense::RestoreOfflineLicense: license_request parse failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
|
||||
LOGE("CdmLicense::RestoreOfflineLicense: license request type: expected = "
|
||||
"%d, actual = %d",
|
||||
SignedMessage::LICENSE_REQUEST, signed_request.type());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
key_request_ = signed_request.msg();
|
||||
} else {
|
||||
if (!session_->GenerateDerivedKeys(signed_request.msg())) return false;
|
||||
}
|
||||
|
||||
CdmResponseType sts = HandleKeyResponse(license_response);
|
||||
|
||||
if (sts != KEY_ADDED) return false;
|
||||
|
||||
if (!license_renewal_response.empty()) {
|
||||
sts = HandleKeyUpdateResponse(true, license_renewal_response);
|
||||
|
||||
if (sts != KEY_ADDED) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmLicense::PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
|
||||
std::string* server_url) {
|
||||
if (!initialized_) {
|
||||
LOGE("CdmLicense::PrepareServiceCertificateRequest: not initialized");
|
||||
return false;
|
||||
}
|
||||
if (!signed_request) {
|
||||
LOGE("CdmLicense::PrepareServiceCertificateRequest: no signed request"
|
||||
" provided");
|
||||
return false;
|
||||
}
|
||||
if (!server_url) {
|
||||
LOGE("CdmLicense::PrepareServiceCertificateRequest: no server url"
|
||||
" provided");
|
||||
return false;
|
||||
}
|
||||
SignedMessage signed_message;
|
||||
signed_message.set_type(SignedMessage::SERVICE_CERTIFICATE_REQUEST);
|
||||
signed_message.SerializeToString(signed_request);
|
||||
*server_url = server_url_;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleServiceCertificateResponse(
|
||||
const video_widevine_server::sdk::SignedMessage& signed_response) {
|
||||
|
||||
SignedDeviceCertificate signed_service_certificate;
|
||||
if (!signed_service_certificate.ParseFromString(signed_response.msg())) {
|
||||
LOGE("CdmLicense::HandleServiceCertificateResponse: unable to parse"
|
||||
"signed device certificate");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
RsaPublicKey root_ca_key;
|
||||
std::string ca_public_key(
|
||||
&kServiceCertificateCAPublicKey[0],
|
||||
&kServiceCertificateCAPublicKey[sizeof(kServiceCertificateCAPublicKey)]);
|
||||
if (!root_ca_key.Init(ca_public_key)) {
|
||||
LOGE("CdmLicense::HandleServiceCertificateResponse: public key "
|
||||
"initialization failed");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!root_ca_key.VerifySignature(
|
||||
signed_service_certificate.device_certificate(),
|
||||
signed_service_certificate.signature())) {
|
||||
LOGE("CdmLicense::HandleServiceCertificateResponse: service "
|
||||
"certificate verification failed");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
DeviceCertificate service_certificate;
|
||||
if (!service_certificate.ParseFromString(
|
||||
signed_service_certificate.device_certificate())) {
|
||||
LOGE("CdmLicense::HandleServiceCertificateResponse: unable to parse "
|
||||
"retrieved service certificate");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (service_certificate.type() !=
|
||||
video_widevine_server::sdk::DeviceCertificate_CertificateType_SERVICE) {
|
||||
LOGE("CdmLicense::HandleServiceCertificateResponse: certificate not of type"
|
||||
" service, %d",
|
||||
service_certificate.type());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
service_certificate_ = signed_service_certificate.device_certificate();
|
||||
return NEED_KEY;
|
||||
}
|
||||
|
||||
CdmResponseType CdmLicense::HandleKeyErrorResponse(
|
||||
const SignedMessage& signed_message) {
|
||||
|
||||
LicenseError license_error;
|
||||
if (!license_error.ParseFromString(signed_message.msg())) {
|
||||
LOGE("CdmLicense::HandleKeyErrorResponse: Unable to parse license error");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
switch (license_error.error_code()) {
|
||||
case LicenseError::INVALID_DEVICE_CERTIFICATE:
|
||||
return NEED_PROVISIONING;
|
||||
case LicenseError::REVOKED_DEVICE_CERTIFICATE:
|
||||
return DEVICE_REVOKED;
|
||||
case LicenseError::SERVICE_UNAVAILABLE:
|
||||
default:
|
||||
LOGW("CdmLicense::HandleKeyErrorResponse: Unknwon error type = %d",
|
||||
license_error.error_code());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
|
||||
return loaded_keys_.find(key_id) != loaded_keys_.end();
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
469
core/src/license_protocol.proto
Normal file
469
core/src/license_protocol.proto
Normal file
@@ -0,0 +1,469 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// license_protocol.proto
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Definitions of the protocol buffer messages used in the Widevine license
|
||||
// exchange protocol.
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package video_widevine_server.sdk;
|
||||
|
||||
// need this if we are using libprotobuf-cpp-2.3.0-lite
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
enum LicenseType {
|
||||
STREAMING = 1;
|
||||
OFFLINE = 2;
|
||||
}
|
||||
|
||||
// LicenseIdentification is propagated from LicenseRequest to License,
|
||||
// incrementing version with each iteration.
|
||||
message LicenseIdentification {
|
||||
optional bytes request_id = 1;
|
||||
optional bytes session_id = 2;
|
||||
optional bytes purchase_id = 3;
|
||||
optional LicenseType type = 4;
|
||||
optional int32 version = 5;
|
||||
}
|
||||
|
||||
message License {
|
||||
message Policy {
|
||||
// Indicates that playback of the content is allowed.
|
||||
optional bool can_play = 1 [default = false];
|
||||
|
||||
// Indicates that the license may be persisted to non-volatile
|
||||
// storage for offline use.
|
||||
optional bool can_persist = 2 [default = false];
|
||||
|
||||
// Indicates that renewal of this license is allowed.
|
||||
optional bool can_renew = 3 [default = false];
|
||||
|
||||
// For the |*duration*| fields, playback must halt when
|
||||
// license_start_time (seconds since the epoch (UTC)) +
|
||||
// license_duration_seconds is exceeded. A value of 0
|
||||
// indicates that there is no limit to the duration.
|
||||
|
||||
// Indicates the rental window.
|
||||
optional int64 rental_duration_seconds = 4 [default = 0];
|
||||
|
||||
// Indicates the viewing window, once playback has begun.
|
||||
optional int64 playback_duration_seconds = 5 [default = 0];
|
||||
|
||||
// Indicates the time window for this specific license.
|
||||
optional int64 license_duration_seconds = 6 [default = 0];
|
||||
|
||||
// The |renewal*| fields only apply if |can_renew| is true.
|
||||
|
||||
// The window of time, in which playback is allowed to continue while
|
||||
// renewal is attempted, yet unsuccessful due to backend problems with
|
||||
// the license server.
|
||||
optional int64 renewal_recovery_duration_seconds = 7 [default = 0];
|
||||
|
||||
// All renewal requests for this license shall be directed to the
|
||||
// specified URL.
|
||||
optional string renewal_server_url = 8;
|
||||
|
||||
// How many seconds after license_start_time, before renewal is first
|
||||
// attempted.
|
||||
optional int64 renewal_delay_seconds = 9 [default = 0];
|
||||
|
||||
// Specifies the delay in seconds between subsequent license
|
||||
// renewal requests, in case of failure.
|
||||
optional int64 renewal_retry_interval_seconds = 10 [default = 0];
|
||||
|
||||
// Indicates that the license shall be sent for renewal when usage is
|
||||
// started.
|
||||
optional bool renew_with_usage = 11 [default = false];
|
||||
}
|
||||
|
||||
message KeyContainer {
|
||||
enum KeyType {
|
||||
// Exactly one key of this type must appear.
|
||||
SIGNING = 1;
|
||||
CONTENT = 2;
|
||||
KEY_CONTROL = 3;
|
||||
OPERATOR_SESSION = 4;
|
||||
}
|
||||
|
||||
// The SecurityLevel enumeration allows the server to communicate the level
|
||||
// of robustness required by the client, in order to use the key.
|
||||
enum SecurityLevel {
|
||||
// Software-based whitebox crypto is required.
|
||||
SW_SECURE_CRYPTO = 1;
|
||||
|
||||
// Software crypto and an obfuscated decoder is required.
|
||||
SW_SECURE_DECODE = 2;
|
||||
|
||||
// The key material and crypto operations must be performed within a
|
||||
// hardware backed trusted execution environment.
|
||||
HW_SECURE_CRYPTO = 3;
|
||||
|
||||
// The crypto and decoding of content must be performed within a hardware
|
||||
// backed trusted execution environment.
|
||||
HW_SECURE_DECODE = 4;
|
||||
|
||||
// The crypto, decoding and all handling of the media (compressed and
|
||||
// uncompressed) must be handled within a hardware backed trusted
|
||||
// execution environment.
|
||||
HW_SECURE_ALL = 5;
|
||||
}
|
||||
|
||||
message KeyControl {
|
||||
// If present, the key control must be communicated to the secure
|
||||
// environment prior to any usage. This message is automatically generated
|
||||
// by the Widevine License Server SDK.
|
||||
optional bytes key_control_block = 1;
|
||||
optional bytes iv = 2;
|
||||
}
|
||||
|
||||
message OutputProtection {
|
||||
// Indicates whether HDCP is required on digital outputs, and which
|
||||
// version should be used.
|
||||
enum HDCP {
|
||||
HDCP_NONE = 0;
|
||||
HDCP_V1 = 1;
|
||||
HDCP_V2 = 2;
|
||||
}
|
||||
optional HDCP hdcp = 1 [default = HDCP_NONE];
|
||||
|
||||
// Indicate the CGMS setting to be inserted on analog output.
|
||||
enum CGMS {
|
||||
CGMS_NONE = 42;
|
||||
COPY_FREE = 0;
|
||||
COPY_ONCE = 2;
|
||||
COPY_NEVER = 3;
|
||||
}
|
||||
optional CGMS cgms_flags = 2 [default = CGMS_NONE];
|
||||
}
|
||||
|
||||
message OperatorSessionKeyPermissions {
|
||||
// Permissions/key usage flags for operator service keys
|
||||
// (type = OPERATOR_SESSION).
|
||||
optional bool allow_encrypt = 1 [default = false];
|
||||
optional bool allow_decrypt = 2 [default = false];
|
||||
optional bool allow_sign = 3 [default = false];
|
||||
optional bool allow_signature_verify = 4 [default = false];
|
||||
}
|
||||
|
||||
optional bytes id = 1;
|
||||
optional bytes iv = 2;
|
||||
optional bytes key = 3;
|
||||
optional KeyType type = 4;
|
||||
optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO];
|
||||
optional OutputProtection required_protection = 6;
|
||||
optional OutputProtection requested_protection = 7;
|
||||
optional KeyControl key_control = 8;
|
||||
optional OperatorSessionKeyPermissions operator_session_key_permissions = 9;
|
||||
}
|
||||
|
||||
optional LicenseIdentification id = 1;
|
||||
optional Policy policy = 2;
|
||||
repeated KeyContainer key = 3;
|
||||
optional int64 license_start_time = 4;
|
||||
}
|
||||
|
||||
enum ProtocolVersion {
|
||||
VERSION_2_0 = 20;
|
||||
VERSION_2_1 = 21;
|
||||
}
|
||||
|
||||
message LicenseRequest {
|
||||
message ContentIdentification {
|
||||
message CENC {
|
||||
repeated bytes pssh = 1;
|
||||
optional LicenseType license_type = 2;
|
||||
optional bytes request_id = 3; // Opaque, client-specified.
|
||||
}
|
||||
|
||||
message WebM {
|
||||
optional bytes header = 1;
|
||||
optional LicenseType license_type = 2;
|
||||
optional bytes request_id = 3; // Opaque, client-specified.
|
||||
}
|
||||
|
||||
message ExistingLicense {
|
||||
optional LicenseIdentification license_id = 1;
|
||||
optional int64 seconds_since_started = 2;
|
||||
}
|
||||
|
||||
// Exactly one of these must be present.
|
||||
optional CENC cenc_id = 1;
|
||||
optional WebM webm_id = 2;
|
||||
optional ExistingLicense license = 3;
|
||||
}
|
||||
|
||||
enum RequestType {
|
||||
NEW = 1;
|
||||
RENEWAL = 2;
|
||||
RELEASE = 3;
|
||||
}
|
||||
|
||||
// The client_id provides information authenticating the calling device. It
|
||||
// contains the Widevine keybox token that was installed on the device at the
|
||||
// factory. This field or encrypted_client_id below is required for a valid
|
||||
// license request, but both should never be present in the same request.
|
||||
optional ClientIdentification client_id = 1;
|
||||
optional ContentIdentification content_id = 2;
|
||||
optional RequestType type = 3;
|
||||
optional int64 request_time = 4;
|
||||
// Old-style decimal-encoded string key control nonce.
|
||||
optional bytes key_control_nonce_deprecated = 5;
|
||||
optional ProtocolVersion protocol_version = 6 [default = VERSION_2_0];
|
||||
// New-style uint32 key control nonce, please use instead of
|
||||
// key_control_nonce_deprecated.
|
||||
optional uint32 key_control_nonce = 7;
|
||||
// Encrypted ClientIdentification message, used for privacy purposes.
|
||||
optional EncryptedClientIdentification encrypted_client_id = 8;
|
||||
}
|
||||
|
||||
message LicenseError {
|
||||
enum Error {
|
||||
// The device credentials are invalid. The device must re-provision.
|
||||
INVALID_DEVICE_CERTIFICATE = 1;
|
||||
// The device credentials have been revoked. Re-provisioning is not
|
||||
// possible.
|
||||
REVOKED_DEVICE_CERTIFICATE = 2;
|
||||
// The service is currently unavailable due to the backend being down
|
||||
// or similar circumstances.
|
||||
SERVICE_UNAVAILABLE = 3;
|
||||
}
|
||||
optional Error error_code = 1;
|
||||
}
|
||||
|
||||
message SignedMessage {
|
||||
enum MessageType {
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
ERROR = 3;
|
||||
SERVICE_CERTIFICATE_REQUEST = 4;
|
||||
SERVICE_CERTIFICATE = 5;
|
||||
}
|
||||
|
||||
optional MessageType type = 1;
|
||||
optional bytes msg = 2;
|
||||
optional bytes signature = 3;
|
||||
optional bytes session_key = 4;
|
||||
}
|
||||
|
||||
// This message is used to pass optional data on initial license issuance.
|
||||
message SessionInit {
|
||||
optional bytes session_id = 1;
|
||||
optional bytes purchase_id = 2;
|
||||
// master_signing_key should be 128 bits in length.
|
||||
optional bytes master_signing_key = 3;
|
||||
// signing_key should be 512 bits in length to be split into two
|
||||
// (server || client) HMAC-SHA256 keys.
|
||||
optional bytes signing_key = 4;
|
||||
optional int64 license_start_time = 5;
|
||||
}
|
||||
|
||||
// This message is used by the server to preserve and restore session state.
|
||||
message SessionState {
|
||||
optional LicenseIdentification license_id = 1;
|
||||
optional bytes signing_key = 2;
|
||||
optional uint32 keybox_system_id = 3;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// certificate_provisioning.proto
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Public protocol buffer definitions for Widevine Device Certificate
|
||||
// Provisioning protocol.
|
||||
|
||||
// Provisioning request sent by client devices to provisioning service.
|
||||
message ProvisioningRequest {
|
||||
// Device root of trust and other client identification. Required.
|
||||
optional ClientIdentification client_id = 1;
|
||||
// Nonce value used to prevent replay attacks. Required.
|
||||
optional bytes nonce = 2;
|
||||
}
|
||||
|
||||
// Provisioning response sent by the provisioning server to client devices.
|
||||
message ProvisioningResponse {
|
||||
// AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded.
|
||||
// Required.
|
||||
optional bytes device_rsa_key = 1;
|
||||
// Initialization vector used to encrypt device_rsa_key. Required.
|
||||
optional bytes device_rsa_key_iv = 2;
|
||||
// Serialized SignedDeviceCertificate. Required.
|
||||
optional bytes device_certificate = 3;
|
||||
// Nonce value matching nonce in ProvisioningRequest. Required.
|
||||
optional bytes nonce = 4;
|
||||
}
|
||||
|
||||
// Serialized ProvisioningRequest or ProvisioningResponse signed with
|
||||
// The message authentication key.
|
||||
message SignedProvisioningMessage {
|
||||
// Serialized ProvisioningRequest or ProvisioningResponse. Required.
|
||||
optional bytes message = 1;
|
||||
// HMAC-SHA256 signature of message. Required.
|
||||
optional bytes signature = 2;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// client_identification.proto
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// ClientIdentification messages used by provisioning and license protocols.
|
||||
|
||||
// ClientIdentification message used to authenticate the client device.
|
||||
message ClientIdentification {
|
||||
enum TokenType {
|
||||
KEYBOX = 0;
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
}
|
||||
|
||||
message NameValue {
|
||||
optional string name = 1;
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
// Type of factory-provisioned device root of trust. Optional.
|
||||
optional TokenType type = 1 [default = KEYBOX];
|
||||
// Factory-provisioned device root of trust. Required.
|
||||
optional bytes token = 2;
|
||||
// Optional client information name/value pairs.
|
||||
repeated NameValue client_info = 3;
|
||||
}
|
||||
|
||||
// EncryptedClientIdentification message used to hold ClientIdentification
|
||||
// messages encrypted for privacy purposes.
|
||||
message EncryptedClientIdentification {
|
||||
// Service ID for which the ClientIdentifcation is encrypted (owner of service
|
||||
// certificate).
|
||||
optional string service_id = 1;
|
||||
// Serial number for the service certificate for which ClientIdentification is
|
||||
// encrypted.
|
||||
optional string service_certificate_serial_number = 2;
|
||||
// Serialized ClientIdentification message, encrypted with the privacy key
|
||||
// using AES-128-CBC with PKCS#5 padding.
|
||||
optional bytes encrypted_client_id = 3;
|
||||
// Initialization vector needed to decrypt encrypted_client_id.
|
||||
optional bytes encrypted_client_id_iv = 4;
|
||||
// AES-128 privacy key, encrytped with the service public public key using
|
||||
// RSA-OAEP.
|
||||
optional bytes encrypted_privacy_key = 5;
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// device_certificate.proto
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Device certificate and certificate status list format definitions.
|
||||
|
||||
// Certificate definition for user devices, intermediate, service, and root
|
||||
// certificates.
|
||||
message DeviceCertificate {
|
||||
enum CertificateType {
|
||||
ROOT = 0;
|
||||
INTERMEDIATE = 1;
|
||||
USER_DEVICE = 2;
|
||||
SERVICE = 3;
|
||||
}
|
||||
|
||||
// Type of certificate. Required.
|
||||
optional CertificateType type = 1;
|
||||
// 128-bit globally unique serial number of certificate.
|
||||
// Value is 0 for root certificate. Required.
|
||||
optional bytes serial_number = 2;
|
||||
// POSIX time, in seconds, when the certificate was created. Required.
|
||||
optional uint32 creation_time_seconds = 3;
|
||||
// Device public key. PKCS#1 ASN.1 DER-encoded. Required.
|
||||
optional bytes public_key = 4;
|
||||
// Widevine system ID for the device. Required for intermediate and
|
||||
// user device certificates.
|
||||
optional uint32 system_id = 5;
|
||||
// True if the certificate corresponds to a test (non production) device or
|
||||
// service. Optional.
|
||||
optional bool test_device = 6 [default = false];
|
||||
// Service identifier (web origin) for the service which owns the certificate.
|
||||
// Required for service certificates.
|
||||
optional string service_id = 7;
|
||||
}
|
||||
|
||||
// DeviceCertificate signed with intermediate or root certificate private key.
|
||||
message SignedDeviceCertificate {
|
||||
// Serialized DeviceCertificate. Required.
|
||||
optional bytes device_certificate = 1;
|
||||
// Signature of device_certificate. Signed with root or intermediate
|
||||
// certificate private key using RSASSA-PSS. Required.
|
||||
optional bytes signature = 2;
|
||||
// Intermediate signing certificate. Present only for user device
|
||||
// certificates. All others signed with root certificate private key.
|
||||
optional SignedDeviceCertificate signer = 3;
|
||||
}
|
||||
|
||||
// Contains device model information for a provisioned device.
|
||||
message ProvisionedDeviceInfo {
|
||||
enum WvSecurityLevel {
|
||||
// Defined in "WV Modular DRM Security Integration Guide for
|
||||
// Common Encryption (CENC)"
|
||||
LEVEL_UNSPECIFIED = 0;
|
||||
LEVEL_1 = 1;
|
||||
LEVEL_2 = 2;
|
||||
LEVEL_3 = 3;
|
||||
}
|
||||
|
||||
// Widevine system ID for the device. Mandatory.
|
||||
optional uint32 system_id = 1;
|
||||
// Name of system-on-a-chip. Optional.
|
||||
optional string soc = 2;
|
||||
// Name of manufacturer. Optional.
|
||||
optional string manufacturer = 3;
|
||||
// Manufacturer's model name. Matches "brand" in device metadata. Optional.
|
||||
optional string model = 4;
|
||||
// Type of device (Phone, Tablet, TV, etc).
|
||||
optional string device_type = 5;
|
||||
// Device model year. Optional.
|
||||
optional uint32 model_year = 6;
|
||||
// Widevine-defined security level. Optional.
|
||||
optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED];
|
||||
// True if the certificate corresponds to a test (non production) device.
|
||||
// Optional.
|
||||
optional bool test_device = 8 [default = false];
|
||||
}
|
||||
|
||||
// Contains the status of the root or an intermediate DeviceCertificate.
|
||||
message DeviceCertificateStatus {
|
||||
enum CertificateStatus {
|
||||
VALID = 0;
|
||||
REVOKED = 1;
|
||||
};
|
||||
|
||||
// Serial number of the DeviceCertificate to which this message refers.
|
||||
// Required.
|
||||
optional bytes serial_number = 1;
|
||||
// Status of the certificate. Optional.
|
||||
optional CertificateStatus status = 2 [default = VALID];
|
||||
// Device model information about the device to which the certificate
|
||||
// corresponds. Required.
|
||||
optional ProvisionedDeviceInfo device_info = 4;
|
||||
}
|
||||
|
||||
// List of DeviceCertificateStatus. Used to propagate certificate revocation and
|
||||
// update list.
|
||||
message DeviceCertificateStatusList {
|
||||
// POSIX time, in seconds, when the list was created. Required.
|
||||
optional uint32 creation_time_seconds = 1;
|
||||
// DeviceCertificateStatus for each certifificate.
|
||||
repeated DeviceCertificateStatus certificate_status = 2;
|
||||
}
|
||||
|
||||
// Signed CertificateStatusList
|
||||
message SignedCertificateStatusList {
|
||||
// Serialized DeviceCertificateStatusList. Required.
|
||||
optional bytes certificate_status_list = 1;
|
||||
// Signature of certificate_status_list. Signed with root certificate private
|
||||
// key using RSASSA-PSS. Required.
|
||||
optional bytes signature = 2;
|
||||
}
|
||||
46
core/src/oemcrypto_adapter_static.cpp
Normal file
46
core/src/oemcrypto_adapter_static.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Wrapper of OEMCrypto APIs for platforms that support Level 1 only.
|
||||
// This should be used when liboemcrypto.so is linked with the CDM code at
|
||||
// compile time.
|
||||
//
|
||||
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "oemcrypto_adapter.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION* session,
|
||||
SecurityLevel level) {
|
||||
return ::OEMCrypto_OpenSession(session);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCrypto_IsKeyboxValid(SecurityLevel level) {
|
||||
return ::OEMCrypto_IsKeyboxValid();
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID, size_t* idLength,
|
||||
SecurityLevel level) {
|
||||
return ::OEMCrypto_GetDeviceID(deviceID, idLength);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData, size_t* keyDataLength,
|
||||
SecurityLevel level) {
|
||||
return ::OEMCrypto_GetKeyData(keyData, keyDataLength);
|
||||
}
|
||||
|
||||
OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t* keybox,
|
||||
size_t keyBoxLength,
|
||||
SecurityLevel level) {
|
||||
return ::OEMCrypto_InstallKeybox(keybox, keyBoxLength);
|
||||
}
|
||||
|
||||
uint32_t OEMCrypto_APIVersion(SecurityLevel level) {
|
||||
return ::OEMCrypto_APIVersion();
|
||||
}
|
||||
|
||||
const char* OEMCrypto_SecurityLevel(SecurityLevel level) {
|
||||
return ::OEMCrypto_SecurityLevel();
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
287
core/src/policy_engine.cpp
Normal file
287
core/src/policy_engine.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "policy_engine.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "clock.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
PolicyEngine::PolicyEngine() {
|
||||
Init(new Clock());
|
||||
}
|
||||
|
||||
PolicyEngine::PolicyEngine(Clock* clock) {
|
||||
Init(clock);
|
||||
}
|
||||
|
||||
PolicyEngine::~PolicyEngine() {
|
||||
if (clock_)
|
||||
delete clock_;
|
||||
}
|
||||
|
||||
void PolicyEngine::Init(Clock* clock) {
|
||||
license_state_ = kLicenseStateInitial;
|
||||
can_decrypt_ = false;
|
||||
license_start_time_ = 0;
|
||||
license_received_time_ = 0;
|
||||
playback_start_time_ = 0;
|
||||
next_renewal_time_ = 0;
|
||||
policy_max_duration_seconds_ = 0;
|
||||
clock_ = clock;
|
||||
}
|
||||
|
||||
void PolicyEngine::OnTimerEvent(bool* event_occurred, CdmEventType* event) {
|
||||
*event_occurred = false;
|
||||
int64_t current_time = clock_->GetCurrentTime();
|
||||
|
||||
// License expiration trumps all.
|
||||
if ((IsLicenseDurationExpired(current_time) ||
|
||||
IsPlaybackDurationExpired(current_time)) &&
|
||||
license_state_ != kLicenseStateExpired) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
can_decrypt_ = false;
|
||||
*event = LICENSE_EXPIRED_EVENT;
|
||||
*event_occurred = true;
|
||||
return;
|
||||
}
|
||||
|
||||
bool renewal_needed = false;
|
||||
|
||||
// Test to determine if renewal should be attempted.
|
||||
switch (license_state_) {
|
||||
case kLicenseStateInitialPendingUsage:
|
||||
case kLicenseStateCanPlay: {
|
||||
if (IsRenewalDelayExpired(current_time))
|
||||
renewal_needed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case kLicenseStateNeedRenewal: {
|
||||
renewal_needed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case kLicenseStateWaitingLicenseUpdate: {
|
||||
if (IsRenewalRetryIntervalExpired(current_time))
|
||||
renewal_needed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case kLicenseStateInitial:
|
||||
case kLicenseStateExpired: {
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
can_decrypt_ = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (renewal_needed) {
|
||||
UpdateRenewalRequest(current_time);
|
||||
*event = LICENSE_RENEWAL_NEEDED_EVENT;
|
||||
*event_occurred = true;
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::SetLicense(
|
||||
const video_widevine_server::sdk::License& license) {
|
||||
license_id_.Clear();
|
||||
license_id_.CopyFrom(license.id());
|
||||
policy_.Clear();
|
||||
UpdateLicense(license);
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateLicense(
|
||||
const video_widevine_server::sdk::License& license) {
|
||||
if (!license.has_policy())
|
||||
return;
|
||||
|
||||
if (kLicenseStateExpired == license_state_) {
|
||||
LOGD("PolicyEngine::UpdateLicense: updating an expired license");
|
||||
}
|
||||
|
||||
policy_.MergeFrom(license.policy());
|
||||
|
||||
if (!policy_.can_play()) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
return;
|
||||
}
|
||||
|
||||
// some basic license validation
|
||||
if (license_state_ == kLicenseStateInitial) {
|
||||
// license start time needs to be present in the initial response
|
||||
if (!license.has_license_start_time())
|
||||
return;
|
||||
}
|
||||
else {
|
||||
// if renewal, discard license if version has not been updated
|
||||
if (license.id().version() > license_id_.version())
|
||||
license_id_.CopyFrom(license.id());
|
||||
else
|
||||
return;
|
||||
}
|
||||
|
||||
// Update time information
|
||||
int64_t current_time = clock_->GetCurrentTime();
|
||||
if (license.has_license_start_time())
|
||||
license_start_time_ = license.license_start_time();
|
||||
license_received_time_ = current_time;
|
||||
next_renewal_time_ = current_time +
|
||||
policy_.renewal_delay_seconds();
|
||||
|
||||
// Calculate policy_max_duration_seconds_. policy_max_duration_seconds_
|
||||
// will be set to the minimum of the following policies :
|
||||
// rental_duration_seconds and license_duration_seconds.
|
||||
// The value is used to determine when the license expires.
|
||||
policy_max_duration_seconds_ = 0;
|
||||
|
||||
if (policy_.has_rental_duration_seconds())
|
||||
policy_max_duration_seconds_ = policy_.rental_duration_seconds();
|
||||
|
||||
if ((policy_.license_duration_seconds() > 0) &&
|
||||
((policy_.license_duration_seconds() <
|
||||
policy_max_duration_seconds_) ||
|
||||
policy_max_duration_seconds_ == 0)) {
|
||||
policy_max_duration_seconds_ = policy_.license_duration_seconds();
|
||||
}
|
||||
|
||||
if (Properties::begin_license_usage_when_received())
|
||||
playback_start_time_ = current_time;
|
||||
|
||||
// Update state
|
||||
if (Properties::begin_license_usage_when_received()) {
|
||||
if (policy_.renew_with_usage()) {
|
||||
license_state_ = kLicenseStateNeedRenewal;
|
||||
}
|
||||
else {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
can_decrypt_ = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (license_state_ == kLicenseStateInitial) {
|
||||
license_state_ = kLicenseStateInitialPendingUsage;
|
||||
}
|
||||
else {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
can_decrypt_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::BeginDecryption() {
|
||||
if ((playback_start_time_ == 0) &&
|
||||
(!Properties::begin_license_usage_when_received())) {
|
||||
switch (license_state_) {
|
||||
case kLicenseStateInitialPendingUsage:
|
||||
case kLicenseStateNeedRenewal:
|
||||
case kLicenseStateWaitingLicenseUpdate:
|
||||
playback_start_time_ = clock_->GetCurrentTime();
|
||||
|
||||
if (policy_.renew_with_usage()) {
|
||||
license_state_ = kLicenseStateNeedRenewal;
|
||||
}
|
||||
else {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
can_decrypt_ = true;
|
||||
}
|
||||
break;
|
||||
case kLicenseStateCanPlay:
|
||||
case kLicenseStateInitial:
|
||||
case kLicenseStateExpired:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) {
|
||||
std::stringstream ss;
|
||||
int64_t current_time = clock_->GetCurrentTime();
|
||||
|
||||
if (license_state_ == kLicenseStateInitial)
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
(*key_info)[QUERY_KEY_LICENSE_TYPE] =
|
||||
license_id_.type() == video_widevine_server::sdk::STREAMING ?
|
||||
QUERY_VALUE_STREAMING : QUERY_VALUE_OFFLINE;
|
||||
(*key_info)[QUERY_KEY_PLAY_ALLOWED] = policy_.can_play() ?
|
||||
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
||||
(*key_info)[QUERY_KEY_PERSIST_ALLOWED] = policy_.can_persist() ?
|
||||
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
||||
(*key_info)[QUERY_KEY_RENEW_ALLOWED] = policy_.can_renew() ?
|
||||
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
||||
int64_t remaining_time = policy_max_duration_seconds_ +
|
||||
license_received_time_ - current_time;
|
||||
if (remaining_time < 0)
|
||||
remaining_time = 0;
|
||||
ss << remaining_time;
|
||||
(*key_info)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str();
|
||||
remaining_time = policy_.playback_duration_seconds() + playback_start_time_ -
|
||||
current_time;
|
||||
if (remaining_time < 0)
|
||||
remaining_time = 0;
|
||||
ss << remaining_time;
|
||||
(*key_info)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str();
|
||||
(*key_info)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url();
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
|
||||
license_state_ = kLicenseStateWaitingLicenseUpdate;
|
||||
next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds();
|
||||
}
|
||||
|
||||
// For the policy time fields checked in the following methods, a value of 0
|
||||
// indicates that there is no limit to the duration. These methods
|
||||
// will always return false if the value is 0.
|
||||
bool PolicyEngine::IsLicenseDurationExpired(int64_t current_time) {
|
||||
return policy_max_duration_seconds_ &&
|
||||
license_received_time_ + policy_max_duration_seconds_ <=
|
||||
current_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::IsPlaybackDurationExpired(int64_t current_time) {
|
||||
return (policy_.playback_duration_seconds() > 0) &&
|
||||
playback_start_time_ &&
|
||||
playback_start_time_ + policy_.playback_duration_seconds() <=
|
||||
current_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::IsRenewalDelayExpired(int64_t current_time) {
|
||||
return policy_.can_renew() &&
|
||||
(policy_.renewal_delay_seconds() > 0) &&
|
||||
license_received_time_ + policy_.renewal_delay_seconds() <=
|
||||
current_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::IsRenewalRecoveryDurationExpired(
|
||||
int64_t current_time) {
|
||||
// NOTE: Renewal Recovery Duration is currently not used.
|
||||
return (policy_.renewal_recovery_duration_seconds() > 0) &&
|
||||
license_received_time_ + policy_.renewal_recovery_duration_seconds() <=
|
||||
current_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::IsRenewalRetryIntervalExpired(
|
||||
int64_t current_time) {
|
||||
return policy_.can_renew() &&
|
||||
(policy_.renewal_retry_interval_seconds() > 0) &&
|
||||
next_renewal_time_ <= current_time;
|
||||
}
|
||||
|
||||
} // wvcdm
|
||||
217
core/src/privacy_crypto.cpp
Normal file
217
core/src/privacy_crypto.cpp
Normal file
@@ -0,0 +1,217 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Definition of classes representing RSA public keys used
|
||||
// for signature verification and encryption and decryption.
|
||||
//
|
||||
|
||||
#include "privacy_crypto.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "openssl/aes.h"
|
||||
#include "openssl/bio.h"
|
||||
#include "openssl/err.h"
|
||||
#include "openssl/pem.h"
|
||||
#include "openssl/sha.h"
|
||||
|
||||
namespace {
|
||||
const int kPssSaltLength = 20;
|
||||
const int kRsaPkcs1OaepPaddingLength = 41;
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
bool AesCbcKey::Init(const std::string& key) {
|
||||
if (key.empty()) {
|
||||
LOGE("AesCbcKey::Init: no key provided");
|
||||
return false;
|
||||
}
|
||||
if (key.size() != AES_BLOCK_SIZE) {
|
||||
LOGE("AesCbcKey::Init: unexpected key size: %d", key.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_init(&ctx_);
|
||||
if (EVP_EncryptInit(&ctx_, EVP_aes_128_cbc(),
|
||||
reinterpret_cast<const uint8_t*>(&key[0]), NULL) == 0) {
|
||||
LOGE("AesCbcKey::Init: AES CBC key setup failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
std::string* iv) {
|
||||
if (in.empty()) {
|
||||
LOGE("AesCbcKey::Encrypt: no cleartext provided");
|
||||
return false;
|
||||
}
|
||||
if (iv == NULL) {
|
||||
LOGE("AesCbcKey::Encrypt: initialization vector destination not provided");
|
||||
return false;
|
||||
}
|
||||
if (iv->size() != AES_BLOCK_SIZE) {
|
||||
LOGE("AesCbcKey::Encrypt: invalid iv size: %d", iv->size());
|
||||
return false;
|
||||
}
|
||||
if (out == NULL) {
|
||||
LOGE("AesCbcKey::Encrypt: crypttext destination not provided");
|
||||
return false;
|
||||
}
|
||||
if (!initialized_) {
|
||||
LOGE("AesCbcKey::Encrypt: AES key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit(&ctx_, NULL, NULL,
|
||||
reinterpret_cast<const uint8_t*>(iv->data())) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: AES CBC iv setup failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
out->resize(in.size() + AES_BLOCK_SIZE);
|
||||
int out_length = out->size();
|
||||
if (EVP_EncryptUpdate(
|
||||
&ctx_, reinterpret_cast<uint8_t*>(&(*out)[0]), &out_length,
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(in.data())),
|
||||
in.size()) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: encryption failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
int padding = 0;
|
||||
if (EVP_EncryptFinal(&ctx_, reinterpret_cast<uint8_t*>(&(*out)[out_length]),
|
||||
&padding) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: PKCS7 padding failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
out->resize(out_length + padding);
|
||||
return true;
|
||||
}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {
|
||||
if (key_ != NULL) {
|
||||
RSA_free(key_);
|
||||
}
|
||||
}
|
||||
|
||||
bool RsaPublicKey::Init(const std::string& serialized_key) {
|
||||
|
||||
if (serialized_key.empty()) {
|
||||
LOGE("RsaPublicKey::Init: no serialized key provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()),
|
||||
serialized_key.size());
|
||||
if (bio == NULL) {
|
||||
LOGE("RsaPublicKey::Init: BIO_new_mem_buf returned NULL");
|
||||
return false;
|
||||
}
|
||||
key_ = d2i_RSAPublicKey_bio(bio, NULL);
|
||||
BIO_free(bio);
|
||||
|
||||
if (key_ == NULL) {
|
||||
LOGE("RsaPublicKey::Init: RSA key deserialization failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
std::string* encrypted_message) {
|
||||
if (clear_message.empty()) {
|
||||
LOGE("RsaPublicKey::Encrypt: message to be encrypted is empty");
|
||||
return false;
|
||||
}
|
||||
if (encrypted_message == NULL) {
|
||||
LOGE("RsaPublicKey::Encrypt: no encrypt message buffer provided");
|
||||
return false;
|
||||
}
|
||||
if (key_ == NULL) {
|
||||
LOGE("RsaPublicKey::Encrypt: RSA key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
int rsa_size = RSA_size(key_);
|
||||
if (static_cast<int>(clear_message.size()) >
|
||||
rsa_size - kRsaPkcs1OaepPaddingLength) {
|
||||
LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d",
|
||||
" max allowed %d)", clear_message.size(),
|
||||
rsa_size - kRsaPkcs1OaepPaddingLength);
|
||||
return false;
|
||||
}
|
||||
encrypted_message->assign(rsa_size, 0);
|
||||
if (RSA_public_encrypt(
|
||||
clear_message.size(),
|
||||
const_cast<unsigned char*>(
|
||||
reinterpret_cast<const unsigned char*>(clear_message.data())),
|
||||
reinterpret_cast<unsigned char*>(&(*encrypted_message)[0]), key_,
|
||||
RSA_PKCS1_OAEP_PADDING) != rsa_size) {
|
||||
LOGE("RsaPublicKey::Encrypt: encrypt failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
const std::string& signature) {
|
||||
if (key_ == NULL) {
|
||||
LOGE("RsaPublicKey::VerifySignature: RSA key not initialized");
|
||||
return false;
|
||||
}
|
||||
if (message.empty()) {
|
||||
LOGE("RsaPublicKey::VerifySignature: signed message is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
int rsa_size = RSA_size(key_);
|
||||
if (static_cast<int>(signature.size()) != rsa_size) {
|
||||
LOGE(
|
||||
"RsaPublicKey::VerifySignature: message signature is of the wrong "
|
||||
"size (expected %d, actual %d)",
|
||||
rsa_size, signature.size());
|
||||
return false;
|
||||
}
|
||||
// Decrypt the signature.
|
||||
std::string padded_digest(signature.size(), 0);
|
||||
if (RSA_public_decrypt(
|
||||
signature.size(),
|
||||
const_cast<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));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
122
core/src/properties.cpp
Normal file
122
core/src/properties.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "log.h"
|
||||
#include "properties_configuration.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
const char* kSecurityLevelDirs[] = {"L1/", "L3/"};
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
bool Properties::begin_license_usage_when_received_;
|
||||
bool Properties::require_explicit_renew_request_;
|
||||
bool Properties::oem_crypto_use_secure_buffers_;
|
||||
bool Properties::oem_crypto_use_fifo_;
|
||||
bool Properties::oem_crypto_use_userspace_buffers_;
|
||||
bool Properties::use_certificates_as_identification_;
|
||||
bool Properties::extract_pssh_data_;
|
||||
bool Properties::decrypt_with_empty_session_support_;
|
||||
bool Properties::security_level_path_backward_compatibility_support_;
|
||||
scoped_ptr<CdmClientPropertySetMap> Properties::session_property_set_;
|
||||
|
||||
void Properties::Init() {
|
||||
begin_license_usage_when_received_ = kPropertyBeginLicenseUsageWhenReceived;
|
||||
require_explicit_renew_request_ = kPropertyRequireExplicitRenewRequest;
|
||||
oem_crypto_use_secure_buffers_ = kPropertyOemCryptoUseSecureBuffers;
|
||||
oem_crypto_use_fifo_ = kPropertyOemCryptoUseFifo;
|
||||
oem_crypto_use_userspace_buffers_ = kPropertyOemCryptoUseUserSpaceBuffers;
|
||||
use_certificates_as_identification_ =
|
||||
kPropertyUseCertificatesAsIdentification;
|
||||
extract_pssh_data_ = kExtractPsshData;
|
||||
decrypt_with_empty_session_support_ = kDecryptWithEmptySessionSupport;
|
||||
security_level_path_backward_compatibility_support_ =
|
||||
kSecurityLevelPathBackwardCompatibilitySupport;
|
||||
session_property_set_.reset(new CdmClientPropertySetMap());
|
||||
}
|
||||
|
||||
bool Properties::AddSessionPropertySet(
|
||||
const CdmSessionId& session_id, const CdmClientPropertySet* property_set) {
|
||||
if (NULL == session_property_set_.get()) {
|
||||
return false;
|
||||
}
|
||||
std::pair<CdmClientPropertySetMap::iterator, bool> result =
|
||||
session_property_set_->insert(
|
||||
std::pair<const CdmSessionId, const CdmClientPropertySet*>(
|
||||
session_id, property_set));
|
||||
return result.second;
|
||||
}
|
||||
|
||||
bool Properties::RemoveSessionPropertySet(const CdmSessionId& session_id) {
|
||||
if (NULL == session_property_set_.get()) {
|
||||
return false;
|
||||
}
|
||||
return (1 == session_property_set_->erase(session_id));
|
||||
}
|
||||
|
||||
const CdmClientPropertySet* Properties::GetCdmClientPropertySet(
|
||||
const CdmSessionId& session_id) {
|
||||
if (NULL != session_property_set_.get()) {
|
||||
CdmClientPropertySetMap::const_iterator it =
|
||||
session_property_set_->find(session_id);
|
||||
if (it != session_property_set_->end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const std::string Properties::GetSecurityLevel(const CdmSessionId& session_id) {
|
||||
const CdmClientPropertySet* property_set =
|
||||
GetCdmClientPropertySet(session_id);
|
||||
if (NULL == property_set) {
|
||||
LOGE("Properties::GetSecurityLevel: cannot find property set for %s",
|
||||
session_id.c_str());
|
||||
return "";
|
||||
}
|
||||
return property_set->security_level();
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> Properties::GetServiceCertificate(
|
||||
const CdmSessionId& session_id) {
|
||||
const CdmClientPropertySet* property_set =
|
||||
GetCdmClientPropertySet(session_id);
|
||||
if (NULL == property_set) {
|
||||
LOGE("Properties::GetServiceCertificate: cannot find property set for %s",
|
||||
session_id.c_str());
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
return property_set->service_certificate();
|
||||
}
|
||||
|
||||
bool Properties::UsePrivacyMode(const CdmSessionId& session_id) {
|
||||
const CdmClientPropertySet* property_set =
|
||||
GetCdmClientPropertySet(session_id);
|
||||
if (NULL == property_set) {
|
||||
LOGE("Properties::UsePrivacyMode: cannot find property set for %s",
|
||||
session_id.c_str());
|
||||
return false;
|
||||
}
|
||||
return property_set->use_privacy_mode();
|
||||
}
|
||||
|
||||
uint32_t Properties::GetSessionSharingId(const CdmSessionId& session_id) {
|
||||
const CdmClientPropertySet* property_set =
|
||||
GetCdmClientPropertySet(session_id);
|
||||
if (NULL == property_set) {
|
||||
LOGE("Properties::GetSessionSharingId: cannot find property set for %s",
|
||||
session_id.c_str());
|
||||
return 0;
|
||||
}
|
||||
return property_set->session_sharing_id();
|
||||
}
|
||||
|
||||
bool Properties::GetSecurityLevelDirectories(std::vector<std::string>* dirs) {
|
||||
dirs->resize(sizeof(kSecurityLevelDirs) / sizeof(const char*));
|
||||
for (size_t i = 0; i < dirs->size(); ++i) {
|
||||
(*dirs)[i] = kSecurityLevelDirs[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
160
core/src/string_conversions.cpp
Normal file
160
core/src/string_conversions.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "string_conversions.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
#include "modp_b64w.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static bool CharToDigit(char ch, unsigned char* digit) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
*digit = ch - '0';
|
||||
} else {
|
||||
ch = tolower(ch);
|
||||
if ((ch >= 'a') && (ch <= 'f')) {
|
||||
*digit = ch - 'a' + 10;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// converts an ascii hex string(2 bytes per digit) into a decimal byte string
|
||||
std::vector<uint8_t> a2b_hex(const std::string& byte) {
|
||||
std::vector<uint8_t> array;
|
||||
unsigned int count = byte.size();
|
||||
if (count == 0 || (count % 2) != 0) {
|
||||
LOGE("Invalid input size %u for string %s", count, byte.c_str());
|
||||
return array;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < count / 2; ++i) {
|
||||
unsigned char msb = 0; // most significant 4 bits
|
||||
unsigned char lsb = 0; // least significant 4 bits
|
||||
if (!CharToDigit(byte[i * 2], &msb) ||
|
||||
!CharToDigit(byte[i * 2 + 1], &lsb)) {
|
||||
LOGE("Invalid hex value %c%c at index %d", byte[i*2], byte[i*2+1], i);
|
||||
return array;
|
||||
}
|
||||
array.push_back((msb << 4) | lsb);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
std::string a2bs_hex(const std::string& byte) {
|
||||
std::vector<uint8_t> array = a2b_hex(byte);
|
||||
return std::string(array.begin(), array.end());
|
||||
}
|
||||
|
||||
std::string b2a_hex(const std::vector<uint8_t>& byte) {
|
||||
return HexEncode(&byte[0], byte.size());
|
||||
}
|
||||
|
||||
std::string b2a_hex(const std::string& byte) {
|
||||
return HexEncode(reinterpret_cast<const uint8_t *>(byte.data()),
|
||||
byte.length());
|
||||
}
|
||||
|
||||
// Filename-friendly base64 encoding (RFC4648), commonly referred to
|
||||
// as Base64WebSafeEncode.
|
||||
// This is the encoding required to interface with the provisioning
|
||||
// server's Apiary interface as well as for certain license server
|
||||
// transactions. It is also used for logging certain strings.
|
||||
// The difference between web safe encoding vs regular encoding is that
|
||||
// the web safe version replaces '+' with '-' and '/' with '_'.
|
||||
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
int in_size = bin_input.size();
|
||||
std::string b64_output(modp_b64w_encode_len(in_size), 0);
|
||||
|
||||
int out_size = modp_b64w_encode(&b64_output[0],
|
||||
reinterpret_cast<const char*>(&bin_input[0]),
|
||||
in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64SafeEncode failed");
|
||||
return std::string();
|
||||
}
|
||||
|
||||
b64_output.resize(out_size);
|
||||
return b64_output;
|
||||
}
|
||||
|
||||
std::string Base64SafeEncodeNoPad(const std::vector<uint8_t>& bin_input) {
|
||||
std::string b64_output = Base64SafeEncode(bin_input);
|
||||
// Output size: ceiling [ bin_input.size() * 4 / 3 ].
|
||||
b64_output.resize((bin_input.size() * 4 + 2) / 3);
|
||||
return b64_output;
|
||||
}
|
||||
|
||||
// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred
|
||||
// as Base64WebSafeDecode.
|
||||
std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
||||
if (b64_input.empty()) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
int in_size = b64_input.size();
|
||||
std::vector<uint8_t> bin_output(modp_b64w_decode_len(in_size), 0);
|
||||
int out_size = modp_b64w_decode(reinterpret_cast<char*>(&bin_output[0]),
|
||||
b64_input.data(),
|
||||
in_size);
|
||||
if (out_size == -1) {
|
||||
LOGE("Base64SafeDecode failed");
|
||||
return std::vector<uint8_t>(0);
|
||||
}
|
||||
|
||||
bin_output.resize(out_size);
|
||||
return bin_output;
|
||||
}
|
||||
|
||||
std::string HexEncode(const uint8_t* in_buffer, unsigned int size) {
|
||||
static const char kHexChars[] = "0123456789ABCDEF";
|
||||
|
||||
// Each input byte creates two output hex characters.
|
||||
std::string out_buffer(size * 2, '\0');
|
||||
|
||||
for (unsigned int i = 0; i < size; ++i) {
|
||||
char byte = in_buffer[i];
|
||||
out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];
|
||||
out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf];
|
||||
}
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
std::string IntToString(int value) {
|
||||
// log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
|
||||
// So round up to allocate 3 output characters per byte, plus 1 for '-'.
|
||||
const int kOutputBufSize = 3 * sizeof(int) + 1;
|
||||
char buffer[kOutputBufSize];
|
||||
memset(buffer, 0, kOutputBufSize);
|
||||
snprintf(buffer, kOutputBufSize, "%d", value);
|
||||
|
||||
std::string out_string(buffer);
|
||||
return out_string;
|
||||
}
|
||||
|
||||
std::string UintToString(unsigned int value) {
|
||||
// log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
|
||||
// So round up to allocate 3 output characters per byte.
|
||||
const int kOutputBufSize = 3 * sizeof(unsigned int);
|
||||
char buffer[kOutputBufSize];
|
||||
memset(buffer, 0, kOutputBufSize);
|
||||
snprintf(buffer, kOutputBufSize, "%u", value);
|
||||
|
||||
std::string out_string(buffer);
|
||||
return out_string;
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
73
core/test/base64_test.cpp
Normal file
73
core/test/base64_test.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Test vectors as suggested by http://tools.ietf.org/html/rfc4648#section-10
|
||||
const std::string kNullString("");
|
||||
const std::string kf("f");
|
||||
const std::string kfo("fo");
|
||||
const std::string kfoo("foo");
|
||||
const std::string kfoob("foob");
|
||||
const std::string kfooba("fooba");
|
||||
const std::string kfoobar("foobar");
|
||||
const std::string kfB64("Zg==");
|
||||
const std::string kfoB64("Zm8=");
|
||||
const std::string kfooB64("Zm9v");
|
||||
const std::string kfoobB64("Zm9vYg==");
|
||||
const std::string kfoobaB64("Zm9vYmE=");
|
||||
const std::string kfoobarB64("Zm9vYmFy");
|
||||
|
||||
// Arbitrary clear test vectors
|
||||
const std::string kMultipleOf24BitsData("Good day!");
|
||||
const std::string kOneByteOverData("Hello Friend!");
|
||||
const std::string kTwoBytesOverData("Hello Friend!!");
|
||||
const std::string kTestData =
|
||||
"\030\361\\\366\267> \331\210\360\\-\311:\324\256\376"
|
||||
"\261\234\241\326d\326\177\346\346\223\333Y\305\214\330";
|
||||
|
||||
// Arbitrary encoded test vectors
|
||||
const std::string kMultipleOf24BitsB64Data("R29vZCBkYXkh");
|
||||
const std::string kOneByteOverB64Data("SGVsbG8gR29vZ2xlcg==");
|
||||
const std::string kTwoBytesOverB64Data("SGVsbG8gR29vZ2xlcnM=");
|
||||
const std::string kB64TestData = "GPFc9rc-INmI8FwtyTrUrv6xnKHWZNZ_5uaT21nFjNg=";
|
||||
|
||||
const std::pair<const std::string*, const std::string*> kBase64TestVectors[] = {
|
||||
make_pair(&kNullString, &kNullString),
|
||||
make_pair(&kf, &kfB64),
|
||||
make_pair(&kfo, &kfoB64),
|
||||
make_pair(&kfoo, &kfooB64),
|
||||
make_pair(&kfoob, &kfoobB64),
|
||||
make_pair(&kfooba, &kfoobaB64),
|
||||
make_pair(&kfoobar, &kfoobarB64),
|
||||
make_pair(&kMultipleOf24BitsData, &kMultipleOf24BitsB64Data),
|
||||
make_pair(&kOneByteOverData, &kOneByteOverB64Data),
|
||||
make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data),
|
||||
make_pair(&kTestData, &kB64TestData),
|
||||
};
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class Base64EncodeDecodeTest : public ::testing::TestWithParam<
|
||||
std::pair<const std::string*, const std::string*> > {};
|
||||
|
||||
TEST_P(Base64EncodeDecodeTest, EncodeDecodeTest) {
|
||||
std::pair<const std::string*, const std::string*> values = GetParam();
|
||||
std::vector<uint8_t> decoded_vector = Base64SafeDecode(values.second->data());
|
||||
std::string decoded_string(decoded_vector.begin(), decoded_vector.end());
|
||||
EXPECT_STREQ(values.first->data(), decoded_string.data());
|
||||
std::string b64_string = Base64SafeEncode(decoded_vector);
|
||||
EXPECT_STREQ(values.second->data(), b64_string.data());
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(ExecutesBase64Test, Base64EncodeDecodeTest,
|
||||
::testing::ValuesIn(kBase64TestVectors));
|
||||
|
||||
} // namespace wvcdm
|
||||
324
core/test/cdm_engine_test.cpp
Normal file
324
core/test/cdm_engine_test.cpp
Normal file
@@ -0,0 +1,324 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#if defined(CHROMIUM_BUILD)
|
||||
#include "base/at_exit.h"
|
||||
#include "base/message_loop/message_loop.h"
|
||||
#endif
|
||||
#include "cdm_engine.h"
|
||||
#include "config_test_env.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "license_request.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "scoped_ptr.h"
|
||||
#include "string_conversions.h"
|
||||
#include "url_request.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace {
|
||||
// Http OK response code.
|
||||
const int kHttpOk = 200;
|
||||
|
||||
// Default license server, can be configured using --server command line option
|
||||
// Default key id (pssh), can be configured using --keyid command line option
|
||||
std::string g_client_auth;
|
||||
wvcdm::KeyId g_key_id;
|
||||
wvcdm::CdmKeySystem g_key_system;
|
||||
std::string g_license_server;
|
||||
std::string g_port;
|
||||
wvcdm::KeyId g_wrong_key_id;
|
||||
int g_use_full_path = 0; // cannot use boolean in getopt_long
|
||||
|
||||
// WHAT: This is an RSA certificate message from the provisioning server.
|
||||
// The client sends this certificate to a license server for
|
||||
// device authentication by the license server.
|
||||
// WHY: This certificate is used to test the CDM engine's provisioning
|
||||
// response handling.
|
||||
static wvcdm::CdmProvisioningResponse kValidJsonProvisioningResponse =
|
||||
"{\"signedResponse\": {"
|
||||
"\"message\": \"CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo"
|
||||
"TP751tvoADf86iLrf73mEzF58eSlaOjCpJRf2R3dojbNeSTy3JICmCc8vKtMjZRX9QWTvJbq_cg"
|
||||
"yMB8FQC8enuYhOaw1yJDYyCFHgik34NrUVUfmvaKKdSKQimqAZmjXi6P0znAn-XdPtz2xJVRxZp"
|
||||
"NH3QCD1bGcH_O1ercBW2JwF9KNalKFsxQrBhIwvyx-q-Ah4vf4r3M2HzY6JTHvcYGGc7dJNA3Xe"
|
||||
"WfCrYIvg0SGCP_z7Y2wICIA36VMwR3gnwNZlKkx6WGCCgsaU6IbLm4HpRBZfajuiOlasoYN4z1R"
|
||||
"lQ14Z32fdaFy8xOqLl-ZukxjWa7wv9zOSveH6JcHap1FS3R-RZ7E5WhfjxSTS0nWWZgmAjS2PkP"
|
||||
"9g4GPNsnpsrVymI39j6R6jPoc3__2EGN6qAvmp4pFKR7lQyslgNn2vYLuE0Ps5mIXVkxNiZOO3T"
|
||||
"jxgZyHaHOm1KmAZKI0EfddMATJCTt-UeLG3haqS_pYaBWcQ_xzWhoEHWU7_6ZaWrWemV8CVCg6s"
|
||||
"OB1SRI5MrkRBBSV0r8UKddLJGthZVjuTG75KK72KE9yhe86mCadvfVYe5keJ5GOC-t1EiFzBo4c"
|
||||
"4oqwkOCkkmYX_BEuZ3pOWztFp1_Br2Tl_fziw4O2vNIPCXB9yEewV6PkYPziTue3x4vRqD_mYjm"
|
||||
"1ia8fxISQnEC0vrqvrFFs9fLAHPlsvaRFnhv_XKpRwFoBdfqWTakb3k6uRz0Oh2SJ8euzFIyQNB"
|
||||
"efesMWk45DSrQjnlwlKXwZSiDKjAss0W2WwIb9F_x5LdB1Aa-CBudLVdxf62ggYaNZ57qx3YeHA"
|
||||
"jkqMGIF7Fq09D4OxM0jRsnrmXbJWKleUpJi7nHJgQGZk2ifN95gjuTNcRaGfYXMOsDoWdkrNAq0"
|
||||
"LScsPB06xEUR0DcO9vWx0zAEK7gsxxHziR7ZaYiIIkPysRR92r2NoLFPOUXf8j8ait-51jZmPKn"
|
||||
"bD6adieLy6ujSl907QsUgyGvokLs1OCsYHZr-X6vnyMjdk4G3QfmWwRepD_CMyXGvtLbTNCto7E"
|
||||
"L_M2yPZveAwYWwNlBtWK21gwIU2dgY298z7_S6jaQBc29f25sREjvN793ttYsPaeyom08qHYDnb"
|
||||
"jae3XX-2qqde6AGXlv__jO8WDZ5od6DWu2ThqV10ijVGFfGniRsSruzq0iq8zuAqTOGhmA9Dw7b"
|
||||
"rNlI95P4LpJA5pbjmNdnX7CQa2oHUuojmwlXRYuOA28PNEf-sc7ZPmMyFzedJi4EpkqzeQspEdH"
|
||||
"yNMf23iEjK6GOff7dgAaxg9vYHyprhkEml4BdmFVYwCYQy8o6KRcA0NgJb8c3tg4d3aRXWp6L-F"
|
||||
"sVhwqvq6FLOunSTNRIqhr2mOjRpU5w4mx-9GJRtk4XEcKT9YgUHGOUjGwfhQ5gBQDyZZVTddIUb"
|
||||
"MOThsSg7zr38oUCfgXeZaai3X2foKo1Bt94Q_q18dw5xNAN5e7rSwfilltHL23zbZduuhWkvp8S"
|
||||
"dag_NbO2C4IRMkzbjQBmiO9ixjXRhdqHlRRWcfR0wbQvEhD47egRVfnhKZ0W9G2-FGhyGuwJCq4"
|
||||
"CCAISEAfZ_94TqpXBImeAUzYhNr0Y48SbiwUijgIwggEKAoIBAQDRigR9nFm4mfBUh1Y3SGyOcF"
|
||||
"E-yK2NtfDiQe9l70KtkOeH4sB6MMB8g1QKPbUE8SBjPvXVJC_2DAWKjALzk4Aw-K-VmYe_Ag9CH"
|
||||
"JiS-XcfUYEGgK4jVMxadEq3LufEEREKUZnzjgQlR39dzgjFqIrC1bwfy3_99RsjPt6QpWPg36PI"
|
||||
"O4UKlmwBDTFzSOJB-4IV8Opy5Zv84BqPuyO9P5e3bXj_shRfy_XAGG2HGP_PpOCZWEfxuce0Iyu"
|
||||
"vpTPLQpTOgNw-VvUBGCWMZFoERopmqp_pQwWZ2a-EwlT_vvYY4SkuNjflBskR70xz4QzEo9665g"
|
||||
"k6I-HbHrTv29KEiAllAgMBAAEomSASgAIkKz1CSdFJVKcpO56jW0vsjKp92_cdqXBSEY3nuhzug"
|
||||
"_LFluMJx_IqATUcCOY-w6w0yKn2ezfZGE0MDIaCngEgQFI_DRoaSOBNNeirF59uYM0sK3P2eGS9"
|
||||
"G6F0l-OUXJdSO0b_LO8AbAK9LA3j7UHaajupJI1mdc4VtJfPRTsml2vIeKhDWXWaSvmeHgfF_tp"
|
||||
"-OV7oPuk6Ub26xpCp2He2rEAblCYEl25Zlz97K4DhyTOV5_xuSdSt-KbTLY9cWM5i9ncND1RzCc"
|
||||
"4qOixKarnMM5DdpZhs3B5xVj3yBAM1mVxPD2sZnqHSEN2EK7BMlHEnnyxhX0MGE36TQZR7P-I-G"
|
||||
"rUFCq8CCAESEDAxMjM0NTY3ODlBQkNERUYYspIEIo4CMIIBCgKCAQEApwA2YGXcvVRaKkC04RWU"
|
||||
"WBFPlFjd3qcfPCzgiAkpYVdnXlZ-7iePWTSaKqqdtE76p2rUyXpTwU6f4zT3PbfJEEdPKNo_zjF"
|
||||
"7_QYQ6_e-kvmv-z5o2u4aZEzzKfJznjnY9m_YsoCCcY61pPLCPs0KyrYEzZoTi1RzVCVUjL6Yem"
|
||||
"et2rNOs_qCqEpnmFZXVHHNEn_towHAaoskA5aIvpdmKrxTyYMGUVqIZRMY5Drta_FhW0zIHvTCr"
|
||||
"gheLV_4En-i_LshGDDa_kD7AcouNw7O3XaHgkYLOnePwHIHLH-dHoZb7Scp3wOXYu9E01s925xe"
|
||||
"G3s5tAttBGu7uyxfz7N6BQIDAQABKNKF2MwEEoADe9NAqNAxHpU13bMgz8LPySZJU8hY1RLwcfT"
|
||||
"UM47Xb3m-F-s2cfI7w08668f79kD45uRRzkVc8GbRIlVyzVC0WgIvtxEkYRKfgF_J7snUe2J2NN"
|
||||
"1FrkK7H3oYhcfPyYZH_SPZJr5HPoBFQTmS5A4l24U1dzQ6Z7_q-oS6uT0DiagTnzWhEg6AEnIkT"
|
||||
"sJtK3cZuKGYq3NDefZ7nslPuLXxdXl6SAEOtrk-RvCY6EBqYOuPUXgxXOEPbyM289R6aHQyPPYw"
|
||||
"qs9Pt9_E4BuMqCsbf5H5mLms9FA-wRx6mK2IaOboT4tf9_YObp3hVeL3WyxzXncETzJdE1GPGlO"
|
||||
"t_x5S_MylgJKbiWQYSdmqs3fzYExunw3wvI4tPHT_O8A_xKjyTEAvE5cBuCkfjwT716qUOzFUzF"
|
||||
"gZYLHnFiQLZekZUbUUlWY_CwU9Cv0UtxqQ6Oa835_Ug8_n1BwX6BPbmbcWe2Y19laSnDWg4JBNl"
|
||||
"F2CyP9N75jPtW9rVfjUSqKEPOwaIgwzNDkyMjM3NDcAAAA=\","
|
||||
"\"signature\": \"r-LpoZcbbr2KtoPaFnuWTVBh4Gup1k8vn0ClW2qm32A=\"}}";
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class WvCdmEngineTest : public testing::Test {
|
||||
public:
|
||||
virtual void SetUp() {
|
||||
cdm_engine_.OpenSession(g_key_system, NULL, &session_id_);
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
protected:
|
||||
void GenerateKeyRequest(const std::string& key_system,
|
||||
const std::string& key_id) {
|
||||
CdmAppParameterMap app_parameters;
|
||||
std::string server_url;
|
||||
std::string init_data = key_id;
|
||||
CdmKeySetId key_set_id;
|
||||
|
||||
if (!Properties::extract_pssh_data()) {
|
||||
EXPECT_TRUE(CdmEngine::ExtractWidevinePssh(key_id, &init_data));
|
||||
}
|
||||
|
||||
EXPECT_EQ(KEY_MESSAGE,
|
||||
cdm_engine_.GenerateKeyRequest(session_id_,
|
||||
key_set_id,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_,
|
||||
&server_url));
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
EXPECT_EQ(KEY_MESSAGE,
|
||||
cdm_engine_.GenerateRenewalRequest(session_id_,
|
||||
&key_msg_,
|
||||
&server_url_));
|
||||
}
|
||||
|
||||
// posts a request and extracts the drm message from the response
|
||||
std::string GetKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth) {
|
||||
// Use secure connection and chunk transfer coding.
|
||||
UrlRequest url_request(server_url + client_auth, g_port, true, true);
|
||||
if (!url_request.is_connected()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
url_request.PostRequest(key_msg_);
|
||||
std::string response;
|
||||
int resp_bytes = url_request.GetResponse(&response);
|
||||
LOGD("response:\r\n%s", response.c_str());
|
||||
LOGD("end %d bytes response dump", resp_bytes);
|
||||
|
||||
int status_code = url_request.GetStatusCode(response);
|
||||
EXPECT_EQ(kHttpOk, status_code);
|
||||
|
||||
if (status_code != kHttpOk) {
|
||||
return "";
|
||||
} else {
|
||||
std::string drm_msg;
|
||||
LicenseRequest lic_request;
|
||||
lic_request.GetDrmMessage(response, drm_msg);
|
||||
LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(),
|
||||
HexEncode(reinterpret_cast<const uint8_t*>(drm_msg.data()),
|
||||
drm_msg.size()).c_str());
|
||||
return drm_msg;
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyNewKeyResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
std::string& init_data){
|
||||
std::string resp = GetKeyRequestResponse(server_url,
|
||||
client_auth);
|
||||
CdmKeySetId key_set_id;
|
||||
EXPECT_EQ(cdm_engine_.AddKey(session_id_, resp, &key_set_id), KEY_ADDED);
|
||||
}
|
||||
|
||||
void VerifyRenewalKeyResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
std::string& init_data) {
|
||||
std::string resp = GetKeyRequestResponse(server_url,
|
||||
client_auth);
|
||||
EXPECT_EQ(cdm_engine_.RenewKey(session_id_, resp), wvcdm::KEY_ADDED);
|
||||
}
|
||||
|
||||
CdmEngine cdm_engine_;
|
||||
std::string key_msg_;
|
||||
std::string session_id_;
|
||||
std::string server_url_;
|
||||
};
|
||||
|
||||
TEST(WvCdmProvisioningTest, ProvisioningTest) {
|
||||
CdmEngine cdm_engine;
|
||||
CdmProvisioningRequest prov_request;
|
||||
std::string provisioning_server_url;
|
||||
|
||||
cdm_engine.GetProvisioningRequest(&prov_request, &provisioning_server_url);
|
||||
cdm_engine.HandleProvisioningResponse(kValidJsonProvisioningResponse);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, BaseMessageTest) {
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, WrongMessageTest) {
|
||||
std::string wrong_message = a2bs_hex(g_wrong_key_id);
|
||||
GenerateKeyRequest(g_key_system, wrong_message);
|
||||
|
||||
// We should receive a response with no license, i.e. the extracted license
|
||||
// response message should be empty.
|
||||
ASSERT_EQ("", GetKeyRequestResponse(g_license_server, g_client_auth));
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, NormalDecryption) {
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
VerifyNewKeyResponse(g_license_server, g_client_auth, g_key_id);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, LicenseRenewal) {
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
VerifyNewKeyResponse(g_license_server, g_client_auth, g_key_id);
|
||||
|
||||
GenerateRenewalRequest(g_key_system, g_key_id);
|
||||
VerifyRenewalKeyResponse(server_url_.empty() ? g_license_server : server_url_,
|
||||
g_client_auth,
|
||||
g_key_id);
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
wvcdm::InitLogging(argc, argv);
|
||||
|
||||
wvcdm::ConfigTestEnv config(wvcdm::kGoogleLicenseServerTest);
|
||||
g_client_auth.assign(config.client_auth());
|
||||
g_key_system.assign(config.key_system());
|
||||
g_wrong_key_id.assign(config.wrong_key_id());
|
||||
|
||||
// The following variables are configurable through command line options.
|
||||
g_license_server.assign(config.license_server());
|
||||
g_key_id.assign(config.key_id());
|
||||
g_port.assign(config.port());
|
||||
std::string license_server(g_license_server);
|
||||
|
||||
int show_usage = 0;
|
||||
static const struct option long_options[] = {
|
||||
{ "use_full_path", no_argument, &g_use_full_path, 0 },
|
||||
{ "keyid", required_argument, NULL, 'k' },
|
||||
{ "port", required_argument, NULL, 'p' },
|
||||
{ "server", required_argument, NULL, 's' },
|
||||
{ "vmodule", required_argument, NULL, 0 },
|
||||
{ "v", required_argument, NULL, 0 },
|
||||
{ NULL, 0, NULL, '\0' }
|
||||
};
|
||||
|
||||
int option_index = 0;
|
||||
int opt = 0;
|
||||
while ((opt = getopt_long(argc, argv, "k:p:s:u", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'k': {
|
||||
g_key_id.clear();
|
||||
g_key_id.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
g_port.clear();
|
||||
g_port.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
g_license_server.clear();
|
||||
g_license_server.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
g_use_full_path = 1;
|
||||
break;
|
||||
}
|
||||
case '?': {
|
||||
show_usage = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (show_usage) {
|
||||
std::cout << std::endl;
|
||||
std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl;
|
||||
std::cout << " enclose multiple arguments in '' when using adb shell" << std::endl;
|
||||
std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --port=<connection port>";
|
||||
std::cout << "specifies the port number, in decimal format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << g_port << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --server=<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;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --use_full_path";
|
||||
std::cout << "specify server url is not a proxy server" << std::endl;
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << g_license_server << std::endl;
|
||||
std::cout << "Port: " << g_port << std::endl;
|
||||
std::cout << "KeyID: " << g_key_id << std::endl << std::endl;
|
||||
|
||||
g_key_id = wvcdm::a2bs_hex(g_key_id);
|
||||
config.set_license_server(g_license_server);
|
||||
config.set_port(g_port);
|
||||
config.set_key_id(g_key_id);
|
||||
|
||||
#if defined(CHROMIUM_BUILD)
|
||||
base::AtExitManager exit;
|
||||
base::MessageLoop ttr(base::MessageLoop::TYPE_IO);
|
||||
#endif
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
108
core/test/config_test_env.cpp
Normal file
108
core/test/config_test_env.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "config_test_env.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// WHAT: URL of provisioning server (returned by GetProvisioningRequest())
|
||||
const std::string kProductionProvisioningServerUrl =
|
||||
"https://www.googleapis.com/"
|
||||
"certificateprovisioning/v1/devicecertificates/create"
|
||||
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
|
||||
|
||||
// WHAT: URL of test provisioning server - This is a placeholder for
|
||||
// an alternate provisioning server.
|
||||
// WHY: request_license_test uses this url.
|
||||
const std::string kProductionTestProvisioningServerUrl =
|
||||
"https://www.googleapis.com/"
|
||||
"certificateprovisioning/v1/devicecertificates/create"
|
||||
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
|
||||
|
||||
// Google test License Server parameters
|
||||
// This is a test server that has limited content and relatively
|
||||
// open access controls. It is maintained for integration testing of
|
||||
// CDM and OEMCrypto implementations.
|
||||
|
||||
// WHAT: URL for test license server.
|
||||
const std::string kGgLicenseServer =
|
||||
"http://widevine-proxy.appspot.com/proxy";
|
||||
|
||||
// WHAT: Test client authorization string.
|
||||
// WHY: Needed to pass client info to server.
|
||||
const std::string kGgClientAuth = "";
|
||||
|
||||
// WHAT: License info for test content. This is a valid
|
||||
// license for test content, registered with the
|
||||
// test license server.
|
||||
// Video ID: CJTHyHGbKKA
|
||||
// KeyId: 20ea8aa187aa50a7aa66723d7225c18f
|
||||
// ContentID: 0894c7c8719b28a0
|
||||
//
|
||||
// SD Video KeyID (142-144): 20EA8AA187AA50A7AA66723D7225C18F
|
||||
// HD Video KeyID (145,146): 1BEFCD2630185DFEA1A36E5C91C0BA63
|
||||
// Audio KeyID (148,149,150): 812A73CD62A45336B22FD13D00102106
|
||||
const std::string kGgKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"08011210812A73CD62A45336B22FD13D00102106"; // pssh data
|
||||
|
||||
// WHAT: An invalid license id, expected to fail
|
||||
const std::string kWrongKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0901121094889920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
|
||||
// Sample license server parameters
|
||||
// NOTE: This data is not valid. It will be replaced with license
|
||||
// server-specific data supplied by server administrators.
|
||||
|
||||
// WHAT: URL for license server.
|
||||
const std::string kTtCpLicenseServer =
|
||||
"http://widevine-proxy.appspot.com/invalid";
|
||||
|
||||
// WHAT: Client authorization string for license server request
|
||||
// WHY: May be needed to pass client info to server.
|
||||
const std::string kTtCpClientAuth = "";
|
||||
|
||||
// WHAT: License ID for test license server. This is not
|
||||
// a valid license ID unless the ID is registered
|
||||
// with the server. A valid license ID will be supplied
|
||||
// by server administrators.
|
||||
const std::string kTtCpKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0801121030313233343536373839616263646566"; // pssh data
|
||||
|
||||
// WHAT: Table of license servers useable for testing.
|
||||
// WHY: Allow testing against multiple license servers.
|
||||
// Fields:
|
||||
// id - enum for identifying/selecting this license server
|
||||
// url - url of license server
|
||||
// client_tag - default client authorization string
|
||||
// key_id - license id for test license on this server
|
||||
// port - http port
|
||||
// use_chunked_transfer - server communication setting
|
||||
// use_secure_transfer - server communication setting
|
||||
const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = {
|
||||
{ wvcdm::kGoogleLicenseServerTest, kGgLicenseServer,
|
||||
kGgClientAuth, kGgKeyId, kDefaultHttpsPort, true, true },
|
||||
{ wvcdm::kPartnerLicenseServer, kTtCpLicenseServer,
|
||||
kTtCpClientAuth, kTtCpKeyId, kDefaultHttpPort, false, false }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id)
|
||||
: client_auth_(license_servers[server_id].client_tag),
|
||||
key_id_(license_servers[server_id].key_id),
|
||||
key_system_("com.widevine.alpha"),
|
||||
license_server_(license_servers[server_id].url),
|
||||
port_(license_servers[server_id].port),
|
||||
provisioning_server_url_(kProductionProvisioningServerUrl),
|
||||
provisioning_test_server_url_(kProductionTestProvisioningServerUrl),
|
||||
use_chunked_transfer_(license_servers[server_id].use_chunked_transfer),
|
||||
use_secure_transfer_(license_servers[server_id].use_secure_transfer),
|
||||
wrong_key_id_(kWrongKeyId) {}
|
||||
|
||||
} // namespace wvcdm
|
||||
80
core/test/config_test_env.h
Normal file
80
core/test/config_test_env.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
#define CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace {
|
||||
const std::string kDefaultHttpsPort = "443";
|
||||
const std::string kDefaultHttpPort = "80";
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
//WHAT: Index into table of alternate license servers (license_servers[]).
|
||||
//WHY: Allow testing against multiple license server.
|
||||
typedef enum {
|
||||
kGoogleLicenseServerTest,
|
||||
kPartnerLicenseServer
|
||||
} LicenseServerId;
|
||||
|
||||
// Configures default test environment.
|
||||
class ConfigTestEnv {
|
||||
public:
|
||||
typedef struct {
|
||||
LicenseServerId id;
|
||||
std::string url;
|
||||
std::string client_tag;
|
||||
std::string key_id;
|
||||
std::string port;
|
||||
bool use_chunked_transfer;
|
||||
bool use_secure_transfer;
|
||||
} LicenseServerConfiguration;
|
||||
|
||||
explicit ConfigTestEnv(LicenseServerId server_id);
|
||||
~ConfigTestEnv() {};
|
||||
|
||||
const std::string& client_auth() const { return client_auth_; }
|
||||
const KeyId& key_id() const { return key_id_; }
|
||||
const CdmKeySystem& key_system() const { return key_system_; }
|
||||
const std::string& license_server() const { return license_server_; }
|
||||
const std::string& port() const { return port_; }
|
||||
const std::string& provisioning_server_url() const {
|
||||
return provisioning_server_url_;
|
||||
}
|
||||
const std::string& provisioning_test_server_url() const {
|
||||
return provisioning_test_server_url_;
|
||||
}
|
||||
bool use_chunked_transfer() { return use_chunked_transfer_; }
|
||||
bool use_secure_transfer() { return use_secure_transfer_; }
|
||||
const KeyId& wrong_key_id() const { return wrong_key_id_; }
|
||||
|
||||
void set_key_id(KeyId& key_id) { key_id_.assign(key_id); }
|
||||
void set_key_system(CdmKeySystem& key_system) {
|
||||
key_system_.assign(key_system);
|
||||
}
|
||||
void set_license_server(std::string& license_server) {
|
||||
license_server_.assign(license_server);
|
||||
}
|
||||
void set_port(std::string& port) { port_.assign(port); }
|
||||
|
||||
private:
|
||||
std::string client_auth_;
|
||||
KeyId key_id_;
|
||||
CdmKeySystem key_system_;
|
||||
std::string license_server_;
|
||||
std::string port_;
|
||||
std::string provisioning_server_url_;
|
||||
std::string provisioning_test_server_url_;
|
||||
bool use_chunked_transfer_;
|
||||
bool use_secure_transfer_;
|
||||
KeyId wrong_key_id_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
1478
core/test/device_files_unittest.cpp
Normal file
1478
core/test/device_files_unittest.cpp
Normal file
File diff suppressed because it is too large
Load Diff
266
core/test/file_store_unittest.cpp
Normal file
266
core/test/file_store_unittest.cpp
Normal file
@@ -0,0 +1,266 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "properties.h"
|
||||
#include "test_vectors.h"
|
||||
|
||||
namespace {
|
||||
const std::string kTestDirName = "test_dir";
|
||||
const std::string kTestFileName = "test.txt";
|
||||
const std::string kTestFileName2 = "test2.txt";
|
||||
const std::string kTestFileNameExt = ".txt";
|
||||
const std::string kWildcard = "*";
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class FileTest : public testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() { CreateTestDir(); }
|
||||
virtual void TearDown() { RemoveTestDir(); }
|
||||
|
||||
void CreateTestDir() {
|
||||
File file;
|
||||
if (!file.Exists(test_vectors::kTestDir)) {
|
||||
EXPECT_TRUE(file.CreateDirectory(test_vectors::kTestDir));
|
||||
}
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kTestDir));
|
||||
}
|
||||
|
||||
void RemoveTestDir() {
|
||||
File file;
|
||||
EXPECT_TRUE(file.Remove(test_vectors::kTestDir));
|
||||
}
|
||||
|
||||
std::string GenerateRandomData(uint32_t len) {
|
||||
std::string data(len, 0);
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
data[i] = rand() % 256;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(FileTest, FileExists) {
|
||||
File file;
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kFileExists));
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kDirExists));
|
||||
EXPECT_FALSE(file.Exists(test_vectors::kFileDoesNotExist));
|
||||
EXPECT_FALSE(file.Exists(test_vectors::kDirDoesNotExist));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, CreateDirectory) {
|
||||
File file;
|
||||
std::string dir_wo_delimiter =
|
||||
test_vectors::kTestDir.substr(0, test_vectors::kTestDir.size() - 1);
|
||||
if (file.Exists(dir_wo_delimiter)) EXPECT_TRUE(file.Remove(dir_wo_delimiter));
|
||||
EXPECT_FALSE(file.Exists(dir_wo_delimiter));
|
||||
EXPECT_TRUE(file.CreateDirectory(dir_wo_delimiter));
|
||||
EXPECT_TRUE(file.Exists(dir_wo_delimiter));
|
||||
EXPECT_TRUE(file.Remove(dir_wo_delimiter));
|
||||
EXPECT_TRUE(file.CreateDirectory(test_vectors::kTestDir));
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kTestDir));
|
||||
EXPECT_TRUE(file.Remove(test_vectors::kTestDir));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, RemoveDir) {
|
||||
File file;
|
||||
EXPECT_TRUE(file.Remove(test_vectors::kTestDir));
|
||||
EXPECT_FALSE(file.Exists(test_vectors::kTestDir));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, OpenFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
File handle;
|
||||
EXPECT_TRUE(handle.Remove(path));
|
||||
|
||||
File file;
|
||||
EXPECT_TRUE(file.Open(path, File::kCreate));
|
||||
file.Close();
|
||||
|
||||
EXPECT_TRUE(handle.Exists(path));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, RemoveDirAndFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
|
||||
File file;
|
||||
EXPECT_TRUE(file.Open(path, File::kCreate));
|
||||
file.Close();
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
EXPECT_TRUE(file.Remove(path));
|
||||
EXPECT_FALSE(file.Exists(path));
|
||||
|
||||
EXPECT_TRUE(file.Open(path, File::kCreate));
|
||||
file.Close();
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
RemoveTestDir();
|
||||
EXPECT_FALSE(file.Exists(test_vectors::kTestDir));
|
||||
EXPECT_FALSE(file.Exists(path));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, RemoveWildcardFiles) {
|
||||
std::string path1 = test_vectors::kTestDir + kTestFileName;
|
||||
std::string path2 = test_vectors::kTestDir + kTestFileName2;
|
||||
std::string wildcard_path =
|
||||
test_vectors::kTestDir + kWildcard + kTestFileNameExt;
|
||||
|
||||
File file;
|
||||
EXPECT_TRUE(file.Open(path1, File::kCreate));
|
||||
file.Close();
|
||||
EXPECT_TRUE(file.Open(path2, File::kCreate));
|
||||
file.Close();
|
||||
EXPECT_TRUE(file.Exists(path1));
|
||||
EXPECT_TRUE(file.Exists(path2));
|
||||
EXPECT_TRUE(file.Remove(wildcard_path));
|
||||
EXPECT_FALSE(file.Exists(path1));
|
||||
EXPECT_FALSE(file.Exists(path2));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, IsDir) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
File file;
|
||||
EXPECT_TRUE(file.Open(path, File::kCreate));
|
||||
file.Close();
|
||||
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kTestDir));
|
||||
EXPECT_FALSE(file.IsDirectory(path));
|
||||
EXPECT_TRUE(file.IsDirectory(test_vectors::kTestDir));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, IsRegularFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
File file;
|
||||
EXPECT_TRUE(file.Open(path, File::kCreate));
|
||||
file.Close();
|
||||
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kTestDir));
|
||||
EXPECT_TRUE(file.IsRegularFile(path));
|
||||
EXPECT_FALSE(file.IsRegularFile(test_vectors::kTestDir));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, FileSize) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
File file;
|
||||
file.Remove(path);
|
||||
|
||||
std::string write_data = GenerateRandomData(600);
|
||||
File wr_file;
|
||||
EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary));
|
||||
EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size()));
|
||||
wr_file.Close();
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
|
||||
EXPECT_EQ(static_cast<ssize_t>(write_data.size()), file.FileSize(path));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, WriteReadTextFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
File file;
|
||||
file.Remove(path);
|
||||
|
||||
std::string write_data = "This is a test";
|
||||
File wr_file;
|
||||
EXPECT_TRUE(wr_file.Open(path, File::kCreate));
|
||||
EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size()));
|
||||
wr_file.Close();
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
|
||||
std::string read_data;
|
||||
read_data.resize(file.FileSize(path));
|
||||
File rd_file;
|
||||
EXPECT_TRUE(rd_file.Open(path, File::kReadOnly));
|
||||
EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size()));
|
||||
rd_file.Close();
|
||||
EXPECT_EQ(write_data, read_data);
|
||||
}
|
||||
|
||||
TEST_F(FileTest, WriteReadBinaryFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
File file;
|
||||
file.Remove(path);
|
||||
|
||||
std::string write_data = GenerateRandomData(600);
|
||||
File wr_file;
|
||||
EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary));
|
||||
EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size()));
|
||||
wr_file.Close();
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
|
||||
std::string read_data;
|
||||
read_data.resize(file.FileSize(path));
|
||||
File rd_file;
|
||||
EXPECT_TRUE(rd_file.Open(path, File::kReadOnly));
|
||||
EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size()));
|
||||
rd_file.Close();
|
||||
EXPECT_EQ(write_data, read_data);
|
||||
}
|
||||
|
||||
TEST_F(FileTest, CopyFile) {
|
||||
std::string path = test_vectors::kTestDir + kTestFileName;
|
||||
File file;
|
||||
file.Remove(path);
|
||||
|
||||
std::string write_data = GenerateRandomData(600);
|
||||
File wr_file;
|
||||
EXPECT_TRUE(wr_file.Open(path, File::kCreate | File::kBinary));
|
||||
EXPECT_TRUE(wr_file.Write(write_data.data(), write_data.size()));
|
||||
wr_file.Close();
|
||||
ASSERT_TRUE(file.Exists(path));
|
||||
|
||||
std::string path_copy = test_vectors::kTestDir + kTestFileName2;
|
||||
EXPECT_FALSE(file.Exists(path_copy));
|
||||
EXPECT_TRUE(file.Copy(path, path_copy));
|
||||
|
||||
std::string read_data;
|
||||
read_data.resize(file.FileSize(path_copy));
|
||||
File rd_file;
|
||||
EXPECT_TRUE(rd_file.Open(path_copy, File::kReadOnly));
|
||||
EXPECT_TRUE(rd_file.Read(&read_data[0], read_data.size()));
|
||||
rd_file.Close();
|
||||
EXPECT_EQ(write_data, read_data);
|
||||
EXPECT_EQ(file.FileSize(path), file.FileSize(path_copy));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, ListEmptyDirectory) {
|
||||
std::vector<std::string> files;
|
||||
File file;
|
||||
EXPECT_TRUE(file.List(test_vectors::kTestDir, &files));
|
||||
EXPECT_EQ(0u, files.size());
|
||||
}
|
||||
|
||||
TEST_F(FileTest, ListFiles) {
|
||||
File file;
|
||||
std::string path = test_vectors::kTestDir + kTestDirName;
|
||||
EXPECT_TRUE(file.CreateDirectory(path));
|
||||
|
||||
path = test_vectors::kTestDir + kTestFileName;
|
||||
std::string write_data = GenerateRandomData(600);
|
||||
EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary));
|
||||
EXPECT_TRUE(file.Write(write_data.data(), write_data.size()));
|
||||
file.Close();
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
|
||||
path = test_vectors::kTestDir + kTestFileName2;
|
||||
write_data = GenerateRandomData(600);
|
||||
EXPECT_TRUE(file.Open(path, File::kCreate | File::kBinary));
|
||||
EXPECT_TRUE(file.Write(write_data.data(), write_data.size()));
|
||||
file.Close();
|
||||
EXPECT_TRUE(file.Exists(path));
|
||||
|
||||
std::vector<std::string> files;
|
||||
EXPECT_TRUE(file.List(test_vectors::kTestDir, &files));
|
||||
EXPECT_EQ(3u, files.size());
|
||||
|
||||
for (size_t i = 0; i < files.size(); ++i) {
|
||||
EXPECT_TRUE(files[i] == kTestDirName ||
|
||||
files[i] == kTestFileName ||
|
||||
files[i] == kTestFileName2);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
278
core/test/http_socket.cpp
Normal file
278
core/test/http_socket.cpp
Normal file
@@ -0,0 +1,278 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "http_socket.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "openssl/bio.h"
|
||||
#include "openssl/err.h"
|
||||
#include "openssl/x509.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
SSL_CTX* HttpSocket::InitSslContext(void) {
|
||||
const SSL_METHOD* method;
|
||||
SSL_CTX* ctx;
|
||||
|
||||
OpenSSL_add_all_algorithms();
|
||||
SSL_load_error_strings();
|
||||
method = SSLv3_client_method();
|
||||
ctx = SSL_CTX_new(method);
|
||||
if (NULL == ctx) {
|
||||
LOGE("failed to create SSL context");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void HttpSocket::ShowServerCertificate(const SSL* ssl) {
|
||||
X509* cert;
|
||||
char* line;
|
||||
|
||||
// gets the server certificate
|
||||
cert = SSL_get_peer_certificate(ssl);
|
||||
if (cert != NULL) {
|
||||
LOGV("server certificate:");
|
||||
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
|
||||
LOGV("subject: %s", line);
|
||||
free(line);
|
||||
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
|
||||
LOGV("issuer: %s", line);
|
||||
free(line);
|
||||
X509_free(cert);
|
||||
} else {
|
||||
LOGE("Failed to get server certificate");
|
||||
}
|
||||
}
|
||||
|
||||
HttpSocket::HttpSocket()
|
||||
: secure_connect_(true),
|
||||
socket_fd_(-1),
|
||||
ssl_(NULL),
|
||||
ssl_ctx_(NULL),
|
||||
timeout_enabled_(false) {
|
||||
SSL_library_init();
|
||||
}
|
||||
|
||||
HttpSocket::~HttpSocket() { CloseSocket(); }
|
||||
|
||||
void HttpSocket::CloseSocket() {
|
||||
if (socket_fd_ != -1) {
|
||||
close(socket_fd_);
|
||||
socket_fd_ = -1;
|
||||
}
|
||||
if (secure_connect_) {
|
||||
if (ssl_) {
|
||||
SSL_free(ssl_);
|
||||
ssl_ = NULL;
|
||||
}
|
||||
if (ssl_ctx_) {
|
||||
CloseSslContext(ssl_ctx_);
|
||||
ssl_ctx_ = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the domain name and resource path from the input url parameter.
|
||||
// The results are put in domain_name and resource_path respectively.
|
||||
// The format of the url can begin with <protocol/scheme>:://domain server/...
|
||||
// or dowmain server/resource_path
|
||||
void HttpSocket::GetDomainNameAndPathFromUrl(const std::string& url,
|
||||
std::string& domain_name,
|
||||
std::string& resource_path) {
|
||||
domain_name.clear();
|
||||
resource_path.clear();
|
||||
|
||||
size_t start = url.find("//");
|
||||
size_t end = url.npos;
|
||||
if (start != url.npos) {
|
||||
end = url.find("/", start + 2);
|
||||
if (end != url.npos) {
|
||||
domain_name.assign(url, start + 2, end - start - 2);
|
||||
resource_path.assign(url, end + 1, url.npos);
|
||||
} else {
|
||||
domain_name.assign(url, start + 2, url.npos);
|
||||
}
|
||||
} else {
|
||||
// no scheme/protocol in url
|
||||
end = url.find("/");
|
||||
if (end != url.npos) {
|
||||
domain_name.assign(url, 0, end);
|
||||
resource_path.assign(url, end + 1, url.npos);
|
||||
} else {
|
||||
domain_name.assign(url);
|
||||
}
|
||||
}
|
||||
// strips port number if present, e.g. https://www.domain.com:8888/...
|
||||
end = domain_name.find(":");
|
||||
if (end != domain_name.npos) {
|
||||
domain_name.erase(end);
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpSocket::Connect(const char* url, const std::string& port,
|
||||
bool enable_timeout, bool secure_connection) {
|
||||
secure_connect_ = secure_connection;
|
||||
if (secure_connect_) ssl_ctx_ = InitSslContext();
|
||||
|
||||
GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_);
|
||||
|
||||
socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_fd_ < 0) {
|
||||
LOGE("cannot open socket %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) ==
|
||||
-1) {
|
||||
CloseSocket();
|
||||
LOGE("setsockopt error %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
struct addrinfo* addr_info = NULL;
|
||||
bool status = true;
|
||||
int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info);
|
||||
if (ret != 0) {
|
||||
CloseSocket();
|
||||
LOGE("getaddrinfo failed with %d", ret);
|
||||
status = false;
|
||||
} else {
|
||||
if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) {
|
||||
CloseSocket();
|
||||
LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(),
|
||||
errno);
|
||||
status = false;
|
||||
}
|
||||
}
|
||||
timeout_enabled_ = enable_timeout;
|
||||
if (addr_info != NULL) {
|
||||
freeaddrinfo(addr_info);
|
||||
}
|
||||
|
||||
if (!status) return false;
|
||||
|
||||
// secures connection
|
||||
if (secure_connect_ && ssl_ctx_) {
|
||||
ssl_ = SSL_new(ssl_ctx_);
|
||||
if (!ssl_) {
|
||||
LOGE("failed SSL_new");
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE);
|
||||
if (!a_bio) {
|
||||
LOGE("BIO_new_socket error");
|
||||
return false;
|
||||
}
|
||||
|
||||
SSL_set_bio(ssl_, a_bio, a_bio);
|
||||
int ret = SSL_connect(ssl_);
|
||||
if (1 != ret) {
|
||||
char buf[256];
|
||||
LOGE("SSL_connect error:%s", ERR_error_string(ERR_get_error(), buf));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int HttpSocket::Read(char* data, int len) { return (Read(data, len, 0)); }
|
||||
|
||||
// makes non-blocking mode only during read, it supports timeout for read
|
||||
// returns -1 for error, number of bytes read for success
|
||||
int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
|
||||
bool use_timeout = (timeout_enabled_ && (timeout_in_ms > 0));
|
||||
int original_flags = 0;
|
||||
if (use_timeout) {
|
||||
original_flags = fcntl(socket_fd_, F_GETFL, 0);
|
||||
if (original_flags == -1) {
|
||||
LOGE("fcntl error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
|
||||
LOGE("fcntl error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int total_read = 0;
|
||||
int read = 0;
|
||||
int to_read = len;
|
||||
while (to_read > 0) {
|
||||
if (use_timeout) {
|
||||
fd_set read_fds;
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_in_ms / 1000;
|
||||
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(socket_fd_, &read_fds);
|
||||
if (select(socket_fd_ + 1, &read_fds, NULL, NULL, &tv) == -1) {
|
||||
LOGE("select failed");
|
||||
break;
|
||||
}
|
||||
if (!FD_ISSET(socket_fd_, &read_fds)) {
|
||||
LOGD("socket read timeout");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (secure_connect_)
|
||||
read = SSL_read(ssl_, data, to_read);
|
||||
else
|
||||
read = recv(socket_fd_, data, to_read, 0);
|
||||
|
||||
if (read > 0) {
|
||||
to_read -= read;
|
||||
data += read;
|
||||
total_read += read;
|
||||
} else if (read == 0) {
|
||||
// in blocking mode, zero read mean's peer closed.
|
||||
// in non-blocking mode, select said that there is data. so it should not
|
||||
// happen
|
||||
break;
|
||||
} else {
|
||||
LOGE("recv returned %d, error = %d", read, errno);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_timeout) {
|
||||
fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
|
||||
int HttpSocket::Write(const char* data, int len) {
|
||||
int total_sent = 0;
|
||||
int sent = 0;
|
||||
int to_send = len;
|
||||
while (to_send > 0) {
|
||||
if (secure_connect_)
|
||||
sent = SSL_write(ssl_, data, to_send);
|
||||
else
|
||||
sent = send(socket_fd_, data, to_send, 0);
|
||||
|
||||
if (sent > 0) {
|
||||
to_send -= sent;
|
||||
data += sent;
|
||||
total_sent += sent;
|
||||
} else if (sent == 0) {
|
||||
usleep(10); // retry later
|
||||
} else {
|
||||
LOGE("send returned error %d", errno);
|
||||
}
|
||||
}
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
50
core/test/http_socket.h
Normal file
50
core/test/http_socket.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_HTTP_SOCKET_H_
|
||||
#define CDM_TEST_HTTP_SOCKET_H_
|
||||
|
||||
#include <string>
|
||||
#include "openssl/ssl.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides basic Linux based TCP socket interface.
|
||||
class HttpSocket {
|
||||
public:
|
||||
HttpSocket();
|
||||
~HttpSocket();
|
||||
|
||||
void CloseSocket();
|
||||
bool Connect(const char* url, const std::string& port, bool enable_timeout,
|
||||
bool secure_connection);
|
||||
void GetDomainNameAndPathFromUrl(const std::string& url,
|
||||
std::string& domain_name,
|
||||
std::string& resource_path);
|
||||
const std::string& domain_name() const { return domain_name_; };
|
||||
const std::string& resource_path() const { return resource_path_; };
|
||||
int Read(char* data, int len);
|
||||
int Read(char* data, int len, int timeout_in_ms);
|
||||
int Write(const char* data, int len);
|
||||
|
||||
private:
|
||||
void CloseSslContext(SSL_CTX* ctx) const {
|
||||
if (ctx) SSL_CTX_free(ctx);
|
||||
}
|
||||
SSL_CTX* InitSslContext(void);
|
||||
void ShowServerCertificate(const SSL* ssl);
|
||||
|
||||
std::string domain_name_;
|
||||
bool secure_connect_;
|
||||
std::string resource_path_;
|
||||
int socket_fd_;
|
||||
SSL* ssl_;
|
||||
SSL_CTX* ssl_ctx_;
|
||||
bool timeout_enabled_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_HTTP_SOCKET_H_
|
||||
211
core/test/http_socket_test.cpp
Normal file
211
core/test/http_socket_test.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <errno.h>
|
||||
#include "gtest/gtest.h"
|
||||
#include "http_socket.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
#include "url_request.h"
|
||||
|
||||
namespace {
|
||||
// Random URL for tests.
|
||||
const std::string kHttpsTestServer("https://www.google.com");
|
||||
std::string gTestServer(kHttpsTestServer);
|
||||
std::string gTestData("Hello");
|
||||
const int kHttpBufferSize = 4096;
|
||||
char gBuffer[kHttpBufferSize];
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class HttpSocketTest : public testing::Test {
|
||||
public:
|
||||
HttpSocketTest() {}
|
||||
~HttpSocketTest() { socket_.CloseSocket(); }
|
||||
|
||||
protected:
|
||||
bool Connect(const std::string& server_url, bool secure_connection) {
|
||||
|
||||
std::string port = secure_connection ? "443" : "80";
|
||||
if (socket_.Connect(server_url.c_str(), port, true, secure_connection)) {
|
||||
LOGD("connected to %s", socket_.domain_name().c_str());
|
||||
} else {
|
||||
LOGE("failed to connect to %s", socket_.domain_name().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostRequest(const std::string& data) {
|
||||
std::string request("POST ");
|
||||
if (socket_.resource_path().empty())
|
||||
request.append(socket_.domain_name());
|
||||
else
|
||||
request.append(socket_.resource_path());
|
||||
request.append(" HTTP/1.1\r\n");
|
||||
request.append("Host: ");
|
||||
request.append(socket_.domain_name());
|
||||
request.append("\r\nUser-Agent: httpSocketTest/1.0\r\n");
|
||||
request.append("Content-Length: ");
|
||||
memset(gBuffer, 0, kHttpBufferSize);
|
||||
snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast<int>(data.size()));
|
||||
request.append(gBuffer);
|
||||
request.append("Content-Type: multipart/form-data\r\n");
|
||||
|
||||
// newline terminates header
|
||||
request.append("\r\n");
|
||||
|
||||
// append data
|
||||
request.append(data);
|
||||
socket_.Write(request.c_str(), request.size());
|
||||
LOGD("request: %s", request.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetResponse() {
|
||||
int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000);
|
||||
if (bytes < 0) {
|
||||
LOGE("read error = ", errno);
|
||||
return false;
|
||||
} else {
|
||||
LOGD("read %d bytes", bytes);
|
||||
std::string response(gBuffer, bytes);
|
||||
LOGD("response: %s", response.c_str());
|
||||
LOGD("end response dump");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HttpSocket socket_;
|
||||
std::string domain_name_;
|
||||
std::string resource_path_;
|
||||
};
|
||||
|
||||
TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest) {
|
||||
socket_.GetDomainNameAndPathFromUrl(
|
||||
"https://code.google.com/p/googletest/wiki/Primer", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl(
|
||||
"http://code.google.com/p/googletest/wiki/Primer/", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer/", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl(
|
||||
"code.google.com/p/googletest/wiki/Primer", domain_name_, resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com/", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("", domain_name_, resource_path_);
|
||||
EXPECT_TRUE(domain_name_.empty());
|
||||
EXPECT_TRUE(resource_path_.empty());
|
||||
|
||||
// Test with random numeric URL
|
||||
socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888/drm",
|
||||
domain_name_, resource_path_);
|
||||
EXPECT_STREQ("10.11.12.13", domain_name_.c_str());
|
||||
EXPECT_STREQ("drm", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("10.11.12.13", domain_name_.c_str());
|
||||
EXPECT_TRUE(resource_path_.empty());
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, ConnectTest) {
|
||||
const bool kUseSecureConnection = true;
|
||||
|
||||
if (gTestServer.find("https") != std::string::npos) {
|
||||
EXPECT_TRUE(Connect(gTestServer, kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
|
||||
// https connection allows insecure connection through port 80 as well
|
||||
EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
} else {
|
||||
EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
|
||||
// Test for the case that non-https connection must not use port 443
|
||||
EXPECT_FALSE(Connect(gTestServer, kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
EXPECT_FALSE(Connect("ww.g.c", kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
|
||||
EXPECT_FALSE(Connect("ww.g.c", !kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, RoundTripTest) {
|
||||
int secure_connection =
|
||||
(gTestServer.find("https") != std::string::npos) ? true : false;
|
||||
ASSERT_TRUE(Connect(gTestServer, secure_connection));
|
||||
EXPECT_TRUE(PostRequest(gTestData));
|
||||
GetResponse();
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
std::string temp;
|
||||
std::string test_server(kHttpsTestServer);
|
||||
std::string test_data(gTestData);
|
||||
for (int i = 1; i < argc; i++) {
|
||||
temp.assign(argv[i]);
|
||||
if (temp.find("--server=") == 0) {
|
||||
gTestServer.assign(temp.substr(strlen("--server=")));
|
||||
} else if (temp.find("--data=") == 0) {
|
||||
gTestData.assign(temp.substr(strlen("--data=")));
|
||||
} else {
|
||||
std::cout << "error: unknown option '" << argv[i] << "'" << std::endl;
|
||||
std::cout << "usage: http_socket_test [options]" << std::endl
|
||||
<< std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout
|
||||
<< "configure the test server url, please include http[s] in the url"
|
||||
<< std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << test_server << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --data=<data>";
|
||||
std::cout << "configure data to send, in ascii string format"
|
||||
<< std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << test_data << std::endl << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << gTestServer << std::endl;
|
||||
std::cout << "Data: " << gTestData << std::endl;
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
81
core/test/license_request.cpp
Normal file
81
core/test/license_request.cpp
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "license_request.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static const std::string kTwoBlankLines("\r\n\r\n");
|
||||
|
||||
size_t LicenseRequest::FindHeaderEndPosition(
|
||||
const std::string& response) const {
|
||||
return(response.find(kTwoBlankLines));
|
||||
}
|
||||
|
||||
// This routine parses the license server's response message and
|
||||
// extracts the drm message from the response header.
|
||||
void LicenseRequest::GetDrmMessage(const std::string& response,
|
||||
std::string& drm_msg) {
|
||||
if (response.empty()) {
|
||||
drm_msg.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extracts DRM message.
|
||||
// Content-Length = GLS line + Header(s) + empty line + drm message;
|
||||
// we use the empty line to locate the drm message, and compute
|
||||
// the drm message length as below instead of using Content-Length
|
||||
size_t header_end_pos = FindHeaderEndPosition(response);
|
||||
if (header_end_pos != std::string::npos) {
|
||||
header_end_pos += kTwoBlankLines.size(); // points to response body
|
||||
|
||||
drm_msg.clear();
|
||||
size_t drm_msg_pos = response.find(kTwoBlankLines, header_end_pos);
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg_pos += kTwoBlankLines.size(); // points to drm message
|
||||
} else {
|
||||
// For backward compatibility, no blank line after error code
|
||||
drm_msg_pos = response.find("\r\n", header_end_pos);
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg_pos += 2; // points to drm message
|
||||
}
|
||||
}
|
||||
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg = response.substr(drm_msg_pos);
|
||||
} else {
|
||||
drm_msg = response.substr(header_end_pos);
|
||||
}
|
||||
} else {
|
||||
LOGE("response body not found");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns heartbeat url in heartbeat_url.
|
||||
// The heartbeat url is stored as meta data in the response message.
|
||||
void LicenseRequest::GetHeartbeatUrl(const std::string& response,
|
||||
std::string& heartbeat_url) {
|
||||
if (response.empty()) {
|
||||
heartbeat_url.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
size_t header_end_pos = FindHeaderEndPosition(response);
|
||||
if (header_end_pos != std::string::npos) {
|
||||
header_end_pos += kTwoBlankLines.size(); // points to response body
|
||||
|
||||
heartbeat_url.clear();
|
||||
size_t heartbeat_url_pos = response.find("Heartbeat-Url: ",
|
||||
header_end_pos);
|
||||
if (heartbeat_url_pos != std::string::npos) {
|
||||
heartbeat_url_pos += sizeof("Heartbeat-Url: ");
|
||||
heartbeat_url.assign(response.substr(heartbeat_url_pos));
|
||||
} else {
|
||||
LOGE("heartbeat url not found");
|
||||
}
|
||||
} else {
|
||||
LOGE("response body not found");
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
30
core/test/license_request.h
Normal file
30
core/test/license_request.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_LICENSE_REQUEST_H_
|
||||
#define CDM_TEST_LICENSE_REQUEST_H_
|
||||
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Parses response from a license request.
|
||||
// This class assumes a particular response format defined by
|
||||
// Google license servers.
|
||||
class LicenseRequest {
|
||||
public:
|
||||
LicenseRequest() {};
|
||||
~LicenseRequest() {};
|
||||
|
||||
void GetDrmMessage(const std::string& response, std::string& drm_msg);
|
||||
void GetHeartbeatUrl(const std::string& response, std::string& heartbeat_url);
|
||||
|
||||
private:
|
||||
size_t FindHeaderEndPosition(const std::string& response) const;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(LicenseRequest);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_LICENSE_REQUEST_H_
|
||||
788
core/test/policy_engine_unittest.cpp
Normal file
788
core/test/policy_engine_unittest.cpp
Normal file
@@ -0,0 +1,788 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include "clock.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "license.h"
|
||||
#include "policy_engine.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
//protobuf generated classes.
|
||||
using video_widevine_server::sdk::License;
|
||||
using video_widevine_server::sdk::License_Policy;
|
||||
using video_widevine_server::sdk::LicenseIdentification;
|
||||
using video_widevine_server::sdk::STREAMING;
|
||||
using video_widevine_server::sdk::OFFLINE;
|
||||
|
||||
// gmock methods
|
||||
using ::testing::Return;
|
||||
using ::testing::AtLeast;
|
||||
|
||||
|
||||
class MockClock : public Clock {
|
||||
public:
|
||||
MOCK_METHOD0(GetCurrentTime, int64_t());
|
||||
};
|
||||
|
||||
class PolicyEngineTest : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
mock_clock_ = new MockClock();
|
||||
policy_engine_ = new PolicyEngine(mock_clock_);
|
||||
|
||||
license_start_time_ = 1413517500; // ~ 01/01/2013
|
||||
license_renewal_delay_ = 604200; // 7 days - 10 minutes
|
||||
license_renewal_retry_interval_ = 30;
|
||||
license_duration_ = 604800; // 7 days
|
||||
playback_duration_ = 86400; // 24 hours
|
||||
|
||||
license_.set_license_start_time(license_start_time_);
|
||||
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(1);
|
||||
id->set_type(STREAMING);
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy = license_.mutable_policy();
|
||||
policy->set_can_play(true);
|
||||
policy->set_can_persist(true);
|
||||
policy->set_can_renew(true);
|
||||
policy->set_rental_duration_seconds(license_duration_);
|
||||
policy->set_playback_duration_seconds(playback_duration_);
|
||||
policy->set_license_duration_seconds(license_duration_);
|
||||
policy->set_renewal_recovery_duration_seconds(license_duration_ -
|
||||
license_renewal_delay_); // 10 minutes
|
||||
|
||||
// Note: not a real URL - used for testing Policy/PolicyEngine interfaces
|
||||
policy->set_renewal_server_url(
|
||||
"https://test.google.com/license/GetCencLicense");
|
||||
policy->set_renewal_delay_seconds(license_renewal_delay_);
|
||||
policy->set_renewal_retry_interval_seconds(
|
||||
license_renewal_retry_interval_);
|
||||
policy->set_renew_with_usage(false);
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
delete policy_engine_;
|
||||
// Done by policy engine: delete mock_clock_;
|
||||
policy_engine_ = NULL;
|
||||
mock_clock_ = NULL;
|
||||
}
|
||||
|
||||
MockClock* mock_clock_;
|
||||
PolicyEngine* policy_engine_;
|
||||
License license_;
|
||||
License_Policy* policy_;
|
||||
|
||||
int64_t license_start_time_;
|
||||
int64_t license_renewal_delay_;
|
||||
int64_t license_renewal_retry_interval_;
|
||||
int64_t license_duration_;
|
||||
int64_t playback_duration_;
|
||||
};
|
||||
|
||||
TEST_F(PolicyEngineTest, NoLicense) {
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackSuccess) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 10));
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackFailed_CanPlayFalse) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 5));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_can_play(false);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackFails_RentalDurationExpired) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 3600))
|
||||
.WillOnce(Return(license_start_time_ + 3601));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_rental_duration_seconds(3600);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackFails_PlaybackDurationExpired) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 10000))
|
||||
.WillOnce(Return(license_start_time_ + 13598))
|
||||
.WillOnce(Return(license_start_time_ + 13602));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_playback_duration_seconds(3600);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackFails_LicenseDurationExpired) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 3600))
|
||||
.WillOnce(Return(license_start_time_ + 3601));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_license_duration_seconds(3600);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackOk_RentalDuration0) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 3600))
|
||||
.WillOnce(Return(license_start_time_ + 3601));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_rental_duration_seconds(0);
|
||||
policy->set_license_duration_seconds(3600);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackOk_PlaybackDuration0) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 10000))
|
||||
.WillOnce(Return(license_start_time_ + 10005))
|
||||
.WillOnce(Return(license_start_time_ + 13598))
|
||||
.WillOnce(Return(license_start_time_ + 13602));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_playback_duration_seconds(0);
|
||||
policy->set_license_duration_seconds(3600);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackOk_LicenseDuration0) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 3600))
|
||||
.WillOnce(Return(license_start_time_ + 3601));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_license_duration_seconds(0);
|
||||
policy->set_rental_duration_seconds(3600);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackOk_Durations0) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 604800))
|
||||
.WillOnce(Return(license_start_time_ + 604810));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_rental_duration_seconds(0);
|
||||
policy->set_playback_duration_seconds(0);
|
||||
policy->set_license_duration_seconds(0);
|
||||
policy->set_renewal_delay_seconds(604900);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackFailed_CanRenewFalse) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ -
|
||||
playback_duration_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ + 10));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_can_renew(false);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccess) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ -
|
||||
playback_duration_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 15))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ +
|
||||
license_renewal_retry_interval_ + 10));
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
license_.set_license_start_time(license_start_time_ +
|
||||
license_renewal_delay_ + 15);
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(2);
|
||||
policy_engine_->UpdateLicense(license_);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackFailed_RenewFailedVersionNotUpdated) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ -
|
||||
playback_duration_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ + 10));
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
license_.set_license_start_time(license_start_time_ +
|
||||
license_renewal_delay_ + 15);
|
||||
policy_engine_->UpdateLicense(license_);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackFailed_RepeatedRenewFailures) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ -
|
||||
playback_duration_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 70))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 80))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ + 15));
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterExpiry) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ -
|
||||
playback_duration_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 70))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 80))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ + 30))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ + 40));
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_EXPIRED_EVENT, event);
|
||||
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
|
||||
license_.set_license_start_time(license_start_time_ +
|
||||
license_duration_ + 20);
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(2);
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy = license_.mutable_policy();
|
||||
policy->set_playback_duration_seconds(playback_duration_ + 100);
|
||||
policy->set_license_duration_seconds(license_duration_ + 100);
|
||||
|
||||
policy_engine_->UpdateLicense(license_);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackOk_RenewSuccessAfterFailures) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ -
|
||||
playback_duration_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ - 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 20))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 40))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 50))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 55))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 67))
|
||||
.WillOnce(Return(license_start_time_ + license_renewal_delay_ + 200));
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
license_.set_license_start_time(license_start_time_ +
|
||||
license_renewal_delay_ + 55);
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(2);
|
||||
policy_engine_->UpdateLicense(license_);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, PlaybackOk_RenewedWithUsage) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + 20))
|
||||
.WillOnce(Return(license_start_time_ + 40))
|
||||
.WillOnce(Return(license_start_time_ + 50));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_renew_with_usage(true);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_TRUE(event_occurred);
|
||||
EXPECT_EQ(LICENSE_RENEWAL_NEEDED_EVENT, event);
|
||||
|
||||
license_.set_license_start_time(license_start_time_ + 30);
|
||||
policy->set_renew_with_usage(false);
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_version(2);
|
||||
policy_engine_->UpdateLicense(license_);
|
||||
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, QueryFailed_LicenseNotReceived) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_));
|
||||
|
||||
CdmQueryMap query_info;
|
||||
EXPECT_EQ(UNKNOWN_ERROR, policy_engine_->Query(&query_info));
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, QuerySuccess) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 100));
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
CdmQueryMap query_info;
|
||||
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
|
||||
EXPECT_EQ(QUERY_VALUE_STREAMING, query_info[QUERY_KEY_LICENSE_TYPE]);
|
||||
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
|
||||
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
|
||||
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
|
||||
|
||||
int64_t remaining_time;
|
||||
std::istringstream ss;
|
||||
ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]);
|
||||
ss >> remaining_time;
|
||||
EXPECT_LT(0, remaining_time);
|
||||
ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]);
|
||||
ss >> remaining_time;
|
||||
EXPECT_LT(0, remaining_time);
|
||||
|
||||
EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL],
|
||||
policy->renewal_server_url());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, QuerySuccess_Offline) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 100));
|
||||
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_type(OFFLINE);
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
policy->set_can_play(false);
|
||||
policy->set_can_persist(false);
|
||||
policy->set_can_renew(false);
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_FALSE(policy_engine_->can_decrypt());
|
||||
|
||||
CdmQueryMap query_info;
|
||||
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
|
||||
EXPECT_EQ(QUERY_VALUE_OFFLINE, query_info[QUERY_KEY_LICENSE_TYPE]);
|
||||
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PLAY_ALLOWED]);
|
||||
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
|
||||
EXPECT_EQ(QUERY_VALUE_FALSE, query_info[QUERY_KEY_RENEW_ALLOWED]);
|
||||
|
||||
int64_t remaining_time;
|
||||
std::istringstream ss;
|
||||
ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]);
|
||||
ss >> remaining_time;
|
||||
EXPECT_EQ(0, remaining_time);
|
||||
ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]);
|
||||
ss >> remaining_time;
|
||||
EXPECT_EQ(0, remaining_time);
|
||||
|
||||
EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL],
|
||||
policy->renewal_server_url());
|
||||
}
|
||||
|
||||
TEST_F(PolicyEngineTest, QuerySuccess_DurationExpired) {
|
||||
EXPECT_CALL(*mock_clock_, GetCurrentTime())
|
||||
.WillOnce(Return(license_start_time_ + 1))
|
||||
.WillOnce(Return(license_start_time_ + 5))
|
||||
.WillOnce(Return(license_start_time_ + 10))
|
||||
.WillOnce(Return(license_start_time_ + license_duration_ + 20));
|
||||
|
||||
LicenseIdentification* id = license_.mutable_id();
|
||||
id->set_type(OFFLINE);
|
||||
|
||||
License_Policy* policy = license_.mutable_policy();
|
||||
|
||||
policy_engine_->SetLicense(license_);
|
||||
|
||||
bool event_occurred;
|
||||
CdmEventType event;
|
||||
policy_engine_->OnTimerEvent(&event_occurred, &event);
|
||||
EXPECT_FALSE(event_occurred);
|
||||
|
||||
policy_engine_->BeginDecryption();
|
||||
EXPECT_TRUE(policy_engine_->can_decrypt());
|
||||
|
||||
CdmQueryMap query_info;
|
||||
EXPECT_EQ(NO_ERROR, policy_engine_->Query(&query_info));
|
||||
EXPECT_EQ(QUERY_VALUE_OFFLINE, query_info[QUERY_KEY_LICENSE_TYPE]);
|
||||
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PLAY_ALLOWED]);
|
||||
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_PERSIST_ALLOWED]);
|
||||
EXPECT_EQ(QUERY_VALUE_TRUE, query_info[QUERY_KEY_RENEW_ALLOWED]);
|
||||
|
||||
int64_t remaining_time;
|
||||
std::istringstream ss;
|
||||
ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]);
|
||||
ss >> remaining_time;
|
||||
EXPECT_EQ(0, remaining_time);
|
||||
ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]);
|
||||
ss >> remaining_time;
|
||||
EXPECT_EQ(0, remaining_time);
|
||||
|
||||
EXPECT_EQ(query_info[QUERY_KEY_RENEWAL_SERVER_URL],
|
||||
policy->renewal_server_url());
|
||||
}
|
||||
|
||||
} // wvcdm
|
||||
54
core/test/timer_unittest.cpp
Normal file
54
core/test/timer_unittest.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "timer.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class TestTimerHandler : public TimerHandler {
|
||||
public:
|
||||
TestTimerHandler() : timer_events_(0) {};
|
||||
virtual ~TestTimerHandler() {};
|
||||
|
||||
virtual void OnTimerEvent() {
|
||||
timer_events_++;
|
||||
}
|
||||
|
||||
uint32_t timer_events() { return timer_events_; }
|
||||
void ResetTimerEvents() { timer_events_ = 0; }
|
||||
|
||||
private:
|
||||
uint32_t timer_events_;
|
||||
};
|
||||
|
||||
TEST(TimerTest, ParametersCheck) {
|
||||
Timer timer;
|
||||
EXPECT_FALSE(timer.Start(NULL, 10));
|
||||
|
||||
TestTimerHandler handler;
|
||||
EXPECT_FALSE(timer.Start(&handler, 0));
|
||||
}
|
||||
|
||||
TEST(TimerTest, TimerCheck) {
|
||||
TestTimerHandler handler;
|
||||
Timer timer;
|
||||
uint32_t duration = 10;
|
||||
|
||||
EXPECT_EQ(0u, handler.timer_events());
|
||||
EXPECT_FALSE(timer.IsRunning());
|
||||
|
||||
EXPECT_TRUE(timer.Start(&handler, 1));
|
||||
EXPECT_TRUE(timer.IsRunning());
|
||||
sleep(duration);
|
||||
|
||||
EXPECT_LE(duration-1, handler.timer_events());
|
||||
EXPECT_LE(handler.timer_events(), duration+1);
|
||||
timer.Stop();
|
||||
EXPECT_FALSE(timer.IsRunning());
|
||||
sleep(duration);
|
||||
|
||||
EXPECT_LE(duration-1, handler.timer_events());
|
||||
EXPECT_LE(handler.timer_events(), duration+1);
|
||||
}
|
||||
|
||||
}
|
||||
218
core/test/url_request.cpp
Normal file
218
core/test/url_request.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "url_request.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <sstream>
|
||||
|
||||
#include "http_socket.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
const int kMaxReadAttempts = 4;
|
||||
const int kSingleReadAttempt = 1;
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
UrlRequest::UrlRequest(const std::string& url, const std::string& port,
|
||||
bool secure_connection, bool chunk_transfer_mode)
|
||||
: chunk_transfer_mode_(chunk_transfer_mode),
|
||||
is_connected_(false),
|
||||
port_("80"),
|
||||
request_(""),
|
||||
server_url_(url) {
|
||||
if (!port.empty()) {
|
||||
port_.assign(port);
|
||||
}
|
||||
if (socket_.Connect((server_url_).c_str(), port_, true, secure_connection)) {
|
||||
is_connected_ = true;
|
||||
} else {
|
||||
LOGE("failed to connect to %s, port=%s", socket_.domain_name().c_str(),
|
||||
port.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
UrlRequest::~UrlRequest() { socket_.CloseSocket(); }
|
||||
|
||||
void UrlRequest::AppendChunkToUpload(const std::string& data) {
|
||||
// format of chunk:
|
||||
// size of chunk in hex\r\n
|
||||
// data\r\n
|
||||
// . . .
|
||||
// 0\r\n
|
||||
|
||||
// buffer to store length of chunk
|
||||
memset(buffer_, 0, kHttpBufferSize);
|
||||
snprintf(buffer_, kHttpBufferSize, "%zx\r\n", data.size());
|
||||
request_.append(buffer_); // appends size of chunk
|
||||
LOGD("...\r\n%s", request_.c_str());
|
||||
request_.append(data);
|
||||
request_.append("\r\n"); // marks end of data
|
||||
}
|
||||
|
||||
// Concatenate all chunks into one blob and returns the response with
|
||||
// header information.
|
||||
void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
|
||||
std::string* modified_response) {
|
||||
if (http_response.empty()) return;
|
||||
|
||||
modified_response->clear();
|
||||
const std::string kChunkedTag = "Transfer-Encoding: chunked\r\n\r\n";
|
||||
size_t chunked_tag_pos = http_response.find(kChunkedTag);
|
||||
if (std::string::npos != chunked_tag_pos) {
|
||||
// processes chunked encoding
|
||||
size_t chunk_size = 0;
|
||||
size_t chunk_size_pos = chunked_tag_pos + kChunkedTag.size();
|
||||
sscanf(&http_response[chunk_size_pos], "%zx", &chunk_size);
|
||||
if (chunk_size > http_response.size()) {
|
||||
// precaution, in case we misread chunk size
|
||||
LOGE("invalid chunk size %u", chunk_size);
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for chunks in the following format:
|
||||
// header
|
||||
// chunk size\r\n <-- chunk_size_pos @ beginning of chunk size
|
||||
// chunk data\r\n <-- chunk_pos @ beginning of chunk data
|
||||
// chunk size\r\n
|
||||
// chunk data\r\n
|
||||
// 0\r\n
|
||||
const std::string kCrLf = "\r\n";
|
||||
size_t chunk_pos = http_response.find(kCrLf, chunk_size_pos);
|
||||
modified_response->assign(http_response, 0, chunk_size_pos);
|
||||
|
||||
while ((chunk_size > 0) && (std::string::npos != chunk_pos)) {
|
||||
chunk_pos += kCrLf.size();
|
||||
modified_response->append(http_response, chunk_pos, chunk_size);
|
||||
|
||||
// Search for next chunk
|
||||
chunk_size_pos = chunk_pos + chunk_size + kCrLf.size();
|
||||
sscanf(&http_response[chunk_size_pos], "%zx", &chunk_size);
|
||||
if (chunk_size > http_response.size()) {
|
||||
// precaution, in case we misread chunk size
|
||||
LOGE("invalid chunk size %u", chunk_size);
|
||||
break;
|
||||
}
|
||||
chunk_pos = http_response.find(kCrLf, chunk_size_pos);
|
||||
}
|
||||
} else {
|
||||
// Response is not chunked encoded
|
||||
modified_response->assign(http_response);
|
||||
}
|
||||
}
|
||||
|
||||
int UrlRequest::GetResponse(std::string* message) {
|
||||
message->clear();
|
||||
|
||||
std::string response;
|
||||
const int kTimeoutInMs = 3000;
|
||||
int bytes = 0;
|
||||
for (int attempts = kMaxReadAttempts; attempts > 0; --attempts) {
|
||||
memset(buffer_, 0, kHttpBufferSize);
|
||||
bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs);
|
||||
if (bytes > 0) {
|
||||
response.append(buffer_, bytes);
|
||||
if (bytes < static_cast<int>(kHttpBufferSize)) {
|
||||
attempts = kSingleReadAttempt;
|
||||
}
|
||||
} else {
|
||||
if (bytes < 0) LOGE("read error = ", errno);
|
||||
// bytes == 0 indicates nothing to read
|
||||
}
|
||||
}
|
||||
|
||||
ConcatenateChunkedResponse(response, message);
|
||||
LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str());
|
||||
return message->size();
|
||||
}
|
||||
|
||||
int UrlRequest::GetStatusCode(const std::string& response) {
|
||||
const std::string kHttpVersion("HTTP/1.1");
|
||||
|
||||
int status_code = -1;
|
||||
size_t pos = response.find(kHttpVersion);
|
||||
if (pos != std::string::npos) {
|
||||
pos += kHttpVersion.size();
|
||||
sscanf(response.substr(pos).c_str(), "%d", &status_code);
|
||||
}
|
||||
return status_code;
|
||||
}
|
||||
|
||||
bool UrlRequest::PostRequestChunk(const std::string& data) {
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
request_.append(" HTTP/1.1\r\n");
|
||||
request_.append("Host: ");
|
||||
request_.append(socket_.domain_name());
|
||||
request_.append("\r\nConnection: Keep-Alive\r\n");
|
||||
request_.append("Transfer-Encoding: chunked\r\n");
|
||||
request_.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||
request_.append("Accept-Encoding: gzip,deflate\r\n");
|
||||
request_.append("Accept-Language: en-us,fr\r\n");
|
||||
request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n");
|
||||
request_.append("\r\n"); // empty line to terminate header
|
||||
|
||||
// calls AppendChunkToUpload repeatedly for multiple chunks
|
||||
AppendChunkToUpload(data);
|
||||
|
||||
// terminates last chunk with 0\r\n, then ends header with an empty line
|
||||
request_.append("0\r\n\r\n");
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UrlRequest::PostRequest(const std::string& data) {
|
||||
if (chunk_transfer_mode_) {
|
||||
return PostRequestChunk(data);
|
||||
}
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
request_.append(" HTTP/1.1\r\n");
|
||||
request_.append("Host: ");
|
||||
request_.append(socket_.domain_name());
|
||||
request_.append("\r\nConnection: Keep-Alive\r\n");
|
||||
request_.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||
request_.append("Accept-Encoding: gzip,deflate\r\n");
|
||||
request_.append("Accept-Language: en-us,fr\r\n");
|
||||
request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n");
|
||||
std::ostringstream ss;
|
||||
ss << data.size();
|
||||
request_.append("Content-Length: ");
|
||||
request_.append(ss.str());
|
||||
request_.append("\r\n\r\n");
|
||||
request_.append(data);
|
||||
|
||||
// terminates with \r\n, then ends with an empty line
|
||||
request_.append("\r\n\r\n");
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str());
|
||||
LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UrlRequest::PostCertRequestInQueryString(const std::string& data) {
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
request_.append("&signedRequest=");
|
||||
request_.append(data);
|
||||
request_.append(" HTTP/1.1\r\n");
|
||||
request_.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||
request_.append("Host: ");
|
||||
request_.append(socket_.domain_name());
|
||||
request_.append("\r\nAccept: */*");
|
||||
request_.append("\r\nContent-Type: application/json");
|
||||
request_.append("\r\nContent-Length: 0");
|
||||
request_.append("\r\n"); // empty line to terminate header
|
||||
request_.append("\r\n"); // terminates the request
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str());
|
||||
LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
45
core/test/url_request.h
Normal file
45
core/test/url_request.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_URL_REQUEST_H_
|
||||
#define CDM_TEST_URL_REQUEST_H_
|
||||
|
||||
#include <string>
|
||||
#include "http_socket.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides simple HTTP request and response service.
|
||||
// Only POST request method is implemented.
|
||||
class UrlRequest {
|
||||
public:
|
||||
UrlRequest(const std::string& url, const std::string& port,
|
||||
bool secure_connect, bool chunk_transfer_mode);
|
||||
~UrlRequest();
|
||||
|
||||
void AppendChunkToUpload(const std::string& data);
|
||||
void ConcatenateChunkedResponse(const std::string http_response,
|
||||
std::string* modified_response);
|
||||
int GetResponse(std::string* message);
|
||||
int GetStatusCode(const std::string& response);
|
||||
bool is_connected() const { return is_connected_; }
|
||||
bool PostRequest(const std::string& data);
|
||||
bool PostRequestChunk(const std::string& data);
|
||||
bool PostCertRequestInQueryString(const std::string& data);
|
||||
|
||||
private:
|
||||
static const unsigned int kHttpBufferSize = 4096;
|
||||
char buffer_[kHttpBufferSize];
|
||||
bool chunk_transfer_mode_;
|
||||
bool is_connected_;
|
||||
std::string port_;
|
||||
std::string request_;
|
||||
HttpSocket socket_;
|
||||
std::string server_url_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_URL_REQUEST_H_
|
||||
38
linux/src/lock.cpp
Normal file
38
linux/src/lock.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Lock class - provides a simple mutex implementation, modeled after Android
|
||||
|
||||
#include "lock.h"
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class Lock::Impl {
|
||||
public:
|
||||
pthread_mutex_t mutex_;
|
||||
};
|
||||
|
||||
Lock::Lock() : impl_(new Lock::Impl()) {
|
||||
pthread_mutex_init(&impl_->mutex_, NULL);
|
||||
}
|
||||
|
||||
Lock::~Lock() {
|
||||
pthread_mutex_destroy(&impl_->mutex_);
|
||||
delete impl_;
|
||||
}
|
||||
|
||||
void Lock::Acquire() {
|
||||
pthread_mutex_lock(&impl_->mutex_);
|
||||
}
|
||||
|
||||
void Lock::Release() {
|
||||
pthread_mutex_unlock(&impl_->mutex_);
|
||||
}
|
||||
|
||||
bool Lock::Try() {
|
||||
int result = pthread_mutex_trylock(&impl_->mutex_);
|
||||
return result == 0;
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
43
linux/src/log.cpp
Normal file
43
linux/src/log.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Log - implemented using stdout.
|
||||
//
|
||||
#define LOG_BUF_SIZE 4096
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static LogPriority g_cutoff = LOG_WARN;
|
||||
|
||||
void InitLogging(int argc, const char* const* argv) {
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strncmp(argv[i], "-v", 2) == 0) {
|
||||
g_cutoff = LOG_VERBOSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Log(const char* file, int line, LogPriority level, const char* fmt, ...) {
|
||||
const char* severities[] = { "ERROR", "WARN", "INFO", "DEBUG", "VERBOSE" };
|
||||
if (level >= sizeof(severities) / sizeof(*severities)) {
|
||||
printf("[FATAL:%s(%d)] Invalid log priority level: %d\n", file, line,
|
||||
level);
|
||||
return;
|
||||
}
|
||||
if (level > g_cutoff) return;
|
||||
|
||||
va_list ap;
|
||||
char buf[LOG_BUF_SIZE];
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
|
||||
va_end(ap);
|
||||
printf("[%s:%s(%d)] ", severities[level], file, line);
|
||||
fputs(buf, stdout);
|
||||
putc('\n', stdout);
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
1328
oemcrypto/include/OEMCryptoCENC.h
Normal file
1328
oemcrypto/include/OEMCryptoCENC.h
Normal file
File diff suppressed because it is too large
Load Diff
3887
oemcrypto/test/oemcrypto_test.cpp
Normal file
3887
oemcrypto/test/oemcrypto_test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
4
run_test.sh
Executable file
4
run_test.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
set -ex
|
||||
./build/build.py x86-64 -r
|
||||
./out/x86-64/Release/wvcdm_shared_api_unittest
|
||||
Reference in New Issue
Block a user