blob: 7bd95af1a2700d60438f1286e6cb24a775c86624 [file] [log] [blame]
/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* This is a collection of helper utilities for use with the "mount-encrypted"
* utility.
*
*/
#define _FILE_OFFSET_BITS 64
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <linux/fs.h>
#include <linux/loop.h>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <openssl/evp.h>
#include "mount_encrypted.h"
#include "mount_helpers.h"
static const gchar kRootDir[] = "/";
static const gchar kSysBlockPath[] = "/sys/block";
static const gchar kLoopPrefix[] = "loop";
static const gchar kLoopTemplate[] = "/dev/loop%u";
static const gchar kLoopControl[] = "/dev/loop-control";
static const gchar kDevTemplate[] = "/dev/%s";
static const int kLoopMajor = 7;
static const unsigned int kResizeStepSeconds = 2;
static const uint64_t kResizeBlocks = 32768 * 10;
static const uint64_t kBlocksPerGroup = 32768;
static const uint64_t kInodeRatioDefault = 16384;
static const uint64_t kInodeRatioMinimum = 2048;
static const gchar* const kExt4ExtendedOptions = "discard,lazy_itable_init";
int remove_tree(const char* tree) {
const gchar* rm[] = {"/bin/rm", "-rf", tree, NULL};
return runcmd(rm, NULL);
}
uint64_t blk_size(const char* device) {
uint64_t bytes;
int fd;
if ((fd = open(device, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)) < 0) {
PERROR("open(%s)", device);
return 0;
}
if (ioctl(fd, BLKGETSIZE64, &bytes)) {
PERROR("ioctl(%s, BLKGETSIZE64)", device);
return 0;
}
close(fd);
return bytes;
}
int runcmd(const gchar* argv[], gchar** output) {
gint rc;
gchar *out = NULL, *errout = NULL;
GError* err = NULL;
g_spawn_sync(kRootDir, (gchar**)argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, &out,
&errout, &rc, &err);
if (err) {
ERROR("%s: %s", argv[0], err->message);
g_error_free(err);
return -1;
}
if (rc)
ERROR("%s failed (%d)\n%s\n%s", argv[0], rc, out, errout);
if (output)
*output = out;
else
g_free(out);
g_free(errout);
return rc;
}
int same_vfs(const char* mnt_a, const char* mnt_b) {
struct stat stat_a, stat_b;
if (lstat(mnt_a, &stat_a)) {
PERROR("lstat(%s)", mnt_a);
exit(1);
}
if (lstat(mnt_b, &stat_b)) {
PERROR("lstat(%s)", mnt_b);
exit(1);
}
return (stat_a.st_dev == stat_b.st_dev);
}
/* Returns allocated string that holds [length]*2 + 1 characters. */
char* stringify_hex(uint8_t* binary, size_t length) {
char* string;
size_t i;
string = (char*)malloc(length * 2 + 1);
if (!string) {
PERROR("malloc");
return NULL;
}
for (i = 0; i < length; ++i)
sprintf(string + (i * 2), "%02x", binary[i]);
string[length * 2] = '\0';
return string;
}
/* Returns allocated byte array that holds strlen([string])/2 bytes. */
uint8_t* hexify_string(char* string, uint8_t* binary, size_t length) {
size_t bytes, i;
bytes = strlen(string) / 2;
if (bytes > length) {
ERROR("Hex string too long (%zu) for byte array (%zu)", bytes, length);
return NULL;
}
for (i = 0; i < bytes; ++i) {
if (sscanf(&string[i * 2], "%2hhx", &binary[i]) != 1) {
ERROR("Invalid hex code at byte %zu.", i);
return NULL;
}
}
return binary;
}
/* Overwrite file contents. Useless on SSD. :( */
void shred(const char* pathname) {
uint8_t patterns[] = {0xA5, 0x5A, 0xFF, 0x00};
FILE* target;
struct stat info;
uint8_t* pattern;
int fd, i;
/* Give up if we can't safely open or stat the target. */
if ((fd = open(pathname, O_WRONLY | O_NOFOLLOW | O_CLOEXEC)) < 0) {
PERROR("%s", pathname);
return;
}
if (fstat(fd, &info)) {
close(fd);
PERROR("%s", pathname);
return;
}
if (!(target = fdopen(fd, "w"))) {
close(fd);
PERROR("%s", pathname);
return;
}
/* Ignore errors here, since there's nothing we can really do. */
pattern = (uint8_t*)malloc(info.st_size);
for (i = 0; i < sizeof(patterns); ++i) {
memset(pattern, patterns[i], info.st_size);
if (fseek(target, 0, SEEK_SET))
PERROR("%s", pathname);
if (fwrite(pattern, info.st_size, 1, target) != 1)
PERROR("%s", pathname);
if (fflush(target))
PERROR("%s", pathname);
if (fdatasync(fd))
PERROR("%s", pathname);
}
free(pattern);
/* fclose() closes the fd too. */
fclose(target);
}
static int is_loop_device(int fd) {
struct stat info;
return (fstat(fd, &info) == 0 && S_ISBLK(info.st_mode) &&
major(info.st_rdev) == kLoopMajor);
}
static int loop_is_attached(int fd, struct loop_info64* info) {
struct loop_info64 local_info;
return ioctl(fd, LOOP_GET_STATUS64, info ? info : &local_info) == 0;
}
/* Returns the matching loopback name. */
static int loop_locate(gchar** loopback, const char* name) {
int fd = -1;
size_t namelen = 0;
DIR* sysfs_block_dir = NULL;
namelen = strlen(name);
if (namelen >= LO_NAME_SIZE) {
ERROR("'%s' too long (>= %d)", name, LO_NAME_SIZE);
return -1;
}
*loopback = NULL;
/* Read /sys/block to discover all loop devices. */
sysfs_block_dir = opendir(kSysBlockPath);
if (!sysfs_block_dir) {
PERROR("open(%s)", kSysBlockPath);
return -1;
}
while (1) {
int attached = 0;
struct loop_info64 info;
struct dirent* dirent = NULL;
errno = 0;
dirent = readdir(sysfs_block_dir);
if (!dirent) {
if (errno) {
PERROR("readdir(%s)", kSysBlockPath);
goto failed;
}
break;
}
if (strncmp(dirent->d_name, kLoopPrefix, sizeof(kLoopPrefix) - 1)) {
continue;
}
g_free(*loopback);
*loopback = g_strdup_printf(kDevTemplate, dirent->d_name);
if (!*loopback) {
PERROR("g_strdup_printf");
goto failed;
}
fd = open(*loopback, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0) {
PERROR("open(%s)", *loopback);
goto failed;
}
if (!is_loop_device(fd)) {
close(fd);
continue;
}
memset(&info, 0, sizeof(info));
attached = loop_is_attached(fd, &info);
close(fd);
DEBUG("Saw %s on %s", info.lo_file_name, *loopback);
if (attached && name &&
strncmp((char*)info.lo_file_name, name, namelen) == 0) {
DEBUG("Using %s", *loopback);
/* Reopen for working on it. Note that strictly
* speaking, there is a TOCTOU issue here because other
* code can theoretically tear down and re-use the loop
* device at any point in time. However, in practice we
* assume that the devices cryptohomed has created are
* only manipulated subsequently by cryptohomed, so we
* should be safe.
*/
fd = open(*loopback, O_RDWR | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0) {
PERROR("open(%s)", *loopback);
goto failed;
}
if (!is_loop_device(fd) || !loop_is_attached(fd, NULL)) {
ERROR("%s in bad state", *loopback);
close(fd);
goto failed;
}
closedir(sysfs_block_dir);
return fd;
}
}
ERROR("Ran out of loopback devices");
failed:
closedir(sysfs_block_dir);
g_free(*loopback);
*loopback = NULL;
return -1;
}
static int loop_detach_fd(int fd) {
if (ioctl(fd, LOOP_CLR_FD, 0)) {
PERROR("LOOP_CLR_FD");
return 0;
}
return 1;
}
int loop_detach(const gchar* loopback) {
int fd, rc = 1;
fd = open(loopback, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (fd < 0) {
PERROR("open(%s)", loopback);
return 0;
}
if (!is_loop_device(fd) || !loop_is_attached(fd, NULL) || !loop_detach_fd(fd))
rc = 0;
close(fd);
return rc;
}
int loop_detach_name(const char* name) {
gchar* loopback = NULL;
int loopfd, rc;
loopfd = loop_locate(&loopback, name);
if (loopfd < 0)
return 0;
rc = loop_detach_fd(loopfd);
close(loopfd);
g_free(loopback);
return rc;
}
/* Closes fd, returns name of loopback device pathname. */
gchar* loop_attach(int fd, const char* name) {
gchar* loopback = NULL;
gchar* result = NULL;
int control_fd = -1;
int loop_fd = -1;
int num = -1;
struct loop_info64 info;
control_fd = open(kLoopControl, O_RDONLY | O_NOFOLLOW | O_CLOEXEC);
if (control_fd < 0) {
PERROR("open(%s)", kLoopControl);
goto out;
}
while (1) {
/* LOOP_CTL_GET_FREE returns the number of an unused loop device
* or if there is none, creates a new loop device and returns
* its number. Note that this races against other code trying
* to get a loop device concurrently, so it's possible another
* process picks the same "free" loop device as we do, and then
* collides with us binding it to a backing file...
*
* Fortunately, LOOP_SET_FD is atomic, i.e. it fails when the
* loop device is already attached to a file. We use this for
* detecting collisions and retry on EBUSY.
*/
num = ioctl(control_fd, LOOP_CTL_GET_FREE);
if (num < 0) {
PERROR("ioctl(LOOP_CTL_GET_FREE)");
goto out;
}
g_free(loopback);
loopback = g_strdup_printf(kLoopTemplate, num);
if (!loopback) {
PERROR("g_strdup_printf");
goto out;
}
loop_fd = open(loopback, O_RDWR | O_NOFOLLOW | O_CLOEXEC);
if (loop_fd < 0) {
PERROR("open(%s)", loopback);
goto out;
}
if (!is_loop_device(loop_fd)) {
goto out;
}
if (ioctl(loop_fd, LOOP_SET_FD, fd) == 0) {
break;
}
/* Retry on LOOP_SET_FD coming back with EBUSY. */
if (errno != EBUSY) {
PERROR("LOOP_SET_FD");
goto out;
}
}
DEBUG("Allocated loop device %d\n", num);
memset(&info, 0, sizeof(info));
strncpy((char*)info.lo_file_name, name, LO_NAME_SIZE);
if (ioctl(loop_fd, LOOP_SET_STATUS64, &info)) {
PERROR("LOOP_SET_STATUS64");
goto out;
}
result = loopback;
loopback = NULL;
out:
close(control_fd);
close(loop_fd);
close(fd);
g_free(loopback);
return result;
}
int dm_setup(uint64_t sectors,
const gchar* encryption_key,
const char* name,
const gchar* device,
const char* path,
int discard) {
/* Mount loopback device with dm-crypt using the encryption key. */
gchar* table = g_strdup_printf("0 %" PRIu64
" crypt "
"aes-cbc-essiv:sha256 %s "
"0 %s 0%s",
sectors, encryption_key, device,
discard ? " 1 allow_discards" : "");
if (!table) {
PERROR("g_strdup_printf");
return 0;
}
const gchar* argv[] = {"/sbin/dmsetup", "create", name, "--noudevrules",
"--noudevsync", "--table", table, NULL};
/* TODO(keescook): replace with call to libdevmapper. */
if (runcmd(argv, NULL) != 0) {
g_free(table);
return 0;
}
g_free(table);
/* Make sure the dm-crypt device showed up. */
if (access(path, R_OK)) {
ERROR("%s does not exist", path);
return 0;
}
return 1;
}
int dm_teardown(const gchar* device) {
const char* argv[] = {"/sbin/dmsetup", "remove", device,
"--noudevrules", "--noudevsync", NULL};
/* TODO(keescook): replace with call to libdevmapper. */
if (runcmd(argv, NULL) != 0)
return 0;
return 1;
}
char* dm_get_key(const gchar* device) {
gchar* output = NULL;
char* key;
int i;
const char* argv[] = {"/sbin/dmsetup", "table", "--showkeys", device, NULL};
/* TODO(keescook): replace with call to libdevmapper. */
if (runcmd(argv, &output) != 0)
return NULL;
/* Key is 4th field in the output. */
for (i = 0, key = strtok(output, " "); i < 4 && key;
++i, key = strtok(NULL, " ")) {
}
/* Create a copy of the key and free the output buffer. */
if (key) {
key = strdup(key);
g_free(output);
}
return key;
}
int sparse_create(const char* path, uint64_t bytes) {
int sparsefd;
sparsefd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC,
S_IRUSR | S_IWUSR);
if (sparsefd < 0)
goto out;
if (ftruncate(sparsefd, bytes)) {
int saved_errno = errno;
close(sparsefd);
unlink(path);
errno = saved_errno;
sparsefd = -1;
}
out:
return sparsefd;
}
/* When creating a filesystem that will grow, the inode ratio is calculated
* using the starting size not the hinted "resize" size, which means the
* number of inodes can be highly constrained on tiny starting filesystems.
* Instead, calculate what the correct inode ratio should be for a given
* filesystem based on its expected starting and ending sizes.
*
* inode-ratio_mkfs =
*
* ceil(blocks_max / group-ratio) * size_mkfs
* ------------------------------------------------------------------
* ceil(size_max / inode-ratio_max) * ceil(blocks_mkfs / group-ratio)
*/
static uint64_t get_inode_ratio(uint64_t block_bytes_in,
uint64_t blocks_mkfs_in,
uint64_t blocks_max_in) {
double block_bytes = (double)block_bytes_in;
double blocks_mkfs = (double)blocks_mkfs_in;
double blocks_max = (double)blocks_max_in;
double size_max, size_mkfs, groups_max, groups_mkfs, inodes_max;
double denom, inode_ratio_mkfs;
size_max = block_bytes * blocks_max;
size_mkfs = block_bytes * blocks_mkfs;
groups_max = ceil(blocks_max / kBlocksPerGroup);
groups_mkfs = ceil(blocks_mkfs / kBlocksPerGroup);
inodes_max = ceil(size_max / kInodeRatioDefault);
denom = inodes_max * groups_mkfs;
/* Make sure we never trigger divide-by-zero. */
if (denom == 0.0)
goto failure;
inode_ratio_mkfs = (groups_max * size_mkfs) / denom;
/* Make sure we never calculate anything totally huge. */
if (inode_ratio_mkfs > blocks_mkfs)
goto failure;
/* Make sure we never calculate anything totally tiny. */
if (inode_ratio_mkfs < kInodeRatioMinimum)
goto failure;
return (uint64_t)inode_ratio_mkfs;
failure:
return kInodeRatioDefault;
}
/* Creates an ext4 filesystem.
* device: path to block device to create filesystem on.
* block_bytes: bytes per block to use for filesystem.
* blocks_min: starting number of blocks on filesystem.
* blocks_max: largest expected size in blocks of filesystem, for growth hints.
*
* Returns 1 on success, 0 on failure.
*/
int filesystem_build(const char* device,
uint64_t block_bytes,
uint64_t blocks_min,
uint64_t blocks_max) {
int rc = 0;
uint64_t inode_ratio;
gchar* blocksize = g_strdup_printf("%" PRIu64, block_bytes);
if (!blocksize) {
PERROR("g_strdup_printf");
goto out;
}
gchar* blocks_str;
blocks_str = g_strdup_printf("%" PRIu64, blocks_min);
if (!blocks_str) {
PERROR("g_strdup_printf");
goto free_blocksize;
}
gchar* extended;
if (blocks_min < blocks_max) {
extended =
g_strdup_printf("%s,resize=%" PRIu64, kExt4ExtendedOptions, blocks_max);
} else {
extended = g_strdup_printf("%s", kExt4ExtendedOptions);
}
if (!extended) {
PERROR("g_strdup_printf");
goto free_blocks_str;
}
inode_ratio = get_inode_ratio(block_bytes, blocks_min, blocks_max);
gchar* inode_ratio_str;
inode_ratio_str = g_strdup_printf("%" PRIu64, inode_ratio);
if (!inode_ratio_str) {
PERROR("g_strdup_printf");
goto free_extended;
}
/* Add scope to ensure compilation with C++: "error: jump bypasses
* initialization" goto * above jumps over the definition of mkfs[] and
* tune2fs[] */
{
const gchar* mkfs[] = {"/sbin/mkfs.ext4",
"-T",
"default",
"-b",
blocksize,
"-m",
"0",
"-O",
"^huge_file,^flex_bg",
"-i",
inode_ratio_str,
"-E",
extended,
device,
blocks_str,
NULL};
rc = (runcmd(mkfs, NULL) == 0);
if (!rc)
goto free_inode_ratio_str;
}
{
const gchar* tune2fs[] = {"/sbin/tune2fs", "-c", "0", "-i", "0",
device, NULL};
rc = (runcmd(tune2fs, NULL) == 0);
}
free_inode_ratio_str:
g_free(inode_ratio_str);
free_extended:
g_free(extended);
free_blocks_str:
g_free(blocks_str);
free_blocksize:
g_free(blocksize);
out:
return rc;
}
/* Spawns a filesystem resizing process. */
int filesystem_resize(const char* device,
uint64_t blocks,
uint64_t blocks_max) {
/* Ignore resizing if we know the filesystem was built to max size. */
if (blocks >= blocks_max) {
INFO("Resizing aborted. blocks:%" PRIu64 " >= blocks_max:%" PRIu64, blocks,
blocks_max);
return 1;
}
/* TODO(keescook): Read superblock to find out the current size of
* the filesystem (since statvfs does not report the correct value).
* For now, instead of doing multi-step resizing, just resize to the
* full size of the block device in one step.
*/
blocks = blocks_max;
INFO("Resizing started in %d second steps.", kResizeStepSeconds);
do {
gchar* blocks_str;
sleep(kResizeStepSeconds);
blocks += kResizeBlocks;
if (blocks > blocks_max)
blocks = blocks_max;
blocks_str = g_strdup_printf("%" PRIu64, blocks);
if (!blocks_str) {
PERROR("g_strdup_printf");
return 0;
}
const gchar* resize[] = {"/sbin/resize2fs", "-f", device, blocks_str, NULL};
INFO("Resizing filesystem on %s to %" PRIu64 ".", device, blocks);
if (runcmd(resize, NULL)) {
ERROR("resize2fs failed");
return 0;
}
g_free(blocks_str);
} while (blocks < blocks_max);
INFO("Resizing finished.");
return 1;
}
char* keyfile_read(const char* keyfile, uint8_t* system_key) {
char* key = NULL;
unsigned char* cipher = NULL;
gsize length;
uint8_t* plain = NULL;
int plain_length, final_len;
GError* error = NULL;
EVP_CIPHER_CTX ctx;
const EVP_CIPHER* algo = EVP_aes_256_cbc();
DEBUG("Reading keyfile %s", keyfile);
if (EVP_CIPHER_key_length(algo) != DIGEST_LENGTH) {
ERROR("cipher key size mismatch (got %d, want %d)",
EVP_CIPHER_key_length(algo), DIGEST_LENGTH);
goto out;
}
if (access(keyfile, R_OK)) {
/* This file being missing is handled in caller, so
* do not emit error message.
*/
INFO("%s does not exist.", keyfile);
goto out;
}
if (!g_file_get_contents(keyfile, (gchar**)&cipher, &length, &error)) {
ERROR("Unable to read %s: %s", keyfile, error->message);
g_error_free(error);
goto out;
}
plain = (uint8_t*)malloc(length + EVP_CIPHER_block_size(algo));
if (!plain) {
PERROR("malloc");
goto free_cipher;
}
DEBUG("Decrypting keyfile %s", keyfile);
/* Use the default IV. */
if (!EVP_DecryptInit(&ctx, algo, system_key, NULL)) {
SSL_ERROR("EVP_DecryptInit");
goto free_plain;
}
if (!EVP_DecryptUpdate(&ctx, plain, &plain_length, cipher, length)) {
SSL_ERROR("EVP_DecryptUpdate");
goto free_ctx;
}
if (!EVP_DecryptFinal(&ctx, plain + plain_length, &final_len)) {
SSL_ERROR("EVP_DecryptFinal");
goto free_ctx;
}
plain_length += final_len;
if (plain_length != DIGEST_LENGTH) {
ERROR("Decrypted encryption key length (%d) is not %d.", plain_length,
DIGEST_LENGTH);
goto free_ctx;
}
debug_dump_hex("encryption key", plain, DIGEST_LENGTH);
key = stringify_hex(plain, DIGEST_LENGTH);
free_ctx:
EVP_CIPHER_CTX_cleanup(&ctx);
free_plain:
free(plain);
free_cipher:
g_free(cipher);
out:
DEBUG("key:%p", key);
return key;
}
/* Pushes a previously written file out to disk. Returns 1 on success and 0 if
* it cannot be guaranteed that the file has hit the disk.
*/
int file_sync_full(const char* filename) {
int fd, sync_result, close_result;
int retval = 0;
char* dirname = g_path_get_dirname((gchar*)filename);
/* sync file */
fd = TEMP_FAILURE_RETRY(open(filename, O_WRONLY | O_CLOEXEC));
if (fd < 0) {
ERROR("Could not open %s for syncing", filename);
goto free_dirname;
}
sync_result = TEMP_FAILURE_RETRY(fdatasync(fd));
close_result = close(fd); // close() may not be retried.
if (sync_result < 0) {
ERROR("Failed to sync %s", filename);
goto free_dirname;
}
if (close_result < 0) {
ERROR("Failed to close %s", filename);
goto free_dirname;
}
/* sync directory */
fd = TEMP_FAILURE_RETRY(open(dirname, O_RDONLY | O_CLOEXEC));
if (fd < 0) {
ERROR("Could not open directory %s for syncing", dirname);
goto free_dirname;
}
sync_result = TEMP_FAILURE_RETRY(fsync(fd));
close_result = close(fd); // close() may not be retried.
if (sync_result < 0) {
ERROR("Failed to sync %s", filename);
goto free_dirname;
}
if (close_result < 0) {
ERROR("Failed to close %s", filename);
goto free_dirname;
}
retval = 1;
free_dirname:
g_free(dirname);
return retval;
}
int keyfile_write(const char* keyfile, uint8_t* system_key, char* string) {
int rc = 0;
size_t length;
uint8_t plain[DIGEST_LENGTH];
uint8_t* cipher = NULL;
int cipher_length, final_len, tries, result;
GError* error = NULL;
EVP_CIPHER_CTX ctx;
const EVP_CIPHER* algo = EVP_aes_256_cbc();
mode_t mask;
DEBUG("Staring to process keyfile %s", keyfile);
/* Have key file be read/write only by root user. */
mask = umask(0077);
if (EVP_CIPHER_key_length(algo) != DIGEST_LENGTH) {
ERROR("cipher key size mismatch (got %d, want %d)",
EVP_CIPHER_key_length(algo), DIGEST_LENGTH);
goto out;
}
if (access(keyfile, R_OK) == 0) {
ERROR("%s already exists.", keyfile);
goto out;
}
length = strlen(string);
if (length != sizeof(plain) * 2) {
ERROR("Encryption key string length (%zu) is not %zu.", length,
sizeof(plain) * 2);
goto out;
}
length = sizeof(plain);
if (!hexify_string(string, plain, length)) {
ERROR("Failed to convert encryption key to byte array");
goto out;
}
debug_dump_hex("encryption key", plain, DIGEST_LENGTH);
cipher = (uint8_t*)malloc(length + EVP_CIPHER_block_size(algo));
if (!cipher) {
PERROR("malloc");
goto out;
}
DEBUG("Encrypting keyfile %s", keyfile);
/* Use the default IV. */
if (!EVP_EncryptInit(&ctx, algo, system_key, NULL)) {
SSL_ERROR("EVP_EncryptInit");
goto free_cipher;
}
if (!EVP_EncryptUpdate(&ctx, cipher, &cipher_length, (unsigned char*)plain,
length)) {
SSL_ERROR("EVP_EncryptUpdate");
goto free_ctx;
}
if (!EVP_EncryptFinal(&ctx, cipher + cipher_length, &final_len)) {
SSL_ERROR("EVP_EncryptFinal");
goto free_ctx;
}
length = cipher_length + final_len;
DEBUG("Writing %zu bytes to %s", length, keyfile);
/* TODO(keescook): use fd here, and set secure delete. Unsupported
* by ext4 currently. :(
* int f;
* ioctl(fd, EXT2_IOC_GETFLAGS, &f);
* f |= EXT2_SECRM_FL;
* ioctl(fd, EXT2_IOC_SETFLAGS, &f);
*/
/* Note that the combination of g_file_set_contents() and
* file_sync_full() isn't atomic, but the time window of non-atomicity
* should be pretty small.
*/
tries = 3;
while (tries > 0 && !(result = g_file_set_contents(keyfile, (gchar*)cipher,
length, &error))) {
ERROR("Unable to write %s: %s", keyfile, error->message);
g_error_free(error);
--tries;
}
if (!result)
goto free_ctx;
tries = 3;
while (tries > 0 && !(result = file_sync_full(keyfile))) {
ERROR("Unable to sync %s.", keyfile);
--tries;
}
if (!result)
goto free_ctx;
rc = 1;
free_ctx:
EVP_CIPHER_CTX_cleanup(&ctx);
free_cipher:
free(cipher);
out:
umask(mask);
DEBUG("keyfile write rc:%d", rc);
return rc;
}