genode/repos/dde_rump/src/server/rump_cgd/cgd.cc

512 lines
12 KiB
C++
Raw Normal View History

dde_rump: block encryption server using cgd(4) The 'rump_cgd' server provides block level encryption for a block session by employing the 'cgd(4)' device provided by the rumpkernel. 'rump_cgd' uses a Block_session to get access to an existing block device and provides another Block_session to its clients. Each block written or read by the client is transperently encrypted or decrypted by the server. For now 'rump_cgd' may only _configure_ a 'cgd' device but is unable to generate a configuration. The used cipher is hardcoded to _aes-cbc_ with a keysize of 256 bit. Furthermore the server is able to serve one client only. To ease the usage, its interface is modelled after the interface of 'cgdconfig(8)'. As implications thereof the key must have the same format as used by 'cgdconfig'. That means the key is a base 64 encoded string in which the first 4 bytes denote the actual length of the key in bits (these 4 bytes are stored in big endian order). Preparing a raw (e.g. without partition table) encrypted Ext2 disk image is done by executing 'tool/rump': ! dd if=/dev/urandom of=/path/to/disk_image ! rump -c /path/to/disk_image # key is printed to stdout ! rump -c -k <key> -F ext2fs /path/to/disk_image To use this disk image the following config snippet can be used: ! <start name="rump_cgd"> ! <resource name="RAM" quantum="8M" /> ! <provides><service name="Block"/></provides> ! <config action="configure"> ! <params> ! <method>key</method>} ! <key>AAABAJhpB2Y2UvVjkFdlP4m44449Pi3A/uW211mkanSulJo8</key> ! </params> ! </config> ! <route> ! <service name="Block"> <child name="ahci"/> </service> ! <any-service> <parent/> <any-child/> </any-service> ! </route> ! </start> the Block service provided by rump_cgd may be used by a file system server in return: ! <start name="rump_fs"> ! <resource name="RAM" quantum="16M"/> ! <provides><service name="File_system"/></provides> ! <config fs="ext2fs"> ! <policy label="" root="/" writeable="yes"/> ! </config> ! <route> ! <service name="Block"> <child name="rump_cgd"/> </service> ! <any-service> <parent/> <any-child/> </any-service> ! </route> ! </start> Since 'tool/rump' just utilizes the rumpkernel running on the host system to do its duty there is a script called 'tool/cgdconf' that extracts the key from a 'cgdconfig(8)' generated configuration file and also is able to generade such a file from a given key. Thereby the interoperabilty between 'rump_cgd' and the general rumpkernel based tools is secured.
2014-04-16 15:06:27 +02:00
/**
* \brief RUMP cgd
* \author Josef Soentgen
* \date 2014-04-11
*/
/*
* Copyright (C) 2014 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
/* Genode includes */
#include <os/config.h>
/* repo includes */
#include <rump_cgd/cgd.h>
/* local includes */
#include "cgd.h"
/* rump includes */
extern "C" {
#include <sys/cdefs.h>
#include <sys/errno.h>
#include <fcntl.h>
#include <sys/dirent.h>
#include <sys/disklabel.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/reboot.h>
#include <dev/cgdvar.h>
#include <rump/rump.h>
#include <rump/rump_syscalls.h>
}
static const bool verbose = false;
#define PDBGV(...) if (verbose) PDBG(__VA_ARGS__)
/**
* Miscellaneous methods used for converting the key string
*/
namespace {
/**
* Decode base64 encoding
*/
class Base64
{
private:
/* initialize the lookup table */
static constexpr const unsigned char _ascii_to_value[256]
{
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64,
64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64,
64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64,
64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64
};
public:
static size_t decode(char *dst, char const *src, size_t src_len)
{
unsigned char const *usrc = reinterpret_cast<unsigned char const*>(src);
size_t i;
for (i = 0; i < src_len; i += 4) {
*(dst++) = _ascii_to_value[usrc[i]] << 2 | _ascii_to_value[usrc[i + 1]] >> 4;
*(dst++) = _ascii_to_value[usrc[i + 1]] << 4 | _ascii_to_value[usrc[i + 2]] >> 2;
*(dst++) = _ascii_to_value[usrc[i + 2]] << 6 | _ascii_to_value[usrc[i + 3]];
}
return src_len * 3/4;
}
};
/* lookup table instance */
constexpr unsigned char Base64::_ascii_to_value[256];
/**
* Convert unsigned big endian value to host endianess
*/
unsigned read_big_endian(unsigned val)
{
unsigned char *p = reinterpret_cast<unsigned char*>(&val);
return (p[3]<<0) | (p[2]<<8) | (p[1]<<16) | (p[0]<<24);
}
}
/**
* cgd(4) wrapper
*/
namespace Cgd {
struct Params
{
enum {
ALGORITHM_LEN = 16,
IVMETHOD_LEN = 16,
KEY_LEN = 64,
PASSPHRASE_LEN = 64,
SALT_LEN = 29,
};
char algorithm[ALGORITHM_LEN];
char ivmethod[IVMETHOD_LEN];
char key[KEY_LEN];
Genode::size_t keylen = 0;
Genode::size_t blocksize = 0;
Params()
{
Genode::memset(algorithm, 0, sizeof(*algorithm));
Genode::memset(ivmethod, 0, sizeof(*ivmethod));
Genode::memset(key, 0, sizeof(*key));
}
static Params *generate()
{
return 0;
}
};
/**
* Configuration of the cgd device
*
* cgdconfig(8) saves the key as base 64 encoded string. The first 4 bytes
* of this string are the length of the key in bits stored as big endian.
*/
class Config
{
public:
/* same as in cgdconfig(8) */
typedef enum { ACTION_INVALID, ACTION_CONFIGURE, ACTION_GENERATE } Action;
private:
enum { ACTION_VALUE_LEN = 16, /* action attribute value length */
ENCODED_KEY_LEN = 64, /* encoded key string buffer */
DECODED_KEY_LEN = 36, /* decoded key string buffer */
VALID_ENCODED_KEY_LEN = 48, /* length of 256Bit key */
};
Genode::Config &_cfg;
Action _action;
Params *_params;
/**
* Get action from config attribute
*
* \return action
*/
Action _get_action()
{
char action_val[ACTION_VALUE_LEN];
if (_cfg.xml_node().has_attribute("action"))
_cfg.xml_node().attribute("action").value(action_val, sizeof (action_val));
else
return ACTION_INVALID;
if (Genode::strcmp(action_val, "configure") == 0)
return ACTION_CONFIGURE;
if (Genode::strcmp(action_val, "generate") == 0)
return ACTION_GENERATE;
return ACTION_INVALID;
}
/**
* Decode key string in cgdconfig(8) format
*
* \param dst pointer to buffer to store decoded key
* \param src pointer to buffer with encoded key string
* \param src_len length of encoded key string buffer
*
* \return true if decoding was successful and false otherwise
*/
bool _decode_key_string(char *dst, char const *src, size_t src_len)
{
char dec_key[DECODED_KEY_LEN];
/* check that buffer is large enough */
if (sizeof (dec_key) < (src_len*3/4))
return false;
(void) Base64::decode(dec_key, src, src_len);
unsigned *p = reinterpret_cast<unsigned *>(dec_key);
unsigned bits = read_big_endian(*p);
Genode::memcpy(dst, p + 1, bits / 8);
return true;
}
/**
* Parse given configuration
*
* This method throws a Genode::Exception if an error in the given
* configuration is found.
*/
void _parse_config()
{
if (_cfg.xml_node().has_sub_node("params")) {
Genode::Xml_node pnode = _cfg.xml_node().sub_node("params");
char method_val[4];
pnode.sub_node("method").value(method_val, sizeof (method_val));
bool use_key = Genode::strcmp(method_val, "key") == 0 ? true : false;
if (!use_key) {
PERR("no valid method specified.");
throw Genode::Exception();
}
_params = new (Genode::env()->heap()) Params();
Genode::strncpy(_params->algorithm, CGD_ALGORITHM,
Params::ALGORITHM_LEN);
Genode::strncpy(_params->ivmethod, CGD_IVMETHOD,
Params::IVMETHOD_LEN);
char enc_key[ENCODED_KEY_LEN];
pnode.sub_node("key").value(enc_key, sizeof (enc_key));
size_t enc_key_len = Genode::strlen(enc_key);
if (enc_key_len != VALID_ENCODED_KEY_LEN) {
PERR("incorrect encoded key found.");
throw Genode::Exception();
}
if (!_decode_key_string(_params->key, enc_key, enc_key_len)) {
PERR("could not decode key string.");
throw Genode::Exception();
}
_params->keylen = CGD_KEYLEN;
/* let cgd(4) figure out the right blocksize */
_params->blocksize = -1;
} else {
PERR("no <params> node found.");
throw Genode::Exception();
}
}
public:
Config()
:
_cfg(*Genode::config()),
_action(_get_action()),
_params(0)
{
_parse_config();
}
~Config() { destroy(Genode::env()->heap(), _params); }
Action action() { return _action; }
Params* params() { return _params; }
};
}
/**
* Constructor
*
* \param fd used by the rumpkernel to execute syscalls on
*/
Cgd::Device::Device(int fd)
:
_fd(fd),
_blk_sz(0), _blk_cnt(0)
{
/**
* We query the disklabel(5) on CGD_RAW_DEVICE to get the
* actual block size and the number of blocks in the
* first partition. Though there may be more partitions
* we always use the first one.
*/
disklabel dl;
Genode::memset(&dl, 0, sizeof (dl));
int err = rump_sys_ioctl(_fd, DIOCGDINFO, &dl);
if (err) {
cgd_ioctl ci;
rump_sys_ioctl(_fd, CGDIOCCLR, &ci);
rump_sys_close(_fd);
PERR("could not read geometry of '%s'", CGD_RAW_DEVICE);
throw Genode::Exception();
}
_blk_sz = dl.d_secsize;
_blk_cnt = dl.d_partitions[0].p_size;
}
/**
* Destructor
*/
Cgd::Device::~Device()
{
/* unconfigure cgd(4) device to explicitly clean up buffers */
cgd_ioctl ci;
rump_sys_ioctl(_fd, CGDIOCCLR, &ci);
rump_sys_close(_fd);
}
/**
* Get name of the actual cgd device
*
* \return name
*/
char const *Cgd::Device::name() const { return CGD_RAW_DEVICE; }
/**
* Read from the cgd device
*
* \param dst pointer to buffer
* \param len size of the buffer
* \param seek_offset offset from where to start reading
*
* \return bytes read
*/
size_t Cgd::Device::read(char *dst, size_t len, seek_off_t seek_offset)
{
ssize_t ret = rump_sys_pread(_fd, dst, len, seek_offset);
return ret == -1 ? 0 : ret;
}
/**
* Write to cgd device
*
* \param src pointer to buffer
* \param len size of the buffer
* \param seek_offset offset from where to start writing
*
* \return bytes written
*/
size_t Cgd::Device::write(char const *src, size_t len, seek_off_t seek_offset)
{
/* should we append? */
if (seek_offset == ~0ULL) {
off_t off = rump_sys_lseek(_fd, 0, SEEK_END);
if (off == -1)
return 0;
seek_offset = off;
}
ssize_t ret = rump_sys_pwrite(_fd, src, len, seek_offset);
return ret == -1 ? 0 : ret;
}
/**
* Configure the actual cgd device
*
* \param alloc memory allocator for metadata
* \param p pointer to parameters
* \param dev name of the block device used as backing device for cgd
*
* \return configured cgd device
*/
Cgd::Device *Cgd::Device::configure(Genode::Allocator *alloc, Cgd::Params const *p,
char const *dev)
{
int fd = rump_sys_open(CGD_RAW_DEVICE, O_RDWR);
if (fd == -1) {
PERR("could not open '%s'", CGD_RAW_DEVICE);
throw Genode::Exception();
}
PDBGV("dev: '%s' alg: '%s' ivmethod: '%s' blocksize: %zu keylen: %zu",
dev, p->algorithm, p->ivmethod, p->blocksize, p->keylen);
/* perform configuration of cgd device */
cgd_ioctl ci;
Genode::memset(&ci, 0, sizeof (ci));
ci.ci_disk = dev;
ci.ci_alg = p->algorithm;
ci.ci_ivmethod = p->ivmethod;
ci.ci_key = p->key;
ci.ci_keylen = p->keylen;
ci.ci_blocksize = p->blocksize;
int err = rump_sys_ioctl(fd, CGDIOCSET, &ci);
if (err == -1) {
rump_sys_close(fd);
PERR("could not configure '%s'", CGD_RAW_DEVICE);
throw Genode::Exception();
}
cgd_user cu;
Genode::memset(&cu, 0, sizeof (cu));
err = rump_sys_ioctl(fd, CGDIOCGET, &cu);
if (err == -1) { /* unlikely */
/**
* Reuse former cgd_ioctl struct because it is not used by this
* ioctl() anyway.
*/
rump_sys_ioctl(fd, CGDIOCCLR, &ci);
rump_sys_close(fd);
PERR("could not get cgd information.");
throw Genode::Exception();
}
Cgd::Device *cgd = new (alloc) Cgd::Device(fd);
return cgd;
}
/**
* Initialize a new Cgd::Device
*/
Cgd::Device *Cgd::init(Genode::Allocator *alloc, Server::Entrypoint &ep)
{
/* start rumpkernel */
rump_init();
/* register block device */
if (rump_pub_etfs_register(GENODE_DEVICE, GENODE_BLOCK_SESSION,
RUMP_ETFS_BLK)) {
PERR("could not register '%s' within rumpkernel", GENODE_DEVICE);
throw Genode::Exception();
}
Cgd::Config cfg;
Cgd::Config::Action action = cfg.action();
Cgd::Device *cgd_dev = 0;
switch (action) {
case Cgd::Config::ACTION_CONFIGURE:
{
Cgd::Params *p = cfg.params();
if (!p)
throw Genode::Exception();
cgd_dev = Cgd::Device::configure(alloc, p, GENODE_DEVICE);
break;
}
case Cgd::Config::ACTION_GENERATE:
{
Cgd::Params *params = Cgd::Params::generate();
(void)params;
break;
}
case Cgd::Config::ACTION_INVALID:
PERR("invalid action declared");
throw Genode::Exception();
break;
}
PINF("exporting '%s' as Block_session", cgd_dev->name());
return cgd_dev;
}
/**
* Deinitialize a Cgd::Device
*/
void Cgd::deinit(Genode::Allocator *alloc, Cgd::Device *dev)
{
destroy(alloc, dev);
/* halt rumpkernel */
rump_sys_reboot(RB_HALT, 0);
}