Merge from Widevine repo of http://go/wvgerrit/96843 This CL modifies the system time used by the reference OEMCrypto so that it recovers from a clock rollback. When the clock rolls back, it now adjusts the current time and continues forward. This is needed when running unit tests on some platforms that reset the clock at the beginning of a test. Bug: 152649427 Test: unit tests on buildbot. (No production code on Android) Change-Id: I7edcdc0cd4e5938c9a54e745d3a0e008f9eb13ed
287 lines
9.7 KiB
C++
287 lines
9.7 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 "oemcrypto_rsa_key_shared.h"
|
|
#include "string_conversions.h"
|
|
|
|
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)
|
|
: root_of_trust_(config_provisioning_method()),
|
|
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 true;
|
|
}
|
|
|
|
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;
|
|
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<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) {
|
|
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::vector<uint8_t> encrypted_buffer(sizeof(TimeInfo));
|
|
std::vector<uint8_t> clear_buffer(sizeof(TimeInfo));
|
|
|
|
KeyboxError error_code = ValidateKeybox();
|
|
if (error_code != NO_ERROR) {
|
|
LOGE("Keybox is invalid: %d", error_code);
|
|
return false;
|
|
}
|
|
// Use the device key for encrypt/decrypt.
|
|
const std::vector<uint8_t>& key = DeviceRootKey();
|
|
std::unique_ptr<wvcdm::File> file =
|
|
file_system->Open(file_path, wvcdm::FileSystem::kReadOnly);
|
|
if (!file) {
|
|
LOGE("File open failed: %s", file_path.c_str());
|
|
return false;
|
|
}
|
|
// Load time info from previous call.
|
|
file->Read(reinterpret_cast<char*>(&encrypted_buffer[0]), sizeof(TimeInfo));
|
|
// Decrypt the encrypted TimeInfo buffer.
|
|
AES_KEY aes_key;
|
|
AES_set_decrypt_key(&key[0], 128, &aes_key);
|
|
std::vector<uint8_t> iv(wvoec::KEY_IV_SIZE, 0);
|
|
AES_cbc_encrypt(&encrypted_buffer[0], &clear_buffer[0], sizeof(TimeInfo),
|
|
&aes_key, iv.data(), AES_DECRYPT);
|
|
memcpy(&offline_time_info_, &clear_buffer[0], 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;
|
|
|
|
KeyboxError error_code = ValidateKeybox();
|
|
if (error_code != NO_ERROR) {
|
|
LOGE("Keybox is invalid: %d", error_code);
|
|
return false;
|
|
}
|
|
// Use the device key for encrypt/decrypt.
|
|
const std::vector<uint8_t>& key = DeviceRootKey();
|
|
std::vector<uint8_t> encrypted_buffer(sizeof(TimeInfo));
|
|
std::vector<uint8_t> clear_buffer(sizeof(TimeInfo));
|
|
|
|
// Copy updated data and encrypt the buffer.
|
|
memcpy(&clear_buffer[0], &offline_time_info_, sizeof(TimeInfo));
|
|
AES_KEY aes_key;
|
|
AES_set_encrypt_key(&key[0], 128, &aes_key);
|
|
std::vector<uint8_t> iv(wvoec::KEY_IV_SIZE, 0);
|
|
AES_cbc_encrypt(&clear_buffer[0], &encrypted_buffer[0], sizeof(TimeInfo),
|
|
&aes_key, iv.data(), AES_ENCRYPT);
|
|
|
|
std::unique_ptr<wvcdm::File> file;
|
|
wvcdm::FileSystem* file_system = file_system_.get();
|
|
// Write the encrypted buffer 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*>(&encrypted_buffer[0]), 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:
|
|
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;
|
|
}
|
|
|
|
} // namespace wvoec_ref
|