am c0dde166: Merge "Widevine CENC drm engine update" into jb-mr2-dev

* commit 'c0dde166445b228d321258470661591dacc964fb':
  Widevine CENC drm engine update
This commit is contained in:
Jeff Tinker
2013-04-12 15:46:20 -07:00
committed by Android Git Automerger
63 changed files with 2885 additions and 1134 deletions

View File

@@ -4,12 +4,12 @@
LOCAL_PATH := $(call my-dir)
# -----------------------------------------------------------------------------
# Builds cdm_protos.a
# Builds libcdm_protos.a
# Generates *.a, *.pb.h and *.pb.cc for *.proto files.
#
include $(CLEAR_VARS)
LOCAL_MODULE := cdm_protos
LOCAL_MODULE := libcdm_protos
LOCAL_MODULE_CLASS := STATIC_LIBRARIES
LOCAL_C_INCLUDES := \
@@ -18,13 +18,19 @@ LOCAL_C_INCLUDES := \
LOCAL_SRC_FILES := $(call all-proto-files-under, cdm/core/src)
# $(call local-intermediates-dir)/proto/$(LOCAL_PATH)/cdm/core/src is used 21
# to locate *.pb.h by cdm source
# $(call local-intermediates-dir)/proto is used to locate *.pb.h included 23
# by *.pb.cc
LOCAL_EXPORT_C_INCLUDE_DIRS := \
$(call local-intermediates-dir)/proto \
$(call local-intermediates-dir)/proto/$(LOCAL_PATH)/cdm/core/src
include $(BUILD_STATIC_LIBRARY)
# proto_generated_headers is a build system internal variable defined in $(BUILD_STATIC_LIBRARY).
# We can use cdm_proto_gen_headers later to establish the dependency.
# proto_generated_headers is a build system internal variable defined in
# $(BUILD_STATIC_LIBRARY). We can use cdm_proto_gen_headers later to establish
# the dependency.
cdm_proto_gen_headers := $(proto_generated_headers)
# -----------------------------------------------------------------------------
@@ -49,6 +55,7 @@ LOCAL_C_INCLUDES := \
vendor/widevine/libwvdrmengine/include \
vendor/widevine/libwvdrmengine/mediacrypto/include \
vendor/widevine/libwvdrmengine/mediadrm/include \
vendor/widevine/libwvdrmengine/oemcrypto/include \
LOCAL_STATIC_LIBRARIES := \
libcdm \
@@ -65,8 +72,7 @@ LOCAL_SHARED_LIBRARIES := \
libutils \
libstagefright_foundation \
LOCAL_WHOLE_STATIC_LIBRARIES := \
cdm_protos
LOCAL_WHOLE_STATIC_LIBRARIES := libcdm_protos
LOCAL_ADDITIONAL_DEPENDENCIES := $(cdm_proto_gen_headers)

View File

@@ -0,0 +1,61 @@
#!/bin/sh
if [ -z "$ANDROID_BUILD_TOP" ]; then
echo "Android build environment not set"
exit -1
fi
. $ANDROID_BUILD_TOP/build/envsetup.sh
cd $ANDROID_BUILD_TOP/external/gtest/
pwd
mm
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test/gmock
pwd
mm
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/cdm/core/test
pwd
mm
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/cdm/test
pwd
mm
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/mediacrypto/test
pwd
mm
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/mediadrm/test
pwd
mm
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/oemcrypto/test
pwd
mm
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test
pwd
mm
cd $ANDROID_BUILD_TOP/vendor/widevine/libwvdrmengine/test/java/src/com/widevine/test
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/http_socket_test
adb shell /system/bin/cdm_engine_test
adb shell /system/bin/oemcrypto_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"

View File

@@ -15,7 +15,7 @@ LOCAL_C_INCLUDES += \
external/protobuf/src \
../oemcrypto/include
LOCAL_STATIC_LIBRARIES := cdm_protos
LOCAL_STATIC_LIBRARIES := libcdm_protos
LOCAL_ADDITIONAL_DEPENDENCIES := $(cdm_proto_gen_headers)
SRC_DIR := src

View File

@@ -3,6 +3,7 @@
#ifndef CDM_BASE_CDM_ENGINE_H_
#define CDM_BASE_CDM_ENGINE_H_
#include "crypto_engine.h"
#include "timer.h"
#include "wv_cdm_types.h"
@@ -15,13 +16,13 @@ typedef std::map<CdmSessionId, CdmSession*> CdmSessionMap;
class CdmEngine : public TimerHandler {
public:
CdmEngine() {}
CdmEngine();
~CdmEngine();
// Session related methods
CdmResponseType OpenSession(const CdmKeySystem& key_system,
CdmSessionId* session_id);
CdmResponseType CloseSession(CdmSessionId& session_id);
CdmResponseType CloseSession(const CdmSessionId& session_id);
// License related methods
// Construct a valid license request
@@ -66,6 +67,10 @@ class CdmEngine : public TimerHandler {
CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
CdmQueryMap* key_info);
// Query seesion control information
CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id,
CdmQueryMap* key_info);
// Provisioning related methods
CdmResponseType GetProvisioningRequest(CdmProvisioningRequest* request,
std::string* default_url);
@@ -92,20 +97,31 @@ class CdmEngine : public TimerHandler {
bool IsKeyValid(const KeyId& key_id);
// Event listener related methods
bool AttachEventListener(CdmSessionId& session_id,
bool AttachEventListener(const CdmSessionId& session_id,
WvCdmEventListener* listener);
bool DetachEventListener(CdmSessionId& session_id,
bool DetachEventListener(const CdmSessionId& session_id,
WvCdmEventListener* listener);
private:
// private methods
bool ValidateKeySystem(const CdmKeySystem& key_system);
// Cancel all sessions
bool CancelSessions();
void CleanupProvisioingSessions(CdmSession* cdm_session,
CryptoEngine* crypto_engine,
const CdmSessionId& cdm_session_id);
void ComposeJsonRequest(const std::string& message,
const std::string& signature,
CdmProvisioningRequest* request);
// Parse a blob of multiple concatenated PSSH atoms to extract the first
// widevine pssh
// TODO(gmorgan): This should be done by the user of this class.
bool ExtractWidevinePssh(const CdmInitData& init_data,
CdmInitData* output);
bool ParseJsonResponse(const CdmProvisioningResponse& json_str,
const std::string& start_substr,
const std::string& end_substr,
std::string* result);
bool ValidateKeySystem(const CdmKeySystem& key_system);
// timer related methods to drive policy decisions
void EnablePolicyTimer();

View File

@@ -18,12 +18,10 @@ namespace wvcdm {
class CdmSession {
public:
CdmSession() : session_id_(GenerateSessionId()),
license_received_(false),
properties_valid_(false) {}
CdmSession() : session_id_(GenerateSessionId()), license_received_(false) {}
~CdmSession() {}
bool Init();
CdmResponseType Init();
bool DestroySession();
@@ -35,7 +33,9 @@ class CdmSession {
bool VerifySession(const CdmKeySystem& key_system,
const CdmInitData& init_data);
CdmResponseType GenerateKeyRequest(const CdmInitData& init_data,
CdmResponseType GenerateKeyRequest(const CdmInitData& pssh_data,
const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* key_request);
// AddKey() - Accept license response and extract key info.
@@ -47,6 +47,9 @@ class CdmSession {
// Query license information
CdmResponseType QueryKeyStatus(CdmQueryMap* key_info);
// Query session control info
CdmResponseType QueryKeyControlInfo(CdmQueryMap* key_info);
// Decrypt() - Accept encrypted buffer and return decrypted data.
CdmResponseType Decrypt(bool is_encrypted,
const KeyId& key_id,
@@ -77,6 +80,8 @@ class CdmSession {
// Generate unique ID for each new session.
CdmSessionId GenerateSessionId();
bool LoadDeviceCertificate(std::string* cert, std::string* wrapped_key);
// instance variables
const CdmSessionId session_id_;
CdmKeySystem key_system_;
@@ -85,11 +90,11 @@ class CdmSession {
PolicyEngine policy_engine_;
bool license_received_;
bool properties_valid_;
bool require_explicit_renew_request_;
KeyId key_id_;
// Used for certificate based licensing
std::string wrapped_key_;
std::set<WvCdmEventListener*> listeners_;
// TODO(kqyang): CdmKey not defined yet

View File

@@ -5,6 +5,8 @@
#ifndef CDM_BASE_CRYPTO_ENGINE_H_
#define CDM_BASE_CRYPTO_ENGINE_H_
#include <string>
#include "crypto_session.h"
#include "lock.h"
#include "wv_cdm_types.h"
@@ -46,13 +48,7 @@ class CryptoEngine {
} SecurityLevel;
SecurityLevel GetSecurityLevel();
bool properties_valid() const { return properties_valid_; }
bool oem_crypto_use_secure_buffers() const
{ return oem_crypto_use_secure_buffers_; }
bool oem_crypto_use_fifo() const { return oem_crypto_use_fifo_; }
bool oem_crypto_use_userspace_buffers() const
{ return oem_crypto_use_userspace_buffers_; }
std::string GetDeviceUniqueId();
private:
@@ -69,11 +65,6 @@ private:
mutable Lock sessions_lock_;
CryptoSessionMap sessions_;
bool properties_valid_;
bool oem_crypto_use_secure_buffers_;
bool oem_crypto_use_fifo_;
bool oem_crypto_use_userspace_buffers_;
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine);
};

View File

@@ -35,8 +35,6 @@ private:
std::string key_data_;
std::string key_control_;
std::string key_control_iv_;
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoKey);
};
}; // namespace wvcdm

View File

@@ -44,11 +44,24 @@ class CryptoSession {
const std::string& mac_key,
int num_keys,
const CryptoKey* key_array);
bool LoadCertificatePrivateKey(std::string& wrapped_key);
bool RefreshKeys(const std::string& message,
const std::string& signature,
int num_keys,
const CryptoKey* key_array);
bool GenerateNonce(uint32_t* nonce);
bool GenerateDerivedKeys(const std::string& message);
bool GenerateDerivedKeys(const std::string& message,
const std::string& session_key);
bool GenerateSignature(const std::string& message,
std::string* signature);
bool RewrapDeviceRSAKey(const std::string& message,
const uint32_t* nonce,
const uint8_t* enc_rsa_key,
size_t enc_rsa_key_length,
const uint8_t* enc_rsa_key_iv,
uint8_t* wrapped_rsa_key,
size_t* wrapped_rsa_key_length);
// Media data path
bool SelectKey(const std::string& key_id);
@@ -61,6 +74,7 @@ class CryptoSession {
bool is_video);
private:
static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature
void GenerateMacContext(const std::string& input_context,
std::string* deriv_context);

View File

@@ -9,6 +9,7 @@
namespace wvcdm {
using video_widevine_server::sdk::LicenseIdentification;
using video_widevine_server::sdk::SignedMessage;
class CryptoSession;
class PolicyEngine;
@@ -23,19 +24,26 @@ class CdmLicense {
bool Init(const std::string& token, CryptoSession* session,
PolicyEngine* policy_engine);
bool PrepareKeyRequest(const CdmInitData& init_data,
bool PrepareKeyRequest(const CdmInitData& pssh_data,
const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* signed_request);
bool PrepareKeyRenewalRequest(CdmKeyMessage* signed_request);
bool HandleKeyResponse(const CdmKeyResponse& license_response);
bool HandleKeyRenewalResponse(const CdmKeyResponse& license_response);
CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response);
CdmResponseType HandleKeyRenewalResponse(
const CdmKeyResponse& license_response);
private:
CdmResponseType HandleKeyErrorResponse(const SignedMessage& signed_message);
LicenseIdentification license_id_;
CryptoSession* session_;
PolicyEngine* policy_engine_;
std::string token_;
// Used for certificate based licensing
CdmKeyMessage key_request_;
CORE_DISALLOW_COPY_AND_ASSIGN(CdmLicense);
};

View File

@@ -35,16 +35,23 @@ class Lock {
};
// Manages the lock automatically. It will be locked when AutoLock
// is constructed and release when AutoLock goes out of scope
// is constructed and release when AutoLock goes out of scope.
class AutoLock {
public:
explicit AutoLock(Lock& lock);
explicit AutoLock(Lock* lock);
~AutoLock();
explicit AutoLock(Lock& lock) : lock_(&lock) {
lock_->Acquire();
}
explicit AutoLock(Lock* lock) : lock_(lock) {
lock_->Acquire();
}
~AutoLock() {
lock_->Release();
}
private:
class Impl;
Impl *impl_;
Lock *lock_;
CORE_DISALLOW_COPY_AND_ASSIGN(AutoLock);
};

View File

@@ -101,9 +101,6 @@ class PolicyEngine {
int64_t next_renewal_time_;
int64_t policy_max_duration_seconds_;
bool properties_valid_;
bool begin_license_usage_when_received_;
Clock* clock_;
// For testing

View File

@@ -11,39 +11,62 @@
namespace wvcdm {
typedef std::map<std::string, bool> CdmBooleanPropertiesMap;
struct CdmBooleanProperties {
std::string name;
bool value;
};
// This class saves information about features and properties enabled
// for a given platform. At initialization it reads in properties from
// for a given platform. At initialization it initializes properties from
// property_configuration.h. That file specifies features selected for each
// platform. Core CDM can then query enabled features though the GetProperty
// method and tailor its behaviour in a non-platform specific way.
//
// Additional features can be added at runtime as long as the key names do
// not clash. Also, only boolean properties are supported at this time, though
// it should be trivial to in support for other datatypes.
// platform. Core CDM can then query enabled features though specific getter
// methods.
// Setter methods are provided but their only planned use is for testing.
class Properties {
public:
static Properties* GetInstance();
static void Init();
static inline bool begin_license_usage_when_received() {
return begin_license_usage_when_received_;
}
static inline bool require_explicit_renew_request() {
return require_explicit_renew_request_;
}
static inline bool oem_crypto_use_secure_buffers() {
return oem_crypto_use_secure_buffers_;
}
static inline bool oem_crypto_use_fifo() {
return oem_crypto_use_fifo_;
}
static inline bool oem_crypto_use_userspace_buffers() {
return oem_crypto_use_userspace_buffers_;
}
static inline bool use_certificates_as_identification() {
return use_certificates_as_identification_;
}
// value argument is only set if the property was found (true is returned)
bool GetProperty(std::string& key, bool& value);
private:
Properties();
~Properties() {}
static void set_begin_license_usage_when_received(bool flag) {
begin_license_usage_when_received_ = flag;
}
static void set_require_explicit_renew_request(bool flag) {
require_explicit_renew_request_ = flag;
}
static void set_oem_crypto_use_secure_buffers(bool flag) {
oem_crypto_use_secure_buffers_ = flag;
}
static void set_oem_crypto_use_fifo(bool flag) {
oem_crypto_use_fifo_ = flag;
}
static void set_oem_crypto_use_userspace_buffers(bool flag) {
oem_crypto_use_userspace_buffers_ = flag;
}
static void set_use_certificates_as_identification(bool flag) {
use_certificates_as_identification_ = flag;
}
void SetProperty(std::string& key, bool value);
static Properties* instance_;
static Lock properties_lock_;
CdmBooleanPropertiesMap boolean_properties_;
static bool begin_license_usage_when_received_;
static bool require_explicit_renew_request_;
static bool oem_crypto_use_secure_buffers_;
static bool oem_crypto_use_fifo_;
static bool oem_crypto_use_userspace_buffers_;
static bool use_certificates_as_identification_;
CORE_DISALLOW_COPY_AND_ASSIGN(Properties);
};

View File

@@ -16,24 +16,6 @@ static const size_t KEY_PAD_SIZE = 16;
static const size_t KEY_SIZE = 16;
static const size_t MAC_KEY_SIZE = 32;
// define boolean property keys here
// If false begin license usage on first playback
static std::string kPropertyKeyBeginLicenseUsageWhenReceived =
"WVBeginLicenseUsageWhenReceived";
// If false, calls to Generate Key request, after the first one,
// will result in a renewal request being generated
static std::string kPropertyKeyRequireExplicitRenewRequest =
"WVRequireExplicitRenewRequest";
// Set only one of the three below to true. If secure buffer
// is selected, fallback to userspace buffers may occur
// if L1/L2 OEMCrypto APIs fail
static std::string kPropertyKeyOemCryptoUseSecureBuffers =
"WVBeginLicenseOemCryptoUseSecureBuffer";
static std::string kPropertyKeyOemCryptoUseFifo =
"WVBeginLicenseOemCryptoUseFifo";
static std::string kPropertyKeyOemCryptoUseUserSpaceBuffers =
"WVBeginLicenseOemCryptoUseUserSpaceBuffers";
// define query keys, values here
static const std::string QUERY_KEY_LICENSE_TYPE = "LicenseType";
// "Streaming", "Offline"
@@ -49,8 +31,12 @@ static const std::string QUERY_KEY_PLAYBACK_DURATION_REMAINING =
"PlaybackDurationRemaining"; // non-negative integer
static const std::string QUERY_KEY_RENEWAL_SERVER_URL = "RenewalServerUrl";
// url
static const std::string QUERY_KEY_OEMCRYPTO_SESSION_ID = "OemCryptoSessionId";
// session id
static const std::string QUERY_KEY_SECURITY_LEVEL = "SecurityLevel";
// "L1", "L3"
static const std::string QUERY_KEY_DEVICE_ID = "DeviceID";
// device unique id
static const std::string QUERY_VALUE_TRUE = "True";
static const std::string QUERY_VALUE_FALSE = "False";

View File

@@ -35,6 +35,8 @@ enum CdmResponseType {
KEY_MESSAGE,
NEED_KEY,
KEY_CANCELED,
NEED_PROVISIONING,
DEVICE_REVOKED,
};
#define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \

View File

@@ -7,7 +7,10 @@
#include "buffer_reader.h"
#include "cdm_session.h"
#include "crypto_engine.h"
#include "license_protocol.pb.h"
#include "log.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_event_listener.h"
@@ -17,7 +20,17 @@
namespace wvcdm {
typedef std::map<CdmSessionId,CdmSession*>::iterator CdmSessionIter;
// Protobuf generated classes.
using video_widevine_server::sdk::ClientIdentification;
using video_widevine_server::sdk::ProvisioningRequest;
using video_widevine_server::sdk::ProvisioningResponse;
using video_widevine_server::sdk::SignedProvisioningMessage;
typedef std::map<CdmSessionId,CdmSession*>::const_iterator CdmSessionIter;
CdmEngine::CdmEngine() {
Properties::Init();
}
CdmEngine::~CdmEngine() {
CancelSessions();
@@ -57,10 +70,11 @@ CdmResponseType CdmEngine::OpenSession(
CdmSessionId new_session_id = new_session->session_id();
if (!new_session->Init()) {
CdmResponseType sts = new_session->Init();
if (sts != NO_ERROR) {
LOGE("CdmEngine::OpenSession: bad session init");
delete(new_session);
return UNKNOWN_ERROR;
return sts;
}
sessions_[new_session_id] = new_session;
@@ -68,7 +82,7 @@ CdmResponseType CdmEngine::OpenSession(
return NO_ERROR;
}
CdmResponseType CdmEngine::CloseSession(CdmSessionId& session_id) {
CdmResponseType CdmEngine::CloseSession(const CdmSessionId& session_id) {
LOGI("CdmEngine::CloseSession");
CdmSessionIter iter = sessions_.find(session_id);
@@ -123,6 +137,8 @@ CdmResponseType CdmEngine::GenerateKeyRequest(
// TODO(edwinwong, rfrias): need to pass in license type and app parameters
CdmResponseType sts = iter->second->GenerateKeyRequest(extracted_pssh,
license_type,
app_parameters,
key_request);
if (KEY_MESSAGE != sts) {
@@ -168,7 +184,7 @@ CdmResponseType CdmEngine::AddKey(
if (KEY_ADDED != sts) {
LOGE("CdmEngine::AddKey: keys not added, result = %d", (int)sts);
}
EnablePolicyTimer();
return sts;
}
@@ -195,7 +211,7 @@ CdmResponseType CdmEngine::CancelKeyRequest(
}
// TODO(edwinwong, rfrias): unload keys here
DisablePolicyTimer();
return NO_ERROR;
}
@@ -299,6 +315,8 @@ CdmResponseType CdmEngine::QueryStatus(CdmQueryMap* key_info) {
return KEY_ERROR;
}
(*key_info)[QUERY_KEY_DEVICE_ID] = crypto_engine->GetDeviceUniqueId();
return NO_ERROR;
}
@@ -314,16 +332,313 @@ CdmResponseType CdmEngine::QueryKeyStatus(
return iter->second->QueryKeyStatus(key_info);
}
CdmResponseType CdmEngine::QueryKeyControlInfo(
const CdmSessionId& session_id,
CdmQueryMap* key_info) {
LOGI("CdmEngine::QueryKeyControlInfo");
CdmSessionIter iter = sessions_.find(session_id);
if (iter == sessions_.end()) {
LOGE("CdmEngine::QueryKeyControlInfo: session_id not found = %s", session_id.c_str());
return KEY_ERROR;
}
return iter->second->QueryKeyControlInfo(key_info);
}
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;
}
/*
* This function converts message and signature into base64 format.
* It then wraps it in JSON format expected by the Apiary frontend.
* Apiary requires the base64 encoding to replace '+' with minus '-',
* and '/' with underscore '_'; opposite to stubby's.
*
* Returns the JSON formated string in *request.
* The JSON formated request takes the following format:
* {
* 'signedRequest': {
* 'message': 'base64 encoded message',
* 'signature': 'base64 encoded signature'
* }
* }
*/
void CdmEngine::ComposeJsonRequest(
const std::string& message,
const std::string& signature,
CdmProvisioningRequest* request) {
// 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());
}
/*
* Composes a device provisioning request and output the request in JSON format
* in *request. It also returns the default url for the provisioning server
* in *default_url.
*
* Returns NO_ERROR for success and UNKNOWN_ERROR if fails.
*/
CdmResponseType CdmEngine::GetProvisioningRequest(
CdmProvisioningRequest* request,
std::string* default_url) {
// TODO(edwinwong, rfrias): add implementation
if (!request || !default_url) {
LOGE("CdmEngine::GetProvisioningRequest: invalid input parameters");
return UNKNOWN_ERROR;
}
//
//---------------------------------------------------------------------------
// This function can be called before a cdm session is created.
// First creates a cdm session, then creates a crypto session.
//
CdmSession* cdm_session = new CdmSession();
if (!cdm_session) {
LOGE("CdmEngine::GetProvisioningRequest: fails to create a cdm session");
return UNKNOWN_ERROR;
}
if (cdm_session->session_id().empty()) {
LOGE("CdmEngine::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");
delete cdm_session;
return UNKNOWN_ERROR;
}
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");
delete cdm_session;
return UNKNOWN_ERROR;
}
//
//---------------------------------------------------------------------------
// Prepares device provisioning request.
//
ProvisioningRequest provisioning_request;
ClientIdentification* client_id = provisioning_request.mutable_client_id();
client_id->set_type(ClientIdentification::KEYBOX);
std::string token;
if (!crypto_engine->GetToken(&token)) {
LOGE("CdmEngine::GetProvisioningRequest: fails to get token");
CleanupProvisioingSessions(cdm_session, crypto_engine, 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");
crypto_engine->DestroySession(cdm_session_id);
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
return UNKNOWN_ERROR;
}
provisioning_request.set_nonce(UintToString(nonce));
// Serializes the provisioning request.
std::string serialized_request;
provisioning_request.SerializeToString(&serialized_request);
// Derives signing and encryption keys and constructs signature.
std::string request_signature;
if (!crypto_session->PrepareRequest(serialized_request, &request_signature)) {
request->clear();
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
return UNKNOWN_ERROR;
}
if (request_signature.empty()) {
request->clear();
CleanupProvisioingSessions(cdm_session, crypto_engine, 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();
//
//---------------------------------------------------------------------------
// Closes the cdm session.
//
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
return NO_ERROR;
}
/*
* Parses the input json_str and locates substring using start_substr and
* end_stubstr. The found base64 substring is then decoded and returns
* in *result.
*
* Returns true for success and false if fails.
*/
bool CdmEngine::ParseJsonResponse(
const CdmProvisioningResponse& json_str,
const std::string& start_substr,
const std::string& end_substr,
std::string* result) {
std::string b64_string;
size_t start = json_str.find(start_substr);
if (start == json_str.npos) {
LOGE("ParseJsonResponse: cannot find start substring");
return false;
} else {
size_t end = json_str.find(end_substr, start + start_substr.length());
if (end == json_str.npos) {
LOGE("ParseJsonResponse cannot locate end substring");
return false;
}
size_t b64_string_size = end - start - start_substr.length();
b64_string.assign(json_str, start + start_substr.length(), b64_string_size);
// 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
std::vector<uint8_t> result_vector = Base64SafeDecode(b64_string);
result->assign(result_vector.begin(), result_vector.end());
return true;
}
/*
* The response message consists of a device certificate and the device RSA key.
* The device RSA key is stored in the T.E.E. The device certificate is stored
* in the device.
*
* Returns NO_ERROR for success and UNKNOWN_ERROR if fails.
*/
CdmResponseType CdmEngine::HandleProvisioningResponse(
CdmProvisioningResponse& response) {
// TODO(edwinwong, rfrias): add implementation
if (response.empty()) {
LOGE("CdmEngine::HandleProvisioningResponse: Empty provisioning response.");
return UNKNOWN_ERROR;
}
//---------------------------------------------------------------------------
// Extracts response from JSON string, decodes base64 signed message
const std::string kMessageStart = "\"message\": \"";
const std::string kMessageEnd = "\",";
std::string signed_message;
if (!ParseJsonResponse(response, kMessageStart, kMessageEnd, &signed_message)) {
LOGE("Fails to extract signed message from JSON response");
return UNKNOWN_ERROR;
}
// Extracts signature from JSON string, decodes base64 signature
const std::string kSignatureStart = "\"signature\": \"";
const std::string kSignatureEnd = "\"";
std::string signature;
if (!ParseJsonResponse(response, kSignatureStart, kSignatureEnd, &signature)) {
LOGE("Fails to extract signature from JSON response");
return UNKNOWN_ERROR;
}
//
//---------------------------------------------------------------------------
// First creates a cdm session, then creates a crypto 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;
return UNKNOWN_ERROR;
}
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
if (!crypto_engine) {
LOGE("CdmEngine::HandleProvisioningResponse: fails to create a crypto engine");
delete cdm_session;
return UNKNOWN_ERROR;
}
CdmSessionId cdm_session_id = cdm_session->session_id();
CryptoSession* crypto_session = crypto_engine->CreateSession(cdm_session_id);
if (!crypto_session) {
LOGE("CdmEngine::HandleProvisioningResponse: fails to create a crypto session");
delete cdm_session;
return UNKNOWN_ERROR;
}
//---------------------------------------------------------------------------
// Authenticates provisioning response using D1s (server key derived from
// the provisioing request's input). Validate provisioning response and
// stores private device RSA key and certificate.
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);
return UNKNOWN_ERROR;
}
// TODO(edwinwong): stores private device RSA key and cert
const std::string& device_certificate = provisioning_response.device_certificate();
//
//---------------------------------------------------------------------------
// Closes the cdm session.
//
CleanupProvisioingSessions(cdm_session, crypto_engine, cdm_session_id);
return NO_ERROR;
}
@@ -371,7 +686,7 @@ bool CdmEngine::IsKeyValid(const KeyId& key_id) {
}
bool CdmEngine::AttachEventListener(
CdmSessionId& session_id,
const CdmSessionId& session_id,
WvCdmEventListener* listener) {
CdmSessionIter iter = sessions_.find(session_id);
@@ -383,7 +698,7 @@ bool CdmEngine::AttachEventListener(
}
bool CdmEngine::DetachEventListener(
CdmSessionId& session_id,
const CdmSessionId& session_id,
WvCdmEventListener* listener) {
CdmSessionIter iter = sessions_.find(session_id);

View File

@@ -4,6 +4,7 @@
#include "cdm_session.h"
#include <iostream>
#include <sstream>
#include "clock.h"
#include "crypto_engine.h"
@@ -16,31 +17,32 @@ namespace wvcdm {
typedef std::set<WvCdmEventListener*>::iterator CdmEventListenerIter;
bool CdmSession::Init() {
CdmResponseType CdmSession::Init() {
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
if (!crypto_engine) {
LOGE("CdmSession::Init failed to get CryptoEngine instance.");
return false;
return UNKNOWN_ERROR;
}
crypto_session_ = crypto_engine->CreateSession(session_id_);
if (!crypto_session_) {
return false;
return UNKNOWN_ERROR;
}
std::string token;
if (!crypto_engine->GetToken(&token)) return false;
if (!Properties::GetInstance()->GetProperty(
kPropertyKeyRequireExplicitRenewRequest,
require_explicit_renew_request_)) {
LOGE("CdmSession::Init: Unable to access property - require explicit renew");
if (Properties::use_certificates_as_identification()) {
if (!LoadDeviceCertificate(&token, &wrapped_key_))
return NEED_PROVISIONING;
}
else {
properties_valid_ = true;
if (!crypto_engine->GetToken(&token))
return UNKNOWN_ERROR;
}
return license_parser_.Init(token, crypto_session_, &policy_engine_);
if (license_parser_.Init(token, crypto_session_, &policy_engine_))
return NO_ERROR;
else
return UNKNOWN_ERROR;
}
bool CdmSession::DestroySession() {
@@ -58,13 +60,11 @@ bool CdmSession::VerifySession(const CdmKeySystem& key_system,
return true;
}
CdmResponseType CdmSession::GenerateKeyRequest(const CdmInitData& init_data,
CdmKeyMessage* key_request) {
if (!properties_valid_) {
LOGW("CdmSession::GenerateKeyRequest: Unable to access properties");
return UNKNOWN_ERROR;
}
CdmResponseType CdmSession::GenerateKeyRequest(
const CdmInitData& pssh_data,
const CdmLicenseType license_type,
CdmAppParameterMap& app_parameters,
CdmKeyMessage* key_request) {
if (!crypto_session_) {
LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session");
return UNKNOWN_ERROR;
@@ -76,11 +76,19 @@ CdmResponseType CdmSession::GenerateKeyRequest(const CdmInitData& init_data,
}
if (license_received_) {
return require_explicit_renew_request_ ?
return Properties::require_explicit_renew_request() ?
UNKNOWN_ERROR : GenerateRenewalRequest(key_request);
}
else {
if(!license_parser_.PrepareKeyRequest(init_data, key_request)) {
if (Properties::use_certificates_as_identification()) {
if (!crypto_session_->LoadCertificatePrivateKey(wrapped_key_))
return NEED_PROVISIONING;
}
if (!license_parser_.PrepareKeyRequest(pssh_data,
license_type,
app_parameters,
key_request)) {
return KEY_ERROR;
} else {
return KEY_MESSAGE;
@@ -101,16 +109,16 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
}
if (license_received_) {
return require_explicit_renew_request_ ?
return Properties::require_explicit_renew_request() ?
UNKNOWN_ERROR : RenewKey(key_response);
}
else {
if (!license_parser_.HandleKeyResponse(key_response)) {
return KEY_ERROR;
} else {
CdmResponseType sts = license_parser_.HandleKeyResponse(key_response);
if (sts == KEY_ADDED)
license_received_ = true;
return KEY_ADDED;
}
return sts;
}
}
@@ -118,6 +126,16 @@ CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) {
return policy_engine_.Query(key_info);
}
CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) {
if ((!crypto_session_) || (!crypto_session_->IsOpen()))
return UNKNOWN_ERROR;
std::stringstream ss;
ss << crypto_session_->oec_session_id();
(*key_info)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str();
return NO_ERROR;
}
// CancelKeyRequest() - Cancel session.
CdmResponseType CdmSession::CancelKeyRequest() {
// TODO(gmorgan): cancel and clean up session
@@ -144,12 +162,12 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted,
key_id_ = key_id;
}
else {
return UNKNOWN_ERROR;
return NEED_KEY;
}
}
}
return crypto_session_->Decrypt(is_encrypted, encrypt_buffer, encrypt_length,
return crypto_session_->Decrypt(is_encrypted, encrypt_buffer, encrypt_length,
iv, block_offset, decrypt_buffer, is_video);
}
@@ -157,7 +175,7 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted,
// GenerateRenewalRequest() - Construct valid renewal request for the current
// session keys.
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request) {
if(!license_parser_.PrepareKeyRenewalRequest(key_request)) {
if (!license_parser_.PrepareKeyRenewalRequest(key_request)) {
return KEY_ERROR;
} else {
return KEY_MESSAGE;
@@ -166,11 +184,7 @@ CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request) {
// RenewKey() - Accept renewal response and update key info.
CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
if (!license_parser_.HandleKeyRenewalResponse(key_response)) {
return KEY_ERROR;
} else {
return KEY_ADDED;
}
return license_parser_.HandleKeyRenewalResponse(key_response);
}
bool CdmSession::IsKeyValid(const KeyId& key_id) {
@@ -187,6 +201,12 @@ CdmSessionId CdmSession::GenerateSessionId() {
return kSessionPrefix + IntToString(++session_num);
}
bool CdmSession::LoadDeviceCertificate(std::string* certificate,
std::string* wrapped_key) {
// TODO(edwingwong,rfrias): Need to read in the private key
return false;
}
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {
std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener);
return result.second;

View File

@@ -1,43 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Author: tinskip@google.com (Thomas Inskip)
//
// Description:
// Public protocol buffer definitions for Widevine Device Certificate
// Provisioning protocol.
syntax = "proto2";
package video_widevine_server.sdk;
import "vendor/widevine/libwvdrmengine/cdm/core/src/client_identification.proto";
option optimize_for = LITE_RUNTIME;
// Provisioning request sent by client devices to provisioning service.
message ProvisioningRequest {
// Device root of trust and other client identification. Required.
optional ClientIdentification client_id = 1;
// Nonce value used to prevent replay attacks. Required.
optional bytes nonce = 2;
}
// Provisioning response sent by the provisioning server to client devices.
message ProvisioningResponse {
// AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded.
// Required.
optional bytes device_rsa_key = 1;
// Initialization vector used to encrypt device_rsa_key. Required.
optional bytes device_rsa_key_iv = 2;
// Serialized DeviceCertificate. Required.
optional bytes device_certificate = 3;
// Nonce value matching nonce in ProvisioningRequest. Required.
optional bytes nonce = 4;
}
// Serialized ProvisioningRequest or ProvisioningResponse signed with
// The message authentication key.
message SignedProvisioningMessage {
// Serialized ProvisioningRequest or ProvisioningResponse. Required.
optional bytes message = 1;
// HMAC-SHA256 signature of message. Required.
optional bytes signature = 2;
}

View File

@@ -0,0 +1,27 @@
// ----------------------------------------------------------------------------
// client_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 DeviceCertificateFileFormat {
optional int32 version = 1;
optional bytes certificate = 2;
optional bytes wrapped_private_key = 3;
}
message LicenseFileFormat {
optional int32 version = 1;
optional bytes key_set_id = 2;
optional bytes license_request = 3;
optional bytes license = 4;
}

View File

@@ -1,30 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Author: tinskip@google.com (Thomas Inskip)
//
// Description:
// ClientIdentification message used by provisioning and license protocols.
syntax = "proto2";
package video_widevine_server.sdk;
option java_outer_classname = "ClientIdentificationProtos";
option optimize_for = LITE_RUNTIME;
// ClientIdentification message used to authenticate the client device.
message ClientIdentification {
enum TokenType {
KEYBOX = 0;
}
message NameValue {
optional string name = 1;
optional string value = 2;
}
// Type of factory-provisioned device root of trust. Optional.
optional TokenType type = 1 [default = KEYBOX];
// Factory-provisioned device root of trust. Required.
optional bytes token = 2;
// Optional client information name/value pairs.
repeated NameValue client_info = 3;
}

View File

@@ -6,6 +6,7 @@
#include "crypto_engine.h"
#include <iostream>
#include <vector>
#include "log.h"
#include "OEMCryptoCENC.h"
@@ -24,11 +25,7 @@ Lock CryptoEngine::crypto_engine_lock_;
// CryptoEngine methods
CryptoEngine::CryptoEngine() : initialized_(false),
properties_valid_(false),
oem_crypto_use_secure_buffers_(false),
oem_crypto_use_fifo_(false),
oem_crypto_use_userspace_buffers_(false) {}
CryptoEngine::CryptoEngine() : initialized_(false) {}
CryptoEngine::~CryptoEngine() {
if (initialized_) {
@@ -66,29 +63,6 @@ void CryptoEngine::DeleteInstance() {
}
bool CryptoEngine::Init() {
properties_valid_ = true;
if (!Properties::GetInstance()->GetProperty(
kPropertyKeyOemCryptoUseSecureBuffers,
oem_crypto_use_secure_buffers_)) {
LOGW("CryptoEngine::CryptoEngine: Unable to access property - oemcrypto use secure buffers");
properties_valid_ = false;
}
if (!Properties::GetInstance()->GetProperty(
kPropertyKeyOemCryptoUseFifo,
oem_crypto_use_fifo_)) {
LOGW("CryptoEngine::CryptoEngine: Unable to access property - oemcrypto use fifos");
properties_valid_ = false;
}
if (!Properties::GetInstance()->GetProperty(
kPropertyKeyOemCryptoUseUserSpaceBuffers,
oem_crypto_use_userspace_buffers_)) {
LOGW("CryptoEngine::CryptoEngine: Unable to access property - oemcrypto use userspace buffers");
properties_valid_ = false;
}
LOGV("CryptoEngine::Init: Lock");
AutoLock auto_lock(crypto_lock_);
if (!initialized_) {
@@ -221,4 +195,19 @@ CryptoEngine::SecurityLevel CryptoEngine::GetSecurityLevel() {
return kSecurityLevelUnknown;
}
std::string CryptoEngine::GetDeviceUniqueId() {
std::vector<uint8_t> id;
size_t idLength = 32;
id.resize(idLength);
OEMCryptoResult sts = OEMCrypto_GetDeviceID(&id[0], &idLength);
if (OEMCrypto_SUCCESS != sts) {
return std::string();
}
return std::string(reinterpret_cast<const char*>(&id[0]));
}
}; // namespace wvcdm

View File

@@ -11,6 +11,7 @@
#include "log.h"
// TODO(gmorgan,jtinker): decide if OEMCryptoCENC is needed here.
#include "OEMCryptoCENC.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
@@ -114,39 +115,18 @@ bool CryptoSession::PrepareRequest(const std::string& message,
return false;
}
uint8_t signature_buf[32];
size_t length = 32;
OEMCryptoResult sts;
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(message, &mac_deriv_message);
GenerateEncryptContext(message, &enc_deriv_message);
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t) oec_session_id_);
sts = OEMCrypto_GenerateDerivedKeys(
oec_session_id_,
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
return false;
if (!Properties::use_certificates_as_identification()) {
if (!GenerateDerivedKeys(message)) {
return false;
}
}
LOGV("GenerateSignature: id=%ld", (uint32_t) oec_session_id_);
sts = OEMCrypto_GenerateSignature(
oec_session_id_,
reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
signature_buf,
&length);
if (OEMCrypto_SUCCESS != sts) {
if (!GenerateSignature(message, signature)) {
return false;
}
signature->assign(reinterpret_cast<const char*>(signature_buf), length);
return true;
}
@@ -155,17 +135,15 @@ bool CryptoSession::PrepareRenewalRequest(const std::string& message,
LOGV("CryptoSession::PrepareRenewalRequest: Lock");
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
AutoLock auto_lock(crypto_engine->crypto_lock_);
uint8_t signature_buf[32];
size_t length = 32;
OEMCryptoResult sts = OEMCrypto_GenerateSignature(
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
message.size(), signature_buf, &length);
if (OEMCrypto_SUCCESS != sts) {
if (!signature) {
LOGE("CryptoSession::PrepareRenewalRequest : No output destination provided.");
return false;
}
signature->assign(reinterpret_cast<const char*>(signature_buf), length);
if (!GenerateSignature(message, signature)) {
return false;
}
return true;
}
@@ -255,6 +233,25 @@ bool CryptoSession::LoadKeys(const std::string& message,
num_keys, &load_key_array[0]));
}
bool CryptoSession::LoadCertificatePrivateKey(std::string& wrapped_key) {
LOGV("CryptoSession::LoadKeys: Lock");
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
AutoLock auto_lock(crypto_engine->crypto_lock_);
LOGV("LoadDeviceRSAKey: id=%ld", (uint32_t) oec_session_id_);
OEMCryptoResult sts = OEMCrypto_LoadDeviceRSAKey(
oec_session_id_,
reinterpret_cast<const uint8_t*>(wrapped_key.data()),
wrapped_key.size());
if (OEMCrypto_SUCCESS != sts) {
LOGD("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts);
return false;
}
return true;
}
bool CryptoSession::RefreshKeys(const std::string& message,
const std::string& signature,
int num_keys,
@@ -310,6 +307,85 @@ bool CryptoSession::SelectKey(const std::string& key_id) {
return true;
}
bool CryptoSession::GenerateDerivedKeys(const std::string& message) {
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(message, &mac_deriv_message);
GenerateEncryptContext(message, &enc_deriv_message);
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t) oec_session_id_);
OEMCryptoResult sts = OEMCrypto_GenerateDerivedKeys(
oec_session_id_,
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGD("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
return false;
}
return true;
}
bool CryptoSession::GenerateDerivedKeys(const std::string& message,
const std::string& session_key) {
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(message, &mac_deriv_message);
GenerateEncryptContext(message, &enc_deriv_message);
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t) oec_session_id_);
OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey(
oec_session_id_,
reinterpret_cast<const uint8_t*>(session_key.data()),
session_key.size(),
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGD("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts);
return false;
}
return true;
}
bool CryptoSession::GenerateSignature(const std::string& message,
std::string* signature) {
LOGV("GenerateSignature: id=%ld", (uint32_t) oec_session_id_);
uint8_t signature_buf[32];
uint32_t length = 32;
OEMCryptoResult sts;
if (Properties::use_certificates_as_identification()) {
sts = OEMCrypto_GenerateRSASignature(
oec_session_id_,
reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
signature_buf,
&length);
}
else {
sts = OEMCrypto_GenerateSignature(
oec_session_id_,
reinterpret_cast<const uint8_t*>(message.data()),
message.size(),
signature_buf,
&length);
}
if (OEMCrypto_SUCCESS != sts) {
LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
return false;
}
signature->assign(reinterpret_cast<const char*>(signature_buf), length);
return true;
}
CdmResponseType CryptoSession::Decrypt(bool is_encrypted,
const uint8_t* encrypt_buffer,
size_t encrypt_length,
@@ -370,10 +446,7 @@ bool CryptoSession::GenerateNonce(uint32_t* nonce) {
bool CryptoSession::SetDestinationBufferType() {
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
if (!crypto_engine->properties_valid())
return false;
if (crypto_engine->oem_crypto_use_secure_buffers()) {
if (Properties::oem_crypto_use_secure_buffers()) {
if (crypto_engine->GetSecurityLevel() == CryptoEngine::kSecurityLevelL1) {
destination_buffer_type_ = OEMCrypto_BufferType_Secure;
}
@@ -381,10 +454,10 @@ bool CryptoSession::SetDestinationBufferType() {
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
}
}
else if (crypto_engine->oem_crypto_use_fifo()) {
else if (Properties::oem_crypto_use_fifo()) {
destination_buffer_type_ = OEMCrypto_BufferType_Direct;
}
else if (crypto_engine->oem_crypto_use_userspace_buffers()) {
else if (Properties::oem_crypto_use_userspace_buffers()) {
destination_buffer_type_ = OEMCrypto_BufferType_Clear;
}
else {
@@ -395,4 +468,47 @@ bool CryptoSession::SetDestinationBufferType() {
return true;
}
bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
const uint32_t* nonce,
const uint8_t* enc_rsa_key,
size_t enc_rsa_key_length,
const uint8_t* enc_rsa_key_iv,
uint8_t* wrapped_rsa_key,
size_t* wrapped_rsa_key_length) {
LOGV("CryptoSession::RewrapDeviceRSAKey: Lock+++");
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
AutoLock auto_lock(crypto_engine->crypto_lock_);
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;
}
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,
wrapped_rsa_key,
wrapped_rsa_key_length);
if (OEMCrypto_SUCCESS != status) {
return false;
}
return true;
}
}; // namespace wvcdm

View File

@@ -1,117 +0,0 @@
// Copyright 2013 Google Inc. All Rights Reserved.
// Author: tinskip@google.com (Thomas Inskip)
//
// Description:
// Device certificate and certificate status list format definitions.
syntax = "proto2";
package video_widevine_server.sdk;
option optimize_for = LITE_RUNTIME;
option java_outer_classname = "DeviceCertificateProtos";
option java_package = "com.google.video.widevine.protos";
// Certificate definition for user devices, intermediate, and root certificates.
message DeviceCertificate {
enum CertificateType {
ROOT = 0;
INTERMEDIATE = 1;
USER_DEVICE = 2;
}
// Type of certificate. Required.
optional CertificateType type = 1;
// 128-bit globally unique serial number of certificate.
// Value is 0 for root certificate. Required.
optional bytes serial_number = 2;
// POSIX time, in seconds, when the certificate was created. Required.
optional uint32 creation_time_seconds = 3;
// Device public key. PKCS#1 ASN.1 DER-encoded. Required.
optional bytes public_key = 4;
// Widevine system ID for the device. Required for intermediate and
// user device certificates.
optional uint32 system_id = 5;
// True if the certificate corresponds to a test (non production) device.
// Optional.
optional bool test_device = 6 [default = false];
}
// DeviceCertificate signed with intermediate or root certificate private key.
message SignedDeviceCertificate {
// Serialized DeviceCertificate. Required.
optional bytes device_certificate = 1;
// Signature of device_certificate. Signed with root or intermediate
// certificate private key using RSASSA-PSS. Required.
optional bytes signature = 2;
// Intermediate signing certificate. Present only for user device
// certificates. All others signed with root certificate private key.
optional SignedDeviceCertificate signer = 3;
}
// Contains device model information for a provisioned device.
message ProvisionedDeviceInfo {
enum WvSecurityLevel {
// Defined in Widevine Security Integration Guide for DASH on Android:
// https://docs.google.com/a/google.com/document/d/1Zum-fcJeoIw6KG1kDP_KepIE5h9gAZg0PaMtemBvk9c/edit#heading=h.1t3h5sf
LEVEL_UNSPECIFIED = 0;
LEVEL_1 = 1;
LEVEL_2 = 2;
LEVEL_3 = 3;
}
// Widevine system ID for the device. Mandatory.
optional uint32 system_id = 1;
// Name of system-on-a-chip. Optional.
optional string soc = 2;
// Name of manufacturer. Optional.
optional string manufacturer = 3;
// Manufacturer's model name. Matches "brand" in device metadata. Optional.
optional string model = 4;
// Type of device (Phone, Tablet, TV, etc).
optional string device_type = 5;
// Device model year. Optional.
optional uint32 model_year = 6;
// Widevine-defined security level. Optional.
optional WvSecurityLevel security_level = 7 [default = LEVEL_UNSPECIFIED];
// True if the certificate corresponds to a test (non production) device.
// Optional.
optional bool test_device = 8 [default = false];
}
// Contains the status of the root or an intermediate DeviceCertificate.
message DeviceCertificateStatus {
enum CertificateStatus {
VALID = 0;
REVOKED = 1;
};
// Serial number of the DeviceCertificate to which this message refers.
// Required.
optional bytes serial_number = 1;
// Status of the certificate. Optional.
optional CertificateStatus status = 2 [default = VALID];
// Current version of a valid certificate. Present only if status = VALID.
optional uint32 current_certificate_version = 3;
// Device model information about the device to which the certificate
// corresponds. Required.
optional ProvisionedDeviceInfo device_info = 4;
}
// List of DeviceCertificateStatus. Used to propagate certificate revocation and
// update list.
message DeviceCertificateStatusList {
// POSIX time, in seconds, when the list was created. Required.
optional uint32 creation_time_seconds = 1;
// DeviceCertificateStatus for each certifificate.
repeated DeviceCertificateStatus certificate_status = 2;
}
// Signed CertificateStatusList
message SignedCertificateStatusList {
// Serialized CertificateStatusList. Required.
optional bytes certificate_status_list = 1;
// Signature of certificate_status_list. Signed with root certificate private
// key using RSASSA-PSS. Required.
optional bytes signature = 2;
}

View File

@@ -2,26 +2,62 @@
#include "license.h"
#include <vector>
#include "crypto_session.h"
#include "log.h"
#include "policy_engine.h"
#include "properties.h"
#include "string_conversions.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
// Protobuf generated classes.
using video_widevine_server::sdk::ClientIdentification;
using video_widevine_server::sdk::ClientIdentification_NameValue;
using video_widevine_server::sdk::LicenseRequest;
using video_widevine_server::sdk::LicenseRequest_ClientIdentification;
using video_widevine_server::sdk::LicenseRequest_ClientIdentification_NameValue;
using video_widevine_server::sdk::LicenseRequest_ContentIdentification;
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_CENC;
using video_widevine_server::sdk::License;
using video_widevine_server::sdk::License_KeyContainer;
using video_widevine_server::sdk::LicenseError;
using video_widevine_server::sdk::SignedMessage;
using video_widevine_server::sdk::STREAMING;
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_ExistingLicense;
static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
std::vector<CryptoKey> key_array;
// Extract content key(s)
for (int i = 0; i < license.key_size(); ++i) {
// TODO(kqyang): Key ID size is not fixed in spec, but conventionally we
// always use 16 bytes key id. We'll need to update oemcrypto to support
// variable size key id.
if (license.key(i).id().size() == KEY_ID_SIZE &&
license.key(i).key().size() == KEY_SIZE + KEY_PAD_SIZE &&
license.key(i).type() == License_KeyContainer::CONTENT) {
CryptoKey key;
key.set_key_id(license.key(i).id());
// Strip off PKCS#5 padding
key.set_key_data(
license.key(i).key().substr(0, KEY_SIZE));
key.set_key_data_iv(license.key(i).iv());
if (license.key(i).has_key_control()) {
key.set_key_control(
license.key(i).key_control().key_control_block());
key.set_key_control_iv(
license.key(i).key_control().iv());
}
key_array.push_back(key);
}
}
return key_array;
}
CdmLicense::CdmLicense(): session_(NULL) {}
CdmLicense::~CdmLicense() {}
@@ -40,6 +76,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) {
if (!session_ ||
token_.empty()) {
@@ -59,12 +97,22 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
session_->GenerateRequestId(request_id);
LicenseRequest license_request;
LicenseRequest_ClientIdentification* client_id =
license_request.mutable_client_id();
ClientIdentification* client_id = license_request.mutable_client_id();
client_id->set_type(LicenseRequest_ClientIdentification::KEYBOX);
if (Properties::use_certificates_as_identification())
client_id->set_type(ClientIdentification::DEVICE_CERTIFICATE);
else
client_id->set_type(ClientIdentification::KEYBOX);
client_id->set_token(token_);
ClientIdentification_NameValue client_info;
CdmAppParameterMap::const_iterator iter;
for (iter = app_parameters.begin(); iter != app_parameters.end(); iter++) {
ClientIdentification_NameValue* client_info = client_id->add_client_info();
client_info->set_name(iter->first);
client_info->set_value(iter->second);
}
// Content Identification may be a cenc_id, a webm_id or a license_id
LicenseRequest_ContentIdentification* content_id =
license_request.mutable_content_id();
@@ -115,6 +163,9 @@ bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
signed_message.SerializeToString(signed_request);
if (Properties::use_certificates_as_identification())
key_request_ = *signed_request;
return true;
}
@@ -166,25 +217,39 @@ bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) {
return true;
}
bool CdmLicense::HandleKeyResponse(const CdmKeyResponse& license_response) {
CdmResponseType CdmLicense::HandleKeyResponse(
const CdmKeyResponse& license_response) {
if (!session_) {
return false;
return KEY_ERROR;
}
if (license_response.empty()) {
LOGE("CdmLicense::HandleKeyResponse : Empty license response.");
return false;
return KEY_ERROR;
}
SignedMessage signed_response;
if (!signed_response.ParseFromString(license_response))
return false;
return KEY_ERROR;
if (signed_response.type() == SignedMessage::ERROR) {
return HandleKeyErrorResponse(signed_response);
}
if (!signed_response.has_signature())
return false;
return KEY_ERROR;
License license;
if (!license.ParseFromString(signed_response.msg()))
return false;
return KEY_ERROR;
if (Properties::use_certificates_as_identification()) {
if (!signed_response.has_session_key())
return KEY_ERROR;
if (!session_->GenerateDerivedKeys(key_request_,
signed_response.session_key()))
return KEY_ERROR;
}
// Extract mac key
std::string mac_key_iv;
@@ -201,98 +266,105 @@ bool CdmLicense::HandleKeyResponse(const CdmKeyResponse& license_response) {
}
if (mac_key_iv.size() != KEY_IV_SIZE ||
mac_key.size() != MAC_KEY_SIZE)
{
return false;
mac_key.size() != MAC_KEY_SIZE) {
return KEY_ERROR;
}
// License Id should not be empty for renewable license
if (!license.has_id()) return false;
if (!license.has_id()) return KEY_ERROR;
license_id_.CopyFrom(license.id());
}
CryptoKey* key_array = new CryptoKey[license.key_size()];
if (key_array == NULL) return false;
// Extract content key(s)
int num_keys = 0;
for (int i = 0; i < license.key_size(); ++i) {
// TODO(kqyang): Key ID size is not fixed in spec, but conventionally we
// always use 16 bytes key id. We'll need to update oemcrypto to support
// variable size key id.
if (license.key(i).id().size() == KEY_ID_SIZE &&
license.key(i).key().size() == KEY_SIZE + KEY_PAD_SIZE &&
license.key(i).type() == License_KeyContainer::CONTENT) {
key_array[num_keys].set_key_id(license.key(i).id());
// Strip off PKCS#5 padding
key_array[num_keys].set_key_data(
license.key(i).key().substr(0, KEY_SIZE));
key_array[num_keys].set_key_data_iv(license.key(i).iv());
if (license.key(i).has_key_control()) {
key_array[num_keys].set_key_control(
license.key(i).key_control().struct_());
key_array[num_keys].set_key_control_iv(
license.key(i).key_control().iv());
}
num_keys++;
}
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
if (!key_array.size()) {
LOGE("CdmLicense::HandleKeyResponse : No content keys.");
return KEY_ERROR;
}
if (num_keys == 0) return false;
policy_engine_->SetLicense(license);
bool status = session_->LoadKeys(signed_response.msg(),
signed_response.signature(),
mac_key_iv,
mac_key,
num_keys,
key_array);
delete[] key_array;
return status;
if (session_->LoadKeys(signed_response.msg(),
signed_response.signature(),
mac_key_iv,
mac_key,
key_array.size(),
&key_array[0])) {
return KEY_ADDED;
}
else {
return KEY_ERROR;
}
}
bool CdmLicense::HandleKeyRenewalResponse(
CdmResponseType CdmLicense::HandleKeyRenewalResponse(
const CdmKeyResponse& license_response) {
if (!session_) {
return false;
return KEY_ERROR;
}
if (license_response.empty()) {
LOGE("CdmLicense::HandleKeyRenewalResponse : Empty license response.");
return false;
return KEY_ERROR;
}
SignedMessage signed_response;
if (!signed_response.ParseFromString(license_response))
return false;
return KEY_ERROR;
if (signed_response.type() == SignedMessage::ERROR) {
return HandleKeyErrorResponse(signed_response);
}
if (!signed_response.has_signature())
return false;
// TODO(jfore): refresh the keys in oemcrypto
return KEY_ERROR;
License license;
if (!license.ParseFromString(signed_response.msg()))
return false;
return KEY_ERROR;
if (!license.has_id()) return false;
if (!license.has_id()) return KEY_ERROR;
if (license.id().version() > license_id_.version()) {
//This is the normal case.
// This is the normal case.
license_id_.CopyFrom(license.id());
policy_engine_->UpdateLicense(license);
} else {
// This isn't supposed to happen.
// TODO(jfore): Handle wrap? We can miss responses and that should be
// considered normal until retries are exhausted.
// policy_.set_can_play(false);
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
if (session_->RefreshKeys(signed_response.msg(),
signed_response.signature(),
key_array.size(),
&key_array[0])) {
return KEY_ADDED;
}
else {
return KEY_ERROR;
}
}
return true;
// This isn't supposed to happen.
// TODO(jfore): Handle wrap? We can miss responses and that should be
// considered normal until retries are exhausted.
return KEY_ERROR;
}
CdmResponseType CdmLicense::HandleKeyErrorResponse(
const SignedMessage& signed_message) {
LicenseError license_error;
if (!license_error.ParseFromString(signed_message.msg()))
return KEY_ERROR;
switch (license_error.error_code()) {
case LicenseError::INVALID_CREDENTIALS:
return NEED_PROVISIONING;
case LicenseError::REVOKED_CREDENTIALS:
return DEVICE_REVOKED;
case LicenseError::SERVICE_UNAVAILABLE:
default:
return KEY_ERROR;
}
}
} // namespace wvcdm

View File

@@ -1,7 +1,18 @@
// ----------------------------------------------------------------------------
// license_protocol.proto
// ----------------------------------------------------------------------------
// Copyright 2013 Google Inc. All Rights Reserved.
//
// Description:
// Merges certificate_provisioning.proto and client_identification.proto
// inline to avoid having to hardcode the import path. This is a temporary
// workaround for not getting proto_path to work in Android build envionment.
//
syntax = "proto2";
package video_widevine_server.sdk;
// need this if we are using libprotobuf-cpp-2.3.0-lite
option optimize_for = LITE_RUNTIME;
enum LicenseType {
@@ -74,6 +85,8 @@ message License {
// Exactly one key of this type must appear.
SIGNING = 1;
CONTENT = 2;
KEY_CONTROL = 3;
OPERATOR_SESSION = 4;
}
// The SecurityLevel enumeration allows the server to communicate the level
@@ -104,7 +117,7 @@ message License {
// https://docs.google.com/a/google.com/document/d/17eDxzzGpPc2qSm7zW68_5ensuxbHErYCvD3IxSKETRo/edit#
// If present, the key control must be communicated to the secure
// environment prior to any usage.
optional bytes struct = 1;
optional bytes key_control_block = 1;
optional bytes iv = 2;
}
@@ -143,22 +156,12 @@ message License {
optional int64 license_start_time = 4;
}
enum ProtocolVersion {
VERSION_2_0 = 20;
VERSION_2_1 = 21;
}
message LicenseRequest {
message ClientIdentification {
enum TokenType {
KEYBOX = 0;
}
message NameValue {
optional string name = 1;
optional string value = 2;
}
optional TokenType type = 1;
optional bytes token = 2;
repeated NameValue client_info = 3;
}
message ContentIdentification {
message CENC {
repeated bytes pssh = 1;
@@ -189,30 +192,46 @@ message LicenseRequest {
RELEASE = 3;
}
// The client_id provides information authenticating the calling device. It
// contains the Widevine keybox token that was installed on the device at the
// factory. This field is required for a valid license request.
optional ClientIdentification client_id = 1;
optional ContentIdentification content_id = 2;
optional RequestType type = 3;
optional int64 request_time = 4;
optional bytes key_control_nonce = 5;
optional ProtocolVersion protocol_version = 6 [default = VERSION_2_0];
}
message LicenseError {
enum Error {
INVALID_CREDENTIALS = 1;
REVOKED_CREDENTIALS = 2;
SERVICE_UNAVAILABLE = 3;
}
optional Error error_code = 1;
}
message SignedMessage {
enum MessageType {
LICENSE_REQUEST = 1;
LICENSE = 2;
ERROR = 3;
}
optional MessageType type = 1;
optional bytes msg = 2;
optional bytes signature = 3;
optional bytes session_key = 4;
}
// This message is used to pass optional data on initial license issuance.
message SessionInit {
optional string session_id = 1;
optional string purchase_id = 2;
optional string master_signing_key = 3;
optional string signing_key = 4;
optional bytes session_id = 1;
optional bytes purchase_id = 2;
optional bytes master_signing_key = 3;
optional bytes signing_key = 4;
optional int64 license_start_time = 5;
}
@@ -222,3 +241,74 @@ message SessionState {
optional bytes signing_key = 2;
optional uint32 keybox_system_id = 3;
}
// ----------------------------------------------------------------------------
// certificate_provisioning.proto
// ----------------------------------------------------------------------------
// Copyright 2013 Google Inc. All Rights Reserved.
// Author: tinskip@google.com (Thomas Inskip)
//
// Description:
// Public protocol buffer definitions for Widevine Device Certificate
// Provisioning protocol.
// Provisioning request sent by client devices to provisioning service.
message ProvisioningRequest {
// Device root of trust and other client identification. Required.
optional ClientIdentification client_id = 1;
// Nonce value used to prevent replay attacks. Required.
optional bytes nonce = 2;
}
// Provisioning response sent by the provisioning server to client devices.
message ProvisioningResponse {
// AES-128 encrypted device private RSA key. PKCS#1 ASN.1 DER-encoded.
// Required.
optional bytes device_rsa_key = 1;
// Initialization vector used to encrypt device_rsa_key. Required.
optional bytes device_rsa_key_iv = 2;
// Serialized DeviceCertificate. Required.
optional bytes device_certificate = 3;
// Nonce value matching nonce in ProvisioningRequest. Required.
optional bytes nonce = 4;
}
// Serialized ProvisioningRequest or ProvisioningResponse signed with
// The message authentication key.
message SignedProvisioningMessage {
// Serialized ProvisioningRequest or ProvisioningResponse. Required.
optional bytes message = 1;
// HMAC-SHA256 signature of message. Required.
optional bytes signature = 2;
}
// ----------------------------------------------------------------------------
// client_identification.proto
// ----------------------------------------------------------------------------
// Copyright 2013 Google Inc. All Rights Reserved.
// Author: tinskip@google.com (Thomas Inskip)
//
// Description:
// ClientIdentification message used by provisioning and license protocols.
option java_outer_classname = "ClientIdentificationProtos";
// ClientIdentification message used to authenticate the client device.
message ClientIdentification {
enum TokenType {
KEYBOX = 0;
DEVICE_CERTIFICATE = 1;
}
message NameValue {
optional string name = 1;
optional string value = 2;
}
// Type of factory-provisioned device root of trust. Optional.
optional TokenType type = 1 [default = KEYBOX];
// Factory-provisioned device root of trust. Required.
optional bytes token = 2;
// Optional client information name/value pairs.
repeated NameValue client_info = 3;
}

View File

@@ -10,9 +10,9 @@
#include "log.h"
#include "properties.h"
#include "properties_configuration.h"
#include "string_conversions.h"
#include "clock.h"
#include "wv_cdm_constants.h"
namespace wvcdm {
@@ -38,14 +38,6 @@ void PolicyEngine::Init(Clock* clock) {
next_renewal_time_ = 0;
policy_max_duration_seconds_ = 0;
clock_ = clock;
properties_valid_ = true;
if (!Properties::GetInstance()->GetProperty(
kPropertyKeyBeginLicenseUsageWhenReceived,
begin_license_usage_when_received_)) {
LOGW("PolicyEngine::PolicyEngine: Unable to access property - begin license usage");
properties_valid_ = false;
}
}
void PolicyEngine::OnTimerEvent(bool& event_occured, CdmEventType& event) {
@@ -114,8 +106,7 @@ void PolicyEngine::SetLicense(
void PolicyEngine::UpdateLicense(
const video_widevine_server::sdk::License& license) {
if (!license.has_policy() || kLicenseStateExpired == license_state_ ||
!properties_valid_)
if (!license.has_policy() || kLicenseStateExpired == license_state_)
return;
policy_.MergeFrom(license.policy());
@@ -180,11 +171,11 @@ void PolicyEngine::UpdateLicense(
policy_max_duration_seconds_ = policy_.license_duration_seconds();
}
if (begin_license_usage_when_received_)
if (Properties::begin_license_usage_when_received())
playback_start_time_ = current_time;
// Update state
if (begin_license_usage_when_received_) {
if (Properties::begin_license_usage_when_received()) {
if (policy_.renew_with_usage()) {
license_state_ = kLicenseStateNeedRenewal;
}
@@ -209,7 +200,7 @@ void PolicyEngine::UpdateLicense(
void PolicyEngine::BeginDecryption() {
if ((playback_start_time_ == 0) &&
(!begin_license_usage_when_received_)) {
(!Properties::begin_license_usage_when_received())) {
switch (license_state_) {
case kLicenseStateInitialPendingUsage:
case kLicenseStateNeedRenewal:

View File

@@ -6,39 +6,21 @@
namespace wvcdm {
Properties* Properties::instance_ = NULL;
Lock Properties::properties_lock_;
bool Properties::begin_license_usage_when_received_;
bool Properties::require_explicit_renew_request_;
bool Properties::oem_crypto_use_secure_buffers_;
bool Properties::oem_crypto_use_fifo_;
bool Properties::oem_crypto_use_userspace_buffers_;
bool Properties::use_certificates_as_identification_;
Properties::Properties() {
// Read in static properties/features
uint32_t size = sizeof(kCdmBooleanProperties)/sizeof(CdmBooleanProperties);
for (uint32_t i = 0; i < size; i++) {
boolean_properties_[kCdmBooleanProperties[i].name] =
kCdmBooleanProperties[i].value;
}
}
Properties* Properties::GetInstance() {
AutoLock auto_lock(properties_lock_);
if (instance_ == NULL) {
instance_ = new Properties();
}
return instance_;
}
bool Properties::GetProperty(std::string& key, bool& value) {
CdmBooleanPropertiesMap::iterator itr = boolean_properties_.find(key);
if (itr == boolean_properties_.end()) {
return false;
}
value = itr->second;
return true;
}
void Properties::SetProperty(std::string& key, bool value) {
boolean_properties_[key] = value;
void Properties::Init() {
begin_license_usage_when_received_ = kPropertyBeginLicenseUsageWhenReceived;
require_explicit_renew_request_ = kPropertyRequireExplicitRenewRequest;
oem_crypto_use_secure_buffers_ = kPropertyOemCryptoUseSecureBuffers;
oem_crypto_use_fifo_ = kPropertyOemCryptoUseFifo;
oem_crypto_use_userspace_buffers_ = kPropertyOemCryptoUseUserSpaceBuffers;
use_certificates_as_identification_ =
kPropertyUseCertificatesAsIdentification;
}
} // namespace wvcdm

View File

@@ -10,6 +10,7 @@
#include "log.h"
#include "string_conversions.h"
#include "url_request.h"
#include "wv_cdm_types.h"
namespace {
// Default license server, can be configured using --server command line option
@@ -21,6 +22,55 @@ 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 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=\"}}";
} // namespace
namespace wvcdm {
@@ -117,6 +167,15 @@ class WvCdmEngineTest : public testing::Test {
std::string session_id_;
};
TEST_F(WvCdmEngineTest, ProvisioningTest) {
CdmProvisioningRequest prov_request;
std::string provisioning_server_url = "";
cdm_engine_.GetProvisioningRequest(&prov_request, &provisioning_server_url);
cdm_engine_.HandleProvisioningResponse(kJsonResponse);
}
TEST_F(WvCdmEngineTest, BaseMessageTest) {
cdm_engine_.OpenSession(g_key_system, &session_id_);
GenerateKeyRequest(g_key_system, g_key_id);

View File

@@ -78,20 +78,32 @@ TEST(LicenseTestSession, InitNullSession) {
TEST_F(LicenseTest, PrepareKeyRequest) {
std::string signed_request;
license_.PrepareKeyRequest(a2bs_hex(kInitData), &signed_request);
CdmAppParameterMap app_parameters;
license_.PrepareKeyRequest(a2bs_hex(kInitData),
kLicenseTypeStreaming,
app_parameters,
&signed_request);
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
}
TEST_F(LicenseTest, HandleKeyResponseValid) {
std::string signed_request;
license_.PrepareKeyRequest(a2bs_hex(kInitData), &signed_request);
CdmAppParameterMap app_parameters;
license_.PrepareKeyRequest(a2bs_hex(kInitData),
kLicenseTypeStreaming,
app_parameters,
&signed_request);
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
EXPECT_TRUE(license_.HandleKeyResponse(a2bs_hex(kValidResponse)));
}
TEST_F(LicenseTest, HandleKeyResponseInvalid) {
std::string signed_request;
license_.PrepareKeyRequest(a2bs_hex(kInitData), &signed_request);
CdmAppParameterMap app_parameters;
license_.PrepareKeyRequest(a2bs_hex(kInitData),
kLicenseTypeStreaming,
app_parameters,
&signed_request);
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
EXPECT_FALSE(license_.HandleKeyResponse(a2bs_hex(kInvalidResponse)));
}

View File

@@ -49,7 +49,7 @@ void UrlRequest::AppendChunkToUpload(const std::string& data) {
int UrlRequest::GetResponse(std::string& response) {
response.clear();
const int kTimeoutInMs = 1000;
const int kTimeoutInMs = 1500;
int bytes = 0;
int total_bytes = 0;
do {

View File

@@ -8,14 +8,23 @@
namespace wvcdm {
// set property values below
static CdmBooleanProperties kCdmBooleanProperties[] = {
{ kPropertyKeyBeginLicenseUsageWhenReceived, false },
{ kPropertyKeyRequireExplicitRenewRequest, false },
{ kPropertyKeyOemCryptoUseSecureBuffers, true },
{ kPropertyKeyOemCryptoUseFifo, false },
{ kPropertyKeyOemCryptoUseUserSpaceBuffers, false },
};
// If false begin license usage on first playback
const bool kPropertyBeginLicenseUsageWhenReceived = false;
// If false, calls to Generate Key request, after the first one,
// will result in a renewal request being generated
const bool kPropertyRequireExplicitRenewRequest = false;
// Set only one of the three below to true. If secure buffer
// is selected, fallback to userspace buffers may occur
// if L1/L2 OEMCrypto APIs fail
const bool kPropertyOemCryptoUseSecureBuffers = true;
const bool kPropertyOemCryptoUseFifo = false;
const bool kPropertyOemCryptoUseUserSpaceBuffers = false;
// If false, keyboxes will be used as client identification
// and passed as the token in the license request
const bool kPropertyUseCertificatesAsIdentification = false;
} // namespace wvcdm

View File

@@ -20,7 +20,7 @@ class WvContentDecryptionModule {
// Session related methods
virtual CdmResponseType OpenSession(const CdmKeySystem& key_system,
CdmSessionId* session_id);
virtual CdmResponseType CloseSession(CdmSessionId& session_id);
virtual CdmResponseType CloseSession(const CdmSessionId& session_id);
// Construct a valid license request.
virtual CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id,
@@ -43,6 +43,10 @@ class WvContentDecryptionModule {
virtual CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
CdmQueryMap* key_info);
// Query session control information
virtual CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id,
CdmQueryMap* key_info);
// Provisioning related methods
virtual CdmResponseType GetProvisioningRequest(
CdmProvisioningRequest* request,
@@ -67,9 +71,9 @@ class WvContentDecryptionModule {
void* decrypt_buffer);
// Event listener related methods
virtual bool AttachEventListener(CdmSessionId& session_id,
virtual bool AttachEventListener(const CdmSessionId& session_id,
WvCdmEventListener* listener);
virtual bool DetachEventListener(CdmSessionId& session_id,
virtual bool DetachEventListener(const CdmSessionId& session_id,
WvCdmEventListener* listener);
private:

View File

@@ -17,7 +17,6 @@ Lock::Lock() : impl_(new Lock::Impl()) {
Lock::~Lock() {
delete impl_;
impl_ = NULL;
}
void Lock::Acquire() {
@@ -32,25 +31,6 @@ bool Lock::Try() {
return (impl_->lock_.tryLock() == 0);
}
class AutoLock::Impl {
public:
android::Mutex::Autolock *autolock_;
};
AutoLock::AutoLock(Lock& lock) : impl_(new AutoLock::Impl()) {
impl_->autolock_ = new android::Mutex::Autolock(lock.impl_->lock_);
}
AutoLock::AutoLock(Lock* lock) : impl_(new AutoLock::Impl()) {
impl_->autolock_ = new android::Mutex::Autolock(lock->impl_->lock_);
}
AutoLock::~AutoLock() {
delete impl_->autolock_;
delete impl_;
impl_ = NULL;
}
}; // namespace wvcdm

View File

@@ -25,7 +25,7 @@ CdmResponseType WvContentDecryptionModule::OpenSession(
}
CdmResponseType WvContentDecryptionModule::CloseSession(
CdmSessionId& session_id) {
const CdmSessionId& session_id) {
return cdm_engine_->CloseSession(session_id);
}
@@ -67,6 +67,12 @@ CdmResponseType WvContentDecryptionModule::QueryKeyStatus(
return cdm_engine_->QueryKeyStatus(session_id, key_info);
}
CdmResponseType WvContentDecryptionModule::QueryKeyControlInfo(
const CdmSessionId& session_id,
CdmQueryMap* key_info) {
return cdm_engine_->QueryKeyControlInfo(session_id, key_info);
}
CdmResponseType WvContentDecryptionModule::GetProvisioningRequest(
CdmProvisioningRequest* request,
std::string* default_url) {
@@ -103,13 +109,13 @@ CdmResponseType WvContentDecryptionModule::Decrypt(
}
bool WvContentDecryptionModule::AttachEventListener(
CdmSessionId& session_id,
const CdmSessionId& session_id,
WvCdmEventListener* listener) {
return cdm_engine_->AttachEventListener(session_id, listener);
}
bool WvContentDecryptionModule::DetachEventListener(
CdmSessionId& session_id,
const CdmSessionId& session_id,
WvCdmEventListener* listener) {
return cdm_engine_->DetachEventListener(session_id, listener);
}

View File

@@ -161,27 +161,39 @@ TEST_F(WvCdmRequestLicenseTest, QueryKeyStatus) {
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
CdmQueryMap query_info;
CdmQueryMap::iterator itr;
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.QueryKeyStatus(session_id_, &query_info));
EXPECT_EQ(wvcdm::QUERY_VALUE_STREAMING,
query_info[wvcdm::QUERY_KEY_LICENSE_TYPE]);
EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE,
query_info[wvcdm::QUERY_KEY_PLAY_ALLOWED]);
EXPECT_EQ(wvcdm::QUERY_VALUE_FALSE,
query_info[wvcdm::QUERY_KEY_PERSIST_ALLOWED]);
EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE,
query_info[wvcdm::QUERY_KEY_RENEW_ALLOWED]);
itr = query_info.find(wvcdm::QUERY_KEY_LICENSE_TYPE);
ASSERT_TRUE(itr != query_info.end());
EXPECT_EQ(wvcdm::QUERY_VALUE_STREAMING, itr->second);
itr = query_info.find(wvcdm::QUERY_KEY_PLAY_ALLOWED);
ASSERT_TRUE(itr != query_info.end());
EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE, itr->second);
itr = query_info.find(wvcdm::QUERY_KEY_PERSIST_ALLOWED);
ASSERT_TRUE(itr != query_info.end());
EXPECT_EQ(wvcdm::QUERY_VALUE_FALSE, itr->second);
itr = query_info.find(wvcdm::QUERY_KEY_RENEW_ALLOWED);
ASSERT_TRUE(itr != query_info.end());
EXPECT_EQ(wvcdm::QUERY_VALUE_TRUE, itr->second);
int64_t remaining_time;
std::istringstream ss;
ss.str(query_info[QUERY_KEY_LICENSE_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_LE(0, remaining_time);
ss.str(query_info[QUERY_KEY_PLAYBACK_DURATION_REMAINING]);
ss >> remaining_time;
EXPECT_LE(0, remaining_time);
itr = query_info.find(wvcdm::QUERY_KEY_LICENSE_DURATION_REMAINING);
ASSERT_TRUE(itr != query_info.end());
ss.str(itr->second);
ASSERT_TRUE(ss >> remaining_time);
EXPECT_LT(0, remaining_time);
itr = query_info.find(wvcdm::QUERY_KEY_PLAYBACK_DURATION_REMAINING);
ASSERT_TRUE(itr != query_info.end());
ss.clear();
ss.str(itr->second);
ASSERT_TRUE(ss >> remaining_time);
EXPECT_LT(0, remaining_time);
EXPECT_LE(0, (int)query_info[QUERY_KEY_RENEWAL_SERVER_URL].size());
itr = query_info.find(wvcdm::QUERY_KEY_RENEWAL_SERVER_URL);
ASSERT_TRUE(itr != query_info.end());
EXPECT_LT(0, (int)itr->second.size());
decryptor_.CloseSession(session_id_);
}
@@ -192,10 +204,39 @@ TEST_F(WvCdmRequestLicenseTest, QueryStatus) {
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
CdmQueryMap query_info;
CdmQueryMap::iterator itr;
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.QueryStatus(&query_info));
EXPECT_EQ(wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3,
query_info[wvcdm::QUERY_KEY_SECURITY_LEVEL]);
itr = query_info.find(wvcdm::QUERY_KEY_SECURITY_LEVEL);
ASSERT_TRUE(itr != query_info.end());
EXPECT_EQ(2, (int)itr->second.size());
EXPECT_EQ(wvcdm::QUERY_VALUE_SECURITY_LEVEL_L3.at(0),
itr->second.at(0));
itr = query_info.find(wvcdm::QUERY_KEY_DEVICE_ID);
ASSERT_TRUE(itr != query_info.end());
EXPECT_GT((int)itr->second.size(), 0);
decryptor_.CloseSession(session_id_);
}
TEST_F(WvCdmRequestLicenseTest, QueryKeyControlInfo) {
decryptor_.OpenSession(g_key_system, &session_id_);
GenerateKeyRequest(g_key_system, g_key_id);
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
CdmQueryMap query_info;
CdmQueryMap::iterator itr;
EXPECT_EQ(wvcdm::NO_ERROR, decryptor_.QueryKeyControlInfo(session_id_,
&query_info));
uint32_t oem_crypto_session_id;
itr = query_info.find(wvcdm::QUERY_KEY_OEMCRYPTO_SESSION_ID);
ASSERT_TRUE(itr != query_info.end());
std::istringstream ss;
ss.str(itr->second);
EXPECT_TRUE(ss >> oem_crypto_session_id);
decryptor_.CloseSession(session_id_);
}
@@ -247,6 +288,52 @@ TEST_F(WvCdmRequestLicenseTest, ClearDecryptionTest) {
decryptor_.CloseSession(session_id_);
}
TEST_F(WvCdmRequestLicenseTest, ClearDecryptionNoKeyTest) {
decryptor_.OpenSession(g_key_system, &session_id_);
// key 1, clear, 256b
DecryptionData data;
data.is_encrypted = false;
data.key_id = wvcdm::a2bs_hex("77777777777777777777777777777777");
data.encrypt_data = wvcdm::a2b_hex(
"9da401105ab8da443e93e6fe089dfc69e00a9a51690d406872f338c5fa7dd3d5"
"abf8dfd660aaff3e327850a56eedf707c03e2d1a00f9f0371e3e19ea32b13267"
"7bc083ccbb83e6d9c03794ee97f50081221a8e5eb123f6dfa895e7a971166483"
"cdadd61cd8d0f859501e750e9d356d57252ecd9f7388459f5470de9d92198c44"
"0b520055b3b9a1c6b2c9d21e78dce99622d9d031fc7dee28a6d1d6dfb81502eb"
"463c4c189555f496d9aa529b3f5522e9f46dcf70b2bfe8df47daf02b6a267f93"
"f80d871786eb4bd7f08f9c52079c034a9534d885ba4c00cbe2234cfbb5205a56"
"41dd760f83d0f09f27881ad490efa8b99b7ab24b34311a2e8416b1a80d736ad7");
data.iv = wvcdm::a2b_hex("50a6c61c3f7c2b37e72b0c047000dd4a");
data.block_offset = 0;
data.decrypt_data = wvcdm::a2b_hex(
"9da401105ab8da443e93e6fe089dfc69e00a9a51690d406872f338c5fa7dd3d5"
"abf8dfd660aaff3e327850a56eedf707c03e2d1a00f9f0371e3e19ea32b13267"
"7bc083ccbb83e6d9c03794ee97f50081221a8e5eb123f6dfa895e7a971166483"
"cdadd61cd8d0f859501e750e9d356d57252ecd9f7388459f5470de9d92198c44"
"0b520055b3b9a1c6b2c9d21e78dce99622d9d031fc7dee28a6d1d6dfb81502eb"
"463c4c189555f496d9aa529b3f5522e9f46dcf70b2bfe8df47daf02b6a267f93"
"f80d871786eb4bd7f08f9c52079c034a9534d885ba4c00cbe2234cfbb5205a56"
"41dd760f83d0f09f27881ad490efa8b99b7ab24b34311a2e8416b1a80d736ad7");
std::vector<uint8_t> decrypt_buffer;
size_t encrypt_length = data.encrypt_data.size();
decrypt_buffer.resize(encrypt_length);
EXPECT_EQ(NO_ERROR, decryptor_.Decrypt(session_id_,
data.is_encrypted,
data.key_id,
&data.encrypt_data.front(),
encrypt_length,
data.iv,
data.block_offset,
&decrypt_buffer.front()));
EXPECT_TRUE(std::equal(data.decrypt_data.begin(), data.decrypt_data.end(),
decrypt_buffer.begin()));
decryptor_.CloseSession(session_id_);
}
TEST_F(WvCdmRequestLicenseTest, DecryptionTest) {
decryptor_.OpenSession(g_key_system, &session_id_);
GenerateKeyRequest(g_key_system, g_key_id);

View File

@@ -27,8 +27,7 @@ LOCAL_C_INCLUDES := \
vendor/widevine/libwvdrmengine/cdm/include \
vendor/widevine/libwvdrmengine/oemcrypto/include
LOCAL_C_INCLUDES += \
external/protobuf/src
LOCAL_C_INCLUDES += external/protobuf/src
LOCAL_ADDITIONAL_DEPENDENCIES := $(cdm_proto_gen_headers)
@@ -40,13 +39,13 @@ LOCAL_STATIC_LIBRARIES := \
libl3crypto \
libprotobuf-cpp-2.3.0-lite
LOCAL_WHOLE_STATIC_LIBRARIES := \
cdm_protos
LOCAL_WHOLE_STATIC_LIBRARIES := libcdm_protos
LOCAL_SHARED_LIBRARIES := \
libchromium_net \
libcrypto \
libdl \
liblog \
libstlport \
libutils

View File

@@ -1,51 +0,0 @@
# Makes a unit test. test_name must be
# passed in as the base filename (without the .cpp).
$(call assert-not-null,test_name)
LOCAL_MODULE := $(test_name)
LOCAL_MODULE_TAGS := tests
LOCAL_MODULE_CLASS := EXECUTABLES
PROTO_SRC_DIR := $(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src
LOCAL_SRC_FILES := \
$(test_name).cpp \
core/test/config_test_env.cpp \
core/test/http_socket.cpp \
core/test/license_request.cpp \
core/test/url_request.cpp \
$(PROTO_SRC_DIR)/license_protocol.pb.cc
LOCAL_C_INCLUDES += \
bionic \
external/gtest/include \
external/stlport/stlport \
vendor/widevine/libwvdrmengine/cdm/core/include \
vendor/widevine/libwvdrmengine/cdm/core/test \
vendor/widevine/libwvdrmengine/cdm/include
# Add protocol buffer generated headers
#
LOCAL_C_INCLUDES += \
$(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src \
external/protobuf/src
LOCAL_ADDITIONAL_DEPENDENCIES += $(proto_generated_headers)
LOCAL_STATIC_LIBRARIES := \
libcdm \
libgtest \
libgtest_main \
libprotobuf-cpp-2.3.0-lite
LOCAL_WHOLE_STATIC_LIBRARIES := \
license_protocol_protos
LOCAL_SHARED_LIBRARIES := \
libstlport \
libchromium_net \
libcrypto \
liboemcrypto \
libutils
include $(BUILD_EXECUTABLE)

View File

@@ -8,6 +8,7 @@
#include "media/drm/DrmAPI.h"
#include "media/stagefright/foundation/ABase.h"
#include "utils/Errors.h"
#include "WVGenericCryptoInterface.h"
namespace wvdrm {
@@ -23,6 +24,8 @@ class WVDrmFactory : public android::DrmFactory {
private:
DISALLOW_EVIL_CONSTRUCTORS(WVDrmFactory);
static WVGenericCryptoInterface sOemCryptoInterface;
};
} // namespace wvdrm

View File

@@ -467,7 +467,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
if (!RangeCheck(message, message_length, key_array[i].key_id,
key_array[i].key_id_length, false) ||
!RangeCheck(message, message_length, key_array[i].key_data,
wvcdm::KEY_SIZE, false) ||
key_array[i].key_data_length, false) ||
!RangeCheck(message, message_length, key_array[i].key_data_iv,
wvcdm::KEY_IV_SIZE, false) ||
!RangeCheck(message, message_length, key_array[i].key_control,
@@ -488,6 +488,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
// Decrypt and install keys in key object
// Each key will have a key control block. They will all have the same nonce.
bool status = true;
std::vector<uint8_t> key_id;
std::vector<uint8_t> enc_key_data;
std::vector<uint8_t> key_data_iv;
@@ -497,11 +498,12 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
key_id.assign(key_array[i].key_id,
key_array[i].key_id + key_array[i].key_id_length);
enc_key_data.assign(key_array[i].key_data,
key_array[i].key_data + wvcdm::KEY_SIZE);
key_array[i].key_data + key_array[i].key_data_length);
key_data_iv.assign(key_array[i].key_data_iv,
key_array[i].key_data_iv + wvcdm::KEY_IV_SIZE);
if (key_array[i].key_control == NULL) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
key_control.assign(key_array[i].key_control,
key_array[i].key_control + wvcdm::KEY_CONTROL_SIZE);
@@ -510,12 +512,13 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
if (!session_ctx->InstallKey(key_id, enc_key_data, key_data_iv, key_control,
key_control_iv)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
}
// All keys processed. Flush nonce table
session_ctx->FlushNonces();
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
// enc_mac_key can be NULL if license renewal is not supported
if (enc_mac_key == NULL) return OEMCrypto_SUCCESS;
@@ -582,6 +585,7 @@ OEMCryptoResult OEMCrypto_RefreshKeys(OEMCrypto_SESSION session,
}
// Decrypt and refresh keys in key refresh object
bool status = true;
std::vector<uint8_t> key_id;
std::vector<uint8_t> key_control;
std::vector<uint8_t> key_control_iv;
@@ -604,9 +608,14 @@ OEMCryptoResult OEMCrypto_RefreshKeys(OEMCrypto_SESSION session,
}
if (!session_ctx->RefreshKey(key_id, key_control, key_control_iv)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
}
session_ctx->FlushNonces();
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
session_ctx->StartTimer();
return OEMCrypto_SUCCESS;
}
@@ -693,11 +702,8 @@ OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session,
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
std::vector<uint8_t> iv_v(iv, iv + 16);
std::vector<uint8_t> content(data_addr, data_addr + data_length);
if (!crypto_engine->DecryptCTR(session_ctx, iv_v, (int)offset,
content, is_encrypted,
if (!crypto_engine->DecryptCTR(session_ctx, iv, (int)offset,
data_addr, data_length, is_encrypted,
destination, buffer_type)) {
LOGE("[OEMCrypto_DecryptCTR(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
@@ -1103,7 +1109,26 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt(OEMCrypto_SESSION session,
return level1.OEMCrypto_Generic_Encrypt(session, in_buffer, buffer_length,
iv, algorithm, out_buffer);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Enrypt(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Enrypt(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (in_buffer == NULL || buffer_length == 0 ||
iv == NULL || out_buffer == NULL) {
LOGE("[OEMCrypto_Generic_Enrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Encrypt(in_buffer, buffer_length, iv, algorithm,
out_buffer)) {
LOGE("[OEMCrypto_Generic_Enrypt(): OEMCrypto_ERROR_UNKNOWN_FAILURE]");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1120,7 +1145,25 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt(OEMCrypto_SESSION session,
return level1.OEMCrypto_Generic_Decrypt(session, in_buffer, buffer_length,
iv, algorithm, out_buffer);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (!session_ctx->Generic_Decrypt(in_buffer, buffer_length, iv, algorithm,
out_buffer)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (in_buffer == NULL || buffer_length == 0 ||
iv == NULL || out_buffer == NULL) {
LOGE("[OEMCrypto_Generic_Decrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1138,7 +1181,28 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session,
algorithm, signature,
signature_length);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Sign(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Sign(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (*signature_length < SHA256_DIGEST_LENGTH) {
*signature_length = SHA256_DIGEST_LENGTH;
return OEMCrypto_ERROR_SHORT_BUFFER;
}
if (in_buffer == NULL || buffer_length == 0 || signature == NULL) {
LOGE("[OEMCrypto_Generic_Sign(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Sign(in_buffer, buffer_length, algorithm,
signature, signature_length)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1156,7 +1220,27 @@ OEMCryptoResult OEMCrypto_Generic_Verify(OEMCrypto_SESSION session,
algorithm, signature,
signature_length);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Verify(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Verify(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (signature_length != SHA256_DIGEST_LENGTH) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (in_buffer == NULL || buffer_length == 0 || signature == NULL) {
LOGE("[OEMCrypto_Generic_Verify(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Verify(in_buffer, buffer_length, algorithm,
signature, signature_length)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
}; // namespace wvoec_mock

View File

@@ -467,7 +467,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
if (!RangeCheck(message, message_length, key_array[i].key_id,
key_array[i].key_id_length, false) ||
!RangeCheck(message, message_length, key_array[i].key_data,
wvcdm::KEY_SIZE, false) ||
key_array[i].key_data_length, false) ||
!RangeCheck(message, message_length, key_array[i].key_data_iv,
wvcdm::KEY_IV_SIZE, false) ||
!RangeCheck(message, message_length, key_array[i].key_control,
@@ -488,6 +488,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
// Decrypt and install keys in key object
// Each key will have a key control block. They will all have the same nonce.
bool status = true;
std::vector<uint8_t> key_id;
std::vector<uint8_t> enc_key_data;
std::vector<uint8_t> key_data_iv;
@@ -497,11 +498,12 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
key_id.assign(key_array[i].key_id,
key_array[i].key_id + key_array[i].key_id_length);
enc_key_data.assign(key_array[i].key_data,
key_array[i].key_data + wvcdm::KEY_SIZE);
key_array[i].key_data + key_array[i].key_data_length);
key_data_iv.assign(key_array[i].key_data_iv,
key_array[i].key_data_iv + wvcdm::KEY_IV_SIZE);
if (key_array[i].key_control == NULL) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
key_control.assign(key_array[i].key_control,
key_array[i].key_control + wvcdm::KEY_CONTROL_SIZE);
@@ -510,12 +512,13 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
if (!session_ctx->InstallKey(key_id, enc_key_data, key_data_iv, key_control,
key_control_iv)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
}
// All keys processed. Flush nonce table
session_ctx->FlushNonces();
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
// enc_mac_key can be NULL if license renewal is not supported
if (enc_mac_key == NULL) return OEMCrypto_SUCCESS;
@@ -582,6 +585,7 @@ OEMCryptoResult OEMCrypto_RefreshKeys(OEMCrypto_SESSION session,
}
// Decrypt and refresh keys in key refresh object
bool status = true;
std::vector<uint8_t> key_id;
std::vector<uint8_t> key_control;
std::vector<uint8_t> key_control_iv;
@@ -604,9 +608,14 @@ OEMCryptoResult OEMCrypto_RefreshKeys(OEMCrypto_SESSION session,
}
if (!session_ctx->RefreshKey(key_id, key_control, key_control_iv)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
}
session_ctx->FlushNonces();
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
session_ctx->StartTimer();
return OEMCrypto_SUCCESS;
}
@@ -693,11 +702,8 @@ OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session,
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
std::vector<uint8_t> iv_v(iv, iv + 16);
std::vector<uint8_t> content(data_addr, data_addr + data_length);
if (!crypto_engine->DecryptCTR(session_ctx, iv_v, (int)offset,
content, is_encrypted,
if (!crypto_engine->DecryptCTR(session_ctx, iv, (int)offset,
data_addr, data_length, is_encrypted,
destination, buffer_type)) {
LOGE("[OEMCrypto_DecryptCTR(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
@@ -1103,7 +1109,26 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt(OEMCrypto_SESSION session,
return level1.OEMCrypto_Generic_Encrypt(session, in_buffer, buffer_length,
iv, algorithm, out_buffer);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Enrypt(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Enrypt(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (in_buffer == NULL || buffer_length == 0 ||
iv == NULL || out_buffer == NULL) {
LOGE("[OEMCrypto_Generic_Enrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Encrypt(in_buffer, buffer_length, iv, algorithm,
out_buffer)) {
LOGE("[OEMCrypto_Generic_Enrypt(): OEMCrypto_ERROR_UNKNOWN_FAILURE]");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1120,7 +1145,25 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt(OEMCrypto_SESSION session,
return level1.OEMCrypto_Generic_Decrypt(session, in_buffer, buffer_length,
iv, algorithm, out_buffer);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (!session_ctx->Generic_Decrypt(in_buffer, buffer_length, iv, algorithm,
out_buffer)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (in_buffer == NULL || buffer_length == 0 ||
iv == NULL || out_buffer == NULL) {
LOGE("[OEMCrypto_Generic_Decrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1138,7 +1181,28 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session,
algorithm, signature,
signature_length);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Sign(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Sign(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (*signature_length < SHA256_DIGEST_LENGTH) {
*signature_length = SHA256_DIGEST_LENGTH;
return OEMCrypto_ERROR_SHORT_BUFFER;
}
if (in_buffer == NULL || buffer_length == 0 || signature == NULL) {
LOGE("[OEMCrypto_Generic_Sign(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Sign(in_buffer, buffer_length, algorithm,
signature, signature_length)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1156,7 +1220,27 @@ OEMCryptoResult OEMCrypto_Generic_Verify(OEMCrypto_SESSION session,
algorithm, signature,
signature_length);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Verify(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Verify(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (signature_length != SHA256_DIGEST_LENGTH) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (in_buffer == NULL || buffer_length == 0 || signature == NULL) {
LOGE("[OEMCrypto_Generic_Verify(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Verify(in_buffer, buffer_length, algorithm,
signature, signature_length)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
}; // namespace wvoec_mock

View File

@@ -467,7 +467,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
if (!RangeCheck(message, message_length, key_array[i].key_id,
key_array[i].key_id_length, false) ||
!RangeCheck(message, message_length, key_array[i].key_data,
wvcdm::KEY_SIZE, false) ||
key_array[i].key_data_length, false) ||
!RangeCheck(message, message_length, key_array[i].key_data_iv,
wvcdm::KEY_IV_SIZE, false) ||
!RangeCheck(message, message_length, key_array[i].key_control,
@@ -488,6 +488,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
// Decrypt and install keys in key object
// Each key will have a key control block. They will all have the same nonce.
bool status = true;
std::vector<uint8_t> key_id;
std::vector<uint8_t> enc_key_data;
std::vector<uint8_t> key_data_iv;
@@ -497,11 +498,12 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
key_id.assign(key_array[i].key_id,
key_array[i].key_id + key_array[i].key_id_length);
enc_key_data.assign(key_array[i].key_data,
key_array[i].key_data + wvcdm::KEY_SIZE);
key_array[i].key_data + key_array[i].key_data_length);
key_data_iv.assign(key_array[i].key_data_iv,
key_array[i].key_data_iv + wvcdm::KEY_IV_SIZE);
if (key_array[i].key_control == NULL) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
key_control.assign(key_array[i].key_control,
key_array[i].key_control + wvcdm::KEY_CONTROL_SIZE);
@@ -510,12 +512,13 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
if (!session_ctx->InstallKey(key_id, enc_key_data, key_data_iv, key_control,
key_control_iv)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
}
// All keys processed. Flush nonce table
session_ctx->FlushNonces();
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
// enc_mac_key can be NULL if license renewal is not supported
if (enc_mac_key == NULL) return OEMCrypto_SUCCESS;
@@ -582,6 +585,7 @@ OEMCryptoResult OEMCrypto_RefreshKeys(OEMCrypto_SESSION session,
}
// Decrypt and refresh keys in key refresh object
bool status = true;
std::vector<uint8_t> key_id;
std::vector<uint8_t> key_control;
std::vector<uint8_t> key_control_iv;
@@ -604,9 +608,14 @@ OEMCryptoResult OEMCrypto_RefreshKeys(OEMCrypto_SESSION session,
}
if (!session_ctx->RefreshKey(key_id, key_control, key_control_iv)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
}
session_ctx->FlushNonces();
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
session_ctx->StartTimer();
return OEMCrypto_SUCCESS;
}
@@ -693,11 +702,8 @@ OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session,
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
std::vector<uint8_t> iv_v(iv, iv + 16);
std::vector<uint8_t> content(data_addr, data_addr + data_length);
if (!crypto_engine->DecryptCTR(session_ctx, iv_v, (int)offset,
content, is_encrypted,
if (!crypto_engine->DecryptCTR(session_ctx, iv, (int)offset,
data_addr, data_length, is_encrypted,
destination, buffer_type)) {
LOGE("[OEMCrypto_DecryptCTR(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
@@ -1103,7 +1109,26 @@ OEMCryptoResult OEMCrypto_Generic_Encrypt(OEMCrypto_SESSION session,
return level1.OEMCrypto_Generic_Encrypt(session, in_buffer, buffer_length,
iv, algorithm, out_buffer);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Enrypt(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Enrypt(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (in_buffer == NULL || buffer_length == 0 ||
iv == NULL || out_buffer == NULL) {
LOGE("[OEMCrypto_Generic_Enrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Encrypt(in_buffer, buffer_length, iv, algorithm,
out_buffer)) {
LOGE("[OEMCrypto_Generic_Enrypt(): OEMCrypto_ERROR_UNKNOWN_FAILURE]");
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1120,7 +1145,25 @@ OEMCryptoResult OEMCrypto_Generic_Decrypt(OEMCrypto_SESSION session,
return level1.OEMCrypto_Generic_Decrypt(session, in_buffer, buffer_length,
iv, algorithm, out_buffer);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Decrypt(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (!session_ctx->Generic_Decrypt(in_buffer, buffer_length, iv, algorithm,
out_buffer)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (in_buffer == NULL || buffer_length == 0 ||
iv == NULL || out_buffer == NULL) {
LOGE("[OEMCrypto_Generic_Decrypt(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1138,7 +1181,28 @@ OEMCryptoResult OEMCrypto_Generic_Sign(OEMCrypto_SESSION session,
algorithm, signature,
signature_length);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Sign(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Sign(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (*signature_length < SHA256_DIGEST_LENGTH) {
*signature_length = SHA256_DIGEST_LENGTH;
return OEMCrypto_ERROR_SHORT_BUFFER;
}
if (in_buffer == NULL || buffer_length == 0 || signature == NULL) {
LOGE("[OEMCrypto_Generic_Sign(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Sign(in_buffer, buffer_length, algorithm,
signature, signature_length)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
extern "C"
@@ -1156,7 +1220,27 @@ OEMCryptoResult OEMCrypto_Generic_Verify(OEMCrypto_SESSION session,
algorithm, signature,
signature_length);
}
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_Generic_Verify(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
LOGE("[OEMCrypto_Generic_Verify(): ERROR_NO_INVALID_SESSION]");
return OEMCrypto_ERROR_INVALID_SESSION;
}
if (signature_length != SHA256_DIGEST_LENGTH) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
if (in_buffer == NULL || buffer_length == 0 || signature == NULL) {
LOGE("[OEMCrypto_Generic_Verify(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
if (!session_ctx->Generic_Verify(in_buffer, buffer_length, algorithm,
signature, signature_length)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
}
return OEMCrypto_SUCCESS;
}
}; // namespace wvoec_mock

View File

@@ -18,7 +18,7 @@ class WVCryptoPlugin : public android::CryptoPlugin {
wvcdm::WvContentDecryptionModule* cdm);
virtual ~WVCryptoPlugin() {}
virtual bool requiresSecureDecoderComponent(const char *mime) const;
virtual bool requiresSecureDecoderComponent(const char* mime) const;
virtual ssize_t decrypt(bool secure, const uint8_t key[16],
const uint8_t iv[16], Mode mode, const void* srcPtr,
@@ -26,11 +26,13 @@ class WVCryptoPlugin : public android::CryptoPlugin {
void* dstPtr, android::AString* errorDetailMsg);
private:
wvcdm::CdmSessionId configureTestMode(const void* data, size_t size);
DISALLOW_EVIL_CONSTRUCTORS(WVCryptoPlugin);
wvcdm::WvContentDecryptionModule* const mCDM;
wvcdm::CdmSessionId mSessionId;
bool mTestMode;
const wvcdm::CdmSessionId mSessionId;
};
} // namespace wvdrm

View File

@@ -8,6 +8,7 @@
#include "WVCryptoPlugin.h"
#include <string.h>
#include <string>
#include <vector>
#include <openssl/sha.h>
@@ -26,19 +27,37 @@ using namespace wvcdm;
WVCryptoPlugin::WVCryptoPlugin(const void* data, size_t size,
WvContentDecryptionModule* cdm)
: mCDM(cdm),
mSessionId(static_cast<const char*>(data), size),
mTestMode(false) {
size_t index = mSessionId.find("test_mode");
mTestMode(false),
mSessionId(configureTestMode(data, size)) {}
wvcdm::CdmSessionId WVCryptoPlugin::configureTestMode(const void* data, size_t size) {
wvcdm::CdmSessionId sessionId(static_cast<const char *>(data), size);
size_t index = sessionId.find("test_mode");
if (index != string::npos) {
mSessionId = mSessionId.substr(0, index);
mTestMode = true;
sessionId = sessionId.substr(0, index);
mTestMode = true;
}
return sessionId;
}
bool WVCryptoPlugin::requiresSecureDecoderComponent(const char *mime) const {
// TODO: Determine if we are using L1 or L3 and return an appropriate value.
// For Demo 2, we are always L3.
return false;
bool WVCryptoPlugin::requiresSecureDecoderComponent(const char* mime) const {
if (!strncasecmp(mime, "video/", 6)) {
// Type is video, so query CDM to see if we require a secure decoder.
CdmQueryMap status;
CdmResponseType res = mCDM->QueryStatus(&status);
if (res != wvcdm::NO_ERROR) {
ALOGE("Error querying CDM status: %u", res);
return false;
}
return status[QUERY_KEY_SECURITY_LEVEL] == QUERY_VALUE_SECURITY_LEVEL_L1;
} else {
// Type is not video, so never require a secure decoder.
return false;
}
}
// Returns negative values for error code and
@@ -49,14 +68,24 @@ ssize_t WVCryptoPlugin::decrypt(bool secure, const uint8_t key[KEY_ID_SIZE],
const uint8_t iv[KEY_IV_SIZE], Mode mode,
const void* srcPtr, const SubSample* subSamples,
size_t numSubSamples, void* dstPtr,
AString *errorDetailMsg) {
AString* errorDetailMsg) {
if (mode != kMode_Unencrypted && mode != kMode_AES_CTR) {
return BAD_TYPE;
}
// If the caller requested secure decrypt, verify that we can comply.
if (secure) {
// TODO: Can't do secure in the Demo 2 milestone
return -EPERM;
CdmQueryMap status;
CdmResponseType res = mCDM->QueryStatus(&status);
if (res != wvcdm::NO_ERROR) {
ALOGE("Error querying CDM status: %u", res);
return PERMISSION_DENIED;
} else if (status[QUERY_KEY_SECURITY_LEVEL] !=
QUERY_VALUE_SECURITY_LEVEL_L1) {
return PERMISSION_DENIED;
}
}
// Convert parameters to the form the CDM wishes to consume them in.

View File

@@ -13,7 +13,6 @@ LOCAL_C_INCLUDES := \
vendor/widevine/libwvdrmengine/cdm/core/include \
vendor/widevine/libwvdrmengine/cdm/include \
vendor/widevine/libwvdrmengine/mediacrypto/include \
vendor/widevine/libwvdrmengine/mediacrypto/test \
vendor/widevine/libwvdrmengine/test/gmock/include \
LOCAL_STATIC_LIBRARIES := \
@@ -29,6 +28,7 @@ LOCAL_SHARED_LIBRARIES := \
libcrypto \
libdl \
liblog \
libstagefright_foundation \
libstlport \
libutils \
@@ -44,8 +44,7 @@ LOCAL_C_INCLUDES += \
LOCAL_ADDITIONAL_DEPENDENCIES += $(proto_generated_headers)
LOCAL_WHOLE_STATIC_LIBRARIES := \
cdm_protos
LOCAL_WHOLE_STATIC_LIBRARIES := libcdm_protos
# End protobuf section

View File

@@ -1,28 +0,0 @@
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
#ifndef WV_CRYPTO_PLUGIN_MOCK_CDM_H_
#define WV_CRYPTO_PLUGIN_MOCK_CDM_H_
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
namespace wvcdm {
class MockCDM : public WvContentDecryptionModule {
public:
MOCK_METHOD8(Decrypt, CdmResponseType(const CdmSessionId&, bool, const KeyId&,
const uint8_t*, size_t,
const std::vector<uint8_t>&, size_t,
void*));
};
} // namespace wvcdm
#endif // WV_CRYPTO_PLUGIN_MOCK_CDM_H_

View File

@@ -4,12 +4,15 @@
#include <stdio.h>
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "media/stagefright/foundation/ABase.h"
#include "media/stagefright/foundation/AString.h"
#include "MockCDM.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
#include "WVCryptoPlugin.h"
using namespace android;
@@ -18,6 +21,16 @@ using namespace testing;
using namespace wvcdm;
using namespace wvdrm;
class MockCDM : public WvContentDecryptionModule {
public:
MOCK_METHOD8(Decrypt, CdmResponseType(const CdmSessionId&, bool, const KeyId&,
const uint8_t*, size_t,
const std::vector<uint8_t>&, size_t,
void*));
MOCK_METHOD1(QueryStatus, CdmResponseType(CdmQueryMap*));
};
class WVCryptoPluginTest : public Test {
protected:
static const uint32_t kSessionIdSize = 16;
@@ -58,25 +71,46 @@ TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
MockCDM cdm;
WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm);
CdmQueryMap l1Map;
l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
CdmQueryMap l3Map;
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
EXPECT_CALL(cdm, QueryStatus(_))
.WillOnce(DoAll(SetArgPointee<0>(l1Map),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<0>(l3Map),
Return(wvcdm::NO_ERROR)));
EXPECT_TRUE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
"WVCryptoPlugin incorrectly allows an insecure video decoder on L1";
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
"WVCryptoPlugin incorrectly expects a secure video decoder";
"WVCryptoPlugin incorrectly expects a secure video decoder on L3";
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("audio/aac")) <<
"WVCryptoPlugin incorrectly expects a secure audio decoder";
}
TEST_F(WVCryptoPluginTest, RejectsSecureDecode) {
TEST_F(WVCryptoPluginTest, RejectsSecureDecodeOnL3) {
MockCDM cdm;
WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm);
CdmQueryMap l3Map;
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
// Decrypt should not be called because we specified an unsupported
// security level
EXPECT_CALL(cdm, Decrypt(_, _, _, _, _, _, _, _))
.Times(0);
EXPECT_CALL(cdm, QueryStatus(_))
.WillOnce(DoAll(SetArgPointee<0>(l3Map),
Return(wvcdm::NO_ERROR)));
ssize_t res = plugin.decrypt(true, keyId, iv, CryptoPlugin::kMode_AES_CTR,
in, subSamples, kSubSampleCount, out, NULL);
EXPECT_EQ(static_cast<ssize_t>(-EPERM), res) <<
EXPECT_LT(res, static_cast<ssize_t>(0)) <<
"WVCryptoPlugin allowed decryption to proceed despite being asked for an "
"unsupported security level";
}

View File

@@ -12,6 +12,7 @@ LOCAL_C_INCLUDES := \
vendor/widevine/libwvdrmengine/cdm/core/include \
vendor/widevine/libwvdrmengine/cdm/include \
vendor/widevine/libwvdrmengine/mediadrm/include \
vendor/widevine/libwvdrmengine/oemcrypto/include \
LOCAL_MODULE := libwvdrmdrmplugin

View File

@@ -5,15 +5,20 @@
#ifndef WV_DRM_PLUGIN_H_
#define WV_DRM_PLUGIN_H_
#include <map>
#include "media/drm/DrmAPI.h"
#include "media/stagefright/foundation/ABase.h"
#include "media/stagefright/foundation/AString.h"
#include "OEMCryptoCENC.h"
#include "utils/Errors.h"
#include "utils/KeyedVector.h"
#include "utils/List.h"
#include "utils/String8.h"
#include "utils/Vector.h"
#include "wv_cdm_event_listener.h"
#include "wv_content_decryption_module.h"
#include "WVGenericCryptoInterface.h"
namespace wvdrm {
@@ -22,11 +27,22 @@ using android::List;
using android::status_t;
using android::String8;
using android::Vector;
using std::map;
using wvcdm::CdmEventType;
using wvcdm::CdmSessionId;
using wvcdm::CdmResponseType;
using wvcdm::WvContentDecryptionModule;
class WVDrmPlugin : public android::DrmPlugin {
const OEMCrypto_Algorithm kInvalidCrytpoAlgorithm =
static_cast<OEMCrypto_Algorithm>(-1);
class WVDrmPlugin : public android::DrmPlugin,
public wvcdm::WvCdmEventListener {
public:
WVDrmPlugin(wvcdm::WvContentDecryptionModule* cdm);
virtual ~WVDrmPlugin() {}
WVDrmPlugin(WvContentDecryptionModule* cdm,
WVGenericCryptoInterface* crypto);
virtual ~WVDrmPlugin();
virtual status_t openSession(Vector<uint8_t>& sessionId);
@@ -72,39 +88,77 @@ class WVDrmPlugin : public android::DrmPlugin {
virtual status_t setPropertyByteArray(const String8& name,
const Vector<uint8_t>& value);
virtual status_t setCipherAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm);
virtual status_t setMacAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm);
virtual status_t setCipherAlgorithm(const Vector<uint8_t>& sessionId,
const String8& algorithm);
virtual status_t encrypt(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keyId,
Vector<uint8_t> const &input,
Vector<uint8_t> const &iv,
Vector<uint8_t> &output);
virtual status_t setMacAlgorithm(const Vector<uint8_t>& sessionId,
const String8& algorithm);
virtual status_t decrypt(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keyId,
Vector<uint8_t> const &input,
Vector<uint8_t> const &iv,
Vector<uint8_t> &output);
virtual status_t encrypt(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keyId,
const Vector<uint8_t>& input,
const Vector<uint8_t>& iv,
Vector<uint8_t>& output);
virtual status_t sign(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keyId,
Vector<uint8_t> const &message,
Vector<uint8_t> &signature);
virtual status_t decrypt(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keyId,
const Vector<uint8_t>& input,
const Vector<uint8_t>& iv,
Vector<uint8_t>& output);
virtual status_t verify(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keyId,
Vector<uint8_t> const &message,
Vector<uint8_t> const &signature,
bool &match);
virtual status_t sign(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keyId,
const Vector<uint8_t>& message,
Vector<uint8_t>& signature);
virtual status_t verify(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keyId,
const Vector<uint8_t>& message,
const Vector<uint8_t>& signature,
bool& match);
virtual void onEvent(const CdmSessionId& cdmSessionId,
CdmEventType cdmEventType);
private:
DISALLOW_EVIL_CONSTRUCTORS(WVDrmPlugin);
wvcdm::WvContentDecryptionModule* mCDM;
struct CryptoSession {
public:
CryptoSession()
: mOecSessionId(-1),
mCipherAlgorithm(kInvalidCrytpoAlgorithm),
mMacAlgorithm(kInvalidCrytpoAlgorithm) {}
CryptoSession(OEMCrypto_SESSION sessionId)
: mOecSessionId(sessionId),
mCipherAlgorithm(kInvalidCrytpoAlgorithm),
mMacAlgorithm(kInvalidCrytpoAlgorithm) {}
OEMCrypto_SESSION oecSessionId() const { return mOecSessionId; }
OEMCrypto_Algorithm cipherAlgorithm() const { return mCipherAlgorithm; }
void setCipherAlgorithm(OEMCrypto_Algorithm newAlgorithm) {
mCipherAlgorithm = newAlgorithm;
}
OEMCrypto_Algorithm macAlgorithm() const { return mMacAlgorithm; }
void setMacAlgorithm(OEMCrypto_Algorithm newAlgorithm) {
mMacAlgorithm = newAlgorithm;
}
private:
OEMCrypto_SESSION mOecSessionId;
OEMCrypto_Algorithm mCipherAlgorithm;
OEMCrypto_Algorithm mMacAlgorithm;
};
WvContentDecryptionModule* mCDM;
WVGenericCryptoInterface* mCrypto;
map<CdmSessionId, CryptoSession> mCryptoSessions;
};
} // namespace wvdrm

View File

@@ -0,0 +1,64 @@
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
#ifndef WV_GENERIC_CRYPTO_INTERFACE_H_
#define WV_GENERIC_CRYPTO_INTERFACE_H_
#include "OEMCryptoCENC.h"
namespace wvdrm {
class WVGenericCryptoInterface {
public:
WVGenericCryptoInterface() {}
virtual ~WVGenericCryptoInterface() {}
virtual OEMCryptoResult selectKey(const OEMCrypto_SESSION session,
const uint8_t* key_id,
size_t key_id_length) {
return OEMCrypto_SelectKey(session, key_id, key_id_length);
}
virtual OEMCryptoResult encrypt(OEMCrypto_SESSION session,
const uint8_t* in_buffer,
size_t buffer_length, const uint8_t* iv,
OEMCrypto_Algorithm algorithm,
uint8_t* out_buffer) {
return OEMCrypto_Generic_Encrypt(session, in_buffer, buffer_length, iv,
algorithm, out_buffer);
}
virtual OEMCryptoResult decrypt(OEMCrypto_SESSION session,
const uint8_t* in_buffer,
size_t buffer_length, const uint8_t* iv,
OEMCrypto_Algorithm algorithm,
uint8_t* out_buffer) {
return OEMCrypto_Generic_Decrypt(session, in_buffer, buffer_length, iv,
algorithm, out_buffer);
}
virtual OEMCryptoResult sign(OEMCrypto_SESSION session,
const uint8_t* in_buffer, size_t buffer_length,
OEMCrypto_Algorithm algorithm,
uint8_t* signature, size_t* signature_length) {
return OEMCrypto_Generic_Sign(session, in_buffer, buffer_length, algorithm,
signature, signature_length);
}
virtual OEMCryptoResult verify(OEMCrypto_SESSION session,
const uint8_t* in_buffer, size_t buffer_length,
OEMCrypto_Algorithm algorithm,
const uint8_t* signature,
size_t signature_length) {
return OEMCrypto_Generic_Verify(session, in_buffer, buffer_length,
algorithm, signature, signature_length);
}
private:
DISALLOW_EVIL_CONSTRUCTORS(WVGenericCryptoInterface);
};
} // namespace wvdrm
#endif // WV_GENERIC_CRYPTO_INTERFACE_H_

View File

@@ -8,9 +8,11 @@
#include "WVDrmPlugin.h"
#include <sstream>
#include <string>
#include <vector>
#include "media/stagefright/MediaErrors.h"
#include "utils/Errors.h"
#include "wv_cdm_constants.h"
@@ -20,7 +22,24 @@ using namespace android;
using namespace std;
using namespace wvcdm;
WVDrmPlugin::WVDrmPlugin(WvContentDecryptionModule* cdm) : mCDM(cdm) {}
WVDrmPlugin::WVDrmPlugin(WvContentDecryptionModule* cdm,
WVGenericCryptoInterface* crypto)
: mCDM(cdm), mCrypto(crypto) {}
WVDrmPlugin::~WVDrmPlugin() {
typedef map<CdmSessionId, CryptoSession>::iterator mapIterator;
for (mapIterator iter = mCryptoSessions.begin();
iter != mCryptoSessions.end();
++iter) {
bool bRes = mCDM->DetachEventListener(iter->first, this);
if (!bRes) {
ALOGE("Received failure when trying to detach WVDrmPlugin as an event"
"listener.");
}
}
mCryptoSessions.clear();
}
status_t WVDrmPlugin::openSession(Vector<uint8_t>& sessionId) {
CdmSessionId cdmSessionId;
@@ -30,11 +49,46 @@ status_t WVDrmPlugin::openSession(Vector<uint8_t>& sessionId) {
return android::UNKNOWN_ERROR;
}
sessionId.clear();
sessionId.appendArray(reinterpret_cast<const uint8_t*>(cdmSessionId.data()),
cdmSessionId.size());
bool success = false;
return android::OK;
// Register for events
bool listenerAttached = mCDM->AttachEventListener(cdmSessionId, this);
if (listenerAttached) {
// Construct a CryptoSession
CdmQueryMap info;
CdmResponseType res = mCDM->QueryKeyControlInfo(cdmSessionId, &info);
if (res == wvcdm::NO_ERROR && info.count(QUERY_KEY_OEMCRYPTO_SESSION_ID)) {
OEMCrypto_SESSION oecSessionId;
istringstream(info[QUERY_KEY_OEMCRYPTO_SESSION_ID]) >> oecSessionId;
mCryptoSessions[cdmSessionId] = CryptoSession(oecSessionId);
success = true;
}
} else {
ALOGE("Received failure when trying to attach WVDrmPlugin as an event"
"listener.");
}
if (success) {
// Marshal Session ID
sessionId.clear();
sessionId.appendArray(reinterpret_cast<const uint8_t*>(cdmSessionId.data()),
cdmSessionId.size());
return android::OK;
} else {
if (listenerAttached) {
mCDM->DetachEventListener(cdmSessionId, this);
}
mCDM->CloseSession(cdmSessionId);
return android::UNKNOWN_ERROR;
}
}
status_t WVDrmPlugin::closeSession(const Vector<uint8_t>& sessionId) {
@@ -42,6 +96,7 @@ status_t WVDrmPlugin::closeSession(const Vector<uint8_t>& sessionId) {
CdmResponseType res = mCDM->CloseSession(cdmSessionId);
if (res == wvcdm::NO_ERROR) {
mCryptoSessions.erase(cdmSessionId);
return android::OK;
} else {
return android::UNKNOWN_ERROR;
@@ -67,7 +122,6 @@ status_t WVDrmPlugin::getKeyRequest(
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
CdmInitData cdmInitData(initData.begin(), initData.end());
// TODO: Do something with mimeType?
CdmAppParameterMap cdmParameters;
for (size_t i = 0; i < optionalParameters.size(); ++i) {
@@ -90,7 +144,6 @@ status_t WVDrmPlugin::getKeyRequest(
return android::UNKNOWN_ERROR;
}
// TODO: Do something more with defaultUrl?
defaultUrl.clear();
request.clear();
@@ -104,7 +157,7 @@ status_t WVDrmPlugin::provideKeyResponse(
const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& response,
Vector<uint8_t>& keySetId) {
// TODO: return keySetId for persisted offline content
// TODO: return keySetId for persisted offline content
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
CdmKeyResponse cdmResponse(response.begin(), response.end());
@@ -119,13 +172,13 @@ status_t WVDrmPlugin::provideKeyResponse(
status_t WVDrmPlugin::removeKeys(const Vector<uint8_t>& keySetId) {
// TODO: remove persisted offline keys associated with keySetId
return android::UNKNOWN_ERROR;
return android::ERROR_UNSUPPORTED;
}
status_t WVDrmPlugin::restoreKeys(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keySetId) {
// TODO: restore persisted offline keys associated with keySetId
return android::UNKNOWN_ERROR;
return android::ERROR_UNSUPPORTED;
}
status_t WVDrmPlugin::queryKeyStatus(
@@ -232,76 +285,306 @@ status_t WVDrmPlugin::releaseSecureStops(const Vector<uint8_t>& ssRelease) {
status_t WVDrmPlugin::getPropertyString(const String8& name,
String8& value) const {
// TODO: Implement this function once the CDM query API is finalized.
return -EPERM;
if (name == "vendor") {
value = "Google";
} else if (name == "version") {
value = "1.0";
} else if (name == "description") {
value = "Widevine CDM";
} else if (name == "algorithms") {
value = "AES/CBC/NoPadding,HmacSHA256";
} else if (name == "securityLevel") {
CdmQueryMap status;
CdmResponseType res = mCDM->QueryStatus(&status);
if (res != wvcdm::NO_ERROR) {
ALOGE("Error querying CDM status: %u", res);
return android::UNKNOWN_ERROR;
} else if (!status.count(QUERY_KEY_SECURITY_LEVEL)) {
ALOGE("CDM did not report a security level");
return android::UNKNOWN_ERROR;
}
const string& securityLevel = status[QUERY_KEY_SECURITY_LEVEL];
value.clear();
value.append(securityLevel.data(), securityLevel.size());
} else {
ALOGE("App requested unknown property %s", name.string());
return android::ERROR_UNSUPPORTED;
}
return android::OK;
}
status_t WVDrmPlugin::getPropertyByteArray(const String8& name,
Vector<uint8_t>& value) const {
// TODO: Implement this function once the CDM query API is finalized.
return -EPERM;
Vector<uint8_t>& value) const {
if (name == "deviceUniqueId") {
CdmQueryMap status;
CdmResponseType res = mCDM->QueryStatus(&status);
if (res != wvcdm::NO_ERROR) {
ALOGE("Error querying CDM status: %u", res);
return android::UNKNOWN_ERROR;
} else if (!status.count(QUERY_KEY_DEVICE_ID)) {
ALOGE("CDM did not report a unique ID");
return android::UNKNOWN_ERROR;
}
const string& uniqueId = status[QUERY_KEY_DEVICE_ID];
value.clear();
value.appendArray(reinterpret_cast<const uint8_t*>(uniqueId.data()),
uniqueId.size());
} else {
ALOGE("App requested unknown property %s", name.string());
return android::ERROR_UNSUPPORTED;
}
return android::OK;
}
status_t WVDrmPlugin::setPropertyString(const String8& name,
const String8& value) {
// TODO: Implement this function once the CDM query API is finalized.
return -EPERM;
return android::ERROR_UNSUPPORTED;
}
status_t WVDrmPlugin::setPropertyByteArray(const String8& name,
const Vector<uint8_t>& value) {
// TODO: Implement this function once the CDM query API is finalized.
return -EPERM;
return android::ERROR_UNSUPPORTED;
}
status_t WVDrmPlugin::setCipherAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm) {
// TODO: Implement this function once the OEMCrypto API supports it
return -EPERM;
status_t WVDrmPlugin::setCipherAlgorithm(const Vector<uint8_t>& sessionId,
const String8& algorithm) {
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
if (!mCryptoSessions.count(cdmSessionId)) {
return android::NO_INIT;
}
CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId];
if (algorithm == "AES/CBC/NoPadding") {
cryptoSession.setCipherAlgorithm(OEMCrypto_AES_CBC_128_NO_PADDING);
} else {
return android::BAD_VALUE;
}
return android::OK;
}
status_t WVDrmPlugin::setMacAlgorithm(Vector<uint8_t> const &sessionId,
String8 const &algorithm) {
// TODO: Implement this function once the OEMCrypto API supports it
return -EPERM;
status_t WVDrmPlugin::setMacAlgorithm(const Vector<uint8_t>& sessionId,
const String8& algorithm) {
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
if (!mCryptoSessions.count(cdmSessionId)) {
return android::NO_INIT;
}
CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId];
if (algorithm == "HmacSHA256") {
cryptoSession.setMacAlgorithm(OEMCrypto_HMAC_SHA256);
} else {
return android::BAD_VALUE;
}
return android::OK;
}
status_t WVDrmPlugin::encrypt(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keyId,
Vector<uint8_t> const &input,
Vector<uint8_t> const &iv,
Vector<uint8_t> &output) {
// TODO: Implement this function once the OEMCrypto API supports it
return -EPERM;
status_t WVDrmPlugin::encrypt(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keyId,
const Vector<uint8_t>& input,
const Vector<uint8_t>& iv,
Vector<uint8_t>& output) {
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
if (!mCryptoSessions.count(cdmSessionId)) {
return android::NO_INIT;
}
const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId];
if (cryptoSession.cipherAlgorithm() == kInvalidCrytpoAlgorithm) {
return android::NO_INIT;
}
OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(),
keyId.array(), keyId.size());
if (res != OEMCrypto_SUCCESS) {
ALOGE("OEMCrypto_SelectKey failed with %u", res);
return android::UNKNOWN_ERROR;
}
output.resize(input.size());
res = mCrypto->encrypt(cryptoSession.oecSessionId(), input.array(),
input.size(), iv.array(),
cryptoSession.cipherAlgorithm(), output.editArray());
if (res == OEMCrypto_SUCCESS) {
return android::OK;
} else {
ALOGE("OEMCrypto_Generic_Encrypt failed with %u", res);
return android::UNKNOWN_ERROR;
}
}
status_t WVDrmPlugin::decrypt(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keyId,
Vector<uint8_t> const &input,
Vector<uint8_t> const &iv,
Vector<uint8_t> &output) {
// TODO: Implement this function once the OEMCrypto API supports it
return -EPERM;
status_t WVDrmPlugin::decrypt(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keyId,
const Vector<uint8_t>& input,
const Vector<uint8_t>& iv,
Vector<uint8_t>& output) {
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
if (!mCryptoSessions.count(cdmSessionId)) {
return android::NO_INIT;
}
const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId];
if (cryptoSession.cipherAlgorithm() == kInvalidCrytpoAlgorithm) {
return android::NO_INIT;
}
OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(),
keyId.array(), keyId.size());
if (res != OEMCrypto_SUCCESS) {
ALOGE("OEMCrypto_SelectKey failed with %u", res);
return android::UNKNOWN_ERROR;
}
output.resize(input.size());
res = mCrypto->decrypt(cryptoSession.oecSessionId(), input.array(),
input.size(), iv.array(),
cryptoSession.cipherAlgorithm(), output.editArray());
if (res == OEMCrypto_SUCCESS) {
return android::OK;
} else {
ALOGE("OEMCrypto_Generic_Decrypt failed with %u", res);
return android::UNKNOWN_ERROR;
}
}
status_t WVDrmPlugin::sign(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keyId,
Vector<uint8_t> const &message,
Vector<uint8_t> &signature) {
// TODO: Implement this function once the OEMCrypto API supports it
return -EPERM;
status_t WVDrmPlugin::sign(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keyId,
const Vector<uint8_t>& message,
Vector<uint8_t>& signature) {
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
if (!mCryptoSessions.count(cdmSessionId)) {
return android::NO_INIT;
}
const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId];
if (cryptoSession.macAlgorithm() == kInvalidCrytpoAlgorithm) {
return android::NO_INIT;
}
OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(),
keyId.array(), keyId.size());
if (res != OEMCrypto_SUCCESS) {
ALOGE("OEMCrypto_SelectKey failed with %u", res);
return android::UNKNOWN_ERROR;
}
size_t signatureSize = 0;
res = mCrypto->sign(cryptoSession.oecSessionId(), message.array(),
message.size(), cryptoSession.macAlgorithm(),
signature.editArray(), &signatureSize);
if (res != OEMCrypto_ERROR_SHORT_BUFFER) {
ALOGE("OEMCrypto_Generic_Sign failed with %u when requesting signature "
"size", res);
return android::UNKNOWN_ERROR;
}
signature.resize(signatureSize);
res = mCrypto->sign(cryptoSession.oecSessionId(), message.array(),
message.size(), cryptoSession.macAlgorithm(),
signature.editArray(), &signatureSize);
if (res == OEMCrypto_SUCCESS) {
return android::OK;
} else {
ALOGE("OEMCrypto_Generic_Sign failed with %u", res);
return android::UNKNOWN_ERROR;
}
}
status_t WVDrmPlugin::verify(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &keyId,
Vector<uint8_t> const &message,
Vector<uint8_t> const &signature,
bool &match) {
// TODO: Implement this function once the OEMCrypto API supports it
return -EPERM;
status_t WVDrmPlugin::verify(const Vector<uint8_t>& sessionId,
const Vector<uint8_t>& keyId,
const Vector<uint8_t>& message,
const Vector<uint8_t>& signature,
bool& match) {
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
if (!mCryptoSessions.count(cdmSessionId)) {
return android::NO_INIT;
}
const CryptoSession& cryptoSession = mCryptoSessions[cdmSessionId];
if (cryptoSession.macAlgorithm() == kInvalidCrytpoAlgorithm) {
return android::NO_INIT;
}
OEMCryptoResult res = mCrypto->selectKey(cryptoSession.oecSessionId(),
keyId.array(), keyId.size());
if (res != OEMCrypto_SUCCESS) {
ALOGE("OEMCrypto_SelectKey failed with %u", res);
return android::UNKNOWN_ERROR;
}
res = mCrypto->verify(cryptoSession.oecSessionId(), message.array(),
message.size(), cryptoSession.macAlgorithm(),
signature.array(), signature.size());
if (res == OEMCrypto_SUCCESS) {
match = true;
return android::OK;
} else if (res == OEMCrypto_ERROR_SIGNATURE_FAILURE) {
match = false;
return android::OK;
} else {
ALOGE("OEMCrypto_Generic_Verify failed with %u", res);
return android::UNKNOWN_ERROR;
}
}
void WVDrmPlugin::onEvent(const CdmSessionId& cdmSessionId,
CdmEventType cdmEventType) {
Vector<uint8_t> sessionId;
EventType eventType;
switch (cdmEventType) {
case LICENSE_EXPIRED_EVENT:
eventType = kDrmPluginEventKeyExpired;
break;
case LICENSE_RENEWAL_NEEDED_EVENT:
eventType = kDrmPluginEventKeyNeeded;
break;
default:
ALOGE("Unknown CDM Event Received by WVDrmPlugin: %u", cdmEventType);
return;
}
sessionId.appendArray(reinterpret_cast<const uint8_t*>(cdmSessionId.data()),
cdmSessionId.size());
// Call base-class method with translated event.
sendEvent(eventType, 0, &sessionId, NULL);
}
// TODO: Hook up to event listener methods on CDM once Android API for
// eventing is finalized.
} // namespace wvdrm

View File

@@ -13,7 +13,7 @@ LOCAL_C_INCLUDES := \
vendor/widevine/libwvdrmengine/cdm/core/include \
vendor/widevine/libwvdrmengine/cdm/include \
vendor/widevine/libwvdrmengine/mediadrm/include \
vendor/widevine/libwvdrmengine/mediadrm/test \
vendor/widevine/libwvdrmengine/oemcrypto/include \
vendor/widevine/libwvdrmengine/test/gmock/include \
LOCAL_STATIC_LIBRARIES := \
@@ -44,8 +44,7 @@ LOCAL_C_INCLUDES += \
LOCAL_ADDITIONAL_DEPENDENCIES += $(proto_generated_headers)
LOCAL_WHOLE_STATIC_LIBRARIES := \
cdm_protos
LOCAL_WHOLE_STATIC_LIBRARIES := libcdm_protos
# End protobuf section

View File

@@ -1,53 +0,0 @@
//
// Copyright 2013 Google Inc. All Rights Reserved.
//
#ifndef WV_DRM_PLUGIN_MOCK_CDM_H_
#define WV_DRM_PLUGIN_MOCK_CDM_H_
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
namespace wvcdm {
class MockCDM : public WvContentDecryptionModule {
public:
MOCK_METHOD2(OpenSession, CdmResponseType(const CdmKeySystem&,
CdmSessionId*));
MOCK_METHOD1(CloseSession, CdmResponseType(CdmSessionId&));
MOCK_METHOD5(GenerateKeyRequest, CdmResponseType(const CdmSessionId&,
const CdmInitData&,
const CdmLicenseType,
CdmAppParameterMap&,
CdmKeyMessage*));
MOCK_METHOD2(AddKey, CdmResponseType(const CdmSessionId&,
const CdmKeyResponse&));
MOCK_METHOD1(CancelKeyRequest, CdmResponseType(const CdmSessionId&));
MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&,
CdmQueryMap*));
MOCK_METHOD2(GetProvisioningRequest, CdmResponseType(CdmProvisioningRequest*,
std::string*));
MOCK_METHOD1(HandleProvisioningResponse,
CdmResponseType(CdmProvisioningResponse&));
MOCK_METHOD1(GetSecureStops, CdmResponseType(CdmSecureStops*));
MOCK_METHOD1(ReleaseSecureStops,
CdmResponseType(const CdmSecureStopReleaseMessage&));
};
} // namespace wvcdm
#endif // WV_DRM_PLUGIN_MOCK_CDM_H_

View File

@@ -5,11 +5,13 @@
#include <stdio.h>
#include <string>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "media/stagefright/foundation/ABase.h"
#include "media/stagefright/foundation/AString.h"
#include "MockCDM.h"
#include "wv_cdm_constants.h"
#include "wv_cdm_types.h"
#include "wv_content_decryption_module.h"
#include "WVDrmPlugin.h"
using namespace android;
@@ -18,14 +20,93 @@ using namespace testing;
using namespace wvcdm;
using namespace wvdrm;
class MockCDM : public WvContentDecryptionModule {
public:
MOCK_METHOD2(OpenSession, CdmResponseType(const CdmKeySystem&,
CdmSessionId*));
MOCK_METHOD1(CloseSession, CdmResponseType(const CdmSessionId&));
MOCK_METHOD5(GenerateKeyRequest, CdmResponseType(const CdmSessionId&,
const CdmInitData&,
const CdmLicenseType,
CdmAppParameterMap&,
CdmKeyMessage*));
MOCK_METHOD2(AddKey, CdmResponseType(const CdmSessionId&,
const CdmKeyResponse&));
MOCK_METHOD1(CancelKeyRequest, CdmResponseType(const CdmSessionId&));
MOCK_METHOD1(QueryStatus, CdmResponseType(CdmQueryMap*));
MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&,
CdmQueryMap*));
MOCK_METHOD2(QueryKeyControlInfo, CdmResponseType(const CdmSessionId&,
CdmQueryMap*));
MOCK_METHOD2(GetProvisioningRequest, CdmResponseType(CdmProvisioningRequest*,
std::string*));
MOCK_METHOD1(HandleProvisioningResponse,
CdmResponseType(CdmProvisioningResponse&));
MOCK_METHOD1(GetSecureStops, CdmResponseType(CdmSecureStops*));
MOCK_METHOD1(ReleaseSecureStops,
CdmResponseType(const CdmSecureStopReleaseMessage&));
MOCK_METHOD2(AttachEventListener, bool(const CdmSessionId&,
WvCdmEventListener*));
MOCK_METHOD2(DetachEventListener, bool(const CdmSessionId&,
WvCdmEventListener*));
};
class MockCrypto : public WVGenericCryptoInterface {
public:
MOCK_METHOD3(selectKey, OEMCryptoResult(const OEMCrypto_SESSION,
const uint8_t*, size_t));
MOCK_METHOD6(encrypt, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
size_t, const uint8_t*,
OEMCrypto_Algorithm, uint8_t*));
MOCK_METHOD6(decrypt, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
size_t, const uint8_t*,
OEMCrypto_Algorithm, uint8_t*));
MOCK_METHOD6(sign, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*, size_t,
OEMCrypto_Algorithm, uint8_t*, size_t*));
MOCK_METHOD6(verify, OEMCryptoResult(OEMCrypto_SESSION, const uint8_t*,
size_t, OEMCrypto_Algorithm,
const uint8_t*, size_t));
};
class MockDrmPluginListener : public DrmPluginListener {
public:
MOCK_METHOD4(sendEvent, void(DrmPlugin::EventType, int,
const Vector<uint8_t>*, const Vector<uint8_t>*));
};
template <uint8_t DIGIT>
CdmResponseType setSessionIdOnMap(Unused, CdmQueryMap* map) {
static const char oecId[] = {DIGIT + '0', '\0'};
(*map)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = oecId;
return wvcdm::NO_ERROR;
}
class WVDrmPluginTest : public Test {
protected:
static const uint32_t kSessionIdSize = 16;
uint8_t sessionIdRaw[kSessionIdSize];
Vector<uint8_t> sessionId;
CdmSessionId cdmSessionId;
virtual void SetUp() {
uint8_t sessionIdRaw[kSessionIdSize];
// Fill the session ID
FILE* fp = fopen("/dev/urandom", "r");
fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp);
fclose(fp);
@@ -33,26 +114,34 @@ class WVDrmPluginTest : public Test {
sessionId.appendArray(sessionIdRaw, kSessionIdSize);
cdmSessionId.assign(sessionId.begin(), sessionId.end());
// Set default CdmResponseType value for gMock
// Set default return values for gMock
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
DefaultValue<OEMCryptoResult>::Set(OEMCrypto_SUCCESS);
DefaultValue<bool>::Set(true);
}
};
TEST_F(WVDrmPluginTest, OpensSessions) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
uint8_t sessionIdRaw[kSessionIdSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp);
fclose(fp);
CdmSessionId cdmSessionId(sessionIdRaw, sessionIdRaw + kSessionIdSize);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.WillOnce(DoAll(SetArgPointee<1>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
// Provide expected behavior when plugin requests session control info
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
@@ -61,7 +150,8 @@ TEST_F(WVDrmPluginTest, OpensSessions) {
TEST_F(WVDrmPluginTest, ClosesSessions) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
EXPECT_CALL(cdm, CloseSession(cdmSessionId))
.Times(1);
@@ -73,7 +163,8 @@ TEST_F(WVDrmPluginTest, ClosesSessions) {
TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const uint32_t kInitDataSize = 128;
uint8_t initDataRaw[kInitDataSize];
@@ -141,7 +232,8 @@ TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
TEST_F(WVDrmPluginTest, AddsKeys) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const uint32_t kResponseSize = 256;
uint8_t responseRaw[kResponseSize];
@@ -169,7 +261,8 @@ TEST_F(WVDrmPluginTest, AddsKeys) {
TEST_F(WVDrmPluginTest, QueriesKeyStatus) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
KeyedVector<String8, String8> expectedLicenseStatus;
CdmQueryMap cdmLicenseStatus;
@@ -201,7 +294,8 @@ TEST_F(WVDrmPluginTest, QueriesKeyStatus) {
TEST_F(WVDrmPluginTest, GetsProvisioningRequests) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const uint32_t kRequestSize = 256;
uint8_t requestRaw[kRequestSize];
@@ -230,7 +324,8 @@ TEST_F(WVDrmPluginTest, GetsProvisioningRequests) {
TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const uint32_t kResponseSize = 512;
uint8_t responseRaw[kResponseSize];
@@ -252,7 +347,8 @@ TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) {
TEST_F(WVDrmPluginTest, GetsSecureStops) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const uint32_t kStopSize = 53;
static const uint32_t kStopCount = 7;
@@ -293,7 +389,8 @@ TEST_F(WVDrmPluginTest, GetsSecureStops) {
TEST_F(WVDrmPluginTest, ReleasesSecureStops) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const uint32_t kMessageSize = 128;
uint8_t messageRaw[kMessageSize];
@@ -313,43 +410,85 @@ TEST_F(WVDrmPluginTest, ReleasesSecureStops) {
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, DoesNotGetStringProperties) {
TEST_F(WVDrmPluginTest, ReturnsExpectedPropertyValues) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
String8 result;
CdmQueryMap l1Map;
l1Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
status_t res = plugin.getPropertyString(String8("property"), result);
CdmQueryMap l3Map;
l3Map[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
ASSERT_NE(OK, res);
EXPECT_TRUE(result.isEmpty());
static const string uniqueId = "The Universe";
CdmQueryMap idMap;
idMap[QUERY_KEY_DEVICE_ID] = uniqueId;
EXPECT_CALL(cdm, QueryStatus(_))
.WillOnce(DoAll(SetArgPointee<0>(l1Map),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<0>(l3Map),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<0>(idMap),
Return(wvcdm::NO_ERROR)));
String8 stringResult;
status_t res = plugin.getPropertyString(String8("vendor"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_EQ(String8("Google"), stringResult);
res = plugin.getPropertyString(String8("version"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_EQ(String8("1.0"), stringResult);
res = plugin.getPropertyString(String8("description"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_EQ(String8("Widevine CDM"), stringResult);
res = plugin.getPropertyString(String8("algorithms"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_EQ(String8("AES/CBC/NoPadding,HmacSHA256"), stringResult);
res = plugin.getPropertyString(String8("securityLevel"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_EQ(String8("L1"), stringResult);
res = plugin.getPropertyString(String8("securityLevel"), stringResult);
ASSERT_EQ(OK, res);
EXPECT_EQ(String8("L3"), stringResult);
Vector<uint8_t> vectorResult;
res = plugin.getPropertyByteArray(String8("deviceUniqueId"), vectorResult);
ASSERT_EQ(OK, res);
EXPECT_THAT(vectorResult, ElementsAreArray(uniqueId.data(), uniqueId.size()));
}
TEST_F(WVDrmPluginTest, DoesNotGetByteProperties) {
TEST_F(WVDrmPluginTest, DoesNotGetUnknownProperties) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
Vector<uint8_t> result;
status_t res = plugin.getPropertyByteArray(String8("property"), result);
String8 stringResult;
Vector<uint8_t> vectorResult;
status_t res = plugin.getPropertyString(String8("unknownProperty"),
stringResult);
ASSERT_NE(OK, res);
EXPECT_TRUE(result.isEmpty());
EXPECT_TRUE(stringResult.isEmpty());
res = plugin.getPropertyByteArray(String8("unknownProperty"),
vectorResult);
ASSERT_NE(OK, res);
EXPECT_TRUE(vectorResult.isEmpty());
}
TEST_F(WVDrmPluginTest, DoesNotSetStringProperties) {
TEST_F(WVDrmPluginTest, DoesNotSetProperties) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
status_t res = plugin.setPropertyString(String8("property"),
String8("ignored"));
ASSERT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, DoesNotSetByteProperties) {
MockCDM cdm;
WVDrmPlugin plugin(&cdm);
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const uint32_t kValueSize = 32;
uint8_t valueRaw[kValueSize];
@@ -360,7 +499,465 @@ TEST_F(WVDrmPluginTest, DoesNotSetByteProperties) {
Vector<uint8_t> value;
value.appendArray(valueRaw, kValueSize);
status_t res = plugin.setPropertyByteArray(String8("property"), value);
status_t res = plugin.setPropertyString(String8("property"),
String8("ignored"));
ASSERT_NE(OK, res);
res = plugin.setPropertyByteArray(String8("property"), value);
ASSERT_NE(OK, res);
}
TEST_F(WVDrmPluginTest, FailsGenericMethodsWithoutAnAlgorithmSet) {
MockCDM cdm;
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
Vector<uint8_t> keyId;
Vector<uint8_t> input;
Vector<uint8_t> iv;
Vector<uint8_t> output;
bool match;
// Provide expected behavior to support session creation
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
// Note that we do not set the algorithms. This should cause these methods
// to fail.
res = plugin.encrypt(sessionId, keyId, input, iv, output);
EXPECT_EQ(NO_INIT, res);
res = plugin.decrypt(sessionId, keyId, input, iv, output);
EXPECT_EQ(NO_INIT, res);
res = plugin.sign(sessionId, keyId, input, output);
EXPECT_EQ(NO_INIT, res);
res = plugin.verify(sessionId, keyId, input, output, match);
EXPECT_EQ(NO_INIT, res);
}
MATCHER_P(IsIV, iv, "") {
for (size_t i = 0; i < KEY_IV_SIZE; ++i) {
if (iv[i] != arg[i]) {
return false;
}
}
return true;
}
TEST_F(WVDrmPluginTest, CallsGenericEncrypt) {
MockCDM cdm;
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const size_t kDataSize = 256;
uint8_t keyIdRaw[KEY_ID_SIZE];
uint8_t inputRaw[kDataSize];
uint8_t ivRaw[KEY_IV_SIZE];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(inputRaw, sizeof(uint8_t), kDataSize, fp);
fread(ivRaw, sizeof(uint8_t), KEY_IV_SIZE, fp);
fclose(fp);
Vector<uint8_t> keyId;
keyId.appendArray(keyIdRaw, KEY_ID_SIZE);
Vector<uint8_t> input;
input.appendArray(inputRaw, kDataSize);
Vector<uint8_t> iv;
iv.appendArray(ivRaw, KEY_IV_SIZE);
Vector<uint8_t> output;
{
InSequence calls;
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, encrypt(4, _, kDataSize, IsIV(ivRaw),
OEMCrypto_AES_CBC_128_NO_PADDING, _))
.With(Args<1, 2>(ElementsAreArray(inputRaw, kDataSize)))
.Times(1);
}
// Provide expected behavior to support session creation
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.setCipherAlgorithm(sessionId, String8("AES/CBC/NoPadding"));
ASSERT_EQ(OK, res);
res = plugin.encrypt(sessionId, keyId, input, iv, output);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, CallsGenericDecrypt) {
MockCDM cdm;
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const size_t kDataSize = 256;
uint8_t keyIdRaw[KEY_ID_SIZE];
uint8_t inputRaw[kDataSize];
uint8_t ivRaw[KEY_IV_SIZE];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(inputRaw, sizeof(uint8_t), kDataSize, fp);
fread(ivRaw, sizeof(uint8_t), KEY_IV_SIZE, fp);
fclose(fp);
Vector<uint8_t> keyId;
keyId.appendArray(keyIdRaw, KEY_ID_SIZE);
Vector<uint8_t> input;
input.appendArray(inputRaw, kDataSize);
Vector<uint8_t> iv;
iv.appendArray(ivRaw, KEY_IV_SIZE);
Vector<uint8_t> output;
{
InSequence calls;
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, decrypt(4, _, kDataSize, IsIV(ivRaw),
OEMCrypto_AES_CBC_128_NO_PADDING, _))
.With(Args<1, 2>(ElementsAreArray(inputRaw, kDataSize)))
.Times(1);
}
// Provide expected behavior to support session creation
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.setCipherAlgorithm(sessionId, String8("AES/CBC/NoPadding"));
ASSERT_EQ(OK, res);
res = plugin.decrypt(sessionId, keyId, input, iv, output);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, CallsGenericSign) {
MockCDM cdm;
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const size_t kDataSize = 256;
uint8_t keyIdRaw[KEY_ID_SIZE];
uint8_t messageRaw[kDataSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(messageRaw, sizeof(uint8_t), kDataSize, fp);
fclose(fp);
Vector<uint8_t> keyId;
keyId.appendArray(keyIdRaw, KEY_ID_SIZE);
Vector<uint8_t> message;
message.appendArray(messageRaw, kDataSize);
Vector<uint8_t> signature;
{
InSequence calls;
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
Pointee(0)))
.With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)))
.WillOnce(DoAll(SetArgPointee<5>(64),
Return(OEMCrypto_ERROR_SHORT_BUFFER)));
EXPECT_CALL(crypto, sign(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
Pointee(64)))
.With(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)))
.Times(1);
}
// Provide expected behavior to support session creation
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.setMacAlgorithm(sessionId, String8("HmacSHA256"));
ASSERT_EQ(OK, res);
res = plugin.sign(sessionId, keyId, message, signature);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, CallsGenericVerify) {
MockCDM cdm;
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
static const size_t kDataSize = 256;
static const size_t kSignatureSize = 16;
uint8_t keyIdRaw[KEY_ID_SIZE];
uint8_t messageRaw[kDataSize];
uint8_t signatureRaw[kSignatureSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(keyIdRaw, sizeof(uint8_t), KEY_ID_SIZE, fp);
fread(messageRaw, sizeof(uint8_t), kDataSize, fp);
fread(signatureRaw, sizeof(uint8_t), kSignatureSize, fp);
fclose(fp);
Vector<uint8_t> keyId;
keyId.appendArray(keyIdRaw, KEY_ID_SIZE);
Vector<uint8_t> message;
message.appendArray(messageRaw, kDataSize);
Vector<uint8_t> signature;
signature.appendArray(signatureRaw, kSignatureSize);
bool match;
{
InSequence calls;
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
kSignatureSize))
.With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)),
Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize))))
.WillOnce(Return(OEMCrypto_SUCCESS));
EXPECT_CALL(crypto, selectKey(4, _, KEY_ID_SIZE))
.With(Args<1, 2>(ElementsAreArray(keyIdRaw, KEY_ID_SIZE)))
.Times(1);
EXPECT_CALL(crypto, verify(4, _, kDataSize, OEMCrypto_HMAC_SHA256, _,
kSignatureSize))
.With(AllOf(Args<1, 2>(ElementsAreArray(messageRaw, kDataSize)),
Args<4, 5>(ElementsAreArray(signatureRaw, kSignatureSize))))
.WillOnce(Return(OEMCrypto_ERROR_SIGNATURE_FAILURE));
}
// Provide expected behavior to support session creation
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.setMacAlgorithm(sessionId, String8("HmacSHA256"));
ASSERT_EQ(OK, res);
res = plugin.verify(sessionId, keyId, message, signature, match);
ASSERT_EQ(OK, res);
EXPECT_TRUE(match);
res = plugin.verify(sessionId, keyId, message, signature, match);
ASSERT_EQ(OK, res);
EXPECT_FALSE(match);
}
TEST_F(WVDrmPluginTest, RegistersForEvents) {
MockCDM cdm;
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
EXPECT_CALL(cdm, AttachEventListener(cdmSessionId, &plugin))
.Times(1);
// Provide expected behavior to support session creation
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know this call will happen but we aren't interested in it.
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
}
TEST_F(WVDrmPluginTest, UnregistersForAllEventsOnDestruction) {
MockCDM cdm;
MockCrypto crypto;
{
WVDrmPlugin plugin(&cdm, &crypto);
uint8_t sessionIdRaw1[kSessionIdSize];
uint8_t sessionIdRaw2[kSessionIdSize];
FILE* fp = fopen("/dev/urandom", "r");
fread(sessionIdRaw1, sizeof(uint8_t), kSessionIdSize, fp);
fread(sessionIdRaw2, sizeof(uint8_t), kSessionIdSize, fp);
fclose(fp);
CdmSessionId cdmSessionId1(sessionIdRaw1, sessionIdRaw1 + kSessionIdSize);
CdmSessionId cdmSessionId2(sessionIdRaw2, sessionIdRaw2 + kSessionIdSize);
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.WillOnce(DoAll(SetArgPointee<1>(cdmSessionId1),
Return(wvcdm::NO_ERROR)))
.WillOnce(DoAll(SetArgPointee<1>(cdmSessionId2),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId1, _))
.WillOnce(Invoke(setSessionIdOnMap<4>));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId2, _))
.WillOnce(Invoke(setSessionIdOnMap<5>));
EXPECT_CALL(cdm, DetachEventListener(cdmSessionId1, &plugin))
.Times(1);
EXPECT_CALL(cdm, DetachEventListener(cdmSessionId2, &plugin))
.Times(1);
// Let gMock know this call will happen but we aren't interested in it.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
}
}
TEST_F(WVDrmPluginTest, MarshalsEvents) {
MockCDM cdm;
MockCrypto crypto;
WVDrmPlugin plugin(&cdm, &crypto);
sp<MockDrmPluginListener> listener = new MockDrmPluginListener();
{
InSequence calls;
EXPECT_CALL(*listener, sendEvent(DrmPlugin::kDrmPluginEventKeyExpired, 0,
Pointee(ElementsAreArray(sessionIdRaw,
kSessionIdSize)),
NULL))
.Times(1);
EXPECT_CALL(*listener, sendEvent(DrmPlugin::kDrmPluginEventKeyNeeded, 0,
Pointee(ElementsAreArray(sessionIdRaw,
kSessionIdSize)),
NULL))
.Times(1);
}
// Provide expected behavior to support session creation
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
.Times(AtLeast(1))
.WillRepeatedly(DoAll(SetArgPointee<1>(cdmSessionId),
Return(wvcdm::NO_ERROR)));
EXPECT_CALL(cdm, QueryKeyControlInfo(cdmSessionId, _))
.Times(AtLeast(1))
.WillRepeatedly(Invoke(setSessionIdOnMap<4>));
// Let gMock know these calls will happen but we aren't interested in them.
EXPECT_CALL(cdm, AttachEventListener(_, _))
.Times(AtLeast(0));
EXPECT_CALL(cdm, DetachEventListener(_, _))
.Times(AtLeast(0));
status_t res = plugin.setListener(listener);
ASSERT_EQ(OK, res);
res = plugin.openSession(sessionId);
ASSERT_EQ(OK, res);
plugin.onEvent(cdmSessionId, LICENSE_EXPIRED_EVENT);
plugin.onEvent(cdmSessionId, LICENSE_RENEWAL_NEEDED_EVENT);
}

View File

@@ -1,109 +0,0 @@
/*********************************************************************
* L3CryptoCENC.h
*
* (c) Copyright 2013 Google, Inc.
*
* Reference APIs needed to support Widevine's crypto algorithms.
*********************************************************************/
#ifndef L3CRYPTO_CENC_H_
#define L3CRYPTO_CENC_H_
#include "OEMCryptoCENC.h"
#ifdef __cplusplus
extern "C" {
#endif
#define L3CRYPTO_VERSION "5.0"
static const char l3c_version[] = L3CRYPTO_VERSION;
typedef uint32_t OEMCrypto_SESSION;
#define L3Crypto_Initialize _l3cc01
#define L3Crypto_Terminate _l3cc02
#define L3Crypto_InstallKeybox _l3cc03
#define L3Crypto_GetKeyData _l3cc04
#define L3Crypto_IsKeyboxValid _l3cc05
#define L3Crypto_GetRandom _l3cc06
#define L3Crypto_GetDeviceID _l3cc07
#define L3Crypto_WrapKeybox _l3cc08
#define L3Crypto_OpenSession _l3cc09
#define L3Crypto_CloseSession _l3cc10
#define L3Crypto_DecryptCTR _l3cc11
#define L3Crypto_GenerateDerivedKeys _l3cc12
#define L3Crypto_GenerateSignature _l3cc13
#define L3Crypto_GenerateNonce _l3cc14
#define L3Crypto_LoadKeys _l3cc15
#define L3Crypto_RefreshKeys _l3cc16
#define L3Crypto_SelectKey _l3cc17
OEMCryptoResult L3Crypto_Initialize(void);
OEMCryptoResult L3Crypto_Terminate(void);
OEMCryptoResult L3Crypto_OpenSession(OEMCrypto_SESSION *session);
OEMCryptoResult L3Crypto_CloseSession(OEMCrypto_SESSION session);
OEMCryptoResult L3Crypto_GenerateDerivedKeys(
OEMCrypto_SESSION session,
const uint8_t *mac_key_context,
uint32_t mac_key_context_length,
const uint8_t *enc_key_context,
uint32_t enc_key_context_length);
OEMCryptoResult L3Crypto_GenerateNonce(
OEMCrypto_SESSION session,
uint32_t* nonce);
OEMCryptoResult L3Crypto_GenerateSignature(
OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
uint8_t* signature,
size_t* signature_length);
OEMCryptoResult L3Crypto_LoadKeys(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
const uint8_t* signature,
size_t signature_length,
const uint8_t* enc_mac_key_iv,
const uint8_t* enc_mac_key,
size_t num_keys,
const OEMCrypto_KeyObject* key_array);
OEMCryptoResult
L3Crypto_RefreshKeys(OEMCrypto_SESSION session,
const uint8_t* message,
size_t message_length,
const uint8_t* signature,
size_t signature_length,
size_t num_keys,
const OEMCrypto_KeyRefreshObject* key_array);
OEMCryptoResult L3Crypto_SelectKey(const OEMCrypto_SESSION session,
const uint8_t* key_id,
size_t key_id_length);
OEMCryptoResult
L3Crypto_DecryptCTR(OEMCrypto_SESSION session,
const uint8_t *data_addr,
size_t data_length,
bool is_encrypted,
const uint8_t *iv,
size_t offset,
const OEMCrypto_DestBufferDesc* out_buffer);
OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t *keybox,
size_t keyBoxLength);
OEMCryptoResult L3Crypto_IsKeyboxValid(void);
OEMCryptoResult L3Crypto_GetDeviceID(uint8_t* deviceID,
size_t *idLength);
OEMCryptoResult L3Crypto_GetKeyData(uint8_t* keyData,
size_t *keyDataLength);
OEMCryptoResult L3Crypto_GetRandom(uint8_t* randomData,
size_t dataLength);
OEMCryptoResult L3Crypto_WrapKeybox(const uint8_t *keybox,
size_t keyBoxLength,
uint8_t *wrappedKeybox,
size_t *wrappedKeyBoxLength,
const uint8_t *transportKey,
size_t transportKeyLength);
#ifdef __cplusplus
}
#endif
#endif // L3CRYPTO_CENC_H_

View File

@@ -17,7 +17,6 @@ Lock::Lock() : impl_(new Lock::Impl()) {
Lock::~Lock() {
delete impl_;
impl_ = NULL;
}
void Lock::Acquire() {
@@ -32,23 +31,4 @@ bool Lock::Try() {
return (impl_->lock_.tryLock() == 0);
}
class AutoLock::Impl {
public:
android::Mutex::Autolock* autolock_;
};
AutoLock::AutoLock(Lock& lock) : impl_(new AutoLock::Impl()) {
impl_->autolock_ = new android::Mutex::Autolock(lock.impl_->lock_);
}
AutoLock::AutoLock(Lock* lock) : impl_(new AutoLock::Impl()) {
impl_->autolock_ = new android::Mutex::Autolock(lock->impl_->lock_);
}
AutoLock::~AutoLock() {
delete impl_->autolock_;
delete impl_;
impl_ = NULL;
}
}; // namespace wvcdm

View File

@@ -29,22 +29,29 @@ class Lock {
private:
class Impl;
Impl* impl_;
Impl *impl_;
CORE_DISALLOW_COPY_AND_ASSIGN(Lock);
};
// Manages the lock automatically. It will be locked when AutoLock
// is constructed and release when AutoLock goes out of scope
// is constructed and release when AutoLock goes out of scope.
class AutoLock {
public:
explicit AutoLock(Lock& lock);
explicit AutoLock(Lock* lock);
~AutoLock();
explicit AutoLock(Lock& lock) : lock_(&lock) {
lock_->Acquire();
}
explicit AutoLock(Lock* lock) : lock_(lock) {
lock_->Acquire();
}
~AutoLock() {
lock_->Release();
}
private:
class Impl;
Impl* impl_;
Lock *lock_;
CORE_DISALLOW_COPY_AND_ASSIGN(AutoLock);
};

View File

@@ -285,8 +285,8 @@ bool SessionContext::GenerateRSASignature(const uint8_t* message,
}
// Add PSS padding.
uint8_t padded_digest[*signature_length];
int status = RSA_padding_add_PKCS1_PSS(rsa_key_, padded_digest, hash,
std::vector<uint8_t> padded_digest(*signature_length);
int status = RSA_padding_add_PKCS1_PSS(rsa_key_, &padded_digest[0], hash,
EVP_sha1(), kPssSaltLength);
if (status == -1) {
LOGE("[GeneratRSASignature(): error padding hash.]");
@@ -295,7 +295,7 @@ bool SessionContext::GenerateRSASignature(const uint8_t* message,
}
// Encrypt PSS padded digest.
status = RSA_private_encrypt(*signature_length, padded_digest, signature,
status = RSA_private_encrypt(*signature_length, &padded_digest[0], signature,
rsa_key_, RSA_NO_PADDING);
if (status == -1) {
LOGE("[GeneratRSASignature(): error in private encrypt.]");
@@ -314,7 +314,7 @@ bool SessionContext::ValidateMessage(const uint8_t* given_message,
if (signature_length != SHA256_DIGEST_LENGTH) {
return false;
}
uint8_t computed_signature[signature_length];
uint8_t computed_signature[SHA256_DIGEST_LENGTH];
if (! GenerateSignature(given_message, message_length,
computed_signature, &signature_length)) {
return false;
@@ -408,7 +408,7 @@ bool SessionContext::InstallKey(const KeyId& key_id,
std::cout << " InstallKey: content_key = "
<< wvcdm::b2a_hex(content_key) << std::endl;
std::cout << " InstallKey: key_control = "
<< wvcdm::b2a_hex(content_key) << std::endl;
<< wvcdm::b2a_hex(key_control_str) << std::endl;
#endif
// Key control must be supplied by license server
@@ -762,6 +762,12 @@ bool SessionContext::UpdateMacKey(const std::vector<uint8_t>& enc_mac_key,
bool SessionContext::SelectContentKey(const KeyId& key_id) {
const Key* content_key = session_keys_.Find(key_id);
#if 0
std::cout << " Select Key: key_id = "
<< wvcdm::b2a_hex(key_id) << std::endl;
std::cout << " Select Key: key = "
<< wvcdm::b2a_hex(content_key->value()) << std::endl;
#endif
if (NULL == content_key) {
LOGE("[SelectContentKey(): No key matches key id]");
return false;
@@ -849,16 +855,18 @@ bool CryptoEngine::DecryptMessage(SessionContext* session,
}
bool CryptoEngine::DecryptCTR(SessionContext* session,
const std::vector<uint8_t>& iv,
const uint8_t* iv,
size_t byte_offset,
const std::vector<uint8_t>& cipher_data,
const uint8_t* cipher_data,
size_t cipher_data_length,
bool is_encrypted,
void* clear_data,
BufferType buffer_type) {
if (! is_encrypted) {
// If the data is clear, we do not need a current key selected.
if (!is_encrypted) {
memcpy(reinterpret_cast<uint8_t*>(clear_data),
&cipher_data[0], cipher_data.size());
cipher_data, cipher_data_length);
return true;
}
@@ -910,10 +918,6 @@ bool CryptoEngine::DecryptCTR(SessionContext* session,
// Local copy (will be modified).
uint8_t aes_iv[AES_BLOCK_SIZE];
if (static_cast<int>(iv.size()) != AES_BLOCK_SIZE) {
LOGE("[DecryptCTR(): FAILURE: iv has wrong length]");
return false;
}
memcpy(aes_iv, &iv[0], AES_BLOCK_SIZE);
// Encrypt the IV.
@@ -927,9 +931,9 @@ bool CryptoEngine::DecryptCTR(SessionContext* session,
// Decryption.
unsigned int byte_offset_cur = byte_offset;
AES_ctr128_encrypt(
&cipher_data[0], reinterpret_cast<uint8_t*>(clear_data), cipher_data.size(),
cipher_data, reinterpret_cast<uint8_t*>(clear_data), cipher_data_length,
&aes_key, aes_iv, ecount_buf, &byte_offset_cur);
if (byte_offset_cur != ((byte_offset + cipher_data.size()) % AES_BLOCK_SIZE)) {
if (byte_offset_cur != ((byte_offset + cipher_data_length) % AES_BLOCK_SIZE)) {
LOGE("[DecryptCTR(): FAILURE: byte offset wrong.]");
return false;
}

View File

@@ -233,9 +233,10 @@ class CryptoEngine {
std::vector<uint8_t>* decrypted);
bool DecryptCTR(SessionContext* session,
const std::vector<uint8_t>& iv,
const uint8_t* iv,
size_t byte_offset,
const std::vector<uint8_t>& cipher_data,
const uint8_t* cipher_data,
size_t cipher_data_length,
bool is_encrypted,
void* clear_data,
BufferType buffer_type);

View File

@@ -75,6 +75,8 @@ OEMCryptoResult OEMCrypto_Initialize(void) {
return OEMCrypto_ERROR_INIT_FAILED;
}
LOGD("[OEMCrypto_Initialize(): success]");
LOGW("WARNING -- this is the reference implementation of OEMCrypto.");
printf("WARNING -- you are using the reference implementation of OEMCrypto.\n");
return OEMCrypto_SUCCESS;
}
@@ -331,6 +333,7 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
// Decrypt and install keys in key object
// Each key will have a key control block. They will all have the same nonce.
bool status = true;
std::vector<uint8_t> key_id;
std::vector<uint8_t> enc_key_data;
std::vector<uint8_t> key_data_iv;
@@ -344,7 +347,8 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
key_data_iv.assign(key_array[i].key_data_iv,
key_array[i].key_data_iv + wvcdm::KEY_IV_SIZE);
if (key_array[i].key_control == NULL) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
key_control.assign(key_array[i].key_control,
key_array[i].key_control + wvcdm::KEY_CONTROL_SIZE);
@@ -353,12 +357,13 @@ OEMCryptoResult OEMCrypto_LoadKeys(OEMCrypto_SESSION session,
if (!session_ctx->InstallKey(key_id, enc_key_data, key_data_iv, key_control,
key_control_iv)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
}
// All keys processed. Flush nonce table
session_ctx->FlushNonces();
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
// enc_mac_key can be NULL if license renewal is not supported
if (enc_mac_key == NULL) return OEMCrypto_SUCCESS;
@@ -425,6 +430,7 @@ OEMCryptoResult OEMCrypto_RefreshKeys(OEMCrypto_SESSION session,
}
// Decrypt and refresh keys in key refresh object
bool status = true;
std::vector<uint8_t> key_id;
std::vector<uint8_t> key_control;
std::vector<uint8_t> key_control_iv;
@@ -447,9 +453,14 @@ OEMCryptoResult OEMCrypto_RefreshKeys(OEMCrypto_SESSION session,
}
if (!session_ctx->RefreshKey(key_id, key_control, key_control_iv)) {
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
status = false;
break;
}
}
session_ctx->FlushNonces();
if (!status) return OEMCrypto_ERROR_UNKNOWN_FAILURE;
session_ctx->StartTimer();
return OEMCrypto_SUCCESS;
}
@@ -462,10 +473,12 @@ OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session,
printf("-- OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session,\n");
dump_hex("key_id", key_id, key_id_length);
}
#ifndef NDEBUG
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_SelectKey(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
#endif
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
@@ -519,10 +532,12 @@ OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session,
return OEMCrypto_ERROR_SHORT_BUFFER;
}
#ifndef NDEBUG
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
LOGE("[OEMCrypto_DecryptCTR(): ERROR_KEYBOX_INVALID]");
return OEMCrypto_ERROR_KEYBOX_INVALID;
}
#endif
SessionContext* session_ctx = crypto_engine->FindSession(session);
if (!session_ctx || !session_ctx->isValid()) {
@@ -536,14 +551,11 @@ OEMCryptoResult OEMCrypto_DecryptCTR(OEMCrypto_SESSION session,
return OEMCrypto_ERROR_INVALID_CONTEXT;
}
std::vector<uint8_t> iv_v(iv, iv + 16);
std::vector<uint8_t> content(data_addr, data_addr + data_length);
if (!crypto_engine->DecryptCTR(session_ctx, iv_v, (int)offset,
content, is_encrypted,
if (!crypto_engine->DecryptCTR(session_ctx, iv, (int)offset,
data_addr, data_length, is_encrypted,
destination, buffer_type)) {
LOGE("[OEMCrypto_DecryptCTR(): OEMCrypto_ERROR_INVALID_CONTEXT]");
return OEMCrypto_ERROR_INVALID_CONTEXT;
LOGE("[OEMCrypto_DecryptCTR(): OEMCrypto_ERROR_DECRYPT_FAILED]");
return OEMCrypto_ERROR_DECRYPT_FAILED;
}
return OEMCrypto_SUCCESS;

View File

@@ -2119,6 +2119,47 @@ TEST_F(OEMCryptoClientTest, DecryptUnencrypted) {
testTearDown();
}
TEST_F(OEMCryptoClientTest, DecryptUnencryptedNoKey) {
OEMCryptoResult sts;
testSetUp();
InstallKeybox(kDefaultKeybox);
Session& s = createSession("NOKEY");
s.open();
// CLear data should be copied even if there is no key selected.
// Set up our expected input and output
vector<uint8_t> unencryptedData = wvcdm::a2b_hex(
"1558497b6d994be343ed1c6d6313e0537b843e9a9c0836d1e83fe33154191ce9"
"a14d8d95bebaddc03bd471827170f527c0a166b9068b273d1bc57fbb13975ee4"
"f6b9a31743da6c447acbb712e81b13eddfd4e96c76010ac9b8aa1b6b3152b0fc"
"39ad33e5719656069f9ede9ebba7a77dd2e2074eec5c1b7ffc427a6f1be168f0"
"b5857713a44623862c903284bc53417e23c65602b52c1cb699e8352453eb9698"
"0b31459b90c26c907b549c1ab293725e414d4e45f5b30af7a55f95499a7dc89f"
"7d13ba90b34aef6b49484b0701bf96ea8b660c24bb4e92a2d1c43beb434fa386"
"1071380388799ac31d79285f5817687ed3e2eeb73a30744e77b757686c9ba5ad");
vector<uint8_t> encryptionIv = wvcdm::a2b_hex(
"49fc3efaaf614ed81d595847b928edd0");
// Describe the output
uint8_t outputBuffer[256];
OEMCrypto_DestBufferDesc destBuffer;
destBuffer.type = OEMCrypto_BufferType_Clear;
destBuffer.buffer.clear.address = outputBuffer;
destBuffer.buffer.clear.max_length = sizeof(outputBuffer);
// Decrypt the data
sts = OEMCrypto_DecryptCTR(s.session_id(), &unencryptedData[0],
unencryptedData.size(), false, &encryptionIv[0], 0,
&destBuffer);
ASSERT_EQ(OEMCrypto_SUCCESS, sts);
ASSERT_EQ(0, memcmp(&unencryptedData[0], outputBuffer,
unencryptedData.size()));
s.close();
testTearDown();
}
TEST_F(OEMCryptoClientTest, DecryptSecureToClear) {
OEMCryptoResult sts;
testSetUp();
@@ -2728,9 +2769,9 @@ TEST_F(OEMCryptoClientTest, VersionNumber) {
const char* level = OEMCrypto_SecurityLevel();
ASSERT_NE((char *)NULL, level);
ASSERT_EQ('L', level[0]);
cout << "OEMCrypto Security Level is "<< level << endl;
cout << " OEMCrypto Security Level is "<< level << endl;
uint32_t version = OEMCrypto_APIVersion();
cout << "OEMCrypto API version is " << version << endl;
cout << " OEMCrypto API version is " << version << endl;
ASSERT_LT((uint32_t)5, version);
testTearDown();

View File

@@ -17,6 +17,8 @@ namespace wvdrm {
using namespace android;
WVGenericCryptoInterface WVDrmFactory::sOemCryptoInterface;
bool WVDrmFactory::isCryptoSchemeSupported(const uint8_t uuid[16]) {
return isWidevineUUID(uuid);
}
@@ -28,7 +30,7 @@ status_t WVDrmFactory::createDrmPlugin(const uint8_t uuid[16],
return BAD_VALUE;
}
*plugin = new WVDrmPlugin(getCDM());
*plugin = new WVDrmPlugin(getCDM(), &sOemCryptoInterface);
return OK;
}

View File

@@ -13,6 +13,8 @@ LOCAL_C_INCLUDES := \
frameworks/av/include \
frameworks/native/include \
vendor/widevine/libwvdrmengine/include \
vendor/widevine/libwvdrmengine/mediadrm/include \
vendor/widevine/libwvdrmengine/oemcrypto/include \
LOCAL_STATIC_LIBRARIES := \
libgtest \

View File

@@ -1,15 +0,0 @@
/*
* Copyright 2012 Google Inc. All Rights Reserved.
*/
#include "gtest/gtest.h"
#include "utils/UniquePtr.h"
#include "WVCreateDrmPluginFactory.h"
using namespace android;
TEST(CreateDrmPluginFactoryTest, CreatesObject) {
UniquePtr<DrmPluginFactory> factory(createDrmPluginFactory());
EXPECT_NE((DrmPluginFactory*)NULL, factory.get()) << "createDrmPluginFactory() returned null";
}

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2012 Google Inc. All Rights Reserved.
*/
#include "gtest/gtest.h"
#include "utils/UniquePtr.h"
#include "WVDrmPluginFactory.h"
using namespace wvdrm;
const uint8_t kWidevineUUID[16] = {
0xED,0xEF,0x8B,0xA9,0x79,0xD6,0x4A,0xCE,
0xA3,0xC8,0x27,0xDC,0xD5,0x1D,0x21,0xED
};
const uint8_t kOldNetflixWidevineUUID[16] = {
0x29,0x70,0x1F,0xE4,0x3C,0xC7,0x4A,0x34,
0x8C,0x5B,0xAE,0x90,0xC7,0x43,0x9A,0x47
};
const uint8_t kUnknownUUID[16] = {
0x6A,0x7F,0xAA,0xB0,0x83,0xC7,0x9E,0x20,
0x08,0xBC,0xEF,0x32,0x34,0x1A,0x9A,0x26
};
TEST(WVDrmPluginFactoryTest, SupportsSupportedCryptoSchemes) {
UniquePtr<WVDrmPluginFactory> factory(new WVDrmPluginFactory());
EXPECT_TRUE(factory->isCryptoSchemeSupported(kWidevineUUID)) <<
"WVPluginFactory does not support Widevine's UUID";
EXPECT_TRUE(factory->isCryptoSchemeSupported(kOldNetflixWidevineUUID)) <<
"WVPluginFactory does not support the old Netflix Widevine UUID";
}
TEST(WVDrmPluginFactoryTest, DoesNotSupportUnsupportedCryptoSchemes) {
UniquePtr<WVDrmPluginFactory> factory(new WVDrmPluginFactory());
EXPECT_FALSE(factory->isCryptoSchemeSupported(kUnknownUUID)) <<
"WVPluginFactory incorrectly claims to support an unknown UUID";
}