blob: 57ee45e1c4396a264ec60d5b6219901adc35e71c [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/security/kms-stateless/kms-stateless.h"
#include <fcntl.h>
#include <lib/fdio/watcher.h>
#include <lib/zx/clock.h>
#include <lib/zx/time.h>
#include <stdint.h>
#include <stdio.h>
#include <filesystem>
#include <memory>
#include <fbl/string_buffer.h>
#include <fbl/unique_fd.h>
#include <ramdevice-client/ramdisk.h>
#include "src/security/keysafe/keysafe.h"
#include "tee-client-api/tee_client_api.h"
namespace kms_stateless {
namespace {
const size_t kDerivedKeySize = 16;
const char kDeviceClass[] = "/dev/class/tee";
// Path estimated to be "/dev/class/tee/XXX".
const size_t kMaxPathLen = 64;
// UUID of the keysafe TA.
const TEEC_UUID kKeysafeTaUuid = TA_KEYSAFE_UUID;
// Wrapper around TEEC_Session to ensure correct deletion.
class ScopedTeecSession {
public:
ScopedTeecSession(TEEC_Session session_) { session = session_; }
TEEC_Result invokeCommand(uint32_t commandID, TEEC_Operation* operation) {
return TEEC_InvokeCommand(&session, commandID, operation, nullptr);
}
~ScopedTeecSession() { TEEC_CloseSession(&session); }
private:
TEEC_Session session;
};
// Wrapper around TEEC_Context to ensure correct deletion.
class ScopedTeecContext {
public:
ScopedTeecContext() : context_({}), initialized_(false) {}
~ScopedTeecContext() {
if (initialized_) {
TEEC_FinalizeContext(&context_);
}
}
TEEC_Result initialize(const char* device_path) {
TEEC_Result result = TEEC_InitializeContext(device_path, &context_);
if (result == TEEC_SUCCESS) {
initialized_ = true;
}
return result;
}
std::unique_ptr<ScopedTeecSession> openSession() {
TEEC_Session session = {0, 0};
TEEC_Result result = TEEC_OpenSession(&context_, &session, &kKeysafeTaUuid, TEEC_LOGIN_PUBLIC,
0 /* connectionData*/, nullptr /* operation */,
nullptr /* returnOrigin */);
if (result != TEEC_SUCCESS) {
fprintf(stderr, "TEE Unable to open session. Error: %X\n", result);
return std::unique_ptr<ScopedTeecSession>();
}
std::unique_ptr<ScopedTeecSession> session_ptr = std::make_unique<ScopedTeecSession>(session);
return session_ptr;
}
private:
TEEC_Context context_;
bool initialized_;
};
// Gets a hardware derived key from a tee device at |device_path|.
//
// Arguments:
// device_path: The path to the tee device. If device_path is nullptr, we would let tee client
// api select the correct path to connect.
// key_info: The key information as part of the input to the key derivation function.
// key_info_size: The size of |key_info|.
// key_buffer: The caller allocated buffer to store the derived key.
// key_size: The size of the derived key.
//
// Returns true if the operation succeed, false otherwise.
bool GetKeyFromTeeDevice(const char* device_path, uint8_t* key_info, size_t key_info_size,
uint8_t* key_buffer, size_t* key_size, size_t key_buffer_size) {
ScopedTeecContext scoped_teec_context;
TEEC_Result result = scoped_teec_context.initialize(device_path);
if (result != TEEC_SUCCESS) {
fprintf(stderr, "Failed to initialize TEE context: %X\n", result);
return false;
}
std::unique_ptr<ScopedTeecSession> session_ptr = scoped_teec_context.openSession();
if (!session_ptr.get()) {
fprintf(stderr, "Failed to open TEE Session to Keysafe!\n");
return false;
}
TEEC_Operation op{};
op.paramTypes =
TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_NONE, TEEC_NONE, TEEC_MEMREF_TEMP_OUTPUT);
op.params[0].tmpref.buffer = key_info;
op.params[0].tmpref.size = key_info_size;
op.params[3].tmpref.buffer = key_buffer;
op.params[3].tmpref.size = key_buffer_size;
result = session_ptr->invokeCommand(TA_KEYSAFE_CMD_GET_USER_DATA_STORAGE_KEY, &op);
if (result == TEEC_ERROR_SHORT_BUFFER) {
fprintf(stderr, "Output buffer for TEE key is too small!\n");
*key_size = op.params[3].tmpref.size;
return false;
}
if (result != TEEC_SUCCESS) {
fprintf(stderr, "Failed to get TEE key: result=0x%x\n", result);
return false;
}
*key_size = op.params[3].tmpref.size;
return true;
}
// Rotates an existing hardware derived key from a tee device at |device_path|.
//
// Arguments:
// device_path: The path to the tee device. If device_path is nullptr, we would let tee client
// api select the correct path to connect.
// key_info: The key information that identifies the key to be rotated.
// key_info_size: The size of |key_info|.
//
// Returns TEEC_SUCCESS if the operation succeeds, TEEC_ERROR_* otherwise.
static TEEC_Result RotateKeyFromTeeDevice(const char* device_path, uint8_t* key_info,
size_t key_info_size) {
ScopedTeecContext scoped_teec_context;
TEEC_Result result = scoped_teec_context.initialize(device_path);
if (result != TEEC_SUCCESS) {
fprintf(stderr, "Failed to initialize TEE context: %X\n", result);
return result;
}
std::unique_ptr<ScopedTeecSession> session_ptr = scoped_teec_context.openSession();
if (!session_ptr.get()) {
fprintf(stderr, "Failed to open TEE Session to Keysafe!\n");
return TEEC_ERROR_GENERIC;
}
TEEC_Operation op{};
op.paramTypes = TEEC_PARAM_TYPES(TEEC_MEMREF_TEMP_INPUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);
op.params[0].tmpref.buffer = key_info;
op.params[0].tmpref.size = key_info_size;
result = session_ptr->invokeCommand(TA_KEYSAFE_CMD_ROTATE_HARDWARE_DERIVED_KEY, &op);
if (result != TEEC_SUCCESS) {
fprintf(stderr, "Failed to rotate TEE key: result=0x%x\n", result);
return result;
}
return TEEC_SUCCESS;
}
// The structure to pass as cookie to fdio_watch_directory callback function.
struct WatchTeeArgs {
// The callback function called when a hardware key is successfully derived.
GetHardwareDerivedKeyCallback callback;
uint8_t* key_info;
size_t key_info_size;
};
// Callback function called when a TEE device is found.
zx_status_t WatchTee(int dirfd, int event, const char* filename, void* cookie) {
fbl::StringBuffer<kMaxPathLen> device_path;
device_path.Append(kDeviceClass).Append("/").Append(filename);
// Hardware derived key is expected to be 128-bit AES key.
std::unique_ptr<uint8_t[]> key_buffer(new uint8_t[kDerivedKeySize]);
size_t key_size = 0;
WatchTeeArgs* args = reinterpret_cast<WatchTeeArgs*>(cookie);
if (!GetKeyFromTeeDevice(device_path.c_str(), std::move(args->key_info), args->key_info_size,
key_buffer.get(), &key_size, kDerivedKeySize)) {
fprintf(stderr, "Failed to get hardware derived key from TEE!\n");
return ZX_ERR_IO;
}
if (key_size != kDerivedKeySize) {
fprintf(stderr, "The hardware derived key is of wrong size!\n");
return ZX_ERR_IO;
}
zx_status_t status = args->callback(std::move(key_buffer), key_size);
if (status == ZX_OK) {
return ZX_ERR_STOP;
} else {
fprintf(stderr, "Get hardware key callback function returns error!\n");
return status;
}
}
} // namespace
zx_status_t GetHardwareDerivedKey(GetHardwareDerivedKeyCallback callback,
uint8_t key_info[kExpectedKeyInfoSize]) {
if (wait_for_device(kDeviceClass, ZX_SEC(5)) != ZX_OK) {
fprintf(stderr, "Error waiting for tee device directory!\n");
return ZX_ERR_IO;
}
fbl::unique_fd dirfd(open(kDeviceClass, O_RDONLY));
if (!dirfd.is_valid()) {
fprintf(stderr, "Failed to open tee device directory!\n");
return ZX_ERR_IO;
}
WatchTeeArgs args = {std::move(callback), std::move(key_info), kExpectedKeyInfoSize};
zx_status_t watch_status =
fdio_watch_directory(dirfd.get(), WatchTee, ZX_SEC(5), reinterpret_cast<void*>(&args));
if (watch_status != ZX_ERR_STOP) {
fprintf(stderr, "Failed to get hardware derived key!\n");
return watch_status;
}
return ZX_OK;
}
zx_status_t GetHardwareDerivedKeyFromService(GetHardwareDerivedKeyCallback callback,
uint8_t key_info[kExpectedKeyInfoSize]) {
// Hardware derived key is expected to be 128-bit AES key.
std::unique_ptr<uint8_t[]> key_buffer(new uint8_t[kDerivedKeySize]);
size_t key_size = 0;
if (!GetKeyFromTeeDevice(nullptr, key_info, kExpectedKeyInfoSize, key_buffer.get(), &key_size,
kDerivedKeySize)) {
fprintf(stderr, "Failed to get hardware derived key from TEE!\n");
return ZX_ERR_IO;
}
if (key_size != kDerivedKeySize) {
fprintf(stderr, "The hardware derived key is of wrong size!\n");
return ZX_ERR_IO;
}
return callback(std::move(key_buffer), key_size);
}
zx_status_t RotateHardwareDerivedKeyFromService(uint8_t key_info[kExpectedKeyInfoSize]) {
TEEC_Result result = RotateKeyFromTeeDevice(nullptr, key_info, kExpectedKeyInfoSize);
if (result == TEEC_ERROR_NOT_SUPPORTED) {
fprintf(stderr, "Hardware key rotation not supported by TEE!\n");
return ZX_ERR_NOT_SUPPORTED;
}
if (result != TEEC_SUCCESS) {
fprintf(stderr, "Failed to rotate hardware key from TEE!\n");
return ZX_ERR_IO;
}
return ZX_OK;
}
} // namespace kms_stateless