am c0dde166: Merge "Widevine CENC drm engine update" into jb-mr2-dev
* commit 'c0dde166445b228d321258470661591dacc964fb': Widevine CENC drm engine update
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
61
libwvdrmengine/build_and_run_all_unit_tests.sh
Executable file
61
libwvdrmengine/build_and_run_all_unit_tests.sh
Executable 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"
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -35,6 +35,8 @@ enum CdmResponseType {
|
||||
KEY_MESSAGE,
|
||||
NEED_KEY,
|
||||
KEY_CANCELED,
|
||||
NEED_PROVISIONING,
|
||||
DEVICE_REVOKED,
|
||||
};
|
||||
|
||||
#define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "cdm_session.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "clock.h"
|
||||
#include "crypto_engine.h"
|
||||
@@ -16,31 +17,32 @@ namespace wvcdm {
|
||||
|
||||
typedef std::set<WvCdmEventListener*>::iterator CdmEventListenerIter;
|
||||
|
||||
bool CdmSession::Init() {
|
||||
CdmResponseType CdmSession::Init() {
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
if (!crypto_engine) {
|
||||
LOGE("CdmSession::Init failed to get CryptoEngine instance.");
|
||||
return false;
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
crypto_session_ = crypto_engine->CreateSession(session_id_);
|
||||
if (!crypto_session_) {
|
||||
return false;
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
std::string token;
|
||||
if (!crypto_engine->GetToken(&token)) return false;
|
||||
|
||||
if (!Properties::GetInstance()->GetProperty(
|
||||
kPropertyKeyRequireExplicitRenewRequest,
|
||||
require_explicit_renew_request_)) {
|
||||
LOGE("CdmSession::Init: Unable to access property - require explicit renew");
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
if (!LoadDeviceCertificate(&token, &wrapped_key_))
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
else {
|
||||
properties_valid_ = true;
|
||||
if (!crypto_engine->GetToken(&token))
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
return license_parser_.Init(token, crypto_session_, &policy_engine_);
|
||||
if (license_parser_.Init(token, crypto_session_, &policy_engine_))
|
||||
return NO_ERROR;
|
||||
else
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
bool CdmSession::DestroySession() {
|
||||
@@ -58,13 +60,11 @@ bool CdmSession::VerifySession(const CdmKeySystem& key_system,
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::GenerateKeyRequest(const CdmInitData& init_data,
|
||||
CdmKeyMessage* key_request) {
|
||||
if (!properties_valid_) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: Unable to access properties");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
const CdmInitData& pssh_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request) {
|
||||
if (!crypto_session_) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session");
|
||||
return UNKNOWN_ERROR;
|
||||
@@ -76,11 +76,19 @@ CdmResponseType CdmSession::GenerateKeyRequest(const CdmInitData& init_data,
|
||||
}
|
||||
|
||||
if (license_received_) {
|
||||
return require_explicit_renew_request_ ?
|
||||
return Properties::require_explicit_renew_request() ?
|
||||
UNKNOWN_ERROR : GenerateRenewalRequest(key_request);
|
||||
}
|
||||
else {
|
||||
if(!license_parser_.PrepareKeyRequest(init_data, key_request)) {
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
if (!crypto_session_->LoadCertificatePrivateKey(wrapped_key_))
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
|
||||
if (!license_parser_.PrepareKeyRequest(pssh_data,
|
||||
license_type,
|
||||
app_parameters,
|
||||
key_request)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
return KEY_MESSAGE;
|
||||
@@ -101,16 +109,16 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
|
||||
}
|
||||
|
||||
if (license_received_) {
|
||||
return require_explicit_renew_request_ ?
|
||||
return Properties::require_explicit_renew_request() ?
|
||||
UNKNOWN_ERROR : RenewKey(key_response);
|
||||
}
|
||||
else {
|
||||
if (!license_parser_.HandleKeyResponse(key_response)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
CdmResponseType sts = license_parser_.HandleKeyResponse(key_response);
|
||||
|
||||
if (sts == KEY_ADDED)
|
||||
license_received_ = true;
|
||||
return KEY_ADDED;
|
||||
}
|
||||
|
||||
return sts;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +126,16 @@ CdmResponseType CdmSession::QueryKeyStatus(CdmQueryMap* key_info) {
|
||||
return policy_engine_.Query(key_info);
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) {
|
||||
if ((!crypto_session_) || (!crypto_session_->IsOpen()))
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << crypto_session_->oec_session_id();
|
||||
(*key_info)[QUERY_KEY_OEMCRYPTO_SESSION_ID] = ss.str();
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// CancelKeyRequest() - Cancel session.
|
||||
CdmResponseType CdmSession::CancelKeyRequest() {
|
||||
// TODO(gmorgan): cancel and clean up session
|
||||
@@ -144,12 +162,12 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted,
|
||||
key_id_ = key_id;
|
||||
}
|
||||
else {
|
||||
return UNKNOWN_ERROR;
|
||||
return NEED_KEY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crypto_session_->Decrypt(is_encrypted, encrypt_buffer, encrypt_length,
|
||||
return crypto_session_->Decrypt(is_encrypted, encrypt_buffer, encrypt_length,
|
||||
iv, block_offset, decrypt_buffer, is_video);
|
||||
}
|
||||
|
||||
@@ -157,7 +175,7 @@ CdmResponseType CdmSession::Decrypt(bool is_encrypted,
|
||||
// GenerateRenewalRequest() - Construct valid renewal request for the current
|
||||
// session keys.
|
||||
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request) {
|
||||
if(!license_parser_.PrepareKeyRenewalRequest(key_request)) {
|
||||
if (!license_parser_.PrepareKeyRenewalRequest(key_request)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
return KEY_MESSAGE;
|
||||
@@ -166,11 +184,7 @@ CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request) {
|
||||
|
||||
// RenewKey() - Accept renewal response and update key info.
|
||||
CdmResponseType CdmSession::RenewKey(const CdmKeyResponse& key_response) {
|
||||
if (!license_parser_.HandleKeyRenewalResponse(key_response)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
return KEY_ADDED;
|
||||
}
|
||||
return license_parser_.HandleKeyRenewalResponse(key_response);
|
||||
}
|
||||
|
||||
bool CdmSession::IsKeyValid(const KeyId& key_id) {
|
||||
@@ -187,6 +201,12 @@ CdmSessionId CdmSession::GenerateSessionId() {
|
||||
return kSessionPrefix + IntToString(++session_num);
|
||||
}
|
||||
|
||||
bool CdmSession::LoadDeviceCertificate(std::string* certificate,
|
||||
std::string* wrapped_key) {
|
||||
// TODO(edwingwong,rfrias): Need to read in the private key
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {
|
||||
std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener);
|
||||
return result.second;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
27
libwvdrmengine/cdm/core/src/client_files.proto
Normal file
27
libwvdrmengine/cdm/core/src/client_files.proto
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
64
libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h
Normal file
64
libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h
Normal 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_
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
Reference in New Issue
Block a user