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:
Fred Gylys-Colwell
2014-06-25 13:02:54 -07:00
parent 8a8feb747c
commit b5e8b87fed
66 changed files with 2927 additions and 1998 deletions

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

@@ -7,6 +7,9 @@
#include "wv_cdm_types.h"
#include <stddef.h>
#include <stdlib.h>
namespace wvcdm {
// File class. The implementation is platform dependent.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

@@ -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();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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