Files
android/libwvdrmengine/oemcrypto/ref/src/oemcrypto_engine_ref.cpp
Alex Dale f6f5099604 Restructed reference root of trust (2/3 DRM Cert)
[ Merge of http://go/wvgerrit/115551 ]

This change is the second part of a three part change for restructing
the root of trust used by the reference implementation.

The use of RSA_shared_ptr has been replaced with the standard library
std::shared_ptr using the RsaPrivateKey wrapper class.  The
AuthenticationRoot class now uses this for the built-in DRM cert key.

RSA decryption and signature operations within the session context are
now performed the RsaPrivateKey class.  This has reduced the code size
and complexity within the reference and testbed, focusing their
implementation on key policy and less on mechanics.

Bug: 168544740
Bug: 135283522
Test: oemcrypto_unittests ce_cdm_tests
Change-Id: Ic743a529a9858f3182290d8bcf5e1633737b005b
2021-03-24 19:14:17 -07:00

280 lines
9.3 KiB
C++

// Copyright 2018 Google LLC. All Rights Reserved. This file and proprietary
// source code may only be used and distributed under the Widevine Master
// License Agreement.
//
// Reference implementation of OEMCrypto APIs
//
#include "oemcrypto_engine_ref.h"
#include <assert.h>
#include <string.h>
#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>
#include <openssl/aes.h>
#include <openssl/err.h>
#include "clock.h"
#include "keys.h"
#include "log.h"
#include "oemcrypto_key_ref.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<wvcdm::FileSystem>&& 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<std::mutex> 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<std::mutex> 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) {
if (root_of_trust_.HasDrmCertKey()) {
return new SessionContext(this, sid, root_of_trust_.ShareDrmCertKey());
}
return new SessionContext(this, sid);
}
UsageTable* CryptoEngine::MakeUsageTable() { return new UsageTable(this); }
bool CryptoEngine::DestroySession(SessionId sid) {
SessionContext* sctx = FindSession(sid);
std::unique_lock<std::mutex> lock(session_table_lock_);
if (sctx) {
sessions_.erase(sid);
delete sctx;
return true;
} else {
return false;
}
}
SessionContext* CryptoEngine::FindSession(SessionId sid) {
std::unique_lock<std::mutex> 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<wvcdm::File> 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<char*>(&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<wvcdm::File> 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<char*>(&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<uint8_t*>(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