/* * \brief Integration of the Consistent Block Encrypter (CBE) * \author Martin Stein * \author Josef Soentgen * \date 2020-11-10 */ /* * Copyright (C) 2020 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ #ifndef _CBE__VFS__TRUST_ANCHOR_VFS_H_ #define _CBE__VFS__TRUST_ANCHOR_VFS_H_ /* local includes */ #include namespace Util { using namespace Genode; struct Trust_anchor_vfs; }; /* namespace Util */ struct Util::Trust_anchor_vfs { struct Invalid_request : Genode::Exception { }; using Path = Genode::Path<256>; Vfs::File_system &_vfs; struct Io_response_handler : Vfs::Io_response_handler { Genode::Signal_context_capability _io_sigh; Io_response_handler(Genode::Signal_context_capability io_sigh) : _io_sigh(io_sigh) { } void read_ready_response() override { } void io_progress_response() override { if (_io_sigh.valid()) { Genode::Signal_transmitter(_io_sigh).submit(); } } }; Io_response_handler _io_response_handler; struct File { struct Could_not_open_file : Genode::Exception { }; struct Io_job_already_constructed : Genode::Exception { }; struct Cannot_drop_unconstructed_io_job : Genode::Exception { }; struct Completed_io_job { bool completed; bool success; }; File(File const &) = delete; File &operator=(File const&) = delete; Vfs::File_system &_vfs; Vfs::Vfs_handle *_vfs_handle; Genode::Constructible _io_job { }; File(Path const &base_path, char const *name, Vfs::File_system &vfs, Genode::Allocator &alloc, Io_response_handler &io_response_handler) : _vfs { vfs }, _vfs_handle { nullptr } { using Result = Vfs::Directory_service::Open_result; Path file_path = base_path; file_path.append_element(name); Result const res = _vfs.open(file_path.string(), Vfs::Directory_service::OPEN_MODE_RDWR, (Vfs::Vfs_handle **)&_vfs_handle, alloc); if (res != Result::OPEN_OK) { error("could not open '", file_path.string(), "'"); throw Could_not_open_file(); } _vfs_handle->handler(&io_response_handler); } ~File() { _vfs.close(_vfs_handle); } bool submit_io_job(Util::Io_job::Buffer &buffer, Util::Io_job::Operation op) { if (_io_job.constructed()) { // throw Io_job_already_constructed(); return false; } _io_job.construct(*_vfs_handle, op, buffer, 0); return true; } bool execute_io_job() { if (!_io_job.constructed()) { return false; } return _io_job->execute(); } void drop_io_job() { if (!_io_job.constructed()) { throw Cannot_drop_unconstructed_io_job(); } _io_job.destruct(); } Completed_io_job completed_io_job() { if (!_io_job.constructed()) { return { false, false }; } return { _io_job->completed(), _io_job->succeeded() }; } }; Util::Io_job::Buffer _init_io_buffer { }; Genode::Constructible _init_file { }; Util::Io_job::Buffer _encrypt_io_buffer { }; Genode::Constructible _encrypt_file { }; Util::Io_job::Buffer _decrypt_io_buffer { }; Genode::Constructible _decrypt_file { }; Util::Io_job::Buffer _generate_key_io_buffer { }; Genode::Constructible _generate_key_file { }; Util::Io_job::Buffer _last_hash_io_buffer { }; Genode::Constructible _last_hash_file { }; Path _ta_dir { }; struct Job { enum class Type { NONE, DECRYPT_WRITE, DECRYPT_READ, ENCRYPT_WRITE, ENCRYPT_READ, GENERATE, INIT_WRITE, INIT_READ, HASH_READ, HASH_UPDATE_WRITE, HASH_UPDATE_READ, }; Type type { Type::NONE }; static char const *to_string(Type const type) { switch (type) { case Type::NONE: return "NONE"; case Type::DECRYPT_WRITE: return "DECRYPT_WRITE"; case Type::DECRYPT_READ: return "DECRYPT_READ"; case Type::ENCRYPT_WRITE: return "ENCRYPT_WRITE"; case Type::ENCRYPT_READ: return "ENCRYPT_READ"; case Type::GENERATE: return "GENERATE"; case Type::INIT_WRITE: return "INIT_WRITE"; case Type::INIT_READ: return "INIT_READ"; case Type::HASH_READ: return "HASH_READ"; case Type::HASH_UPDATE_WRITE: return "HASH_UPDATE_WRITE"; case Type::HASH_UPDATE_READ: return "HASH_UPDATE_READ"; } return nullptr; } enum class State { NONE, PENDING, IN_PROGRESS, COMPLETE }; State state { State::NONE }; static char const *to_string(State const state) { switch (state) { case State::NONE: return "NONE"; case State::PENDING: return "PENDING"; case State::IN_PROGRESS: return "IN_PROGRESS"; case State::COMPLETE: return "COMPLETE"; } return nullptr; } Cbe::Hash hash; Cbe::Key_plaintext_value plain; Cbe::Key_ciphertext_value cipher; Cbe::Trust_anchor_request request { }; bool success { false }; void reset() { type = Type::NONE; state = State::NONE; request = Cbe::Trust_anchor_request(); } bool valid() const { return state != State::NONE; } bool completed() const { return state == State::COMPLETE; } bool equals(Cbe::Trust_anchor_request const &other) const { return request.operation() == other.operation() && request.tag() == other.tag(); } void print(Genode::Output &out) const { if (!valid()) { Genode::print(out, ""); return; } Genode::print(out, "type: ", to_string(type)); Genode::print(out, " state: ", to_string(state)); Genode::print(out, " request: ", request); } }; Job _job { }; bool _execute_decrypt(Job &job, bool write) { bool progress = false; File::Completed_io_job completed_io_job { false, false }; switch (job.state) { case Job::State::PENDING: { using Op = Util::Io_job::Operation; Op const op = write ? Op::WRITE : Op::READ; if (!_decrypt_file->submit_io_job(_decrypt_io_buffer, op)) { break; } job.state = Job::State::IN_PROGRESS; progress |= true; } [[fallthrough]]; case Job::State::IN_PROGRESS: if (!_decrypt_file->execute_io_job()) { break; } progress |= true; completed_io_job = _decrypt_file->completed_io_job(); if (!completed_io_job.completed) { break; } _decrypt_file->drop_io_job(); /* setup second phase */ if (write) { /* * In case the write request was not successful it * is not needed to try to read the result. */ if (!completed_io_job.success) { job.state = Job::State::COMPLETE; job.request.success(false); break; } _decrypt_io_buffer = { .base = _job.cipher.value, .size = sizeof (_job.cipher) }; job.type = Job::Type::DECRYPT_READ; job.state = Job::State::PENDING; break; } job.state = Job::State::COMPLETE; job.success = completed_io_job.success; job.request.success(job.success); [[fallthrough]]; case Job::State::COMPLETE: break; case Job::State::NONE: break; } return progress; } bool _execute_encrypt(Job &job, bool write) { bool progress = false; File::Completed_io_job completed_io_job { false, false }; switch (job.state) { case Job::State::PENDING: { using Op = Util::Io_job::Operation; Op const op = write ? Op::WRITE : Op::READ; if (!_encrypt_file->submit_io_job(_encrypt_io_buffer, op)) { break; } job.state = Job::State::IN_PROGRESS; progress |= true; } [[fallthrough]]; case Job::State::IN_PROGRESS: if (!_encrypt_file->execute_io_job()) { break; } progress |= true; completed_io_job = _encrypt_file->completed_io_job(); if (!completed_io_job.completed) { break; } _encrypt_file->drop_io_job(); /* setup second phase */ if (write) { /* * In case the write request was not successful it * is not needed to try to read the result. */ if (!completed_io_job.success) { job.state = Job::State::COMPLETE; job.request.success(false); break; } _encrypt_io_buffer = { .base = _job.cipher.value, .size = sizeof (_job.cipher) }; job.type = Job::Type::ENCRYPT_READ; job.state = Job::State::PENDING; break; } job.state = Job::State::COMPLETE; job.success = completed_io_job.success; job.request.success(job.success); [[fallthrough]]; case Job::State::COMPLETE: break; case Job::State::NONE: break; } return progress; } bool _execute_generate(Job &job) { bool progress = false; File::Completed_io_job completed_io_job { false, false }; switch (job.state) { case Job::State::PENDING: if (!_generate_key_file->submit_io_job(_generate_key_io_buffer, Util::Io_job::Operation::READ)) { break; } job.state = Job::State::IN_PROGRESS; progress |= true; [[fallthrough]]; case Job::State::IN_PROGRESS: if (!_generate_key_file->execute_io_job()) { break; } progress |= true; completed_io_job = _generate_key_file->completed_io_job(); if (!completed_io_job.completed) { break; } _generate_key_file->drop_io_job(); job.state = Job::State::COMPLETE; job.success = completed_io_job.success; job.request.success(job.success); [[fallthrough]]; case Job::State::COMPLETE: break; case Job::State::NONE: break; } return progress; } bool _execute_init(Job &job, bool write) { bool progress = false; File::Completed_io_job completed_io_job { false, false }; switch (job.state) { case Job::State::PENDING: { using Op = Util::Io_job::Operation; Op const op = write ? Op::WRITE : Op::READ; if (!_init_file->submit_io_job(_init_io_buffer, op)) { break; } job.state = Job::State::IN_PROGRESS; progress |= true; } [[fallthrough]]; case Job::State::IN_PROGRESS: if (!_init_file->execute_io_job()) { break; } progress |= true; completed_io_job = _init_file->completed_io_job(); if (!completed_io_job.completed) { break; } _init_file->drop_io_job(); /* setup second phase */ if (write) { /* * In case the write request was not successful it * is not needed to try to read the result. */ if (!completed_io_job.success) { job.state = Job::State::COMPLETE; job.request.success(false); break; } _init_io_buffer = { .base = _job.cipher.value, .size = sizeof (_job.cipher) }; job.type = Job::Type::INIT_READ; job.state = Job::State::PENDING; break; } job.state = Job::State::COMPLETE; job.success = completed_io_job.success; job.request.success(job.success); [[fallthrough]]; case Job::State::COMPLETE: break; case Job::State::NONE: break; } return progress; } bool _execute_read_hash(Job &job) { bool progress = false; File::Completed_io_job completed_io_job { false, false }; switch (job.state) { case Job::State::PENDING: if (!_last_hash_file->submit_io_job(_last_hash_io_buffer, Util::Io_job::Operation::READ)) { break; } job.state = Job::State::IN_PROGRESS; progress |= true; [[fallthrough]]; case Job::State::IN_PROGRESS: if (!_last_hash_file->execute_io_job()) { break; } progress |= true; completed_io_job = _last_hash_file->completed_io_job(); if (!completed_io_job.completed) { break; } _last_hash_file->drop_io_job(); job.state = Job::State::COMPLETE; job.success = completed_io_job.success; job.request.success(job.success); [[fallthrough]]; case Job::State::COMPLETE: break; case Job::State::NONE: break; } return progress; } bool _execute_update_hash(Job &job, bool write) { bool progress = false; File::Completed_io_job completed_io_job { false, false }; switch (job.state) { case Job::State::PENDING: { using Op = Util::Io_job::Operation; Op const op = write ? Op::WRITE : Op::READ; if (!_last_hash_file->submit_io_job(_last_hash_io_buffer, op)) { break; } job.state = Job::State::IN_PROGRESS; progress |= true; } [[fallthrough]]; case Job::State::IN_PROGRESS: if (!_last_hash_file->execute_io_job()) { break; } progress |= true; completed_io_job = _last_hash_file->completed_io_job(); if (!completed_io_job.completed) { break; } _last_hash_file->drop_io_job(); /* setup second phase */ if (write) { /* * In case the write request was not successful it * is not needed to try to read the result. */ if (!completed_io_job.success) { job.state = Job::State::COMPLETE; job.request.success(false); break; } _last_hash_io_buffer = { .base = _job.hash.values, .size = sizeof (_job.hash) }; job.type = Job::Type::HASH_UPDATE_READ; job.state = Job::State::PENDING; break; } job.state = Job::State::COMPLETE; job.success = completed_io_job.success; job.request.success(job.success); [[fallthrough]]; case Job::State::COMPLETE: break; case Job::State::NONE: break; } return progress; } Trust_anchor_vfs(Vfs::File_system &vfs, Genode::Allocator &alloc, Path const &path, Genode::Signal_context_capability io_sigh) : _vfs { vfs }, _io_response_handler { io_sigh }, _ta_dir { path } { _init_file.construct(path, "initialize", _vfs, alloc, _io_response_handler); _encrypt_file.construct(path, "encrypt", _vfs, alloc, _io_response_handler); _decrypt_file.construct(path, "decrypt", _vfs, alloc, _io_response_handler); _generate_key_file.construct(path, "generate_key", _vfs, alloc, _io_response_handler); _last_hash_file.construct(path, "hashsum", _vfs, alloc, _io_response_handler); } bool request_acceptable() const { return !_job.valid(); } void submit_create_key_request(Cbe::Trust_anchor_request const &request) { _job = { .type = Job::Type::GENERATE, .state = Job::State::PENDING, .hash = Cbe::Hash(), .plain = Cbe::Key_plaintext_value(), .cipher = Cbe::Key_ciphertext_value(), .request = request, .success = false, }; _generate_key_io_buffer = { .base = _job.plain.value, .size = sizeof (_job.plain) }; } void submit_superblock_hash_request(Cbe::Trust_anchor_request const &request) { _job = { .type = Job::Type::HASH_READ, .state = Job::State::PENDING, .hash = Cbe::Hash(), .plain = Cbe::Key_plaintext_value(), .cipher = Cbe::Key_ciphertext_value(), .request = request, .success = false, }; _last_hash_io_buffer = { .base = _job.hash.values, .size = sizeof (_job.hash) }; } void submit_secure_superblock_request(Cbe::Trust_anchor_request const &request, Cbe::Hash const &hash) { _job = { .type = Job::Type::HASH_UPDATE_WRITE, .state = Job::State::PENDING, .hash = hash, .plain = Cbe::Key_plaintext_value(), .cipher = Cbe::Key_ciphertext_value(), .request = request, .success = false, }; _last_hash_io_buffer = { .base = _job.hash.values, .size = sizeof (_job.hash) }; } void submit_encrypt_key_request(Cbe::Trust_anchor_request const &request, Cbe::Key_plaintext_value const &plain) { _job = { .type = Job::Type::ENCRYPT_WRITE, .state = Job::State::PENDING, .hash = Cbe::Hash(), .plain = plain, .cipher = Cbe::Key_ciphertext_value(), .request = request, .success = false, }; _encrypt_io_buffer = { .base = _job.plain.value, .size = sizeof (_job.plain) }; } void submit_decrypt_key_request(Cbe::Trust_anchor_request const &request, Cbe::Key_ciphertext_value const &cipher) { _job = { .type = Job::Type::DECRYPT_WRITE, .state = Job::State::PENDING, .hash = Cbe::Hash(), .plain = Cbe::Key_plaintext_value(), .cipher = cipher, .request = request, .success = false, }; _decrypt_io_buffer = { .base = _job.cipher.value, .size = sizeof (_job.cipher) }; } Cbe::Trust_anchor_request peek_completed_request() { return _job.completed() ? _job.request : Cbe::Trust_anchor_request(); } void drop_completed_request(Cbe::Trust_anchor_request const &request) { if (!_job.equals(request)) { throw Invalid_request(); } _job.reset(); } Cbe::Hash peek_completed_superblock_hash(Cbe::Trust_anchor_request const &request) { if (!_job.equals(request) || !_job.completed()) { throw Invalid_request(); } return _job.hash; } Cbe::Key_plaintext_value peek_completed_key_value_plaintext(Cbe::Trust_anchor_request const &request) { if (!_job.equals(request) || !_job.completed()) { throw Invalid_request(); } return _job.plain; } Cbe::Key_ciphertext_value peek_completed_key_value_ciphertext(Cbe::Trust_anchor_request const &request) { if (!_job.equals(request) || !_job.completed()) { throw Invalid_request(); } return _job.cipher; } bool execute() { bool progress = false; switch (_job.type) { case Job::Type::NONE: break; case Job::Type::DECRYPT_WRITE: progress |= _execute_decrypt(_job, true); break; case Job::Type::DECRYPT_READ: progress |= _execute_decrypt(_job, false); break; case Job::Type::ENCRYPT_WRITE: progress |= _execute_encrypt(_job, true); break; case Job::Type::ENCRYPT_READ: progress |= _execute_encrypt(_job, false); break; case Job::Type::GENERATE: progress |= _execute_generate(_job); break; case Job::Type::INIT_WRITE: progress |= _execute_init(_job, true); break; case Job::Type::INIT_READ: progress |= _execute_init(_job, false); break; case Job::Type::HASH_READ: progress |= _execute_read_hash(_job); break; case Job::Type::HASH_UPDATE_WRITE: progress |= _execute_update_hash(_job, true); break; case Job::Type::HASH_UPDATE_READ: progress |= _execute_update_hash(_job, false); break; } return progress; } }; #endif /* _CBE__VFS__TRUST_ANCHOR_VFS_H_ */