Support permanent device decryption using cryptsetup-reencrypt --decrypt.

This commit is contained in:
Milan Broz
2015-01-27 14:09:29 +01:00
parent 8157e47ad4
commit 6d51e8ab69
3 changed files with 162 additions and 36 deletions

View File

@@ -1,4 +1,4 @@
.TH CRYPTSETUP-REENCRYPT "8" "December 2013" "cryptsetup-reencrypt" "Maintenance Commands"
.TH CRYPTSETUP-REENCRYPT "8" "January 2015" "cryptsetup-reencrypt" "Maintenance Commands"
.SH NAME
cryptsetup-reencrypt - tool for offline LUKS device re-encryption
.SH SYNOPSIS
@@ -36,9 +36,15 @@ To start (or continue) re-encryption for <device> use:
.PP
\fIcryptsetup-reencrypt\fR <device>
\fB<options>\fR can be [\-\-block-size, \-\-cipher, \-\-hash, \-\-iter-time,
\-\-use-random | \-\-use-urandom, \-\-key-file, \-\-key-slot, \-\-keyfile-offset,
\-\-keyfile-size, \-\-tries, \-\-use-directio, \-\-use-fsync, \-\-write-log]
\fB<options>\fR can be [\-\-batch-mode, \-\-block-size, \-\-cipher, \-\-debug,
\-\-device-size, \-\-hash, \-\-iter-time, \-\-use-random | \-\-use-urandom,
\-\-keep-key, \-\-key-size, \-\-key-file, \-\-key-slot, \-\-keyfile-offset,
\-\-keyfile-size, \-\-tries, \-\-use-directio, \-\-use-fsync, \-\-verbose, \-\-write-log]
To encrypt data on (not yet encrypted) device, use \fI\-\-new\fR with combination
with \fI\-\-reduce-device-size\fR.
To remove encryption from device, use \fI\-\-decrypt\fR.
For detailed description of encryption and key file options see \fIcryptsetup(8)\fR
man page.
@@ -68,7 +74,7 @@ you can destructively shrink device with \-\-reduce-device-size option.
.B "\-\-hash, \-h \fI<hash-spec>\fR"
Specifies the hash used in the LUKS key setup scheme and volume key digest.
NOTE: if this parameter is not specified, default hash algorithm is always used
\fBNOTE:\fR if this parameter is not specified, default hash algorithm is always used
for new device header.
.TP
.B "\-\-iter-time, \-i \fI<milliseconds>\fR"
@@ -83,7 +89,7 @@ Define which kernel random number generator will be used to create the volume ke
.B "\-\-key-file, \-d \fIname\fR"
Read the passphrase from file.
WARNING: \-\-key-file option can be used only if there only one active keyslot,
\fBWARNING:\fR \-\-key-file option can be used only if there only one active keyslot,
or alternatively, also if \-\-key-slot option is specified (then all other keyslots
will be disabled in new LUKS device).
@@ -93,7 +99,7 @@ passphrases.
.B "\-\-key-slot, \-S <0-7>"
Specify which key slot is used.
WARNING: All other keyslots will be disabled if this option is used.
\fBWARNING:\fR All other keyslots will be disabled if this option is used.
.TP
.B "\-\-keyfile-offset \fIvalue\fR"
Skip \fIvalue\fR bytes at the beginning of the key file.
@@ -123,14 +129,14 @@ Instead of real device size, use specified value.
It means that only specified area (from the start of the device
to the specified size) will be reencrypted.
WARNING: This is destructive operation.
\fBWARNING:\fR This is destructive operation.
If no unit suffix is specified, the size is in bytes.
Unit suffix can be S for 512 byte sectors, K/M/G/T (or KiB,MiB,GiB,TiB)
for units with 1024 base or KB/MB/GB/TB for 1000 base (SI scale).
WARNING: This is destructive operation.
\fBWARNING:\fR This is destructive operation.
.TP
.B "\-\-reduce-device-size \fIsize[units]\fR"
Enlarge data offset to specified value by shrinking device size.
@@ -144,7 +150,7 @@ partition (so last sectors contains no data).
For units suffix see \-\-device-size parameter description.
WARNING: This is destructive operation and cannot be reverted.
\fBWARNING:\fR This is destructive operation and cannot be reverted.
Use with extreme care - shrinked filesystems are usually unrecoverable.
You cannot shrink device more than by 64 MiB (131072 sectors).
@@ -154,8 +160,12 @@ Create new header (encrypt not yet encrypted device).
This option must be used together with \-\-reduce-device-size.
WARNING: This is destructive operation and cannot be reverted.
\fBWARNING:\fR This is destructive operation and cannot be reverted.
.TP
.B "\-\-decrypt"
Remove encryption (decrypt already encrypted device and remove LUKS header).
\fBWARNING:\fR This is destructive operation and cannot be reverted.
.TP
.B "\-\-use-directio"
Use direct-io (O_DIRECT) for all read/write data operations related
@@ -203,6 +213,10 @@ fdisk \-u /dev/sdb # move sdb1 partition end + 4096 sectors
(or use resize2fs or tool for your filesystem and shrink it)
cryptsetup-reencrypt /dev/sdb1 \-\-new \-\-reduce-device-size 4096S
.TP
Remove LUKS encryption completely
cryptsetup-reencrypt /dev/sdb1 \-\-decrypt
.SH REPORTING BUGS
Report bugs, including ones in the documentation, on
@@ -213,7 +227,7 @@ Please attach the output of the failed command with the
.SH AUTHORS
Cryptsetup-reencrypt was written by Milan Broz <gmazyland@gmail.com>.
.SH COPYRIGHT
Copyright \(co 2012-2014 Milan Broz
Copyright \(co 2012-2015 Milan Broz
.br
Copyright \(co 2012-2013 Red Hat, Inc.

View File

@@ -2,7 +2,7 @@
* cryptsetup-reencrypt - crypt utility for offline re-encryption
*
* Copyright (C) 2012, Red Hat, Inc. All rights reserved.
* Copyright (C) 2012-2014, Milan Broz All rights reserved.
* Copyright (C) 2012-2015, Milan Broz All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@@ -48,6 +48,7 @@ static int opt_key_slot = CRYPT_ANY_SLOT;
static int opt_key_size = 0;
static int opt_new = 0;
static int opt_keep_key = 0;
static int opt_decrypt = 0;
static const char *opt_reduce_size_str = NULL;
static uint64_t opt_reduce_size = 0;
@@ -62,12 +63,14 @@ struct reenc_ctx {
char *device;
char *device_uuid;
uint64_t device_size; /* overrided by parameter */
uint64_t device_size_real;
uint64_t device_size_new_real;
uint64_t device_size_org_real;
uint64_t device_offset;
uint64_t device_shift;
int in_progress:1;
enum { FORWARD = 0, BACKWARD = 1 } reencrypt_direction;
enum { REENCRYPT = 0, ENCRYPT = 1, DECRYPT = 2 } reencrypt_mode;
char header_file_org[PATH_MAX];
char header_file_new[PATH_MAX];
@@ -265,9 +268,9 @@ static int write_log(struct reenc_ctx *rc)
memset(rc->log_buf, 0, SECTOR_SIZE);
snprintf(rc->log_buf, SECTOR_SIZE, "# LUKS reencryption log, DO NOT EDIT OR DELETE.\n"
"version = %d\nUUID = %s\ndirection = %d\n"
"version = %d\nUUID = %s\ndirection = %d\nmode = %d\n"
"offset = %" PRIu64 "\nshift = %" PRIu64 "\n# EOF\n",
1, rc->device_uuid, rc->reencrypt_direction,
2, rc->device_uuid, rc->reencrypt_direction, rc->reencrypt_mode,
rc->device_offset, rc->device_shift);
if (lseek(rc->log_fd, 0, SEEK_SET) == -1)
@@ -293,7 +296,7 @@ static int parse_line_log(struct reenc_ctx *rc, const char *line)
return 0;
if (sscanf(line, "version = %d", &i) == 1) {
if (i != 1) {
if (i < 1 || i > 2) {
log_dbg("Log: Unexpected version = %i", i);
return -EINVAL;
}
@@ -311,6 +314,13 @@ static int parse_line_log(struct reenc_ctx *rc, const char *line)
} else if (sscanf(line, "shift = %" PRIu64, &u64) == 1) {
log_dbg("Log: shift = %" PRIu64, u64);
rc->device_shift = u64;
} else if (sscanf(line, "mode = %d", &i) == 1) { /* added in v2 */
log_dbg("Log: mode = %i", i);
rc->reencrypt_mode = i;
if (rc->reencrypt_mode != REENCRYPT &&
rc->reencrypt_mode != ENCRYPT &&
rc->reencrypt_mode != DECRYPT)
return -EINVAL;
} else
return -EINVAL;
@@ -473,6 +483,10 @@ static int backup_luks_headers(struct reenc_ctx *rc)
goto out;
log_verbose(_("LUKS header backup of device %s created.\n"), rc->device);
/* For decrypt, new header will be fake one, so we are done here. */
if (rc->reencrypt_mode == DECRYPT)
goto out;
if ((r = create_empty_header(rc->header_file_new, rc->header_file_org,
crypt_get_data_offset(cd))))
goto out;
@@ -525,10 +539,13 @@ static int backup_fake_header(struct reenc_ctx *rc)
struct crypt_device *cd_new = NULL;
struct crypt_params_luks1 params = {0};
char cipher [MAX_CIPHER_LEN], cipher_mode[MAX_CIPHER_LEN];
const char *header_file_fake;
int r;
log_dbg("Creating fake (cipher_null) header for original device.");
log_dbg("Creating fake (cipher_null) header for %s device.",
(rc->reencrypt_mode == DECRYPT) ? "new" : "original");
header_file_fake = (rc->reencrypt_mode == DECRYPT) ? rc->header_file_new : rc->header_file_org;
if (!opt_key_size)
opt_key_size = DEFAULT_LUKS1_KEYBITS;
@@ -541,7 +558,7 @@ static int backup_fake_header(struct reenc_ctx *rc)
}
}
r = create_empty_header(rc->header_file_org, NULL, 0);
r = create_empty_header(header_file_fake, NULL, MAX_BCK_SECTORS);
if (r < 0)
return r;
@@ -549,7 +566,7 @@ static int backup_fake_header(struct reenc_ctx *rc)
params.data_alignment = 0;
params.data_device = rc->device;
r = crypt_init(&cd_new, rc->header_file_org);
r = crypt_init(&cd_new, header_file_fake);
if (r < 0)
return r;
@@ -563,6 +580,10 @@ static int backup_fake_header(struct reenc_ctx *rc)
if (r < 0)
goto out;
/* The real header is backup header created in backup_luks_headers() */
if (rc->reencrypt_mode == DECRYPT)
goto out;
r = create_empty_header(rc->header_file_new, rc->header_file_org, 0);
if (r < 0)
goto out;
@@ -794,6 +815,41 @@ static int copy_data_backward(struct reenc_ctx *rc, int fd_old, int fd_new,
return quit ? -EAGAIN : 0;
}
static void zero_rest_of_device(int fd, size_t block_size, void *buf,
uint64_t *bytes, uint64_t offset)
{
ssize_t s1, s2;
log_dbg("Zeroing rest of device.");
if (lseek64(fd, offset, SEEK_SET) < 0) {
log_dbg(_("Cannot seek to device offset.\n"));
return;
}
memset(buf, 0, block_size);
s1 = block_size;
while (!quit && *bytes) {
if (*bytes < s1)
s1 = *bytes;
s2 = write(fd, buf, s1);
if (s2 < 0) {
log_dbg("Write error, expecting %zu, got %zd.",
block_size, s2);
return;
}
if (opt_fsync && fsync(fd) < 0) {
log_dbg("Write error, fsync.");
return;
}
*bytes -= s2;
}
}
static int copy_data(struct reenc_ctx *rc)
{
size_t block_size = opt_bsize * 1024 * 1024;
@@ -816,13 +872,22 @@ static int copy_data(struct reenc_ctx *rc)
goto out;
}
/* Check size */
if (ioctl(fd_new, BLKGETSIZE64, &rc->device_size_real) < 0) {
if (ioctl(fd_old, BLKGETSIZE64, &rc->device_size_org_real) < 0) {
log_err(_("Cannot get device size.\n"));
goto out;
}
rc->device_size = opt_device_size ?: rc->device_size_real;
if (ioctl(fd_new, BLKGETSIZE64, &rc->device_size_new_real) < 0) {
log_err(_("Cannot get device size.\n"));
goto out;
}
if (opt_device_size)
rc->device_size = opt_device_size;
else if (rc->reencrypt_mode == DECRYPT)
rc->device_size = rc->device_size_org_real;
else
rc->device_size = rc->device_size_new_real;
if (posix_memalign((void *)&buf, alignment(fd_new), block_size)) {
log_err(_("Allocation of aligned memory failed.\n"));
@@ -838,9 +903,18 @@ static int copy_data(struct reenc_ctx *rc)
else
r = copy_data_backward(rc, fd_old, fd_new, block_size, buf, &bytes);
set_int_block(1);
print_progress(rc, bytes, 1);
/* Zero (wipe) rest of now plain-only device when decrypting.
* (To not leave any sign of encryption here.) */
if (!r && rc->reencrypt_mode == DECRYPT &&
rc->device_size_new_real > rc->device_size_org_real) {
bytes = rc->device_size_new_real - rc->device_size_org_real;
zero_rest_of_device(fd_new, block_size, buf, &bytes, rc->device_size_org_real);
}
set_int_block(1);
if (r == -EAGAIN)
log_err(_("Interrupted by a signal.\n"));
else if (r < 0)
@@ -981,7 +1055,7 @@ static int initialize_passphrase(struct reenc_ctx *rc, const char *device)
log_dbg("Passhrases initialization.");
if (opt_new && !rc->in_progress) {
if (rc->reencrypt_mode == ENCRYPT && !rc->in_progress) {
r = init_passphrase1(rc, cd, _("Enter new passphrase: "), opt_key_slot, 0);
return r > 0 ? 0 : r;
}
@@ -1001,7 +1075,9 @@ static int initialize_passphrase(struct reenc_ctx *rc, const char *device)
if (opt_key_file) {
r = init_keyfile(rc, cd, opt_key_slot);
} else if (rc->in_progress || opt_key_slot != CRYPT_ANY_SLOT) {
} else if (rc->in_progress ||
opt_key_slot != CRYPT_ANY_SLOT ||
rc->reencrypt_mode == DECRYPT) {
r = init_passphrase1(rc, cd, msg, opt_key_slot, 1);
} else for (i = 0; i < MAX_SLOT; i++) {
ki = crypt_keyslot_status(cd, i);
@@ -1068,6 +1144,13 @@ static int initialize_context(struct reenc_ctx *rc, const char *device)
rc->reencrypt_direction = BACKWARD;
rc->device_offset = (uint64_t)~0;
}
if (opt_new)
rc->reencrypt_mode = ENCRYPT;
else if (opt_decrypt)
rc->reencrypt_mode = DECRYPT;
else
rc->reencrypt_mode = REENCRYPT;
}
return 0;
@@ -1109,14 +1192,23 @@ static int run_reencrypt(const char *device)
log_dbg("Running reencryption.");
if (!rc.in_progress) {
if (opt_new) {
if ((r = initialize_passphrase(&rc, rc.device)) ||
if ((r = initialize_passphrase(&rc, rc.device)))
goto out;
if (rc.reencrypt_mode == ENCRYPT) {
/* Create fake header for exising device */
if ((r = backup_fake_header(&rc)))
goto out;
} else {
if ((r = backup_luks_headers(&rc)))
goto out;
/* Create fake header for decrypted device */
if (rc.reencrypt_mode == DECRYPT &&
(r = backup_fake_header(&rc)))
goto out;
} else if ((r = initialize_passphrase(&rc, rc.device)) ||
(r = backup_luks_headers(&rc)) ||
(r = device_check(&rc, MAKE_UNUSABLE)))
if ((r = device_check(&rc, MAKE_UNUSABLE)))
goto out;
}
} else {
if ((r = initialize_passphrase(&rc, rc.header_file_new)))
goto out;
@@ -1132,6 +1224,8 @@ static int run_reencrypt(const char *device)
} else
log_dbg("Keeping existing key, skipping data area reencryption.");
// FIXME: fix error path above to not skip this
if (rc.reencrypt_mode != DECRYPT)
r = restore_luks_header(&rc);
out:
destroy_context(&rc);
@@ -1185,6 +1279,7 @@ int main(int argc, const char **argv)
{ "reduce-device-size",'\0', POPT_ARG_STRING, &opt_reduce_size_str, 0, N_("Reduce data device size (move data offset). DANGEROUS!"), N_("bytes") },
{ "device-size", '\0', POPT_ARG_STRING, &opt_device_size_str, 0, N_("Use only specified device size (ignore rest of device). DANGEROUS!"), N_("bytes") },
{ "new", 'N', POPT_ARG_NONE, &opt_new, 0, N_("Create new header on not encrypted device."), NULL },
{ "decrypt", '\0', POPT_ARG_NONE, &opt_decrypt, 0, N_("Permanently decrypt device (remove encryption)."), NULL },
POPT_TABLEEND
};
poptContext popt_context;
@@ -1279,6 +1374,14 @@ int main(int argc, const char **argv)
usage(popt_context, EXIT_FAILURE, _("Option --keep-key can be used only with --hash or --iter-time."),
poptGetInvocationName(popt_context));
if (opt_new && opt_decrypt)
usage(popt_context, EXIT_FAILURE, _("Option --new cannot be used together with --decrypt."),
poptGetInvocationName(popt_context));
if (opt_decrypt && (opt_cipher || opt_hash || opt_reduce_size || opt_keep_key || opt_device_size))
usage(popt_context, EXIT_FAILURE, _("Option --decrypt is incompatible with specified parameters."),
poptGetInvocationName(popt_context));
if (opt_debug) {
opt_verbose = 1;
crypt_set_debug_level(-1);

View File

@@ -206,6 +206,7 @@ which wipefs >/dev/null || skip "Cannot find wipefs, test skipped."
HASH1=b69dae56a14d1a8314ed40664c4033ea0a550eea2673e04df42a66ac6b9faf2c
HASH2=d85ef2a08aeac2812a648deb875485a6e3848fc3d43ce4aa380937f08199f86b
HASH3=e4e5749032a5163c45125eccf3e8598ba5ed840df442c97e1d5ad4ad84359605
HASH4=2daeb1f36095b44b318410b3f4e8b5d989dcc7bb023d1426c492dab0a3053e74
echo "[1] Reencryption"
prepare 8192
@@ -314,5 +315,13 @@ add_scsi_device sector_size=512 dev_size_mb=25 physblk_exp=3
test_logging "[4096/512 sector]" || fail
test_logging_tmpfs || fail
echo "[10] Removal of encryption"
prepare 8192
echo $PWD1 | $CRYPTSETUP -q luksFormat -i 1 $LOOPDEV1 || fail
wipe $PWD1
check_hash $PWD1 $HASH1
echo $PWD1 | $REENC $LOOPDEV1 -q --decrypt
check_hash_dev $LOOPDEV1 $HASH4
remove_mapping
exit 0