Certificate provisioning verification
bug: 8620943 This is a merge of changes made to the Widevine CDM repository during certificate provisioning verification. The following changes are included: Fixes for certificate based licensing https://widevine-internal-review.googlesource.com/#/c/5162/ Base64 encode and decode now handles non-multiple of 24-bits input https://widevine-internal-review.googlesource.com/#/c/4981/ Fixed issues with device provisioning response handling https://widevine-internal-review.googlesource.com/#/c/5153/ Persistent storage to support device certificates https://widevine-internal-review.googlesource.com/#/c/5161/ Enable loading of certificates https://widevine-internal-review.googlesource.com/#/c/5172/ Provide license server url https://widevine-internal-review.googlesource.com/#/c/5173/ Change-Id: I0c032c1ae0055dcc1a7a77ad4b0ea0898030dc7d
This commit is contained in:
@@ -11,11 +11,11 @@ cd $ANDROID_BUILD_TOP/external/gtest/
|
||||
pwd
|
||||
mm
|
||||
|
||||
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test/gmock
|
||||
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine
|
||||
pwd
|
||||
mm
|
||||
|
||||
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/cdm/core/test
|
||||
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test/gmock
|
||||
pwd
|
||||
mm
|
||||
|
||||
@@ -43,18 +43,5 @@ cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test/java/src/com/widevine/
|
||||
pwd
|
||||
mm
|
||||
|
||||
echo "waiting for device"
|
||||
adb root && adb wait-for-device remount && adb sync
|
||||
|
||||
adb shell /system/bin/request_license_test
|
||||
adb shell /system/bin/policy_engine_unittest
|
||||
adb shell /system/bin/libwvdrmmediacrypto_test
|
||||
adb shell /system/bin/libwvdrmdrmplugin_test
|
||||
adb shell /system/bin/cdm_engine_test
|
||||
adb shell /system/bin/oemcrypto_test
|
||||
adb shell LD_LIBRARY_PATH=/system/vendor/lib/mediadrm/ /system/bin/libwvdrmengine_test
|
||||
|
||||
adb shell am start com.widevine.test/com.widevine.test.MediaDrmAPITest
|
||||
|
||||
# TODO: make this test more command line friendly
|
||||
echo "check logcat output for MediaDrmAPITest"
|
||||
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine
|
||||
./run_all_unit_tests.sh
|
||||
|
||||
@@ -12,6 +12,7 @@ LOCAL_C_INCLUDES := \
|
||||
vendor/widevine/libwvdrmengine/oemcrypto/include
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
external/openssl/include \
|
||||
external/protobuf/src \
|
||||
../oemcrypto/include
|
||||
|
||||
@@ -26,11 +27,13 @@ LOCAL_SRC_FILES := \
|
||||
$(CORE_SRC_DIR)/cdm_session.cpp \
|
||||
$(CORE_SRC_DIR)/crypto_engine.cpp \
|
||||
$(CORE_SRC_DIR)/crypto_session.cpp \
|
||||
$(CORE_SRC_DIR)/device_files.cpp \
|
||||
$(CORE_SRC_DIR)/license.cpp \
|
||||
$(CORE_SRC_DIR)/policy_engine.cpp \
|
||||
$(CORE_SRC_DIR)/properties.cpp \
|
||||
$(CORE_SRC_DIR)/string_conversions.cpp \
|
||||
$(SRC_DIR)/clock.cpp \
|
||||
$(SRC_DIR)/file_store.cpp \
|
||||
$(SRC_DIR)/lock.cpp \
|
||||
$(SRC_DIR)/log.cpp \
|
||||
$(SRC_DIR)/properties.cpp \
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
#ifndef CDM_BASE_CDM_ENGINE_H_
|
||||
#define CDM_BASE_CDM_ENGINE_H_
|
||||
|
||||
#include "crypto_engine.h"
|
||||
#include "timer.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmSession;
|
||||
class CryptoEngine;
|
||||
class WvCdmEventListener;
|
||||
|
||||
typedef std::map<CdmSessionId, CdmSession*> CdmSessionMap;
|
||||
@@ -32,13 +32,11 @@ class CdmEngine : public TimerHandler {
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request);
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// Accept license response and extract key info.
|
||||
CdmResponseType AddKey(const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmKeyResponse& key_data);
|
||||
|
||||
// Cancel session and unload keys.
|
||||
@@ -48,16 +46,11 @@ class CdmEngine : public TimerHandler {
|
||||
|
||||
// Construct valid renewal request for the current session keys.
|
||||
CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
CdmKeyMessage* key_request);
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// Accept renewal response and update key info.
|
||||
CdmResponseType RenewKey(const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmKeyResponse& key_data);
|
||||
|
||||
// Query system information
|
||||
@@ -107,9 +100,7 @@ class CdmEngine : public TimerHandler {
|
||||
// private methods
|
||||
// Cancel all sessions
|
||||
bool CancelSessions();
|
||||
void CleanupProvisioingSessions(CdmSession* cdm_session,
|
||||
CryptoEngine* crypto_engine,
|
||||
const CdmSessionId& cdm_session_id);
|
||||
void CleanupProvisioningSession(const CdmSessionId& cdm_session_id);
|
||||
void ComposeJsonRequest(const std::string& message,
|
||||
const std::string& signature,
|
||||
CdmProvisioningRequest* request);
|
||||
@@ -131,6 +122,7 @@ class CdmEngine : public TimerHandler {
|
||||
virtual void OnTimerEvent();
|
||||
|
||||
// instance variables
|
||||
CdmSession* provisioning_session_;
|
||||
CdmSessionMap sessions_;
|
||||
|
||||
// policy timer
|
||||
|
||||
@@ -36,7 +36,8 @@ class CdmSession {
|
||||
CdmResponseType GenerateKeyRequest(const CdmInitData& pssh_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request);
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// AddKey() - Accept license response and extract key info.
|
||||
CdmResponseType AddKey(const CdmKeyResponse& key_response);
|
||||
@@ -65,7 +66,8 @@ class CdmSession {
|
||||
// License renewal
|
||||
// GenerateRenewalRequest() - Construct valid renewal request for the current
|
||||
// session keys.
|
||||
CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request);
|
||||
CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// RenewKey() - Accept renewal response and update key info.
|
||||
CdmResponseType RenewKey(const CdmKeyResponse& key_response);
|
||||
|
||||
@@ -56,10 +56,11 @@ class CryptoSession {
|
||||
bool GenerateSignature(const std::string& message,
|
||||
std::string* signature);
|
||||
bool RewrapDeviceRSAKey(const std::string& message,
|
||||
const uint32_t* nonce,
|
||||
const uint8_t* enc_rsa_key,
|
||||
const std::string& signature,
|
||||
const std::string& nonce,
|
||||
const std::string& enc_rsa_key,
|
||||
size_t enc_rsa_key_length,
|
||||
const uint8_t* enc_rsa_key_iv,
|
||||
const std::string& rsa_key_iv,
|
||||
uint8_t* wrapped_rsa_key,
|
||||
size_t* wrapped_rsa_key_length);
|
||||
|
||||
|
||||
33
libwvdrmengine/cdm/core/include/device_files.h
Normal file
33
libwvdrmengine/cdm/core/include/device_files.h
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
#ifndef CDM_BASE_DEVICE_FILES_H_
|
||||
#define CDM_BASE_DEVICE_FILES_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class DeviceFiles {
|
||||
public:
|
||||
static bool StoreCertificate(const std::string& certificate,
|
||||
const std::string& wrapped_private_key);
|
||||
static bool RetrieveCertificate(std::string* certificate,
|
||||
std::string* wrapped_private_key);
|
||||
|
||||
static std::string GetPath(const char* dir, const char * filename);
|
||||
static const char* kBasePath;
|
||||
static const char* kIdmPath;
|
||||
static const char* kCencPath;
|
||||
static const char* kDeviceCertificateFileName;
|
||||
|
||||
private:
|
||||
static bool Hash(const std::string& data, std::string* hash);
|
||||
static bool StoreFile(const char* name, const std::string& data);
|
||||
static bool RetrieveFile(const char* name, std::string* data);
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(DeviceFiles);
|
||||
}; // namespace wvcdm
|
||||
|
||||
}
|
||||
|
||||
#endif // CDM_BASE_DEVICE_FILES_H_
|
||||
52
libwvdrmengine/cdm/core/include/file_store.h
Normal file
52
libwvdrmengine/cdm/core/include/file_store.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// File - Platform independent interface for a File class
|
||||
//
|
||||
#ifndef CDM_BASE_FILE_STORE_H_
|
||||
#define CDM_BASE_FILE_STORE_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// File class. The implementation is platform dependent.
|
||||
class File {
|
||||
public:
|
||||
// defines as bit flag
|
||||
enum OpenFlags {
|
||||
kNoFlags = 0,
|
||||
kBinary = 1,
|
||||
kCreate = 2,
|
||||
kReadOnly = 4, // defauts to read and write access
|
||||
kTruncate = 8
|
||||
};
|
||||
|
||||
File();
|
||||
File(const std::string& file_path, int flags);
|
||||
virtual ~File();
|
||||
|
||||
bool Open(const std::string& file_path, int flags);
|
||||
void Close();
|
||||
bool IsOpen();
|
||||
bool IsBad();
|
||||
|
||||
ssize_t Read(void *buf, size_t bytes);
|
||||
ssize_t Write(const void* buf, size_t bytes);
|
||||
|
||||
static bool Exists(const std::string& file_path);
|
||||
static bool Remove(const std::string& file_path);
|
||||
static bool CreateDirectory(const std::string dir_path);
|
||||
static bool IsDirectory(const std::string& dir_path);
|
||||
static bool IsRegularFile(const std::string& file_path);
|
||||
static ssize_t FileSize(const std::string& file_path);
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl *impl_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(File);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_FILE_STORE_H_
|
||||
@@ -27,8 +27,10 @@ class CdmLicense {
|
||||
bool PrepareKeyRequest(const CdmInitData& pssh_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* signed_request);
|
||||
bool PrepareKeyRenewalRequest(CdmKeyMessage* signed_request);
|
||||
CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
bool PrepareKeyRenewalRequest(CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response);
|
||||
CdmResponseType HandleKeyRenewalResponse(
|
||||
const CdmKeyResponse& license_response);
|
||||
@@ -39,6 +41,7 @@ private:
|
||||
LicenseIdentification license_id_;
|
||||
CryptoSession* session_;
|
||||
PolicyEngine* policy_engine_;
|
||||
std::string server_url_;
|
||||
std::string token_;
|
||||
|
||||
// Used for certificate based licensing
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "buffer_reader.h"
|
||||
#include "cdm_session.h"
|
||||
#include "crypto_engine.h"
|
||||
#include "device_files.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
@@ -29,7 +30,7 @@ using video_widevine_server::sdk::SignedProvisioningMessage;
|
||||
|
||||
typedef std::map<CdmSessionId,CdmSession*>::const_iterator CdmSessionIter;
|
||||
|
||||
CdmEngine::CdmEngine() {
|
||||
CdmEngine::CdmEngine() : provisioning_session_(NULL) {
|
||||
Properties::Init();
|
||||
}
|
||||
|
||||
@@ -105,7 +106,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request) {
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
LOGI("CdmEngine::GenerateKeyRequest");
|
||||
|
||||
CdmSessionIter iter = sessions_.find(session_id);
|
||||
@@ -140,7 +142,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
|
||||
CdmResponseType sts = iter->second->GenerateKeyRequest(extracted_pssh,
|
||||
license_type,
|
||||
app_parameters,
|
||||
key_request);
|
||||
key_request,
|
||||
server_url);
|
||||
|
||||
if (KEY_MESSAGE != sts) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, sts=%d",
|
||||
@@ -156,9 +159,6 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
|
||||
|
||||
CdmResponseType CdmEngine::AddKey(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmKeyResponse& key_data) {
|
||||
LOGI("CdmEngine::AddKey");
|
||||
|
||||
@@ -168,14 +168,6 @@ CdmResponseType CdmEngine::AddKey(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate key_system has not changed
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate init_data has not changed
|
||||
}
|
||||
|
||||
if (key_data.empty()) {
|
||||
LOGE("CdmEngine::AddKey: no key_data");
|
||||
return KEY_ERROR;
|
||||
@@ -218,10 +210,8 @@ CdmResponseType CdmEngine::CancelKeyRequest(
|
||||
|
||||
CdmResponseType CdmEngine::GenerateRenewalRequest(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
CdmKeyMessage* key_request) {
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
LOGI("CdmEngine::GenerateRenewalRequest");
|
||||
|
||||
CdmSessionIter iter = sessions_.find(session_id);
|
||||
@@ -230,14 +220,6 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate key_system has not changed
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate init_data has not changed
|
||||
}
|
||||
|
||||
if (!key_request) {
|
||||
LOGE("CdmEngine::GenerateRenewalRequest: no key request destination provided");
|
||||
return KEY_ERROR;
|
||||
@@ -245,7 +227,8 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
|
||||
|
||||
key_request->clear();
|
||||
|
||||
CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request);
|
||||
CdmResponseType sts = iter->second->GenerateRenewalRequest(key_request,
|
||||
server_url);
|
||||
|
||||
if (KEY_MESSAGE != sts) {
|
||||
LOGE("CdmEngine::GenerateRenewalRequest: key request generation failed, sts=%d",
|
||||
@@ -258,9 +241,6 @@ CdmResponseType CdmEngine::GenerateRenewalRequest(
|
||||
|
||||
CdmResponseType CdmEngine::RenewKey(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmKeyResponse& key_data) {
|
||||
LOGI("CdmEngine::RenewKey");
|
||||
|
||||
@@ -270,14 +250,6 @@ CdmResponseType CdmEngine::RenewKey(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate key_system has not changed
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate init_data has not changed
|
||||
}
|
||||
|
||||
if (key_data.empty()) {
|
||||
LOGE("CdmEngine::RenewKey: no key_data");
|
||||
return KEY_ERROR;
|
||||
@@ -357,15 +329,9 @@ CdmResponseType CdmEngine::QueryKeyControlInfo(
|
||||
return iter->second->QueryKeyControlInfo(key_info);
|
||||
}
|
||||
|
||||
void CdmEngine::CleanupProvisioingSessions(
|
||||
CdmSession* cdm_session,
|
||||
CryptoEngine* crypto_engine,
|
||||
const CdmSessionId& cdm_session_id) {
|
||||
if (NULL == cdm_session) return;
|
||||
|
||||
cdm_session->DestroySession();
|
||||
if (crypto_engine) crypto_engine->DestroySession(cdm_session_id);
|
||||
delete cdm_session;
|
||||
void CdmEngine::CleanupProvisioningSession(const CdmSessionId& cdm_session_id) {
|
||||
CloseSession(cdm_session_id);
|
||||
provisioning_session_ = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -391,21 +357,16 @@ void CdmEngine::ComposeJsonRequest(
|
||||
// performs base64 encoding for message
|
||||
std::vector<uint8_t> message_vector(message.begin(), message.end());
|
||||
std::string message_b64 = Base64SafeEncode(message_vector);
|
||||
LOGD("b64 serialized req:\r\n%s", message_b64.data());
|
||||
|
||||
// performs base64 encoding for signature
|
||||
std::vector<uint8_t> signature_vector(signature.begin(), signature.end());
|
||||
std::string signature_b64 = Base64SafeEncode(signature_vector);
|
||||
LOGD("b64 signature:\r\n%s", signature_b64.data());
|
||||
|
||||
// TODO(edwinwong): write a function to escape JSON output
|
||||
request->assign("{'signedRequest':{'message':'");
|
||||
request->append(message_b64);
|
||||
request->append("','signature':'");
|
||||
request->append(signature_b64);
|
||||
request->append("'}}");
|
||||
LOGD("json str:\r\n%s", request->c_str());
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -419,7 +380,12 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
|
||||
CdmProvisioningRequest* request,
|
||||
std::string* default_url) {
|
||||
if (!request || !default_url) {
|
||||
LOGE("CdmEngine::GetProvisioningRequest: invalid input parameters");
|
||||
LOGE("GetProvisioningRequest: invalid input parameters");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (provisioning_session_) {
|
||||
LOGE("GetProvisioningRequest: duplicate provisioning request?");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
@@ -430,19 +396,19 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
|
||||
//
|
||||
CdmSession* cdm_session = new CdmSession();
|
||||
if (!cdm_session) {
|
||||
LOGE("CdmEngine::GetProvisioningRequest: fails to create a cdm session");
|
||||
LOGE("GetProvisioningRequest: fails to create a cdm session");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (cdm_session->session_id().empty()) {
|
||||
LOGE("CdmEngine::GetProvisioningRequest: fails to generate session ID");
|
||||
LOGE("GetProvisioningRequest: fails to generate session ID");
|
||||
delete cdm_session;
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
if (!crypto_engine) {
|
||||
LOGE("CdmEngine::GetProvisioningRequest: fails to create a crypto engine");
|
||||
LOGE("GetProvisioningRequest: fails to create a crypto engine");
|
||||
delete cdm_session;
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
@@ -450,11 +416,13 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
|
||||
CdmSessionId cdm_session_id = cdm_session->session_id();
|
||||
CryptoSession* crypto_session = crypto_engine->CreateSession(cdm_session_id);
|
||||
if (!crypto_session) {
|
||||
LOGE("CdmEngine::GetProvisioningRequest: fails to create a crypto session");
|
||||
LOGE("GetProvisioningRequest: fails to create a crypto session");
|
||||
delete cdm_session;
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
provisioning_session_ = cdm_session;
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
// Prepares device provisioning request.
|
||||
@@ -464,20 +432,24 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
|
||||
client_id->set_type(ClientIdentification::KEYBOX);
|
||||
std::string token;
|
||||
if (!crypto_engine->GetToken(&token)) {
|
||||
LOGE("CdmEngine::GetProvisioningRequest: fails to get token");
|
||||
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
|
||||
LOGE("GetProvisioningRequest: fails to get token");
|
||||
CleanupProvisioningSession(cdm_session_id);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
client_id->set_token(token);
|
||||
|
||||
uint32_t nonce;
|
||||
if (!crypto_session->GenerateNonce(&nonce)) {
|
||||
LOGE("CdmEngine::GetProvisioningRequest: fails to generate a nonce");
|
||||
LOGE("GetProvisioningRequest: fails to generate a nonce");
|
||||
crypto_engine->DestroySession(cdm_session_id);
|
||||
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
|
||||
CleanupProvisioningSession(cdm_session_id);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
provisioning_request.set_nonce(UintToString(nonce));
|
||||
|
||||
// The provisioning server does not convert the nonce to uint32_t, it just
|
||||
// passes the binary data to the response message.
|
||||
std::string the_nonce(reinterpret_cast<char*>(&nonce), sizeof(nonce));
|
||||
provisioning_request.set_nonce(the_nonce);
|
||||
|
||||
// Serializes the provisioning request.
|
||||
std::string serialized_request;
|
||||
@@ -487,27 +459,23 @@ CdmResponseType CdmEngine::GetProvisioningRequest(
|
||||
std::string request_signature;
|
||||
if (!crypto_session->PrepareRequest(serialized_request, &request_signature)) {
|
||||
request->clear();
|
||||
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
|
||||
CleanupProvisioningSession(cdm_session_id);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (request_signature.empty()) {
|
||||
request->clear();
|
||||
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
|
||||
CleanupProvisioningSession(cdm_session_id);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// converts request into JSON string
|
||||
ComposeJsonRequest(serialized_request, request_signature, request);
|
||||
|
||||
// TODO(edwinwong): returns default provisioning server url
|
||||
default_url->clear();
|
||||
static const std::string kDefaultProvisioningServerUrl =
|
||||
"http://www-googleapis-test.sandbox.google.com/certificateprovisioning/v1/devicecertificates/create";
|
||||
default_url->assign(kDefaultProvisioningServerUrl);
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
// Closes the cdm session.
|
||||
//
|
||||
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
@@ -537,12 +505,6 @@ bool CdmEngine::ParseJsonResponse(
|
||||
|
||||
size_t b64_string_size = end - start - start_substr.length();
|
||||
b64_string.assign(json_str, start + start_substr.length(), b64_string_size);
|
||||
|
||||
// Due to the size of the message, debug string cannot dump out the
|
||||
// entire string. Dump the beginning and end to verify instead.
|
||||
LOGD("size=%u, b64_string start=%s, end=%s", b64_string.length(),
|
||||
b64_string.substr(0, 16).c_str(),
|
||||
b64_string.substr(b64_string_size - 16).c_str());
|
||||
}
|
||||
|
||||
// Decodes base64 substring and returns it in *result
|
||||
@@ -587,32 +549,23 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
// First creates a cdm session, then creates a crypto session.
|
||||
// Creates a crypto session using provisioning_session_.
|
||||
//
|
||||
CdmSession* cdm_session = new CdmSession();
|
||||
if (!cdm_session) {
|
||||
LOGE("CdmEngine::HandleProvisioningResponse: fails to create a cdm session");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (cdm_session->session_id().empty()) {
|
||||
LOGE("CdmEngine::HandleProvisioningResponse: fails to generate session ID");
|
||||
delete cdm_session;
|
||||
if (!provisioning_session_) {
|
||||
LOGE("HandleProvisioningResponse: invalid provisioning session");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
if (!crypto_engine) {
|
||||
LOGE("CdmEngine::HandleProvisioningResponse: fails to create a crypto engine");
|
||||
delete cdm_session;
|
||||
LOGE("HandleProvisioningResponse: fails to create a crypto engine");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CdmSessionId cdm_session_id = cdm_session->session_id();
|
||||
CryptoSession* crypto_session = crypto_engine->CreateSession(cdm_session_id);
|
||||
CdmSessionId cdm_session_id = provisioning_session_->session_id();
|
||||
CryptoSession* crypto_session = crypto_engine->FindSession(cdm_session_id);
|
||||
if (!crypto_session) {
|
||||
LOGE("CdmEngine::HandleProvisioningResponse: fails to create a crypto session");
|
||||
delete cdm_session;
|
||||
LOGE("HandleProvisioningResponse: fails to find %s", cdm_session_id.c_str());
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
@@ -621,36 +574,50 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
// the provisioing request's input). Validate provisioning response and
|
||||
// stores private device RSA key and certificate.
|
||||
ProvisioningResponse provisioning_response;
|
||||
|
||||
if (!provisioning_response.ParseFromString(signed_message)) {
|
||||
LOGE("CdmEngine::HandleProvisioningResponse: fails to parse signed message");
|
||||
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
const std::string& enc_rsa_key = provisioning_response.device_rsa_key();
|
||||
const std::string& enc_rsa_key_iv = provisioning_response.device_rsa_key_iv();
|
||||
uint32_t nonce = strtoul(provisioning_response.nonce().data(), NULL, 10);
|
||||
std::vector<uint8_t> wrapped_rsa_key;
|
||||
size_t wrapped_rsa_key_length = 0;
|
||||
if (!crypto_session->RewrapDeviceRSAKey(response,
|
||||
&nonce,
|
||||
reinterpret_cast<const uint8_t*>(enc_rsa_key.data()),
|
||||
enc_rsa_key.length(),
|
||||
reinterpret_cast<const uint8_t*>(enc_rsa_key_iv.data()),
|
||||
&wrapped_rsa_key[0],
|
||||
&wrapped_rsa_key_length)) {
|
||||
LOGE("CdmEngine::HandleProvisioningResponse: RewrapDeviceRSAKey fails");
|
||||
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
|
||||
LOGE("HandleProvisioningResponse: fails to parse signed message");
|
||||
CleanupProvisioningSession(cdm_session_id);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!provisioning_response.has_device_rsa_key()) {
|
||||
LOGE("HandleProvisioningResponse: invalid response - key not found");
|
||||
CleanupProvisioningSession(cdm_session_id);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
const std::string& enc_rsa_key = provisioning_response.device_rsa_key();
|
||||
const std::string& rsa_key_iv = provisioning_response.device_rsa_key_iv();
|
||||
const std::string& nonce = provisioning_response.nonce();
|
||||
|
||||
const int kRsaKeySize = 256;
|
||||
size_t wrapped_rsa_key_length = kRsaKeySize + enc_rsa_key.length();
|
||||
std::vector<uint8_t> wrapped_rsa_key;
|
||||
wrapped_rsa_key.resize(wrapped_rsa_key_length);
|
||||
|
||||
if (!crypto_session->RewrapDeviceRSAKey(signed_message,
|
||||
signature,
|
||||
nonce.data(),
|
||||
enc_rsa_key,
|
||||
enc_rsa_key.size(),
|
||||
rsa_key_iv,
|
||||
&wrapped_rsa_key[0],
|
||||
&wrapped_rsa_key_length)) {
|
||||
LOGE("HandleProvisioningResponse: RewrapDeviceRSAKey fails");
|
||||
CleanupProvisioningSession(cdm_session_id);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// TODO(edwinwong): stores private device RSA key and cert
|
||||
const std::string& device_certificate = provisioning_response.device_certificate();
|
||||
std::string the_wrapped_rsa_key(wrapped_rsa_key.begin(), wrapped_rsa_key.end());
|
||||
DeviceFiles::StoreCertificate(device_certificate, the_wrapped_rsa_key);
|
||||
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
// Closes the cdm session.
|
||||
//
|
||||
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
|
||||
CleanupProvisioningSession(cdm_session_id);
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "clock.h"
|
||||
#include "crypto_engine.h"
|
||||
#include "device_files.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
@@ -64,7 +65,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
const CdmInitData& pssh_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request) {
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
if (!crypto_session_) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session");
|
||||
return UNKNOWN_ERROR;
|
||||
@@ -77,7 +79,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
|
||||
if (license_received_) {
|
||||
return Properties::require_explicit_renew_request() ?
|
||||
UNKNOWN_ERROR : GenerateRenewalRequest(key_request);
|
||||
UNKNOWN_ERROR : GenerateRenewalRequest(key_request,
|
||||
server_url);
|
||||
}
|
||||
else {
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
@@ -88,7 +91,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
if (!license_parser_.PrepareKeyRequest(pssh_data,
|
||||
license_type,
|
||||
app_parameters,
|
||||
key_request)) {
|
||||
key_request,
|
||||
server_url)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
return KEY_MESSAGE;
|
||||
@@ -178,8 +182,10 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted,
|
||||
// License renewal
|
||||
// GenerateRenewalRequest() - Construct valid renewal request for the current
|
||||
// session keys.
|
||||
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request) {
|
||||
if (!license_parser_.PrepareKeyRenewalRequest(key_request)) {
|
||||
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
if (!license_parser_.PrepareKeyRenewalRequest(key_request,
|
||||
server_url)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
return KEY_MESSAGE;
|
||||
@@ -207,8 +213,8 @@ CdmSessionId CdmSession::GenerateSessionId() {
|
||||
|
||||
bool CdmSession::LoadDeviceCertificate(std::string* certificate,
|
||||
std::string* wrapped_key) {
|
||||
// TODO(edwingwong,rfrias): Need to read in the private key
|
||||
return false;
|
||||
return DeviceFiles::RetrieveCertificate(certificate,
|
||||
wrapped_key);
|
||||
}
|
||||
|
||||
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
const uint32_t kMaxSignatureBufLength = 256;
|
||||
// Encode unsigned integer into a big endian formatted string
|
||||
std::string EncodeUint32(unsigned int u) {
|
||||
std::string s;
|
||||
@@ -357,8 +358,8 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message,
|
||||
bool CryptoSession::GenerateSignature(const std::string& message,
|
||||
std::string* signature) {
|
||||
LOGV("GenerateSignature: id=%ld", (uint32_t) oec_session_id_);
|
||||
uint8_t signature_buf[32];
|
||||
uint32_t length = 32;
|
||||
uint8_t signature_buf[kMaxSignatureBufLength];
|
||||
size_t length = kMaxSignatureBufLength;
|
||||
OEMCryptoResult sts;
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
sts = OEMCrypto_GenerateRSASignature(
|
||||
@@ -475,10 +476,11 @@ bool CryptoSession::SetDestinationBufferType() {
|
||||
}
|
||||
|
||||
bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
|
||||
const uint32_t* nonce,
|
||||
const uint8_t* enc_rsa_key,
|
||||
const std::string& signature,
|
||||
const std::string& nonce,
|
||||
const std::string& enc_rsa_key,
|
||||
size_t enc_rsa_key_length,
|
||||
const uint8_t* enc_rsa_key_iv,
|
||||
const std::string& rsa_key_iv,
|
||||
uint8_t* wrapped_rsa_key,
|
||||
size_t* wrapped_rsa_key_length) {
|
||||
LOGV("CryptoSession::RewrapDeviceRSAKey: Lock+++");
|
||||
@@ -487,31 +489,28 @@ bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
|
||||
|
||||
LOGV("crypto session id=%ld", static_cast<uint32_t>(oec_session_id_));
|
||||
|
||||
// HMAC-SHA256 signature
|
||||
uint8_t signature[kSignatureSize];
|
||||
size_t signature_length = kSignatureSize;
|
||||
OEMCryptoResult status = OEMCrypto_GenerateSignature(
|
||||
oec_session_id_,
|
||||
reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
signature,
|
||||
&signature_length);
|
||||
if (OEMCrypto_SUCCESS != status) {
|
||||
LOGE("CryptoSession::RewrapDeviceRSAKey: GenerateSiganture failed");
|
||||
return false;
|
||||
const uint8_t* signed_msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
const uint8_t* msg_rsa_key = NULL;
|
||||
const uint8_t* msg_rsa_key_iv = NULL;
|
||||
const uint32_t* msg_nonce = NULL;
|
||||
if (enc_rsa_key.size() >= MAC_KEY_SIZE && rsa_key_iv.size() >= KEY_IV_SIZE) {
|
||||
msg_rsa_key = signed_msg + GetOffset(message, enc_rsa_key);
|
||||
msg_rsa_key_iv = signed_msg + GetOffset(message, rsa_key_iv);
|
||||
msg_nonce = reinterpret_cast<const uint32_t*>(signed_msg + GetOffset(message, nonce));
|
||||
}
|
||||
|
||||
status = OEMCrypto_RewrapDeviceRSAKey(
|
||||
OEMCryptoResult status = OEMCrypto_RewrapDeviceRSAKey(
|
||||
oec_session_id_,
|
||||
reinterpret_cast<const uint8_t*>(message.data()), message.length(),
|
||||
signature, signature_length,
|
||||
nonce,
|
||||
enc_rsa_key, enc_rsa_key_length,
|
||||
enc_rsa_key_iv,
|
||||
signed_msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
|
||||
msg_nonce,
|
||||
msg_rsa_key, enc_rsa_key_length,
|
||||
msg_rsa_key_iv,
|
||||
wrapped_rsa_key,
|
||||
wrapped_rsa_key_length);
|
||||
|
||||
if (OEMCrypto_SUCCESS != status) {
|
||||
LOGE("OEMCrypto_RewrapDeviceRSAKey fails with %d", status);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
196
libwvdrmengine/cdm/core/src/device_files.cpp
Normal file
196
libwvdrmengine/cdm/core/src/device_files.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "device_files.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <string.h>
|
||||
|
||||
#include "device_files.pb.h"
|
||||
#include "file_store.h"
|
||||
#include "log.h"
|
||||
#include "openssl/sha.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// TODO(rfrias): Make this work for non-unix paths
|
||||
const char* DeviceFiles::kBasePath = "/data/drm";
|
||||
const char* DeviceFiles::kIdmPath = "/data/drm/IDM";
|
||||
const char* DeviceFiles::kCencPath = "/data/drm/IDM/CENC";
|
||||
const char* DeviceFiles::kDeviceCertificateFileName = "cert.bin";
|
||||
|
||||
// Protobuf generated classes.
|
||||
using video_widevine_client::sdk::DeviceCertificate;
|
||||
using video_widevine_client::sdk::HashedFile;
|
||||
|
||||
bool DeviceFiles::StoreCertificate(const std::string& certificate,
|
||||
const std::string& wrapped_private_key) {
|
||||
// Fill in file information
|
||||
video_widevine_client::sdk::File file;
|
||||
|
||||
file.set_type(video_widevine_client::sdk::File::DEVICE_CERTIFICATE);
|
||||
file.set_version(video_widevine_client::sdk::File::VERSION_1);
|
||||
|
||||
DeviceCertificate *device_certificate = file.mutable_device_certificate();
|
||||
device_certificate->set_certificate(certificate);
|
||||
device_certificate->set_wrapped_private_key(wrapped_private_key);
|
||||
|
||||
std::string serialized_string;
|
||||
file.SerializeToString(&serialized_string);
|
||||
|
||||
// calculate SHA hash
|
||||
std::string hash;
|
||||
if (!Hash(serialized_string, &hash)) {
|
||||
LOGW("DeviceFiles::StoreCertificate: Hash computation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// File 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(kDeviceCertificateFileName, serialized_string);
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveCertificate(std::string* certificate,
|
||||
std::string* wrapped_private_key) {
|
||||
|
||||
std::string serialized_hashed_file;
|
||||
if (!RetrieveFile(kDeviceCertificateFileName, &serialized_hashed_file))
|
||||
return false;
|
||||
|
||||
HashedFile hashed_file;
|
||||
if (!hashed_file.ParseFromString(serialized_hashed_file)) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse hash file");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string hash;
|
||||
if (!Hash(hashed_file.file(), &hash)) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Hash computation failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hash.compare(hashed_file.hash())) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Hash mismatch");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!file.ParseFromString(hashed_file.file())) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Unable to parse file");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.type() != video_widevine_client::sdk::File::DEVICE_CERTIFICATE) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Incorrect file type");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.version() != video_widevine_client::sdk::File::VERSION_1) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Incorrect file version");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.has_device_certificate()) {
|
||||
LOGW("DeviceFiles::RetrieveCertificate: Certificate not present");
|
||||
return false;
|
||||
}
|
||||
|
||||
DeviceCertificate device_certificate = file.device_certificate();
|
||||
|
||||
*certificate = device_certificate.certificate();
|
||||
*wrapped_private_key = device_certificate.wrapped_private_key();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::Hash(const std::string& data, std::string* hash) {
|
||||
if (!hash)
|
||||
return false;
|
||||
|
||||
hash->resize(SHA256_DIGEST_LENGTH);
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
SHA256_Update(&sha256, data.data(), data.size());
|
||||
SHA256_Final(reinterpret_cast<unsigned char*>(const_cast<char*>(hash->data())), &sha256);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreFile(const char* name, const std::string& data) {
|
||||
if (!name)
|
||||
return false;
|
||||
|
||||
if (!File::IsDirectory(kCencPath)) {
|
||||
if (!File::CreateDirectory(kCencPath))
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path = GetPath(kCencPath, name);
|
||||
|
||||
File file(path, File::kCreate | File::kTruncate | File::kBinary);
|
||||
if (file.IsBad()) {
|
||||
LOGW("DeviceFiles::StoreFile: File open failed: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t bytes = file.Write(data.data(), data.size());
|
||||
|
||||
file.Close();
|
||||
|
||||
if (bytes != static_cast<ssize_t>(data.size())) {
|
||||
LOGW("DeviceFiles::StoreFile: write failed: %d %d", data.size(), bytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::RetrieveFile(const char* name, std::string* data) {
|
||||
if (!data)
|
||||
return false;
|
||||
|
||||
std::string path = GetPath(kCencPath, name);
|
||||
|
||||
if (!File::Exists(path)) {
|
||||
LOGW("DeviceFiles::RetrieveFile: %s does not exist", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t bytes = File::FileSize(path);
|
||||
|
||||
if (bytes <= 0) {
|
||||
LOGW("DeviceFiles::RetrieveFile: File size invalid: %d", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
File file(path, File::kReadOnly | File::kBinary);
|
||||
if (file.IsBad()) {
|
||||
LOGW("DeviceFiles::RetrieveFile: File open failed: %s", path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
data->resize(bytes);
|
||||
|
||||
bytes = file.Read(reinterpret_cast<void*>(const_cast<char*>(data->data())),
|
||||
data->size());
|
||||
|
||||
if (bytes != static_cast<ssize_t>(data->size())) {
|
||||
LOGW("DeviceFiles::StoreFile: write failed: %d %d", data->size(), bytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string DeviceFiles::GetPath(const char* dir, const char * filename) {
|
||||
// TODO(rfrias): Make this work for non-unix paths
|
||||
std::string path = dir;
|
||||
path += "/";
|
||||
path += filename;
|
||||
return path;
|
||||
}
|
||||
|
||||
}
|
||||
47
libwvdrmengine/cdm/core/src/device_files.proto
Normal file
47
libwvdrmengine/cdm/core/src/device_files.proto
Normal file
@@ -0,0 +1,47 @@
|
||||
// ----------------------------------------------------------------------------
|
||||
// device_files.proto
|
||||
// ----------------------------------------------------------------------------
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Format of various files stored at the device.
|
||||
//
|
||||
syntax = "proto2";
|
||||
|
||||
package video_widevine_client.sdk;
|
||||
|
||||
// need this if we are using libprotobuf-cpp-2.3.0-lite
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
message DeviceCertificate {
|
||||
optional bytes certificate = 1;
|
||||
optional bytes wrapped_private_key = 2;
|
||||
}
|
||||
|
||||
message License {
|
||||
optional bytes key_set_id = 1;
|
||||
optional bytes pssh_data = 2;
|
||||
optional bytes license_request = 3;
|
||||
optional bytes license = 4;
|
||||
}
|
||||
|
||||
message File {
|
||||
enum FileType {
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
LICENSE = 2;
|
||||
}
|
||||
|
||||
enum FileVersion {
|
||||
VERSION_1 = 1;
|
||||
}
|
||||
|
||||
optional FileType type = 1;
|
||||
optional FileVersion version = 2 [default = VERSION_1];
|
||||
optional DeviceCertificate device_certificate = 3;
|
||||
repeated License licenses = 4;
|
||||
}
|
||||
|
||||
message HashedFile {
|
||||
optional bytes file = 1;
|
||||
optional bytes hash = 2;
|
||||
}
|
||||
@@ -91,7 +91,8 @@ bool CdmLicense::Init(const std::string& token,
|
||||
bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* signed_request) {
|
||||
CdmKeyMessage* signed_request,
|
||||
std::string* server_url) {
|
||||
if (!session_ ||
|
||||
token_.empty()) {
|
||||
return false;
|
||||
@@ -104,6 +105,10 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
|
||||
LOGE("CdmLicense::PrepareKeyRequest : No signed request provided.");
|
||||
return false;
|
||||
}
|
||||
if (!server_url) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest : No server url provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(gmorgan): Request ID owned by session?
|
||||
std::string request_id;
|
||||
@@ -191,6 +196,9 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
|
||||
std::string serialized_license_req;
|
||||
license_request.SerializeToString(&serialized_license_req);
|
||||
|
||||
if (Properties::use_certificates_as_identification())
|
||||
key_request_ = serialized_license_req;
|
||||
|
||||
// Derive signing and encryption keys and construct signature.
|
||||
std::string license_request_signature;
|
||||
if (!session_->PrepareRequest(serialized_license_req,
|
||||
@@ -212,13 +220,12 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
|
||||
|
||||
signed_message.SerializeToString(signed_request);
|
||||
|
||||
if (Properties::use_certificates_as_identification())
|
||||
key_request_ = *signed_request;
|
||||
|
||||
*server_url = server_url_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) {
|
||||
bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request,
|
||||
std::string* server_url) {
|
||||
if (!session_) {
|
||||
return false;
|
||||
}
|
||||
@@ -226,6 +233,10 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) {
|
||||
LOGE("CdmLicense::PrepareKeyRenewalRequest : No signed request provided.");
|
||||
return false;
|
||||
}
|
||||
if (!server_url) {
|
||||
LOGE("CdmLicense::PrepareKeyRenewalRequest : No server url provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LicenseRequest license_request;
|
||||
license_request.set_type(LicenseRequest::RENEWAL);
|
||||
@@ -263,7 +274,7 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) {
|
||||
signed_message.set_msg(serialized_license_req);
|
||||
|
||||
signed_message.SerializeToString(signed_request);
|
||||
|
||||
*server_url = server_url_;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -332,6 +343,10 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (license.policy().has_renewal_server_url()) {
|
||||
server_url_ = license.policy().renewal_server_url();
|
||||
}
|
||||
|
||||
policy_engine_->SetLicense(license);
|
||||
|
||||
if (session_->LoadKeys(signed_response.msg(),
|
||||
@@ -378,6 +393,11 @@ CdmResponseType CdmLicense::HandleKeyRenewalResponse(
|
||||
// This is the normal case.
|
||||
license_id_.CopyFrom(license.id());
|
||||
|
||||
if (license.policy().has_renewal_server_url() &&
|
||||
license.policy().renewal_server_url().size() > 0) {
|
||||
server_url_ = license.policy().renewal_server_url();
|
||||
}
|
||||
|
||||
policy_engine_->UpdateLicense(license);
|
||||
|
||||
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "string_conversions.h"
|
||||
|
||||
@@ -12,14 +12,41 @@
|
||||
#include "log.h"
|
||||
|
||||
namespace {
|
||||
// Helper for Base64SafeDecode()
|
||||
char B64ToBin(char inch) {
|
||||
if (inch >= 'A' && inch <= 'Z') return inch - 'A';
|
||||
if (inch >= 'a' && inch <= 'z') return inch - 'a' + 26;
|
||||
if (inch >= '0' && inch <= '9') return inch - '0' + 52;
|
||||
if (inch == '-') return 62;
|
||||
// if (inch == '_')
|
||||
return 63;
|
||||
/*
|
||||
* Returns a 8-bit char that is mapped to the 6-bit base64 in_ch.
|
||||
*
|
||||
* Extracted from http://www.ietf.org/rfc/rfc3548.txt.
|
||||
*
|
||||
The "URL and Filename safe" Base 64 Alphabet
|
||||
|
||||
Value Encoding Value Encoding Value Encoding Value Encoding
|
||||
0 A 17 R 34 i 51 z
|
||||
1 B 18 S 35 j 52 0
|
||||
2 C 19 T 36 k 53 1
|
||||
3 D 20 U 37 l 54 2
|
||||
4 E 21 V 38 m 55 3
|
||||
5 F 22 W 39 n 56 4
|
||||
6 G 23 X 40 o 57 5
|
||||
7 H 24 Y 41 p 58 6
|
||||
8 I 25 Z 42 q 59 7
|
||||
9 J 26 a 43 r 60 8
|
||||
10 K 27 b 44 s 61 9
|
||||
11 L 28 c 45 t 62 - (minus)
|
||||
12 M 29 d 46 u 63 _
|
||||
13 N 30 e 47 v (underline)
|
||||
14 O 31 f 48 w
|
||||
15 P 32 g 49 x
|
||||
16 Q 33 h 50 y (pad) =
|
||||
*/
|
||||
char B64ToBin(char in_ch) {
|
||||
if (in_ch >= 'A' && in_ch <= 'Z') return in_ch - 'A';
|
||||
if (in_ch >= 'a' && in_ch <= 'z') return in_ch - 'a' + 26;
|
||||
if (in_ch >= '0' && in_ch <= '9') return in_ch - '0' + 52;
|
||||
if (in_ch == '-') return 62;
|
||||
if (in_ch == '_') return 63;
|
||||
|
||||
// arbitrary delimiter not in Base64 encoded alphabet, do not pick 0
|
||||
return '?';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,10 +102,13 @@ std::string b2a_hex(const std::string& byte) {
|
||||
byte.length());
|
||||
}
|
||||
|
||||
// Filename-friendly base64 encoding (RFC4648).
|
||||
// Filename-friendly base64 encoding (RFC4648), commonly referred as
|
||||
// Base64WebSafeEncode.
|
||||
// This is the encoding required by GooglePlay 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) {
|
||||
static const char kBase64Chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
@@ -87,9 +117,13 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
int in_size = bin_input.size();
|
||||
int rup = ((in_size % 3) != 0) ? 1 : 0;
|
||||
int out_size = ((in_size * 4) / 3) + rup;
|
||||
int final_quantum_in_bytes = in_size % 3;
|
||||
int full_in_chunks = in_size / 3;
|
||||
int out_size = full_in_chunks * 4;
|
||||
if (final_quantum_in_bytes) out_size += 4;
|
||||
|
||||
std::string b64_output(out_size, '\0');
|
||||
int in_index = 0;
|
||||
int out_index = 0;
|
||||
@@ -98,7 +132,7 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
static const unsigned long kInMask = 0xff;
|
||||
static const unsigned long kOutMask = 0x3f;
|
||||
|
||||
while (in_index < in_size) {
|
||||
for (int i = 0; i < full_in_chunks; ++i) {
|
||||
// up to 3 bytes (0..255) in
|
||||
buffer = (bin_input.at(in_index) & kInMask);
|
||||
buffer <<= 8;
|
||||
@@ -106,6 +140,7 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
buffer <<= 8;
|
||||
buffer |= (++in_index >= in_size) ? 0 : (bin_input.at(in_index) & kInMask);
|
||||
++in_index;
|
||||
|
||||
// up to 4 bytes (0..63) out
|
||||
out_cc = (buffer >> 18) & kOutMask;
|
||||
b64_output.at(out_index) = kBase64Chars[out_cc];
|
||||
@@ -123,10 +158,49 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
b64_output.at(out_index) = kBase64Chars[out_cc];
|
||||
++out_index;
|
||||
}
|
||||
|
||||
if (final_quantum_in_bytes) {
|
||||
switch(final_quantum_in_bytes) {
|
||||
case 1: {
|
||||
// reads 24-bits data, which is made up of one 8-bits char
|
||||
buffer = (bin_input.at(in_index++) & kInMask);
|
||||
buffer <<= 16;
|
||||
|
||||
// writes two 6-bits chars followed by two '=' padding char
|
||||
out_cc = (buffer >> 18) & kOutMask;
|
||||
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
||||
out_cc = (buffer >> 12) & kOutMask;
|
||||
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
||||
b64_output.at(out_index++) = '=';
|
||||
b64_output.at(out_index) = '=';
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// reads 24-bits data, which is made up of two 8-bits chars
|
||||
buffer = (bin_input.at(in_index++) & kInMask);
|
||||
buffer <<= 8;
|
||||
buffer |= (bin_input.at(in_index++) & kInMask);
|
||||
buffer <<= 8;
|
||||
|
||||
// writes three 6-bits chars followed by one '=' padding char
|
||||
out_cc = (buffer >> 18) & kOutMask;
|
||||
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
||||
out_cc = (buffer >> 12) & kOutMask;
|
||||
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
||||
out_cc = (buffer >> 6) & kOutMask;
|
||||
b64_output.at(out_index++) = kBase64Chars[out_cc];
|
||||
b64_output.at(out_index) = '=';
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return b64_output;
|
||||
}
|
||||
|
||||
// Decode for Filename-friendly base64 encoding (RFC4648).
|
||||
// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred
|
||||
// as Base64WebSafeDecode.
|
||||
// This is the encoding required by GooglePlay for certain
|
||||
// license server transactions. It is also used for logging
|
||||
// certain strings.
|
||||
@@ -134,9 +208,9 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
||||
if (b64_input.empty()) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
|
||||
int in_size = b64_input.size();
|
||||
// out_size should be an integral number of bytes, assuming correct encode
|
||||
int out_size = ((in_size * 3) / 4);
|
||||
int out_size = in_size;
|
||||
std::vector<uint8_t> bin_output(out_size, '\0');
|
||||
int in_index = 0;
|
||||
int out_index = 0;
|
||||
@@ -144,7 +218,14 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
||||
unsigned char out_cc;
|
||||
static const unsigned long kOutMask = 0xff;
|
||||
|
||||
while (in_index < in_size) {
|
||||
int counter = 0;
|
||||
size_t delimiter_pos = b64_input.rfind('=');
|
||||
if (delimiter_pos != std::string::npos) {
|
||||
// Special case for partial last quantum indicated by '='
|
||||
// at the end of encoded input.
|
||||
counter = 1;
|
||||
}
|
||||
for (; counter < (in_size / 4); ++counter) {
|
||||
// up to 4 bytes (0..63) in
|
||||
buffer = B64ToBin(b64_input.at(in_index));
|
||||
buffer <<= 6;
|
||||
@@ -167,6 +248,37 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
||||
bin_output.at(out_index) = out_cc;
|
||||
++out_index;
|
||||
}
|
||||
|
||||
if (delimiter_pos != std::string::npos) {
|
||||
// it is either 2 chars plus 2 '=' or 3 chars plus one '='
|
||||
buffer = B64ToBin(b64_input.at(in_index++));
|
||||
buffer <<= 6;
|
||||
buffer |= B64ToBin(b64_input.at(in_index++));
|
||||
buffer <<= 6;
|
||||
char special_char = b64_input.at(in_index++);
|
||||
if ('=' == special_char) {
|
||||
// we have 2 chars and 2 '='
|
||||
buffer <<= 6;
|
||||
out_cc = (buffer >> 16) & kOutMask;
|
||||
bin_output.at(out_index++) = out_cc;
|
||||
out_cc = (buffer >> 8) & kOutMask;
|
||||
bin_output.at(out_index) = out_cc;
|
||||
} else {
|
||||
// we have 3 chars and 1 '='
|
||||
buffer |= B64ToBin(special_char);
|
||||
buffer <<= 6;
|
||||
buffer |= B64ToBin(b64_input.at(in_index));
|
||||
out_cc = (buffer >> 16) & kOutMask;
|
||||
bin_output.at(out_index++) = out_cc;
|
||||
out_cc = (buffer >> 8) & kOutMask;
|
||||
bin_output.at(out_index++) = out_cc;
|
||||
out_cc = buffer & kOutMask;
|
||||
bin_output.at(out_index) = out_cc;
|
||||
}
|
||||
}
|
||||
|
||||
// adjust vector to reflect true size
|
||||
bin_output.resize(out_index);
|
||||
return bin_output;
|
||||
}
|
||||
|
||||
|
||||
193
libwvdrmengine/cdm/core/test/base64_test.cpp
Normal file
193
libwvdrmengine/cdm/core/test/base64_test.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
std::string kMultipleOf24BitsData("Good day!");
|
||||
std::string kOneByteOverData("Hello Googler");
|
||||
std::string kTwoBytesOverData("Hello Googlers");
|
||||
std::string kMultipleOf24BitsB64Data("R29vZCBkYXkh");
|
||||
std::string kOneByteOverB64Data("SGVsbG8gR29vZ2xlcg==");
|
||||
std::string kTwoBytesOverB64Data("SGVsbG8gR29vZ2xlcnM=");
|
||||
std::string kTestData =
|
||||
"\030\361\\\366\267> \331\210\360\\-\311:\324\256\376"
|
||||
"\261\234\241\326d\326\177\346\346\223\333Y\305\214\330";
|
||||
std::string kB64TestData = "GPFc9rc-INmI8FwtyTrUrv6xnKHWZNZ_5uaT21nFjNg=";
|
||||
std::string kB64ShortString("r-LpoZcbbr2KtoPaFnuWTVBh4Gup1k8vn0ClW2qm32A=");
|
||||
std::string kB64LongString =
|
||||
"CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo"
|
||||
"TP751tvoADf86iLrf73mEzF58eSlaOjCpJRf2R3dojbNeSTy3JICmCc8vKtMjZRX9QWTvJbq_cg"
|
||||
"yMB8FQC8enuYhOaw1yJDYyCFHgik34NrUVUfmvaKKdSKQimqAZmjXi6P0znAn-XdPtz2xJVRxZp"
|
||||
"NH3QCD1bGcH_O1ercBW2JwF9KNalKFsxQrBhIwvyx-q-Ah4vf4r3M2HzY6JTHvcYGGc7dJNA3Xe"
|
||||
"WfCrYIvg0SGCP_z7Y2wICIA36VMwR3gnwNZlKkx6WGCCgsaU6IbLm4HpRBZfajuiOlasoYN4z1R"
|
||||
"lQ14Z32fdaFy8xOqLl-ZukxjWa7wv9zOSveH6JcHap1FS3R-RZ7E5WhfjxSTS0nWWZgmAjS2PkP"
|
||||
"9g4GPNsnpsrVymI39j6R6jPoc3__2EGN6qAvmp4pFKR7lQyslgNn2vYLuE0Ps5mIXVkxNiZOO3T"
|
||||
"jxgZyHaHOm1KmAZKI0EfddMATJCTt-UeLG3haqS_pYaBWcQ_xzWhoEHWU7_6ZaWrWemV8CVCg6s"
|
||||
"OB1SRI5MrkRBBSV0r8UKddLJGthZVjuTG75KK72KE9yhe86mCadvfVYe5keJ5GOC-t1EiFzBo4c"
|
||||
"4oqwkOCkkmYX_BEuZ3pOWztFp1_Br2Tl_fziw4O2vNIPCXB9yEewV6PkYPziTue3x4vRqD_mYjm"
|
||||
"1ia8fxISQnEC0vrqvrFFs9fLAHPlsvaRFnhv_XKpRwFoBdfqWTakb3k6uRz0Oh2SJ8euzFIyQNB"
|
||||
"efesMWk45DSrQjnlwlKXwZSiDKjAss0W2WwIb9F_x5LdB1Aa-CBudLVdxf62ggYaNZ57qx3YeHA"
|
||||
"jkqMGIF7Fq09D4OxM0jRsnrmXbJWKleUpJi7nHJgQGZk2ifN95gjuTNcRaGfYXMOsDoWdkrNAq0"
|
||||
"LScsPB06xEUR0DcO9vWx0zAEK7gsxxHziR7ZaYiIIkPysRR92r2NoLFPOUXf8j8ait-51jZmPKn"
|
||||
"bD6adieLy6ujSl907QsUgyGvokLs1OCsYHZr-X6vnyMjdk4G3QfmWwRepD_CMyXGvtLbTNCto7E"
|
||||
"L_M2yPZveAwYWwNlBtWK21gwIU2dgY298z7_S6jaQBc29f25sREjvN793ttYsPaeyom08qHYDnb"
|
||||
"jae3XX-2qqde6AGXlv__jO8WDZ5od6DWu2ThqV10ijVGFfGniRsSruzq0iq8zuAqTOGhmA9Dw7b"
|
||||
"rNlI95P4LpJA5pbjmNdnX7CQa2oHUuojmwlXRYuOA28PNEf-sc7ZPmMyFzedJi4EpkqzeQspEdH"
|
||||
"yNMf23iEjK6GOff7dgAaxg9vYHyprhkEml4BdmFVYwCYQy8o6KRcA0NgJb8c3tg4d3aRXWp6L-F"
|
||||
"sVhwqvq6FLOunSTNRIqhr2mOjRpU5w4mx-9GJRtk4XEcKT9YgUHGOUjGwfhQ5gBQDyZZVTddIUb"
|
||||
"MOThsSg7zr38oUCfgXeZaai3X2foKo1Bt94Q_q18dw5xNAN5e7rSwfilltHL23zbZduuhWkvp8S"
|
||||
"dag_NbO2C4IRMkzbjQBmiO9ixjXRhdqHlRRWcfR0wbQvEhD47egRVfnhKZ0W9G2-FGhyGuwJCq4"
|
||||
"CCAISEAfZ_94TqpXBImeAUzYhNr0Y48SbiwUijgIwggEKAoIBAQDRigR9nFm4mfBUh1Y3SGyOcF"
|
||||
"E-yK2NtfDiQe9l70KtkOeH4sB6MMB8g1QKPbUE8SBjPvXVJC_2DAWKjALzk4Aw-K-VmYe_Ag9CH"
|
||||
"JiS-XcfUYEGgK4jVMxadEq3LufEEREKUZnzjgQlR39dzgjFqIrC1bwfy3_99RsjPt6QpWPg36PI"
|
||||
"O4UKlmwBDTFzSOJB-4IV8Opy5Zv84BqPuyO9P5e3bXj_shRfy_XAGG2HGP_PpOCZWEfxuce0Iyu"
|
||||
"vpTPLQpTOgNw-VvUBGCWMZFoERopmqp_pQwWZ2a-EwlT_vvYY4SkuNjflBskR70xz4QzEo9665g"
|
||||
"k6I-HbHrTv29KEiAllAgMBAAEomSASgAIkKz1CSdFJVKcpO56jW0vsjKp92_cdqXBSEY3nuhzug"
|
||||
"_LFluMJx_IqATUcCOY-w6w0yKn2ezfZGE0MDIaCngEgQFI_DRoaSOBNNeirF59uYM0sK3P2eGS9"
|
||||
"G6F0l-OUXJdSO0b_LO8AbAK9LA3j7UHaajupJI1mdc4VtJfPRTsml2vIeKhDWXWaSvmeHgfF_tp"
|
||||
"-OV7oPuk6Ub26xpCp2He2rEAblCYEl25Zlz97K4DhyTOV5_xuSdSt-KbTLY9cWM5i9ncND1RzCc"
|
||||
"4qOixKarnMM5DdpZhs3B5xVj3yBAM1mVxPD2sZnqHSEN2EK7BMlHEnnyxhX0MGE36TQZR7P-I-G"
|
||||
"rUFCq8CCAESEDAxMjM0NTY3ODlBQkNERUYYspIEIo4CMIIBCgKCAQEApwA2YGXcvVRaKkC04RWU"
|
||||
"WBFPlFjd3qcfPCzgiAkpYVdnXlZ-7iePWTSaKqqdtE76p2rUyXpTwU6f4zT3PbfJEEdPKNo_zjF"
|
||||
"7_QYQ6_e-kvmv-z5o2u4aZEzzKfJznjnY9m_YsoCCcY61pPLCPs0KyrYEzZoTi1RzVCVUjL6Yem"
|
||||
"et2rNOs_qCqEpnmFZXVHHNEn_towHAaoskA5aIvpdmKrxTyYMGUVqIZRMY5Drta_FhW0zIHvTCr"
|
||||
"gheLV_4En-i_LshGDDa_kD7AcouNw7O3XaHgkYLOnePwHIHLH-dHoZb7Scp3wOXYu9E01s925xe"
|
||||
"G3s5tAttBGu7uyxfz7N6BQIDAQABKNKF2MwEEoADe9NAqNAxHpU13bMgz8LPySZJU8hY1RLwcfT"
|
||||
"UM47Xb3m-F-s2cfI7w08668f79kD45uRRzkVc8GbRIlVyzVC0WgIvtxEkYRKfgF_J7snUe2J2NN"
|
||||
"1FrkK7H3oYhcfPyYZH_SPZJr5HPoBFQTmS5A4l24U1dzQ6Z7_q-oS6uT0DiagTnzWhEg6AEnIkT"
|
||||
"sJtK3cZuKGYq3NDefZ7nslPuLXxdXl6SAEOtrk-RvCY6EBqYOuPUXgxXOEPbyM289R6aHQyPPYw"
|
||||
"qs9Pt9_E4BuMqCsbf5H5mLms9FA-wRx6mK2IaOboT4tf9_YObp3hVeL3WyxzXncETzJdE1GPGlO"
|
||||
"t_x5S_MylgJKbiWQYSdmqs3fzYExunw3wvI4tPHT_O8A_xKjyTEAvE5cBuCkfjwT716qUOzFUzF"
|
||||
"gZYLHnFiQLZekZUbUUlWY_CwU9Cv0UtxqQ6Oa835_Ug8_n1BwX6BPbmbcWe2Y19laSnDWg4JBNl"
|
||||
"F2CyP9N75jPtW9rVfjUSqKEPOwaIgwzNDkyMjM3NDcAAAA=";
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class Base64Test : public testing::Test {
|
||||
public:
|
||||
Base64Test() {}
|
||||
~Base64Test() {}
|
||||
|
||||
};
|
||||
|
||||
TEST_F(Base64Test, Base64MultipleOf24BitsTest)
|
||||
{
|
||||
// encodes string
|
||||
std::vector<uint8_t> message_vector(kMultipleOf24BitsData.begin(),
|
||||
kMultipleOf24BitsData.end());
|
||||
std::string message_b64 = Base64SafeEncode(message_vector);
|
||||
|
||||
// decodes string
|
||||
std::vector<uint8_t> result_vector = Base64SafeDecode(message_b64);
|
||||
std::string result;
|
||||
result.assign(result_vector.begin(), result_vector.end());
|
||||
EXPECT_STREQ(kMultipleOf24BitsData.data(), result.data());
|
||||
}
|
||||
|
||||
TEST_F(Base64Test, Base64OneByteOverTest)
|
||||
{
|
||||
// encodes string
|
||||
std::vector<uint8_t> message_vector(kOneByteOverData.begin(),
|
||||
kOneByteOverData.end());
|
||||
std::string message_b64 = Base64SafeEncode(message_vector);
|
||||
|
||||
// decodes string
|
||||
std::vector<uint8_t> result_vector = Base64SafeDecode(message_b64);
|
||||
std::string result;
|
||||
result.assign(result_vector.begin(), result_vector.end());
|
||||
EXPECT_STREQ(kOneByteOverData.data(), result.data());
|
||||
}
|
||||
|
||||
TEST_F(Base64Test, Base64TwoBytesOverTest)
|
||||
{
|
||||
// encodes string
|
||||
std::vector<uint8_t> message_vector(kTwoBytesOverData.begin(),
|
||||
kTwoBytesOverData.end());
|
||||
std::string message_b64 = Base64SafeEncode(message_vector);
|
||||
|
||||
// decodes string
|
||||
std::vector<uint8_t> result_vector = Base64SafeDecode(message_b64);
|
||||
std::string result;
|
||||
result.assign(result_vector.begin(), result_vector.end());
|
||||
EXPECT_STREQ(kTwoBytesOverData.data(), result.data());
|
||||
}
|
||||
|
||||
TEST_F(Base64Test, Base64EncodeTest)
|
||||
{
|
||||
// encodes string
|
||||
std::vector<uint8_t> message_vector(kTestData.begin(), kTestData.end());
|
||||
std::string message_b64 = Base64SafeEncode(message_vector);
|
||||
std::string result;
|
||||
result.assign(message_b64.begin(), message_b64.end());
|
||||
EXPECT_STREQ(kB64TestData.data(), result.data());
|
||||
|
||||
// decodes string
|
||||
std::vector<uint8_t> result_vector = Base64SafeDecode(message_b64);
|
||||
result.clear();
|
||||
result.assign(result_vector.begin(), result_vector.end());
|
||||
EXPECT_STREQ(kTestData.data(), result.data());
|
||||
}
|
||||
|
||||
TEST_F(Base64Test, Base64MultipleOf24BitsDecodeTest)
|
||||
{
|
||||
// decodes string
|
||||
std::vector<uint8_t> decoded_vector = Base64SafeDecode(kMultipleOf24BitsB64Data);
|
||||
std::string result;
|
||||
result.assign(decoded_vector.begin(), decoded_vector.end());
|
||||
EXPECT_STREQ(kMultipleOf24BitsData.data(), result.data());
|
||||
|
||||
// encodes string
|
||||
std::string b64_string = Base64SafeEncode(decoded_vector);
|
||||
EXPECT_STREQ(kMultipleOf24BitsB64Data.data(), b64_string.data());
|
||||
}
|
||||
|
||||
TEST_F(Base64Test, Base64OneByteOverDecodeTest)
|
||||
{
|
||||
// decodes string
|
||||
std::vector<uint8_t> decoded_vector = Base64SafeDecode(kOneByteOverB64Data);
|
||||
std::string result;
|
||||
result.assign(decoded_vector.begin(), decoded_vector.end());
|
||||
EXPECT_STREQ(kOneByteOverData.data(), result.data());
|
||||
|
||||
// encodes string
|
||||
std::string b64_string = Base64SafeEncode(decoded_vector);
|
||||
EXPECT_STREQ(kOneByteOverB64Data.data(), b64_string.data());
|
||||
}
|
||||
|
||||
TEST_F(Base64Test, Base64TwoBytesOverDecodeTest)
|
||||
{
|
||||
// decodes string
|
||||
std::vector<uint8_t> decoded_vector = Base64SafeDecode(kTwoBytesOverB64Data);
|
||||
std::string result;
|
||||
result.assign(decoded_vector.begin(), decoded_vector.end());
|
||||
EXPECT_STREQ(kTwoBytesOverData.data(), result.data());
|
||||
|
||||
// encodes string
|
||||
std::string b64_string = Base64SafeEncode(decoded_vector);
|
||||
EXPECT_STREQ(kTwoBytesOverB64Data.data(), b64_string.data());
|
||||
}
|
||||
|
||||
TEST_F(Base64Test, Base64ShortDecodeTest)
|
||||
{
|
||||
// decodes string
|
||||
std::vector<uint8_t> decoded_vector = Base64SafeDecode(kB64ShortString);
|
||||
|
||||
// encodes string
|
||||
std::string b64_string = Base64SafeEncode(decoded_vector);
|
||||
EXPECT_STREQ(kB64ShortString.data(), b64_string.data());
|
||||
}
|
||||
|
||||
TEST_F(Base64Test, Base64LongDecodeTest)
|
||||
{
|
||||
// decodes string
|
||||
std::vector<uint8_t> decoded_vector = Base64SafeDecode(kB64LongString);
|
||||
|
||||
// encodes string
|
||||
std::string b64_string = Base64SafeEncode(decoded_vector);
|
||||
EXPECT_STREQ(kB64LongString.data(), b64_string.data());
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
@@ -24,53 +24,55 @@ wvcdm::KeyId g_wrong_key_id;
|
||||
int g_use_full_path = 0; // cannot use boolean in getopt_long
|
||||
|
||||
static wvcdm::CdmProvisioningResponse kJsonResponse =
|
||||
"{\"signedResponse\": {"
|
||||
"\"message\": \"CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo"
|
||||
"TP751tvoADf86iLrf73mEzF58eSlaOjCpJRf2R3dojbNeSTy3JICmCc8vKtMjZRX9QWTvJbq_cg"
|
||||
"yMB8FQC8enuYhOaw1yJDYyCFHgik34NrUVUfmvaKKdSKQimqAZmjXi6P0znAn-XdPtz2xJVRxZp"
|
||||
"NH3QCD1bGcH_O1ercBW2JwF9KNalKFsxQrBhIwvyx-q-Ah4vf4r3M2HzY6JTHvcYGGc7dJNA3Xe"
|
||||
"WfCrYIvg0SGCP_z7Y2wICIA36VMwR3gnwNZlKkx6WGCCgsaU6IbLm4HpRBZfajuiOlasoYN4z1R"
|
||||
"lQ14Z32fdaFy8xOqLl-ZukxjWa7wv9zOSveH6JcHap1FS3R-RZ7E5WhfjxSTS0nWWZgmAjS2PkP"
|
||||
"9g4GPNsnpsrVymI39j6R6jPoc3__2EGN6qAvmp4pFKR7lQyslgNn2vYLuE0Ps5mIXVkxNiZOO3T"
|
||||
"jxgZyHaHOm1KmAZKI0EfddMATJCTt-UeLG3haqS_pYaBWcQ_xzWhoEHWU7_6ZaWrWemV8CVCg6s"
|
||||
"OB1SRI5MrkRBBSV0r8UKddLJGthZVjuTG75KK72KE9yhe86mCadvfVYe5keJ5GOC-t1EiFzBo4c"
|
||||
"4oqwkOCkkmYX_BEuZ3pOWztFp1_Br2Tl_fziw4O2vNIPCXB9yEewV6PkYPziTue3x4vRqD_mYjm"
|
||||
"1ia8fxISQnEC0vrqvrFFs9fLAHPlsvaRFnhv_XKpRwFoBdfqWTakb3k6uRz0Oh2SJ8euzFIyQNB"
|
||||
"efesMWk45DSrQjnlwlKXwZSiDKjAss0W2WwIb9F_x5LdB1Aa-CBudLVdxf62ggYaNZ57qx3YeHA"
|
||||
"jkqMGIF7Fq09D4OxM0jRsnrmXbJWKleUpJi7nHJgQGZk2ifN95gjuTNcRaGfYXMOsDoWdkrNAq0"
|
||||
"LScsPB06xEUR0DcO9vWx0zAEK7gsxxHziR7ZaYiIIkPysRR92r2NoLFPOUXf8j8ait-51jZmPKn"
|
||||
"bD6adieLy6ujSl907QsUgyGvokLs1OCsYHZr-X6vnyMjdk4G3QfmWwRepD_CMyXGvtLbTNCto7E"
|
||||
"L_M2yPZveAwYWwNlBtWK21gwIU2dgY298z7_S6jaQBc29f25sREjvN793ttYsPaeyom08qHYDnb"
|
||||
"jae3XX-2qqde6AGXlv__jO8WDZ5od6DWu2ThqV10ijVGFfGniRsSruzq0iq8zuAqTOGhmA9Dw7b"
|
||||
"rNlI95P4LpJA5pbjmNdnX7CQa2oHUuojmwlXRYuOA28PNEf-sc7ZPmMyFzedJi4EpkqzeQspEdH"
|
||||
"yNMf23iEjK6GOff7dgAaxg9vYHyprhkEml4BdmFVYwCYQy8o6KRcA0NgJb8c3tg4d3aRXWp6L-F"
|
||||
"sVhwqvq6FLOunSTNRIqhr2mOjRpU5w4mx-9GJRtk4XEcKT9YgUHGOUjGwfhQ5gBQDyZZVTddIUb"
|
||||
"MOThsSg7zr38oUCfgXeZaai3X2foKo1Bt94Q_q18dw5xNAN5e7rSwfilltHL23zbZduuhWkvp8S"
|
||||
"dag_NbO2C4IRMkzbjQBmiO9ixjXRhdqHlRRWcfR0wbQvEhD47egRVfnhKZ0W9G2-FGhyGuwJCq4"
|
||||
"CCAISEAfZ_94TqpXBImeAUzYhNr0Y48SbiwUijgIwggEKAoIBAQDRigR9nFm4mfBUh1Y3SGyOcF"
|
||||
"E-yK2NtfDiQe9l70KtkOeH4sB6MMB8g1QKPbUE8SBjPvXVJC_2DAWKjALzk4Aw-K-VmYe_Ag9CH"
|
||||
"JiS-XcfUYEGgK4jVMxadEq3LufEEREKUZnzjgQlR39dzgjFqIrC1bwfy3_99RsjPt6QpWPg36PI"
|
||||
"O4UKlmwBDTFzSOJB-4IV8Opy5Zv84BqPuyO9P5e3bXj_shRfy_XAGG2HGP_PpOCZWEfxuce0Iyu"
|
||||
"vpTPLQpTOgNw-VvUBGCWMZFoERopmqp_pQwWZ2a-EwlT_vvYY4SkuNjflBskR70xz4QzEo9665g"
|
||||
"k6I-HbHrTv29KEiAllAgMBAAEomSASgAIkKz1CSdFJVKcpO56jW0vsjKp92_cdqXBSEY3nuhzug"
|
||||
"_LFluMJx_IqATUcCOY-w6w0yKn2ezfZGE0MDIaCngEgQFI_DRoaSOBNNeirF59uYM0sK3P2eGS9"
|
||||
"G6F0l-OUXJdSO0b_LO8AbAK9LA3j7UHaajupJI1mdc4VtJfPRTsml2vIeKhDWXWaSvmeHgfF_tp"
|
||||
"-OV7oPuk6Ub26xpCp2He2rEAblCYEl25Zlz97K4DhyTOV5_xuSdSt-KbTLY9cWM5i9ncND1RzCc"
|
||||
"4qOixKarnMM5DdpZhs3B5xVj3yBAM1mVxPD2sZnqHSEN2EK7BMlHEnnyxhX0MGE36TQZR7P-I-G"
|
||||
"rUFCq8CCAESEDAxMjM0NTY3ODlBQkNERUYYspIEIo4CMIIBCgKCAQEApwA2YGXcvVRaKkC04RWU"
|
||||
"WBFPlFjd3qcfPCzgiAkpYVdnXlZ-7iePWTSaKqqdtE76p2rUyXpTwU6f4zT3PbfJEEdPKNo_zjF"
|
||||
"7_QYQ6_e-kvmv-z5o2u4aZEzzKfJznjnY9m_YsoCCcY61pPLCPs0KyrYEzZoTi1RzVCVUjL6Yem"
|
||||
"et2rNOs_qCqEpnmFZXVHHNEn_towHAaoskA5aIvpdmKrxTyYMGUVqIZRMY5Drta_FhW0zIHvTCr"
|
||||
"gheLV_4En-i_LshGDDa_kD7AcouNw7O3XaHgkYLOnePwHIHLH-dHoZb7Scp3wOXYu9E01s925xe"
|
||||
"G3s5tAttBGu7uyxfz7N6BQIDAQABKNKF2MwEEoADe9NAqNAxHpU13bMgz8LPySZJU8hY1RLwcfT"
|
||||
"UM47Xb3m-F-s2cfI7w08668f79kD45uRRzkVc8GbRIlVyzVC0WgIvtxEkYRKfgF_J7snUe2J2NN"
|
||||
"1FrkK7H3oYhcfPyYZH_SPZJr5HPoBFQTmS5A4l24U1dzQ6Z7_q-oS6uT0DiagTnzWhEg6AEnIkT"
|
||||
"sJtK3cZuKGYq3NDefZ7nslPuLXxdXl6SAEOtrk-RvCY6EBqYOuPUXgxXOEPbyM289R6aHQyPPYw"
|
||||
"qs9Pt9_E4BuMqCsbf5H5mLms9FA-wRx6mK2IaOboT4tf9_YObp3hVeL3WyxzXncETzJdE1GPGlO"
|
||||
"t_x5S_MylgJKbiWQYSdmqs3fzYExunw3wvI4tPHT_O8A_xKjyTEAvE5cBuCkfjwT716qUOzFUzF"
|
||||
"gZYLHnFiQLZekZUbUUlWY_CwU9Cv0UtxqQ6Oa835_Ug8_n1BwX6BPbmbcWe2Y19laSnDWg4JBNl"
|
||||
"F2CyP9N75jPtW9rVfjUSqKEPOwaIgwzNDkyMjM3NDcAAAA=\","
|
||||
"\"signature\": \"r-LpoZcbbr2KtoPaFnuWTVBh4Gup1k8vn0ClW2qm32A=\"}}";
|
||||
"{\"kind\": \"certificateprovisioning#certificateProvisioningResponse\","
|
||||
"\"signedResponse\": {"
|
||||
"\"message\": \"CtAJiVocnKtls7HO9SZtMg7-aEZosRT-qAjLnKt4FZ_5jvW-BEVBPNj1yeXh"
|
||||
"o_wla-VdgYQBRsnXuONH4Rh7Kg0T1mv3ybc2VIU4imZ46nW7FsZYRxz3EwWkFelIav9JDeHBlat"
|
||||
"qOKJEXtCiaySgdzNyZDDsZHxQu8Yh4ZeiHTVNHotoAiFy9qoUO0oaHpZ8xJxDkuN0u9-yauLdGy"
|
||||
"F8B3u2CJjO7ztbYC1a5b8NTTDzN3hKdXy9FcR0a5R5YDzvlCWj-TeEIFSop0t4QI7bGLaubXJ_K"
|
||||
"y7ppcLwM4LAOs12G4TyXa3R3A64rjF7tBdnbsA2S1ELpePWPK2jtvUu1TBWxS2bToU1PQrY8C9E"
|
||||
"bDaN7TpE92sOyrW1sIGkit03thAaRjHUl_JtLyqKCFUamjsQqCi7pk_4IvoFJiYKAfE5Wf5cbP0"
|
||||
"IrFW3CSkgYtO_d7jnyTUaweh9eP_8udIoUNkJjHMpglR_VJcWyIqw-hg8PqWm7AcYBUNDF0sYzM"
|
||||
"uMiw2uq4RSBq8vklZ_nJjVc2GZ1pSqpaLLghDpviGUbEoGGJP4I15pveCKbyOj6TfKbeiN2jgit"
|
||||
"1CaHMOmirOeSpD0gODGHQaTuVFbWQg-jgsbhht0NDHyqwmPDrCe78h2gXS6k0qDb3HL9M4QT5E4"
|
||||
"-E2cZLfmvtrDokugAwf0eZM14464Oi_o6NK-Gbic8q1VnI1Y7uDctVHPAlV2NjsCVoaALtpNQZ0"
|
||||
"HmaOF_jJ2RxCSyQCX1sxkRR_jeKyH10_i-GkXPokB3Z23LvHOheEVNmJJNvM9YBSw318aAiOIAo"
|
||||
"Wdffrfo9j4xDs34W-ndKn344BNUq8tGmo3EVhSg6pdLPcCWSpjbgQxnBe_cwmKJVKU39GPMFV0P"
|
||||
"vEeYKWljptsdUc9MH4XLomyalSPGCOkoVIwdPd1qXLhh1mmdNRm_7X-7zBezltjw42XKjtOKwBi"
|
||||
"_UFmgY6i4Mjbk9ShExjWpvgKNntWepIZGZhYVP1tt4H-wqsXMfupTviHf_FaQX3cadUbO_6imfa"
|
||||
"91MvnFe5v0iIi1HxsQzl0NmIByHFNUfHpzrZ0RzkmxsRY5lMLpOfdue7NJ5IZN1iVgiSS3llwa3"
|
||||
"2s2Yq-gCzqv7vwdIwAxZ__gOr42LR__KCB51g9xwXdVLH71Dv_WDgDv5LJas9MVz5vv_TDo9Gqf"
|
||||
"ZdEI8M2h0LbhdCVGxyYoeWVl6iHrPoeP6mJ5uaDZAAn_IdoVVPuCA8Wqmi_kbRdduwkuyasbIVx"
|
||||
"dMwR7cRJz5Rl5kw36kjJtOv4wy9NU41PgNNuW1_2dWtIeKOcBJH2-5KbQbnntbKNdcPqZhTaoci"
|
||||
"_3qDZtdlSqIJbDh7_4ZBkbcOHJ73jPKNOiLeDGD_rXFuPy4mAuxkZ8shXcw57xsIjz_OkB_Vr_2"
|
||||
"2JhNMicnjITZDTGZzN4xyPldtOINCQGBBpUBLOaiWEFePqG9m5uWv6_NtpPt3I_YQnfUzv2RLFe"
|
||||
"1pOLktoorTPWi8TH8hXnnmhHLMvd9r1Z1nISnZGmn_EhEFt-N-CCpiWAo5yQWIUA0iMqCdLHTub"
|
||||
"l-tuhHyLqmiBUU9O7Ynwl12k2nqJTbbeSCt1mueee8j5bITUGZ8KAAtbIe_k0XMl5mRZWVzoBw0"
|
||||
"RKmvBiWSRnOESEOdEAZw1ALRVjzEhSkDqGoIa7wkKsAIIAhIQB9n_3hOqlcEiZ4BTNiE2vRjgzd"
|
||||
"aLBSKOAjCCAQoCggEBAKfruetSk8ULthZLZCoqaViWPEdlO50Bi1ChIjXCiuGNp566o74R4r_ou"
|
||||
"8V7VX-Ze6p2Zpbm3iTPbB-vORaINQECi8TOA1xmxH0u7jrqHwfYX2a1EdCzp3DX5_lF2Rb44FI7"
|
||||
"gKW1BwIeUDOdaMKApxZOp-jt1MBsMaMZDWgvQ5EAULzESq04zsynylaScm-IaZrY0mC3ynGz2YW"
|
||||
"6TxbQKBUJsOUnpEEIDK2qlyEEESp0UFcTn2Lakda7MFI9wMAlH_qrDRdlDcLwGeD2qT8Lm4CFhV"
|
||||
"cNDm3nqpONmBCJlebqSvavf5tALlIdR5CdfFV64beT8zWG1J-Rz6AGmHYgCucCAwEAASiZIDABE"
|
||||
"oACFJ8UtXGiM__6SQFHXjN69lqzWfJKhplriW7m9wdCO-LEcCNFpUmzWBd1JppY11hL9AYO8OTI"
|
||||
"Habeb65R51q_SjulaxlJwureJAYe-cO7RTvQvCddK9SJaDxeBe367ot_pnXvh66tksCkfvakY7W"
|
||||
"igaDB8WVIaml6hmDADZgGAVfrPVWVPXMaY9LYg0r3r5PllfrmG7uXU0JcIa_0N-3MgT4QV4qIG2"
|
||||
"9K9givfjELYidRpDDXu8KKcw0NYu-bTUNRaNrx6yaIpSbozen5HHBVdb5gJFB-922gr9mqY1PF_"
|
||||
"-ulwvcXLn-Oa17QJ0YL1LkswDjVSC89MpJWkjW_qVlF8Rq2BQqwAggBEhCr7oe2E5WSaDltfP2r"
|
||||
"q_jyGK_qu4sFIo4CMIIBCgKCAQEAmtIaWtGjInLYjdQic5DPUGUgnZah-FZVFt0SwcH90u1UhQW"
|
||||
"eRgBH1psYrYqaaS8zu6vclTCT17E0WsDJlS8P8dBDlSMlyrNBZjCTDyczkI68tM5v0HXnpDCb5s"
|
||||
"RaCSlP7Dl9BV_67GZPFoVuw53QCaI8cQ4RHcNVupExNRcFiCc-HSb3tZO7yyZyoB1Yx6GJnlts3"
|
||||
"WA3A0DqVQIY79IGqdtTkKwydiNXVqR5ksMecqALwh9vKx6oSkxNat3I1BKQnzTY0pYChDjprabV"
|
||||
"O9dAM2fYwGELJYel9qQV6LT6ar0PbKCMGa9qR-YwYiR8-SN-jpvmKL0h98bhTsC65DUQswIDAQA"
|
||||
"BKJkgMAESgANZMzj1jPLFR8wnWKrTwL7dLF8qNJOPf7KDdJeqioqEkAe7mZiH3ZlQlE5EjPFGn5"
|
||||
"IytduT2SdPGZoAsMEavJb_nETlELNXW16bQSPKJXr1gYrzoJ86t2ePrbXOTDQUV9pIi9yCLgWYp"
|
||||
"r9PVx9osIRfd4qHg_MPEITQhlxvMvX51-_NxiQpdDf3HH-x2tMu6pELU7eruIPhBPxhrOJ9eWTK"
|
||||
"54w6ygZ0-RdEgJKRSCCtTcjjom1TJ_GimvqVJZ449LeVycc23BsJDkBNE9XOARSCHK0i3VDWTg-"
|
||||
"autjKClFWaVcyoNDqW6cfdODF7YyEYP8sBbYenMoJn5pizXaYvl3qc4LY8a-8HVFU6akgCvcj2w"
|
||||
"vaAMOdQvh5f5m6X33lLfE0w8R4JFkqxB7UC5Ev_nPDkyX1urg76t31_9qTmQAlxdoIhmBsPziSW"
|
||||
"jE3gII_qetwT7z1cPX8bGGPELwFiG5sQ6jQbHlGV-gaL2v-Peh6HZbGltsoI0wm7_N1i-BwiyUi"
|
||||
"BPHgGJ8=\","
|
||||
"\"signature\": \"fRiAeyze4AtvYXpxWImduHrJ1lZ4K0bRT1lk2jVIpcs=\"}}";
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -84,22 +86,23 @@ class WvCdmEngineTest : public testing::Test {
|
||||
void GenerateKeyRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
wvcdm::CdmAppParameterMap app_parameters;
|
||||
std::string server_url;
|
||||
EXPECT_EQ(cdm_engine_.GenerateKeyRequest(session_id_,
|
||||
true, // is_key_system_present
|
||||
key_system,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_), wvcdm::KEY_MESSAGE);
|
||||
&key_msg_,
|
||||
&server_url), wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
std::string server_url;
|
||||
EXPECT_EQ(cdm_engine_.GenerateRenewalRequest(session_id_,
|
||||
true, // is_key_system_init_data_present,
|
||||
key_system,
|
||||
init_data,
|
||||
&key_msg_),
|
||||
&key_msg_,
|
||||
&server_url),
|
||||
wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
@@ -147,18 +150,10 @@ class WvCdmEngineTest : public testing::Test {
|
||||
client_auth,
|
||||
200);
|
||||
if (is_renewal) {
|
||||
EXPECT_EQ(cdm_engine_.RenewKey(session_id_,
|
||||
true, // is_key_system_init_data_present
|
||||
g_key_system,
|
||||
init_data,
|
||||
resp), wvcdm::KEY_ADDED);
|
||||
EXPECT_EQ(cdm_engine_.RenewKey(session_id_, resp), wvcdm::KEY_ADDED);
|
||||
}
|
||||
else {
|
||||
EXPECT_EQ(cdm_engine_.AddKey(session_id_,
|
||||
true, // is_key_system_init_data_present
|
||||
g_key_system,
|
||||
init_data,
|
||||
resp), wvcdm::KEY_ADDED);
|
||||
EXPECT_EQ(cdm_engine_.AddKey(session_id_, resp), wvcdm::KEY_ADDED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
92
libwvdrmengine/cdm/core/test/device_files_unittest.cpp
Normal file
92
libwvdrmengine/cdm/core/test/device_files_unittest.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "device_files.h"
|
||||
#include "file_store.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
TEST(DeviceFilesTest, StoreCertificate) {
|
||||
std::string device_certificate_path =
|
||||
DeviceFiles::GetPath(DeviceFiles::kCencPath,
|
||||
DeviceFiles::kDeviceCertificateFileName);
|
||||
|
||||
if (!File::Exists(DeviceFiles::kCencPath))
|
||||
EXPECT_TRUE(File::CreateDirectory(DeviceFiles::kCencPath));
|
||||
if (File::Exists(device_certificate_path))
|
||||
EXPECT_TRUE(File::Remove(device_certificate_path));
|
||||
|
||||
char test_buf[1200];
|
||||
for (size_t i = 0; i < sizeof(test_buf); i++) {
|
||||
test_buf[i] = i % 128;
|
||||
}
|
||||
|
||||
size_t cert_len = 500;
|
||||
std::string certificate(&test_buf[0], cert_len);
|
||||
std::string wrapped_private_key(&test_buf[cert_len],
|
||||
sizeof(test_buf) - cert_len - 1);
|
||||
|
||||
EXPECT_TRUE(DeviceFiles::StoreCertificate(certificate, wrapped_private_key));
|
||||
EXPECT_TRUE(File::Exists(device_certificate_path));
|
||||
EXPECT_GT(File::FileSize(device_certificate_path),
|
||||
(ssize_t)sizeof(test_buf));
|
||||
}
|
||||
|
||||
TEST(DeviceFilesTest, StoreCertificateInitial) {
|
||||
std::string device_certificate_path =
|
||||
DeviceFiles::GetPath(DeviceFiles::kCencPath,
|
||||
DeviceFiles::kDeviceCertificateFileName);
|
||||
|
||||
if (File::Exists(DeviceFiles::kCencPath))
|
||||
EXPECT_TRUE(File::Remove(DeviceFiles::kIdmPath));
|
||||
|
||||
char test_buf[1200];
|
||||
for (size_t i = 0; i < sizeof(test_buf); i++) {
|
||||
test_buf[i] = i % 128;
|
||||
}
|
||||
|
||||
size_t cert_len = 500;
|
||||
std::string certificate(&test_buf[0], cert_len);
|
||||
std::string wrapped_private_key(&test_buf[cert_len],
|
||||
sizeof(test_buf) - cert_len - 1);
|
||||
|
||||
EXPECT_TRUE(DeviceFiles::StoreCertificate(certificate, wrapped_private_key));
|
||||
EXPECT_TRUE(File::Exists(device_certificate_path));
|
||||
EXPECT_GT(File::FileSize(device_certificate_path),
|
||||
(ssize_t)sizeof(test_buf));
|
||||
}
|
||||
|
||||
TEST(DeviceFilesTest, RetrieveCertificate) {
|
||||
std::string device_certificate_path =
|
||||
DeviceFiles::GetPath(DeviceFiles::kCencPath,
|
||||
DeviceFiles::kDeviceCertificateFileName);
|
||||
|
||||
if (File::Exists(DeviceFiles::kCencPath))
|
||||
EXPECT_TRUE(File::Remove(DeviceFiles::kIdmPath));
|
||||
|
||||
char test_buf[1200];
|
||||
for (size_t i = 0; i < sizeof(test_buf); i++) {
|
||||
test_buf[i] = i % 128;
|
||||
}
|
||||
|
||||
size_t cert_len = 500;
|
||||
std::string certificate(&test_buf[0], cert_len);
|
||||
std::string wrapped_private_key(&test_buf[cert_len],
|
||||
sizeof(test_buf) - cert_len - 1);
|
||||
|
||||
EXPECT_TRUE(DeviceFiles::StoreCertificate(certificate, wrapped_private_key));
|
||||
EXPECT_TRUE(File::Exists(device_certificate_path));
|
||||
EXPECT_GT(File::FileSize(device_certificate_path),
|
||||
(ssize_t)sizeof(test_buf));
|
||||
|
||||
std::string in_certificate;
|
||||
std::string in_wrapped_private_key;
|
||||
EXPECT_TRUE(DeviceFiles::RetrieveCertificate(&in_certificate,
|
||||
&in_wrapped_private_key));
|
||||
EXPECT_TRUE(memcmp(certificate.data(), in_certificate.data(),
|
||||
certificate.size()) == 0);
|
||||
EXPECT_TRUE(memcmp(wrapped_private_key.data(), in_wrapped_private_key.data(),
|
||||
wrapped_private_key.size()) == 0);
|
||||
}
|
||||
|
||||
}
|
||||
174
libwvdrmengine/cdm/core/test/file_store_unittest.cpp
Normal file
174
libwvdrmengine/cdm/core/test/file_store_unittest.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "file_store.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace {
|
||||
// TODO(rfrias): Make this work for non-unix paths
|
||||
const std::string kDataDrmDir = "/data/drm";
|
||||
const std::string kIdmTestDir = "/data/drm/IDMtest";
|
||||
const std::string kCencTestDir = "/data/drm/IDMtest/CENCtest";
|
||||
const std::string kCencTestDirWithSlash = "/data/drm/IDMtest/CENCtest/";
|
||||
const std::string kTestFile01 = "/data/drm/IDMtest/CENCtest/file01.txt";
|
||||
|
||||
const std::string kFileExists = "/system/bin/sh";
|
||||
const std::string kDirExists = "/system/bin";
|
||||
const std::string kFileDoesNotExist = "/system/bin/shxyxyxy";
|
||||
const std::string kDirDoesNotExist = "/system/binxyxyxy";
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
TEST(FileTest, FileExists) {
|
||||
EXPECT_TRUE(File::Exists(kFileExists));
|
||||
EXPECT_TRUE(File::Exists(kDirExists));
|
||||
EXPECT_FALSE(File::Exists(kFileDoesNotExist));
|
||||
EXPECT_FALSE(File::Exists(kDirDoesNotExist));
|
||||
}
|
||||
|
||||
TEST(FileTest, CreateDirectory) {
|
||||
if (File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::Remove(kIdmTestDir));
|
||||
EXPECT_FALSE(File::Exists(kCencTestDir));
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
EXPECT_TRUE(File::Remove(kIdmTestDir));
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDirWithSlash));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
EXPECT_TRUE(File::Remove(kIdmTestDir));
|
||||
}
|
||||
|
||||
TEST(FileTest, RemoveDir) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
EXPECT_TRUE(File::Remove(kCencTestDir));
|
||||
EXPECT_FALSE(File::Exists(kCencTestDir));
|
||||
}
|
||||
|
||||
TEST(FileTest, OpenFileUsingConstructor) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
File::Remove(kTestFile01);
|
||||
File file(kTestFile01, File::kCreate);
|
||||
EXPECT_TRUE(file.IsOpen());
|
||||
file.Close();
|
||||
EXPECT_TRUE(File::Exists(kTestFile01));
|
||||
}
|
||||
|
||||
TEST(FileTest, OpenFile) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
File::Remove(kTestFile01);
|
||||
File file;
|
||||
file.Open(kTestFile01, File::kCreate);
|
||||
EXPECT_TRUE(file.IsOpen());
|
||||
file.Close();
|
||||
EXPECT_TRUE(File::Exists(kTestFile01));
|
||||
}
|
||||
|
||||
TEST(FileTest, RemoveDirAndFile) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
File file(kTestFile01, File::kCreate);
|
||||
EXPECT_TRUE(file.IsOpen());
|
||||
file.Close();
|
||||
EXPECT_TRUE(File::Remove(kTestFile01));
|
||||
EXPECT_TRUE(File::Remove(kCencTestDir));
|
||||
EXPECT_FALSE(File::Exists(kTestFile01));
|
||||
EXPECT_FALSE(File::Exists(kCencTestDir));
|
||||
}
|
||||
|
||||
TEST(FileTest, IsDir) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
File file(kTestFile01, File::kCreate);
|
||||
EXPECT_TRUE(file.IsOpen());
|
||||
file.Close();
|
||||
EXPECT_TRUE(File::Exists(kTestFile01));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
EXPECT_FALSE(File::IsDirectory(kTestFile01));
|
||||
EXPECT_TRUE(File::IsDirectory(kCencTestDir));
|
||||
}
|
||||
|
||||
TEST(FileTest, IsRegularFile) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
File file(kTestFile01, File::kCreate);
|
||||
EXPECT_TRUE(file.IsOpen());
|
||||
file.Close();
|
||||
EXPECT_TRUE(File::Exists(kTestFile01));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
EXPECT_TRUE(File::IsRegularFile(kTestFile01));
|
||||
EXPECT_FALSE(File::IsRegularFile(kCencTestDir));
|
||||
}
|
||||
|
||||
TEST(FileTest, WriteReadTextFile) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
File::Remove(kTestFile01);
|
||||
|
||||
const char* test_string = "This is a test";
|
||||
File file1(kTestFile01, File::kCreate);
|
||||
EXPECT_TRUE(file1.IsOpen());
|
||||
EXPECT_TRUE(file1.Write(test_string, strlen(test_string)+1));
|
||||
file1.Close();
|
||||
EXPECT_TRUE(File::Exists(kTestFile01));
|
||||
|
||||
char buf[100];
|
||||
File file2(kTestFile01, File::kReadOnly);
|
||||
EXPECT_TRUE(file2.IsOpen());
|
||||
EXPECT_EQ((ssize_t)strlen(test_string)+1, file2.Read(buf, sizeof(buf)));
|
||||
file2.Close();
|
||||
EXPECT_STREQ(test_string, buf);
|
||||
}
|
||||
|
||||
TEST(FileTest, WriteReadBinaryFile) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
EXPECT_TRUE(File::Exists(kCencTestDir));
|
||||
File::Remove(kTestFile01);
|
||||
|
||||
unsigned char test_buf[600];
|
||||
for (size_t i = 0; i < sizeof(test_buf); i++) {
|
||||
test_buf[i] = i % 128;
|
||||
}
|
||||
File file1(kTestFile01, File::kCreate | File::kBinary);
|
||||
EXPECT_TRUE(file1.IsOpen());
|
||||
EXPECT_TRUE(file1.Write(test_buf, sizeof(test_buf)));
|
||||
file1.Close();
|
||||
EXPECT_TRUE(File::Exists(kTestFile01));
|
||||
|
||||
char buf[1000];
|
||||
File file2(kTestFile01, File::kReadOnly);
|
||||
EXPECT_TRUE(file2.IsOpen());
|
||||
EXPECT_EQ((ssize_t)sizeof(test_buf), file2.Read(buf, sizeof(buf)));
|
||||
file2.Close();
|
||||
EXPECT_TRUE(memcmp(test_buf, buf, sizeof(test_buf)) == 0);
|
||||
}
|
||||
|
||||
TEST(FileTest, FileSize) {
|
||||
if (!File::Exists(kCencTestDir))
|
||||
EXPECT_TRUE(File::CreateDirectory(kCencTestDir));
|
||||
File::Remove(kTestFile01);
|
||||
|
||||
unsigned char test_buf[600];
|
||||
for (size_t i = 0; i < sizeof(test_buf); i++) {
|
||||
test_buf[i] = i % 128;
|
||||
}
|
||||
File file1(kTestFile01, File::kCreate | File::kBinary);
|
||||
EXPECT_TRUE(file1.IsOpen());
|
||||
EXPECT_TRUE(file1.Write(test_buf, sizeof(test_buf)));
|
||||
file1.Close();
|
||||
EXPECT_TRUE(File::Exists(kTestFile01));
|
||||
|
||||
EXPECT_EQ((ssize_t)sizeof(test_buf), File::FileSize(kTestFile01));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -79,20 +79,24 @@ TEST(LicenseTestSession, InitNullSession) {
|
||||
TEST_F(LicenseTest, PrepareKeyRequest) {
|
||||
std::string signed_request;
|
||||
CdmAppParameterMap app_parameters;
|
||||
std::string server_url;
|
||||
license_.PrepareKeyRequest(a2bs_hex(kInitData),
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&signed_request);
|
||||
&signed_request,
|
||||
&server_url);
|
||||
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, HandleKeyResponseValid) {
|
||||
std::string signed_request;
|
||||
CdmAppParameterMap app_parameters;
|
||||
std::string server_url;
|
||||
license_.PrepareKeyRequest(a2bs_hex(kInitData),
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&signed_request);
|
||||
&signed_request,
|
||||
&server_url);
|
||||
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
|
||||
EXPECT_TRUE(license_.HandleKeyResponse(a2bs_hex(kValidResponse)));
|
||||
}
|
||||
@@ -100,10 +104,12 @@ TEST_F(LicenseTest, HandleKeyResponseValid) {
|
||||
TEST_F(LicenseTest, HandleKeyResponseInvalid) {
|
||||
std::string signed_request;
|
||||
CdmAppParameterMap app_parameters;
|
||||
std::string server_url;
|
||||
license_.PrepareKeyRequest(a2bs_hex(kInitData),
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&signed_request);
|
||||
&signed_request,
|
||||
&server_url);
|
||||
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
|
||||
EXPECT_FALSE(license_.HandleKeyResponse(a2bs_hex(kInvalidResponse)));
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "http_socket.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
@@ -49,7 +50,7 @@ void UrlRequest::AppendChunkToUpload(const std::string& data) {
|
||||
int UrlRequest::GetResponse(std::string& response) {
|
||||
response.clear();
|
||||
|
||||
const int kTimeoutInMs = 1500;
|
||||
const int kTimeoutInMs = 1500 * 2;
|
||||
int bytes = 0;
|
||||
int total_bytes = 0;
|
||||
do {
|
||||
@@ -102,5 +103,29 @@ bool UrlRequest::PostRequest(const std::string& data) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UrlRequest::AppendData(const std::string& data) {
|
||||
request_.append(data);
|
||||
request_.append("\r\n"); // marks end of data
|
||||
}
|
||||
|
||||
bool UrlRequest::PostCertRequest(const std::string& data) {
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
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: ");
|
||||
request_.append(UintToString(data.size()));
|
||||
request_.append("\r\n"); // empty line to terminate header
|
||||
request_.append("\r\n"); // terminates the request
|
||||
|
||||
AppendData(data);
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -17,10 +17,12 @@ class UrlRequest {
|
||||
~UrlRequest();
|
||||
|
||||
void AppendChunkToUpload(const std::string& data);
|
||||
void AppendData(const std::string& data);
|
||||
int GetResponse(std::string& response);
|
||||
int GetStatusCode(const std::string& response);
|
||||
bool is_connected() const { return is_connected_; }
|
||||
bool PostRequest(const std::string& data);
|
||||
bool PostCertRequest(const std::string& data);
|
||||
|
||||
private:
|
||||
static const unsigned int kHttpBufferSize = 4096;
|
||||
|
||||
@@ -27,7 +27,8 @@ class WvContentDecryptionModule {
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request);
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// Accept license response and extract key info.
|
||||
virtual CdmResponseType AddKey(const CdmSessionId& session_id,
|
||||
|
||||
206
libwvdrmengine/cdm/src/file_store.cpp
Normal file
206
libwvdrmengine/cdm/src/file_store.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// File class - provides a simple android specific file implementation
|
||||
|
||||
#include "file_store.h"
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace {
|
||||
const char* kCurrentDirectory = ".";
|
||||
const char* kParentDirectory = "..";
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class File::Impl {
|
||||
public:
|
||||
FILE* file_;
|
||||
};
|
||||
|
||||
File::File() : impl_(new File::Impl()) {}
|
||||
|
||||
File::File(const std::string& file_path, int flags) :
|
||||
impl_(new File::Impl()) {
|
||||
Open(file_path, flags);
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
Close();
|
||||
delete impl_;
|
||||
}
|
||||
|
||||
bool File::Open(const std::string& name, int flags) {
|
||||
std::string openFlags = "";
|
||||
|
||||
if (((flags & File::kTruncate) && Exists(name)) ||
|
||||
((flags & File::kCreate) && !Exists(name))) {
|
||||
FILE* fp = fopen(name.c_str(), "w+");
|
||||
if (fp) {
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & File::kBinary) {
|
||||
openFlags = (flags & File::kReadOnly)? "rb" : "rb+";
|
||||
} else {
|
||||
openFlags = (flags & File::kReadOnly)? "r" : "r+";
|
||||
}
|
||||
|
||||
impl_->file_ = fopen(name.c_str(), openFlags.c_str());
|
||||
if (!impl_->file_) {
|
||||
LOGW("File::Open: fopen failed: %d", errno);
|
||||
}
|
||||
return IsOpen();
|
||||
}
|
||||
|
||||
void File::Close() {
|
||||
if (impl_->file_) {
|
||||
fclose(impl_->file_);
|
||||
impl_->file_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool File::IsOpen() {
|
||||
return impl_->file_ != NULL;
|
||||
}
|
||||
|
||||
bool File::IsBad() {
|
||||
if (impl_->file_)
|
||||
return ferror(impl_->file_) != 0;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t File::Read(void* buffer, size_t bytes) {
|
||||
if (impl_->file_) {
|
||||
size_t len = fread(buffer, 1, bytes, impl_->file_);
|
||||
if (len == 0) {
|
||||
LOGW("File::Read: fread failed: %d", errno);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
LOGW("File::Read: file not open");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ssize_t File::Write(const void* buffer, size_t bytes) {
|
||||
if (impl_->file_) {
|
||||
size_t len = fwrite(buffer, 1, bytes, impl_->file_);
|
||||
if (len == 0) {
|
||||
LOGW("File::Write: fwrite failed: %d", errno);
|
||||
}
|
||||
return len;
|
||||
}
|
||||
LOGW("File::Write: file not open");
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool File::Exists(const std::string& file) {
|
||||
struct stat buf;
|
||||
int res = stat(file.c_str(), &buf) == 0;
|
||||
if (!res) {
|
||||
LOGV("File::Exists: stat failed: %d", errno);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool File::Remove(const std::string& file_path) {
|
||||
if (IsDirectory(file_path)) {
|
||||
DIR* dir;
|
||||
if ((dir = opendir(file_path.c_str())) != NULL) {
|
||||
// first remove files and dir within it
|
||||
struct dirent* entry;
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strcmp(entry->d_name, kCurrentDirectory) &&
|
||||
(strcmp(entry->d_name, kParentDirectory))) {
|
||||
std::string file_path_to_remove = file_path + '/';
|
||||
file_path_to_remove += entry->d_name;
|
||||
if (!Remove(file_path_to_remove)) {
|
||||
closedir(dir);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
if (rmdir(file_path.c_str())) {
|
||||
LOGW("File::Remove: rmdir failed: %d", errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
if (unlink(file_path.c_str())) {
|
||||
LOGW("File::Remove: unlink failed: %d", errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(std::string path) {
|
||||
size_t size = path.size();
|
||||
if ((size == 1) && (path[0] == '/'))
|
||||
return true;
|
||||
|
||||
if (size <= 1)
|
||||
return false;
|
||||
|
||||
if (path.at(size-1) == '/') {
|
||||
--size;
|
||||
path.resize(size);
|
||||
}
|
||||
|
||||
size_t pos = path.find('/', 1);
|
||||
while (pos < size) {
|
||||
path.at(pos) = '\0';
|
||||
if (mkdir(path.c_str(), 0775) != 0) {
|
||||
if (errno != EEXIST) {
|
||||
LOGW("File::CreateDirectory: mkdir failed: %d\n", errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
path.at(pos) = '/';
|
||||
pos = path.find('/', pos+1);
|
||||
}
|
||||
if (mkdir(path.c_str(), 0775) != 0) {
|
||||
if (errno != EEXIST) {
|
||||
LOGW("File::CreateDirectory: mkdir failed: %d\n", errno);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::IsDirectory(const std::string& path) {
|
||||
struct stat buf;
|
||||
if (stat(path.c_str(), &buf) == 0)
|
||||
return buf.st_mode & S_IFDIR;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
bool File::IsRegularFile(const std::string& path) {
|
||||
struct stat buf;
|
||||
if (stat(path.c_str(), &buf) == 0)
|
||||
return buf.st_mode & S_IFREG;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
ssize_t File::FileSize(const std::string& file_path) {
|
||||
struct stat buf;
|
||||
if (stat(file_path.c_str(), &buf) == 0)
|
||||
return buf.st_size;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
@@ -34,20 +34,19 @@ CdmResponseType WvContentDecryptionModule::GenerateKeyRequest(
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request) {
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
CdmKeySystem key_system;
|
||||
return cdm_engine_->GenerateKeyRequest(session_id, false, key_system,
|
||||
init_data, license_type,
|
||||
app_parameters, key_request);
|
||||
app_parameters, key_request,
|
||||
server_url);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::AddKey(
|
||||
const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data) {
|
||||
CdmKeySystem key_system;
|
||||
CdmInitData init_data;
|
||||
return cdm_engine_->AddKey(session_id, false, key_system,
|
||||
init_data, key_data);
|
||||
return cdm_engine_->AddKey(session_id, key_data);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::CancelKeyRequest(
|
||||
|
||||
@@ -3,10 +3,22 @@
|
||||
#
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
test_name := base64_test
|
||||
test_src_dir := ../core/test
|
||||
include $(LOCAL_PATH)/unit-test.mk
|
||||
|
||||
test_name := cdm_engine_test
|
||||
test_src_dir := ../core/test
|
||||
include $(LOCAL_PATH)/unit-test.mk
|
||||
|
||||
test_name := device_files_unittest
|
||||
test_src_dir := ../core/test
|
||||
include $(LOCAL_PATH)/unit-test.mk
|
||||
|
||||
test_name := file_store_unittest
|
||||
test_src_dir := ../core/test
|
||||
include $(LOCAL_PATH)/unit-test.mk
|
||||
|
||||
test_name := http_socket_test
|
||||
test_src_dir := ../core/test
|
||||
include $(LOCAL_PATH)/unit-test.mk
|
||||
|
||||
@@ -23,6 +23,9 @@ 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
|
||||
|
||||
static const std::string kDefaultProvisioningServerUrl =
|
||||
"http://www-googleapis-test.sandbox.google.com/certificateprovisioning/v1/devicecertificates/create";
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -46,11 +49,14 @@ class WvCdmRequestLicenseTest : public testing::Test {
|
||||
void GenerateKeyRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
wvcdm::CdmAppParameterMap app_parameters;
|
||||
std::string server_url;
|
||||
EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_), wvcdm::KEY_MESSAGE);
|
||||
&key_msg_,
|
||||
&server_url), wvcdm::KEY_MESSAGE);
|
||||
EXPECT_EQ(0, server_url.size());
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(const std::string& key_system,
|
||||
@@ -58,11 +64,14 @@ class WvCdmRequestLicenseTest : public testing::Test {
|
||||
// TODO application makes a license request, CDM will renew the license
|
||||
// when appropriate.
|
||||
wvcdm::CdmAppParameterMap app_parameters;
|
||||
std::string server_url;
|
||||
EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_), wvcdm::KEY_MESSAGE);
|
||||
&key_msg_,
|
||||
&server_url), wvcdm::KEY_MESSAGE);
|
||||
EXPECT_NE(0, server_url.size());
|
||||
}
|
||||
|
||||
// posts a request and extracts the drm message from the response
|
||||
@@ -101,6 +110,34 @@ class WvCdmRequestLicenseTest : public testing::Test {
|
||||
return drm_msg;
|
||||
}
|
||||
|
||||
// posts a request and extracts the drm message from the response
|
||||
std::string GetCertRequestResponse(const std::string& server_url,
|
||||
int expected_response) {
|
||||
UrlRequest url_request(server_url, g_port);
|
||||
|
||||
if (!url_request.is_connected()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
url_request.PostCertRequest(key_msg_);
|
||||
std::string response;
|
||||
int resp_bytes = url_request.GetResponse(response);
|
||||
LOGD("size=%u, response start: %s", response.size(),
|
||||
response.substr(0, 1024).c_str());
|
||||
LOGD("end: %s", response.substr(response.size() - 256).c_str());
|
||||
LOGD("end %d bytes response dump", resp_bytes);
|
||||
|
||||
// Youtube server returns 400 for invalid message while play server returns
|
||||
// 500, so just test inequity here for invalid message
|
||||
int status_code = url_request.GetStatusCode(response);
|
||||
if (expected_response == 200) {
|
||||
EXPECT_EQ(200, status_code);
|
||||
} else {
|
||||
EXPECT_NE(200, status_code);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
void VerifyKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
std::string& init_data,
|
||||
@@ -124,6 +161,19 @@ class WvCdmRequestLicenseTest : public testing::Test {
|
||||
std::string session_id_;
|
||||
};
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, ProvisioningTest) {
|
||||
decryptor_.OpenSession(g_key_system, &session_id_);
|
||||
std::string provisioning_server_url = "";
|
||||
|
||||
decryptor_.GetProvisioningRequest(&key_msg_, &provisioning_server_url);
|
||||
EXPECT_STREQ(provisioning_server_url.data(), kDefaultProvisioningServerUrl.data());
|
||||
|
||||
std::string response = GetCertRequestResponse(kDefaultProvisioningServerUrl, 200);
|
||||
if (!response.empty())
|
||||
decryptor_.HandleProvisioningResponse(response);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, BaseMessageTest) {
|
||||
decryptor_.OpenSession(g_key_system, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
|
||||
@@ -145,13 +145,16 @@ status_t WVDrmPlugin::getKeyRequest(
|
||||
}
|
||||
|
||||
CdmKeyMessage keyRequest;
|
||||
string cdmDefaultUrl;
|
||||
|
||||
CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, cdmInitData,
|
||||
cdmLicenseType,
|
||||
cdmParameters, &keyRequest);
|
||||
cdmParameters, &keyRequest,
|
||||
&cdmDefaultUrl);
|
||||
|
||||
if (isCdmResponseTypeSuccess(res)) {
|
||||
defaultUrl.clear();
|
||||
defaultUrl.setTo(cdmDefaultUrl.data(), cdmDefaultUrl.size());
|
||||
|
||||
request.clear();
|
||||
request.appendArray(reinterpret_cast<const uint8_t*>(keyRequest.data()),
|
||||
|
||||
@@ -27,11 +27,11 @@ class MockCDM : public WvContentDecryptionModule {
|
||||
|
||||
MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&));
|
||||
|
||||
MOCK_METHOD5(GenerateKeyRequest, CdmResponseType(const CdmSessionId&,
|
||||
MOCK_METHOD6(GenerateKeyRequest, CdmResponseType(const CdmSessionId&,
|
||||
const CdmInitData&,
|
||||
const CdmLicenseType,
|
||||
CdmAppParameterMap&,
|
||||
CdmKeyMessage*));
|
||||
CdmKeyMessage*, string*));
|
||||
|
||||
MOCK_METHOD2(AddKey, CdmResponseType(const CdmSessionId&,
|
||||
const CdmKeyResponse&));
|
||||
@@ -179,7 +179,6 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
initData.appendArray(initDataRaw, kInitDataSize);
|
||||
|
||||
CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize);
|
||||
Vector<uint8_t> request;
|
||||
|
||||
KeyedVector<String8, String8> parameters;
|
||||
CdmAppParameterMap cdmParameters;
|
||||
@@ -191,7 +190,7 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
parameters.add(String8("answer"), String8("42"));
|
||||
cdmParameters["answer"] = "42";
|
||||
|
||||
String8 defaultUrl;
|
||||
static const char* kDefaultUrl = "http://google.com/";
|
||||
|
||||
{
|
||||
InSequence calls;
|
||||
@@ -199,19 +198,25 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeOffline, cdmParameters, _))
|
||||
kLicenseTypeOffline, cdmParameters, _,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<4>(cdmRequest),
|
||||
SetArgPointee<5>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeStreaming, cdmParameters,
|
||||
_))
|
||||
_, _))
|
||||
.WillOnce(DoAll(SetArgPointee<4>(cdmRequest),
|
||||
SetArgPointee<5>(kDefaultUrl),
|
||||
Return(wvcdm::KEY_MESSAGE)));
|
||||
}
|
||||
|
||||
Vector<uint8_t> request;
|
||||
String8 defaultUrl;
|
||||
|
||||
status_t res = plugin.getKeyRequest(sessionId, initData,
|
||||
String8("video/h264"),
|
||||
DrmPlugin::kKeyType_Offline,
|
||||
@@ -219,7 +224,7 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_TRUE(defaultUrl.isEmpty());
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
|
||||
res = plugin.getKeyRequest(sessionId, initData, String8("video/h264"),
|
||||
DrmPlugin::kKeyType_Streaming, parameters,
|
||||
@@ -227,7 +232,7 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_TRUE(defaultUrl.isEmpty());
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, AddsKeys) {
|
||||
@@ -319,7 +324,7 @@ TEST_F(WVDrmPluginTest, GetsProvisioningRequests) {
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_EQ(String8(kDefaultUrl), defaultUrl);
|
||||
EXPECT_STREQ(kDefaultUrl, defaultUrl.string());
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) {
|
||||
|
||||
24
libwvdrmengine/run_all_unit_tests.sh
Executable file
24
libwvdrmengine/run_all_unit_tests.sh
Executable file
@@ -0,0 +1,24 @@
|
||||
#!/bin/sh
|
||||
|
||||
if [ -z "$ANDROID_BUILD_TOP" ]; then
|
||||
echo "Android build environment not set"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
echo "waiting for device"
|
||||
adb root && adb wait-for-device remount && adb sync
|
||||
|
||||
adb shell /system/bin/request_license_test
|
||||
adb shell /system/bin/policy_engine_unittest
|
||||
adb shell /system/bin/libwvdrmmediacrypto_test
|
||||
adb shell /system/bin/libwvdrmdrmplugin_test
|
||||
adb shell /system/bin/cdm_engine_test
|
||||
adb shell /system/bin/oemcrypto_test
|
||||
adb shell /system/bin/file_store_unittest
|
||||
adb shell /system/bin/device_files_unittest
|
||||
adb shell LD_LIBRARY_PATH=/system/vendor/lib/mediadrm/ /system/bin/libwvdrmengine_test
|
||||
|
||||
adb shell am start com.widevine.test/com.widevine.test.MediaDrmAPITest
|
||||
# TODO: make this test more command line friendly
|
||||
echo "check logcat output for MediaDrmAPITest"
|
||||
|
||||
Reference in New Issue
Block a user