Source release 19.2.0
This commit is contained in:
@@ -81,9 +81,7 @@ CdmSession::CdmSession(wvutil::FileSystem* file_system,
|
||||
security_level_(kSecurityLevelUninitialized),
|
||||
requested_security_level_(kLevelDefault),
|
||||
is_initial_usage_update_(true),
|
||||
is_usage_update_needed_(false),
|
||||
mock_license_parser_in_use_(false),
|
||||
mock_policy_engine_in_use_(false) {
|
||||
is_usage_update_needed_(false) {
|
||||
assert(metrics_); // metrics_ must not be null.
|
||||
crypto_metrics_ = metrics_->GetCryptoMetrics();
|
||||
crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_));
|
||||
@@ -263,8 +261,11 @@ CdmResponseType CdmSession::RestoreOfflineSession(const CdmKeySetId& key_set_id,
|
||||
usage_entry_ = std::move(license_data.usage_entry);
|
||||
usage_entry_index_ = license_data.usage_entry_index;
|
||||
|
||||
CdmResponseType result = LoadPrivateOrLegacyKey(
|
||||
license_data.drm_certificate, license_data.wrapped_private_key);
|
||||
CdmResponseType result = crypto_session_->MarkOfflineSession();
|
||||
if (result != NO_ERROR) return result;
|
||||
|
||||
result = LoadPrivateOrLegacyKey(license_data.drm_certificate,
|
||||
license_data.wrapped_private_key);
|
||||
if (result != NO_ERROR) return result;
|
||||
|
||||
// Attempts to restore a released offline license are treated as a release
|
||||
@@ -866,18 +867,10 @@ CdmResponseType CdmSession::DeleteUsageEntry(uint32_t usage_entry_index) {
|
||||
// The usage entry cannot be deleted if it has a crypto session handling
|
||||
// it, so close and reopen session.
|
||||
UpdateUsageEntryInformation();
|
||||
CdmResponseType sts;
|
||||
crypto_session_->Close();
|
||||
crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_));
|
||||
M_TIME(sts = crypto_session_->Open(requested_security_level_),
|
||||
crypto_metrics_, crypto_session_open_, sts, requested_security_level_);
|
||||
if (sts != NO_ERROR) return sts;
|
||||
|
||||
usage_table_ = nullptr;
|
||||
bool has_support = false;
|
||||
if (crypto_session_->HasUsageTableSupport(&has_support) && has_support) {
|
||||
usage_table_ = crypto_session_->GetUsageTable();
|
||||
}
|
||||
crypto_session_->Close();
|
||||
CdmResponseType sts = ResetCryptoSession();
|
||||
if (sts != NO_ERROR) return sts;
|
||||
|
||||
if (usage_table_ == nullptr) {
|
||||
LOGE("Usage table header unavailable");
|
||||
@@ -1012,14 +1005,8 @@ bool CdmSession::StoreLicense(CdmOfflineLicenseState state, int* error_detail) {
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::RemoveKeys() {
|
||||
CdmResponseType sts;
|
||||
crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_));
|
||||
// Ignore errors
|
||||
M_TIME(sts = crypto_session_->Open(requested_security_level_),
|
||||
crypto_metrics_, crypto_session_open_, sts, requested_security_level_);
|
||||
policy_engine_.reset(
|
||||
new PolicyEngine(session_id_, nullptr, crypto_session_.get()));
|
||||
return CdmResponseType(NO_ERROR);
|
||||
crypto_session_->Close();
|
||||
return ResetCryptoSession();
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::RemoveLicense() {
|
||||
@@ -1313,6 +1300,77 @@ CdmResponseType CdmSession::GenerateRsaSignature(const std::string& message,
|
||||
return crypto_session_->GenerateRsaSignature(message, signature, scheme);
|
||||
}
|
||||
|
||||
CdmResponseType CdmSession::ResetCryptoSession() {
|
||||
LOGD("Resetting crypto session: session_id = %s, ksid = %s",
|
||||
IdToString(session_id_), IdToString(key_set_id_));
|
||||
if (mock_crypto_session_in_use_) {
|
||||
// If the crypto session is not reset, then there is nothing to do.
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
CdmResponseType sts;
|
||||
crypto_session_.reset(CryptoSession::MakeCryptoSession(crypto_metrics_));
|
||||
usage_table_ = nullptr;
|
||||
M_TIME(sts = crypto_session_->Open(requested_security_level_),
|
||||
crypto_metrics_, crypto_session_open_, sts, requested_security_level_);
|
||||
|
||||
CdmResponseType final_sts(NO_ERROR);
|
||||
if (sts != NO_ERROR) {
|
||||
// Challenging case, still need to reset other components.
|
||||
LOGE("Failed to open crypto session: sts = %s", sts.ToString().c_str());
|
||||
final_sts = sts;
|
||||
} else {
|
||||
// Reset all component dependent on the crypto session.
|
||||
security_level_ = crypto_session_->GetSecurityLevel();
|
||||
crypto_metrics_->crypto_session_security_level_.Record(security_level_);
|
||||
|
||||
if (!file_handle_->Init(security_level_)) {
|
||||
LOGE("Unable to initialize file handle");
|
||||
final_sts = CdmResponseType(SESSION_FILE_HANDLE_INIT_ERROR);
|
||||
}
|
||||
|
||||
if (!file_handle_->HasCertificate(atsc_mode_enabled_)) {
|
||||
LOGE("Missing certificate: atsc_mode_enabled = %s",
|
||||
BoolToString(atsc_mode_enabled_));
|
||||
final_sts = CdmResponseType(NEED_PROVISIONING);
|
||||
}
|
||||
|
||||
bool has_support = false;
|
||||
if (crypto_session_->HasUsageTableSupport(&has_support) && has_support) {
|
||||
usage_table_ = crypto_session_->GetUsageTable();
|
||||
}
|
||||
}
|
||||
|
||||
// Even if the session is not open, other members need new session pointer.
|
||||
if (mock_policy_engine_in_use_) {
|
||||
// Simply pass the new pointer.
|
||||
policy_engine_->set_crypto_session(crypto_session_.get());
|
||||
} else {
|
||||
// Attempt to maintain event listener.
|
||||
WvCdmEventListener* event_listener =
|
||||
policy_engine_ ? policy_engine_->event_listener() : nullptr;
|
||||
policy_engine_.reset(
|
||||
new PolicyEngine(session_id_, event_listener, crypto_session_.get()));
|
||||
}
|
||||
|
||||
if (mock_license_parser_in_use_) {
|
||||
license_parser_->set_crypto_session(crypto_session_.get());
|
||||
license_parser_->set_policy_engine(policy_engine_.get());
|
||||
} else {
|
||||
license_parser_.reset(new CdmLicense(session_id_));
|
||||
std::string service_certificate;
|
||||
if (!Properties::GetServiceCertificate(session_id_, &service_certificate))
|
||||
service_certificate.clear();
|
||||
if (!license_parser_->Init(Properties::UsePrivacyMode(session_id_),
|
||||
service_certificate, crypto_session_.get(),
|
||||
policy_engine_.get())) {
|
||||
LOGE("Failed to initialize license parser");
|
||||
final_sts = CdmResponseType(LICENSE_PARSER_INIT_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return final_sts;
|
||||
}
|
||||
|
||||
// For testing only - takes ownership of pointers
|
||||
|
||||
void CdmSession::set_license_parser(CdmLicense* license_parser) {
|
||||
@@ -1322,6 +1380,7 @@ void CdmSession::set_license_parser(CdmLicense* license_parser) {
|
||||
|
||||
void CdmSession::set_crypto_session(CryptoSession* crypto_session) {
|
||||
crypto_session_.reset(crypto_session);
|
||||
mock_crypto_session_in_use_ = true;
|
||||
}
|
||||
|
||||
void CdmSession::set_policy_engine(PolicyEngine* policy_engine) {
|
||||
|
||||
@@ -658,7 +658,7 @@ CdmResponseType CryptoSession::GetProvisioningToken(
|
||||
additional_token);
|
||||
} else if (pre_provision_token_type_ ==
|
||||
kClientTokenDrmCertificateReprovisioning) {
|
||||
status = GetTokenFromEmbeddedCertificate(token);
|
||||
status = GetTokenFromEmbeddedCertificate(requested_security_level, token);
|
||||
}
|
||||
metrics_->crypto_session_get_token_.Increment(status);
|
||||
return status;
|
||||
@@ -970,6 +970,15 @@ CdmResponseType CryptoSession::Open(
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::MarkOfflineSession() {
|
||||
OEMCryptoResult sts;
|
||||
WithOecSessionLock("MarkOfflineSession", [&] {
|
||||
sts = OEMCrypto_MarkOfflineSession(oec_session_id_);
|
||||
});
|
||||
if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) sts = OEMCrypto_SUCCESS;
|
||||
return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "MarkOfflineSession");
|
||||
}
|
||||
|
||||
void CryptoSession::Close() {
|
||||
LOGV("Closing crypto session: id = %u, open = %s", oec_session_id_,
|
||||
open_ ? "true" : "false");
|
||||
@@ -1244,7 +1253,7 @@ CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest(
|
||||
}
|
||||
|
||||
OEMCryptoResult sts;
|
||||
// TODO: b/305093063 - Refactor to a switch statement to improve readability
|
||||
// TODO: b/330770643 - Refactor to a switch statement to improve readability
|
||||
if (pre_provision_token_type_ == kClientTokenKeybox) {
|
||||
should_specify_algorithm = false;
|
||||
} else if (pre_provision_token_type_ == kClientTokenOemCert) {
|
||||
@@ -1437,19 +1446,19 @@ CdmResponseType CryptoSession::GetBootCertificateChain(
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::GetTokenFromEmbeddedCertificate(
|
||||
std::string* token) {
|
||||
RequestedSecurityLevel requested_security_level, std::string* token) {
|
||||
RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED);
|
||||
RETURN_IF_NULL(token, PARAMETER_NULL);
|
||||
token->clear();
|
||||
|
||||
CdmClientTokenType token_type = kClientTokenUninitialized;
|
||||
const CdmResponseType sts =
|
||||
GetProvisioningMethod(requested_security_level_, &token_type);
|
||||
GetProvisioningMethod(requested_security_level, &token_type);
|
||||
if (sts != NO_ERROR) {
|
||||
LOGE("Failed to get token type");
|
||||
return sts;
|
||||
}
|
||||
if (token_type != kClientTokenDrmCertificateReprovisioning) {
|
||||
token->clear();
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
@@ -1458,7 +1467,7 @@ CdmResponseType CryptoSession::GetTokenFromEmbeddedCertificate(
|
||||
OEMCryptoResult status =
|
||||
WithOecReadLock("GetTokenFromEmbeddedCertificate - attempt 1", [&] {
|
||||
return OEMCrypto_GetEmbeddedDrmCertificate(
|
||||
reinterpret_cast<uint8_t*>(&embedded_certificate.front()),
|
||||
MutableStringDataPointer(&embedded_certificate),
|
||||
&certificate_length);
|
||||
});
|
||||
|
||||
@@ -1467,26 +1476,27 @@ CdmResponseType CryptoSession::GetTokenFromEmbeddedCertificate(
|
||||
status =
|
||||
WithOecReadLock("GetTokenFromEmbeddedCertificate - attempt 2", [&] {
|
||||
return OEMCrypto_GetEmbeddedDrmCertificate(
|
||||
reinterpret_cast<uint8_t*>(&embedded_certificate.front()),
|
||||
MutableStringDataPointer(&embedded_certificate),
|
||||
&certificate_length);
|
||||
});
|
||||
}
|
||||
|
||||
if (status == OEMCrypto_SUCCESS) {
|
||||
// TODO: b/312782308 - Store embedded certificate as SignedDrmCertificate
|
||||
video_widevine_client::sdk::HashedFile hashed_file;
|
||||
video_widevine_client::sdk::File file;
|
||||
embedded_certificate.resize(certificate_length);
|
||||
if (hashed_file.ParseFromString(embedded_certificate) &&
|
||||
file.ParseFromString(hashed_file.file())) {
|
||||
*token = file.device_certificate().certificate();
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
if (status != OEMCrypto_SUCCESS) {
|
||||
return MapOEMCryptoResult(status, GET_TOKEN_FROM_EMBEDDED_CERT_ERROR,
|
||||
"GetTokenFromEmbeddedCertificate");
|
||||
}
|
||||
embedded_certificate.resize(certificate_length);
|
||||
|
||||
token->clear();
|
||||
return MapOEMCryptoResult(status, GET_TOKEN_FROM_EMBEDDED_CERT_ERROR,
|
||||
"GetTokenFromEmbeddedCertificate");
|
||||
// TODO: b/312782308 - Store embedded certificate as SignedDrmCertificate
|
||||
video_widevine_client::sdk::HashedFile hashed_file;
|
||||
video_widevine_client::sdk::File file;
|
||||
if (!hashed_file.ParseFromString(embedded_certificate) ||
|
||||
!file.ParseFromString(hashed_file.file())) {
|
||||
LOGE("Failed to parse embedded certificate");
|
||||
return CdmResponseType(GET_TOKEN_FROM_EMBEDDED_CERT_ERROR);
|
||||
}
|
||||
*token = file.device_certificate().certificate();
|
||||
return CdmResponseType(NO_ERROR);
|
||||
}
|
||||
|
||||
CdmResponseType CryptoSession::GetDeviceInformation(
|
||||
@@ -2995,7 +3005,12 @@ CdmResponseType CryptoSession::ShrinkUsageTableHeader(
|
||||
});
|
||||
|
||||
if (result == OEMCrypto_ERROR_SHORT_BUFFER) {
|
||||
usage_table_header->resize(usage_table_header_len);
|
||||
// Don't shrink the buffer yet so OEMCrypto can see the existing data.
|
||||
if (usage_table_header_len > usage_table_header->size()) {
|
||||
usage_table_header->resize(usage_table_header_len);
|
||||
} else {
|
||||
usage_table_header_len = usage_table_header->size();
|
||||
}
|
||||
WithOecWriteLock("ShrinkUsageTableHeader Attempt 2", [&] {
|
||||
result = OEMCrypto_ShrinkUsageTableHeader(
|
||||
requested_security_level, new_entry_count,
|
||||
|
||||
@@ -680,7 +680,7 @@ void InitializationData::DumpToLogs() const {
|
||||
if (!is_supported()) {
|
||||
LOGD("InitData: Not supported");
|
||||
}
|
||||
if (!IsEmpty()) {
|
||||
if (IsEmpty()) {
|
||||
LOGD("InitData: Empty");
|
||||
}
|
||||
std::string type_info = type();
|
||||
@@ -736,6 +736,9 @@ void InitializationData::DumpToLogs() const {
|
||||
LOGD("InitData: entitlement_key_id %d: %s -> %s", i,
|
||||
wvutil::b2a_hex(key.entitlement_key_id()).c_str(),
|
||||
wvutil::b2a_hex(key.key_id()).c_str());
|
||||
LOGD("InitData: entitled_key %d: %s", i,
|
||||
wvutil::b2a_hex(key.key()).c_str());
|
||||
LOGD("InitData: iv %d: %s", i, wvutil::b2a_hex(key.iv()).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -195,9 +195,7 @@ std::vector<CryptoKey> ExtractContentKeys(
|
||||
} // namespace
|
||||
|
||||
CdmLicense::CdmLicense(const CdmSessionId& session_id)
|
||||
: crypto_session_(nullptr),
|
||||
policy_engine_(nullptr),
|
||||
session_id_(session_id),
|
||||
: session_id_(session_id),
|
||||
initialized_(false),
|
||||
protocol_version_(video_widevine::VERSION_2_2),
|
||||
renew_with_client_id_(false),
|
||||
@@ -207,16 +205,18 @@ CdmLicense::CdmLicense(const CdmSessionId& session_id)
|
||||
license_key_type_(kLicenseKeyTypeContent) {}
|
||||
|
||||
CdmLicense::CdmLicense(const CdmSessionId& session_id, wvutil::Clock* clock)
|
||||
: crypto_session_(nullptr),
|
||||
policy_engine_(nullptr),
|
||||
session_id_(session_id),
|
||||
: session_id_(session_id),
|
||||
initialized_(false),
|
||||
protocol_version_(video_widevine::VERSION_2_2),
|
||||
renew_with_client_id_(false),
|
||||
is_offline_(false),
|
||||
use_privacy_mode_(false),
|
||||
clock_(clock),
|
||||
license_key_type_(kLicenseKeyTypeContent) {
|
||||
clock_.reset(clock);
|
||||
if (!clock_) {
|
||||
LOGW("Input |clock| is null, using default");
|
||||
clock_.reset(new wvutil::Clock());
|
||||
}
|
||||
}
|
||||
|
||||
CdmLicense::~CdmLicense() {}
|
||||
|
||||
@@ -232,3 +232,7 @@ OEMCryptoResult OEMCrypto_GetBCCType(RequestedSecurityLevel level,
|
||||
WEAK OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION, bool) {
|
||||
return OEMCrypto_SUCCESS;
|
||||
}
|
||||
|
||||
WEAK OEMCryptoResult OEMCrypto_MarkOfflineSession(OEMCrypto_SESSION) {
|
||||
return OEMCrypto_ERROR_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
@@ -39,9 +39,12 @@ PolicyEngine::PolicyEngine(CdmSessionId session_id,
|
||||
if (version >= kMinOemCryptoApiVersionSupportsRenewalDelayBase) {
|
||||
policy_timers_.reset(new PolicyTimersV18());
|
||||
}
|
||||
} else {
|
||||
LOGW("Failed to get API version: session_id = %s", IdToString(session_id_));
|
||||
}
|
||||
|
||||
if (policy_timers_ == nullptr) {
|
||||
if (!policy_timers_) {
|
||||
// Use V16 policy timers if getting version failed.
|
||||
policy_timers_.reset(new PolicyTimersV16());
|
||||
}
|
||||
InitDevice(crypto_session);
|
||||
|
||||
@@ -254,6 +254,31 @@ bool PSSVerify(const uint8_t* message, size_t mLen,
|
||||
return !memcmp(H, H2, hLen);
|
||||
}
|
||||
|
||||
bool AesCryptCommon(CCOperation op, const std::string& in,
|
||||
const std::string& iv, const std::string& key,
|
||||
std::string* out, bool has_padding) {
|
||||
assert(!in.empty());
|
||||
assert(iv.size() == kCCBlockSizeAES128);
|
||||
assert(out != nullptr);
|
||||
assert(!key.empty());
|
||||
|
||||
std::string temp;
|
||||
temp.resize(in.length() + kCCBlockSizeAES128);
|
||||
size_t length;
|
||||
CCCryptorStatus result = CCCrypt(
|
||||
op, kCCAlgorithmAES128, has_padding ? kCCOptionPKCS7Padding : 0,
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace wvcdm {
|
||||
@@ -269,27 +294,13 @@ bool AesCbcKey::Init(const std::string& key) {
|
||||
}
|
||||
|
||||
bool AesCbcKey::Encrypt(const std::string& in, const std::string& iv,
|
||||
std::string* out) {
|
||||
assert(!in.empty());
|
||||
assert(iv.size() == kCCBlockSizeAES128);
|
||||
assert(out != nullptr);
|
||||
assert(!key_.empty());
|
||||
std::string* out, bool has_padding) {
|
||||
return AesCryptCommon(kCCEncrypt, in, iv, key_, out, has_padding);
|
||||
}
|
||||
|
||||
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;
|
||||
bool AesCbcKey::Decrypt(const std::string& in, const std::string& iv,
|
||||
std::string* out, bool has_padding) {
|
||||
return AesCryptCommon(kCCDecrypt, in, iv, key_, out, has_padding);
|
||||
}
|
||||
|
||||
RsaPublicKey::RsaPublicKey() {}
|
||||
@@ -385,6 +396,12 @@ std::string Md5Hash(const std::string& data) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha1Hash(const std::string& data) {
|
||||
std::string hash(CC_SHA_DIGEST_LENGTH, '\0');
|
||||
CC_SHA1(data.data(), data.size(), reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha256Hash(const std::string& data) {
|
||||
std::string hash(CC_SHA256_DIGEST_LENGTH, '\0');
|
||||
CC_SHA256(data.data(), data.size(), reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
|
||||
@@ -90,7 +90,7 @@ bool AesCbcKey::Init(const std::string& key) {
|
||||
}
|
||||
|
||||
bool AesCbcKey::Encrypt(const std::string& in, const std::string& iv,
|
||||
std::string* out) {
|
||||
std::string* out, bool has_padding) {
|
||||
if (in.empty()) {
|
||||
LOGE("No cleartext provided");
|
||||
return false;
|
||||
@@ -117,6 +117,7 @@ bool AesCbcKey::Encrypt(const std::string& in, const std::string& iv,
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
if (!has_padding) EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0);
|
||||
|
||||
out->resize(in.size() + AES_BLOCK_SIZE);
|
||||
int out_length = static_cast<int>(out->size());
|
||||
@@ -145,6 +146,63 @@ bool AesCbcKey::Encrypt(const std::string& in, const std::string& iv,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AesCbcKey::Decrypt(const std::string& in, const std::string& iv,
|
||||
std::string* out, bool has_padding) {
|
||||
if (in.empty()) {
|
||||
LOGE("No ciphertext provided");
|
||||
return false;
|
||||
}
|
||||
if (iv.size() != AES_BLOCK_SIZE) {
|
||||
LOGE("Invalid IV size: %zu", iv.size());
|
||||
return false;
|
||||
}
|
||||
if (out == nullptr) {
|
||||
LOGE("Ciphertext output parameter |out| not provided");
|
||||
return false;
|
||||
}
|
||||
if (key_.empty()) {
|
||||
LOGE("AES key not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX* evp_cipher_ctx = EVP_CIPHER_CTX_new();
|
||||
if (EVP_DecryptInit(evp_cipher_ctx, EVP_aes_128_cbc(),
|
||||
reinterpret_cast<const uint8_t*>(&key_[0]),
|
||||
reinterpret_cast<const uint8_t*>(&iv[0])) == 0) {
|
||||
LOGE("AES CBC setup failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
if (!has_padding) EVP_CIPHER_CTX_set_padding(evp_cipher_ctx, 0);
|
||||
|
||||
out->resize(in.size() + AES_BLOCK_SIZE);
|
||||
int out_length = static_cast<int>(out->size());
|
||||
if (EVP_DecryptUpdate(evp_cipher_ctx, reinterpret_cast<uint8_t*>(&(*out)[0]),
|
||||
&out_length,
|
||||
reinterpret_cast<const uint8_t*>(in.data()),
|
||||
static_cast<int>(in.size())) == 0) {
|
||||
LOGE("AES CBC encryption failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
int padding = 0;
|
||||
if (EVP_DecryptFinal_ex(evp_cipher_ctx,
|
||||
reinterpret_cast<uint8_t*>(&(*out)[out_length]),
|
||||
&padding) == 0) {
|
||||
LOGE("PKCS7 padding failure: %s",
|
||||
ERR_error_string(ERR_get_error(), nullptr));
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(evp_cipher_ctx);
|
||||
out->resize(out_length + padding);
|
||||
return true;
|
||||
}
|
||||
|
||||
RsaPublicKey::RsaPublicKey() {}
|
||||
|
||||
RsaPublicKey::~RsaPublicKey() {}
|
||||
@@ -410,6 +468,13 @@ std::string Md5Hash(const std::string& data) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha1Hash(const std::string& data) {
|
||||
std::string hash(SHA_DIGEST_LENGTH, '\0');
|
||||
SHA1(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||
reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha256Hash(const std::string& data) {
|
||||
std::string hash(SHA256_DIGEST_LENGTH, '\0');
|
||||
SHA256(reinterpret_cast<const uint8_t*>(data.data()), data.size(),
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <CommonCrypto/CommonDigest.h>
|
||||
# define SHA1 CC_SHA1
|
||||
# define SHA_DIGEST_LENGTH CC_SHA_DIGEST_LENGTH
|
||||
# define SHA256 CC_SHA256
|
||||
# define SHA256_DIGEST_LENGTH CC_SHA256_DIGEST_LENGTH
|
||||
# define SHA512 CC_SHA512
|
||||
@@ -32,7 +34,12 @@ AesCbcKey::~AesCbcKey() {}
|
||||
bool AesCbcKey::Init(const std::string& key) { return false; }
|
||||
|
||||
bool AesCbcKey::Encrypt(const std::string& in, const std::string& iv,
|
||||
std::string* out) {
|
||||
std::string* out, bool has_padding) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AesCbcKey::Decrypt(const std::string& in, const std::string& iv,
|
||||
std::string* out, bool bool_padding) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -65,6 +72,12 @@ std::string Md5Hash(const std::string& data) {
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha1Hash(const std::string& data) {
|
||||
std::string hash(SHA_DIGEST_LENGTH, '\0');
|
||||
SHA1(data.data(), data.size(), reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
return hash;
|
||||
}
|
||||
|
||||
std::string Sha256Hash(const std::string& data) {
|
||||
std::string hash(SHA256_DIGEST_LENGTH, '\0');
|
||||
SHA256(data.data(), data.size(), reinterpret_cast<uint8_t*>(&hash[0]));
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "crypto_session.h"
|
||||
#include "device_files.h"
|
||||
#include "license_protocol.pb.h"
|
||||
#include "privacy_crypto.h"
|
||||
#include "wv_cdm_constants.h"
|
||||
#include "wv_cdm_types.h"
|
||||
@@ -18,16 +19,22 @@ constexpr size_t kKeyboxSystemIdOffset = 4;
|
||||
// Index of certificate within cerificate chain which contains the
|
||||
// system ID (0 = leaf/device cert, 1 = intermediate/device family cert).
|
||||
constexpr size_t kOemCertSystemIdIndex = 1;
|
||||
// OID of X.509 certificate extension containing the Widevine system ID.
|
||||
// OID of X.509 TBSCertificate extension containing the Widevine
|
||||
// system ID.
|
||||
const char kWidevineSystemIdExtensionOid[] = "1.3.6.1.4.1.11129.4.1.1";
|
||||
|
||||
constexpr size_t kSystemIdLength = sizeof(uint32_t);
|
||||
|
||||
constexpr bool IsSupportedSecurityLevel(CdmSecurityLevel security_level) {
|
||||
constexpr bool IsSupportedCdmSecurityLevel(CdmSecurityLevel security_level) {
|
||||
return security_level == kSecurityLevelL1 ||
|
||||
security_level == kSecurityLevelL2 ||
|
||||
security_level == kSecurityLevelL3;
|
||||
}
|
||||
|
||||
constexpr bool IsSupportedRequestedSecurityLevel(
|
||||
RequestedSecurityLevel security_level) {
|
||||
return security_level == kLevelDefault || security_level == kLevel3;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
SystemIdExtractor::SystemIdExtractor(RequestedSecurityLevel security_level,
|
||||
@@ -45,28 +52,35 @@ bool SystemIdExtractor::ExtractSystemId(uint32_t* system_id) {
|
||||
LOGE("Output |system_id| is null");
|
||||
return false;
|
||||
}
|
||||
if (crypto_session_->GetCachedSystemId(system_id)) {
|
||||
if (!VerifySecurityLevelExpectations()) {
|
||||
// VerifySecurityLevelExpectations() will log details.
|
||||
return false;
|
||||
}
|
||||
if (crypto_session_->IsOpen() &&
|
||||
crypto_session_->GetCachedSystemId(system_id)) {
|
||||
return true;
|
||||
}
|
||||
CdmClientTokenType type = kClientTokenUninitialized;
|
||||
const CdmResponseType status =
|
||||
crypto_session_->GetProvisioningMethod(security_level_, &type);
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to get provisioning method: security_level = %s, status = %d",
|
||||
RequestedSecurityLevelToString(security_level_), status.ToInt());
|
||||
LOGE("Failed to get provisioning method: security_level = %s, status = %s",
|
||||
RequestedSecurityLevelToString(security_level_),
|
||||
status.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
bool success = false;
|
||||
switch (type) {
|
||||
case kClientTokenDrmCert:
|
||||
// TODO: b/309675153 - Extract system id when using DRM reprovisioning.
|
||||
case kClientTokenDrmCertificateReprovisioning:
|
||||
LOGW(
|
||||
"Cannot get a system ID from a DRM certificate, "
|
||||
"using null system ID: security_level = %s",
|
||||
RequestedSecurityLevelToString(security_level_));
|
||||
*system_id = NULL_SYSTEM_ID;
|
||||
return true;
|
||||
case kClientTokenDrmCertificateReprovisioning:
|
||||
success = ExtractSystemIdDrmReprovisioning(system_id);
|
||||
break;
|
||||
case kClientTokenKeybox:
|
||||
success = ExtractSystemIdProv20(system_id);
|
||||
break;
|
||||
@@ -90,7 +104,10 @@ bool SystemIdExtractor::ExtractSystemId(uint32_t* system_id) {
|
||||
// static
|
||||
bool SystemIdExtractor::ExtractSystemIdFromKeyboxData(
|
||||
const std::string& key_data, uint32_t* system_id) {
|
||||
if (system_id == nullptr) return false;
|
||||
if (system_id == nullptr) {
|
||||
LOGE("Output |system_id| is null");
|
||||
return false;
|
||||
}
|
||||
if (key_data.size() < (kKeyboxSystemIdOffset + kSystemIdLength)) {
|
||||
LOGE("Keybox data does not contain system ID: key_data_size = %zu",
|
||||
key_data.size());
|
||||
@@ -106,7 +123,10 @@ bool SystemIdExtractor::ExtractSystemIdFromKeyboxData(
|
||||
// static
|
||||
bool SystemIdExtractor::ExtractSystemIdFromOemCert(const std::string& oem_cert,
|
||||
uint32_t* system_id) {
|
||||
if (system_id == nullptr) return false;
|
||||
if (system_id == nullptr) {
|
||||
LOGE("Output |system_id| is null");
|
||||
return false;
|
||||
}
|
||||
return ExtractExtensionValueFromCertificate(oem_cert,
|
||||
kWidevineSystemIdExtensionOid,
|
||||
kOemCertSystemIdIndex, system_id);
|
||||
@@ -122,8 +142,9 @@ bool SystemIdExtractor::ExtractSystemIdProv20(uint32_t* system_id) {
|
||||
return true;
|
||||
}
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to get keybox data: security_level = %s, status = %d",
|
||||
RequestedSecurityLevelToString(security_level_), status.ToInt());
|
||||
LOGE("Failed to get keybox data: security_level = %s, status = %s",
|
||||
RequestedSecurityLevelToString(security_level_),
|
||||
status.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!ExtractSystemIdFromKeyboxData(key_data, system_id)) {
|
||||
@@ -138,8 +159,9 @@ bool SystemIdExtractor::ExtractSystemIdProv30(uint32_t* system_id) {
|
||||
const CdmResponseType status =
|
||||
crypto_session_->GetTokenFromOemCert(security_level_, &oem_cert);
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to get OEM certificate: security_level = %s, status = %d",
|
||||
RequestedSecurityLevelToString(security_level_), status.ToInt());
|
||||
LOGE("Failed to get OEM certificate: security_level = %s, status = %s",
|
||||
RequestedSecurityLevelToString(security_level_),
|
||||
status.ToString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!ExtractSystemIdFromOemCert(oem_cert, system_id)) {
|
||||
@@ -152,7 +174,7 @@ bool SystemIdExtractor::ExtractSystemIdProv30(uint32_t* system_id) {
|
||||
bool SystemIdExtractor::ExtractSystemIdProv40(uint32_t* system_id) {
|
||||
const CdmSecurityLevel security_level =
|
||||
crypto_session_->GetSecurityLevel(security_level_);
|
||||
if (!IsSupportedSecurityLevel(security_level)) {
|
||||
if (!IsSupportedCdmSecurityLevel(security_level)) {
|
||||
LOGE("Unsupported security level: %s",
|
||||
CdmSecurityLevelToString(security_level));
|
||||
return false;
|
||||
@@ -160,7 +182,7 @@ bool SystemIdExtractor::ExtractSystemIdProv40(uint32_t* system_id) {
|
||||
DeviceFiles real_device_files(fs_);
|
||||
// Mock DeviceFiles for testing.
|
||||
DeviceFiles& device_files =
|
||||
(test_device_files_ ? *test_device_files_ : real_device_files);
|
||||
(test_device_files_ != nullptr ? *test_device_files_ : real_device_files);
|
||||
if (!device_files.Init(security_level)) {
|
||||
LOGE("Failed to initialize device files: security_level = %s",
|
||||
CdmSecurityLevelToString(security_level));
|
||||
@@ -190,4 +212,74 @@ bool SystemIdExtractor::ExtractSystemIdProv40(uint32_t* system_id) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemIdExtractor::ExtractSystemIdDrmReprovisioning(uint32_t* system_id) {
|
||||
std::string token;
|
||||
const CdmResponseType status =
|
||||
crypto_session_->GetTokenFromEmbeddedCertificate(security_level_, &token);
|
||||
if (status != NO_ERROR) {
|
||||
LOGE("Failed to load embedded certificate token");
|
||||
return false;
|
||||
}
|
||||
|
||||
video_widevine::SignedDrmCertificate signed_certificate;
|
||||
video_widevine::DrmCertificate drm_certificate;
|
||||
if (!signed_certificate.ParseFromString(token) ||
|
||||
!drm_certificate.ParseFromString(signed_certificate.drm_certificate())) {
|
||||
LOGE("Failed to parse embedded certificate");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!drm_certificate.has_system_id()) {
|
||||
LOGE("Failed to extract system_id from embedded certificate");
|
||||
return false;
|
||||
}
|
||||
|
||||
*system_id = drm_certificate.system_id();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemIdExtractor::VerifySecurityLevelExpectations() {
|
||||
if (!IsSupportedRequestedSecurityLevel(security_level_)) {
|
||||
LOGE("Unsupported requested extractor security level: %d",
|
||||
static_cast<int>(security_level_));
|
||||
return false;
|
||||
}
|
||||
if (!crypto_session_->IsOpen()) {
|
||||
// Any other issues with the security level should be caught and
|
||||
// handled by the respective extractor methods.
|
||||
return true;
|
||||
}
|
||||
// The SystemIdExtractor is intended to work with unopened
|
||||
// CryptoSessions, but does not restrict this.
|
||||
// If the crypto session is open, it is already tied to a
|
||||
// security level; for the extractor work as expected the
|
||||
// session's security level must be the same as extractor's
|
||||
// requested security level.
|
||||
const CdmSecurityLevel session_security_level =
|
||||
crypto_session_->GetSecurityLevel();
|
||||
if (!IsSupportedCdmSecurityLevel(session_security_level)) {
|
||||
LOGE("Failed to get session security level: %s",
|
||||
CdmSecurityLevelToString(session_security_level));
|
||||
return false;
|
||||
}
|
||||
const CdmSecurityLevel extractor_security_level =
|
||||
crypto_session_->GetSecurityLevel(security_level_);
|
||||
if (!IsSupportedCdmSecurityLevel(extractor_security_level)) {
|
||||
LOGE("Failed to get extractor security level: %s",
|
||||
CdmSecurityLevelToString(extractor_security_level));
|
||||
return false;
|
||||
}
|
||||
if (session_security_level != extractor_security_level) {
|
||||
LOGE(
|
||||
"Extractor and session security levels do not match: "
|
||||
"session_security_level = %s, extractor_security_level = %s, "
|
||||
"requested_security_level = %s",
|
||||
CdmSecurityLevelToString(session_security_level),
|
||||
CdmSecurityLevelToString(extractor_security_level),
|
||||
RequestedSecurityLevelToString(security_level_));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} // namespace wvcdm
|
||||
|
||||
@@ -12,6 +12,8 @@ namespace wvcdm {
|
||||
namespace {
|
||||
const char kEmptyIdRep[] = "<empty>";
|
||||
const char kNullIdRep[] = "<null>";
|
||||
const char kFalseRep[] = "false";
|
||||
const char kTrueRep[] = "true";
|
||||
|
||||
// Thread local buffer used by UnknownEnumValueToString() to represent
|
||||
// unknown enum values.
|
||||
@@ -896,6 +898,8 @@ const char* IdPtrToString(const std::string* id) {
|
||||
return id->empty() ? kEmptyIdRep : id->c_str();
|
||||
}
|
||||
|
||||
const char* BoolToString(bool value) { return value ? kTrueRep : kFalseRep; }
|
||||
|
||||
const char* OemCryptoResultToString(OEMCryptoResult result) {
|
||||
switch (result) {
|
||||
/* OEMCrypto return values */
|
||||
@@ -1031,6 +1035,8 @@ const char* OemCryptoResultToString(OEMCryptoResult result) {
|
||||
return "ERROR_INSUFFICIENT_PRIVILEGE";
|
||||
case OEMCrypto_ERROR_INVALID_KEY:
|
||||
return "ERROR_INVALID_KEY";
|
||||
case OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE:
|
||||
return "OEMCrypto_ERROR_INVALID_OEM_CERTIFICATE";
|
||||
/* ODK return values */
|
||||
case ODK_ERROR_CORE_MESSAGE:
|
||||
return "ERROR_CORE_MESSAGE";
|
||||
|
||||
Reference in New Issue
Block a user