// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine License // Agreement. // // Crypto - wrapper classes for OEMCrypto interface // #include "crypto_session.h" #include #include #include #include #include #include "advance_iv_ctr.h" #include "arraysize.h" #include "cdm_random.h" #include "cdm_usage_table.h" #include "content_key_session.h" #include "crypto_key.h" #include "entitlement_key_session.h" #include "log.h" #include "odk_structs.h" #include "okp_fallback_policy.h" #include "platform.h" #include "privacy_crypto.h" #include "properties.h" #include "pst_report.h" #include "string_conversions.h" #include "wv_cdm_constants.h" // Stringify turns macro arguments into static C strings. // Example: STRINGIFY(this_argument) -> "this_argument" #define STRINGIFY(PARAM) #PARAM namespace { bool WrapIfNecessary(bool ret_value) { return ret_value; } wvcdm::CdmResponseType WrapIfNecessary(wvcdm::CdmResponseEnum ret_value) { return wvcdm::CdmResponseType(ret_value); } wvcdm::CdmSecurityLevel WrapIfNecessary(wvcdm::CdmSecurityLevel ret_value) { return ret_value; } OEMCryptoResult WrapIfNecessary(OEMCryptoResult ret_value) { return ret_value; } } // namespace #define RETURN_IF_NULL(PARAM, ret_value) \ if ((PARAM) == nullptr) { \ LOGE("Output parameter |" STRINGIFY(PARAM) "| not provided"); \ return WrapIfNecessary(ret_value); \ } #define RETURN_IF_UNINITIALIZED(ret_value) \ if (!IsInitialized()) { \ LOGE("Crypto session is not initialized"); \ return WrapIfNecessary(ret_value); \ } #define RETURN_IF_NOT_OPEN(ret_value) \ if (!open_) { \ LOGE("Crypto session is not open"); \ return WrapIfNecessary(ret_value); \ } #define CRYPTO_ERROR(cdm_err, oem_err) \ CdmResponseType(cdm_err, oem_err, __func__) #ifdef HAS_DUAL_KEY /** * Internal only OEMCrypto method. This is called before parsing the license * response to indicate the response uses dual keys. If this isn't called, * it is using single keys. */ extern "C" OEMCryptoResult OEMCrypto_UseSecondaryKey(OEMCrypto_SESSION session, bool dual_key); #endif namespace wvcdm { namespace { using UsageTableLock = std::unique_lock; constexpr size_t KiB = 1024; constexpr size_t MiB = 1024 * 1024; constexpr uint32_t kRsaSignatureLength = 256; constexpr size_t kEstimatedInitialUsageTableHeader = 40; constexpr size_t kAes128BlockSize = 16; constexpr int kMaxTerminateCountDown = 5; const std::string kStringNotAvailable = "NA"; // TODO(b/174412779): Remove when b/170704368 is fixed. // This is a Qualcomm specific error code const int kRsaSsaPssSignatureLengthError = 10085; // Constants relating to OEMCrypto resource rating tiers. These tables are // ordered by resource rating tier from lowest to highest. These should be // updated whenever the supported range of resource rating tiers changes. constexpr size_t kMaxSubsampleRegionSizes[] = { 100 * KiB, // Tier 1 - Low 500 * KiB, // Tier 2 - Medium 1 * MiB, // Tier 3 - High 4 * MiB, // Tier 4 - Very High }; // The +1 in this calculation is because the bounds RESOURCE_RATING_TIER_MAX and // RESOURCE_RATING_TIER_MIN are inclusive. static_assert(wvutil::ArraySize(kMaxSubsampleRegionSizes) == RESOURCE_RATING_TIER_MAX - RESOURCE_RATING_TIER_MIN + 1, "The kMaxSubsampleRegionSizes table needs to be updated to " "reflect the supported range of resource rating tiers"); constexpr size_t kDefaultMaxSubsampleRegionSize = kMaxSubsampleRegionSizes[0]; constexpr size_t kMaxExternalDeviceIdLength = 64; // This maps a few common OEMCryptoResult to CdmResponseType. Many mappings // are not universal but are OEMCrypto method specific. Those will be // specified in the CryptoSession method rather than here. CdmResponseType MapOEMCryptoResult(OEMCryptoResult result, CdmResponseEnum default_status, const char* crypto_session_method) { if (result != OEMCrypto_SUCCESS) { LOGE("Mapping OEMCrypto result: crypto_session_method = %s, result = %d", crypto_session_method ? crypto_session_method : "N/A", static_cast(result)); } CdmResponseEnum status{}; switch (result) { case OEMCrypto_SUCCESS: status = NO_ERROR; break; case OEMCrypto_ERROR_NOT_IMPLEMENTED: status = NOT_IMPLEMENTED_ERROR; break; case OEMCrypto_ERROR_TOO_MANY_SESSIONS: status = INSUFFICIENT_CRYPTO_RESOURCES; break; case OEMCrypto_ERROR_SESSION_LOST_STATE: status = SESSION_LOST_STATE_ERROR; break; case OEMCrypto_ERROR_SYSTEM_INVALIDATED: status = SYSTEM_INVALIDATED_ERROR; break; default: status = default_status; } return CdmResponseType(status, result, crypto_session_method); } void AdvanceDestBuffer(OEMCrypto_DestBufferDesc* dest_buffer, size_t bytes) { switch (dest_buffer->type) { case OEMCrypto_BufferType_Clear: dest_buffer->buffer.clear.clear_buffer += bytes; dest_buffer->buffer.clear.clear_buffer_length -= bytes; return; case OEMCrypto_BufferType_Secure: dest_buffer->buffer.secure.offset += bytes; return; case OEMCrypto_BufferType_Direct: // Nothing to do for this buffer type. return; } LOGE("Unrecognized OEMCryptoBufferType %u - doing nothing", static_cast(dest_buffer->type)); } bool GetGenericSigningAlgorithm(CdmSigningAlgorithm algorithm, OEMCrypto_Algorithm* oec_algorithm) { RETURN_IF_NULL(oec_algorithm, false); if (kSigningAlgorithmHmacSha256 != algorithm) { LOGW("Unrecognized signing algorithm: %d", algorithm); return false; } *oec_algorithm = OEMCrypto_HMAC_SHA256; return true; } bool GetGenericEncryptionAlgorithm(CdmEncryptionAlgorithm algorithm, OEMCrypto_Algorithm* oec_algorithm) { RETURN_IF_NULL(oec_algorithm, false); if (kEncryptionAlgorithmAesCbc128 != algorithm) { LOGW("Unrecognized encryption algorithm: %d", algorithm); return false; } *oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING; return true; } size_t GenericEncryptionBlockSize(CdmEncryptionAlgorithm algorithm) { if (kEncryptionAlgorithmAesCbc128 != algorithm) { LOGW("Unrecognized encryption algorithm: %d", algorithm); return 0; } return kAes128BlockSize; } uint8_t* MutableStringDataPointer(std::string* s) { if (s == nullptr) return nullptr; if (s->empty()) return nullptr; return reinterpret_cast(&s->front()); } } // namespace // CryptoSession variables allocation. wvutil::shared_mutex CryptoSession::static_field_mutex_; wvutil::shared_mutex CryptoSession::oem_crypto_mutex_; bool CryptoSession::initialized_ = false; bool CryptoSession::needs_keybox_provisioning_ = false; int CryptoSession::session_count_ = 0; int CryptoSession::termination_counter_ = 0; std::unique_ptr CryptoSession::usage_table_l1_; std::unique_ptr CryptoSession::usage_table_l3_; std::recursive_mutex CryptoSession::usage_table_mutex_; std::atomic CryptoSession::request_id_index_source_(0); std::unique_ptr CryptoSession::okp_fallback_policy_l1_; size_t GetOffset(std::string message, std::string field) { size_t pos = message.find(field); if (pos == std::string::npos) { LOGE("Cannot find the |field| offset in message: field = %s", IdToString(field)); pos = 0; } return pos; } OEMCrypto_Substring GetSubstring(const std::string& message, const std::string& field, bool set_zero) { OEMCrypto_Substring substring; if (set_zero || field.empty() || message.empty()) { substring.offset = 0; substring.length = 0; } else { size_t pos = message.find(field); if (pos == std::string::npos) { LOGW("Cannot find the |field| substring in message: field = %s", IdToString(field)); substring.offset = 0; substring.length = 0; } else { substring.offset = pos; substring.length = field.length(); } } return substring; } void GenerateMacContext(const std::string& input_context, std::string* deriv_context) { if (!deriv_context) { LOGE("Output parameter |deriv_context| not 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(wvutil::EncodeUint32(kSigningKeySizeBits * 2)); } void GenerateEncryptContext(const std::string& input_context, std::string* deriv_context) { if (!deriv_context) { LOGE("Output parameter |deriv_context| not provided"); return; } const std::string kEncryptionKeyLabel = "ENCRYPTION"; const size_t kEncryptionKeySizeBits = wvcdm::CONTENT_KEY_SIZE * 8; deriv_context->assign(kEncryptionKeyLabel); deriv_context->append(1, '\0'); deriv_context->append(input_context); deriv_context->append(wvutil::EncodeUint32(kEncryptionKeySizeBits)); } OEMCryptoCipherMode ToOEMCryptoCipherMode(CdmCipherMode cipher_mode) { return cipher_mode == kCipherModeCtr ? OEMCrypto_CipherMode_CENC : OEMCrypto_CipherMode_CBCS; } CryptoSession::CryptoSession(metrics::CryptoMetrics* metrics) : metrics_(metrics), system_id_(NULL_SYSTEM_ID), open_(false), pre_provision_token_type_(kClientTokenUninitialized), update_usage_table_after_close_session_(false), is_destination_buffer_type_valid_(false), requested_security_level_(kLevelDefault), api_version_(0), max_subsample_region_size_(0) { assert(metrics); Init(); life_span_.Start(); } CryptoSession::~CryptoSession() { if (open_) { Close(); } WithStaticFieldWriteLock("~CryptoSession", [&] { if (session_count_ > 0) { --session_count_; } else { LOGE("Invalid crypto session count: session_count_ = %d", session_count_); } }); TryTerminate(); M_RECORD(metrics_, crypto_session_life_span_, life_span_.AsMs()); } CdmResponseType CryptoSession::GetProvisioningMethod( RequestedSecurityLevel requested_security_level, CdmClientTokenType* token_type) { OEMCrypto_ProvisioningMethod method; WithOecReadLock("GetProvisioningMethod", [&] { method = OEMCrypto_GetProvisioningMethod(requested_security_level); }); metrics_->oemcrypto_provisioning_method_.Record(method); CdmClientTokenType type; switch (method) { case OEMCrypto_OEMCertificate: type = kClientTokenOemCert; break; case OEMCrypto_Keybox: type = kClientTokenKeybox; break; case OEMCrypto_DrmCertificate: type = kClientTokenDrmCert; break; case OEMCrypto_BootCertificateChain: type = kClientTokenBootCertChain; break; case OEMCrypto_ProvisioningError: default: if (static_cast(method) == 0 && needs_keybox_provisioning_) { LOGW("Overriding provisioning method, assuming keybox"); type = kClientTokenKeybox; break; } LOGE("OEMCrypto_GetProvisioningMethod failed: method = %d", static_cast(method)); metrics_->oemcrypto_provisioning_method_.SetError(method); return CdmResponseType(GET_PROVISIONING_METHOD_ERROR); } *token_type = type; return CdmResponseType(NO_ERROR); } void CryptoSession::Init() { LOGV("Initializing crypto session"); bool initialized = false; WithStaticFieldWriteLock("Init", [&] { session_count_ += 1; if (!initialized_) { std::string sandbox_id; OEMCryptoResult sts; WithOecWriteLock("Init", [&] { if (Properties::GetSandboxId(&sandbox_id) && !sandbox_id.empty()) { sts = OEMCrypto_SetSandbox( reinterpret_cast(sandbox_id.c_str()), sandbox_id.length()); metrics_->oemcrypto_set_sandbox_.Record(sandbox_id); } M_TIME(sts = OEMCrypto_InitializeAndCheckKeybox( &needs_keybox_provisioning_), metrics_, oemcrypto_initialize_, sts); }); if (OEMCrypto_SUCCESS != sts) { LOGE("OEMCrypto_Initialize failed: status = %d", static_cast(sts)); return; } termination_counter_ = Properties::delay_oem_crypto_termination() ? kMaxTerminateCountDown : 0; initialized_ = true; initialized = true; } }); if (initialized) { CacheVersion(); } } void CryptoSession::ReinitializeForTest() { if (initialized_) { const OEMCryptoResult status = OEMCrypto_Terminate(); if (OEMCrypto_SUCCESS != status) { LOGE("OEMCrypto_Terminate failed: %d", status); return; } initialized_ = false; // Tables will be reinitialized by tests when needed. usage_table_l1_.reset(); usage_table_l3_.reset(); } // Give up if we cannot initialize at all. const OEMCryptoResult status = OEMCrypto_Initialize(); if (OEMCrypto_SUCCESS != status) { LOGE("OEMCrypto_Initialize failed: %d", status); return; } OEMCrypto_SetMaxAPIVersion(ODK_MAJOR_VERSION); OEMCrypto_EnterTestMode(); initialized_ = true; // For integration and unit tests we will install a test keybox and do not // need to do keybox provisioning. needs_keybox_provisioning_ = false; // This was skipped in Init because initialization failed. CacheVersion(); } void CryptoSession::CacheVersion() { uint32_t version; std::string api_version = CryptoSession::GetApiVersion(kLevelDefault, &version) ? std::to_string(version) : kStringNotAvailable; std::string api_minor_version = CryptoSession::GetApiMinorVersion(kLevelDefault, &version) ? std::to_string(version) : kStringNotAvailable; LOGD("OEMCrypto version (default security level): %s.%s", api_version.c_str(), api_minor_version.c_str()); api_version = CryptoSession::GetApiVersion(kLevel3, &version) ? std::to_string(version) : kStringNotAvailable; api_minor_version = CryptoSession::GetApiMinorVersion(kLevel3, &version) ? std::to_string(version) : kStringNotAvailable; LOGD("OEMCrypto version (L3 security level): %s.%s", api_version.c_str(), api_minor_version.c_str()); if (needs_keybox_provisioning_) { WithStaticFieldWriteLock("SystemFallbackPolicy", [&] { if (!okp_fallback_policy_l1_) { LOGD("OEMCrypto needs keybox provisioning"); // Only create once. Possible that OEMCrypto is initialized // and terminated many times over the life cycle of the OTA // keybox provisioning process. okp_fallback_policy_l1_ = okp::SystemFallbackPolicy::Create(); if (okp_fallback_policy_l1_) okp_fallback_policy_l1_->MarkNeedsProvisioning(); } }); } } bool CryptoSession::TryTerminate() { LOGV("Terminating crypto session"); const bool terminated = WithStaticFieldWriteLock("TryTerminate", [&] { LOGV( "Terminating crypto session: initialized_ = %s, session_count_ = %d, " "termination_counter_ = %d", initialized_ ? "true" : "false", session_count_, termination_counter_); if (termination_counter_ > 0) { --termination_counter_; } if (session_count_ > 0 || termination_counter_ > 0 || !initialized_) return false; const OEMCryptoResult sts = WithOecWriteLock("Terminate", [&] { return OEMCrypto_Terminate(); }); if (OEMCrypto_SUCCESS != sts) { LOGE("OEMCrypto_Terminate failed: status = %d", static_cast(sts)); } initialized_ = false; return true; }); if (terminated) { UsageTableLock lock(usage_table_mutex_); usage_table_l1_.reset(); usage_table_l3_.reset(); } return terminated; } void CryptoSession::DisableDelayedTermination() { LOGV("Disable delayed termination"); WithStaticFieldWriteLock("DisableDelayedTermination", [&] { termination_counter_ = 0; }); } bool CryptoSession::SetUpUsageTable( RequestedSecurityLevel requested_security_level) { if (usage_table_ != nullptr) { LOGE("Usage table is already set up for the current crypto session"); return false; } const CdmSecurityLevel security_level = GetSecurityLevel(requested_security_level); if (security_level != kSecurityLevelL1 && security_level != kSecurityLevelL3) { LOGD("Unsupported security level for usage support: security_level = %d", static_cast(security_level)); return false; } // Check if usage support is available. bool supports_usage_table = false; if (!HasUsageTableSupport(requested_security_level, &supports_usage_table)) { metrics_->oemcrypto_usage_table_support_.SetError( USAGE_INFORMATION_SUPPORT_FAILED); return false; } metrics_->oemcrypto_usage_table_support_.Record( supports_usage_table ? kUsageEntrySupport : kNonSecureUsageSupport); if (!supports_usage_table) { return false; } LOGV("Usage table lock: SetUpUsageTable()"); UsageTableLock auto_lock(usage_table_mutex_); // TODO(b/141350978): Prevent any recursive logic. // Manipulate only the usage table for the requested security level. std::unique_ptr& table = security_level == kSecurityLevelL1 ? usage_table_l1_ : usage_table_l3_; if (!table) { // This may be called twice within the same thread when the table // is initialized. On the second call |header| will not be null, // causing this block to be skipped. table.reset(new CdmUsageTable()); if (!table->Init(security_level, this)) { LOGE("Failed to initialize and sync usage usage table"); // Must be cleared globally to prevent the next session to be // opened from using the invalid CdmUsageTable. table.reset(); return false; } } usage_table_ = table.get(); metrics_->usage_table_header_initial_size_.Record(usage_table_->size()); return true; } CdmResponseType CryptoSession::GetTokenFromKeybox( RequestedSecurityLevel requested_security_level, std::string* key_data) { RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(key_data, PARAMETER_NULL); LOGV("requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); // Devices with an invalid L1 keybox which support OTA keybox // provisioning don't have keybox data. const bool keybox_provisioning_required = WithStaticFieldReadLock( "GetTokenFromKeybox - keybox_provisioning_required", [&] { if (requested_security_level_ != kLevelDefault) return false; return needs_keybox_provisioning_; }); if (keybox_provisioning_required) return CdmResponseType(NEED_PROVISIONING); size_t key_data_length = KEYBOX_KEY_DATA_SIZE; key_data->assign(key_data_length, '\0'); OEMCryptoResult status; WithOecReadLock("GetTokenFromKeybox", [&] { M_TIME(status = OEMCrypto_GetKeyData( reinterpret_cast(&key_data->front()), &key_data_length, requested_security_level), metrics_, oemcrypto_get_key_data_, status, metrics::Pow2Bucket(key_data_length)); }); if (OEMCrypto_SUCCESS == status) { key_data->resize(key_data_length); return CdmResponseType(NO_ERROR); } key_data->clear(); return MapOEMCryptoResult(status, GET_TOKEN_FROM_KEYBOX_ERROR, "GetTokenFromKeybox"); } CdmResponseType CryptoSession::GetTokenFromOemCert( RequestedSecurityLevel requested_security_level, std::string* oem_cert) { RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(oem_cert, PARAMETER_NULL); LOGV("requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); const bool cache_success = WithOecSessionLock("GetTokenFromOemCert - check cached", [&] { if (oem_token_.empty()) { return false; } oem_cert->assign(oem_token_); return true; }); if (cache_success) return CdmResponseType(NO_ERROR); size_t oem_cert_length = CERTIFICATE_DATA_SIZE; oem_cert->assign(oem_cert_length, '\0'); OEMCryptoResult status = WithOecReadLock("GetTokenFromOemCert - attempt 1", [&] { return OEMCrypto_GetOEMPublicCertificate( reinterpret_cast(&oem_cert->front()), &oem_cert_length, requested_security_level); }); metrics_->oemcrypto_get_oem_public_certificate_.Increment(status); if (status == OEMCrypto_ERROR_SHORT_BUFFER) { oem_cert->assign(oem_cert_length, '\0'); status = WithOecReadLock("GetTokenFromOemCert - attempt 2", [&] { return OEMCrypto_GetOEMPublicCertificate( reinterpret_cast(&oem_cert->front()), &oem_cert_length, requested_security_level); }); metrics_->oemcrypto_get_oem_public_certificate_.Increment(status); } if (status == OEMCrypto_SUCCESS) { oem_cert->resize(oem_cert_length); WithOecSessionLock("GetTokenFromOemCert - set cache", [&] { oem_token_ = *oem_cert; }); return CdmResponseType(NO_ERROR); } oem_cert->clear(); return MapOEMCryptoResult(status, GET_TOKEN_FROM_OEM_CERT_ERROR, "GetTokenFromOemCert"); } CdmResponseType CryptoSession::GetProvisioningToken( std::string* token, std::string* additional_token) { RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); return GetProvisioningToken(requested_security_level_, token, additional_token); } CdmResponseType CryptoSession::GetProvisioningToken( RequestedSecurityLevel requested_security_level, std::string* token, std::string* additional_token) { if (token == nullptr || additional_token == nullptr) { metrics_->crypto_session_get_token_.Increment( CdmResponseType(PARAMETER_NULL)); RETURN_IF_NULL(token, PARAMETER_NULL); RETURN_IF_NULL(additional_token, PARAMETER_NULL); } if (!IsInitialized()) { metrics_->crypto_session_get_token_.Increment( CdmResponseType(CRYPTO_SESSION_NOT_INITIALIZED)); return CdmResponseType(CRYPTO_SESSION_NOT_INITIALIZED); } CdmResponseType status(UNKNOWN_CLIENT_TOKEN_TYPE); if (pre_provision_token_type_ == kClientTokenKeybox) { status = GetTokenFromKeybox(requested_security_level, token); } else if (pre_provision_token_type_ == kClientTokenOemCert) { status = GetTokenFromOemCert(requested_security_level, token); } else if (pre_provision_token_type_ == kClientTokenBootCertChain) { status = GetBootCertificateChain(requested_security_level, token, additional_token); } metrics_->crypto_session_get_token_.Increment(status); return status; } CdmSecurityLevel CryptoSession::GetSecurityLevel() { LOGV("Getting security level"); RETURN_IF_NOT_OPEN(kSecurityLevelUninitialized); return GetSecurityLevel(requested_security_level_); } CdmSecurityLevel CryptoSession::GetSecurityLevel( RequestedSecurityLevel requested_security_level) { LOGV("Getting security level: requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); RETURN_IF_UNINITIALIZED(kSecurityLevelUninitialized); const OEMCrypto_Security_Level level = WithOecReadLock( "GetSecurityLevel", [&] { return OEMCrypto_SecurityLevel(requested_security_level); }); if (level == 0) { LOGE("Security level is unknown: requested_security_level = %d", static_cast(requested_security_level)); return kSecurityLevelUnknown; } if (level == OEMCrypto_Level1) { return kSecurityLevelL1; } if (level == OEMCrypto_Level2) { return kSecurityLevelL2; } if (level == OEMCrypto_Level3) { return kSecurityLevelL3; } LOGE( "Ill-formed security level: " "level = \"L%u\", requested_security_level = %s", static_cast(level), RequestedSecurityLevelToString(requested_security_level)); return kSecurityLevelUnknown; } CdmResponseType CryptoSession::GetInternalDeviceUniqueId( std::string* device_id) { RETURN_IF_NULL(device_id, PARAMETER_NULL); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); size_t device_id_length = 64; device_id->assign(device_id_length, '\0'); OEMCryptoResult sts = WithOecReadLock("GetInternalDeviceUniqueId Attempt 1", [&] { return OEMCrypto_GetDeviceID( reinterpret_cast(&device_id->front()), &device_id_length, requested_security_level_); }); metrics_->oemcrypto_get_device_id_.Increment(sts); if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { device_id->resize(device_id_length, '\0'); sts = WithOecReadLock("GetInternalDeviceUniqueId Attempt 2", [&] { return OEMCrypto_GetDeviceID( reinterpret_cast(&device_id->front()), &device_id_length, requested_security_level_); }); metrics_->oemcrypto_get_device_id_.Increment(sts); } // Either the authentication root is a keybox or the device has transitioned // to using OEMCerts. // OEMCryptos, like the Level 3, that transition from Provisioning 2.0 to // 3.0 would have a new device ID, which would affect SPOID calculation. // In order to resolve this, we use OEMCrypto_GetDeviceID if it is // implemented, so the OEMCrypto can continue to report the same device ID. if (sts == OEMCrypto_SUCCESS) { device_id->resize(device_id_length); return CdmResponseType(NO_ERROR); } device_id->clear(); if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED && pre_provision_token_type_ == kClientTokenOemCert) { return GetTokenFromOemCert(requested_security_level_, device_id); } const bool use_null_device_id = WithStaticFieldReadLock( "GetInternalDeviceUniqueId() use_null_device_id", [&] { if (requested_security_level_ != kLevelDefault) return false; if (!needs_keybox_provisioning_) return false; if (sts != OEMCrypto_ERROR_KEYBOX_INVALID && sts != OEMCrypto_ERROR_NO_DEVICEID) { // Logging other error for debugging, but null device // ID should still be returned. LOGE("Unexpected error: sts = %d", sts); } return true; }); if (use_null_device_id) { LOGD("Using null device ID"); constexpr size_t kKeyboxDeviceIdLength = 32; device_id->assign(kKeyboxDeviceIdLength, '\0'); return CdmResponseType(NO_ERROR); } return MapOEMCryptoResult(sts, GET_DEVICE_ID_ERROR, "GetInternalDeviceUniqueId"); } CdmResponseType CryptoSession::GetExternalDeviceUniqueId( std::string* device_id) { RETURN_IF_NULL(device_id, PARAMETER_NULL); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); const CdmResponseType status = GetInternalDeviceUniqueId(device_id); if (status != NO_ERROR) return status; if (device_id->size() > kMaxExternalDeviceIdLength) { // To keep the size of the value passed back to the application down, hash // the large OEM Public Cert to a smaller value. *device_id = Sha256Hash(*device_id); } return CdmResponseType(NO_ERROR); } bool CryptoSession::GetApiVersion(uint32_t* version) { LOGV("Getting API version"); RETURN_IF_NOT_OPEN(false); return GetApiVersion(requested_security_level_, version); } bool CryptoSession::GetApiVersion(RequestedSecurityLevel security_level, uint32_t* version) { LOGV("Getting API version: security_level = %s", RequestedSecurityLevelToString(security_level)); if (!version) { LOGE("Output parameter |version| not provided"); return false; } RETURN_IF_UNINITIALIZED(false); WithOecReadLock("GetApiVersion", [&] { *version = OEMCrypto_APIVersion(security_level); }); // Record the version into the metrics. metrics_->oemcrypto_api_version_.Record(*version); return true; } bool CryptoSession::GetApiMinorVersion(RequestedSecurityLevel security_level, uint32_t* minor_version) { LOGV("Getting API minor version: security_level = %s", RequestedSecurityLevelToString(security_level)); if (!minor_version) { LOGE("Output parameter |minor_version| not provided"); return false; } RETURN_IF_UNINITIALIZED(false); WithOecReadLock("GetApiMinorVersion", [&] { *minor_version = OEMCrypto_MinorAPIVersion(security_level); }); // Record the minor version into the metrics. metrics_->oemcrypto_minor_api_version_.Record(*minor_version); return true; } bool CryptoSession::GetCachedSystemId(uint32_t* system_id) { RETURN_IF_NULL(system_id, false); RETURN_IF_NOT_OPEN(false); if (system_id_ == NULL_SYSTEM_ID) return false; *system_id = system_id_; return true; } void CryptoSession::SetSystemId(uint32_t system_id) { if (!IsOpen()) return; // Ignore silently. system_id_ = system_id; metrics_->crypto_session_system_id_.Record(system_id_); } CdmResponseType CryptoSession::GetProvisioningId(std::string* provisioning_id) { RETURN_IF_NULL(provisioning_id, PARAMETER_NULL); RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); if (pre_provision_token_type_ == kClientTokenOemCert || pre_provision_token_type_ == kClientTokenBootCertChain) { // OEM Cert and BCC devices have no provisioning-unique ID embedded in // them, so we synthesize one by using the External Device-Unique ID // and inverting all the bits. const CdmResponseType status = GetExternalDeviceUniqueId(provisioning_id); if (status != NO_ERROR) return status; for (char& c : *provisioning_id) { c ^= 0xff; } return CdmResponseType(NO_ERROR); } if (pre_provision_token_type_ == kClientTokenKeybox) { std::string token; CdmResponseType status = GetTokenFromKeybox(requested_security_level_, &token); if (status != NO_ERROR) return status; if (token.size() < 24) { LOGE("Keybox token size too small: %zu", token.size()); return CdmResponseType(KEYBOX_TOKEN_TOO_SHORT); } provisioning_id->assign(reinterpret_cast(&token[8]), 16); return CdmResponseType(NO_ERROR); } LOGE("Unsupported pre-provision token type: %d", static_cast(pre_provision_token_type_)); return CdmResponseType(UNKNOWN_CLIENT_TOKEN_TYPE); } uint8_t CryptoSession::GetSecurityPatchLevel() { uint8_t patch; WithOecReadLock("GetSecurityPatchLevel", [&] { patch = OEMCrypto_Security_Patch_Level(requested_security_level_); }); metrics_->oemcrypto_security_patch_level_.Record(patch); return patch; } CdmResponseType CryptoSession::Open( RequestedSecurityLevel requested_security_level) { LOGD("Opening crypto session: requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); if (open_) return CdmResponseType(NO_ERROR); if (!SetUpUsageTable(requested_security_level)) { // Ignore errors since we do not know when a session is opened, // if it is intended to be used for offline/usage session related // or otherwise. LOGW("Session opened without a usage table"); } CdmResponseType result = GetProvisioningMethod(requested_security_level, &pre_provision_token_type_); if (result != NO_ERROR) return result; OEMCrypto_SESSION sid; requested_security_level_ = requested_security_level; OEMCryptoResult sts; WithOecWriteLock("Open() calling OEMCrypto_OpenSession", [&] { sts = OEMCrypto_OpenSession(&sid, requested_security_level); }); if (sts != OEMCrypto_SUCCESS) { WithStaticFieldReadLock( "Open() reporting OEMCrypto_OpenSession Failure", [&] { LOGE( "OEMCrypto_Open failed: status = %d, session_count_ = %d," " initialized_ = %s", static_cast(sts), session_count_, initialized_ ? "true" : "false"); }); return MapOEMCryptoResult(sts, OPEN_CRYPTO_SESSION_ERROR, "Open"); } WithStaticFieldWriteLock("Open() termination_counter", [&] { termination_counter_ = Properties::delay_oem_crypto_termination() ? kMaxTerminateCountDown : 0; }); oec_session_id_ = static_cast(sid); LOGV("Opened session: id = %u", oec_session_id_); open_ = true; // Set up request ID uint64_t request_id_base = wvutil::CdmRandom::RandomInRange(std::numeric_limits::max()); uint64_t request_id_index = request_id_index_source_.fetch_add(1, std::memory_order_relaxed); request_id_ = wvutil::HexEncode(reinterpret_cast(&request_id_base), sizeof(request_id_base)) + wvutil::HexEncode(reinterpret_cast(&request_id_index), sizeof(request_id_index)); // Initialize key session WithOecSessionLock("Open() calling key_session_.reset()", [&] { key_session_.reset(new ContentKeySession(requested_security_level_, oec_session_id_, metrics_)); }); if (!GetApiVersion(&api_version_)) { LOGE("Failed to get API version"); return CdmResponseType(USAGE_SUPPORT_GET_API_FAILED); } return CdmResponseType(NO_ERROR); } void CryptoSession::Close() { LOGV("Closing crypto session: id = %u, open = %s", oec_session_id_, open_ ? "true" : "false"); if (!open_) return; const OEMCryptoResult close_sts = WithOecWriteLock( "Close", [&] { return OEMCrypto_CloseSession(oec_session_id_); }); metrics_->oemcrypto_close_session_.Increment(close_sts); // Clear cached values. has_usage_table_support_ = kBooleanUnset; oem_token_.clear(); system_id_ = NULL_SYSTEM_ID; pre_provision_token_type_ = kClientTokenUninitialized; if (close_sts != OEMCrypto_SUCCESS) { LOGW("OEMCrypto_CloseSession failed: status = %d", static_cast(close_sts)); } switch (close_sts) { case OEMCrypto_SUCCESS: case OEMCrypto_ERROR_INVALID_SESSION: case OEMCrypto_ERROR_SYSTEM_INVALIDATED: usage_table_ = nullptr; open_ = false; break; case OEMCrypto_ERROR_CLOSE_SESSION_FAILED: default: // empty case break; } } CdmResponseType CryptoSession::PrepareAndSignLicenseRequest( const std::string& message, std::string* core_message, std::string* signature, bool& should_specify_algorithm, OEMCrypto_SignatureHashAlgorithm& algorithm) { LOGV("Preparing and signing license request: id = %u", oec_session_id_); RETURN_IF_NULL(signature, PARAMETER_NULL); RETURN_IF_NULL(core_message, PARAMETER_NULL); RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); OEMCryptoResult sts; WithOecSessionLock("GetSignatureHashAlgorithm", [&] { sts = OEMCrypto_GetSignatureHashAlgorithm(oec_session_id_, &algorithm); }); metrics_->oemcrypto_get_signature_hash_algorithm_.Increment(sts, algorithm); if (sts == OEMCrypto_SUCCESS) { should_specify_algorithm = true; } else if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { should_specify_algorithm = false; } else { return MapOEMCryptoResult(sts, GET_SIGNATURE_HASH_ALGORITHM_ERROR_1, "PrepareAndSignLicenseRequest"); } size_t signature_length = 0; size_t core_message_length = 0; *core_message = ""; std::string combined_message = *core_message + message; // First call is intended to determine the required size of the // output buffers. WithOecSessionLock("PrepareAndSignLicenseRequest", [&] { M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest( oec_session_id_, reinterpret_cast( const_cast(combined_message.data())), combined_message.size(), &core_message_length, nullptr, &signature_length), metrics_, oemcrypto_prep_and_sign_license_request_, sts); }); if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { // TODO(b/174412779): Remove when b/170704368 is fixed. // Temporary workaround. If this error is returned the only way to // recover is for the app to reprovision. if (static_cast(sts) == kRsaSsaPssSignatureLengthError) { LOGE("OEMCrypto PrepareAndSignLicenseRequest result = %d", static_cast(sts)); return CRYPTO_ERROR(NEED_PROVISIONING, sts); } return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "PrepareAndSignLicenseRequest"); } // Resize. core_message->resize(core_message_length); signature->resize(signature_length); combined_message = *core_message + message; WithOecSessionLock("PrepareAndSignLicenseRequest", [&] { M_TIME(sts = OEMCrypto_PrepAndSignLicenseRequest( oec_session_id_, reinterpret_cast( const_cast(combined_message.data())), combined_message.size(), &core_message_length, reinterpret_cast(const_cast(signature->data())), &signature_length), metrics_, oemcrypto_prep_and_sign_license_request_, sts); }); if (OEMCrypto_SUCCESS == sts) { signature->resize(signature_length); *core_message = std::move(combined_message); // Truncate combined message to only contain the core message. core_message->resize(core_message_length); return CdmResponseType(NO_ERROR); } // TODO(b/174412779): Remove when b/170704368 is fixed. // Temporary workaround. If this error is returned the only way to // recover is for the app to reprovision. if (static_cast(sts) == kRsaSsaPssSignatureLengthError) { LOGE("OEMCrypto PrepareAndSignLicenseRequest result = %d", static_cast(sts)); return CRYPTO_ERROR(NEED_PROVISIONING, sts); } return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "PrepareAndSignLicenseRequest"); } #ifdef HAS_DUAL_KEY CdmResponseType CryptoSession::UseSecondaryKey(bool dual_key) { OEMCryptoResult sts; WithOecSessionLock("UseSecondaryKey", [&] { sts = OEMCrypto_UseSecondaryKey(oec_session_id_, dual_key); }); return MapOEMCryptoResult(sts, LOAD_KEY_ERROR, "UseSecondaryKey"); } #else CdmResponseType CryptoSession::UseSecondaryKey(bool /* dual_key */) { return CdmResponseType(NO_ERROR); } #endif CdmResponseType CryptoSession::LoadLicense(const std::string& signed_message, const std::string& core_message, const std::string& signature, CdmLicenseKeyType key_type) { LOGV("Loading license: id = %u", oec_session_id_); const std::string combined_message = core_message + signed_message; OEMCryptoResult sts; WithOecSessionLock("LoadLicense", [&] { if (key_type == kLicenseKeyTypeEntitlement && key_session_->Type() != KeySession::kEntitlement) { key_session_.reset(new EntitlementKeySession(requested_security_level_, oec_session_id_, metrics_)); } M_TIME(sts = OEMCrypto_LoadLicense( oec_session_id_, reinterpret_cast(combined_message.data()), combined_message.size(), core_message.size(), reinterpret_cast(signature.data()), signature.size()), metrics_, oemcrypto_load_license_, sts); }); switch (sts) { case OEMCrypto_SUCCESS: return CdmResponseType(KEY_ADDED); case OEMCrypto_ERROR_BUFFER_TOO_LARGE: LOGE("LoadLicense buffer too large: size = %zu", combined_message.size()); return CRYPTO_ERROR(LOAD_LICENSE_ERROR, sts); case OEMCrypto_ERROR_TOO_MANY_KEYS: LOGE("Too many keys in license"); return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts); default: break; } return MapOEMCryptoResult(sts, LOAD_LICENSE_ERROR, "LoadLicense"); } CdmResponseType CryptoSession::PrepareAndSignRenewalRequest( const std::string& message, std::string* core_message, std::string* signature) { LOGV("Preparing and signing renewal request: id = %u", oec_session_id_); if (signature == nullptr) { LOGE("Output parameter |signature| not provided"); return CdmResponseType(PARAMETER_NULL); } if (core_message == nullptr) { LOGE("Output parameter |core_message| not provided"); return CdmResponseType(PARAMETER_NULL); } OEMCryptoResult sts; size_t signature_length = 0; size_t core_message_length = 0; *core_message = ""; std::string combined_message = *core_message + message; // First call is intended to determine the required size of the // output buffers. WithOecSessionLock("PrepareAndSignRenewalRequest", [&] { M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest( oec_session_id_, reinterpret_cast( const_cast(combined_message.data())), combined_message.size(), &core_message_length, nullptr, &signature_length), metrics_, oemcrypto_prep_and_sign_renewal_request_, sts); }); if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "PrepareAndSignRenewalRequest"); } // Resize. core_message->resize(core_message_length); signature->resize(signature_length); combined_message = *core_message + message; WithOecSessionLock("PrepareAndSignRenewalRequest", [&] { M_TIME(sts = OEMCrypto_PrepAndSignRenewalRequest( oec_session_id_, reinterpret_cast( const_cast(combined_message.data())), combined_message.size(), &core_message_length, reinterpret_cast(const_cast(signature->data())), &signature_length), metrics_, oemcrypto_prep_and_sign_renewal_request_, sts); }); if (OEMCrypto_SUCCESS == sts) { signature->resize(signature_length); *core_message = std::move(combined_message); // Truncate combined message to only contain the core message. core_message->resize(core_message_length); return CdmResponseType(NO_ERROR); } return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "PrepareAndSignRenewalRequest"); } CdmResponseType CryptoSession::LoadRenewal(const std::string& signed_message, const std::string& core_message, const std::string& signature) { LOGV("Loading license renewal: id = %u", oec_session_id_); const std::string combined_message = core_message + signed_message; OEMCryptoResult sts; WithOecSessionLock("LoadRenewal", [&] { M_TIME(sts = OEMCrypto_LoadRenewal( oec_session_id_, reinterpret_cast(combined_message.data()), combined_message.size(), core_message.size(), reinterpret_cast(signature.data()), signature.size()), metrics_, oemcrypto_load_renewal_, sts); }); if (sts == OEMCrypto_SUCCESS) { return CdmResponseType(KEY_ADDED); } if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) { LOGE("Buffer too large: size = %zu", combined_message.size()); return CRYPTO_ERROR(LOAD_RENEWAL_ERROR, sts); } return MapOEMCryptoResult(sts, LOAD_RENEWAL_ERROR, "LoadRenewal"); } CdmResponseType CryptoSession::PrepareAndSignProvisioningRequest( const std::string& message, std::string* core_message, std::string* signature, bool& should_specify_algorithm, OEMCrypto_SignatureHashAlgorithm& algorithm) { LOGV("Preparing and signing provisioning request: id = %u", oec_session_id_); if (signature == nullptr) { LOGE("Output parameter |signature| not provided"); return CdmResponseType(PARAMETER_NULL); } if (core_message == nullptr) { LOGE("Output parameter |core_message| not provided"); return CdmResponseType(PARAMETER_NULL); } OEMCryptoResult sts; if (pre_provision_token_type_ == kClientTokenKeybox) { should_specify_algorithm = false; const CdmResponseType status = GenerateDerivedKeys(message); if (status != NO_ERROR) return status; } else if (pre_provision_token_type_ == kClientTokenOemCert) { should_specify_algorithm = true; WithOecSessionLock("LoadOEMPrivateKey", [&] { sts = OEMCrypto_LoadOEMPrivateKey(oec_session_id_); }); if (sts != OEMCrypto_SUCCESS) { return MapOEMCryptoResult(sts, GET_TOKEN_FROM_OEM_CERT_ERROR, "PrepareAndSignProvisioningRequest"); } } else if (pre_provision_token_type_ == kClientTokenBootCertChain) { should_specify_algorithm = true; // Do nothing here. The key to signing the provisioning 4.0 request for each // stage has been loaded already when it was generated by OEMCrypto. } else { LOGE("Unknown method %d", pre_provision_token_type_); return CdmResponseType(UNKNOWN_CLIENT_TOKEN_TYPE); } if (should_specify_algorithm) { WithOecSessionLock("GetSignatureHashAlgorithm", [&] { sts = OEMCrypto_GetSignatureHashAlgorithm(oec_session_id_, &algorithm); }); metrics_->oemcrypto_get_signature_hash_algorithm_.Increment(sts, algorithm); if (sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { should_specify_algorithm = false; } else if (sts != OEMCrypto_SUCCESS) { return MapOEMCryptoResult(sts, GET_SIGNATURE_HASH_ALGORITHM_ERROR_3, "PrepareAndSignProvisioningRequest"); } } size_t signature_length = 0; size_t core_message_length = 0; *core_message = ""; std::string combined_message = *core_message + message; // First call is intended to determine the required size of the // output buffers. WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] { M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest( oec_session_id_, reinterpret_cast( const_cast(combined_message.data())), combined_message.size(), &core_message_length, nullptr, &signature_length), metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts); }); if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "PrepareAndSignProvisioningRequest"); } // Resize. core_message->resize(core_message_length); signature->resize(signature_length); combined_message = *core_message + message; WithOecSessionLock("PrepareAndSignProvisioningRequest", [&] { M_TIME(sts = OEMCrypto_PrepAndSignProvisioningRequest( oec_session_id_, reinterpret_cast( const_cast(combined_message.data())), combined_message.size(), &core_message_length, reinterpret_cast(const_cast(signature->data())), &signature_length), metrics_, oemcrypto_prep_and_sign_provisioning_request_, sts); }); if (OEMCrypto_SUCCESS == sts) { signature->resize(signature_length); *core_message = std::move(combined_message); // Truncate combined message to only contain the core message. core_message->resize(core_message_length); return CdmResponseType(NO_ERROR); } return MapOEMCryptoResult(sts, GENERATE_SIGNATURE_ERROR, "PrepareAndSignProvisioningRequest"); } CdmResponseType CryptoSession::LoadEntitledContentKeys( const std::vector& key_array) { const OEMCryptoResult sts = WithOecSessionLock( "LoadEntitledContentKeys", [&] { return key_session_->LoadEntitledContentKeys(key_array); }); switch (sts) { case OEMCrypto_SUCCESS: return CdmResponseType(KEY_ADDED); case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts); case OEMCrypto_ERROR_INVALID_CONTEXT: return CRYPTO_ERROR(NOT_AN_ENTITLEMENT_SESSION, sts); case OEMCrypto_KEY_NOT_ENTITLED: return CRYPTO_ERROR(NO_MATCHING_ENTITLEMENT_KEY, sts); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts); default: return CRYPTO_ERROR(LOAD_ENTITLED_CONTENT_KEYS_ERROR, sts); } } CdmResponseType CryptoSession::LoadCertificatePrivateKey( const CryptoWrappedKey& private_key) { const OEMCrypto_PrivateKeyType key_type = (private_key.type() == CryptoWrappedKey::kEcc) ? OEMCrypto_ECC_Private_Key : OEMCrypto_RSA_Private_Key; const std::string& wrapped_key = private_key.key(); LOGV("Loading device DRM key: id = %u", oec_session_id_); // TODO(b/140813486): determine if cert is RSA or ECC. OEMCryptoResult sts; WithOecSessionLock( "LoadCertificatePrivateKey() calling OEMCrypto_LoadDRMPrivateKey()", [&] { M_TIME(sts = OEMCrypto_LoadDRMPrivateKey( oec_session_id_, key_type, reinterpret_cast(wrapped_key.data()), wrapped_key.size()), metrics_, oemcrypto_load_device_drm_key_, sts); }); return MapOEMCryptoResult(sts, LOAD_DEVICE_RSA_KEY_ERROR, "LoadCertificatePrivateKey"); } CdmResponseType CryptoSession::GetBootCertificateChain( std::string* bcc, std::string* additional_signature) { RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); return GetBootCertificateChain(requested_security_level_, bcc, additional_signature); } CdmResponseType CryptoSession::GetBootCertificateChain( RequestedSecurityLevel requested_security_level, std::string* bcc, std::string* additional_signature) { RETURN_IF_NULL(bcc, PARAMETER_NULL); RETURN_IF_NULL(additional_signature, PARAMETER_NULL); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); LOGV("requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); CdmClientTokenType token_type = kClientTokenUninitialized; const CdmResponseType status = GetProvisioningMethod(requested_security_level, &token_type); if (status != NO_ERROR) { LOGE("Failed to get token type"); return status; } if (token_type != kClientTokenBootCertChain) { return CdmResponseType( PROVISIONING_TYPE_IS_NOT_BOOT_CERTIFICATE_CHAIN_ERROR); } if (requested_security_level != kLevelDefault) { LOGE("CDM only supports L1 BCC"); return CdmResponseType(NOT_IMPLEMENTED_ERROR); } size_t bcc_length = 0; size_t additional_signature_length = 0; OEMCryptoResult sts = WithOecReadLock("GetBootCertificateChain Attempt 1", [&] { return OEMCrypto_GetBootCertificateChain(nullptr, &bcc_length, nullptr, &additional_signature_length); }); if (sts == OEMCrypto_ERROR_SHORT_BUFFER) { bcc->resize(bcc_length); additional_signature->resize(additional_signature_length); sts = WithOecReadLock("GetBootCertificateChain Attempt 2", [&] { return OEMCrypto_GetBootCertificateChain( MutableStringDataPointer(bcc), &bcc_length, MutableStringDataPointer(additional_signature), &additional_signature_length); }); } if (sts != OEMCrypto_SUCCESS) { return MapOEMCryptoResult(sts, GET_BOOT_CERTIFICATE_CHAIN_ERROR, "GetBootCertificateChain"); } bcc->resize(bcc_length); additional_signature->resize(additional_signature_length); return CdmResponseType(NO_ERROR); } CdmResponseType CryptoSession::GenerateCertificateKeyPair( std::string* public_key, std::string* public_key_signature, std::string* wrapped_private_key, CryptoWrappedKey::Type* key_type) { LOGV("Generating certificate key pair: id = %u", oec_session_id_); RETURN_IF_NULL(public_key, PARAMETER_NULL); RETURN_IF_NULL(public_key_signature, PARAMETER_NULL); RETURN_IF_NULL(wrapped_private_key, PARAMETER_NULL); RETURN_IF_NULL(key_type, PARAMETER_NULL); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); // Round 1, get the size of all the fields. size_t public_key_length = 0; size_t public_key_signature_length = 0; size_t wrapped_private_key_length = 0; OEMCrypto_PrivateKeyType oemcrypto_key_type; OEMCryptoResult status; WithOecSessionLock("GenerateCertificateKeyPair Attempt 1", [&] { M_TIME(status = OEMCrypto_GenerateCertificateKeyPair( oec_session_id_, nullptr, &public_key_length, nullptr, &public_key_signature_length, nullptr, &wrapped_private_key_length, &oemcrypto_key_type), metrics_, oemcrypto_generate_certificate_key_pair_, status); }); if (status != OEMCrypto_ERROR_SHORT_BUFFER) { return MapOEMCryptoResult(status, GENERATE_CERTIFICATE_KEY_PAIR_ERROR, "GenerateCertificateKeyPair"); } public_key->resize(public_key_length); public_key_signature->resize(public_key_signature_length); wrapped_private_key->resize(wrapped_private_key_length); WithOecSessionLock("GenerateCertificateKeyPair Attempt 2", [&] { M_TIME( status = OEMCrypto_GenerateCertificateKeyPair( oec_session_id_, MutableStringDataPointer(public_key), &public_key_length, MutableStringDataPointer(public_key_signature), &public_key_signature_length, MutableStringDataPointer(wrapped_private_key), &wrapped_private_key_length, &oemcrypto_key_type), metrics_, oemcrypto_generate_certificate_key_pair_, status); }); if (status != OEMCrypto_SUCCESS) { return MapOEMCryptoResult(status, GENERATE_CERTIFICATE_KEY_PAIR_ERROR, "GenerateCertificateKeyPair"); } public_key->resize(public_key_length); public_key_signature->resize(public_key_signature_length); wrapped_private_key->resize(wrapped_private_key_length); if (oemcrypto_key_type == OEMCrypto_RSA_Private_Key) { *key_type = CryptoWrappedKey::kRsa; } else if (oemcrypto_key_type == OEMCrypto_ECC_Private_Key) { *key_type = CryptoWrappedKey::kEcc; } else { LOGE("Unexpected key type returned from GenerateCertificateKeyPair: %d", static_cast(oemcrypto_key_type)); // TODO(b/261185349): add OEMCrypto_PrivateKeyType to CdmResponseType return CdmResponseType(GENERATE_CERTIFICATE_KEY_PAIR_UNKNOWN_TYPE_ERROR); } return CdmResponseType(NO_ERROR); } CdmResponseType CryptoSession::LoadOemCertificatePrivateKey( const CryptoWrappedKey& private_key) { LOGV("Load OEM cert and private key: id = %u", oec_session_id_); const OEMCrypto_PrivateKeyType key_type = (private_key.type() == CryptoWrappedKey::kEcc) ? OEMCrypto_ECC_Private_Key : OEMCrypto_RSA_Private_Key; const std::string& wrapped_private_key = private_key.key(); OEMCryptoResult status; WithOecSessionLock("InstallOemPrivateKey", [&] { M_TIME(status = OEMCrypto_InstallOemPrivateKey( oec_session_id_, key_type, reinterpret_cast(wrapped_private_key.data()), wrapped_private_key.size()), metrics_, oemcrypto_install_oem_private_key_, status); }); return MapOEMCryptoResult(status, LOAD_OEM_CERTIFICATE_PRIVATE_KEY_ERROR, "InstallOemPrivateKey"); } // Private. CdmResponseType CryptoSession::SelectKey(const std::string& key_id, CdmCipherMode cipher_mode) { RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); const OEMCryptoResult sts = WithOecSessionLock("SelectKey", [&] { RETURN_IF_NULL(key_session_, OEMCrypto_ERROR_INVALID_SESSION); return key_session_->SelectKey(key_id, cipher_mode); }); switch (sts) { // SelectKey errors. case OEMCrypto_SUCCESS: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_KEY_EXPIRED: return CRYPTO_ERROR(NEED_KEY, sts); case OEMCrypto_ERROR_INVALID_SESSION: return CRYPTO_ERROR(INVALID_SESSION_1, sts); case OEMCrypto_ERROR_NO_DEVICE_KEY: return CRYPTO_ERROR(NO_DEVICE_KEY_1, sts); case OEMCrypto_ERROR_NO_CONTENT_KEY: return CRYPTO_ERROR(NO_CONTENT_KEY_2, sts); case OEMCrypto_ERROR_CONTROL_INVALID: case OEMCrypto_ERROR_KEYBOX_INVALID: return CRYPTO_ERROR(UNKNOWN_SELECT_KEY_ERROR_2, sts); case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts); case OEMCrypto_ERROR_UNKNOWN_FAILURE: return CRYPTO_ERROR(UNKNOWN_SELECT_KEY_ERROR_1, sts); case OEMCrypto_ERROR_ANALOG_OUTPUT: return CRYPTO_ERROR(ANALOG_OUTPUT_ERROR, sts); case OEMCrypto_ERROR_INSUFFICIENT_HDCP: return CRYPTO_ERROR(INSUFFICIENT_OUTPUT_PROTECTION, sts); // LoadEntitledContentKeys errors. // |key_session_| may make calls to OEMCrypto_LoadEntitledContentKeys // if the key selected has not yet been loaded. case OEMCrypto_ERROR_INVALID_CONTEXT: return CRYPTO_ERROR(NOT_AN_ENTITLEMENT_SESSION, sts); case OEMCrypto_KEY_NOT_ENTITLED: return CRYPTO_ERROR(NO_MATCHING_ENTITLEMENT_KEY, sts); // Obsolete errors. case OEMCrypto_KEY_NOT_LOADED: return CRYPTO_ERROR(NO_CONTENT_KEY_3, sts); // Catch all else. default: return MapOEMCryptoResult(sts, UNKNOWN_SELECT_KEY_ERROR_2, "SelectKey"); } } CdmResponseType CryptoSession::GenerateDerivedKeys(const std::string& message) { OEMCryptoResult sts; WithOecSessionLock("GenerateDerivedKeys without session_key", [&] { sts = key_session_->GenerateDerivedKeys(message); }); return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR_2, "GenerateDerivedKeys"); } CdmResponseType CryptoSession::GenerateDerivedKeys( const std::string& message, const std::string& session_key) { OEMCryptoResult sts; WithOecSessionLock("GenerateDerivedKeys with session_key", [&] { sts = key_session_->GenerateDerivedKeys(message, session_key); }); return MapOEMCryptoResult(sts, GENERATE_DERIVED_KEYS_ERROR, "GenerateDerivedKeys"); } CdmResponseType CryptoSession::GenerateRsaSignature(const std::string& message, std::string* signature) { LOGV("Generating RSA signature: id = %u", oec_session_id_); RETURN_IF_NULL(signature, PARAMETER_NULL); OEMCryptoResult sts; signature->resize(kRsaSignatureLength); size_t length = signature->size(); // At most two attempts. // The first attempt may fail due to buffer too short for (int i = 0; i < 2; ++i) { WithOecSessionLock("GenerateRsaSignature", [&] { M_TIME( sts = OEMCrypto_GenerateRSASignature( oec_session_id_, reinterpret_cast(message.data()), message.size(), reinterpret_cast(const_cast(signature->data())), &length, kSign_RSASSA_PSS), metrics_, oemcrypto_generate_rsa_signature_, sts, metrics::Pow2Bucket(length)); }); if (OEMCrypto_SUCCESS == sts) { // Trim signature buffer and done signature->resize(length); return CdmResponseType(NO_ERROR); } if (OEMCrypto_ERROR_SHORT_BUFFER != sts) { break; } // Retry with proper-sized signature buffer signature->resize(length); } return MapOEMCryptoResult(sts, RSA_SIGNATURE_GENERATION_ERROR, "OEMCrypto_GenerateRSASignature"); } size_t CryptoSession::GetMaxSubsampleRegionSize() { // If we haven't cached the answer yet, fetch it from OEMCrypto. if (max_subsample_region_size_ == 0) { uint32_t tier = 0; if (GetResourceRatingTier(&tier)) { // Subtract RESOURCE_RATING_TIER_MIN to get a 0-based index into the // table. const uint32_t index = tier - RESOURCE_RATING_TIER_MIN; if (index < wvutil::ArraySize(kMaxSubsampleRegionSizes)) { max_subsample_region_size_ = kMaxSubsampleRegionSizes[index]; } } // If something went wrong, use the default. if (max_subsample_region_size_ == 0) { LOGW("Unable to get maximum subsample region size. Defaulting to %zu", kDefaultMaxSubsampleRegionSize); max_subsample_region_size_ = kDefaultMaxSubsampleRegionSize; } } return max_subsample_region_size_; } CdmResponseType CryptoSession::Decrypt( const CdmDecryptionParametersV16& params) { if (!is_destination_buffer_type_valid_) { if (!SetDestinationBufferType()) return CdmResponseType(UNKNOWN_ERROR); } OEMCryptoBufferType output_descriptor_type = params.is_secure ? destination_buffer_type_ : OEMCrypto_BufferType_Clear; if (params.is_secure && output_descriptor_type == OEMCrypto_BufferType_Clear) { return CdmResponseType(SECURE_BUFFER_REQUIRED); } if (params.samples.size() == 0) return CdmResponseType(CANNOT_DECRYPT_ZERO_SAMPLES); if (std::any_of(std::begin(params.samples), std::end(params.samples), [](const CdmDecryptionSample& sample) -> bool { return sample.subsamples.size() == 0; })) { return CdmResponseType(CANNOT_DECRYPT_ZERO_SUBSAMPLES); } // Convert all the sample and subsample definitions to OEMCrypto structs. // This code also caches whether any of the data is protected, to save later // code the trouble of iterating over all the subsamples to check. bool is_any_sample_protected = false; std::vector oec_samples; oec_samples.reserve(params.samples.size()); std::vector> oec_subsample_vectors; oec_subsample_vectors.reserve(params.samples.size()); for (const CdmDecryptionSample& sample : params.samples) { oec_samples.emplace_back(); OEMCrypto_SampleDescription& oec_sample = oec_samples.back(); // Set up the sample's input buffer oec_sample.buffers.input_data = sample.encrypt_buffer; oec_sample.buffers.input_data_length = sample.encrypt_buffer_length; // Set up the sample's output buffer OEMCrypto_DestBufferDesc& output_descriptor = oec_sample.buffers.output_descriptor; output_descriptor.type = output_descriptor_type; switch (output_descriptor.type) { case OEMCrypto_BufferType_Clear: output_descriptor.buffer.clear.clear_buffer = static_cast(sample.decrypt_buffer) + sample.decrypt_buffer_offset; output_descriptor.buffer.clear.clear_buffer_length = sample.decrypt_buffer_size - sample.decrypt_buffer_offset; break; case OEMCrypto_BufferType_Secure: output_descriptor.buffer.secure.secure_buffer = sample.decrypt_buffer; output_descriptor.buffer.secure.offset = sample.decrypt_buffer_offset; output_descriptor.buffer.secure.secure_buffer_length = sample.decrypt_buffer_size; break; case OEMCrypto_BufferType_Direct: output_descriptor.buffer.direct.is_video = params.is_video; break; } // Convert all the sample's subsamples. This code also tallies the total // size as a sanity check. oec_subsample_vectors.emplace_back(); std::vector& oec_subsamples = oec_subsample_vectors.back(); oec_subsamples.reserve(sample.subsamples.size()); size_t sample_size = 0; bool is_any_subsample_protected = false; size_t current_block_offset = 0; for (const CdmDecryptionSubsample& subsample : sample.subsamples) { oec_subsamples.push_back(OEMCrypto_SubSampleDescription{ subsample.clear_bytes, subsample.protected_bytes, 0, // subsample_flags current_block_offset}); is_any_subsample_protected |= (subsample.protected_bytes > 0); sample_size += subsample.clear_bytes + subsample.protected_bytes; if (params.cipher_mode == kCipherModeCtr) { current_block_offset = (current_block_offset + subsample.protected_bytes) % kAes128BlockSize; } // TODO(b/149524614): This block is not necessary except for // backwards-compatibility while we are transitioning from the v15 API to // the v16 API. if (params.observe_legacy_fields) { OEMCrypto_SubSampleDescription& oec_subsample = oec_subsamples.back(); oec_subsample.subsample_flags = subsample.flags; oec_subsample.block_offset = subsample.block_offset; } } is_any_sample_protected |= is_any_subsample_protected; // TODO(b/149524614): This check is not necessary except for // backwards-compatibility while we are transitioning from the v15 API to // the v16 API. if (!params.observe_legacy_fields) { // Set the actual subsample_flags now that all the subsamples are // converted. oec_subsamples.front().subsample_flags |= OEMCrypto_FirstSubsample; oec_subsamples.back().subsample_flags |= OEMCrypto_LastSubsample; } // Check that the total size is valid if (sample_size != oec_sample.buffers.input_data_length) return CdmResponseType(SAMPLE_AND_SUBSAMPLE_SIZE_MISMATCH); // Set up the sample's IV if (is_any_subsample_protected) { if (sizeof(oec_sample.iv) != sample.iv.size()) return CdmResponseType(INVALID_IV_SIZE); memcpy(oec_sample.iv, sample.iv.data(), sizeof(oec_sample.iv)); } else { memset(oec_sample.iv, 0, sizeof(oec_sample.iv)); } // Attach the subsamples to the sample description oec_sample.subsamples = oec_subsamples.data(); oec_sample.subsamples_length = oec_subsamples.size(); } // Convert the pattern descriptor OEMCrypto_CENCEncryptPatternDesc oec_pattern{params.pattern.encrypt_blocks, params.pattern.skip_blocks}; // TODO(b/146581957): Remove this workaround once OEMCrypto treats (0,0) as // 'cbcs' instead of 'cbc1'. if (params.cipher_mode == kCipherModeCbc && oec_pattern.encrypt == 0 && oec_pattern.skip == 0) { // (10, 0) is the preferred pattern for decrypting every block in 'cbcs' oec_pattern.encrypt = 10; } // Check if a key needs to be selected if (is_any_sample_protected) { CdmResponseType result = SelectKey(params.key_id, params.cipher_mode); if (result != NO_ERROR) return result; } // Perform decrypt const OEMCryptoResult sts = DecryptMultipleSamples(oec_samples, params.cipher_mode, oec_pattern); if (sts != OEMCrypto_SUCCESS && last_decrypt_error_ != sts) { // Decrypt errors and warnings are only logged when the error code // changes. This is in anticipation that if an error code is // returned, then the same error code is likely to be returned in // the next call. The calling application may make several more // decrypt requests before the error is handled by the app. last_decrypt_error_ = sts; if (sts == OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION) { LOGW( "OEMCrypto_DecryptCENC is warning of mixed HDCP output protection: " "oec_session_id = %u", oec_session_id_); } else { LOGE( "OEMCrypto_DecryptCENC failed: oec_session_id = %u, " "security_level = %s, status = %d", oec_session_id_, RequestedSecurityLevelToString(requested_security_level_), static_cast(sts)); } } switch (sts) { case OEMCrypto_SUCCESS: case OEMCrypto_WARNING_MIXED_OUTPUT_PROTECTION: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, sts); case OEMCrypto_ERROR_KEY_EXPIRED: return CRYPTO_ERROR(NEED_KEY, sts); case OEMCrypto_ERROR_INVALID_SESSION: return CRYPTO_ERROR(INVALID_SESSION_2, sts); case OEMCrypto_ERROR_DECRYPT_FAILED: case OEMCrypto_ERROR_UNKNOWN_FAILURE: return CRYPTO_ERROR(DECRYPT_ERROR, sts); case OEMCrypto_ERROR_INSUFFICIENT_HDCP: return CRYPTO_ERROR(INSUFFICIENT_OUTPUT_PROTECTION, sts); case OEMCrypto_ERROR_ANALOG_OUTPUT: return CRYPTO_ERROR(ANALOG_OUTPUT_ERROR, sts); case OEMCrypto_ERROR_OUTPUT_TOO_LARGE: return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts); default: return CRYPTO_ERROR(UNKNOWN_ERROR, sts); } } bool CryptoSession::HasUsageTableSupport(bool* has_support) { RETURN_IF_NOT_OPEN(false); RETURN_IF_NULL(has_support, false); return WithOecReadLock("HasUsageTableSupport", [&] { // Use cached value if set. if (has_usage_table_support_ != kBooleanUnset) { *has_support = (has_usage_table_support_ == kBooleanTrue); return true; } if (!HasUsageTableSupportInternal(requested_security_level_, has_support)) { return false; } // Cache result if successful. has_usage_table_support_ = (*has_support ? kBooleanTrue : kBooleanFalse); return true; }); } bool CryptoSession::HasUsageTableSupport( RequestedSecurityLevel requested_security_level, bool* has_support) { RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(has_support, false); return WithOecReadLock("HasUsageTableSupport", [&] { return HasUsageTableSupportInternal(requested_security_level, has_support); }); } bool CryptoSession::HasUsageTableSupportInternal( RequestedSecurityLevel requested_security_level, bool* has_support) { LOGV("requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); *has_support = WithOecReadLock("HasUsageTableSupport", [&] { return OEMCrypto_SupportsUsageTable(requested_security_level); }); return true; } CdmResponseType CryptoSession::DeactivateUsageInformation( const std::string& provider_session_token) { LOGV("Deactivating usage information: id = %u", oec_session_id_); uint8_t* pst = reinterpret_cast( const_cast(provider_session_token.data())); // TODO(fredgc or rfrias): make sure oec_session_id_ is valid. OEMCryptoResult status; WithOecWriteLock("DeactivateUsageInformation", [&] { status = OEMCrypto_DeactivateUsageEntry(oec_session_id_, pst, provider_session_token.length()); }); metrics_->oemcrypto_deactivate_usage_entry_.Increment(status); if (status != OEMCrypto_SUCCESS) { LOGE("OEMCrypto_DeactivateUsageEntry failed: status = %d", static_cast(status)); } switch (status) { case OEMCrypto_SUCCESS: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_INVALID_CONTEXT: return CRYPTO_ERROR(KEY_CANCELED, status); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, status); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, status); default: return CRYPTO_ERROR(DEACTIVATE_USAGE_ENTRY_ERROR, status); } } CdmResponseType CryptoSession::GenerateUsageReport( const std::string& provider_session_token, std::string* usage_report, UsageDurationStatus* usage_duration_status, int64_t* seconds_since_started, int64_t* seconds_since_last_played) { LOGV("Generating usage report: id = %u", oec_session_id_); RETURN_IF_NULL(usage_report, PARAMETER_NULL); uint8_t* pst = reinterpret_cast( const_cast(provider_session_token.data())); size_t usage_length = 0; OEMCryptoResult status; WithOecWriteLock("GenerateUsageReport Attempt 1", [&] { status = OEMCrypto_ReportUsage(oec_session_id_, pst, provider_session_token.length(), nullptr, &usage_length); }); metrics_->oemcrypto_report_usage_.Increment(status); if (status != OEMCrypto_SUCCESS && status != OEMCrypto_ERROR_SHORT_BUFFER) { return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR, "GenerateUsageReport"); } std::vector buffer(usage_length); WithOecWriteLock("GenerateUsageReport Attempt 2", [&] { status = OEMCrypto_ReportUsage(oec_session_id_, pst, provider_session_token.length(), &buffer[0], &usage_length); }); metrics_->oemcrypto_report_usage_.Increment(status); if (status != OEMCrypto_SUCCESS) { return MapOEMCryptoResult(status, GENERATE_USAGE_REPORT_ERROR, "OEMCrypto_ReportUsage"); } if (usage_length != buffer.size()) { buffer.resize(usage_length); } (*usage_report) = std::string(reinterpret_cast(&buffer[0]), buffer.size()); wvutil::Unpacked_PST_Report pst_report(&buffer[0]); *usage_duration_status = kUsageDurationsInvalid; if (usage_length < pst_report.report_size()) { LOGE( "Parsed usage report smaller than expected: " "usage_length = %zu, report_size = %zu", usage_length, pst_report.report_size()); return CdmResponseType( NO_ERROR); // usage report available but no duration information } if (kUnused == pst_report.status()) { *usage_duration_status = kUsageDurationPlaybackNotBegun; return CdmResponseType(NO_ERROR); } LOGV("OEMCrypto_PST_Report.status: %d\n", static_cast(pst_report.status())); LOGV("OEMCrypto_PST_Report.clock_security_level: %d\n", static_cast(pst_report.clock_security_level())); LOGV("OEMCrypto_PST_Report.pst_length: %d\n", static_cast(pst_report.pst_length())); LOGV("OEMCrypto_PST_Report.padding: %d\n", static_cast(pst_report.padding())); LOGV("OEMCrypto_PST_Report.seconds_since_license_received: %" PRId64 "\n", pst_report.seconds_since_license_received()); LOGV("OEMCrypto_PST_Report.seconds_since_first_decrypt: %" PRId64 "\n", pst_report.seconds_since_first_decrypt()); LOGV("OEMCrypto_PST_Report.seconds_since_last_decrypt: %" PRId64 "\n", pst_report.seconds_since_last_decrypt()); LOGV("OEMCrypto_PST_Report: %s\n", wvutil::b2a_hex(*usage_report).c_str()); if (kInactiveUnused == pst_report.status()) { *usage_duration_status = kUsageDurationPlaybackNotBegun; return CdmResponseType(NO_ERROR); } // Before OEMCrypto v13, When usage report state is inactive, we have to // deduce whether the license was ever used. if (kInactive == pst_report.status() && (0 > pst_report.seconds_since_first_decrypt() || pst_report.seconds_since_license_received() < pst_report.seconds_since_first_decrypt())) { *usage_duration_status = kUsageDurationPlaybackNotBegun; return CdmResponseType(NO_ERROR); } *usage_duration_status = kUsageDurationsValid; *seconds_since_started = pst_report.seconds_since_first_decrypt(); *seconds_since_last_played = pst_report.seconds_since_last_decrypt(); return CdmResponseType(NO_ERROR); } bool CryptoSession::IsAntiRollbackHwPresent() { bool is_present; WithOecReadLock("IsAntiRollbackHwPresent", [&] { is_present = OEMCrypto_IsAntiRollbackHwPresent(requested_security_level_); }); metrics_->oemcrypto_is_anti_rollback_hw_present_.Record(is_present); return is_present; } CdmResponseType CryptoSession::GenerateNonce(uint32_t* nonce) { RETURN_IF_NULL(nonce, PARAMETER_NULL); OEMCryptoResult result; WithOecWriteLock("GenerateNonce", [&] { result = OEMCrypto_GenerateNonce(oec_session_id_, nonce); }); metrics_->oemcrypto_generate_nonce_.Increment(result); return MapOEMCryptoResult(result, NONCE_GENERATION_ERROR, "GenerateNonce"); } bool CryptoSession::SetDestinationBufferType() { if (Properties::oem_crypto_use_secure_buffers()) { destination_buffer_type_ = OEMCrypto_BufferType_Secure; } else if (Properties::oem_crypto_use_fifo()) { destination_buffer_type_ = OEMCrypto_BufferType_Direct; } else if (Properties::oem_crypto_use_userspace_buffers()) { destination_buffer_type_ = OEMCrypto_BufferType_Clear; } else { return false; } is_destination_buffer_type_valid_ = true; return true; } CdmResponseType CryptoSession::LoadProvisioning( const std::string& signed_message, const std::string& core_message, const std::string& signature, std::string* wrapped_private_key) { LOGV("Loading provisioning certificate: id = %u", oec_session_id_); if (wrapped_private_key == nullptr) { LOGE("Missing wrapped |wrapped_private_key|"); return CdmResponseType(PARAMETER_NULL); } const std::string combined_message = core_message + signed_message; // Round 1, get the size of the wrapped private key buffer. size_t wrapped_private_key_length = 0; OEMCryptoResult status; WithOecSessionLock("LoadProvisioning Attempt 1", [&] { M_TIME(status = OEMCrypto_LoadProvisioning( oec_session_id_, reinterpret_cast(combined_message.data()), combined_message.size(), core_message.size(), reinterpret_cast(signature.data()), signature.size(), nullptr, &wrapped_private_key_length), metrics_, oemcrypto_load_provisioning_, status); }); if (status != OEMCrypto_ERROR_SHORT_BUFFER) { return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR, "LoadProvisioning"); } wrapped_private_key->resize(wrapped_private_key_length); WithOecSessionLock("LoadProvisioning Attempt 2", [&] { M_TIME(status = OEMCrypto_LoadProvisioning( oec_session_id_, reinterpret_cast(combined_message.data()), combined_message.size(), core_message.size(), reinterpret_cast(signature.data()), signature.size(), reinterpret_cast(&wrapped_private_key->front()), &wrapped_private_key_length), metrics_, oemcrypto_load_provisioning_, status); }); if (status == OEMCrypto_SUCCESS) { wrapped_private_key->resize(wrapped_private_key_length); return CdmResponseType(NO_ERROR); } wrapped_private_key->clear(); return MapOEMCryptoResult(status, LOAD_PROVISIONING_ERROR, "LoadProvisioning"); } CdmResponseType CryptoSession::GetHdcpCapabilities(HdcpCapability* current, HdcpCapability* max) { LOGV("Getting HDCP capabilities: id = %u", oec_session_id_); RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); return GetHdcpCapabilities(requested_security_level_, current, max); } CdmResponseType CryptoSession::GetHdcpCapabilities( RequestedSecurityLevel security_level, HdcpCapability* current, HdcpCapability* max) { LOGV("Getting HDCP capabilities: id = %u, security_level = %s", oec_session_id_, RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(current, PARAMETER_NULL); RETURN_IF_NULL(max, PARAMETER_NULL); OEMCryptoResult status; WithOecReadLock("GetHdcpCapabilities", [&] { status = OEMCrypto_GetHDCPCapability(security_level, current, max); }); if (OEMCrypto_SUCCESS == status) { metrics_->oemcrypto_current_hdcp_capability_.Record(*current); metrics_->oemcrypto_max_hdcp_capability_.Record(*max); } else { metrics_->oemcrypto_current_hdcp_capability_.SetError(status); metrics_->oemcrypto_max_hdcp_capability_.SetError(status); } return MapOEMCryptoResult(status, GET_HDCP_CAPABILITY_FAILED, "GetHDCPCapability"); } bool CryptoSession::GetSupportedCertificateTypes( SupportedCertificateTypes* support) { LOGV("Getting supported certificate types: id = %u", oec_session_id_); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(support, false); const uint32_t oec_support = WithOecReadLock("GetSupportedCertificateTypes", [&] { return OEMCrypto_SupportedCertificates(requested_security_level_); }); support->rsa_2048_bit = oec_support & OEMCrypto_Supports_RSA_2048bit; support->rsa_3072_bit = oec_support & OEMCrypto_Supports_RSA_3072bit; support->rsa_cast = oec_support & OEMCrypto_Supports_RSA_CAST; support->ecc_secp256r1 = oec_support & OEMCrypto_Supports_ECC_secp256r1; support->ecc_secp384r1 = oec_support & OEMCrypto_Supports_ECC_secp384r1; support->ecc_secp521r1 = oec_support & OEMCrypto_Supports_ECC_secp521r1; return true; } CdmResponseType CryptoSession::GetNumberOfOpenSessions( RequestedSecurityLevel security_level, size_t* count) { LOGV("Getting number of open sessions: id = %u, security_level = %s", oec_session_id_, RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(count, PARAMETER_NULL); size_t sessions_count; OEMCryptoResult status; WithOecReadLock("GetNumberOfOpenSessions", [&] { status = OEMCrypto_GetNumberOfOpenSessions(security_level, &sessions_count); }); if (OEMCrypto_SUCCESS == status) { metrics_->oemcrypto_number_of_open_sessions_.Record(sessions_count); *count = sessions_count; } else { metrics_->oemcrypto_number_of_open_sessions_.SetError(status); } return MapOEMCryptoResult(status, GET_NUMBER_OF_OPEN_SESSIONS_ERROR, "GetNumberOfOpenSessions"); } CdmResponseType CryptoSession::GetMaxNumberOfSessions( RequestedSecurityLevel security_level, size_t* max) { LOGV("Getting max number of sessions: id = %u, security_level = %s", oec_session_id_, RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(max, PARAMETER_NULL); size_t max_sessions = 0; OEMCryptoResult status; WithOecReadLock("GetMaxNumberOfSessions", [&] { status = OEMCrypto_GetMaxNumberOfSessions(security_level, &max_sessions); }); if (OEMCrypto_SUCCESS == status) { metrics_->oemcrypto_max_number_of_sessions_.Record(max_sessions); *max = max_sessions; } else { metrics_->oemcrypto_max_number_of_sessions_.SetError(status); } return MapOEMCryptoResult(status, GET_MAX_NUMBER_OF_OPEN_SESSIONS_ERROR, "GetMaxNumberOfOpenSessions"); } CdmResponseType CryptoSession::GetSrmVersion(uint16_t* srm_version) { LOGV("Getting SRM version"); RETURN_IF_UNINITIALIZED(CRYPTO_SESSION_NOT_INITIALIZED); RETURN_IF_NULL(srm_version, PARAMETER_NULL); const OEMCryptoResult status = WithOecReadLock("GetSrmVersion", [&] { return OEMCrypto_GetCurrentSRMVersion(srm_version); }); // SRM is an optional feature. Whether it is implemented is up to the // discretion of OEMs. OEMs may implement this method, but SRM is not // required if there is only a local display, as such no SRM version // is available/reportable. |srm_version| is only set if SUCCESS is // returned. switch (status) { case OEMCrypto_SUCCESS: return CdmResponseType(NO_ERROR); case OEMCrypto_LOCAL_DISPLAY_ONLY: LOGD("No SRM: Local display only"); return CRYPTO_ERROR(NO_SRM_VERSION, status); case OEMCrypto_ERROR_NOT_IMPLEMENTED: LOGD("No SRM: Not implemented"); return CRYPTO_ERROR(NO_SRM_VERSION, status); default: return MapOEMCryptoResult(status, GET_SRM_VERSION_ERROR, "GetCurrentSRMVersion"); } } bool CryptoSession::GetResourceRatingTier(uint32_t* tier) { LOGV("Getting resource rating tier"); RETURN_IF_NOT_OPEN(false); return GetResourceRatingTier(requested_security_level_, tier); } bool CryptoSession::GetResourceRatingTier(RequestedSecurityLevel security_level, uint32_t* tier) { LOGV("Getting resource rating tier: security_level = %s", RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(tier, false); WithOecReadLock("GetResourceRatingTier", [&] { *tier = OEMCrypto_ResourceRatingTier(security_level); metrics_->oemcrypto_resource_rating_tier_.Record(*tier); }); if (*tier < RESOURCE_RATING_TIER_MIN || *tier > RESOURCE_RATING_TIER_MAX) { uint32_t api_version; if (GetApiVersion(security_level, &api_version)) { if (api_version >= OEM_CRYPTO_API_VERSION_SUPPORTS_RESOURCE_RATING_TIER) { LOGW("Invalid resource rating tier: %u", *tier); } } } return true; } bool CryptoSession::GetBuildInformation(std::string* info) { LOGV("Getting build information"); RETURN_IF_NOT_OPEN(false); return GetBuildInformation(requested_security_level_, info); } bool CryptoSession::GetBuildInformation(RequestedSecurityLevel security_level, std::string* info) { LOGV("Getting build information: security_level = %s", RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(info, false); size_t info_length = 128; info->assign(info_length, '\0'); OEMCryptoResult result = WithOecReadLock("GetBuildInformation", [&] { return OEMCrypto_BuildInformation(&info->front(), &info_length, security_level); }); if (result == OEMCrypto_ERROR_SHORT_BUFFER) { info->assign(info_length, '\0'); result = WithOecReadLock("GetBuildInformation Attempt 2", [&] { return OEMCrypto_BuildInformation(&info->front(), &info_length, security_level); }); } if (result != OEMCrypto_SUCCESS) { LOGE("GetBuildInformation failed: result = %d", result); info->clear(); return false; } info->resize(info_length); return true; } bool CryptoSession::GetWatermarkingSupport(CdmWatermarkingSupport* support) { RETURN_IF_NOT_OPEN(false); return GetWatermarkingSupport(requested_security_level_, support); } bool CryptoSession::GetWatermarkingSupport( RequestedSecurityLevel security_level, CdmWatermarkingSupport* support) { LOGV("security_level = %s", RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(support, false); const OEMCrypto_WatermarkingSupport oec_support = WithOecReadLock( "GetWatermarkingSupport", [&] { return OEMCrypto_GetWatermarkingSupport(security_level); }); switch (oec_support) { case OEMCrypto_WatermarkingNotSupported: *support = kWatermarkingNotSupported; break; case OEMCrypto_WatermarkingConfigurable: *support = kWatermarkingConfigurable; break; case OEMCrypto_WatermarkingAlwaysOn: *support = kWatermarkingAlwaysOn; break; case OEMCrypto_WatermarkingError: default: LOGE("GetWatermarkingSupport error: security_level = %s, result = %d", RequestedSecurityLevelToString(security_level), static_cast(oec_support)); metrics_->oemcrypto_watermarking_support_.SetError(oec_support); return false; } metrics_->oemcrypto_watermarking_support_.Record(oec_support); return true; } bool CryptoSession::GetProductionReadiness(CdmProductionReadiness* readiness) { RETURN_IF_NOT_OPEN(false); return GetProductionReadiness(requested_security_level_, readiness); } bool CryptoSession::GetProductionReadiness( RequestedSecurityLevel security_level, CdmProductionReadiness* readiness) { LOGV("security_level = %s", RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(readiness, false); const OEMCryptoResult result = WithOecReadLock("GetProductionReadiness", [&] { return OEMCrypto_ProductionReady(security_level); }); metrics_->oemcrypto_production_readiness_.Record(result); switch (result) { case OEMCrypto_SUCCESS: *readiness = kProductionReadinessTrue; break; case OEMCrypto_ERROR_NOT_IMPLEMENTED: *readiness = kProductionReadinessUnknown; break; case OEMCrypto_ERROR_UNKNOWN_FAILURE: default: // Other vendor-defined codes indicate not production ready. LOGD("Not production ready: security_level = %s, result = %d", RequestedSecurityLevelToString(security_level), static_cast(result)); *readiness = kProductionReadinessFalse; break; } return true; } bool CryptoSession::GetMaximumUsageTableEntries( RequestedSecurityLevel security_level, size_t* number_of_entries) { LOGV("Getting maximum usage table entries: security_level = %s", RequestedSecurityLevelToString(security_level)); RETURN_IF_UNINITIALIZED(false); if (number_of_entries == nullptr) { LOGE("Output parameter |number_of_entries| not provided"); return false; } WithOecReadLock("GetMaxUsageTableEntries", [&] { *number_of_entries = OEMCrypto_MaximumUsageTableHeaderSize(security_level); }); // Record the number of entries into the metrics. metrics_->oemcrypto_maximum_usage_table_header_size_.Record( static_cast(*number_of_entries)); if (*number_of_entries == 0) { // Special value, indicating that the table size is not directly // limited. return true; } return *number_of_entries >= kMinimumUsageTableEntriesSupported; } bool CryptoSession::GetDecryptHashSupport(RequestedSecurityLevel security_level, uint32_t* decrypt_hash_support) { LOGV("Checking if decrypt hash is supported"); RETURN_IF_UNINITIALIZED(false); RETURN_IF_NULL(decrypt_hash_support, false); WithOecReadLock("GetDecryptHashSupport", [&] { *decrypt_hash_support = OEMCrypto_SupportsDecryptHash(security_level); }); switch (*decrypt_hash_support) { case OEMCrypto_Hash_Not_Supported: case OEMCrypto_CRC_Clear_Buffer: case OEMCrypto_Partner_Defined_Hash: break; case OEMCrypto_ERROR_NOT_IMPLEMENTED: *decrypt_hash_support = OEMCrypto_Hash_Not_Supported; break; default: // Not flagging an error since it is only used in test LOGW("OEMCrypto_SupportsDecryptHash unrecognized result = %d", static_cast(*decrypt_hash_support)); return false; } return true; } CdmResponseType CryptoSession::SetDecryptHash(uint32_t frame_number, const std::string& hash) { LOGV("Setting decrypt hash"); OEMCryptoResult sts; WithOecSessionLock("SetDecryptHash", [&] { sts = OEMCrypto_SetDecryptHash( oec_session_id_, frame_number, reinterpret_cast(hash.data()), hash.size()); metrics_->oemcrypto_set_decrypt_hash_.Increment(sts); }); return MapOEMCryptoResult(sts, SET_DECRYPT_HASH_ERROR, "SetDecryptHash"); } CdmResponseType CryptoSession::GetDecryptHashError(std::string* error_string) { LOGV("Getting decrypt hash error"); RETURN_IF_NULL(error_string, PARAMETER_NULL); error_string->clear(); uint32_t failed_frame_number = 0; OEMCryptoResult sts; WithOecSessionLock("GetDecryptHashError", [&] { sts = OEMCrypto_GetHashErrorCode(oec_session_id_, &failed_frame_number); }); error_string->assign(std::to_string(sts)); if (sts != OEMCrypto_SUCCESS) { LOGE("OEMCrypto_GetHashErrorCode failed: status = %d", static_cast(sts)); } switch (sts) { case OEMCrypto_SUCCESS: case OEMCrypto_ERROR_BAD_HASH: error_string->assign(std::to_string(sts)); error_string->append(","); error_string->append(std::to_string(failed_frame_number)); return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts); case OEMCrypto_ERROR_UNKNOWN_FAILURE: case OEMCrypto_ERROR_NOT_IMPLEMENTED: default: return CRYPTO_ERROR(GET_DECRYPT_HASH_ERROR, sts); } } CdmResponseType CryptoSession::GenericEncrypt(const std::string& in_buffer, const std::string& key_id, const std::string& iv, CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { LOGV("Generic encrypt: id = %u", oec_session_id_); RETURN_IF_NULL(out_buffer, PARAMETER_NULL); OEMCrypto_Algorithm oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING; if (iv.size() != GenericEncryptionBlockSize(algorithm) || !GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) { return CdmResponseType(INVALID_PARAMETERS_ENG_13); } if (out_buffer->size() < in_buffer.size()) { out_buffer->resize(in_buffer.size()); } // TODO(jfore): We need to select a key with a cipher mode and algorithm // doesn't seem to fit. Is it ok to just use a default value here? // Or do we need to pass it in? CdmResponseType result = SelectKey(key_id, kCipherModeCbc); if (result != NO_ERROR) return result; OEMCryptoResult sts; WithOecSessionLock("GenericEncrypt", [&] { sts = key_session_->GenericEncrypt(in_buffer, iv, oec_algorithm, out_buffer); }); if (OEMCrypto_SUCCESS != sts) { LOGE("OEMCrypto_Generic_Encrypt failed: status = %d", static_cast(sts)); } switch (sts) { case OEMCrypto_SUCCESS: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_KEY_EXPIRED: return CRYPTO_ERROR(NEED_KEY, sts); case OEMCrypto_ERROR_NO_CONTENT_KEY: case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15. return CRYPTO_ERROR(KEY_NOT_FOUND_3, sts); case OEMCrypto_ERROR_OUTPUT_TOO_LARGE: return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts); default: return CRYPTO_ERROR(UNKNOWN_ERROR, sts); } } CdmResponseType CryptoSession::GenericDecrypt(const std::string& in_buffer, const std::string& key_id, const std::string& iv, CdmEncryptionAlgorithm algorithm, std::string* out_buffer) { LOGV("Generic decrypt: id = %u", oec_session_id_); RETURN_IF_NULL(out_buffer, PARAMETER_NULL); OEMCrypto_Algorithm oec_algorithm = OEMCrypto_AES_CBC_128_NO_PADDING; if (iv.size() != GenericEncryptionBlockSize(algorithm) || !GetGenericEncryptionAlgorithm(algorithm, &oec_algorithm)) { return CdmResponseType(INVALID_PARAMETERS_ENG_14); } if (out_buffer->size() < in_buffer.size()) { out_buffer->resize(in_buffer.size()); } // TODO(jfore): We need to select a key with a cipher mode and algorithm // doesn't seem to fit. Is it ok to just use a default value here? // Or do we need to pass it in? CdmResponseType result = SelectKey(key_id, kCipherModeCbc); if (result != NO_ERROR) return result; OEMCryptoResult sts; WithOecSessionLock("GenericDecrypt", [&] { sts = key_session_->GenericDecrypt(in_buffer, iv, oec_algorithm, out_buffer); }); if (OEMCrypto_SUCCESS != sts) { LOGE("OEMCrypto_Generic_Decrypt failed: status = %d", static_cast(sts)); } switch (sts) { case OEMCrypto_SUCCESS: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_KEY_EXPIRED: return CRYPTO_ERROR(NEED_KEY, sts); case OEMCrypto_ERROR_NO_CONTENT_KEY: case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15. return CRYPTO_ERROR(KEY_NOT_FOUND_4, sts); case OEMCrypto_ERROR_OUTPUT_TOO_LARGE: return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts); default: return CRYPTO_ERROR(UNKNOWN_ERROR, sts); } } CdmResponseType CryptoSession::GenericSign(const std::string& message, const std::string& key_id, CdmSigningAlgorithm algorithm, std::string* signature) { LOGV("Generic sign: id = %u", oec_session_id_); RETURN_IF_NULL(signature, PARAMETER_NULL); OEMCrypto_Algorithm oec_algorithm = OEMCrypto_HMAC_SHA256; if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) { return CdmResponseType(INVALID_PARAMETERS_ENG_15); } // TODO(jfore): We need to select a key with a cipher mode and algorithm // doesn't seem to fit. Is it ok to just use a default value here? // Or do we need to pass it in? CdmResponseType result = SelectKey(key_id, kCipherModeCbc); if (result != NO_ERROR) return result; OEMCryptoResult sts; WithOecSessionLock("GenericSign", [&] { sts = key_session_->GenericSign(message, oec_algorithm, signature); }); switch (sts) { case OEMCrypto_SUCCESS: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_KEY_EXPIRED: return CRYPTO_ERROR(NEED_KEY, sts); case OEMCrypto_ERROR_NO_CONTENT_KEY: case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15. return CRYPTO_ERROR(KEY_NOT_FOUND_5, sts); case OEMCrypto_ERROR_OUTPUT_TOO_LARGE: return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts); default: return CRYPTO_ERROR(UNKNOWN_ERROR, sts); } } CdmResponseType CryptoSession::GenericVerify(const std::string& message, const std::string& key_id, CdmSigningAlgorithm algorithm, const std::string& signature) { LOGV("Generic verify: id = %u", oec_session_id_); OEMCrypto_Algorithm oec_algorithm = OEMCrypto_HMAC_SHA256; if (!GetGenericSigningAlgorithm(algorithm, &oec_algorithm)) { return CdmResponseType(INVALID_PARAMETERS_ENG_16); } // TODO(jfore): We need to select a key with a cipher mode and algorithm // doesn't seem to fit. Is it ok to just use a default value here? // Or do we need to pass it in? CdmResponseType result = SelectKey(key_id, kCipherModeCbc); if (result != NO_ERROR) return result; OEMCryptoResult sts; WithOecSessionLock("GenericVerify", [&] { sts = key_session_->GenericVerify(message, oec_algorithm, signature); }); if (OEMCrypto_SUCCESS != sts) { LOGE("OEMCrypto_Generic_Verify failed: status = %d", static_cast(sts)); } switch (sts) { case OEMCrypto_SUCCESS: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_KEY_EXPIRED: return CRYPTO_ERROR(NEED_KEY, sts); case OEMCrypto_ERROR_NO_CONTENT_KEY: case OEMCrypto_KEY_NOT_LOADED: // obsolete in v15. return CRYPTO_ERROR(KEY_NOT_FOUND_6, sts); case OEMCrypto_ERROR_OUTPUT_TOO_LARGE: return CRYPTO_ERROR(OUTPUT_TOO_LARGE_ERROR, sts); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, sts); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, sts); default: return CRYPTO_ERROR(UNKNOWN_ERROR, sts); } } CdmResponseType CryptoSession::CreateUsageTableHeader( RequestedSecurityLevel requested_security_level, UsageTableHeader* usage_table_header) { LOGV("Creating usage table header: requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); RETURN_IF_NULL(usage_table_header, PARAMETER_NULL); usage_table_header->resize(kEstimatedInitialUsageTableHeader); size_t usage_table_header_size = usage_table_header->size(); OEMCryptoResult result; WithOecWriteLock("CreateUsageTableHeader Attempt 1", [&] { result = OEMCrypto_CreateUsageTableHeader( requested_security_level, reinterpret_cast( const_cast(usage_table_header->data())), &usage_table_header_size); metrics_->oemcrypto_create_usage_table_header_.Increment(result); }); if (result == OEMCrypto_ERROR_SHORT_BUFFER) { usage_table_header->resize(usage_table_header_size); WithOecWriteLock("CreateUsageTableHeader Attempt 2", [&] { result = OEMCrypto_CreateUsageTableHeader( requested_security_level, reinterpret_cast( const_cast(usage_table_header->data())), &usage_table_header_size); metrics_->oemcrypto_create_usage_table_header_.Increment(result); }); } switch (result) { case OEMCrypto_SUCCESS: usage_table_header->resize(usage_table_header_size); return CdmResponseType(NO_ERROR); default: return MapOEMCryptoResult(result, CREATE_USAGE_TABLE_ERROR, "CreateUsageTableHeader"); } } CdmResponseType CryptoSession::LoadUsageTableHeader( RequestedSecurityLevel requested_security_level, const UsageTableHeader& usage_table_header) { LOGV("Loading usage table header: requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); const OEMCryptoResult result = WithOecWriteLock("LoadUsageTableHeader", [&] { return OEMCrypto_LoadUsageTableHeader( requested_security_level, reinterpret_cast(usage_table_header.data()), usage_table_header.size()); }); metrics_->oemcrypto_load_usage_table_header_.Increment(result); if (result != OEMCrypto_SUCCESS) { if (result == OEMCrypto_WARNING_GENERATION_SKEW) { LOGW("OEMCrypto_LoadUsageTableHeader warning: generation skew"); } else { LOGE("OEMCrypto_LoadUsageTableHeader failed: status = %d", static_cast(result)); } } switch (result) { case OEMCrypto_SUCCESS: case OEMCrypto_WARNING_GENERATION_SKEW: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_GENERATION_SKEW: return CRYPTO_ERROR(LOAD_USAGE_HEADER_GENERATION_SKEW, result); case OEMCrypto_ERROR_SIGNATURE_FAILURE: return CRYPTO_ERROR(LOAD_USAGE_HEADER_SIGNATURE_FAILURE, result); case OEMCrypto_ERROR_BAD_MAGIC: return CRYPTO_ERROR(LOAD_USAGE_HEADER_BAD_MAGIC, result); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, result); case OEMCrypto_ERROR_UNKNOWN_FAILURE: default: return CRYPTO_ERROR(LOAD_USAGE_HEADER_UNKNOWN_ERROR, result); } } CdmResponseType CryptoSession::ShrinkUsageTableHeader( RequestedSecurityLevel requested_security_level, uint32_t new_entry_count, UsageTableHeader* usage_table_header) { LOGV("Shrinking usage table header: requested_security_level = %s", RequestedSecurityLevelToString(requested_security_level)); RETURN_IF_NULL(usage_table_header, PARAMETER_NULL); size_t usage_table_header_len = 0; OEMCryptoResult result; WithOecWriteLock("ShrinkUsageTableHeader Attempt 1", [&] { result = OEMCrypto_ShrinkUsageTableHeader(requested_security_level, new_entry_count, nullptr, &usage_table_header_len); metrics_->oemcrypto_shrink_usage_table_header_.Increment(result); }); if (result == OEMCrypto_ERROR_SHORT_BUFFER) { usage_table_header->resize(usage_table_header_len); WithOecWriteLock("ShrinkUsageTableHeader Attempt 2", [&] { result = OEMCrypto_ShrinkUsageTableHeader( requested_security_level, new_entry_count, reinterpret_cast( const_cast(usage_table_header->data())), &usage_table_header_len); metrics_->oemcrypto_shrink_usage_table_header_.Increment(result); }); } switch (result) { case OEMCrypto_SUCCESS: usage_table_header->resize(usage_table_header_len); return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_ENTRY_IN_USE: return CRYPTO_ERROR(SHRINK_USAGE_TABLE_HEADER_ENTRY_IN_USE, result); default: return MapOEMCryptoResult(result, SHRINK_USAGE_TABLE_HEADER_UNKNOWN_ERROR, "ShrinkUsageTableHeader"); } } CdmResponseType CryptoSession::CreateUsageEntry(UsageEntryIndex* entry_index) { LOGV("Creating usage entry: id = %u", oec_session_id_); RETURN_IF_NULL(entry_index, PARAMETER_NULL); OEMCryptoResult result; WithOecWriteLock("CreateUsageEntry", [&] { result = OEMCrypto_CreateNewUsageEntry(oec_session_id_, entry_index); metrics_->oemcrypto_create_new_usage_entry_.Increment(result); }); if (result != OEMCrypto_SUCCESS) { LOGE("OEMCrypto_CreateNewUsageEntry failed: status = %d", static_cast(result)); } switch (result) { case OEMCrypto_SUCCESS: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_INSUFFICIENT_RESOURCES: return CRYPTO_ERROR(INSUFFICIENT_CRYPTO_RESOURCES, result); case OEMCrypto_ERROR_SESSION_LOST_STATE: return CRYPTO_ERROR(SESSION_LOST_STATE_ERROR, result); case OEMCrypto_ERROR_SYSTEM_INVALIDATED: return CRYPTO_ERROR(SYSTEM_INVALIDATED_ERROR, result); default: return CRYPTO_ERROR(CREATE_USAGE_ENTRY_UNKNOWN_ERROR, result); } } CdmResponseType CryptoSession::LoadUsageEntry(UsageEntryIndex entry_index, const UsageEntry& usage_entry) { LOGV("Loading usage entry: id = %u", oec_session_id_); OEMCryptoResult result; WithOecWriteLock("LoadUsageEntry", [&] { result = OEMCrypto_LoadUsageEntry( oec_session_id_, entry_index, reinterpret_cast(usage_entry.data()), usage_entry.size()); metrics_->oemcrypto_load_usage_entry_.Increment(result); }); if (result != OEMCrypto_SUCCESS) { if (result == OEMCrypto_WARNING_GENERATION_SKEW) { LOGW("OEMCrypto_LoadUsageEntry warning: generation skew"); } else { LOGE("OEMCrypto_LoadUsageEntry failed: status = %d", static_cast(result)); } } switch (result) { case OEMCrypto_SUCCESS: case OEMCrypto_WARNING_GENERATION_SKEW: return CdmResponseType(NO_ERROR); case OEMCrypto_ERROR_INVALID_SESSION: // This case is special, as it could imply that the provided // session ID is invalid (CDM internal bug), or that the entry // being loaded is already in use in a different session. // It is up to the caller to handle this. return CRYPTO_ERROR(LOAD_USAGE_ENTRY_INVALID_SESSION, result); case OEMCrypto_ERROR_GENERATION_SKEW: return CRYPTO_ERROR(LOAD_USAGE_ENTRY_GENERATION_SKEW, result); case OEMCrypto_ERROR_SIGNATURE_FAILURE: return CRYPTO_ERROR(LOAD_USAGE_ENTRY_SIGNATURE_FAILURE, result); default: return MapOEMCryptoResult(result, LOAD_USAGE_ENTRY_UNKNOWN_ERROR, "LoadUsageEntry"); } } CdmResponseType CryptoSession::UpdateUsageEntry( UsageTableHeader* usage_table_header, UsageEntry* usage_entry) { LOGV("Updating usage entry: id = %u", oec_session_id_); RETURN_IF_NULL(usage_table_header, PARAMETER_NULL); RETURN_IF_NULL(usage_entry, PARAMETER_NULL); size_t usage_table_header_len = 0; size_t usage_entry_len = 0; OEMCryptoResult result; WithOecWriteLock("UpdateUsageEntry Attempt 1", [&] { result = OEMCrypto_UpdateUsageEntry(oec_session_id_, nullptr, &usage_table_header_len, nullptr, &usage_entry_len); }); metrics_->oemcrypto_update_usage_entry_.Increment(result); if (result == OEMCrypto_ERROR_SHORT_BUFFER) { usage_table_header->resize(usage_table_header_len); usage_entry->resize(usage_entry_len); WithOecWriteLock("UpdateUsageEntry Attempt 2", [&] { result = OEMCrypto_UpdateUsageEntry( oec_session_id_, reinterpret_cast( const_cast(usage_table_header->data())), &usage_table_header_len, reinterpret_cast(const_cast(usage_entry->data())), &usage_entry_len); }); metrics_->oemcrypto_update_usage_entry_.Increment(result); } if (result == OEMCrypto_SUCCESS) { usage_table_header->resize(usage_table_header_len); usage_entry->resize(usage_entry_len); } return MapOEMCryptoResult(result, UPDATE_USAGE_ENTRY_UNKNOWN_ERROR, "UpdateUsageEntry"); } CdmResponseType CryptoSession::MoveUsageEntry(UsageEntryIndex new_entry_index) { LOGV("Moving usage entry: id = %u", oec_session_id_); OEMCryptoResult result; WithOecWriteLock("MoveUsageEntry", [&] { result = OEMCrypto_MoveEntry(oec_session_id_, new_entry_index); metrics_->oemcrypto_move_entry_.Increment(result); }); switch (result) { case OEMCrypto_ERROR_ENTRY_IN_USE: LOGW("OEMCrypto_MoveEntry failed: Destination index in use: index = %u", new_entry_index); return CRYPTO_ERROR(MOVE_USAGE_ENTRY_DESTINATION_IN_USE, result); default: return MapOEMCryptoResult(result, MOVE_USAGE_ENTRY_UNKNOWN_ERROR, "MoveUsageEntry"); } } bool CryptoSession::GetAnalogOutputCapabilities(bool* can_support_output, bool* can_disable_output, bool* can_support_cgms_a) { LOGV("Getting analog output capabilities: id = %u", oec_session_id_); RETURN_IF_UNINITIALIZED(false); const uint32_t flags = WithOecReadLock("GetAnalogOutputCapabilities", [&] { return OEMCrypto_GetAnalogOutputFlags(requested_security_level_); }); if ((flags & OEMCrypto_Unknown_Analog_Output) != 0) return false; *can_support_cgms_a = flags & OEMCrypto_Supports_CGMS_A; *can_support_output = flags & OEMCrypto_Supports_Analog_Output; *can_disable_output = flags & OEMCrypto_Can_Disable_Analog_Ouptput; return true; } OEMCryptoResult CryptoSession::DecryptMultipleSamples( const std::vector& samples, CdmCipherMode cipher_mode, const OEMCrypto_CENCEncryptPatternDesc& pattern) { OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE; // If there's only one sample, automatically fall through to avoid a redundant // roundtrip through OEMCrypto_DecryptCENC() if (samples.size() > 1) { WithOecSessionLock("DecryptMultipleSamples", [&] { sts = key_session_->Decrypt(samples.data(), samples.size(), pattern); }); } if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) { // Fall back to sending each sample individually for (const OEMCrypto_SampleDescription& sample : samples) { sts = DecryptSample(sample, cipher_mode, pattern); if (sts != OEMCrypto_SUCCESS) break; } } return sts; } OEMCryptoResult CryptoSession::DecryptSample( const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode, const OEMCrypto_CENCEncryptPatternDesc& pattern) { OEMCryptoResult sts = OEMCrypto_ERROR_BUFFER_TOO_LARGE; // If there's only one subsample and it contains only one type of region, // automatically fall through to avoid a redundant roundtrip through // OEMCrypto_DecryptCENC() if (sample.subsamples_length > 1 || (sample.subsamples[0].num_bytes_clear > 0 && sample.subsamples[0].num_bytes_encrypted > 0)) { WithOecSessionLock("DecryptSample", [&] { sts = key_session_->Decrypt(&sample, 1, pattern); }); } if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE) { // Fall back to sending each subsample region individually sts = OEMCrypto_SUCCESS; OEMCrypto_SampleDescription fake_sample = sample; for (size_t i = 0; i < sample.subsamples_length; ++i) { const OEMCrypto_SubSampleDescription& original_subsample = sample.subsamples[i]; if (original_subsample.num_bytes_clear > 0) { const size_t length = original_subsample.num_bytes_clear; OEMCrypto_SubSampleDescription clear_subsample{ length, 0, // num_bytes_encrypted 0, // subsample_flags 0 // block_offset, not relevant for clear data }; if (original_subsample.subsample_flags & OEMCrypto_FirstSubsample) { clear_subsample.subsample_flags |= OEMCrypto_FirstSubsample; } if ((original_subsample.subsample_flags & OEMCrypto_LastSubsample) && original_subsample.num_bytes_encrypted == 0) { clear_subsample.subsample_flags |= OEMCrypto_LastSubsample; } fake_sample.buffers.input_data_length = length; fake_sample.subsamples = &clear_subsample; fake_sample.subsamples_length = 1; sts = LegacyDecrypt(fake_sample, cipher_mode, pattern); if (sts != OEMCrypto_SUCCESS) break; fake_sample.buffers.input_data += length; AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length); } if (original_subsample.num_bytes_encrypted > 0) { const size_t length = original_subsample.num_bytes_encrypted; OEMCrypto_SubSampleDescription encrypted_subsample{ 0, // num_bytes_clear length, 0, // subsample_flags original_subsample.block_offset}; if ((original_subsample.subsample_flags & OEMCrypto_FirstSubsample) && original_subsample.num_bytes_clear == 0) { encrypted_subsample.subsample_flags |= OEMCrypto_FirstSubsample; } if (original_subsample.subsample_flags & OEMCrypto_LastSubsample) { encrypted_subsample.subsample_flags |= OEMCrypto_LastSubsample; } fake_sample.buffers.input_data_length = length; fake_sample.subsamples = &encrypted_subsample; fake_sample.subsamples_length = 1; sts = LegacyDecrypt(fake_sample, cipher_mode, pattern); if (sts != OEMCrypto_SUCCESS) break; fake_sample.buffers.input_data += length; AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, length); if (cipher_mode == kCipherModeCtr) { wvutil::AdvanceIvCtr(&fake_sample.iv, original_subsample.block_offset + original_subsample.num_bytes_encrypted); } } } } return sts; } OEMCryptoResult CryptoSession::LegacyDecrypt( const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode, const OEMCrypto_CENCEncryptPatternDesc& pattern) { const size_t max_chunk_size = GetMaxSubsampleRegionSize(); OEMCryptoResult sts = OEMCrypto_ERROR_NOT_IMPLEMENTED; // We can be sure this is only called with one subsample containing one // region of data. const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0]; const bool is_encrypted = (subsample.num_bytes_encrypted > 0); const bool is_only_subsample = subsample.subsample_flags == (OEMCrypto_FirstSubsample | OEMCrypto_LastSubsample); if (!is_encrypted && is_only_subsample) { WithOecSessionLock("LegacyDecrypt() calling CopyBuffer", [&] { M_TIME(sts = OEMCrypto_CopyBuffer( oec_session_id_, sample.buffers.input_data, sample.buffers.input_data_length, &sample.buffers.output_descriptor, subsample.subsample_flags), metrics_, oemcrypto_copy_buffer_, sts, metrics::Pow2Bucket(sample.buffers.input_data_length)); }); if (sts == OEMCrypto_ERROR_BUFFER_TOO_LARGE && sample.buffers.input_data_length > max_chunk_size) { // OEMCrypto_CopyBuffer rejected the buffer as too large, so chunk it up // into 100 KiB sections. sts = LegacyCopyBufferInChunks(sample, max_chunk_size); } } if (is_encrypted || sts == OEMCrypto_ERROR_NOT_IMPLEMENTED) { WithOecSessionLock("LegacyDecrypt() calling key_session_->Decrypt()", [&] { sts = key_session_->Decrypt(&sample, 1, pattern); }); 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. const size_t pattern_length = (pattern.encrypt + pattern.skip) * kAes128BlockSize; const size_t chunk_size = pattern_length > 0 ? max_chunk_size - (max_chunk_size % pattern_length) : max_chunk_size; if (sample.buffers.input_data_length > chunk_size) { sts = LegacyDecryptInChunks(sample, cipher_mode, pattern, chunk_size); } } } return sts; } OEMCryptoResult CryptoSession::LegacyCopyBufferInChunks( const OEMCrypto_SampleDescription& sample, size_t max_chunk_size) { const uint8_t* input_data = sample.buffers.input_data; size_t remaining_input_data = sample.buffers.input_data_length; OEMCrypto_DestBufferDesc output_descriptor = sample.buffers.output_descriptor; uint8_t subsample_flags = OEMCrypto_FirstSubsample; OEMCryptoResult sts = OEMCrypto_SUCCESS; while (remaining_input_data > 0) { // Calculate the size of the next chunk. const size_t chunk_size = std::min(remaining_input_data, max_chunk_size); // Re-add "last subsample" flag if this is the last subsample. if (chunk_size == remaining_input_data) { subsample_flags |= OEMCrypto_LastSubsample; } WithOecSessionLock("LegacyCopyBufferInChunks", [&] { M_TIME(sts = OEMCrypto_CopyBuffer(oec_session_id_, input_data, chunk_size, &output_descriptor, subsample_flags), metrics_, oemcrypto_copy_buffer_, sts, metrics::Pow2Bucket(chunk_size)); }); if (sts != OEMCrypto_SUCCESS) break; // Clear any subsample flags before the next loop iteration. subsample_flags = 0; // Update the source and destination buffers based on the amount of data // copied. input_data += chunk_size; remaining_input_data -= chunk_size; AdvanceDestBuffer(&output_descriptor, chunk_size); } return sts; } OEMCryptoResult CryptoSession::LegacyDecryptInChunks( const OEMCrypto_SampleDescription& sample, CdmCipherMode cipher_mode, const OEMCrypto_CENCEncryptPatternDesc& pattern, size_t max_chunk_size) { const OEMCrypto_SubSampleDescription& subsample = sample.subsamples[0]; const bool is_protected = (subsample.num_bytes_encrypted > 0); OEMCrypto_SampleDescription fake_sample = sample; OEMCrypto_SubSampleDescription fake_subsample = subsample; fake_sample.subsamples = &fake_subsample; fake_subsample.subsample_flags = subsample.subsample_flags & OEMCrypto_FirstSubsample; size_t remaining_input_data = sample.buffers.input_data_length; OEMCryptoResult sts = OEMCrypto_SUCCESS; while (remaining_input_data > 0) { // Calculate the size of the next chunk. const size_t chunk_size = std::min(remaining_input_data, max_chunk_size); fake_sample.buffers.input_data_length = chunk_size; if (is_protected) { fake_subsample.num_bytes_encrypted = chunk_size; } else { fake_subsample.num_bytes_clear = chunk_size; } // Re-add "last subsample" flag if this is the last subsample. if (chunk_size == remaining_input_data) { fake_subsample.subsample_flags |= subsample.subsample_flags & OEMCrypto_LastSubsample; } // |pattern| and |fake_subsample.block_offset| do not need to change because // |max_chunk_size| is guaranteed to be an even multiple of the // pattern length long, which is also guaranteed to be an exact number // of AES blocks long. WithOecSessionLock("LegacyDecryptInChunks", [&] { sts = key_session_->Decrypt(&fake_sample, 1, pattern); }); if (sts != OEMCrypto_SUCCESS) break; // Clear any subsample flags before the next loop iteration. fake_subsample.subsample_flags = 0; // Update the IV so that it is valid for the next iteration. This should not // be done on the last iteration both to save time and because the 'cbcs' // calculation can underflow if the chunk is less than the max chunk size. if (remaining_input_data > chunk_size) { if (cipher_mode == kCipherModeCtr) { // For 'cenc', update the IV depending on how many encrypted blocks // we passed. wvutil::AdvanceIvCtr(&fake_sample.iv, chunk_size + fake_subsample.block_offset); } else if (cipher_mode == kCipherModeCbc) { // For 'cbcs', use the last ciphertext block as the next IV. The last // block that was encrypted is probably not the last block of the // subsample. Since the max 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. const uint8_t* const buffer_end = fake_sample.buffers.input_data + chunk_size; const uint8_t* const block_end = buffer_end - kAes128BlockSize * pattern.skip; static_assert(sizeof(fake_sample.iv) == kAes128BlockSize, "The size of an AES-128 block and the size of an AES-128 " "IV have become misaligned"); memcpy(fake_sample.iv, block_end - kAes128BlockSize, kAes128BlockSize); } } // Update the source and destination buffers based on the amount of data // copied. fake_sample.buffers.input_data += chunk_size; remaining_input_data -= chunk_size; AdvanceDestBuffer(&fake_sample.buffers.output_descriptor, chunk_size); } return sts; } CdmResponseType CryptoSession::SetDebugIgnoreKeyboxCount(uint32_t count) { OEMCryptoResult status = OEMCrypto_SetDebugIgnoreKeyboxCount(count); return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetDebugIgnoreKeyboxCount"); } CdmResponseType CryptoSession::SetAllowTestKeybox(bool allow) { OEMCryptoResult status = OEMCrypto_SetAllowTestKeybox(allow); return MapOEMCryptoResult(status, UNKNOWN_ERROR, "SetAllowTestKeybox"); } okp::SystemFallbackPolicy* CryptoSession::GetOkpFallbackPolicy() { const auto getter = [&]() -> okp::SystemFallbackPolicy* { // If not set, then OTA keybox provisioning is not supported or // not needed. if (!okp_fallback_policy_l1_) return nullptr; return okp_fallback_policy_l1_.get(); }; return WithStaticFieldReadLock("GetOkpFallbackPolicy", getter); } CdmResponseType CryptoSession::PrepareOtaProvisioningRequest( bool use_test_key, std::string* request) { RETURN_IF_NULL(request, PARAMETER_NULL); RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); size_t buffer_length = 0; OEMCryptoResult status = WithOecWriteLock("PrepareOtaProvisioningRequest", [&] { return OEMCrypto_GenerateOTARequest( oec_session_id_, nullptr, &buffer_length, use_test_key ? 1 : 0); }); if (status != OEMCrypto_ERROR_SHORT_BUFFER) return MapOEMCryptoResult(status, UNKNOWN_ERROR, "PrepareOtaProvisioningRequest"); if (buffer_length == 0) { LOGE("OTA request size is zero"); return CdmResponseType(UNKNOWN_ERROR); } request->resize(buffer_length); uint8_t* buf = reinterpret_cast(&request->front()); status = WithOecWriteLock("PrepareOtaProvisioningRequest", [&] { return OEMCrypto_GenerateOTARequest(oec_session_id_, buf, &buffer_length, use_test_key ? 1 : 0); }); if (OEMCrypto_SUCCESS != status) { request->clear(); } else if (buffer_length != request->size()) { request->resize(buffer_length); } return MapOEMCryptoResult(status, UNKNOWN_ERROR, "PrepareOtaProvisioningRequest"); } CdmResponseType CryptoSession::LoadOtaProvisioning( bool use_test_key, const std::string& response) { RETURN_IF_NOT_OPEN(CRYPTO_SESSION_NOT_OPEN); const OEMCryptoResult status = WithOecWriteLock("LoadOtaProvisioning", [&] { return OEMCrypto_ProcessOTAKeybox( oec_session_id_, reinterpret_cast(response.data()), response.size(), use_test_key ? 1 : 0); }); if (status == OEMCrypto_SUCCESS) { WithOecWriteLock("LoadOtaProvisioning", [&] { needs_keybox_provisioning_ = false; }); } return MapOEMCryptoResult(status, UNKNOWN_ERROR, "LoadOtaProvisioning"); } template auto CryptoSession::WithStaticFieldWriteLock(const char* tag, Func body) -> decltype(body()) { LOGV("Static field write lock: %s", tag); std::unique_lock auto_lock(static_field_mutex_); return body(); } template auto CryptoSession::WithStaticFieldReadLock(const char* tag, Func body) -> decltype(body()) { LOGV("Static field read lock: %s", tag); wvutil::shared_lock auto_lock(static_field_mutex_); return body(); } template auto CryptoSession::WithOecWriteLock(const char* tag, Func body) -> decltype(body()) { LOGV("OEMCrypto write lock: %s", tag); std::unique_lock auto_lock(oem_crypto_mutex_); return body(); } template auto CryptoSession::WithOecReadLock(const char* tag, Func body) -> decltype(body()) { LOGV("OEMCrypto read lock: %s", tag); wvutil::shared_lock auto_lock(oem_crypto_mutex_); return body(); } template auto CryptoSession::WithOecSessionLock(const char* tag, Func body) -> decltype(body()) { LOGV("OEMCrypto session lock: %s", tag); wvutil::shared_lock oec_auto_lock(oem_crypto_mutex_); std::unique_lock session_auto_lock(oem_crypto_session_mutex_); return body(); } bool CryptoSession::IsInitialized() { return WithStaticFieldReadLock("IsInitialized", [] { return initialized_; }); } // CryptoSesssionFactory support std::mutex CryptoSession::factory_mutex_; // The factory will either be set by WvCdmTestBase, or a default factory is // created on the first call to MakeCryptoSession. std::unique_ptr CryptoSession::factory_ = std::unique_ptr(); CryptoSession* CryptoSession::MakeCryptoSession( metrics::CryptoMetrics* crypto_metrics) { std::unique_lock auto_lock(factory_mutex_); // If the factory_ has not been set, then use a default factory. if (!factory_) factory_.reset(new CryptoSessionFactory()); return factory_->MakeCryptoSession(crypto_metrics); } CryptoSession* CryptoSessionFactory::MakeCryptoSession( metrics::CryptoMetrics* crypto_metrics) { return new CryptoSession(crypto_metrics); } } // namespace wvcdm