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:
Jeff Tinker
2013-03-26 10:42:23 -07:00
committed by Android Git Automerger
211 changed files with 51913 additions and 144 deletions

View File

@@ -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

View 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)

View File

@@ -1,2 +0,0 @@
Rahul and Edwin fill this out
Rahul and Edwin write tests

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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_

View 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

View 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

View 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

View 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

View 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

View 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

View 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;
}

View 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

View 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

View 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();
}

View 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

View 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_

View 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

View 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_

View 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();
}

View 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

View 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_

View 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
}

View 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

View 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_

View 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();
}

View 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();
}

View File

@@ -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_

View 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_

View File

@@ -1,2 +0,0 @@
Rahul and Edwin fill this out
Rahul and Edwin write tests

View 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

View 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

View 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

View 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

View 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

View File

@@ -1,2 +0,0 @@
Rahul and Edwin fill this out
Rahul and Edwin write tests

View 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();
}

View 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)

View File

@@ -1,2 +0,0 @@
Jeff and Juce.
Maybe we should add a README that says this is the implementation if ICrypto.

View File

@@ -1 +0,0 @@
Jeff and Juce.

View File

@@ -1 +0,0 @@
Jeff and Juce.

View File

@@ -1 +0,0 @@
Jeff and Juce.

View File

@@ -1 +0,0 @@
Jeff and Jerry.

View File

@@ -1 +0,0 @@
Jeff and Jerry.

View File

@@ -1 +0,0 @@
Jeff and Jerry.

View File

@@ -1 +0,0 @@
Jeff and Jerry.

View File

@@ -1 +0,0 @@
Juce and Jerry to fill in.

View 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_

View 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_

View 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_

View 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_

View 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_

View 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)

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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_

View 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

View 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_

View 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

View 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_

View 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_

View 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_

View 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_

View 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));
}

View 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_

View 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)

View 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_

View 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

View 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)

View 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_

View 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";
}

View 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)

View 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_

View 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

View 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)

View 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_

View 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);
}

View 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_

View 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 sessions 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 sessions 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_

View 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