Source release v2.1.2-0-773 + third_party libs

Change-Id: Ia07608577b65b301c22a8ff4bf7f743c2d3f9274
This commit is contained in:
Joey Parrish
2014-06-10 13:36:59 -07:00
parent 557c42130a
commit aaa3c6192a
42 changed files with 2425 additions and 918 deletions

5
README
View File

@@ -1,5 +1,5 @@
README for Widevine CDM Partner Kit v2.1
Date: 5/30/2014
Date: 6/03/2014
This document provides additional details on installation, system
setup, building, and testing components of the Widevine Content
@@ -223,11 +223,12 @@ above the test directory. The test-releated GYP targets will have "test" or
Here's a current list of GYP files and test-related targets:
File - Targets Purpose
cdm/cdm_api_internal.gyp
cdm/cdm.gyp
license_protocol Create protobuf sources for license protocol
device_files Create protobuf sources for license storage
wvcdm_sysdep Build system-dependent layer of CDM
wvcdm_shared Build CDM shared library
cdm/cdm_unittests.gyp
wvcdm_shared_api_unittest Build CDM unit tests
cdm/test/gtest.gyp
gtest Provides gtest modules for unit tests

View File

@@ -11,19 +11,20 @@ import gyp
cdm_top = os.path.abspath(os.path.dirname(__file__))
parser = argparse.ArgumentParser()
parser.add_argument('platform',
help='The platform configuration to use (x86-64, ...). '
'Should be one of the folder names inside platforms/')
parser.add_argument('-r', '--release',
dest='build_config', default='Debug',
action='store_const', const='Release',
help='Builds a release build (equivalent to -c Release)')
parser.add_argument('-c', '--config',
dest='build_config', default='Debug',
help='Select a build config (Debug, Release)')
parser.add_argument('-g', '--generator', default='make',
help='Which build system to use (make, ninja, ...)')
def IsNinjaInstalled():
"""Determine if ninja is installed."""
try:
bit_bucket = open(os.devnull, 'w')
subprocess.check_call(['ninja', '--version'], stdout=bit_bucket,
stderr=bit_bucket)
return True
except subprocess.CalledProcessError:
# Error code returned, probably not the ninja we're looking for.
return False
except OSError:
# No such command found.
return False
def VerboseSubprocess(args):
@@ -93,12 +94,33 @@ def ImportPlatform(name, gyp_args):
def main(args):
if IsNinjaInstalled():
default_generator = 'ninja'
else:
default_generator = 'make'
parser = argparse.ArgumentParser()
parser.add_argument('platform',
help='The platform configuration to use (x86-64, ...). '
'Should be one of the folder names inside platforms/')
parser.add_argument('-r', '--release',
dest='build_config', default='Debug',
action='store_const', const='Release',
help='Builds a release build (equivalent to -c Release)')
parser.add_argument('-c', '--config',
dest='build_config', default='Debug',
help='Select a build config (Debug, Release). '
'Defaults to Debug.')
parser.add_argument('-g', '--generator', default=default_generator,
help='Which build system to use (make, ninja, ...). '
'Defaults to ninja when available, make otherwise.')
options = parser.parse_args(args)
gyp_args = [
'--format=%s' % options.generator,
'--depth=%s' % cdm_top,
'%s/cdm/cdm_api_external.gyp' % cdm_top,
'%s/cdm/cdm_unittests.gyp' % cdm_top,
]
output_path = ImportPlatform(options.platform, gyp_args)
@@ -116,4 +138,4 @@ def main(args):
if __name__ == '__main__':
main(sys.argv[1:])
sys.exit(main(sys.argv[1:]))

View File

@@ -48,13 +48,13 @@
],
},
{
'target_name': '<(cdm_target_name)',
'type': '<(cdm_target_type)',
'target_name': 'wvcdm_static',
'type': 'static_library',
'defines': ['CDM_IMPLEMENTATION'],
'dependencies': [
'device_files',
'license_protocol',
'wvcdm_sysdep',
'device_files',
'<(oemcrypto_target)',
],
'include_dirs': [
@@ -82,9 +82,36 @@
'../core/src/privacy_crypto.cpp',
'../core/src/properties.cpp',
],
'conditions': [
['disable_privacy_crypto=="true"', {
'sources!': [ # exclude
'../core/src/privacy_crypto.cpp',
],
'sources': [ # include
'../core/src/privacy_crypto_dummy.cpp',
],
}],
],
'direct_dependencies': [
'device_files',
'license_protocol',
],
'conditions': [
['use_system_protobuf=="true"', {
'direct_dependent_settings': {
'libraries': [
'-lprotobuf',
],
},
}],
],
},
{
'target_name': 'wvcdm_shared',
'type': 'shared_library',
'dependencies': [
'wvcdm_static',
],
},
],
}

View File

@@ -26,10 +26,9 @@
'-lssl',
'-lcrypto',
'-lpthread',
'-lprotobuf',
],
'dependencies': [
'cdm_api_internal.gyp:<(cdm_target_name)',
'cdm.gyp:wvcdm_shared',
'test/gmock.gyp:gmock',
'test/gmock.gyp:gmock_main',
'test/gtest.gyp:gtest',

View File

@@ -14,6 +14,9 @@ typedef __int64 int64_t;
#include <stdint.h>
#endif
#include <string>
#include <vector>
// Define CDM_EXPORT so that functionality implemented by the CDM module
// can be exported to consumers.
#if defined(WIN32)

View File

@@ -1,3 +1,3 @@
// Widevine CDM Kit Version
#define WV_CDM_VERSION "v2.1.1-0-738"
#define WV_CDM_VERSION "v2.1.2-0-773"

View File

@@ -97,7 +97,7 @@ cdm::Status WvContentDecryptionModule::GenerateKeyRequest(
std::string security_level;
std::string privacy_mode;
kVectorBytes service_certificate;
VectorBytes service_certificate;
host_->GetPlatformString("SecurityLevel", &security_level);
host_->GetPlatformString("PrivacyOn", &privacy_mode);
@@ -353,10 +353,22 @@ cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt(
CdmSessionId session_id; // it's empty but cdm_engine will locate via key_id.
CdmResponseType status = NO_ERROR;
uint8_t* output_buffer = decrypted_block
? reinterpret_cast<uint8_t*>(
decrypted_block->DecryptedBuffer()->Data())
: NULL;
size_t output_size = 0;
for (int i = 0; i < encrypted_buffer.num_subsamples; ++i) {
const cdm::SubsampleEntry& subsample = encrypted_buffer.subsamples[i];
output_size += subsample.cipher_bytes + subsample.clear_bytes;
}
parameters.decrypt_buffer_length = output_size;
if (decrypted_block) {
cdm::Buffer* buffer = host_->Allocate(output_size);
decrypted_block->SetDecryptedBuffer(buffer);
parameters.decrypt_buffer = buffer->Data();
} else {
parameters.decrypt_buffer = NULL;
}
size_t offset = 0;
size_t encrypted_offset = 0;
uint32_t block_ctr = 0;
@@ -377,13 +389,10 @@ cdm::Status WvContentDecryptionModule::DoSubsampleDecrypt(
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_buffer =
&encrypted_buffer.data[encrypted_buffer.data_offset + offset];
parameters.decrypt_buffer_offset = offset;
parameters.encrypt_length = bytes;
parameters.decrypt_buffer_length = encrypted_buffer.data_size - offset;
parameters.block_offset = encrypted_offset % kIvSize;
offset += bytes;

View File

@@ -47,8 +47,8 @@ double GetCurrentTestTime() {
return tv.tv_sec;
}
using wvcdm::kStringPairs;
using wvcdm::kVectorPairs;
using wvcdm::StringPairs;
using wvcdm::VectorPairs;
// These classes below are naive implementation of the abstract classes defined
// in the CDM interface (content_decryptiom_module.h), which are used for tests
@@ -357,8 +357,8 @@ class TestHost : public cdm::Host, public IHostTime {
cdm::ContentDecryptionModule* host_cdm_ptr_;
// These are containers for the platform sharing data.
std::set<kStringPairs> platform_strings_set_;
std::set<kVectorPairs> platform_vectors_set_;
std::set<StringPairs> platform_strings_set_;
std::set<VectorPairs> platform_vectors_set_;
TestHostFile test_host_file_;
void LoadPersistentValues();
SimplePThread simple_thread_;
@@ -473,12 +473,12 @@ void TestHost::GetPrivateData(int32_t* instance,
int TestHost::GetPlatformString(const std::string& name,
std::string* value) {
std::set<kStringPairs>::iterator it;
std::set<StringPairs>::iterator it;
for (it = platform_strings_set_.begin();
it != platform_strings_set_.end(); ++it) {
kStringPairs sp = *it;
StringPairs sp = *it;
std::string x = sp.first;
std::string y = sp.second;
if (x == name) {
@@ -492,14 +492,14 @@ int TestHost::GetPlatformString(const std::string& name,
int TestHost::SetPlatformString(const std::string& name,
const std::string& value) {
kStringPairs sp(name, value);
StringPairs sp(name, value);
platform_strings_set_.insert(sp);
return 1;
}
int TestHost::PersistPlatformString(const std::string& name,
const std::string& value) {
kStringPairs sp(name, value);
StringPairs sp(name, value);
// Write the pairs to a file such that they can be retrieved by
// GetPlatformString() even after a power cycle.
return 1;
@@ -508,17 +508,17 @@ int TestHost::PersistPlatformString(const std::string& name,
int TestHost::SetPlatformByteArray(const std::string& name,
const std::vector<uint8_t>& value) {
// A zero value pair is an erase only.
std::set<kVectorPairs>::iterator it;
std::set<VectorPairs>::iterator it;
for (it = platform_vectors_set_.begin();
it != platform_vectors_set_.end(); ++it) {
kVectorPairs vp = *it;
VectorPairs vp = *it;
if (vp.first == name) {
platform_vectors_set_.erase(vp);
}
}
if (value.size() > 0) {
kVectorPairs vp(name, value);
VectorPairs vp(name, value);
platform_vectors_set_.insert(vp);
}
@@ -540,7 +540,7 @@ int TestHost::PersistPlatformByteArray(const std::string& name,
memcpy(&string_buffer[0], &value[0], value.size());
test_host_file_.Write(&string_buffer[0], value.size());
}
kVectorPairs vp(name, value);
VectorPairs vp(name, value);
SetPlatformByteArray(name, value);
// open file and persist this so that it can be retrieved as a string/vector
@@ -550,11 +550,11 @@ int TestHost::PersistPlatformByteArray(const std::string& name,
int TestHost::GetPlatformByteArray(const std::string& name,
std::vector<uint8_t>* value) {
std::set<kVectorPairs>::iterator it;
std::set<VectorPairs>::iterator it;
for (it = platform_vectors_set_.begin();
it != platform_vectors_set_.end(); ++it) {
kVectorPairs vp = *it;
VectorPairs vp = *it;
std::string x = vp.first;
if (x == name) {
*value = vp.second;
@@ -693,7 +693,6 @@ 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
@@ -735,7 +734,7 @@ class WvCdmApiTest : public testing::Test {
// emulate pulling a certificate out of persistent storage.
static const size_t kPrivacyCertSize = 256;
kVectorBytes cert(kPrivacyCertSize);
VectorBytes cert(kPrivacyCertSize);
for (size_t i = 0; i < cert.size(); i++) {
cert[i] = i;
}
@@ -756,18 +755,19 @@ class WvCdmApiTest : public testing::Test {
if (status == cdm::kNeedsDeviceCertificate) {
std::string provisioning_server_url;
std::string prov_request;
if (cdm_->GetProvisioningRequest(&prov_request,
&provisioning_server_url) == cdm::kSuccess) {
UrlRequest url_request(provisioning_server_url,
kDefaultHttpsPort, true, true);
status = cdm_->GetProvisioningRequest(&prov_request,
&provisioning_server_url);
if (status == cdm::kSuccess) {
UrlRequest url_request(provisioning_server_url);
url_request.PostCertRequestInQueryString(prov_request);
std::string message;
int resp_bytes = url_request.GetResponse(&message);
if (resp_bytes) {
if (cdm_->HandleProvisioningResponse(message) == cdm::kSuccess) {
std::string message;
bool ok = url_request.GetResponse(&message);
EXPECT_TRUE(ok);
if (ok) {
status = cdm_->HandleProvisioningResponse(message);
if (status == cdm::kSuccess) {
status = cdm_->GenerateKeyRequest(NULL, 0,
(const uint8_t*)init_data.data(), init_data.length());
}
@@ -782,7 +782,7 @@ class WvCdmApiTest : public testing::Test {
const std::string& client_auth,
int expected_response) {
// Use secure connection and chunk transfer coding.
UrlRequest url_request(server_url + client_auth, g_port, true, true);
UrlRequest url_request(server_url + client_auth);
if (!url_request.is_connected()) {
return "";
}
@@ -887,20 +887,12 @@ class WvCdmApiTest : public testing::Test {
buf.num_subsamples = 1;
buf.timestamp = 10;
TestBuffer* out_buf =
dynamic_cast<TestBuffer*>(host_->Allocate(buf.data_size));
memset(out_buf->Data(), 0, buf.data_size);
TestDecryptedBlock output;
output.SetDecryptedBuffer(out_buf);
cdm::Status status;
status = cdm_->Decrypt(buf, &output);
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(0, memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0],
buf.data_size));
LOGI("Verbose Acknowledgement of Test Pass!!!\n");
}
// Level 1 / Level 2 payload comes back in the cpu memory as cleartext.
@@ -976,22 +968,14 @@ class WvCdmApiTest : public testing::Test {
buf.num_subsamples = sub.size();
buf.timestamp = 10;
TestBuffer* out_buf = dynamic_cast<TestBuffer*>(host_->Allocate(
buf.data_size));
memset(out_buf->Data(), 0, buf.data_size);
TestDecryptedBlock output;
output.SetDecryptedBuffer(out_buf);
cdm::Status status;
status = cdm_->Decrypt(buf, &output);
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kSuccess, status);
EXPECT_EQ(
0,
memcmp(output.DecryptedBuffer()->Data(), &data.decrypt_data[0],
buf.data_size));
LOGI("Verbose Acknowledgement of Test Pass!!!\n");
}
void DecryptClearSubsampleTestWithMissingSubsampleInfo() {
@@ -1064,15 +1048,9 @@ class WvCdmApiTest : public testing::Test {
//buf.num_subsamples = sub.size();
buf.timestamp = 10;
TestBuffer* out_buf =
dynamic_cast<TestBuffer*>(host_->Allocate(buf.data_size));
memset(out_buf->Data(), 0, buf.data_size);
TestDecryptedBlock output;
output.SetDecryptedBuffer(out_buf);
cdm::Status status;
status = cdm_->Decrypt(buf, &output);
cdm::Status status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kDecryptError, status);
buf.subsamples = &sub[0];
@@ -1088,7 +1066,6 @@ class WvCdmApiTest : public testing::Test {
buf.subsamples = NULL;
status = cdm_->Decrypt(buf, &output);
EXPECT_EQ(cdm::kDecryptError, status);
LOGI("Verbose Acknowledgement of Test Pass!!!\n");
}
// Level 1 passes encrypted payload straight through. By calling the
@@ -1157,8 +1134,6 @@ class WvCdmApiTest : public testing::Test {
EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kSuccess, status);
LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n");
}
// Level 1 passes encrypted payload straight through. By calling the
@@ -1240,8 +1215,6 @@ class WvCdmApiTest : public testing::Test {
EXPECT_EQ(cdm::kSuccess, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kSuccess, status);
LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n");
}
void WithMissingSubsampleInfoTest() {
@@ -1335,8 +1308,6 @@ class WvCdmApiTest : public testing::Test {
EXPECT_EQ(cdm::kDecryptError, status);
status = cdm_->DecryptDecodeAndRenderFrame(buf);
EXPECT_EQ(cdm::kDecryptError, status);
LOGI("Verbose Acknowledgement of Level 1 Test Pass!!!\n");
}
void TimerTest(void* session_id) { host_->SetTimer(1000, session_id); }
@@ -1505,14 +1476,12 @@ int main(int argc, char** argv) {
// 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},
@@ -1528,11 +1497,6 @@ int main(int argc, char** argv) {
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);
@@ -1559,11 +1523,6 @@ int main(int argc, char** argv) {
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"
@@ -1584,12 +1543,10 @@ int main(int argc, char** argv) {
std::cout << std::endl;
std::cout << "Server: " << g_license_server << std::endl;
std::cout << "Port: " << g_port << std::endl;
std::cout << "KeyID: " << g_key_id << std::endl << std::endl;
g_key_id = wvcdm::a2bs_hex(g_key_id);
config.set_license_server(g_license_server);
config.set_port(g_port);
config.set_key_id(g_key_id);
return RUN_ALL_TESTS();

View File

@@ -88,10 +88,11 @@ class CdmEngine {
std::string* cert,
std::string* wrapped_key);
// Secure stop related methods
CdmResponseType GetSecureStops(CdmSecureStops* secure_stops);
CdmResponseType ReleaseSecureStops(
const CdmSecureStopReleaseMessage& message);
CdmResponseType Unprovision(CdmSecurityLevel security_level);
// Usage related methods for streaming licenses
CdmResponseType GetUsageInfo(CdmUsageInfo* usage_info);
CdmResponseType ReleaseUsageInfo(const CdmUsageInfoReleaseMessage& message);
// Decryption and key related methods
// Accept encrypted buffer and return decrypted data.
@@ -124,6 +125,9 @@ class CdmEngine {
CdmReleaseKeySetMap release_key_sets_;
CertificateProvisioning cert_provisioning_;
SecurityLevel cert_provisioning_requested_security_level_;
CdmSession* usage_session_;
int64_t last_usage_information_update_time;
CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine);
};

View File

@@ -28,6 +28,8 @@ class CdmSession {
CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id,
const CdmLicenseType license_type);
CdmResponseType RestoreUsageSession(const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response);
void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; }
const CdmKeySystem& key_system() { return key_system_; }
@@ -86,6 +88,12 @@ class CdmSession {
void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
SecurityLevel GetRequestedSecurityLevel();
CdmSecurityLevel GetSecurityLevel();
CdmResponseType UpdateUsageInformation();
bool is_usage_update_needed() { return is_usage_update_needed_; }
void reset_is_usage_update_needed() { is_usage_update_needed_ = false; }
private:
@@ -93,7 +101,9 @@ class CdmSession {
CdmSessionId GenerateSessionId();
bool GenerateKeySetId(CdmKeySetId* key_set_id);
CdmResponseType StoreLicense();
bool StoreLicense(DeviceFiles::LicenseState state);
bool DeleteLicense();
// instance variables
const CdmSessionId session_id_;
@@ -103,13 +113,16 @@ class CdmSession {
PolicyEngine policy_engine_;
bool license_received_;
bool reinitialize_session_;
bool is_offline_;
bool is_release_;
bool is_usage_update_needed_;
CdmLicenseType license_type_;
// information useful for offline and usage scenarios
CdmKeyMessage key_request_;
CdmKeyResponse key_response_;
// license type offline related information
CdmInitData offline_init_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_;

View File

@@ -45,7 +45,8 @@ class CryptoSession {
const std::string& signature,
const std::string& mac_key_iv,
const std::string& mac_key,
int num_keys, const CryptoKey* key_array);
const std::vector<CryptoKey>& key_array,
const std::string& provider_session_token);
bool LoadCertificatePrivateKey(std::string& wrapped_key);
bool RefreshKeys(const std::string& message, const std::string& signature,
int num_keys, const CryptoKey* key_array);
@@ -63,6 +64,15 @@ class CryptoSession {
// Media data path
CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
CdmResponseType UpdateUsageInformation();
CdmResponseType GenerateUsageReport(
const std::string& provider_session_token,
std::string* usage_report);
CdmResponseType ReleaseUsageInformation(
const std::string& message,
const std::string& signature,
const std::string& provider_session_token);
bool GetRandom(size_t data_length, uint8_t* random_data);
private:

View File

@@ -5,6 +5,10 @@
#include "wv_cdm_types.h"
#if defined(UNIT_TEST)
#include <gtest/gtest_prod.h>
#endif
namespace wvcdm {
class File;
@@ -18,10 +22,10 @@ class DeviceFiles {
} LicenseState;
DeviceFiles(): file_(NULL), security_level_(kSecurityLevelUninitialized),
initialized_(false) {}
virtual ~DeviceFiles() {}
initialized_(false), test_file_(false) {}
virtual ~DeviceFiles();
virtual bool Init(const File* handle, CdmSecurityLevel security_level);
virtual bool Init(CdmSecurityLevel security_level);
virtual bool StoreCertificate(const std::string& certificate,
const std::string& wrapped_private_key);
@@ -49,25 +53,52 @@ class DeviceFiles {
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);
virtual bool StoreUsageInfo(const std::string& provider_session_token,
const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response);
virtual bool DeleteUsageInfo(const std::string& provider_session_token);
virtual bool DeleteUsageInfo();
virtual bool RetrieveUsageInfo(
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info);
private:
bool StoreFile(const char* name, const std::string& serialized_file);
bool RetrieveFile(const char* name, std::string* serialized_file);
// Certificate and offline licenses are now stored in security
// level specific directories. In an earlier version they were
// stored in a common directory and need to be copied over.
virtual void SecurityLevelPathBackwardCompatibility();
// For testing only:
static std::string GetCertificateFileName();
static std::string GetLicenseFileNameExtension();
static std::string GetUsageInfoFileName();
void SetTestFile(File* file);
#if defined(UNIT_TEST)
FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel);
FRIEND_TEST(DeviceFilesStoreTest, StoreCertificate);
FRIEND_TEST(DeviceFilesStoreTest, StoreLicense);
FRIEND_TEST(DeviceFilesTest, DeleteLicense);
FRIEND_TEST(DeviceFilesTest, ReadCertificate);
FRIEND_TEST(DeviceFilesTest, RetrieveLicenses);
FRIEND_TEST(DeviceFilesTest, SecurityLevelPathBackwardCompatibility);
FRIEND_TEST(DeviceFilesTest, StoreLicenses);
FRIEND_TEST(DeviceFilesTest, UpdateLicenseState);
FRIEND_TEST(DeviceFilesUsageInfoTest, Delete);
FRIEND_TEST(DeviceFilesUsageInfoTest, Read);
FRIEND_TEST(DeviceFilesUsageInfoTest, Store);
FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest);
FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test);
FRIEND_TEST(WvCdmUsageInfoTest, DISABLED_UsageInfo);
#endif
File* file_;
CdmSecurityLevel security_level_;
bool initialized_;
bool test_file_;
CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles);
};

View File

@@ -7,6 +7,9 @@
#include "wv_cdm_types.h"
#include <stddef.h>
#include <stdlib.h>
namespace wvcdm {
// File class. The implementation is platform dependent.

View File

@@ -40,12 +40,16 @@ class CdmLicense {
CdmResponseType HandleKeyUpdateResponse(
bool is_renewal, const CdmKeyResponse& license_response);
bool RestoreOfflineLicense(CdmKeyMessage& license_request,
CdmKeyResponse& license_response,
CdmKeyResponse& license_renewal_response);
bool RestoreOfflineLicense(const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response,
const CdmKeyResponse& license_renewal_response);
bool RestoreUsageLicense(const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response);
bool HasInitData() { return !stored_init_data_.empty(); }
bool IsKeyLoaded(const KeyId& key_id);
std::string provider_session_token() { return provider_session_token_; }
private:
bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
std::string* server_url);
@@ -67,6 +71,7 @@ class CdmLicense {
std::string stored_init_data_;
bool initialized_;
std::set<KeyId> loaded_keys_;
std::string provider_session_token_;
// Used for certificate based licensing
CdmKeyMessage key_request_;

View File

@@ -17,6 +17,8 @@ typedef enum {
LOG_VERBOSE
} LogPriority;
extern LogPriority g_cutoff;
// 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

View File

@@ -24,30 +24,27 @@
#include <string>
#include "openssl/evp.h"
#include "openssl/rsa.h"
#include "wv_cdm_types.h"
namespace wvcdm {
class AesCbcKey {
public:
AesCbcKey() : initialized_(false) {};
~AesCbcKey() {};
AesCbcKey();
~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_;
std::string key_;
CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey);
};
class RsaPublicKey {
public:
RsaPublicKey() : key_(NULL) {}
RsaPublicKey();
~RsaPublicKey();
// Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey
@@ -64,7 +61,7 @@ class RsaPublicKey {
const std::string& signature);
private:
RSA* key_;
std::string serialized_key_;
CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey);
};

View File

@@ -23,15 +23,15 @@ 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::vector<std::string> CdmSecureStops;
typedef std::vector<uint8_t> CdmSecureStopReleaseMessage;
typedef std::vector<std::string> CdmUsageInfo;
typedef std::string CdmUsageInfoReleaseMessage;
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;
typedef std::pair<std::string, std::string> StringPairs;
typedef std::vector<uint8_t> VectorBytes;
typedef std::pair<std::string, VectorBytes> VectorPairs;
enum CdmResponseType {
NO_ERROR,

View File

@@ -6,6 +6,8 @@
#include <sstream>
#include "cdm_session.h"
#include "clock.h"
#include "device_files.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
@@ -14,14 +16,24 @@
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
namespace {
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
const size_t kMinNoncesPerSession = 4;
} // unnamed namespace
namespace wvcdm {
CdmEngine::CdmEngine()
: cert_provisioning_requested_security_level_(kLevelDefault) {
: cert_provisioning_requested_security_level_(kLevelDefault),
usage_session_(NULL),
last_usage_information_update_time(0) {
Properties::Init();
}
CdmEngine::~CdmEngine() {
if (NULL != usage_session_)
delete usage_session_;
CdmSessionMap::iterator i(sessions_.begin());
for (; i != sessions_.end(); ++i) {
delete i->second;
@@ -478,13 +490,87 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
wrapped_key);
}
CdmResponseType CdmEngine::GetSecureStops(
CdmSecureStops* secure_stops) {
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
DeviceFiles handle;
if (!handle.Init(security_level)) {
LOGE("CdmEngine::Unprovision: unable to initialize device files");
return UNKNOWN_ERROR;
}
if (!handle.DeleteAllFiles()) {
LOGE("CdmEngine::Unprovision: unable to delete files");
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
CdmResponseType CdmEngine::ReleaseSecureStops(
const CdmSecureStopReleaseMessage& message) {
CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
if (NULL == usage_session_) {
usage_session_ = new CdmSession(NULL);
}
CdmResponseType status = usage_session_->Init();
if (NO_ERROR != status) {
LOGE("CdmEngine::GetUsageInfo: session init error");
return status;
}
DeviceFiles handle;
if (!handle.Init(usage_session_->GetSecurityLevel())) {
LOGE("CdmEngine::GetUsageInfo: unable to initialize device files");
return status;
}
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> > license_info;
if (!handle.RetrieveUsageInfo(&license_info)) {
LOGE("CdmEngine::GetUsageInfo: unable to read usage information");
return UNKNOWN_ERROR;
}
if (0 == license_info.size()) {
usage_info->resize(0);
return NO_ERROR;
}
std::string server_url;
// rate limit secure stop messages based on minimum nonce
// table size per session
usage_info->resize(license_info.size() >= kMinNoncesPerSession - 1
? kMinNoncesPerSession - 1
: license_info.size());
for (size_t i = 0; i < usage_info->size(); ++i) {
status = usage_session_->RestoreUsageSession(license_info[i].first,
license_info[i].second);
if (KEY_ADDED != status) {
LOGE("CdmEngine::GetUsageInfo: restore usage session error: %ld",
status);
usage_info->clear();
return status;
}
status = usage_session_->GenerateReleaseRequest(&(*usage_info)[i],
&server_url);
if (KEY_MESSAGE != status) {
LOGE("CdmEngine::GetUsageInfo: generate release request error: %ld",
status);
usage_info->clear();
return status;
}
}
return KEY_MESSAGE;
}
CdmResponseType CdmEngine::ReleaseUsageInfo(
const CdmUsageInfoReleaseMessage& message) {
if (NULL == usage_session_) {
LOGE("CdmEngine::ReleaseUsageInfo: cdm session not initialized");
return UNKNOWN_ERROR;
}
CdmResponseType status = usage_session_->ReleaseKey(message);
if (NO_ERROR != status) {
LOGE("CdmEngine::ReleaseUsageInfo: release key error: %ld", status);
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
@@ -527,8 +613,8 @@ CdmResponseType CdmEngine::Decrypt(
}
if (iter == sessions_.end()) {
LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d",
session_id.size(),
session_id.c_str());
session_id.c_str(),
session_id.size());
return KEY_ERROR;
}
@@ -604,9 +690,31 @@ bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
}
void CdmEngine::OnTimerEvent() {
Clock clock;
uint64_t current_time = clock.GetCurrentTime();
bool update_usage_information = false;
if (current_time - last_usage_information_update_time >
kUpdateUsageInformationPeriod) {
update_usage_information = true;
last_usage_information_update_time = current_time;
}
for (CdmSessionMap::iterator iter = sessions_.begin();
iter != sessions_.end(); ++iter) {
iter != sessions_.end(); ++iter) {
iter->second->OnTimerEvent();
if (update_usage_information && iter->second->is_usage_update_needed()) {
// usage is updated for all sessions so this needs to be
// called only once per update usage information period
CdmResponseType status = iter->second->UpdateUsageInformation();
if (NO_ERROR != status) {
LOGW("Update usage information failed: %u", status);
} else {
update_usage_information = false;
}
}
iter->second->reset_is_usage_update_needed();
}
}

View File

@@ -30,7 +30,9 @@ CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set)
crypto_session_(NULL),
license_received_(false),
reinitialize_session_(false),
license_type_(kLicenseTypeStreaming),
is_offline_(false),
is_release_(false),
is_usage_update_needed_(false),
is_certificate_loaded_(false) {
if (cdm_client_property_set) {
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
@@ -47,9 +49,8 @@ CdmResponseType CdmSession::Init() {
std::string token;
if (Properties::use_certificates_as_identification()) {
File file;
DeviceFiles handle;
if (!handle.Init(&file, session.get()->GetSecurityLevel()) ||
if (!handle.Init(session.get()->GetSecurityLevel()) ||
!handle.RetrieveCertificate(&token, &wrapped_key_)) {
return NEED_PROVISIONING;
}
@@ -71,15 +72,14 @@ CdmResponseType CdmSession::RestoreOfflineSession(
key_set_id_ = key_set_id;
// Retrieve license information from persistent store
File file;
DeviceFiles handle;
if (!handle.Init(&file, crypto_session_->GetSecurityLevel()))
if (!handle.Init(crypto_session_->GetSecurityLevel()))
return UNKNOWN_ERROR;
DeviceFiles::LicenseState license_state;
if (!handle.RetrieveLicense(key_set_id, &license_state, &offline_init_data_,
&offline_key_request_, &offline_key_response_,
&key_request_, &key_response_,
&offline_key_renewal_request_,
&offline_key_renewal_response_,
&offline_release_server_url_)) {
@@ -102,14 +102,39 @@ CdmResponseType CdmSession::RestoreOfflineSession(
}
}
if (!license_parser_.RestoreOfflineLicense(offline_key_request_,
offline_key_response_,
if (!license_parser_.RestoreOfflineLicense(key_request_, key_response_,
offline_key_renewal_response_)) {
return UNKNOWN_ERROR;
}
license_received_ = true;
license_type_ = license_type;
is_offline_ = true;
is_release_ = license_type == kLicenseTypeRelease;
return KEY_ADDED;
}
CdmResponseType CdmSession::RestoreUsageSession(
const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response) {
key_request_ = key_request;
key_response_ = key_response;
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_.RestoreUsageLicense(key_request_, key_response_)) {
return UNKNOWN_ERROR;
}
license_received_ = true;
is_offline_ = false;
is_release_ = true;
return KEY_ADDED;
}
@@ -135,9 +160,17 @@ CdmResponseType CdmSession::GenerateKeyRequest(
return UNKNOWN_ERROR;
}
license_type_ = license_type;
switch (license_type) {
case kLicenseTypeStreaming: is_offline_ = false; break;
case kLicenseTypeOffline: is_offline_ = true; break;
case kLicenseTypeRelease: is_release_ = true; break;
default:
LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld",
license_type);
return UNKNOWN_ERROR;
}
if (license_type_ == kLicenseTypeRelease) {
if (is_release_) {
return GenerateReleaseRequest(key_request, server_url);
} else if (license_received_) { // renewal
return GenerateRenewalRequest(key_request, server_url);
@@ -168,9 +201,9 @@ CdmResponseType CdmSession::GenerateKeyRequest(
return KEY_ERROR;
}
if (license_type_ == kLicenseTypeOffline) {
key_request_ = *key_request;
if (is_offline_) {
offline_init_data_ = init_data.data();
offline_key_request_ = *key_request;
offline_release_server_url_ = *server_url;
}
@@ -191,7 +224,7 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
return UNKNOWN_ERROR;
}
if (license_type_ == kLicenseTypeRelease) {
if (is_release_) {
return ReleaseKey(key_response);
} else if (license_received_) { // renewal
return RenewKey(key_response);
@@ -201,25 +234,11 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
if (sts != KEY_ADDED) return sts;
license_received_ = true;
key_response_ = key_response;
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;
}
if (is_offline_ || !license_parser_.provider_session_token().empty()) {
sts = StoreLicense();
if (sts != NO_ERROR) return sts;
}
*key_set_id = key_set_id_;
@@ -290,7 +309,17 @@ CdmResponseType CdmSession::CancelKeyRequest() {
CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
if (crypto_session_.get() == NULL || !crypto_session_->IsOpen())
return UNKNOWN_ERROR;
return crypto_session_->Decrypt(params);
CdmResponseType status = crypto_session_->Decrypt(params);
if (NO_ERROR == status) {
if (!is_usage_update_needed_) {
is_usage_update_needed_ =
!license_parser_.provider_session_token().empty();
}
}
return status;
}
// License renewal
@@ -303,7 +332,7 @@ CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request,
return KEY_ERROR;
}
if (license_type_ == kLicenseTypeOffline) {
if (is_offline_) {
offline_key_renewal_request_ = *key_request;
}
return KEY_MESSAGE;
@@ -315,7 +344,7 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
license_parser_.HandleKeyUpdateResponse(true, key_response);
if (sts != KEY_ADDED) return sts;
if (license_type_ == kLicenseTypeOffline) {
if (is_offline_) {
offline_key_renewal_response_ = key_response;
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) return UNKNOWN_ERROR;
}
@@ -324,23 +353,28 @@ CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
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;
is_release_ = true;
if (!license_parser_.PrepareKeyUpdateRequest(false, key_request, server_url))
return UNKNOWN_ERROR;
if (is_offline_) { // Mark license as being released
if (!StoreLicense(DeviceFiles::kLicenseStateReleasing))
return UNKNOWN_ERROR;
}
return UNKNOWN_ERROR;
return KEY_MESSAGE;
}
// 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_);
CdmResponseType sts = license_parser_.HandleKeyUpdateResponse(false,
key_response);
if (NO_ERROR != sts)
return sts;
return sts;
if (is_offline_ || !license_parser_.provider_session_token().empty()) {
DeleteLicense();
}
return NO_ERROR;
}
bool CdmSession::IsKeyLoaded(const KeyId& key_id) {
@@ -361,9 +395,8 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
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()))
if (!handle.Init(crypto_session_->GetSecurityLevel()))
return false;
while (key_set_id->empty()) {
@@ -380,18 +413,75 @@ bool CdmSession::GenerateKeySetId(CdmKeySetId* key_set_id) {
return true;
}
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
File file;
CdmResponseType CdmSession::StoreLicense() {
if (is_offline_) {
if (!GenerateKeySetId(&key_set_id_)) {
LOGE("CdmSession::StoreLicense: Unable to generate key set Id");
return UNKNOWN_ERROR;
}
if (!StoreLicense(DeviceFiles::kLicenseStateActive)) {
LOGE("CdmSession::StoreLicense: Unable to store license");
CdmResponseType sts = Init();
if (sts != NO_ERROR) {
LOGW("CdmSession::StoreLicense: Reinitialization failed");
return sts;
}
key_set_id_.clear();
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
std::string provider_session_token = license_parser_.provider_session_token();
if (provider_session_token.empty()) {
LOGE("CdmSession::StoreLicense: No provider session token and not offline");
return UNKNOWN_ERROR;
}
DeviceFiles handle;
if (!handle.Init(&file, crypto_session_->GetSecurityLevel()))
if (!handle.Init(crypto_session_->GetSecurityLevel())) {
LOGE("CdmSession::StoreLicense: Unable to initialize device files");
return UNKNOWN_ERROR;
}
if (!handle.StoreUsageInfo(provider_session_token, key_request_,
key_response_)) {
LOGE("CdmSession::StoreLicense: Unable to store usage info");
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
bool CdmSession::StoreLicense(DeviceFiles::LicenseState state) {
DeviceFiles handle;
if (!handle.Init(crypto_session_->GetSecurityLevel()))
return false;
return handle.StoreLicense(
key_set_id_, state, offline_init_data_, offline_key_request_,
offline_key_response_, offline_key_renewal_request_,
key_set_id_, state, offline_init_data_, key_request_,
key_response_, offline_key_renewal_request_,
offline_key_renewal_response_, offline_release_server_url_);
}
bool CdmSession::DeleteLicense() {
if (!is_offline_ && license_parser_.provider_session_token().empty())
return false;
DeviceFiles handle;
if (!handle.Init(crypto_session_->GetSecurityLevel())) {
LOGE("CdmSession::DeleteLicense: Unable to initialize device files");
return false;
}
if (is_offline_)
return handle.DeleteLicense(key_set_id_);
else
return handle.DeleteUsageInfo(
license_parser_.provider_session_token());
}
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {
std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener);
return result.second;
@@ -433,4 +523,15 @@ SecurityLevel CdmSession::GetRequestedSecurityLevel() {
return kLevelDefault;
}
CdmSecurityLevel CdmSession::GetSecurityLevel() {
if (NULL == crypto_session_.get())
return kSecurityLevelUninitialized;
return crypto_session_.get()->GetSecurityLevel();
}
CdmResponseType CdmSession::UpdateUsageInformation() {
return crypto_session_->UpdateUsageInformation();
}
} // namespace wvcdm

View File

@@ -28,10 +28,9 @@ 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.
* This function converts SignedProvisioningRequest into base64 string. It then
* wraps it in JSON format expected by the frontend. This server requires a
* "web-safe" base 64 encoding, where '+' becomes '-' and '/' becomes '_'.
*
* Returns the JSON formated string in *request. The JSON string will be
* appended as a query parameter, i.e. signedRequest=<base 64 encoded
@@ -103,7 +102,8 @@ CdmResponseType CertificateProvisioning::GetProvisioningRequest(
switch (cert_type) {
case kCertificateWidevine:
options->set_certificate_type(
video_widevine_server::sdk::ProvisioningOptions_CertificateType_RSA_WIDEVINE);
video_widevine_server::sdk::
ProvisioningOptions_CertificateType_WIDEVINE_DRM);
break;
case kCertificateX509:
options->set_certificate_type(
@@ -262,9 +262,8 @@ CdmResponseType CertificateProvisioning::HandleProvisioningResponse(
const std::string& device_certificate =
provisioning_response.device_certificate();
File file;
DeviceFiles handle;
if (!handle.Init(&file, crypto_session_.GetSecurityLevel())) {
if (!handle.Init(crypto_session_.GetSecurityLevel())) {
LOGE("HandleProvisioningResponse: failed to init DeviceFiles");
return UNKNOWN_ERROR;
}

View File

@@ -155,7 +155,7 @@ bool CryptoSession::GetDeviceUniqueId(std::string* device_id) {
return false;
}
device_id->assign(reinterpret_cast<char *>(&id[0]), id_length);
device_id->assign(reinterpret_cast<char*>(&id[0]), id_length);
return true;
}
@@ -340,12 +340,11 @@ size_t CryptoSession::GetOffset(std::string message, std::string field) {
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) {
CdmResponseType CryptoSession::LoadKeys(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token) {
LOGV("CryptoSession::LoadKeys: Lock");
AutoLock auto_lock(crypto_lock_);
@@ -358,10 +357,10 @@ CdmResponseType CryptoSession::LoadKeys(const std::string& message,
} 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];
std::vector<OEMCrypto_KeyObject> load_keys(keys.size());
for (size_t i = 0; i < keys.size(); ++i) {
const CryptoKey* ki = &keys[i];
OEMCrypto_KeyObject* ko = &load_keys[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());
@@ -377,11 +376,17 @@ CdmResponseType CryptoSession::LoadKeys(const std::string& message,
ko->key_control = NULL;
}
}
uint8_t* pst = NULL;
if (!provider_session_token.empty()) {
pst =
const_cast<uint8_t*>(msg) + GetOffset(message, provider_session_token);
}
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], NULL, 0);
enc_mac_key_iv, enc_mac_key, keys.size(), &load_keys[0], pst,
provider_session_token.length());
if (OEMCrypto_SUCCESS == sts) {
return KEY_ADDED;
@@ -605,7 +610,8 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
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;
buffer_descriptor.buffer.clear.max_length =
params.decrypt_buffer_length - params.decrypt_buffer_offset;
break;
case OEMCrypto_BufferType_Secure:
buffer_descriptor.buffer.secure.handle = params.decrypt_buffer;
@@ -635,6 +641,84 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
}
}
CdmResponseType CryptoSession::UpdateUsageInformation() {
return (OEMCrypto_UpdateUsageTable() == OEMCrypto_SUCCESS) ? NO_ERROR
: UNKNOWN_ERROR;
}
CdmResponseType CryptoSession::GenerateUsageReport(
const std::string& provider_session_token, std::string* usage_report) {
LOGV("GenerateUsageReport: id=%ld", (uint32_t)oec_session_id_);
if (NULL == usage_report) {
LOGE("usage_report parameter is null");
return UNKNOWN_ERROR;
}
AutoLock auto_lock(crypto_lock_);
uint8_t* pst = reinterpret_cast<uint8_t*>(
const_cast<char*>(provider_session_token.data()));
OEMCryptoResult status =
OEMCrypto_DeactivateUsageEntry(pst, provider_session_token.length());
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::GenerateUsageReport: Deactivate Usage Entry error=%ld",
status);
return UNKNOWN_ERROR;
}
size_t usage_length = 0;
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), NULL,
&usage_length);
if (OEMCrypto_ERROR_SHORT_BUFFER != status) {
LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld", status);
return UNKNOWN_ERROR;
}
usage_report->resize(usage_length);
OEMCrypto_PST_Report* report = reinterpret_cast<OEMCrypto_PST_Report*>(
const_cast<char*>(usage_report->data()));
status = OEMCrypto_ReportUsage(oec_session_id_, pst,
provider_session_token.length(), report,
&usage_length);
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::GenerateUsageReport: Report Usage error=%ld", status);
return UNKNOWN_ERROR;
}
if (usage_length < usage_report->length()) {
usage_report->resize(usage_length);
}
return NO_ERROR;
}
CdmResponseType CryptoSession::ReleaseUsageInformation(
const std::string& message, const std::string& signature,
const std::string& provider_session_token) {
LOGV("ReleaseUsageInformation: id=%ld", (uint32_t)oec_session_id_);
AutoLock auto_lock(crypto_lock_);
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
const uint8_t* sig = reinterpret_cast<const uint8_t*>(signature.data());
const uint8_t* pst = msg + GetOffset(message, provider_session_token);
OEMCryptoResult status = OEMCrypto_DeleteUsageEntry(
oec_session_id_, pst, provider_session_token.length(), msg,
message.length(), sig, signature.length());
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::ReleaseUsageInformation: Report Usage error=%ld",
status);
return UNKNOWN_ERROR;
}
return NO_ERROR;
}
bool CryptoSession::GenerateNonce(uint32_t* nonce) {
if (!nonce) {
LOGE("input parameter is null");

View File

@@ -2,14 +2,22 @@
#include "device_files.h"
#if defined(__APPLE__)
# include <CommonCrypto/CommonDigest.h>
# define SHA256 CC_SHA256
# define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH
#else
# include <openssl/sha.h>
#endif
#include <cstring>
#include <string>
#include "device_files.pb.h"
#include "file_store.h"
#include "log.h"
#include "openssl/sha.h"
#include "properties.h"
#include "string_conversions.h"
// Protobuf generated classes.
using video_widevine_client::sdk::DeviceCertificate;
@@ -17,9 +25,13 @@ using video_widevine_client::sdk::HashedFile;
using video_widevine_client::sdk::License;
using video_widevine_client::sdk::License_LicenseState_ACTIVE;
using video_widevine_client::sdk::License_LicenseState_RELEASING;
using video_widevine_client::sdk::UsageInfo;
using video_widevine_client::sdk::UsageInfo_ProviderSession;
namespace {
const char kCertificateFileName[] = "cert.bin";
const char kUsageInfoFileName[] = "usage.bin";
const char kLicenseFileNameExt[] = ".lic";
const char kWildcard[] = "*";
const char kDirectoryDelimiter = '/';
@@ -27,15 +39,27 @@ const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"};
size_t kSecurityLevelPathCompatibilityExclusionListSize =
sizeof(kSecurityLevelPathCompatibilityExclusionList) /
sizeof(*kSecurityLevelPathCompatibilityExclusionList);
} // namespace
bool Hash(const std::string& data, std::string* hash) {
if (!hash) return false;
hash->resize(SHA256_DIGEST_LENGTH);
const unsigned char* input =
reinterpret_cast<const unsigned char*>(data.data());
unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]);
SHA256(input, data.size(), output);
return true;
}
} // unnamed namespace
namespace wvcdm {
bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) {
if (handle == NULL) {
LOGW("DeviceFiles::Init: Invalid file handle parameter");
return false;
}
DeviceFiles::~DeviceFiles() {
if (!file_ && !test_file_) delete file_;
}
bool DeviceFiles::Init(CdmSecurityLevel security_level) {
switch (security_level) {
case kSecurityLevelL1:
case kSecurityLevelL2:
@@ -45,7 +69,7 @@ bool DeviceFiles::Init(const File* handle, CdmSecurityLevel security_level) {
LOGW("DeviceFiles::Init: Unsupported security level %d", security_level);
return false;
}
file_ = const_cast<File*>(handle);
file_ = new File();
security_level_ = security_level;
initialized_ = true;
return true;
@@ -68,24 +92,10 @@ bool DeviceFiles::StoreCertificate(const std::string& certificate,
device_certificate->set_certificate(certificate);
device_certificate->set_wrapped_private_key(wrapped_private_key);
std::string serialized_string;
file.SerializeToString(&serialized_string);
std::string serialized_file;
file.SerializeToString(&serialized_file);
// 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);
return StoreFile(kCertificateFileName, serialized_file);
}
bool DeviceFiles::RetrieveCertificate(std::string* certificate,
@@ -99,29 +109,12 @@ bool DeviceFiles::RetrieveCertificate(std::string* certificate,
SecurityLevelPathBackwardCompatibility();
}
std::string serialized_hashed_file;
if (!RetrieveFile(kCertificateFileName, &serialized_hashed_file))
std::string serialized_file;
if (!RetrieveFile(kCertificateFileName, &serialized_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())) {
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file");
return false;
}
@@ -187,25 +180,11 @@ bool DeviceFiles::StoreLicense(const std::string& key_set_id,
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 serialized_file;
file.SerializeToString(&serialized_file);
std::string file_name = key_set_id + kLicenseFileNameExt;
return StoreFile(file_name.c_str(), serialized_string);
return StoreFile(file_name.c_str(), serialized_file);
}
bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
@@ -220,29 +199,12 @@ bool DeviceFiles::RetrieveLicense(const std::string& key_set_id,
return false;
}
std::string serialized_hashed_file;
std::string serialized_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;
}
if (!RetrieveFile(file_name.c_str(), &serialized_file)) return false;
video_widevine_client::sdk::File file;
if (!file.ParseFromString(hashed_file.file())) {
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::RetrieveLicense: Unable to parse file");
return false;
}
@@ -352,18 +314,144 @@ bool DeviceFiles::LicenseExists(const std::string& key_set_id) {
return file_->Exists(path);
}
bool DeviceFiles::Hash(const std::string& data, std::string* hash) {
if (!hash) return false;
bool DeviceFiles::StoreUsageInfo(const std::string& provider_session_token,
const CdmKeyMessage& key_request,
const CdmKeyResponse& key_response) {
if (!initialized_) {
LOGW("DeviceFiles::StoreUsageInfo: not initialized");
return false;
}
std::string serialized_file;
video_widevine_client::sdk::File file;
if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) {
file.set_type(video_widevine_client::sdk::File::USAGE_INFO);
file.set_version(video_widevine_client::sdk::File::VERSION_1);
} else {
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::StoreUsageInfo: Unable to parse file");
return false;
}
}
UsageInfo* usage_info = file.mutable_usage_info();
UsageInfo_ProviderSession* provider_session = usage_info->add_sessions();
provider_session->set_token(provider_session_token.data(),
provider_session_token.size());
provider_session->set_license_request(key_request.data(),
key_request.size());
provider_session->set_license(key_response.data(),
key_response.size());
file.SerializeToString(&serialized_file);
return StoreFile(kUsageInfoFileName, serialized_file);
}
bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) {
if (!initialized_) {
LOGW("DeviceFiles::DeleteUsageInfo: not initialized");
return false;
}
std::string serialized_file;
if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) return false;
video_widevine_client::sdk::File file;
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::DeleteUsageInfo: Unable to parse file");
return false;
}
UsageInfo* updated_info = file.mutable_usage_info();
UsageInfo info(*(const_cast<const UsageInfo*>(updated_info)));
updated_info->clear_sessions();
bool found = false;
for (int i = 0; i < info.sessions_size(); ++i) {
if (info.sessions(i).token().compare(provider_session_token) == 0) {
found = true;
} else {
updated_info->add_sessions()->set_token(info.sessions(i).token());
updated_info->add_sessions()->set_license_request(
info.sessions(i).license_request());
updated_info->add_sessions()->set_license(info.sessions(i).license());
}
}
if (!found) {
LOGW("DeviceFiles::DeleteUsageInfo: Unable to find provider session "
"token: %s", b2a_hex(provider_session_token).c_str());
return false;
}
file.SerializeToString(&serialized_file);
return StoreFile(kUsageInfoFileName, serialized_file);
}
bool DeviceFiles::DeleteUsageInfo() {
if (!initialized_) {
LOGW("DeviceFiles::DeleteUsageInfo: not initialized");
return false;
}
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::DeleteUsageInfo: Unable to get base path");
return false;
}
path.append(kUsageInfoFileName);
return file_->Remove(path);
}
bool DeviceFiles::RetrieveUsageInfo(std::vector<
std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info) {
if (!initialized_) {
LOGW("DeviceFiles::RetrieveUsageInfo: not initialized");
return false;
}
if (NULL == usage_info) {
LOGW("DeviceFiles::RetrieveUsageInfo: license destination not "
"provided");
return false;
}
std::string serialized_file;
if (!RetrieveFile(kUsageInfoFileName, &serialized_file)) {
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
return false;
}
path += kUsageInfoFileName;
if (!file_->Exists(path) || 0 == file_->FileSize(path)) {
usage_info->resize(0);
return true;
}
return false;
}
video_widevine_client::sdk::File file;
if (!file.ParseFromString(serialized_file)) {
LOGW("DeviceFiles::RetrieveUsageInfo: Unable to parse file");
return false;
}
usage_info->resize(file.usage_info().sessions_size());
for (int i = 0; i < file.usage_info().sessions_size(); ++i) {
(*usage_info)[i] =
std::make_pair(file.usage_info().sessions(i).license_request(),
file.usage_info().sessions(i).license());
}
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) {
bool DeviceFiles::StoreFile(const char* name,
const std::string& serialized_file) {
if (!file_) {
LOGW("DeviceFiles::StoreFile: Invalid file handle");
return false;
@@ -374,6 +462,21 @@ bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
return false;
}
// calculate SHA hash
std::string hash;
if (!Hash(serialized_file, &hash)) {
LOGW("DeviceFiles::StoreFile: Hash computation failed");
return false;
}
// Fill in hashed file data
HashedFile hash_file;
hash_file.set_file(serialized_file);
hash_file.set_hash(hash);
std::string serialized_hash_file;
hash_file.SerializeToString(&serialized_hash_file);
std::string path;
if (!Properties::GetDeviceFilesBasePath(security_level_, &path)) {
LOGW("DeviceFiles::StoreFile: Unable to get base path");
@@ -391,19 +494,24 @@ bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
return false;
}
ssize_t bytes = file_->Write(data.data(), data.size());
ssize_t bytes = file_->Write(serialized_hash_file.data(),
serialized_hash_file.size());
file_->Close();
if (bytes != static_cast<ssize_t>(data.size())) {
LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes);
if (bytes != static_cast<ssize_t>(serialized_hash_file.size())) {
LOGW("DeviceFiles::StoreFile: write failed: (actual: %d, expected: %d)",
bytes,
serialized_hash_file.size());
return false;
}
LOGV("DeviceFiles::StoreFile: success: %s (%db)", path.c_str(), data.size());
LOGV("DeviceFiles::StoreFile: success: %s (%db)",
path.c_str(),
serialized_hash_file.size());
return true;
}
bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
bool DeviceFiles::RetrieveFile(const char* name, std::string* serialized_file) {
if (!file_) {
LOGW("DeviceFiles::RetrieveFile: Invalid file handle");
return false;
@@ -414,8 +522,8 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
return false;
}
if (!data) {
LOGW("DeviceFiles::RetrieveFile: Unspecified data parameter");
if (!serialized_file) {
LOGW("DeviceFiles::RetrieveFile: Unspecified serialized_file parameter");
return false;
}
@@ -434,7 +542,7 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
ssize_t bytes = file_->FileSize(path);
if (bytes <= 0) {
LOGW("DeviceFiles::RetrieveFile: File size invalid: %d", path.c_str());
LOGW("DeviceFiles::RetrieveFile: File size invalid: %s", path.c_str());
return false;
}
@@ -442,17 +550,37 @@ bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
return false;
}
data->resize(bytes);
bytes = file_->Read(&(*data)[0], data->size());
std::string serialized_hash_file;
serialized_hash_file.resize(bytes);
bytes = file_->Read(&serialized_hash_file[0], serialized_hash_file.size());
file_->Close();
if (bytes != static_cast<ssize_t>(data->size())) {
if (bytes != static_cast<ssize_t>(serialized_hash_file.size())) {
LOGW("DeviceFiles::RetrieveFile: read failed");
return false;
}
LOGV("DeviceFiles::RetrieveFile: success: %s (%db)", path.c_str(),
data->size());
serialized_hash_file.size());
HashedFile hash_file;
if (!hash_file.ParseFromString(serialized_hash_file)) {
LOGW("DeviceFiles::RetrieveFile: Unable to parse hash file");
return false;
}
std::string hash;
if (!Hash(hash_file.file(), &hash)) {
LOGW("DeviceFiles::RetrieveFile: Hash computation failed");
return false;
}
if (hash.compare(hash_file.hash())) {
LOGW("DeviceFiles::RetrieveFile: Hash mismatch");
return false;
}
*serialized_file = hash_file.file();
return true;
}
@@ -528,4 +656,14 @@ std::string DeviceFiles::GetLicenseFileNameExtension() {
return kLicenseFileNameExt;
}
std::string DeviceFiles::GetUsageInfoFileName() {
return kUsageInfoFileName;
}
void DeviceFiles::SetTestFile(File* file) {
if (file_) delete file_;
file_ = file;
test_file_ = true;
}
} // namespace wvcdm

View File

@@ -33,10 +33,21 @@ message License {
optional bytes release_server_url = 7;
}
message UsageInfo {
message ProviderSession {
optional bytes token = 1;
optional bytes license_request = 2;
optional bytes license = 3;
}
repeated ProviderSession sessions = 1;
}
message File {
enum FileType {
DEVICE_CERTIFICATE = 1;
LICENSE = 2;
USAGE_INFO = 3;
}
enum FileVersion {
@@ -47,9 +58,11 @@ message File {
optional FileVersion version = 2 [default = VERSION_1];
optional DeviceCertificate device_certificate = 3;
optional License license = 4;
optional UsageInfo usage_info = 5;
}
message HashedFile {
optional bytes file = 1;
// A raw (not hex-encoded) SHA256, taken over the bytes of 'file'.
optional bytes hash = 2;
}

View File

@@ -6,6 +6,7 @@
#include "crypto_key.h"
#include "crypto_session.h"
#include "device_files.h"
#include "log.h"
#include "policy_engine.h"
#include "properties.h"
@@ -81,15 +82,16 @@ 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::License;
using video_widevine_server::sdk::License_KeyContainer;
using video_widevine_server::sdk::LicenseError;
using video_widevine_server::sdk::LicenseIdentification;
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_WebM;
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;
@@ -424,7 +426,19 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal,
LicenseRequest_ContentIdentification_ExistingLicense* current_license =
license_request.mutable_content_id()->mutable_license();
current_license->mutable_license_id()->CopyFrom(policy_engine_->license_id());
LicenseIdentification license_id = policy_engine_->license_id();
current_license->mutable_license_id()->CopyFrom(license_id);
if (!is_renewal) {
if (license_id.has_provider_session_token()) {
std::string usage_report;
if (!session_->GenerateUsageReport(license_id.provider_session_token(),
&usage_report)) {
return false;
}
current_license->set_session_usage_table_entry(usage_report);
}
}
// Get/set the nonce. This value will be reflected in the Key Control Block
// of the license response.
@@ -488,7 +502,7 @@ CdmResponseType CdmLicense::HandleKeyResponse(
break;
case SignedMessage::SERVICE_CERTIFICATE:
return CdmLicense::HandleServiceCertificateResponse(signed_response);
case SignedMessage::ERROR:
case SignedMessage::ERROR_RESPONSE:
return HandleKeyErrorResponse(signed_response);
default:
LOGE("CdmLicense::HandleKeyResponse: unrecognized signed message type: %d"
@@ -546,6 +560,10 @@ CdmResponseType CdmLicense::HandleKeyResponse(
return KEY_ERROR;
}
std::string provider_session_token;
if (license.id().has_provider_session_token())
provider_session_token_ = license.id().provider_session_token();
if (license.policy().has_renewal_server_url()) {
server_url_ = license.policy().renewal_server_url();
}
@@ -556,8 +574,8 @@ CdmResponseType CdmLicense::HandleKeyResponse(
signed_response.signature(),
mac_key_iv,
mac_key,
key_array.size(),
&key_array[0]);
key_array,
provider_session_token);
if (KEY_ADDED == resp) {
loaded_keys_.clear();
@@ -587,7 +605,7 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
return KEY_ERROR;
}
if (signed_response.type() == SignedMessage::ERROR) {
if (signed_response.type() == SignedMessage::ERROR_RESPONSE) {
return HandleKeyErrorResponse(signed_response);
}
@@ -615,6 +633,14 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
server_url_ = license.policy().renewal_server_url();
}
}
else {
if (license.id().has_provider_session_token()) {
provider_session_token_ = license.id().provider_session_token();
session_->ReleaseUsageInformation(signed_response.msg(),
signed_response.signature(),
provider_session_token_);
}
}
policy_engine_->UpdateLicense(license);
@@ -631,8 +657,9 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
}
bool CdmLicense::RestoreOfflineLicense(
CdmKeyMessage& license_request, CdmKeyResponse& license_response,
CdmKeyResponse& license_renewal_response) {
const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response,
const CdmKeyResponse& license_renewal_response) {
if (license_request.empty() || license_response.empty()) {
LOGE(
@@ -675,6 +702,77 @@ bool CdmLicense::RestoreOfflineLicense(
return true;
}
bool CdmLicense::RestoreUsageLicense(const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response) {
if (license_request.empty() || license_response.empty()) {
LOGE(
"CdmLicense::RestoreUsageLicense: 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::RestoreUsageLicense: license_request parse failed");
return false;
}
if (signed_request.type() != SignedMessage::LICENSE_REQUEST) {
LOGE(
"CdmLicense::RestoreUsageLicense: license request type: expected = %d,"
" actual = %d",
SignedMessage::LICENSE_REQUEST, signed_request.type());
return false;
}
key_request_ = signed_request.msg();
SignedMessage signed_response;
if (!signed_response.ParseFromString(license_response)) {
LOGE("CdmLicense::RestoreUsageLicense: unable to parse signed license"
" response");
return false;
}
if (SignedMessage::LICENSE != signed_response.type()) {
LOGE("CdmLicense::RestoreUsageLicense: unrecognized signed message type: %d"
, signed_response.type());
return false;
}
if (Properties::use_certificates_as_identification()) {
if (!signed_response.has_session_key()) {
LOGE("CdmLicense::RestoreUsageLicense: no session keys present");
return false;
}
if (!session_->GenerateDerivedKeys(key_request_,
signed_response.session_key()))
return false;
} else {
if (!session_->GenerateDerivedKeys(key_request_)) return false;
}
if (!signed_response.has_signature()) {
LOGE("CdmLicense::RestoreUsageLicense: license response is not signed");
return false;
}
License license;
if (!license.ParseFromString(signed_response.msg())) {
LOGE("CdmLicense::RestoreUsageLicense: unable to parse license response");
return false;
}
policy_engine_->SetLicense(license);
return true;
}
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
return loaded_keys_.find(key_id) != loaded_keys_.end();
}
bool CdmLicense::PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
std::string* server_url) {
if (!initialized_) {
@@ -798,8 +896,4 @@ bool CdmLicense::PrepareContentId(const CdmLicenseType license_type,
return true;
}
bool CdmLicense::IsKeyLoaded(const KeyId& key_id) {
return loaded_keys_.find(key_id) != loaded_keys_.end();
}
} // namespace wvcdm

View File

@@ -27,6 +27,7 @@ message LicenseIdentification {
optional bytes purchase_id = 3;
optional LicenseType type = 4;
optional int32 version = 5;
optional bytes provider_session_token = 6;
}
message License {
@@ -126,6 +127,8 @@ message License {
HDCP_NONE = 0;
HDCP_V1 = 1;
HDCP_V2 = 2;
HDCP_V2_1 = 3;
HDCP_V2_2 = 4;
}
optional HDCP hdcp = 1 [default = HDCP_NONE];
@@ -139,6 +142,15 @@ message License {
optional CGMS cgms_flags = 2 [default = CGMS_NONE];
}
message VideoResolutionConstraint {
// Minimum and maximum video resolutions in the range (height x width).
optional uint32 min_resolution_pixels = 1;
optional uint32 max_resolution_pixels = 2;
// Optional output protection requirements for this range. If not
// specified, the OutputProtection in the KeyContainer applies.
optional OutputProtection required_protection = 3;
}
message OperatorSessionKeyPermissions {
// Permissions/key usage flags for operator service keys
// (type = OPERATOR_SESSION).
@@ -157,12 +169,20 @@ message License {
optional OutputProtection requested_protection = 7;
optional KeyControl key_control = 8;
optional OperatorSessionKeyPermissions operator_session_key_permissions = 9;
// Optional video resolution constraints. If the video resolution of the
// content being decrypted/decoded falls within one of the specified ranges,
// the optional required_protections may be applied. Otherwise an error will
// be reported.
repeated VideoResolutionConstraint video_resolution_constraints = 10;
}
optional LicenseIdentification id = 1;
optional Policy policy = 2;
repeated KeyContainer key = 3;
optional int64 license_start_time = 4;
optional bool remote_attestation_verified = 5 [default = false];
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 6;
}
enum ProtocolVersion {
@@ -187,6 +207,8 @@ message LicenseRequest {
message ExistingLicense {
optional LicenseIdentification license_id = 1;
optional int64 seconds_since_started = 2;
optional int64 seconds_since_last_played = 3;
optional bytes session_usage_table_entry = 4;
}
// Exactly one of these must be present.
@@ -233,11 +255,22 @@ message LicenseError {
optional Error error_code = 1;
}
message RemoteAttestation {
// Encrypted ClientIdentification message containing the device remote
// attestation certificate. Required.
optional EncryptedClientIdentification certificate = 1;
// Bytes of salt which were added to the remote attestation challenge prior to
// signing it. Required.
optional bytes salt = 2;
// Signed remote attestation challenge + salt. Required.
optional bytes signature = 3;
}
message SignedMessage {
enum MessageType {
LICENSE_REQUEST = 1;
LICENSE = 2;
ERROR = 3;
ERROR_RESPONSE = 3;
SERVICE_CERTIFICATE_REQUEST = 4;
SERVICE_CERTIFICATE = 5;
}
@@ -246,10 +279,20 @@ message SignedMessage {
optional bytes msg = 2;
optional bytes signature = 3;
optional bytes session_key = 4;
// Remote attestation data which will be present in the initial license
// request for ChromeOS client devices operating in verified mode. Remote
// attestation challenge data is |msg| field above. Optional.
optional RemoteAttestation remote_attestation = 5;
}
// This message is used to pass optional data on initial license issuance.
message SessionInit {
enum ReplayControl {
NO_SESSION_USAGE = 0;
NONCE_REQUIRED_AND_NEW_SESSION_USAGE = 1;
NONCE_REQUIRED_OR_EXISTING_SESSION_USAGE = 2;
}
optional bytes session_id = 1;
optional bytes purchase_id = 2;
// master_signing_key should be 128 bits in length.
@@ -258,6 +301,19 @@ message SessionInit {
// (server || client) HMAC-SHA256 keys.
optional bytes signing_key = 4;
optional int64 license_start_time = 5;
// Client token for the session. This session is for use by the license
// provider, and is akin to a client cookie. It will be copied to
// License::provider_client_token, and sent back by the client in
// ClientIdentification::provider_client_token in all license requests
// thereafter.
optional bytes provider_client_token = 6;
// Session token for the session. This token is for use by the license
// provider, and is akin to a session cookie. It will be copied to
// LicenseIdentfication::provider_session_token, and sent back in all
// license renewal and release requests for the session thereafter.
optional bytes provider_session_token = 7;
// Replay control indicator which will be encoded into V9+ KeyControl blocks.
optional ReplayControl replay_control = 8 [default = NO_SESSION_USAGE];
}
// This message is used by the server to preserve and restore session state.
@@ -280,7 +336,7 @@ message SessionState {
// in the case of X509 certificates, the certificate authority to use.
message ProvisioningOptions {
enum CertificateType {
RSA_WIDEVINE = 0; // Default. The original certificate type.
WIDEVINE_DRM = 0; // Default. The original certificate type.
X509 = 1; // X.509 certificate.
}
@@ -336,6 +392,7 @@ message ClientIdentification {
enum TokenType {
KEYBOX = 0;
DEVICE_CERTIFICATE = 1;
REMOTE_ATTESTATION_CERTIFICATE = 2;
}
message NameValue {
@@ -343,12 +400,36 @@ message ClientIdentification {
optional string value = 2;
}
// Capabilities which not all clients may support. Used for the license
// exchange protocol only.
message ClientCapabilities {
enum HdcpVersion {
HDCP_NONE = 0;
HDCP_V1 = 1;
HDCP_V2 = 2;
HDCP_V2_1 = 3;
HDCP_V2_2 = 4;
}
optional bool client_token = 1 [default = false];
optional bool session_token = 2 [default = false];
optional bool video_resolution_constraints = 3 [default = false];
optional HdcpVersion max_hdcp_version = 4 [default = HDCP_NONE];
}
// 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;
// Client token generated by the content provider. Optional.
optional bytes provider_client_token = 4;
// Number of licenses received by the client to which the token above belongs.
// Only present if client_token is specified.
optional uint32 license_counter = 5;
// List of non-baseline client capabilities.
optional ClientCapabilities client_capabilities = 6;
}
// EncryptedClientIdentification message used to hold ClientIdentification
@@ -359,16 +440,16 @@ message EncryptedClientIdentification {
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 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
@@ -400,9 +481,10 @@ message DeviceCertificate {
// 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];
// Deprecated field, which used to indicate whether the device was a test
// (non-production) device. The test_device field in ProvisionedDeviceInfo
// below should be observed instead.
optional bool test_device_deprecated = 6 [deprecated = true];
// Service identifier (web origin) for the service which owns the certificate.
// Required for service certificates.
optional string service_id = 7;

View File

@@ -7,38 +7,60 @@
#include "privacy_crypto.h"
#include <openssl/aes.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/sha.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;
RSA* GetKey(const std::string& serialized_key) {
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()),
serialized_key.size());
if (bio == NULL) {
LOGE("GetKey: BIO_new_mem_buf returned NULL");
return NULL;
}
RSA* key = d2i_RSAPublicKey_bio(bio, NULL);
BIO_free(bio);
if (key == NULL) {
LOGE("GetKey: RSA key deserialization failure: %s",
ERR_error_string(ERR_get_error(), NULL));
return NULL;
}
return key;
}
void FreeKey(RSA* key) {
if (key != NULL) {
RSA_free(key);
}
}
} // namespace
namespace wvcdm {
AesCbcKey::AesCbcKey() {}
AesCbcKey::~AesCbcKey() {}
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;
key_ = key;
return true;
}
@@ -60,14 +82,16 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
LOGE("AesCbcKey::Encrypt: crypttext destination not provided");
return false;
}
if (!initialized_) {
if (key_.empty()) {
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",
EVP_CIPHER_CTX ctx;
if (EVP_EncryptInit(&ctx, EVP_aes_128_cbc(),
reinterpret_cast<uint8_t*>(&key_[0]),
reinterpret_cast<uint8_t*>(&(*iv)[0])) == 0) {
LOGE("AesCbcKey::Encrypt: AES CBC setup failure: %s",
ERR_error_string(ERR_get_error(), NULL));
return false;
}
@@ -75,7 +99,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
out->resize(in.size() + AES_BLOCK_SIZE);
int out_length = out->size();
if (EVP_EncryptUpdate(
&ctx_, reinterpret_cast<uint8_t*>(&(*out)[0]), &out_length,
&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",
@@ -84,7 +108,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
}
int padding = 0;
if (EVP_EncryptFinal(&ctx_, reinterpret_cast<uint8_t*>(&(*out)[out_length]),
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));
@@ -95,34 +119,17 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
return true;
}
RsaPublicKey::~RsaPublicKey() {
if (key_ != NULL) {
RSA_free(key_);
}
}
RsaPublicKey::RsaPublicKey() {}
RsaPublicKey::~RsaPublicKey() {}
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;
}
serialized_key_ = serialized_key;
return true;
}
@@ -136,36 +143,46 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message,
LOGE("RsaPublicKey::Encrypt: no encrypt message buffer provided");
return false;
}
if (key_ == NULL) {
if (serialized_key_.empty()) {
LOGE("RsaPublicKey::Encrypt: RSA key not initialized");
return false;
}
int rsa_size = RSA_size(key_);
RSA* key = GetKey(serialized_key_);
if (key == NULL) {
// Error already logged by GetKey.
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);
FreeKey(key);
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_,
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));
FreeKey(key);
return false;
}
return true;
}
bool RsaPublicKey::VerifySignature(const std::string& message,
const std::string& signature) {
if (key_ == NULL) {
if (serialized_key_.empty()) {
LOGE("RsaPublicKey::VerifySignature: RSA key not initialized");
return false;
}
@@ -173,25 +190,33 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
LOGE("RsaPublicKey::VerifySignature: signed message is empty");
return false;
}
RSA* key = GetKey(serialized_key_);
if (key == NULL) {
// Error already logged by GetKey.
return false;
}
int rsa_size = RSA_size(key_);
int rsa_size = RSA_size(key);
if (static_cast<int>(signature.size()) != rsa_size) {
LOGE(
"RsaPublicKey::VerifySignature: message signature is of the wrong "
"size (expected %d, actual %d)",
rsa_size, signature.size());
FreeKey(key);
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_,
reinterpret_cast<unsigned char*>(&padded_digest[0]), key,
RSA_NO_PADDING) != rsa_size) {
LOGE("RsaPublicKey::VerifySignature: RSA public decrypt failure: %s",
ERR_error_string(ERR_get_error(), NULL));
FreeKey(key);
return false;
}
@@ -202,12 +227,13 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
// Verify PSS padding.
if (RSA_verify_PKCS1_PSS(
key_, reinterpret_cast<const unsigned char*>(message_digest.data()),
key, reinterpret_cast<const unsigned char*>(message_digest.data()),
EVP_sha1(),
reinterpret_cast<const unsigned char*>(padded_digest.data()),
kPssSaltLength) == 0) {
LOGE("RsaPublicKey::VerifySignature: RSA verify failure: %s",
ERR_error_string(ERR_get_error(), NULL));
FreeKey(key);
return false;
}

View File

@@ -0,0 +1,43 @@
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Description:
// Dummy version of privacy crypto classes for systems which
// can't tolerate OpenSSL as a dependency.
//
#include "privacy_crypto.h"
namespace wvcdm {
AesCbcKey::AesCbcKey() {}
AesCbcKey::~AesCbcKey() {}
bool AesCbcKey::Init(const std::string& key) {
return false;
}
bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
std::string* iv) {
return false;
}
RsaPublicKey::RsaPublicKey() {}
RsaPublicKey::~RsaPublicKey() {}
bool RsaPublicKey::Init(const std::string& serialized_key) {
return false;
}
bool RsaPublicKey::Encrypt(const std::string& clear_message,
std::string* encrypted_message) {
return false;
}
bool RsaPublicKey::VerifySignature(const std::string& message,
const std::string& signature) {
return false;
}
} // namespace wvcdm

View File

@@ -77,11 +77,11 @@ std::string b2a_hex(const std::string& byte) {
// 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 '_'.
//
// This is the encoding required to interface with the provisioning server, 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();

View File

@@ -33,7 +33,6 @@ wvcdm::KeyId g_key_id_pssh;
wvcdm::KeyId g_key_id_unwrapped;
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
@@ -138,16 +137,16 @@ class WvCdmEngineTest : public testing::Test {
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);
UrlRequest url_request(server_url + client_auth);
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);
bool ok = url_request.GetResponse(&response);
LOGD("response: %s\n", response.c_str());
EXPECT_TRUE(ok);
int status_code = url_request.GetStatusCode(response);
EXPECT_EQ(kHttpOk, status_code);
@@ -254,14 +253,12 @@ int main(int argc, char **argv) {
// The following variables are configurable through command line options.
g_license_server.assign(config.license_server());
g_key_id_pssh.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 },
@@ -277,11 +274,6 @@ int main(int argc, char **argv) {
g_key_id_pssh.assign(optarg);
break;
}
case 'p': {
g_port.clear();
g_port.assign(optarg);
break;
}
case 's': {
g_license_server.clear();
g_license_server.assign(optarg);
@@ -304,11 +296,6 @@ int main(int argc, char **argv) {
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 << " ";
@@ -327,12 +314,10 @@ int main(int argc, char **argv) {
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_pssh << std::endl << std::endl;
g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh);
config.set_license_server(g_license_server);
config.set_port(g_port);
config.set_key_id(g_key_id_pssh);
// Extract the key ID from the PSSH box.

View File

@@ -3,14 +3,23 @@
#include "config_test_env.h"
namespace {
const std::string kWidevineKeySystem = "com.widevine.alpha";
// Youtube Content Protection license server data
const std::string kYtCpLicenseServer =
"http://wv-ref-eme-player.appspot.com/proxy";
const std::string kYtCpClientAuth = "";
const std::string kYtCpKeyId =
"000000347073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
"0801121030313233343536373839616263646566"; // pssh data
"000000427073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id
"08011a0d7769646576696e655f7465737422" // pssh data (streaming)
"0f73747265616d696e675f636c697031";
const std::string kYtCpOfflineKeyId =
"000000407073736800000000" // blob size and pssh
"EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id
"08011a0d7769646576696e655f7465737422" //pssh data (offline)
"0d6f66666c696e655f636c697031";
// Youtube license server data
const std::string kYtLicenseServer =
@@ -34,6 +43,13 @@ const std::string kGpLicenseServer =
const std::string kGpClientAuth =
"?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine";
const std::string kGpClientOfflineQueryParameters =
"&offline=true";
const std::string kGpClientOfflineRenewalQueryParameters =
"&offline=true&renewal=true";
const std::string kGpClientOfflineReleaseQueryParameters =
"&offline=true&release=true";
const std::string kGpKeyId =
"000000347073736800000000" // blob size and pssh
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
@@ -63,23 +79,49 @@ const std::string kServerSdkLicenseServer =
const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = {
{ wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId,
kDefaultHttpsPort, true, true },
kGpKeyId },
{ wvcdm::kYouTubeContentProtectionServer, kYtCpLicenseServer,
kYtCpClientAuth, kYtCpKeyId, kDefaultHttpPort, false, false }
kYtCpClientAuth, kYtCpKeyId, kYtCpOfflineKeyId }
};
} // 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),
use_chunked_transfer_(license_servers[server_id].use_chunked_transfer),
use_secure_transfer_(license_servers[server_id].use_secure_transfer),
wrong_key_id_(kWrongKeyId) {}
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id) {
Init(server_id);
}
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming) {
Init(server_id);
if (!streaming)
key_id_ = license_servers[server_id].offline_key_id;
}
ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming,
bool renew, bool release) {
Init(server_id);
if (!streaming) {
key_id_ = license_servers[server_id].offline_key_id;
if (wvcdm::kGooglePlayServer == server_id) {
if (renew) {
client_auth_.append(kGpClientOfflineRenewalQueryParameters);
} else if (release) {
client_auth_.append(kGpClientOfflineReleaseQueryParameters);
} else {
client_auth_.append(kGpClientOfflineQueryParameters);
}
}
}
}
void ConfigTestEnv::Init(LicenseServerId server_id) {
client_auth_ = license_servers[server_id].client_tag;
key_id_ = license_servers[server_id].key_id;
key_system_ = kWidevineKeySystem;
license_server_ = license_servers[server_id].url;
provisioning_server_url_ = kProductionProvisioningServerUrl;
wrong_key_id_= kWrongKeyId;
}
} // namespace wvcdm

View File

@@ -6,11 +6,6 @@
#include <string>
#include "wv_cdm_types.h"
namespace {
const std::string kDefaultHttpsPort = "443";
const std::string kDefaultHttpPort = "80";
}
namespace wvcdm {
typedef enum {
kGooglePlayServer,
@@ -25,24 +20,22 @@ class ConfigTestEnv {
std::string url;
std::string client_tag;
std::string key_id;
std::string port;
bool use_chunked_transfer;
bool use_secure_transfer;
std::string offline_key_id;
} LicenseServerConfiguration;
explicit ConfigTestEnv(LicenseServerId server_id);
ConfigTestEnv(LicenseServerId server_id, bool streaming);
ConfigTestEnv(LicenseServerId server_id, bool streaming, bool renew,
bool release);
~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_;
}
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); }
@@ -52,17 +45,15 @@ class ConfigTestEnv {
void set_license_server(std::string& license_server) {
license_server_.assign(license_server);
}
void set_port(std::string& port) { port_.assign(port); }
private:
void Init(LicenseServerId server_id);
std::string client_auth_;
KeyId key_id_;
CdmKeySystem key_system_;
std::string license_server_;
std::string port_;
std::string provisioning_server_url_;
bool use_chunked_transfer_;
bool use_secure_transfer_;
KeyId wrong_key_id_;
CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv);

View File

@@ -26,7 +26,11 @@ using ::testing::StrEq;
namespace {
const uint32_t kCertificateLen = 700;
const uint32_t kWrappedKeyLen = 500;
const uint32_t kProtobufEstimatedLen = 75;
const uint32_t kProtobufEstimatedOverhead = 75;
const uint32_t kLicenseRequestLen = 300;
const uint32_t kLicenseLen = 500;
const uint32_t kProviderSessionTokenLen = 128;
// Structurally valid test certificate.
// The data elements in this module are used to test the storage and
@@ -901,8 +905,7 @@ LicenseInfo license_update_test_data[] = {
"D30B08B2C4673551293A2E68747470733A2F2F746573742E676F6F676C65"
"2E636F6D2F6C6963656E73652F47657443656E634C6963656E736512200A"
"1C78D0E574D0827C3AE78A05EEC90BAC31D10686EC19EB0599F75B2D1AB4"
"C5"
)},
"C5")},
// license being released. all fields are identical except for license
// state and hashed file data
{"", DeviceFiles::kLicenseStateReleasing, "", "", "", "", "", "",
@@ -1001,6 +1004,287 @@ LicenseInfo license_update_test_data[] = {
"7186A244EF561E3B07DC459BC681A0798B180667EA448327F6BBBD30212A"
"49")}};
struct UsageInfo {
std::string provider_session_token;
std::string license_request;
std::string license;
std::string file_data;
};
UsageInfo kUsageInfoTestData[] = {
{"", "", "", // 0 usage info records
wvcdm::a2bs_hex(
"0A06080210012A00122095053501C5FA405B7EF01DA94685C6B20CB36493"
"A9CF1653B720E2BEA3B77929")},
{// 1 usage info record
wvcdm::a2bs_hex(
"924B035FBDA56AE5EF0ED05A08DE7AECC8ABE1835E0C4A548F7803937F4C3B4520EB7"
"F3334FFCDFA00DE56408F09D5019FCE87072D0DC6789817468974B2EA51EE3944B8D7"
"E0A88E4F16EBB80F03BD845231A01E6146841CBAEF0134DCD9300DB2D92732992C0F2"
"310D8E386FB31C67B9477010DEF9D99C4272589572A26A17E"),
wvcdm::a2bs_hex(
"1E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315DB32B6D3FDD1A8E8A09"
"4174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E154BC1548FC40EC70927"
"75531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6BE37645E6800F053B1D"
"A9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D344A419C0F0034A1B5"
"F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410F218E52A853AD214FD"
"05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3BCBAE42DF9EA65E42E"
"151827086EADE71C138B972CC3992CF9ADA944C063816352ED8658D3FA07BE0F32239"
"E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E510445294F44E511BD9B1A"
"F19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E"),
wvcdm::a2bs_hex(
"40FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C029F"
"D2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B037"
"72BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA991C8"
"BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C592436C8"
"B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2EFC6"
"780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A660"
"2B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F4C3"
"5FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24B06"
"53A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6374"
"D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6FB1A"
"C91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB8904A5"
"999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267ACA2"
"E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82989"
"C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A771"
"F561B165987E552824B0C914E708E425C3"),
wvcdm::a2bs_hex(
"0AB307080210012AAC070AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE"
"1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D"
"0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841"
"CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725"
"89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D"
"B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15"
"4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B"
"E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D"
"344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410"
"F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3"
"BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8"
"658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104"
"45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF"
"40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0"
"29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B"
"03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99"
"1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243"
"6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E"
"FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A"
"6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F"
"4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24"
"B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6"
"374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F"
"B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890"
"4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A"
"CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82"
"989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A"
"771F561B165987E552824B0C914E708E425C3122051C8F84C5713500997DC5B325BAE"
"D208B224DFAEB2B034E58046A62F503FED6E")},
{// 2 usage info records
wvcdm::a2bs_hex(
"7290396E183156BDF830B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F8"
"7795EE0B3DA0B425616A66C82349B2E3BB8841C1335536865F919ED2AE671487B608B"
"21A362D888E0AB4F7AB7175B82F108617C3503F175435788AECAF7FFBFE76995D93CD"
"79424A843A247A8D8A6054A5B5404C9C057AACAD91A203229"),
wvcdm::a2bs_hex(
"3478A2D76DEB90BE713B03A11037EA7C305D1AF65099E3F2B92C4D4443A8F481C1177"
"DEF0A3CB49BA5F1448A10AF1207AD2D361B4A1F961B4B1F215B76A9A5005B414EF45E"
"AFBCF2636ABFC01413B27DD11871103579F8C041A799E22888D9ADB798E92A5E29BC4"
"6DECBC90991C65FE151C49F18068C1B65D0E90A9ECDA9248B87C120D5FD8EC81D4D36"
"B529FB2DAD39E0D39578B13B158E2B07C752D86F1A9D8160C93930C1F4F9E1D0D8E2C"
"5AB308732EB27722A6BF8BE852624C2BE3E4FE85819B89BEBA6535FCFBE85FA63A57B"
"D0FBAF284C64FFD97A146B76B3F37B576FC091C03E2222FBD24C2211344B7E2417EFC"
"36C4A54DCCC460CF810E7EA8AC6386D6AB567C819FED88A22CE55EF9BBE62C2CBC7AE"
"EDE5E5A69FF3472418CE2F4514496C59D26E72F3BFE0131F"),
wvcdm::a2bs_hex(
"C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B858330669A81E8131583D2F1"
"40FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F87ADB2A7FC3CF6FF87A7F"
"02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BFE4E25EE821DF7D742B09"
"90398543B16EFCDBB03C6327B79D3664CED442E894020F4410ECC178C92AAEDFE39DC"
"563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27A3B65DE3963D0A5F6E44"
"2A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA090282680ABDD649BECA8970"
"0764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F39F453614C8FFB5A17975"
"6243CB1FDB515834229BC64917C47A2F2E1116FAAC13368015312C31FD41215106469"
"BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949C1833BE52E76602CC3E4"
"E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACADEC140C00C8FA8FC9886"
"2D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77F048ABBC85CB19469638"
"C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882054141086997A1AE5B70"
"9D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97FDFFA9B671E5A65AFCC1"
"C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A5AC9605B3534712A0912"
"4345ACB09665E357E58946871BC140D365"),
wvcdm::a2bs_hex(
"0ADF0E080210012AD80E0AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE"
"1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D"
"0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841"
"CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725"
"89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D"
"B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15"
"4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B"
"E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D"
"344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410"
"F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3"
"BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8"
"658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104"
"45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF"
"40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0"
"29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B"
"03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99"
"1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243"
"6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E"
"FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A"
"6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F"
"4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24"
"B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6"
"374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F"
"B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890"
"4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A"
"CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82"
"989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A"
"771F561B165987E552824B0C914E708E425C30AA9070A80017290396E183156BDF830"
"B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F87795EE0B3DA0B425616A"
"66C82349B2E3BB8841C1335536865F919ED2AE671487B608B21A362D888E0AB4F7AB7"
"175B82F108617C3503F175435788AECAF7FFBFE76995D93CD79424A843A247A8D8A60"
"54A5B5404C9C057AACAD91A20322912AC023478A2D76DEB90BE713B03A11037EA7C30"
"5D1AF65099E3F2B92C4D4443A8F481C1177DEF0A3CB49BA5F1448A10AF1207AD2D361"
"B4A1F961B4B1F215B76A9A5005B414EF45EAFBCF2636ABFC01413B27DD11871103579"
"F8C041A799E22888D9ADB798E92A5E29BC46DECBC90991C65FE151C49F18068C1B65D"
"0E90A9ECDA9248B87C120D5FD8EC81D4D36B529FB2DAD39E0D39578B13B158E2B07C7"
"52D86F1A9D8160C93930C1F4F9E1D0D8E2C5AB308732EB27722A6BF8BE852624C2BE3"
"E4FE85819B89BEBA6535FCFBE85FA63A57BD0FBAF284C64FFD97A146B76B3F37B576F"
"C091C03E2222FBD24C2211344B7E2417EFC36C4A54DCCC460CF810E7EA8AC6386D6AB"
"567C819FED88A22CE55EF9BBE62C2CBC7AEEDE5E5A69FF3472418CE2F4514496C59D2"
"6E72F3BFE0131F1AF403C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B8583"
"30669A81E8131583D2F140FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F8"
"7ADB2A7FC3CF6FF87A7F02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BF"
"E4E25EE821DF7D742B0990398543B16EFCDBB03C6327B79D3664CED442E894020F441"
"0ECC178C92AAEDFE39DC563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27"
"A3B65DE3963D0A5F6E442A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA0902"
"82680ABDD649BECA89700764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F3"
"9F453614C8FFB5A179756243CB1FDB515834229BC64917C47A2F2E1116FAAC1336801"
"5312C31FD41215106469BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949"
"C1833BE52E76602CC3E4E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACA"
"DEC140C00C8FA8FC98862D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77"
"F048ABBC85CB19469638C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882"
"054141086997A1AE5B709D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97"
"FDFFA9B671E5A65AFCC1C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A"
"5AC9605B3534712A09124345ACB09665E357E58946871BC140D3651220464E4A1BB23"
"1A5B0287888B34CA0A8CF5396EB2B8313377DC5ED5C41A9B389A9")},
{// 3 usage info records
wvcdm::a2bs_hex(
"983358221FB8DBF892047F00AA661F217EEC4E7A1626E8F98E025509E4D65A685E7D9"
"B169B98B16934F6E43E0E0E854A3FA9EB8E9A9D08E9D9B3A6C766AA44F7C655879BA2"
"DF5F38732FB7EDCA66D8C13A855B15E32CC9389B7DD119BA1F2417825FF1F52970F8E"
"985D34DD353D2AC8B24267353E5B8406C098427C4559A90CC"),
wvcdm::a2bs_hex(
"483EAC68243092009D06FAB41DB594ACB22E068C9524810758ECFF8BAB7E1B1ACA988"
"C3987023F01EFEC11529C7326279742E805E755A08EBBD9AA322F305805BE1166AB45"
"CB156FB0A9E6734371F4028707EE01CF2FB08465707E7E5613DD90D74B0D02536E26C"
"F1261CDDA8713943F3620ECC54095C76F8CD3CE31948C3CC0C9EB5582A4D087A54B39"
"1B4CDCBC98E35830B5932F6CF8D16427EF115CFF0A99499513702DD54C758E53248BB"
"5D195F2A2DD1DB18F97562F1F9034E223CEDB1E09ED1B0FE26089C20ED43B5D87B51F"
"6FC6C9F86255FBF70DF233F2665D604355BF9740A3B755521102E0B485C5CCCA607A9"
"A1BEB757BEDEF12327C637D17D6401E3756719F99BBE69B9CE4C8E47C2AC771F35A8E"
"E3FC4D58B2B2269CF85728E4DA7231BC8F0FD7C50E2A1EE9"),
wvcdm::a2bs_hex(
"5826D3A95F78879292612BCE06D845D64285CD45A7EAA6C87A9DBC3290B0B6AC95315"
"809F8CC7938768F9BD342C62CD4CE055866394489D955247CB0535001D50EFF4FEDF0"
"9501C58569B1EB9AA2305A113A5F4D4524AD34148A2DC48D2F522937F44A57FC76F57"
"EB1D4819C438EA42C7F8974FC7D2FE61CAAB3E1F27172FE6B8675DF4CCF1329A6EFB3"
"1F686FB0DC0F8B552D78970708D50C82ADBE333B585F6DE5A0D01D106F8232EB9ED45"
"42A2DC5AA031CC44652E8A42EDCA5AB08B0B5CA61A922E69A119E556F6014642522EA"
"1550F6D6E63EB25ACC03A4DD3F22F4686ED525F994FABA87629AF5939C16BA68C0F09"
"3EFE033CD319180BF69FCB72AC5123EBCB9DCF1AF00F0A68E31FF5B18FA8CFF3DFBB7"
"DA45413799105D67FA78217710D2F6C33394DD4088100013295FF43CF0598E6FE5C05"
"F03417CCD031F01CF63BECD444C750DF198345F155AB2B2AB94394A3C0C0AE05E386D"
"E6CC565AE82398BD0E377D6ABE103B9D5E84582C3772584B759891FC4B121A113370E"
"2DF5372DD81FB6358C64B0F6EB8F26193CA119E4D9D3D38036FA450EE2047CB2CE265"
"0FF37DF85BE23D58C17379FEC08DC0648236A107AE66178EEBF78F05F3B898424FA02"
"668B51F838AFA90D367B5CB425372D8CC3790BEA8AFB8795251FA09340D85A7F0B003"
"134C838F08BB1054D18404C3F69130700E"),
wvcdm::a2bs_hex(
"0A8B16080210012A84160AA9070A8001924B035FBDA56AE5EF0ED05A08DE7AECC8ABE"
"1835E0C4A548F7803937F4C3B4520EB7F3334FFCDFA00DE56408F09D5019FCE87072D"
"0DC6789817468974B2EA51EE3944B8D7E0A88E4F16EBB80F03BD845231A01E6146841"
"CBAEF0134DCD9300DB2D92732992C0F2310D8E386FB31C67B9477010DEF9D99C42725"
"89572A26A17E12AC021E6FFBE66FC6153E7749906EC8F684E819467E16CAF317F315D"
"B32B6D3FDD1A8E8A094174D92D063B88E4835EAB78BD09541EA7FE72F132EB7364E15"
"4BC1548FC40EC7092775531508C95F9ED5D76F36BC0C198C3A33A1F9415B343905D6B"
"E37645E6800F053B1DA9A20286EFCBBC320424ADF7FB6E3D5D8E86C35E576A1A2A37D"
"344A419C0F0034A1B5F767D3C61D90DCA1119E5024C34EDE8FA7DD128696D8C435410"
"F218E52A853AD214FD05D0F8B3CB4832CFCD97FE159E6DEE64CE82CDAEC0321AE71B3"
"BCBAE42DF9EA65E42E151827086EADE71C138B972CC3992CF9ADA944C063816352ED8"
"658D3FA07BE0F32239E74A65932B069AAC4E8386DB59154AF9AEF71448128C66E5104"
"45294F44E511BD9B1AF19D4D67E99363093BE888D4B2AB841CAFF252CAD13EDF8E1AF"
"40340FC62339728520E6C0C09907C26F3FB78287231661952A8B699E47AE241B999C0"
"29FD2067836DC4BC64F66998A3ECD197DAE36F808A2E5A4C5BF25DD580E52B1C39A8B"
"03772BF82D58929766F2DA04F0E616F92B3A0EB75661B8FF5DE1EB807C990F9E6BA99"
"1C8BAD5EB63B37E8663A4E22AA9DB2015D3DF8BED1C8313F85F13B9483C7A39C59243"
"6C8B13C23F78F55CE812795335059F7F527CA580306A0AEE5A6D957A91F498F64AA2E"
"FC6780716400E17C7EEA30B2E6523B902986995E003C2D919A7DC7C0122CE9410037A"
"6602B59A63B5C89473D4E02DE35C1F01B12ADB48A3D94D43693F08268FECCC78DAF6F"
"4C35FA32C538CD73FBF3CEA274B01179C02473486311956E5A0C78E44C59B2F34FF24"
"B0653A6379A2F5F6F51467CAE26D55CC5BBDCFC9BCFA7B8C5CBF82EBE7BD340C3DAE6"
"374D0692052C529AA33D7A6799C8F1F59C78575E51F707013026CC4F83F6B3328EE6F"
"B1AC91929A4491338E93D10EE6193014A73BA241A9A833EA835217894EB4FD4BDB890"
"4A5999928325D0AC31B6D58609EDD9D85E88F74B5BD6FA7BDD83C51EEB91633ED267A"
"CA2E103904BBE4C031A6483858FBAD74DACD01711F7B882749FFFBA0DB6C7D7109D82"
"989C7D4DB5A0F1E7506AC24C89CECAF231EFF99F96AD76E57DABDD3C2DFBA7BAA869A"
"771F561B165987E552824B0C914E708E425C30AA9070A80017290396E183156BDF830"
"B7BF31BA762CB2675528C9004FD24A61DAFB587ABCF1D36F87795EE0B3DA0B425616A"
"66C82349B2E3BB8841C1335536865F919ED2AE671487B608B21A362D888E0AB4F7AB7"
"175B82F108617C3503F175435788AECAF7FFBFE76995D93CD79424A843A247A8D8A60"
"54A5B5404C9C057AACAD91A20322912AC023478A2D76DEB90BE713B03A11037EA7C30"
"5D1AF65099E3F2B92C4D4443A8F481C1177DEF0A3CB49BA5F1448A10AF1207AD2D361"
"B4A1F961B4B1F215B76A9A5005B414EF45EAFBCF2636ABFC01413B27DD11871103579"
"F8C041A799E22888D9ADB798E92A5E29BC46DECBC90991C65FE151C49F18068C1B65D"
"0E90A9ECDA9248B87C120D5FD8EC81D4D36B529FB2DAD39E0D39578B13B158E2B07C7"
"52D86F1A9D8160C93930C1F4F9E1D0D8E2C5AB308732EB27722A6BF8BE852624C2BE3"
"E4FE85819B89BEBA6535FCFBE85FA63A57BD0FBAF284C64FFD97A146B76B3F37B576F"
"C091C03E2222FBD24C2211344B7E2417EFC36C4A54DCCC460CF810E7EA8AC6386D6AB"
"567C819FED88A22CE55EF9BBE62C2CBC7AEEDE5E5A69FF3472418CE2F4514496C59D2"
"6E72F3BFE0131F1AF403C45FDCB3296A0EBE24FF381E027E6E2EF1AC289C67D3B8583"
"30669A81E8131583D2F140FD64615BDED0ED8316ABFD9C7E887433E1CAA6EA8E0C4F8"
"7ADB2A7FC3CF6FF87A7F02AFF03BF5DB640AD8DDB572C41532E673618DCD8C33EF2BF"
"E4E25EE821DF7D742B0990398543B16EFCDBB03C6327B79D3664CED442E894020F441"
"0ECC178C92AAEDFE39DC563AC226FE9E0EF22E1C896C4F2835CDFDCD50B6C4DBA2B27"
"A3B65DE3963D0A5F6E442A3C32008AB9D1ACBE4F366990EB43F8EE213B71E98DA0902"
"82680ABDD649BECA89700764561379F1DD23490CE967632ECA349AF8E1CBFA1F3A4F3"
"9F453614C8FFB5A179756243CB1FDB515834229BC64917C47A2F2E1116FAAC1336801"
"5312C31FD41215106469BEE77D0EF2FE10CF645B3E82902EAF53A676933D0EC433949"
"C1833BE52E76602CC3E4E784C002E20624BCE0F38F9CBC478439899DA7F15554D0ACA"
"DEC140C00C8FA8FC98862D9933938781B30CB9C76899B3A48DBF170DDA0A18ED37D77"
"F048ABBC85CB19469638C2A32AA3180CF3943BD6B8C5CB26F2EA70868F18B0707C882"
"054141086997A1AE5B709D4D0AA2B358990F244BA76C8E40791D29A0C63C9EF620B97"
"FDFFA9B671E5A65AFCC1C94CAACE0443E9D91F14028935BEA3988831BEBBFD3EB7C3A"
"5AC9605B3534712A09124345ACB09665E357E58946871BC140D3650AA9070A8001983"
"358221FB8DBF892047F00AA661F217EEC4E7A1626E8F98E025509E4D65A685E7D9B16"
"9B98B16934F6E43E0E0E854A3FA9EB8E9A9D08E9D9B3A6C766AA44F7C655879BA2DF5"
"F38732FB7EDCA66D8C13A855B15E32CC9389B7DD119BA1F2417825FF1F52970F8E985"
"D34DD353D2AC8B24267353E5B8406C098427C4559A90CC12AC02483EAC68243092009"
"D06FAB41DB594ACB22E068C9524810758ECFF8BAB7E1B1ACA988C3987023F01EFEC11"
"529C7326279742E805E755A08EBBD9AA322F305805BE1166AB45CB156FB0A9E673437"
"1F4028707EE01CF2FB08465707E7E5613DD90D74B0D02536E26CF1261CDDA8713943F"
"3620ECC54095C76F8CD3CE31948C3CC0C9EB5582A4D087A54B391B4CDCBC98E35830B"
"5932F6CF8D16427EF115CFF0A99499513702DD54C758E53248BB5D195F2A2DD1DB18F"
"97562F1F9034E223CEDB1E09ED1B0FE26089C20ED43B5D87B51F6FC6C9F86255FBF70"
"DF233F2665D604355BF9740A3B755521102E0B485C5CCCA607A9A1BEB757BEDEF1232"
"7C637D17D6401E3756719F99BBE69B9CE4C8E47C2AC771F35A8EE3FC4D58B2B2269CF"
"85728E4DA7231BC8F0FD7C50E2A1EE91AF4035826D3A95F78879292612BCE06D845D6"
"4285CD45A7EAA6C87A9DBC3290B0B6AC95315809F8CC7938768F9BD342C62CD4CE055"
"866394489D955247CB0535001D50EFF4FEDF09501C58569B1EB9AA2305A113A5F4D45"
"24AD34148A2DC48D2F522937F44A57FC76F57EB1D4819C438EA42C7F8974FC7D2FE61"
"CAAB3E1F27172FE6B8675DF4CCF1329A6EFB31F686FB0DC0F8B552D78970708D50C82"
"ADBE333B585F6DE5A0D01D106F8232EB9ED4542A2DC5AA031CC44652E8A42EDCA5AB0"
"8B0B5CA61A922E69A119E556F6014642522EA1550F6D6E63EB25ACC03A4DD3F22F468"
"6ED525F994FABA87629AF5939C16BA68C0F093EFE033CD319180BF69FCB72AC5123EB"
"CB9DCF1AF00F0A68E31FF5B18FA8CFF3DFBB7DA45413799105D67FA78217710D2F6C3"
"3394DD4088100013295FF43CF0598E6FE5C05F03417CCD031F01CF63BECD444C750DF"
"198345F155AB2B2AB94394A3C0C0AE05E386DE6CC565AE82398BD0E377D6ABE103B9D"
"5E84582C3772584B759891FC4B121A113370E2DF5372DD81FB6358C64B0F6EB8F2619"
"3CA119E4D9D3D38036FA450EE2047CB2CE2650FF37DF85BE23D58C17379FEC08DC064"
"8236A107AE66178EEBF78F05F3B898424FA02668B51F838AFA90D367B5CB425372D8C"
"C3790BEA8AFB8795251FA09340D85A7F0B003134C838F08BB1054D18404C3F6913070"
"0E12202FF1FBA9926A24A1F79970EC427DDF87B4421488F7952499BC33CEB282D9E48"
"A")}};
} // namespace
class MockFile : public File {
@@ -1053,6 +1337,9 @@ class DeviceFilesSecurityLevelTest
: public DeviceFilesTest,
public ::testing::WithParamInterface<CdmSecurityLevel> {};
class DeviceFilesUsageInfoTest : public DeviceFilesTest,
public ::testing::WithParamInterface<int> {};
MATCHER(IsCreateFileFlagSet, "") { return File::kCreate & arg; }
MATCHER(IsBinaryFileFlagSet, "") { return File::kBinary & arg; }
MATCHER_P(IsStrEq, str, "") {
@@ -1060,18 +1347,29 @@ MATCHER_P(IsStrEq, str, "") {
// as well as pointer to data but that will introduce a dependency on tr1
return memcmp(arg, str.c_str(), str.size()) == 0;
}
MATCHER_P2(Contains, str1, str2, "") {
MATCHER_P3(Contains, str1, str2, size, "") {
// Estimating the length of data. We can have gmock provide length
// as well as pointer to data but that will introduce a dependency on tr1
std::string data(arg, str1.size() + str2.size() + kProtobufEstimatedLen);
std::string data(
arg, size + str1.size() + str2.size() + kProtobufEstimatedOverhead);
return (data.find(str1) != std::string::npos &&
data.find(str2) != std::string::npos);
}
MATCHER_P4(Contains, str1, str2, str3, size, "") {
// Estimating the length of data. We can have gmock provide length
// as well as pointer to data but that will introduce a dependency on tr1
std::string data(arg, size + str1.size() + str2.size() + str3.size() +
kProtobufEstimatedOverhead);
return (data.find(str1) != std::string::npos &&
data.find(str2) != std::string::npos &&
data.find(str3) != std::string::npos);
}
MATCHER_P6(Contains, str1, str2, str3, str4, str5, str6, "") {
// Estimating the length of data. We can have gmock provide length
// as well as pointer to data but that will introduce a dependency on tr1
std::string data(arg, str1.size() + str2.size() + str3.size() + str4.size() +
str5.size() + str6.size() + kProtobufEstimatedLen);
str5.size() + str6.size() +
kProtobufEstimatedOverhead);
return (data.find(str1) != std::string::npos &&
data.find(str2) != std::string::npos &&
data.find(str3) != std::string::npos &&
@@ -1100,14 +1398,15 @@ TEST_P(DeviceFilesStoreTest, StoreCertificate) {
EXPECT_CALL(file, Open(StrEq(device_certificate_path),
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
.WillOnce(Return(true));
EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key),
EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0),
Gt(certificate.size() + wrapped_private_key.size())))
.WillOnce(ReturnArg<1>());
EXPECT_CALL(file, Close()).Times(1);
EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key));
}
@@ -1132,7 +1431,8 @@ TEST_F(DeviceFilesTest, ReadCertificate) {
EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
std::string certificate, wrapped_private_key;
ASSERT_TRUE(
@@ -1161,14 +1461,15 @@ TEST_P(DeviceFilesSecurityLevelTest, SecurityLevel) {
EXPECT_CALL(file, Open(StrEq(device_certificate_path),
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
.WillOnce(Return(true));
EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key),
EXPECT_CALL(file, Write(Contains(certificate, wrapped_private_key, 0),
Gt(certificate.size() + wrapped_private_key.size())))
.WillOnce(ReturnArg<1>());
EXPECT_CALL(file, Close()).Times(1);
EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, security_level));
EXPECT_TRUE(device_files.Init(security_level));
device_files.SetTestFile(&file);
EXPECT_TRUE(device_files.StoreCertificate(certificate, wrapped_private_key));
}
@@ -1208,7 +1509,8 @@ TEST_P(DeviceFilesStoreTest, StoreLicense) {
EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
EXPECT_TRUE(device_files.StoreLicense(
license_test_data[license_num].key_set_id,
license_test_data[license_num].license_state,
@@ -1226,7 +1528,8 @@ INSTANTIATE_TEST_CASE_P(StoreLicense, DeviceFilesStoreTest,
TEST_F(DeviceFilesTest, StoreLicenses) {
MockFile file;
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
.Times(kNumberOfLicenses).WillRepeatedly(Return(true));
.Times(kNumberOfLicenses)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(_)).Times(0);
for (size_t i = 0; i < kNumberOfLicenses; ++i) {
@@ -1251,7 +1554,8 @@ TEST_F(DeviceFilesTest, StoreLicenses) {
EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
for (size_t i = 0; i < kNumberOfLicenses; i++) {
EXPECT_TRUE(device_files.StoreLicense(
license_test_data[i].key_set_id, license_test_data[i].license_state,
@@ -1286,7 +1590,8 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) {
EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
DeviceFiles::LicenseState license_state;
CdmInitData pssh_data;
CdmKeyMessage key_request;
@@ -1338,12 +1643,11 @@ TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) {
std::string old_path = base_path + DeviceFiles::GetCertificateFileName();
old_files.push_back(DeviceFiles::GetCertificateFileName());
EXPECT_CALL(file, IsRegularFile(StrEq(old_path)))
.WillOnce(Return(true));
EXPECT_CALL(file, IsRegularFile(StrEq(old_path))).WillOnce(Return(true));
EXPECT_CALL(file, Remove(StrEq(old_path))).WillOnce(Return(true));
for (size_t i = 0; i < security_dirs.size(); ++i) {
new_path = base_path + security_dirs[i] +
DeviceFiles::GetCertificateFileName();
new_path =
base_path + security_dirs[i] + DeviceFiles::GetCertificateFileName();
EXPECT_CALL(file, Copy(StrEq(old_path), StrEq(new_path)))
.WillOnce(Return(true));
}
@@ -1377,7 +1681,8 @@ TEST_F(DeviceFilesTest, SecurityLevelPathBackwardCompatibility) {
EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
Properties::Init();
std::string certificate, wrapped_private_key;
@@ -1391,12 +1696,14 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) {
license_update_test_data[0].key_set_id +
DeviceFiles::GetLicenseFileNameExtension();
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_))).Times(2)
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(_)).Times(0);
EXPECT_CALL(file, Open(StrEq(license_path),
AllOf(IsCreateFileFlagSet(), IsBinaryFileFlagSet())))
.Times(2).WillRepeatedly(Return(true));
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, Write(IsStrEq(license_update_test_data[0].file_data),
Eq(license_update_test_data[0].file_data.size())))
.WillOnce(ReturnArg<1>());
@@ -1407,7 +1714,8 @@ TEST_F(DeviceFilesTest, UpdateLicenseState) {
EXPECT_CALL(file, Read(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
EXPECT_TRUE(device_files.StoreLicense(
license_update_test_data[0].key_set_id,
license_update_test_data[0].license_state,
@@ -1437,7 +1745,9 @@ TEST_F(DeviceFilesTest, DeleteLicense) {
size_t size = license_test_data[0].file_data.size();
EXPECT_CALL(file, Exists(StrEq(license_path))).Times(2).WillOnce(Return(true))
EXPECT_CALL(file, Exists(StrEq(license_path)))
.Times(2)
.WillOnce(Return(true))
.WillOnce(Return(false));
EXPECT_CALL(file, FileSize(StrEq(license_path))).WillOnce(Return(size));
EXPECT_CALL(file, Open(StrEq(license_path), IsBinaryFileFlagSet()))
@@ -1451,7 +1761,8 @@ TEST_F(DeviceFilesTest, DeleteLicense) {
EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(&file, kSecurityLevelL1));
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
DeviceFiles::LicenseState license_state;
CdmInitData pssh_data;
CdmKeyMessage key_request;
@@ -1475,4 +1786,154 @@ TEST_F(DeviceFilesTest, DeleteLicense) {
EXPECT_FALSE(device_files.LicenseExists(license_test_data[0].key_set_id));
}
TEST_P(DeviceFilesUsageInfoTest, Read) {
MockFile file;
std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName();
int index = GetParam();
std::string data;
if (index >= 0) {
data = kUsageInfoTestData[index].file_data;
}
if (index >= 0) {
EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(true));
EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size()));
EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet()))
.WillOnce(Return(true));
EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll(
SetArrayArgument<0>(data.begin(), data.end()), Return(data.size())));
EXPECT_CALL(file, Close()).Times(1);
} else {
EXPECT_CALL(file, Exists(StrEq(path))).Times(2).WillRepeatedly(
Return(false));
EXPECT_CALL(file, FileSize(_)).Times(0);
EXPECT_CALL(file, Open(_, _)).Times(0);
EXPECT_CALL(file, Close()).Times(0);
}
EXPECT_CALL(file, Write(_, _)).Times(0);
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> > license_info;
ASSERT_TRUE(device_files.RetrieveUsageInfo(&license_info));
if (index >= 0) {
EXPECT_EQ(index, license_info.size());
for (size_t i = 0; i < license_info.size(); ++i) {
bool found = false;
for (size_t j = 0; j <= static_cast<size_t>(index); ++j) {
if ((license_info[i].first.compare(
kUsageInfoTestData[j].license_request) == 0) &&
(license_info[i].second.compare(kUsageInfoTestData[j].license) ==
0)) {
found = true;
}
}
EXPECT_TRUE(found);
}
} else {
EXPECT_EQ(0, license_info.size());
}
}
TEST_P(DeviceFilesUsageInfoTest, Store) {
MockFile file;
std::string pst(GenerateRandomData(kProviderSessionTokenLen));
std::string license_request(GenerateRandomData(kLicenseRequestLen));
std::string license(GenerateRandomData(kLicenseLen));
std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName();
int index = GetParam();
std::string data;
if (index >= 0) {
data = kUsageInfoTestData[index].file_data;
}
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
.WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(_)).Times(0);
EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(index >= 0));
if (index >= 0) {
EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size()));
EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet()))
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll(
SetArrayArgument<0>(data.begin(), data.end()), Return(data.size())));
EXPECT_CALL(file, Close()).Times(2);
} else {
EXPECT_CALL(file, FileSize(_)).Times(0);
EXPECT_CALL(file, Open(_, _)).Times(1).WillOnce(Return(true));
EXPECT_CALL(file, Close()).Times(1);
}
EXPECT_CALL(file, Write(Contains(pst, license_request, license, data.size()),
Gt(pst.size() + license_request.size() +
license.size()))).WillOnce(ReturnArg<1>());
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
ASSERT_TRUE(device_files.StoreUsageInfo(pst, license_request, license));
}
TEST_P(DeviceFilesUsageInfoTest, Delete) {
MockFile file;
std::string path = device_base_path_ + DeviceFiles::GetUsageInfoFileName();
int index = GetParam();
if (index < 0) return;
std::string data, pst, prev_data, prev_pst, prev_license;
if (index >= 0) {
data = kUsageInfoTestData[index].file_data;
if (index >= 1) {
pst = kUsageInfoTestData[index].provider_session_token;
prev_data = kUsageInfoTestData[index - 1].file_data;
prev_pst = kUsageInfoTestData[index - 1].provider_session_token;
prev_license = kUsageInfoTestData[index - 1].license;
}
}
EXPECT_CALL(file, IsDirectory(StrEq(device_base_path_)))
.WillRepeatedly(Return(true));
EXPECT_CALL(file, CreateDirectory(_)).Times(0);
EXPECT_CALL(file, Exists(StrEq(path))).WillOnce(Return(index >= 0));
EXPECT_CALL(file, FileSize(StrEq(path))).WillOnce(Return(data.size()));
if (index >= 1) {
EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet()))
.Times(2)
.WillRepeatedly(Return(true));
EXPECT_CALL(file, Write(Contains(prev_pst, prev_license, prev_data.size()),
Gt(prev_pst.size() + prev_license.size())))
.WillOnce(ReturnArg<1>());
EXPECT_CALL(file, Close()).Times(2);
} else {
EXPECT_CALL(file, Open(StrEq(path), IsBinaryFileFlagSet()))
.WillOnce(Return(true));
EXPECT_CALL(file, Write(_, _)).Times(0);
EXPECT_CALL(file, Close()).Times(1);
}
EXPECT_CALL(file, Read(NotNull(), Eq(data.size()))).WillOnce(DoAll(
SetArrayArgument<0>(data.begin(), data.end()), Return(data.size())));
DeviceFiles device_files;
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
device_files.SetTestFile(&file);
if (index >= 1) {
ASSERT_TRUE(device_files.DeleteUsageInfo(pst));
} else {
ASSERT_FALSE(device_files.DeleteUsageInfo(pst));
}
}
INSTANTIATE_TEST_CASE_P(UsageInfo, DeviceFilesUsageInfoTest,
::testing::Values(-1, 0, 1, 2, 3));
} // namespace wvcdm

View File

@@ -5,17 +5,35 @@
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <string.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/x509.h>
#include "log.h"
#include "openssl/bio.h"
#include "openssl/err.h"
#include "openssl/x509.h"
namespace wvcdm {
namespace {
SSL_CTX* HttpSocket::InitSslContext(void) {
// Helper function to tokenize a string. This makes it easier to avoid silly
// parsing bugs that creep in easily when each part of the string is parsed
// with its own piece of code.
bool Tokenize(const std::string& source, const std::string& delim,
const size_t offset, std::string* substring_output,
size_t* next_offset) {
size_t start_of_delim = source.find(delim, offset);
if (start_of_delim == std::string::npos) {
return false;
}
substring_output->assign(source, offset, start_of_delim - offset);
*next_offset = start_of_delim + delim.size();
return true;
}
SSL_CTX* InitSslContext() {
const SSL_METHOD* method;
SSL_CTX* ctx;
@@ -23,21 +41,18 @@ SSL_CTX* HttpSocket::InitSslContext(void) {
SSL_load_error_strings();
method = SSLv3_client_method();
ctx = SSL_CTX_new(method);
if (NULL == ctx) {
if (!ctx)
LOGE("failed to create SSL context");
}
return ctx;
}
void HttpSocket::ShowServerCertificate(const SSL* ssl) {
X509* cert;
char* line;
// unused, may be useful for debugging SSL-related issues.
void ShowServerCertificate(const SSL* ssl) {
// gets the server certificate
cert = SSL_get_peer_certificate(ssl);
if (cert != NULL) {
X509* cert = SSL_get_peer_certificate(ssl);
if (cert) {
char* line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
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);
@@ -49,183 +64,252 @@ void HttpSocket::ShowServerCertificate(const SSL* ssl) {
}
}
HttpSocket::HttpSocket()
: secure_connect_(true),
socket_fd_(-1),
// Wait for a socket to be ready for reading or writing.
// Establishing a connection counts as "ready for write".
// Returns false on select error or timeout.
// Returns true when the socket is ready.
bool SocketWait(int fd, bool for_read, int timeout_in_ms) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd, &fds);
struct timeval tv;
tv.tv_sec = timeout_in_ms / 1000;
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
fd_set *read_fds = NULL;
fd_set *write_fds = NULL;
if (for_read) {
read_fds = &fds;
} else {
write_fds = &fds;
}
int ret = select(fd + 1, read_fds, write_fds, NULL, &tv);
if (ret == 0) {
LOGE("socket timed out");
return false;
} else if (ret == -1) {
LOGE("select failed, errno = %d", errno);
return false;
}
// socket ready.
return true;
}
} // namespace
namespace wvcdm {
// Parses the URL and extracts all relevant information.
// static
bool HttpSocket::ParseUrl(const std::string& url,
std::string* scheme,
bool* secure_connect,
std::string* domain_name,
int* port,
std::string* path) {
size_t offset = 0;
if (!Tokenize(url, "://", offset, scheme, &offset)) {
LOGE("Invalid URL, scheme not found: %s", url.c_str());
return false;
}
// If the scheme is http or https, set secure_connect and port accordingly.
// Otherwise, consider the scheme unsupported and fail.
if (*scheme == "http") {
*secure_connect = false;
*port = 80;
} else if (*scheme == "https") {
*secure_connect = true;
*port = 443;
} else {
LOGE("Invalid URL, scheme not supported: %s", url.c_str());
return false;
}
if (!Tokenize(url, "/", offset, domain_name, &offset)) {
// The rest of the URL belongs to the domain name.
domain_name->assign(url, offset, std::string::npos);
// No explicit path after the domain name.
path->assign("/");
} else {
// The rest of the URL, including the preceding slash, belongs to the path.
path->assign(url, offset - 1, std::string::npos);
}
// The domain name may optionally contain a port which overrides the default.
std::string domain_name_without_port;
size_t port_offset;
if (Tokenize(*domain_name, ":", 0, &domain_name_without_port,
&port_offset)) {
*port = atoi(domain_name->c_str() + port_offset);
if (*port <= 0 || *port >= 65536) {
LOGE("Invalid URL, port not valid: %s", url.c_str());
return false;
}
domain_name->assign(domain_name_without_port);
}
return true;
}
HttpSocket::HttpSocket(const std::string& url)
: socket_fd_(-1),
ssl_(NULL),
ssl_ctx_(NULL),
timeout_enabled_(false) {
ssl_ctx_(NULL) {
valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_,
&resource_path_);
SSL_library_init();
}
HttpSocket::~HttpSocket() { CloseSocket(); }
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;
}
if (ssl_) {
SSL_free(ssl_);
ssl_ = NULL;
}
if (ssl_ctx_) {
SSL_CTX_free(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);
}
bool HttpSocket::Connect(int timeout_in_ms) {
if (!valid_url_) {
return false;
}
// 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_);
// get a socket
socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd_ < 0) {
LOGE("cannot open socket %d", errno);
LOGE("cannot open socket, errno = %d", errno);
return false;
}
int reuse = 1;
if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) ==
-1) {
// set the socket in non-blocking mode
int original_flags = fcntl(socket_fd_, F_GETFL, 0);
if (original_flags == -1) {
LOGE("fcntl error, errno = %d", errno);
CloseSocket();
return false;
}
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
LOGE("fcntl error, errno = %d", errno);
CloseSocket();
LOGE("setsockopt error %d", errno);
return false;
}
// lookup the server IP
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);
int ret = getaddrinfo(domain_name_.c_str(), NULL, &hints, &addr_info);
if (ret != 0) {
CloseSocket();
LOGE("getaddrinfo failed with %d", ret);
status = false;
LOGE("getaddrinfo failed, errno = %d", ret);
return false;
}
// set the port
struct sockaddr_in* addr_ipv4 = reinterpret_cast<struct sockaddr_in*>(
addr_info->ai_addr);
addr_ipv4->sin_port = htons(port_);
// connect to the server
ret = connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen);
freeaddrinfo(addr_info);
if (ret == 0) {
// connected right away.
} else {
if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) {
if (errno != EINPROGRESS) {
// failed right away.
LOGE("cannot connect to %s, errno = %d", domain_name_.c_str(), errno);
CloseSocket();
LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(),
errno);
status = false;
return false;
} else {
// in progress. block until timeout expired or connection established.
if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) {
LOGE("cannot connect to %s", domain_name_.c_str());
CloseSocket();
return false;
}
}
}
timeout_enabled_ = enable_timeout;
if (addr_info != NULL) {
freeaddrinfo(addr_info);
}
if (!status) return false;
// set up SSL if needed
if (secure_connect_) {
ssl_ctx_ = InitSslContext();
if (!ssl_ctx_) {
CloseSocket();
return false;
}
// secures connection
if (secure_connect_ && ssl_ctx_) {
ssl_ = SSL_new(ssl_ctx_);
if (!ssl_) {
LOGE("failed SSL_new");
CloseSocket();
return false;
}
BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE);
if (!a_bio) {
LOGE("BIO_new_socket error");
CloseSocket();
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;
}
do {
ret = SSL_connect(ssl_);
if (ret != 1) {
int ssl_err = SSL_get_error(ssl_, ret);
if (ssl_err != SSL_ERROR_WANT_READ &&
ssl_err != SSL_ERROR_WANT_WRITE) {
char buf[256];
LOGE("SSL_connect error: %s", ERR_error_string(ERR_get_error(), buf));
CloseSocket();
return false;
}
bool for_read = ssl_err == SSL_ERROR_WANT_READ;
if (!SocketWait(socket_fd_, for_read, timeout_in_ms)) {
LOGE("cannot connect to %s", domain_name_.c_str());
CloseSocket();
return false;
}
}
} while (ret != 1);
}
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
// Returns -1 for error, number of bytes read for success.
// The timeout here only applies to the span between packets of data, for the
// sake of simplicity.
int HttpSocket::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 (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) {
LOGE("unable to read from %s", domain_name_.c_str());
return -1;
}
int read;
if (secure_connect_)
read = SSL_read(ssl_, data, to_read);
else
@@ -236,27 +320,26 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
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
// The connection has been closed. No more data.
break;
} else {
LOGE("recv returned %d, error = %d", read, errno);
break;
LOGE("recv returned %d, errno = %d", read, errno);
return -1;
}
}
if (use_timeout) {
fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again
}
return total_read;
}
int HttpSocket::Write(const char* data, int len) {
// Returns -1 for error, number of bytes written for success.
// The timeout here only applies to the span between packets of data, for the
// sake of simplicity.
int HttpSocket::Write(const char* data, int len, int timeout_in_ms) {
int total_sent = 0;
int sent = 0;
int to_send = len;
while (to_send > 0) {
int sent;
if (secure_connect_)
sent = SSL_write(ssl_, data, to_send);
else
@@ -267,11 +350,17 @@ int HttpSocket::Write(const char* data, int len) {
data += sent;
total_sent += sent;
} else if (sent == 0) {
usleep(10); // retry later
// We filled up the pipe. Wait for room to write.
if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) {
LOGE("unable to write to %s", domain_name_.c_str());
return -1;
}
} else {
LOGE("send returned error %d", errno);
LOGE("send returned %d, errno = %d", sent, errno);
return -1;
}
}
return total_sent;
}

View File

@@ -4,43 +4,52 @@
#define CDM_TEST_HTTP_SOCKET_H_
#include <string>
#include "openssl/ssl.h"
#include "wv_cdm_types.h"
#include <gtest/gtest_prod.h>
#include <openssl/ssl.h>
#include "wv_cdm_types.h" // CORE_DISALLOW_COPY_AND_ASSIGN
namespace wvcdm {
// Provides basic Linux based TCP socket interface.
class HttpSocket {
public:
HttpSocket();
// A scheme (http:// or https://) is required for the URL.
explicit HttpSocket(const std::string& url);
~HttpSocket();
bool Connect(int timeout_in_ms);
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);
const std::string& scheme() const { return scheme_; }
bool secure_connect() const { return secure_connect_; }
const std::string& domain_name() const { return domain_name_; }
int port() const { return port_; }
const std::string& resource_path() const { return resource_path_; }
int Read(char* data, int len, int timeout_in_ms);
int Write(const char* data, int len);
int Write(const char* data, int len, int timeout_in_ms);
private:
void CloseSslContext(SSL_CTX* ctx) const {
if (ctx) SSL_CTX_free(ctx);
}
SSL_CTX* InitSslContext(void);
void ShowServerCertificate(const SSL* ssl);
static bool ParseUrl(const std::string& url,
std::string* scheme,
bool* secure_connect,
std::string* domain_name,
int* port,
std::string* path);
FRIEND_TEST(HttpSocketTest, ParseUrlTest);
std::string domain_name_;
std::string scheme_;
bool secure_connect_;
std::string domain_name_;
int port_;
std::string resource_path_;
bool valid_url_;
int socket_fd_;
SSL* ssl_;
SSL_CTX* ssl_ctx_;
bool timeout_enabled_;
CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket);
};

View File

@@ -3,6 +3,7 @@
#include <errno.h>
#include "gtest/gtest.h"
#include "http_socket.h"
#include "scoped_ptr.h"
#include "log.h"
#include "string_conversions.h"
#include "url_request.h"
@@ -10,10 +11,14 @@
namespace {
// Arbitrary URL for tests.
const std::string kHttpsTestServer("https://www.google.com");
const std::string kHttpTestServer("http://www.google.com");
// This URL and data are used by RoundTripTest, and can be overridden on the
// command line.
std::string gTestServer(kHttpsTestServer);
std::string gTestData("Hello");
// Arbitrary buffer size and timeout settings.
const int kHttpBufferSize = 4096;
char gBuffer[kHttpBufferSize];
const int kTimeout = 3000;
}
namespace wvcdm {
@@ -21,153 +26,179 @@ namespace wvcdm {
class HttpSocketTest : public testing::Test {
public:
HttpSocketTest() {}
~HttpSocketTest() { socket_.CloseSocket(); }
~HttpSocketTest() {}
protected:
bool Connect(const std::string& server_url, bool secure_connection) {
bool Connect(const std::string& server_url) {
socket_.reset(new HttpSocket(server_url));
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());
if (socket_->Connect(kTimeout)) {
LOGD("connected to %s", socket_->domain_name().c_str());
return true;
} else {
LOGE("failed to connect to %s", socket_.domain_name().c_str());
LOGE("failed to connect to %s", server_url.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(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(socket_->domain_name());
request.append("\r\n");
// Important! Otherwise, the HTTP 1.1 default behavior for a server is to
// keep the connection open for a subsequent request.
request.append("Connection: close\r\n");
request.append("User-Agent: httpSocketTest/1.0\r\n");
char buffer[32] = {0};
snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(data.size()));
request.append("Content-Length: ");
memset(gBuffer, 0, kHttpBufferSize);
snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast<int>(data.size()));
request.append(gBuffer);
request.append(buffer);
request.append("\r\n");
request.append("Content-Type: multipart/form-data\r\n");
// newline terminates header
// an extra newline terminates HTTP headers.
request.append("\r\n");
// append data
request.append(data);
socket_.Write(request.c_str(), request.size());
socket_->Write(request.c_str(), request.size(), kTimeout);
LOGD("request: %s", request.c_str());
return true;
}
bool GetResponse() {
int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000);
bool GetResponse(std::string* response) {
char buffer[kHttpBufferSize];
int bytes = socket_->Read(buffer, sizeof(buffer), kTimeout);
if (bytes < 0) {
LOGE("read error = ", errno);
LOGE("read error, errno = %d", 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;
}
LOGD("read %d bytes", bytes);
response->assign(buffer, bytes);
return true;
}
HttpSocket socket_;
scoped_ptr<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());
struct ParseUrlTests {
const char* url;
const char* scheme;
bool secure_connect;
const char* domain_name;
int port;
const char* path;
};
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());
ParseUrlTests parse_url_tests[] = {
{
"https://code.google.com/p/googletest/wiki/Primer", // url
"https", // scheme
true, // secure_connect
"code.google.com", // domain_name
443, // port
"/p/googletest/wiki/Primer", // path
},
{
"http://code.google.com/p/googletest/wiki/Primer/", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/p/googletest/wiki/Primer/", // path
},
{
"http://code.google.com/", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/", // path
},
{
"http://code.google.com", // url
"http", // scheme
false, // secure_connect
"code.google.com", // domain_name
80, // port
"/", // path
},
{
"http://10.11.12.13:8888/drm", // url
"http", // scheme
false, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/drm", // path
},
{
"http://10.11.12.13:8888", // url
"http", // scheme
false, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/", // path
},
{
"https://10.11.12.13:8888", // url
"https", // scheme
true, // secure_connect
"10.11.12.13", // domain_name
8888, // port
"/", // path
},
{ NULL } // list terminator
};
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());
TEST_F(HttpSocketTest, ParseUrlTest) {
std::string scheme;
bool secure_connect;
std::string domain_name;
int port;
std::string path;
ParseUrlTests* test = NULL;
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 arbitrary 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());
for (test = &parse_url_tests[0]; test->url != NULL; ++test) {
bool ok = HttpSocket::ParseUrl(test->url, &scheme, &secure_connect,
&domain_name, &port, &path);
EXPECT_TRUE(ok);
if (ok) {
EXPECT_EQ(test->scheme, scheme);
EXPECT_EQ(test->secure_connect, secure_connect);
EXPECT_EQ(test->domain_name, domain_name);
EXPECT_EQ(test->port, port);
EXPECT_EQ(test->path, path);
}
}
}
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();
EXPECT_TRUE(Connect(kHttpsTestServer));
EXPECT_TRUE(Connect(kHttpTestServer));
EXPECT_FALSE(Connect("ww.g.c"));
EXPECT_FALSE(Connect("http://ww.g.c"));
EXPECT_FALSE(Connect("https://ww.g.c"));
}
TEST_F(HttpSocketTest, RoundTripTest) {
int secure_connection =
(gTestServer.find("https") != std::string::npos) ? true : false;
ASSERT_TRUE(Connect(gTestServer, secure_connection));
ASSERT_TRUE(Connect(gTestServer));
EXPECT_TRUE(PostRequest(gTestData));
GetResponse();
socket_.CloseSocket();
std::string response;
EXPECT_TRUE(GetResponse(&response));
LOGD("response: %s", response.c_str());
}
} // namespace wvcdm

View File

@@ -10,52 +10,16 @@
#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
}
const int kReadBufferSize = 1024;
const int kConnectTimeoutMs = 5000;
const int kWriteTimeoutMs = 3000;
const int kReadTimeoutMs = 3000;
// 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) {
void ConcatenateChunkedResponse(const std::string http_response,
std::string* modified_response) {
if (http_response.empty()) return;
modified_response->clear();
@@ -103,33 +67,52 @@ void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
}
}
int UrlRequest::GetResponse(std::string* message) {
message->clear();
} // namespace
namespace wvcdm {
UrlRequest::UrlRequest(const std::string& url)
: is_connected_(false),
socket_(url) {
if (socket_.Connect(kConnectTimeoutMs)) {
is_connected_ = true;
} else {
LOGE("failed to connect to %s, port=%d", socket_.domain_name().c_str(),
socket_.port());
}
}
UrlRequest::~UrlRequest() {}
bool UrlRequest::GetResponse(std::string* message) {
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);
// Keep reading until end of stream (0 bytes read) or timeout. Partial
// buffers worth of data can and do happen, especially with OpenSSL in
// non-blocking mode.
while (true) {
char read_buffer[kReadBufferSize];
int bytes = socket_.Read(read_buffer, sizeof(read_buffer), kReadTimeoutMs);
if (bytes > 0) {
response.append(buffer_, bytes);
if (bytes < static_cast<int>(kHttpBufferSize)) {
attempts = kSingleReadAttempt;
}
response.append(read_buffer, bytes);
} else if (bytes < 0) {
LOGE("read error, errno = %d", errno);
return false;
} else {
if (bytes < 0) LOGE("read error = ", errno);
// bytes == 0 indicates nothing to read
// end of stream.
break;
}
}
ConcatenateChunkedResponse(response, message);
LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str());
return message->size();
return true;
}
// static
int UrlRequest::GetStatusCode(const std::string& response) {
const std::string kHttpVersion("HTTP/1.1");
const std::string kHttpVersion("HTTP/1.1 ");
int status_code = -1;
size_t pos = response.find(kHttpVersion);
@@ -140,79 +123,48 @@ int UrlRequest::GetStatusCode(const std::string& response) {
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
bool UrlRequest::PostRequestWithPath(const std::string& path,
const std::string& data) {
std::string request;
// calls AppendChunkToUpload repeatedly for multiple chunks
AppendChunkToUpload(data);
request.append("POST ");
request.append(path);
request.append(" HTTP/1.1\r\n");
// terminates last chunk with 0\r\n, then ends header with an empty line
request_.append("0\r\n\r\n");
request.append("Host: ");
request.append(socket_.domain_name());
request.append("\r\n");
socket_.Write(request_.c_str(), request_.size());
return true;
request.append("Connection: close\r\n");
request.append("User-Agent: Widevine CDM v1.0\r\n");
// buffer to store length of data as a string
char data_size_buffer[32] = {0};
snprintf(data_size_buffer, sizeof(data_size_buffer), "%zd", data.size());
request.append("Content-Length: ");
request.append(data_size_buffer); // appends size of data
request.append("\r\n");
request.append("\r\n"); // empty line to terminate headers
request.append(data);
int ret = socket_.Write(request.c_str(), request.size(), kWriteTimeoutMs);
LOGD("HTTP request: (%d): %s", request.size(), b2a_hex(request).c_str());
return ret != -1;
}
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;
return PostRequestWithPath(socket_.resource_path(), data);
}
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
std::string path = socket_.resource_path();
path.append("&signedRequest=");
path.append(data);
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;
return PostRequestWithPath(path, "");
}
} // namespace wvcdm

View File

@@ -13,29 +13,22 @@ namespace wvcdm {
// 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);
explicit UrlRequest(const std::string& url);
~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);
bool GetResponse(std::string* message);
static int GetStatusCode(const std::string& response);
private:
static const unsigned int kHttpBufferSize = 4096;
char buffer_[kHttpBufferSize];
bool chunk_transfer_mode_;
bool PostRequestWithPath(const std::string& path, const std::string& data);
bool is_connected_;
std::string port_;
std::string request_;
HttpSocket socket_;
std::string server_url_;
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
};

View File

@@ -12,7 +12,7 @@
namespace wvcdm {
static LogPriority g_cutoff = LOG_WARN;
LogPriority g_cutoff = LOG_WARN;
void InitLogging(int argc, const char* const* argv) {
for (int i = 1; i < argc; i++) {

View File

@@ -0,0 +1,65 @@
// Copyright 2014 Google Inc. All Rights Reserved.
#ifndef WVOEC_OEMCRYPTO_LOGGING_H_
#define WVOEC_OEMCRYPTO_LOGGING_H_
#include "OEMCryptoCENC.h"
#include "log.h"
#include <string>
#include <iostream>
namespace wvoec_mock {
// The constants below represent integers with a single "on" bit that
// represents categories of logging This allows users to specify with
// more precision what they want to log. LogCategoryEnabled(category)
// is used to see if the category passed in the parameters is to
// be logged based on the current settings. Categories can be combines
// using the | (or) bitwise operator. For example
// LogCategoryEnabled(category1 | category2) will return true if
// category1 and/or category2 are set to logging.
const int kLoggingTraceOEMCryptoCalls = 0x01;
const int kLoggingDumpContentKeys = 0x02;
const int kLoggingDumpKeyControlBlocks = 0x04;
const int kLoggingDumpDerivedKeys = 0x08;
const int kLoggingTraceNonce = 0x10;
const int kLoggingTraceDecryption = 0x20;
const int kLoggingTraceUsageTable = 0x40;
const int kLoggingDumpTraceAll = 0xFF;
void SetLoggingSettings(int level, int categories);
// set level of logging
void SetLoggingLevel(int level);
void TurnOffLoggingForAllCategories();
// Returns true if the category passed is set to logging.
// Returns false otherwise. The category constant declared
// above are passed.
bool LogCategoryEnabled(int category);
// Turn on logging for the categories passed.
void AddLoggingForCategories(int categories);
// Turn off logging for the categories passed.
void RemoveLoggingForCategories(int categories);
void dump_hex_helper(std::string& buffer, std::string name,
const uint8_t* vector, size_t length);
void dump_hex(std::string name, const uint8_t* vector, size_t length);
void dump_array_part_helper(std::string& buffer, std::string array,
size_t index, std::string name,
const uint8_t* vector, size_t length);
void dump_array_part(std::string array, size_t index,
std::string name, const uint8_t* vector, size_t length);
} // namespace wvoec_mock
#endif

View File

@@ -1837,7 +1837,7 @@ class DISABLED_TestKeybox : public OEMCryptoClientTest {
}
};
TEST_F(DISABLED_TestKeybox, CheckSystemID) {
TEST_F(OEMCryptoClientTest, DISABLED_CheckSystemID) {
OEMCryptoResult sts;
uint8_t key_data[256];
size_t key_data_len = sizeof(key_data);
@@ -1884,6 +1884,7 @@ TEST_F(DISABLED_TestKeybox, BadCRCKeybox) {
InstallKeybox(keybox, false);
sts = OEMCrypto_IsKeyboxValid();
ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts);
InstallKeybox(kDefaultKeybox, true);
}
TEST_F(DISABLED_TestKeybox, BadMagicKeybox) {
@@ -1893,6 +1894,7 @@ TEST_F(DISABLED_TestKeybox, BadMagicKeybox) {
InstallKeybox(keybox, false);
sts = OEMCrypto_IsKeyboxValid();
ASSERT_EQ(OEMCrypto_ERROR_BAD_MAGIC, sts);
InstallKeybox(kDefaultKeybox, true);
}
TEST_F(DISABLED_TestKeybox, BadDataKeybox) {
@@ -1902,6 +1904,7 @@ TEST_F(DISABLED_TestKeybox, BadDataKeybox) {
InstallKeybox(keybox, false);
sts = OEMCrypto_IsKeyboxValid();
ASSERT_EQ(OEMCrypto_ERROR_BAD_CRC, sts);
InstallKeybox(kDefaultKeybox, true);
}
TEST_F(DISABLED_TestKeybox, GenerateSignature) {
@@ -2148,6 +2151,30 @@ TEST_F(DISABLED_TestKeybox, LoadKeyWithBadNonce) {
ASSERT_NE(OEMCrypto_SUCCESS, sts);
}
TEST_F(DISABLED_TestKeybox, LoadKeyWithRepeatNonce) {
InstallKeybox(kDefaultKeybox, true);
Session s;
s.open();
s.GenerateDerivedKeys();
uint32_t nonce = s.get_nonce();
s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled, nonce);
s.EncryptAndSign();
s.LoadTestKeys();
s.close();
s.open();
s.GenerateDerivedKeys();
s.FillSimpleMessage(0, wvoec_mock::kControlNonceEnabled,
nonce); // same old nonce.
s.EncryptAndSign();
OEMCryptoResult sts = OEMCrypto_LoadKeys(
s.session_id(), s.message_ptr(), sizeof(MessageData), &s.signature()[0],
s.signature().size(), s.encrypted_license().mac_key_iv,
s.encrypted_license().mac_keys, kNumKeys, s.key_array(), NULL, 0);
ASSERT_NE(OEMCrypto_SUCCESS, sts);
}
TEST_F(DISABLED_TestKeybox, LoadKeyWithBadVerification) {
InstallKeybox(kDefaultKeybox, true);
Session s;
@@ -4601,6 +4628,85 @@ TEST_P(DISABLED_UsageTableTest, EmptyTable) {
}
}
TEST_P(DISABLED_UsageTableTest, FiftyEntries) {
if (OEMCrypto_SupportsUsageTable()) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
Session s1;
s1.open();
s1.GenerateDerivedKeys();
std::string pst1 = "pst saved";
s1.FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired,
s1.get_nonce(), pst1);
s1.EncryptAndSign();
s1.LoadTestKeys(pst1, new_mac_keys_);
sleep(kShortSleep);
cout << "Making 49\n";
const size_t ENTRY_COUNT = 49;// API says should hold at least 50 entries.
Session sessions[ENTRY_COUNT];
for (int i=0; i<ENTRY_COUNT; i++) {
sessions[i].open();
sessions[i].GenerateDerivedKeys();
std::string pst = "pst ";
char c = 'A' + i;
pst = pst + c;
sessions[i].FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired,
sessions[i].get_nonce(), pst);
sessions[i].EncryptAndSign();
sessions[i].LoadTestKeys(pst, new_mac_keys_);
sessions[i].GenerateReport(pst);
sessions[i].close();
}
cout << "Checking 49\n";
for (int i=0; i<ENTRY_COUNT; i++) {
Session s;
s.open();
std::string pst = "pst ";
char c = 'A' + i;
pst = pst + c;
s.GenerateReport(pst, true, &sessions[i]);
EXPECT_EQ(kUnused, s.pst_report()->status);
s.close();
}
sleep(kShortSleep);
cout << "Making another 49\n";
// If I add too many entries, it can delete the older ones first, except
// it shouldn't delete the one attached to an open session. (s1)
for (int i=0; i<ENTRY_COUNT; i++) {
sessions[i].open();
sessions[i].GenerateDerivedKeys();
std::string pst = "newer pst ";
char c = 'A' + i;
pst = pst + c;
sessions[i].FillSimpleMessage(
0, wvoec_mock::kControlNonceEnabled | wvoec_mock::kControlNonceRequired,
sessions[i].get_nonce(), pst);
sessions[i].EncryptAndSign();
sessions[i].LoadTestKeys(pst, new_mac_keys_);
sessions[i].GenerateReport(pst);
sessions[i].close();
}
cout << "Checking another 49\n";
for (int i=0; i<49; i++) {
Session s;
s.open();
std::string pst = "newer pst ";
char c = 'A' + i;
pst = pst + c;
s.GenerateReport(pst, true, &sessions[i]);
EXPECT_EQ(kUnused, s.pst_report()->status);
s.close();
}
s1.close();
s1.open(); // Make sure s1's entry is still in the table.
s1.GenerateReport(pst1);
EXPECT_EQ(kUnused, s1.pst_report()->status);
s1.close();
}
}
TEST_P(DISABLED_UsageTableTest, DeleteUnusedEntry) {
if (OEMCrypto_SupportsUsageTable()) {
ASSERT_EQ(OEMCrypto_SUCCESS, OEMCrypto_UpdateUsageTable());
@@ -4826,9 +4932,7 @@ TEST_P(DISABLED_UsageTableTest, GenericEncrypt) {
wvoec_mock::kControlNonceRequired,
nonce, pst);
s.EncryptAndSign();
printf("Getting ready to load keys.\n");
s.LoadTestKeys(pst, new_mac_keys_);
printf("done to load keys.\n");
OEMCryptoResult sts;
unsigned int key_index = 0;
uint8_t expected_encrypted[kBufferSize];
@@ -4842,7 +4946,6 @@ TEST_P(DISABLED_UsageTableTest, GenericEncrypt) {
encrypted);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
EXPECT_EQ(0, memcmp(encrypted, expected_encrypted, kBufferSize));
printf("doing the generate report");
s.GenerateReport(pst);
EXPECT_EQ(kActive, s.pst_report()->status);
EXPECT_ALMOST(
@@ -5164,21 +5267,24 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) {
Session s1;
Session s2;
Session s3;
time_t loaded = time(NULL);
LoadOfflineLicense(s1, pst1);
time_t loaded1 = time(NULL);
LoadOfflineLicense(s2, pst2);
time_t loaded2 = time(NULL);
LoadOfflineLicense(s3, pst3);
time_t loaded3 = time(NULL);
sleep(kLongSleep);
time_t first_decrypt = time(NULL);
s1.open();
s1.GenerateDerivedKeys();
s1.LoadTestKeys(pst1, new_mac_keys_);
time_t first_decrypt1 = time(NULL);
s1.TestDecryptCTR();
s2.open();
s2.GenerateDerivedKeys();
s2.LoadTestKeys(pst2, new_mac_keys_);
time_t first_decrypt2 = time(NULL);
s2.TestDecryptCTR();
sleep(kLongSleep);
@@ -5196,6 +5302,7 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) {
OEMCrypto_Terminate();
sleep(kShortSleep);
OEMCrypto_Initialize();
InstallKeybox(kDefaultKeybox, true);
// After a reboot, we should be able to reload keys, and generate reports.
sleep(kLongSleep);
@@ -5210,34 +5317,36 @@ TEST_P(DISABLED_UsageTableTest, TimingTest) {
s2.open();
s3.open();
sleep(kLongSleep);
time_t report_generated = time(NULL);
time_t report_generated1 = time(NULL);
s1.GenerateReport(pst1);
time_t report_generated2 = time(NULL);
s2.GenerateReport(pst2);
time_t report_generated3 = time(NULL);
s3.GenerateReport(pst3);
EXPECT_EQ(kInactive, s1.pst_report()->status);
EXPECT_ALMOST(
report_generated - loaded,
report_generated1 - loaded1,
wvcdm::htonll64(s1.pst_report()->seconds_since_license_received));
EXPECT_ALMOST(
report_generated - first_decrypt,
report_generated1 - first_decrypt1,
wvcdm::htonll64(s1.pst_report()->seconds_since_first_decrypt));
EXPECT_ALMOST(report_generated - second_decrypt,
EXPECT_ALMOST(report_generated1 - second_decrypt,
wvcdm::htonll64(s1.pst_report()->seconds_since_last_decrypt));
EXPECT_EQ(kActive, s2.pst_report()->status);
EXPECT_ALMOST(
report_generated - loaded,
report_generated2 - loaded2,
wvcdm::htonll64(s2.pst_report()->seconds_since_license_received));
EXPECT_ALMOST(
report_generated - first_decrypt,
report_generated2 - first_decrypt2,
wvcdm::htonll64(s2.pst_report()->seconds_since_first_decrypt));
EXPECT_ALMOST(report_generated - third_decrypt,
EXPECT_ALMOST(report_generated2 - third_decrypt,
wvcdm::htonll64(s2.pst_report()->seconds_since_last_decrypt));
EXPECT_EQ(kUnused, s3.pst_report()->status);
EXPECT_ALMOST(
report_generated - loaded,
report_generated3 - loaded3,
wvcdm::htonll64(s3.pst_report()->seconds_since_license_received));
// We don't expect first or last decrypt for unused report.
}

View File

@@ -16,8 +16,7 @@
'protoc_dir%': '/usr/bin',
'certificate_provision%': 'false',
'force_use_of_secure_buffers%': 'false',
'cdm_target_name%': 'wvcdm_shared',
'cdm_target_type%': 'shared_library',
'disable_privacy_crypto%': 'false',
}, # end variables
'target_defaults': {

View File

@@ -5,27 +5,38 @@
# use_system_protobuf to false.
{
# Some sources are used by both libprotobuf_lite and libprotobuf.
# Representing this with dependencies means that protobuf_lite must be built
# with both the target and host toolsets. Although gyp allows for this,
# this causes bugs in the generated output for both xcode and ninja.
# Therefore we include this variable in the sources of both libraries
# instead.
'variables': {
'protobuf_lite_sources': [
'<(protobuf_source_dir)/src/google/protobuf/extension_set.cc',
'<(protobuf_source_dir)/src/google/protobuf/generated_message_util.cc',
'<(protobuf_source_dir)/src/google/protobuf/message_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/repeated_field.cc',
'<(protobuf_source_dir)/src/google/protobuf/wire_format_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/coded_stream.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream_impl_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/common.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/once.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/stringprintf.cc',
],
},
'targets': [
{
'target_name': 'protobuf_lite',
'type': 'static_library',
'toolsets': ['host', 'target'],
'toolsets': ['target'],
'sources': [
'<(protobuf_source_dir)/src/google/protobuf/extension_set.cc',
'<(protobuf_source_dir)/src/google/protobuf/generated_message_util.cc',
'<(protobuf_source_dir)/src/google/protobuf/message_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/repeated_field.cc',
'<(protobuf_source_dir)/src/google/protobuf/wire_format_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/coded_stream.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream.cc',
'<(protobuf_source_dir)/src/google/protobuf/io/zero_copy_stream_impl_lite.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_gcc.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/atomicops_internals_x86_msvc.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/common.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/once.cc',
'<(protobuf_source_dir)/src/google/protobuf/stubs/stringprintf.cc',
'<@(protobuf_lite_sources)',
],
'include_dirs': [
'<(protobuf_source_dir)',
@@ -42,6 +53,8 @@
'type': 'static_library',
'toolsets': ['host'],
'sources': [
'<@(protobuf_lite_sources)',
'<(protobuf_source_dir)/src/google/protobuf/descriptor.cc',
'<(protobuf_source_dir)/src/google/protobuf/descriptor.pb.cc',
'<(protobuf_source_dir)/src/google/protobuf/descriptor_database.cc',
@@ -71,9 +84,6 @@
'<(protobuf_source_dir)',
'<(protobuf_source_dir)/src',
],
'dependencies': [
'protobuf_lite',
],
},
{
'target_name': 'protoc',