// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "Header.h" #include #include #include #include #include "common/dout.h" #include "common/errno.h" #define dout_subsys ceph_subsys_rbd #undef dout_prefix #define dout_prefix *_dout << "librbd::crypto::luks::Header: " << this << " " \ << __func__ << ": " namespace librbd { namespace crypto { namespace luks { Header::Header(CephContext* cct) : m_cct(cct), m_fd(-1), m_cd(nullptr) { } Header::~Header() { if (m_fd != -1) { close(m_fd); m_fd = -1; } if (m_cd != nullptr) { crypt_free(m_cd); m_cd = nullptr; } } void Header::libcryptsetup_log_wrapper(int level, const char* msg, void* header) { ((Header*)header)->libcryptsetup_log(level, msg); } void Header::libcryptsetup_log(int level, const char* msg) { switch (level) { case CRYPT_LOG_NORMAL: ldout(m_cct, 5) << "[libcryptsetup] " << msg << dendl; break; case CRYPT_LOG_ERROR: lderr(m_cct) << "[libcryptsetup] " << msg << dendl; break; case CRYPT_LOG_VERBOSE: ldout(m_cct, 10) << "[libcryptsetup] " << msg << dendl; break; case CRYPT_LOG_DEBUG: ldout(m_cct, 20) << "[libcryptsetup] " << msg << dendl; break; } } int Header::init() { if (m_fd != -1) { return 0; } // create anonymous file m_fd = syscall(SYS_memfd_create, "LibcryptsetupInterface", 0); if (m_fd == -1) { lderr(m_cct) << "error creating anonymous file: " << cpp_strerror(-errno) << dendl; return -errno; } std::string path = "/proc/" + std::to_string(getpid()) + "/fd/" + std::to_string(m_fd); if (m_cct->_conf->subsys.should_gather()) { crypt_set_debug_level(CRYPT_DEBUG_ALL); } // init libcryptsetup handle auto r = crypt_init(&m_cd, path.c_str()); if (r != 0) { lderr(m_cct) << "crypt_init failed: " << cpp_strerror(r) << dendl; return r; } // redirect logging crypt_set_log_callback(m_cd, &libcryptsetup_log_wrapper, this); return 0; } int Header::write(const ceph::bufferlist& bl) { ceph_assert(m_fd != -1); auto r = bl.write_fd(m_fd); if (r != 0) { lderr(m_cct) << "error writing header: " << cpp_strerror(r) << dendl; } return r; } ssize_t Header::read(ceph::bufferlist* bl) { ceph_assert(m_fd != -1); // get current header size struct stat st; ssize_t r = fstat(m_fd, &st); if (r < 0) { r = -errno; lderr(m_cct) << "failed to stat anonymous file: " << cpp_strerror(r) << dendl; return r; } r = bl->read_fd(m_fd, st.st_size); if (r < 0) { lderr(m_cct) << "error reading header: " << cpp_strerror(r) << dendl; } ldout(m_cct, 20) << "read size = " << r << dendl; return r; } int Header::format(const char* type, const char* alg, const char* key, size_t key_size, const char* cipher_mode, uint32_t sector_size, uint32_t data_alignment, bool insecure_fast_mode) { ceph_assert(m_cd != nullptr); ldout(m_cct, 20) << "sector size: " << sector_size << ", data alignment: " << data_alignment << dendl; // required for passing libcryptsetup device size check if (ftruncate(m_fd, 4096) != 0) { lderr(m_cct) << "failed to truncate anonymous file: " << cpp_strerror(-errno) << dendl; return -errno; } struct crypt_params_luks1 luks1params; struct crypt_params_luks2 luks2params; const size_t converted_data_alignment = data_alignment / 512; void* params = nullptr; if (strcmp(type, CRYPT_LUKS1) == 0) { memset(&luks1params, 0, sizeof(luks1params)); luks1params.data_alignment = converted_data_alignment; params = &luks1params; } else if (strcmp(type, CRYPT_LUKS2) == 0) { memset(&luks2params, 0, sizeof(luks2params)); luks2params.data_alignment = converted_data_alignment; luks2params.sector_size = sector_size; params = &luks2params; } // this mode should be used for testing only if (insecure_fast_mode) { struct crypt_pbkdf_type pbkdf; memset(&pbkdf, 0, sizeof(pbkdf)); pbkdf.type = CRYPT_KDF_PBKDF2; pbkdf.flags = CRYPT_PBKDF_NO_BENCHMARK; pbkdf.hash = "sha256"; pbkdf.iterations = 1000; pbkdf.time_ms = 1; auto r = crypt_set_pbkdf_type(m_cd, &pbkdf); if (r != 0) { lderr(m_cct) << "crypt_set_pbkdf_type failed: " << cpp_strerror(r) << dendl; return r; } } auto r = crypt_format( m_cd, type, alg, cipher_mode, NULL, key, key_size, params); if (r != 0) { lderr(m_cct) << "crypt_format failed: " << cpp_strerror(r) << dendl; return r; } return 0; } int Header::add_keyslot(const char* passphrase, size_t passphrase_size) { ceph_assert(m_cd != nullptr); auto r = crypt_keyslot_add_by_volume_key( m_cd, CRYPT_ANY_SLOT, NULL, 0, passphrase, passphrase_size); if (r < 0) { lderr(m_cct) << "crypt_keyslot_add_by_volume_key failed: " << cpp_strerror(r) << dendl; return r; } return 0; } int Header::load(const char* type) { ceph_assert(m_cd != nullptr); // libcryptsetup checks if device size matches the header and keyslots size // in LUKS2, 2 X 4MB header + 128MB keyslots if (ftruncate(m_fd, 136 * 1024 * 1024) != 0) { lderr(m_cct) << "failed to truncate anonymous file: " << cpp_strerror(-errno) << dendl; return -errno; } auto r = crypt_load(m_cd, type, NULL); if (r != 0) { ldout(m_cct, 20) << "crypt_load failed: " << cpp_strerror(r) << dendl; return r; } ldout(m_cct, 20) << "sector size: " << get_sector_size() << ", data offset: " << get_data_offset() << dendl; return 0; } int Header::read_volume_key(const char* passphrase, size_t passphrase_size, char* volume_key, size_t* volume_key_size) { ceph_assert(m_cd != nullptr); auto r = crypt_volume_key_get( m_cd, CRYPT_ANY_SLOT, volume_key, volume_key_size, passphrase, passphrase_size); if (r < 0) { ldout(m_cct, 20) << "crypt_volume_key_get failed: " << cpp_strerror(r) << dendl; return r; } return 0; } int Header::get_sector_size() { ceph_assert(m_cd != nullptr); return crypt_get_sector_size(m_cd); } uint64_t Header::get_data_offset() { ceph_assert(m_cd != nullptr); return crypt_get_data_offset(m_cd) << 9; } const char* Header::get_cipher() { ceph_assert(m_cd != nullptr); return crypt_get_cipher(m_cd); } const char* Header::get_cipher_mode() { ceph_assert(m_cd != nullptr); return crypt_get_cipher_mode(m_cd); } const char* Header::get_format_name() { ceph_assert(m_cd != nullptr); return crypt_get_type(m_cd); } } // namespace luks } // namespace crypto } // namespace librbd