// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary // source code may only be used and distributed under the Widevine // License Agreement. // // Reference implementation of OEMCrypto APIs // #include "oemcrypto_engine_ref.h" #include #include #include #include #include #include #include #include #include "clock.h" #include "keys.h" #include "log.h" #include "oemcrypto_key_ref.h" #include "oemcrypto_rsa_key_shared.h" #include "string_conversions.h" namespace { // Lower bits in SessionId are actual session id. The rest higher bits are // session type. const uint32_t kSessionIdTypeShift = 28; const uint32_t kSessionIdMask = (1u << kSessionIdTypeShift) - 1u; } // namespace namespace wvoec_ref { // Note: The class CryptoEngine is configured at compile time by compiling in // different device property files. The methods in this file are generic to // all configurations. See the files oemcrypto_engine_device_properties*.cpp // for methods that are configured for specific configurations. CryptoEngine::CryptoEngine(std::unique_ptr&& file_system) : file_system_(std::move(file_system)), usage_table_() { ERR_load_crypto_strings(); } CryptoEngine::~CryptoEngine() { ERR_free_strings(); } bool CryptoEngine::Initialize() { std::string file_path = GetUsageTimeFileFullPath(); LoadOfflineTimeInfo(file_path); usage_table_.reset(MakeUsageTable()); return root_of_trust_.Initialize(config_provisioning_method()); } void CryptoEngine::Terminate() { std::string file_path = GetUsageTimeFileFullPath(); SaveOfflineTimeInfo(file_path); std::unique_lock lock(session_table_lock_); ActiveSessions::iterator it; for (it = sessions_.begin(); it != sessions_.end(); ++it) { delete it->second; } sessions_.clear(); root_of_trust_.Clear(); } SessionId CryptoEngine::OpenSession() { std::unique_lock lock(session_table_lock_); static OEMCrypto_SESSION unique_id = 1; SessionId id = ++unique_id; // Check if too many sessions have been opened. if (SessionTypeBits(id) != 0) { return 0; } // Apply session type to higher bits. id = (kSessionTypeOEMCrypto << kSessionIdTypeShift) | (id & kSessionIdMask); sessions_[id] = MakeSession(id); return id; } SessionContext* CryptoEngine::MakeSession(SessionId sid) { return new SessionContext(this, sid, root_of_trust_.SharedRsaKey()); } UsageTable* CryptoEngine::MakeUsageTable() { return new UsageTable(this); } bool CryptoEngine::DestroySession(SessionId sid) { SessionContext* sctx = FindSession(sid); std::unique_lock lock(session_table_lock_); if (sctx) { sessions_.erase(sid); delete sctx; return true; } else { return false; } } SessionContext* CryptoEngine::FindSession(SessionId sid) { std::unique_lock lock(session_table_lock_); ActiveSessions::iterator it = sessions_.find(sid); if (it != sessions_.end()) { return it->second; } return nullptr; } int64_t CryptoEngine::MonotonicTime() { // Use the monotonic clock for times that don't have to be stable across // device boots. int64_t now = wvcdm::Clock().GetCurrentTime() + offline_time_info_.rollback_offset; static int64_t then = now; if (now < then) { LOGW("Clock rollback detected: %ld seconds", then - now); offline_time_info_.rollback_offset += then - now; now = then; } then = now; return now; } int64_t CryptoEngine::SystemTime() { const int64_t current_time = MonotonicTime(); // Write time info to disk if kTimeInfoUpdateWindowInSeconds has elapsed since // last write. if (current_time - offline_time_info_.previous_time > kTimeInfoUpdateWindowInSeconds) { std::string file_path = GetUsageTimeFileFullPath(); SaveOfflineTimeInfo(file_path); } return current_time; } std::string CryptoEngine::GetUsageTimeFileFullPath() const { std::string file_path; // Note: file path is OK for a real implementation, but using security // level 1 would be better. // TODO(fredgc, jfore): Address how this property is presented to the ref. // For now, the file path is empty. /*if (!wvcdm::Properties::GetDeviceFilesBasePath(wvcdm::kSecurityLevelL3, &file_path)) { LOGE("Unable to get base path"); }*/ return file_path + kStoredUsageTimeFileName; } bool CryptoEngine::LoadOfflineTimeInfo(const std::string& file_path) { memset(&offline_time_info_, 0, sizeof(TimeInfo)); wvcdm::FileSystem* file_system = file_system_.get(); if (file_system->Exists(file_path)) { std::unique_ptr file = file_system->Open(file_path, wvcdm::FileSystem::kReadOnly); if (!file) { // This error is expected at first initialization. LOGE("File open failed (this is expected on first initialization): %s", file_path.c_str()); return false; } // Load time info from previous call. file->Read(reinterpret_cast(&offline_time_info_), sizeof(TimeInfo)); // Detect offline time rollback after loading from disk. // Add any time offsets in the past to the current time. int64_t current_time = MonotonicTime(); if (offline_time_info_.previous_time > current_time) { // Current time is earlier than the previously saved time. Time has been // rolled back. Update the rollback offset. offline_time_info_.rollback_offset += offline_time_info_.previous_time - current_time; // Keep current time at previous recorded time. current_time = offline_time_info_.previous_time; } // The new previous_time will either stay the same or move forward. offline_time_info_.previous_time = current_time; } return true; } bool CryptoEngine::SaveOfflineTimeInfo(const std::string& file_path) { // Add any time offsets in the past to the current time. If there was an // earlier offline rollback, the rollback offset will be updated in // LoadOfflineTimeInfo(). It guarantees that the current time to be saved // will never go back. const int64_t current_time = MonotonicTime(); // The new previous_time will either stay the same or move forward. if (current_time > offline_time_info_.previous_time) offline_time_info_.previous_time = current_time; std::unique_ptr file; wvcdm::FileSystem* file_system = file_system_.get(); // Write the current time and offset to disk. file = file_system->Open( file_path, wvcdm::FileSystem::kCreate | wvcdm::FileSystem::kTruncate); if (!file) { LOGE("File open failed: %s", file_path.c_str()); return false; } file->Write(reinterpret_cast(&offline_time_info_), sizeof(TimeInfo)); return true; } bool CryptoEngine::NonceCollision(uint32_t nonce) { for (const auto& session_pair : sessions_) { const SessionContext* session = session_pair.second; if (nonce == session->nonce()) return true; } return false; } OEMCrypto_HDCP_Capability CryptoEngine::config_current_hdcp_capability() { return config_local_display_only() ? HDCP_NO_DIGITAL_OUTPUT : HDCP_V1; } OEMCrypto_HDCP_Capability CryptoEngine::config_maximum_hdcp_capability() { return HDCP_NO_DIGITAL_OUTPUT; } OEMCryptoResult CryptoEngine::SetDestination( const OEMCrypto_DestBufferDesc& out_description, size_t data_length, uint8_t subsample_flags) { size_t max_length = 0; switch (out_description.type) { case OEMCrypto_BufferType_Clear: destination_ = out_description.buffer.clear.address; max_length = out_description.buffer.clear.address_length; break; case OEMCrypto_BufferType_Secure: if (out_description.buffer.secure.handle_length < out_description.buffer.secure.offset) { LOGE("Secure buffer offset too large: %zu < %zu", out_description.buffer.secure.handle_length, out_description.buffer.secure.offset); return OEMCrypto_ERROR_SHORT_BUFFER; } destination_ = reinterpret_cast(out_description.buffer.secure.handle) + out_description.buffer.secure.offset; max_length = out_description.buffer.secure.handle_length - out_description.buffer.secure.offset; break; case OEMCrypto_BufferType_Direct: // Direct buffer type is only used on some specialized devices where // oemcrypto has a direct connection to the screen buffer. It is not, // for example, supported on Android. destination_ = nullptr; break; default: return OEMCrypto_ERROR_INVALID_CONTEXT; } const size_t max_allowed = max_sample_size(); if (max_allowed > 0 && (max_allowed < max_length || max_allowed < data_length)) { LOGE("Output too large (or buffer too small)."); return OEMCrypto_ERROR_OUTPUT_TOO_LARGE; } if (out_description.type != OEMCrypto_BufferType_Direct && max_length < data_length) { LOGE("[SetDestination(): OEMCrypto_ERROR_SHORT_BUFFER]"); return OEMCrypto_ERROR_SHORT_BUFFER; } adjust_destination(out_description, data_length, subsample_flags); if ((out_description.type != OEMCrypto_BufferType_Direct) && (destination_ == nullptr)) { return OEMCrypto_ERROR_INVALID_CONTEXT; } return OEMCrypto_SUCCESS; } uint32_t CryptoEngine::SessionTypeBits(SessionId sid) { return sid >> kSessionIdTypeShift; } } // namespace wvoec_ref