diff --git a/man/cryptsetup-reencrypt.8 b/man/cryptsetup-reencrypt.8 index e0de6561..76c62f7a 100644 --- a/man/cryptsetup-reencrypt.8 +++ b/man/cryptsetup-reencrypt.8 @@ -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 use: .PP \fIcryptsetup-reencrypt\fR -\fB\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\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\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\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 . .SH COPYRIGHT -Copyright \(co 2012-2014 Milan Broz +Copyright \(co 2012-2015 Milan Broz .br Copyright \(co 2012-2013 Red Hat, Inc. diff --git a/src/cryptsetup_reencrypt.c b/src/cryptsetup_reencrypt.c index bf02e4a1..1db3588d 100644 --- a/src/cryptsetup_reencrypt.c +++ b/src/cryptsetup_reencrypt.c @@ -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))) - goto out; + goto out; + if ((r = device_check(&rc, MAKE_UNUSABLE))) + goto out; + } } else { if ((r = initialize_passphrase(&rc, rc.header_file_new))) goto out; @@ -1132,7 +1224,9 @@ static int run_reencrypt(const char *device) } else log_dbg("Keeping existing key, skipping data area reencryption."); - r = restore_luks_header(&rc); + // FIXME: fix error path above to not skip this + if (rc.reencrypt_mode != DECRYPT) + r = restore_luks_header(&rc); out: destroy_context(&rc); return r; @@ -1184,7 +1278,8 @@ int main(int argc, const char **argv) { "keyfile-size", 'l', POPT_ARG_LONG, &opt_keyfile_size, 0, N_("Limits the read from keyfile"), N_("bytes") }, { "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 }, + { "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); diff --git a/tests/reencryption-compat-test b/tests/reencryption-compat-test index b2faffcb..7cc7be87 100755 --- a/tests/reencryption-compat-test +++ b/tests/reencryption-compat-test @@ -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