Source release v3.4.1

This commit is contained in:
Gene Morgan
2017-09-01 14:17:56 -07:00
parent 8082775924
commit 183aacf0a3
50 changed files with 1508 additions and 2342 deletions

View File

@@ -17,6 +17,9 @@ class CryptoKey {
const std::string& key_data_iv() const { return key_data_iv_; }
const std::string& key_control() const { return key_control_; }
const std::string& key_control_iv() const { return key_control_iv_; }
const std::string& sub_session_key_id() const {return sub_session_key_id_;}
const std::string& sub_session_key() const {return sub_session_key_;}
const std::string& track_label() const { return track_label_; }
CdmCipherMode cipher_mode() const { return cipher_mode_; }
void set_key_id(const std::string& key_id) { key_id_ = key_id; }
void set_key_data(const std::string& key_data) { key_data_ = key_data; }
@@ -28,6 +31,15 @@ class CryptoKey {
void set_cipher_mode(CdmCipherMode cipher_mode) {
cipher_mode_ = cipher_mode;
}
void set_sub_session_key_id(const std::string& sub_session_key_id) {
sub_session_key_id_ = sub_session_key_id;
}
void set_sub_session_key(const std::string& sub_session_key) {
sub_session_key_ = sub_session_key;
}
void set_track_label(const std::string& track_label) {
track_label_ = track_label;
}
bool HasKeyControl() const { return key_control_.size() >= 16; }
@@ -37,6 +49,9 @@ class CryptoKey {
std::string key_data_;
std::string key_control_;
std::string key_control_iv_;
std::string sub_session_key_id_;
std::string track_label_;
std::string sub_session_key_;
CdmCipherMode cipher_mode_;
};

View File

@@ -10,12 +10,39 @@
#include "lock.h"
#include "oemcrypto_adapter.h"
#include "OEMCryptoCENC.h"
#include "scoped_ptr.h"
#include "wv_cdm_types.h"
namespace wvcdm {
class CryptoKey;
typedef std::map<CryptoKeyId, CryptoKey*> CryptoKeyMap;
typedef std::map<std::string, CryptoSessionId> SubLicenseSessionMap;
class KeySession {
protected:
KeySession() {}
public:
typedef enum { kDefault, kSubLicense } KeySessionType;
virtual ~KeySession() {}
virtual KeySessionType Type() = 0;
virtual bool GenerateDerivedKeys(const std::string& message) = 0;
virtual bool GenerateDerivedKeys(const std::string& message,
const std::string& session_key) = 0;
virtual OEMCryptoResult LoadKeys(const std::string& message,
const std::string& signature,
const std::string& mac_key_iv,
const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
CdmCipherMode* cipher_mode) = 0;
virtual OEMCryptoResult SelectKey(const std::string& key_id) = 0;
virtual OEMCryptoResult Decrypt(
const CdmDecryptionParameters& params,
OEMCrypto_DestBufferDesc& buffer_descriptor,
OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) = 0;
};
class CryptoSession {
public:
@@ -79,7 +106,7 @@ class CryptoSession {
std::string* wrapped_private_key);
// Media data path
virtual CdmResponseType Decrypt(const CdmDecryptionParameters& parameters);
virtual CdmResponseType Decrypt(const CdmDecryptionParameters& params);
// Usage related methods
virtual bool UsageInformationSupport(bool* has_support);
@@ -129,19 +156,21 @@ class CryptoSession {
CdmSigningAlgorithm algorithm,
const std::string& signature);
virtual CdmResponseType AddSubSession(const std::string& sub_session_key_id);
// TODO(jfore): exists is set based on whether a sub session exists. For now,
// that is not assumed to be an error.
virtual bool GenerateSubSessionNonce(const std::string& sub_session_key_id,
bool* exists, uint32_t* nonce);
private:
bool GetProvisioningMethod(CdmClientTokenType& token_type);
void Init();
void Terminate();
bool GetTokenFromKeybox(std::string* token);
bool GetTokenFromOemCert(std::string* token);
void GenerateMacContext(const std::string& input_context,
std::string* deriv_context);
void GenerateEncryptContext(const std::string& input_context,
std::string* deriv_context);
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();
bool RewrapDeviceRSAKey(
@@ -154,7 +183,7 @@ class CryptoSession {
const std::string& private_key, const std::string& iv,
const std::string& wrapping_key, std::string* wrapped_private_key);
bool SelectKey(const std::string& key_id);
CdmResponseType SelectKey(const std::string& key_id);
static const OEMCrypto_Algorithm kInvalidAlgorithm =
static_cast<OEMCrypto_Algorithm>(-1);
@@ -177,7 +206,7 @@ class CryptoSession {
static void IncrementIV(uint64_t increase_by, std::vector<uint8_t>* iv_out);
static const size_t kAes128BlockSize = 16; // Block size for AES_CBC_128
static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature
static const size_t kSignatureSize = 32; // size for HMAC-SHA256 signature
static Lock crypto_lock_;
static bool initialized_;
static int session_count_;
@@ -187,6 +216,10 @@ class CryptoSession {
std::string oem_token_; // Cached OEMCrypto Public Key
bool update_usage_table_after_close_session_;
CryptoSessionId oec_session_id_;
SubLicenseSessionMap sub_license_oec_sessions_;
// Used for sub license sessions.
std::string wrapped_key_;
scoped_ptr<KeySession> key_session_;
OEMCryptoBufferType destination_buffer_type_;
bool is_destination_buffer_type_valid_;

View File

@@ -5,6 +5,7 @@
#include <string>
#include "license_protocol.pb.h"
#include "wv_cdm_types.h"
namespace wvcdm {
@@ -27,6 +28,7 @@ class InitializationData {
const CdmInitData& data() const { return data_; }
std::vector<uint8_t> hls_iv() const { return hls_iv_; }
CdmHlsMethod hls_method() const { return hls_method_; }
std::vector<video_widevine::SubLicense> ExtractEmbeddedKeys() const;
private:
// Parse a blob of multiple concatenated PSSH atoms to extract the first

View File

@@ -21,6 +21,7 @@ class Clock;
class CryptoSession;
class PolicyEngine;
class ServiceCertificate;
class CryptoKey;
class CdmLicense {
public:
@@ -43,6 +44,7 @@ class CdmLicense {
const CdmKeyResponse& license_response);
virtual CdmResponseType HandleKeyUpdateResponse(
bool is_renewal, const CdmKeyResponse& license_response);
virtual CdmResponseType HandleSubLicense(const InitializationData& init_data);
virtual bool RestoreOfflineLicense(
const CdmKeyMessage& license_request,
@@ -107,6 +109,11 @@ class CdmLicense {
// For testing
// CdmLicense takes ownership of the clock.
CdmLicense(const CdmSessionId& session_id, Clock* clock);
// For sublicense key embedding. This key array will be initilized with any
// sub session keys we may have received in a license response. These keys
// may be used to support key rotation.
std::vector<CryptoKey> sub_session_key_array_;
#if defined(UNIT_TEST)
friend class CdmLicenseTest;
#endif

View File

@@ -50,6 +50,8 @@ class PolicyEngine {
// permits playback.
virtual void SetLicense(const video_widevine::License& license);
virtual void UpdateLicenseKeys(const video_widevine::License& license);
// SetLicenseForRelease is used when releasing a license. The keys in this
// license will be ignored, and any old keys will be expired.
virtual void SetLicenseForRelease(

View File

@@ -109,6 +109,7 @@ class Properties {
FRIEND_TEST(CdmSessionTest, InitFailCryptoError);
FRIEND_TEST(CdmSessionTest, InitNeedsProvisioning);
FRIEND_TEST(CdmLicenseTest, PrepareKeyRequestValidation);
FRIEND_TEST(SubLicenseTest, VerifySubSessionData);
#endif
private:

View File

@@ -271,6 +271,13 @@ enum CdmResponseType {
PARSE_RESPONSE_ERROR_2,
PARSE_RESPONSE_ERROR_3, /* 230 */
PARSE_RESPONSE_ERROR_4,
LICENSE_REQUEST_INVALID_SUBLICENSE,
INVALID_SESSION_1,
NO_DEVICE_KEY_1,
NO_CONTENT_KEY_2, /* 235 */
INSUFFICIENT_CRYPTO_RESOURCES_2,
UNKNOWN_SELECT_KEY_ERROR_1,
UNKNOWN_SELECT_KEY_ERROR_2,
};
enum CdmKeyStatus {
@@ -294,6 +301,7 @@ enum CdmLicenseType {
// Like Streaming, but stricter. Does not permit storage of any kind.
// Named after the 'temporary' session type in EME, which has this behavior.
kLicenseTypeTemporary,
kLicenseTypeSubSession
};
enum SecurityLevel {

View File

@@ -243,6 +243,8 @@ CdmResponseType CdmSession::GenerateKeyRequest(
case kLicenseTypeRelease:
is_release_ = true;
break;
case kLicenseTypeSubSession:
return license_parser_->HandleSubLicense(init_data);
default:
LOGE("CdmSession::GenerateKeyRequest: unrecognized license type: %ld",
license_type);
@@ -270,6 +272,17 @@ CdmResponseType CdmSession::GenerateKeyRequest(
return KEY_REQUEST_ERROR_1;
}
std::vector<video_widevine::SubLicense> embedded_key_data =
init_data.ExtractEmbeddedKeys();
for (int i = 0; i < embedded_key_data.size(); ++i) {
CdmResponseType sts = crypto_session_->AddSubSession(
embedded_key_data[i].sub_session_key_id());
if (NO_ERROR != sts) {
LOGE("CdmSession::GenerateKeyRequest: Unable to generate sub session");
return sts;
}
}
app_parameters_ = app_parameters;
CdmResponseType status = license_parser_->PrepareKeyRequest(
init_data, license_type,

View File

@@ -26,6 +26,47 @@ std::string EncodeUint32(unsigned int u) {
s.append(1, (u >> 0) & 0xFF);
return s;
}
size_t GetOffset(std::string message, std::string field) {
size_t pos = message.find(field);
if (pos == std::string::npos) {
LOGE("CryptoSession::GetOffset : Cannot find offset for %s", field.c_str());
pos = 0;
}
return pos;
}
void GenerateMacContext(const std::string& input_context,
std::string* deriv_context) {
if (!deriv_context) {
LOGE("CryptoSession::GenerateMacContext : No output destination provided.");
return;
}
const std::string kSigningKeyLabel = "AUTHENTICATION";
const size_t kSigningKeySizeBits = wvcdm::MAC_KEY_SIZE * 8;
deriv_context->assign(kSigningKeyLabel);
deriv_context->append(1, '\0');
deriv_context->append(input_context);
deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2));
}
void GenerateEncryptContext(const std::string& input_context,
std::string* deriv_context) {
if (!deriv_context) {
LOGE(
"CryptoSession::GenerateEncryptContext : No output destination "
"provided.");
return;
}
const std::string kEncryptionKeyLabel = "ENCRYPTION";
const size_t kEncryptionKeySizeBits = wvcdm::KEY_SIZE * 8;
deriv_context->assign(kEncryptionKeyLabel);
deriv_context->append(1, '\0');
deriv_context->append(input_context);
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
}
const uint32_t kRsaSignatureLength = 256;
const size_t kMaximumChunkSize = 100 * 1024; // 100 KiB
}
@@ -37,6 +78,464 @@ bool CryptoSession::initialized_ = false;
int CryptoSession::session_count_ = 0;
uint64_t CryptoSession::request_id_index_ = 0;
class DefaultKeySession : public KeySession {
public:
explicit DefaultKeySession(CryptoSessionId oec_session_id)
: oec_session_id_(oec_session_id) {}
virtual ~DefaultKeySession() {}
KeySessionType Type() { return kDefault; }
bool GenerateDerivedKeys(const std::string& message) {
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(message, &mac_deriv_message);
GenerateEncryptContext(message, &enc_deriv_message);
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
OEMCryptoResult sts = OEMCrypto_GenerateDerivedKeys(
oec_session_id_,
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
return false;
}
return true;
}
bool GenerateDerivedKeys(const std::string& message,
const std::string& session_key) {
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(message, &mac_deriv_message);
GenerateEncryptContext(message, &enc_deriv_message);
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey(
oec_session_id_,
reinterpret_cast<const uint8_t*>(session_key.data()),
session_key.size(),
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d",
sts);
return false;
}
return true;
}
OEMCryptoResult LoadKeys(const std::string& message,
const std::string& signature,
const std::string& mac_key_iv,
const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
CdmCipherMode* cipher_mode) {
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
const uint8_t* enc_mac_key = NULL;
const uint8_t* enc_mac_key_iv = NULL;
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
enc_mac_key = msg + GetOffset(message, mac_key);
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
} else {
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
}
std::vector<OEMCrypto_KeyObject> load_keys(keys.size());
for (size_t i = 0; i < keys.size(); ++i) {
const CryptoKey* ki = &keys[i];
OEMCrypto_KeyObject* ko = &load_keys[i];
ko->key_id = msg + GetOffset(message, ki->key_id());
ko->key_id_length = ki->key_id().length();
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
ko->key_data = msg + GetOffset(message, ki->key_data());
ko->key_data_length = ki->key_data().length();
if (ki->HasKeyControl()) {
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
ko->key_control = msg + GetOffset(message, ki->key_control());
} else {
LOGE("For key %d: XXX key has no control block. size=%d", i,
ki->key_control().size());
ko->key_control_iv = NULL;
ko->key_control = NULL;
}
ko->cipher_mode = ki->cipher_mode() == kCipherModeCbc
? OEMCrypto_CipherMode_CBC
: OEMCrypto_CipherMode_CTR;
*cipher_mode = ki->cipher_mode();
}
uint8_t* pst = NULL;
if (!provider_session_token.empty()) {
pst = const_cast<uint8_t*>(msg) +
GetOffset(message, provider_session_token);
}
LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_);
return OEMCrypto_LoadKeys(
oec_session_id_, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), enc_mac_key_iv, enc_mac_key, keys.size(),
&load_keys[0], pst, provider_session_token.length());
}
OEMCryptoResult SelectKey(const std::string& key_id) {
// Crypto session lock already locked.
if (!cached_key_id_.empty() && cached_key_id_ == key_id) {
// Already using the desired key.
return OEMCrypto_SUCCESS;
}
cached_key_id_ = key_id;
const uint8_t* key_id_string =
reinterpret_cast<const uint8_t*>(cached_key_id_.data());
OEMCryptoResult sts = OEMCrypto_SelectKey(oec_session_id_, key_id_string,
cached_key_id_.size());
if (OEMCrypto_SUCCESS != sts) {
cached_key_id_.clear();
}
return sts;
}
OEMCryptoResult Decrypt(
const CdmDecryptionParameters& params,
OEMCrypto_DestBufferDesc& buffer_descriptor,
OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) {
return OEMCrypto_DecryptCENC(
oec_session_id_, params.encrypt_buffer, params.encrypt_length,
params.is_encrypted, &(*params.iv).front(), params.block_offset,
&buffer_descriptor, &pattern_descriptor, params.subsample_flags);
}
private:
CryptoSessionId oec_session_id_;
KeyId cached_key_id_;
};
// TODO(jfore): Move this class into it's own module.
class SubLicenseKeySession : public KeySession {
typedef enum {
kInitializing,
kInitialLicenseLoaded,
kInitialLicenseFailed,
} SubLicenseState;
public:
SubLicenseKeySession(SubLicenseSessionMap& sub_license_oec_sessions,
std::string& wrapped_private_device_key,
SecurityLevel requested_security_level)
: state_(kInitializing),
wrapped_private_device_key_(wrapped_private_device_key),
sub_license_oec_sessions_(sub_license_oec_sessions),
requested_security_level_(requested_security_level) {}
virtual ~SubLicenseKeySession() {}
KeySessionType Type() { return kSubLicense; }
// This version of GenerateDerivedKeys is for devices using keyboxes. It is
// not supported using sub licenses.
bool GenerateDerivedKeys(const std::string& message) {
return false;
}
// GenerateDerivedKeys is called for each open oemcrypto session and is only
// called once.
bool GenerateDerivedKeys(const std::string& message,
const std::string& session_key) {
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(message, &mac_deriv_message);
GenerateEncryptContext(message, &enc_deriv_message);
for (SubLicenseSessionMap::iterator it = sub_license_oec_sessions_.begin();
it != sub_license_oec_sessions_.end(); it++) {
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)it->second);
OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey(
it->second, reinterpret_cast<const uint8_t*>(session_key.data()),
session_key.size(),
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d",
sts);
return false;
}
}
return true;
}
// Load the keys in |keys|. The initial keys are saved for key rotation.
OEMCryptoResult LoadKeys(const std::string& message,
const std::string& signature,
const std::string& mac_key_iv,
const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
CdmCipherMode* cipher_mode) {
if (state_ == kInitializing) {
state_ = kInitialLicenseLoaded;
keys_ = keys;
OEMCryptoResult sts = DoLoadKeys(message,
signature,
mac_key_iv,
mac_key,
keys,
provider_session_token,
cipher_mode);
if (OEMCrypto_SUCCESS != sts) {
state_ = kInitialLicenseFailed;
}
return sts;
}
return DoSubLicenseLoadKeys(message, signature, mac_key_iv, mac_key,
keys[0], provider_session_token,
cipher_mode);
}
// Each oemcrypto session contains a single key. Find the right sub session
// and save it's id as the selected oemcrypto session.
OEMCryptoResult SelectKey(const std::string& key_id) {
for (int i = 0; i < keys_.size(); ++i) {
if (keys_[i].key_id() == key_id) {
cached_sub_session_key_id_ = keys_[i].sub_session_key_id();
}
}
return OEMCrypto_SUCCESS;
}
// Decrypt performs the decryption using the selected oemcrypto session.
// TODO(jfore): Support DecryptInChunks.
OEMCryptoResult Decrypt(
const CdmDecryptionParameters& params,
OEMCrypto_DestBufferDesc& buffer_descriptor,
OEMCrypto_CENCEncryptPatternDesc& pattern_descriptor) {
SubLicenseSessionMap::iterator it =
sub_license_oec_sessions_.find(cached_sub_session_key_id_);
if (it == sub_license_oec_sessions_.end()) {
return OEMCrypto_ERROR_INVALID_SESSION;
}
return OEMCrypto_DecryptCENC(
it->second, params.encrypt_buffer, params.encrypt_length,
params.is_encrypted, &(*params.iv).front(), params.block_offset,
&buffer_descriptor, &pattern_descriptor, params.subsample_flags);
}
private:
// Destroy each open oemcrypto session and relace them with new ones.
OEMCryptoResult ResetCryptoSessions() {
for (SubLicenseSessionMap::iterator it = sub_license_oec_sessions_.begin();
it != sub_license_oec_sessions_.end(); it++) {
OEMCryptoResult sts;
sts = OEMCrypto_CloseSession(it->second);
if (OEMCrypto_SUCCESS != sts) {
return sts;
}
sts = OEMCrypto_OpenSession(&it->second, requested_security_level_);
if (OEMCrypto_SUCCESS != sts) {
return sts;
}
sts = OEMCrypto_LoadDeviceRSAKey(
it->second,
reinterpret_cast<const uint8_t*>(wrapped_private_device_key_.data()),
wrapped_private_device_key_.size());
if (OEMCrypto_SUCCESS != sts) {
return sts;
}
}
return OEMCrypto_SUCCESS;
}
// DoLoadKeys loads a single key into each oemcrypto session.
OEMCryptoResult DoLoadKeys(const std::string& message,
const std::string& signature,
const std::string& mac_key_iv,
const std::string& mac_key,
const std::vector<CryptoKey>& keys,
const std::string& provider_session_token,
CdmCipherMode* cipher_mode) {
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
const uint8_t* enc_mac_key = NULL;
const uint8_t* enc_mac_key_iv = NULL;
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
enc_mac_key = msg + GetOffset(message, mac_key);
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
} else {
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
}
uint8_t* pst = NULL;
if (!provider_session_token.empty()) {
pst = const_cast<uint8_t*>(msg) +
GetOffset(message, provider_session_token);
}
// TODO(jfore): Use C++ 11 range loop?
for (int i = 0; i < keys.size(); i++) {
OEMCrypto_KeyObject key_object;
const CryptoKey& key_data = keys[i];
key_object.key_id = msg + GetOffset(message, key_data.key_id());
key_object.key_id_length = key_data.key_id().length();
key_object.key_data_iv = msg + GetOffset(message, key_data.key_data_iv());
key_object.key_data = msg + GetOffset(message, key_data.key_data());
key_object.key_data_length = key_data.key_data().length();
if (key_data.HasKeyControl()) {
key_object.key_control_iv =
msg + GetOffset(message, key_data.key_control_iv());
key_object.key_control =
msg + GetOffset(message, key_data.key_control());
} else {
LOGE("For key %s: XXX key has no control block. size=%d",
key_data.key_id().c_str(), key_data.key_control().size());
key_object.key_control_iv = NULL;
key_object.key_control = NULL;
}
key_object.cipher_mode = key_data.cipher_mode() == kCipherModeCbc
? OEMCrypto_CipherMode_CBC
: OEMCrypto_CipherMode_CTR;
*cipher_mode = key_data.cipher_mode();
SubLicenseSessionMap::iterator oec_session_id =
sub_license_oec_sessions_.find(key_data.sub_session_key_id());
if (oec_session_id == sub_license_oec_sessions_.end()) {
LOGE("CryptoSession::LoadKeys: Unrecognized sub session %s",
key_data.sub_session_key_id().c_str());
return OEMCrypto_ERROR_INVALID_SESSION;
}
OEMCryptoResult sts;
sts = OEMCrypto_LoadKeys(
oec_session_id->second, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), enc_mac_key_iv, enc_mac_key, 1, &key_object,
pst, provider_session_token.length());
if (sts != OEMCrypto_SUCCESS) {
return sts;
}
sts = OEMCrypto_SelectKey(
oec_session_id->second,
reinterpret_cast<const uint8_t*>(key_data.key_id().data()),
key_data.key_id().size());
}
keys_ = keys;
return OEMCrypto_SUCCESS;
}
// DoLoadKeys loads a single key into each oemcrypto session.
OEMCryptoResult DoSubLicenseLoadKeys(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
const CryptoKey& key, const std::string& provider_session_token,
CdmCipherMode* cipher_mode) {
SubLicenseSessionMap::iterator it = sub_license_oec_sessions_.end();
int key_index = 0;
for (; key_index < keys_.size(); key_index++) {
if (keys_[key_index].track_label() == key.track_label()) {
it = sub_license_oec_sessions_.find(
keys_[key_index].sub_session_key_id());
CryptoKey tmp = key;
tmp.set_sub_session_key_id(keys_[key_index].sub_session_key_id());
tmp.set_sub_session_key(keys_[key_index].sub_session_key());
keys_[key_index] = tmp;
break;
}
}
if (it == sub_license_oec_sessions_.end()) {
return OEMCrypto_ERROR_INVALID_SESSION;
}
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)it->second);
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(key.track_label(), &mac_deriv_message);
GenerateEncryptContext(key.track_label(), &enc_deriv_message);
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
const uint8_t* enc_mac_key = NULL;
const uint8_t* enc_mac_key_iv = NULL;
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
enc_mac_key = msg + GetOffset(message, mac_key);
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
} else {
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
}
uint8_t* pst = NULL;
if (!provider_session_token.empty()) {
pst = const_cast<uint8_t*>(msg) +
GetOffset(message, provider_session_token);
}
OEMCryptoResult sts;
const std::string& sub_session_key = keys_[key_index].sub_session_key();
LOGE("ssksize = %d", sub_session_key.size());
sts = OEMCrypto_DeriveKeysFromSessionKey(
it->second, reinterpret_cast<const uint8_t*>(sub_session_key.data()),
sub_session_key.size(),
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d",
sts);
return sts;
}
OEMCrypto_KeyObject key_object;
key_object.key_id = msg + GetOffset(message, keys_[key_index].key_id());
key_object.key_id_length = keys_[key_index].key_id().length();
key_object.key_data_iv = msg + GetOffset(
message, keys_[key_index].key_data_iv());
key_object.key_data = msg + GetOffset(message, keys_[key_index].key_data());
key_object.key_data_length = keys_[key_index].key_data().length();
if (key.HasKeyControl()) {
key_object.key_control_iv =
msg + GetOffset(message, keys_[key_index].key_control_iv());
key_object.key_control = msg + GetOffset(
message, keys_[key_index].key_control());
}
key_object.cipher_mode = keys_[key_index].cipher_mode() == kCipherModeCbc
? OEMCrypto_CipherMode_CBC
: OEMCrypto_CipherMode_CTR;
sts = OEMCrypto_LoadKeys(
it->second, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), enc_mac_key_iv, enc_mac_key, 1, &key_object,
pst, provider_session_token.length());
if (sts != OEMCrypto_SUCCESS) {
return sts;
}
sts = OEMCrypto_SelectKey(
it->second,
reinterpret_cast<const uint8_t*>(keys_[key_index].key_id().data()),
keys_[key_index].key_id().size());
return sts;
}
SubLicenseState state_;
std::string cached_sub_session_key_id_;
std::string wrapped_private_device_key_;
std::string message_;
std::string session_key_;
std::vector<CryptoKey> keys_;
SubLicenseSessionMap& sub_license_oec_sessions_;
SecurityLevel requested_security_level_;
};
CryptoSession::CryptoSession()
: open_(false),
update_usage_table_after_close_session_(false),
@@ -349,6 +848,7 @@ CdmResponseType CryptoSession::Open(SecurityLevel requested_security_level) {
OEMCrypto_GetRandom(reinterpret_cast<uint8_t*>(&request_id_base_),
sizeof(request_id_base_));
++request_id_index_;
key_session_.reset(new DefaultKeySession(oec_session_id_));
return NO_ERROR;
}
@@ -424,49 +924,6 @@ bool CryptoSession::PrepareRenewalRequest(const std::string& message,
return true;
}
void CryptoSession::GenerateMacContext(const std::string& input_context,
std::string* deriv_context) {
if (!deriv_context) {
LOGE("CryptoSession::GenerateMacContext : No output destination provided.");
return;
}
const std::string kSigningKeyLabel = "AUTHENTICATION";
const size_t kSigningKeySizeBits = MAC_KEY_SIZE * 8;
deriv_context->assign(kSigningKeyLabel);
deriv_context->append(1, '\0');
deriv_context->append(input_context);
deriv_context->append(EncodeUint32(kSigningKeySizeBits * 2));
}
void CryptoSession::GenerateEncryptContext(const std::string& input_context,
std::string* deriv_context) {
if (!deriv_context) {
LOGE(
"CryptoSession::GenerateEncryptContext : No output destination "
"provided.");
return;
}
const std::string kEncryptionKeyLabel = "ENCRYPTION";
const size_t kEncryptionKeySizeBits = KEY_SIZE * 8;
deriv_context->assign(kEncryptionKeyLabel);
deriv_context->append(1, '\0');
deriv_context->append(input_context);
deriv_context->append(EncodeUint32(kEncryptionKeySizeBits));
}
size_t CryptoSession::GetOffset(std::string message, std::string field) {
size_t pos = message.find(field);
if (pos == std::string::npos) {
LOGE("CryptoSession::GetOffset : Cannot find offset for %s", field.c_str());
pos = 0;
}
return pos;
}
CdmResponseType CryptoSession::LoadKeys(
const std::string& message, const std::string& signature,
const std::string& mac_key_iv, const std::string& mac_key,
@@ -475,49 +932,9 @@ CdmResponseType CryptoSession::LoadKeys(
LOGV("CryptoSession::LoadKeys: Lock");
AutoLock auto_lock(crypto_lock_);
const uint8_t* msg = reinterpret_cast<const uint8_t*>(message.data());
const uint8_t* enc_mac_key = NULL;
const uint8_t* enc_mac_key_iv = NULL;
if (mac_key.size() >= MAC_KEY_SIZE && mac_key_iv.size() >= KEY_IV_SIZE) {
enc_mac_key = msg + GetOffset(message, mac_key);
enc_mac_key_iv = msg + GetOffset(message, mac_key_iv);
} else {
LOGV("CryptoSession::LoadKeys: enc_mac_key not set");
}
std::vector<OEMCrypto_KeyObject> load_keys(keys.size());
for (size_t i = 0; i < keys.size(); ++i) {
const CryptoKey* ki = &keys[i];
OEMCrypto_KeyObject* ko = &load_keys[i];
ko->key_id = msg + GetOffset(message, ki->key_id());
ko->key_id_length = ki->key_id().length();
ko->key_data_iv = msg + GetOffset(message, ki->key_data_iv());
ko->key_data = msg + GetOffset(message, ki->key_data());
ko->key_data_length = ki->key_data().length();
if (ki->HasKeyControl()) {
ko->key_control_iv = msg + GetOffset(message, ki->key_control_iv());
ko->key_control = msg + GetOffset(message, ki->key_control());
} else {
LOGE("For key %d: XXX key has no control block. size=%d", i,
ki->key_control().size());
ko->key_control_iv = NULL;
ko->key_control = NULL;
}
ko->cipher_mode = ki->cipher_mode() == kCipherModeCbc
? OEMCrypto_CipherMode_CBC
: OEMCrypto_CipherMode_CTR;
cipher_mode_ = ki->cipher_mode();
}
uint8_t* pst = NULL;
if (!provider_session_token.empty()) {
pst =
const_cast<uint8_t*>(msg) + GetOffset(message, provider_session_token);
}
LOGV("LoadKeys: id=%ld", (uint32_t)oec_session_id_);
OEMCryptoResult sts = OEMCrypto_LoadKeys(
oec_session_id_, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()), signature.size(),
enc_mac_key_iv, enc_mac_key, keys.size(), &load_keys[0], pst,
provider_session_token.length());
OEMCryptoResult sts = key_session_->LoadKeys(
message, signature, mac_key_iv, mac_key, keys, provider_session_token,
&cipher_mode_);
if (OEMCrypto_SUCCESS == sts) {
if (!provider_session_token.empty()) {
@@ -559,6 +976,7 @@ bool CryptoSession::LoadCertificatePrivateKey(std::string& wrapped_key) {
LOGE("LoadCertificatePrivateKey: OEMCrypto_LoadDeviceRSAKey error=%d", sts);
return false;
}
wrapped_key_ = wrapped_key;
return true;
}
@@ -592,79 +1010,47 @@ bool CryptoSession::RefreshKeys(const std::string& message,
}
}
LOGV("RefreshKeys: id=%ld", static_cast<uint32_t>(oec_session_id_));
return (
OEMCrypto_SUCCESS ==
OEMCrypto_RefreshKeys(oec_session_id_, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), num_keys, &load_key_array[0]));
OEMCryptoResult sts = OEMCrypto_RefreshKeys(
oec_session_id_, msg, message.size(),
reinterpret_cast<const uint8_t*>(signature.data()),
signature.size(), num_keys, &load_key_array[0]);
return sts == OEMCrypto_SUCCESS;
}
bool CryptoSession::SelectKey(const std::string& key_id) {
// Crypto session lock already locked.
if (!cached_key_id_.empty() && cached_key_id_ == key_id) {
// Already using the desired key.
return true;
CdmResponseType CryptoSession::SelectKey(const std::string& key_id) {
OEMCryptoResult sts = key_session_->SelectKey(key_id);
switch (sts) {
case OEMCrypto_SUCCESS:
return NO_ERROR;
case OEMCrypto_ERROR_KEY_EXPIRED:
return NEED_KEY;
case OEMCrypto_ERROR_INSUFFICIENT_HDCP:
return INSUFFICIENT_OUTPUT_PROTECTION;
case OEMCrypto_ERROR_INVALID_SESSION:
return INVALID_SESSION_1;
case OEMCrypto_ERROR_NO_DEVICE_KEY:
return NO_DEVICE_KEY_1;
case OEMCrypto_ERROR_NO_CONTENT_KEY:
return NO_CONTENT_KEY_2;
case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES:
return INSUFFICIENT_CRYPTO_RESOURCES_2;
case OEMCrypto_ERROR_UNKNOWN_FAILURE:
return UNKNOWN_SELECT_KEY_ERROR_1;
case OEMCrypto_ERROR_CONTROL_INVALID:
case OEMCrypto_ERROR_KEYBOX_INVALID:
default:
return UNKNOWN_SELECT_KEY_ERROR_2;
}
cached_key_id_ = key_id;
const uint8_t* key_id_string =
reinterpret_cast<const uint8_t*>(cached_key_id_.data());
OEMCryptoResult sts =
OEMCrypto_SelectKey(oec_session_id_, key_id_string,
cached_key_id_.size());
if (OEMCrypto_SUCCESS != sts) {
cached_key_id_.clear();
return false;
}
return true;
}
bool CryptoSession::GenerateDerivedKeys(const std::string& message) {
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(message, &mac_deriv_message);
GenerateEncryptContext(message, &enc_deriv_message);
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
OEMCryptoResult sts = OEMCrypto_GenerateDerivedKeys(
oec_session_id_,
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenerateDerivedKeys: OEMCrypto_GenerateDerivedKeys error=%d", sts);
return false;
}
return true;
return key_session_->GenerateDerivedKeys(message);
}
bool CryptoSession::GenerateDerivedKeys(const std::string& message,
const std::string& session_key) {
std::string mac_deriv_message;
std::string enc_deriv_message;
GenerateMacContext(message, &mac_deriv_message);
GenerateEncryptContext(message, &enc_deriv_message);
LOGV("GenerateDerivedKeys: id=%ld", (uint32_t)oec_session_id_);
OEMCryptoResult sts = OEMCrypto_DeriveKeysFromSessionKey(
oec_session_id_, reinterpret_cast<const uint8_t*>(session_key.data()),
session_key.size(),
reinterpret_cast<const uint8_t*>(mac_deriv_message.data()),
mac_deriv_message.size(),
reinterpret_cast<const uint8_t*>(enc_deriv_message.data()),
enc_deriv_message.size());
if (OEMCrypto_SUCCESS != sts) {
LOGE("GenerateDerivedKeys: OEMCrypto_DeriveKeysFromSessionKey err=%d", sts);
return false;
}
return true;
return key_session_->GenerateDerivedKeys(message, session_key);
}
bool CryptoSession::GenerateSignature(const std::string& message,
@@ -801,7 +1187,7 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
AutoLock auto_lock(crypto_lock_);
// Check if key needs to be selected
if (params.is_encrypted) {
if (!SelectKey(*params.key_id)) {
if (NO_ERROR != SelectKey(*params.key_id)) {
return NEED_KEY;
}
}
@@ -810,12 +1196,13 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
params.is_encrypted, &(*params.iv).front(), params.block_offset,
&buffer_descriptor, &pattern_descriptor, params.subsample_flags);
sts = key_session_->Decrypt(params, buffer_descriptor, pattern_descriptor);
if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) {
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it up
// into sections no more than 100 KiB. The exact chunk size needs to be
// an even number of pattern repetitions long or else the pattern will get
// out of sync.
// OEMCrypto_DecryptCENC rejected the buffer as too large, so chunk it
// up into sections no more than 100 KiB. The exact chunk size needs to
// be an even number of pattern repetitions long or else the pattern
// will get out of sync.
const size_t pattern_length =
(pattern_descriptor.encrypt + pattern_descriptor.skip) *
kAes128BlockSize;
@@ -1015,8 +1402,10 @@ CdmResponseType CryptoSession::DeleteUsageInformation(
reinterpret_cast<const uint8_t*>(provider_session_token.c_str()),
provider_session_token.length());
if (OEMCrypto_SUCCESS != status) {
LOGE("CryptoSession::DeleteUsageInformation: Delete Usage Table error =%ld",
status);
LOGE(
"CryptoSession::DeleteUsageInformation: Delete Usage Table error "
"=%ld",
status);
response = UNKNOWN_ERROR;
}
status = OEMCrypto_UpdateUsageTable();
@@ -1239,7 +1628,9 @@ bool CryptoSession::GetHdcpCapabilities(HdcpCapability* current,
LOGV("GetHdcpCapabilities: id=%ld", (uint32_t)oec_session_id_);
if (!initialized_) return false;
if (current == NULL || max == NULL) {
LOGE("CryptoSession::GetHdcpCapabilities: |current|, |max| cannot be NULL");
LOGE(
"CryptoSession::GetHdcpCapabilities: |current|, |max| cannot be "
"NULL");
return false;
}
OEMCryptoResult status = OEMCrypto_GetHDCPCapability(
@@ -1323,7 +1714,7 @@ CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer,
}
AutoLock auto_lock(crypto_lock_);
if (!SelectKey(key_id)) {
if (NO_ERROR != SelectKey(key_id)) {
return KEY_ERROR_1;
}
@@ -1364,7 +1755,7 @@ CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer,
}
AutoLock auto_lock(crypto_lock_);
if (!SelectKey(key_id)) {
if (NO_ERROR != SelectKey(key_id)) {
return KEY_ERROR_2;
}
@@ -1405,7 +1796,7 @@ CdmResponseType CryptoSession::GenericSign(const std::string& message,
size_t length = signature->size();
AutoLock auto_lock(crypto_lock_);
if (!SelectKey(key_id)) {
if (NO_ERROR != SelectKey(key_id)) {
return KEY_ERROR_3;
}
@@ -1452,7 +1843,7 @@ CdmResponseType CryptoSession::GenericVerify(const std::string& message,
}
AutoLock auto_lock(crypto_lock_);
if (!SelectKey(key_id)) {
if (NO_ERROR != SelectKey(key_id)) {
return KEY_ERROR_4;
}
@@ -1473,6 +1864,65 @@ CdmResponseType CryptoSession::GenericVerify(const std::string& message,
return NO_ERROR;
}
CdmResponseType CryptoSession::AddSubSession(
const std::string& sub_session_key_id) {
size_t exists = sub_license_oec_sessions_.count(sub_session_key_id);
if (exists > 0) {
// TODO(jfore): Should this be an error if the key exists? If so add a new
// error. If not, perhaps this should just print info message.
LOGE("AddSubSession: SubSession already exists for id: %s",
sub_session_key_id.c_str());
return UNKNOWN_ERROR;
}
CryptoSessionId sid;
OEMCryptoResult sts = OEMCrypto_OpenSession(&sid, requested_security_level_);
if (OEMCrypto_ERROR_TOO_MANY_SESSIONS == sts) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
sts, session_count_, (int)initialized_);
return INSUFFICIENT_CRYPTO_RESOURCES;
} else if (OEMCrypto_SUCCESS != sts) {
LOGE("OEMCrypto_Open failed: %d, open sessions: %ld, initialized: %d",
sts, session_count_, (int)initialized_);
return UNKNOWN_ERROR;
}
sts = OEMCrypto_LoadDeviceRSAKey(
sid, reinterpret_cast<const uint8_t*>(wrapped_key_.data()),
wrapped_key_.size());
sub_license_oec_sessions_[sub_session_key_id] = sid;
if (key_session_->Type() != KeySession::kSubLicense) {
key_session_.reset(new SubLicenseKeySession(
sub_license_oec_sessions_, wrapped_key_, requested_security_level_));
}
return NO_ERROR;
}
bool CryptoSession::GenerateSubSessionNonce(
const std::string& sub_session_key_id, bool* exists, uint32_t* nonce) {
if (!exists || !nonce) {
LOGE("input parameter is null");
return false;
}
LOGV("CryptoSession::GenerateSubSessionNonce: Lock");
AutoLock auto_lock(crypto_lock_);
SubLicenseSessionMap::iterator it =
sub_license_oec_sessions_.find(sub_session_key_id);
if (it == sub_license_oec_sessions_.end()) {
// A subsession does not exist. Indicate that and return success.
*exists = false;
return true;
}
*exists = true;
OEMCryptoResult result;
result = OEMCrypto_GenerateNonce(it->second, nonce);
return OEMCrypto_SUCCESS == result;
}
OEMCrypto_Algorithm CryptoSession::GenericSigningAlgorithm(
CdmSigningAlgorithm algorithm) {
if (kSigningAlgorithmHmacSha256 == algorithm) {
@@ -1514,8 +1964,8 @@ OEMCryptoResult CryptoSession::CopyBufferInChunks(
const size_t additional_offset =
params.encrypt_length - remaining_encrypt_length;
// Update the remaining length of the original buffer only after calculating
// the new values.
// Update the remaining length of the original buffer only after
// calculating the new values.
remaining_encrypt_length -= chunk_size;
// Update the destination buffer with the new offset.
@@ -1583,8 +2033,8 @@ OEMCryptoResult CryptoSession::DecryptInChunks(
// calculating the new values.
remaining_encrypt_length -= chunk_size;
// Update the destination buffer with the new offset. Because OEMCrypto can
// modify the OEMCrypto_DestBufferDesc during the call to
// Update the destination buffer with the new offset. Because OEMCrypto
// can modify the OEMCrypto_DestBufferDesc during the call to
// OEMCrypto_DecryptCENC, (and is known to do so on some platforms) a new
// OEMCrypto_DestBufferDesc must be allocated for each call.
OEMCrypto_DestBufferDesc buffer_descriptor = full_buffer_descriptor;
@@ -1655,8 +2105,8 @@ OEMCryptoResult CryptoSession::DecryptInChunks(
// For cbcs, we must look for the last encrypted block, which is
// probably not the last block of the subsample. Luckily, since the
// buffer size is guaranteed to be an even number of pattern
// repetitions long, we can use the pattern to know how many blocks to
// look back.
// repetitions long, we can use the pattern to know how many blocks
// to look back.
block_end = buffer_end - kAes128BlockSize * pattern_descriptor.skip;
}

View File

@@ -35,9 +35,9 @@ const int kDefaultNumJsonTokens = 128;
namespace wvcdm {
// Protobuf generated classes.
using video_widevine::WidevineCencHeader;
using video_widevine::WidevineCencHeader_Algorithm;
using video_widevine::WidevineCencHeader_Algorithm_AESCTR;
using video_widevine::WidevinePsshData;
using video_widevine::WidevinePsshData_Algorithm;
using video_widevine::WidevinePsshData_Algorithm_AESCTR;
InitializationData::InitializationData(const std::string& type,
const CdmInitData& data)
@@ -70,6 +70,22 @@ InitializationData::InitializationData(const std::string& type,
}
}
// Parse the pssh data and return the embedded key data if it exists.
std::vector<video_widevine::SubLicense>
InitializationData::ExtractEmbeddedKeys() const {
std::vector<video_widevine::SubLicense> keys;
WidevinePsshData cenc_header;
if (!is_cenc_ || !cenc_header.ParseFromString(data_) ||
cenc_header.sub_licenses().size() == 0)
return keys;
keys.reserve(cenc_header.sub_licenses().size());
for (int i = 0; i < cenc_header.sub_licenses().size(); ++i) {
keys.push_back(cenc_header.sub_licenses(i));
}
return keys;
}
// Parse a blob of multiple concatenated PSSH atoms to extract the first
// Widevine PSSH.
bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
@@ -339,8 +355,9 @@ bool InitializationData::ConstructWidevineInitData(
return false;
}
if (method != kHlsMethodAes128 && method != kHlsMethodSampleAes) {
LOGV("InitializationData::ConstructWidevineInitData: Invalid method"
" parameter");
LOGV(
"InitializationData::ConstructWidevineInitData: Invalid method"
" parameter");
return false;
}
@@ -466,10 +483,10 @@ bool InitializationData::ConstructWidevineInitData(
}
// Now format as Widevine init data protobuf
WidevineCencHeader cenc_header;
WidevinePsshData cenc_header;
// TODO(rfrias): The algorithm is a deprecated field, but proto changes
// have not yet been pushed to production. Set until then.
cenc_header.set_algorithm(WidevineCencHeader_Algorithm_AESCTR);
cenc_header.set_algorithm(WidevinePsshData_Algorithm_AESCTR);
for (size_t i = 0; i < key_ids.size(); ++i) {
cenc_header.add_key_id(key_ids[i]);
}

View File

@@ -48,17 +48,41 @@ using video_widevine::ClientIdentification_NameValue;
using video_widevine::DrmDeviceCertificate;
using video_widevine::EncryptedClientIdentification;
using video_widevine::License;
using video_widevine::License_KeyContainer;
using video_widevine::LicenseError;
using video_widevine::LicenseIdentification;
using video_widevine::LicenseRequest;
using video_widevine::LicenseRequest_ContentIdentification;
using video_widevine::LicenseRequest_ContentIdentification_CencDeprecated;
using video_widevine::LicenseRequest_ContentIdentification_WebmDeprecated;
using video_widevine::LicenseRequest_ContentIdentification_ExistingLicense;
using video_widevine::LicenseRequest_ContentIdentification_WebmDeprecated;
using video_widevine::License_KeyContainer;
using video_widevine::SignedDrmDeviceCertificate;
using video_widevine::SignedMessage;
static std::vector<CryptoKey> ExtractSubSessionKeys(const License& license) {
std::vector<CryptoKey> key_array;
// Extract sub session key(s)
for (int i = 0; i < license.key_size(); ++i) {
CryptoKey key;
switch (license.key(i).type()) {
case License_KeyContainer::SUB_SESSION:
key.set_key_data(license.key(i).key());
key.set_key_data_iv(license.key(i).iv());
key.set_key_id(license.key(i).id());
key.set_track_label(license.key(i).track_label());
key_array.push_back(key);
break;
default:
// Ignore all but SUB_SESSION key types.
break;
}
}
return key_array;
}
static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
std::vector<CryptoKey> key_array;
@@ -87,6 +111,7 @@ static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
if (license.has_protection_scheme()) {
four_cc = license.protection_scheme();
}
key.set_track_label(license.key(i).track_label());
switch (four_cc) {
// b/30713238: Android N assumed that the "protection scheme" Four
// CC code, after being extracted from the protobuf, was host byte
@@ -115,11 +140,28 @@ static std::vector<CryptoKey> ExtractContentKeys(const License& license) {
}
break;
default:
// Ignore SIGNING key types as they are not content related
// Ignore SIGNING and SUB_SESSION key types as they are not content
// related.
break;
}
}
std::vector<CryptoKey> sub_session_keys = ExtractSubSessionKeys(license);
// Match the track label from the key arrays and add sub_license_key_id to
// the content key array.
LOGI("Received %d subsession keys", sub_session_keys.size());
if (!sub_session_keys.empty()) {
for (int i = 0; i < key_array.size(); ++i) {
if (key_array[i].track_label().empty()) continue;
for (int x = 0; x < sub_session_keys.size(); ++x) {
if (sub_session_keys[x].track_label() == key_array[i].track_label()) {
key_array[i].set_sub_session_key_id(sub_session_keys[x].key_id());
key_array[i].set_sub_session_key(sub_session_keys[x].key_data());
}
}
}
}
return key_array;
}
@@ -221,8 +263,8 @@ CdmResponseType CdmLicense::PrepareKeyRequest(
status = PrepareClientId(app_parameters, &license_request);
if (NO_ERROR != status) return status;
status = PrepareContentId(init_data, license_type, request_id,
&license_request);
status =
PrepareContentId(init_data, license_type, request_id, &license_request);
if (NO_ERROR != status) return status;
license_request.set_type(LicenseRequest::NEW);
@@ -237,6 +279,37 @@ CdmResponseType CdmLicense::PrepareKeyRequest(
}
license_request.set_key_control_nonce(nonce);
LOGD("PrepareKeyRequest: nonce=%u", nonce);
// Prepare the request for any embedded keys that may exist in the
// initialization data.
std::vector<video_widevine::SubLicense> embedded_key_data =
init_data.ExtractEmbeddedKeys();
for (int i = 0; i < embedded_key_data.size(); ++i) {
bool exists = false;
if (!crypto_session_->GenerateSubSessionNonce(
embedded_key_data[i].sub_session_key_id(), &exists, &nonce)) {
return LICENSE_REQUEST_NONCE_GENERATION_ERROR;
}
if (exists) {
SignedMessage signed_sub_license;
License_KeyContainer keyc;
// Parse the sub license for this track to extract the label.
if (!signed_sub_license.ParseFromString(embedded_key_data[i].key_msg()) ||
!keyc.ParseFromString(signed_sub_license.msg()) ||
keyc.track_label().empty()) {
return LICENSE_REQUEST_INVALID_SUBLICENSE;
}
LicenseRequest::SubSessionData* sub_session_data =
license_request.add_sub_session_data();
sub_session_data->set_sub_session_key_id(
embedded_key_data[i].sub_session_key_id());
sub_session_data->set_nonce(nonce);
sub_session_data->set_track_label(keyc.track_label());
}
}
license_request.set_protocol_version(video_widevine::VERSION_2_1);
// License request is complete. Serialize it.
@@ -438,7 +511,6 @@ CdmResponseType CdmLicense::HandleKeyResponse(
LOGE("CdmLicense::HandleKeyResponse: no session keys present");
return SESSION_KEYS_NOT_FOUND;
}
if (!crypto_session_->GenerateDerivedKeys(key_request_,
signed_response.session_key()))
return GENERATE_DERIVED_KEYS_ERROR;
@@ -492,7 +564,8 @@ CdmResponseType CdmLicense::HandleKeyResponse(
renew_with_client_id_ = license.policy().always_include_client_id();
}
CdmResponseType resp = crypto_session_->LoadKeys(
CdmResponseType resp = NO_ERROR;
resp = crypto_session_->LoadKeys(
signed_response.msg(), signed_response.signature(), mac_key_iv, mac_key,
key_array, provider_session_token_);
@@ -587,6 +660,45 @@ CdmResponseType CdmLicense::HandleKeyUpdateResponse(
}
}
CdmResponseType CdmLicense::HandleSubLicense(
const InitializationData& init_data) {
std::vector<video_widevine::SubLicense> subkeys =
init_data.ExtractEmbeddedKeys();
std::set<KeyId> loaded_keys;
// Build a license with the rotated keys.
License license;
for (int i = 0; i < subkeys.size(); ++i) {
SignedMessage sm;
if (!sm.ParseFromString(subkeys[i].key_msg())) {
return LICENSE_REQUEST_INVALID_SUBLICENSE;
}
License_KeyContainer keyc;
if (!keyc.ParseFromString(sm.msg())) {
return LICENSE_REQUEST_INVALID_SUBLICENSE;
}
std::vector<CryptoKey> keys;
keys.resize(1);
keys[0].set_key_id(keyc.id());
keys[0].set_key_data(keyc.key());
keys[0].set_key_data_iv(keyc.iv());
keys[0].set_key_control(keyc.key_control().key_control_block());
keys[0].set_key_control_iv(keyc.key_control().iv());
keys[0].set_track_label(keyc.track_label());
CdmResponseType result = crypto_session_->LoadKeys(
sm.msg(), sm.signature(), std::string(), std::string(), keys,
std::string());
if (result != KEY_ADDED) {
return result;
}
loaded_keys.insert(keyc.id());
*license.add_key() = keyc;
}
loaded_keys_.swap(loaded_keys);
policy_engine_->UpdateLicenseKeys(license);
return KEY_MESSAGE;
}
bool CdmLicense::RestoreOfflineLicense(
const CdmKeyMessage& license_request,
const CdmKeyResponse& license_response,
@@ -994,8 +1106,7 @@ CdmResponseType CdmLicense::PrepareContentId(
template <typename T>
bool CdmLicense::SetTypeAndId(CdmLicenseType license_type,
const std::string& request_id,
T* content_id) {
const std::string& request_id, T* content_id) {
switch (license_type) {
case kLicenseTypeOffline:
content_id->set_license_type(video_widevine::OFFLINE);

View File

@@ -101,6 +101,7 @@ message License {
CONTENT = 2;
KEY_CONTROL = 3;
OPERATOR_SESSION = 4;
SUB_SESSION = 5;
}
// The SecurityLevel enumeration allows the server to communicate the level
@@ -199,6 +200,9 @@ message License {
// supports anti rollback of the user table. Content provider can query the
// client capabilities to determine if the client support this feature.
optional bool anti_rollback_usage_table = 11 [default = false];
// Optional not limited to commonly known track types such as SD, HD.
// It can be some provider defined label to identify the track.
optional string track_label = 12;
}
optional LicenseIdentification id = 1;
@@ -266,6 +270,19 @@ message LicenseRequest {
//}
}
message SubSessionData {
// Required. The key ID for the corresponding SUB_SESSION_KEY. The
// value must match the sub_session_key_id field for a
// corresponding SubLicense message from the PSSH.
optional string sub_session_key_id = 1;
// Required. The nonce for the track.
optional uint32 nonce = 2;
// Required for initial license request used for each CONTENT key_container
// to know which nonce to use for building its key control block.
// Not needed for renewal license request.
optional string track_label = 3;
}
enum RequestType {
NEW = 1;
RENEWAL = 2;
@@ -289,6 +306,9 @@ message LicenseRequest {
optional uint32 key_control_nonce = 7;
// Encrypted ClientIdentification message, used for privacy purposes.
optional EncryptedClientIdentification encrypted_client_id = 8;
// Optional sub session context information. Required for using
// SubLicenses from the PSSH.
repeated SubSessionData sub_session_data = 9;
}
message LicenseError {
@@ -302,6 +322,7 @@ message LicenseError {
// or similar circumstances.
SERVICE_UNAVAILABLE = 3;
}
optional Error error_code = 1;
}
@@ -406,7 +427,7 @@ message ProvisioningOptions {
optional CertificateType certificate_type = 1 [default = WIDEVINE_DRM];
// Contains the application-specific name used to identify the certificate
// authority for signing the generated certificate. This is required iff the
// authority for signing the generated certificate. This is required if the
// certificate type is X509.
optional string certificate_authority = 2;
}
@@ -665,19 +686,33 @@ message ProvisionedDeviceInfo {
}
// ----------------------------------------------------------------------------
// widevine_header.proto
// widevine_pssh.proto
// ----------------------------------------------------------------------------
// Description:
// Public protocol buffer definitions for Widevine Cenc Header
// protocol.
message WidevineCencHeader {
// Each SubLicense message represents a single content key. These keys can be
// added to Widevine CENC initialization data to support both content grouping
// and key rotation.
message SubLicense {
// Required. The key_id of a SUB_SESSION_KEY received in the master license.
// SUB_SESSION_KEY is defined in the Widevine License Protocol.
optional string sub_session_key_id = 1;
// Required. The key_msg contains the bytes of a serialized SignedMessage
// proto. Internally the message field will contain a serialized KeyContainer
// holding a single content key.
optional bytes key_msg = 2;
}
message WidevinePsshData {
enum Algorithm {
UNENCRYPTED = 0;
AESCTR = 1;
};
// Replaced with protection_scheme.
optional Algorithm algorithm = 1 [deprecated=true];
optional Algorithm algorithm = 1;
repeated bytes key_id = 2;
// Content provider name.
@@ -702,10 +737,22 @@ message WidevineCencHeader {
// serialized SignedMessage.
optional bytes grouped_license = 8;
// Protection scheme identifying the encryption algorithm. Represented as one
// of the following 4CC values: 'cenc' (AES-CTR), 'cbc1' (AES-CBC),
// 'cens' (AES-CTR subsample), 'cbcs' (AES-CBC subsample).
// Protection scheme identifying the encryption algorithm. The protection
// scheme is represented as a uint32 value. The uint32 contains 4 bytes each
// representing a single ascii character in one of the 4CC protection scheme
// values.
// 'cenc' (AES-CTR) protection_scheme = 0x63656E63,
// 'cbc1' (AES-CBC) protection_scheme = 0x63626331,
// 'cens' (AES-CTR subsample) protection_scheme = 0x63656E73,
// 'cbcs' (AES-CBC subsample) protection_scheme = 0x63626373.
optional uint32 protection_scheme = 9;
// Optional. For media using key rotation, this represents the duration
// of each crypto period in seconds.
optional uint32 crypto_period_seconds = 10;
// Required when using content keys that are embedded in content.
repeated SubLicense sub_licenses = 11;
}
// Signed device certificate definition.

View File

@@ -14,20 +14,17 @@ extern "C" OEMCrypto_ProvisioningMethod OEMCrypto_GetProvisioningMethod() {
}
extern "C" OEMCryptoResult OEMCrypto_GetOEMPublicCertificate(
OEMCrypto_SESSION session, uint8_t* public_cert,
size_t* public_cert_length) {
OEMCrypto_SESSION, uint8_t* /* public_cert */,
size_t* /* public_cert_length */) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}
extern "C" OEMCryptoResult OEMCrypto_RewrapDeviceRSAKey30(
OEMCrypto_SESSION session,
const uint32_t *nonce,
const uint8_t* encrypted_message_key,
size_t encrypted_message_key_length,
const uint8_t* enc_rsa_key,
size_t enc_rsa_key_length,
const uint8_t* enc_rsa_key_iv,
uint8_t* wrapped_rsa_key,
size_t* wrapped_rsa_key_length) {
OEMCrypto_SESSION, const uint32_t* /* nonce */,
const uint8_t* /* encrypted_message_key */,
size_t /* encrypted_message_key_length */,
const uint8_t* /* enc_rsa_key */, size_t /* enc_rsa_key_length */,
const uint8_t* /* enc_rsa_key_iv */, uint8_t* /* wrapped_rsa_key */,
size_t* /* wrapped_rsa_key_length */) {
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
}

View File

@@ -159,6 +159,14 @@ void PolicyEngine::SetLicense(const License& license) {
UpdateLicense(license);
}
void PolicyEngine::UpdateLicenseKeys(const video_widevine::License& license) {
// Use the current policy and set the new keys.
video_widevine::License loadable = license;
loadable.mutable_policy()->CopyFrom(policy_);
license_keys_->SetFromLicense(loadable);
NotifyKeysChange(kKeyStatusUsable);
}
void PolicyEngine::SetLicenseForRelease(const License& license) {
license_id_.Clear();
license_id_.CopyFrom(license.id());

View File

@@ -10,14 +10,28 @@
#include <iostream>
#include <vector>
#include <modp_b64.h>
#include <modp_b64w.h>
#include "log.h"
namespace wvcdm {
static bool CharToDigit(char ch, unsigned char* digit) {
static const char kBase64Codes[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// Gets the low |n| bits of |in|.
#define GET_LOW_BITS(in, n) ((in) & ((1 << (n)) - 1))
// Gets the given (zero-indexed) bits [a, b) of |in|.
#define GET_BITS(in, a, b) GET_LOW_BITS((in) >> (a), (b) - (a))
// Calculates a/b using round-up division (only works for positive numbers).
#define CEIL_DIVIDE(a, b) ((((a) - 1) / (b)) + 1)
int DecodeBase64Char(char c) {
const char* it = strchr(kBase64Codes, c);
if (it == NULL)
return -1;
return it - kBase64Codes;
}
bool DecodeHexChar(char ch, unsigned char* digit) {
if (ch >= '0' && ch <= '9') {
*digit = ch - '0';
} else {
@@ -43,8 +57,8 @@ std::vector<uint8_t> a2b_hex(const std::string& byte) {
for (unsigned int i = 0; i < count / 2; ++i) {
unsigned char msb = 0; // most significant 4 bits
unsigned char lsb = 0; // least significant 4 bits
if (!CharToDigit(byte[i * 2], &msb) ||
!CharToDigit(byte[i * 2 + 1], &lsb)) {
if (!DecodeHexChar(byte[i * 2], &msb) ||
!DecodeHexChar(byte[i * 2 + 1], &lsb)) {
LOGE("Invalid hex value %c%c at index %d", byte[i * 2], byte[i * 2 + 1],
i);
return array;
@@ -80,23 +94,50 @@ std::string b2a_hex(const std::string& byte) {
}
// Encode for standard base64 encoding (RFC4648).
// https://en.wikipedia.org/wiki/Base64
// Text | M | a | n |
// ASCI | 77 (0x4d) | 97 (0x61) | 110 (0x6e) |
// Bits | 0 1 0 0 1 1 0 1 0 1 1 0 0 0 0 1 0 1 1 0 1 1 1 0 |
// Index | 19 | 22 | 5 | 46 |
// Base64 | T | W | F | u |
// | <----------------- 24-bits -----------------> |
std::string Base64Encode(const std::vector<uint8_t>& bin_input) {
if (bin_input.empty()) {
return std::string();
}
int in_size = bin_input.size();
std::string b64_output(modp_b64_encode_len(in_size), 0);
// |temp| stores a 24-bit block that is treated as an array where insertions
// occur from high to low.
uint32_t temp = 0;
size_t out_index = 0;
const size_t out_size = CEIL_DIVIDE(bin_input.size(), 3) * 4;
std::string result(out_size, '\0');
for (size_t i = 0; i < bin_input.size(); i++) {
// "insert" 8-bits of data
temp |= (bin_input[i] << ((2 - (i % 3)) * 8));
int out_size = modp_b64_encode(
&b64_output[0], reinterpret_cast<const char*>(&bin_input[0]), in_size);
if (out_size == -1) {
LOGE("Base64Encode failed");
return std::string();
if (i % 3 == 2) {
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
result[out_index++] = kBase64Codes[GET_BITS(temp, 6, 12)];
result[out_index++] = kBase64Codes[GET_BITS(temp, 0, 6)];
temp = 0;
}
}
b64_output.resize(out_size);
return b64_output;
if (bin_input.size() % 3 == 1) {
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
result[out_index++] = '=';
result[out_index++] = '=';
} else if (bin_input.size() % 3 == 2) {
result[out_index++] = kBase64Codes[GET_BITS(temp, 18, 24)];
result[out_index++] = kBase64Codes[GET_BITS(temp, 12, 18)];
result[out_index++] = kBase64Codes[GET_BITS(temp, 6, 12)];
result[out_index++] = '=';
}
return result;
}
// Filename-friendly base64 encoding (RFC4648), commonly referred to
@@ -111,18 +152,14 @@ std::string Base64SafeEncode(const std::vector<uint8_t>& bin_input) {
return std::string();
}
int in_size = bin_input.size();
std::string b64_output(modp_b64w_encode_len(in_size), 0);
int out_size = modp_b64w_encode(
&b64_output[0], reinterpret_cast<const char*>(&bin_input[0]), in_size);
if (out_size == -1) {
LOGE("Base64SafeEncode failed");
return std::string();
std::string ret = Base64Encode(bin_input);
for (size_t i = 0; i < ret.size(); i++) {
if (ret[i] == '+')
ret[i] = '-';
else if (ret[i] == '/')
ret[i] = '_';
}
b64_output.resize(out_size);
return b64_output;
return ret;
}
std::string Base64SafeEncodeNoPad(const std::vector<uint8_t>& bin_input) {
@@ -138,17 +175,57 @@ std::vector<uint8_t> Base64Decode(const std::string& b64_input) {
return std::vector<uint8_t>();
}
int in_size = b64_input.size();
std::vector<uint8_t> bin_output(modp_b64_decode_len(in_size), 0);
int out_size = modp_b64_decode(reinterpret_cast<char*>(&bin_output[0]),
b64_input.data(), in_size);
if (out_size == -1) {
LOGE("Base64Decode failed");
return std::vector<uint8_t>(0);
const size_t out_size_max = CEIL_DIVIDE(b64_input.size() * 3, 4);
std::vector<uint8_t> result(out_size_max, '\0');
// |temp| stores 24-bits of data that is treated as an array where insertions
// occur from high to low.
uint32_t temp = 0;
size_t out_index = 0;
size_t i;
for (i = 0; i < b64_input.size(); i++) {
if (b64_input[i] == '=') {
// Verify an '=' only appears at the end. We want i to remain at the
// first '=', so we need an inner loop.
for (size_t j = i; j < b64_input.size(); j++) {
if (b64_input[j] != '=') {
LOGE("base64Decode failed");
return std::vector<uint8_t>();
}
}
break;
}
const int decoded = DecodeBase64Char(b64_input[i]);
if (decoded < 0) {
LOGE("base64Decode failed");
return std::vector<uint8_t>();
}
// "insert" 6-bits of data
temp |= (decoded << ((3 - (i % 4)) * 6));
if (i % 4 == 3) {
result[out_index++] = GET_BITS(temp, 16, 24);
result[out_index++] = GET_BITS(temp, 8, 16);
result[out_index++] = GET_BITS(temp, 0, 8);
temp = 0;
}
}
bin_output.resize(out_size);
return bin_output;
switch (i % 4) {
case 1:
LOGE("base64Decode failed");
return std::vector<uint8_t>();
case 2:
result[out_index++] = GET_BITS(temp, 16, 24);
break;
case 3:
result[out_index++] = GET_BITS(temp, 16, 24);
result[out_index++] = GET_BITS(temp, 8, 16);
break;
}
result.resize(out_index);
return result;
}
// Decode for Filename-friendly base64 encoding (RFC4648), commonly referred
@@ -158,22 +235,16 @@ std::vector<uint8_t> Base64SafeDecode(const std::string& b64_input) {
return std::vector<uint8_t>();
}
std::string b64_padded(b64_input);
while (b64_padded.size() % 4 != 0) {
b64_padded = b64_padded + "=";
// Make a copy so we can modify it to replace the web-safe special characters
// with the normal ones.
std::string input_copy = b64_input;
for (size_t i = 0; i < input_copy.size(); i++) {
if (input_copy[i] == '-')
input_copy[i] = '+';
else if (input_copy[i] == '_')
input_copy[i] = '/';
}
int in_size = b64_padded.size();
std::vector<uint8_t> bin_output(modp_b64w_decode_len(in_size), 0);
int out_size = modp_b64w_decode(reinterpret_cast<char*>(&bin_output[0]),
b64_padded.data(), in_size);
if (out_size == -1) {
LOGE("Base64SafeDecode failed");
return std::vector<uint8_t>(0);
}
bin_output.resize(out_size);
return bin_output;
return Base64Decode(input_copy);
}
std::string HexEncode(const uint8_t* in_buffer, unsigned int size) {

View File

@@ -53,6 +53,9 @@ const std::pair<const std::string *, const std::string *> kBase64TestVectors[] =
make_pair(&kTwoBytesOverData, &kTwoBytesOverB64Data),
make_pair(&kTestData, &kB64TestData)};
const std::string kBase64ErrorVectors[] = {"Foo$sa", "Foo\x99\x23\xfa\02",
"Foo==Foo", "FooBa"};
std::string ConvertToBase64WebSafe(const std::string &std_base64_string) {
std::string str(std_base64_string);
for (size_t i = 0; i < str.size(); ++i) {
@@ -89,9 +92,19 @@ TEST_P(Base64EncodeDecodeTest, WebSafeEncodeDecodeTest) {
EXPECT_STREQ(encoded_string.data(), b64_string.data());
}
class Base64ErrorDecodeTest : public ::testing::TestWithParam<std::string> {};
TEST_P(Base64ErrorDecodeTest, EncoderErrors) {
std::vector<uint8_t> result = Base64Decode(GetParam());
EXPECT_EQ(0u, result.size());
}
INSTANTIATE_TEST_CASE_P(ExecutesBase64Test, Base64EncodeDecodeTest,
::testing::ValuesIn(kBase64TestVectors));
INSTANTIATE_TEST_CASE_P(ExecutesBase64Test, Base64ErrorDecodeTest,
::testing::ValuesIn(kBase64ErrorVectors));
class HtoNLL64Test : public ::testing::Test {};
TEST_F(HtoNLL64Test, PositiveNumber) {

View File

@@ -17,7 +17,7 @@
namespace wvcdm {
// Protobuf generated classes.
using video_widevine::WidevineCencHeader;
using video_widevine::WidevinePsshData;
namespace {
@@ -164,6 +164,19 @@ const std::string kZeroSizedPsshBox = a2bs_hex(
// data:
"08011a0d7769646576696e655f74657374220f73747265616d696e675f636c697031");
const std::string kSubLicensePsshBox = a2bs_hex(
// Widevine PSSH box
"0000009f" // atom size (whole buffer)
"70737368" // atom type="pssh"
"00000000" // v0, flags=0
"edef8ba979d64acea3c827dcd51d21ed" // system id (Widevine)
"0000007f" // data size
// data:
"0801120d746573745f6b65795f69645f30120d746573745f6b65795f69645f31220f"
"746573745f636f6e74656e745f69645a250a147375625f73657373696f6e5f6b6579"
"5f69645f30120d7375625f6c6963656e73655f305a250a147375625f73657373696f"
"6e5f6b65795f69645f31120d7375625f6c6963656e73655f31");
// HLS test attribute key and values
const std::string kHlsIvHexValue = "6DF49213A781E338628D0E9C812D328E";
const std::string kHlsIvValue = "0x" + kHlsIvHexValue;
@@ -353,7 +366,9 @@ struct HlsInitDataVariant {
HlsInitDataVariant(CdmHlsMethod method, const std::string& provider,
const std::string& content_id, const std::string& key_id,
bool success)
: method_(method), provider_(provider), content_id_(content_id),
: method_(method),
provider_(provider),
content_id_(content_id),
success_(success) {
if (key_id.size() > 0) key_ids_.push_back(key_id);
}
@@ -410,13 +425,31 @@ TEST_P(InitializationDataTest, Parse) {
EXPECT_FALSE(init_data.IsEmpty());
}
INSTANTIATE_TEST_CASE_P(ParsePssh, InitializationDataTest,
::testing::Values(kWidevinePssh, kWidevinePsshFirst,
kWidevinePsshAfterV0Pssh,
kWidevinePsshAfterNonZeroFlags,
kWidevinePsshAfterV1Pssh,
kWidevineV1Pssh, kOtherBoxFirst,
kZeroSizedPsshBox));
INSTANTIATE_TEST_CASE_P(
ParsePssh, InitializationDataTest,
::testing::Values(kWidevinePssh, kWidevinePsshFirst,
kWidevinePsshAfterV0Pssh, kWidevinePsshAfterNonZeroFlags,
kWidevinePsshAfterV1Pssh, kWidevineV1Pssh, kOtherBoxFirst,
kZeroSizedPsshBox, kSubLicensePsshBox));
TEST_F(InitializationDataTest, ExtractSubLicense) {
InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, kSubLicensePsshBox);
EXPECT_FALSE(init_data.IsEmpty());
std::vector<video_widevine::SubLicense> keys =
init_data.ExtractEmbeddedKeys();
ASSERT_EQ(keys.size(), 2UL);
ASSERT_EQ(keys[0].sub_session_key_id(), "sub_session_key_id_0");
ASSERT_EQ(keys[1].sub_session_key_id(), "sub_session_key_id_1");
ASSERT_EQ(keys[0].key_msg(), "sub_license_0");
ASSERT_EQ(keys[1].key_msg(), "sub_license_1");
}
TEST_F(InitializationDataTest, ExtractEmptySubLicense) {
InitializationData init_data(ISO_BMFF_VIDEO_MIME_TYPE, kWidevinePssh);
EXPECT_FALSE(init_data.IsEmpty());
std::vector<video_widevine::SubLicense> keys =
init_data.ExtractEmbeddedKeys();
ASSERT_TRUE(keys.empty());
}
TEST_P(HlsKeyFormatVersionsExtractionTest, ExtractKeyFormatVersions) {
std::vector<std::string> versions = GetParam();
@@ -598,9 +631,9 @@ TEST_P(HlsConstructionTest, InitData) {
EXPECT_EQ(param.success_, InitializationData::ConstructWidevineInitData(
param.method_, uri, &value));
if (param.success_) {
WidevineCencHeader cenc_header;
WidevinePsshData cenc_header;
EXPECT_TRUE(cenc_header.ParseFromString(value));
EXPECT_EQ(video_widevine::WidevineCencHeader_Algorithm_AESCTR,
EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR,
cenc_header.algorithm());
for (size_t i = 0; i < param.key_ids_.size(); ++i) {
bool key_id_found = false;
@@ -620,9 +653,14 @@ TEST_P(HlsConstructionTest, InitData) {
cenc_header.content_id());
uint32_t protection_scheme = 0;
switch (param.method_) {
case kHlsMethodAes128: protection_scheme = kFourCcCbc1; break;
case kHlsMethodSampleAes: protection_scheme = kFourCcCbcs; break;
default: break;
case kHlsMethodAes128:
protection_scheme = kFourCcCbc1;
break;
case kHlsMethodSampleAes:
protection_scheme = kFourCcCbcs;
break;
default:
break;
}
EXPECT_EQ(protection_scheme, cenc_header.protection_scheme());
}
@@ -633,8 +671,8 @@ INSTANTIATE_TEST_CASE_P(
::testing::Values(
HlsInitDataVariant(kHlsMethodAes128, "", kHlsTestContentId,
kHlsTestKeyId1, false),
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
"", kHlsTestKeyId1, false),
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider, "",
kHlsTestKeyId1, false),
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
kHlsTestContentId, "", false),
HlsInitDataVariant(kHlsMethodAes128, kHlsTestProvider,
@@ -699,9 +737,9 @@ TEST_P(HlsParseTest, Parse) {
EXPECT_EQ(kHlsMethodSampleAes, init_data.hls_method());
}
WidevineCencHeader cenc_header;
WidevinePsshData cenc_header;
EXPECT_TRUE(cenc_header.ParseFromString(init_data.data()));
EXPECT_EQ(video_widevine::WidevineCencHeader_Algorithm_AESCTR,
EXPECT_EQ(video_widevine::WidevinePsshData_Algorithm_AESCTR,
cenc_header.algorithm());
if (param.key_.compare(kJsonProvider) == 0) {
EXPECT_EQ(param.value_, cenc_header.provider());

View File

@@ -79,6 +79,32 @@ const std::string kLicenseRequestSignature = a2bs_hex(
"9AE18B91516E0CDD0B581590DDDEA2A2527E2C9ABA273629B586A9D22D451A827E332CFC3E"
"9BEDB6CF3D8713F9E11675DF1F5DB9038DBBECAB9D1683F8722CAF6E18EC8C04AEE5");
const std::string kSubLicensePssh = a2bs_hex(
"000002317073736800000000edef8ba979d64acea3c827dcd51d21ed000002010801120d54"
"6573744b6579415544494f30120a546573744b6579534430120a546573744b65794844301a"
"0048e3dc959b0650025a9b010a101f10e4700b1a5b731c545fe2405cea1b12860112620a0d"
"546573744b6579415544494f3012102f4b661d1064b5ea82efcd3ef850f45b1a10a02c5cea"
"5182383c064c06abbc79bfa8200242240a1023acc9a0ef2bca66af2a1307cc9edeb21210e1"
"f1d352b4c6b1aad1fd78423db256946205415544494f1a20aaf4537f09332c502a88f43a18"
"a3e21ec28bbde675c5d87054fbca06f98e98015a95010a10c034cf6ae181b8d07f2e79142d"
"792bb3128001125c0a0a546573744b657953443012106d37a0a201afdef8a494f89a4b0772"
"4a1a10457db86b73bf87177a5cc61c0d04b690200242240a1001fe432d2a8afb7054ae76a3"
"9c2727e612108761b7e0ba354ee4132117a9de12abd3620253441a20e3f37529cb795b35a0"
"d186e4ce7187f08dda5f1df136ddb92eb0a65a899635005a95010a1021cdec9b2105c6b643"
"e71f68e5302c85128001125c0a0a546573744b6579484430121012a3e3afe1e23be2c3fc55"
"fddad877451a108f31ff0865f4d4fb41d96414297f7728200242240a1061e3cbca755b36c8"
"e7d6dda03af20e4f1210a2fa2fc5d10c9c84ddc5511446ce77e9620248441a20a945699aef"
"49355b0214b636edb7670bbe350c58b69cd52f207953b380a52df2");
const std::string kSubSessionKeyID1 =
a2bs_hex("1f10e4700b1a5b731c545fe2405cea1b");
const std::string kSubSessionKeyID2 =
a2bs_hex("c034cf6ae181b8d07f2e79142d792bb3");
const std::string kSubSessionKeyID3 =
a2bs_hex("21cdec9b2105c6b643e71f68e5302c85");
class MockCryptoSession : public CryptoSession {
public:
MOCK_METHOD0(IsOpen, bool());
@@ -88,6 +114,9 @@ class MockCryptoSession : public CryptoSession {
MOCK_METHOD1(GetApiVersion, bool(uint32_t*));
MOCK_METHOD1(GenerateNonce, bool(uint32_t*));
MOCK_METHOD3(PrepareRequest, bool(const std::string&, bool, std::string*));
MOCK_METHOD3(GenerateSubSessionNonce,
bool(const std::string& sub_session_key_id, bool* exists,
uint32_t* nonce));
};
class MockPolicyEngine : public PolicyEngine {
@@ -126,11 +155,12 @@ using ::testing::SetArgPointee;
class CdmLicenseTest : public ::testing::Test {
protected:
CdmLicenseTest(const std::string& pssh = (kCencInitDataHdr + kCencPssh))
: pssh_(pssh) {}
virtual void SetUp() {
clock_ = new MockClock();
crypto_session_ = new MockCryptoSession();
init_data_ = new MockInitializationData(CENC_INIT_DATA_FORMAT,
kCencInitDataHdr + kCencPssh);
crypto_session_ = new testing::StrictMock<MockCryptoSession>();
init_data_ = new MockInitializationData(CENC_INIT_DATA_FORMAT, pssh_);
policy_engine_ = new MockPolicyEngine(crypto_session_);
}
@@ -142,7 +172,7 @@ class CdmLicenseTest : public ::testing::Test {
if (clock_) delete clock_;
}
void CreateCdmLicense() {
virtual void CreateCdmLicense() {
cdm_license_ = new CdmLicense(kCdmSessionId, clock_);
clock_ = NULL;
}
@@ -153,6 +183,12 @@ class CdmLicenseTest : public ::testing::Test {
MockInitializationData* init_data_;
MockPolicyEngine* policy_engine_;
ServiceCertificate service_cert_;
std::string pssh_;
};
class SubLicenseTest : public CdmLicenseTest {
protected:
SubLicenseTest() : CdmLicenseTest(kSubLicensePssh) {}
};
TEST_F(CdmLicenseTest, InitSuccess) {
@@ -303,4 +339,88 @@ TEST_F(CdmLicenseTest, PrepareKeyRequestValidation) {
EXPECT_EQ(kNonce, license_request.key_control_nonce());
}
TEST_F(SubLicenseTest, VerifySubSessionData) {
bool usage_information_support = true;
CryptoSession::HdcpCapability current_hdcp_version = HDCP_NO_DIGITAL_OUTPUT;
CryptoSession::HdcpCapability max_hdcp_version = HDCP_V2_1;
uint32_t crypto_session_api_version = 9;
EXPECT_CALL(*crypto_session_, IsOpen()).WillOnce(Return(true));
EXPECT_CALL(*crypto_session_, GenerateRequestId(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kCryptoRequestId), Return(true)));
EXPECT_CALL(*crypto_session_, UsageInformationSupport(NotNull()))
.WillOnce(
DoAll(SetArgPointee<0>(usage_information_support), Return(true)));
EXPECT_CALL(*crypto_session_, GetHdcpCapabilities(NotNull(), NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(current_hdcp_version),
SetArgPointee<1>(max_hdcp_version), Return(true)));
EXPECT_CALL(*crypto_session_, GetApiVersion(NotNull()))
.WillOnce(
DoAll(SetArgPointee<0>(crypto_session_api_version), Return(true)));
EXPECT_CALL(*clock_, GetCurrentTime()).WillOnce(Return(kLicenseStartTime));
EXPECT_CALL(*crypto_session_, GenerateNonce(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(kNonce), Return(true)));
EXPECT_CALL(*crypto_session_, PrepareRequest(_, Eq(false), NotNull()))
.WillOnce(
DoAll(SetArgPointee<2>(kLicenseRequestSignature), Return(true)));
// SubLicense session data calls.
// TODO(jfore): These calls are being invoked twice each. This should not
// present a functional problem, but we should investigate why.
EXPECT_CALL(*crypto_session_,
GenerateSubSessionNonce(kSubSessionKeyID1, NotNull(), NotNull()))
.WillRepeatedly(
DoAll(SetArgPointee<1>(true), SetArgPointee<2>(0), Return(true)));
EXPECT_CALL(*crypto_session_,
GenerateSubSessionNonce(kSubSessionKeyID2, NotNull(), NotNull()))
.WillRepeatedly(
DoAll(SetArgPointee<1>(true), SetArgPointee<2>(1), Return(true)));
EXPECT_CALL(*crypto_session_,
GenerateSubSessionNonce(kSubSessionKeyID3, NotNull(), NotNull()))
.WillRepeatedly(
DoAll(SetArgPointee<1>(true), SetArgPointee<2>(2), Return(true)));
CreateCdmLicense();
// TODO(gmorgan) fix below - no default service certificate
//service_cert_.Init(kDefaultServiceCertificate);
EXPECT_TRUE(cdm_license_->Init(
&service_cert_, kToken, kClientTokenDrmCert, kEmptyString,
crypto_session_, policy_engine_));
CdmAppParameterMap app_parameters;
CdmKeyMessage signed_request;
Properties::set_use_certificates_as_identification(true);
std::string server_url;
EXPECT_EQ(cdm_license_->PrepareKeyRequest(*init_data_, kLicenseTypeStreaming,
app_parameters, &signed_request,
&server_url),
KEY_MESSAGE);
EXPECT_TRUE(!signed_request.empty());
SignedMessage signed_message;
EXPECT_TRUE(signed_message.ParseFromString(signed_request));
LicenseRequest license_request;
EXPECT_TRUE(license_request.ParseFromString(signed_message.msg()));
EXPECT_EQ(3, license_request.sub_session_data().size());
for (int i = 0; i < license_request.sub_session_data().size(); ++i) {
const video_widevine::LicenseRequest_SubSessionData& sl =
license_request.sub_session_data(i);
EXPECT_EQ(static_cast<uint32_t>(i), sl.nonce());
switch (i) {
case 0:
EXPECT_EQ(kSubSessionKeyID1, sl.sub_session_key_id());
EXPECT_EQ("AUDIO", sl.track_label());
break;
case 1:
EXPECT_EQ(kSubSessionKeyID2, sl.sub_session_key_id());
EXPECT_EQ("SD", sl.track_label());
break;
case 3:
EXPECT_EQ(kSubSessionKeyID3, sl.sub_session_key_id());
EXPECT_EQ("HD", sl.track_label());
break;
}
}
}
} // namespace wvcdm

View File

@@ -487,6 +487,27 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
break;
case PARSE_RESPONSE_ERROR_4: *os << "PARSE_RESPONSE_ERROR_4";
break;
case LICENSE_REQUEST_INVALID_SUBLICENSE:
*os << "LICENSE_REQUEST_INVALID_SUBLICENSE";
break;
case INVALID_SESSION_1:
*os << "INVALID_SESSION_1";
break;
case NO_DEVICE_KEY_1:
*os << "NO_DEVICE_KEY_1";
break;
case NO_CONTENT_KEY_2:
*os << "NO_CONTENT_KEY_2";
break;
case INSUFFICIENT_CRYPTO_RESOURCES_2:
*os << "INSUFFICIENT_CRYPTO_RESOURCES_2";
break;
case UNKNOWN_SELECT_KEY_ERROR_1:
*os << "UNKNOWN_SELECT_KEY_ERROR_1";
break;
case UNKNOWN_SELECT_KEY_ERROR_2:
*os << "UNKNOWN_SELECT_KEY_ERROR_2";
break;
default:
*os << "Unknown CdmResponseType";
break;