am 1a8aa0dd: Initial import of Widevine Common Encryption DRM engine
* commit '1a8aa0dd05ee6edcdb0694602c64c59d77291a25': Initial import of Widevine Common Encryption DRM engine
This commit is contained in:
@@ -1,9 +1,63 @@
|
||||
# widevine prebuilts only available for ARM
|
||||
# To build this dir you must define BOARD_WIDEVINE_OEMCRYPTO_LEVEL in the board config.
|
||||
ifdef BOARD_WIDEVINE_OEMCRYPTO_LEVEL
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# TODO: add back in when we have make files that compile.
|
||||
# include $(call all-subdir-makefiles)
|
||||
LOCAL_SRC_FILES := \
|
||||
src/WVCDMSingleton.cpp \
|
||||
src/WVCreatePluginFactories.cpp \
|
||||
src/WVCryptoFactory.cpp \
|
||||
src/WVDrmFactory.cpp \
|
||||
src/WVUUID.cpp \
|
||||
|
||||
endif # BOARD_WIDEVINE_OEMCRYPTO_LEVEL
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/stlport/stlport \
|
||||
frameworks/av/include \
|
||||
frameworks/native/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/core/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/include \
|
||||
vendor/widevine/libwvdrmengine/include \
|
||||
vendor/widevine/libwvdrmengine/mediacrypto/include \
|
||||
vendor/widevine/libwvdrmengine/mediadrm/include \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libcdm \
|
||||
libprotobuf-cpp-2.3.0-lite \
|
||||
libwvdrmcryptoplugin \
|
||||
libwvdrmdrmplugin \
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libdl \
|
||||
liblog \
|
||||
liboemcrypto \
|
||||
libstlport \
|
||||
libutils \
|
||||
|
||||
# CDM's protobuffers are not part of the library
|
||||
PROTO_SRC_DIR := $(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src
|
||||
|
||||
LOCAL_SRC_FILES += \
|
||||
$(PROTO_SRC_DIR)/license_protocol.pb.cc \
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src \
|
||||
external/protobuf/src \
|
||||
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES += $(proto_generated_headers)
|
||||
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES := \
|
||||
license_protocol_protos \
|
||||
|
||||
# End protobuf section
|
||||
|
||||
LOCAL_MODULE := libwvdrmengine
|
||||
|
||||
LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_SHARED_LIBRARIES)/mediadrm
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
include vendor/widevine/libwvdrmengine/oemcrypto/mock/Android.mk
|
||||
include vendor/widevine/libwvdrmengine/cdm/Android.mk
|
||||
include vendor/widevine/libwvdrmengine/mediacrypto/Android.mk
|
||||
include vendor/widevine/libwvdrmengine/mediadrm/Android.mk
|
||||
|
||||
63
libwvdrmengine/cdm/Android.mk
Normal file
63
libwvdrmengine/cdm/Android.mk
Normal file
@@ -0,0 +1,63 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Builds the protobuf static library and generate .pb.cc and .pb.h
|
||||
# license_protocol.pb.cc
|
||||
# license_protocol.pb.h
|
||||
# license_protocol.a
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_MODULE := license_protocol_protos
|
||||
LOCAL_MODULE_CLASS := STATIC_LIBRARIES
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/stlport/stlport
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(call all-proto-files-under, core/src)
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
# ----------------------------------------------------------------
|
||||
# Builds libcdm.a
|
||||
#
|
||||
include $(CLEAR_VARS)
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/stlport/stlport \
|
||||
vendor/widevine/libwvdrmengine/cdm/core/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/include \
|
||||
vendor/widevine/libwvdrmengine/oemcrypto/include
|
||||
|
||||
# Add protocol buffer generated headers
|
||||
#
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src \
|
||||
external/protobuf/src \
|
||||
../oemcrypto/include
|
||||
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES += $(proto_generated_headers)
|
||||
|
||||
SRC_DIR := src
|
||||
CORE_SRC_DIR := core/src
|
||||
LOCAL_SRC_FILES := \
|
||||
$(CORE_SRC_DIR)/buffer_reader.cpp \
|
||||
$(CORE_SRC_DIR)/cdm_engine.cpp \
|
||||
$(CORE_SRC_DIR)/cdm_session.cpp \
|
||||
$(CORE_SRC_DIR)/crypto_engine.cpp \
|
||||
$(CORE_SRC_DIR)/crypto_session.cpp \
|
||||
$(CORE_SRC_DIR)/license.cpp \
|
||||
$(CORE_SRC_DIR)/policy_engine.cpp \
|
||||
$(CORE_SRC_DIR)/string_conversions.cpp \
|
||||
$(SRC_DIR)/clock.cpp \
|
||||
$(SRC_DIR)/lock.cpp \
|
||||
$(SRC_DIR)/log.cpp \
|
||||
$(SRC_DIR)/timer.cpp \
|
||||
$(SRC_DIR)/wv_content_decryption_module.cpp \
|
||||
|
||||
LOCAL_MODULE := libcdm
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
Rahul and Edwin fill this out
|
||||
Rahul and Edwin write tests
|
||||
68
libwvdrmengine/cdm/core/include/buffer_reader.h
Normal file
68
libwvdrmengine/cdm/core/include/buffer_reader.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_BUFFER_READER_H_
|
||||
#define CDM_BASE_BUFFER_READER_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Annotate a function indicating the caller must examine the return value.
|
||||
// Use like:
|
||||
// int foo() WARN_UNUSED_RESULT;
|
||||
// To explicitly ignore a result, see |ignore_result()| in <base/basictypes.h>.
|
||||
#if defined(COMPILER_GCC)
|
||||
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#else
|
||||
#define WARN_UNUSED_RESULT
|
||||
#endif
|
||||
|
||||
class BufferReader {
|
||||
public:
|
||||
BufferReader(const uint8_t* buf, size_t size)
|
||||
: buf_(buf), size_(size), pos_(0) {}
|
||||
|
||||
bool HasBytes(int count) { return (pos() + count <= size()); }
|
||||
|
||||
// Read a value from the stream, performing endian correction,
|
||||
// and advance the stream pointer.
|
||||
bool Read1(uint8_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read2(uint16_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read2s(int16_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4(uint32_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4s(int32_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read8(uint64_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read8s(int64_t* v) WARN_UNUSED_RESULT;
|
||||
|
||||
bool ReadString(std::string* str, int count) WARN_UNUSED_RESULT;
|
||||
bool ReadVec(std::vector<uint8_t>* t, int count) WARN_UNUSED_RESULT;
|
||||
|
||||
// These variants read a 4-byte integer of the corresponding signedness and
|
||||
// store it in the 8-byte return type.
|
||||
bool Read4Into8(uint64_t* v) WARN_UNUSED_RESULT;
|
||||
bool Read4sInto8s(int64_t* v) WARN_UNUSED_RESULT;
|
||||
|
||||
// Advance the stream by this many bytes.
|
||||
bool SkipBytes(int nbytes) WARN_UNUSED_RESULT;
|
||||
|
||||
const uint8_t* data() const { return buf_; }
|
||||
size_t size() const { return size_; }
|
||||
size_t pos() const { return pos_; }
|
||||
|
||||
protected:
|
||||
const uint8_t* buf_;
|
||||
size_t size_;
|
||||
size_t pos_;
|
||||
|
||||
template<typename T> bool Read(T* t) WARN_UNUSED_RESULT;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(BufferReader);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_BUFFER_READER_H_
|
||||
123
libwvdrmengine/cdm/core/include/cdm_engine.h
Normal file
123
libwvdrmengine/cdm/core/include/cdm_engine.h
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_CDM_ENGINE_H_
|
||||
#define CDM_BASE_CDM_ENGINE_H_
|
||||
|
||||
#include "timer.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmSession;
|
||||
class WvCdmEventListener;
|
||||
|
||||
typedef std::map<CdmSessionId, CdmSession*> CdmSessionMap;
|
||||
|
||||
class CdmEngine : public TimerHandler {
|
||||
public:
|
||||
CdmEngine() {}
|
||||
~CdmEngine();
|
||||
|
||||
// Session related methods
|
||||
CdmResponseType OpenSession(const CdmKeySystem& key_system,
|
||||
CdmSessionId* session_id);
|
||||
CdmResponseType CloseSession(CdmSessionId& session_id);
|
||||
|
||||
// License related methods
|
||||
// Construct a valid license request
|
||||
CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id,
|
||||
bool is_key_system_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmNameValueMap& app_parameters,
|
||||
CdmKeyMessage* key_request);
|
||||
|
||||
// Accept license response and extract key info.
|
||||
CdmResponseType AddKey(const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmKeyResponse& key_data);
|
||||
|
||||
// Cancel session and unload keys.
|
||||
CdmResponseType CancelKeyRequest(const CdmSessionId& session_id,
|
||||
bool is_key_system_present,
|
||||
const CdmKeySystem& key_system);
|
||||
|
||||
// Construct valid renewal request for the current session keys.
|
||||
CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
CdmKeyMessage* key_request);
|
||||
|
||||
// Accept renewal response and update key info.
|
||||
CdmResponseType RenewKey(const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmKeyResponse& key_data);
|
||||
|
||||
// Query license information
|
||||
CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
|
||||
CdmNameValueMap* key_info);
|
||||
|
||||
|
||||
// Provisioning related methods
|
||||
CdmResponseType GetProvisioningRequest(CdmProvisioningRequest* request,
|
||||
std::string* default_url);
|
||||
|
||||
CdmResponseType HandleProvisioningResponse(CdmProvisioningResponse& response);
|
||||
|
||||
// Secure stop related methods
|
||||
CdmResponseType GetSecureStops(CdmSecureStops* secure_stops);
|
||||
CdmResponseType ReleaseSecureStops(const CdmSecureStopReleaseMessage& message);
|
||||
|
||||
// Decryption and key related methods
|
||||
// Accept encrypted buffer and return decrypted data.
|
||||
CdmResponseType Decrypt(const CdmSessionId& session_id,
|
||||
bool is_encrypted,
|
||||
const KeyId& key_id,
|
||||
const uint8_t* encrypted_buffer,
|
||||
size_t encrypted_size,
|
||||
const std::vector<uint8_t>& iv,
|
||||
size_t block_offset,
|
||||
void* decrypted_buffer);
|
||||
|
||||
// Is the key known to any session?
|
||||
bool IsKeyValid(const KeyId& key_id);
|
||||
|
||||
// Event listener related methods
|
||||
bool AttachEventListener(CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
bool DetachEventListener(CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
private:
|
||||
// private methods
|
||||
bool ValidateKeySystem(const CdmKeySystem& key_system);
|
||||
// Cancel all sessions
|
||||
bool CancelSessions();
|
||||
// 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);
|
||||
|
||||
// timer related methods to drive policy decisions
|
||||
void EnablePolicyTimer();
|
||||
void DisablePolicyTimer();
|
||||
virtual void OnTimerEvent();
|
||||
|
||||
// instance variables
|
||||
CdmSessionMap sessions_;
|
||||
|
||||
// policy timer
|
||||
Timer policy_timer_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_CDM_ENGINE_H_
|
||||
100
libwvdrmengine/cdm/core/include/cdm_session.h
Normal file
100
libwvdrmengine/cdm/core/include/cdm_session.h
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_CDM_SESSION_H_
|
||||
#define CDM_BASE_CDM_SESSION_H_
|
||||
|
||||
#include <set>
|
||||
|
||||
#include "crypto_session.h"
|
||||
#include "license.h"
|
||||
#include "policy_engine.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// TODO(kqyang): Do we need it? CdmKey not defined yet
|
||||
// typedef std::map<KeyId, CdmKey*> CdmSessionKeys;
|
||||
|
||||
class CdmSession {
|
||||
public:
|
||||
CdmSession() : state_(INITIAL), session_id_(GenerateSessionId()) {}
|
||||
~CdmSession() {}
|
||||
|
||||
bool Init();
|
||||
|
||||
bool DestroySession();
|
||||
|
||||
void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; }
|
||||
const CdmKeySystem& key_system() { return key_system_; }
|
||||
|
||||
const CdmSessionId& session_id() { return session_id_; }
|
||||
|
||||
bool VerifySession(const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data);
|
||||
|
||||
CdmResponseType GenerateKeyRequest(const CdmInitData& init_data,
|
||||
CdmKeyMessage* key_request);
|
||||
|
||||
// AddKey() - Accept license response and extract key info.
|
||||
CdmResponseType AddKey(const CdmKeyResponse& key_response);
|
||||
|
||||
// CancelKeyRequest() - Cancel session.
|
||||
CdmResponseType CancelKeyRequest();
|
||||
|
||||
// Decrypt() - Accept encrypted buffer and return decrypted data.
|
||||
CdmResponseType Decrypt(const uint8_t* encrypted_buffer,
|
||||
size_t encrypted_size,
|
||||
size_t block_offset,
|
||||
const std::string& iv,
|
||||
const KeyId& key_id,
|
||||
uint8_t* decrypted_buffer);
|
||||
|
||||
// License renewal
|
||||
// GenerateRenewalRequest() - Construct valid renewal request for the current
|
||||
// session keys.
|
||||
CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request);
|
||||
|
||||
// RenewKey() - Accept renewal response and update key info.
|
||||
CdmResponseType RenewKey(const CdmKeyResponse& key_response);
|
||||
|
||||
bool IsKeyValid(const KeyId& key_id);
|
||||
|
||||
bool AttachEventListener(WvCdmEventListener* listener);
|
||||
bool DetachEventListener(WvCdmEventListener* listener);
|
||||
|
||||
void OnTimerEvent();
|
||||
|
||||
private:
|
||||
|
||||
// Generate unique ID for each new session.
|
||||
CdmSessionId GenerateSessionId();
|
||||
|
||||
typedef enum {
|
||||
INITIAL,
|
||||
LICENSE_REQUESTED,
|
||||
LICENSE_RESPONSE_DONE,
|
||||
RENEWAL_ENABLED,
|
||||
RENEWAL_REQUESTED,
|
||||
LICENSE_EXPIRED
|
||||
} CdmSessionState;
|
||||
|
||||
// instance variables
|
||||
CdmSessionState state_;
|
||||
const CdmSessionId session_id_;
|
||||
CdmKeySystem key_system_;
|
||||
CdmLicense license_parser_;
|
||||
CryptoSession* crypto_session_;
|
||||
PolicyEngine policy_engine_;
|
||||
|
||||
std::set<WvCdmEventListener*> listeners_;
|
||||
|
||||
// TODO(kqyang): CdmKey not defined yet
|
||||
// CdmSessionKeys session_keys_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_CDM_SESSION_H_
|
||||
19
libwvdrmengine/cdm/core/include/clock.h
Normal file
19
libwvdrmengine/cdm/core/include/clock.h
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Clock - Platform independent interface for a time library
|
||||
//
|
||||
#ifndef CDM_BASE_CLOCK_H_
|
||||
#define CDM_BASE_CLOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides time related information. The implementation is platform dependent.
|
||||
|
||||
// Provides the number of seconds since an epoch (00:00 hours, Jan 1, 1970 UTC)
|
||||
int64_t GetCurrentTime();
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_CLOCK_H_
|
||||
61
libwvdrmengine/cdm/core/include/crypto_engine.h
Normal file
61
libwvdrmengine/cdm/core/include/crypto_engine.h
Normal file
@@ -0,0 +1,61 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// OEMCrypto Client - wrapper class for C-style OEMCrypto interface
|
||||
//
|
||||
#ifndef CDM_BASE_CRYPTO_ENGINE_H_
|
||||
#define CDM_BASE_CRYPTO_ENGINE_H_
|
||||
|
||||
#include "crypto_session.h"
|
||||
#include "lock.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::map<CdmSessionId,CryptoSession*> CryptoSessionMap;
|
||||
|
||||
class CryptoEngine {
|
||||
|
||||
friend class CryptoSession;
|
||||
|
||||
private:
|
||||
|
||||
CryptoEngine();
|
||||
~CryptoEngine();
|
||||
|
||||
public:
|
||||
|
||||
// get an instance of Crypto engine
|
||||
static CryptoEngine* GetInstance();
|
||||
|
||||
bool Init();
|
||||
bool Terminate();
|
||||
bool ValidateKeybox();
|
||||
|
||||
CryptoSession* CreateSession(const CdmSessionId& session_id);
|
||||
CryptoSession* FindSession(const CdmSessionId& session_id);
|
||||
bool DestroySession(const CdmSessionId& session_id);
|
||||
bool DestroySessions();
|
||||
|
||||
bool GetToken(std::string* token);
|
||||
|
||||
private:
|
||||
|
||||
void DeleteInstance();
|
||||
static CryptoEngine* CreateSingleton();
|
||||
|
||||
CryptoSession* FindSessionInternal(const CdmSessionId& session_id);
|
||||
|
||||
static CryptoEngine* crypto_engine_;
|
||||
static Lock crypto_engine_lock_;
|
||||
|
||||
bool initialized_;
|
||||
mutable Lock crypto_lock_;
|
||||
mutable Lock sessions_lock_;
|
||||
CryptoSessionMap sessions_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_CRYPTO_ENGINE_H_
|
||||
44
libwvdrmengine/cdm/core/include/crypto_key.h
Normal file
44
libwvdrmengine/cdm/core/include/crypto_key.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// OEMCrypto Client - wrapper class for C-style OEMCrypto interface
|
||||
//
|
||||
#ifndef CDM_BASE_CRYPTO_KEY_H_
|
||||
#define CDM_BASE_CRYPTO_KEY_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CryptoKey {
|
||||
public:
|
||||
CryptoKey() {};
|
||||
~CryptoKey() {};
|
||||
|
||||
const std::string& key_id() const { return key_id_; }
|
||||
const std::string& key_data() const { return key_data_; }
|
||||
const std::string& key_data_iv() const { return key_data_iv_; }
|
||||
const std::string& key_control() const { return key_control_; }
|
||||
const std::string& key_control_iv() const { return key_control_iv_; }
|
||||
void set_key_id(const std::string& key_id) { key_id_ = key_id; }
|
||||
void set_key_data(const std::string& key_data) { key_data_ = key_data; }
|
||||
void set_key_data_iv(const std::string& iv) { key_data_iv_ = iv; }
|
||||
void set_key_control(const std::string& ctl) { key_control_ = ctl; }
|
||||
void set_key_control_iv(const std::string& ctl_iv) {
|
||||
key_control_iv_ = ctl_iv;
|
||||
}
|
||||
|
||||
bool HasKeyControl() const { return key_control_.size() >= 16; }
|
||||
|
||||
private:
|
||||
std::string key_id_;
|
||||
std::string key_data_iv_;
|
||||
std::string key_data_;
|
||||
std::string key_control_;
|
||||
std::string key_control_iv_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoKey);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_CRYPTO_KEY_H_
|
||||
81
libwvdrmengine/cdm/core/include/crypto_session.h
Normal file
81
libwvdrmengine/cdm/core/include/crypto_session.h
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// OEMCrypto Client - wrapper class for C-style OEMCrypto interface
|
||||
//
|
||||
#ifndef CDM_BASE_CRYPTO_SESSSION_H_
|
||||
#define CDM_BASE_CRYPTO_SESSSION_H_
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "crypto_key.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::map<CryptoKeyId,CryptoKey*> CryptoKeyMap;
|
||||
|
||||
// TODO(gmorgan): fill out input and output descriptors
|
||||
typedef void* InputDescriptor;
|
||||
typedef void* OutputDescriptor;
|
||||
|
||||
class CryptoSession {
|
||||
public:
|
||||
CryptoSession();
|
||||
explicit CryptoSession(const std::string& sname);
|
||||
~CryptoSession();
|
||||
|
||||
bool Open();
|
||||
void Close();
|
||||
|
||||
bool IsValid() { return valid_; }
|
||||
bool IsOpen() { return open_; }
|
||||
bool SuccessStatus();
|
||||
CryptoResult session_status() { return session_status_; }
|
||||
CryptoSessionId oec_session_id() { return oec_session_id_; }
|
||||
CdmSessionId cdm_session_id() { return cdm_session_id_; }
|
||||
|
||||
// Key request/response
|
||||
void GenerateRequestId(std::string& req_id_str);
|
||||
bool PrepareRequest(const std::string& key_deriv_message,
|
||||
std::string* signature);
|
||||
bool PrepareRenewalRequest(const std::string& message,
|
||||
std::string* signature);
|
||||
bool LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
int num_keys,
|
||||
const CryptoKey* key_array);
|
||||
bool RefreshKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
int num_keys,
|
||||
const CryptoKey* key_array);
|
||||
bool GenerateNonce(uint32_t* nonce);
|
||||
|
||||
// Media data path
|
||||
bool SelectKey(const std::string& key_id);
|
||||
bool Decrypt(const InputDescriptor input, OutputDescriptor output);
|
||||
|
||||
private:
|
||||
|
||||
void GenerateMacContext(const std::string& input_context,
|
||||
std::string* deriv_context);
|
||||
void GenerateEncryptContext(const std::string& input_context,
|
||||
std::string* deriv_context);
|
||||
size_t GetOffset(std::string message, std::string field);
|
||||
|
||||
bool valid_;
|
||||
bool open_;
|
||||
CdmSessionId cdm_session_id_;
|
||||
CryptoSessionId oec_session_id_;
|
||||
CryptoResult session_status_;
|
||||
|
||||
CryptoKeyMap keys_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoSession);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_CRYPTO_SESSSION_H_
|
||||
41
libwvdrmengine/cdm/core/include/license.h
Normal file
41
libwvdrmengine/cdm/core/include/license.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_LICENSE_H_
|
||||
#define CDM_BASE_LICENSE_H_
|
||||
|
||||
#include "license_protocol.pb.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
using video_widevine_server::sdk::LicenseIdentification;
|
||||
|
||||
class CryptoSession;
|
||||
|
||||
class CdmLicense {
|
||||
|
||||
public:
|
||||
|
||||
CdmLicense();
|
||||
~CdmLicense();
|
||||
|
||||
bool Init(const std::string& token, CryptoSession* session);
|
||||
|
||||
bool PrepareKeyRequest(const CdmInitData& init_data,
|
||||
CdmKeyMessage* signed_request);
|
||||
bool PrepareKeyRenewalRequest(CdmKeyMessage* signed_request);
|
||||
bool HandleKeyResponse(const CdmKeyResponse& license_response);
|
||||
bool HandleKeyRenewalResponse(const CdmKeyResponse& license_response);
|
||||
|
||||
private:
|
||||
|
||||
LicenseIdentification license_id_;
|
||||
CryptoSession* session_;
|
||||
std::string token_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmLicense);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_LICENSE_H_
|
||||
54
libwvdrmengine/cdm/core/include/lock.h
Normal file
54
libwvdrmengine/cdm/core/include/lock.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Lock - Platform independent interface for a Mutex class
|
||||
//
|
||||
#ifndef CDM_BASE_LOCK_H_
|
||||
#define CDM_BASE_LOCK_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Simple lock class. The implementation is platform dependent.
|
||||
//
|
||||
// The lock must be unlocked by the thread that locked it.
|
||||
// The lock is also not recursive (ie. cannot be taken multiple times).
|
||||
class Lock {
|
||||
public:
|
||||
Lock();
|
||||
~Lock();
|
||||
|
||||
void Acquire();
|
||||
void Release();
|
||||
|
||||
// Acquires a lock if not held and returns true.
|
||||
// Returns false if the lock is held by another thread.
|
||||
bool Try();
|
||||
|
||||
friend class AutoLock;
|
||||
|
||||
private:
|
||||
class 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
|
||||
class AutoLock {
|
||||
public:
|
||||
explicit AutoLock(Lock& lock);
|
||||
explicit AutoLock(Lock* lock);
|
||||
~AutoLock();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl *impl_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(AutoLock);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_LOCK_H_
|
||||
31
libwvdrmengine/cdm/core/include/log.h
Normal file
31
libwvdrmengine/cdm/core/include/log.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Log - Platform independent interface for a Logging class
|
||||
//
|
||||
#ifndef CDM_BASE_LOG_H_
|
||||
#define CDM_BASE_LOG_H_
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Simple logging class. The implementation is platform dependent.
|
||||
|
||||
typedef enum {
|
||||
LOG_ERROR,
|
||||
LOG_WARN,
|
||||
LOG_INFO,
|
||||
LOG_DEBUG,
|
||||
LOG_VERBOSE
|
||||
} LogPriority;
|
||||
|
||||
void log_write(LogPriority priority, const char *fmt, ...);
|
||||
|
||||
// Log APIs
|
||||
#define LOGE(...) ((void)log_write(wvcdm::LOG_ERROR, __VA_ARGS__))
|
||||
#define LOGW(...) ((void)log_write(wvcdm::LOG_WARN, __VA_ARGS__))
|
||||
#define LOGI(...) ((void)log_write(wvcdm::LOG_INFO, __VA_ARGS__))
|
||||
#define LOGD(...) ((void)log_write(wvcdm::LOG_DEBUG, __VA_ARGS__))
|
||||
#define LOGV(...) ((void)log_write(wvcdm::LOG_VERBOSE, __VA_ARGS__))
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_LOG_H_
|
||||
83
libwvdrmengine/cdm/core/include/policy_engine.h
Normal file
83
libwvdrmengine/cdm/core/include/policy_engine.h
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_POLICY_ENGINE_H_
|
||||
#define CDM_BASE_POLICY_ENGINE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "license_protocol.pb.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// This acts as an oracle that basically says "Yes(true) you may still decrypt
|
||||
// or no(false) you may not decrypt this data anymore."
|
||||
class PolicyEngine {
|
||||
public:
|
||||
PolicyEngine();
|
||||
~PolicyEngine();
|
||||
|
||||
// |current_time| is used to check if license has to be renewed or expired.
|
||||
void OnTimerEvent(int64_t current_time, bool event_occurred, CdmEventType& event);
|
||||
|
||||
// SetLicense is used in handling the initial license response. It stores
|
||||
// an exact copy of the policy information stored in the license.
|
||||
// The license state transitions to kLicenseStateCanPlay if the license
|
||||
// permits playback.
|
||||
void SetLicense(const video_widevine_server::sdk::License& license);
|
||||
|
||||
// UpdateLicense is used in handling a license response for a renewal request.
|
||||
// The response may only contain any policy fields that have changed. In this
|
||||
// case an exact copy is not what we want to happen. We also will receive an
|
||||
// updated license_start_time from the server. The license will transition to
|
||||
// kLicenseStateCanPlay if the license permits playback.
|
||||
void UpdateLicense(const video_widevine_server::sdk::License& license);
|
||||
|
||||
const video_widevine_server::sdk::LicenseIdentification& license_id() {
|
||||
return license_id_;
|
||||
}
|
||||
|
||||
private:
|
||||
typedef enum {
|
||||
kLicenseStateInitial,
|
||||
kLicenseStateCanPlay,
|
||||
kLicenseStateCannotPlay,
|
||||
kLicenseStateNeedRenewal,
|
||||
kLicenseStateWaitingLicenseUpdate,
|
||||
kLicenseStateExpired
|
||||
} LicenseState;
|
||||
|
||||
bool IsLicenseDurationExpired(int64_t current_time);
|
||||
bool IsRenewalDelayExpired(int64_t current_time);
|
||||
bool IsRenewalRecoveryDurationExpired(int64_t current_time);
|
||||
bool IsRenewalRetryIntervalExpired(int64_t current_time);
|
||||
|
||||
void UpdateRenewalRequest(int64_t current_time);
|
||||
|
||||
LicenseState license_state_;
|
||||
|
||||
// This is the current policy information for this license. This gets updated
|
||||
// as license renewals occur.
|
||||
video_widevine_server::sdk::License::Policy policy_;
|
||||
|
||||
// This is the license id field from server response. This data gets passed
|
||||
// back to the server in each renewal request. When we get a renewal response
|
||||
// from the license server we will get an updated id field.
|
||||
video_widevine_server::sdk::LicenseIdentification license_id_;
|
||||
|
||||
// This is the license start time that gets sent from the server in each
|
||||
// license request or renewal.
|
||||
int64_t license_start_time_;
|
||||
|
||||
// This is used as a reference point for policy management. This value
|
||||
// represents an offset from license_start_time_. This is used to calculate
|
||||
// the time where renewal retries should occur.
|
||||
int64_t next_renewal_time_;
|
||||
int64_t policy_max_duration_seconds_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(PolicyEngine);
|
||||
};
|
||||
} // wvcdm
|
||||
|
||||
#endif // CDM_BASE_POLICY_ENGINE_H_
|
||||
|
||||
25
libwvdrmengine/cdm/core/include/string_conversions.h
Normal file
25
libwvdrmengine/cdm/core/include/string_conversions.h
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_STRING_CONVERSIONS_H_
|
||||
#define CDM_BASE_STRING_CONVERSIONS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
std::vector<uint8_t> a2b_hex(const std::string& b);
|
||||
std::string a2bs_hex(const std::string& b);
|
||||
std::string b2a_hex(const std::vector<uint8_t>& b);
|
||||
std::string b2a_hex(const std::string& b);
|
||||
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input);
|
||||
std::vector<uint8_t> Base64SafeDecode(const std::string& bin_input);
|
||||
std::string HexEncode(const uint8_t* bytes, unsigned size);
|
||||
std::string IntToString(int value);
|
||||
std::string UintToString(unsigned int value);
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_STRING_CONVERSIONS_H_
|
||||
50
libwvdrmengine/cdm/core/include/timer.h
Normal file
50
libwvdrmengine/cdm/core/include/timer.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Timer - Platform independent interface for a Timer class
|
||||
//
|
||||
#ifndef CDM_BASE_TIMER_H_
|
||||
#define CDM_BASE_TIMER_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Timer Handler class.
|
||||
//
|
||||
// Derive from this class if you wish to receive events when the timer
|
||||
// expires. Provide the handler when setting up a new Timer.
|
||||
|
||||
class TimerHandler {
|
||||
public:
|
||||
TimerHandler() {};
|
||||
virtual ~TimerHandler() {};
|
||||
|
||||
virtual void OnTimerEvent() = 0;
|
||||
};
|
||||
|
||||
// Timer class. The implementation is platform dependent.
|
||||
//
|
||||
// This class provides a simple recurring timer API. The class receiving
|
||||
// timer expiry events should derive from TimerHandler.
|
||||
// Specify the receiver class and the periodicty of timer events when
|
||||
// the timer is initiated by calling Start.
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
Timer();
|
||||
~Timer();
|
||||
|
||||
void Start(TimerHandler *handler, uint32_t time_in_secs);
|
||||
void Stop();
|
||||
bool IsRunning();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl *impl_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(Timer);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_TIMER_H_
|
||||
18
libwvdrmengine/cdm/core/include/wv_cdm_constants.h
Normal file
18
libwvdrmengine/cdm/core/include/wv_cdm_constants.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_WV_CDM_CONSTANTS_H_
|
||||
#define CDM_BASE_WV_CDM_CONSTANTS_H_
|
||||
|
||||
namespace wvcdm {
|
||||
static const size_t KEY_CONTROL_SIZE = 16;
|
||||
// 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.
|
||||
static const size_t KEY_ID_SIZE = 16;
|
||||
static const size_t KEY_IV_SIZE = 16;
|
||||
static const size_t KEY_PAD_SIZE = 16;
|
||||
static const size_t KEY_SIZE = 16;
|
||||
static const size_t MAC_KEY_SIZE = 32;
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_WV_CDM_CONSTANTS_H_
|
||||
28
libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h
Normal file
28
libwvdrmengine/cdm/core/include/wv_cdm_event_listener.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_WV_CDM_EVENT_LISTENER_H_
|
||||
#define CDM_BASE_WV_CDM_EVENT_LISTENER_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Listener for events from the Content Decryption Module.
|
||||
// The caller of the CDM API must provide an implementation for onEvent
|
||||
// and signal its intent by using the Attach/DetachEventListener methods
|
||||
// in the WvContentDecryptionModule class.
|
||||
class WvCdmEventListener {
|
||||
public:
|
||||
WvCdmEventListener() {}
|
||||
virtual ~WvCdmEventListener() {}
|
||||
|
||||
virtual void onEvent(const CdmSessionId& session_id,
|
||||
CdmEventType cdm_event) = 0;
|
||||
|
||||
private:
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(WvCdmEventListener);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_WV_CDM_EVENT_LISTENER_H_
|
||||
60
libwvdrmengine/cdm/core/include/wv_cdm_types.h
Normal file
60
libwvdrmengine/cdm/core/include/wv_cdm_types.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_WV_CDM_TYPES_H_
|
||||
#define CDM_BASE_WV_CDM_TYPES_H_
|
||||
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::string CdmKeySystem;
|
||||
typedef std::string CdmInitData;
|
||||
typedef std::string CdmKeyMessage;
|
||||
typedef std::string CdmKeyResponse;
|
||||
typedef std::string KeyId;
|
||||
typedef std::string CdmSessionId;
|
||||
typedef std::string RequestId;
|
||||
typedef uint32_t CryptoResult;
|
||||
typedef uint32_t CryptoSessionId;
|
||||
typedef std::string CryptoKeyId;
|
||||
typedef std::map<std::string, std::string> CdmNameValueMap;
|
||||
typedef std::vector<std::string> CdmSecureStops;
|
||||
typedef std::vector<uint8_t> CdmSecureStopReleaseMessage;
|
||||
typedef std::string CdmProvisioningRequest;
|
||||
typedef std::string CdmProvisioningResponse;
|
||||
|
||||
enum CdmResponseType {
|
||||
NO_ERROR,
|
||||
UNKNOWN_ERROR,
|
||||
KEY_ADDED,
|
||||
KEY_ERROR,
|
||||
KEY_MESSAGE,
|
||||
NEED_KEY,
|
||||
KEY_CANCELED,
|
||||
};
|
||||
|
||||
#define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
enum CdmEventType {
|
||||
LICENSE_EXPIRED_EVENT,
|
||||
LICENSE_RENEWAL_NEEDED_EVENT
|
||||
};
|
||||
|
||||
enum CdmLicenseType {
|
||||
kLicenseTypeOffline,
|
||||
kLicenseTypeStreaming
|
||||
};
|
||||
|
||||
// forward class references
|
||||
class KeyMessage;
|
||||
class Request;
|
||||
class Key;
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_WV_CDM_TYPES_H_
|
||||
94
libwvdrmengine/cdm/core/src/buffer_reader.cpp
Normal file
94
libwvdrmengine/cdm/core/src/buffer_reader.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "buffer_reader.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
bool BufferReader::Read1(uint8_t* v) {
|
||||
if (!HasBytes(1)) {
|
||||
LOGE("BufferReader::Read1 : Failure while parsing: Not enough bytes (1)");
|
||||
return false;
|
||||
}
|
||||
|
||||
*v = buf_[pos_++];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Internal implementation of multi-byte reads
|
||||
template<typename T> bool BufferReader::Read(T* v) {
|
||||
if (!HasBytes(sizeof(T))) {
|
||||
LOGE("BufferReader::Read<T> : Failure while parsing: Not enough bytes (%u)", sizeof(T));
|
||||
return false;
|
||||
}
|
||||
|
||||
T tmp = 0;
|
||||
for (size_t i = 0; i < sizeof(T); i++) {
|
||||
tmp <<= 8;
|
||||
tmp += buf_[pos_++];
|
||||
}
|
||||
*v = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::Read2(uint16_t* v) { return Read(v); }
|
||||
bool BufferReader::Read2s(int16_t* v) { return Read(v); }
|
||||
bool BufferReader::Read4(uint32_t* v) { return Read(v); }
|
||||
bool BufferReader::Read4s(int32_t* v) { return Read(v); }
|
||||
bool BufferReader::Read8(uint64_t* v) { return Read(v); }
|
||||
bool BufferReader::Read8s(int64_t* v) { return Read(v); }
|
||||
|
||||
bool BufferReader::ReadString(std::string* str, int count) {
|
||||
if (!HasBytes(count)) {
|
||||
LOGE("BufferReader::ReadString : Failure while parsing: Not enough bytes (%d)", count);
|
||||
return false;
|
||||
}
|
||||
|
||||
str->assign(buf_ + pos_, buf_ + pos_ + count);
|
||||
pos_ += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::ReadVec(std::vector<uint8_t>* vec, int count) {
|
||||
if (!HasBytes(count)) {
|
||||
LOGE("BufferReader::ReadVec : Failure while parsing: Not enough bytes (%d)", count);
|
||||
return false;
|
||||
}
|
||||
|
||||
vec->clear();
|
||||
vec->insert(vec->end(), buf_ + pos_, buf_ + pos_ + count);
|
||||
pos_ += count;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::SkipBytes(int bytes) {
|
||||
if (!HasBytes(bytes)) {
|
||||
LOGE("BufferReader::SkipBytes : Failure while parsing: Not enough bytes (%d)", bytes);
|
||||
return false;
|
||||
}
|
||||
|
||||
pos_ += bytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::Read4Into8(uint64_t* v) {
|
||||
uint32_t tmp;
|
||||
if (!Read4(&tmp)) {
|
||||
return false;
|
||||
}
|
||||
*v = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferReader::Read4sInto8s(int64_t* v) {
|
||||
// Beware of the need for sign extension.
|
||||
int32_t tmp;
|
||||
if (!Read4s(&tmp)) {
|
||||
return false;
|
||||
}
|
||||
*v = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
463
libwvdrmengine/cdm/core/src/cdm_engine.cpp
Normal file
463
libwvdrmengine/cdm/core/src/cdm_engine.cpp
Normal file
@@ -0,0 +1,463 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "cdm_engine.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "buffer_reader.h"
|
||||
#include "cdm_session.h"
|
||||
#include "log.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
|
||||
#ifndef CDM_POLICY_TIMER_DURATION_SECONDS
|
||||
#define CDM_POLICY_TIMER_DURATION_SECONDS 1
|
||||
#endif
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::map<CdmSessionId,CdmSession*>::iterator CdmSessionIter;
|
||||
|
||||
CdmEngine::~CdmEngine() {
|
||||
CancelSessions();
|
||||
|
||||
CdmSessionMap::iterator i(sessions_.begin());
|
||||
for (; i != sessions_.end(); ++i)
|
||||
delete i->second;
|
||||
sessions_.clear();
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::OpenSession(
|
||||
const CdmKeySystem& key_system,
|
||||
CdmSessionId* session_id) {
|
||||
LOGI("CdmEngine::OpenSession");
|
||||
|
||||
if (!ValidateKeySystem(key_system)) {
|
||||
LOGI("CdmEngine::OpenSession: invalid key_system = %s", key_system.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!session_id) {
|
||||
LOGE("CdmEngine::OpenSession: no session ID destination provided");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
// TODO(edwinwong, rfrias): Save key_system in session for validation checks
|
||||
CdmSession* new_session = new CdmSession();
|
||||
if (!new_session) {
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (new_session->session_id().empty()) {
|
||||
LOGE("CdmEngine::OpenSession: failure to generate session ID");
|
||||
delete(new_session);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CdmSessionId new_session_id = new_session->session_id();
|
||||
|
||||
if (!new_session->Init()) {
|
||||
LOGE("CdmEngine::OpenSession: bad session init");
|
||||
delete(new_session);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
sessions_[new_session_id] = new_session;
|
||||
*session_id = new_session_id;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::CloseSession(CdmSessionId& session_id) {
|
||||
LOGI("CdmEngine::CloseSession");
|
||||
|
||||
CdmSession* cdm_session = sessions_[session_id];
|
||||
|
||||
if (!cdm_session) {
|
||||
LOGE("CdmEngine::CloseSession: session not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
sessions_.erase(session_id);
|
||||
cdm_session->DestroySession();
|
||||
delete cdm_session;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GenerateKeyRequest(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_key_system_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmNameValueMap& app_parameters,
|
||||
CdmKeyMessage* key_request) {
|
||||
LOGI("CdmEngine::GenerateKeyRequest");
|
||||
|
||||
CdmSession* session = sessions_[session_id];
|
||||
|
||||
if (!session) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: session_id not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_key_system_present) {
|
||||
// TODO(edwinwong, rfrias): validate key_system has not changed
|
||||
}
|
||||
|
||||
if (init_data.empty()) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: no init_data provided");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (!key_request) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: no key request destination provided");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmInitData extracted_pssh;
|
||||
if (!ExtractWidevinePssh(init_data, &extracted_pssh)) {
|
||||
key_request->clear();
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
key_request->clear();
|
||||
|
||||
// TODO(edwinwong, rfrias): need to pass in license type and app parameters
|
||||
CdmResponseType sts = session->GenerateKeyRequest(extracted_pssh,
|
||||
key_request);
|
||||
|
||||
if (KEY_MESSAGE != sts) {
|
||||
LOGE("CdmEngine::GenerateKeyRequest: key request generation failed, sts=%d",
|
||||
(int)sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
// TODO(edwinwong, rfrias): persist init_data, license_type, app_parameters
|
||||
// in session
|
||||
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::AddKey(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmKeyResponse& key_data) {
|
||||
LOGI("CdmEngine::AddKey");
|
||||
|
||||
CdmSession* session = sessions_[session_id];
|
||||
|
||||
if (!session) {
|
||||
LOGE("CdmEngine::AddKey: session_id not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate key_system has not changed
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate init_data has not changed
|
||||
}
|
||||
|
||||
if (key_data.empty()) {
|
||||
LOGE("CdmEngine::AddKey: no key_data");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType sts = session->AddKey(key_data);
|
||||
if (KEY_ADDED != sts) {
|
||||
LOGE("CdmEngine::AddKey: keys not added, result = %d", (int)sts);
|
||||
}
|
||||
|
||||
return sts;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::CancelKeyRequest(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_key_system_present,
|
||||
const CdmKeySystem& key_system) {
|
||||
LOGI("CdmEngine::CancelKeyRequest");
|
||||
|
||||
//TODO(gmorgan): Issue: what is semantics of canceling a key request. Should
|
||||
//this call cancel all keys for the session?
|
||||
// TODO(jfore): We should disable the policy timer here if there are no
|
||||
// active sessions. Sessions are currently not being destroyed here. We can
|
||||
// add this logic once the semantics of canceling the key is worked out.
|
||||
|
||||
CdmSession* session = sessions_[session_id];
|
||||
|
||||
if (!session) {
|
||||
LOGE("CdmEngine::CancelKeyRequest: session_id not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_key_system_present) {
|
||||
// TODO(edwinwong, rfrias): validate key_system has not changed
|
||||
}
|
||||
|
||||
// TODO(edwinwong, rfrias): unload keys here
|
||||
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GenerateRenewalRequest(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
CdmKeyMessage* key_request) {
|
||||
LOGI("CdmEngine::GenerateRenewalRequest");
|
||||
|
||||
CdmSession* session = sessions_[session_id];
|
||||
|
||||
if (!session) {
|
||||
LOGE("CdmEngine::GenerateRenewalRequest: session_id not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate key_system has not changed
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate init_data has not changed
|
||||
}
|
||||
|
||||
if (!key_request) {
|
||||
LOGE("CdmEngine::GenerateRenewalRequest: no key request destination provided");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
key_request->clear();
|
||||
|
||||
CdmResponseType sts = session->GenerateRenewalRequest(key_request);
|
||||
|
||||
if (KEY_MESSAGE != sts) {
|
||||
LOGE("CdmEngine::GenerateRenewalRequest: key request generation failed, sts=%d",
|
||||
(int)sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::RenewKey(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_key_system_init_data_present,
|
||||
const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data,
|
||||
const CdmKeyResponse& key_data) {
|
||||
LOGI("CdmEngine::RenewKey");
|
||||
|
||||
CdmSession* session = sessions_[session_id];
|
||||
|
||||
if (!session) {
|
||||
LOGE("CdmEngine::RenewKey: session_id not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate key_system has not changed
|
||||
}
|
||||
|
||||
if (is_key_system_init_data_present) {
|
||||
// TODO(edwinwong, rfrias): validate init_data has not changed
|
||||
}
|
||||
|
||||
if (key_data.empty()) {
|
||||
LOGE("CdmEngine::RenewKey: no key_data");
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType sts = session->RenewKey(key_data);
|
||||
if (KEY_ADDED != sts) {
|
||||
LOGE("CdmEngine::RenewKey: keys not added, sts=%d", (int)sts);
|
||||
return sts;
|
||||
}
|
||||
|
||||
return KEY_ADDED;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::QueryKeyStatus(
|
||||
const CdmSessionId& session_id,
|
||||
CdmNameValueMap* key_info) {
|
||||
// TODO(edwinwong, rfrias): add implementation
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GetProvisioningRequest(
|
||||
CdmProvisioningRequest* request,
|
||||
std::string* default_url) {
|
||||
// TODO(edwinwong, rfrias): add implementation
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
CdmProvisioningResponse& response) {
|
||||
// TODO(edwinwong, rfrias): add implementation
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GetSecureStops(
|
||||
CdmSecureStops* secure_stops) {
|
||||
// TODO(edwinwong, rfrias): add implementation
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::ReleaseSecureStops(
|
||||
const CdmSecureStopReleaseMessage& message) {
|
||||
// TODO(edwinwong, rfrias): add implementation
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::Decrypt(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_encrypted,
|
||||
const KeyId& key_id,
|
||||
const uint8_t* encrypted_buffer,
|
||||
size_t encrypted_size,
|
||||
const std::vector<uint8_t>& iv,
|
||||
size_t block_offset,
|
||||
void* decrypted_buffer) {
|
||||
CdmSession* session = sessions_[session_id];
|
||||
|
||||
if (!session) {
|
||||
LOGW("CdmEngine::Decrypt: session_id not found = %s", session_id.c_str());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
// TODO(edwinwong, rfrias): Need to add implemenation and hook up
|
||||
// decryption though to oem_crypto
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
bool CdmEngine::IsKeyValid(const KeyId& key_id) {
|
||||
for (CdmSessionIter iter = sessions_.begin();
|
||||
iter != sessions_.end(); ++iter) {
|
||||
if (iter->second->IsKeyValid(key_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CdmEngine::AttachEventListener(
|
||||
CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener) {
|
||||
|
||||
CdmSessionIter iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return iter->second->AttachEventListener(listener);
|
||||
}
|
||||
|
||||
bool CdmEngine::DetachEventListener(
|
||||
CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener) {
|
||||
|
||||
CdmSessionIter iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return iter->second->DetachEventListener(listener);
|
||||
}
|
||||
|
||||
bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
|
||||
return (key_system.find("widevine") != std::string::npos);
|
||||
}
|
||||
|
||||
bool CdmEngine::CancelSessions() {
|
||||
// TODO(gmorgan) Implement CancelSessions()
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse a blob of multiple concatenated PSSH atoms to extract the first
|
||||
// widevine pssh
|
||||
// TODO(kqyang): temporary workaround - remove after b/7928472 is resolved
|
||||
bool CdmEngine::ExtractWidevinePssh(
|
||||
const CdmInitData& init_data, CdmInitData* output) {
|
||||
|
||||
BufferReader reader(
|
||||
reinterpret_cast<const uint8_t*>(init_data.data()), init_data.length());
|
||||
|
||||
// TODO(kqyang): Extracted from an actual init_data;
|
||||
// Need to find out where it comes from.
|
||||
static const uint8_t kWidevineSystemId[] = {
|
||||
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
|
||||
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
||||
};
|
||||
|
||||
// one PSSH blob consists of:
|
||||
// 4 byte size of the PSSH atom, inclusive
|
||||
// "pssh"
|
||||
// 4 byte flags, value 0
|
||||
// 16 byte system id
|
||||
// 4 byte size of PSSH data, exclusive
|
||||
while (1) {
|
||||
// size of PSSH atom, used for skipping
|
||||
uint32_t size;
|
||||
if (!reader.Read4(&size)) return false;
|
||||
|
||||
// "pssh"
|
||||
std::vector<uint8_t> pssh;
|
||||
if (!reader.ReadVec(&pssh, 4)) return false;
|
||||
if (memcmp(&pssh[0], "pssh", 4)) return false;
|
||||
|
||||
// flags
|
||||
uint32_t flags;
|
||||
if (!reader.Read4(&flags)) return false;
|
||||
if (flags != 0) return false;
|
||||
|
||||
// system id
|
||||
std::vector<uint8_t> system_id;
|
||||
if (!reader.ReadVec(&system_id, sizeof(kWidevineSystemId))) return false;
|
||||
|
||||
if (memcmp(&system_id[0], kWidevineSystemId,
|
||||
sizeof(kWidevineSystemId))) {
|
||||
// skip the remaining contents of the atom,
|
||||
// after size field, atom name, flags and system id
|
||||
if (!reader.SkipBytes(
|
||||
size - 4 - 4 - 4 - sizeof(kWidevineSystemId))) return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// size of PSSH box
|
||||
uint32_t pssh_length;
|
||||
if (!reader.Read4(&pssh_length)) return false;
|
||||
|
||||
output->clear();
|
||||
if (!reader.ReadString(output, pssh_length)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// we did not find a matching record
|
||||
return false;
|
||||
}
|
||||
|
||||
void CdmEngine::EnablePolicyTimer() {
|
||||
|
||||
if (!policy_timer_.IsRunning())
|
||||
policy_timer_.Start(this, CDM_POLICY_TIMER_DURATION_SECONDS);
|
||||
}
|
||||
|
||||
void CdmEngine::DisablePolicyTimer() {
|
||||
|
||||
if (policy_timer_.IsRunning())
|
||||
policy_timer_.Stop();
|
||||
}
|
||||
|
||||
void CdmEngine::OnTimerEvent() {
|
||||
|
||||
for (CdmSessionIter iter = sessions_.begin();
|
||||
iter != sessions_.end(); ++iter) {
|
||||
iter->second->OnTimerEvent();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
144
libwvdrmengine/cdm/core/src/cdm_session.cpp
Normal file
144
libwvdrmengine/cdm/core/src/cdm_session.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
// Author: jfore@google.com (Jeff Fore), rkuroiwa@google.com (Rintaro Kuroiwa)
|
||||
|
||||
#include "cdm_session.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "crypto_engine.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
#include "clock.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::set<WvCdmEventListener*>::iterator CdmEventListenerIter;
|
||||
|
||||
bool CdmSession::Init() {
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
if (!crypto_engine) {
|
||||
LOGE("CdmSession::Init failed to get CryptoEngine instance.");
|
||||
return false;
|
||||
}
|
||||
|
||||
crypto_session_ = crypto_engine->CreateSession(session_id_);
|
||||
if (!crypto_session_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string token;
|
||||
if (!crypto_engine->GetToken(&token)) return false;
|
||||
|
||||
return license_parser_.Init(token, crypto_session_);
|
||||
}
|
||||
|
||||
bool CdmSession::DestroySession() {
|
||||
if (crypto_session_) {
|
||||
delete crypto_session_;
|
||||
crypto_session_ = NULL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmSession::VerifySession(const CdmKeySystem& key_system,
|
||||
const CdmInitData& init_data) {
|
||||
// TODO(gmorgan): Compare key_system and init_data with value received
|
||||
// during session startup - they should be the same.
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::GenerateKeyRequest(const CdmInitData& init_data,
|
||||
CdmKeyMessage* key_request) {
|
||||
crypto_session_->Open();
|
||||
if(!license_parser_.PrepareKeyRequest(init_data, key_request)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// AddKey() - Accept license response and extract key info.
|
||||
CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response) {
|
||||
if (!license_parser_.HandleKeyResponse(key_response)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
return KEY_ADDED;
|
||||
}
|
||||
}
|
||||
|
||||
// CancelKeyRequest() - Cancel session.
|
||||
CdmResponseType CdmSession::CancelKeyRequest() {
|
||||
// TODO(gmorgan): cancel and clean up session
|
||||
crypto_session_->Close();
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
// Decrypt() - Accept encrypted buffer and return decrypted data.
|
||||
CdmResponseType CdmSession::Decrypt(const uint8_t* encrypted_buffer,
|
||||
size_t encrypted_size,
|
||||
size_t block_offset,
|
||||
const std::string& iv,
|
||||
const KeyId& key_id,
|
||||
uint8_t* decrypted_buffer) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// License renewal
|
||||
// GenerateRenewalRequest() - Construct valid renewal request for the current
|
||||
// session keys.
|
||||
CdmResponseType CdmSession::GenerateRenewalRequest(CdmKeyMessage* key_request) {
|
||||
if(!license_parser_.PrepareKeyRenewalRequest(key_request)) {
|
||||
return KEY_ERROR;
|
||||
} else {
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
bool CdmSession::IsKeyValid(const KeyId& key_id) {
|
||||
// TODO(gmorgan): lookup key and determine if valid.
|
||||
// return (session_keys_.find(key_id) != session_keys_.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
CdmSessionId CdmSession::GenerateSessionId() {
|
||||
static const std::string kSessionPrefix("Session");
|
||||
static int session_num = 1;
|
||||
// TODO(rkuroiwa): Want this to be unique. Probably doing Hash(time+init_data)
|
||||
// to get something that is reasonably unique.
|
||||
return kSessionPrefix + IntToString(++session_num);
|
||||
}
|
||||
|
||||
bool CdmSession::AttachEventListener(WvCdmEventListener* listener) {
|
||||
std::pair<CdmEventListenerIter, bool> result = listeners_.insert(listener);
|
||||
return result.second;
|
||||
}
|
||||
|
||||
bool CdmSession::DetachEventListener(WvCdmEventListener* listener) {
|
||||
return (listeners_.erase(listener) == 1);
|
||||
}
|
||||
|
||||
void CdmSession::OnTimerEvent() {
|
||||
bool event_occurred = false;
|
||||
CdmEventType event;
|
||||
|
||||
policy_engine_.OnTimerEvent(GetCurrentTime(), event_occurred, event);
|
||||
|
||||
if (event_occurred) {
|
||||
for (CdmEventListenerIter iter = listeners_.begin();
|
||||
iter != listeners_.end(); ++iter) {
|
||||
(*iter)->onEvent(session_id(), event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
178
libwvdrmengine/cdm/core/src/crypto_engine.cpp
Normal file
178
libwvdrmengine/cdm/core/src/crypto_engine.cpp
Normal file
@@ -0,0 +1,178 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Crypto - wrapper classes for OEMCrypto interface
|
||||
//
|
||||
|
||||
#include "crypto_engine.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "log.h"
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
CryptoEngine* CryptoEngine::crypto_engine_ = NULL;
|
||||
Lock CryptoEngine::crypto_engine_lock_;
|
||||
|
||||
// wrapper classes for OEMCrypto interface
|
||||
// CryptoEngine -- top-level interface
|
||||
// CryptoSession -- session-specific interface
|
||||
// CryptoKey -- key interface
|
||||
|
||||
// CryptoEngine methods
|
||||
|
||||
CryptoEngine::CryptoEngine() : initialized_(false) {}
|
||||
|
||||
CryptoEngine::~CryptoEngine() {
|
||||
if (initialized_) {
|
||||
Terminate();
|
||||
}
|
||||
|
||||
CryptoSessionMap::iterator i(sessions_.begin());
|
||||
for (; i != sessions_.end(); ++i)
|
||||
delete i->second;
|
||||
sessions_.clear();
|
||||
}
|
||||
|
||||
// get the instance of OEMCrypto Client
|
||||
CryptoEngine* CryptoEngine::GetInstance() {
|
||||
if (NULL == crypto_engine_) {
|
||||
crypto_engine_ = CreateSingleton();
|
||||
}
|
||||
return crypto_engine_;
|
||||
}
|
||||
|
||||
CryptoEngine* CryptoEngine::CreateSingleton() {
|
||||
AutoLock auto_lock(crypto_engine_lock_);
|
||||
if (NULL == crypto_engine_) {
|
||||
crypto_engine_ = new CryptoEngine;
|
||||
}
|
||||
return crypto_engine_;
|
||||
}
|
||||
|
||||
void CryptoEngine::DeleteInstance() {
|
||||
if (NULL != crypto_engine_) {
|
||||
delete crypto_engine_;
|
||||
LOGV("CryptoEngine::DeleteInstance");
|
||||
crypto_engine_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool CryptoEngine::Init() {
|
||||
LOGV("CryptoEngine::Init: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!initialized_) {
|
||||
OEMCryptoResult result = OEMCrypto_Initialize();
|
||||
initialized_ = (OEMCrypto_SUCCESS == result);
|
||||
}
|
||||
return initialized_;
|
||||
}
|
||||
|
||||
bool CryptoEngine::Terminate() {
|
||||
DestroySessions();
|
||||
LOGV("CryptoEngine::Terminate: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
OEMCryptoResult result = OEMCrypto_Terminate();
|
||||
if (OEMCrypto_SUCCESS == result) {
|
||||
initialized_ = false;
|
||||
}
|
||||
return !initialized_;
|
||||
}
|
||||
|
||||
bool CryptoEngine::ValidateKeybox() {
|
||||
LOGV("CryptoEngine::ValidateKeybox: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
OEMCryptoResult result = OEMCrypto_IsKeyboxValid();
|
||||
return (OEMCrypto_SUCCESS == result);
|
||||
}
|
||||
|
||||
CryptoSession* CryptoEngine::CreateSession(const CdmSessionId& session_id) {
|
||||
LOGV("CryptoEngine::CreateSession: SLock");
|
||||
AutoLock auto_lock(sessions_lock_);
|
||||
if (0 == sessions_.size()) {
|
||||
if (!Init()) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
CryptoSessionMap::iterator it = sessions_.find(session_id);
|
||||
if (it != sessions_.end()) {
|
||||
LOGE("CryptoEngine::CreateSession : Duplicate session ID.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CryptoSession* new_session = new CryptoSession(session_id);
|
||||
if (!new_session) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!new_session->Open()) {
|
||||
delete new_session;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
sessions_[session_id] = new_session;
|
||||
return new_session;
|
||||
}
|
||||
|
||||
CryptoSession* CryptoEngine::FindSessionInternal(
|
||||
const CdmSessionId& session_id) {
|
||||
// must hold sessions_lock_
|
||||
CryptoSessionMap::iterator it = sessions_.find(session_id);
|
||||
if (it != sessions_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CryptoSession* CryptoEngine::FindSession(const CdmSessionId& session_id) {
|
||||
LOGV("CryptoEngine::FindSession: SLock");
|
||||
AutoLock auto_lock(sessions_lock_);
|
||||
return FindSessionInternal(session_id);
|
||||
}
|
||||
|
||||
bool CryptoEngine::DestroySession(const CdmSessionId& session_id) {
|
||||
LOGV("CryptoEngine::DestroySession: SLock");
|
||||
AutoLock auto_lock(sessions_lock_);
|
||||
if (0 == sessions_.size()) {
|
||||
return false;
|
||||
}
|
||||
CryptoSession* session = FindSessionInternal(session_id);
|
||||
if (session) {
|
||||
delete session;
|
||||
sessions_.erase(session_id);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CryptoEngine::DestroySessions() {
|
||||
for (CryptoSessionMap::iterator it = sessions_.begin();
|
||||
it != sessions_.end(); ++it) {
|
||||
delete it->second;
|
||||
}
|
||||
sessions_.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoEngine::GetToken(std::string* token) {
|
||||
LOGV("CryptoEngine::GetToken: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
if (!token) {
|
||||
LOGE("CryptoEngine::GetToken : No token passed to method.");
|
||||
return false;
|
||||
}
|
||||
uint8_t buf[72];
|
||||
size_t buflen = 72;
|
||||
OEMCryptoResult sts = OEMCrypto_GetKeyData(buf, &buflen);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
token->assign((const char*)buf, (size_t)buflen);
|
||||
return true;
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
338
libwvdrmengine/cdm/core/src/crypto_session.cpp
Normal file
338
libwvdrmengine/cdm/core/src/crypto_session.cpp
Normal file
@@ -0,0 +1,338 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Crypto - wrapper classes for OEMCrypto interface
|
||||
//
|
||||
|
||||
#include "crypto_session.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "crypto_engine.h"
|
||||
#include "log.h"
|
||||
// TODO(gmorgan,jtinker): decide if OEMCryptoCENC is needed here.
|
||||
#include "OEMCryptoCENC.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
// Encode unsigned integer into a big endian formatted string
|
||||
std::string EncodeUint32(unsigned int u) {
|
||||
std::string s;
|
||||
s.append(1, (u >> 24) & 0xFF);
|
||||
s.append(1, (u >> 16) & 0xFF);
|
||||
s.append(1, (u >> 8) & 0xFF);
|
||||
s.append(1, (u >> 0) & 0xFF);
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// wrapper classes for OEMCrypto interface
|
||||
// CryptoEngine -- top-level interface
|
||||
// CryptoSession -- session-specific interface
|
||||
// CryptoKey -- key interface
|
||||
|
||||
// CryptoSession methods
|
||||
|
||||
CryptoSession::CryptoSession() : valid_(false), open_(false) {}
|
||||
|
||||
CryptoSession::CryptoSession(const std::string& sname) : valid_(true),
|
||||
open_(false), cdm_session_id_(sname) {}
|
||||
|
||||
CryptoSession::~CryptoSession() {
|
||||
if (open_) {
|
||||
Close();
|
||||
}
|
||||
LOGV("CryptoSession::dtor: SLock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
crypto_engine->sessions_.erase(cdm_session_id_);
|
||||
if (0 == crypto_engine->sessions_.size()) {
|
||||
crypto_engine->DeleteInstance();
|
||||
}
|
||||
|
||||
CryptoKeyMap::iterator i(keys_.begin());
|
||||
for (; i != keys_.end(); ++i)
|
||||
delete i->second;
|
||||
keys_.clear();
|
||||
}
|
||||
|
||||
bool CryptoSession::Open() {
|
||||
LOGV("CryptoSession::Open: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
OEMCrypto_SESSION sid;
|
||||
OEMCryptoResult sts;
|
||||
if (open_)
|
||||
return false;
|
||||
sts = OEMCrypto_OpenSession(&sid);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
open_ = false;
|
||||
} else {
|
||||
oec_session_id_ = static_cast<CryptoSessionId>(sid);
|
||||
LOGV("OpenSession: id= %ld", (uint32_t) oec_session_id_);
|
||||
open_ = true;
|
||||
}
|
||||
return open_;
|
||||
}
|
||||
|
||||
void CryptoSession::Close() {
|
||||
LOGV("CryptoSession::Close: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
LOGV("CloseSession: id=%ld open=%s", (uint32_t) oec_session_id_, open_? "true" : "false") ;
|
||||
if (open_) {
|
||||
OEMCryptoResult sts = OEMCrypto_CloseSession(oec_session_id_);
|
||||
if (OEMCrypto_SUCCESS == sts) {
|
||||
open_ = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CryptoSession::GenerateRequestId(std::string& req_id_str) {
|
||||
LOGV("CryptoSession::GenerateRequestId: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
// TODO(gmorgan): Get unique ID from OEMCrypto
|
||||
req_id_str.assign("987654321");
|
||||
}
|
||||
|
||||
bool CryptoSession::PrepareRequest(const std::string& message,
|
||||
std::string* signature) {
|
||||
LOGV("CryptoSession::PrepareRequest: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
|
||||
if (!signature) {
|
||||
LOGE("CryptoSession::PrepareRequest : No output destination provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t signature_buf[32];
|
||||
size_t length = 32;
|
||||
OEMCryptoResult sts;
|
||||
|
||||
std::string mac_deriv_message;
|
||||
std::string enc_deriv_message;
|
||||
GenerateMacContext(message, &mac_deriv_message);
|
||||
GenerateEncryptContext(message, &enc_deriv_message);
|
||||
|
||||
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t) oec_session_id_);
|
||||
sts = OEMCrypto_GenerateDerivedKeys(
|
||||
oec_session_id_,
|
||||
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
|
||||
mac_deriv_message.size(),
|
||||
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
|
||||
enc_deriv_message.size());
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("GenerateSignature: id=%ld", (uint32_t) oec_session_id_);
|
||||
sts = OEMCrypto_GenerateSignature(
|
||||
oec_session_id_,
|
||||
reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
signature_buf,
|
||||
&length);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
signature->assign(reinterpret_cast<const char*>(signature_buf), length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::PrepareRenewalRequest(const std::string& message,
|
||||
std::string* signature) {
|
||||
LOGV("CryptoSession::PrepareRenewalRequest: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
uint8_t signature_buf[32];
|
||||
size_t length = 32;
|
||||
|
||||
OEMCryptoResult sts = OEMCrypto_GenerateSignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(), signature_buf, &length);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
|
||||
signature->assign(reinterpret_cast<const char*>(signature_buf), length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CryptoSession::GenerateMacContext(const std::string& input_context,
|
||||
std::string* deriv_context) {
|
||||
if (!deriv_context) {
|
||||
LOGE("CryptoSession::GenerateMacContext : No output destination provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string kSigningKeyLabel = "AUTHENTICATION";
|
||||
const size_t kSigningKeySizeBits = MAC_KEY_SIZE * 8;
|
||||
|
||||
deriv_context->assign(kSigningKeyLabel);
|
||||
deriv_context->append(1, '\0');
|
||||
deriv_context->append(input_context);
|
||||
deriv_context->append(EncodeUint32(kSigningKeySizeBits));
|
||||
}
|
||||
|
||||
void CryptoSession::GenerateEncryptContext(const std::string& input_context,
|
||||
std::string* deriv_context) {
|
||||
if (!deriv_context) {
|
||||
LOGE("CryptoSession::GenerateEncryptContext : No output destination provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string kEncryptionKeyLabel = "ENCRYPTION";
|
||||
const size_t kEncryptionKeySizeBits = KEY_SIZE * 8;
|
||||
|
||||
deriv_context->assign(kEncryptionKeyLabel);
|
||||
deriv_context->append(1, '\0');
|
||||
deriv_context->append(input_context);
|
||||
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
|
||||
}
|
||||
|
||||
size_t CryptoSession::GetOffset(std::string message, std::string field) {
|
||||
size_t pos = message.find(field);
|
||||
if (pos == std::string::npos) {
|
||||
LOGE("CryptoSession::GetOffset : Cannot find offset for %s", field.c_str());
|
||||
pos = 0;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
bool CryptoSession::LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
int num_keys,
|
||||
const CryptoKey* key_array) {
|
||||
LOGV("CryptoSession::LoadKeys: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
|
||||
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
const uint8_t* enc_mac_key = NULL;
|
||||
const uint8_t* enc_mac_key_iv = NULL;
|
||||
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
|
||||
enc_mac_key = msg + GetOffset(message, mac_key);
|
||||
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
|
||||
}
|
||||
OEMCrypto_KeyObject load_key_array[num_keys];
|
||||
for (int i=0; i<num_keys; ++i) {
|
||||
const CryptoKey* ki = &key_array[i];
|
||||
OEMCrypto_KeyObject* ko = &load_key_array[i];
|
||||
ko->key_id = msg + GetOffset(message, ki->key_id());
|
||||
ko->key_id_length = ki->key_id().length();
|
||||
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
|
||||
ko->key_data = msg + GetOffset(message, ki->key_data());
|
||||
if (ki->HasKeyControl()) {
|
||||
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
|
||||
ko->key_control = msg + GetOffset(message, ki->key_control());
|
||||
}
|
||||
else {
|
||||
LOGE("For key %d: XXX key has no control block. size=%d", i, ki->key_control().size());
|
||||
ko->key_control_iv = NULL;
|
||||
ko->key_control = NULL;
|
||||
}
|
||||
}
|
||||
LOGV("LoadKeys: id=%ld", (uint32_t) oec_session_id_);
|
||||
return (OEMCrypto_SUCCESS == OEMCrypto_LoadKeys(
|
||||
oec_session_id_, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()),
|
||||
signature.size(), enc_mac_key_iv, enc_mac_key,
|
||||
num_keys, load_key_array));
|
||||
}
|
||||
|
||||
bool CryptoSession::RefreshKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
int num_keys,
|
||||
const CryptoKey* key_array) {
|
||||
LOGV("CryptoSession::RefreshKeys: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
|
||||
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
|
||||
OEMCrypto_KeyRefreshObject load_key_array[num_keys];
|
||||
for (int i=0; i<num_keys; ++i) {
|
||||
const CryptoKey* ki = &key_array[i];
|
||||
OEMCrypto_KeyRefreshObject* ko = &load_key_array[i];
|
||||
if (ki->key_id().empty()) {
|
||||
ko->key_id = NULL;
|
||||
} else {
|
||||
ko->key_id = msg + GetOffset(message, ki->key_id());
|
||||
}
|
||||
if (ki->HasKeyControl()) {
|
||||
if (ki->key_control_iv().empty()) {
|
||||
ko->key_control_iv = NULL;
|
||||
} else {
|
||||
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
|
||||
}
|
||||
ko->key_control = msg + GetOffset(message, ki->key_control());
|
||||
}
|
||||
else {
|
||||
ko->key_control_iv = NULL;
|
||||
ko->key_control = NULL;
|
||||
}
|
||||
}
|
||||
LOGV("RefreshKeys: id=%ld", static_cast<uint32_t>(oec_session_id_));
|
||||
return (OEMCrypto_SUCCESS == OEMCrypto_RefreshKeys(
|
||||
oec_session_id_, msg, message.size(),
|
||||
reinterpret_cast<const uint8_t*>(signature.data()),
|
||||
signature.size(),
|
||||
num_keys, load_key_array));
|
||||
}
|
||||
|
||||
bool CryptoSession::SelectKey(const std::string& key_id) {
|
||||
LOGV("CryptoSession::SelectKey: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
const uint8_t* key_id_string =
|
||||
reinterpret_cast<const uint8_t*>(key_id.data());
|
||||
|
||||
LOGV("SelectKey: id=%ld", static_cast<uint32_t>(oec_session_id_));
|
||||
OEMCryptoResult sts = OEMCrypto_SelectKey(oec_session_id_, key_id_string,
|
||||
key_id.size());
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::Decrypt(const InputDescriptor input,
|
||||
const OutputDescriptor output) {
|
||||
LOGV("CryptoSession::Decrypt: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
// TODO(gmorgan): handle inputs and outputs to decrypt call
|
||||
const uint8_t* data_addr = NULL;
|
||||
uint32_t data_length = 0;
|
||||
bool is_encrypted = false;
|
||||
uint8_t* iv = NULL;
|
||||
uint32_t offset = 0;
|
||||
const OEMCrypto_DestBufferDesc* out_buffer = NULL;
|
||||
OEMCryptoResult sts = OEMCrypto_DecryptCTR(oec_session_id_, data_addr,
|
||||
data_length, is_encrypted, iv,
|
||||
offset, out_buffer);
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateNonce(uint32_t* nonce) {
|
||||
if (!nonce) {
|
||||
LOGE("input parameter is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGV("CryptoSession::GenerateNonce: Lock");
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
AutoLock auto_lock(crypto_engine->crypto_lock_);
|
||||
return(OEMCrypto_SUCCESS == OEMCrypto_GenerateNonce(oec_session_id_, nonce));
|
||||
}
|
||||
}; // namespace wvcdm
|
||||
298
libwvdrmengine/cdm/core/src/license.cpp
Normal file
298
libwvdrmengine/cdm/core/src/license.cpp
Normal file
@@ -0,0 +1,298 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "license.h"
|
||||
|
||||
#include "crypto_session.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Protobuf generated classes.
|
||||
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::SignedMessage;
|
||||
using video_widevine_server::sdk::STREAMING;
|
||||
using video_widevine_server::sdk::LicenseRequest_ContentIdentification_ExistingLicense;
|
||||
|
||||
CdmLicense::CdmLicense(): session_(NULL) {}
|
||||
|
||||
CdmLicense::~CdmLicense() {}
|
||||
|
||||
bool CdmLicense::Init(const std::string& token, CryptoSession* session) {
|
||||
if (token.size() == 0)
|
||||
return false;
|
||||
if (session == NULL || !session->IsValid() || !session->IsOpen())
|
||||
return false;
|
||||
token_ = token;
|
||||
session_ = session;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmLicense::PrepareKeyRequest(const CdmInitData& init_data,
|
||||
CdmKeyMessage* signed_request) {
|
||||
if (!session_ ||
|
||||
token_.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (init_data.empty()) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest : No init data provided;");
|
||||
return false;
|
||||
}
|
||||
if (!signed_request) {
|
||||
LOGE("CdmLicense::PrepareKeyRequest : No signed request provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(gmorgan): Request ID owned by session?
|
||||
std::string request_id;
|
||||
session_->GenerateRequestId(request_id);
|
||||
|
||||
LicenseRequest license_request;
|
||||
LicenseRequest_ClientIdentification* client_id =
|
||||
license_request.mutable_client_id();
|
||||
|
||||
client_id->set_type(LicenseRequest_ClientIdentification::KEYBOX);
|
||||
client_id->set_token(token_);
|
||||
|
||||
// Content Identification may be a cenc_id, a webm_id or a license_id
|
||||
LicenseRequest_ContentIdentification* content_id =
|
||||
license_request.mutable_content_id();
|
||||
|
||||
LicenseRequest_ContentIdentification_CENC* cenc_content_id =
|
||||
content_id->mutable_cenc_id();
|
||||
cenc_content_id->add_pssh(init_data);
|
||||
cenc_content_id->set_license_type(STREAMING);
|
||||
cenc_content_id->set_request_id(request_id);
|
||||
|
||||
// TODO(jfore): The time field will be updated once the cdm wrapper
|
||||
// has been updated to pass us in the time.
|
||||
license_request.set_request_time(0);
|
||||
|
||||
license_request.set_type(LicenseRequest::NEW);
|
||||
|
||||
// Get/set the nonce. This value will be reflected in the Key Control Block
|
||||
// of the license response.
|
||||
uint32_t nonce;
|
||||
if (!session_->GenerateNonce(&nonce)) {
|
||||
return false;
|
||||
}
|
||||
license_request.set_key_control_nonce(UintToString(nonce));
|
||||
LOGD("PrepareKeyRequest: nonce=%u", nonce);
|
||||
|
||||
// License request is complete. Serialize it.
|
||||
std::string serialized_license_req;
|
||||
license_request.SerializeToString(&serialized_license_req);
|
||||
|
||||
// Derive signing and encryption keys and construct signature.
|
||||
std::string license_request_signature;
|
||||
if (!session_->PrepareRequest(serialized_license_req,
|
||||
&license_request_signature)) {
|
||||
signed_request->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (license_request_signature.empty()) {
|
||||
signed_request->clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Put serialize license request and signature together
|
||||
SignedMessage signed_message;
|
||||
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
||||
signed_message.set_signature(license_request_signature);
|
||||
signed_message.set_msg(serialized_license_req);
|
||||
|
||||
signed_message.SerializeToString(signed_request);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmLicense::PrepareKeyRenewalRequest(CdmKeyMessage* signed_request) {
|
||||
if (!session_) {
|
||||
return false;
|
||||
}
|
||||
if (!signed_request) {
|
||||
LOGE("CdmLicense::PrepareKeyRenewalRequest : No signed request provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
LicenseRequest license_request;
|
||||
license_request.set_type(LicenseRequest::RENEWAL);
|
||||
|
||||
LicenseRequest_ContentIdentification_ExistingLicense* current_license =
|
||||
license_request.mutable_content_id()->mutable_license();
|
||||
current_license->mutable_license_id()->CopyFrom(license_id_);
|
||||
|
||||
// Get/set the nonce. This value will be reflected in the Key Control Block
|
||||
// of the license response.
|
||||
uint32_t nonce;
|
||||
if (!session_->GenerateNonce(&nonce)) {
|
||||
return false;
|
||||
}
|
||||
license_request.set_key_control_nonce(UintToString(nonce));
|
||||
LOGD("PrepareKeyRenewalRequest: nonce=%u", nonce);
|
||||
|
||||
// License request is complete. Serialize it.
|
||||
std::string serialized_license_req;
|
||||
license_request.SerializeToString(&serialized_license_req);
|
||||
|
||||
// Construct signature.
|
||||
std::string license_request_signature;
|
||||
if (!session_->PrepareRenewalRequest(serialized_license_req,
|
||||
&license_request_signature))
|
||||
return false;
|
||||
|
||||
if (license_request_signature.empty()) return false;
|
||||
|
||||
// Put serialize license request and signature together
|
||||
SignedMessage signed_message;
|
||||
signed_message.set_type(SignedMessage::LICENSE_REQUEST);
|
||||
signed_message.set_signature(license_request_signature);
|
||||
signed_message.set_msg(serialized_license_req);
|
||||
|
||||
signed_message.SerializeToString(signed_request);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CdmLicense::HandleKeyResponse(const CdmKeyResponse& license_response) {
|
||||
if (!session_) {
|
||||
return false;
|
||||
}
|
||||
if (license_response.empty()) {
|
||||
LOGE("CdmLicense::HandleKeyResponse : Empty license response.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SignedMessage signed_response;
|
||||
if (!signed_response.ParseFromString(license_response))
|
||||
return false;
|
||||
|
||||
if (!signed_response.has_signature())
|
||||
return false;
|
||||
|
||||
License license;
|
||||
if (!license.ParseFromString(signed_response.msg()))
|
||||
return false;
|
||||
|
||||
// Extract mac key
|
||||
std::string mac_key_iv;
|
||||
std::string mac_key;
|
||||
if (license.policy().can_renew())
|
||||
{
|
||||
for (int i = 0; i < license.key_size(); ++i) {
|
||||
if (license.key(i).type() == License_KeyContainer::SIGNING) {
|
||||
mac_key_iv.assign(license.key(i).iv());
|
||||
|
||||
// Strip off PKCS#5 padding
|
||||
mac_key.assign(license.key(i).key().data(), MAC_KEY_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (mac_key_iv.size() != KEY_IV_SIZE ||
|
||||
mac_key.size() != MAC_KEY_SIZE)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// License Id should not be empty for renewable license
|
||||
if (!license.has_id()) return false;
|
||||
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
if (num_keys == 0) return false;
|
||||
|
||||
// TODO(kqyang): move protocol buffer related stuff in policy
|
||||
// engine to this file.
|
||||
// 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;
|
||||
}
|
||||
|
||||
bool CdmLicense::HandleKeyRenewalResponse(
|
||||
const CdmKeyResponse& license_response) {
|
||||
if (!session_) {
|
||||
return false;
|
||||
}
|
||||
if (license_response.empty()) {
|
||||
LOGE("CdmLicense::HandleKeyRenewalResponse : Empty license response.");
|
||||
return false;
|
||||
}
|
||||
|
||||
SignedMessage signed_response;
|
||||
if (!signed_response.ParseFromString(license_response))
|
||||
return false;
|
||||
|
||||
if (!signed_response.has_signature())
|
||||
return false;
|
||||
|
||||
// TODO(jfore): refresh the keys in oemcrypto
|
||||
|
||||
License license;
|
||||
if (!license.ParseFromString(signed_response.msg()))
|
||||
return false;
|
||||
|
||||
if (!license.has_id()) return false;
|
||||
|
||||
if (license.id().version() > license_id_.version()) {
|
||||
//This is the normal case.
|
||||
license_id_.CopyFrom(license.id());
|
||||
|
||||
// TODO(kqyang): should we move protocol buffer related stuff in policy
|
||||
// engine to this file instead?
|
||||
// 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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
224
libwvdrmengine/cdm/core/src/license_protocol.proto
Normal file
224
libwvdrmengine/cdm/core/src/license_protocol.proto
Normal file
@@ -0,0 +1,224 @@
|
||||
syntax = "proto2";
|
||||
|
||||
package video_widevine_server.sdk;
|
||||
|
||||
option optimize_for = LITE_RUNTIME;
|
||||
|
||||
enum LicenseType {
|
||||
STREAMING = 1;
|
||||
OFFLINE = 2;
|
||||
}
|
||||
|
||||
// LicenseIdentification is propagated from LicenseRequest to License,
|
||||
// incrementing version with each iteration.
|
||||
message LicenseIdentification {
|
||||
optional bytes request_id = 1;
|
||||
optional bytes session_id = 2;
|
||||
optional bytes purchase_id = 3;
|
||||
optional LicenseType type = 4;
|
||||
optional int32 version = 5;
|
||||
}
|
||||
|
||||
message License {
|
||||
message Policy {
|
||||
// Indicates that playback of the content is allowed.
|
||||
optional bool can_play = 1 [default = false];
|
||||
|
||||
// Indicates that the license may be persisted to non-volatile
|
||||
// storage for offline use.
|
||||
optional bool can_persist = 2 [default = false];
|
||||
|
||||
// Indicates that renewal of this license is allowed.
|
||||
optional bool can_renew = 3 [default = false];
|
||||
|
||||
// For the |*duration*| fields, playback must halt when
|
||||
// license_start_time (seconds since the epoch (UTC)) +
|
||||
// license_duration_seconds is exceeded. A value of 0
|
||||
// indicates that there is no limit to the duration.
|
||||
|
||||
// Indicates the rental window.
|
||||
optional int64 rental_duration_seconds = 4 [default = 0];
|
||||
|
||||
// Indicates the viewing window, once playback has begun.
|
||||
optional int64 playback_duration_seconds = 5 [default = 0];
|
||||
|
||||
// Indicates the time window for this specific license.
|
||||
optional int64 license_duration_seconds = 6 [default = 0];
|
||||
|
||||
// The |renewal*| fields only apply if |can_renew| is true.
|
||||
|
||||
// The window of time, in which playback is allowed to continue while
|
||||
// renewal is attempted, yet unsuccessful due to backend problems with
|
||||
// the license server.
|
||||
optional int64 renewal_recovery_duration_seconds = 7 [default = 0];
|
||||
|
||||
// All renewal requests for this license shall be directed to the
|
||||
// specified URL.
|
||||
optional string renewal_server_url = 8;
|
||||
|
||||
// How many seconds after license_start_time, before renewal is first
|
||||
// attempted.
|
||||
optional int64 renewal_delay_seconds = 9 [default = 0];
|
||||
|
||||
// Specifies the delay in seconds between subsequent license
|
||||
// renewal requests, in case of failure.
|
||||
optional int64 renewal_retry_interval_seconds = 10 [default = 0];
|
||||
|
||||
// Indicates that the license shall be sent for renewal when usage is
|
||||
// started.
|
||||
optional bool renew_with_usage = 11 [default = false];
|
||||
}
|
||||
|
||||
message KeyContainer {
|
||||
enum KeyType {
|
||||
// Exactly one key of this type must appear.
|
||||
SIGNING = 1;
|
||||
CONTENT = 2;
|
||||
}
|
||||
|
||||
// The SecurityLevel enumeration allows the server to communicate the level
|
||||
// of robustness required by the client, in order to use the key.
|
||||
enum SecurityLevel {
|
||||
// Software-based whitebox crypto is required.
|
||||
SW_SECURE_CRYPTO = 1;
|
||||
|
||||
// Software crypto and an obfuscated decoder is required.
|
||||
SW_SECURE_DECODE = 2;
|
||||
|
||||
// The key material and crypto operations must be performed within a
|
||||
// hardware backed trusted execution environment.
|
||||
HW_SECURE_CRYPTO = 3;
|
||||
|
||||
// The crypto and decoding of content must be performed within a hardware
|
||||
// backed trusted execution environment.
|
||||
HW_SECURE_DECODE = 4;
|
||||
|
||||
// The crypto, decoding and all handling of the media (compressed and
|
||||
// uncompressed) must be handled within a hardware backed trusted
|
||||
// execution environment.
|
||||
HW_SECURE_ALL = 5;
|
||||
}
|
||||
|
||||
message KeyControl {
|
||||
// |key_control| is documented here:
|
||||
// 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 iv = 2;
|
||||
}
|
||||
|
||||
message OutputProtection {
|
||||
// Indicates whether HDCP is required on digital outputs, and which
|
||||
// version should be used.
|
||||
enum HDCP {
|
||||
HDCP_NONE = 0;
|
||||
HDCP_V1 = 1;
|
||||
HDCP_V2 = 2;
|
||||
}
|
||||
optional HDCP hdcp = 1 [default = HDCP_NONE];
|
||||
|
||||
// Indicate the CGMS setting to be inserted on analog output.
|
||||
enum CGMS {
|
||||
CGMS_NONE = 42;
|
||||
COPY_FREE = 0;
|
||||
COPY_ONCE = 2;
|
||||
COPY_NEVER = 3;
|
||||
}
|
||||
optional CGMS cgms_flags = 2 [default = CGMS_NONE];
|
||||
}
|
||||
optional bytes id = 1;
|
||||
optional bytes iv = 2;
|
||||
optional bytes key = 3;
|
||||
optional KeyType type = 4;
|
||||
optional SecurityLevel level = 5 [default = SW_SECURE_CRYPTO];
|
||||
optional OutputProtection required_protection = 6;
|
||||
optional OutputProtection requested_protection = 7;
|
||||
optional KeyControl key_control = 8;
|
||||
}
|
||||
|
||||
optional LicenseIdentification id = 1;
|
||||
optional Policy policy = 2;
|
||||
repeated KeyContainer key = 3;
|
||||
optional int64 license_start_time = 4;
|
||||
}
|
||||
|
||||
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;
|
||||
optional LicenseType license_type = 2;
|
||||
optional bytes request_id = 3; // Opaque, client-specified.
|
||||
}
|
||||
|
||||
message WebM {
|
||||
optional bytes header = 1;
|
||||
optional LicenseType license_type = 2;
|
||||
optional bytes request_id = 3; // Opaque, client-specified.
|
||||
}
|
||||
|
||||
message ExistingLicense {
|
||||
optional LicenseIdentification license_id = 1;
|
||||
optional int64 seconds_since_started = 2;
|
||||
}
|
||||
|
||||
// Exactly one of these must be present.
|
||||
optional CENC cenc_id = 1;
|
||||
optional WebM webm_id = 2;
|
||||
optional ExistingLicense license = 3;
|
||||
}
|
||||
|
||||
enum RequestType {
|
||||
NEW = 1;
|
||||
RENEWAL = 2;
|
||||
RELEASE = 3;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
message SignedMessage {
|
||||
enum MessageType {
|
||||
LICENSE_REQUEST = 1;
|
||||
LICENSE = 2;
|
||||
}
|
||||
|
||||
optional MessageType type = 1;
|
||||
optional bytes msg = 2;
|
||||
optional bytes signature = 3;
|
||||
}
|
||||
|
||||
// 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 int64 license_start_time = 5;
|
||||
}
|
||||
|
||||
// This message is used by the server to preserve and restore session state.
|
||||
message SessionState {
|
||||
optional LicenseIdentification license_id = 1;
|
||||
optional bytes signing_key = 2;
|
||||
optional uint32 keybox_system_id = 3;
|
||||
}
|
||||
211
libwvdrmengine/cdm/core/src/policy_engine.cpp
Normal file
211
libwvdrmengine/cdm/core/src/policy_engine.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "policy_engine.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
PolicyEngine::PolicyEngine() :
|
||||
license_state_(kLicenseStateInitial),
|
||||
license_start_time_(0),
|
||||
next_renewal_time_(0),
|
||||
policy_max_duration_seconds_(0) {
|
||||
}
|
||||
|
||||
PolicyEngine::~PolicyEngine() {
|
||||
}
|
||||
|
||||
void PolicyEngine::OnTimerEvent(int64_t current_time, bool event_occured, CdmEventType& event) {
|
||||
event_occured = false;
|
||||
|
||||
// License expiration trumps all.
|
||||
if (IsLicenseDurationExpired(current_time) &&
|
||||
license_state_ != kLicenseStateExpired) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
event = LICENSE_EXPIRED_EVENT;
|
||||
event_occured = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Test to determine if renewal should be attempted.
|
||||
switch (license_state_) {
|
||||
case kLicenseStateCanPlay: {
|
||||
if (IsRenewalDelayExpired(current_time)) {
|
||||
license_state_ = kLicenseStateNeedRenewal;
|
||||
UpdateRenewalRequest(current_time);
|
||||
event = LICENSE_RENEWAL_NEEDED_EVENT;
|
||||
event_occured = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case kLicenseStateNeedRenewal: {
|
||||
UpdateRenewalRequest(current_time);
|
||||
event = LICENSE_RENEWAL_NEEDED_EVENT;
|
||||
event_occured = true;
|
||||
return;
|
||||
}
|
||||
|
||||
case kLicenseStateWaitingLicenseUpdate: {
|
||||
if (IsRenewalRetryIntervalExpired(current_time)) {
|
||||
UpdateRenewalRequest(current_time);
|
||||
event = LICENSE_RENEWAL_NEEDED_EVENT;
|
||||
event_occured = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case kLicenseStateInitial:
|
||||
case kLicenseStateExpired: {
|
||||
return;
|
||||
}
|
||||
|
||||
default: {
|
||||
license_state_ = kLicenseStateCannotPlay;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Fix up differences between Eureka and other platforms' use cases.
|
||||
// Eureka does not get calls to decrypt. license usage begins
|
||||
// when we receive the initial license. renew_with_usage will cause renewal to
|
||||
// occur on the first call to OnTimeEvent after PolicyEngine::SetLicense is
|
||||
// called.
|
||||
void PolicyEngine::SetLicense(
|
||||
const video_widevine_server::sdk::License& license) {
|
||||
license_id_.Clear();
|
||||
license_id_.CopyFrom(license.id());
|
||||
policy_.Clear();
|
||||
UpdateLicense(license);
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateLicense(
|
||||
const video_widevine_server::sdk::License& license) {
|
||||
if (!license.has_policy() || kLicenseStateExpired == license_state_)
|
||||
return;
|
||||
|
||||
policy_.MergeFrom(license.policy());
|
||||
policy_max_duration_seconds_ = 0;
|
||||
|
||||
// Calculate policy_max_duration_seconds_. policy_max_duration_seconds_
|
||||
// will be set to the minimum of the following policies :
|
||||
// rental_duration_seconds, playback_duration_seconds, and
|
||||
// license_duration_seconds. The value is used to determine
|
||||
// when the license expires.
|
||||
if (policy_.has_rental_duration_seconds())
|
||||
policy_max_duration_seconds_ = policy_.rental_duration_seconds();
|
||||
|
||||
if ((policy_.has_playback_duration_seconds() &&
|
||||
(policy_.playback_duration_seconds() < policy_max_duration_seconds_)) ||
|
||||
!policy_max_duration_seconds_) {
|
||||
policy_max_duration_seconds_ = policy_.playback_duration_seconds();
|
||||
}
|
||||
|
||||
if ((policy_.has_license_duration_seconds() &&
|
||||
(policy_.license_duration_seconds() < policy_max_duration_seconds_)) ||
|
||||
!policy_max_duration_seconds_) {
|
||||
policy_max_duration_seconds_ = policy_.license_duration_seconds();
|
||||
}
|
||||
|
||||
switch (license_state_) {
|
||||
case kLicenseStateInitial: {
|
||||
// Process initial license.
|
||||
license_start_time_ = license.license_start_time();
|
||||
if (policy_.can_play()) {
|
||||
license_state_ =
|
||||
policy_.renew_with_usage() ?
|
||||
kLicenseStateNeedRenewal : kLicenseStateCanPlay;
|
||||
} else {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
}
|
||||
next_renewal_time_ = license_start_time_
|
||||
+ policy_.renewal_delay_seconds();
|
||||
}
|
||||
break;
|
||||
|
||||
case kLicenseStateExpired:
|
||||
// Ignore policy updates.
|
||||
return;
|
||||
|
||||
default: {
|
||||
// Process license renewal.
|
||||
if (license.id().version() > license_id_.version()) {
|
||||
// This is the normal case.
|
||||
policy_.MergeFrom(license.policy());
|
||||
license_id_.CopyFrom(license.id());
|
||||
} 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);
|
||||
}
|
||||
|
||||
if (license.has_license_start_time()) {
|
||||
// license start has been updated. Transition back to the
|
||||
// normal kLicenseStateCanPlay state if playback is allowed by
|
||||
// the updated license.
|
||||
license_start_time_ = license.license_start_time();
|
||||
next_renewal_time_ = license_start_time_
|
||||
+ policy_.renewal_delay_seconds();
|
||||
license_state_ =
|
||||
policy_.can_play() ? kLicenseStateCanPlay : kLicenseStateExpired;
|
||||
} else {
|
||||
// license start was not updated. Continue sending renewel requests
|
||||
// at the specified retry rate. To perform this we transition directly
|
||||
// to kLicenseStateWaitingLicenseUpdate. While in this state
|
||||
// IsRenewalRetryIntervalExpired will always return false if retries are
|
||||
// not allowed. Note that next_renewal_time_ was updated when this
|
||||
// renewal was requested.
|
||||
license_state_ =
|
||||
policy_.can_play() ?
|
||||
kLicenseStateWaitingLicenseUpdate : kLicenseStateExpired;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PolicyEngine::UpdateRenewalRequest(int64_t current_time) {
|
||||
license_state_ = kLicenseStateWaitingLicenseUpdate;
|
||||
next_renewal_time_ = current_time + policy_.renewal_retry_interval_seconds();
|
||||
}
|
||||
|
||||
// For the policy time fields checked in the following methods, a value of 0
|
||||
// indicates that there is no limit to the duration. These methods
|
||||
// will always return false if the value is 0.
|
||||
bool PolicyEngine::IsLicenseDurationExpired(int64_t current_time) {
|
||||
return policy_max_duration_seconds_ &&
|
||||
license_start_time_ + policy_max_duration_seconds_ <=
|
||||
current_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::IsRenewalDelayExpired(int64_t current_time) {
|
||||
return (policy_.renewal_delay_seconds() > 0) &&
|
||||
license_start_time_ + policy_.renewal_delay_seconds() <=
|
||||
current_time;
|
||||
}
|
||||
|
||||
// TODO(jfore): there is some gray around how this should be
|
||||
// implemented. It currently is not.
|
||||
bool PolicyEngine::IsRenewalRecoveryDurationExpired(
|
||||
int64_t current_time) {
|
||||
return (policy_.renewal_recovery_duration_seconds() > 0) &&
|
||||
license_start_time_ + policy_.renewal_recovery_duration_seconds() <=
|
||||
current_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::IsRenewalRetryIntervalExpired(
|
||||
int64_t current_time) {
|
||||
return (policy_.renewal_retry_interval_seconds() > 0) &&
|
||||
next_renewal_time_ <= current_time;
|
||||
}
|
||||
|
||||
} // wvcdm
|
||||
|
||||
211
libwvdrmengine/cdm/core/src/string_conversions.cpp
Normal file
211
libwvdrmengine/cdm/core/src/string_conversions.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "string_conversions.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace {
|
||||
// Helper for Base64SafeDecode()
|
||||
char B64ToBin(char inch) {
|
||||
if (inch >= 'A' && inch <= 'Z') return inch - 'A';
|
||||
if (inch >= 'a' && inch <= 'z') return inch - 'a' + 26;
|
||||
if (inch >= '0' && inch <= '9') return inch - '0' + 52;
|
||||
if (inch == '-') return 62;
|
||||
// if (inch == '_')
|
||||
return 63;
|
||||
}
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static bool CharToDigit(char ch, unsigned char* digit) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
*digit = ch - '0';
|
||||
} else {
|
||||
ch = tolower(ch);
|
||||
if ((ch >= 'a') && (ch <= 'f')) {
|
||||
*digit = ch - 'a' + 10;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// converts an ascii hex string(2 bytes per digit) into a decimal byte string
|
||||
std::vector<uint8_t> a2b_hex(const std::string& byte) {
|
||||
std::vector<uint8_t> array;
|
||||
unsigned int count = byte.size();
|
||||
if (count == 0 || (count % 2) != 0) {
|
||||
LOGE("Invalid input size %u for string %s", count, byte.c_str());
|
||||
return array;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < count / 2; ++i) {
|
||||
unsigned char msb = 0; // most significant 4 bits
|
||||
unsigned char lsb = 0; // least significant 4 bits
|
||||
if (!CharToDigit(byte[i * 2], &msb) ||
|
||||
!CharToDigit(byte[i * 2 + 1], &lsb)) {
|
||||
LOGE("Invalid hex value %c%c at index %d", byte[i*2], byte[i*2+1], i);
|
||||
return array;
|
||||
}
|
||||
array.push_back((msb << 4) | lsb);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
std::string a2bs_hex(const std::string& byte) {
|
||||
std::vector<uint8_t> array = a2b_hex(byte);
|
||||
return std::string(array.begin(), array.end());
|
||||
}
|
||||
|
||||
std::string b2a_hex(const std::vector<uint8_t>& byte) {
|
||||
return HexEncode(&byte[0], byte.size());
|
||||
}
|
||||
|
||||
std::string b2a_hex(const std::string& byte) {
|
||||
return HexEncode(reinterpret_cast<const uint8_t *>(byte.data()),
|
||||
byte.length());
|
||||
}
|
||||
|
||||
// Filename-friendly base64 encoding (RFC4648).
|
||||
// This is the encoding required by GooglePlay for certain
|
||||
// license server transactions. It is also used for logging
|
||||
// certain strings.
|
||||
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
static const char kBase64Chars[] =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789-_";
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
}
|
||||
int in_size = bin_input.size();
|
||||
int rup = ((in_size % 3) != 0) ? 1 : 0;
|
||||
int out_size = ((in_size * 4) / 3) + rup;
|
||||
std::string b64_output(out_size, '\0');
|
||||
int in_index = 0;
|
||||
int out_index = 0;
|
||||
unsigned long buffer;
|
||||
unsigned char out_cc;
|
||||
static const unsigned long kInMask = 0xff;
|
||||
static const unsigned long kOutMask = 0x3f;
|
||||
|
||||
while (in_index < in_size) {
|
||||
// up to 3 bytes (0..255) in
|
||||
buffer = (bin_input.at(in_index) & kInMask);
|
||||
buffer <<= 8;
|
||||
buffer |= (++in_index >= in_size) ? 0 : (bin_input.at(in_index) & kInMask);
|
||||
buffer <<= 8;
|
||||
buffer |= (++in_index >= in_size) ? 0 : (bin_input.at(in_index) & kInMask);
|
||||
++in_index;
|
||||
// up to 4 bytes (0..63) out
|
||||
out_cc = (buffer >> 18) & kOutMask;
|
||||
b64_output.at(out_index) = kBase64Chars[out_cc];
|
||||
if (++out_index >= out_size)
|
||||
break;
|
||||
out_cc = (buffer >> 12) & kOutMask;
|
||||
b64_output.at(out_index) = kBase64Chars[out_cc];
|
||||
if (++out_index >= out_size)
|
||||
break;
|
||||
out_cc = (buffer >> 6) & kOutMask;
|
||||
b64_output.at(out_index) = kBase64Chars[out_cc];
|
||||
if (++out_index >= out_size)
|
||||
break;
|
||||
out_cc = buffer & kOutMask;
|
||||
b64_output.at(out_index) = kBase64Chars[out_cc];
|
||||
++out_index;
|
||||
}
|
||||
return b64_output;
|
||||
}
|
||||
|
||||
// Decode for Filename-friendly base64 encoding (RFC4648).
|
||||
// This is the encoding required by GooglePlay for certain
|
||||
// license server transactions. It is also used for logging
|
||||
// certain strings.
|
||||
std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
|
||||
if (b64_input.empty()) {
|
||||
return std::vector<uint8_t>();
|
||||
}
|
||||
int in_size = b64_input.size();
|
||||
// out_size should be an integral number of bytes, assuming correct encode
|
||||
int out_size = ((in_size * 3) / 4);
|
||||
std::vector<uint8_t> bin_output(out_size, '\0');
|
||||
int in_index = 0;
|
||||
int out_index = 0;
|
||||
unsigned long buffer;
|
||||
unsigned char out_cc;
|
||||
static const unsigned long kOutMask = 0xff;
|
||||
|
||||
while (in_index < in_size) {
|
||||
// up to 4 bytes (0..63) in
|
||||
buffer = B64ToBin(b64_input.at(in_index));
|
||||
buffer <<= 6;
|
||||
buffer |= (++in_index >= in_size) ? 0 : B64ToBin(b64_input.at(in_index));
|
||||
buffer <<= 6;
|
||||
buffer |= (++in_index >= in_size) ? 0 : B64ToBin(b64_input.at(in_index));
|
||||
buffer <<= 6;
|
||||
buffer |= (++in_index >= in_size) ? 0 : B64ToBin(b64_input.at(in_index));
|
||||
++in_index;
|
||||
// up to 3 bytes (0..255) out
|
||||
out_cc = (buffer >> 16) & kOutMask;
|
||||
bin_output.at(out_index) = out_cc;
|
||||
if (++out_index >= out_size)
|
||||
break;
|
||||
out_cc = (buffer >> 8) & kOutMask;
|
||||
bin_output.at(out_index) = out_cc;
|
||||
if (++out_index >= out_size)
|
||||
break;
|
||||
out_cc = buffer & kOutMask;
|
||||
bin_output.at(out_index) = out_cc;
|
||||
++out_index;
|
||||
}
|
||||
return bin_output;
|
||||
}
|
||||
|
||||
std::string HexEncode(const uint8_t* in_buffer, unsigned int size) {
|
||||
static const char kHexChars[] = "0123456789ABCDEF";
|
||||
|
||||
// Each input byte creates two output hex characters.
|
||||
std::string out_buffer(size * 2, '\0');
|
||||
|
||||
for (unsigned int i = 0; i < size; ++i) {
|
||||
char byte = in_buffer[i];
|
||||
out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];
|
||||
out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf];
|
||||
}
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
std::string IntToString(int value) {
|
||||
// log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
|
||||
// So round up to allocate 3 output characters per byte, plus 1 for '-'.
|
||||
const int kOutputBufSize = 3 * sizeof(int) + 1;
|
||||
char buffer[kOutputBufSize];
|
||||
memset(buffer, 0, kOutputBufSize);
|
||||
snprintf(buffer, kOutputBufSize, "%d", value);
|
||||
|
||||
std::string out_string(buffer, sizeof(buffer));
|
||||
return out_string;
|
||||
}
|
||||
|
||||
std::string UintToString(unsigned int value) {
|
||||
// log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
|
||||
// So round up to allocate 3 output characters per byte.
|
||||
const int kOutputBufSize = 3 * sizeof(unsigned int);
|
||||
char buffer[kOutputBufSize];
|
||||
memset(buffer, 0, kOutputBufSize);
|
||||
snprintf(buffer, kOutputBufSize, "%u", value);
|
||||
|
||||
std::string out_string(buffer, sizeof(buffer));
|
||||
return out_string;
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
245
libwvdrmengine/cdm/core/test/cdm_engine_test.cpp
Normal file
245
libwvdrmengine/cdm/core/test/cdm_engine_test.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "config_test_env.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "license_request.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
#include "url_request.h"
|
||||
|
||||
namespace {
|
||||
// Default license server, can be configured using --server command line option
|
||||
// Default key id (pssh), can be configured using --keyid command line option
|
||||
std::string g_client_auth;
|
||||
wvcdm::KeyId g_key_id;
|
||||
wvcdm::CdmKeySystem g_key_system;
|
||||
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
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class WvCdmEngineTest : public testing::Test {
|
||||
public:
|
||||
WvCdmEngineTest() {}
|
||||
~WvCdmEngineTest() {}
|
||||
|
||||
protected:
|
||||
void GenerateKeyRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
wvcdm::CdmNameValueMap app_parameters;
|
||||
EXPECT_EQ(cdm_engine_.GenerateKeyRequest(session_id_,
|
||||
true, // is_key_system_present
|
||||
key_system,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_), wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
EXPECT_EQ(cdm_engine_.GenerateRenewalRequest(session_id_,
|
||||
true, // is_key_system_init_data_present,
|
||||
key_system,
|
||||
init_data,
|
||||
&key_msg_),
|
||||
wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
// posts a request and extracts the drm message from the response
|
||||
std::string GetKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
int expected_response) {
|
||||
UrlRequest url_request(server_url + client_auth, g_port);
|
||||
|
||||
if (!url_request.is_connected()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
url_request.PostRequest(key_msg_);
|
||||
std::string response;
|
||||
int resp_bytes = url_request.GetResponse(response);
|
||||
LOGD("response:\r\n%s", response.c_str());
|
||||
LOGD("end %d bytes response dump", resp_bytes);
|
||||
|
||||
// Youtube server returns 400 for invalid message while play server returns
|
||||
// 500, so just test inequity here for invalid message
|
||||
int status_code = url_request.GetStatusCode(response);
|
||||
if (expected_response == 200) {
|
||||
EXPECT_EQ(200, status_code);
|
||||
} else {
|
||||
EXPECT_NE(200, status_code);
|
||||
}
|
||||
|
||||
std::string drm_msg;
|
||||
if (200 == status_code) {
|
||||
LicenseRequest lic_request;
|
||||
lic_request.GetDrmMessage(response, drm_msg);
|
||||
LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(),
|
||||
HexEncode(reinterpret_cast<const uint8_t*>(drm_msg.data()),
|
||||
drm_msg.size()).c_str());
|
||||
}
|
||||
return drm_msg;
|
||||
}
|
||||
|
||||
void VerifyKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
std::string& init_data,
|
||||
bool is_renewal) {
|
||||
std::string resp = GetKeyRequestResponse(server_url,
|
||||
client_auth,
|
||||
200);
|
||||
if (is_renewal) {
|
||||
EXPECT_EQ(cdm_engine_.RenewKey(session_id_,
|
||||
true, // is_key_system_init_data_present
|
||||
g_key_system,
|
||||
init_data,
|
||||
resp), wvcdm::KEY_ADDED);
|
||||
}
|
||||
else {
|
||||
EXPECT_EQ(cdm_engine_.AddKey(session_id_,
|
||||
true, // is_key_system_init_data_present
|
||||
g_key_system,
|
||||
init_data,
|
||||
resp), wvcdm::KEY_ADDED);
|
||||
}
|
||||
}
|
||||
|
||||
wvcdm::CdmEngine cdm_engine_;
|
||||
std::string key_msg_;
|
||||
std::string session_id_;
|
||||
};
|
||||
|
||||
TEST_F(WvCdmEngineTest, BaseMessageTest) {
|
||||
cdm_engine_.OpenSession(g_key_system, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth, 200);
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, WrongMessageTest) {
|
||||
cdm_engine_.OpenSession(g_key_system, &session_id_);
|
||||
|
||||
std::string wrong_message = wvcdm::a2bs_hex(g_wrong_key_id);
|
||||
GenerateKeyRequest(g_key_system, wrong_message);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth, 500);
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, NormalDecryption) {
|
||||
cdm_engine_.OpenSession(g_key_system, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmEngineTest, LicenseRenewal) {
|
||||
cdm_engine_.OpenSession(g_key_system, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, false);
|
||||
|
||||
GenerateRenewalRequest(g_key_system, g_key_id);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, true);
|
||||
cdm_engine_.CloseSession(session_id_);
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
wvcdm::ConfigTestEnv config;
|
||||
g_client_auth.assign(config.client_auth());
|
||||
g_key_system.assign(config.key_system());
|
||||
g_wrong_key_id.assign(config.wrong_key_id());
|
||||
|
||||
// The following variables are configurable through command line options.
|
||||
g_license_server.assign(config.license_server());
|
||||
g_key_id.assign(config.key_id());
|
||||
g_port.assign(config.port());
|
||||
std::string license_server(g_license_server);
|
||||
|
||||
int show_usage = 0;
|
||||
static const struct option long_options[] = {
|
||||
{ "use_full_path", no_argument, &g_use_full_path, 0 },
|
||||
{ "keyid", required_argument, NULL, 'k' },
|
||||
{ "port", required_argument, NULL, 'p' },
|
||||
{ "server", required_argument, NULL, 's' },
|
||||
{ NULL, 0, NULL, '\0' }
|
||||
};
|
||||
|
||||
int option_index = 0;
|
||||
int opt = 0;
|
||||
while ((opt = getopt_long(argc, argv, "k:p:s:u", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'k': {
|
||||
g_key_id.clear();
|
||||
g_key_id.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
g_port.clear();
|
||||
g_port.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
g_license_server.clear();
|
||||
g_license_server.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
g_use_full_path = 1;
|
||||
break;
|
||||
}
|
||||
case '?': {
|
||||
show_usage = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (show_usage) {
|
||||
std::cout << std::endl;
|
||||
std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl;
|
||||
std::cout << " enclose multiple arguments in '' when using adb shell" << std::endl;
|
||||
std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --port=<connection port>";
|
||||
std::cout << "specifies the port number, in decimal format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << g_port << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout << "configure the license server url, please include http[s] in the url" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << license_server << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --keyid=<key_id>";
|
||||
std::cout << "configure the key id or pssh, in hex format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " default keyid:";
|
||||
std::cout << g_key_id << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --use_full_path";
|
||||
std::cout << "specify server url is not a proxy server" << std::endl;
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << g_license_server << std::endl;
|
||||
std::cout << "Port: " << g_port << std::endl;
|
||||
std::cout << "KeyID: " << g_key_id << std::endl << std::endl;
|
||||
|
||||
g_key_id = wvcdm::a2bs_hex(g_key_id);
|
||||
config.set_license_server(g_license_server);
|
||||
config.set_port(g_port);
|
||||
config.set_key_id(g_key_id);
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
77
libwvdrmengine/cdm/core/test/config_test_env.cpp
Normal file
77
libwvdrmengine/cdm/core/test/config_test_env.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "config_test_env.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// choice of YT, GP, GP_Huahui, SDK_Hali
|
||||
#define USE_SERVER_YT 1
|
||||
#define USE_SERVER_GP 2
|
||||
#define USE_SERVER_SDK_Hali 3
|
||||
|
||||
// select which server to use for testing
|
||||
#define USE_SERVER USE_SERVER_GP
|
||||
|
||||
#if (USE_SERVER == USE_SERVER_SDK_Hali)
|
||||
|
||||
static const std::string kLicenseServer =
|
||||
"http://hamid.kir.corp.google.com:8888/drm";
|
||||
static const std::string kClientAuth = "";
|
||||
static const std::string kKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0801121030313233343536373839616263646566"; // key - for gHali
|
||||
|
||||
#elif (USE_SERVER == USE_SERVER_YT)
|
||||
|
||||
static const std::string kLicenseServer =
|
||||
"https://www.youtube.com/api/drm/widevine?video_id=03681262dc412c06&source=YOUTUBE";
|
||||
static const std::string kClientAuth = "";
|
||||
static const std::string kKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0801121093789920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
|
||||
#elif (USE_SERVER == USE_SERVER_GP)
|
||||
|
||||
static const std::string kLicenseServer =
|
||||
"https://jmt17.google.com/video-dev/license/GetCencLicense";
|
||||
|
||||
// NOTE: Append a userdata attribute to place a unique marker that the
|
||||
// server team can use to track down specific requests during debugging
|
||||
// e.g., "<existing-client-auth-string>&userdata=<your-ldap>.<your-tag>"
|
||||
// "<existing-client-auth-string>&userdata=jbmr2.dev"
|
||||
static const std::string kClientAuth =
|
||||
"?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine";
|
||||
|
||||
static const std::string kKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
|
||||
"08011210e02562e04cd55351b14b3d748d36ed8e"; // pssh data
|
||||
|
||||
#else
|
||||
#error "Must define USE_SERVER"
|
||||
#endif
|
||||
|
||||
//static const char kWidevineKeySystem[] = "com.widevine.alpha";
|
||||
|
||||
// An invalid key id, expected to fail
|
||||
static const std::string kWrongKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0901121094889920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
ConfigTestEnv::ConfigTestEnv()
|
||||
: client_auth_(kClientAuth),
|
||||
key_id_(kKeyId),
|
||||
key_system_("com.widevine.alpha"),
|
||||
license_server_(kLicenseServer),
|
||||
port_("80"),
|
||||
wrong_key_id_(kWrongKeyId) {
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
46
libwvdrmengine/cdm/core/test/config_test_env.h
Normal file
46
libwvdrmengine/cdm/core/test/config_test_env.h
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
#define CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Configures default test environment.
|
||||
class ConfigTestEnv {
|
||||
public:
|
||||
ConfigTestEnv();
|
||||
~ConfigTestEnv() {};
|
||||
|
||||
const std::string& client_auth() const { return client_auth_; }
|
||||
const KeyId& key_id() const { return key_id_; }
|
||||
const CdmKeySystem& key_system() const { return key_system_; }
|
||||
const std::string& license_server() const { return license_server_; }
|
||||
const std::string& port() const { return port_; }
|
||||
const KeyId& wrong_key_id() const { return wrong_key_id_; }
|
||||
|
||||
void set_key_id(KeyId& key_id) { key_id_.assign(key_id); }
|
||||
void set_key_system(CdmKeySystem& key_system) {
|
||||
key_system_.assign(key_system);
|
||||
}
|
||||
void set_license_server(std::string& license_server) {
|
||||
license_server_.assign(license_server);
|
||||
}
|
||||
void set_port(std::string& port) { port_.assign(port); }
|
||||
|
||||
private:
|
||||
std::string client_auth_;
|
||||
KeyId key_id_;
|
||||
CdmKeySystem key_system_;
|
||||
std::string license_server_;
|
||||
std::string port_;
|
||||
KeyId wrong_key_id_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_CONFIG_TEST_ENV_H_
|
||||
192
libwvdrmengine/cdm/core/test/http_socket.cpp
Normal file
192
libwvdrmengine/cdm/core/test/http_socket.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "http_socket.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include "log.h"
|
||||
namespace wvcdm {
|
||||
|
||||
HttpSocket::HttpSocket() : socket_fd_(-1), timeout_enabled_(false) {}
|
||||
|
||||
HttpSocket::~HttpSocket()
|
||||
{
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
void HttpSocket::CloseSocket()
|
||||
{
|
||||
if (socket_fd_ != -1) {
|
||||
close(socket_fd_);
|
||||
socket_fd_ = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the domain name and resource path from the input url parameter.
|
||||
// The results are put in domain_name and resource_path respectively.
|
||||
// The format of the url can begin with <protocol/scheme>:://domain server/...
|
||||
// or dowmain server/resource_path
|
||||
void HttpSocket::GetDomainNameAndPathFromUrl(const std::string& url,
|
||||
std::string& domain_name,
|
||||
std::string& resource_path) {
|
||||
domain_name.clear();
|
||||
resource_path.clear();
|
||||
|
||||
size_t start = url.find("//");
|
||||
size_t end = url.npos;
|
||||
if (start != url.npos) {
|
||||
end = url.find("/", start + 2);
|
||||
if (end != url.npos) {
|
||||
domain_name.assign(url, start + 2, end - start - 2);
|
||||
resource_path.assign(url, end + 1, url.npos);
|
||||
} else {
|
||||
domain_name.assign(url, start + 2, url.npos);
|
||||
}
|
||||
} else {
|
||||
// no scheme/protocol in url
|
||||
end = url.find("/");
|
||||
if (end != url.npos) {
|
||||
domain_name.assign(url, 0, end);
|
||||
resource_path.assign(url, end + 1, url.npos);
|
||||
} else {
|
||||
domain_name.assign(url);
|
||||
}
|
||||
}
|
||||
// strips port number if present, e.g. https://www.domain.com:8888/...
|
||||
end = domain_name.find(":");
|
||||
if (end != domain_name.npos) {
|
||||
domain_name.erase(end);
|
||||
}
|
||||
}
|
||||
|
||||
bool HttpSocket::Connect(const char* url, const std::string& port, bool enable_timeout)
|
||||
{
|
||||
GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_);
|
||||
|
||||
socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_fd_ < 0) {
|
||||
LOGE("cannot open socket %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
|
||||
CloseSocket();
|
||||
LOGE("setsockopt error %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
struct addrinfo* addr_info = NULL;
|
||||
bool status = true;
|
||||
int ret = getaddrinfo(domain_name_.c_str(), port.c_str(), &hints, &addr_info);
|
||||
if (ret != 0) {
|
||||
CloseSocket();
|
||||
LOGE("getaddrinfo failed with %d", ret);
|
||||
status = false;
|
||||
} else {
|
||||
if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) {
|
||||
CloseSocket();
|
||||
LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(), errno);
|
||||
status = false;
|
||||
}
|
||||
}
|
||||
timeout_enabled_ = enable_timeout;
|
||||
if (addr_info != NULL) {
|
||||
freeaddrinfo(addr_info);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
int HttpSocket::Read(char* data, int len) {
|
||||
return(Read(data, len, 0));
|
||||
}
|
||||
|
||||
// makes non-blocking mode only during read, it supports timeout for read
|
||||
// returns -1 for error, number of bytes read for success
|
||||
int HttpSocket::Read(char* data, int len, int timeout_in_ms)
|
||||
{
|
||||
bool use_timeout = (timeout_enabled_ && (timeout_in_ms > 0));
|
||||
int original_flags = 0;
|
||||
if (use_timeout) {
|
||||
original_flags = fcntl(socket_fd_, F_GETFL, 0);
|
||||
if (original_flags == -1) {
|
||||
LOGE("fcntl error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
|
||||
LOGE("fcntl error %d", errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int total_read = 0;
|
||||
int read = 0;
|
||||
int to_read = len;
|
||||
while (to_read > 0) {
|
||||
if (use_timeout) {
|
||||
fd_set read_fds;
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_in_ms / 1000;
|
||||
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(socket_fd_, &read_fds);
|
||||
if (select(socket_fd_ + 1, &read_fds, NULL, NULL, &tv) == -1) {
|
||||
LOGE("select failed");
|
||||
break;
|
||||
}
|
||||
if (!FD_ISSET(socket_fd_, &read_fds)) {
|
||||
LOGE("socket read timeout");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
read = recv(socket_fd_, data, to_read, 0);
|
||||
if (read > 0) {
|
||||
to_read -= read;
|
||||
data += read;
|
||||
total_read += read;
|
||||
} else if (read == 0) {
|
||||
// in blocking mode, zero read mean's peer closed.
|
||||
// in non-blocking mode, select said that there is data. so it should not happen
|
||||
break;
|
||||
} else {
|
||||
LOGE("recv returned %d, error = %d", read, errno);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_timeout) {
|
||||
fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
|
||||
int HttpSocket::Write(const char* data, int len)
|
||||
{
|
||||
int total_sent = 0;
|
||||
int sent = 0;
|
||||
int to_send = len;
|
||||
while (to_send > 0) {
|
||||
sent = send(socket_fd_, data, to_send, 0);
|
||||
if (sent > 0) {
|
||||
to_send -= sent;
|
||||
data += sent;
|
||||
total_sent += sent;
|
||||
} else if (sent == 0) {
|
||||
usleep(10); // retry later
|
||||
} else {
|
||||
LOGE("send returned error %d", errno);
|
||||
}
|
||||
}
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
39
libwvdrmengine/cdm/core/test/http_socket.h
Normal file
39
libwvdrmengine/cdm/core/test/http_socket.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_HTTP_SOCKET_H_
|
||||
#define CDM_TEST_HTTP_SOCKET_H_
|
||||
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides basic Linux based TCP socket interface.
|
||||
class HttpSocket {
|
||||
public:
|
||||
HttpSocket();
|
||||
~HttpSocket();
|
||||
|
||||
void CloseSocket();
|
||||
bool Connect(const char* url, const std::string& port, bool enable_timeout);
|
||||
void GetDomainNameAndPathFromUrl(const std::string& url,
|
||||
std::string& domain_name,
|
||||
std::string& resource_path);
|
||||
const std::string& domain_name() const { return domain_name_; };
|
||||
const std::string& resource_path() const { return resource_path_; };
|
||||
int Read(char* data, int len);
|
||||
int Read(char* data, int len, int timeout_in_ms);
|
||||
int Write(const char* data, int len);
|
||||
|
||||
private:
|
||||
std::string domain_name_;
|
||||
std::string resource_path_;
|
||||
int socket_fd_;
|
||||
bool timeout_enabled_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_HTTP_SOCKET_H_
|
||||
194
libwvdrmengine/cdm/core/test/http_socket_test.cpp
Normal file
194
libwvdrmengine/cdm/core/test/http_socket_test.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <errno.h>
|
||||
#include "gtest/gtest.h"
|
||||
#include "http_socket.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
std::string gTestServer("https://www.google.com");
|
||||
std::string gTestData("Hello");
|
||||
const int kHttpBufferSize = 4096;
|
||||
char gBuffer[kHttpBufferSize];
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class HttpSocketTest : public testing::Test {
|
||||
public:
|
||||
HttpSocketTest() {}
|
||||
~HttpSocketTest() { socket_.CloseSocket(); }
|
||||
|
||||
protected:
|
||||
bool Connect(const std::string& server_url) {
|
||||
|
||||
if (socket_.Connect(server_url.c_str(), "80", true)) {
|
||||
LOGD("connected to %s", socket_.domain_name().c_str());
|
||||
} else {
|
||||
LOGE("failed to connect to %s", socket_.domain_name().c_str());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PostRequest(const std::string& data) {
|
||||
std::string request("POST ");
|
||||
if (socket_.resource_path().empty())
|
||||
request.append(socket_.domain_name());
|
||||
else
|
||||
request.append(socket_.resource_path());
|
||||
request.append(" HTTP/1.1\r\n");
|
||||
request.append("Host: ");
|
||||
request.append(socket_.domain_name());
|
||||
request.append("\r\nUser-Agent: httpSocketTest/1.0\r\n");
|
||||
request.append("Content-Length: ");
|
||||
memset(gBuffer, 0, kHttpBufferSize);
|
||||
snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast<int>(data.size()));
|
||||
request.append(gBuffer);
|
||||
request.append("Content-Type: multipart/form-data\r\n");
|
||||
|
||||
// newline terminates header
|
||||
request.append("\r\n");
|
||||
|
||||
// append data
|
||||
request.append(data);
|
||||
socket_.Write(request.c_str(), request.size());
|
||||
LOGD("request: %s", request.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetResponse() {
|
||||
int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000);
|
||||
if (bytes < 0) {
|
||||
LOGE("read error = ", errno);
|
||||
return false;
|
||||
} else {
|
||||
LOGD("read %d bytes", bytes);
|
||||
std::string response(gBuffer, bytes);
|
||||
LOGD("response: %s", response.c_str());
|
||||
LOGD("end response dump");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
HttpSocket socket_;
|
||||
std::string domain_name_;
|
||||
std::string resource_path_;
|
||||
};
|
||||
|
||||
TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest)
|
||||
{
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/p/googletest/wiki/Primer",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/p/googletest/wiki/Primer/",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer/", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com/",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://code.google.com",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com/p/googletest/wiki/Primer",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("p/googletest/wiki/Primer", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("code.google.com/",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("code.google.com", domain_name_.c_str());
|
||||
EXPECT_STREQ("", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_TRUE(domain_name_.empty());
|
||||
EXPECT_TRUE(resource_path_.empty());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://10.21.200.68:8888/drm",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("10.21.200.68", domain_name_.c_str());
|
||||
EXPECT_STREQ("drm", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://10.21.200.68:8888",
|
||||
domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("10.21.200.68", domain_name_.c_str());
|
||||
EXPECT_TRUE(resource_path_.empty());
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, ConnectTest)
|
||||
{
|
||||
EXPECT_TRUE(Connect(gTestServer));
|
||||
socket_.CloseSocket();
|
||||
EXPECT_FALSE(Connect("ww.g.c"));
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, RoundTripTest)
|
||||
{
|
||||
ASSERT_TRUE(Connect(gTestServer));
|
||||
EXPECT_TRUE(PostRequest(gTestData));
|
||||
GetResponse();
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
std::string temp;
|
||||
std::string test_server(gTestServer);
|
||||
std::string test_data(gTestData);
|
||||
for (int i=1; i<argc; i++) {
|
||||
temp.assign(argv[i]);
|
||||
if (temp.find("--server=") == 0) {
|
||||
gTestServer.assign(temp.substr(strlen("--server=")));
|
||||
} else if (temp.find("--data=") == 0) {
|
||||
gTestData.assign(temp.substr(strlen("--data=")));
|
||||
}
|
||||
else {
|
||||
std::cout << "error: unknown option '" << argv[i] << "'" << std::endl;
|
||||
std::cout << "usage: http_socket_test [options]" << std::endl << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout << "configure the test server url, please include http[s] in the url" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << test_server << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --data=<data>";
|
||||
std::cout << "configure data to send, in ascii string format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << test_data << std::endl << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << gTestServer << std::endl;
|
||||
std::cout << "Data: " << gTestData << std::endl;
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
82
libwvdrmengine/cdm/core/test/license_request.cpp
Normal file
82
libwvdrmengine/cdm/core/test/license_request.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "license_request.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static const std::string kTwoBlankLines("\r\n\r\n");
|
||||
|
||||
size_t LicenseRequest::FindHeaderEndPosition(
|
||||
const std::string& response) const {
|
||||
return(response.find(kTwoBlankLines));
|
||||
}
|
||||
|
||||
// Returns drm message in drm_msg.
|
||||
// The drm message is at the end of the response message.
|
||||
void LicenseRequest::GetDrmMessage(const std::string& response,
|
||||
std::string& drm_msg) {
|
||||
if (response.empty()) {
|
||||
drm_msg.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Extracts DRM message.
|
||||
// Content-Length = GLS line + Header(s) + empty line + drm message;
|
||||
// we use the empty line to locate the drm message, and compute
|
||||
// the drm message length as below instead of using Content-Length
|
||||
size_t header_end_pos = FindHeaderEndPosition(response);
|
||||
if (header_end_pos != std::string::npos) {
|
||||
header_end_pos += kTwoBlankLines.size(); // points to response body
|
||||
|
||||
drm_msg.clear();
|
||||
size_t drm_msg_pos = response.find(kTwoBlankLines, header_end_pos);
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg_pos += kTwoBlankLines.size(); // points to drm message
|
||||
} else {
|
||||
// For backward compatibility, no blank line after error code
|
||||
drm_msg_pos = response.find("\r\n", header_end_pos);
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg_pos += 2; // points to drm message
|
||||
}
|
||||
}
|
||||
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg = response.substr(drm_msg_pos);
|
||||
} else {
|
||||
LOGE("drm msg not found");
|
||||
}
|
||||
} else {
|
||||
LOGE("response body not found");
|
||||
}
|
||||
}
|
||||
|
||||
// Returns heartbeat url in heartbeat_url.
|
||||
// The heartbeat url is stored as meta data in the response message.
|
||||
void LicenseRequest::GetHeartbeatUrl(const std::string& response,
|
||||
std::string& heartbeat_url) {
|
||||
if (response.empty()) {
|
||||
heartbeat_url.clear(); // TODO: assign default heartbeat url
|
||||
return;
|
||||
}
|
||||
|
||||
size_t header_end_pos = FindHeaderEndPosition(response);
|
||||
if (header_end_pos != std::string::npos) {
|
||||
header_end_pos += kTwoBlankLines.size(); // points to response body
|
||||
|
||||
heartbeat_url.clear();
|
||||
size_t heartbeat_url_pos = response.find("Heartbeat-Url: ",
|
||||
header_end_pos);
|
||||
if (heartbeat_url_pos != std::string::npos) {
|
||||
heartbeat_url_pos += sizeof("Heartbeat-Url: ");
|
||||
heartbeat_url.assign(response.substr(heartbeat_url_pos));
|
||||
} else {
|
||||
LOGE("heartbeat url not found");
|
||||
}
|
||||
} else {
|
||||
LOGE("response body not found");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} // namespace wvcdm
|
||||
30
libwvdrmengine/cdm/core/test/license_request.h
Normal file
30
libwvdrmengine/cdm/core/test/license_request.h
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_LICENSE_REQUEST_H_
|
||||
#define CDM_TEST_LICENSE_REQUEST_H_
|
||||
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Parses response from a license request.
|
||||
// This class assumes a particular response format defined by
|
||||
// Google license servers.
|
||||
class LicenseRequest {
|
||||
public:
|
||||
LicenseRequest() {};
|
||||
~LicenseRequest() {};
|
||||
|
||||
void GetDrmMessage(const std::string& response, std::string& drm_msg);
|
||||
void GetHeartbeatUrl(const std::string& response, std::string& heartbeat_url);
|
||||
|
||||
private:
|
||||
size_t FindHeaderEndPosition(const std::string& response) const;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(LicenseRequest);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_LICENSE_REQUEST_H_
|
||||
100
libwvdrmengine/cdm/core/test/license_unittest.cpp
Normal file
100
libwvdrmengine/cdm/core/test/license_unittest.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "crypto_engine.h"
|
||||
#include "crypto_session.h"
|
||||
#include "license.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// The test data is based on key box Eureka-Dev-G1-0001520
|
||||
// This unit test should run on oemcrypto mock with the same key box
|
||||
static const char* kInitData = "0801121093789920E8D6520098577DF8F2DD5546";
|
||||
static const char* kSignedRequest =
|
||||
"080112790A4C0800124800000002000001241F344DB9DFF087F01D917910F39B"
|
||||
"60DC7797CD97789EE82516DC07A478CB70B8C08C299293150AA8E01D2DC9808C"
|
||||
"98DAA16E40A0E55DFE3618C7584DD3C7BE4212250A230A140801121093789920"
|
||||
"E8D6520098577DF8F2DD554610011A09393837363534333231180120001A20FA"
|
||||
"E2DDCD7F1ACA4B728EC957FEE802F8A5541557ACA784EE0D05BFCC0E65FEA1";
|
||||
static const char* kValidResponse =
|
||||
"080212D9020A190A093938373635343332311208C434AB9240A9EF2420012800"
|
||||
"120E0801180120809A9E0128809A9E011A461210B72EEBF582B04BDB15C2E0E3"
|
||||
"20B21C351A30E51FC1D27F70DB8E0DDF8C051BD6E251A44599DBCE4E1BE663FD"
|
||||
"3AFAB191A7DD5736841FB04CE558E7F17BD9812A2DBA20011A6E0A1093789920"
|
||||
"E8D6520098577DF8F2DD55461210367E8714B6F10087AFDE542EDC5C91541A20"
|
||||
"ED51D4E84D81C8CBD8E2046EE079F8A2016268A2F192B902FDA241FEEB10C014"
|
||||
"200242240A109209D46191B8752147C9F6A1CE2BEE6E12107910F39B60DC7797"
|
||||
"CD97789EE82516DC1A6E0A107B1328EB61B554E293F75B1E3E94CC3B1210676F"
|
||||
"69BBDA35EE972B77BC1328A087391A20D2B9FA92B164F5F6362CAD9200A11661"
|
||||
"B8F71E9CE671A3A252D34586526B68FA200242240A109D7B13420FD6217666CC"
|
||||
"CD43860FAA3A1210DBCE4E1BE663FD3AFAB191A7DD57368420E9FDCE86051A20"
|
||||
"C6279E32FD2CB9067229E87AFF4B2DE14A077CDF8F061DAEE2CC2D1BCDEF62D0";
|
||||
static const char* kInvalidResponse =
|
||||
"0802128D020A190A093938373635343332311208BA68C949396C438C20012800"
|
||||
"120E0801180120809A9E0128809A9E011A4612105021EB9AEDC1F73E96DE7DCC"
|
||||
"6D7D72401A300A82E118C0BF0DB230FCADE3F49A9777DDD392322240FEF32C97"
|
||||
"F85428E2F6CCFA638B5481464ADBCF199CEC2FCF3AFB20011A480A1093789920"
|
||||
"E8D6520098577DF8F2DD55461210EE52C59B99050A36E10569AFB34D1DA41A20"
|
||||
"C61FCB8019AC9ADE99FF8FCA99ED35E2331B6488A35102F9379AA42C87A22DC7"
|
||||
"20021A480A107B1328EB61B554E293F75B1E3E94CC3B12101BBF5286B859E349"
|
||||
"2E4A47A24C06AC1B1A2061F21836A04E558BEE0244EF41C165F60CF23C580275"
|
||||
"3175D48BAF1C6CA5759F200220A2BCCA86051A203FD4671075D9DEC6486A9317"
|
||||
"70669993306831EDD57D77F34EFEB467470BA364";
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class LicenseTest : public ::testing::Test {
|
||||
protected:
|
||||
virtual void SetUp() {
|
||||
CryptoEngine* crypto_engine = CryptoEngine::GetInstance();
|
||||
EXPECT_TRUE(crypto_engine != NULL);
|
||||
session_ = crypto_engine->CreateSession("Dummy");
|
||||
EXPECT_TRUE(session_ != NULL);
|
||||
|
||||
std::string token;
|
||||
EXPECT_TRUE(crypto_engine->GetToken(&token));
|
||||
|
||||
EXPECT_TRUE(session_->IsOpen());
|
||||
EXPECT_TRUE(license_.Init(token, session_));
|
||||
}
|
||||
|
||||
virtual void TearDown() {
|
||||
session_->Close();
|
||||
delete session_;
|
||||
}
|
||||
|
||||
CryptoSession* session_;
|
||||
CdmLicense license_;
|
||||
};
|
||||
|
||||
TEST(LicenseTestSession, InitNullSession) {
|
||||
CdmLicense license;
|
||||
EXPECT_FALSE(license.Init("Dummy", NULL));
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, PrepareKeyRequest) {
|
||||
std::string signed_request;
|
||||
license_.PrepareKeyRequest(a2bs_hex(kInitData), &signed_request);
|
||||
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, HandleKeyResponseValid) {
|
||||
std::string signed_request;
|
||||
license_.PrepareKeyRequest(a2bs_hex(kInitData), &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);
|
||||
EXPECT_EQ(signed_request, a2bs_hex(kSignedRequest));
|
||||
EXPECT_FALSE(license_.HandleKeyResponse(a2bs_hex(kInvalidResponse)));
|
||||
}
|
||||
|
||||
// TODO(kqyang): add unit test cases for PrepareKeyRenewalRequest
|
||||
// and HandleRenewalKeyResponse
|
||||
|
||||
}
|
||||
106
libwvdrmengine/cdm/core/test/url_request.cpp
Normal file
106
libwvdrmengine/cdm/core/test/url_request.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "url_request.h"
|
||||
|
||||
#include "http_socket.h"
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
UrlRequest::UrlRequest(const std::string& url, const std::string& port)
|
||||
: is_connected_(false),
|
||||
port_("80"),
|
||||
request_(""),
|
||||
server_url_(url)
|
||||
{
|
||||
if (!port.empty()) {
|
||||
port_.assign(port);
|
||||
}
|
||||
if (socket_.Connect((server_url_).c_str(), port_, true)) {
|
||||
LOGD("connected to %s", socket_.domain_name().c_str());
|
||||
is_connected_ = true;
|
||||
} else {
|
||||
LOGE("failed to connect to %s, port=%s",
|
||||
socket_.domain_name().c_str(), port.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
UrlRequest::~UrlRequest()
|
||||
{
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
void UrlRequest::AppendChunkToUpload(const std::string& data) {
|
||||
// format of chunk:
|
||||
// size of chunk in hex\r\n
|
||||
// data\r\n
|
||||
// . . .
|
||||
// 0\r\n
|
||||
|
||||
// buffer to store length of chunk
|
||||
memset(buffer_, 0, kHttpBufferSize);
|
||||
snprintf(buffer_, kHttpBufferSize, "%x\r\n", data.size());
|
||||
request_.append(buffer_); // appends size of chunk
|
||||
LOGD("...\r\n%s", request_.c_str());
|
||||
request_.append(data);
|
||||
request_.append("\r\n"); // marks end of data
|
||||
}
|
||||
|
||||
int UrlRequest::GetResponse(std::string& response) {
|
||||
response.clear();
|
||||
|
||||
const int kTimeoutInMs = 1000;
|
||||
int bytes = 0;
|
||||
int total_bytes = 0;
|
||||
do {
|
||||
memset(buffer_, 0, kHttpBufferSize);
|
||||
bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs);
|
||||
if (bytes > 0) {
|
||||
response.append(buffer_, bytes);
|
||||
total_bytes += bytes;
|
||||
} else {
|
||||
if (bytes < 0) LOGE("read error = ", errno);
|
||||
// bytes == 0 indicates nothing to read
|
||||
}
|
||||
} while (bytes > 0);
|
||||
return total_bytes;
|
||||
}
|
||||
|
||||
int UrlRequest::GetStatusCode(const std::string& response) {
|
||||
const std::string kHttpVersion("HTTP/1.1");
|
||||
|
||||
int status_code = -1;
|
||||
size_t pos = response.find(kHttpVersion);
|
||||
if (pos != std::string::npos) {
|
||||
pos += kHttpVersion.size();
|
||||
sscanf(response.substr(pos).c_str(), "%d", &status_code);
|
||||
}
|
||||
return status_code;
|
||||
}
|
||||
|
||||
bool UrlRequest::PostRequest(const std::string& data) {
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
request_.append(" HTTP/1.1\r\n");
|
||||
request_.append("Host: ");
|
||||
request_.append(socket_.domain_name());
|
||||
request_.append("\r\nConnection: Keep-Alive\r\n");
|
||||
request_.append("Transfer-Encoding: chunked\r\n");
|
||||
request_.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||
request_.append("Accept-Encoding: gzip,deflate\r\n");
|
||||
request_.append("Accept-Language: en-us,fr\r\n");
|
||||
request_.append("Accept-Charset: iso-8859-1,*,utf-8\r\n");
|
||||
request_.append("\r\n"); // empty line to terminate header
|
||||
|
||||
// calls AppendChunkToUpload repeatedly for multiple chunks
|
||||
AppendChunkToUpload(data);
|
||||
|
||||
// terminates last chunk with 0\r\n, then ends header with an empty line
|
||||
request_.append("0\r\n\r\n");
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace wvcdm
|
||||
39
libwvdrmengine/cdm/core/test/url_request.h
Normal file
39
libwvdrmengine/cdm/core/test/url_request.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_TEST_URL_REQUEST_H_
|
||||
#define CDM_TEST_URL_REQUEST_H_
|
||||
|
||||
#include <string>
|
||||
#include "http_socket.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides simple HTTP request and response service.
|
||||
// Only POST request method is implemented.
|
||||
class UrlRequest {
|
||||
public:
|
||||
UrlRequest(const std::string& url, const std::string& port);
|
||||
~UrlRequest();
|
||||
|
||||
void AppendChunkToUpload(const std::string& data);
|
||||
int GetResponse(std::string& response);
|
||||
int GetStatusCode(const std::string& response);
|
||||
bool is_connected() const { return is_connected_; }
|
||||
bool PostRequest(const std::string& data);
|
||||
|
||||
private:
|
||||
static const unsigned int kHttpBufferSize = 4096;
|
||||
char buffer_[kHttpBufferSize];
|
||||
bool is_connected_;
|
||||
std::string port_;
|
||||
std::string request_;
|
||||
HttpSocket socket_;
|
||||
std::string server_url_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // CDM_TEST_URL_REQUEST_H_
|
||||
861
libwvdrmengine/cdm/core/test/wvcdm_decryptor_unittest.cpp
Normal file
861
libwvdrmengine/cdm/core/test/wvcdm_decryptor_unittest.cpp
Normal file
@@ -0,0 +1,861 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
#include "base/at_exit.h"
|
||||
#include "base/basictypes.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/message_loop.h"
|
||||
#include "base/sys_byteorder.h"
|
||||
#include "crypto/encryptor.h"
|
||||
#include "crypto/hmac.h"
|
||||
#include "crypto/symmetric_key.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "eureka/widevine_cdm/oemcrypto/client/oemcrypto_client.h"
|
||||
#include "eureka/widevine_cdm/oemcrypto/mock/src/cmac.h"
|
||||
#include "eureka/widevine_cdm/oemcrypto/mock/src/oemcrypto_keybox_mock.h"
|
||||
#include "wv_cdm_types.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace {
|
||||
|
||||
namespace wv_license_protocol = video_widevine_server::sdk;
|
||||
|
||||
using wv_license_protocol::License;
|
||||
using wv_license_protocol::LicenseIdentification;
|
||||
using wv_license_protocol::LicenseRequest;
|
||||
using wv_license_protocol::SessionState;
|
||||
using wv_license_protocol::SignedMessage;
|
||||
|
||||
enum PolicyType {
|
||||
kDefault = 0,
|
||||
kNoPlay,
|
||||
kShortDuration
|
||||
};
|
||||
|
||||
struct PolicyItem {
|
||||
PolicyType type;
|
||||
bool can_play;
|
||||
bool can_renew;
|
||||
int duration_seconds;
|
||||
int renewal_delay_seconds;
|
||||
int renewal_retry_interval_seconds;
|
||||
};
|
||||
|
||||
struct PolicyItem PolicyItems[] = {
|
||||
{
|
||||
kDefault,
|
||||
true,
|
||||
true,
|
||||
1000,
|
||||
100,
|
||||
0
|
||||
},
|
||||
{
|
||||
kShortDuration,
|
||||
true,
|
||||
true,
|
||||
12,
|
||||
2,
|
||||
2
|
||||
},
|
||||
{
|
||||
kNoPlay,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
}
|
||||
};
|
||||
|
||||
// TODO(jfore): Move this into the test class.
|
||||
/*const*/ char kTestSigningKey[] = {
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
|
||||
};
|
||||
const int kTestSigningKeySize = arraysize(kTestSigningKey);
|
||||
|
||||
const char* kEncryptionKeyLabel = "ENCRYPTION";
|
||||
const uint32_t kEncryptionKeySizeBits = 128;
|
||||
const char* kSigningKeyLabel = "AUTHENTICATION";
|
||||
const uint32_t kSigningKeySizeBits = 256;
|
||||
|
||||
// This is a container to hold the info for an encrypted frame.
|
||||
struct WvCdmEncryptedFrameInfo {
|
||||
char plain_text[32];
|
||||
int plain_text_size;
|
||||
uint8_t key_id[32];
|
||||
int key_id_size;
|
||||
uint8_t content_key[32];
|
||||
int content_key_size;
|
||||
uint8_t encrypted_data[64];
|
||||
int encrypted_data_size;
|
||||
PolicyType policy_type;
|
||||
};
|
||||
|
||||
const WvCdmEncryptedFrameInfo kWvCdmEncryptedFrames[] = {
|
||||
{
|
||||
"Original data.", 14,
|
||||
{ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
|
||||
0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35
|
||||
}, 16,
|
||||
{ 0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
|
||||
}, 16,
|
||||
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x82, 0x54, 0xab, 0x89, 0xa2, 0x75, 0xcd,
|
||||
0x85, 0x83, 0x61, 0x3f, 0xfe, 0x13, 0x58
|
||||
}, 23,
|
||||
kShortDuration
|
||||
},
|
||||
{
|
||||
"Original data.", 14,
|
||||
{ 0x08, 0x01, 0x12, 0x10, 0x6f, 0x13, 0x33, 0xe7,
|
||||
0x6e, 0x59, 0x5e, 0xb5, 0x8c, 0x04, 0x30, 0x72,
|
||||
0xcb, 0xb2, 0x50, 0x68
|
||||
}, 20,
|
||||
{ 0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
|
||||
}, 16,
|
||||
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x82, 0x54, 0xab, 0x89, 0xa2, 0x75, 0xcd,
|
||||
0x85, 0x83, 0x61, 0x3f, 0xfe, 0x13, 0x58
|
||||
}, 23,
|
||||
kShortDuration
|
||||
},
|
||||
{
|
||||
"Changed Original data.", 22,
|
||||
{ 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02,
|
||||
0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02
|
||||
}, 16,
|
||||
{ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23
|
||||
}, 16,
|
||||
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x57, 0x66, 0xf4, 0x12, 0x1a, 0xed, 0xb5,
|
||||
0x79, 0x1c, 0x8e, 0x25, 0xd7, 0x17, 0xe7, 0x5e,
|
||||
0x16, 0xe3, 0x40, 0x08, 0x27, 0x11, 0xe9
|
||||
}, 31,
|
||||
kShortDuration
|
||||
},
|
||||
{
|
||||
"Original data.", 14,
|
||||
{ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
|
||||
0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f
|
||||
}, 16,
|
||||
{ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
|
||||
}, 16,
|
||||
{ 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x9c, 0x71, 0x26, 0x57, 0x3e, 0x25, 0x37,
|
||||
0xf7, 0x31, 0x81, 0x19, 0x64, 0xce, 0xbc
|
||||
}, 23,
|
||||
kShortDuration
|
||||
},
|
||||
// For license renewal test. This has kNoPlay.
|
||||
{
|
||||
// Differnent key and key id.
|
||||
"Original data.", 14,
|
||||
{ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33
|
||||
}, 16,
|
||||
{ 0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
|
||||
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
|
||||
}, 16,
|
||||
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0x82, 0x54, 0xab, 0x89, 0xa2, 0x75, 0xcd,
|
||||
0x85, 0x83, 0x61, 0x3f, 0xfe, 0x13, 0x58
|
||||
}, 23,
|
||||
kNoPlay
|
||||
}
|
||||
};
|
||||
|
||||
bool GetPolicy(PolicyType policy_type, License::Policy* policy) {
|
||||
DCHECK(policy);
|
||||
PolicyItem policy_item;
|
||||
switch (policy_type) {
|
||||
case kDefault:
|
||||
policy_item = PolicyItems[0];
|
||||
DCHECK_EQ(policy_item.type, kDefault);
|
||||
break;
|
||||
case kShortDuration:
|
||||
policy_item = PolicyItems[1];
|
||||
DCHECK_EQ(policy_item.type, kShortDuration);
|
||||
break;
|
||||
case kNoPlay:
|
||||
policy_item = PolicyItems[2];
|
||||
DCHECK_EQ(policy_item.type, kNoPlay);
|
||||
break;
|
||||
default:
|
||||
NOTREACHED();
|
||||
return false;
|
||||
}
|
||||
policy->set_can_play(policy_item.can_play);
|
||||
policy->set_can_renew(policy_item.can_renew);
|
||||
policy->set_license_duration_seconds(policy_item.duration_seconds);
|
||||
policy->set_renewal_delay_seconds(policy_item.renewal_delay_seconds);
|
||||
policy->set_renewal_retry_interval_seconds(
|
||||
policy_item.renewal_retry_interval_seconds);
|
||||
return true;
|
||||
}
|
||||
|
||||
SessionState GetTestSessionState() {
|
||||
static const std::string kTestSessionId = "SomeSessionId";
|
||||
SessionState session_cache;
|
||||
session_cache.mutable_license_id()->set_session_id(kTestSessionId);
|
||||
session_cache.mutable_license_id()->set_version(0);
|
||||
session_cache.set_signing_key(kTestSigningKey, kTestSigningKeySize);
|
||||
return session_cache;
|
||||
}
|
||||
|
||||
// Since "GetTime" is used in this test where some functions cannot reach
|
||||
// Host instance, this will be used as a universal clock for this test.
|
||||
double GetCurrentTestTime() {
|
||||
return base::Time::Now().ToDoubleT();
|
||||
}
|
||||
|
||||
// This encrypts the content key using the device key in cdm/base/device_key.h.
|
||||
std::string EncryptUsingKey(const std::string& input,
|
||||
const std::string& iv,
|
||||
const std::string& encryption_key) {
|
||||
//static const int kAesBlockSize = 16;
|
||||
crypto::Encryptor aes_ecryptor;
|
||||
scoped_ptr<crypto::SymmetricKey> device_key(
|
||||
crypto::SymmetricKey::Import(crypto::SymmetricKey::AES,
|
||||
encryption_key));
|
||||
if (!aes_ecryptor.Init(device_key.get(), crypto::Encryptor::CBC, iv))
|
||||
return "";
|
||||
|
||||
std::string encrypted_data;
|
||||
if (!aes_ecryptor.Encrypt(input, &encrypted_data))
|
||||
return "";
|
||||
|
||||
return encrypted_data;
|
||||
}
|
||||
|
||||
// Takes the license and the session state and generates a SignedMessage. The
|
||||
// return value is the serialized version of the SignedMessage object.
|
||||
std::string GenerateSignedLicenseResponse(const License& license,
|
||||
const std::string& signing_key) {
|
||||
SignedMessage signed_message;
|
||||
bool success = license.SerializeToString(
|
||||
signed_message.mutable_msg());
|
||||
DCHECK(success);
|
||||
|
||||
crypto::HMAC hmacer(crypto::HMAC::SHA256);
|
||||
if (!hmacer.Init(signing_key))
|
||||
return "";
|
||||
|
||||
static const int kDigestSize = 32;
|
||||
uint8_t digest[kDigestSize] = { 0 };
|
||||
if (!hmacer.Sign(signed_message.msg(), digest, kDigestSize))
|
||||
return "";
|
||||
|
||||
signed_message.set_signature(digest, kDigestSize);
|
||||
|
||||
std::string signed_message_bytes;
|
||||
success = signed_message.SerializeToString(&signed_message_bytes);
|
||||
DCHECK(success);
|
||||
|
||||
return signed_message_bytes;
|
||||
}
|
||||
|
||||
// Note: We only use one session. So there aren't any list of sessions stored
|
||||
// anywhere.
|
||||
std::string GenerateNewSignedLicense(
|
||||
const LicenseRequest& license_request,
|
||||
const License::Policy& policies,
|
||||
const License::KeyContainer& content_key,
|
||||
const std::string& encryption_key,
|
||||
const std::string& signing_key) {
|
||||
DCHECK(license_request.content_id().has_cenc_id());
|
||||
SessionState session_cache = GetTestSessionState();
|
||||
DCHECK(session_cache.has_signing_key());
|
||||
|
||||
session_cache.mutable_license_id()->set_request_id(
|
||||
license_request.content_id().webm_id().request_id());
|
||||
session_cache.mutable_license_id()->set_type(
|
||||
license_request.content_id().webm_id().license_type());
|
||||
|
||||
License license;
|
||||
license.mutable_id()->CopyFrom(session_cache.license_id());
|
||||
license.mutable_policy()->CopyFrom(policies);
|
||||
license.set_license_start_time(GetCurrentTestTime());
|
||||
|
||||
License::KeyContainer* renewal_signing_key = license.add_key();
|
||||
renewal_signing_key->set_key(session_cache.signing_key());
|
||||
renewal_signing_key->set_type(License::KeyContainer::SIGNING);
|
||||
|
||||
license.add_key()->CopyFrom(content_key);
|
||||
for (int i = 0; i < license.key_size(); ++i) {
|
||||
license.mutable_key(i)->set_iv("0123456789012345");
|
||||
license.mutable_key(i)->set_key(
|
||||
EncryptUsingKey(license.key(i).key(),
|
||||
license.key(i).iv(),
|
||||
encryption_key));
|
||||
if (license.key(i).key().empty())
|
||||
return "";
|
||||
}
|
||||
|
||||
return GenerateSignedLicenseResponse(license, signing_key);
|
||||
}
|
||||
|
||||
bool GetContentKeyFromKeyId(const std::string& key_id,
|
||||
std::string* content_key) {
|
||||
DCHECK(content_key);
|
||||
for (unsigned int i = 0; i < arraysize(kWvCdmEncryptedFrames); ++i) {
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[i];
|
||||
if (frame.key_id_size != static_cast<int>(key_id.size()))
|
||||
continue;
|
||||
if (!memcmp(frame.key_id, key_id.data(), frame.key_id_size)) {
|
||||
content_key->assign(frame.content_key,
|
||||
frame.content_key + frame.content_key_size);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string DeriveKey(const std::string& key,
|
||||
const std::string& purpose,
|
||||
const std::string& context,
|
||||
const uint32_t size_bits) {
|
||||
if (key.size() != 16)
|
||||
return "";
|
||||
|
||||
// We only handle even multiples of 16 bytes (128 bits) right now.
|
||||
if ((size_bits % 128) || (size_bits > (128 * 255))) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
|
||||
const EVP_CIPHER *cipher = EVP_aes_128_cbc();
|
||||
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
|
||||
|
||||
size_t reslen;
|
||||
unsigned char res[128];
|
||||
unsigned char counter;
|
||||
for (counter = 1; counter <= (size_bits / 128); counter++) {
|
||||
if (!CMAC_Init(cmac_ctx, key.data(), key.size(), cipher, 0))
|
||||
break;
|
||||
|
||||
std::string message;
|
||||
message.append(1, counter);
|
||||
message.append(purpose);
|
||||
message.append(1, '\0');
|
||||
message.append(context);
|
||||
uint32_t size_l = htonl(size_bits);
|
||||
message.append(reinterpret_cast<char*>(&size_l), sizeof(size_l));
|
||||
if (!CMAC_Update(cmac_ctx, message.data(), message.size()))
|
||||
break;
|
||||
|
||||
if (!CMAC_Final(cmac_ctx, res, &reslen))
|
||||
break;
|
||||
|
||||
result.append((const char*)res, reslen);
|
||||
}
|
||||
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
|
||||
if (counter <= (size_bits / 128))
|
||||
return "";
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool VerifyTestSignature(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& key) {
|
||||
crypto::HMAC hmacer(crypto::HMAC::SHA256);
|
||||
if (!hmacer.Init(key))
|
||||
return false;
|
||||
|
||||
if (!hmacer.Verify(message, signature))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GenerateNewLicenseResponse(const LicenseRequest& license_request,
|
||||
const PolicyType& policy_type) {
|
||||
if (!license_request.content_id().has_cenc_id())
|
||||
return "";
|
||||
|
||||
std::string content_key_id = license_request.content_id().cenc_id().pssh(0);
|
||||
|
||||
std::string content_key;
|
||||
if (!GetContentKeyFromKeyId(content_key_id,
|
||||
&content_key)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (content_key_id.size() > 16)
|
||||
content_key_id.resize(16);
|
||||
|
||||
License::Policy policies;
|
||||
if (!GetPolicy(policy_type, &policies))
|
||||
return "";
|
||||
|
||||
std::string context;
|
||||
if (!license_request.SerializeToString(&context))
|
||||
return "";
|
||||
|
||||
wvoec_mock::WvKeybox keybox;
|
||||
|
||||
// TODO(): Fix this to use a constant for key length.
|
||||
std::string widevine_device_key(keybox.device_key().value());
|
||||
std::string encryption_key = DeriveKey(widevine_device_key,
|
||||
std::string(kEncryptionKeyLabel),
|
||||
context,
|
||||
kEncryptionKeySizeBits);
|
||||
std::string signing_key = DeriveKey(widevine_device_key,
|
||||
std::string(kSigningKeyLabel),
|
||||
context,
|
||||
kSigningKeySizeBits);
|
||||
|
||||
memcpy(kTestSigningKey, &signing_key[0], 32);
|
||||
|
||||
License::KeyContainer key_container;
|
||||
key_container.set_id(content_key_id);
|
||||
key_container.set_key(content_key);
|
||||
key_container.set_type(License::KeyContainer::CONTENT);
|
||||
return GenerateNewSignedLicense(license_request,
|
||||
policies,
|
||||
key_container,
|
||||
encryption_key,
|
||||
signing_key);
|
||||
}
|
||||
|
||||
std::string GenerateLicenseRenewalResponse(
|
||||
const SignedMessage& signed_message,
|
||||
const PolicyType& policy_type) {
|
||||
SessionState session_cache = GetTestSessionState();
|
||||
|
||||
LicenseRequest license_request;
|
||||
if (!license_request.ParseFromString(signed_message.msg()))
|
||||
return "";
|
||||
|
||||
std::string session_id = license_request.content_id().license().
|
||||
license_id().session_id();
|
||||
if (session_id.compare(session_cache.license_id().session_id()))
|
||||
return "";
|
||||
|
||||
if (!VerifyTestSignature(signed_message.msg(),
|
||||
signed_message.signature(),
|
||||
session_cache.signing_key())) {
|
||||
return "";
|
||||
}
|
||||
session_cache.mutable_license_id()->set_version(
|
||||
session_cache.license_id().version() + 1);
|
||||
|
||||
License license;
|
||||
license.mutable_id()->CopyFrom(session_cache.license_id());
|
||||
|
||||
// Always get Policy object with kDefault for renewal.
|
||||
License::Policy policy;
|
||||
GetPolicy(policy_type, &policy);
|
||||
license.mutable_policy()->Swap(&policy);
|
||||
license.set_license_start_time(GetCurrentTestTime());
|
||||
|
||||
return GenerateSignedLicenseResponse(license, session_cache.signing_key());
|
||||
}
|
||||
|
||||
std::string GenerateLicenseResponse(const std::string& signed_request,
|
||||
const PolicyType& policy) {
|
||||
SignedMessage signed_message;
|
||||
if (!signed_message.ParseFromString(signed_request))
|
||||
return "";
|
||||
|
||||
LicenseRequest license_request;
|
||||
if (!license_request.ParseFromString(signed_message.msg()))
|
||||
return "";
|
||||
|
||||
if (license_request.type() == LicenseRequest::NEW) {
|
||||
return GenerateNewLicenseResponse(license_request, policy);
|
||||
} else if (license_request.type() == LicenseRequest::RENEWAL) {
|
||||
return GenerateLicenseRenewalResponse(signed_message, policy);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
struct WvCdmEncryptedData {
|
||||
char plain_text[32];
|
||||
int plain_text_size;
|
||||
uint8_t key_id[32];
|
||||
int key_id_size;
|
||||
uint8_t content_key[32];
|
||||
int content_key_size;
|
||||
uint8_t encrypted_data[64];
|
||||
int encrypted_data_size;
|
||||
const char* license_response;
|
||||
int license_response_size;
|
||||
};
|
||||
|
||||
// Container used to pass data from GenerateLicenseRequest to Decrypt.
|
||||
// TODO(rkuroiwa): This class was made before KeyMessage existed; this
|
||||
// should be removed.
|
||||
class LicenseRequestParameter {
|
||||
public:
|
||||
explicit LicenseRequestParameter(const WvCdmEncryptedData& frame)
|
||||
: init_data(new uint8_t[frame.key_id_size]),
|
||||
init_data_size(frame.key_id_size),
|
||||
session_id(NULL),
|
||||
session_id_size(0),
|
||||
key_request(NULL),
|
||||
key_request_size(0),
|
||||
default_url(NULL),
|
||||
default_url_size(0) {
|
||||
memcpy(init_data.get(), frame.key_id, frame.key_id_size);
|
||||
}
|
||||
|
||||
~LicenseRequestParameter() {
|
||||
}
|
||||
|
||||
scoped_array<uint8_t> init_data;
|
||||
int init_data_size;
|
||||
scoped_array<char> session_id;
|
||||
int session_id_size;
|
||||
scoped_array<uint8_t> key_request;
|
||||
int key_request_size;
|
||||
scoped_array<char> default_url;
|
||||
int default_url_size;
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(LicenseRequestParameter);
|
||||
};
|
||||
|
||||
// |encrypted_data| is encrypted from |plain_text| using |key|. |key_id| is
|
||||
// used to distinguish |key|.
|
||||
struct WebmEncryptedData {
|
||||
uint8 plain_text[32];
|
||||
int plain_text_size;
|
||||
uint8 key_id[32];
|
||||
int key_id_size;
|
||||
uint8 key[32];
|
||||
int key_size;
|
||||
uint8 encrypted_data[64];
|
||||
int encrypted_data_size;
|
||||
};
|
||||
|
||||
// Frames 0 & 1 are encrypted with the same key. Frame 2 is encrypted with a
|
||||
// different key. Frame 3 is unencrypted.
|
||||
const WebmEncryptedData kWebmEncryptedFrames[] = {
|
||||
{
|
||||
// plaintext
|
||||
"Original data.", 14,
|
||||
// key_id
|
||||
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13
|
||||
}, 20,
|
||||
// key
|
||||
{ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23
|
||||
}, 16,
|
||||
// encrypted_data
|
||||
{ 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xf0, 0xd1, 0x12, 0xd5, 0x24, 0x81, 0x96,
|
||||
0x55, 0x1b, 0x68, 0x9f, 0x38, 0x91, 0x85
|
||||
}, 23
|
||||
},
|
||||
{
|
||||
// plaintext
|
||||
"Changed Original data.", 22,
|
||||
// key_id
|
||||
{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13
|
||||
}, 20,
|
||||
// key
|
||||
{ 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
|
||||
0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23
|
||||
}, 16,
|
||||
// encrypted_data
|
||||
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x57, 0x66, 0xf4, 0x12, 0x1a, 0xed, 0xb5,
|
||||
0x79, 0x1c, 0x8e, 0x25, 0xd7, 0x17, 0xe7, 0x5e,
|
||||
0x16, 0xe3, 0x40, 0x08, 0x27, 0x11, 0xe9
|
||||
}, 31
|
||||
},
|
||||
{
|
||||
// plaintext
|
||||
"Original data.", 14,
|
||||
// key_id
|
||||
{ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f, 0x30
|
||||
}, 13,
|
||||
// key
|
||||
{ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
|
||||
}, 16,
|
||||
// encrypted_data
|
||||
{ 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x01, 0x9c, 0x71, 0x26, 0x57, 0x3e, 0x25, 0x37,
|
||||
0xf7, 0x31, 0x81, 0x19, 0x64, 0xce, 0xbc
|
||||
}, 23
|
||||
},
|
||||
{
|
||||
// plaintext
|
||||
"Changed Original data.", 22,
|
||||
// key_id
|
||||
{ 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
|
||||
0x2c, 0x2d, 0x2e, 0x2f, 0x30
|
||||
}, 13,
|
||||
// key
|
||||
{ 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
|
||||
0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40
|
||||
}, 16,
|
||||
// encrypted_data
|
||||
{ 0x00, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64,
|
||||
0x20, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x61,
|
||||
0x6c, 0x20, 0x64, 0x61, 0x74, 0x61, 0x2e
|
||||
}, 23
|
||||
}
|
||||
};
|
||||
|
||||
static const uint8 kWebmWrongSizedKey[] = { 0x20, 0x20 };
|
||||
|
||||
static const char kClearKeySystem[] = "org.w3.clearkey";
|
||||
static const char *kWidevineKeySystem = "com.widevine.alpha";
|
||||
static const char kKeyType[] = "any";
|
||||
|
||||
// All three ContentIdentification are supported but cenc_id only supports
|
||||
// one key for now.
|
||||
// Using key_id kCencTestRequest will return the content_key kCencTestRequest
|
||||
//
|
||||
static const char kCencTestRequest[] = "0123456789ABCDEF";
|
||||
static const char kCencTestContentKey[] = {
|
||||
0x16, 0x23, 0xa3, 0x16, 0x67, 0x6d, 0xb7, 0x70,
|
||||
0xfe, 0x78, 0xf6, 0x58, 0x42, 0xb8, 0x16, 0x3c
|
||||
};
|
||||
|
||||
// System Id of the Widevine DRM system for identification in pssh
|
||||
static const uint8 kWidevineSystemId[] = {
|
||||
0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE,
|
||||
0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED,
|
||||
};
|
||||
|
||||
static std::string EncodeInt32(int i) {
|
||||
std::string s;
|
||||
s.resize(sizeof(int));
|
||||
memcpy(&*s.begin(), &i, sizeof(int));
|
||||
return s;
|
||||
}
|
||||
|
||||
// Generate PSSH blob from init data
|
||||
static std::string GeneratePSSHBlob(const uint8* init_data,
|
||||
int init_data_length) {
|
||||
std::string output;
|
||||
|
||||
// 4 byte size of the PSSH atom, inclusive
|
||||
int size = 4 + 4 + 4 + sizeof(kWidevineSystemId) + 4 + init_data_length;
|
||||
output.append(EncodeInt32(base::HostToNet32(size)));
|
||||
|
||||
// "pssh"
|
||||
output.append("pssh");
|
||||
|
||||
// 4 byte flags, value 0
|
||||
int flag = 0;
|
||||
output.append(EncodeInt32(base::HostToNet32(flag)));
|
||||
|
||||
// 16 byte system id
|
||||
output.append(reinterpret_cast<const char*>(kWidevineSystemId),
|
||||
sizeof(kWidevineSystemId));
|
||||
|
||||
// 4 byte size of PSSH data, exclusive
|
||||
output.append(EncodeInt32(base::HostToNet32(init_data_length)));
|
||||
|
||||
// pssh data
|
||||
output.append(reinterpret_cast<const char*>(init_data),
|
||||
init_data_length);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
}; // anonymous
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class WvCdmDecryptorTest : public testing::Test {
|
||||
|
||||
public:
|
||||
WvCdmDecryptorTest() {}
|
||||
|
||||
~WvCdmDecryptorTest() {}
|
||||
|
||||
protected:
|
||||
void GenerateKeyRequest(const uint8* key_id, int key_id_size,
|
||||
std::string& message_buffer) {
|
||||
std::string init_data = GeneratePSSHBlob(key_id, key_id_size);
|
||||
wvcdm::CdmResponseType res = decryptor_.GenerateKeyRequest(
|
||||
kWidevineKeySystem, init_data, &message_buffer, &session_id_string_);
|
||||
EXPECT_TRUE(res == wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
void PrepareForRenewalRequest(int i) {
|
||||
}
|
||||
|
||||
void GetRenewalMessage(std::string& message_buffer) {
|
||||
}
|
||||
|
||||
void GetFailedRenewalMessage(std::string& message_buffer) {
|
||||
}
|
||||
|
||||
public:
|
||||
void AddKeyAndExpectToSucceed(const uint8* key_id, int key_id_size,
|
||||
const uint8* key, int key_size) {
|
||||
std::string cma_key((const char*)key, key_size);
|
||||
std::string init_data = GeneratePSSHBlob(key_id, key_id_size);
|
||||
wvcdm::CdmResponseType res = decryptor_.AddKey(
|
||||
kWidevineKeySystem, init_data, cma_key, session_id_string_);
|
||||
EXPECT_TRUE(res == wvcdm::KEY_ADDED);
|
||||
}
|
||||
|
||||
void AddKeyAndExpectToFail(const uint8* key_id, int key_id_size,
|
||||
const uint8* key, int key_size) {
|
||||
std::string cma_key((const char*)key, key_size);
|
||||
std::string init_data = GeneratePSSHBlob(key_id, key_id_size);
|
||||
wvcdm::CdmResponseType res = decryptor_.AddKey(
|
||||
kWidevineKeySystem, init_data, cma_key, session_id_string_);
|
||||
EXPECT_TRUE(res == wvcdm::KEY_ADDED);
|
||||
}
|
||||
protected:
|
||||
|
||||
wvcdm::WvContentDecryptionModule decryptor_;
|
||||
std::string session_id_string_;
|
||||
};
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, RenewalTest) {
|
||||
std::string response;
|
||||
std::string message;
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[3];
|
||||
License::Policy policies;
|
||||
DCHECK(GetPolicy(frame.policy_type, &policies));
|
||||
|
||||
GenerateKeyRequest(frame.key_id, frame.key_id_size, message);
|
||||
response = GenerateLicenseResponse(message, kShortDuration);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
PrepareForRenewalRequest(1);
|
||||
sleep(policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
GetRenewalMessage(message);
|
||||
response = GenerateLicenseResponse(message, frame.policy_type);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, MultiRenewalTest) {
|
||||
std::string response;
|
||||
std::string message;
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[3];
|
||||
License::Policy policies;
|
||||
DCHECK(GetPolicy(frame.policy_type, &policies));
|
||||
|
||||
GenerateKeyRequest(frame.key_id, frame.key_id_size, message);
|
||||
response = GenerateLicenseResponse(message, kShortDuration);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
PrepareForRenewalRequest(1);
|
||||
sleep(policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
GetRenewalMessage(message);
|
||||
response = GenerateLicenseResponse(message, frame.policy_type);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
|
||||
PrepareForRenewalRequest(2);
|
||||
sleep(policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
GetRenewalMessage(message);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, RenewalRetryTest_ExpectSuccess) {
|
||||
std::string response;
|
||||
std::string message;
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[3];
|
||||
License::Policy policies;
|
||||
DCHECK(GetPolicy(frame.policy_type, &policies));
|
||||
|
||||
GenerateKeyRequest(frame.key_id, frame.key_id_size, message);
|
||||
response = GenerateLicenseResponse(message, kShortDuration);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
|
||||
int loop_seconds =
|
||||
policies.license_duration_seconds() - policies.renewal_delay_seconds();
|
||||
int loop_count = loop_seconds / policies.renewal_retry_interval_seconds();
|
||||
if (loop_seconds % policies.renewal_retry_interval_seconds())
|
||||
++loop_count;
|
||||
for (int i = 1; i <= loop_count; ++i) {
|
||||
PrepareForRenewalRequest(i);
|
||||
sleep(policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
GetRenewalMessage(message);
|
||||
}
|
||||
|
||||
response = GenerateLicenseResponse(message, frame.policy_type);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, RenewalRetryTest_ExpectLicenseExpiration) {
|
||||
std::string response;
|
||||
std::string message;
|
||||
const WvCdmEncryptedFrameInfo& frame = kWvCdmEncryptedFrames[3];
|
||||
License::Policy policies;
|
||||
DCHECK(GetPolicy(frame.policy_type, &policies));
|
||||
|
||||
GenerateKeyRequest(frame.key_id, frame.key_id_size, message);
|
||||
response = GenerateLicenseResponse(message, kShortDuration);
|
||||
AddKeyAndExpectToSucceed(frame.key_id, frame.key_id_size,
|
||||
reinterpret_cast<const uint8*>(response.data()),
|
||||
response.size());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
|
||||
int loop_seconds =
|
||||
policies.license_duration_seconds() - policies.renewal_delay_seconds();
|
||||
int loop_count = loop_seconds / policies.renewal_retry_interval_seconds() + 1;
|
||||
if (loop_seconds % policies.renewal_retry_interval_seconds())
|
||||
++loop_count;
|
||||
|
||||
for (int i = 1; i <= loop_count; ++i) {
|
||||
PrepareForRenewalRequest(i);
|
||||
sleep(i > 1 ?
|
||||
policies.renewal_retry_interval_seconds() :
|
||||
policies.renewal_delay_seconds());
|
||||
MessageLoop::current()->RunUntilIdle();
|
||||
if (i < loop_count)
|
||||
GetRenewalMessage(message);
|
||||
}
|
||||
GetFailedRenewalMessage(message);
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
// TODO(rkuroiwa): Find where to put this main function just for Widevine CDM
|
||||
// unit tests.
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
base::AtExitManager exit;
|
||||
MessageLoop ttr(MessageLoop::TYPE_IO);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
201
libwvdrmengine/cdm/core/test/wvcdm_test.cpp
Normal file
201
libwvdrmengine/cdm/core/test/wvcdm_test.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/at_exit.h"
|
||||
#include "net/url_request/url_request.h"
|
||||
#include "net/url_request/url_request_test_util.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "string_conversions.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Default license server to play server, can be configured using --server command line option
|
||||
std::string gGpLicenseServer =
|
||||
"https://jmt17.google.com/video-dev/license/GetCencLicense";
|
||||
std::string gYtLicenseServer =
|
||||
"https://www.youtube.com/api/drm/widevine?video_id=03681262dc412c06&source=YOUTUBE";
|
||||
|
||||
std::string gLicenseServer(gYtLicenseServer);
|
||||
|
||||
// Default key id (pssh), can be configured using --keyid command line option
|
||||
std::string gKeyID = "000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"08011210e02562e04cd55351b14b3d748d36ed8e"; // pssh data
|
||||
|
||||
// An invalid key id, expected to fail
|
||||
std::string kWrongKeyID = "000000347073736800000000" // blob size and psshb
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0901121094889920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
|
||||
static const char kWidevineKeySystem[] = "com.widevine.alpha";
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm_test {
|
||||
|
||||
class WvCdmDecryptorTest : public testing::Test {
|
||||
public:
|
||||
WvCdmDecryptorTest() {}
|
||||
~WvCdmDecryptorTest() {}
|
||||
|
||||
protected:
|
||||
void GenerateKeyRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
EXPECT_EQ(decryptor_.GenerateKeyRequest(key_system,
|
||||
init_data,
|
||||
&key_msg_,
|
||||
&session_id_), wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
EXPECT_EQ(decryptor_.GenerateRenewalRequest(key_system,
|
||||
init_data,
|
||||
session_id_, &key_msg_),
|
||||
wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
std::string GetKeyRequestResponse(const std::string& server_url,
|
||||
int expected_response) {
|
||||
net::TestDelegate d;
|
||||
net::TestNetworkDelegate network_delegate;
|
||||
net::TestURLRequestContext context(true);
|
||||
context.set_network_delegate(&network_delegate);
|
||||
scoped_ptr<net::HostResolver> resolver(
|
||||
net::HostResolver::CreateDefaultResolver(NULL));
|
||||
context.set_host_resolver(resolver.get());
|
||||
context.Init();
|
||||
net::URLRequest r(GURL(server_url), &d, &context);
|
||||
r.EnableChunkedUpload();
|
||||
r.set_method("POST");
|
||||
r.AppendChunkToUpload(key_msg_.data(), key_msg_.size(), true);
|
||||
r.Start();
|
||||
EXPECT_TRUE(r.is_pending());
|
||||
|
||||
MessageLoop::current()->Run();
|
||||
|
||||
std::string data = d.data_received();
|
||||
// Youtube server returns 400 for invalid message while play server returns
|
||||
// 500, so just test inequity here for invalid message
|
||||
if (expected_response == 200) {
|
||||
EXPECT_EQ(200, r.GetResponseCode()) << data;
|
||||
} else {
|
||||
EXPECT_NE(200, r.GetResponseCode()) << data;
|
||||
}
|
||||
EXPECT_EQ(net::URLRequestStatus::SUCCESS, r.status().status());
|
||||
EXPECT_TRUE(d.bytes_received() > 0);
|
||||
// Extract DRM message:
|
||||
// https://docs.google.com/a/google.com/document/d/1Xue3bgwv2qIAnuFIZ-HCcix43dvH2UxsOEA_8FCBO3I/edit#
|
||||
if (r.status().status() == net::URLRequestStatus::SUCCESS) {
|
||||
size_t pos = data.find("\r\n");
|
||||
if (pos != data.npos) data = data.substr(pos+2);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
void VerifyKeyRequestResponse(const std::string& server_url,
|
||||
std::string& init_data,
|
||||
bool is_renewal) {
|
||||
std::string resp = GetKeyRequestResponse(server_url, 200);
|
||||
|
||||
std::cout << "Message: " << wvcdm::b2a_hex(key_msg_) << std::endl;
|
||||
std::cout << "Response: " << wvcdm::b2a_hex(resp) << std::endl;
|
||||
|
||||
if (is_renewal) {
|
||||
EXPECT_EQ(decryptor_.RenewKey(kWidevineKeySystem,
|
||||
init_data, resp,
|
||||
session_id_), wvcdm::KEY_ADDED);
|
||||
}
|
||||
else {
|
||||
EXPECT_EQ(decryptor_.AddKey(kWidevineKeySystem,
|
||||
init_data, resp,
|
||||
session_id_), wvcdm::KEY_ADDED);
|
||||
}
|
||||
|
||||
std::cout << "back from AddKey" << std::endl;
|
||||
|
||||
}
|
||||
|
||||
wvcdm::WvContentDecryptionModule decryptor_;
|
||||
|
||||
std::string key_msg_;
|
||||
std::string session_id_;
|
||||
};
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, BaseMessageTest)
|
||||
{
|
||||
GenerateKeyRequest(kWidevineKeySystem, gKeyID);
|
||||
|
||||
GetKeyRequestResponse(gLicenseServer, 200);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, WrongMessageTest)
|
||||
{
|
||||
std::string wrong_message = wvcdm::a2b_hex(kWrongKeyID);
|
||||
|
||||
GenerateKeyRequest(kWidevineKeySystem, wrong_message);
|
||||
|
||||
GetKeyRequestResponse(gLicenseServer, 500);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, NormalWebMDecryption) {
|
||||
GenerateKeyRequest(kWidevineKeySystem, gKeyID);
|
||||
|
||||
VerifyKeyRequestResponse(gLicenseServer, gKeyID, false);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmDecryptorTest, LicenseRenewal) {
|
||||
GenerateKeyRequest(kWidevineKeySystem, gKeyID);
|
||||
|
||||
VerifyKeyRequestResponse(gLicenseServer, gKeyID, false);
|
||||
|
||||
GenerateRenewalRequest(kWidevineKeySystem, gKeyID);
|
||||
|
||||
VerifyKeyRequestResponse(gLicenseServer, gKeyID, true);
|
||||
}
|
||||
|
||||
} // namespace wvcdm_test
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
std::string temp;
|
||||
std::string license_server(gLicenseServer);
|
||||
std::string key_id(gKeyID);
|
||||
for (int i=1; i<argc; i++) {
|
||||
temp.assign(argv[i]);
|
||||
if (temp.find("--server=") == 0) {
|
||||
gLicenseServer.assign(temp.substr(strlen("--server=")));
|
||||
}
|
||||
else if (temp.find("--keyid=") == 0) {
|
||||
gKeyID.assign(temp.substr(strlen("--keyid=")));
|
||||
}
|
||||
else {
|
||||
std::cout << "error: unknown option '" << argv[i] << "'" << std::endl;
|
||||
std::cout << "usage: wvcdm_test [options]" << std::endl << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout << "configure the license server url, please include http[s] in the url" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << license_server << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " --keyid=<key_id>";
|
||||
std::cout << "configure the key id or pssh, in hex format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << key_id << std::endl << std::endl;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << gLicenseServer << std::endl;
|
||||
std::cout << "KeyID: " << gKeyID << std::endl << std::endl;
|
||||
|
||||
gKeyID = wvcdm::a2b_hex(gKeyID);
|
||||
|
||||
base::AtExitManager exit;
|
||||
MessageLoop ttr(MessageLoop::TYPE_IO);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012 Google Inc. All Rights Reserved.
|
||||
*/
|
||||
|
||||
#ifndef WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
#define WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
|
||||
#include <media/stagefright/foundation/ABase.h>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/KeyedVector.h>
|
||||
#include "sys/types.h"
|
||||
|
||||
#include <media/hardware/CryptoAPI.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmDecryptor;
|
||||
|
||||
/**
|
||||
* This class allows the invoker of the ContentDecryptionModule to receive
|
||||
* license requests and error information. The invoker should provide a
|
||||
* concrete implementation for this class.
|
||||
*/
|
||||
class CdmClient
|
||||
{
|
||||
public:
|
||||
typedef enum {
|
||||
kErrorInvalidInitData = 0,
|
||||
kErrorInvalidKeyResponse = 1,
|
||||
kErrorInvalidSessionId = 2,
|
||||
kErrorCdmDecryptorInitializationFailed = 3,
|
||||
kErrorKeyAddFailed = 4
|
||||
} Error;
|
||||
|
||||
CdmClient();
|
||||
virtual ~CdmClient();
|
||||
|
||||
virtual void keyMessage(const android::String8& sessionId,
|
||||
const android::Vector<uint8_t>& message,
|
||||
const android::String8& defaultUrl) = 0;
|
||||
|
||||
virtual void keyAdded(const android::String8& sessionId) = 0;
|
||||
virtual void keyError(const android::String8& sessionId, Error error) = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* The ContentDecryptionModule provides a mechanism to create a license
|
||||
* request, handle the license response and handle decryption of content.
|
||||
*/
|
||||
class ContentDecryptionModule
|
||||
{
|
||||
|
||||
public:
|
||||
explicit ContentDecryptionModule(CdmClient* client);
|
||||
virtual ~ContentDecryptionModule();
|
||||
|
||||
/**
|
||||
* This generates a license request message and session ID for given
|
||||
* initialization data.
|
||||
*
|
||||
* @param[in] initData contains Content Protection system specific data
|
||||
* (initialization or PSSH data).
|
||||
* @return false on error.
|
||||
* @note CdmClient::keyMessage and CdmClient::keyError may be called
|
||||
* on successful license generation or error, respectively.
|
||||
*/
|
||||
bool generateKeyRequest(const android::Vector<uint8_t>& initData);
|
||||
|
||||
/**
|
||||
* This takes in a license response and sets up the crypto content
|
||||
* in preparation for decryption for a given session.
|
||||
*
|
||||
* @param[in] sessionId identifies the license request message created by
|
||||
* generateKeyRequest though the CdmClient::KeyMessage method.
|
||||
* @param[in] keyResponse is the license response from the license server.
|
||||
* @note CdmClient::keyError may be called on error.
|
||||
*/
|
||||
void addKey(const android::String8& sessionId,
|
||||
const android::Vector<uint8_t>& keyResponse);
|
||||
|
||||
/**
|
||||
* TODO:rfrias,edwinwong: These interfaces are pending finalization
|
||||
* of OEMCrypto APIs
|
||||
*/
|
||||
void decryptVideo(const uint8_t* keyId, const uint8_t* iv,
|
||||
size_t ivLength, const void* input,
|
||||
const android::CryptoPlugin::SubSample* subSamples,
|
||||
size_t numSubSamples, void* outputHandle, size_t* outputOffset,
|
||||
size_t* outputLength);
|
||||
|
||||
/**
|
||||
* TODO:rfrias,edwinwong: These interfaces are pending finalization
|
||||
* of OEMCrypto APIs
|
||||
*/
|
||||
void decryptAudio(const uint8_t* keyId, const uint8_t* iv,
|
||||
size_t ivLength, const void* input,
|
||||
const android::CryptoPlugin::SubSample* subSamples,
|
||||
size_t numSubSamples, void* output, size_t* outputLength);
|
||||
|
||||
/**
|
||||
* This releases resources and crypto contexts associated with a
|
||||
* given session.
|
||||
*
|
||||
* @param[in] sessionId identifies a license and associated crypto
|
||||
* contexts
|
||||
*/
|
||||
virtual void closeSession(const android::String8& sessionId);
|
||||
|
||||
private:
|
||||
CdmClient* const mClient;
|
||||
android::KeyedVector<android::String8, CdmDecryptor*> mSessionIdToDecryptorMap;
|
||||
|
||||
DISALLOW_EVIL_CONSTRUCTORS(ContentDecryptionModule);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
81
libwvdrmengine/cdm/include/wv_content_decryption_module.h
Normal file
81
libwvdrmengine/cdm/include/wv_content_decryption_module.h
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef CDM_BASE_WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
#define CDM_BASE_WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
#include "utils/UniquePtr.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmEngine;
|
||||
class WvCdmEventListener;
|
||||
|
||||
class WvContentDecryptionModule {
|
||||
public:
|
||||
WvContentDecryptionModule();
|
||||
virtual ~WvContentDecryptionModule();
|
||||
|
||||
// Session related methods
|
||||
virtual CdmResponseType OpenSession(const CdmKeySystem& key_system,
|
||||
CdmSessionId* session_id);
|
||||
virtual CdmResponseType CloseSession(CdmSessionId& session_id);
|
||||
|
||||
// Construct a valid license request.
|
||||
virtual CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id,
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmNameValueMap& app_parameters,
|
||||
CdmKeyMessage* key_request);
|
||||
|
||||
// Accept license response and extract key info.
|
||||
virtual CdmResponseType AddKey(const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data);
|
||||
|
||||
// Cancel session
|
||||
virtual CdmResponseType CancelKeyRequest(const CdmSessionId& session_id);
|
||||
|
||||
// Query license information
|
||||
virtual CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
|
||||
CdmNameValueMap* key_info);
|
||||
|
||||
// Provisioning related methods
|
||||
virtual CdmResponseType GetProvisioningRequest(
|
||||
CdmProvisioningRequest* request,
|
||||
std::string* default_url);
|
||||
|
||||
virtual CdmResponseType HandleProvisioningResponse(
|
||||
CdmProvisioningResponse& response);
|
||||
|
||||
// Secure stop related methods
|
||||
virtual CdmResponseType GetSecureStops(CdmSecureStops* secure_stops);
|
||||
virtual CdmResponseType ReleaseSecureStops(
|
||||
const CdmSecureStopReleaseMessage& message);
|
||||
|
||||
// Accept encrypted buffer and return decrypted data.
|
||||
virtual CdmResponseType Decrypt(const CdmSessionId& session_id,
|
||||
bool is_encrypted,
|
||||
const KeyId& key_id,
|
||||
const uint8_t* encrypted_buffer,
|
||||
size_t encrypted_size,
|
||||
const std::vector<uint8_t>& iv,
|
||||
size_t block_offset,
|
||||
void* decrypted_buffer);
|
||||
|
||||
// Event listener related methods
|
||||
virtual bool AttachEventListener(CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
virtual bool DetachEventListener(CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
private:
|
||||
|
||||
// instance variables
|
||||
UniquePtr<CdmEngine> cdm_engine_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(WvContentDecryptionModule);
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // CDM_BASE_WV_CONTENT_DECRYPTION_MODULE_H_
|
||||
@@ -1,2 +0,0 @@
|
||||
Rahul and Edwin fill this out
|
||||
Rahul and Edwin write tests
|
||||
19
libwvdrmengine/cdm/src/clock.cpp
Normal file
19
libwvdrmengine/cdm/src/clock.cpp
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Clock - implemented using the standard linux time library
|
||||
|
||||
#include "clock.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
int64_t GetCurrentTime() {
|
||||
struct timeval tv;
|
||||
tv.tv_sec = tv.tv_usec = 0;
|
||||
gettimeofday(&tv, NULL);
|
||||
return tv.tv_sec;
|
||||
}
|
||||
|
||||
|
||||
}; // namespace wvcdm
|
||||
56
libwvdrmengine/cdm/src/lock.cpp
Normal file
56
libwvdrmengine/cdm/src/lock.cpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Lock class - provides a simple android specific mutex implementation
|
||||
|
||||
#include "lock.h"
|
||||
#include "utils/Mutex.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class Lock::Impl {
|
||||
public:
|
||||
android::Mutex lock_;
|
||||
};
|
||||
|
||||
Lock::Lock() : impl_(new Lock::Impl()) {
|
||||
}
|
||||
|
||||
Lock::~Lock() {
|
||||
delete impl_;
|
||||
impl_ = NULL;
|
||||
}
|
||||
|
||||
void Lock::Acquire() {
|
||||
impl_->lock_.lock();
|
||||
}
|
||||
|
||||
void Lock::Release() {
|
||||
impl_->lock_.unlock();
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
||||
33
libwvdrmengine/cdm/src/log.cpp
Normal file
33
libwvdrmengine/cdm/src/log.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Log - implemented using the standard Android logging mechanism
|
||||
|
||||
#define LOG_TAG "WVCdm"
|
||||
#define LOG_BUF_SIZE 1024
|
||||
|
||||
#include "log.h"
|
||||
#include "utils/Log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
void log_write(LogPriority level, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char buf[LOG_BUF_SIZE];
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
android_LogPriority prio = ANDROID_LOG_VERBOSE;
|
||||
|
||||
switch(level) {
|
||||
case LOG_ERROR: prio = ANDROID_LOG_ERROR; break;
|
||||
case LOG_WARN: prio = ANDROID_LOG_WARN; break;
|
||||
case LOG_INFO: prio = ANDROID_LOG_INFO; break;
|
||||
case LOG_DEBUG: prio = ANDROID_LOG_DEBUG; break;
|
||||
case LOG_VERBOSE: prio = ANDROID_LOG_VERBOSE; break;
|
||||
}
|
||||
|
||||
__android_log_write(prio, LOG_TAG, buf);
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
57
libwvdrmengine/cdm/src/timer.cpp
Normal file
57
libwvdrmengine/cdm/src/timer.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Timer class - provides a simple Android specific timer implementation
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include "timer.h"
|
||||
#include "utils/Thread.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class Timer::Impl : public android::Thread {
|
||||
public:
|
||||
Impl() : Thread(false), handler_(NULL), period_(0) {}
|
||||
virtual ~Impl() {};
|
||||
|
||||
void Start(TimerHandler *handler, uint32_t time_in_secs) {
|
||||
handler_ = handler;
|
||||
period_ = time_in_secs;
|
||||
run();
|
||||
}
|
||||
|
||||
private:
|
||||
virtual bool threadLoop() {
|
||||
sleep(period_);
|
||||
handler_->OnTimerEvent();
|
||||
return true;
|
||||
}
|
||||
|
||||
TimerHandler *handler_;
|
||||
uint32_t period_;
|
||||
};
|
||||
|
||||
Timer::Timer() : impl_(new Timer::Impl()) {
|
||||
}
|
||||
|
||||
Timer::~Timer() {
|
||||
if (IsRunning())
|
||||
Stop();
|
||||
|
||||
delete impl_;
|
||||
impl_ = NULL;
|
||||
}
|
||||
|
||||
void Timer::Start(TimerHandler *handler, uint32_t time_in_secs) {
|
||||
impl_->Start(handler, time_in_secs);
|
||||
}
|
||||
|
||||
void Timer::Stop() {
|
||||
impl_->requestExitAndWait();
|
||||
}
|
||||
|
||||
bool Timer::IsRunning() {
|
||||
return impl_->getTid() < 0;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
112
libwvdrmengine/cdm/src/wv_content_decryption_module.cpp
Normal file
112
libwvdrmengine/cdm/src/wv_content_decryption_module.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "cdm_engine.h"
|
||||
#include "log.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_event_listener.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
WvContentDecryptionModule::WvContentDecryptionModule() :
|
||||
cdm_engine_(new CdmEngine()) {
|
||||
}
|
||||
|
||||
WvContentDecryptionModule::~WvContentDecryptionModule() {
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::OpenSession(
|
||||
const CdmKeySystem& key_system,
|
||||
CdmSessionId* session_id) {
|
||||
return cdm_engine_->OpenSession(key_system, session_id);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::CloseSession(
|
||||
CdmSessionId& session_id) {
|
||||
return cdm_engine_->CloseSession(session_id);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::GenerateKeyRequest(
|
||||
const CdmSessionId& session_id,
|
||||
const CdmInitData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmNameValueMap& app_parameters,
|
||||
CdmKeyMessage* key_request) {
|
||||
CdmKeySystem key_system;
|
||||
return cdm_engine_->GenerateKeyRequest(session_id, false, key_system,
|
||||
init_data, license_type,
|
||||
app_parameters, key_request);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::AddKey(
|
||||
const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data) {
|
||||
CdmKeySystem key_system;
|
||||
CdmInitData init_data;
|
||||
return cdm_engine_->AddKey(session_id, false, key_system,
|
||||
init_data, key_data);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::CancelKeyRequest(
|
||||
const CdmSessionId& session_id) {
|
||||
CdmKeySystem key_system;
|
||||
return cdm_engine_->CancelKeyRequest(session_id, false, key_system);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::QueryKeyStatus(
|
||||
const CdmSessionId& session_id,
|
||||
CdmNameValueMap* key_info) {
|
||||
return cdm_engine_->QueryKeyStatus(session_id, key_info);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::GetProvisioningRequest(
|
||||
CdmProvisioningRequest* request,
|
||||
std::string* default_url) {
|
||||
return cdm_engine_->GetProvisioningRequest(request, default_url);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::HandleProvisioningResponse(
|
||||
CdmProvisioningResponse& response) {
|
||||
return cdm_engine_->HandleProvisioningResponse(response);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::GetSecureStops(
|
||||
CdmSecureStops* secure_stops) {
|
||||
return cdm_engine_->GetSecureStops(secure_stops);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::ReleaseSecureStops(
|
||||
const CdmSecureStopReleaseMessage& message) {
|
||||
return cdm_engine_->ReleaseSecureStops(message);
|
||||
}
|
||||
|
||||
CdmResponseType WvContentDecryptionModule::Decrypt(
|
||||
const CdmSessionId& session_id,
|
||||
bool is_encrypted,
|
||||
const KeyId& key_id,
|
||||
const uint8_t* encrypted_buffer,
|
||||
size_t encrypted_size,
|
||||
const std::vector<uint8_t>& iv,
|
||||
size_t block_offset,
|
||||
void* decrypted_buffer) {
|
||||
return cdm_engine_->Decrypt(session_id, is_encrypted, key_id,
|
||||
encrypted_buffer, encrypted_size, iv,
|
||||
block_offset, decrypted_buffer);
|
||||
}
|
||||
|
||||
bool WvContentDecryptionModule::AttachEventListener(
|
||||
CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener) {
|
||||
return cdm_engine_->AttachEventListener(session_id, listener);
|
||||
}
|
||||
|
||||
bool WvContentDecryptionModule::DetachEventListener(
|
||||
CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener) {
|
||||
return cdm_engine_->DetachEventListener(session_id, listener);
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
@@ -1,2 +0,0 @@
|
||||
Rahul and Edwin fill this out
|
||||
Rahul and Edwin write tests
|
||||
249
libwvdrmengine/cdm/test/request_license_test.cpp
Normal file
249
libwvdrmengine/cdm/test/request_license_test.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include <errno.h>
|
||||
#include <getopt.h>
|
||||
|
||||
#include "config_test_env.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "license_request.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
#include "url_request.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace {
|
||||
// Default license server, can be configured using --server command line option
|
||||
// Default key id (pssh), can be configured using --keyid command line option
|
||||
std::string g_client_auth;
|
||||
wvcdm::KeyId g_key_id;
|
||||
wvcdm::CdmKeySystem g_key_system;
|
||||
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
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class WvCdmRequestLicenseTest : public testing::Test {
|
||||
public:
|
||||
WvCdmRequestLicenseTest() {}
|
||||
~WvCdmRequestLicenseTest() {}
|
||||
|
||||
protected:
|
||||
void GenerateKeyRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
wvcdm::CdmNameValueMap app_parameters;
|
||||
EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_), wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
void GenerateRenewalRequest(const std::string& key_system,
|
||||
const std::string& init_data) {
|
||||
// TODO application makes a license request, CDM will renew the license
|
||||
// when appropriate.
|
||||
wvcdm::CdmNameValueMap app_parameters;
|
||||
EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_), wvcdm::KEY_MESSAGE);
|
||||
}
|
||||
|
||||
// posts a request and extracts the drm message from the response
|
||||
std::string GetKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
int expected_response) {
|
||||
UrlRequest url_request(server_url + client_auth, g_port);
|
||||
|
||||
if (!url_request.is_connected()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
url_request.PostRequest(key_msg_);
|
||||
std::string response;
|
||||
int resp_bytes = url_request.GetResponse(response);
|
||||
LOGD("response:\r\n%s", response.c_str());
|
||||
LOGD("end %d bytes response dump", resp_bytes);
|
||||
|
||||
// Youtube server returns 400 for invalid message while play server returns
|
||||
// 500, so just test inequity here for invalid message
|
||||
int status_code = url_request.GetStatusCode(response);
|
||||
if (expected_response == 200) {
|
||||
EXPECT_EQ(200, status_code);
|
||||
} else {
|
||||
EXPECT_NE(200, status_code);
|
||||
}
|
||||
|
||||
std::string drm_msg;
|
||||
if (200 == status_code) {
|
||||
LicenseRequest lic_request;
|
||||
lic_request.GetDrmMessage(response, drm_msg);
|
||||
LOGV("drm msg: %u bytes\r\n%s", drm_msg.size(),
|
||||
HexEncode(reinterpret_cast<const uint8_t*>(drm_msg.data()),
|
||||
drm_msg.size()).c_str());
|
||||
}
|
||||
return drm_msg;
|
||||
}
|
||||
|
||||
void VerifyKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth,
|
||||
std::string& init_data,
|
||||
bool is_renewal) {
|
||||
std::string resp = GetKeyRequestResponse(server_url,
|
||||
client_auth,
|
||||
200);
|
||||
|
||||
if (is_renewal) {
|
||||
// TODO application makes a license request, CDM will renew the license
|
||||
// when appropriate
|
||||
wvcdm::CdmNameValueMap app_parameters;
|
||||
EXPECT_EQ(decryptor_.GenerateKeyRequest(session_id_,
|
||||
init_data,
|
||||
kLicenseTypeStreaming,
|
||||
app_parameters,
|
||||
&key_msg_), wvcdm::KEY_ADDED);
|
||||
}
|
||||
else {
|
||||
EXPECT_EQ(decryptor_.AddKey(session_id_, resp), wvcdm::KEY_ADDED);
|
||||
}
|
||||
}
|
||||
|
||||
wvcdm::WvContentDecryptionModule decryptor_;
|
||||
std::string key_msg_;
|
||||
std::string session_id_;
|
||||
};
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, BaseMessageTest) {
|
||||
decryptor_.OpenSession(g_key_system, &session_id_);
|
||||
GenerateKeyRequest(g_key_system, g_key_id);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth, 200);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, WrongMessageTest) {
|
||||
decryptor_.OpenSession(g_key_system, &session_id_);
|
||||
|
||||
std::string wrong_message = wvcdm::a2bs_hex(g_wrong_key_id);
|
||||
GenerateKeyRequest(g_key_system, wrong_message);
|
||||
GetKeyRequestResponse(g_license_server, g_client_auth, 500);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
TEST_F(WvCdmRequestLicenseTest, NormalDecryption) {
|
||||
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);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// TODO License renewal is not yet implented
|
||||
TEST_F(WvCdmRequestLicenseTest, LicenseRenewal) {
|
||||
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);
|
||||
|
||||
GenerateRenewalRequest(g_key_system, g_key_id);
|
||||
VerifyKeyRequestResponse(g_license_server, g_client_auth, g_key_id, true);
|
||||
decryptor_.CloseSession(session_id_);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
wvcdm::ConfigTestEnv config;
|
||||
g_client_auth.assign(config.client_auth());
|
||||
g_key_system.assign(config.key_system());
|
||||
g_wrong_key_id.assign(config.wrong_key_id());
|
||||
|
||||
// The following variables are configurable through command line options.
|
||||
g_license_server.assign(config.license_server());
|
||||
g_key_id.assign(config.key_id());
|
||||
g_port.assign(config.port());
|
||||
std::string license_server(g_license_server);
|
||||
|
||||
int show_usage = 0;
|
||||
static const struct option long_options[] = {
|
||||
{ "use_full_path", no_argument, &g_use_full_path, 0 },
|
||||
{ "keyid", required_argument, NULL, 'k' },
|
||||
{ "port", required_argument, NULL, 'p' },
|
||||
{ "server", required_argument, NULL, 's' },
|
||||
{ NULL, 0, NULL, '\0' }
|
||||
};
|
||||
|
||||
int option_index = 0;
|
||||
int opt = 0;
|
||||
while ((opt = getopt_long(argc, argv, "k:p:s:u", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'k': {
|
||||
g_key_id.clear();
|
||||
g_key_id.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'p': {
|
||||
g_port.clear();
|
||||
g_port.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
g_license_server.clear();
|
||||
g_license_server.assign(optarg);
|
||||
break;
|
||||
}
|
||||
case 'u': {
|
||||
g_use_full_path = 1;
|
||||
break;
|
||||
}
|
||||
case '?': {
|
||||
show_usage = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (show_usage) {
|
||||
std::cout << std::endl;
|
||||
std::cout << "usage: " << argv[0] << " [options]" << std::endl << std::endl;
|
||||
std::cout << " enclose multiple arguments in '' when using adb shell" << std::endl;
|
||||
std::cout << " e.g. adb shell '" << argv[0] << " --server=\"url\"'" << std::endl << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --port=<connection port>";
|
||||
std::cout << "specifies the port number, in decimal format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << g_port << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --server=<server_url>";
|
||||
std::cout << "configure the license server url, please include http[s] in the url" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " ";
|
||||
std::cout << "default: " << license_server << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --keyid=<key_id>";
|
||||
std::cout << "configure the key id or pssh, in hex format" << std::endl;
|
||||
std::cout << std::setw(30) << std::left << " default keyid:";
|
||||
std::cout << g_key_id << std::endl;
|
||||
|
||||
std::cout << std::setw(30) << std::left << " --use_full_path";
|
||||
std::cout << "specify server url is not a proxy server" << std::endl;
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "Server: " << g_license_server << std::endl;
|
||||
std::cout << "Port: " << g_port << std::endl;
|
||||
std::cout << "KeyID: " << g_key_id << std::endl << std::endl;
|
||||
|
||||
g_key_id = wvcdm::a2bs_hex(g_key_id);
|
||||
config.set_license_server(g_license_server);
|
||||
config.set_port(g_port);
|
||||
config.set_key_id(g_key_id);
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
51
libwvdrmengine/cdm/unit-test.mk
Normal file
51
libwvdrmengine/cdm/unit-test.mk
Normal file
@@ -0,0 +1,51 @@
|
||||
# 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)
|
||||
@@ -1,2 +0,0 @@
|
||||
Jeff and Juce.
|
||||
Maybe we should add a README that says this is the implementation if ICrypto.
|
||||
@@ -1 +0,0 @@
|
||||
Jeff and Juce.
|
||||
@@ -1 +0,0 @@
|
||||
Jeff and Juce.
|
||||
@@ -1 +0,0 @@
|
||||
Jeff and Juce.
|
||||
@@ -1 +0,0 @@
|
||||
Jeff and Jerry.
|
||||
@@ -1 +0,0 @@
|
||||
Jeff and Jerry.
|
||||
@@ -1 +0,0 @@
|
||||
Jeff and Jerry.
|
||||
@@ -1 +0,0 @@
|
||||
Jeff and Jerry.
|
||||
@@ -1 +0,0 @@
|
||||
Juce and Jerry to fill in.
|
||||
16
libwvdrmengine/include/WVCDMSingleton.h
Normal file
16
libwvdrmengine/include/WVCDMSingleton.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_CDM_SINGLETON_H_
|
||||
#define WV_CDM_SINGLETON_H_
|
||||
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
wvcdm::WvContentDecryptionModule* getCDM();
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
#endif // WV_CDM_SINGLETON_H_
|
||||
16
libwvdrmengine/include/WVCreatePluginFactories.h
Normal file
16
libwvdrmengine/include/WVCreatePluginFactories.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_CREATE_PLUGIN_FACTORIES_H_
|
||||
#define WV_CREATE_PLUGIN_FACTORIES_H_
|
||||
|
||||
#include "media/drm/DrmAPI.h"
|
||||
#include "media/hardware/CryptoAPI.h"
|
||||
|
||||
extern "C" {
|
||||
android::DrmFactory* createDrmFactory();
|
||||
android::CryptoFactory* createCryptoFactory();
|
||||
}
|
||||
|
||||
#endif // WV_CREATE_PLUGIN_FACTORIES_H_
|
||||
34
libwvdrmengine/include/WVCryptoFactory.h
Normal file
34
libwvdrmengine/include/WVCryptoFactory.h
Normal file
@@ -0,0 +1,34 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_CRYPTO_FACTORY_H_
|
||||
#define WV_CRYPTO_FACTORY_H_
|
||||
|
||||
#include "media/hardware/CryptoAPI.h"
|
||||
#include "media/stagefright/foundation/ABase.h"
|
||||
#include "utils/Errors.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
class WVCryptoFactory : public android::CryptoFactory {
|
||||
public:
|
||||
WVCryptoFactory();
|
||||
virtual ~WVCryptoFactory();
|
||||
|
||||
virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) const;
|
||||
|
||||
virtual android::status_t createPlugin(const uint8_t uuid[16],
|
||||
const void* data, size_t size,
|
||||
android::CryptoPlugin** plugin);
|
||||
|
||||
private:
|
||||
DISALLOW_EVIL_CONSTRUCTORS(WVCryptoFactory);
|
||||
|
||||
void* mLegacyLibraryHandle;
|
||||
android::CryptoFactory* mLegacyFactory;
|
||||
};
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
#endif // WV_CRYPTO_FACTORY_H_
|
||||
30
libwvdrmengine/include/WVDrmFactory.h
Normal file
30
libwvdrmengine/include/WVDrmFactory.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_DRM_FACTORY_H_
|
||||
#define WV_DRM_FACTORY_H_
|
||||
|
||||
#include "media/drm/DrmAPI.h"
|
||||
#include "media/stagefright/foundation/ABase.h"
|
||||
#include "utils/Errors.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
class WVDrmFactory : public android::DrmFactory {
|
||||
public:
|
||||
WVDrmFactory() {};
|
||||
virtual ~WVDrmFactory() {};
|
||||
|
||||
virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]);
|
||||
|
||||
virtual android::status_t createDrmPlugin(const uint8_t uuid[16],
|
||||
android::DrmPlugin** plugin);
|
||||
|
||||
private:
|
||||
DISALLOW_EVIL_CONSTRUCTORS(WVDrmFactory);
|
||||
};
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
#endif // WV_DRM_FACTORY_H_
|
||||
16
libwvdrmengine/include/WVUUID.h
Normal file
16
libwvdrmengine/include/WVUUID.h
Normal file
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_UUID_H_
|
||||
#define WV_UUID_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
bool isWidevineUUID(const uint8_t uuid[16]);
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
#endif // WV_UUID_H_
|
||||
37
libwvdrmengine/level3/Android.mk
Normal file
37
libwvdrmengine/level3/Android.mk
Normal file
@@ -0,0 +1,37 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(TARGET_ARCH)/l3crypto_engine_mock.cpp \
|
||||
$(TARGET_ARCH)/l3crypto_key_mock.cpp \
|
||||
$(TARGET_ARCH)/l3crypto_keybox_mock.cpp \
|
||||
$(TARGET_ARCH)/l3crypto_mock.cpp \
|
||||
$(TARGET_ARCH)/lock.cpp \
|
||||
$(TARGET_ARCH)/log.cpp \
|
||||
$(TARGET_ARCH)/string_conversions.cpp \
|
||||
$(TARGET_ARCH)/wvcrc.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
bionic \
|
||||
external/openssh \
|
||||
external/openssl/include \
|
||||
external/stlport/stlport \
|
||||
vendor/widevine/libwvdrmengine/oemcrypto/include \
|
||||
$(LOCAL_PATH)/include \
|
||||
$(LOCAL_PATH)/$(TARGET_ARCH)
|
||||
|
||||
LOCAL_MODULE := libl3crypto
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libcrypto \
|
||||
libcutils \
|
||||
libdl \
|
||||
liblog \
|
||||
liboemcrypto \
|
||||
libstlport \
|
||||
libutils \
|
||||
libz \
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
524
libwvdrmengine/level3/arm/l3crypto_engine_mock.cpp
Normal file
524
libwvdrmengine/level3/arm/l3crypto_engine_mock.cpp
Normal file
@@ -0,0 +1,524 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* mock implementation of OEMCrypto APIs
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "l3crypto_engine_mock.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "l3crypto_key_mock.h"
|
||||
#include "openssl/aes.h"
|
||||
#include "openssl/cmac.h"
|
||||
#include "openssl/hmac.h"
|
||||
#include "openssl/rand.h"
|
||||
#include "openssl/sha.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace {
|
||||
// Increment counter for AES-CTR
|
||||
void ctr128_inc(uint8_t* counter) {
|
||||
uint32_t n = 16;
|
||||
do {
|
||||
if (++counter[--n] != 0) return;
|
||||
} while (n);
|
||||
}
|
||||
}
|
||||
|
||||
namespace wvoec_obfs {
|
||||
|
||||
SessionKeyTable::~SessionKeyTable() {
|
||||
for (KeyMap::iterator i = keys_.begin(); i != keys_.end(); ++i) {
|
||||
if (NULL != i->second) {
|
||||
delete i->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SessionKeyTable::Insert(const KeyId key_id, const Key& key_data) {
|
||||
if (keys_.find(key_id) != keys_.end()) return false;
|
||||
keys_[key_id] = new Key(key_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
Key* SessionKeyTable::Find(const KeyId key_id) {
|
||||
if (keys_.find(key_id) == keys_.end()) {
|
||||
return NULL;
|
||||
}
|
||||
return keys_[key_id];
|
||||
}
|
||||
|
||||
void SessionKeyTable::Remove(const KeyId key_id) {
|
||||
if (keys_.find(key_id) != keys_.end()) {
|
||||
delete keys_[key_id];
|
||||
keys_.erase(key_id);
|
||||
}
|
||||
}
|
||||
|
||||
void SessionContext::Open() {
|
||||
}
|
||||
|
||||
void SessionContext::Close() {
|
||||
}
|
||||
|
||||
// Internal utility function to derive key using CMAC-128
|
||||
bool SessionContext::DeriveKey(const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& context,
|
||||
int counter,
|
||||
std::vector<uint8_t>* out) {
|
||||
if (key.empty() || counter > 2 || context.empty() || out == NULL) {
|
||||
LOGE("[DeriveKey(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return false;
|
||||
}
|
||||
|
||||
const EVP_CIPHER* cipher = EVP_aes_128_cbc();
|
||||
CMAC_CTX* cmac_ctx = CMAC_CTX_new();
|
||||
|
||||
if (!CMAC_Init(cmac_ctx, &key[0], key.size(), cipher, 0)) {
|
||||
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> message;
|
||||
message.push_back(counter);
|
||||
message.insert(message.end(), context.begin(), context.end());
|
||||
|
||||
if (!CMAC_Update(cmac_ctx, &message[0], message.size())) {
|
||||
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t reslen;
|
||||
uint8_t res[128];
|
||||
if (!CMAC_Final(cmac_ctx, res, &reslen)) {
|
||||
LOGE("[DeriveKey(): OEMCrypto_ERROR_CMAC_FAILURE]");
|
||||
return false;
|
||||
}
|
||||
|
||||
out->assign(res, res+reslen);
|
||||
|
||||
CMAC_CTX_free(cmac_ctx);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionContext::DeriveKeys(const std::vector<uint8_t>& mac_key_context,
|
||||
const std::vector<uint8_t>& enc_key_context) {
|
||||
// Generate derived key for mac key
|
||||
std::vector<uint8_t> device_key = ce_->keybox().device_key().value();
|
||||
std::vector<uint8_t> mac_key;
|
||||
std::vector<uint8_t> result;
|
||||
if (!DeriveKey(device_key, mac_key_context, 1, &mac_key)) {
|
||||
return false;
|
||||
}
|
||||
if (!DeriveKey(device_key, mac_key_context, 2, &result)) {
|
||||
return false;
|
||||
}
|
||||
mac_key.insert( mac_key.end(), result.begin(), result.end());
|
||||
|
||||
// Generate derived key for encryption key
|
||||
std::vector<uint8_t> enc_key;
|
||||
if (!DeriveKey(device_key, enc_key_context, 1, &enc_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_mac_key(mac_key);
|
||||
set_encryption_key(enc_key);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Utility function to generate a message signature
|
||||
bool SessionContext::GenerateSignature(const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length) {
|
||||
|
||||
if (message == NULL || message_length == 0 ||
|
||||
signature == NULL || signature_length == 0) {
|
||||
LOGE("[OEMCrypto_GenerateSignature(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mac_key_.empty() || mac_key_.size() != wvcdm::MAC_KEY_SIZE) {
|
||||
LOGE("[GenerateSignature(): No MAC Key]");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (*signature_length < SHA256_DIGEST_LENGTH) {
|
||||
*signature_length = SHA256_DIGEST_LENGTH;
|
||||
return false;
|
||||
}
|
||||
|
||||
unsigned int md_len = *signature_length;
|
||||
if (HMAC(EVP_sha256(), &mac_key_[0], SHA256_DIGEST_LENGTH,
|
||||
message, message_length, signature, &md_len)) {
|
||||
*signature_length = md_len;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate message signature
|
||||
bool SessionContext::ValidateMessage(const uint8_t* given_message,
|
||||
size_t message_length,
|
||||
const uint8_t* given_signature,
|
||||
size_t signature_length) {
|
||||
|
||||
if (signature_length != SHA256_DIGEST_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
uint8_t computed_signature[signature_length];
|
||||
if (! GenerateSignature(given_message, message_length,
|
||||
computed_signature, &signature_length)) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(given_signature, computed_signature, signature_length)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionContext::ParseKeyControl(
|
||||
const std::vector<uint8_t>& key_control_string,
|
||||
KeyControlBlock& key_control_block) {
|
||||
|
||||
key_control_block.Invalidate();
|
||||
|
||||
if (key_control_string.size() < wvcdm::KEY_CONTROL_SIZE) {
|
||||
return false;
|
||||
}
|
||||
if (!key_control_block.SetFromString(key_control_string)) {
|
||||
LOGE("KCB: BAD Size or Structure");
|
||||
return false;
|
||||
}
|
||||
if (!key_control_block.Validate()) {
|
||||
LOGE("KCB: BAD Signature");
|
||||
return false;
|
||||
}
|
||||
// TODO(fredgc): This checks each key against a nonce and then throws it out.
|
||||
// Instead, it should use the same nonce for the whole message.
|
||||
// if (!CheckNonce(key_control_block.nonce())) {
|
||||
// LOGE("KCB: BAD Nonce");
|
||||
// return false;
|
||||
// }
|
||||
|
||||
LOGD("KCB:");
|
||||
LOGD(" valid: %d", key_control_block.valid());
|
||||
LOGD(" duration: %d", key_control_block.duration());
|
||||
LOGD(" nonce: %08X", key_control_block.nonce());
|
||||
LOGD(" bits: %08X", key_control_block.control_bits());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionContext::InstallKey(const KeyId& key_id,
|
||||
const std::vector<uint8_t>& key_data,
|
||||
const std::vector<uint8_t>& key_data_iv,
|
||||
const std::vector<uint8_t>& key_control,
|
||||
const std::vector<uint8_t>& key_control_iv) {
|
||||
|
||||
// Decrypt encrypted key_data using derived encryption key and offered iv
|
||||
std::vector<uint8_t> content_key;
|
||||
std::vector<uint8_t> key_control_str;
|
||||
KeyControlBlock key_control_block;
|
||||
|
||||
if (!ce_->DecryptMessage(this, encryption_key_, key_data_iv,
|
||||
key_data, &content_key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Key control must be supplied by license server
|
||||
if (key_control.empty()) {
|
||||
LOGE("[Installkey(): WARNING: No Key Control]");
|
||||
key_control_block.Invalidate();
|
||||
return false;
|
||||
} else {
|
||||
if (key_control_iv.empty()) {
|
||||
LOGE("[Installkey(): ERROR: No Key Control IV]");
|
||||
return false;
|
||||
}
|
||||
if (!ce_->DecryptMessage(this, content_key, key_control_iv,
|
||||
key_control, &key_control_str)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ParseKeyControl(key_control_str, key_control_block)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Key key(KEYTYPE_CONTENT, content_key, key_control_block);
|
||||
session_keys_.Insert(key_id, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionContext::RefreshKey(const KeyId& key_id,
|
||||
const std::vector<uint8_t>& key_control,
|
||||
const std::vector<uint8_t>& key_control_iv) {
|
||||
if (key_id.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Key* content_key = session_keys_.Find(key_id);
|
||||
|
||||
if (NULL == content_key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!key_control.empty()) {
|
||||
const std::vector<uint8_t> content_key_value = content_key->value();
|
||||
|
||||
// Decrypt encrypted key control block
|
||||
// We don't actually make use of it in Oemcrypto mock, just to verify its
|
||||
// validity
|
||||
std::vector<uint8_t> control;
|
||||
if (key_control_iv.empty()) {
|
||||
control = key_control;
|
||||
} else if (!ce_->DecryptMessage(this, content_key_value, key_control_iv,
|
||||
key_control, &control)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KeyControlBlock key_control_block;
|
||||
if (!ParseKeyControl(control, key_control_block)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!content_key->UpdateControl(key_control_block)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionContext::UpdateMacKey(const std::vector<uint8_t>& enc_mac_key,
|
||||
const std::vector<uint8_t>& iv) {
|
||||
|
||||
// Decrypt mac key from enc_mac_key using device_key
|
||||
std::vector<uint8_t> mac_key;
|
||||
if (!ce_->DecryptMessage(this, encryption_key_, iv,
|
||||
enc_mac_key, &mac_key)) {
|
||||
return false;
|
||||
}
|
||||
mac_key_ = mac_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SessionContext::SelectContentKey(const KeyId& key_id) {
|
||||
const Key* content_key = session_keys_.Find(key_id);
|
||||
if (NULL == content_key) {
|
||||
LOGE("[SelectContentKey(): No key matches key id]");
|
||||
return false;
|
||||
}
|
||||
current_content_key_ = content_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SessionContext::AddNonce(uint32_t nonce) {
|
||||
nonce_table_.AddNonce(nonce);
|
||||
}
|
||||
|
||||
bool SessionContext::CheckNonce(uint32_t nonce) {
|
||||
return nonce_table_.CheckNonce(nonce);
|
||||
}
|
||||
|
||||
|
||||
CryptoEngine::CryptoEngine() :
|
||||
ce_state_(CE_INITIALIZED), current_session_(NULL) {
|
||||
valid_ = true;
|
||||
}
|
||||
|
||||
CryptoEngine::~CryptoEngine() {
|
||||
current_session_ = NULL;
|
||||
sessions_.clear();
|
||||
}
|
||||
|
||||
void CryptoEngine::Terminate() {
|
||||
}
|
||||
|
||||
KeyboxError CryptoEngine::ValidateKeybox() { return keybox_.Validate(); }
|
||||
|
||||
SessionId CryptoEngine::CreateSession() {
|
||||
wvcdm::AutoLock lock(session_table_lock_);
|
||||
static int unique_id = 1;
|
||||
SessionId sid = (SessionId)++unique_id;
|
||||
SessionContext* sctx = new SessionContext(this, sid);
|
||||
sessions_[sid] = sctx;
|
||||
return sid;
|
||||
}
|
||||
|
||||
bool CryptoEngine::DestroySession(SessionId sid) {
|
||||
SessionContext* sctx = FindSession(sid);
|
||||
wvcdm::AutoLock lock(session_table_lock_);
|
||||
if (sctx) {
|
||||
sessions_.erase(sid);
|
||||
delete sctx;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
SessionContext* CryptoEngine::FindSession(SessionId sid) {
|
||||
wvcdm::AutoLock lock(session_table_lock_);
|
||||
ActiveSessions::iterator it = sessions_.find(sid);
|
||||
if (it != sessions_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Internal utility function to decrypt the message
|
||||
bool CryptoEngine::DecryptMessage(SessionContext* session,
|
||||
const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& iv,
|
||||
const std::vector<uint8_t>& message,
|
||||
std::vector<uint8_t>* decrypted) {
|
||||
if (key.empty() || iv.empty() || message.empty() || !decrypted) {
|
||||
LOGE("[DecryptMessage(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return false;
|
||||
}
|
||||
|
||||
decrypted->resize(message.size());
|
||||
uint8_t iv_buffer[16];
|
||||
memcpy(iv_buffer, &iv[0], 16);
|
||||
AES_KEY aes_key;
|
||||
AES_set_decrypt_key(&key[0], 128, &aes_key);
|
||||
AES_cbc_encrypt(&message[0], &(decrypted->front()), message.size(),
|
||||
&aes_key, iv_buffer, AES_DECRYPT);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoEngine::DecryptCTR(SessionContext* session,
|
||||
const std::vector<uint8_t>& iv,
|
||||
size_t byte_offset,
|
||||
const std::vector<uint8_t>& cipher_data,
|
||||
bool is_encrypted,
|
||||
void* clear_data,
|
||||
BufferType buffer_type) {
|
||||
|
||||
// Check there is a content key
|
||||
if (session->current_content_key() == NULL) {
|
||||
LOGE("[DecryptCTR(): OEMCrypto_ERROR_NO_CONTENT_KEY]");
|
||||
return false;
|
||||
}
|
||||
const KeyControlBlock& control = session->current_content_key()->control();
|
||||
if (control.control_bits() & kControlDataPathSecure) {
|
||||
if (buffer_type == BUFFER_TYPE_CLEAR) {
|
||||
LOGE("[DecryptCTR(): Secure key with insecure buffer]");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// TODO(fredgc): Check duration of key.
|
||||
|
||||
const std::vector<uint8_t>& content_key = session->current_content_key()->value();
|
||||
|
||||
// Set the AES key.
|
||||
if (static_cast<int>(content_key.size()) != AES_BLOCK_SIZE) {
|
||||
LOGE("[DecryptCTR(): CONTENT_KEY has wrong size.");
|
||||
return false;
|
||||
}
|
||||
const uint8_t* key_u8 = &content_key[0];
|
||||
AES_KEY aes_key;
|
||||
if (AES_set_encrypt_key(key_u8, AES_BLOCK_SIZE * 8, &aes_key) != 0) {
|
||||
LOGE("[DecryptCTR(): FAILURE]");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (buffer_type == BUFFER_TYPE_DIRECT) {
|
||||
// For reference implementation, we quietly drop direct video.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (buffer_type == BUFFER_TYPE_SECURE) {
|
||||
// For reference implementation, we also quietly drop secure data.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! is_encrypted) {
|
||||
memcpy(reinterpret_cast<uint8_t*>(clear_data),
|
||||
&cipher_data[0], cipher_data.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Local copy (will be modified).
|
||||
uint8_t aes_iv[AES_BLOCK_SIZE];
|
||||
if (static_cast<int>(iv.size()) != AES_BLOCK_SIZE) {
|
||||
LOGE("[DecryptCTR(): FAILURE: iv has wrong length]");
|
||||
return false;
|
||||
}
|
||||
memcpy(aes_iv, &iv[0], AES_BLOCK_SIZE);
|
||||
|
||||
// Encrypt the IV.
|
||||
uint8_t ecount_buf[AES_BLOCK_SIZE];
|
||||
if (byte_offset != 0) {
|
||||
// The context is needed only when not starting a new block.
|
||||
AES_encrypt(aes_iv, ecount_buf, &aes_key);
|
||||
ctr128_inc(aes_iv);
|
||||
}
|
||||
|
||||
// Decryption.
|
||||
unsigned int byte_offset_cur = byte_offset;
|
||||
AES_ctr128_encrypt(
|
||||
&cipher_data[0], reinterpret_cast<uint8_t*>(clear_data), cipher_data.size(),
|
||||
&aes_key, aes_iv, ecount_buf, &byte_offset_cur);
|
||||
if (byte_offset_cur != ((byte_offset + cipher_data.size()) % AES_BLOCK_SIZE)) {
|
||||
LOGE("[DecryptCTR(): FAILURE: byte offset wrong.]");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void NonceTable::AddNonce(uint32_t nonce) {
|
||||
int new_slot = -1;
|
||||
int oldest_slot = -1;
|
||||
|
||||
for (int i = 0; i < kTableSize; ++i) {
|
||||
if (valid_[i]) {
|
||||
++age_[i];
|
||||
if (-1 == oldest_slot) {
|
||||
oldest_slot = i;
|
||||
} else {
|
||||
if (age_[i] > age_[oldest_slot]) {
|
||||
oldest_slot = i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (-1 == new_slot) {
|
||||
age_[i] = 0;
|
||||
nonces_[i] = nonce;
|
||||
valid_[i] = true;
|
||||
new_slot = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-1 == new_slot) {
|
||||
// reuse oldest
|
||||
// assert (oldest_slot != -1)
|
||||
int i = oldest_slot;
|
||||
age_[i] = 0;
|
||||
nonces_[i] = nonce;
|
||||
valid_[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool NonceTable::CheckNonce(uint32_t nonce) {
|
||||
for (int i = 0; i < kTableSize; ++i) {
|
||||
if (valid_[i]) {
|
||||
if (nonce == nonces_[i]) {
|
||||
valid_[i] = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}; // namespace wvoec_obfs
|
||||
208
libwvdrmengine/level3/arm/l3crypto_engine_mock.h
Normal file
208
libwvdrmengine/level3/arm/l3crypto_engine_mock.h
Normal file
@@ -0,0 +1,208 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* mock implementation of OEMCrypto APIs
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef L3CRYPTO_ENGINE_MOCK_H_
|
||||
#define L3CRYPTO_ENGINE_MOCK_H_
|
||||
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include <vector>
|
||||
#include "lock.h"
|
||||
#include "l3crypto_key_mock.h"
|
||||
#include "l3crypto_keybox_mock.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvoec_obfs {
|
||||
|
||||
enum SessionState {
|
||||
SESSION_STATE_ILLEGAL,
|
||||
SESSION_STATE_INITIALIZED,
|
||||
SESSION_STATE_HAS_DERIVED_KEYS,
|
||||
SESSION_STATE_ACTIVE,
|
||||
SESSION_STATE_ERROR
|
||||
};
|
||||
|
||||
enum BufferType {
|
||||
BUFFER_TYPE_CLEAR,
|
||||
BUFFER_TYPE_SECURE,
|
||||
BUFFER_TYPE_DIRECT
|
||||
};
|
||||
|
||||
class SessionContext;
|
||||
class CryptoEngine;
|
||||
|
||||
typedef uint32_t SessionId;
|
||||
typedef std::map<SessionId, SessionContext*> ActiveSessions;
|
||||
|
||||
typedef std::vector<uint8_t> KeyId;
|
||||
typedef std::map<KeyId, Key*> KeyMap;
|
||||
|
||||
// SessionKeyTable holds the keys for the current session
|
||||
class SessionKeyTable {
|
||||
public:
|
||||
SessionKeyTable() {}
|
||||
~SessionKeyTable();
|
||||
|
||||
bool Insert(const KeyId key_id, const Key& key_data);
|
||||
Key* Find(const KeyId key_id);
|
||||
void Remove(const KeyId key_id);
|
||||
|
||||
private:
|
||||
KeyMap keys_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(SessionKeyTable);
|
||||
};
|
||||
|
||||
class NonceTable {
|
||||
public:
|
||||
static const int kTableSize = 16;
|
||||
NonceTable() {
|
||||
for (int i = 0; i < kTableSize; ++i) {
|
||||
valid_[i] = false;
|
||||
}
|
||||
}
|
||||
~NonceTable() {};
|
||||
void AddNonce(uint32_t nonce);
|
||||
bool CheckNonce(uint32_t nonce);
|
||||
private:
|
||||
bool valid_[kTableSize];
|
||||
uint32_t age_[kTableSize];
|
||||
uint32_t nonces_[kTableSize];
|
||||
};
|
||||
|
||||
class SessionContext {
|
||||
private:
|
||||
SessionContext() {}
|
||||
|
||||
public:
|
||||
explicit SessionContext(CryptoEngine* ce, SessionId sid)
|
||||
: valid_(true), ce_(ce), id_(sid), current_content_key_(NULL) {}
|
||||
~SessionContext() {}
|
||||
|
||||
void Open();
|
||||
void Close();
|
||||
|
||||
bool isValid() { return valid_; }
|
||||
|
||||
bool DeriveKeys(const std::vector<uint8_t>& mac_context,
|
||||
const std::vector<uint8_t>& enc_context);
|
||||
bool GenerateSignature(const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length);
|
||||
|
||||
bool ValidateMessage(const uint8_t* message,
|
||||
size_t message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length);
|
||||
bool InstallKey(const KeyId& key_id,
|
||||
const std::vector<uint8_t>& key_data,
|
||||
const std::vector<uint8_t>& key_data_iv,
|
||||
const std::vector<uint8_t>& key_control,
|
||||
const std::vector<uint8_t>& key_control_iv);
|
||||
bool ParseKeyControl(const std::vector<uint8_t>& key_control_string,
|
||||
KeyControlBlock& key_control_block);
|
||||
bool RefreshKey(const KeyId& key_id,
|
||||
const std::vector<uint8_t>& key_control,
|
||||
const std::vector<uint8_t>& key_control_iv);
|
||||
bool UpdateMacKey(const std::vector<uint8_t>& mac_key, const std::vector<uint8_t>& iv);
|
||||
bool SelectContentKey(const KeyId& key_id);
|
||||
const Key* current_content_key(void) {return current_content_key_;}
|
||||
void set_mac_key(const std::vector<uint8_t>& mac_key) { mac_key_ = mac_key; }
|
||||
const std::vector<uint8_t>& mac_key() { return mac_key_; }
|
||||
|
||||
void set_encryption_key(const std::vector<uint8_t>& enc_key) {
|
||||
encryption_key_ = enc_key;
|
||||
}
|
||||
const std::vector<uint8_t>& encryption_key() { return encryption_key_; }
|
||||
|
||||
void AddNonce(uint32_t nonce);
|
||||
bool CheckNonce(uint32_t nonce);
|
||||
|
||||
private:
|
||||
|
||||
bool DeriveKey(const std::vector<uint8_t>& key, const std::vector<uint8_t>& context,
|
||||
int counter, std::vector<uint8_t>* out);
|
||||
|
||||
bool valid_;
|
||||
CryptoEngine* ce_;
|
||||
SessionId id_;
|
||||
SessionState state_;
|
||||
std::vector<uint8_t> mac_key_;
|
||||
std::vector<uint8_t> encryption_key_;
|
||||
const Key* current_content_key_;
|
||||
SessionKeyTable session_keys_;
|
||||
NonceTable nonce_table_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(SessionContext);
|
||||
};
|
||||
|
||||
class CryptoEngine {
|
||||
|
||||
private:
|
||||
|
||||
enum CryptoEngineState {
|
||||
CE_ILLEGAL,
|
||||
CE_INITIALIZED,
|
||||
CE_HAS_KEYBOX,
|
||||
CE_HAS_SESSIONS,
|
||||
CE_ERROR
|
||||
};
|
||||
|
||||
public:
|
||||
|
||||
CryptoEngine();
|
||||
~CryptoEngine();
|
||||
|
||||
bool Initialized() { return (ce_state_ != CE_ILLEGAL); }
|
||||
|
||||
void Terminate();
|
||||
|
||||
bool isValid() { return valid_; }
|
||||
|
||||
KeyboxError ValidateKeybox();
|
||||
WvKeybox& keybox() { return keybox_; }
|
||||
|
||||
SessionId CreateSession();
|
||||
|
||||
bool DestroySession(SessionId sid);
|
||||
|
||||
SessionContext* FindSession(SessionId sid);
|
||||
|
||||
void set_current_session_(SessionContext* current) {
|
||||
current_session_ = current;
|
||||
}
|
||||
|
||||
bool DecryptMessage(SessionContext* session,
|
||||
const std::vector<uint8_t>& key,
|
||||
const std::vector<uint8_t>& iv,
|
||||
const std::vector<uint8_t>& message,
|
||||
std::vector<uint8_t>* decrypted);
|
||||
|
||||
bool DecryptCTR(SessionContext* session,
|
||||
const std::vector<uint8_t>& iv,
|
||||
size_t byte_offset,
|
||||
const std::vector<uint8_t>& cipher_data,
|
||||
bool is_encrypted,
|
||||
void* clear_data,
|
||||
BufferType buffer_type);
|
||||
|
||||
private:
|
||||
|
||||
bool valid_;
|
||||
CryptoEngineState ce_state_;
|
||||
SessionContext* current_session_;
|
||||
ActiveSessions sessions_;
|
||||
WvKeybox keybox_;
|
||||
wvcdm::Lock session_table_lock_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CryptoEngine);
|
||||
};
|
||||
|
||||
}; // namespace wvoec_eng
|
||||
#endif
|
||||
97
libwvdrmengine/level3/arm/l3crypto_key_mock.cpp
Normal file
97
libwvdrmengine/level3/arm/l3crypto_key_mock.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* mock implementation of OEMCrypto APIs
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include <cstring>
|
||||
#include "l3crypto_key_mock.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvoec_obfs {
|
||||
|
||||
bool KeyControlBlock::Validate() {
|
||||
|
||||
valid_ = false;
|
||||
if (0x6b63746c != verification_) {
|
||||
return false;
|
||||
}
|
||||
// TODO(gmorgan): validate control bits
|
||||
valid_ = true;
|
||||
return valid_;
|
||||
}
|
||||
|
||||
uint32_t KeyControlBlock::ExtractField(const std::vector<uint8_t>& str, int idx) {
|
||||
int bidx = idx * 4;
|
||||
uint32_t t = static_cast<unsigned char>(str[bidx]) << 24;
|
||||
t |= static_cast<unsigned char>(str[bidx+1]) << 16;
|
||||
t |= static_cast<unsigned char>(str[bidx+2]) << 8;
|
||||
t |= static_cast<unsigned char>(str[bidx+3]);
|
||||
return t;
|
||||
}
|
||||
|
||||
bool KeyControlBlock::SetFromString(const std::vector<uint8_t>& key_control_string) {
|
||||
if (key_control_string.size() < wvcdm::KEY_CONTROL_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
verification_ = ExtractField(key_control_string, 0);
|
||||
duration_ = ExtractField(key_control_string, 1);
|
||||
nonce_ = ExtractField(key_control_string, 2);
|
||||
control_bits_ = ExtractField(key_control_string, 0);
|
||||
|
||||
return Validate();
|
||||
}
|
||||
|
||||
Key::Key(KeyType ktype, const std::vector<uint8_t>& key_string,
|
||||
const KeyControlBlock& control) :
|
||||
valid_(true), type_(ktype),
|
||||
value_(key_string), has_control_(true),
|
||||
control_(control) {
|
||||
}
|
||||
|
||||
bool Key::setValue(const char* key_string, size_t key_string_length) {
|
||||
valid_ = false;
|
||||
if (!key_string || key_string_length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
value_.assign(key_string, key_string + key_string_length);
|
||||
|
||||
if (isValidType() && has_control_) {
|
||||
valid_ = true;
|
||||
}
|
||||
return valid_;
|
||||
}
|
||||
|
||||
bool Key::setType(KeyType ktype) {
|
||||
valid_ = false;
|
||||
type_ = ktype;
|
||||
if (value_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isValidType() && has_control_) {
|
||||
valid_ = true;
|
||||
}
|
||||
return valid_;
|
||||
}
|
||||
|
||||
bool Key::setControl(const KeyControlBlock& control) {
|
||||
valid_ = false;
|
||||
if (!control.valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
control_ = control;
|
||||
has_control_ = true;
|
||||
|
||||
if (isValidType() && !value_.empty()) {
|
||||
valid_ = true;
|
||||
}
|
||||
return valid_;
|
||||
}
|
||||
|
||||
}; // namespace wvoec_eng
|
||||
117
libwvdrmengine/level3/arm/l3crypto_key_mock.h
Normal file
117
libwvdrmengine/level3/arm/l3crypto_key_mock.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* mock implementation of OEMCrypto APIs
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef L3CRYPTO_KEY_MOCK_H_
|
||||
#define L3CRYPTO_KEY_MOCK_H_
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wvoec_obfs {
|
||||
|
||||
enum KeyType {
|
||||
KEYTYPE_UNKNOWN,
|
||||
KEYTYPE_PREPROV,
|
||||
KEYTYPE_ROOT,
|
||||
KEYTYPE_DEVICE,
|
||||
KEYTYPE_CONTENT,
|
||||
KEYTYPE_CONTENT_AUDIO,
|
||||
KEYTYPE_CONTENT_VIDEO,
|
||||
KEYTYPE_MAX
|
||||
};
|
||||
|
||||
const uint32_t kControlObserveDataPath = (1<<31);
|
||||
const uint32_t kControlObserveHDCP = (1<<30);
|
||||
const uint32_t kControlObserveCGMS = (1<<29);
|
||||
const uint32_t kControlDataPathSecure = (1<<4);
|
||||
const uint32_t kControlNonceEnabled = (1<<3);
|
||||
const uint32_t kControlHDCPRequired = (1<<2);
|
||||
const uint32_t kControlCGMSMask = (0x03);
|
||||
const uint32_t kControlCGMSCopyFreely = (0x00);
|
||||
const uint32_t kControlCGMSCopyOnce = (0x02);
|
||||
const uint32_t kControlCGMSCopyNever = (0x03);
|
||||
|
||||
class KeyControlBlock {
|
||||
public:
|
||||
KeyControlBlock() {}
|
||||
KeyControlBlock(const std::vector<uint8_t>& key_control_string) {
|
||||
valid_ = SetFromString(key_control_string);
|
||||
}
|
||||
~KeyControlBlock() {}
|
||||
|
||||
bool SetFromString(const std::vector<uint8_t>& key_control_string);
|
||||
bool Validate();
|
||||
void Invalidate() { valid_ = false; }
|
||||
|
||||
bool valid() const { return valid_; }
|
||||
uint32_t duration() const { return duration_; }
|
||||
uint32_t nonce() const { return nonce_; }
|
||||
uint32_t control_bits() const { return control_bits_; }
|
||||
|
||||
private:
|
||||
|
||||
uint32_t ExtractField(const std::vector<uint8_t>& str, int idx);
|
||||
|
||||
bool valid_;
|
||||
uint32_t verification_;
|
||||
uint32_t duration_;
|
||||
uint32_t nonce_;
|
||||
uint32_t control_bits_;
|
||||
};
|
||||
|
||||
// AES-128 crypto key
|
||||
class Key {
|
||||
public:
|
||||
Key() : valid_(false), type_(KEYTYPE_UNKNOWN), has_control_(false) {}
|
||||
Key(const Key& key) : valid_(key.valid_), type_(key.type_),
|
||||
value_(key.value_),
|
||||
has_control_(key.has_control_),
|
||||
control_(key.control_) {}
|
||||
Key(KeyType type, const std::vector<uint8_t>& key_string,
|
||||
const KeyControlBlock& control);
|
||||
|
||||
virtual ~Key() {};
|
||||
|
||||
// Key is valid iff setValue(), setType(), and setControl() have been called
|
||||
bool setValue(const char* key_string, size_t key_string_length);
|
||||
bool setType(KeyType ktype);
|
||||
bool setControl(const KeyControlBlock& control);
|
||||
bool UpdateControl(const KeyControlBlock& control) { return true; }
|
||||
|
||||
KeyType keyType() { return type_; }
|
||||
const std::vector<uint8_t>& value() const { return value_; }
|
||||
const KeyControlBlock& control() const { return control_; }
|
||||
|
||||
bool isDeviceKey() { return (KEYTYPE_DEVICE == type_); }
|
||||
bool isRootKey() { return (KEYTYPE_ROOT == type_); }
|
||||
bool isPreprovKey() { return (KEYTYPE_PREPROV == type_); }
|
||||
bool isContentKey() {
|
||||
bool ctypes = (KEYTYPE_CONTENT == type_) ||
|
||||
(KEYTYPE_CONTENT_AUDIO == type_) ||
|
||||
(KEYTYPE_CONTENT_VIDEO == type_);
|
||||
return ctypes;
|
||||
}
|
||||
bool isValidType() {
|
||||
return ((KEYTYPE_UNKNOWN < type_) && (KEYTYPE_MAX > type_));
|
||||
}
|
||||
bool isValid() { return valid_; }
|
||||
|
||||
void clear() { value_.clear(); valid_ = false; }
|
||||
|
||||
private:
|
||||
bool valid_;
|
||||
KeyType type_;
|
||||
std::vector<uint8_t> value_;
|
||||
bool has_control_;
|
||||
KeyControlBlock control_;
|
||||
};
|
||||
|
||||
}; // namespace wvoec_eng
|
||||
|
||||
#endif
|
||||
109
libwvdrmengine/level3/arm/l3crypto_keybox_mock.cpp
Normal file
109
libwvdrmengine/level3/arm/l3crypto_keybox_mock.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* mock implementation of OEMCrypto APIs
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "l3crypto_keybox_mock.h"
|
||||
#include <arpa/inet.h> // TODO(fredgc): Add ntoh to wv_cdm_utilities.h
|
||||
#include <string>
|
||||
#include <cstring>
|
||||
#include <sys/types.h>
|
||||
#include "log.h"
|
||||
#include "wvcrc32.h"
|
||||
#include "wv_keybox.h"
|
||||
|
||||
namespace wvoec_obfs {
|
||||
|
||||
const WidevineKeybox kDefaultKeybox = {
|
||||
// Sample keybox used for test vectors
|
||||
{
|
||||
// deviceID
|
||||
0x54, 0x65, 0x73, 0x74, 0x4b, 0x65, 0x79, 0x30, // TestKey01
|
||||
0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ........
|
||||
}, {
|
||||
// key
|
||||
0xfb, 0xda, 0x04, 0x89, 0xa1, 0x58, 0x16, 0x0e,
|
||||
0xa4, 0x02, 0xe9, 0x29, 0xe3, 0xb6, 0x8f, 0x04,
|
||||
}, {
|
||||
// data
|
||||
0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x10, 0x19,
|
||||
0x07, 0xd9, 0xff, 0xde, 0x13, 0xaa, 0x95, 0xc1,
|
||||
0x22, 0x67, 0x80, 0x53, 0x36, 0x21, 0x36, 0xbd,
|
||||
0xf8, 0x40, 0x8f, 0x82, 0x76, 0xe4, 0xc2, 0xd8,
|
||||
0x7e, 0xc5, 0x2b, 0x61, 0xaa, 0x1b, 0x9f, 0x64,
|
||||
0x6e, 0x58, 0x73, 0x49, 0x30, 0xac, 0xeb, 0xe8,
|
||||
0x99, 0xb3, 0xe4, 0x64, 0x18, 0x9a, 0x14, 0xa8,
|
||||
0x72, 0x02, 0xfb, 0x02, 0x57, 0x4e, 0x70, 0x64,
|
||||
0x0b, 0xd2, 0x2e, 0xf4, 0x4b, 0x2d, 0x7e, 0x39,
|
||||
}, {
|
||||
// magic
|
||||
0x6b, 0x62, 0x6f, 0x78,
|
||||
}, {
|
||||
// Crc
|
||||
0x0a, 0x7a, 0x2c, 0x35,
|
||||
}
|
||||
};
|
||||
|
||||
WvKeybox::WvKeybox() : valid_(false) {
|
||||
Prepare();
|
||||
}
|
||||
|
||||
bool WvKeybox::Prepare() {
|
||||
InstallKeybox(reinterpret_cast<const uint8_t*>(&kDefaultKeybox),
|
||||
sizeof(kDefaultKeybox));
|
||||
valid_ = true;
|
||||
return valid_;
|
||||
}
|
||||
|
||||
KeyboxError WvKeybox::Validate() {
|
||||
if (!valid_) {
|
||||
LOGE("[KEYBOX NOT LOADED]");
|
||||
return OTHER_ERROR;
|
||||
}
|
||||
if (strncmp(reinterpret_cast<char*>(magic_), "kbox", 4) != 0) {
|
||||
LOGE("[KEYBOX HAS BAD MAGIC]");
|
||||
return BAD_MAGIC;
|
||||
}
|
||||
uint32_t crc_computed;
|
||||
uint32_t* crc_stored = (uint32_t*)crc_;
|
||||
WidevineKeybox keybox;
|
||||
memset(&keybox, 0, sizeof(keybox));
|
||||
memcpy(keybox.device_id_, &device_id_[0], device_id_.size());
|
||||
memcpy(keybox.device_key_, &device_key_.value()[0], sizeof(keybox.device_key_));
|
||||
memcpy(keybox.data_, key_data_, sizeof(keybox.data_));
|
||||
memcpy(keybox.magic_, magic_, sizeof(keybox.magic_));
|
||||
|
||||
crc_computed = ntohl(wvcrc32(reinterpret_cast<uint8_t*>(&keybox),
|
||||
sizeof(keybox) - 4)); // Don't include last 4 bytes.
|
||||
if (crc_computed != *crc_stored) {
|
||||
LOGE("[KEYBOX CRC problem: computed = %08x, stored = %08x]\n",
|
||||
crc_computed, *crc_stored);
|
||||
return BAD_CRC;
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
bool WvKeybox::InstallKeybox(const uint8_t* buffer, size_t keyBoxLength) {
|
||||
if (keyBoxLength != 128) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const WidevineKeybox* keybox
|
||||
= reinterpret_cast<const WidevineKeybox*>(buffer);
|
||||
device_id_.assign(keybox->device_id_,
|
||||
keybox->device_id_ + sizeof(keybox->device_id_));
|
||||
device_key_.setValue(reinterpret_cast<const char*>(keybox->device_key_),
|
||||
sizeof(keybox->device_key_));
|
||||
device_key_.setType(KEYTYPE_DEVICE);
|
||||
memcpy(key_data_, keybox->data_, sizeof(keybox->data_));
|
||||
memcpy(magic_, keybox->magic_, sizeof(keybox->magic_));
|
||||
memcpy(crc_, keybox->crc_, sizeof(keybox->crc_));
|
||||
return true;
|
||||
}
|
||||
|
||||
}; // namespace wvoec_eng
|
||||
50
libwvdrmengine/level3/arm/l3crypto_keybox_mock.h
Normal file
50
libwvdrmengine/level3/arm/l3crypto_keybox_mock.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* mock implementation of OEMCrypto APIs
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#ifndef L3CRYPTO_KEYBOX_MOCK_H_
|
||||
#define L3CRYPTO_KEYBOX_MOCK_H_
|
||||
|
||||
#include "l3crypto_key_mock.h"
|
||||
|
||||
namespace wvoec_obfs {
|
||||
|
||||
const int DEVICE_KEY_LENGTH = 16;
|
||||
typedef uint8_t WvKeyboxKey[DEVICE_KEY_LENGTH];
|
||||
|
||||
const int KEY_DATA_LENGTH = 72;
|
||||
typedef uint8_t WvKeyboxKeyData[KEY_DATA_LENGTH];
|
||||
|
||||
enum KeyboxError { NO_ERROR, BAD_CRC, BAD_MAGIC, OTHER_ERROR };
|
||||
|
||||
// Widevine keybox
|
||||
class WvKeybox {
|
||||
public:
|
||||
WvKeybox();
|
||||
~WvKeybox() {}
|
||||
|
||||
KeyboxError Validate();
|
||||
const std::vector<uint8_t>& device_id() { return device_id_; }
|
||||
Key& device_key() { return device_key_; }
|
||||
const WvKeyboxKeyData& key_data() { return key_data_; }
|
||||
size_t key_data_length() { return KEY_DATA_LENGTH; }
|
||||
bool InstallKeybox(const uint8_t* keybox, size_t keyBoxLength);
|
||||
|
||||
private:
|
||||
|
||||
bool Prepare();
|
||||
|
||||
bool valid_;
|
||||
std::vector<uint8_t> device_id_;
|
||||
Key device_key_;
|
||||
WvKeyboxKeyData key_data_;
|
||||
uint8_t magic_[4];
|
||||
uint8_t crc_[4];
|
||||
};
|
||||
|
||||
}; // namespace wvoec_eng
|
||||
#endif
|
||||
633
libwvdrmengine/level3/arm/l3crypto_mock.cpp
Normal file
633
libwvdrmengine/level3/arm/l3crypto_mock.cpp
Normal file
@@ -0,0 +1,633 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Copyright 2013 Google Inc. All Rights Reserved.
|
||||
*
|
||||
* mock implementation of OEMCrypto APIs
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
#include "L3CryptoCENC.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include "log.h"
|
||||
#include "l3crypto_engine_mock.h"
|
||||
#include "openssl/rand.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvoec_obfs {
|
||||
|
||||
static CryptoEngine* crypto_engine = NULL;
|
||||
|
||||
// Set this to true when you are generating test vectors.
|
||||
const bool trace_all_calls = false;
|
||||
|
||||
static void dump_hex(std::string name, const uint8_t* vector, size_t length) {
|
||||
printf("%s = ", name.c_str());
|
||||
if (vector == NULL) {
|
||||
printf("NULL;\n");
|
||||
return;
|
||||
}
|
||||
// TODO(fredgc): replace with HEXEncode.
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (i == 0) {
|
||||
printf("\n wvcdm::a2b_hex(\"");
|
||||
} else if (i % 32 == 0) {
|
||||
printf("\"\n \"");
|
||||
}
|
||||
printf("%02X", vector[i]);
|
||||
}
|
||||
printf("\");\n");
|
||||
}
|
||||
|
||||
void dump_array_part(std::string array, size_t index,
|
||||
std::string name, const uint8_t* vector, size_t length) {
|
||||
if (vector == NULL) {
|
||||
printf("%s[%d].%s = NULL;\n", array.c_str(), index, name.c_str());
|
||||
return;
|
||||
}
|
||||
printf("std::string s%d_", index);
|
||||
dump_hex(name, vector, length);
|
||||
printf("%s[%d].%s = message_ptr + message.find(s%d_%s.data());\n",
|
||||
array.c_str(), index, name.c_str(), index, name.c_str());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_Initialize(void) {
|
||||
if (trace_all_calls) {
|
||||
printf("------------------------- L3Crypto_Initialize(void)\n");
|
||||
}
|
||||
|
||||
crypto_engine = new CryptoEngine;
|
||||
|
||||
if (!crypto_engine || !crypto_engine->Initialized()) {
|
||||
LOGE("[L3Crypto_Initialize(): failed]");
|
||||
return OEMCrypto_ERROR_INIT_FAILED;
|
||||
}
|
||||
LOGD("[L3Crypto_Initialize(): success]");
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_Terminate(void) {
|
||||
if (trace_all_calls) {
|
||||
printf("----------------- OEMCryptoResult L3Crypto_Terminate(void)\n");
|
||||
}
|
||||
|
||||
if (!crypto_engine) {
|
||||
LOGE("[L3Crypto_Terminate(): failed]");
|
||||
return OEMCrypto_ERROR_TERMINATE_FAILED;
|
||||
}
|
||||
|
||||
if (crypto_engine->Initialized()) {
|
||||
crypto_engine->Terminate();
|
||||
}
|
||||
|
||||
delete crypto_engine;
|
||||
crypto_engine = NULL;
|
||||
LOGD("[L3Crypto_Terminate(): success]");
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_OpenSession(OEMCrypto_SESSION* session) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_OpenSession(OEMCrypto_SESSION *session)\n");
|
||||
}
|
||||
SessionId sid = crypto_engine->CreateSession();
|
||||
*session = (OEMCrypto_SESSION)sid;
|
||||
LOGD("[L3Crypto_OpenSession(): SID=%08x]", sid);
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_CloseSession(OEMCrypto_SESSION session) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_CloseSession(OEMCrypto_SESSION session)\n");
|
||||
}
|
||||
if (!crypto_engine->DestroySession((SessionId)session)) {
|
||||
LOGD("[L3Crypto_CloseSession(SID=%08X): failed]", session);
|
||||
return OEMCrypto_ERROR_CLOSE_SESSION_FAILED;
|
||||
} else {
|
||||
LOGD("[L3Crypto_CloseSession(SID=%08X): success]", session);
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_GenerateNonce(OEMCrypto_SESSION session,
|
||||
uint32_t* nonce) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_GenerateNonce(OEMCrypto_SESSION session,\n");
|
||||
}
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[L3Crypto_GenerateNonce(): ERROR_NO_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
uint32_t nonce_value;
|
||||
uint8_t* nonce_string = reinterpret_cast<uint8_t*>(&nonce_value);
|
||||
|
||||
// Generate 4 bytes of random data
|
||||
if (!RAND_bytes(nonce_string, 4)) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
session_ctx->AddNonce(nonce_value);
|
||||
*nonce = nonce_value;
|
||||
if (trace_all_calls) {
|
||||
printf("nonce = %08x\n", nonce_value);
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
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) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_GenerateDerivedKeys(\n");
|
||||
dump_hex("mac_key_context", mac_key_context, (size_t)mac_key_context_length);
|
||||
dump_hex("enc_key_context", enc_key_context, (size_t)enc_key_context_length);
|
||||
}
|
||||
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
|
||||
LOGE("[L3Crypto_GenerateDerivedKeys(): ERROR_KEYBOX_INVALID]");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[L3Crypto_GenerateDerivedKeys(): ERROR_NO_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> mac_ctx_str(mac_key_context,
|
||||
mac_key_context + mac_key_context_length);
|
||||
const std::vector<uint8_t> enc_ctx_str(enc_key_context,
|
||||
enc_key_context + enc_key_context_length);
|
||||
|
||||
// Generate mac and encryption keys for current session context
|
||||
if (!session_ctx->DeriveKeys(mac_ctx_str, enc_ctx_str)) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_GenerateSignature(
|
||||
OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_GenerateSignature(\n");
|
||||
dump_hex("message", message, message_length);
|
||||
}
|
||||
|
||||
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
|
||||
LOGE("[L3Crypto_GenerateSignature(): ERROR_KEYBOX_INVALID]");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
|
||||
if (message == NULL || message_length == 0 ||
|
||||
signature == NULL || signature_length == 0) {
|
||||
LOGE("[L3Crypto_GenerateSignature(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[L3Crypto_GenerateSignature(): ERROR_NO_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
if (session_ctx->GenerateSignature(message,
|
||||
message_length,
|
||||
signature,
|
||||
signature_length)) {
|
||||
if (trace_all_calls) {
|
||||
dump_hex("signature", signature, *signature_length);
|
||||
}
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;;
|
||||
}
|
||||
|
||||
bool RangeCheck(const uint8_t* message,
|
||||
uint32_t message_length,
|
||||
const uint8_t* field,
|
||||
uint32_t field_length,
|
||||
bool allow_null) {
|
||||
if (field == NULL) return allow_null;
|
||||
if (field < message) return false;
|
||||
if (field + field_length > message + message_length) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
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) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_LoadKeys(OEMCrypto_SESSION session,\n");
|
||||
dump_hex("message", message, message_length);
|
||||
dump_hex("signature", signature, signature_length);
|
||||
dump_hex("enc_mac_key_iv", enc_mac_key_iv, wvcdm::KEY_IV_SIZE);
|
||||
dump_hex("enc_mac_key", enc_mac_key, wvcdm::MAC_KEY_SIZE);
|
||||
for (size_t i = 0; i < num_keys; i++) {
|
||||
printf("key_array[%d].key_id_length=%d;\n", i, key_array[i].key_id_length);
|
||||
dump_array_part("key_array", i, "key_id",
|
||||
key_array[i].key_id, key_array[i].key_id_length);
|
||||
dump_array_part("key_array", i, "key_data_iv",
|
||||
key_array[i].key_data_iv, wvcdm::KEY_IV_SIZE);
|
||||
dump_array_part("key_array", i, "key_data",
|
||||
key_array[i].key_data, wvcdm::KEY_IV_SIZE);
|
||||
dump_array_part("key_array", i, "key_control_iv",
|
||||
key_array[i].key_control_iv, wvcdm::KEY_IV_SIZE);
|
||||
dump_array_part("key_array", i, "key_control",
|
||||
key_array[i].key_control, wvcdm::KEY_IV_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
|
||||
LOGE("[L3Crypto_LoadKeys(): ERROR_KEYBOX_INVALID]");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[L3Crypto_LoadKeys(): ERROR_NO_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
if (message == NULL || message_length == 0 ||
|
||||
signature == NULL || signature_length == 0 ||
|
||||
key_array == NULL || num_keys == 0) {
|
||||
LOGE("[L3Crypto_LoadKeys(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
// Range check
|
||||
if (!RangeCheck(message, message_length, enc_mac_key,
|
||||
wvcdm::MAC_KEY_SIZE, true) ||
|
||||
!RangeCheck(message, message_length, enc_mac_key_iv,
|
||||
wvcdm::KEY_IV_SIZE, true)) {
|
||||
LOGE("[L3Crypto_LoadKeys(): OEMCrypto_ERROR_SIGNATURE_FAILURE - range check.]");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
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) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_data_iv,
|
||||
wvcdm::KEY_IV_SIZE, false) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_control,
|
||||
wvcdm::KEY_CONTROL_SIZE, true) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_control_iv,
|
||||
wvcdm::KEY_IV_SIZE, true)) {
|
||||
LOGE("[L3Crypto_LoadKeys(): OEMCrypto_ERROR_SIGNATURE_FAILURE -range check %d]", i);
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate message signature
|
||||
if (!session_ctx->ValidateMessage(message, message_length, signature, signature_length)) {
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
|
||||
// Decrypt and install keys in key object
|
||||
std::vector<uint8_t> key_id;
|
||||
std::vector<uint8_t> enc_key_data;
|
||||
std::vector<uint8_t> key_data_iv;
|
||||
std::vector<uint8_t> key_control;
|
||||
std::vector<uint8_t> key_control_iv;
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
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_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;
|
||||
}
|
||||
key_control.assign(key_array[i].key_control,
|
||||
key_array[i].key_control + wvcdm::KEY_CONTROL_SIZE);
|
||||
key_control_iv.assign(key_array[i].key_control_iv,
|
||||
key_array[i].key_control_iv + wvcdm::KEY_IV_SIZE);
|
||||
|
||||
if (!session_ctx->InstallKey(key_id, enc_key_data, key_data_iv, key_control,
|
||||
key_control_iv)) {
|
||||
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;
|
||||
|
||||
// V2 license protocol: update mac key after processing license response
|
||||
const std::vector<uint8_t> enc_mac_key_str = std::vector<uint8_t>(
|
||||
enc_mac_key, enc_mac_key + wvcdm::MAC_KEY_SIZE);
|
||||
const std::vector<uint8_t> enc_mac_key_iv_str = std::vector<uint8_t>(
|
||||
enc_mac_key_iv, enc_mac_key_iv + wvcdm::KEY_IV_SIZE);
|
||||
|
||||
if (!session_ctx->UpdateMacKey(enc_mac_key_str, enc_mac_key_iv_str)) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
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) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_RefreshKeys(OEMCrypto_SESSION session,\n");
|
||||
}
|
||||
|
||||
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
|
||||
LOGE("[L3Crypto_RefreshKeys(): ERROR_KEYBOX_INVALID]");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[L3Crypto_RefreshKeys(): ERROR_NO_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
if (message == NULL || message_length == 0 ||
|
||||
signature == NULL || signature_length == 0) {
|
||||
LOGE("[L3Crypto_RefreshKeys(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
// Range check
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
if (!RangeCheck(message, message_length, key_array[i].key_id,
|
||||
key_array[i].key_id_length, true) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_control,
|
||||
wvcdm::KEY_CONTROL_SIZE, true) ||
|
||||
!RangeCheck(message, message_length, key_array[i].key_control_iv,
|
||||
wvcdm::KEY_IV_SIZE, true)) {
|
||||
LOGE("[L3Crypto_RefreshKeys(): OEMCrypto_ERROR_SIGNATURE_FAILURE]");
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate message signature
|
||||
if (!session_ctx->ValidateMessage(message, message_length,
|
||||
signature, signature_length)) {
|
||||
return OEMCrypto_ERROR_SIGNATURE_FAILURE;
|
||||
}
|
||||
|
||||
// Decrypt and refresh keys in key refresh object
|
||||
std::vector<uint8_t> key_id;
|
||||
std::vector<uint8_t> key_control;
|
||||
std::vector<uint8_t> key_control_iv;
|
||||
for (unsigned int i = 0; i < num_keys; i++) {
|
||||
// TODO(gmorgan): key_id may be null if special control key type (TBS)
|
||||
if (key_array[i].key_id != NULL) {
|
||||
key_id.assign(key_array[i].key_id,
|
||||
key_array[i].key_id + key_array[i].key_id_length);
|
||||
} else {
|
||||
key_id.clear();
|
||||
}
|
||||
if (key_array[i].key_control != NULL) {
|
||||
key_control.assign(key_array[i].key_control,
|
||||
key_array[i].key_control + wvcdm::KEY_CONTROL_SIZE);
|
||||
key_control_iv.assign(key_array[i].key_control_iv,
|
||||
key_array[i].key_control_iv + wvcdm::KEY_IV_SIZE);
|
||||
} else {
|
||||
key_control.clear();
|
||||
key_control_iv.clear();
|
||||
}
|
||||
|
||||
if (!session_ctx->RefreshKey(key_id, key_control, key_control_iv)) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_SelectKey(const OEMCrypto_SESSION session,
|
||||
const uint8_t* key_id,
|
||||
size_t key_id_length) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_SelectKey(const OEMCrypto_SESSION session,\n");
|
||||
dump_hex("key_id", key_id, key_id_length);
|
||||
}
|
||||
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
|
||||
LOGE("[L3Crypto_SelectKey(): ERROR_KEYBOX_INVALID]");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[L3Crypto_SelectKey(): ERROR_NO_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> key_id_str = std::vector<uint8_t>(key_id, key_id + key_id_length);
|
||||
if (!session_ctx->SelectContentKey(key_id_str)) {
|
||||
LOGE("[L3Crypto_SelectKey(): FAIL]");
|
||||
return OEMCrypto_ERROR_NO_CONTENT_KEY;
|
||||
}
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
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) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_DecryptCTR(OEMCrypto_SESSION session,\n");
|
||||
}
|
||||
wvoec_obfs::BufferType buffer_type = BUFFER_TYPE_DIRECT;
|
||||
void *destination = NULL;
|
||||
size_t max_length = 0;
|
||||
switch (out_buffer->type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
buffer_type = BUFFER_TYPE_CLEAR;
|
||||
destination = out_buffer->buffer.clear.address;
|
||||
max_length = out_buffer->buffer.clear.max_length;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
buffer_type = BUFFER_TYPE_SECURE;
|
||||
destination = out_buffer->buffer.secure.handle;
|
||||
max_length = out_buffer->buffer.secure.max_length;
|
||||
break;
|
||||
default:
|
||||
case OEMCrypto_BufferType_Direct:
|
||||
buffer_type = BUFFER_TYPE_DIRECT;
|
||||
destination = NULL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (buffer_type != BUFFER_TYPE_DIRECT && max_length < data_length) {
|
||||
LOGE("[L3Crypto_DecryptCTR(): OEMCrypto_ERROR_SHORT_BUFFER]");
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
|
||||
if (NO_ERROR != crypto_engine->ValidateKeybox()) {
|
||||
LOGE("[L3Crypto_DecryptCTR(): ERROR_KEYBOX_INVALID]");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
|
||||
SessionContext* session_ctx = crypto_engine->FindSession(session);
|
||||
if (!session_ctx || !session_ctx->isValid()) {
|
||||
LOGE("[L3Crypto_DecryptCTR(): ERROR_NO_INVALID_SESSION]");
|
||||
return OEMCrypto_ERROR_INVALID_SESSION;
|
||||
}
|
||||
|
||||
if (data_addr == NULL || data_length == 0 ||
|
||||
iv == NULL || out_buffer == NULL) {
|
||||
LOGE("[L3Crypto_DecryptCTR(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> iv_v(iv, iv+16);
|
||||
std::vector<uint8_t> content(data_addr, data_addr+data_length);
|
||||
|
||||
if (!crypto_engine->DecryptCTR(session_ctx, iv_v, (int)offset,
|
||||
content, is_encrypted,
|
||||
destination, buffer_type)) {
|
||||
LOGE("[L3Crypto_DecryptCTR(): OEMCrypto_ERROR_INVALID_CONTEXT]");
|
||||
return OEMCrypto_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_InstallKeybox(const uint8_t* keybox,
|
||||
size_t keyBoxLength) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_InstallKeybox(const uint8_t *keybox,\n");
|
||||
}
|
||||
if (crypto_engine->keybox().InstallKeybox(keybox, keyBoxLength)) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return OEMCrypto_ERROR_WRITE_KEYBOX;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_IsKeyboxValid(void) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_IsKeyboxValid(void) {\n");
|
||||
}
|
||||
switch(crypto_engine->ValidateKeybox()) {
|
||||
case NO_ERROR: return OEMCrypto_SUCCESS;
|
||||
case BAD_CRC: return OEMCrypto_ERROR_BAD_CRC;
|
||||
case BAD_MAGIC: return OEMCrypto_ERROR_BAD_MAGIC;
|
||||
default:
|
||||
case OTHER_ERROR: return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_GetDeviceID(uint8_t* deviceID,
|
||||
size_t* idLength) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_GetDeviceID(uint8_t* deviceID,\n");
|
||||
}
|
||||
std::vector<uint8_t> dev_id_string = crypto_engine->keybox().device_id();
|
||||
if (dev_id_string.empty()) {
|
||||
LOGE("[L3Crypto_GetDeviceId(): Keybox Invalid]");
|
||||
return OEMCrypto_ERROR_KEYBOX_INVALID;
|
||||
}
|
||||
|
||||
size_t dev_id_len = dev_id_string.size();
|
||||
if (*idLength < dev_id_len) {
|
||||
*idLength = dev_id_len;
|
||||
LOGE("[L3Crypto_GetDeviceId(): ERROR_SHORT_BUFFER]");
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
memset(deviceID, 0, *idLength);
|
||||
memcpy(deviceID, &dev_id_string[0], dev_id_len);
|
||||
*idLength = dev_id_len;
|
||||
LOGD("[L3Crypto_GetDeviceId(): success]");
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_GetKeyData(uint8_t* keyData,
|
||||
size_t* keyDataLength) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_GetKeyData(uint8_t* keyData,\n");
|
||||
}
|
||||
size_t length = crypto_engine->keybox().key_data_length();
|
||||
if (*keyDataLength < length) {
|
||||
*keyDataLength = length;
|
||||
LOGE("[L3Crypto_GetKeyData(): ERROR_SHORT_BUFFER]");
|
||||
return OEMCrypto_ERROR_SHORT_BUFFER;
|
||||
}
|
||||
memset(keyData, 0, *keyDataLength);
|
||||
memcpy(keyData, crypto_engine->keybox().key_data(), length);
|
||||
*keyDataLength = length;
|
||||
LOGD("[L3Crypto_GetKeyData(): success]");
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_GetRandom(uint8_t* randomData, size_t dataLength) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_GetRandom(uint8_t* randomData, size_t dataLength) {\n");
|
||||
}
|
||||
if (!randomData) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
if (RAND_bytes(randomData, dataLength)) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
OEMCryptoResult L3Crypto_WrapKeybox(const uint8_t* keybox,
|
||||
size_t keyBoxLength,
|
||||
uint8_t* wrappedKeybox,
|
||||
size_t* wrappedKeyBoxLength,
|
||||
const uint8_t* transportKey,
|
||||
size_t transportKeyLength) {
|
||||
if (trace_all_calls) {
|
||||
printf("-- OEMCryptoResult L3Crypto_WrapKeybox(const uint8_t *keybox,\n");
|
||||
}
|
||||
if (!keybox || !wrappedKeybox || !wrappedKeyBoxLength
|
||||
|| (keyBoxLength != *wrappedKeyBoxLength)) {
|
||||
return OEMCrypto_ERROR_UNKNOWN_FAILURE;
|
||||
}
|
||||
// This implementation ignores the transport key. For test keys, we
|
||||
// don't need to encrypt the keybox.
|
||||
memcpy(wrappedKeybox, keybox, keyBoxLength);
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
}; // namespace wvoec_obfs
|
||||
54
libwvdrmengine/level3/arm/lock.cpp
Normal file
54
libwvdrmengine/level3/arm/lock.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Lock class - provides a simple android specific mutex implementation
|
||||
|
||||
#include "lock.h"
|
||||
#include "utils/Mutex.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class Lock::Impl {
|
||||
public:
|
||||
android::Mutex lock_;
|
||||
};
|
||||
|
||||
Lock::Lock() : impl_(new Lock::Impl()) {
|
||||
}
|
||||
|
||||
Lock::~Lock() {
|
||||
delete impl_;
|
||||
impl_ = NULL;
|
||||
}
|
||||
|
||||
void Lock::Acquire() {
|
||||
impl_->lock_.lock();
|
||||
}
|
||||
|
||||
void Lock::Release() {
|
||||
impl_->lock_.unlock();
|
||||
}
|
||||
|
||||
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
|
||||
54
libwvdrmengine/level3/arm/lock.h
Normal file
54
libwvdrmengine/level3/arm/lock.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Lock - Platform independent interface for a Mutex class
|
||||
//
|
||||
#ifndef L3CRYPTO_LOCK_H_
|
||||
#define L3CRYPTO_LOCK_H_
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Simple lock class. The implementation is platform dependent.
|
||||
//
|
||||
// The lock must be unlocked by the thread that locked it.
|
||||
// The lock is also not recursive (ie. cannot be taken multiple times).
|
||||
class Lock {
|
||||
public:
|
||||
Lock();
|
||||
~Lock();
|
||||
|
||||
void Acquire();
|
||||
void Release();
|
||||
|
||||
// Acquires a lock if not held and returns true.
|
||||
// Returns false if the lock is held by another thread.
|
||||
bool Try();
|
||||
|
||||
friend class AutoLock;
|
||||
|
||||
private:
|
||||
class 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
|
||||
class AutoLock {
|
||||
public:
|
||||
explicit AutoLock(Lock& lock);
|
||||
explicit AutoLock(Lock* lock);
|
||||
~AutoLock();
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
Impl *impl_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(AutoLock);
|
||||
};
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // L3CRYPTO_LOCK_H_
|
||||
33
libwvdrmengine/level3/arm/log.cpp
Normal file
33
libwvdrmengine/level3/arm/log.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Log - implemented using the standard Android logging mechanism
|
||||
|
||||
#define LOG_TAG "WVCdm"
|
||||
#define LOG_BUF_SIZE 1024
|
||||
|
||||
#include "log.h"
|
||||
#include "utils/Log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
void log_write(LogPriority level, const char *fmt, ...) {
|
||||
va_list ap;
|
||||
char buf[LOG_BUF_SIZE];
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
android_LogPriority prio = ANDROID_LOG_VERBOSE;
|
||||
|
||||
switch(level) {
|
||||
case LOG_ERROR: prio = ANDROID_LOG_ERROR; break;
|
||||
case LOG_WARN: prio = ANDROID_LOG_WARN; break;
|
||||
case LOG_INFO: prio = ANDROID_LOG_INFO; break;
|
||||
case LOG_DEBUG: prio = ANDROID_LOG_DEBUG; break;
|
||||
case LOG_VERBOSE: prio = ANDROID_LOG_VERBOSE; break;
|
||||
}
|
||||
|
||||
__android_log_write(prio, LOG_TAG, buf);
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
31
libwvdrmengine/level3/arm/log.h
Normal file
31
libwvdrmengine/level3/arm/log.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Log - Platform independent interface for a Logging class
|
||||
//
|
||||
#ifndef OEMCRYPTO_LOG_H_
|
||||
#define OEMCRYPTO_LOG_H_
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Simple logging class. The implementation is platform dependent.
|
||||
|
||||
typedef enum {
|
||||
LOG_ERROR,
|
||||
LOG_WARN,
|
||||
LOG_INFO,
|
||||
LOG_DEBUG,
|
||||
LOG_VERBOSE
|
||||
} LogPriority;
|
||||
|
||||
void log_write(LogPriority priority, const char *fmt, ...);
|
||||
|
||||
// Log APIs
|
||||
#define LOGE(...) ((void)log_write(wvcdm::LOG_ERROR, __VA_ARGS__))
|
||||
#define LOGW(...) ((void)log_write(wvcdm::LOG_WARN, __VA_ARGS__))
|
||||
#define LOGI(...) ((void)log_write(wvcdm::LOG_INFO, __VA_ARGS__))
|
||||
#define LOGD(...) ((void)log_write(wvcdm::LOG_DEBUG, __VA_ARGS__))
|
||||
#define LOGV(...) ((void)log_write(wvcdm::LOG_VERBOSE, __VA_ARGS__))
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // OEMCRYPTO_LOG_H_
|
||||
79
libwvdrmengine/level3/arm/string_conversions.cpp
Normal file
79
libwvdrmengine/level3/arm/string_conversions.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#include "string_conversions.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
static bool CharToDigit(char ch, unsigned char* digit) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
*digit = ch - '0';
|
||||
} else {
|
||||
ch = tolower(ch);
|
||||
if ((ch >= 'a') && (ch <= 'f')) {
|
||||
*digit = ch - 'a' + 10;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// converts an ascii hex string(2 bytes per digit) into a decimal byte string
|
||||
std::vector<uint8_t> a2b_hex(const std::string& byte) {
|
||||
std::vector<uint8_t> array(0);
|
||||
unsigned int count = byte.size();
|
||||
if (count == 0 || (count % 2) != 0) {
|
||||
LOGE("Invalid input size %u for string %s", count, byte.c_str());
|
||||
return array;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < count / 2; ++i) {
|
||||
unsigned char msb = 0; // most significant 4 bits
|
||||
unsigned char lsb = 0; // least significant 4 bits
|
||||
if (!CharToDigit(byte[i * 2], &msb) ||
|
||||
!CharToDigit(byte[i * 2 + 1], &lsb)) {
|
||||
LOGE("Invalid hex value %c%c at index %d", byte[i*2], byte[i*2+1], i);
|
||||
return array;
|
||||
}
|
||||
array.push_back((msb << 4) | lsb);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
std::string b2a_hex(const std::vector<uint8_t>& byte) {
|
||||
return HexEncode(&byte[0], byte.size());
|
||||
}
|
||||
|
||||
std::string HexEncode(const uint8_t* in_buffer, unsigned int size) {
|
||||
static const char kHexChars[] = "0123456789ABCDEF";
|
||||
|
||||
// Each input byte creates two output hex characters.
|
||||
std::string out_buffer(size * 2, '\0');
|
||||
|
||||
for (unsigned int i = 0; i < size; ++i) {
|
||||
char byte = in_buffer[i];
|
||||
out_buffer[(i << 1)] = kHexChars[(byte >> 4) & 0xf];
|
||||
out_buffer[(i << 1) + 1] = kHexChars[byte & 0xf];
|
||||
}
|
||||
return out_buffer;
|
||||
}
|
||||
|
||||
std::string IntToString(int value) {
|
||||
// log10(2) ~= 0.3 bytes needed per bit or per byte log10(2**8) ~= 2.4.
|
||||
// So round up to allocate 3 output characters per byte, plus 1 for '-'.
|
||||
const int kOutputBufSize = 3 * sizeof(int) + 1;
|
||||
char buffer[kOutputBufSize];
|
||||
memset(buffer, 0, kOutputBufSize);
|
||||
snprintf(buffer, kOutputBufSize, "%d", value);
|
||||
|
||||
std::string out_string(buffer, sizeof(buffer));
|
||||
return out_string;
|
||||
}
|
||||
|
||||
}; // namespace wvcdm
|
||||
18
libwvdrmengine/level3/arm/string_conversions.h
Normal file
18
libwvdrmengine/level3/arm/string_conversions.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef OEMCRYPTO_STRING_CONVERSIONS_H_
|
||||
#define OEMCRYPTO_STRING_CONVERSIONS_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
std::vector<uint8_t> a2b_hex(const std::string& b);
|
||||
std::string b2a_hex(const std::vector<uint8_t>& b);
|
||||
std::string HexEncode(const uint8_t* bytes, unsigned size);
|
||||
std::string IntToString(int value);
|
||||
|
||||
}; // namespace wvcdm
|
||||
|
||||
#endif // OEMCRYPTO_STRING_CONVERSIONS_H_
|
||||
14
libwvdrmengine/level3/arm/wv_cdm_constants.h
Normal file
14
libwvdrmengine/level3/arm/wv_cdm_constants.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef OEMCRYPTO_WV_CDM_CONSTANTS_H_
|
||||
#define OEMCRYPTO_WV_CDM_CONSTANTS_H_
|
||||
|
||||
namespace wvcdm {
|
||||
static const size_t KEY_CONTROL_SIZE = 16;
|
||||
static const size_t KEY_IV_SIZE = 16;
|
||||
static const size_t KEY_PAD_SIZE = 16;
|
||||
static const size_t KEY_SIZE = 16;
|
||||
static const size_t MAC_KEY_SIZE = 32;
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // OEMCRYPTO_WV_CDM_CONSTANTS_H_
|
||||
49
libwvdrmengine/level3/arm/wv_cdm_types.h
Normal file
49
libwvdrmengine/level3/arm/wv_cdm_types.h
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef L3CRYPTO_WV_CDM_TYPES_H_
|
||||
#define L3CRYPTO_WV_CDM_TYPES_H_
|
||||
|
||||
#include <map>
|
||||
#include <stdint.h>
|
||||
#include <string>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
typedef std::string CdmKeySystem;
|
||||
typedef std::string CdmInitData;
|
||||
typedef std::string CdmKeyMessage;
|
||||
typedef std::string CdmKeyResponse;
|
||||
typedef std::string KeyId;
|
||||
typedef std::string CdmSessionId;
|
||||
typedef std::string RequestId;
|
||||
typedef uint32_t CryptoResult;
|
||||
typedef uint32_t CryptoSessionId;
|
||||
typedef std::string CryptoKeyId;
|
||||
|
||||
enum CdmResponseType {
|
||||
NO_ERROR,
|
||||
UNKNOWN_ERROR,
|
||||
KEY_ADDED,
|
||||
KEY_ERROR,
|
||||
KEY_MESSAGE,
|
||||
NEED_KEY,
|
||||
KEY_CANCELED,
|
||||
};
|
||||
|
||||
#define CORE_DISALLOW_COPY_AND_ASSIGN(TypeName) \
|
||||
TypeName(const TypeName&); \
|
||||
void operator=(const TypeName&)
|
||||
|
||||
enum CdmEventType {
|
||||
LICENSE_EXPIRED,
|
||||
LICENSE_RENEWAL_NEEDED
|
||||
};
|
||||
|
||||
// forward class references
|
||||
class KeyMessage;
|
||||
class Request;
|
||||
class Key;
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // L3CRYPTO_WV_CDM_TYPES_H_
|
||||
24
libwvdrmengine/level3/arm/wv_keybox.h
Normal file
24
libwvdrmengine/level3/arm/wv_keybox.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
|
||||
#ifndef WV_KEYBOX_H_
|
||||
#define WV_KEYBOX_H_
|
||||
|
||||
namespace wvoec_obfs {
|
||||
|
||||
// This is the format of a Widevine keybox.
|
||||
typedef struct { // 128 bytes total.
|
||||
// C character string identifying the device. Null terminated.
|
||||
uint8_t device_id_[32];
|
||||
// 128 bit AES key assigned to device. Generated by Widevine.
|
||||
uint8_t device_key_[16];
|
||||
// Key Data. Encrypted data.
|
||||
uint8_t data_[72];
|
||||
// Constant code used to recognize a valid keybox "kbox" = 0x6b626f78.
|
||||
uint8_t magic_[4];
|
||||
// The CRC checksum of the first 124 bytes of the keybox.
|
||||
uint8_t crc_[4];
|
||||
} WidevineKeybox;
|
||||
|
||||
}
|
||||
|
||||
#endif // WV_KEYBOX_H_
|
||||
93
libwvdrmengine/level3/arm/wvcrc.cpp
Normal file
93
libwvdrmengine/level3/arm/wvcrc.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
/*********************************************************************
|
||||
* wvcrc32.cpp
|
||||
*
|
||||
* (c) Copyright 2011-2012 Google, Inc.
|
||||
*
|
||||
* Compte CRC32 Checksum. Needed for verification of WV Keybox.
|
||||
*********************************************************************/
|
||||
|
||||
#include "wvcrc32.h"
|
||||
|
||||
#define INIT_CRC32 0xffffffff
|
||||
|
||||
uint32_t wvrunningcrc32(uint8_t* p_begin, int i_count, uint32_t i_crc) {
|
||||
static uint32_t CRC32[256] = {
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9,
|
||||
0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61,
|
||||
0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9,
|
||||
0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011,
|
||||
0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039,
|
||||
0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81,
|
||||
0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49,
|
||||
0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1,
|
||||
0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae,
|
||||
0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16,
|
||||
0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde,
|
||||
0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066,
|
||||
0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e,
|
||||
0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6,
|
||||
0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e,
|
||||
0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686,
|
||||
0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
||||
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637,
|
||||
0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f,
|
||||
0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47,
|
||||
0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
||||
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff,
|
||||
0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
||||
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7,
|
||||
0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f,
|
||||
0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7,
|
||||
0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
||||
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f,
|
||||
0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640,
|
||||
0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
||||
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8,
|
||||
0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
||||
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30,
|
||||
0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088,
|
||||
0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
||||
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0,
|
||||
0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
||||
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18,
|
||||
0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0,
|
||||
0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
||||
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668,
|
||||
0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4
|
||||
};
|
||||
|
||||
/* Calculate the CRC */
|
||||
while (i_count > 0) {
|
||||
i_crc = (i_crc << 8) ^ CRC32[(i_crc >> 24) ^ ((uint32_t) * p_begin) ];
|
||||
p_begin++;
|
||||
i_count--;
|
||||
}
|
||||
|
||||
return(i_crc);
|
||||
}
|
||||
|
||||
uint32_t wvcrc32(uint8_t* p_begin, int i_count) {
|
||||
return(wvrunningcrc32(p_begin, i_count, INIT_CRC32));
|
||||
}
|
||||
16
libwvdrmengine/level3/arm/wvcrc32.h
Normal file
16
libwvdrmengine/level3/arm/wvcrc32.h
Normal file
@@ -0,0 +1,16 @@
|
||||
/*********************************************************************
|
||||
* wvcrc32.h
|
||||
*
|
||||
* (c) Copyright 2011-2012 Google, Inc.
|
||||
*
|
||||
* Compte CRC32 Checksum. Needed for verification of WV Keybox.
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef WV_CRC_32_H_
|
||||
#define WV_CRC_32_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
uint32_t wvcrc32(uint8_t* p_begin, int i_count);
|
||||
|
||||
#endif // WV_CRC_32_H_
|
||||
20
libwvdrmengine/mediacrypto/Android.mk
Normal file
20
libwvdrmengine/mediacrypto/Android.mk
Normal file
@@ -0,0 +1,20 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
src/WVCryptoPlugin.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/stlport/stlport \
|
||||
frameworks/av/include \
|
||||
frameworks/native/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/core/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/include \
|
||||
vendor/widevine/libwvdrmengine/mediacrypto/include \
|
||||
|
||||
LOCAL_MODULE := libwvdrmcryptoplugin
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
37
libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h
Normal file
37
libwvdrmengine/mediacrypto/include/WVCryptoPlugin.h
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_CRYPTO_PLUGIN_H_
|
||||
#define WV_CRYPTO_PLUGIN_H_
|
||||
|
||||
#include "media/hardware/CryptoAPI.h"
|
||||
#include "media/stagefright/foundation/ABase.h"
|
||||
#include "media/stagefright/foundation/AString.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
class WVCryptoPlugin : public android::CryptoPlugin {
|
||||
public:
|
||||
WVCryptoPlugin(const void* data, size_t size,
|
||||
wvcdm::WvContentDecryptionModule* cdm);
|
||||
virtual ~WVCryptoPlugin() {}
|
||||
|
||||
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,
|
||||
const SubSample* subSamples, size_t numSubSamples,
|
||||
void* dstPtr, android::AString* errorDetailMsg);
|
||||
|
||||
private:
|
||||
DISALLOW_EVIL_CONSTRUCTORS(WVCryptoPlugin);
|
||||
|
||||
wvcdm::WvContentDecryptionModule* const mCDM;
|
||||
const wvcdm::CdmSessionId mSessionId;
|
||||
};
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
#endif // WV_CRYPTO_PLUGIN_H_
|
||||
106
libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp
Normal file
106
libwvdrmengine/mediacrypto/src/WVCryptoPlugin.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
//#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "WVCdm"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "WVCryptoPlugin.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/Errors.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace wvcdm;
|
||||
|
||||
WVCryptoPlugin::WVCryptoPlugin(const void* data, size_t size,
|
||||
WvContentDecryptionModule* cdm)
|
||||
: mCDM(cdm),
|
||||
mSessionId(static_cast<const char*>(data), size) {}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Returns negative values for error code and
|
||||
// positive values for the size of decrypted data. In theory, the output size
|
||||
// can be larger than the input size, but in practice this should never happen
|
||||
// for AES-CTR.
|
||||
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) {
|
||||
if (mode != kMode_Unencrypted && mode != kMode_AES_CTR) {
|
||||
return BAD_TYPE;
|
||||
}
|
||||
|
||||
if (secure) {
|
||||
// TODO: Can't do secure in the Demo 2 milestone
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
// Convert parameters to the form the CDM wishes to consume them in.
|
||||
const KeyId keyId(reinterpret_cast<const char*>(key), KEY_ID_SIZE);
|
||||
const vector<uint8_t> ivVector(iv, iv + KEY_IV_SIZE);
|
||||
const uint8_t* const source = static_cast<const uint8_t*>(srcPtr);
|
||||
uint8_t* const dest = static_cast<uint8_t*>(dstPtr);
|
||||
|
||||
// Iterate through subsamples, sending them to the CDM serially.
|
||||
size_t offset = 0;
|
||||
|
||||
for (size_t i = 0; i < numSubSamples; ++i) {
|
||||
const SubSample &subSample = subSamples[i];
|
||||
|
||||
if (mode == kMode_Unencrypted && subSample.mNumBytesOfEncryptedData != 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
// "Decrypt" any unencrypted data. Per the ISO-CENC standard, clear data
|
||||
// comes before encrypted data.
|
||||
if (subSample.mNumBytesOfClearData != 0) {
|
||||
CdmResponseType res = mCDM->Decrypt(mSessionId, false, keyId,
|
||||
source + offset,
|
||||
subSample.mNumBytesOfClearData,
|
||||
ivVector, offset % 16, dest + offset);
|
||||
|
||||
if (res != wvcdm::NO_ERROR) {
|
||||
ALOGE("Decrypt error result in session %s during unencrypted block: %d",
|
||||
mSessionId.c_str(), res);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset += subSample.mNumBytesOfClearData;
|
||||
}
|
||||
|
||||
// Decrypt any encrypted data. Per the ISO-CENC standard, encrypted data
|
||||
// comes after clear data.
|
||||
if (subSample.mNumBytesOfEncryptedData != 0) {
|
||||
CdmResponseType res = mCDM->Decrypt(mSessionId, true, keyId,
|
||||
source + offset,
|
||||
subSample.mNumBytesOfEncryptedData,
|
||||
ivVector, offset % 16, dest + offset);
|
||||
|
||||
if (res != wvcdm::NO_ERROR) {
|
||||
ALOGE("Decrypt error result in session %s during encrypted block: %d",
|
||||
mSessionId.c_str(), res);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
offset += subSample.mNumBytesOfEncryptedData;
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(offset);
|
||||
}
|
||||
|
||||
} // namespace wvdrm
|
||||
54
libwvdrmengine/mediacrypto/test/Android.mk
Normal file
54
libwvdrmengine/mediacrypto/test/Android.mk
Normal file
@@ -0,0 +1,54 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
WVCryptoPlugin_test.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/gtest/include \
|
||||
external/stlport/stlport \
|
||||
frameworks/av/include \
|
||||
frameworks/native/include \
|
||||
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 := \
|
||||
libcdm \
|
||||
libgmock \
|
||||
libgmock_main \
|
||||
libgtest \
|
||||
libprotobuf-cpp-2.3.0-lite \
|
||||
libwvdrmcryptoplugin \
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
liblog \
|
||||
liboemcrypto \
|
||||
libstlport \
|
||||
libutils \
|
||||
|
||||
# CDM's protobuffers are not part of the library
|
||||
PROTO_SRC_DIR := $(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src
|
||||
|
||||
LOCAL_SRC_FILES += \
|
||||
$(PROTO_SRC_DIR)/license_protocol.pb.cc \
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src \
|
||||
external/protobuf/src \
|
||||
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES += $(proto_generated_headers)
|
||||
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES := \
|
||||
license_protocol_protos \
|
||||
|
||||
# End protobuf section
|
||||
|
||||
LOCAL_MODULE := libwvdrmmediacrypto_test
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
28
libwvdrmengine/mediacrypto/test/MockCDM.h
Normal file
28
libwvdrmengine/mediacrypto/test/MockCDM.h
Normal file
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_CRYPTO_PLUGIN_MOCK_CDM_H_
|
||||
#define WV_CRYPTO_PLUGIN_MOCK_CDM_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "wv_cdm_types.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class MockCDM : public WvContentDecryptionModule {
|
||||
public:
|
||||
MOCK_METHOD8(Decrypt, CdmResponseType(const CdmSessionId&, bool, const KeyId&,
|
||||
const uint8_t*, size_t,
|
||||
const std::vector<uint8_t>&, size_t,
|
||||
void*));
|
||||
};
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
#endif // WV_CRYPTO_PLUGIN_MOCK_CDM_H_
|
||||
118
libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp
Normal file
118
libwvdrmengine/mediacrypto/test/WVCryptoPlugin_test.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#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 "WVCryptoPlugin.h"
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
using namespace wvcdm;
|
||||
using namespace wvdrm;
|
||||
|
||||
class WVCryptoPluginTest : public Test {
|
||||
protected:
|
||||
static const uint32_t kSessionIdSize = 16;
|
||||
uint8_t sessionId[kSessionIdSize];
|
||||
|
||||
uint8_t keyId[KEY_ID_SIZE];
|
||||
uint8_t iv[KEY_IV_SIZE];
|
||||
|
||||
static const uint32_t kDataSize = 64;
|
||||
uint8_t in[kDataSize];
|
||||
uint8_t out[kDataSize];
|
||||
|
||||
static const uint32_t kSubSampleCount = 3;
|
||||
CryptoPlugin::SubSample subSamples[kSubSampleCount];
|
||||
|
||||
virtual void SetUp() {
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(sessionId, sizeof(uint8_t), kSessionIdSize, fp);
|
||||
fread(keyId, sizeof(uint8_t), KEY_ID_SIZE, fp);
|
||||
fread(iv, sizeof(uint8_t), KEY_IV_SIZE, fp);
|
||||
fread(in, sizeof(uint8_t), kDataSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
memset(out, 0, sizeof(out));
|
||||
|
||||
memset(subSamples, 0, sizeof(subSamples));
|
||||
subSamples[0].mNumBytesOfEncryptedData = 16;
|
||||
subSamples[1].mNumBytesOfClearData = 16;
|
||||
subSamples[1].mNumBytesOfEncryptedData = 24;
|
||||
subSamples[2].mNumBytesOfEncryptedData = 8;
|
||||
|
||||
// Set default CdmResponseType value for gMock
|
||||
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(WVCryptoPluginTest, CorrectlyReportsSecureBuffers) {
|
||||
MockCDM cdm;
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm);
|
||||
|
||||
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("video/mp4")) <<
|
||||
"WVCryptoPlugin incorrectly expects a secure video decoder";
|
||||
EXPECT_FALSE(plugin.requiresSecureDecoderComponent("audio/aac")) <<
|
||||
"WVCryptoPlugin incorrectly expects a secure audio decoder";
|
||||
}
|
||||
|
||||
TEST_F(WVCryptoPluginTest, RejectsSecureDecode) {
|
||||
MockCDM cdm;
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm);
|
||||
|
||||
// Decrypt should not be called because we specified an unsupported
|
||||
// security level
|
||||
EXPECT_CALL(cdm, Decrypt(_, _, _, _, _, _, _, _))
|
||||
.Times(0);
|
||||
|
||||
ssize_t res = plugin.decrypt(true, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
in, subSamples, kSubSampleCount, out, NULL);
|
||||
|
||||
EXPECT_EQ(static_cast<ssize_t>(-EPERM), res) <<
|
||||
"WVCryptoPlugin allowed decryption to proceed despite being asked for an "
|
||||
"unsupported security level";
|
||||
}
|
||||
|
||||
TEST_F(WVCryptoPluginTest, AttemptsToDecrypt) {
|
||||
MockCDM cdm;
|
||||
WVCryptoPlugin plugin(sessionId, kSessionIdSize, &cdm);
|
||||
|
||||
// Specify the expected calls to Decrypt
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize), true,
|
||||
ElementsAreArray(keyId, KEY_ID_SIZE), in, 16,
|
||||
ElementsAreArray(iv, KEY_IV_SIZE), 0, out))
|
||||
.WillOnce(Return(wvcdm::NO_ERROR));
|
||||
|
||||
EXPECT_CALL(cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize), false,
|
||||
ElementsAreArray(keyId, KEY_ID_SIZE), in + 16, 16,
|
||||
ElementsAreArray(iv, KEY_IV_SIZE), 0, out + 16))
|
||||
.WillOnce(Return(wvcdm::NO_ERROR));
|
||||
|
||||
EXPECT_CALL(cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize), true,
|
||||
ElementsAreArray(keyId, KEY_ID_SIZE), in + 32, 24,
|
||||
ElementsAreArray(iv, KEY_IV_SIZE), 0, out + 32))
|
||||
.WillOnce(Return(wvcdm::NO_ERROR));
|
||||
|
||||
EXPECT_CALL(cdm, Decrypt(ElementsAreArray(sessionId, kSessionIdSize), true,
|
||||
ElementsAreArray(keyId, KEY_ID_SIZE), in + 56, 8,
|
||||
ElementsAreArray(iv, KEY_IV_SIZE), 8, out + 56))
|
||||
.WillOnce(Return(wvcdm::NO_ERROR));
|
||||
}
|
||||
|
||||
ssize_t res = plugin.decrypt(false, keyId, iv, CryptoPlugin::kMode_AES_CTR,
|
||||
in, subSamples, kSubSampleCount, out, NULL);
|
||||
|
||||
EXPECT_EQ(static_cast<ssize_t>(kDataSize), res) <<
|
||||
"WVCryptoPlugin decrypted the wrong number of bytes";
|
||||
}
|
||||
20
libwvdrmengine/mediadrm/Android.mk
Normal file
20
libwvdrmengine/mediadrm/Android.mk
Normal file
@@ -0,0 +1,20 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
src/WVDrmPlugin.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/stlport/stlport \
|
||||
frameworks/av/include \
|
||||
frameworks/native/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/core/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/include \
|
||||
vendor/widevine/libwvdrmengine/mediadrm/include \
|
||||
|
||||
LOCAL_MODULE := libwvdrmdrmplugin
|
||||
|
||||
LOCAL_MODULE_TAGS := optional
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
80
libwvdrmengine/mediadrm/include/WVDrmPlugin.h
Normal file
80
libwvdrmengine/mediadrm/include/WVDrmPlugin.h
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_DRM_PLUGIN_H_
|
||||
#define WV_DRM_PLUGIN_H_
|
||||
|
||||
#include "media/drm/DrmAPI.h"
|
||||
#include "media/stagefright/foundation/ABase.h"
|
||||
#include "media/stagefright/foundation/AString.h"
|
||||
#include "utils/Errors.h"
|
||||
#include "utils/KeyedVector.h"
|
||||
#include "utils/List.h"
|
||||
#include "utils/String8.h"
|
||||
#include "utils/Vector.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
using android::KeyedVector;
|
||||
using android::List;
|
||||
using android::status_t;
|
||||
using android::String8;
|
||||
using android::Vector;
|
||||
|
||||
class WVDrmPlugin : public android::DrmPlugin {
|
||||
public:
|
||||
WVDrmPlugin(wvcdm::WvContentDecryptionModule* cdm);
|
||||
virtual ~WVDrmPlugin() {}
|
||||
|
||||
virtual status_t openSession(Vector<uint8_t>& sessionId);
|
||||
|
||||
virtual status_t closeSession(const Vector<uint8_t>& sessionId);
|
||||
|
||||
virtual status_t getLicenseRequest(
|
||||
const Vector<uint8_t>& sessionId,
|
||||
const Vector<uint8_t>& initData,
|
||||
const String8& mimeType,
|
||||
LicenseType licenseType,
|
||||
const KeyedVector<String8, String8>& optionalParameters,
|
||||
Vector<uint8_t>& request,
|
||||
String8& defaultUrl);
|
||||
|
||||
virtual status_t provideLicenseResponse(const Vector<uint8_t>& sessionId,
|
||||
const Vector<uint8_t>& response);
|
||||
|
||||
virtual status_t removeLicense(const Vector<uint8_t>& sessionId);
|
||||
|
||||
virtual status_t queryLicenseStatus(
|
||||
const Vector<uint8_t>& sessionId,
|
||||
KeyedVector<String8, String8>& infoMap) const;
|
||||
|
||||
virtual status_t getProvisionRequest(Vector<uint8_t>& request,
|
||||
String8& defaultUrl);
|
||||
|
||||
virtual status_t provideProvisionResponse(const Vector<uint8_t>& response);
|
||||
|
||||
virtual status_t getSecureStops(List<Vector<uint8_t> >& secureStops);
|
||||
|
||||
virtual status_t releaseSecureStops(const Vector<uint8_t>& ssRelease);
|
||||
|
||||
virtual status_t getPropertyString(const String8& name, String8& value) const;
|
||||
|
||||
virtual status_t getPropertyByteArray(const String8& name,
|
||||
Vector<uint8_t>& value) const;
|
||||
|
||||
virtual status_t setPropertyString(const String8& name, const String8& value);
|
||||
|
||||
virtual status_t setPropertyByteArray(const String8& name,
|
||||
const Vector<uint8_t>& value);
|
||||
|
||||
private:
|
||||
DISALLOW_EVIL_CONSTRUCTORS(WVDrmPlugin);
|
||||
|
||||
wvcdm::WvContentDecryptionModule* mCDM;
|
||||
};
|
||||
|
||||
} // namespace wvdrm
|
||||
|
||||
#endif // WV_DRM_PLUGIN_H_
|
||||
257
libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp
Normal file
257
libwvdrmengine/mediadrm/src/WVDrmPlugin.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
//#define LOG_NDEBUG 0
|
||||
#define LOG_TAG "WVCdm"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "WVDrmPlugin.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "utils/Errors.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvdrm {
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace wvcdm;
|
||||
|
||||
WVDrmPlugin::WVDrmPlugin(WvContentDecryptionModule* cdm) : mCDM(cdm) {}
|
||||
|
||||
status_t WVDrmPlugin::openSession(Vector<uint8_t>& sessionId) {
|
||||
CdmSessionId cdmSessionId;
|
||||
CdmResponseType res = mCDM->OpenSession("com.widevine", &cdmSessionId);
|
||||
|
||||
if (res != wvcdm::NO_ERROR) {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
sessionId.clear();
|
||||
sessionId.appendArray(reinterpret_cast<const uint8_t*>(cdmSessionId.data()),
|
||||
cdmSessionId.size());
|
||||
|
||||
return android::OK;
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::closeSession(const Vector<uint8_t>& sessionId) {
|
||||
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
|
||||
CdmResponseType res = mCDM->CloseSession(cdmSessionId);
|
||||
|
||||
if (res == wvcdm::NO_ERROR) {
|
||||
return android::OK;
|
||||
} else {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::getLicenseRequest(
|
||||
const Vector<uint8_t>& sessionId,
|
||||
const Vector<uint8_t>& initData,
|
||||
const String8& mimeType,
|
||||
LicenseType licenseType,
|
||||
const KeyedVector<String8, String8>& optionalParameters,
|
||||
Vector<uint8_t>& request,
|
||||
String8& defaultUrl) {
|
||||
CdmLicenseType cdmLicenseType;
|
||||
if (licenseType == kLicenseType_Offline) {
|
||||
cdmLicenseType = kLicenseTypeOffline;
|
||||
} else if (licenseType == kLicenseType_Streaming) {
|
||||
cdmLicenseType = kLicenseTypeStreaming;
|
||||
} else {
|
||||
return BAD_TYPE;
|
||||
}
|
||||
|
||||
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
|
||||
CdmInitData cdmInitData(initData.begin(), initData.end());
|
||||
// TODO: Do something with mimeType?
|
||||
|
||||
CdmNameValueMap cdmParameters;
|
||||
for (size_t i = 0; i < optionalParameters.size(); ++i) {
|
||||
const String8& key = optionalParameters.keyAt(i);
|
||||
const String8& value = optionalParameters.valueAt(i);
|
||||
|
||||
string cdmKey(key.string(), key.size());
|
||||
string cdmValue(value.string(), value.size());
|
||||
|
||||
cdmParameters[cdmKey] = cdmValue;
|
||||
}
|
||||
|
||||
CdmKeyMessage keyRequest;
|
||||
|
||||
CdmResponseType res = mCDM->GenerateKeyRequest(cdmSessionId, cdmInitData,
|
||||
cdmLicenseType,
|
||||
cdmParameters, &keyRequest);
|
||||
|
||||
if (res != wvcdm::KEY_MESSAGE) {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
// TODO: Do something more with defaultUrl?
|
||||
defaultUrl.clear();
|
||||
|
||||
request.clear();
|
||||
request.appendArray(reinterpret_cast<const uint8_t*>(keyRequest.data()),
|
||||
keyRequest.size());
|
||||
|
||||
return android::OK;
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::provideLicenseResponse(
|
||||
const Vector<uint8_t>& sessionId,
|
||||
const Vector<uint8_t>& response) {
|
||||
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
|
||||
CdmKeyResponse cdmResponse(response.begin(), response.end());
|
||||
|
||||
CdmResponseType res = mCDM->AddKey(cdmSessionId, cdmResponse);
|
||||
|
||||
if (res == wvcdm::KEY_ADDED || res == wvcdm::NO_ERROR) {
|
||||
return android::OK;
|
||||
} else {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::removeLicense(const Vector<uint8_t>& sessionId) {
|
||||
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
|
||||
|
||||
CdmResponseType res = mCDM->CancelKeyRequest(cdmSessionId);
|
||||
|
||||
if (res == wvcdm::NO_ERROR) {
|
||||
return android::OK;
|
||||
} else {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::queryLicenseStatus(
|
||||
const Vector<uint8_t>& sessionId,
|
||||
KeyedVector<String8, String8>& infoMap) const {
|
||||
CdmSessionId cdmSessionId(sessionId.begin(), sessionId.end());
|
||||
CdmNameValueMap cdmLicenseInfo;
|
||||
|
||||
CdmResponseType res = mCDM->QueryKeyStatus(cdmSessionId, &cdmLicenseInfo);
|
||||
|
||||
if (res != wvcdm::NO_ERROR) {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
infoMap.clear();
|
||||
for (CdmNameValueMap::const_iterator iter = cdmLicenseInfo.begin();
|
||||
iter != cdmLicenseInfo.end();
|
||||
++iter) {
|
||||
const string& cdmKey = iter->first;
|
||||
const string& cdmValue = iter->second;
|
||||
|
||||
String8 key(cdmKey.data(), cdmKey.size());
|
||||
String8 value(cdmValue.data(), cdmValue.size());
|
||||
|
||||
infoMap.add(key, value);
|
||||
}
|
||||
|
||||
return android::OK;
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::getProvisionRequest(Vector<uint8_t>& request,
|
||||
String8& defaultUrl) {
|
||||
CdmProvisioningRequest cdmProvisionRequest;
|
||||
string cdmDefaultUrl;
|
||||
|
||||
CdmResponseType res = mCDM->GetProvisioningRequest(&cdmProvisionRequest,
|
||||
&cdmDefaultUrl);
|
||||
|
||||
if (res != wvcdm::NO_ERROR) {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
request.clear();
|
||||
request.appendArray(reinterpret_cast<const uint8_t*>(
|
||||
cdmProvisionRequest.data()),
|
||||
cdmProvisionRequest.size());
|
||||
|
||||
defaultUrl.clear();
|
||||
defaultUrl.setTo(cdmDefaultUrl.data(), cdmDefaultUrl.size());
|
||||
|
||||
return android::OK;
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::provideProvisionResponse(
|
||||
const Vector<uint8_t>& response) {
|
||||
CdmProvisioningResponse cdmResponse(response.begin(), response.end());
|
||||
|
||||
CdmResponseType res = mCDM->HandleProvisioningResponse(cdmResponse);
|
||||
|
||||
if (res == wvcdm::NO_ERROR) {
|
||||
return android::OK;
|
||||
} else {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::getSecureStops(List<Vector<uint8_t> >& secureStops) {
|
||||
CdmSecureStops cdmSecureStops;
|
||||
|
||||
CdmResponseType res = mCDM->GetSecureStops(&cdmSecureStops);
|
||||
|
||||
if (res != wvcdm::NO_ERROR) {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
secureStops.clear();
|
||||
for (CdmSecureStops::const_iterator iter = cdmSecureStops.begin();
|
||||
iter != cdmSecureStops.end();
|
||||
++iter) {
|
||||
const string& cdmStop = *iter;
|
||||
|
||||
Vector<uint8_t> stop;
|
||||
stop.appendArray(reinterpret_cast<const uint8_t*>(cdmStop.data()),
|
||||
cdmStop.size());
|
||||
|
||||
secureStops.push_back(stop);
|
||||
}
|
||||
|
||||
return android::OK;
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::releaseSecureStops(const Vector<uint8_t>& ssRelease) {
|
||||
CdmSecureStopReleaseMessage cdmMessage(ssRelease.begin(), ssRelease.end());
|
||||
|
||||
CdmResponseType res = mCDM->ReleaseSecureStops(cdmMessage);
|
||||
|
||||
if (res == wvcdm::NO_ERROR) {
|
||||
return android::OK;
|
||||
} else {
|
||||
return android::UNKNOWN_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::getPropertyString(const String8& name,
|
||||
String8& value) const {
|
||||
// TODO: Implement this function once the CDM query API is finalized.
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::getPropertyByteArray(const String8& name,
|
||||
Vector<uint8_t>& value) const {
|
||||
// TODO: Implement this function once the CDM query API is finalized.
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::setPropertyString(const String8& name,
|
||||
const String8& value) {
|
||||
// TODO: Implement this function once the CDM query API is finalized.
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
status_t WVDrmPlugin::setPropertyByteArray(const String8& name,
|
||||
const Vector<uint8_t>& value) {
|
||||
// TODO: Implement this function once the CDM query API is finalized.
|
||||
return -EPERM;
|
||||
}
|
||||
|
||||
// TODO: Hook up to event listener methods on CDM once Android API for
|
||||
// eventing is finalized.
|
||||
} // namespace wvdrm
|
||||
54
libwvdrmengine/mediadrm/test/Android.mk
Normal file
54
libwvdrmengine/mediadrm/test/Android.mk
Normal file
@@ -0,0 +1,54 @@
|
||||
LOCAL_PATH := $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
WVDrmPlugin_test.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
bionic \
|
||||
external/gtest/include \
|
||||
external/stlport/stlport \
|
||||
frameworks/av/include \
|
||||
frameworks/native/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/core/include \
|
||||
vendor/widevine/libwvdrmengine/cdm/include \
|
||||
vendor/widevine/libwvdrmengine/mediadrm/include \
|
||||
vendor/widevine/libwvdrmengine/mediadrm/test \
|
||||
vendor/widevine/libwvdrmengine/test/gmock/include \
|
||||
|
||||
LOCAL_STATIC_LIBRARIES := \
|
||||
libcdm \
|
||||
libgmock \
|
||||
libgmock_main \
|
||||
libgtest \
|
||||
libprotobuf-cpp-2.3.0-lite \
|
||||
libwvdrmdrmplugin \
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
liblog \
|
||||
liboemcrypto \
|
||||
libstlport \
|
||||
libutils \
|
||||
|
||||
# CDM's protobuffers are not part of the library
|
||||
PROTO_SRC_DIR := $(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src
|
||||
|
||||
LOCAL_SRC_FILES += \
|
||||
$(PROTO_SRC_DIR)/license_protocol.pb.cc \
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(proto_generated_cc_sources_dir)/$(LOCAL_PATH)/core/src \
|
||||
external/protobuf/src \
|
||||
|
||||
LOCAL_ADDITIONAL_DEPENDENCIES += $(proto_generated_headers)
|
||||
|
||||
LOCAL_WHOLE_STATIC_LIBRARIES := \
|
||||
license_protocol_protos \
|
||||
|
||||
# End protobuf section
|
||||
|
||||
LOCAL_MODULE := libwvdrmdrmplugin_test
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
include $(BUILD_EXECUTABLE)
|
||||
53
libwvdrmengine/mediadrm/test/MockCDM.h
Normal file
53
libwvdrmengine/mediadrm/test/MockCDM.h
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#ifndef WV_DRM_PLUGIN_MOCK_CDM_H_
|
||||
#define WV_DRM_PLUGIN_MOCK_CDM_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gmock/gmock.h"
|
||||
#include "wv_cdm_types.h"
|
||||
#include "wv_content_decryption_module.h"
|
||||
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class MockCDM : public WvContentDecryptionModule {
|
||||
public:
|
||||
MOCK_METHOD2(OpenSession, CdmResponseType(const CdmKeySystem&,
|
||||
CdmSessionId*));
|
||||
|
||||
MOCK_METHOD1(CloseSession, CdmResponseType(CdmSessionId&));
|
||||
|
||||
MOCK_METHOD5(GenerateKeyRequest, CdmResponseType(const CdmSessionId&,
|
||||
const CdmInitData&,
|
||||
const CdmLicenseType,
|
||||
CdmNameValueMap&,
|
||||
CdmKeyMessage*));
|
||||
|
||||
MOCK_METHOD2(AddKey, CdmResponseType(const CdmSessionId&,
|
||||
const CdmKeyResponse&));
|
||||
|
||||
MOCK_METHOD1(CancelKeyRequest, CdmResponseType(const CdmSessionId&));
|
||||
|
||||
MOCK_METHOD2(QueryKeyStatus, CdmResponseType(const CdmSessionId&,
|
||||
CdmNameValueMap*));
|
||||
|
||||
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_
|
||||
372
libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp
Normal file
372
libwvdrmengine/mediadrm/test/WVDrmPlugin_test.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
//
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
|
||||
#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 "WVDrmPlugin.h"
|
||||
|
||||
using namespace android;
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
using namespace wvcdm;
|
||||
using namespace wvdrm;
|
||||
|
||||
class WVDrmPluginTest : public Test {
|
||||
protected:
|
||||
static const uint32_t kSessionIdSize = 16;
|
||||
Vector<uint8_t> sessionId;
|
||||
CdmSessionId cdmSessionId;
|
||||
|
||||
virtual void SetUp() {
|
||||
uint8_t sessionIdRaw[kSessionIdSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(sessionIdRaw, sizeof(uint8_t), kSessionIdSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
sessionId.appendArray(sessionIdRaw, kSessionIdSize);
|
||||
cdmSessionId.assign(sessionId.begin(), sessionId.end());
|
||||
|
||||
// Set default CdmResponseType value for gMock
|
||||
DefaultValue<CdmResponseType>::Set(wvcdm::NO_ERROR);
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
EXPECT_CALL(cdm, OpenSession(StrEq("com.widevine"), _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(cdmSessionId),
|
||||
Return(wvcdm::NO_ERROR)));
|
||||
|
||||
status_t res = plugin.openSession(sessionId);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(sessionId, ElementsAreArray(sessionIdRaw, kSessionIdSize));
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, ClosesSessions) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
EXPECT_CALL(cdm, CloseSession(cdmSessionId))
|
||||
.Times(1);
|
||||
|
||||
status_t res = plugin.closeSession(sessionId);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, GeneratesKeyRequests) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
static const uint32_t kInitDataSize = 128;
|
||||
uint8_t initDataRaw[kInitDataSize];
|
||||
static const uint32_t kRequestSize = 256;
|
||||
uint8_t requestRaw[kRequestSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(initDataRaw, sizeof(uint8_t), kInitDataSize, fp);
|
||||
fread(requestRaw, sizeof(uint8_t), kRequestSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
Vector<uint8_t> initData;
|
||||
initData.appendArray(initDataRaw, kInitDataSize);
|
||||
|
||||
CdmKeyMessage cdmRequest(requestRaw, requestRaw + kRequestSize);
|
||||
Vector<uint8_t> request;
|
||||
|
||||
KeyedVector<String8, String8> parameters;
|
||||
CdmNameValueMap cdmParameters;
|
||||
|
||||
parameters.add(String8("paddingScheme"), String8("PKCS7"));
|
||||
cdmParameters["paddingScheme"] = "PKCS7";
|
||||
parameters.add(String8("favoriteParticle"), String8("tetraquark"));
|
||||
cdmParameters["favoriteParticle"] = "tetraquark";
|
||||
parameters.add(String8("answer"), String8("42"));
|
||||
cdmParameters["answer"] = "42";
|
||||
|
||||
String8 defaultUrl;
|
||||
|
||||
{
|
||||
InSequence calls;
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeOffline, cdmParameters, _))
|
||||
.WillOnce(DoAll(SetArgPointee<4>(cdmRequest),
|
||||
Return(wvcdm::NO_ERROR)));
|
||||
|
||||
EXPECT_CALL(cdm, GenerateKeyRequest(cdmSessionId,
|
||||
ElementsAreArray(initDataRaw,
|
||||
kInitDataSize),
|
||||
kLicenseTypeStreaming, cdmParameters,
|
||||
_))
|
||||
.WillOnce(DoAll(SetArgPointee<4>(cdmRequest),
|
||||
Return(wvcdm::NO_ERROR)));
|
||||
}
|
||||
|
||||
status_t res = plugin.getLicenseRequest(sessionId, initData,
|
||||
String8("video/h264"),
|
||||
DrmPlugin::kLicenseType_Offline,
|
||||
parameters, request, defaultUrl);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_TRUE(defaultUrl.isEmpty());
|
||||
|
||||
res = plugin.getLicenseRequest(sessionId, initData, String8("video/h264"),
|
||||
DrmPlugin::kLicenseType_Streaming, parameters,
|
||||
request, defaultUrl);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_TRUE(defaultUrl.isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, AddsKeys) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
static const uint32_t kResponseSize = 256;
|
||||
uint8_t responseRaw[kResponseSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(responseRaw, sizeof(uint8_t), kResponseSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
Vector<uint8_t> response;
|
||||
response.appendArray(responseRaw, kResponseSize);
|
||||
|
||||
EXPECT_CALL(cdm, AddKey(cdmSessionId, ElementsAreArray(responseRaw,
|
||||
kResponseSize)))
|
||||
.Times(1);
|
||||
|
||||
status_t res = plugin.provideLicenseResponse(sessionId, response);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, CancelsKeyRequests) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
EXPECT_CALL(cdm, CancelKeyRequest(cdmSessionId))
|
||||
.Times(1);
|
||||
|
||||
status_t res = plugin.removeLicense(sessionId);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, QueriesKeyStatus) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
KeyedVector<String8, String8> expectedLicenseStatus;
|
||||
CdmNameValueMap cdmLicenseStatus;
|
||||
|
||||
expectedLicenseStatus.add(String8("areTheKeysAllRight"), String8("yes"));
|
||||
cdmLicenseStatus["areTheKeysAllRight"] = "yes";
|
||||
expectedLicenseStatus.add(String8("isGMockAwesome"), String8("ohhhhhhYeah"));
|
||||
cdmLicenseStatus["isGMockAwesome"] = "ohhhhhhYeah";
|
||||
expectedLicenseStatus.add(String8("answer"), String8("42"));
|
||||
cdmLicenseStatus["answer"] = "42";
|
||||
|
||||
EXPECT_CALL(cdm, QueryKeyStatus(cdmSessionId, _))
|
||||
.WillOnce(DoAll(SetArgPointee<1>(cdmLicenseStatus),
|
||||
Return(wvcdm::NO_ERROR)));
|
||||
|
||||
KeyedVector<String8, String8> licenseStatus;
|
||||
|
||||
status_t res = plugin.queryLicenseStatus(sessionId, licenseStatus);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
|
||||
ASSERT_EQ(expectedLicenseStatus.size(), licenseStatus.size());
|
||||
for (size_t i = 0; i < expectedLicenseStatus.size(); ++i) {
|
||||
const String8& key = expectedLicenseStatus.keyAt(i);
|
||||
EXPECT_NE(android::NAME_NOT_FOUND, licenseStatus.indexOfKey(key));
|
||||
EXPECT_EQ(expectedLicenseStatus.valueFor(key), licenseStatus.valueFor(key));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, GetsProvisioningRequests) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
static const uint32_t kRequestSize = 256;
|
||||
uint8_t requestRaw[kRequestSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(requestRaw, sizeof(uint8_t), kRequestSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
CdmProvisioningRequest cdmRequest(requestRaw, requestRaw + kRequestSize);
|
||||
|
||||
static const char* kDefaultUrl = "http://google.com/";
|
||||
|
||||
EXPECT_CALL(cdm, GetProvisioningRequest(_, _))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(cdmRequest),
|
||||
SetArgPointee<1>(kDefaultUrl),
|
||||
Return(wvcdm::NO_ERROR)));
|
||||
|
||||
Vector<uint8_t> request;
|
||||
String8 defaultUrl;
|
||||
|
||||
status_t res = plugin.getProvisionRequest(request, defaultUrl);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
EXPECT_THAT(request, ElementsAreArray(requestRaw, kRequestSize));
|
||||
EXPECT_EQ(String8(kDefaultUrl), defaultUrl);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, HandlesProvisioningResponses) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
static const uint32_t kResponseSize = 512;
|
||||
uint8_t responseRaw[kResponseSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(responseRaw, sizeof(uint8_t), kResponseSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
Vector<uint8_t> response;
|
||||
response.appendArray(responseRaw, kResponseSize);
|
||||
|
||||
EXPECT_CALL(cdm, HandleProvisioningResponse(ElementsAreArray(responseRaw,
|
||||
kResponseSize)))
|
||||
.Times(1);
|
||||
|
||||
status_t res = plugin.provideProvisionResponse(response);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, GetsSecureStops) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
static const uint32_t kStopSize = 53;
|
||||
static const uint32_t kStopCount = 7;
|
||||
uint8_t stopsRaw[kStopCount][kStopSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
for (uint32_t i = 0; i < kStopCount; ++i) {
|
||||
fread(stopsRaw[i], sizeof(uint8_t), kStopSize, fp);
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
CdmSecureStops cdmStops;
|
||||
for (uint32_t i = 0; i < kStopCount; ++i) {
|
||||
cdmStops.push_back(string(stopsRaw[i], stopsRaw[i] + kStopSize));
|
||||
}
|
||||
|
||||
EXPECT_CALL(cdm, GetSecureStops(_))
|
||||
.WillOnce(DoAll(SetArgPointee<0>(cdmStops),
|
||||
Return(wvcdm::NO_ERROR)));
|
||||
|
||||
List<Vector<uint8_t> > stops;
|
||||
|
||||
status_t res = plugin.getSecureStops(stops);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
|
||||
List<Vector<uint8_t> >::iterator iter = stops.begin();
|
||||
uint32_t rawIter = 0;
|
||||
while (rawIter < kStopCount && iter != stops.end()) {
|
||||
EXPECT_THAT(*iter, ElementsAreArray(stopsRaw[rawIter], kStopSize));
|
||||
|
||||
++iter;
|
||||
++rawIter;
|
||||
}
|
||||
// Assert that both lists are the same length
|
||||
EXPECT_EQ(kStopCount, rawIter);
|
||||
EXPECT_EQ(stops.end(), iter);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, ReleasesSecureStops) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
static const uint32_t kMessageSize = 128;
|
||||
uint8_t messageRaw[kMessageSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(messageRaw, sizeof(uint8_t), kMessageSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
Vector<uint8_t> message;
|
||||
message.appendArray(messageRaw, kMessageSize);
|
||||
|
||||
EXPECT_CALL(cdm, ReleaseSecureStops(ElementsAreArray(messageRaw,
|
||||
kMessageSize)))
|
||||
.Times(1);
|
||||
|
||||
status_t res = plugin.releaseSecureStops(message);
|
||||
|
||||
ASSERT_EQ(OK, res);
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, DoesNotGetStringProperties) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
String8 result;
|
||||
|
||||
status_t res = plugin.getPropertyString(String8("property"), result);
|
||||
|
||||
ASSERT_NE(OK, res);
|
||||
EXPECT_TRUE(result.isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, DoesNotGetByteProperties) {
|
||||
MockCDM cdm;
|
||||
WVDrmPlugin plugin(&cdm);
|
||||
|
||||
Vector<uint8_t> result;
|
||||
|
||||
status_t res = plugin.getPropertyByteArray(String8("property"), result);
|
||||
|
||||
ASSERT_NE(OK, res);
|
||||
EXPECT_TRUE(result.isEmpty());
|
||||
}
|
||||
|
||||
TEST_F(WVDrmPluginTest, DoesNotSetStringProperties) {
|
||||
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);
|
||||
|
||||
static const uint32_t kValueSize = 32;
|
||||
uint8_t valueRaw[kValueSize];
|
||||
FILE* fp = fopen("/dev/urandom", "r");
|
||||
fread(valueRaw, sizeof(uint8_t), kValueSize, fp);
|
||||
fclose(fp);
|
||||
|
||||
Vector<uint8_t> value;
|
||||
value.appendArray(valueRaw, kValueSize);
|
||||
|
||||
status_t res = plugin.setPropertyByteArray(String8("property"), value);
|
||||
|
||||
ASSERT_NE(OK, res);
|
||||
}
|
||||
109
libwvdrmengine/oemcrypto/include/L3CryptoCENC.h
Normal file
109
libwvdrmengine/oemcrypto/include/L3CryptoCENC.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*********************************************************************
|
||||
* 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_
|
||||
982
libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h
Normal file
982
libwvdrmengine/oemcrypto/include/OEMCryptoCENC.h
Normal file
@@ -0,0 +1,982 @@
|
||||
/*********************************************************************
|
||||
* OEMCryptoCENC.h
|
||||
*
|
||||
* (c) Copyright 2013 Google, Inc.
|
||||
*
|
||||
* Reference APIs needed to support Widevine's crypto algorithms.
|
||||
*********************************************************************/
|
||||
|
||||
#ifndef OEMCRYPTO_CENC_H_
|
||||
#define OEMCRYPTO_CENC_H_
|
||||
|
||||
#include<stddef.h>
|
||||
#include<stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define OEMCRYPTO_VERSION "5.0"
|
||||
static const char oec_version[] = OEMCRYPTO_VERSION;
|
||||
|
||||
typedef uint32_t OEMCrypto_SESSION;
|
||||
|
||||
typedef enum OEMCryptoResult {
|
||||
OEMCrypto_SUCCESS = 0,
|
||||
OEMCrypto_ERROR_INIT_FAILED = 1,
|
||||
OEMCrypto_ERROR_TERMINATE_FAILED = 2,
|
||||
OEMCrypto_ERROR_OPEN_FAILURE = 3,
|
||||
OEMCrypto_ERROR_CLOSE_FAILURE = 4,
|
||||
OEMCrypto_ERROR_ENTER_SECURE_PLAYBACK_FAILED = 5,
|
||||
OEMCrypto_ERROR_EXIT_SECURE_PLAYBACK_FAILED = 6,
|
||||
OEMCrypto_ERROR_SHORT_BUFFER = 7,
|
||||
OEMCrypto_ERROR_NO_DEVICE_KEY = 8,
|
||||
OEMCrypto_ERROR_NO_ASSET_KEY = 9,
|
||||
OEMCrypto_ERROR_KEYBOX_INVALID = 10,
|
||||
OEMCrypto_ERROR_NO_KEYDATA = 11,
|
||||
OEMCrypto_ERROR_NO_CW = 12,
|
||||
OEMCrypto_ERROR_DECRYPT_FAILED = 13,
|
||||
OEMCrypto_ERROR_WRITE_KEYBOX = 14,
|
||||
OEMCrypto_ERROR_WRAP_KEYBOX = 15,
|
||||
OEMCrypto_ERROR_BAD_MAGIC = 16,
|
||||
OEMCrypto_ERROR_BAD_CRC = 17,
|
||||
OEMCrypto_ERROR_NO_DEVICEID = 18,
|
||||
OEMCrypto_ERROR_RNG_FAILED = 19,
|
||||
OEMCrypto_ERROR_RNG_NOT_SUPPORTED = 20,
|
||||
OEMCrypto_ERROR_SETUP = 21,
|
||||
OEMCrypto_ERROR_OPEN_SESSION_FAILED = 22,
|
||||
OEMCrypto_ERROR_CLOSE_SESSION_FAILED = 23,
|
||||
OEMCrypto_ERROR_INVALID_SESSION = 24,
|
||||
OEMCrypto_ERROR_NOT_IMPLEMENTED = 25,
|
||||
OEMCrypto_ERROR_NO_CONTENT_KEY = 26,
|
||||
OEMCrypto_ERROR_CONTROL_INVALID = 27,
|
||||
OEMCrypto_ERROR_UNKNOWN_FAILURE = 28,
|
||||
OEMCrypto_ERROR_INVALID_CONTEXT = 29,
|
||||
OEMCrypto_ERROR_SIGNATURE_FAILURE = 30,
|
||||
OEMCrypto_ERROR_TOO_MANY_SESSIONS = 31,
|
||||
OEMCrypto_ERROR_INVALID_NONCE = 32,
|
||||
OEMCrypto_ERROR_TOO_MANY_KEYS = 33,
|
||||
OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED = 34,
|
||||
OEMCrypto_ERROR_INVALID_RSA_KEY = 35,
|
||||
} OEMCryptoResult;
|
||||
|
||||
/*
|
||||
* OEMCrypto_DestBufferDesc
|
||||
* Describes the type and access information for the memory to receive
|
||||
* decrypted data.
|
||||
*
|
||||
* The OEMCrypto API supports a range of client device architectures.
|
||||
* Different architectures have different methods for acquiring and securing
|
||||
* buffers that will hold portions of the audio or video stream after
|
||||
* decryption. Three basic strategies are recognized for handling decrypted
|
||||
* stream data:
|
||||
* 1. Return the decrypted data in the clear into normal user memory
|
||||
* (ClearBuffer). The caller uses normal memory allocation methods to
|
||||
* acquire a buffer, and supplies the memory address of the buffer in the
|
||||
* descriptor.
|
||||
* 2. Place the decrypted data into protected memory (SecureBuffer). The
|
||||
* caller uses a platform-specific method to acquire the protected buffer
|
||||
* and a user-memory handle that references it. The handle is supplied
|
||||
* to the decrypt call in the descriptor.
|
||||
* 3. Place the decrypted data directly into the audio or video decoder fifo
|
||||
* (Direct). The caller will use platform-specific methods to initialize
|
||||
* the fifo and the decoders. The decrypted stream data is not accessible
|
||||
* to the caller.
|
||||
*
|
||||
* Specific fields are as follows:
|
||||
*
|
||||
* (type == OEMCrypto_BufferType_Clear)
|
||||
* address - Address of start of user memory buffer.
|
||||
* max_length - Size of user memory buffer.
|
||||
* (type == OEMCrypto_BufferType_Secure)
|
||||
* buffer - handle to a platform-specific secure buffer.
|
||||
* max_length - Size of platform-specific secure buffer.
|
||||
* (type == OEMCrypto_BufferType_Direct)
|
||||
* is_video - If true, decrypted bytes are routed to the video
|
||||
* decoder. If false, decrypted bytes are routed to the
|
||||
* audio decoder.
|
||||
*/
|
||||
typedef enum OEMCryptoBufferType {
|
||||
OEMCrypto_BufferType_Clear,
|
||||
OEMCrypto_BufferType_Secure,
|
||||
OEMCrypto_BufferType_Direct
|
||||
} OEMCrytoBufferType;
|
||||
|
||||
typedef struct {
|
||||
OEMCryptoBufferType type;
|
||||
union {
|
||||
struct { // type == OEMCrypto_BufferType_Clear
|
||||
uint8_t* address;
|
||||
size_t max_length;
|
||||
} clear;
|
||||
struct { // type == OEMCrypto_BufferType_Secure
|
||||
void* handle;
|
||||
size_t max_length;
|
||||
} secure;
|
||||
struct { // type == OEMCrypto_BufferType_Direct
|
||||
bool is_video;
|
||||
} direct;
|
||||
} buffer;
|
||||
} OEMCrypto_DestBufferDesc;
|
||||
|
||||
/*
|
||||
* OEMCrypto_KeyObject
|
||||
* Points to the relevant fields for a content key. The fields are extracted
|
||||
* from the License Response message offered to OEMCrypto_LoadKeys(). Each
|
||||
* field points to one of the components of the key. Key data, key control,
|
||||
* and both IV fields are 128 bits (16 bytes):
|
||||
* key_id - the unique id of this key.
|
||||
* key_id_length - the size of key_id.
|
||||
* key_data_iv - the IV for performing AES-128-CBC decryption of the
|
||||
* key_data field.
|
||||
* key_data - the key data. It is encrypted (AES-128-CBC) with the
|
||||
* session's derived encrypt key and the key_data_iv.
|
||||
* key_control_iv - the IV for performing AES-128-CBC decryption of the
|
||||
* key_control field.
|
||||
* key_control - the key control block. It is encrypted (AES-128-CBC) with
|
||||
* the content key from the key_data field.
|
||||
*
|
||||
* The memory for the OEMCrypto_KeyObject fields is allocated and freed
|
||||
* by the caller of OEMCrypto_LoadKeys().
|
||||
*/
|
||||
typedef struct {
|
||||
const uint8_t* key_id;
|
||||
size_t key_id_length;
|
||||
const uint8_t* key_data_iv;
|
||||
const uint8_t* key_data;
|
||||
const uint8_t* key_control_iv;
|
||||
const uint8_t* key_control;
|
||||
} OEMCrypto_KeyObject;
|
||||
|
||||
/*
|
||||
* OEMCrypto_KeyRefreshObject
|
||||
* Points to the relevant fields for renewing a content key. The fields are
|
||||
* extracted from the License Renewal Response message offered to
|
||||
* OEMCrypto_RefreshKeys(). Each field points to one of the components of
|
||||
* the key. All fields are 128 bits (16 bytes):
|
||||
* key_id - the unique id of this key.
|
||||
* key_control_iv - the IV for performing AES-128-CBC decryption of the
|
||||
* key_control field.
|
||||
* key_control - the key control block. It is encrypted (AES-128-CBC) with
|
||||
* the content key from the key_data field.
|
||||
*
|
||||
* The key_data is unchanged from the original OEMCrypto_LoadKeys() call. Some
|
||||
* Key Control Block fields, especially those related to key lifetime, may
|
||||
* change.
|
||||
*
|
||||
* The memory for the OEMCrypto_KeyRefreshObject fields is allocated and freed
|
||||
* by the caller of OEMCrypto_RefreshKeys().
|
||||
*/
|
||||
typedef struct {
|
||||
const uint8_t* key_id;
|
||||
size_t key_id_length;
|
||||
const uint8_t* key_control_iv;
|
||||
const uint8_t* key_control;
|
||||
} OEMCrypto_KeyRefreshObject;
|
||||
|
||||
#define OEMCrypto_Initialize _oecc01
|
||||
#define OEMCrypto_Terminate _oecc02
|
||||
#define OEMCrypto_InstallKeybox _oecc03
|
||||
#define OEMCrypto_GetKeyData _oecc04
|
||||
#define OEMCrypto_IsKeyboxValid _oecc05
|
||||
#define OEMCrypto_GetRandom _oecc06
|
||||
#define OEMCrypto_GetDeviceID _oecc07
|
||||
#define OEMCrypto_WrapKeybox _oecc08
|
||||
#define OEMCrypto_OpenSession _oecc09
|
||||
#define OEMCrypto_CloseSession _oecc10
|
||||
#define OEMCrypto_DecryptCTR _oecc11
|
||||
#define OEMCrypto_GenerateDerivedKeys _oecc12
|
||||
#define OEMCrypto_GenerateSignature _oecc13
|
||||
#define OEMCrypto_GenerateNonce _oecc14
|
||||
#define OEMCrypto_LoadKeys _oecc15
|
||||
#define OEMCrypto_RefreshKeys _oecc16
|
||||
#define OEMCrypto_SelectKey _oecc17
|
||||
#define OEMCrypto_RewrapDeviceRSAKey _oecc18
|
||||
#define OEMCrypto_LoadDeviceRSAKey _oecc19
|
||||
#define OEMCrypto_GenerateRSASignature _oecc20
|
||||
#define OEMCrypto_DeriveKeysFromSessionKey _oecc21
|
||||
|
||||
/*
|
||||
* OEMCrypto_Initialize
|
||||
*
|
||||
* Description:
|
||||
* Initialize the crypto firmware/hardware.
|
||||
*
|
||||
* Parameters:
|
||||
* N/A
|
||||
*
|
||||
* Threading:
|
||||
* No other function calls will be made while this function is running. This
|
||||
* function will not be called again before OEMCrypto_Terminate.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_INIT_FAILED failed to initialize crypto hardware
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_Initialize(void);
|
||||
|
||||
/*
|
||||
* OEMCrypto_Terminate
|
||||
*
|
||||
* Description:
|
||||
* The API closes the crypto operation and releases all resources used.
|
||||
*
|
||||
* Parameters:
|
||||
* N/A
|
||||
*
|
||||
* Threading:
|
||||
* No other OEMCrypto calls are made while this function is running. After
|
||||
* this function is called, no other OEMCrypto calls will be made until another
|
||||
* call to OEMCrypto_Initialize is made.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_TERMINATE_FAILED failed to de-initialize crypto hardware
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_Terminate(void);
|
||||
|
||||
/*
|
||||
* OEMCrypto_OpenSession
|
||||
*
|
||||
* Description:
|
||||
* The API provides for session based crypto initialization for AES CTR mode.
|
||||
*
|
||||
* Parameters:
|
||||
* session (out) - pointer to crypto session identifier.
|
||||
*
|
||||
* Threading:
|
||||
* No other Open/Close session calls will be made while this function is
|
||||
* running. Functions on existing sessions may be called while this function
|
||||
* is active.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_TOO_MANY_SESSIONS failed because too many sessions are open
|
||||
* OEMCrypto_ERROR_OPEN_SESSION_FAILED failed to initialize the crypto session
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_OpenSession(OEMCrypto_SESSION *session);
|
||||
|
||||
/*
|
||||
* OEMCrypto_CloseSession
|
||||
*
|
||||
* Description:
|
||||
* The API provides for session based crypto termination for AES CTR mode.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
*
|
||||
* Threading:
|
||||
* No other Open/Close session calls will be made while this function is
|
||||
* running. Functions on existing sessions may be called while this function
|
||||
* is active.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_INVALID_SESSION no open session with that id.
|
||||
* OEMCrypto_ERROR_CLOSE_SESSION_FAILED failed to terminate the crypto session
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_CloseSession(OEMCrypto_SESSION session);
|
||||
|
||||
/*
|
||||
* OEMCrypto_GenerateDerivedKeys
|
||||
*
|
||||
* Description:
|
||||
* Generates a pair of secondary keys, mac_key and encrypt_key, for handling
|
||||
* signing and content key decryption under the license server protocol
|
||||
* for AES CTR mode.
|
||||
*
|
||||
* Refer to document "OEMCrypto Changes for V2 License Protocol" for details.
|
||||
* This function computes the AES-128-CMAC of the enc_key_context and stores
|
||||
* it in secure memory as the encrypt_key.
|
||||
* It then computes two cycles of AES-128-CMAC of the mac_key_context and
|
||||
* stores it in the mac_key. These two keys will be stored until the next
|
||||
* call to LoadKeys.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* mac_key_context (in) - pointer to memory containing context data for
|
||||
* computing the HMAC generation key.
|
||||
* mac_key_context_length (in) - length of the HMAC key context data.
|
||||
* enc_key_context (in) - pointer to memory containing context data for
|
||||
* computing the encryption key.
|
||||
* enc_key_context_length (in) - length of the encryption key context data.
|
||||
*
|
||||
* Results:
|
||||
* mac_key: the 256 bit mac key is generated and stored in secure memory.
|
||||
* enc_key: the 128 bit encryption key is generated and stored in secure memory.
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with functions on other sessions,
|
||||
* but not with other functions on this session.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_CONTEXT
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_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);
|
||||
|
||||
/*
|
||||
* OEMCrypto_GenerateNonce
|
||||
*
|
||||
* Description:
|
||||
* Generates a 32-bit nonce to detect possible replay attack on the key
|
||||
* control block. The nonce is stored in secure memory and will be used
|
||||
* for the next call to LoadKeys.
|
||||
*
|
||||
* Refer to documents "OEMCrypto Changes for V2 License Protocol" and "Key
|
||||
* Control Block Definition" for details.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* nonce (out) - pointer to memory to received the computed nonce.
|
||||
*
|
||||
* Results:
|
||||
* nonce: the nonce is also stored in secure memory.
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with functions on other sessions,
|
||||
* but not with other functions on this session.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_CONTEXT
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_GenerateNonce(
|
||||
OEMCrypto_SESSION session,
|
||||
uint32_t* nonce);
|
||||
|
||||
/*
|
||||
* OEMCrypto_GenerateSignature
|
||||
*
|
||||
* Description:
|
||||
* Generates a HMAC-SHA256 signature for license request signing under the
|
||||
* license server protocol for AES CTR mode.
|
||||
*
|
||||
* NOTE: OEMCrypto_GenerateDerivedKeys() must be called first to establish the
|
||||
* mac_key
|
||||
*
|
||||
* Refer to document "OEMCrypto Changes for V2 License Protocol" for details.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* message (in) - pointer to memory containing message to be signed.
|
||||
* message_length (in) - length of the message.
|
||||
* signature (out) - pointer to memory to received the computed signature.
|
||||
* signature_length (in/out) - (in) length of the signature buffer.
|
||||
* (out) actual length of the signature
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with functions on other sessions,
|
||||
* but not with other functions on this session.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_CONTEXT
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_GenerateSignature(
|
||||
OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t* signature_length);
|
||||
|
||||
/*
|
||||
* OEMCrypto_LoadKeys
|
||||
*
|
||||
* Description:
|
||||
* Installs a set of keys for performing decryption in the current session.
|
||||
*
|
||||
* The relevant fields have been extracted from the License Response protocol
|
||||
* message, but the entire message and associated signature are provided so
|
||||
* the message can be verified (using HMAC-SHA256 with the derived mac_key).
|
||||
* If the signature verification fails, ignore all other arguments and return
|
||||
* OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add the keys to the session
|
||||
* context.
|
||||
*
|
||||
* The keys will be decrypted using the current encrypt_key (AES-128-CBC) and
|
||||
* the IV given in the KeyObject. Each key control block will be decrypted
|
||||
* using the corresponding content key (AES-128-CBC) and the IV given in the
|
||||
* KeyObject.
|
||||
*
|
||||
* If any key's control block does not have valid verification fields, return
|
||||
* OEMCrypto_ERROR_INVALID_CONTEXT and do not install any keys.
|
||||
*
|
||||
* If any key's control block requires a nonce, and the nonce in the control
|
||||
* block is different from the current nonce, return
|
||||
* OEMCrypto_ERROR_INVALID_NONCE. In that case, do not install any keys.
|
||||
*
|
||||
* The new mac_key is decrypted with the current encrypt_key and the offered
|
||||
* IV. It replaces the current mac_key.
|
||||
*
|
||||
* The mac_key and encrypt_key were generated and stored by the previous call
|
||||
* to OEMCrypto_GenerateDerivedKeys(). The nonce was generated and stored by
|
||||
* the previous call to OEMCrypto_GenerateNonce().
|
||||
*
|
||||
* This session’s elapsed time clock is started at 0. The clock will be used
|
||||
* in OEMCrypto_DecryptCTR.
|
||||
*
|
||||
* NOTE: OEMCrypto_GenerateDerivedKeys() must be called first to establish the
|
||||
* mac_key and encrypt_key.
|
||||
*
|
||||
* Refer to document "OEMCrypto Changes for V2 License Protocol" for details.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* message (in) - pointer to memory containing message to be verified.
|
||||
* message_length (in) - length of the message.
|
||||
* signature (in) - pointer to memory containing the signature.
|
||||
* signature_length (in) - length of the signature.
|
||||
* enc_mac_key_iv (in) - IV for decrypting new mac_key. Size is 128 bits.
|
||||
* enc_mac_key (in) - encrypted mac_key for generating new mac_key. Size is
|
||||
* 256 bits.
|
||||
* num_keys (in) - number of keys present.
|
||||
* key_array (in) - set of keys to be installed.
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with functions on other sessions,
|
||||
* but not with other functions on this session.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_CONTEXT
|
||||
* OEMCrypto_ERROR_SIGNATURE_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_NONCE
|
||||
* OEMCrypto_ERROR_TOO_MANY_KEYS
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_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);
|
||||
|
||||
/*
|
||||
* OEMCrypto_RefreshKeys
|
||||
*
|
||||
* Description:
|
||||
* Updates an existing set of keys for continuing decryption in the
|
||||
* current session.
|
||||
*
|
||||
* The relevant fields have been extracted from the Renewal Response protocol
|
||||
* message, but the entire message and associated signature are provided so
|
||||
* the message can be verified (using HMAC-SHA256 with the current mac_key).
|
||||
* If the signature verification fails, ignore all other arguments and return
|
||||
* OEMCrypto_ERROR_SIGNATURE_FAILURE. Otherwise, add the keys to the session
|
||||
* context.
|
||||
*
|
||||
* NOTE: OEMCrypto_GenerateDerivedKeys() or OEMCrypto_LoadKeys() must be called
|
||||
* first to establish the mac_key
|
||||
*
|
||||
* Refer to document OEMCrypto Changes for V2 License Protocol for details.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* message (in) - pointer to memory containing message to be verified.
|
||||
* message_length (in) - length of the message.
|
||||
* signature (in) - pointer to memory containing the signature.
|
||||
* signature_length (in) - length of the signature.
|
||||
* num_keys (in) - number of keys present.
|
||||
* key_array (in) - set of keys to be installed.
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with functions on other sessions,
|
||||
* but not with other functions on this session.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_CONTEXT
|
||||
* OEMCrypto_ERROR_INVALID_NONCE
|
||||
* OEMCrypto_ERROR_SIGNATURE_FAILURE
|
||||
*/
|
||||
OEMCryptoResult
|
||||
OEMCrypto_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);
|
||||
|
||||
/*
|
||||
* OEMCrypto_SelectKey
|
||||
*
|
||||
* Description:
|
||||
* Select a content key and install it in the hardware key ladder for
|
||||
* subsequent decryption operations (OEMCrypto_DecryptCTR()) for this session.
|
||||
* The specified key must have been previously "installed" via
|
||||
* OEMCrypto_LoadKeys() or OEMCrypto_RefreshKeys().
|
||||
*
|
||||
* This session’s elapsed time clock is started at 0. The clock will be used
|
||||
* in OEMCrypto_DecryptCTR.
|
||||
*
|
||||
* A key control block is associated with the key and the session, and is used
|
||||
* to configure the session context. The Key Control data is documented in
|
||||
* "Key Control Block Definition".
|
||||
*
|
||||
* Step 1: Lookup the content key data via the offered key_id. The key data
|
||||
* includes the key value, the content key IV, the key control
|
||||
* block, and the key control block IV.
|
||||
*
|
||||
* Step 2: Latch the content key into the hardware key ladder. Set
|
||||
* permission flags and timers based on the key's control block.
|
||||
*
|
||||
* Step 3: use the latched content key to decrypt (AES-128-CTR)
|
||||
* to decrypt buffers passed in via OEMCrypto_DecryptCTR(). Continue
|
||||
* to use this key until OEMCrypto_SelectKey() is called again, or
|
||||
* until OEMCrypto_CloseSession() is called.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier
|
||||
* key_id (in) - pointer to the Key ID
|
||||
* key_id_length (in) - length of the Key ID in bytes
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with functions on other sessions,
|
||||
* but not with other functions on this session.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_INVALID_SESSION crypto session ID invalid or not open
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY failed to decrypt device key
|
||||
* OEMCrypto_ERROR_NO_CONTENT_KEY failed to decrypt content key
|
||||
* OEMCrypto_ERROR_CONTROL_INVALID invalid or unsupported control input
|
||||
* OEMCrypto_ERROR_KEYBOX_INVALID cannot decrypt and read from Keybox
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_SelectKey(const OEMCrypto_SESSION session,
|
||||
const uint8_t* key_id,
|
||||
size_t key_id_length);
|
||||
|
||||
/*
|
||||
* OEMCrypto_DecryptCTR
|
||||
*
|
||||
* Description:
|
||||
*
|
||||
* The API decrypts (AES-CTR) or copies the payload in the buffer referenced by
|
||||
* the data_addr parameter to the buffer determined by out_buffer, using the key
|
||||
* previously set by a call to OEMCrypto_SelectKey for the specified session.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* data_addr (in) - An unaligned pointer to this segment of the stream.
|
||||
* data_length (in) - The length of this segment of the stream.
|
||||
* is_encrypted (in) - True if the buffer described by data_addr,
|
||||
* data_length is encrypted. If is_encrypted is false, only the
|
||||
* data_addr and data_length parameters are used. The iv and offset
|
||||
* arguments are ignored.
|
||||
* iv (in) - The initial value block to be used for content decryption.
|
||||
* This is discussed further below.
|
||||
* offset (in) - If non-zero, the decryption block boundary is different
|
||||
* from the start of the data. offset should be subtracted from
|
||||
* data_addr to compute the starting address of the first decrypted
|
||||
* block. The bytes between the decryption block start address and
|
||||
* data_addr are discarded after decryption.
|
||||
* out_buffer (in) - A caller-owned descriptor that specifies the
|
||||
* handling of the decrypted byte stream. See OEMCrypto_DestbufferDesc
|
||||
* for details.
|
||||
*
|
||||
* AES CTR is a stream cipher. The stream may be composed of arbitrary-
|
||||
* length clear and encrypted segments. The encrypted portions of a sample
|
||||
* are collectively treated as a continuous sequence of decryption
|
||||
* block-sized blocks even though the sequence is interrupted by clear blocks.
|
||||
* This means a given encrypted segment may not start or end on a decryption
|
||||
* block boundary.
|
||||
*
|
||||
* If data_addr is not aligned with a decryption block boundary (offset != 0),
|
||||
* the additional offset bytes before data_addr (pre-padding) are included in
|
||||
* the decrypt operation, and they are dropped after decryption. If
|
||||
* data_length + offset is not a multiple of the decryption block size, the
|
||||
* extra bytes in the final decryption block (post-padding) are also dropped
|
||||
* after decryption. The caller is responsible for guaranteeing that all
|
||||
* memory addresses from (data-addr - pre-padding) to (data-addr +
|
||||
* data-length + post-padding) are valid memory addresses.
|
||||
*
|
||||
* After decrypting the entire buffer including any pre-padding and
|
||||
* post-padding, send data_length bytes starting at data_addr to the decoder.
|
||||
*
|
||||
* NOTES:
|
||||
* IV points to the counter value to be used for the initial
|
||||
* encrypted block of the input buffer. The IV length is the AES
|
||||
* block size. For subsequent encrypted AES blocks the IV is
|
||||
* calculated by incrementing the lower 64 bits (byte 8-15) of the
|
||||
* IV value used for the previous block. The counter rolls over to
|
||||
* zero when it reaches its maximum value (0xFFFFFFFFFFFFFFFF).
|
||||
* The upper 64 bits (byte 0-7) of the IV do not change.
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with functions on other sessions,
|
||||
* but not with other functions on this session.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_CONTEXT
|
||||
* OEMCrypto_ERROR_DECRYPT_FAILED
|
||||
*/
|
||||
OEMCryptoResult
|
||||
OEMCrypto_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);
|
||||
|
||||
/*
|
||||
* OEMCrypto_InstallKeybox
|
||||
*
|
||||
* Description:
|
||||
* Unwrap and store the keybox to persistent memory.
|
||||
* The device key must be stored securely.
|
||||
*
|
||||
* This function is used once to load the keybox onto the device at
|
||||
* provisioning time.
|
||||
*
|
||||
* Parameters:
|
||||
* keybox (in) - Pointer to clear keybox data. Must have been originally
|
||||
* wrapped with OEMCrypto_WrapKeybox.
|
||||
* keyboxLength (in) - Length of the keybox data in bytes.
|
||||
*
|
||||
* Threading:
|
||||
* This function is not called simultaneously with any other functions.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_WRITE_KEYBOX failed to handle and store Keybox
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_InstallKeybox(const uint8_t *keybox,
|
||||
size_t keyBoxLength);
|
||||
|
||||
/*
|
||||
* OEMCrypto_IsKeyboxValid
|
||||
*
|
||||
* Description:
|
||||
* Validate the Widevine Keybox stored on the device.
|
||||
*
|
||||
* The API performs two verification steps on the Keybox. It first verifies
|
||||
* the MAGIC field contains a valid signature (must be 'kbox'). The API then
|
||||
* computes the CRC using CRC-32 (Posix 1003.2 standard) and compares the
|
||||
* checksum to the CRC stored in the Keybox. The CRC is computed over the
|
||||
* entire Keybox excluding the 4 CRC bytes (i.e. Keybox[0..123]).
|
||||
*
|
||||
* Parameters:
|
||||
* none
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with any session functions.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS
|
||||
* OEMCrypto_ERROR_BAD_MAGIC
|
||||
* OEMCrypto_ERROR_BAD_CRC
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_IsKeyboxValid(void);
|
||||
|
||||
/*
|
||||
* OEMCrypto_GetDeviceID
|
||||
*
|
||||
* Description:
|
||||
* Retrieve the device's unique identifier from the Keybox.
|
||||
*
|
||||
* Parameters:
|
||||
* deviceId (out) - pointer to the buffer that receives the Device ID
|
||||
* idLength (in/out) - on input, size of the caller's device ID buffer.
|
||||
* On output, the number of bytes written into the buffer.
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with any session functions.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_SHORT_BUFFER buffer is too small to return the device ID
|
||||
* OEMCrypto_ERROR_NO_DEVICEID failed to return Device Id
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_GetDeviceID(uint8_t* deviceID,
|
||||
size_t *idLength);
|
||||
|
||||
/*
|
||||
* OEMCrypto_GetKeyData
|
||||
*
|
||||
* Description:
|
||||
* Returns the Key Data field from the Keybox. The Key Data field does not
|
||||
* need to be encrypted by an OEM root key, but may be if desired.
|
||||
*
|
||||
* If the Key Data field was encrypted with an OEM root key when the Keybox
|
||||
* was stored on the device, then this function should decrypt it and return
|
||||
* the clear Key Data. If the Key Data was not encrypted, then this function
|
||||
* should just access and return the clear Key data.
|
||||
*
|
||||
* Parameters:
|
||||
* keyData (out) - pointer to a caller-managed buffer to hold the Key Data
|
||||
* field from the Keybox
|
||||
* dataLength (in/out) - on input, the allocated buffer size. On output,
|
||||
* the number of bytes in KeyData.
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with any session functions.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_SHORT_BUFFER the buffer is too small to return the KeyData
|
||||
* OEMCrypto_ERROR_NO_KEYDATA failed to return KeyData
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_GetKeyData(uint8_t* keyData,
|
||||
size_t *keyDataLength);
|
||||
|
||||
/*
|
||||
* OEMCrypto_GetRandom
|
||||
*
|
||||
* Description:
|
||||
* Return a buffer filled with hardware-generated random bytes. If the
|
||||
* hardware feature does not exist, return OEMCrypto_ERROR_RNG_NOT_SUPPORTED.
|
||||
*
|
||||
* Parameters:
|
||||
* randomData (out) - Pointer to caller-manager buffer that will receive the
|
||||
* random data.
|
||||
* dataLength (in) - Length of the random data buffer in bytes.
|
||||
*
|
||||
* Threading:
|
||||
* This function may be called simultaneously with any session functions.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_RNG_FAILED failed to generate random number
|
||||
* OEMCrypto_ERROR_RNG_NOT_SUPPORTED function not supported
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_GetRandom(uint8_t* randomData,
|
||||
size_t dataLength);
|
||||
|
||||
/*
|
||||
* OEMCrypto_WrapKeybox
|
||||
*
|
||||
* Description:
|
||||
* Wrap the Keybox with a key derived for the device key. If transportKey
|
||||
* is not NULL, the input keybox is encrypted with transportKey. If so,
|
||||
* decrypt the input keybox before wrapping it, using transportKey in AES-CBC
|
||||
* mode with an IV of all zeroes. This function is only needed if the
|
||||
* provisioning method involves saving the keybox to the file system.
|
||||
*
|
||||
* Parameters:
|
||||
* keybox (in) - Pointer to keybox data.
|
||||
* keyboxLength - Length of the Keybox data in bytes
|
||||
* wrappedKeybox (out) - Pointer to wrapped keybox
|
||||
* wrappedKeyboxLength (out) - Pointer to the length of the wrapped keybox in
|
||||
* bytes
|
||||
* transportKey (in) - An optional AES transport key. If provided, the input
|
||||
* keybox is encrypted with this transport key with AES-CBC
|
||||
* and a null IV.
|
||||
* transportKeyLength - number of bytes in the transportKey
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_WRAP_KEYBOX failed to wrap Keybox
|
||||
* OEMCrypto_ERROR_NOT_IMPLEMENTED
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_WrapKeybox(const uint8_t *keybox,
|
||||
size_t keyBoxLength,
|
||||
uint8_t *wrappedKeybox,
|
||||
size_t *wrappedKeyBoxLength,
|
||||
const uint8_t *transportKey,
|
||||
size_t transportKeyLength);
|
||||
|
||||
|
||||
/*
|
||||
* OEMCrypto_RewrapDeviceRSAKey
|
||||
*
|
||||
* Description:
|
||||
* Verifies an RSA provisioning response is valid and corresponds
|
||||
* to the previous provisioning request by checking the nonce. The RSA
|
||||
* private key is decrypted and stored in secure memory. The RSA key is then
|
||||
* re-encrypted for storage on the filesystem. The OEM may either encrypt it
|
||||
* with the private key from the Widevine Keybox, or with an OEM specific
|
||||
* device key.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* message (in) - pointer to memory containing message to be
|
||||
* - verified.
|
||||
* message_length (in) - length of the message, in bytes.
|
||||
* signature (in) - pointer to memory containing the HMAC-SHA256
|
||||
* - signature for
|
||||
* - message, received from the provisioning server.
|
||||
* signature_length (in) - length of the signature, in bytes.
|
||||
* nonce (in) - The nonce provided in the provisioning response.
|
||||
* enc_rsa_key (in) - Encrypted device private RSA key received from
|
||||
* - the provisioning server. Format is PKCS#1, binary
|
||||
* - DER encoded, and encrypted with the derived
|
||||
* - encryption key, using AES-128-CBC with PKCS#5
|
||||
* - padding.
|
||||
* enc_rsa_key_length (in) - length of the encrypted RSA key, in bytes.
|
||||
* enc_rsa_key_iv (in) - IV for decrypting RSA key. Size is 128 bits.
|
||||
* wrapped_rsa_key (out) - pointer to buffer in which encrypted RSA key
|
||||
* - should be stored. May be null on the first call
|
||||
* - in order to find required buffer size.
|
||||
* wrapped_rsa_key_length (in/out) - length of the encrypted RSA key, in bytes.
|
||||
* wrapped_rsa_key_iv (out) - IV for encrypting/decrypting the RSA private key.
|
||||
* - Size is 128 bits.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_RSA_KEY
|
||||
* OEMCrypto_ERROR_SIGNATURE_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_NONCE
|
||||
* OEMCrypto_ERROR_SHORT_BUFFER
|
||||
*/
|
||||
|
||||
OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
const uint8_t* signature,
|
||||
size_t signature_length,
|
||||
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);
|
||||
|
||||
/*
|
||||
* OEMCrypto_LoadDeviceRSAKey
|
||||
*
|
||||
* Description:
|
||||
* Loads a wrapped RSA private key to secure memory for use by this session
|
||||
* in future calls to OEMCrypto_GenerateRSASignature. The wrapped RSA key
|
||||
* will be one verified and wrapped by OEMCrypto_RewrapDeviceRSAKey. The RSA
|
||||
* private key should be stored in secure memory.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* wrapped_rsa_key (in) - wrapped device RSA key stored on the device.
|
||||
* - Format is PKCS#1, binary DER encoded, and
|
||||
* - encrypted with a key internal to the OEMCrypto
|
||||
* - instance, using AES-128-CBC with PKCS#5
|
||||
* - padding. This is the wrapped key generated
|
||||
* - by OEMCrypto_RewrapDeviceRSAKey.
|
||||
* wrapped_rsa_key_length (in) - length of the wrapped key buffer, in bytes.
|
||||
* wrapped_rsa_key_iv (in) - The initialization vector used to encrypt
|
||||
* - wrapped_rsa_key.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_NO_DEVICE_KEY
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_RSA_KEY
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_LoadDeviceRSAKey(OEMCrypto_SESSION session,
|
||||
const uint8_t* wrapped_rsa_key,
|
||||
size_t wrapped_rsa_key_length);
|
||||
|
||||
/*
|
||||
* OEMCrypto_GenerateRSASignature
|
||||
*
|
||||
* Description:
|
||||
* The OEMCrypto_GenerateRSASignature method is used to sign messages using
|
||||
* the device private RSA key, specifically, it is used to sign the initial
|
||||
* license request.
|
||||
*
|
||||
* Refer to the document "Widevine Security Integration Guide for DASH" for
|
||||
* more details.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* message (in) - pointer to memory containing message to be
|
||||
* - signed.
|
||||
* message_length (in) - length of the message, in bytes.
|
||||
* signature (out) - buffer to hold the message signature. On
|
||||
* - return, it will contain the message signature
|
||||
* - generated with the device private RSA key using
|
||||
* - RSASSA-PSS.
|
||||
* signature_length (in/out) - (in) length of the signature buffer, in bytes.
|
||||
* - (out) actual length of the signature
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_SHORT_BUFFER if the signature buffer is too small.
|
||||
* OEMCrypto_ERROR_CLOSE_SESSION_FAILED illegal/unrecognized handle or the
|
||||
* security engine is not properly initialized.
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_GenerateRSASignature(OEMCrypto_SESSION session,
|
||||
const uint8_t* message,
|
||||
size_t message_length,
|
||||
uint8_t* signature,
|
||||
size_t *signature_length);
|
||||
|
||||
/*
|
||||
* OEMCrypto_DeriveKeysFromSessionKey
|
||||
*
|
||||
* Description:
|
||||
* Generates a pair of secondary keys, mac_key and encrypt_key, for handling
|
||||
* signing and content key decryption under the license server protocol for
|
||||
* AES CTR mode.
|
||||
*
|
||||
* This function is similar to OEMCrypto_GenerateDerivedKeys, except that it
|
||||
* uses a session key to generate the secondary keys instead of the Widevine
|
||||
* Keybox device key. These two keys will be stored in secure memory until
|
||||
* the next call to LoadKeys. The session key is passed in encrypted by the
|
||||
* device RSA public key, and must be decrypted with the RSA private key
|
||||
* before use. Once the enc_key and mac_key have been generated, all calls
|
||||
* to LoadKeys and RefreshKeys proceed in the same manner for license
|
||||
* requests using RSA or using a Widevine keybox token.
|
||||
*
|
||||
* Parameters:
|
||||
* session (in) - crypto session identifier.
|
||||
* enc_session_key (in) - session key, encrypted with the device RSA key
|
||||
* - (from the device certifcate) using RSA-OAEP.
|
||||
* enc_session_key_length (in) - length of session_key, in bytes.
|
||||
* mac_key_context (in) - pointer to memory containing context data for
|
||||
* - computing the HMAC generation key.
|
||||
* mac_key_context_length (in) - length of the HMAC key context data, in bytes.
|
||||
* enc_key_context (in) - pointer to memory containing context data for
|
||||
* - computing the encryption key.
|
||||
* enc_key_context_length (in) - length of the encryption key context data, in
|
||||
* - bytes.
|
||||
*
|
||||
* Returns:
|
||||
* OEMCrypto_SUCCESS success
|
||||
* OEMCrypto_ERROR_DEVICE_NOT_RSA_PROVISIONED
|
||||
* OEMCrypto_ERROR_INVALID_SESSION
|
||||
* OEMCrypto_ERROR_UNKNOWN_FAILURE
|
||||
* OEMCrypto_ERROR_INVALID_CONTEXT
|
||||
*/
|
||||
OEMCryptoResult OEMCrypto_DeriveKeysFromSessionKey(OEMCrypto_SESSION session,
|
||||
const uint8_t* enc_session_key,
|
||||
size_t enc_session_key_length,
|
||||
const uint8_t *mac_key_context,
|
||||
size_t mac_key_context_length,
|
||||
const uint8_t *enc_key_context,
|
||||
size_t enc_key_context_length);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // OEMCRYPTO_CENC_H_
|
||||
38
libwvdrmengine/oemcrypto/mock/Android.mk
Normal file
38
libwvdrmengine/oemcrypto/mock/Android.mk
Normal file
@@ -0,0 +1,38 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= \
|
||||
src/oemcrypto_engine_mock.cpp \
|
||||
src/oemcrypto_key_mock.cpp \
|
||||
src/oemcrypto_keybox_mock.cpp \
|
||||
src/oemcrypto_mock.cpp \
|
||||
src/lock.cpp \
|
||||
src/log.cpp \
|
||||
src/string_conversions.cpp \
|
||||
src/wvcrc.cpp \
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
|
||||
LOCAL_C_INCLUDES += \
|
||||
$(LOCAL_PATH)/../include \
|
||||
$(LOCAL_PATH)/src \
|
||||
bionic \
|
||||
external/gtest/include \
|
||||
external/openssl/include \
|
||||
external/openssl/include/openssl \
|
||||
external/stlport/stlport \
|
||||
|
||||
LOCAL_SHARED_LIBRARIES := \
|
||||
libcrypto \
|
||||
libcutils \
|
||||
libdl \
|
||||
liblog \
|
||||
libstlport \
|
||||
libutils \
|
||||
libz \
|
||||
|
||||
LOCAL_MODULE := liboemcrypto
|
||||
|
||||
include $(BUILD_SHARED_LIBRARY)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user