blob: 67cfe8cbce8988740b617df590c6e244a16af991 [file] [log] [blame]
// Copyright 2021 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/zxcrypt/client.h"
#include <errno.h>
#include <fcntl.h>
#include <fidl/fuchsia.device/cpp/wire.h>
#include <fuchsia/hardware/block/encrypted/c/fidl.h>
#include <inttypes.h>
#include <lib/fdio/cpp/caller.h>
#include <lib/fdio/directory.h>
#include <lib/fdio/fdio.h>
#include <lib/zircon-internal/debug.h>
#include <lib/zx/channel.h>
#include <unistd.h>
#include <zircon/status.h>
#include <memory>
#include <utility>
#include <fbl/string_buffer.h>
#include <fbl/vector.h>
#include <ramdevice-client/ramdisk.h> // Why does wait_for_device_at() come from here?
#include "src/security/kms-stateless/kms-stateless.h"
#define ZXDEBUG 0
namespace zxcrypt {
// The zxcrypt driver
const char* kDriverLib = "zxcrypt.so";
namespace {
// Null key should be 32 bytes.
const size_t kKeyLength = 32;
const char kHardwareKeyInfo[] = "zxcrypt";
// How many bytes to read from /boot/config/zxcrypt?
const size_t kMaxKeySourcePolicyLength = 32;
const char kZxcryptConfigFile[] = "/pkg/config/zxcrypt";
// Reads /boot/config/zxcrypt to determine what key source policy was selected for this product at
// build time.
//
// Returns ZX_OK and sets |out| to the appropriate KeySourcePolicy value if the file contents
// exactly match a known configuration value.
// Returns ZX_ERR_NOT_FOUND if the config file was not present
// Returns ZX_ERR_IO if the config file could not be read
// Returns ZX_ERR_BAD_STATE if the config value was not recognized.
zx_status_t SelectKeySourcePolicy(KeySourcePolicy* out) {
fbl::unique_fd fd(open(kZxcryptConfigFile, O_RDONLY));
if (!fd) {
xprintf("zxcrypt: couldn't open %s\n", kZxcryptConfigFile);
return ZX_ERR_NOT_FOUND;
}
char key_source_buf[kMaxKeySourcePolicyLength + 1];
ssize_t len = read(fd.get(), key_source_buf, sizeof(key_source_buf) - 1);
if (len < 0) {
xprintf("zxcrypt: couldn't read %s\n", kZxcryptConfigFile);
return ZX_ERR_IO;
} else {
// add null terminator
key_source_buf[len] = '\0';
// Dispatch if recognized
if (strcmp(key_source_buf, "null") == 0) {
*out = NullSource;
return ZX_OK;
}
if (strcmp(key_source_buf, "tee") == 0) {
*out = TeeRequiredSource;
return ZX_OK;
}
if (strcmp(key_source_buf, "tee-transitional") == 0) {
*out = TeeTransitionalSource;
return ZX_OK;
}
if (strcmp(key_source_buf, "tee-opportunistic") == 0) {
*out = TeeOpportunisticSource;
return ZX_OK;
}
return ZX_ERR_BAD_STATE;
}
}
} // namespace
// Returns a ordered vector of |KeySource|s, representing all key sources,
// ordered from most-preferred to least-preferred, that we should try for the
// purposes of creating a new volume
__EXPORT
fbl::Vector<KeySource> ComputeEffectiveCreatePolicy(KeySourcePolicy ksp) {
fbl::Vector<KeySource> r;
switch (ksp) {
case NullSource:
r = {kNullSource};
break;
case TeeRequiredSource:
case TeeTransitionalSource:
r = {kTeeSource};
break;
case TeeOpportunisticSource:
r = {kTeeSource, kNullSource};
break;
}
return r;
}
// Returns a ordered vector of |KeySource|s, representing all key sources,
// ordered from most-preferred to least-preferred, that we should try for the
// purposes of unsealing an existing volume
__EXPORT
fbl::Vector<KeySource> ComputeEffectiveUnsealPolicy(KeySourcePolicy ksp) {
fbl::Vector<KeySource> r;
switch (ksp) {
case NullSource:
r = {kNullSource};
break;
case TeeRequiredSource:
r = {kTeeSource};
break;
case TeeTransitionalSource:
case TeeOpportunisticSource:
r = {kTeeSource, kNullSource};
break;
}
return r;
}
__EXPORT
fbl::Vector<KeySource> ComputeEffectivePolicy(KeySourcePolicy ksp, Activity activity) {
fbl::Vector<KeySource> r;
switch (activity) {
case Create:
r = ComputeEffectiveCreatePolicy(ksp);
break;
case Unseal:
r = ComputeEffectiveUnsealPolicy(ksp);
break;
}
return r;
}
__EXPORT
zx_status_t TryWithImplicitKeys(
Activity activity, fit::function<zx_status_t(std::unique_ptr<uint8_t[]>, size_t)> callback) {
KeySourcePolicy source;
zx_status_t rc;
rc = SelectKeySourcePolicy(&source);
if (rc != ZX_OK) {
return rc;
}
rc = ZX_ERR_INTERNAL;
auto ordered_key_sources = ComputeEffectivePolicy(source, activity);
for (auto& key_source : ordered_key_sources) {
switch (key_source) {
case kNullSource: {
auto key_buf = std::unique_ptr<uint8_t[]>(new uint8_t[kKeyLength]);
memset(key_buf.get(), 0, kKeyLength);
rc = callback(std::move(key_buf), kKeyLength);
} break;
case kTeeSource: {
// key info is |kHardwareKeyInfo| padded with 0.
uint8_t key_info[kms_stateless::kExpectedKeyInfoSize] = {0};
memcpy(key_info, kHardwareKeyInfo, sizeof(kHardwareKeyInfo));
// make names for these so the callback to kms_stateless can
// copy them out later
std::unique_ptr<uint8_t[]> key_buf;
size_t key_size;
zx_status_t kms_rc = kms_stateless::GetHardwareDerivedKey(
[&](std::unique_ptr<uint8_t[]> cb_key_buffer, size_t cb_key_size) {
key_size = cb_key_size;
key_buf = std::unique_ptr<uint8_t[]>(new uint8_t[cb_key_size]);
memcpy(key_buf.get(), cb_key_buffer.get(), cb_key_size);
return ZX_OK;
},
key_info);
if (kms_rc != ZX_OK) {
rc = kms_rc;
break;
}
rc = callback(std::move(key_buf), key_size);
} break;
}
if (rc == ZX_OK) {
return rc;
}
}
xprintf("TryWithImplicitKeys (%s): none of the %lu key sources succeeded\n",
activity == Activity::Create ? "create" : "unseal", ordered_key_sources.size());
return rc;
}
EncryptedVolumeClient::EncryptedVolumeClient(zx::channel&& chan) : chan_(std::move(chan)) {}
zx_status_t EncryptedVolumeClient::Format(const uint8_t* key, size_t key_len, uint8_t slot) {
zx_status_t rc;
zx_status_t call_status;
if ((rc = fuchsia_hardware_block_encrypted_DeviceManagerFormat(chan_.get(), key, key_len, slot,
&call_status)) != ZX_OK) {
xprintf("failed to call Format: %s\n", zx_status_get_string(rc));
return rc;
}
if (call_status != ZX_OK) {
xprintf("failed to Format: %s\n", zx_status_get_string(call_status));
}
return call_status;
}
zx_status_t EncryptedVolumeClient::FormatWithImplicitKey(uint8_t slot) {
return TryWithImplicitKeys(Activity::Create,
[&](std::unique_ptr<uint8_t[]> key_buffer, size_t key_size) {
return Format(key_buffer.get(), key_size, slot);
});
}
zx_status_t EncryptedVolumeClient::Unseal(const uint8_t* key, size_t key_len, uint8_t slot) {
zx_status_t rc;
zx_status_t call_status;
if ((rc = fuchsia_hardware_block_encrypted_DeviceManagerUnseal(chan_.get(), key, key_len, slot,
&call_status)) != ZX_OK) {
xprintf("failed to call Unseal: %s\n", zx_status_get_string(rc));
return rc;
}
if (call_status != ZX_OK) {
xprintf("failed to Unseal: %s\n", zx_status_get_string(call_status));
}
return call_status;
}
zx_status_t EncryptedVolumeClient::UnsealWithImplicitKey(uint8_t slot) {
return TryWithImplicitKeys(Activity::Unseal,
[&](std::unique_ptr<uint8_t[]> key_buffer, size_t key_size) {
return Unseal(key_buffer.get(), key_size, slot);
});
}
zx_status_t EncryptedVolumeClient::Seal() {
zx_status_t rc;
zx_status_t call_status;
if ((rc = fuchsia_hardware_block_encrypted_DeviceManagerSeal(chan_.get(), &call_status)) !=
ZX_OK) {
xprintf("failed to call Seal: %s\n", zx_status_get_string(rc));
return rc;
}
if (call_status != ZX_OK) {
xprintf("failed to Seal: %s\n", zx_status_get_string(call_status));
}
return call_status;
}
zx_status_t EncryptedVolumeClient::Shred() {
zx_status_t rc;
zx_status_t call_status;
if ((rc = fuchsia_hardware_block_encrypted_DeviceManagerShred(chan_.get(), &call_status)) !=
ZX_OK) {
xprintf("failed to call Shred: %s\n", zx_status_get_string(rc));
return rc;
}
if (call_status != ZX_OK) {
xprintf("failed to Shred: %s\n", zx_status_get_string(call_status));
}
return call_status;
}
VolumeManager::VolumeManager(fbl::unique_fd&& block_dev_fd, fbl::unique_fd&& devfs_root_fd)
: block_dev_fd_(std::move(block_dev_fd)), devfs_root_fd_(std::move(devfs_root_fd)) {}
zx_status_t VolumeManager::OpenInnerBlockDevice(const zx::duration& timeout, fbl::unique_fd* out) {
zx_status_t rc;
fbl::String path_base;
fdio_cpp::UnownedFdioCaller caller(block_dev_fd_.get());
if (!caller) {
xprintf("could not convert fd to io\n");
return ZX_ERR_BAD_STATE;
}
if ((rc = RelativeTopologicalPath(caller, &path_base)) != ZX_OK) {
xprintf("could not get topological path: %s\n", zx_status_get_string(rc));
return rc;
}
fbl::String path_block_exposed = fbl::String::Concat({path_base, "/zxcrypt/unsealed/block"});
// Early return if path_block_exposed is already present in the device tree
fbl::unique_fd fd(openat(devfs_root_fd_.get(), path_block_exposed.c_str(), O_RDWR));
if (fd) {
out->reset(fd.release());
return ZX_OK;
}
// Wait for the unsealed and block devices to bind
if ((rc = wait_for_device_at(devfs_root_fd_.get(), path_block_exposed.c_str(), timeout.get())) !=
ZX_OK) {
xprintf("timed out waiting for %s to exist: %s\n", path_block_exposed.c_str(),
zx_status_get_string(rc));
return rc;
}
fd.reset(openat(devfs_root_fd_.get(), path_block_exposed.c_str(), O_RDWR));
if (!fd) {
xprintf("failed to open zxcrypt volume\n");
return ZX_ERR_NOT_FOUND;
}
out->reset(fd.release());
return ZX_OK;
}
zx_status_t VolumeManager::OpenClient(const zx::duration& timeout, zx::channel& out) {
fdio_cpp::UnownedFdioCaller caller(block_dev_fd_.get());
if (!caller) {
xprintf("could not convert fd to io\n");
return ZX_ERR_BAD_STATE;
}
return OpenClientWithCaller(caller, timeout, out);
}
zx_status_t VolumeManager::OpenClientWithCaller(fdio_cpp::UnownedFdioCaller& caller,
const zx::duration& timeout, zx::channel& out) {
zx_status_t rc;
fbl::String path_base;
if ((rc = RelativeTopologicalPath(caller, &path_base)) != ZX_OK) {
xprintf("could not get topological path: %s\n", zx_status_get_string(rc));
return rc;
}
fbl::String path_manager = fbl::String::Concat({path_base, "/zxcrypt"});
fbl::unique_fd fd(openat(devfs_root_fd_.get(), path_manager.c_str(), O_RDWR));
if (!fd) {
// No manager device in the /dev tree yet. Try binding the zxcrypt
// driver and waiting for it to appear.
auto resp =
fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(caller.borrow_channel()))
->Bind(::fidl::StringView::FromExternal(kDriverLib));
rc = resp.status();
if (rc == ZX_OK) {
if (resp->is_error()) {
rc = resp->error_value();
}
}
if (rc != ZX_OK) {
xprintf("could not bind zxcrypt driver: %s\n", zx_status_get_string(rc));
return rc;
}
// Await the appearance of the zxcrypt device.
if ((rc = wait_for_device_at(devfs_root_fd_.get(), path_manager.c_str(), timeout.get())) !=
ZX_OK) {
xprintf("zxcrypt driver failed to bind: %s\n", zx_status_get_string(rc));
return rc;
}
fd.reset(openat(devfs_root_fd_.get(), path_manager.c_str(), O_RDWR));
if (!fd) {
xprintf("failed to open zxcrypt manager\n");
return ZX_ERR_NOT_FOUND;
}
}
if ((rc = fdio_get_service_handle(fd.release(), out.reset_and_get_address())) != ZX_OK) {
xprintf("failed to get service handle for zxcrypt manager: %s\n", zx_status_get_string(rc));
return rc;
}
return ZX_OK;
}
zx_status_t VolumeManager::RelativeTopologicalPath(fdio_cpp::UnownedFdioCaller& caller,
fbl::String* out) {
zx_status_t rc;
// Get the full device path
fbl::StringBuffer<PATH_MAX> path;
path.Resize(path.capacity());
size_t path_len;
auto resp =
fidl::WireCall<fuchsia_device::Controller>(zx::unowned_channel(caller.borrow_channel()))
->GetTopologicalPath();
rc = resp.status();
if (rc == ZX_OK) {
if (resp->is_error()) {
rc = resp->error_value();
} else {
auto& r = *resp->value();
path_len = r.path.size();
memcpy(path.data(), r.path.data(), r.path.size());
}
}
if (rc != ZX_OK) {
xprintf("could not find parent device: %s\n", zx_status_get_string(rc));
return rc;
}
// Verify that the path returned starts with "/dev/"
const char* kSlashDevSlash = "/dev/";
if (path_len < strlen(kSlashDevSlash)) {
xprintf("path_len way too short: %lu\n", path_len);
return ZX_ERR_INTERNAL;
}
if (strncmp(path.c_str(), kSlashDevSlash, strlen(kSlashDevSlash)) != 0) {
xprintf("Expected device path to start with '/dev/' but got %s\n", path.c_str());
return ZX_ERR_INTERNAL;
}
// Strip the leading "/dev/" and return the rest
size_t path_len_sans_dev = path_len - strlen(kSlashDevSlash);
memmove(path.begin(), path.begin() + strlen(kSlashDevSlash), path_len_sans_dev);
path.Resize(path_len_sans_dev);
*out = path.ToString();
return ZX_OK;
}
} // namespace zxcrypt