Merge Changes from CDM repository
This CL merges the following changes from the Widevine repository: Avoid CdmSession reinitialization https://widevine-internal-review.googlesource.com/#/c/10530/ Fix timer-related unit tests. https://widevine-internal-review.googlesource.com/#/c/10510/ Correct return statement bug: 15590802 https://widevine-internal-review.googlesource.com/#/c/10553/ Usage reporting fixes bug/15388863 https://widevine-internal-review.googlesource.com/#/c/10460/ Make public methods virtual https://widevine-internal-review.googlesource.com/#/c/10500/ Fix the SetTimer contract in the CDM. https://widevine-internal-review.googlesource.com/#/c/10493/ Move inline CDM methods, add OVERRIDE. https://widevine-internal-review.googlesource.com/#/c/10475/ Simplify storage APIs related cleanup. https://widevine-internal-review.googlesource.com/#/c/10473/ Duration values are not correctly reported when queried b/15592374 https://widevine-internal-review.googlesource.com/#/c/10437/ Propagate IsKeyValid() through ContentDecryptionModule. https://widevine-internal-review.googlesource.com/#/c/10483/ Minor clean up in config_test_env. https://widevine-internal-review.googlesource.com/#/c/10440/ General clean up. https://widevine-internal-review.googlesource.com/#/c/10441/ Refactor HttpSocket and simplify UrlRequest interface. https://widevine-internal-review.googlesource.com/#/c/10410/ Install good keybox at end of unit tests b/15385981 https://widevine-internal-review.googlesource.com/#/c/10374/ Privacy crypto fixes b/15475012 https://widevine-internal-review.googlesource.com/#/c/10383/ Incorporate header files to resolve build issued based on customers feedback. https://widevine-internal-review.googlesource.com/#/c/10420/ Support unprovisioning b/12247651 https://widevine-internal-review.googlesource.com/#/c/10356/ Correct usage of Host::Allocate and Cdm::Decrypt. https://widevine-internal-review.googlesource.com/#/c/10378/ Fix logging bug, arguments in wrong order. https://widevine-internal-review.googlesource.com/#/c/10380/ Rename types that look like constants. https://widevine-internal-review.googlesource.com/#/c/10379/ Fix offline test failures b/13909635 https://widevine-internal-review.googlesource.com/#/c/10348/ Add -DUNIT_TEST to the unit test makefile for Android https://widevine-internal-review.googlesource.com/#/c/10375/ Refactor privacy-crypto and add dummy version. https://widevine-internal-review.googlesource.com/#/c/10353/ Remove References to Apiary https://widevine-internal-review.googlesource.com/#/c/9924/ Delete oldest entry in usage table when full bug: 15184824 https://widevine-internal-review.googlesource.com/#/c/10295/ Port DeviceFiles to iOS. https://widevine-internal-review.googlesource.com/#/c/10355/ Make testing functions in DeviceFiles private. https://widevine-internal-review.googlesource.com/#/c/10354/ Add RSA encryption to haystack https://widevine-internal-review.googlesource.com/#/c/10280/ Add string and vector includes to CDM header. https://widevine-internal-review.googlesource.com/#/c/10352/ First version of oemcrypto logging https://widevine-internal-review.googlesource.com/#/c/10252/ Update Names of Secure Stop Methods bug: 11987015 https://widevine-internal-review.googlesource.com/#/c/10152/ Adjust timing on the Usage Table unit test https://widevine-internal-review.googlesource.com/#/c/10307/ Fix all compiler warnings in CDM source release. https://widevine-internal-review.googlesource.com/#/c/10293/ Fix memset bug: args in wrong order https://widevine-internal-review.googlesource.com/#/c/10292/ Partial revert of 'Remove refs to test prov server, Level3 support...' https://widevine-internal-review.googlesource.com/#/c/10281/ Pack structure OEMCrypto_PST_Report https://widevine-internal-review.googlesource.com/#/c/10243/ Remove refs to test prov server, Level3 support; remove dead code https://widevine-internal-review.googlesource.com/#/c/10220/ Partial revert of 'Document data strings; clean up license server parameters.' https://widevine-internal-review.googlesource.com/#/c/10188/ Document data strings; clean up license server parameters. https://widevine-internal-review.googlesource.com/#/c/10120/ Fix broken build after partner branch merge. https://widevine-internal-review.googlesource.com/#/c/10181/ TODO Cleanup - core/src, core/include https://widevine-internal-review.googlesource.com/#/c/9965/ TODO Cleanup - cdm, chromium, core/test. https://widevine-internal-review.googlesource.com/#/c/9419/ Remove unneeded properties. https://widevine-internal-review.googlesource.com/#/c/10162/ Change-Id: If2bb9d743a562a3875bebb91933c0aaadea286b2
This commit is contained in:
@@ -13,9 +13,9 @@ class CdmClientPropertySet {
|
||||
public:
|
||||
virtual ~CdmClientPropertySet() {}
|
||||
|
||||
virtual std::string security_level() const = 0;
|
||||
virtual const std::string& security_level() const = 0;
|
||||
virtual bool use_privacy_mode() const = 0;
|
||||
virtual std::vector<uint8_t> service_certificate() const = 0;
|
||||
virtual const std::string& service_certificate() const = 0;
|
||||
virtual bool is_session_sharing_enabled() const = 0;
|
||||
virtual uint32_t session_sharing_id() const = 0;
|
||||
virtual void set_session_sharing_id(uint32_t id) = 0;
|
||||
|
||||
@@ -3,15 +3,16 @@
|
||||
#ifndef WVCDM_CORE_CDM_ENGINE_H_
|
||||
#define WVCDM_CORE_CDM_ENGINE_H_
|
||||
|
||||
#include "cdm_session.h"
|
||||
#include "certificate_provisioning.h"
|
||||
#include "initialization_data.h"
|
||||
#include "oemcrypto_adapter.h"
|
||||
#include "scoped_ptr.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class CdmClientPropertySet;
|
||||
class CdmSession;
|
||||
class CryptoEngine;
|
||||
class WvCdmEventListener;
|
||||
|
||||
@@ -24,98 +25,94 @@ class CdmEngine {
|
||||
virtual ~CdmEngine();
|
||||
|
||||
// Session related methods
|
||||
CdmResponseType OpenSession(const CdmKeySystem& key_system,
|
||||
const CdmClientPropertySet* property_set,
|
||||
CdmSessionId* session_id);
|
||||
CdmResponseType CloseSession(const CdmSessionId& session_id);
|
||||
virtual CdmResponseType OpenSession(const CdmKeySystem& key_system,
|
||||
const CdmClientPropertySet* property_set,
|
||||
CdmSessionId* session_id);
|
||||
virtual CdmResponseType CloseSession(const CdmSessionId& session_id);
|
||||
|
||||
CdmResponseType OpenKeySetSession(const CdmKeySetId& key_set_id);
|
||||
CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id);
|
||||
virtual CdmResponseType OpenKeySetSession(const CdmKeySetId& key_set_id);
|
||||
virtual CdmResponseType CloseKeySetSession(const CdmKeySetId& key_set_id);
|
||||
|
||||
// License related methods
|
||||
// Construct a valid license request
|
||||
CdmResponseType GenerateKeyRequest(const CdmSessionId& session_id,
|
||||
const CdmKeySetId& key_set_id,
|
||||
const InitializationData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
virtual CdmResponseType GenerateKeyRequest(
|
||||
const CdmSessionId& session_id, const CdmKeySetId& key_set_id,
|
||||
const InitializationData& init_data, const CdmLicenseType license_type,
|
||||
CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// Accept license response and extract key info.
|
||||
CdmResponseType AddKey(const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data,
|
||||
CdmKeySetId* key_set_id);
|
||||
virtual CdmResponseType AddKey(const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data,
|
||||
CdmKeySetId* key_set_id);
|
||||
|
||||
CdmResponseType RestoreKey(const CdmSessionId& session_id,
|
||||
const CdmKeySetId& key_set_id);
|
||||
virtual CdmResponseType RestoreKey(const CdmSessionId& session_id,
|
||||
const CdmKeySetId& key_set_id);
|
||||
|
||||
CdmResponseType CancelKeyRequest(const CdmSessionId& session_id);
|
||||
virtual CdmResponseType CancelKeyRequest(const CdmSessionId& session_id);
|
||||
|
||||
// Construct valid renewal request for the current session keys.
|
||||
CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
virtual CdmResponseType GenerateRenewalRequest(const CdmSessionId& session_id,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// Accept renewal response and update key info.
|
||||
CdmResponseType RenewKey(const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data);
|
||||
virtual CdmResponseType RenewKey(const CdmSessionId& session_id,
|
||||
const CdmKeyResponse& key_data);
|
||||
|
||||
// Query system information
|
||||
CdmResponseType QueryStatus(CdmQueryMap* info);
|
||||
virtual CdmResponseType QueryStatus(CdmQueryMap* info);
|
||||
|
||||
// Query session information
|
||||
virtual CdmResponseType QuerySessionStatus(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info);
|
||||
|
||||
// Query license information
|
||||
CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info);
|
||||
virtual CdmResponseType QueryKeyStatus(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info);
|
||||
|
||||
// Query seesion control information
|
||||
CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info);
|
||||
virtual CdmResponseType QueryKeyControlInfo(const CdmSessionId& session_id,
|
||||
CdmQueryMap* key_info);
|
||||
|
||||
// Provisioning related methods
|
||||
CdmResponseType GetProvisioningRequest(
|
||||
CdmCertificateType cert_type,
|
||||
const std::string& cert_authority,
|
||||
CdmProvisioningRequest* request,
|
||||
std::string* default_url);
|
||||
virtual CdmResponseType GetProvisioningRequest(
|
||||
CdmCertificateType cert_type, const std::string& cert_authority,
|
||||
CdmProvisioningRequest* request, std::string* default_url);
|
||||
|
||||
CdmResponseType HandleProvisioningResponse(
|
||||
CdmProvisioningResponse& response,
|
||||
std::string* cert,
|
||||
virtual CdmResponseType HandleProvisioningResponse(
|
||||
CdmProvisioningResponse& response, std::string* cert,
|
||||
std::string* wrapped_key);
|
||||
|
||||
virtual CdmResponseType Unprovision(CdmSecurityLevel security_level);
|
||||
|
||||
// Usage related methods for streaming licenses
|
||||
CdmResponseType GetUsageInfo(CdmUsageInfo* usage_info);
|
||||
CdmResponseType ReleaseUsageInfo(const CdmUsageInfoReleaseMessage& message);
|
||||
virtual CdmResponseType GetUsageInfo(CdmUsageInfo* usage_info);
|
||||
virtual CdmResponseType ReleaseUsageInfo(
|
||||
const CdmUsageInfoReleaseMessage& message);
|
||||
|
||||
// Decryption and key related methods
|
||||
// Accept encrypted buffer and return decrypted data.
|
||||
CdmResponseType Decrypt(const CdmSessionId& session_id,
|
||||
const CdmDecryptionParameters& parameters);
|
||||
virtual CdmResponseType Decrypt(const CdmSessionId& session_id,
|
||||
const CdmDecryptionParameters& parameters);
|
||||
|
||||
size_t SessionSize() const { return sessions_.size(); }
|
||||
virtual size_t SessionSize() const { return sessions_.size(); }
|
||||
|
||||
// Is the key known to any session?
|
||||
bool IsKeyLoaded(const KeyId& key_id);
|
||||
bool FindSessionForKey(const KeyId& key_id, CdmSessionId* sessionId);
|
||||
virtual bool IsKeyLoaded(const KeyId& key_id);
|
||||
virtual bool FindSessionForKey(const KeyId& key_id, CdmSessionId* sessionId);
|
||||
|
||||
// Event listener related methods
|
||||
bool AttachEventListener(const CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
bool DetachEventListener(const CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
virtual bool AttachEventListener(const CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
virtual bool DetachEventListener(const CdmSessionId& session_id,
|
||||
WvCdmEventListener* listener);
|
||||
|
||||
// Timer expiration method
|
||||
void OnTimerEvent();
|
||||
virtual void OnTimerEvent();
|
||||
|
||||
private:
|
||||
// private methods
|
||||
// Cancel all sessions
|
||||
bool CancelSessions();
|
||||
bool ValidateKeySystem(const CdmKeySystem& key_system);
|
||||
|
||||
void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
|
||||
@@ -125,8 +122,11 @@ class CdmEngine {
|
||||
CdmReleaseKeySetMap release_key_sets_;
|
||||
CertificateProvisioning cert_provisioning_;
|
||||
SecurityLevel cert_provisioning_requested_security_level_;
|
||||
CdmSession* usage_session_;
|
||||
|
||||
static bool seeded_;
|
||||
|
||||
// usage related variables
|
||||
scoped_ptr<CdmSession> usage_session_;
|
||||
int64_t last_usage_information_update_time;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmEngine);
|
||||
|
||||
@@ -22,84 +22,83 @@ class WvCdmEventListener;
|
||||
class CdmSession {
|
||||
public:
|
||||
explicit CdmSession(const CdmClientPropertySet* cdm_client_property_set);
|
||||
~CdmSession();
|
||||
virtual ~CdmSession();
|
||||
|
||||
CdmResponseType Init();
|
||||
virtual CdmResponseType Init();
|
||||
|
||||
CdmResponseType RestoreOfflineSession(const CdmKeySetId& key_set_id,
|
||||
const CdmLicenseType license_type);
|
||||
CdmResponseType RestoreUsageSession(const CdmKeyMessage& key_request,
|
||||
const CdmKeyResponse& key_response);
|
||||
virtual CdmResponseType RestoreOfflineSession(
|
||||
const CdmKeySetId& key_set_id, const CdmLicenseType license_type);
|
||||
virtual CdmResponseType RestoreUsageSession(
|
||||
const CdmKeyMessage& key_request, const CdmKeyResponse& key_response);
|
||||
|
||||
void set_key_system(const CdmKeySystem& ksystem) { key_system_ = ksystem; }
|
||||
const CdmKeySystem& key_system() { return key_system_; }
|
||||
virtual void set_key_system(const CdmKeySystem& ksystem) {
|
||||
key_system_ = ksystem;
|
||||
}
|
||||
virtual const CdmKeySystem& key_system() { return key_system_; }
|
||||
|
||||
const CdmSessionId& session_id() { return session_id_; }
|
||||
virtual const CdmSessionId& session_id() { return session_id_; }
|
||||
|
||||
bool VerifySession(const CdmKeySystem& key_system,
|
||||
const InitializationData& init_data);
|
||||
|
||||
CdmResponseType GenerateKeyRequest(const InitializationData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
const CdmAppParameterMap& app_parameters,
|
||||
CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
virtual CdmResponseType GenerateKeyRequest(
|
||||
const InitializationData& init_data, const CdmLicenseType license_type,
|
||||
const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// AddKey() - Accept license response and extract key info.
|
||||
CdmResponseType AddKey(const CdmKeyResponse& key_response,
|
||||
CdmKeySetId* key_set_id);
|
||||
virtual CdmResponseType AddKey(const CdmKeyResponse& key_response,
|
||||
CdmKeySetId* key_set_id);
|
||||
|
||||
// CancelKeyRequest() - Cancel session.
|
||||
CdmResponseType CancelKeyRequest();
|
||||
virtual CdmResponseType CancelKeyRequest();
|
||||
|
||||
// Query session status
|
||||
CdmResponseType QueryStatus(CdmQueryMap* key_info);
|
||||
virtual CdmResponseType QueryStatus(CdmQueryMap* key_info);
|
||||
|
||||
// Query license information
|
||||
CdmResponseType QueryKeyStatus(CdmQueryMap* key_info);
|
||||
virtual CdmResponseType QueryKeyStatus(CdmQueryMap* key_info);
|
||||
|
||||
// Query session control info
|
||||
CdmResponseType QueryKeyControlInfo(CdmQueryMap* key_info);
|
||||
virtual CdmResponseType QueryKeyControlInfo(CdmQueryMap* key_info);
|
||||
|
||||
// Decrypt() - Accept encrypted buffer and return decrypted data.
|
||||
CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
|
||||
virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
|
||||
|
||||
// License renewal
|
||||
// GenerateRenewalRequest() - Construct valid renewal request for the current
|
||||
// session keys.
|
||||
CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
virtual CdmResponseType GenerateRenewalRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// RenewKey() - Accept renewal response and update key info.
|
||||
CdmResponseType RenewKey(const CdmKeyResponse& key_response);
|
||||
virtual CdmResponseType RenewKey(const CdmKeyResponse& key_response);
|
||||
|
||||
// License release
|
||||
// GenerateReleaseRequest() - Construct valid release request for the current
|
||||
// session keys.
|
||||
CdmResponseType GenerateReleaseRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
virtual CdmResponseType GenerateReleaseRequest(CdmKeyMessage* key_request,
|
||||
std::string* server_url);
|
||||
|
||||
// ReleaseKey() - Accept response and release key.
|
||||
CdmResponseType ReleaseKey(const CdmKeyResponse& key_response);
|
||||
virtual CdmResponseType ReleaseKey(const CdmKeyResponse& key_response);
|
||||
|
||||
bool IsKeyLoaded(const KeyId& key_id);
|
||||
virtual bool IsKeyLoaded(const KeyId& key_id);
|
||||
|
||||
bool AttachEventListener(WvCdmEventListener* listener);
|
||||
bool DetachEventListener(WvCdmEventListener* listener);
|
||||
virtual bool AttachEventListener(WvCdmEventListener* listener);
|
||||
virtual bool DetachEventListener(WvCdmEventListener* listener);
|
||||
|
||||
void OnTimerEvent();
|
||||
void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
|
||||
virtual void OnTimerEvent();
|
||||
virtual void OnKeyReleaseEvent(const CdmKeySetId& key_set_id);
|
||||
|
||||
SecurityLevel GetRequestedSecurityLevel();
|
||||
CdmSecurityLevel GetSecurityLevel();
|
||||
virtual SecurityLevel GetRequestedSecurityLevel();
|
||||
virtual CdmSecurityLevel GetSecurityLevel();
|
||||
|
||||
CdmResponseType UpdateUsageInformation();
|
||||
virtual CdmResponseType UpdateUsageInformation();
|
||||
|
||||
bool is_usage_update_needed() { return is_usage_update_needed_; }
|
||||
void reset_is_usage_update_needed() { is_usage_update_needed_ = false; }
|
||||
virtual bool is_usage_update_needed() { return is_usage_update_needed_; }
|
||||
virtual void reset_is_usage_update_needed() {
|
||||
is_usage_update_needed_ = false;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// Generate unique ID for each new session.
|
||||
CdmSessionId GenerateSessionId();
|
||||
bool GenerateKeySetId(CdmKeySetId* key_set_id);
|
||||
@@ -115,10 +114,10 @@ class CdmSession {
|
||||
scoped_ptr<CryptoSession> crypto_session_;
|
||||
PolicyEngine policy_engine_;
|
||||
bool license_received_;
|
||||
bool reinitialize_session_;
|
||||
bool is_offline_;
|
||||
bool is_release_;
|
||||
bool is_usage_update_needed_;
|
||||
bool is_initial_decryption_;
|
||||
|
||||
// information useful for offline and usage scenarios
|
||||
CdmKeyMessage key_request_;
|
||||
@@ -133,10 +132,6 @@ class CdmSession {
|
||||
// license type release and offline related information
|
||||
CdmKeySetId key_set_id_;
|
||||
|
||||
// Used for certificate based licensing
|
||||
std::string wrapped_key_;
|
||||
bool is_certificate_loaded_;
|
||||
|
||||
std::set<WvCdmEventListener*> listeners_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(CdmSession);
|
||||
|
||||
@@ -19,61 +19,61 @@ typedef std::map<CryptoKeyId, CryptoKey*> CryptoKeyMap;
|
||||
class CryptoSession {
|
||||
public:
|
||||
CryptoSession();
|
||||
~CryptoSession();
|
||||
virtual ~CryptoSession();
|
||||
|
||||
bool ValidateKeybox();
|
||||
bool GetToken(std::string* token);
|
||||
CdmSecurityLevel GetSecurityLevel();
|
||||
bool GetDeviceUniqueId(std::string* device_id);
|
||||
bool GetSystemId(uint32_t* system_id);
|
||||
bool GetProvisioningId(std::string* provisioning_id);
|
||||
virtual bool ValidateKeybox();
|
||||
virtual bool GetToken(std::string* token);
|
||||
virtual CdmSecurityLevel GetSecurityLevel();
|
||||
virtual bool GetDeviceUniqueId(std::string* device_id);
|
||||
virtual bool GetApiVersion(uint32_t* version);
|
||||
virtual bool GetSystemId(uint32_t* system_id);
|
||||
virtual bool GetProvisioningId(std::string* provisioning_id);
|
||||
|
||||
CdmResponseType Open() { return Open(kLevelDefault); }
|
||||
CdmResponseType Open(SecurityLevel requested_security_level);
|
||||
void Close();
|
||||
virtual CdmResponseType Open() { return Open(kLevelDefault); }
|
||||
virtual CdmResponseType Open(SecurityLevel requested_security_level);
|
||||
virtual void Close();
|
||||
|
||||
bool IsOpen() { return open_; }
|
||||
CryptoSessionId oec_session_id() { return oec_session_id_; }
|
||||
virtual bool IsOpen() { return open_; }
|
||||
virtual CryptoSessionId oec_session_id() { return oec_session_id_; }
|
||||
|
||||
// Key request/response
|
||||
void GenerateRequestId(std::string& req_id_str);
|
||||
bool PrepareRequest(const std::string& key_deriv_message,
|
||||
bool is_provisioning, std::string* signature);
|
||||
bool PrepareRenewalRequest(const std::string& message,
|
||||
std::string* signature);
|
||||
CdmResponseType LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
const std::vector<CryptoKey>& key_array,
|
||||
const std::string& provider_session_token);
|
||||
bool LoadCertificatePrivateKey(std::string& wrapped_key);
|
||||
bool RefreshKeys(const std::string& message, const std::string& signature,
|
||||
int num_keys, const CryptoKey* key_array);
|
||||
bool GenerateNonce(uint32_t* nonce);
|
||||
bool GenerateDerivedKeys(const std::string& message);
|
||||
bool GenerateDerivedKeys(const std::string& message,
|
||||
const std::string& session_key);
|
||||
bool RewrapDeviceRSAKey(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& nonce,
|
||||
const std::string& enc_rsa_key,
|
||||
const std::string& rsa_key_iv,
|
||||
std::string* wrapped_rsa_key);
|
||||
virtual void GenerateRequestId(std::string& req_id_str);
|
||||
virtual bool PrepareRequest(const std::string& key_deriv_message,
|
||||
bool is_provisioning, std::string* signature);
|
||||
virtual bool PrepareRenewalRequest(const std::string& message,
|
||||
std::string* signature);
|
||||
virtual CdmResponseType LoadKeys(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& mac_key_iv,
|
||||
const std::string& mac_key,
|
||||
const std::vector<CryptoKey>& key_array,
|
||||
const std::string& provider_session_token);
|
||||
virtual bool LoadCertificatePrivateKey(std::string& wrapped_key);
|
||||
virtual bool RefreshKeys(const std::string& message,
|
||||
const std::string& signature, int num_keys,
|
||||
const CryptoKey* key_array);
|
||||
virtual bool GenerateNonce(uint32_t* nonce);
|
||||
virtual bool GenerateDerivedKeys(const std::string& message);
|
||||
virtual bool GenerateDerivedKeys(const std::string& message,
|
||||
const std::string& session_key);
|
||||
virtual bool RewrapDeviceRSAKey(const std::string& message,
|
||||
const std::string& signature,
|
||||
const std::string& nonce,
|
||||
const std::string& enc_rsa_key,
|
||||
const std::string& rsa_key_iv,
|
||||
std::string* wrapped_rsa_key);
|
||||
|
||||
// Media data path
|
||||
CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
|
||||
virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
|
||||
|
||||
CdmResponseType UpdateUsageInformation();
|
||||
CdmResponseType GenerateUsageReport(
|
||||
const std::string& provider_session_token,
|
||||
std::string* usage_report);
|
||||
CdmResponseType ReleaseUsageInformation(
|
||||
const std::string& message,
|
||||
const std::string& signature,
|
||||
virtual CdmResponseType UpdateUsageInformation();
|
||||
virtual CdmResponseType GenerateUsageReport(
|
||||
const std::string& provider_session_token, std::string* usage_report);
|
||||
virtual CdmResponseType ReleaseUsageInformation(
|
||||
const std::string& message, const std::string& signature,
|
||||
const std::string& provider_session_token);
|
||||
|
||||
bool GetRandom(size_t data_length, uint8_t* random_data);
|
||||
virtual bool GetRandom(size_t data_length, uint8_t* random_data);
|
||||
|
||||
private:
|
||||
void Init();
|
||||
@@ -82,8 +82,8 @@ class CryptoSession {
|
||||
std::string* deriv_context);
|
||||
void GenerateEncryptContext(const std::string& input_context,
|
||||
std::string* deriv_context);
|
||||
bool GenerateSignature(const std::string& message, bool use_rsa,
|
||||
std::string* signature);
|
||||
bool GenerateSignature(const std::string& message, std::string* signature);
|
||||
bool GenerateRsaSignature(const std::string& message, std::string* signature);
|
||||
size_t GetOffset(std::string message, std::string field);
|
||||
bool SetDestinationBufferType();
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
#if defined(UNIT_TEST)
|
||||
#include <gtest/gtest_prod.h>
|
||||
#endif
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class File;
|
||||
@@ -57,23 +61,38 @@ class DeviceFiles {
|
||||
virtual bool RetrieveUsageInfo(
|
||||
std::vector<std::pair<CdmKeyMessage, CdmKeyResponse> >* usage_info);
|
||||
|
||||
// For testing only
|
||||
static std::string GetCertificateFileName();
|
||||
static std::string GetLicenseFileNameExtension();
|
||||
static std::string GetUsageInfoFileName();
|
||||
void SetTestFile(File* file);
|
||||
|
||||
protected:
|
||||
bool Hash(const std::string& data, std::string* hash);
|
||||
private:
|
||||
bool StoreFile(const char* name, const std::string& serialized_file);
|
||||
bool RetrieveFile(const char* name, std::string* serialized_file);
|
||||
|
||||
private:
|
||||
// Certificate and offline licenses are now stored in security
|
||||
// level specific directories. In an earlier version they were
|
||||
// stored in a common directory and need to be copied over.
|
||||
virtual void SecurityLevelPathBackwardCompatibility();
|
||||
|
||||
// For testing only:
|
||||
static std::string GetCertificateFileName();
|
||||
static std::string GetLicenseFileNameExtension();
|
||||
static std::string GetUsageInfoFileName();
|
||||
void SetTestFile(File* file);
|
||||
#if defined(UNIT_TEST)
|
||||
FRIEND_TEST(DeviceFilesSecurityLevelTest, SecurityLevel);
|
||||
FRIEND_TEST(DeviceFilesStoreTest, StoreCertificate);
|
||||
FRIEND_TEST(DeviceFilesStoreTest, StoreLicense);
|
||||
FRIEND_TEST(DeviceFilesTest, DeleteLicense);
|
||||
FRIEND_TEST(DeviceFilesTest, ReadCertificate);
|
||||
FRIEND_TEST(DeviceFilesTest, RetrieveLicenses);
|
||||
FRIEND_TEST(DeviceFilesTest, SecurityLevelPathBackwardCompatibility);
|
||||
FRIEND_TEST(DeviceFilesTest, StoreLicenses);
|
||||
FRIEND_TEST(DeviceFilesTest, UpdateLicenseState);
|
||||
FRIEND_TEST(DeviceFilesUsageInfoTest, Delete);
|
||||
FRIEND_TEST(DeviceFilesUsageInfoTest, Read);
|
||||
FRIEND_TEST(DeviceFilesUsageInfoTest, Store);
|
||||
FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest);
|
||||
FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test);
|
||||
FRIEND_TEST(WvCdmUsageInfoTest, DISABLED_UsageInfo);
|
||||
#endif
|
||||
|
||||
File* file_;
|
||||
CdmSecurityLevel security_level_;
|
||||
bool initialized_;
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// File class. The implementation is platform dependent.
|
||||
|
||||
@@ -23,32 +23,32 @@ class CdmLicense {
|
||||
public:
|
||||
|
||||
CdmLicense() : session_(NULL), initialized_(false) {}
|
||||
~CdmLicense() {}
|
||||
virtual ~CdmLicense() {}
|
||||
|
||||
bool Init(const std::string& token, CryptoSession* session,
|
||||
virtual bool Init(const std::string& token, CryptoSession* session,
|
||||
PolicyEngine* policy_engine);
|
||||
|
||||
bool PrepareKeyRequest(const InitializationData& init_data,
|
||||
virtual bool PrepareKeyRequest(const InitializationData& init_data,
|
||||
const CdmLicenseType license_type,
|
||||
const CdmAppParameterMap& app_parameters,
|
||||
const CdmSessionId& session_id,
|
||||
CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
bool PrepareKeyUpdateRequest(bool is_renewal, CdmKeyMessage* signed_request,
|
||||
virtual bool PrepareKeyUpdateRequest(bool is_renewal, CdmKeyMessage* signed_request,
|
||||
std::string* server_url);
|
||||
CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response);
|
||||
CdmResponseType HandleKeyUpdateResponse(
|
||||
virtual CdmResponseType HandleKeyResponse(const CdmKeyResponse& license_response);
|
||||
virtual CdmResponseType HandleKeyUpdateResponse(
|
||||
bool is_renewal, const CdmKeyResponse& license_response);
|
||||
|
||||
bool RestoreOfflineLicense(const CdmKeyMessage& license_request,
|
||||
virtual bool RestoreOfflineLicense(const CdmKeyMessage& license_request,
|
||||
const CdmKeyResponse& license_response,
|
||||
const CdmKeyResponse& license_renewal_response);
|
||||
bool RestoreUsageLicense(const CdmKeyMessage& license_request,
|
||||
virtual bool RestoreUsageLicense(const CdmKeyMessage& license_request,
|
||||
const CdmKeyResponse& license_response);
|
||||
bool HasInitData() { return !stored_init_data_.empty(); }
|
||||
bool IsKeyLoaded(const KeyId& key_id);
|
||||
virtual bool HasInitData() { return !stored_init_data_.empty(); }
|
||||
virtual bool IsKeyLoaded(const KeyId& key_id);
|
||||
|
||||
std::string provider_session_token() { return provider_session_token_; }
|
||||
virtual std::string provider_session_token() { return provider_session_token_; }
|
||||
|
||||
private:
|
||||
bool PrepareServiceCertificateRequest(CdmKeyMessage* signed_request,
|
||||
|
||||
@@ -17,6 +17,8 @@ typedef enum {
|
||||
LOG_VERBOSE
|
||||
} LogPriority;
|
||||
|
||||
extern LogPriority g_cutoff;
|
||||
|
||||
// Enable/disable verbose logging (LOGV).
|
||||
// This function is supplied for cases where the system layer does not
|
||||
// initialize logging. This is also needed to initialize logging in
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// oemcrypto_adapter.h
|
||||
// This interface allows CDM to open a Level 3 session instead of
|
||||
// letting the wrapper function choose between level 1 or level 3.
|
||||
//
|
||||
#ifndef WVCDM_CORE_OEMCRYPTO_ADAPTER_H_
|
||||
#define WVCDM_CORE_OEMCRYPTO_ADAPTER_H_
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
using video_widevine_server::sdk::LicenseIdentification;
|
||||
|
||||
class Clock;
|
||||
class PolicyEngineTest;
|
||||
|
||||
@@ -18,53 +20,46 @@ class PolicyEngineTest;
|
||||
class PolicyEngine {
|
||||
public:
|
||||
PolicyEngine();
|
||||
~PolicyEngine();
|
||||
virtual ~PolicyEngine();
|
||||
|
||||
// The value returned should be taken as a hint rather than an absolute
|
||||
// status. It is computed during the last call to either SetLicense/
|
||||
// UpdateLicense/OnTimerEvent/BeginDecryption and may be out of sync
|
||||
// depending on the amount of time elapsed. The current decryption
|
||||
// status is not calculated to avoid overhead in the decryption path.
|
||||
inline bool can_decrypt() { return can_decrypt_; }
|
||||
virtual bool can_decrypt() { return can_decrypt_; }
|
||||
|
||||
// OnTimerEvent is called when a timer fires. It notifies the Policy Engine
|
||||
// that the timer has fired and that it should check whether any events have
|
||||
// occurred since the last timer event. If so, it sets event_occurred to true
|
||||
// and sets event to point to the event that occurred. If not, it sets
|
||||
// event_occurred to false.
|
||||
void OnTimerEvent(bool* event_occurred, CdmEventType* event);
|
||||
virtual void OnTimerEvent(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);
|
||||
virtual void SetLicense(const video_widevine_server::sdk::License& license);
|
||||
|
||||
// Call this on first decrypt to set the start of playback. This is
|
||||
// for cases where usage begins not when the license is received,
|
||||
// but at the start of playback
|
||||
void BeginDecryption(void);
|
||||
// Call this on first decrypt to set the start of playback.
|
||||
virtual void BeginDecryption(void);
|
||||
|
||||
// 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);
|
||||
virtual void UpdateLicense(
|
||||
const video_widevine_server::sdk::License& license);
|
||||
|
||||
CdmResponseType Query(CdmQueryMap* key_info);
|
||||
virtual CdmResponseType Query(CdmQueryMap* key_info);
|
||||
|
||||
const video_widevine_server::sdk::LicenseIdentification& license_id() {
|
||||
return license_id_;
|
||||
}
|
||||
|
||||
bool IsLicenseDurationExpired(int64_t current_time);
|
||||
bool IsPlaybackDurationExpired(int64_t current_time);
|
||||
virtual const LicenseIdentification& license_id() { return license_id_; }
|
||||
|
||||
private:
|
||||
typedef enum {
|
||||
kLicenseStateInitial,
|
||||
kLicenseStateInitialPendingUsage,
|
||||
kLicenseStateCanPlay,
|
||||
kLicenseStateNeedRenewal,
|
||||
kLicenseStateWaitingLicenseUpdate,
|
||||
@@ -73,6 +68,11 @@ class PolicyEngine {
|
||||
|
||||
void Init(Clock* clock);
|
||||
|
||||
bool IsLicenseDurationExpired(int64_t current_time);
|
||||
int64_t GetLicenseDurationRemaining(int64_t current_time);
|
||||
bool IsPlaybackDurationExpired(int64_t current_time);
|
||||
int64_t GetPlaybackDurationRemaining(int64_t current_time);
|
||||
|
||||
bool IsRenewalDelayExpired(int64_t current_time);
|
||||
bool IsRenewalRecoveryDurationExpired(int64_t current_time);
|
||||
bool IsRenewalRetryIntervalExpired(int64_t current_time);
|
||||
|
||||
@@ -24,30 +24,27 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "openssl/evp.h"
|
||||
#include "openssl/rsa.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
class AesCbcKey {
|
||||
public:
|
||||
AesCbcKey() : initialized_(false) {};
|
||||
~AesCbcKey() {};
|
||||
AesCbcKey();
|
||||
~AesCbcKey();
|
||||
|
||||
bool Init(const std::string& key);
|
||||
bool Encrypt(const std::string& in, std::string* out, std::string* iv);
|
||||
|
||||
private:
|
||||
EVP_CIPHER_CTX ctx_;
|
||||
bool initialized_;
|
||||
std::string key_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(AesCbcKey);
|
||||
};
|
||||
|
||||
class RsaPublicKey {
|
||||
public:
|
||||
RsaPublicKey() : key_(NULL) {}
|
||||
RsaPublicKey();
|
||||
~RsaPublicKey();
|
||||
|
||||
// Initializes an RsaPublicKey object using a DER encoded PKCS#1 RSAPublicKey
|
||||
@@ -64,7 +61,7 @@ class RsaPublicKey {
|
||||
const std::string& signature);
|
||||
|
||||
private:
|
||||
RSA* key_;
|
||||
std::string serialized_key_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(RsaPublicKey);
|
||||
};
|
||||
|
||||
@@ -26,12 +26,6 @@ class Properties {
|
||||
public:
|
||||
static void Init();
|
||||
|
||||
static inline bool begin_license_usage_when_received() {
|
||||
return begin_license_usage_when_received_;
|
||||
}
|
||||
static inline bool require_explicit_renew_request() {
|
||||
return require_explicit_renew_request_;
|
||||
}
|
||||
static inline bool oem_crypto_use_secure_buffers() {
|
||||
return oem_crypto_use_secure_buffers_;
|
||||
}
|
||||
@@ -45,9 +39,6 @@ class Properties {
|
||||
static inline bool use_certificates_as_identification() {
|
||||
return use_certificates_as_identification_;
|
||||
}
|
||||
static inline bool decrypt_with_empty_session_support() {
|
||||
return decrypt_with_empty_session_support_;
|
||||
}
|
||||
static inline bool security_level_path_backward_compatibility_support() {
|
||||
return security_level_path_backward_compatibility_support_;
|
||||
}
|
||||
@@ -62,9 +53,10 @@ class Properties {
|
||||
static bool GetFactoryKeyboxPath(std::string* keybox);
|
||||
static bool GetOEMCryptoPath(std::string* library_name);
|
||||
static bool GetSecurityLevelDirectories(std::vector<std::string>* dirs);
|
||||
static const std::string GetSecurityLevel(const CdmSessionId& session_id);
|
||||
static const std::vector<uint8_t> GetServiceCertificate(
|
||||
const CdmSessionId& session_id);
|
||||
static bool GetSecurityLevel(const CdmSessionId& session_id,
|
||||
std::string* security_level);
|
||||
static bool GetServiceCertificate(const CdmSessionId& session_id,
|
||||
std::string* service_certificate);
|
||||
static bool UsePrivacyMode(const CdmSessionId& session_id);
|
||||
static uint32_t GetSessionSharingId(const CdmSessionId& session_id);
|
||||
|
||||
@@ -75,12 +67,6 @@ class Properties {
|
||||
private:
|
||||
static const CdmClientPropertySet* GetCdmClientPropertySet(
|
||||
const CdmSessionId& session_id);
|
||||
static void set_begin_license_usage_when_received(bool flag) {
|
||||
begin_license_usage_when_received_ = flag;
|
||||
}
|
||||
static void set_require_explicit_renew_request(bool flag) {
|
||||
require_explicit_renew_request_ = flag;
|
||||
}
|
||||
static void set_oem_crypto_use_secure_buffers(bool flag) {
|
||||
oem_crypto_use_secure_buffers_ = flag;
|
||||
}
|
||||
@@ -96,23 +82,17 @@ class Properties {
|
||||
static void set_use_certificates_as_identification(bool flag) {
|
||||
use_certificates_as_identification_ = flag;
|
||||
}
|
||||
static void set_decrypt_with_empty_session_support(bool flag) {
|
||||
decrypt_with_empty_session_support_ = flag;
|
||||
}
|
||||
static void set_security_level_path_backward_compatibility_support(
|
||||
bool flag) {
|
||||
security_level_path_backward_compatibility_support_ = flag;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool begin_license_usage_when_received_;
|
||||
static bool require_explicit_renew_request_;
|
||||
static bool oem_crypto_use_secure_buffers_;
|
||||
static bool oem_crypto_use_fifo_;
|
||||
static bool oem_crypto_use_userspace_buffers_;
|
||||
static bool oem_crypto_require_usage_tables_;
|
||||
static bool use_certificates_as_identification_;
|
||||
static bool decrypt_with_empty_session_support_;
|
||||
static bool security_level_path_backward_compatibility_support_;
|
||||
static scoped_ptr<CdmClientPropertySetMap> session_property_set_;
|
||||
|
||||
|
||||
@@ -28,11 +28,6 @@ typedef std::string CdmUsageInfoReleaseMessage;
|
||||
typedef std::string CdmProvisioningRequest;
|
||||
typedef std::string CdmProvisioningResponse;
|
||||
|
||||
// Types for shared host/cdm interface pairs used to shared vendor data.
|
||||
typedef std::pair<std::string, std::string> kStringPairs;
|
||||
typedef std::vector<uint8_t> kVectorBytes;
|
||||
typedef std::pair<std::string, kVectorBytes> kVectorPairs;
|
||||
|
||||
enum CdmResponseType {
|
||||
NO_ERROR,
|
||||
UNKNOWN_ERROR,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "cdm_engine.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
@@ -18,27 +20,30 @@
|
||||
|
||||
namespace {
|
||||
const uint32_t kUpdateUsageInformationPeriod = 60; // seconds
|
||||
const size_t kMinNoncesPerSession = 4;
|
||||
const size_t kUsageReportsPerRequest = 1;
|
||||
} // unnamed namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
bool CdmEngine::seeded_ = false;
|
||||
|
||||
CdmEngine::CdmEngine()
|
||||
: cert_provisioning_requested_security_level_(kLevelDefault),
|
||||
usage_session_(NULL),
|
||||
last_usage_information_update_time(0) {
|
||||
Properties::Init();
|
||||
if (!seeded_) {
|
||||
Clock clock;
|
||||
srand(clock.GetCurrentTime());
|
||||
seeded_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
CdmEngine::~CdmEngine() {
|
||||
CancelSessions();
|
||||
|
||||
if (NULL != usage_session_)
|
||||
delete usage_session_;
|
||||
|
||||
CdmSessionMap::iterator i(sessions_.begin());
|
||||
for (; i != sessions_.end(); ++i)
|
||||
for (; i != sessions_.end(); ++i) {
|
||||
delete i->second;
|
||||
}
|
||||
sessions_.clear();
|
||||
}
|
||||
|
||||
@@ -286,9 +291,6 @@ CdmResponseType CdmEngine::RestoreKey(
|
||||
CdmResponseType CdmEngine::CancelKeyRequest(const CdmSessionId& session_id) {
|
||||
LOGI("CdmEngine::CancelKeyRequest");
|
||||
|
||||
//TODO(gmorgan): Issue: what is semantics of canceling a key request. Should
|
||||
//this call cancel all keys for the session?
|
||||
|
||||
CdmSessionMap::iterator iter = sessions_.find(session_id);
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::CancelKeyRequest: session_id not found = %s",
|
||||
@@ -494,11 +496,23 @@ CdmResponseType CdmEngine::HandleProvisioningResponse(
|
||||
wrapped_key);
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
|
||||
if (NULL == usage_session_) {
|
||||
usage_session_ = new CdmSession(NULL);
|
||||
CdmResponseType CdmEngine::Unprovision(CdmSecurityLevel security_level) {
|
||||
DeviceFiles handle;
|
||||
if (!handle.Init(security_level)) {
|
||||
LOGE("CdmEngine::Unprovision: unable to initialize device files");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (!handle.DeleteAllFiles()) {
|
||||
LOGE("CdmEngine::Unprovision: unable to delete files");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
|
||||
usage_session_.reset(new CdmSession(NULL));
|
||||
|
||||
CdmResponseType status = usage_session_->Init();
|
||||
if (NO_ERROR != status) {
|
||||
LOGE("CdmEngine::GetUsageInfo: session init error");
|
||||
@@ -523,35 +537,32 @@ CdmResponseType CdmEngine::GetUsageInfo(CdmUsageInfo* usage_info) {
|
||||
}
|
||||
|
||||
std::string server_url;
|
||||
// rate limit secure stop messages based on minimum nonce
|
||||
// table size per session
|
||||
usage_info->resize(license_info.size() >= kMinNoncesPerSession - 1
|
||||
? kMinNoncesPerSession - 1
|
||||
: license_info.size());
|
||||
for (size_t i = 0; i < usage_info->size(); ++i) {
|
||||
status = usage_session_->RestoreUsageSession(license_info[i].first,
|
||||
license_info[i].second);
|
||||
if (KEY_ADDED != status) {
|
||||
LOGE("CdmEngine::GetUsageInfo: restore usage session error: %ld",
|
||||
status);
|
||||
usage_info->clear();
|
||||
return status;
|
||||
}
|
||||
status = usage_session_->GenerateReleaseRequest(&(*usage_info)[i],
|
||||
&server_url);
|
||||
if (KEY_MESSAGE != status) {
|
||||
LOGE("CdmEngine::GetUsageInfo: generate release request error: %ld",
|
||||
status);
|
||||
usage_info->clear();
|
||||
return status;
|
||||
}
|
||||
usage_info->resize(kUsageReportsPerRequest);
|
||||
|
||||
uint32_t index = rand() % license_info.size();
|
||||
status = usage_session_->RestoreUsageSession(license_info[index].first,
|
||||
license_info[index].second);
|
||||
if (KEY_ADDED != status) {
|
||||
LOGE("CdmEngine::GetUsageInfo: restore usage session (%d) error %ld",
|
||||
index, status);
|
||||
usage_info->clear();
|
||||
return status;
|
||||
}
|
||||
|
||||
status = usage_session_->GenerateReleaseRequest(&(*usage_info)[0], &server_url);
|
||||
|
||||
if (KEY_MESSAGE != status) {
|
||||
LOGE("CdmEngine::GetUsageInfo: generate release request error: %ld",
|
||||
status);
|
||||
usage_info->clear();
|
||||
return status;
|
||||
}
|
||||
return KEY_MESSAGE;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::ReleaseUsageInfo(
|
||||
const CdmUsageInfoReleaseMessage& message) {
|
||||
if (NULL == usage_session_) {
|
||||
if (NULL == usage_session_.get()) {
|
||||
LOGE("CdmEngine::ReleaseUsageInfo: cdm session not initialized");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
@@ -559,9 +570,8 @@ CdmResponseType CdmEngine::ReleaseUsageInfo(
|
||||
CdmResponseType status = usage_session_->ReleaseKey(message);
|
||||
if (NO_ERROR != status) {
|
||||
LOGE("CdmEngine::ReleaseUsageInfo: release key error: %ld", status);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
return NO_ERROR;
|
||||
return status;
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::Decrypt(
|
||||
@@ -588,23 +598,23 @@ CdmResponseType CdmEngine::Decrypt(
|
||||
LOGE("CdmEngine::Decrypt: no dest decrypt buffer");
|
||||
return KEY_ERROR;
|
||||
} // else we must be level 1 direct and we don't need to return a buffer.
|
||||
// TODO:(eschacker) look at renaming Properties::oem_crypto_use_fifo()
|
||||
// to something like Properties::oem_crypto_use_direct_rendering().
|
||||
}
|
||||
|
||||
CdmSessionMap::iterator iter;
|
||||
if (session_id.empty()) {
|
||||
if (!Properties::decrypt_with_empty_session_support()) return KEY_ERROR;
|
||||
|
||||
// Loop through the sessions to find the session containing the key_id.
|
||||
for (iter = sessions_.begin(); iter != sessions_.end(); ++iter) {
|
||||
if (iter->second->IsKeyLoaded(*parameters.key_id)) break;
|
||||
if (iter->second->IsKeyLoaded(*parameters.key_id)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
iter = sessions_.find(session_id);
|
||||
}
|
||||
if (iter == sessions_.end()) {
|
||||
LOGE("CdmEngine::Decrypt: session_id not found = %s", session_id.c_str());
|
||||
LOGE("CdmEngine::Decrypt: session not found: id=%s, id size=%d",
|
||||
session_id.c_str(),
|
||||
session_id.size());
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
@@ -679,11 +689,6 @@ bool CdmEngine::ValidateKeySystem(const CdmKeySystem& key_system) {
|
||||
return (key_system.find("widevine") != std::string::npos);
|
||||
}
|
||||
|
||||
bool CdmEngine::CancelSessions() {
|
||||
// TODO(gmorgan) Implement CancelSessions()
|
||||
return true;
|
||||
}
|
||||
|
||||
void CdmEngine::OnTimerEvent() {
|
||||
Clock clock;
|
||||
uint64_t current_time = clock.GetCurrentTime();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
// Copyright 2012 Google Inc. All Rights Reserved.
|
||||
// Author: jfore@google.com (Jeff Fore), rkuroiwa@google.com (Rintaro Kuroiwa)
|
||||
|
||||
#include "cdm_session.h"
|
||||
|
||||
@@ -30,11 +29,10 @@ CdmSession::CdmSession(const CdmClientPropertySet* cdm_client_property_set)
|
||||
: session_id_(GenerateSessionId()),
|
||||
crypto_session_(NULL),
|
||||
license_received_(false),
|
||||
reinitialize_session_(false),
|
||||
is_offline_(false),
|
||||
is_release_(false),
|
||||
is_usage_update_needed_(false),
|
||||
is_certificate_loaded_(false) {
|
||||
is_initial_decryption_(true) {
|
||||
if (cdm_client_property_set) {
|
||||
Properties::AddSessionPropertySet(session_id_, cdm_client_property_set);
|
||||
}
|
||||
@@ -51,8 +49,10 @@ CdmResponseType CdmSession::Init() {
|
||||
std::string token;
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
DeviceFiles handle;
|
||||
std::string wrapped_key;
|
||||
if (!handle.Init(session.get()->GetSecurityLevel()) ||
|
||||
!handle.RetrieveCertificate(&token, &wrapped_key_)) {
|
||||
!handle.RetrieveCertificate(&token, &wrapped_key) ||
|
||||
!session->LoadCertificatePrivateKey(wrapped_key)) {
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
} else {
|
||||
@@ -64,7 +64,7 @@ CdmResponseType CdmSession::Init() {
|
||||
|
||||
crypto_session_.reset(session.release());
|
||||
license_received_ = false;
|
||||
reinitialize_session_ = false;
|
||||
is_initial_decryption_ = true;
|
||||
return NO_ERROR;
|
||||
}
|
||||
|
||||
@@ -94,15 +94,6 @@ CdmResponseType CdmSession::RestoreOfflineSession(
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
if (is_certificate_loaded_ ||
|
||||
crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) {
|
||||
is_certificate_loaded_ = true;
|
||||
} else {
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!license_parser_.RestoreOfflineLicense(key_request_, key_response_,
|
||||
offline_key_renewal_response_)) {
|
||||
return UNKNOWN_ERROR;
|
||||
@@ -120,15 +111,6 @@ CdmResponseType CdmSession::RestoreUsageSession(
|
||||
|
||||
key_request_ = key_request;
|
||||
key_response_ = key_response;
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
if (is_certificate_loaded_ ||
|
||||
crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) {
|
||||
is_certificate_loaded_ = true;
|
||||
} else {
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!license_parser_.RestoreUsageLicense(key_request_, key_response_)) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
@@ -139,25 +121,10 @@ CdmResponseType CdmSession::RestoreUsageSession(
|
||||
return KEY_ADDED;
|
||||
}
|
||||
|
||||
bool CdmSession::VerifySession(const CdmKeySystem& key_system,
|
||||
const InitializationData& 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 InitializationData& init_data, const CdmLicenseType license_type,
|
||||
const CdmAppParameterMap& app_parameters, CdmKeyMessage* key_request,
|
||||
std::string* server_url) {
|
||||
if (reinitialize_session_) {
|
||||
CdmResponseType sts = Init();
|
||||
if (sts != NO_ERROR) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: Reinitialization failed");
|
||||
return sts;
|
||||
}
|
||||
}
|
||||
|
||||
if (crypto_session_.get() == NULL) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: Invalid crypto session");
|
||||
return UNKNOWN_ERROR;
|
||||
@@ -181,9 +148,7 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
if (is_release_) {
|
||||
return GenerateReleaseRequest(key_request, server_url);
|
||||
} else if (license_received_) { // renewal
|
||||
return Properties::require_explicit_renew_request()
|
||||
? UNKNOWN_ERROR
|
||||
: GenerateRenewalRequest(key_request, server_url);
|
||||
return GenerateRenewalRequest(key_request, server_url);
|
||||
} else {
|
||||
if (!init_data.is_supported()) {
|
||||
LOGW("CdmSession::GenerateKeyRequest: unsupported init data type (%s)",
|
||||
@@ -195,16 +160,6 @@ CdmResponseType CdmSession::GenerateKeyRequest(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (Properties::use_certificates_as_identification()) {
|
||||
if (is_certificate_loaded_ ||
|
||||
crypto_session_->LoadCertificatePrivateKey(wrapped_key_)) {
|
||||
is_certificate_loaded_ = true;
|
||||
} else {
|
||||
reinitialize_session_ = true;
|
||||
return NEED_PROVISIONING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!license_parser_.PrepareKeyRequest(init_data, license_type,
|
||||
app_parameters, session_id_,
|
||||
key_request, server_url)) {
|
||||
@@ -235,11 +190,10 @@ CdmResponseType CdmSession::AddKey(const CdmKeyResponse& key_response,
|
||||
}
|
||||
|
||||
if (is_release_) {
|
||||
return ReleaseKey(key_response);
|
||||
CdmResponseType sts = ReleaseKey(key_response);
|
||||
return (NO_ERROR == sts) ? KEY_ADDED : sts;
|
||||
} else if (license_received_) { // renewal
|
||||
return Properties::require_explicit_renew_request()
|
||||
? UNKNOWN_ERROR
|
||||
: RenewKey(key_response);
|
||||
return RenewKey(key_response);
|
||||
} else {
|
||||
CdmResponseType sts = license_parser_.HandleKeyResponse(key_response);
|
||||
|
||||
@@ -313,7 +267,6 @@ CdmResponseType CdmSession::QueryKeyControlInfo(CdmQueryMap* key_info) {
|
||||
|
||||
// CancelKeyRequest() - Cancel session.
|
||||
CdmResponseType CdmSession::CancelKeyRequest() {
|
||||
// TODO(gmorgan): cancel and clean up session
|
||||
crypto_session_->Close();
|
||||
return NO_ERROR;
|
||||
}
|
||||
@@ -324,17 +277,12 @@ CdmResponseType CdmSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
return UNKNOWN_ERROR;
|
||||
|
||||
CdmResponseType status = crypto_session_->Decrypt(params);
|
||||
// TODO(rfrias): Remove after support for OEMCrypto_ERROR_KEY_EXPIRED is in
|
||||
if (UNKNOWN_ERROR == status) {
|
||||
Clock clock;
|
||||
int64_t current_time = clock.GetCurrentTime();
|
||||
if (policy_engine_.IsLicenseDurationExpired(current_time) ||
|
||||
policy_engine_.IsPlaybackDurationExpired(current_time)) {
|
||||
return NEED_KEY;
|
||||
}
|
||||
}
|
||||
|
||||
if (NO_ERROR == status) {
|
||||
if (is_initial_decryption_) {
|
||||
policy_engine_.BeginDecryption();
|
||||
is_initial_decryption_ = false;
|
||||
}
|
||||
if (!is_usage_update_needed_) {
|
||||
is_usage_update_needed_ =
|
||||
!license_parser_.provider_session_token().empty();
|
||||
@@ -390,7 +338,7 @@ CdmResponseType CdmSession::GenerateReleaseRequest(CdmKeyMessage* key_request,
|
||||
CdmResponseType CdmSession::ReleaseKey(const CdmKeyResponse& key_response) {
|
||||
CdmResponseType sts = license_parser_.HandleKeyUpdateResponse(false,
|
||||
key_response);
|
||||
if (NO_ERROR != sts)
|
||||
if (KEY_ADDED != sts)
|
||||
return sts;
|
||||
|
||||
if (is_offline_ || !license_parser_.provider_session_token().empty()) {
|
||||
@@ -405,8 +353,6 @@ bool CdmSession::IsKeyLoaded(const KeyId& key_id) {
|
||||
|
||||
CdmSessionId CdmSession::GenerateSessionId() {
|
||||
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 SESSION_ID_PREFIX + IntToString(++session_num);
|
||||
}
|
||||
|
||||
@@ -539,8 +485,9 @@ void CdmSession::OnKeyReleaseEvent(const CdmKeySetId& key_set_id) {
|
||||
}
|
||||
|
||||
SecurityLevel CdmSession::GetRequestedSecurityLevel() {
|
||||
if (Properties::GetSecurityLevel(session_id_)
|
||||
.compare(QUERY_VALUE_SECURITY_LEVEL_L3) == 0) {
|
||||
std::string security_level;
|
||||
if (Properties::GetSecurityLevel(session_id_, &security_level) &&
|
||||
security_level == QUERY_VALUE_SECURITY_LEVEL_L3) {
|
||||
return kLevel3;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace {
|
||||
|
||||
// URL for Google Provisioning Server.
|
||||
// This server supplies the certificate that is needed
|
||||
// The provisioning server supplies the certificate that is needed
|
||||
// to communicate with the License Server.
|
||||
const std::string kProvisioningServerUrl =
|
||||
"https://www.googleapis.com/"
|
||||
@@ -28,10 +28,9 @@ using video_widevine_server::sdk::ProvisioningResponse;
|
||||
using video_widevine_server::sdk::SignedProvisioningMessage;
|
||||
|
||||
/*
|
||||
* This function converts SignedProvisioningRequest into base64 string.
|
||||
* It then wraps it in JSON format expected by the Apiary frontend.
|
||||
* Apiary requires the base64 encoding to replace '+' with minus '-',
|
||||
* and '/' with underscore '_'; opposite to stubby's.
|
||||
* This function converts SignedProvisioningRequest into base64 string. It then
|
||||
* wraps it in JSON format expected by the frontend. This server requires a
|
||||
* "web-safe" base 64 encoding, where '+' becomes '-' and '/' becomes '_'.
|
||||
*
|
||||
* Returns the JSON formated string in *request. The JSON string will be
|
||||
* appended as a query parameter, i.e. signedRequest=<base 64 encoded
|
||||
|
||||
@@ -159,6 +159,25 @@ bool CryptoSession::GetDeviceUniqueId(std::string* device_id) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GetApiVersion(uint32_t* version) {
|
||||
if (!version) {
|
||||
LOGE("CryptoSession::GetApiVersion: No buffer passed to method.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
CdmSecurityLevel level = GetSecurityLevel();
|
||||
SecurityLevel security_level = kLevelDefault;
|
||||
if (kSecurityLevelL3 == level) security_level = kLevel3;
|
||||
|
||||
LOGV("CryptoSession::GetApiVersion: Lock");
|
||||
AutoLock auto_lock(crypto_lock_);
|
||||
*version = OEMCrypto_APIVersion(security_level);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GetSystemId(uint32_t* system_id) {
|
||||
if (!system_id) {
|
||||
LOGE("CryptoSession::GetSystemId : No buffer passed to method.");
|
||||
@@ -270,9 +289,9 @@ bool CryptoSession::PrepareRequest(const std::string& message,
|
||||
if (!Properties::use_certificates_as_identification() || is_provisioning) {
|
||||
if (!GenerateDerivedKeys(message)) return false;
|
||||
|
||||
if (!GenerateSignature(message, false, signature)) return false;
|
||||
if (!GenerateSignature(message, signature)) return false;
|
||||
} else {
|
||||
if (!GenerateSignature(message, true, signature)) return false;
|
||||
if (!GenerateRsaSignature(message, signature)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -290,7 +309,7 @@ bool CryptoSession::PrepareRenewalRequest(const std::string& message,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GenerateSignature(message, false, signature)) {
|
||||
if (!GenerateSignature(message, signature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -508,55 +527,77 @@ bool CryptoSession::GenerateDerivedKeys(const std::string& message,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateSignature(const std::string& message, bool use_rsa,
|
||||
bool CryptoSession::GenerateSignature(const std::string& message,
|
||||
std::string* signature) {
|
||||
LOGV("GenerateSignature: id=%ld", (uint32_t)oec_session_id_);
|
||||
if (!signature) return false;
|
||||
|
||||
size_t length = 0;
|
||||
OEMCryptoResult sts = OEMCrypto_SUCCESS;
|
||||
if (use_rsa) {
|
||||
sts = OEMCrypto_GenerateRSASignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(), NULL, &length, kSign_RSASSA_PSS);
|
||||
size_t length = signature->size();
|
||||
OEMCryptoResult sts = OEMCrypto_GenerateSignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
||||
&length);
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
||||
LOGD("GenerateSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
|
||||
LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
length = kSignatureSize;
|
||||
// TODO(gmorgan,kqyang): Use OEMCrypto_GenerateSignature to determine
|
||||
// length after marvell fixes their implementation.
|
||||
/*
|
||||
sts = OEMCrypto_GenerateSignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(), NULL, &length);
|
||||
*/
|
||||
}
|
||||
|
||||
signature->resize(length);
|
||||
|
||||
if (use_rsa) {
|
||||
sts = OEMCrypto_GenerateRSASignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
||||
&length, kSign_RSASSA_PSS);
|
||||
} else {
|
||||
// Retry with proper-sized signature buffer
|
||||
signature->resize(length);
|
||||
sts = OEMCrypto_GenerateSignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
||||
&length);
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Trim signature buffer
|
||||
signature->resize(length);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CryptoSession::GenerateRsaSignature(const std::string& message,
|
||||
std::string* signature) {
|
||||
LOGV("GenerateRsaSignature: id=%ld", (uint32_t)oec_session_id_);
|
||||
if (!signature) return false;
|
||||
|
||||
size_t length = signature->size();
|
||||
OEMCryptoResult sts = OEMCrypto_GenerateRSASignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
||||
&length, kSign_RSASSA_PSS);
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGD("GenerateSignature: OEMCrypto_GenerateSignature err=%d", sts);
|
||||
return false;
|
||||
if (OEMCrypto_ERROR_SHORT_BUFFER != sts) {
|
||||
LOGD("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Retry with proper-sized signature buffer
|
||||
signature->resize(length);
|
||||
sts = OEMCrypto_GenerateRSASignature(
|
||||
oec_session_id_, reinterpret_cast<const uint8_t*>(message.data()),
|
||||
message.size(),
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(signature->data())),
|
||||
&length, kSign_RSASSA_PSS);
|
||||
|
||||
if (OEMCrypto_SUCCESS != sts) {
|
||||
LOGD("GenerateRsaSignature: OEMCrypto_GenerateRSASignature err=%d", sts);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(fredgc): b/8878371
|
||||
// remove in K, when L1 library reports correct length.
|
||||
// Trim signature buffer
|
||||
signature->resize(length);
|
||||
|
||||
return true;
|
||||
@@ -588,7 +629,8 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
buffer_descriptor.buffer.clear.address =
|
||||
static_cast<uint8_t*>(params.decrypt_buffer) +
|
||||
params.decrypt_buffer_offset;
|
||||
buffer_descriptor.buffer.clear.max_length = params.decrypt_buffer_length;
|
||||
buffer_descriptor.buffer.clear.max_length =
|
||||
params.decrypt_buffer_length - params.decrypt_buffer_offset;
|
||||
break;
|
||||
case OEMCrypto_BufferType_Secure:
|
||||
buffer_descriptor.buffer.secure.handle = params.decrypt_buffer;
|
||||
@@ -768,8 +810,6 @@ bool CryptoSession::RewrapDeviceRSAKey(const std::string& message,
|
||||
reinterpret_cast<uint8_t*>(&(*wrapped_rsa_key)[0]),
|
||||
&wrapped_rsa_key_length);
|
||||
|
||||
// TODO(fredgc): b/8878371
|
||||
// remove in K, when L1 library reports correct length.
|
||||
wrapped_rsa_key->resize(wrapped_rsa_key_length);
|
||||
|
||||
if (OEMCrypto_SUCCESS != status) {
|
||||
|
||||
@@ -2,13 +2,20 @@
|
||||
|
||||
#include "device_files.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
# define SHA256 CC_SHA256
|
||||
# define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH
|
||||
#else
|
||||
# include <openssl/sha.h>
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include "device_files.pb.h"
|
||||
#include "file_store.h"
|
||||
#include "log.h"
|
||||
#include "openssl/sha.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
|
||||
@@ -22,6 +29,7 @@ using video_widevine_client::sdk::UsageInfo;
|
||||
using video_widevine_client::sdk::UsageInfo_ProviderSession;
|
||||
|
||||
namespace {
|
||||
|
||||
const char kCertificateFileName[] = "cert.bin";
|
||||
const char kUsageInfoFileName[] = "usage.bin";
|
||||
const char kLicenseFileNameExt[] = ".lic";
|
||||
@@ -31,6 +39,18 @@ const char* kSecurityLevelPathCompatibilityExclusionList[] = {"ay64.dat"};
|
||||
size_t kSecurityLevelPathCompatibilityExclusionListSize =
|
||||
sizeof(kSecurityLevelPathCompatibilityExclusionList) /
|
||||
sizeof(*kSecurityLevelPathCompatibilityExclusionList);
|
||||
|
||||
bool Hash(const std::string& data, std::string* hash) {
|
||||
if (!hash) return false;
|
||||
hash->resize(SHA256_DIGEST_LENGTH);
|
||||
|
||||
const unsigned char* input =
|
||||
reinterpret_cast<const unsigned char*>(data.data());
|
||||
unsigned char* output = reinterpret_cast<unsigned char*>(&(*hash)[0]);
|
||||
SHA256(input, data.size(), output);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // unnamed namespace
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -343,18 +363,13 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UsageInfo* updated_info = file.mutable_usage_info();
|
||||
UsageInfo info(*(const_cast<const UsageInfo*>(updated_info)));
|
||||
updated_info->clear_sessions();
|
||||
UsageInfo* usage_info = file.mutable_usage_info();
|
||||
int index = 0;
|
||||
bool found = false;
|
||||
for (int i = 0; i < info.sessions_size(); ++i) {
|
||||
if (info.sessions(i).token().compare(provider_session_token) == 0) {
|
||||
for (; index < usage_info->sessions_size(); ++index) {
|
||||
if (usage_info->sessions(index).token().compare(provider_session_token) == 0) {
|
||||
found = true;
|
||||
} else {
|
||||
updated_info->add_sessions()->set_token(info.sessions(i).token());
|
||||
updated_info->add_sessions()->set_license_request(
|
||||
info.sessions(i).license_request());
|
||||
updated_info->add_sessions()->set_license(info.sessions(i).license());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,6 +379,13 @@ bool DeviceFiles::DeleteUsageInfo(const std::string& provider_session_token) {
|
||||
return false;
|
||||
}
|
||||
|
||||
google::protobuf::RepeatedPtrField<UsageInfo_ProviderSession>* sessions =
|
||||
usage_info->mutable_sessions();
|
||||
if (index < usage_info->sessions_size() - 1) {
|
||||
sessions->SwapElements(index, usage_info->sessions_size() - 1);
|
||||
}
|
||||
sessions->RemoveLast();
|
||||
|
||||
file.SerializeToString(&serialized_file);
|
||||
return StoreFile(kUsageInfoFileName, serialized_file);
|
||||
}
|
||||
@@ -430,17 +452,6 @@ bool DeviceFiles::RetrieveUsageInfo(std::vector<
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::Hash(const std::string& data, std::string* hash) {
|
||||
if (!hash) return false;
|
||||
|
||||
hash->resize(SHA256_DIGEST_LENGTH);
|
||||
SHA256_CTX sha256;
|
||||
SHA256_Init(&sha256);
|
||||
SHA256_Update(&sha256, data.data(), data.size());
|
||||
SHA256_Final(reinterpret_cast<unsigned char*>(&(*hash)[0]), &sha256);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DeviceFiles::StoreFile(const char* name,
|
||||
const std::string& serialized_file) {
|
||||
if (!file_) {
|
||||
|
||||
@@ -47,7 +47,7 @@ message File {
|
||||
enum FileType {
|
||||
DEVICE_CERTIFICATE = 1;
|
||||
LICENSE = 2;
|
||||
USAGE_INFO = 2;
|
||||
USAGE_INFO = 3;
|
||||
}
|
||||
|
||||
enum FileVersion {
|
||||
@@ -63,5 +63,6 @@ message File {
|
||||
|
||||
message HashedFile {
|
||||
optional bytes file = 1;
|
||||
// A raw (not hex-encoded) SHA256, taken over the bytes of 'file'.
|
||||
optional bytes hash = 2;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ std::string kDeviceNameKey = "device_name";
|
||||
std::string kProductNameKey = "product_name";
|
||||
std::string kBuildInfoKey = "build_info";
|
||||
std::string kDeviceIdKey = "device_id";
|
||||
std::string kOemCryptoApiVersion = "oemcrypto_api_version";
|
||||
const unsigned char kServiceCertificateCAPublicKey[] = {
|
||||
0x30, 0x82, 0x01, 0x8a, 0x02, 0x82, 0x01, 0x81,
|
||||
0x00, 0xb4, 0xfe, 0x39, 0xc3, 0x65, 0x90, 0x03,
|
||||
@@ -193,10 +194,10 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data,
|
||||
}
|
||||
|
||||
bool privacy_mode_enabled = Properties::UsePrivacyMode(session_id);
|
||||
std::vector<uint8_t> cert = Properties::GetServiceCertificate(session_id);
|
||||
std::string serialized_service_certificate(cert.begin(), cert.end());
|
||||
|
||||
if (serialized_service_certificate.empty())
|
||||
std::string serialized_service_certificate;
|
||||
if (!Properties::GetServiceCertificate(session_id,
|
||||
&serialized_service_certificate) ||
|
||||
serialized_service_certificate.empty())
|
||||
serialized_service_certificate = service_certificate_;
|
||||
|
||||
if (privacy_mode_enabled && serialized_service_certificate.empty()) {
|
||||
@@ -254,12 +255,17 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data,
|
||||
client_info->set_name(kBuildInfoKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
|
||||
if (session_->GetDeviceUniqueId(&value)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kDeviceIdKey);
|
||||
client_info->set_value(value);
|
||||
}
|
||||
uint32_t version = 0;
|
||||
if (session_->GetApiVersion(&version)) {
|
||||
client_info = client_id->add_client_info();
|
||||
client_info->set_name(kOemCryptoApiVersion);
|
||||
client_info->set_value(UintToString(version));
|
||||
}
|
||||
|
||||
if (privacy_mode_enabled) {
|
||||
EncryptedClientIdentification* encrypted_client_id =
|
||||
@@ -353,7 +359,7 @@ bool CdmLicense::PrepareKeyRequest(const InitializationData& init_data,
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(jfore): The time field will be updated once the cdm wrapper
|
||||
// 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);
|
||||
|
||||
@@ -432,8 +438,9 @@ bool CdmLicense::PrepareKeyUpdateRequest(bool is_renewal,
|
||||
if (!is_renewal) {
|
||||
if (license_id.has_provider_session_token()) {
|
||||
std::string usage_report;
|
||||
if (!session_->GenerateUsageReport(license_id.provider_session_token(),
|
||||
&usage_report)) {
|
||||
if (NO_ERROR !=
|
||||
session_->GenerateUsageReport(license_id.provider_session_token(),
|
||||
&usage_report)) {
|
||||
return false;
|
||||
}
|
||||
current_license->set_session_usage_table_entry(usage_report);
|
||||
@@ -560,7 +567,6 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
std::string provider_session_token;
|
||||
if (license.id().has_provider_session_token())
|
||||
provider_session_token_ = license.id().provider_session_token();
|
||||
|
||||
@@ -568,9 +574,6 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
server_url_ = license.policy().renewal_server_url();
|
||||
}
|
||||
|
||||
// TODO(kqyang, jfore, gmorgan): change SetLicense function signature to
|
||||
// be able to return true/false to accept/reject the license. (Pending code
|
||||
// merge from Eureka)
|
||||
policy_engine_->SetLicense(license);
|
||||
|
||||
CdmResponseType resp = session_->LoadKeys(signed_response.msg(),
|
||||
@@ -578,7 +581,7 @@ CdmResponseType CdmLicense::HandleKeyResponse(
|
||||
mac_key_iv,
|
||||
mac_key,
|
||||
key_array,
|
||||
provider_session_token);
|
||||
provider_session_token_);
|
||||
|
||||
if (KEY_ADDED == resp) {
|
||||
loaded_keys_.clear();
|
||||
@@ -630,27 +633,23 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
|
||||
return KEY_ERROR;
|
||||
}
|
||||
|
||||
if (is_renewal) {
|
||||
if (license.policy().has_renewal_server_url() &&
|
||||
license.policy().renewal_server_url().size() > 0) {
|
||||
server_url_ = license.policy().renewal_server_url();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (license.id().has_provider_session_token()) {
|
||||
provider_session_token_ = license.id().provider_session_token();
|
||||
session_->ReleaseUsageInformation(signed_response.msg(),
|
||||
signed_response.signature(),
|
||||
provider_session_token_);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(kqyang, jfore, gmorgan): change UpdateLicense function signature to
|
||||
// be able to return true/false to accept/reject the license. (Pending code
|
||||
// merge from Eureka)
|
||||
policy_engine_->UpdateLicense(license);
|
||||
|
||||
if (!is_renewal) return KEY_ADDED;
|
||||
if (!is_renewal) {
|
||||
if (!license.id().has_provider_session_token()) return KEY_ADDED;
|
||||
|
||||
provider_session_token_ = license.id().provider_session_token();
|
||||
CdmResponseType status =
|
||||
session_->ReleaseUsageInformation(signed_response.msg(),
|
||||
signed_response.signature(),
|
||||
provider_session_token_);
|
||||
return (NO_ERROR == status) ? KEY_ADDED : status;
|
||||
}
|
||||
|
||||
if (license.policy().has_renewal_server_url() &&
|
||||
license.policy().renewal_server_url().size() > 0) {
|
||||
server_url_ = license.policy().renewal_server_url();
|
||||
}
|
||||
|
||||
std::vector<CryptoKey> key_array = ExtractContentKeys(license);
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ class Adapter {
|
||||
if (level1_.InstallKeybox(keybox, size) != OEMCrypto_SUCCESS) {
|
||||
LOGE("Could NOT install keybox from %s. Falling Back to L3.",
|
||||
filename.c_str());
|
||||
false;
|
||||
return false;
|
||||
}
|
||||
LOGI("Installed keybox from %s", filename.c_str());
|
||||
return true;
|
||||
|
||||
@@ -76,7 +76,7 @@ extern "C" OEMCryptoResult OEMCrypto_LoadKeys(
|
||||
const uint8_t* enc_mac_key_iv, const uint8_t* enc_mac_key, size_t num_keys,
|
||||
const OEMCrypto_KeyObject* key_array,
|
||||
const uint8_t* pst, size_t pst_length) {
|
||||
return OEMCrypto_LoadKeys_V8(pair.session, message, message_length, signature,
|
||||
return OEMCrypto_LoadKeys_V8(session, message, message_length, signature,
|
||||
signature_length, enc_mac_key_iv, enc_mac_key,
|
||||
num_keys, key_array);
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
#include "policy_engine.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <limits.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "clock.h"
|
||||
#include "log.h"
|
||||
#include "properties.h"
|
||||
#include "string_conversions.h"
|
||||
#include "clock.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -59,7 +59,6 @@ void PolicyEngine::OnTimerEvent(bool* event_occurred, CdmEventType* event) {
|
||||
|
||||
// Test to determine if renewal should be attempted.
|
||||
switch (license_state_) {
|
||||
case kLicenseStateInitialPendingUsage:
|
||||
case kLicenseStateCanPlay: {
|
||||
if (IsRenewalDelayExpired(current_time))
|
||||
renewal_needed = true;
|
||||
@@ -115,11 +114,6 @@ void PolicyEngine::UpdateLicense(
|
||||
|
||||
policy_.MergeFrom(license.policy());
|
||||
|
||||
if (!policy_.can_play()) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
return;
|
||||
}
|
||||
|
||||
// some basic license validation
|
||||
if (license_state_ == kLicenseStateInitial) {
|
||||
// license start time needs to be present in the initial response
|
||||
@@ -158,35 +152,23 @@ void PolicyEngine::UpdateLicense(
|
||||
policy_max_duration_seconds_ = policy_.license_duration_seconds();
|
||||
}
|
||||
|
||||
if (Properties::begin_license_usage_when_received())
|
||||
playback_start_time_ = current_time;
|
||||
if (!policy_.can_play()) {
|
||||
license_state_ = kLicenseStateExpired;
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsLicenseDurationExpired(current_time)) return;
|
||||
if (IsPlaybackDurationExpired(current_time)) return;
|
||||
|
||||
// Update state
|
||||
if (Properties::begin_license_usage_when_received()) {
|
||||
if (policy_.renew_with_usage()) {
|
||||
license_state_ = kLicenseStateNeedRenewal;
|
||||
}
|
||||
else {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
can_decrypt_ = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (license_state_ == kLicenseStateInitial) {
|
||||
license_state_ = kLicenseStateInitialPendingUsage;
|
||||
}
|
||||
else {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
can_decrypt_ = true;
|
||||
}
|
||||
}
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
can_decrypt_ = true;
|
||||
}
|
||||
|
||||
void PolicyEngine::BeginDecryption() {
|
||||
if ((playback_start_time_ == 0) &&
|
||||
(!Properties::begin_license_usage_when_received())) {
|
||||
if (playback_start_time_ == 0) {
|
||||
switch (license_state_) {
|
||||
case kLicenseStateInitialPendingUsage:
|
||||
case kLicenseStateCanPlay:
|
||||
case kLicenseStateNeedRenewal:
|
||||
case kLicenseStateWaitingLicenseUpdate:
|
||||
playback_start_time_ = clock_->GetCurrentTime();
|
||||
@@ -194,12 +176,7 @@ void PolicyEngine::BeginDecryption() {
|
||||
if (policy_.renew_with_usage()) {
|
||||
license_state_ = kLicenseStateNeedRenewal;
|
||||
}
|
||||
else {
|
||||
license_state_ = kLicenseStateCanPlay;
|
||||
can_decrypt_ = true;
|
||||
}
|
||||
break;
|
||||
case kLicenseStateCanPlay:
|
||||
case kLicenseStateInitial:
|
||||
case kLicenseStateExpired:
|
||||
default:
|
||||
@@ -224,17 +201,10 @@ CdmResponseType PolicyEngine::Query(CdmQueryMap* key_info) {
|
||||
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
||||
(*key_info)[QUERY_KEY_RENEW_ALLOWED] = policy_.can_renew() ?
|
||||
QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
||||
int64_t remaining_time = policy_max_duration_seconds_ +
|
||||
license_received_time_ - current_time;
|
||||
if (remaining_time < 0)
|
||||
remaining_time = 0;
|
||||
ss << remaining_time;
|
||||
ss << GetLicenseDurationRemaining(current_time);
|
||||
(*key_info)[QUERY_KEY_LICENSE_DURATION_REMAINING] = ss.str();
|
||||
remaining_time = policy_.playback_duration_seconds() + playback_start_time_ -
|
||||
current_time;
|
||||
if (remaining_time < 0)
|
||||
remaining_time = 0;
|
||||
ss << remaining_time;
|
||||
ss.str("");
|
||||
ss << GetPlaybackDurationRemaining(current_time);
|
||||
(*key_info)[QUERY_KEY_PLAYBACK_DURATION_REMAINING] = ss.str();
|
||||
(*key_info)[QUERY_KEY_RENEWAL_SERVER_URL] = policy_.renewal_server_url();
|
||||
|
||||
@@ -255,6 +225,16 @@ bool PolicyEngine::IsLicenseDurationExpired(int64_t current_time) {
|
||||
current_time;
|
||||
}
|
||||
|
||||
int64_t PolicyEngine::GetLicenseDurationRemaining(int64_t current_time) {
|
||||
if (0 == policy_max_duration_seconds_) return LLONG_MAX;
|
||||
|
||||
int64_t remaining_time = policy_max_duration_seconds_
|
||||
+ license_received_time_ - current_time;
|
||||
|
||||
if (remaining_time < 0) remaining_time = 0;
|
||||
return remaining_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::IsPlaybackDurationExpired(int64_t current_time) {
|
||||
return (policy_.playback_duration_seconds() > 0) &&
|
||||
playback_start_time_ &&
|
||||
@@ -262,6 +242,17 @@ bool PolicyEngine::IsPlaybackDurationExpired(int64_t current_time) {
|
||||
current_time;
|
||||
}
|
||||
|
||||
int64_t PolicyEngine::GetPlaybackDurationRemaining(int64_t current_time) {
|
||||
if (0 == policy_.playback_duration_seconds()) return LLONG_MAX;
|
||||
if (0 == playback_start_time_) return policy_.playback_duration_seconds();
|
||||
|
||||
int64_t remaining_time = policy_.playback_duration_seconds()
|
||||
+ playback_start_time_ - current_time;
|
||||
|
||||
if (remaining_time < 0) remaining_time = 0;
|
||||
return remaining_time;
|
||||
}
|
||||
|
||||
bool PolicyEngine::IsRenewalDelayExpired(int64_t current_time) {
|
||||
return policy_.can_renew() &&
|
||||
(policy_.renewal_delay_seconds() > 0) &&
|
||||
|
||||
@@ -7,38 +7,60 @@
|
||||
|
||||
#include "privacy_crypto.h"
|
||||
|
||||
#include <openssl/aes.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "openssl/aes.h"
|
||||
#include "openssl/bio.h"
|
||||
#include "openssl/err.h"
|
||||
#include "openssl/pem.h"
|
||||
#include "openssl/sha.h"
|
||||
|
||||
namespace {
|
||||
const int kPssSaltLength = 20;
|
||||
const int kRsaPkcs1OaepPaddingLength = 41;
|
||||
|
||||
RSA* GetKey(const std::string& serialized_key) {
|
||||
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()),
|
||||
serialized_key.size());
|
||||
if (bio == NULL) {
|
||||
LOGE("GetKey: BIO_new_mem_buf returned NULL");
|
||||
return NULL;
|
||||
}
|
||||
RSA* key = d2i_RSAPublicKey_bio(bio, NULL);
|
||||
BIO_free(bio);
|
||||
|
||||
if (key == NULL) {
|
||||
LOGE("GetKey: RSA key deserialization failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
void FreeKey(RSA* key) {
|
||||
if (key != NULL) {
|
||||
RSA_free(key);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
AesCbcKey::AesCbcKey() {}
|
||||
|
||||
AesCbcKey::~AesCbcKey() {}
|
||||
|
||||
bool AesCbcKey::Init(const std::string& key) {
|
||||
if (key.empty()) {
|
||||
LOGE("AesCbcKey::Init: no key provided");
|
||||
return false;
|
||||
}
|
||||
if (key.size() != AES_BLOCK_SIZE) {
|
||||
LOGE("AesCbcKey::Init: unexpected key size: %d", key.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_init(&ctx_);
|
||||
if (EVP_EncryptInit(&ctx_, EVP_aes_128_cbc(),
|
||||
reinterpret_cast<const uint8_t*>(&key[0]), NULL) == 0) {
|
||||
LOGE("AesCbcKey::Init: AES CBC key setup failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
initialized_ = true;
|
||||
key_ = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -60,14 +82,16 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
LOGE("AesCbcKey::Encrypt: crypttext destination not provided");
|
||||
return false;
|
||||
}
|
||||
if (!initialized_) {
|
||||
if (key_.empty()) {
|
||||
LOGE("AesCbcKey::Encrypt: AES key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EVP_EncryptInit(&ctx_, NULL, NULL,
|
||||
reinterpret_cast<const uint8_t*>(iv->data())) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: AES CBC iv setup failure: %s",
|
||||
EVP_CIPHER_CTX ctx;
|
||||
if (EVP_EncryptInit(&ctx, EVP_aes_128_cbc(),
|
||||
reinterpret_cast<uint8_t*>(&key_[0]),
|
||||
reinterpret_cast<uint8_t*>(&(*iv)[0])) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: AES CBC setup failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
@@ -75,7 +99,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
out->resize(in.size() + AES_BLOCK_SIZE);
|
||||
int out_length = out->size();
|
||||
if (EVP_EncryptUpdate(
|
||||
&ctx_, reinterpret_cast<uint8_t*>(&(*out)[0]), &out_length,
|
||||
&ctx, reinterpret_cast<uint8_t*>(&(*out)[0]), &out_length,
|
||||
reinterpret_cast<uint8_t*>(const_cast<char*>(in.data())),
|
||||
in.size()) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: encryption failure: %s",
|
||||
@@ -84,7 +108,7 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
}
|
||||
|
||||
int padding = 0;
|
||||
if (EVP_EncryptFinal(&ctx_, reinterpret_cast<uint8_t*>(&(*out)[out_length]),
|
||||
if (EVP_EncryptFinal(&ctx, reinterpret_cast<uint8_t*>(&(*out)[out_length]),
|
||||
&padding) == 0) {
|
||||
LOGE("AesCbcKey::Encrypt: PKCS7 padding failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
@@ -95,34 +119,17 @@ bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
return true;
|
||||
}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {
|
||||
if (key_ != NULL) {
|
||||
RSA_free(key_);
|
||||
}
|
||||
}
|
||||
RsaPublicKey::RsaPublicKey() {}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {}
|
||||
|
||||
bool RsaPublicKey::Init(const std::string& serialized_key) {
|
||||
|
||||
if (serialized_key.empty()) {
|
||||
LOGE("RsaPublicKey::Init: no serialized key provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO* bio = BIO_new_mem_buf(const_cast<char*>(serialized_key.data()),
|
||||
serialized_key.size());
|
||||
if (bio == NULL) {
|
||||
LOGE("RsaPublicKey::Init: BIO_new_mem_buf returned NULL");
|
||||
return false;
|
||||
}
|
||||
key_ = d2i_RSAPublicKey_bio(bio, NULL);
|
||||
BIO_free(bio);
|
||||
|
||||
if (key_ == NULL) {
|
||||
LOGE("RsaPublicKey::Init: RSA key deserialization failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
return false;
|
||||
}
|
||||
|
||||
serialized_key_ = serialized_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -136,36 +143,46 @@ bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
LOGE("RsaPublicKey::Encrypt: no encrypt message buffer provided");
|
||||
return false;
|
||||
}
|
||||
if (key_ == NULL) {
|
||||
if (serialized_key_.empty()) {
|
||||
LOGE("RsaPublicKey::Encrypt: RSA key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
int rsa_size = RSA_size(key_);
|
||||
RSA* key = GetKey(serialized_key_);
|
||||
if (key == NULL) {
|
||||
// Error already logged by GetKey.
|
||||
return false;
|
||||
}
|
||||
|
||||
int rsa_size = RSA_size(key);
|
||||
if (static_cast<int>(clear_message.size()) >
|
||||
rsa_size - kRsaPkcs1OaepPaddingLength) {
|
||||
LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d",
|
||||
" max allowed %d)", clear_message.size(),
|
||||
rsa_size - kRsaPkcs1OaepPaddingLength);
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
encrypted_message->assign(rsa_size, 0);
|
||||
if (RSA_public_encrypt(
|
||||
clear_message.size(),
|
||||
const_cast<unsigned char*>(
|
||||
reinterpret_cast<const unsigned char*>(clear_message.data())),
|
||||
reinterpret_cast<unsigned char*>(&(*encrypted_message)[0]), key_,
|
||||
reinterpret_cast<unsigned char*>(&(*encrypted_message)[0]), key,
|
||||
RSA_PKCS1_OAEP_PADDING) != rsa_size) {
|
||||
LOGE("RsaPublicKey::Encrypt: encrypt failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
const std::string& signature) {
|
||||
if (key_ == NULL) {
|
||||
if (serialized_key_.empty()) {
|
||||
LOGE("RsaPublicKey::VerifySignature: RSA key not initialized");
|
||||
return false;
|
||||
}
|
||||
@@ -173,25 +190,33 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
LOGE("RsaPublicKey::VerifySignature: signed message is empty");
|
||||
return false;
|
||||
}
|
||||
RSA* key = GetKey(serialized_key_);
|
||||
if (key == NULL) {
|
||||
// Error already logged by GetKey.
|
||||
return false;
|
||||
}
|
||||
|
||||
int rsa_size = RSA_size(key_);
|
||||
int rsa_size = RSA_size(key);
|
||||
if (static_cast<int>(signature.size()) != rsa_size) {
|
||||
LOGE(
|
||||
"RsaPublicKey::VerifySignature: message signature is of the wrong "
|
||||
"size (expected %d, actual %d)",
|
||||
rsa_size, signature.size());
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decrypt the signature.
|
||||
std::string padded_digest(signature.size(), 0);
|
||||
if (RSA_public_decrypt(
|
||||
signature.size(),
|
||||
const_cast<unsigned char*>(
|
||||
reinterpret_cast<const unsigned char*>(signature.data())),
|
||||
reinterpret_cast<unsigned char*>(&padded_digest[0]), key_,
|
||||
reinterpret_cast<unsigned char*>(&padded_digest[0]), key,
|
||||
RSA_NO_PADDING) != rsa_size) {
|
||||
LOGE("RsaPublicKey::VerifySignature: RSA public decrypt failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -202,12 +227,13 @@ bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
|
||||
// Verify PSS padding.
|
||||
if (RSA_verify_PKCS1_PSS(
|
||||
key_, reinterpret_cast<const unsigned char*>(message_digest.data()),
|
||||
key, reinterpret_cast<const unsigned char*>(message_digest.data()),
|
||||
EVP_sha1(),
|
||||
reinterpret_cast<const unsigned char*>(padded_digest.data()),
|
||||
kPssSaltLength) == 0) {
|
||||
LOGE("RsaPublicKey::VerifySignature: RSA verify failure: %s",
|
||||
ERR_error_string(ERR_get_error(), NULL));
|
||||
FreeKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
43
libwvdrmengine/cdm/core/src/privacy_crypto_dummy.cpp
Normal file
43
libwvdrmengine/cdm/core/src/privacy_crypto_dummy.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2013 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Dummy version of privacy crypto classes for systems which
|
||||
// can't tolerate OpenSSL as a dependency.
|
||||
//
|
||||
|
||||
#include "privacy_crypto.h"
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
AesCbcKey::AesCbcKey() {}
|
||||
|
||||
AesCbcKey::~AesCbcKey() {}
|
||||
|
||||
bool AesCbcKey::Init(const std::string& key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
std::string* iv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
RsaPublicKey::RsaPublicKey() {}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {}
|
||||
|
||||
bool RsaPublicKey::Init(const std::string& serialized_key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
std::string* encrypted_message) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
const std::string& signature) {
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
@@ -9,27 +9,21 @@ const char* kSecurityLevelDirs[] = {"L1/", "L3/"};
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
bool Properties::begin_license_usage_when_received_;
|
||||
bool Properties::require_explicit_renew_request_;
|
||||
bool Properties::oem_crypto_use_secure_buffers_;
|
||||
bool Properties::oem_crypto_use_fifo_;
|
||||
bool Properties::oem_crypto_use_userspace_buffers_;
|
||||
bool Properties::oem_crypto_require_usage_tables_;
|
||||
bool Properties::use_certificates_as_identification_;
|
||||
bool Properties::decrypt_with_empty_session_support_;
|
||||
bool Properties::security_level_path_backward_compatibility_support_;
|
||||
scoped_ptr<CdmClientPropertySetMap> Properties::session_property_set_;
|
||||
|
||||
void Properties::Init() {
|
||||
begin_license_usage_when_received_ = kPropertyBeginLicenseUsageWhenReceived;
|
||||
require_explicit_renew_request_ = kPropertyRequireExplicitRenewRequest;
|
||||
oem_crypto_use_secure_buffers_ = kPropertyOemCryptoUseSecureBuffers;
|
||||
oem_crypto_use_fifo_ = kPropertyOemCryptoUseFifo;
|
||||
oem_crypto_use_userspace_buffers_ = kPropertyOemCryptoUseUserSpaceBuffers;
|
||||
oem_crypto_require_usage_tables_ = kPropertyOemCryptoRequireUsageTable;
|
||||
use_certificates_as_identification_ =
|
||||
kPropertyUseCertificatesAsIdentification;
|
||||
decrypt_with_empty_session_support_ = kDecryptWithEmptySessionSupport;
|
||||
security_level_path_backward_compatibility_support_ =
|
||||
kSecurityLevelPathBackwardCompatibilitySupport;
|
||||
session_property_set_.reset(new CdmClientPropertySetMap());
|
||||
@@ -66,27 +60,30 @@ const CdmClientPropertySet* Properties::GetCdmClientPropertySet(
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const std::string Properties::GetSecurityLevel(const CdmSessionId& session_id) {
|
||||
bool Properties::GetSecurityLevel(const CdmSessionId& session_id,
|
||||
std::string* security_level) {
|
||||
const CdmClientPropertySet* property_set =
|
||||
GetCdmClientPropertySet(session_id);
|
||||
if (NULL == property_set) {
|
||||
LOGE("Properties::GetSecurityLevel: cannot find property set for %s",
|
||||
session_id.c_str());
|
||||
return "";
|
||||
return false;
|
||||
}
|
||||
return property_set->security_level();
|
||||
*security_level = property_set->security_level();
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::vector<uint8_t> Properties::GetServiceCertificate(
|
||||
const CdmSessionId& session_id) {
|
||||
bool Properties::GetServiceCertificate(const CdmSessionId& session_id,
|
||||
std::string* service_certificate) {
|
||||
const CdmClientPropertySet* property_set =
|
||||
GetCdmClientPropertySet(session_id);
|
||||
if (NULL == property_set) {
|
||||
LOGE("Properties::GetServiceCertificate: cannot find property set for %s",
|
||||
session_id.c_str());
|
||||
return std::vector<uint8_t>();
|
||||
return false;
|
||||
}
|
||||
return property_set->service_certificate();
|
||||
*service_certificate = property_set->service_certificate();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Properties::UsePrivacyMode(const CdmSessionId& session_id) {
|
||||
|
||||
@@ -77,11 +77,11 @@ std::string b2a_hex(const std::string& byte) {
|
||||
|
||||
// Filename-friendly base64 encoding (RFC4648), commonly referred to
|
||||
// as Base64WebSafeEncode.
|
||||
// This is the encoding required to interface with the provisioning
|
||||
// server's Apiary interface as well as for certain license server
|
||||
// transactions. It is also used for logging certain strings.
|
||||
// The difference between web safe encoding vs regular encoding is that
|
||||
// the web safe version replaces '+' with '-' and '/' with '_'.
|
||||
//
|
||||
// This is the encoding required to interface with the provisioning server, as
|
||||
// well as for certain license server transactions. It is also used for logging
|
||||
// certain strings. The difference between web safe encoding vs regular encoding
|
||||
// is that the web safe version replaces '+' with '-' and '/' with '_'.
|
||||
std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
|
||||
if (bin_input.empty()) {
|
||||
return std::string();
|
||||
|
||||
@@ -33,13 +33,13 @@ wvcdm::KeyId g_key_id_pssh;
|
||||
wvcdm::KeyId g_key_id_unwrapped;
|
||||
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
|
||||
|
||||
// This is the RSA certificate from the provisioning server. The client
|
||||
// sends this certificate to a license server as verification in the
|
||||
// provisioning test case.
|
||||
// This is an RSA certificate message from the provisioning server.
|
||||
// The client sends this certificate to a license server for device
|
||||
// authentication by the license server.
|
||||
// This certificate is used to test the CDM engine's provisioning
|
||||
// response handling.
|
||||
static wvcdm::CdmProvisioningResponse kValidJsonProvisioningResponse =
|
||||
"{\"signedResponse\": {"
|
||||
"\"message\": \"CrAJYTyIdLPiA2jBzMskbE_gFQj69wv23VlJ2e3MBKtK4nJwKyNYGyyluqKo"
|
||||
@@ -136,16 +136,16 @@ class WvCdmEngineTest : public testing::Test {
|
||||
std::string GetKeyRequestResponse(const std::string& server_url,
|
||||
const std::string& client_auth) {
|
||||
// Use secure connection and chunk transfer coding.
|
||||
UrlRequest url_request(server_url + client_auth, g_port, true, true);
|
||||
UrlRequest url_request(server_url + client_auth);
|
||||
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);
|
||||
bool ok = url_request.GetResponse(&response);
|
||||
LOGD("response: %s\n", response.c_str());
|
||||
EXPECT_TRUE(ok);
|
||||
|
||||
int status_code = url_request.GetStatusCode(response);
|
||||
EXPECT_EQ(kHttpOk, status_code);
|
||||
@@ -252,41 +252,38 @@ int main(int argc, char **argv) {
|
||||
// The following variables are configurable through command line options.
|
||||
g_license_server.assign(config.license_server());
|
||||
g_key_id_pssh.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' },
|
||||
{ "vmodule", required_argument, NULL, 0 },
|
||||
{ "v", required_argument, NULL, 0 },
|
||||
{ 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) {
|
||||
while ((opt = getopt_long(argc, argv, "k:s:v", long_options, &option_index)) != -1) {
|
||||
switch (opt) {
|
||||
case 'k': {
|
||||
g_key_id_pssh.clear();
|
||||
g_key_id_pssh.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;
|
||||
case 'v': {
|
||||
// This option _may_ have already been consumed by wvcdm::InitLogging()
|
||||
// above, depending on the platform-specific logging implementation.
|
||||
// We only tell getopt about it so that it is not an error. We ignore
|
||||
// the option here when seen.
|
||||
// TODO: Stop passing argv to InitLogging, and instead set the log
|
||||
// level here through the logging API. We should keep all command-line
|
||||
// parsing at the application level, rather than split between various
|
||||
// apps and various platform-specific logging implementations.
|
||||
break;
|
||||
}
|
||||
case '?': {
|
||||
@@ -302,11 +299,6 @@ int main(int argc, char **argv) {
|
||||
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 << " ";
|
||||
@@ -316,21 +308,15 @@ int main(int argc, char **argv) {
|
||||
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_pssh << 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_pssh << std::endl << std::endl;
|
||||
|
||||
g_key_id_pssh = wvcdm::a2bs_hex(g_key_id_pssh);
|
||||
config.set_license_server(g_license_server);
|
||||
config.set_port(g_port);
|
||||
config.set_key_id(g_key_id_pssh);
|
||||
|
||||
// Extract the key ID from the PSSH box.
|
||||
|
||||
@@ -5,42 +5,50 @@
|
||||
namespace {
|
||||
const std::string kWidevineKeySystem = "com.widevine.alpha";
|
||||
|
||||
// Youtube Content Protection license server data
|
||||
const std::string kYtCpLicenseServer =
|
||||
// Content Protection license server data
|
||||
const std::string kCpLicenseServer =
|
||||
"http://wv-ref-eme-player.appspot.com/proxy";
|
||||
const std::string kYtCpClientAuth = "";
|
||||
const std::string kYtCpKeyId =
|
||||
"000000427073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000022" // Widevine system id
|
||||
"08011a0d7769646576696e655f7465737422" // pssh data (streaming)
|
||||
"0f73747265616d696e675f636c697031";
|
||||
|
||||
const std::string kYtCpOfflineKeyId =
|
||||
"000000407073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000020" // Widevine system id
|
||||
"08011a0d7769646576696e655f7465737422" //pssh data (offline)
|
||||
"0d6f66666c696e655f636c697031";
|
||||
|
||||
// Youtube license server data
|
||||
const std::string kYtLicenseServer =
|
||||
"https://www.youtube.com/api/drm/"
|
||||
"widevine?video_id=03681262dc412c06&source=YOUTUBE";
|
||||
const std::string kYtClientAuth = "";
|
||||
const std::string kYtKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0801121093789920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
const std::string kCpClientAuth = "";
|
||||
const std::string kCpKeyId =
|
||||
"00000042" // blob size
|
||||
"70737368" // "pssh"
|
||||
"00000000" // flags
|
||||
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
|
||||
"00000022" // pssh data size
|
||||
// pssh data:
|
||||
"08011a0d7769646576696e655f746573"
|
||||
"74220f73747265616d696e675f636c69"
|
||||
"7031";
|
||||
const std::string kCpOfflineKeyId =
|
||||
"00000040" // blob size
|
||||
"70737368" // "pssh"
|
||||
"00000000" // flags
|
||||
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
|
||||
"00000020" // pssh data size
|
||||
// pssh data:
|
||||
"08011a0d7769646576696e655f746573"
|
||||
"74220d6f66666c696e655f636c697031";
|
||||
|
||||
// Google Play license server data
|
||||
const std::string kGpLicenseServer =
|
||||
"https://jmt17.google.com/video/license/GetCencLicense";
|
||||
|
||||
// Test client authorization string.
|
||||
// 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"
|
||||
const std::string kGpClientAuth =
|
||||
"?source=YOUTUBE&video_id=EGHC6OHNbOo&oauth=ya.gtsqawidevine";
|
||||
const std::string kGpKeyId =
|
||||
"00000034" // blob size
|
||||
"70737368" // "pssh"
|
||||
"00000000" // flags
|
||||
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
|
||||
"00000014" // pssh data size
|
||||
// pssh data:
|
||||
"08011210e02562e04cd55351b14b3d74"
|
||||
"8d36ed8e";
|
||||
const std::string kGpOfflineKeyId = kGpKeyId;
|
||||
|
||||
const std::string kGpClientOfflineQueryParameters =
|
||||
"&offline=true";
|
||||
@@ -49,33 +57,30 @@ const std::string kGpClientOfflineRenewalQueryParameters =
|
||||
const std::string kGpClientOfflineReleaseQueryParameters =
|
||||
"&offline=true&release=true";
|
||||
|
||||
const std::string kGpKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"edef8ba979d64acea3c827dcd51d21ed00000014" // Widevine system id
|
||||
"08011210e02562e04cd55351b14b3d748d36ed8e"; // pssh data
|
||||
|
||||
// An invalid key id, expected to fail
|
||||
const std::string kWrongKeyId =
|
||||
"000000347073736800000000" // blob size and pssh
|
||||
"EDEF8BA979D64ACEA3C827DCD51D21ED00000014" // Widevine system id
|
||||
"0901121094889920E8D6520098577DF8F2DD5546"; // pssh data
|
||||
"00000034" // blob size
|
||||
"70737368" // "pssh"
|
||||
"00000000" // flags
|
||||
"edef8ba979d64acea3c827dcd51d21ed" // Widevine system id
|
||||
"00000014" // pssh data size
|
||||
// pssh data:
|
||||
"0901121094889920e8d6520098577df8"
|
||||
"f2dd5546";
|
||||
|
||||
// Url returned by GetProvisioningRequest()
|
||||
// URL of provisioning server (returned by GetProvisioningRequest())
|
||||
const std::string kProductionProvisioningServerUrl =
|
||||
"https://www.googleapis.com/"
|
||||
"certificateprovisioning/v1/devicecertificates/create"
|
||||
"?key=AIzaSyB-5OLKTx2iU5mko18DfdwK5611JIjbUhE";
|
||||
|
||||
const std::string kServerSdkLicenseServer =
|
||||
"http://kir03fcpg174.widevine.net/widevine/cgi-bin/drm.cgi";
|
||||
|
||||
const wvcdm::ConfigTestEnv::LicenseServerConfiguration license_servers[] = {
|
||||
{ wvcdm::kGooglePlayServer, kGpLicenseServer, kGpClientAuth, kGpKeyId,
|
||||
kGpKeyId, kDefaultHttpsPort, true, true },
|
||||
{ wvcdm::kYouTubeContentProtectionServer, kYtCpLicenseServer,
|
||||
kYtCpClientAuth, kYtCpKeyId, kYtCpOfflineKeyId, kDefaultHttpPort,
|
||||
false, false }
|
||||
{ wvcdm::kGooglePlayServer, kGpLicenseServer,
|
||||
kGpClientAuth, kGpKeyId, kGpOfflineKeyId },
|
||||
{ wvcdm::kContentProtectionServer, kCpLicenseServer,
|
||||
kCpClientAuth, kCpKeyId, kCpOfflineKeyId },
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -113,11 +118,7 @@ void ConfigTestEnv::Init(LicenseServerId server_id) {
|
||||
key_id_ = license_servers[server_id].key_id;
|
||||
key_system_ = kWidevineKeySystem;
|
||||
license_server_ = license_servers[server_id].url;
|
||||
port_ = license_servers[server_id].port;
|
||||
provisioning_server_url_ = kProductionProvisioningServerUrl;
|
||||
server_sdk_license_server_ = kServerSdkLicenseServer;
|
||||
use_chunked_transfer_ = license_servers[server_id].use_chunked_transfer;
|
||||
use_secure_transfer_ = license_servers[server_id].use_secure_transfer;
|
||||
wrong_key_id_= kWrongKeyId;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,15 +6,10 @@
|
||||
#include <string>
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
namespace {
|
||||
const std::string kDefaultHttpsPort = "443";
|
||||
const std::string kDefaultHttpPort = "80";
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
typedef enum {
|
||||
kGooglePlayServer,
|
||||
kYouTubeContentProtectionServer
|
||||
kContentProtectionServer,
|
||||
} LicenseServerId;
|
||||
|
||||
// Configures default test environment.
|
||||
@@ -26,9 +21,6 @@ class ConfigTestEnv {
|
||||
std::string client_tag;
|
||||
std::string key_id;
|
||||
std::string offline_key_id;
|
||||
std::string port;
|
||||
bool use_chunked_transfer;
|
||||
bool use_secure_transfer;
|
||||
} LicenseServerConfiguration;
|
||||
|
||||
explicit ConfigTestEnv(LicenseServerId server_id);
|
||||
@@ -41,15 +33,9 @@ class ConfigTestEnv {
|
||||
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 std::string& provisioning_server_url() const {
|
||||
return provisioning_server_url_;
|
||||
}
|
||||
const std::string& server_sdk_license_server() const {
|
||||
return server_sdk_license_server_;
|
||||
}
|
||||
bool use_chunked_transfer() { return use_chunked_transfer_; }
|
||||
bool use_secure_transfer() { return use_secure_transfer_; }
|
||||
const KeyId& wrong_key_id() const { return wrong_key_id_; }
|
||||
|
||||
void set_key_id(KeyId& key_id) { key_id_.assign(key_id); }
|
||||
@@ -59,7 +45,6 @@ class ConfigTestEnv {
|
||||
void set_license_server(std::string& license_server) {
|
||||
license_server_.assign(license_server);
|
||||
}
|
||||
void set_port(std::string& port) { port_.assign(port); }
|
||||
|
||||
private:
|
||||
void Init(LicenseServerId server_id);
|
||||
@@ -68,11 +53,7 @@ class ConfigTestEnv {
|
||||
KeyId key_id_;
|
||||
CdmKeySystem key_system_;
|
||||
std::string license_server_;
|
||||
std::string port_;
|
||||
std::string provisioning_server_url_;
|
||||
std::string server_sdk_license_server_;
|
||||
bool use_chunked_transfer_;
|
||||
bool use_secure_transfer_;
|
||||
KeyId wrong_key_id_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(ConfigTestEnv);
|
||||
|
||||
@@ -26,10 +26,15 @@ using ::testing::StrEq;
|
||||
namespace {
|
||||
const uint32_t kCertificateLen = 700;
|
||||
const uint32_t kWrappedKeyLen = 500;
|
||||
|
||||
const uint32_t kProtobufEstimatedOverhead = 75;
|
||||
const uint32_t kLicenseRequestLen = 300;
|
||||
const uint32_t kLicenseLen = 500;
|
||||
const uint32_t kProviderSessionTokenLen = 128;
|
||||
|
||||
// Structurally valid test certificate.
|
||||
// The data elements in this module are used to test the storage and
|
||||
// retrieval of certificates and licenses
|
||||
const std::string kTestCertificate =
|
||||
"124B035F3D256A656F0E505A085E7A6C482B61035E0C4A540F7803137F4C3B45206B7F33"
|
||||
"347F4D7A005E56400F0955011F4E07072D0D46781817460974326A516E3944385760280E"
|
||||
@@ -51,6 +56,10 @@ const std::string kTestCertificate =
|
||||
"523B102906195E003C2D111A7D4740122C6941003726602B59263B5C09473D4E025E3541"
|
||||
"701B122D340A3D145436137002687E4C470D2F6F4C357A3245384D737B734E2274301179"
|
||||
"402473486311156E5A0C78644C593273";
|
||||
|
||||
// A Wrapped Private Key
|
||||
// The data elements in this module are used to test the storage and
|
||||
// retrieval of certificates and licenses
|
||||
const std::string kTestWrappedPrivateKey =
|
||||
"4F724B065326371A2F5F6F51467C2E26555C453B5C7C1B4F2738454B782E3E7B5340435A"
|
||||
"66374D0612052C521A233D7A67194871751C78575E5177070130264C4F037633320E667B"
|
||||
@@ -66,6 +75,10 @@ const std::string kTestWrappedPrivateKey =
|
||||
"5F7F3D6B64525E7227165948101540243C19495C4C702F37490F26613353797825624143"
|
||||
"263043020E1E6760123D51056F2F1E482F2E3D021B27677D3E7E3C0C11757C3448275E08"
|
||||
"382E111263644C6D224714706D760A054A586E17505C3429575A41043F184209";
|
||||
|
||||
// The test certificate in file storage format.
|
||||
// The data elements in this module are used to test the storage and
|
||||
// retrieval of certificates and licenses
|
||||
const std::string kTestCertificateFileData =
|
||||
"0ABD09080110011AB6090ABC05124B035F3D256A656F0E505A085E7A6C482B61035E0C4A"
|
||||
"540F7803137F4C3B45206B7F33347F4D7A005E56400F0955011F4E07072D0D4678181746"
|
||||
@@ -115,10 +128,13 @@ struct LicenseInfo {
|
||||
std::string file_data;
|
||||
};
|
||||
|
||||
// Sample data to test storage and retrieval of license-related data
|
||||
// The license data and URLs in this test are not real.
|
||||
size_t kNumberOfLicenses = 3;
|
||||
|
||||
// Sample license data and related data for storage and use for offline
|
||||
// playback. The license data and URLs in this test are not real. Test
|
||||
// storage and retrieval of license-related data.
|
||||
LicenseInfo license_test_data[] = {
|
||||
|
||||
// license 0
|
||||
{"ksid54C57C966E23CEF5", DeviceFiles::kLicenseStateActive,
|
||||
wvcdm::a2bs_hex("0801121030313233343536373839414243444546"),
|
||||
@@ -310,6 +326,7 @@ LicenseInfo license_test_data[] = {
|
||||
"652E636F6D2F766964656F2D6465762F6C6963656E73652F47657443656E"
|
||||
"634C6963656E73651220F6974C1CFFD00E3144488FC092D3DF4F6007A3CA"
|
||||
"C4756EB046DC74B1C2E512CC")},
|
||||
|
||||
// license 1
|
||||
{"ksidC8EAA2579A282EB0", DeviceFiles::kLicenseStateReleasing,
|
||||
wvcdm::a2bs_hex("0801121030313233343536373839414243444546"),
|
||||
@@ -502,6 +519,7 @@ LicenseInfo license_test_data[] = {
|
||||
"3D3033363831323632646334313263303626736F757263653D594F555455"
|
||||
"42451220EC449C6B026C43004743061B3A3DCB7208B2AD11600254841B96"
|
||||
"1CFA1AD57172")},
|
||||
|
||||
// license 2
|
||||
{"ksidE8C37662C88DC673", DeviceFiles::kLicenseStateReleasing,
|
||||
wvcdm::a2bs_hex("0801121030313233343536373839414243444546"),
|
||||
@@ -693,6 +711,9 @@ LicenseInfo license_test_data[] = {
|
||||
"72702E676F6F676C652E636F6D3A383838382F64726D12205CD2C43C618C"
|
||||
"CA27BBCB2EEBDE32B57CBD51B424FD85DAB715B7F5A87546FD40")}};
|
||||
|
||||
// Sample license data and related data for storage and use for offline
|
||||
// playback. The license data and URLs in this test are not real.
|
||||
// The data is used to test license-related functions.
|
||||
LicenseInfo license_update_test_data[] = {
|
||||
// active license
|
||||
{"key_set_id_: ksid2A048BC7FAEC885A", DeviceFiles::kLicenseStateActive,
|
||||
|
||||
@@ -45,10 +45,10 @@ class FileTest : public testing::Test {
|
||||
|
||||
TEST_F(FileTest, FileExists) {
|
||||
File file;
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kFileExists));
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kDirExists));
|
||||
EXPECT_FALSE(file.Exists(test_vectors::kFileDoesNotExist));
|
||||
EXPECT_FALSE(file.Exists(test_vectors::kDirDoesNotExist));
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kExistentFile));
|
||||
EXPECT_TRUE(file.Exists(test_vectors::kExistentDir));
|
||||
EXPECT_FALSE(file.Exists(test_vectors::kNonExistentFile));
|
||||
EXPECT_FALSE(file.Exists(test_vectors::kNonExistentDir));
|
||||
}
|
||||
|
||||
TEST_F(FileTest, CreateDirectory) {
|
||||
|
||||
@@ -5,17 +5,35 @@
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
#include <string.h>
|
||||
#include <netinet/in.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "openssl/bio.h"
|
||||
#include "openssl/err.h"
|
||||
#include "openssl/x509.h"
|
||||
|
||||
namespace wvcdm {
|
||||
namespace {
|
||||
|
||||
SSL_CTX* HttpSocket::InitSslContext(void) {
|
||||
// Helper function to tokenize a string. This makes it easier to avoid silly
|
||||
// parsing bugs that creep in easily when each part of the string is parsed
|
||||
// with its own piece of code.
|
||||
bool Tokenize(const std::string& source, const std::string& delim,
|
||||
const size_t offset, std::string* substring_output,
|
||||
size_t* next_offset) {
|
||||
size_t start_of_delim = source.find(delim, offset);
|
||||
if (start_of_delim == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
substring_output->assign(source, offset, start_of_delim - offset);
|
||||
*next_offset = start_of_delim + delim.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
SSL_CTX* InitSslContext() {
|
||||
const SSL_METHOD* method;
|
||||
SSL_CTX* ctx;
|
||||
|
||||
@@ -23,21 +41,18 @@ SSL_CTX* HttpSocket::InitSslContext(void) {
|
||||
SSL_load_error_strings();
|
||||
method = SSLv3_client_method();
|
||||
ctx = SSL_CTX_new(method);
|
||||
if (NULL == ctx) {
|
||||
if (!ctx)
|
||||
LOGE("failed to create SSL context");
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void HttpSocket::ShowServerCertificate(const SSL* ssl) {
|
||||
X509* cert;
|
||||
char* line;
|
||||
|
||||
// unused, may be useful for debugging SSL-related issues.
|
||||
void ShowServerCertificate(const SSL* ssl) {
|
||||
// gets the server certificate
|
||||
cert = SSL_get_peer_certificate(ssl);
|
||||
if (cert != NULL) {
|
||||
X509* cert = SSL_get_peer_certificate(ssl);
|
||||
if (cert) {
|
||||
char* line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
|
||||
LOGV("server certificate:");
|
||||
line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
|
||||
LOGV("subject: %s", line);
|
||||
free(line);
|
||||
line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
|
||||
@@ -49,183 +64,252 @@ void HttpSocket::ShowServerCertificate(const SSL* ssl) {
|
||||
}
|
||||
}
|
||||
|
||||
HttpSocket::HttpSocket()
|
||||
: secure_connect_(true),
|
||||
socket_fd_(-1),
|
||||
// Wait for a socket to be ready for reading or writing.
|
||||
// Establishing a connection counts as "ready for write".
|
||||
// Returns false on select error or timeout.
|
||||
// Returns true when the socket is ready.
|
||||
bool SocketWait(int fd, bool for_read, int timeout_in_ms) {
|
||||
fd_set fds;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
|
||||
struct timeval tv;
|
||||
tv.tv_sec = timeout_in_ms / 1000;
|
||||
tv.tv_usec = (timeout_in_ms % 1000) * 1000;
|
||||
|
||||
fd_set *read_fds = NULL;
|
||||
fd_set *write_fds = NULL;
|
||||
if (for_read) {
|
||||
read_fds = &fds;
|
||||
} else {
|
||||
write_fds = &fds;
|
||||
}
|
||||
|
||||
int ret = select(fd + 1, read_fds, write_fds, NULL, &tv);
|
||||
if (ret == 0) {
|
||||
LOGE("socket timed out");
|
||||
return false;
|
||||
} else if (ret == -1) {
|
||||
LOGE("select failed, errno = %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// socket ready.
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Parses the URL and extracts all relevant information.
|
||||
// static
|
||||
bool HttpSocket::ParseUrl(const std::string& url,
|
||||
std::string* scheme,
|
||||
bool* secure_connect,
|
||||
std::string* domain_name,
|
||||
int* port,
|
||||
std::string* path) {
|
||||
size_t offset = 0;
|
||||
|
||||
if (!Tokenize(url, "://", offset, scheme, &offset)) {
|
||||
LOGE("Invalid URL, scheme not found: %s", url.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the scheme is http or https, set secure_connect and port accordingly.
|
||||
// Otherwise, consider the scheme unsupported and fail.
|
||||
if (*scheme == "http") {
|
||||
*secure_connect = false;
|
||||
*port = 80;
|
||||
} else if (*scheme == "https") {
|
||||
*secure_connect = true;
|
||||
*port = 443;
|
||||
} else {
|
||||
LOGE("Invalid URL, scheme not supported: %s", url.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Tokenize(url, "/", offset, domain_name, &offset)) {
|
||||
// The rest of the URL belongs to the domain name.
|
||||
domain_name->assign(url, offset, std::string::npos);
|
||||
// No explicit path after the domain name.
|
||||
path->assign("/");
|
||||
} else {
|
||||
// The rest of the URL, including the preceding slash, belongs to the path.
|
||||
path->assign(url, offset - 1, std::string::npos);
|
||||
}
|
||||
|
||||
// The domain name may optionally contain a port which overrides the default.
|
||||
std::string domain_name_without_port;
|
||||
size_t port_offset;
|
||||
if (Tokenize(*domain_name, ":", 0, &domain_name_without_port,
|
||||
&port_offset)) {
|
||||
*port = atoi(domain_name->c_str() + port_offset);
|
||||
if (*port <= 0 || *port >= 65536) {
|
||||
LOGE("Invalid URL, port not valid: %s", url.c_str());
|
||||
return false;
|
||||
}
|
||||
domain_name->assign(domain_name_without_port);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpSocket::HttpSocket(const std::string& url)
|
||||
: socket_fd_(-1),
|
||||
ssl_(NULL),
|
||||
ssl_ctx_(NULL),
|
||||
timeout_enabled_(false) {
|
||||
ssl_ctx_(NULL) {
|
||||
valid_url_ = ParseUrl(url, &scheme_, &secure_connect_, &domain_name_, &port_,
|
||||
&resource_path_);
|
||||
SSL_library_init();
|
||||
}
|
||||
|
||||
HttpSocket::~HttpSocket() { CloseSocket(); }
|
||||
HttpSocket::~HttpSocket() {
|
||||
CloseSocket();
|
||||
}
|
||||
|
||||
void HttpSocket::CloseSocket() {
|
||||
if (socket_fd_ != -1) {
|
||||
close(socket_fd_);
|
||||
socket_fd_ = -1;
|
||||
}
|
||||
if (secure_connect_) {
|
||||
if (ssl_) {
|
||||
SSL_free(ssl_);
|
||||
ssl_ = NULL;
|
||||
}
|
||||
if (ssl_ctx_) {
|
||||
CloseSslContext(ssl_ctx_);
|
||||
ssl_ctx_ = NULL;
|
||||
}
|
||||
if (ssl_) {
|
||||
SSL_free(ssl_);
|
||||
ssl_ = NULL;
|
||||
}
|
||||
if (ssl_ctx_) {
|
||||
SSL_CTX_free(ssl_ctx_);
|
||||
ssl_ctx_ = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
bool HttpSocket::Connect(int timeout_in_ms) {
|
||||
if (!valid_url_) {
|
||||
return false;
|
||||
}
|
||||
// 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, bool secure_connection) {
|
||||
secure_connect_ = secure_connection;
|
||||
if (secure_connect_) ssl_ctx_ = InitSslContext();
|
||||
|
||||
GetDomainNameAndPathFromUrl(url, domain_name_, resource_path_);
|
||||
|
||||
// get a socket
|
||||
socket_fd_ = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (socket_fd_ < 0) {
|
||||
LOGE("cannot open socket %d", errno);
|
||||
LOGE("cannot open socket, errno = %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
int reuse = 1;
|
||||
if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) ==
|
||||
-1) {
|
||||
// set the socket in non-blocking mode
|
||||
int original_flags = fcntl(socket_fd_, F_GETFL, 0);
|
||||
if (original_flags == -1) {
|
||||
LOGE("fcntl error, errno = %d", errno);
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
if (fcntl(socket_fd_, F_SETFL, original_flags | O_NONBLOCK) == -1) {
|
||||
LOGE("fcntl error, errno = %d", errno);
|
||||
CloseSocket();
|
||||
LOGE("setsockopt error %d", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// lookup the server IP
|
||||
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);
|
||||
int ret = getaddrinfo(domain_name_.c_str(), NULL, &hints, &addr_info);
|
||||
if (ret != 0) {
|
||||
CloseSocket();
|
||||
LOGE("getaddrinfo failed with %d", ret);
|
||||
status = false;
|
||||
LOGE("getaddrinfo failed, errno = %d", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
// set the port
|
||||
struct sockaddr_in* addr_ipv4 = reinterpret_cast<struct sockaddr_in*>(
|
||||
addr_info->ai_addr);
|
||||
addr_ipv4->sin_port = htons(port_);
|
||||
|
||||
// connect to the server
|
||||
ret = connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen);
|
||||
freeaddrinfo(addr_info);
|
||||
|
||||
if (ret == 0) {
|
||||
// connected right away.
|
||||
} else {
|
||||
if (connect(socket_fd_, addr_info->ai_addr, addr_info->ai_addrlen) == -1) {
|
||||
if (errno != EINPROGRESS) {
|
||||
// failed right away.
|
||||
LOGE("cannot connect to %s, errno = %d", domain_name_.c_str(), errno);
|
||||
CloseSocket();
|
||||
LOGE("cannot connect socket to %s, error=%d", domain_name_.c_str(),
|
||||
errno);
|
||||
status = false;
|
||||
return false;
|
||||
} else {
|
||||
// in progress. block until timeout expired or connection established.
|
||||
if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) {
|
||||
LOGE("cannot connect to %s", domain_name_.c_str());
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
timeout_enabled_ = enable_timeout;
|
||||
if (addr_info != NULL) {
|
||||
freeaddrinfo(addr_info);
|
||||
}
|
||||
|
||||
if (!status) return false;
|
||||
// set up SSL if needed
|
||||
if (secure_connect_) {
|
||||
ssl_ctx_ = InitSslContext();
|
||||
if (!ssl_ctx_) {
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
|
||||
// secures connection
|
||||
if (secure_connect_ && ssl_ctx_) {
|
||||
ssl_ = SSL_new(ssl_ctx_);
|
||||
if (!ssl_) {
|
||||
LOGE("failed SSL_new");
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
|
||||
BIO* a_bio = BIO_new_socket(socket_fd_, BIO_NOCLOSE);
|
||||
if (!a_bio) {
|
||||
LOGE("BIO_new_socket error");
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
|
||||
SSL_set_bio(ssl_, a_bio, a_bio);
|
||||
int ret = SSL_connect(ssl_);
|
||||
if (1 != ret) {
|
||||
char buf[256];
|
||||
LOGE("SSL_connect error:%s", ERR_error_string(ERR_get_error(), buf));
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
ret = SSL_connect(ssl_);
|
||||
if (ret != 1) {
|
||||
int ssl_err = SSL_get_error(ssl_, ret);
|
||||
if (ssl_err != SSL_ERROR_WANT_READ &&
|
||||
ssl_err != SSL_ERROR_WANT_WRITE) {
|
||||
char buf[256];
|
||||
LOGE("SSL_connect error: %s", ERR_error_string(ERR_get_error(), buf));
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
bool for_read = ssl_err == SSL_ERROR_WANT_READ;
|
||||
if (!SocketWait(socket_fd_, for_read, timeout_in_ms)) {
|
||||
LOGE("cannot connect to %s", domain_name_.c_str());
|
||||
CloseSocket();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} while (ret != 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
// Returns -1 for error, number of bytes read for success.
|
||||
// The timeout here only applies to the span between packets of data, for the
|
||||
// sake of simplicity.
|
||||
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)) {
|
||||
LOGD("socket read timeout");
|
||||
break;
|
||||
}
|
||||
if (!SocketWait(socket_fd_, /* for_read */ true, timeout_in_ms)) {
|
||||
LOGE("unable to read from %s", domain_name_.c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read;
|
||||
if (secure_connect_)
|
||||
read = SSL_read(ssl_, data, to_read);
|
||||
else
|
||||
@@ -236,27 +320,26 @@ int HttpSocket::Read(char* data, int len, int timeout_in_ms) {
|
||||
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
|
||||
// The connection has been closed. No more data.
|
||||
break;
|
||||
} else {
|
||||
LOGE("recv returned %d, error = %d", read, errno);
|
||||
break;
|
||||
LOGE("recv returned %d, errno = %d", read, errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_timeout) {
|
||||
fcntl(socket_fd_, F_SETFL, original_flags); // now blocking again
|
||||
}
|
||||
return total_read;
|
||||
}
|
||||
|
||||
int HttpSocket::Write(const char* data, int len) {
|
||||
// Returns -1 for error, number of bytes written for success.
|
||||
// The timeout here only applies to the span between packets of data, for the
|
||||
// sake of simplicity.
|
||||
int HttpSocket::Write(const char* data, int len, int timeout_in_ms) {
|
||||
int total_sent = 0;
|
||||
int sent = 0;
|
||||
int to_send = len;
|
||||
|
||||
while (to_send > 0) {
|
||||
int sent;
|
||||
if (secure_connect_)
|
||||
sent = SSL_write(ssl_, data, to_send);
|
||||
else
|
||||
@@ -267,11 +350,17 @@ int HttpSocket::Write(const char* data, int len) {
|
||||
data += sent;
|
||||
total_sent += sent;
|
||||
} else if (sent == 0) {
|
||||
usleep(10); // retry later
|
||||
// We filled up the pipe. Wait for room to write.
|
||||
if (!SocketWait(socket_fd_, /* for_read */ false, timeout_in_ms)) {
|
||||
LOGE("unable to write to %s", domain_name_.c_str());
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
LOGE("send returned error %d", errno);
|
||||
LOGE("send returned %d, errno = %d", sent, errno);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return total_sent;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,43 +4,52 @@
|
||||
#define CDM_TEST_HTTP_SOCKET_H_
|
||||
|
||||
#include <string>
|
||||
#include "openssl/ssl.h"
|
||||
#include "wv_cdm_types.h"
|
||||
|
||||
#include <gtest/gtest_prod.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "wv_cdm_types.h" // CORE_DISALLOW_COPY_AND_ASSIGN
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
// Provides basic Linux based TCP socket interface.
|
||||
class HttpSocket {
|
||||
public:
|
||||
HttpSocket();
|
||||
// A scheme (http:// or https://) is required for the URL.
|
||||
explicit HttpSocket(const std::string& url);
|
||||
~HttpSocket();
|
||||
|
||||
bool Connect(int timeout_in_ms);
|
||||
void CloseSocket();
|
||||
bool Connect(const char* url, const std::string& port, bool enable_timeout,
|
||||
bool secure_connection);
|
||||
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);
|
||||
|
||||
const std::string& scheme() const { return scheme_; }
|
||||
bool secure_connect() const { return secure_connect_; }
|
||||
const std::string& domain_name() const { return domain_name_; }
|
||||
int port() const { return port_; }
|
||||
const std::string& resource_path() const { return resource_path_; }
|
||||
|
||||
int Read(char* data, int len, int timeout_in_ms);
|
||||
int Write(const char* data, int len);
|
||||
int Write(const char* data, int len, int timeout_in_ms);
|
||||
|
||||
private:
|
||||
void CloseSslContext(SSL_CTX* ctx) const {
|
||||
if (ctx) SSL_CTX_free(ctx);
|
||||
}
|
||||
SSL_CTX* InitSslContext(void);
|
||||
void ShowServerCertificate(const SSL* ssl);
|
||||
static bool ParseUrl(const std::string& url,
|
||||
std::string* scheme,
|
||||
bool* secure_connect,
|
||||
std::string* domain_name,
|
||||
int* port,
|
||||
std::string* path);
|
||||
FRIEND_TEST(HttpSocketTest, ParseUrlTest);
|
||||
|
||||
std::string domain_name_;
|
||||
std::string scheme_;
|
||||
bool secure_connect_;
|
||||
std::string domain_name_;
|
||||
int port_;
|
||||
std::string resource_path_;
|
||||
bool valid_url_;
|
||||
|
||||
int socket_fd_;
|
||||
SSL* ssl_;
|
||||
SSL_CTX* ssl_ctx_;
|
||||
bool timeout_enabled_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(HttpSocket);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <errno.h>
|
||||
#include "gtest/gtest.h"
|
||||
#include "http_socket.h"
|
||||
#include "scoped_ptr.h"
|
||||
#include "log.h"
|
||||
#include "string_conversions.h"
|
||||
#include "url_request.h"
|
||||
@@ -10,10 +11,14 @@
|
||||
namespace {
|
||||
// Arbitrary URL for tests.
|
||||
const std::string kHttpsTestServer("https://www.google.com");
|
||||
const std::string kHttpTestServer("http://www.google.com");
|
||||
// This URL and data are used by RoundTripTest, and can be overridden on the
|
||||
// command line.
|
||||
std::string gTestServer(kHttpsTestServer);
|
||||
std::string gTestData("Hello");
|
||||
// Arbitrary buffer size and timeout settings.
|
||||
const int kHttpBufferSize = 4096;
|
||||
char gBuffer[kHttpBufferSize];
|
||||
const int kTimeout = 3000;
|
||||
}
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -21,153 +26,179 @@ namespace wvcdm {
|
||||
class HttpSocketTest : public testing::Test {
|
||||
public:
|
||||
HttpSocketTest() {}
|
||||
~HttpSocketTest() { socket_.CloseSocket(); }
|
||||
~HttpSocketTest() {}
|
||||
|
||||
protected:
|
||||
bool Connect(const std::string& server_url, bool secure_connection) {
|
||||
bool Connect(const std::string& server_url) {
|
||||
socket_.reset(new HttpSocket(server_url));
|
||||
|
||||
std::string port = secure_connection ? "443" : "80";
|
||||
if (socket_.Connect(server_url.c_str(), port, true, secure_connection)) {
|
||||
LOGD("connected to %s", socket_.domain_name().c_str());
|
||||
if (socket_->Connect(kTimeout)) {
|
||||
LOGD("connected to %s", socket_->domain_name().c_str());
|
||||
return true;
|
||||
} else {
|
||||
LOGE("failed to connect to %s", socket_.domain_name().c_str());
|
||||
LOGE("failed to connect to %s", server_url.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(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(socket_->domain_name());
|
||||
request.append("\r\n");
|
||||
|
||||
// Important! Otherwise, the HTTP 1.1 default behavior for a server is to
|
||||
// keep the connection open for a subsequent request.
|
||||
request.append("Connection: close\r\n");
|
||||
|
||||
request.append("User-Agent: httpSocketTest/1.0\r\n");
|
||||
|
||||
char buffer[32] = {0};
|
||||
snprintf(buffer, sizeof(buffer), "%d", static_cast<int>(data.size()));
|
||||
request.append("Content-Length: ");
|
||||
memset(gBuffer, 0, kHttpBufferSize);
|
||||
snprintf(gBuffer, kHttpBufferSize, "%d\r\n", static_cast<int>(data.size()));
|
||||
request.append(gBuffer);
|
||||
request.append(buffer);
|
||||
request.append("\r\n");
|
||||
|
||||
request.append("Content-Type: multipart/form-data\r\n");
|
||||
|
||||
// newline terminates header
|
||||
// an extra newline terminates HTTP headers.
|
||||
request.append("\r\n");
|
||||
|
||||
// append data
|
||||
request.append(data);
|
||||
socket_.Write(request.c_str(), request.size());
|
||||
socket_->Write(request.c_str(), request.size(), kTimeout);
|
||||
|
||||
LOGD("request: %s", request.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetResponse() {
|
||||
int bytes = socket_.Read(gBuffer, kHttpBufferSize, 1000);
|
||||
bool GetResponse(std::string* response) {
|
||||
char buffer[kHttpBufferSize];
|
||||
int bytes = socket_->Read(buffer, sizeof(buffer), kTimeout);
|
||||
if (bytes < 0) {
|
||||
LOGE("read error = ", errno);
|
||||
LOGE("read error, errno = %d", 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;
|
||||
}
|
||||
|
||||
LOGD("read %d bytes", bytes);
|
||||
response->assign(buffer, bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpSocket socket_;
|
||||
scoped_ptr<HttpSocket> socket_;
|
||||
std::string domain_name_;
|
||||
std::string resource_path_;
|
||||
};
|
||||
|
||||
TEST_F(HttpSocketTest, GetDomainNameAndPathFromUrlTest) {
|
||||
socket_.GetDomainNameAndPathFromUrl(
|
||||
"https://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());
|
||||
struct ParseUrlTests {
|
||||
const char* url;
|
||||
const char* scheme;
|
||||
bool secure_connect;
|
||||
const char* domain_name;
|
||||
int port;
|
||||
const char* path;
|
||||
};
|
||||
|
||||
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());
|
||||
ParseUrlTests parse_url_tests[] = {
|
||||
{
|
||||
"https://code.google.com/p/googletest/wiki/Primer", // url
|
||||
"https", // scheme
|
||||
true, // secure_connect
|
||||
"code.google.com", // domain_name
|
||||
443, // port
|
||||
"/p/googletest/wiki/Primer", // path
|
||||
},
|
||||
{
|
||||
"http://code.google.com/p/googletest/wiki/Primer/", // url
|
||||
"http", // scheme
|
||||
false, // secure_connect
|
||||
"code.google.com", // domain_name
|
||||
80, // port
|
||||
"/p/googletest/wiki/Primer/", // path
|
||||
},
|
||||
{
|
||||
"http://code.google.com/", // url
|
||||
"http", // scheme
|
||||
false, // secure_connect
|
||||
"code.google.com", // domain_name
|
||||
80, // port
|
||||
"/", // path
|
||||
},
|
||||
{
|
||||
"http://code.google.com", // url
|
||||
"http", // scheme
|
||||
false, // secure_connect
|
||||
"code.google.com", // domain_name
|
||||
80, // port
|
||||
"/", // path
|
||||
},
|
||||
{
|
||||
"http://10.11.12.13:8888/drm", // url
|
||||
"http", // scheme
|
||||
false, // secure_connect
|
||||
"10.11.12.13", // domain_name
|
||||
8888, // port
|
||||
"/drm", // path
|
||||
},
|
||||
{
|
||||
"http://10.11.12.13:8888", // url
|
||||
"http", // scheme
|
||||
false, // secure_connect
|
||||
"10.11.12.13", // domain_name
|
||||
8888, // port
|
||||
"/", // path
|
||||
},
|
||||
{
|
||||
"https://10.11.12.13:8888", // url
|
||||
"https", // scheme
|
||||
true, // secure_connect
|
||||
"10.11.12.13", // domain_name
|
||||
8888, // port
|
||||
"/", // path
|
||||
},
|
||||
{ NULL } // list terminator
|
||||
};
|
||||
|
||||
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());
|
||||
TEST_F(HttpSocketTest, ParseUrlTest) {
|
||||
std::string scheme;
|
||||
bool secure_connect;
|
||||
std::string domain_name;
|
||||
int port;
|
||||
std::string path;
|
||||
ParseUrlTests* test = NULL;
|
||||
|
||||
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());
|
||||
|
||||
// Test with arbitrary numeric URL
|
||||
socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888/drm",
|
||||
domain_name_, resource_path_);
|
||||
EXPECT_STREQ("10.11.12.13", domain_name_.c_str());
|
||||
EXPECT_STREQ("drm", resource_path_.c_str());
|
||||
|
||||
socket_.GetDomainNameAndPathFromUrl("http://10.11.12.13:8888", domain_name_,
|
||||
resource_path_);
|
||||
EXPECT_STREQ("10.11.12.13", domain_name_.c_str());
|
||||
EXPECT_TRUE(resource_path_.empty());
|
||||
for (test = &parse_url_tests[0]; test->url != NULL; ++test) {
|
||||
bool ok = HttpSocket::ParseUrl(test->url, &scheme, &secure_connect,
|
||||
&domain_name, &port, &path);
|
||||
EXPECT_TRUE(ok);
|
||||
if (ok) {
|
||||
EXPECT_EQ(test->scheme, scheme);
|
||||
EXPECT_EQ(test->secure_connect, secure_connect);
|
||||
EXPECT_EQ(test->domain_name, domain_name);
|
||||
EXPECT_EQ(test->port, port);
|
||||
EXPECT_EQ(test->path, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, ConnectTest) {
|
||||
const bool kUseSecureConnection = true;
|
||||
|
||||
if (gTestServer.find("https") != std::string::npos) {
|
||||
EXPECT_TRUE(Connect(gTestServer, kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
|
||||
// https connection allows insecure connection through port 80 as well
|
||||
EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
} else {
|
||||
EXPECT_TRUE(Connect(gTestServer, !kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
|
||||
// Test for the case that non-https connection must not use port 443
|
||||
EXPECT_FALSE(Connect(gTestServer, kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
}
|
||||
|
||||
EXPECT_FALSE(Connect("ww.g.c", kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
|
||||
EXPECT_FALSE(Connect("ww.g.c", !kUseSecureConnection));
|
||||
socket_.CloseSocket();
|
||||
EXPECT_TRUE(Connect(kHttpsTestServer));
|
||||
EXPECT_TRUE(Connect(kHttpTestServer));
|
||||
EXPECT_FALSE(Connect("ww.g.c"));
|
||||
EXPECT_FALSE(Connect("http://ww.g.c"));
|
||||
EXPECT_FALSE(Connect("https://ww.g.c"));
|
||||
}
|
||||
|
||||
TEST_F(HttpSocketTest, RoundTripTest) {
|
||||
int secure_connection =
|
||||
(gTestServer.find("https") != std::string::npos) ? true : false;
|
||||
ASSERT_TRUE(Connect(gTestServer, secure_connection));
|
||||
ASSERT_TRUE(Connect(gTestServer));
|
||||
EXPECT_TRUE(PostRequest(gTestData));
|
||||
GetResponse();
|
||||
socket_.CloseSocket();
|
||||
|
||||
std::string response;
|
||||
EXPECT_TRUE(GetResponse(&response));
|
||||
LOGD("response: %s", response.c_str());
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -44,9 +44,6 @@ void LicenseRequest::GetDrmMessage(const std::string& response,
|
||||
if (drm_msg_pos != std::string::npos) {
|
||||
drm_msg = response.substr(drm_msg_pos);
|
||||
} else {
|
||||
// TODO(edwinwong, rfrias): hack to get HTTP message body out for
|
||||
// non-Google Play webservers. Need to clean this up. Possibly test
|
||||
// for GLS and decide which part is the drm message
|
||||
drm_msg = response.substr(header_end_pos);
|
||||
}
|
||||
} else {
|
||||
@@ -59,7 +56,7 @@ void LicenseRequest::GetDrmMessage(const std::string& response,
|
||||
void LicenseRequest::GetHeartbeatUrl(const std::string& response,
|
||||
std::string& heartbeat_url) {
|
||||
if (response.empty()) {
|
||||
heartbeat_url.clear(); // TODO: assign default heartbeat url
|
||||
heartbeat_url.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,52 +10,16 @@
|
||||
#include "string_conversions.h"
|
||||
|
||||
namespace {
|
||||
const int kMaxReadAttempts = 4;
|
||||
const int kSingleReadAttempt = 1;
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
UrlRequest::UrlRequest(const std::string& url, const std::string& port,
|
||||
bool secure_connection, bool chunk_transfer_mode)
|
||||
: chunk_transfer_mode_(chunk_transfer_mode),
|
||||
is_connected_(false),
|
||||
port_("80"),
|
||||
request_(""),
|
||||
server_url_(url) {
|
||||
if (!port.empty()) {
|
||||
port_.assign(port);
|
||||
}
|
||||
if (socket_.Connect((server_url_).c_str(), port_, true, secure_connection)) {
|
||||
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, "%zx\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
|
||||
}
|
||||
const int kReadBufferSize = 1024;
|
||||
const int kConnectTimeoutMs = 5000;
|
||||
const int kWriteTimeoutMs = 3000;
|
||||
const int kReadTimeoutMs = 3000;
|
||||
|
||||
// Concatenate all chunks into one blob and returns the response with
|
||||
// header information.
|
||||
void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
|
||||
std::string* modified_response) {
|
||||
void ConcatenateChunkedResponse(const std::string http_response,
|
||||
std::string* modified_response) {
|
||||
if (http_response.empty()) return;
|
||||
|
||||
modified_response->clear();
|
||||
@@ -103,33 +67,52 @@ void UrlRequest::ConcatenateChunkedResponse(const std::string http_response,
|
||||
}
|
||||
}
|
||||
|
||||
int UrlRequest::GetResponse(std::string* message) {
|
||||
message->clear();
|
||||
} // namespace
|
||||
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
UrlRequest::UrlRequest(const std::string& url)
|
||||
: is_connected_(false),
|
||||
socket_(url) {
|
||||
if (socket_.Connect(kConnectTimeoutMs)) {
|
||||
is_connected_ = true;
|
||||
} else {
|
||||
LOGE("failed to connect to %s, port=%d", socket_.domain_name().c_str(),
|
||||
socket_.port());
|
||||
}
|
||||
}
|
||||
|
||||
UrlRequest::~UrlRequest() {}
|
||||
|
||||
bool UrlRequest::GetResponse(std::string* message) {
|
||||
std::string response;
|
||||
const int kTimeoutInMs = 3000;
|
||||
int bytes = 0;
|
||||
for (int attempts = kMaxReadAttempts; attempts > 0; --attempts) {
|
||||
memset(buffer_, 0, kHttpBufferSize);
|
||||
bytes = socket_.Read(buffer_, kHttpBufferSize, kTimeoutInMs);
|
||||
|
||||
// Keep reading until end of stream (0 bytes read) or timeout. Partial
|
||||
// buffers worth of data can and do happen, especially with OpenSSL in
|
||||
// non-blocking mode.
|
||||
while (true) {
|
||||
char read_buffer[kReadBufferSize];
|
||||
int bytes = socket_.Read(read_buffer, sizeof(read_buffer), kReadTimeoutMs);
|
||||
if (bytes > 0) {
|
||||
response.append(buffer_, bytes);
|
||||
if (bytes < static_cast<int>(kHttpBufferSize)) {
|
||||
attempts = kSingleReadAttempt;
|
||||
}
|
||||
response.append(read_buffer, bytes);
|
||||
} else if (bytes < 0) {
|
||||
LOGE("read error, errno = %d", errno);
|
||||
return false;
|
||||
} else {
|
||||
if (bytes < 0) LOGE("read error = ", errno);
|
||||
// bytes == 0 indicates nothing to read
|
||||
// end of stream.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ConcatenateChunkedResponse(response, message);
|
||||
LOGD("HTTP response: (%d): %s", message->size(), b2a_hex(*message).c_str());
|
||||
return message->size();
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
int UrlRequest::GetStatusCode(const std::string& response) {
|
||||
const std::string kHttpVersion("HTTP/1.1");
|
||||
const std::string kHttpVersion("HTTP/1.1 ");
|
||||
|
||||
int status_code = -1;
|
||||
size_t pos = response.find(kHttpVersion);
|
||||
@@ -140,79 +123,48 @@ int UrlRequest::GetStatusCode(const std::string& response) {
|
||||
return status_code;
|
||||
}
|
||||
|
||||
bool UrlRequest::PostRequestChunk(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
|
||||
bool UrlRequest::PostRequestWithPath(const std::string& path,
|
||||
const std::string& data) {
|
||||
std::string request;
|
||||
|
||||
// calls AppendChunkToUpload repeatedly for multiple chunks
|
||||
AppendChunkToUpload(data);
|
||||
request.append("POST ");
|
||||
request.append(path);
|
||||
request.append(" HTTP/1.1\r\n");
|
||||
|
||||
// terminates last chunk with 0\r\n, then ends header with an empty line
|
||||
request_.append("0\r\n\r\n");
|
||||
request.append("Host: ");
|
||||
request.append(socket_.domain_name());
|
||||
request.append("\r\n");
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
return true;
|
||||
request.append("Connection: close\r\n");
|
||||
request.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||
|
||||
// buffer to store length of data as a string
|
||||
char data_size_buffer[32] = {0};
|
||||
snprintf(data_size_buffer, sizeof(data_size_buffer), "%zd", data.size());
|
||||
|
||||
request.append("Content-Length: ");
|
||||
request.append(data_size_buffer); // appends size of data
|
||||
request.append("\r\n");
|
||||
|
||||
request.append("\r\n"); // empty line to terminate headers
|
||||
|
||||
request.append(data);
|
||||
|
||||
int ret = socket_.Write(request.c_str(), request.size(), kWriteTimeoutMs);
|
||||
LOGD("HTTP request: (%d): %s", request.size(), b2a_hex(request).c_str());
|
||||
return ret != -1;
|
||||
}
|
||||
|
||||
bool UrlRequest::PostRequest(const std::string& data) {
|
||||
if (chunk_transfer_mode_) {
|
||||
return PostRequestChunk(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("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");
|
||||
std::ostringstream ss;
|
||||
ss << data.size();
|
||||
request_.append("Content-Length: ");
|
||||
request_.append(ss.str());
|
||||
request_.append("\r\n\r\n");
|
||||
request_.append(data);
|
||||
|
||||
// terminates with \r\n, then ends with an empty line
|
||||
request_.append("\r\n\r\n");
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str());
|
||||
LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str());
|
||||
return true;
|
||||
return PostRequestWithPath(socket_.resource_path(), data);
|
||||
}
|
||||
|
||||
bool UrlRequest::PostCertRequestInQueryString(const std::string& data) {
|
||||
request_.assign("POST /");
|
||||
request_.append(socket_.resource_path());
|
||||
request_.append("&signedRequest=");
|
||||
request_.append(data);
|
||||
request_.append(" HTTP/1.1\r\n");
|
||||
request_.append("User-Agent: Widevine CDM v1.0\r\n");
|
||||
request_.append("Host: ");
|
||||
request_.append(socket_.domain_name());
|
||||
request_.append("\r\nAccept: */*");
|
||||
request_.append("\r\nContent-Type: application/json");
|
||||
request_.append("\r\nContent-Length: 0");
|
||||
request_.append("\r\n"); // empty line to terminate header
|
||||
request_.append("\r\n"); // terminates the request
|
||||
std::string path = socket_.resource_path();
|
||||
path.append("&signedRequest=");
|
||||
path.append(data);
|
||||
|
||||
socket_.Write(request_.c_str(), request_.size());
|
||||
LOGD("HTTP request: (%d): %s", request_.size(), request_.c_str());
|
||||
LOGD("HTTP request: (%d): %s", request_.size(), b2a_hex(request_).c_str());
|
||||
return true;
|
||||
return PostRequestWithPath(path, "");
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -13,29 +13,22 @@ namespace wvcdm {
|
||||
// Only POST request method is implemented.
|
||||
class UrlRequest {
|
||||
public:
|
||||
UrlRequest(const std::string& url, const std::string& port,
|
||||
bool secure_connect, bool chunk_transfer_mode);
|
||||
explicit UrlRequest(const std::string& url);
|
||||
~UrlRequest();
|
||||
|
||||
void AppendChunkToUpload(const std::string& data);
|
||||
void ConcatenateChunkedResponse(const std::string http_response,
|
||||
std::string* modified_response);
|
||||
int GetResponse(std::string* message);
|
||||
int GetStatusCode(const std::string& response);
|
||||
bool is_connected() const { return is_connected_; }
|
||||
|
||||
bool PostRequest(const std::string& data);
|
||||
bool PostRequestChunk(const std::string& data);
|
||||
bool PostCertRequestInQueryString(const std::string& data);
|
||||
|
||||
bool GetResponse(std::string* message);
|
||||
static int GetStatusCode(const std::string& response);
|
||||
|
||||
private:
|
||||
static const unsigned int kHttpBufferSize = 4096;
|
||||
char buffer_[kHttpBufferSize];
|
||||
bool chunk_transfer_mode_;
|
||||
bool PostRequestWithPath(const std::string& path, const std::string& data);
|
||||
|
||||
bool is_connected_;
|
||||
std::string port_;
|
||||
std::string request_;
|
||||
HttpSocket socket_;
|
||||
std::string server_url_;
|
||||
|
||||
CORE_DISALLOW_COPY_AND_ASSIGN(UrlRequest);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user