diff --git a/libwvdrmengine/Android.mk b/libwvdrmengine/Android.mk index 8e678010..c6bdb741 100644 --- a/libwvdrmengine/Android.mk +++ b/libwvdrmengine/Android.mk @@ -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) diff --git a/libwvdrmengine/build_and_run_all_unit_tests.sh b/libwvdrmengine/build_and_run_all_unit_tests.sh new file mode 100755 index 00000000..750b6f5c --- /dev/null +++ b/libwvdrmengine/build_and_run_all_unit_tests.sh @@ -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" diff --git a/libwvdrmengine/cdm/Android.mk b/libwvdrmengine/cdm/Android.mk index 7b315132..04988226 100644 --- a/libwvdrmengine/cdm/Android.mk +++ b/libwvdrmengine/cdm/Android.mk @@ -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 diff --git a/libwvdrmengine/cdm/core/include/cdm_engine.h b/libwvdrmengine/cdm/core/include/cdm_engine.h index da193f01..88cc0d72 100644 --- a/libwvdrmengine/cdm/core/include/cdm_engine.h +++ b/libwvdrmengine/cdm/core/include/cdm_engine.h @@ -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 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(); diff --git a/libwvdrmengine/cdm/core/include/cdm_session.h b/libwvdrmengine/cdm/core/include/cdm_session.h index 3ddb3f2e..6993f1ed 100644 --- a/libwvdrmengine/cdm/core/include/cdm_session.h +++ b/libwvdrmengine/cdm/core/include/cdm_session.h @@ -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 listeners_; // TODO(kqyang): CdmKey not defined yet diff --git a/libwvdrmengine/cdm/core/include/crypto_engine.h b/libwvdrmengine/cdm/core/include/crypto_engine.h index 7ff3cd64..df651960 100644 --- a/libwvdrmengine/cdm/core/include/crypto_engine.h +++ b/libwvdrmengine/cdm/core/include/crypto_engine.h @@ -5,6 +5,8 @@ #ifndef CDM_BASE_CRYPTO_ENGINE_H_ #define CDM_BASE_CRYPTO_ENGINE_H_ +#include + #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); }; diff --git a/libwvdrmengine/cdm/core/include/crypto_key.h b/libwvdrmengine/cdm/core/include/crypto_key.h index 40d83b1d..bdae2441 100644 --- a/libwvdrmengine/cdm/core/include/crypto_key.h +++ b/libwvdrmengine/cdm/core/include/crypto_key.h @@ -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 diff --git a/libwvdrmengine/cdm/core/include/crypto_session.h b/libwvdrmengine/cdm/core/include/crypto_session.h index c0e27d37..f41f3849 100644 --- a/libwvdrmengine/cdm/core/include/crypto_session.h +++ b/libwvdrmengine/cdm/core/include/crypto_session.h @@ -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); diff --git a/libwvdrmengine/cdm/core/include/license.h b/libwvdrmengine/cdm/core/include/license.h index 01b9afca..30f15e31 100644 --- a/libwvdrmengine/cdm/core/include/license.h +++ b/libwvdrmengine/cdm/core/include/license.h @@ -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); }; diff --git a/libwvdrmengine/cdm/core/include/lock.h b/libwvdrmengine/cdm/core/include/lock.h index d9f43efd..e214b2dc 100644 --- a/libwvdrmengine/cdm/core/include/lock.h +++ b/libwvdrmengine/cdm/core/include/lock.h @@ -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); }; diff --git a/libwvdrmengine/cdm/core/include/policy_engine.h b/libwvdrmengine/cdm/core/include/policy_engine.h index 54691b01..2dbeee70 100644 --- a/libwvdrmengine/cdm/core/include/policy_engine.h +++ b/libwvdrmengine/cdm/core/include/policy_engine.h @@ -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 diff --git a/libwvdrmengine/cdm/core/include/properties.h b/libwvdrmengine/cdm/core/include/properties.h index 04ad0321..132cd174 100644 --- a/libwvdrmengine/cdm/core/include/properties.h +++ b/libwvdrmengine/cdm/core/include/properties.h @@ -11,39 +11,62 @@ namespace wvcdm { -typedef std::map 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); }; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h index ac8c6ec5..64662ccc 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_constants.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_constants.h @@ -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"; diff --git a/libwvdrmengine/cdm/core/include/wv_cdm_types.h b/libwvdrmengine/cdm/core/include/wv_cdm_types.h index 821adaa0..44815b6b 100644 --- a/libwvdrmengine/cdm/core/include/wv_cdm_types.h +++ b/libwvdrmengine/cdm/core/include/wv_cdm_types.h @@ -35,6 +35,8 @@ enum CdmResponseType { KEY_MESSAGE, NEED_KEY, KEY_CANCELED, + NEED_PROVISIONING, + DEVICE_REVOKED, }; #define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \ diff --git a/libwvdrmengine/cdm/core/src/cdm_engine.cpp b/libwvdrmengine/cdm/core/src/cdm_engine.cpp index b2713553..6e560102 100644 --- a/libwvdrmengine/cdm/core/src/cdm_engine.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_engine.cpp @@ -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::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::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 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 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 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 wrapped_rsa_key; + size_t wrapped_rsa_key_length = 0; + if (!crypto_session->RewrapDeviceRSAKey(response, + &nonce, + reinterpret_cast(enc_rsa_key.data()), + enc_rsa_key.length(), + reinterpret_cast(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); diff --git a/libwvdrmengine/cdm/core/src/cdm_session.cpp b/libwvdrmengine/cdm/core/src/cdm_session.cpp index 07732020..205c4721 100644 --- a/libwvdrmengine/cdm/core/src/cdm_session.cpp +++ b/libwvdrmengine/cdm/core/src/cdm_session.cpp @@ -4,6 +4,7 @@ #include "cdm_session.h" #include +#include #include "clock.h" #include "crypto_engine.h" @@ -16,31 +17,32 @@ namespace wvcdm { typedef std::set::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 result = listeners_.insert(listener); return result.second; diff --git a/libwvdrmengine/cdm/core/src/certificate_provisioning.proto b/libwvdrmengine/cdm/core/src/certificate_provisioning.proto deleted file mode 100644 index db51b0f2..00000000 --- a/libwvdrmengine/cdm/core/src/certificate_provisioning.proto +++ /dev/null @@ -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; -} diff --git a/libwvdrmengine/cdm/core/src/client_files.proto b/libwvdrmengine/cdm/core/src/client_files.proto new file mode 100644 index 00000000..33ddb04c --- /dev/null +++ b/libwvdrmengine/cdm/core/src/client_files.proto @@ -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; +} diff --git a/libwvdrmengine/cdm/core/src/client_identification.proto b/libwvdrmengine/cdm/core/src/client_identification.proto deleted file mode 100644 index 29ad82da..00000000 --- a/libwvdrmengine/cdm/core/src/client_identification.proto +++ /dev/null @@ -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; -} diff --git a/libwvdrmengine/cdm/core/src/crypto_engine.cpp b/libwvdrmengine/cdm/core/src/crypto_engine.cpp index 79ea0835..42c6056f 100644 --- a/libwvdrmengine/cdm/core/src/crypto_engine.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_engine.cpp @@ -6,6 +6,7 @@ #include "crypto_engine.h" #include +#include #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 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(&id[0])); +} + }; // namespace wvcdm diff --git a/libwvdrmengine/cdm/core/src/crypto_session.cpp b/libwvdrmengine/cdm/core/src/crypto_session.cpp index 535f76cd..66fdf985 100755 --- a/libwvdrmengine/cdm/core/src/crypto_session.cpp +++ b/libwvdrmengine/cdm/core/src/crypto_session.cpp @@ -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(mac_deriv_message.data()), - mac_deriv_message.size(), - reinterpret_cast(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(message.data()), - message.size(), - signature_buf, - &length); - if (OEMCrypto_SUCCESS != sts) { + if (!GenerateSignature(message, signature)) { return false; } - signature->assign(reinterpret_cast(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(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(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(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(mac_deriv_message.data()), + mac_deriv_message.size(), + reinterpret_cast(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(session_key.data()), + session_key.size(), + reinterpret_cast(mac_deriv_message.data()), + mac_deriv_message.size(), + reinterpret_cast(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(message.data()), + message.size(), + signature_buf, + &length); + } + else { + sts = OEMCrypto_GenerateSignature( + oec_session_id_, + reinterpret_cast(message.data()), + message.size(), + signature_buf, + &length); + } + + if (OEMCrypto_SUCCESS != sts) { + LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts); + return false; + } + + signature->assign(reinterpret_cast(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(oec_session_id_)); + + // HMAC-SHA256 signature + uint8_t signature[kSignatureSize]; + size_t signature_length = kSignatureSize; + OEMCryptoResult status = OEMCrypto_GenerateSignature( + oec_session_id_, + reinterpret_cast(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(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 diff --git a/libwvdrmengine/cdm/core/src/device_certificate.proto b/libwvdrmengine/cdm/core/src/device_certificate.proto deleted file mode 100644 index 20b8318a..00000000 --- a/libwvdrmengine/cdm/core/src/device_certificate.proto +++ /dev/null @@ -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; -} diff --git a/libwvdrmengine/cdm/core/src/license.cpp b/libwvdrmengine/cdm/core/src/license.cpp index c54c7e4b..542d6fe3 100644 --- a/libwvdrmengine/cdm/core/src/license.cpp +++ b/libwvdrmengine/cdm/core/src/license.cpp @@ -2,26 +2,62 @@ #include "license.h" +#include + #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 ExtractContentKeys(const License& license) { + std::vector 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 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 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 diff --git a/libwvdrmengine/cdm/core/src/license_protocol.proto b/libwvdrmengine/cdm/core/src/license_protocol.proto index 020d0207..9aed6359 100644 --- a/libwvdrmengine/cdm/core/src/license_protocol.proto +++ b/libwvdrmengine/cdm/core/src/license_protocol.proto @@ -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; +} diff --git a/libwvdrmengine/cdm/core/src/policy_engine.cpp b/libwvdrmengine/cdm/core/src/policy_engine.cpp index 098fda43..5d4ef60d 100644 --- a/libwvdrmengine/cdm/core/src/policy_engine.cpp +++ b/libwvdrmengine/cdm/core/src/policy_engine.cpp @@ -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: diff --git a/libwvdrmengine/cdm/core/src/properties.cpp b/libwvdrmengine/cdm/core/src/properties.cpp index e2dccc21..1f466f9f 100644 --- a/libwvdrmengine/cdm/core/src/properties.cpp +++ b/libwvdrmengine/cdm/core/src/properties.cpp @@ -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 diff --git a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp index 8568d297..51855b76 100644 --- a/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp +++ b/libwvdrmengine/cdm/core/test/cdm_engine_test.cpp @@ -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); diff --git a/libwvdrmengine/cdm/core/test/license_unittest.cpp b/libwvdrmengine/cdm/core/test/license_unittest.cpp index 470745d4..6a692a05 100644 --- a/libwvdrmengine/cdm/core/test/license_unittest.cpp +++ b/libwvdrmengine/cdm/core/test/license_unittest.cpp @@ -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))); } diff --git a/libwvdrmengine/cdm/core/test/url_request.cpp b/libwvdrmengine/cdm/core/test/url_request.cpp index c1bd83ff..27b8bd0c 100644 --- a/libwvdrmengine/cdm/core/test/url_request.cpp +++ b/libwvdrmengine/cdm/core/test/url_request.cpp @@ -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 { diff --git a/libwvdrmengine/cdm/include/properties_configuration.h b/libwvdrmengine/cdm/include/properties_configuration.h index 2f2413a5..49e7a671 100644 --- a/libwvdrmengine/cdm/include/properties_configuration.h +++ b/libwvdrmengine/cdm/include/properties_configuration.h @@ -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 diff --git a/libwvdrmengine/cdm/include/wv_content_decryption_module.h b/libwvdrmengine/cdm/include/wv_content_decryption_module.h index cf13938e..aeb86fed 100644 --- a/libwvdrmengine/cdm/include/wv_content_decryption_module.h +++ b/libwvdrmengine/cdm/include/wv_content_decryption_module.h @@ -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: diff --git a/libwvdrmengine/cdm/src/lock.cpp b/libwvdrmengine/cdm/src/lock.cpp index 49404597..d118c24f 100644 --- a/libwvdrmengine/cdm/src/lock.cpp +++ b/libwvdrmengine/cdm/src/lock.cpp @@ -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 diff --git a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp index b4ef9166..0a273f7c 100644 --- a/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp +++ b/libwvdrmengine/cdm/src/wv_content_decryption_module.cpp @@ -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); } diff --git a/libwvdrmengine/cdm/test/request_license_test.cpp b/libwvdrmengine/cdm/test/request_license_test.cpp index d1e2a1ce..a746d7b1 100644 --- a/libwvdrmengine/cdm/test/request_license_test.cpp +++ b/libwvdrmengine/cdm/test/request_license_test.cpp @@ -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 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); diff --git a/libwvdrmengine/cdm/test/unit-test.mk b/libwvdrmengine/cdm/test/unit-test.mk index 82160ab9..e465ca7c 100644 --- a/libwvdrmengine/cdm/test/unit-test.mk +++ b/libwvdrmengine/cdm/test/unit-test.mk @@ -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 diff --git a/libwvdrmengine/cdm/unit-test.mk b/libwvdrmengine/cdm/unit-test.mk deleted file mode 100644 index b539e286..00000000 --- a/libwvdrmengine/cdm/unit-test.mk +++ /dev/null @@ -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) diff --git a/libwvdrmengine/include/WVDrmFactory.h b/libwvdrmengine/include/WVDrmFactory.h index b2106ffd..e33845b7 100644 --- a/libwvdrmengine/include/WVDrmFactory.h +++ b/libwvdrmengine/include/WVDrmFactory.h @@ -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 diff --git a/libwvdrmengine/level3/arm/entry_points.cpp b/libwvdrmengine/level3/arm/entry_points.cpp index 9ab1ad2d..bf3c100f 100644 --- a/libwvdrmengine/level3/arm/entry_points.cpp +++ b/libwvdrmengine/level3/arm/entry_points.cpp @@ -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 key_id; std::vector enc_key_data; std::vector 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 key_id; std::vector key_control; std::vector 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 iv_v(iv, iv + 16); - std::vector 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 diff --git a/libwvdrmengine/level3/mips/entry_points.cpp b/libwvdrmengine/level3/mips/entry_points.cpp index 9ab1ad2d..bf3c100f 100644 --- a/libwvdrmengine/level3/mips/entry_points.cpp +++ b/libwvdrmengine/level3/mips/entry_points.cpp @@ -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 key_id; std::vector enc_key_data; std::vector 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 key_id; std::vector key_control; std::vector 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 iv_v(iv, iv + 16); - std::vector 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 diff --git a/libwvdrmengine/level3/x86/entry_points.cpp b/libwvdrmengine/level3/x86/entry_points.cpp index 9ab1ad2d..bf3c100f 100644 --- a/libwvdrmengine/level3/x86/entry_points.cpp +++ b/libwvdrmengine/level3/x86/entry_points.cpp @@ -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 key_id; std::vector enc_key_data; std::vector 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 key_id; std::vector key_control; std::vector 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 iv_v(iv, iv + 16); - std::vector 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 diff --git a/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h b/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h index 36222ef2..b24e6c0c 100644 --- a/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h +++ b/libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h @@ -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 diff --git a/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp b/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp index 57cdc1d2..af592fc2 100644 --- a/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp +++ b/libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp @@ -8,6 +8,7 @@ #include "WVCryptoPlugin.h" +#include #include #include #include @@ -26,19 +27,37 @@ using namespace wvcdm; WVCryptoPlugin::WVCryptoPlugin(const void* data, size_t size, WvContentDecryptionModule* cdm) : mCDM(cdm), - mSessionId(static_cast(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(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. diff --git a/libwvdrmengine/mediacrypto/test/Android.mk b/libwvdrmengine/mediacrypto/test/Android.mk index 878fba4f..d99ad100 100644 --- a/libwvdrmengine/mediacrypto/test/Android.mk +++ b/libwvdrmengine/mediacrypto/test/Android.mk @@ -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 diff --git a/libwvdrmengine/mediacrypto/test/MockCDM.h b/libwvdrmengine/mediacrypto/test/MockCDM.h deleted file mode 100644 index e20536d0..00000000 --- a/libwvdrmengine/mediacrypto/test/MockCDM.h +++ /dev/null @@ -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 -#include - -#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&, size_t, - void*)); -}; - -} // namespace wvcdm - -#endif // WV_CRYPTO_PLUGIN_MOCK_CDM_H_ diff --git a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp index 408ccc36..d5cc7db7 100644 --- a/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp +++ b/libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp @@ -4,12 +4,15 @@ #include #include +#include +#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&, 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(-EPERM), res) << + EXPECT_LT(res, static_cast(0)) << "WVCryptoPlugin allowed decryption to proceed despite being asked for an " "unsupported security level"; } diff --git a/libwvdrmengine/mediadrm/Android.mk b/libwvdrmengine/mediadrm/Android.mk index 0ddc6bcc..c58f2e10 100644 --- a/libwvdrmengine/mediadrm/Android.mk +++ b/libwvdrmengine/mediadrm/Android.mk @@ -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 diff --git a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h index 0d0cfab8..2ad773b6 100644 --- a/libwvdrmengine/mediadrm/include/WVDrmPlugin.h +++ b/libwvdrmengine/mediadrm/include/WVDrmPlugin.h @@ -5,15 +5,20 @@ #ifndef WV_DRM_PLUGIN_H_ #define WV_DRM_PLUGIN_H_ +#include + #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(-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& sessionId); @@ -72,39 +88,77 @@ class WVDrmPlugin : public android::DrmPlugin { virtual status_t setPropertyByteArray(const String8& name, const Vector& value); - virtual status_t setCipherAlgorithm(Vector const &sessionId, - String8 const &algorithm); - virtual status_t setMacAlgorithm(Vector const &sessionId, - String8 const &algorithm); + virtual status_t setCipherAlgorithm(const Vector& sessionId, + const String8& algorithm); - virtual status_t encrypt(Vector const &sessionId, - Vector const &keyId, - Vector const &input, - Vector const &iv, - Vector &output); + virtual status_t setMacAlgorithm(const Vector& sessionId, + const String8& algorithm); - virtual status_t decrypt(Vector const &sessionId, - Vector const &keyId, - Vector const &input, - Vector const &iv, - Vector &output); + virtual status_t encrypt(const Vector& sessionId, + const Vector& keyId, + const Vector& input, + const Vector& iv, + Vector& output); - virtual status_t sign(Vector const &sessionId, - Vector const &keyId, - Vector const &message, - Vector &signature); + virtual status_t decrypt(const Vector& sessionId, + const Vector& keyId, + const Vector& input, + const Vector& iv, + Vector& output); - virtual status_t verify(Vector const &sessionId, - Vector const &keyId, - Vector const &message, - Vector const &signature, - bool &match); + virtual status_t sign(const Vector& sessionId, + const Vector& keyId, + const Vector& message, + Vector& signature); + + virtual status_t verify(const Vector& sessionId, + const Vector& keyId, + const Vector& message, + const Vector& 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 mCryptoSessions; }; } // namespace wvdrm diff --git a/libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h b/libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h new file mode 100644 index 00000000..146d647a --- /dev/null +++ b/libwvdrmengine/mediadrm/include/WVGenericCryptoInterface.h @@ -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_ diff --git a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp index 16519a2f..0297dfca 100644 --- a/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp +++ b/libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp @@ -8,9 +8,11 @@ #include "WVDrmPlugin.h" +#include #include #include +#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::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& sessionId) { CdmSessionId cdmSessionId; @@ -30,11 +49,46 @@ status_t WVDrmPlugin::openSession(Vector& sessionId) { return android::UNKNOWN_ERROR; } - sessionId.clear(); - sessionId.appendArray(reinterpret_cast(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(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& sessionId) { @@ -42,6 +96,7 @@ status_t WVDrmPlugin::closeSession(const Vector& 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& sessionId, const Vector& response, Vector& 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& keySetId) { // TODO: remove persisted offline keys associated with keySetId - return android::UNKNOWN_ERROR; + return android::ERROR_UNSUPPORTED; } status_t WVDrmPlugin::restoreKeys(const Vector& sessionId, const Vector& 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& 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& value) const { - // TODO: Implement this function once the CDM query API is finalized. - return -EPERM; + Vector& 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(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& value) { - // TODO: Implement this function once the CDM query API is finalized. - return -EPERM; + return android::ERROR_UNSUPPORTED; } -status_t WVDrmPlugin::setCipherAlgorithm(Vector const &sessionId, - String8 const &algorithm) { - // TODO: Implement this function once the OEMCrypto API supports it - return -EPERM; +status_t WVDrmPlugin::setCipherAlgorithm(const Vector& 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 const &sessionId, - String8 const &algorithm) { - // TODO: Implement this function once the OEMCrypto API supports it - return -EPERM; +status_t WVDrmPlugin::setMacAlgorithm(const Vector& 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 const &sessionId, - Vector const &keyId, - Vector const &input, - Vector const &iv, - Vector &output) { - // TODO: Implement this function once the OEMCrypto API supports it - return -EPERM; +status_t WVDrmPlugin::encrypt(const Vector& sessionId, + const Vector& keyId, + const Vector& input, + const Vector& iv, + Vector& 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 const &sessionId, - Vector const &keyId, - Vector const &input, - Vector const &iv, - Vector &output) { - // TODO: Implement this function once the OEMCrypto API supports it - return -EPERM; +status_t WVDrmPlugin::decrypt(const Vector& sessionId, + const Vector& keyId, + const Vector& input, + const Vector& iv, + Vector& 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 const &sessionId, - Vector const &keyId, - Vector const &message, - Vector &signature) { - // TODO: Implement this function once the OEMCrypto API supports it - return -EPERM; +status_t WVDrmPlugin::sign(const Vector& sessionId, + const Vector& keyId, + const Vector& message, + Vector& 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 const &sessionId, - Vector const &keyId, - Vector const &message, - Vector const &signature, - bool &match) { - // TODO: Implement this function once the OEMCrypto API supports it - return -EPERM; +status_t WVDrmPlugin::verify(const Vector& sessionId, + const Vector& keyId, + const Vector& message, + const Vector& 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 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(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 diff --git a/libwvdrmengine/mediadrm/test/Android.mk b/libwvdrmengine/mediadrm/test/Android.mk index 8a44ad9c..a0d5fa9d 100644 --- a/libwvdrmengine/mediadrm/test/Android.mk +++ b/libwvdrmengine/mediadrm/test/Android.mk @@ -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 diff --git a/libwvdrmengine/mediadrm/test/MockCDM.h b/libwvdrmengine/mediadrm/test/MockCDM.h deleted file mode 100644 index deb36c8b..00000000 --- a/libwvdrmengine/mediadrm/test/MockCDM.h +++ /dev/null @@ -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 -#include - -#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_ diff --git a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp index f30abff1..c6760b87 100644 --- a/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp +++ b/libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp @@ -5,11 +5,13 @@ #include #include +#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*, const Vector*)); +}; + +template +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 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::Set(wvcdm::NO_ERROR); + DefaultValue::Set(OEMCrypto_SUCCESS); + DefaultValue::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 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 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 result; - - status_t res = plugin.getPropertyByteArray(String8("property"), result); + String8 stringResult; + Vector 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 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 keyId; + Vector input; + Vector iv; + Vector 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 keyId; + keyId.appendArray(keyIdRaw, KEY_ID_SIZE); + Vector input; + input.appendArray(inputRaw, kDataSize); + Vector iv; + iv.appendArray(ivRaw, KEY_IV_SIZE); + Vector 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 keyId; + keyId.appendArray(keyIdRaw, KEY_ID_SIZE); + Vector input; + input.appendArray(inputRaw, kDataSize); + Vector iv; + iv.appendArray(ivRaw, KEY_IV_SIZE); + Vector 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 keyId; + keyId.appendArray(keyIdRaw, KEY_ID_SIZE); + Vector message; + message.appendArray(messageRaw, kDataSize); + Vector 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 keyId; + keyId.appendArray(keyIdRaw, KEY_ID_SIZE); + Vector message; + message.appendArray(messageRaw, kDataSize); + Vector 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 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); +} diff --git a/libwvdrmengine/oemcrypto/include/L3CryptoCENC.h b/libwvdrmengine/oemcrypto/include/L3CryptoCENC.h deleted file mode 100644 index d9f9a9f4..00000000 --- a/libwvdrmengine/oemcrypto/include/L3CryptoCENC.h +++ /dev/null @@ -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_ diff --git a/libwvdrmengine/oemcrypto/mock/src/lock.cpp b/libwvdrmengine/oemcrypto/mock/src/lock.cpp index cfec2477..6deccf8d 100644 --- a/libwvdrmengine/oemcrypto/mock/src/lock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/lock.cpp @@ -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 diff --git a/libwvdrmengine/oemcrypto/mock/src/lock.h b/libwvdrmengine/oemcrypto/mock/src/lock.h index bd9402e6..76be6a29 100644 --- a/libwvdrmengine/oemcrypto/mock/src/lock.h +++ b/libwvdrmengine/oemcrypto/mock/src/lock.h @@ -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); }; diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp index 1067b077..fcc8795f 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.cpp @@ -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 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& 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& iv, + const uint8_t* iv, size_t byte_offset, - const std::vector& 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(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(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(clear_data), cipher_data.size(), + cipher_data, reinterpret_cast(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; } diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h index fc55b99a..d6eb8863 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_engine_mock.h @@ -233,9 +233,10 @@ class CryptoEngine { std::vector* decrypted); bool DecryptCTR(SessionContext* session, - const std::vector& iv, + const uint8_t* iv, size_t byte_offset, - const std::vector& cipher_data, + const uint8_t* cipher_data, + size_t cipher_data_length, bool is_encrypted, void* clear_data, BufferType buffer_type); diff --git a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp index bebeb44a..e882ce2a 100644 --- a/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp +++ b/libwvdrmengine/oemcrypto/mock/src/oemcrypto_mock.cpp @@ -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 key_id; std::vector enc_key_data; std::vector 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 key_id; std::vector key_control; std::vector 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 iv_v(iv, iv + 16); - std::vector 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; diff --git a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp index edad844d..27a46244 100644 --- a/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp +++ b/libwvdrmengine/oemcrypto/test/oemcrypto_test.cpp @@ -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 unencryptedData = wvcdm::a2b_hex( + "1558497b6d994be343ed1c6d6313e0537b843e9a9c0836d1e83fe33154191ce9" + "a14d8d95bebaddc03bd471827170f527c0a166b9068b273d1bc57fbb13975ee4" + "f6b9a31743da6c447acbb712e81b13eddfd4e96c76010ac9b8aa1b6b3152b0fc" + "39ad33e5719656069f9ede9ebba7a77dd2e2074eec5c1b7ffc427a6f1be168f0" + "b5857713a44623862c903284bc53417e23c65602b52c1cb699e8352453eb9698" + "0b31459b90c26c907b549c1ab293725e414d4e45f5b30af7a55f95499a7dc89f" + "7d13ba90b34aef6b49484b0701bf96ea8b660c24bb4e92a2d1c43beb434fa386" + "1071380388799ac31d79285f5817687ed3e2eeb73a30744e77b757686c9ba5ad"); + vector 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(); diff --git a/libwvdrmengine/src/WVDrmFactory.cpp b/libwvdrmengine/src/WVDrmFactory.cpp index 6454fe7c..2751866f 100644 --- a/libwvdrmengine/src/WVDrmFactory.cpp +++ b/libwvdrmengine/src/WVDrmFactory.cpp @@ -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; } diff --git a/libwvdrmengine/test/unit/Android.mk b/libwvdrmengine/test/unit/Android.mk index 82b2d3a9..1c6ad957 100644 --- a/libwvdrmengine/test/unit/Android.mk +++ b/libwvdrmengine/test/unit/Android.mk @@ -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 \ diff --git a/libwvdrmengine/test/unit/WVCreateDrmPluginFactory_test.cpp b/libwvdrmengine/test/unit/WVCreateDrmPluginFactory_test.cpp deleted file mode 100644 index 66a3085f..00000000 --- a/libwvdrmengine/test/unit/WVCreateDrmPluginFactory_test.cpp +++ /dev/null @@ -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 factory(createDrmPluginFactory()); - - EXPECT_NE((DrmPluginFactory*)NULL, factory.get()) << "createDrmPluginFactory() returned null"; -} diff --git a/libwvdrmengine/test/unit/WVDrmPluginFactory_test.cpp b/libwvdrmengine/test/unit/WVDrmPluginFactory_test.cpp deleted file mode 100644 index a443fa2a..00000000 --- a/libwvdrmengine/test/unit/WVDrmPluginFactory_test.cpp +++ /dev/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 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 factory(new WVDrmPluginFactory()); - - EXPECT_FALSE(factory->isCryptoSchemeSupported(kUnknownUUID)) << - "WVPluginFactory incorrectly claims to support an unknown UUID"; -}