Source release v3.0.2
This commit is contained in:
@@ -26,7 +26,8 @@ class BufferReader {
|
||||
BufferReader(const uint8_t* buf, size_t size)
|
||||
: buf_(buf), size_(buf != NULL ? size : 0), pos_(0) {}
|
||||
|
||||
bool HasBytes(size_t count) { return (pos() + count <= size()); }
|
||||
bool HasBytes(size_t count) const { return pos_ + count <= size_; }
|
||||
bool IsEOF() const { return pos_ >= size_; }
|
||||
|
||||
// Read a value from the stream, performing endian correction,
|
||||
// and advance the stream pointer.
|
||||
|
||||
@@ -100,7 +100,8 @@ class CdmEngine {
|
||||
|
||||
// Query system information
|
||||
virtual CdmResponseType QueryStatus(SecurityLevel security_level,
|
||||
CdmQueryMap* info);
|
||||
const std::string& key,
|
||||
std::string* value);
|
||||
|
||||
// Query session information
|
||||
virtual CdmResponseType QuerySessionStatus(const CdmSessionId& session_id,
|
||||
|
||||
@@ -136,6 +136,7 @@ class DeviceFiles {
|
||||
FRIEND_TEST(WvCdmRequestLicenseTest, UnprovisionTest);
|
||||
FRIEND_TEST(WvCdmRequestLicenseTest, ForceL3Test);
|
||||
FRIEND_TEST(WvCdmRequestLicenseTest, UsageInfoRetryTest);
|
||||
FRIEND_TEST(WvCdmRequestLicenseTest, UsageReleaseAllTest);
|
||||
FRIEND_TEST(WvCdmUsageInfoTest, UsageInfo);
|
||||
FRIEND_TEST(WvCdmUsageTest, WithClientId);
|
||||
FRIEND_TEST(WvCdmExtendedDurationTest, UsageOverflowTest);
|
||||
|
||||
@@ -85,6 +85,8 @@ class PolicyEngine {
|
||||
|
||||
bool IsLicenseOrPlaybackDurationExpired(int64_t current_time);
|
||||
|
||||
bool CanRenew() { return policy_.can_renew(); }
|
||||
|
||||
private:
|
||||
friend class PolicyEngineTest;
|
||||
|
||||
|
||||
@@ -203,8 +203,10 @@ enum CdmResponseType {
|
||||
LICENSE_REQUEST_NONCE_GENERATION_ERROR,
|
||||
LICENSE_REQUEST_SIGNING_ERROR,
|
||||
EMPTY_LICENSE_REQUEST,
|
||||
EMPTY_PROVISIONING_CERTIFICATE_2,
|
||||
SECURE_BUFFER_REQUIRED,
|
||||
DUPLICATE_SESSION_ID_SPECIFIED,
|
||||
LICENSE_RENEWAL_PROHIBITED,
|
||||
EMPTY_PROVISIONING_CERTIFICATE_2,
|
||||
};
|
||||
|
||||
enum CdmKeyStatus {
|
||||
|
||||
@@ -405,91 +405,115 @@ CdmResponseType CdmEngine::RenewKey(const CdmSessionId& session_id,
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::QueryStatus(SecurityLevel security_level,
|
||||
CdmQueryMap* key_info) {
|
||||
const std::string& key,
|
||||
std::string* value) {
|
||||
LOGI("CdmEngine::QueryStatus");
|
||||
CryptoSession crypto_session;
|
||||
if (security_level == kLevel3) {
|
||||
CdmResponseType status = crypto_session.Open(kLevel3);
|
||||
if (NO_ERROR != status) return INVALID_QUERY_STATUS;
|
||||
}
|
||||
switch (crypto_session.GetSecurityLevel()) {
|
||||
case kSecurityLevelL1:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L1;
|
||||
break;
|
||||
case kSecurityLevelL2:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L2;
|
||||
break;
|
||||
case kSecurityLevelL3:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] = QUERY_VALUE_SECURITY_LEVEL_L3;
|
||||
break;
|
||||
case kSecurityLevelUninitialized:
|
||||
case kSecurityLevelUnknown:
|
||||
(*key_info)[QUERY_KEY_SECURITY_LEVEL] =
|
||||
QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
|
||||
break;
|
||||
default:
|
||||
return INVALID_QUERY_KEY;
|
||||
}
|
||||
|
||||
std::string deviceId;
|
||||
bool success = crypto_session.GetDeviceUniqueId(&deviceId);
|
||||
if (success) {
|
||||
(*key_info)[QUERY_KEY_DEVICE_ID] = deviceId;
|
||||
}
|
||||
if (key == QUERY_KEY_SECURITY_LEVEL) {
|
||||
CdmSecurityLevel security_level = crypto_session.GetSecurityLevel();
|
||||
switch (security_level) {
|
||||
case kSecurityLevelL1:
|
||||
*value = QUERY_VALUE_SECURITY_LEVEL_L1;
|
||||
break;
|
||||
case kSecurityLevelL2:
|
||||
*value = QUERY_VALUE_SECURITY_LEVEL_L2;
|
||||
break;
|
||||
case kSecurityLevelL3:
|
||||
*value = QUERY_VALUE_SECURITY_LEVEL_L3;
|
||||
break;
|
||||
case kSecurityLevelUninitialized:
|
||||
case kSecurityLevelUnknown:
|
||||
*value = QUERY_VALUE_SECURITY_LEVEL_UNKNOWN;
|
||||
break;
|
||||
default:
|
||||
LOGW("CdmEngine::QueryStatus: Unknown security level: %d",
|
||||
security_level);
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
} else if (key == QUERY_KEY_DEVICE_ID) {
|
||||
std::string deviceId;
|
||||
if (!crypto_session.GetDeviceUniqueId(&deviceId)) {
|
||||
LOGW("CdmEngine::QueryStatus: GetDeviceUniqueId failed");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
*value = deviceId;
|
||||
} else if (key == QUERY_KEY_SYSTEM_ID) {
|
||||
uint32_t system_id;
|
||||
if (!crypto_session.GetSystemId(&system_id)) {
|
||||
LOGW("CdmEngine::QueryStatus: GetSystemId failed");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
uint32_t system_id;
|
||||
success = crypto_session.GetSystemId(&system_id);
|
||||
if (success) {
|
||||
std::ostringstream system_id_stream;
|
||||
system_id_stream << system_id;
|
||||
(*key_info)[QUERY_KEY_SYSTEM_ID] = system_id_stream.str();
|
||||
}
|
||||
*value = system_id_stream.str();
|
||||
} else if (key == QUERY_KEY_PROVISIONING_ID) {
|
||||
std::string provisioning_id;
|
||||
if (!crypto_session.GetProvisioningId(&provisioning_id)) {
|
||||
LOGW("CdmEngine::QueryStatus: GetProvisioningId failed");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
std::string provisioning_id;
|
||||
success = crypto_session.GetProvisioningId(&provisioning_id);
|
||||
if (success) {
|
||||
(*key_info)[QUERY_KEY_PROVISIONING_ID] = provisioning_id;
|
||||
}
|
||||
*value = provisioning_id;
|
||||
} else if (key == QUERY_KEY_CURRENT_HDCP_LEVEL ||
|
||||
key == QUERY_KEY_MAX_HDCP_LEVEL) {
|
||||
CryptoSession::HdcpCapability current_hdcp;
|
||||
CryptoSession::HdcpCapability max_hdcp;
|
||||
if (!crypto_session.GetHdcpCapabilities(¤t_hdcp, &max_hdcp)) {
|
||||
LOGW("CdmEngine::QueryStatus: GetHdcpCapabilities failed");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
CryptoSession::HdcpCapability current_hdcp;
|
||||
CryptoSession::HdcpCapability max_hdcp;
|
||||
success = crypto_session.GetHdcpCapabilities(¤t_hdcp, &max_hdcp);
|
||||
if (success) {
|
||||
(*key_info)[QUERY_KEY_CURRENT_HDCP_LEVEL] = MapHdcpVersion(current_hdcp);
|
||||
(*key_info)[QUERY_KEY_MAX_HDCP_LEVEL] = MapHdcpVersion(max_hdcp);
|
||||
}
|
||||
*value = MapHdcpVersion(key == QUERY_KEY_CURRENT_HDCP_LEVEL ? current_hdcp
|
||||
: max_hdcp);
|
||||
} else if (key == QUERY_KEY_USAGE_SUPPORT) {
|
||||
bool supports_usage_reporting;
|
||||
if (!crypto_session.UsageInformationSupport(&supports_usage_reporting)) {
|
||||
LOGW("CdmEngine::QueryStatus: UsageInformationSupport failed");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
bool supports_usage_reporting;
|
||||
success = crypto_session.UsageInformationSupport(&supports_usage_reporting);
|
||||
if (success) {
|
||||
(*key_info)[QUERY_KEY_USAGE_SUPPORT] =
|
||||
supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
||||
}
|
||||
*value = supports_usage_reporting ? QUERY_VALUE_TRUE : QUERY_VALUE_FALSE;
|
||||
} else if (key == QUERY_KEY_NUMBER_OF_OPEN_SESSIONS) {
|
||||
size_t number_of_open_sessions;
|
||||
if (!crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions)) {
|
||||
LOGW("CdmEngine::QueryStatus: GetNumberOfOpenSessions failed");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
size_t number_of_open_sessions;
|
||||
success = crypto_session.GetNumberOfOpenSessions(&number_of_open_sessions);
|
||||
if (success) {
|
||||
std::ostringstream open_sessions_stream;
|
||||
open_sessions_stream << number_of_open_sessions;
|
||||
(*key_info)[QUERY_KEY_NUMBER_OF_OPEN_SESSIONS] =
|
||||
open_sessions_stream.str();
|
||||
}
|
||||
*value = open_sessions_stream.str();
|
||||
} else if (key == QUERY_KEY_MAX_NUMBER_OF_SESSIONS) {
|
||||
size_t maximum_number_of_sessions;
|
||||
if (!crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions)) {
|
||||
LOGW("CdmEngine::QueryStatus: GetMaxNumberOfOpenSessions failed");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
size_t maximum_number_of_sessions;
|
||||
success = crypto_session.GetMaxNumberOfSessions(&maximum_number_of_sessions);
|
||||
if (success) {
|
||||
std::ostringstream max_sessions_stream;
|
||||
max_sessions_stream << maximum_number_of_sessions;
|
||||
(*key_info)[QUERY_KEY_MAX_NUMBER_OF_SESSIONS] =
|
||||
max_sessions_stream.str();
|
||||
}
|
||||
*value = max_sessions_stream.str();
|
||||
} else if (key == QUERY_KEY_OEMCRYPTO_API_VERSION) {
|
||||
uint32_t api_version;
|
||||
if (!crypto_session.GetApiVersion(&api_version)) {
|
||||
LOGW("CdmEngine::QueryStatus: GetApiVersion failed");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
uint32_t api_version;
|
||||
success = crypto_session.GetApiVersion(&api_version);
|
||||
if (success) {
|
||||
std::ostringstream api_version_stream;
|
||||
api_version_stream << api_version;
|
||||
(*key_info)[QUERY_KEY_OEMCRYPTO_API_VERSION] = api_version_stream.str();
|
||||
*value = api_version_stream.str();
|
||||
} else {
|
||||
LOGW("CdmEngine::QueryStatus: Unknown status requested, key = %s",
|
||||
key.c_str());
|
||||
return INVALID_QUERY_KEY;
|
||||
}
|
||||
|
||||
return NO_ERROR;
|
||||
@@ -844,6 +868,11 @@ CdmResponseType CdmEngine::GetUsageInfo(const std::string& app_id,
|
||||
}
|
||||
|
||||
CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
|
||||
if (NULL == usage_property_set_.get()) {
|
||||
usage_property_set_.reset(new UsagePropertySet());
|
||||
}
|
||||
usage_property_set_->set_app_id(app_id);
|
||||
|
||||
CdmResponseType status = NO_ERROR;
|
||||
for (int j = kSecurityLevelL1; j < kSecurityLevelUnknown; ++j) {
|
||||
DeviceFiles handle;
|
||||
@@ -854,6 +883,14 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
|
||||
"stops", j);
|
||||
status = RELEASE_ALL_USAGE_INFO_ERROR_1;
|
||||
} else {
|
||||
SecurityLevel security_level =
|
||||
static_cast<CdmSecurityLevel>(j) == kSecurityLevelL3
|
||||
? kLevel3
|
||||
: kLevelDefault;
|
||||
usage_property_set_->set_security_level(security_level);
|
||||
usage_session_.reset(
|
||||
new CdmSession(usage_property_set_.get(),
|
||||
EMPTY_ORIGIN, NULL, NULL));
|
||||
CdmResponseType status2 = usage_session_->
|
||||
DeleteMultipleUsageInformation(provider_session_tokens);
|
||||
if (status2 != NO_ERROR) {
|
||||
@@ -866,6 +903,7 @@ CdmResponseType CdmEngine::ReleaseAllUsageInfo(const std::string& app_id) {
|
||||
status = RELEASE_ALL_USAGE_INFO_ERROR_2;
|
||||
}
|
||||
}
|
||||
usage_session_.reset(NULL);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ CdmSession::CdmSession(CdmClientPropertySet* cdm_client_property_set,
|
||||
key_set_id_ = *forced_session_id;
|
||||
} else {
|
||||
bool ok = GenerateKeySetId(&key_set_id_);
|
||||
(void)ok; // ok is now used when assertions are turned off.
|
||||
assert(ok);
|
||||
}
|
||||
session_id_ = key_set_id_;
|
||||
|
||||
@@ -641,6 +641,11 @@ CdmResponseType CryptoSession::Decrypt(const CdmDecryptionParameters& params) {
|
||||
buffer_descriptor.type =
|
||||
params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear;
|
||||
|
||||
if (params.is_secure &&
|
||||
buffer_descriptor.type == OEMCrypto_BufferType_Clear) {
|
||||
return SECURE_BUFFER_REQUIRED;
|
||||
}
|
||||
|
||||
switch (buffer_descriptor.type) {
|
||||
case OEMCrypto_BufferType_Clear:
|
||||
buffer_descriptor.buffer.clear.address =
|
||||
|
||||
@@ -55,7 +55,7 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
// (optional, if version == 1) K * 16 byte key ID.
|
||||
// 4 byte size of PSSH data, exclusive. (N)
|
||||
// N byte PSSH data.
|
||||
while (1) {
|
||||
while (!reader.IsEOF()) {
|
||||
size_t start_pos = reader.pos();
|
||||
|
||||
// atom size, used for skipping.
|
||||
@@ -128,6 +128,7 @@ bool InitializationData::ExtractWidevinePssh(const CdmInitData& init_data,
|
||||
"the atom.");
|
||||
return false;
|
||||
}
|
||||
LOGV("CdmEngine::ExtractWidevinePssh: Skipping non-Widevine PSSH.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -327,6 +327,11 @@ CdmResponseType CdmLicense::PrepareKeyUpdateRequest(
|
||||
return INVALID_PARAMETERS_LIC_2;
|
||||
}
|
||||
|
||||
if (is_renewal && !policy_engine_->CanRenew()) {
|
||||
LOGE("CdmLicense::PrepareKeyUpdateRequest: license renewal prohibited");
|
||||
return LICENSE_RENEWAL_PROHIBITED;
|
||||
}
|
||||
|
||||
LicenseRequest license_request;
|
||||
if (is_renewal)
|
||||
license_request.set_type(LicenseRequest::RENEWAL);
|
||||
|
||||
374
core/src/privacy_crypto_apple.cpp
Normal file
374
core/src/privacy_crypto_apple.cpp
Normal file
@@ -0,0 +1,374 @@
|
||||
// Copyright 2015 Google Inc. All Rights Reserved.
|
||||
//
|
||||
// Description:
|
||||
// Privacy crypto implementation for iOS. This fully implements the
|
||||
// privacy_crypto methods. This assumes this is compiled on a Mac and is
|
||||
// being compiled for iOS. Requires iOS 2.0 or later.
|
||||
//
|
||||
// This is never included in the default builds. If compiling using the gyp
|
||||
// files, setting privacy_crypto_impl to "apple" with use this file rather
|
||||
// than the openssl version.
|
||||
|
||||
#include "privacy_crypto.h"
|
||||
|
||||
#include <CommonCrypto/CommonCryptor.h>
|
||||
#include <CommonCrypto/CommonDigest.h>
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <memory>
|
||||
#include <Security/Security.h>
|
||||
#include <Security/SecKey.h>
|
||||
|
||||
#include "string_conversions.h"
|
||||
#include "log.h"
|
||||
|
||||
#define KEYSTORE_NAME "com.google.widevine.publicKey"
|
||||
|
||||
namespace {
|
||||
|
||||
const int kPssSaltLength = 20;
|
||||
const int kOaepMinPadding = 2 * CC_SHA1_DIGEST_LENGTH + 1;
|
||||
|
||||
template<typename T>
|
||||
struct CFDeleter {
|
||||
void operator()(T arg) {
|
||||
CFRelease(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
using CF =
|
||||
std::unique_ptr<typename std::remove_pointer<T>::type, CFDeleter<T> >;
|
||||
|
||||
SecKeyRef ImportPublicKey(const std::string& key) {
|
||||
std::string peerStr = KEYSTORE_NAME;
|
||||
CF<CFDataRef> peerData(CFDataCreate(NULL,
|
||||
reinterpret_cast<const UInt8*>(peerStr.c_str()), peerStr.length()));
|
||||
CF<CFDataRef> keyData(CFDataCreate(NULL,
|
||||
reinterpret_cast<const UInt8*>(key.c_str()), key.length()));
|
||||
|
||||
// Create a selector and delete all old keys.
|
||||
CF<CFMutableDictionaryRef> deleteAttributes(CFDictionaryCreateMutable(
|
||||
NULL, 0, NULL, NULL));
|
||||
CFDictionarySetValue(deleteAttributes.get(), kSecClass, kSecClassKey);
|
||||
CFDictionarySetValue(deleteAttributes.get(), kSecAttrKeyType,
|
||||
kSecAttrKeyTypeRSA);
|
||||
CFDictionarySetValue(deleteAttributes.get(), kSecAttrApplicationTag,
|
||||
peerData.get());
|
||||
SecItemDelete(deleteAttributes.get());
|
||||
|
||||
// Create attributes to add to the keystore.
|
||||
CF<CFMutableDictionaryRef> addAttributes(CFDictionaryCreateMutable(
|
||||
NULL, 0, NULL, NULL));
|
||||
CFDictionarySetValue(addAttributes.get(), kSecClass, kSecClassKey);
|
||||
CFDictionarySetValue(addAttributes.get(), kSecAttrKeyType,
|
||||
kSecAttrKeyTypeRSA);
|
||||
CFDictionarySetValue(addAttributes.get(), kSecAttrApplicationTag,
|
||||
peerData.get());
|
||||
CFDictionarySetValue(addAttributes.get(), kSecValueData,
|
||||
keyData.get());
|
||||
CFDictionarySetValue(addAttributes.get(), kSecAttrKeyClass,
|
||||
kSecAttrKeyClassPublic);
|
||||
CFDictionarySetValue(addAttributes.get(), kSecReturnPersistentRef,
|
||||
kCFBooleanTrue);
|
||||
|
||||
// Add the key to the keystore.
|
||||
CFTypeRef temp;
|
||||
OSStatus status = SecItemAdd(addAttributes.get(), &temp);
|
||||
CF<CFTypeRef> peer(temp);
|
||||
if (!peer || (status != noErr && status != errSecDuplicateItem)) {
|
||||
LOGE("RsaPublicKey::Init: Error adding key to keychain %d", status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Create attributes for for the query.
|
||||
CF<CFMutableDictionaryRef> queryAttributes(CFDictionaryCreateMutable(
|
||||
NULL, 0, NULL, NULL));
|
||||
CFDictionarySetValue(queryAttributes.get(), kSecClass, kSecClassKey);
|
||||
CFDictionarySetValue(queryAttributes.get(), kSecAttrApplicationTag,
|
||||
peerData.get());
|
||||
CFDictionarySetValue(queryAttributes.get(), kSecAttrKeyType,
|
||||
kSecAttrKeyTypeRSA);
|
||||
CFDictionarySetValue(queryAttributes.get(), kSecAttrKeyClass,
|
||||
kSecAttrKeyClassPublic);
|
||||
CFDictionarySetValue(queryAttributes.get(), kSecReturnRef, kCFBooleanTrue);
|
||||
|
||||
// Query the keychain to get the public key ref.
|
||||
CFTypeRef keyRef = NULL;
|
||||
status = SecItemCopyMatching(queryAttributes.get(), &keyRef);
|
||||
if (status != noErr) {
|
||||
LOGE("RsaPublicKey::Init: Error getting key from keystore %d", status);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return reinterpret_cast<SecKeyRef>(const_cast<void*>(keyRef));
|
||||
}
|
||||
|
||||
// Apply a custom mask generation function (MGF) using the hash function SHA1,
|
||||
// this is from OpenSSL.
|
||||
void ApplyMGF1_SHA1(uint8_t *output, size_t outputLength,
|
||||
const uint8_t* seed, size_t seedLength) {
|
||||
size_t outputIndex = 0;
|
||||
for (int i = 0; outputIndex < outputLength; i++) {
|
||||
uint8_t extra[4];
|
||||
extra[0] = (uint8_t)((i >> 24) & 0xFF);
|
||||
extra[1] = (uint8_t)((i >> 16) & 0xFF);
|
||||
extra[2] = (uint8_t)((i >> 8) & 0xFF);
|
||||
extra[3] = (uint8_t)(i & 0xFF);
|
||||
|
||||
CC_SHA1_CTX ctx;
|
||||
CC_SHA1_Init(&ctx);
|
||||
CC_SHA1_Update(&ctx, seed, seedLength);
|
||||
CC_SHA1_Update(&ctx, extra, 4);
|
||||
|
||||
if (outputIndex + CC_SHA1_DIGEST_LENGTH <= outputLength) {
|
||||
CC_SHA1_Final(output + outputIndex, &ctx);
|
||||
} else {
|
||||
uint8_t temp[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1_Final(temp, &ctx);
|
||||
memcpy(output + outputIndex, temp, outputLength - outputIndex);
|
||||
}
|
||||
outputIndex += CC_SHA1_DIGEST_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ApplyOAEPPadding(const std::string& messageStr, size_t rsaSize) {
|
||||
if (messageStr.length() > rsaSize - kOaepMinPadding ) {
|
||||
LOGE("RsaPublicKey::Encrypt: message too large to be encrypted (actual %d",
|
||||
" max allowed %d)", messageStr.size(),
|
||||
rsaSize - kOaepMinPadding );
|
||||
return "";
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc2437#section-9.1.1.2
|
||||
//
|
||||
// result db
|
||||
// |------------------------------------------------------------------------|
|
||||
// |0| seed | pHash |000000000|1| M |
|
||||
// |------------------------------------------------------------------------|
|
||||
// | |<-mdLength->|<-mdLength->|<-psLen->| |<-------messageLength---------->|
|
||||
// |<------------paddingLength------------>|
|
||||
|
||||
std::string ret;
|
||||
ret.resize(rsaSize);
|
||||
size_t messageLength = messageStr.length();
|
||||
size_t paddingLength = rsaSize - messageLength;
|
||||
size_t psLen = paddingLength - kOaepMinPadding;
|
||||
const uint8_t *message = reinterpret_cast<const uint8_t*>(messageStr.data());
|
||||
uint8_t *result = reinterpret_cast<uint8_t*>(&ret[0]);
|
||||
uint8_t *seed = result + 1;
|
||||
uint8_t *db = result + CC_SHA1_DIGEST_LENGTH + 1;
|
||||
|
||||
// Initialize db and message
|
||||
CC_SHA1(NULL, 0, db); // Hash of empty string.
|
||||
result[rsaSize - messageLength - 1] = 0x1;
|
||||
memcpy(result + paddingLength, message, messageLength);
|
||||
|
||||
// Initialize seed
|
||||
if (SecRandomCopyBytes(kSecRandomDefault, CC_SHA1_DIGEST_LENGTH, seed)) {
|
||||
LOGE("RsaPublicKey::Encrypt: unable to get random data %d", errno);
|
||||
return "";
|
||||
}
|
||||
|
||||
// Create the first mask
|
||||
std::vector<uint8_t> dbmask;
|
||||
dbmask.resize(rsaSize - CC_SHA1_DIGEST_LENGTH - 1);
|
||||
ApplyMGF1_SHA1(dbmask.data(), dbmask.size(), seed, CC_SHA1_DIGEST_LENGTH);
|
||||
for (int i = 0; i < dbmask.size(); i++) {
|
||||
db[i] ^= dbmask[i];
|
||||
}
|
||||
|
||||
// Create the second mask
|
||||
uint8_t seedmask[CC_SHA1_DIGEST_LENGTH];
|
||||
ApplyMGF1_SHA1(seedmask, CC_SHA1_DIGEST_LENGTH, db, dbmask.size());
|
||||
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
|
||||
seed[i] ^= seedmask[i];
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool PSSVerify(const uint8_t *message, size_t messageLength,
|
||||
const uint8_t *encodedMessage, size_t encodedMessageLength) {
|
||||
// https://tools.ietf.org/html/rfc3447#section-9.1.2
|
||||
//
|
||||
// M'
|
||||
// |---------------------------------------------------|
|
||||
// | 00 00 00 00 00 00 00 00 | mHash | salt |
|
||||
// |---------------------------------------------------|
|
||||
//
|
||||
// H = hash(M')
|
||||
// dbMask = MGF(H)
|
||||
//
|
||||
// db
|
||||
// |------------------------|
|
||||
// | 00 00 ... 00 01 | salt |
|
||||
// |------------------------|
|
||||
// |<----messageLength----->|
|
||||
//
|
||||
// maskedDb = db ^ dbMask
|
||||
// encodedMessage
|
||||
// |--------------------------------------------------|
|
||||
// | maskedDb | H | bc |
|
||||
// |--------------------------------------------------|
|
||||
|
||||
if (encodedMessage[encodedMessageLength - 1] != 0xbc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t *maskedDb = encodedMessage;
|
||||
size_t dbLength = encodedMessageLength - CC_SHA1_DIGEST_LENGTH - 1;
|
||||
const uint8_t *H = maskedDb + dbLength;
|
||||
|
||||
// Decode db
|
||||
std::vector<uint8_t> dbMask;
|
||||
dbMask.resize(dbLength);
|
||||
ApplyMGF1_SHA1(dbMask.data(), dbMask.size(), H, CC_SHA1_DIGEST_LENGTH);
|
||||
for (int i = 0; i < dbLength; i++) {
|
||||
dbMask[i] ^= maskedDb[i];
|
||||
}
|
||||
|
||||
// Verify db
|
||||
for (int i = 0; i < dbLength - kPssSaltLength - 1; i++) {
|
||||
if (dbMask[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (dbMask[dbLength - kPssSaltLength - 1] != 0x01) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t *salt = dbMask.data() + (dbLength - kPssSaltLength);
|
||||
uint8_t mHash[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1(message, messageLength, mHash);
|
||||
|
||||
// Create our version of the message data (M')
|
||||
std::vector<uint8_t> dataVec;
|
||||
dataVec.resize(8 + CC_SHA1_DIGEST_LENGTH + kPssSaltLength);
|
||||
uint8_t *data = dataVec.data();
|
||||
memcpy(data + 8, mHash, CC_SHA1_DIGEST_LENGTH);
|
||||
memcpy(data + 8 + CC_SHA1_DIGEST_LENGTH, salt, kPssSaltLength);
|
||||
|
||||
// Verify the hash of the message data.
|
||||
uint8_t H2[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1(data, dataVec.size(), H2);
|
||||
return !memcmp(H, H2, CC_SHA1_DIGEST_LENGTH);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
|
||||
AesCbcKey::AesCbcKey() {}
|
||||
|
||||
AesCbcKey::~AesCbcKey() {}
|
||||
|
||||
bool AesCbcKey::Init(const std::string& key) {
|
||||
assert(key.size() == kCCBlockSizeAES128);
|
||||
this->key_ = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AesCbcKey::Encrypt(const std::string& in, std::string* out,
|
||||
std::string* iv) {
|
||||
assert(!in.empty());
|
||||
assert(iv != NULL);
|
||||
assert(iv->size() == kCCBlockSizeAES128);
|
||||
assert(out != NULL);
|
||||
assert(!key_.empty());
|
||||
|
||||
std::string temp;
|
||||
temp.resize(in.length() + kCCBlockSizeAES128);
|
||||
size_t length;
|
||||
CCCryptorStatus result = CCCrypt(kCCEncrypt, kCCAlgorithmAES128,
|
||||
kCCOptionPKCS7Padding, key_.c_str(), key_.length(), iv->c_str(),
|
||||
in.c_str(), in.length(), &temp[0], temp.size(), &length);
|
||||
|
||||
if (result != kCCSuccess) {
|
||||
LOGE("AesCbcKey::Encrypt: Encryption failure: %d", result);
|
||||
return false;
|
||||
}
|
||||
|
||||
out->assign(temp, 0, length);
|
||||
return true;
|
||||
}
|
||||
|
||||
RsaPublicKey::RsaPublicKey() {}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {}
|
||||
|
||||
bool RsaPublicKey::Init(const std::string& serialized_key) {
|
||||
assert(!serialized_key.empty());
|
||||
|
||||
this->serialized_key_ = serialized_key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::Encrypt(const std::string& clear_message,
|
||||
std::string* encrypted_message) {
|
||||
assert(!clear_message.empty());
|
||||
assert(encrypted_message != NULL);
|
||||
|
||||
SecKeyRef key = ImportPublicKey(serialized_key_);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t rsaSize = SecKeyGetBlockSize(key);
|
||||
std::string paddedMessage = ApplyOAEPPadding(clear_message, rsaSize);
|
||||
if (paddedMessage.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t size = paddedMessage.length();
|
||||
std::string buffer;
|
||||
buffer.resize(size);
|
||||
OSStatus status = SecKeyEncrypt(key, kSecPaddingNone,
|
||||
reinterpret_cast<const uint8_t*>(paddedMessage.c_str()),
|
||||
paddedMessage.length(),
|
||||
reinterpret_cast<uint8_t*>(&buffer[0]), &size);
|
||||
if (status != errSecSuccess) {
|
||||
LOGE("RsaPublicKey::Encrypt: Unable to encrypt data %d", status);
|
||||
return false;
|
||||
}
|
||||
|
||||
encrypted_message->assign(buffer, 0, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RsaPublicKey::VerifySignature(const std::string& message,
|
||||
const std::string& signature) {
|
||||
assert(!message.empty());
|
||||
assert(!signature.empty());
|
||||
|
||||
SecKeyRef key = ImportPublicKey(serialized_key_);
|
||||
if (!key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// "decrypt" the signature
|
||||
std::vector<uint8_t> buffer;
|
||||
buffer.resize(signature.length());
|
||||
size_t size = buffer.size();
|
||||
OSStatus status = SecKeyEncrypt(key, kSecPaddingNone,
|
||||
reinterpret_cast<const uint8_t*>(signature.c_str()),
|
||||
signature.length(),
|
||||
buffer.data(), &size);
|
||||
if (status != errSecSuccess) {
|
||||
LOGE("RsaPublicKey::VerifySignature: Unable to decrypt signature %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify the signature
|
||||
if (!PSSVerify(reinterpret_cast<const uint8_t*>(message.c_str()),
|
||||
message.length(),
|
||||
buffer.data(), buffer.size())) {
|
||||
LOGE("RsaPublicKey::VerifySignature: Unable to verify signature %d",
|
||||
status);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace wvcdm
|
||||
@@ -230,7 +230,6 @@ TEST_F(CdmSessionTest, ReInitFail) {
|
||||
}
|
||||
|
||||
TEST_F(CdmSessionTest, InitFailCryptoError) {
|
||||
CdmSecurityLevel level = kSecurityLevelL1;
|
||||
EXPECT_CALL(*crypto_session_, Open(Eq(kLevelDefault)))
|
||||
.WillOnce(Return(UNKNOWN_ERROR));
|
||||
|
||||
|
||||
@@ -146,7 +146,7 @@ ConfigTestEnv::ConfigTestEnv(LicenseServerId server_id, bool streaming,
|
||||
if (!streaming) {
|
||||
key_id_ = license_servers[server_id].offline_key_id;
|
||||
|
||||
if (wvcdm::kGooglePlayServer == server_id) {
|
||||
if (kGooglePlayServer == server_id) {
|
||||
if (renew) {
|
||||
client_auth_.append(kGpClientOfflineRenewalQueryParameters);
|
||||
} else if (release) {
|
||||
|
||||
@@ -29,7 +29,7 @@ class ConfigTestEnv {
|
||||
ConfigTestEnv(LicenseServerId server_id, bool streaming);
|
||||
ConfigTestEnv(LicenseServerId server_id, bool streaming, bool renew,
|
||||
bool release);
|
||||
~ConfigTestEnv(){};
|
||||
~ConfigTestEnv() {};
|
||||
|
||||
const std::string& client_auth() const { return client_auth_; }
|
||||
const KeyId& key_id() const { return key_id_; }
|
||||
|
||||
@@ -1469,7 +1469,6 @@ class DeviceFilesTest : public ::testing::Test {
|
||||
CdmAppParameterMap app_parameters;
|
||||
size_t start_pos = 0;
|
||||
size_t len = str.length();
|
||||
bool more = true;
|
||||
while (start_pos < len) {
|
||||
size_t name_end_pos = str.find(' ', start_pos);
|
||||
if (name_end_pos == std::string::npos) return app_parameters;
|
||||
@@ -1841,7 +1840,6 @@ TEST_F(DeviceFilesTest, RetrieveLicenses) {
|
||||
DeviceFiles device_files;
|
||||
EXPECT_TRUE(device_files.Init(kSecurityLevelL1));
|
||||
device_files.SetTestFile(&file);
|
||||
DeviceFiles::LicenseState license_state;
|
||||
CdmInitData pssh_data;
|
||||
CdmKeyMessage key_request;
|
||||
CdmKeyResponse key_response;
|
||||
|
||||
@@ -47,6 +47,7 @@ SSL_CTX* InitSslContext() {
|
||||
return ctx;
|
||||
}
|
||||
|
||||
#if 0
|
||||
// unused, may be useful for debugging SSL-related issues.
|
||||
void ShowServerCertificate(const SSL* ssl) {
|
||||
// gets the server certificate
|
||||
@@ -64,6 +65,7 @@ void ShowServerCertificate(const SSL* ssl) {
|
||||
LOGE("Failed to get server certificate");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// Wait for a socket to be ready for reading or writing.
|
||||
// Establishing a connection counts as "ready for write".
|
||||
|
||||
@@ -16,7 +16,6 @@ namespace wvcdm {
|
||||
|
||||
namespace {
|
||||
|
||||
const uint32_t kAesBlockSize = 16;
|
||||
const std::string kAesKey = a2bs_hex("000102030405060708090a0b0c0d0e0f");
|
||||
const std::string kAesIv = a2bs_hex("000102030405060708090a0b0c0d0e0f");
|
||||
const std::string kCencInitDataHdr = a2bs_hex(
|
||||
|
||||
@@ -338,6 +338,8 @@ void PrintTo(const enum CdmResponseType& value, ::std::ostream* os) {
|
||||
case EMPTY_LICENSE_REQUEST: *os << "EMPTY_LICENSE_REQUEST";
|
||||
break;
|
||||
case DUPLICATE_SESSION_ID_SPECIFIED: *os << "DUPLICATE_SESSION_ID_SPECIFIED";
|
||||
case LICENSE_RENEWAL_PROHIBITED: *os << "LICENSE_RENEWAL_PROHIBITED";
|
||||
break;
|
||||
default:
|
||||
*os << "Unknown CdmResponseType";
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user