Widevine CENC drm engine update

bug: 8601053

This import syncs to the widevine git repository change
commit 6a99ad1b59ad39495f62954b3065ddc22b78da49

It includes the following changes from the widevine git
repository, which complete the jb-mr2 features

    Fix Unit Test Makefile
    Adds support for device certificate provisioning.
    Support application parameters
    Certificate based licensing
    Proto for client files
    Implement Property Query API
    Add Device Query For Unique ID
    Implement Generic Crypto in DrmEngine
    Do not validate Key IDs on clear playback
    Allow OEMCrypto_DecryptCTR with clear content and no key
    Add a case to the MediaDrm API test to repro b/8594163
    Implement requiresSecureDecoderComponent
    Implement Eventing API
    Add end-to-end decryption test with vectors
    Refactoring of properties class
    Refactor OEMCrypto unittest.
    Fix for b/8567853: License renewal doesn't renew license.
    Add KEY_ERROR callback to WvContentDecryptionModule() ctor.
    Merged certificate_provisioning.proto and
      client_identification.proto to license_protocol.proto.
    Fix nonce check failure after a malformed key in OEC Mock.
    asynchronize decryption
    Allow querying of control information
    make debugging AddKey & Decrypt statuses easier
    Revert "Revert "Send KEY_ERROR event to app on license
      expiration or failure""
    Revert "Send KEY_ERROR event to app on license expiration
      or failure"
    Send KEY_ERROR event to app on license expiration or failure
    remove extra session id copy
    use KeyError constants directly
    replace variable-length arrays with std::vector and fixed-sized array
    pass session ids as const references
    refactor key extraction and update keys on renewal
    Updates to enable renewals and signaling license expiration.
    fix error constant in OEMCrypto_DecryptCTR

Change-Id: I5f7236c7bdff1d5ece6115fd2893f8a1e1e07c50
This commit is contained in:
Jeff Tinker
2013-04-12 14:12:16 -07:00
parent 2f980d7d7e
commit e6b1fedc4c
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,7 +162,7 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted,
key_id_ = key_id;
}
else {
return UNKNOWN_ERROR;
return NEED_KEY;
}
}
}
@@ -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";
}