diff --git a/ChangeLog b/ChangeLog index e4541d0d..aacf9b49 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +2009-09-28 Milan Broz + * Add luksHeaderBackup and luksHeaderRestore commands. + 2009-09-15 Milan Broz * Initialize crypto library before LUKS header load. * Fix manpage to not require --size which expands to device size by default. diff --git a/lib/internal.h b/lib/internal.h index 55a7bc0a..b0e08680 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -48,6 +48,7 @@ struct device_infos { }; struct crypt_device; +int crypt_confirm(struct crypt_device *cd, const char *msg); void set_error_va(const char *fmt, va_list va); void set_error(const char *fmt, ...); diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index cdd30ec8..9d6e1288 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -442,6 +442,32 @@ int crypt_get_volume_key_size(struct crypt_device *cd); typedef enum { SLOT_INVALID, SLOT_INACTIVE, SLOT_ACTIVE, SLOT_ACTIVE_LAST } crypt_keyslot_info; crypt_keyslot_info crypt_keyslot_status(struct crypt_device *cd, int keyslot); +/** + * Backup header and keyslots to file + * + * Returns 0 on success or negative errno value otherwise. + * + * @cd - crypt device handle + * @requested_type - type of header to backup + * @backup_file - file to backup header to + */ +int crypt_header_backup(struct crypt_device *cd, + const char *requested_type, + const char *backup_file); + +/** + * Restore header and keyslots from backup file + * + * Returns 0 on success or negative errno value otherwise. + * + * @cd - crypt device handle + * @requested_type - type of header to restore + * @backup_file - file to restore header from + */ +int crypt_header_restore(struct crypt_device *cd, + const char *requested_type, + const char *backup_file); + /** * Receives last reported error * diff --git a/lib/setup.c b/lib/setup.c index e623c6ad..04b1c438 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -424,6 +424,14 @@ static int yesDialog_wrapper(const char *msg, void *usrptr) return xyesDialog((char*)msg); } +int crypt_confirm(struct crypt_device *cd, const char *msg) +{ + if (!cd || !cd->confirm) + return 1; + else + return cd->confirm(msg, cd->confirm_usrptr); +} + static void key_from_terminal(struct crypt_device *cd, char *msg, char **key, unsigned int *key_len, int force_verify) { @@ -1126,6 +1134,32 @@ int crypt_load(struct crypt_device *cd, return r; } +int crypt_header_backup(struct crypt_device *cd, + const char *requested_type, + const char *backup_file) +{ + if ((requested_type && !isLUKS(requested_type)) || !backup_file) + return -EINVAL; + + log_dbg("Requested header backup of device %s (%s) to " + "file %s.", cd->device, requested_type, backup_file); + + return LUKS_hdr_backup(backup_file, cd->device, &cd->hdr, cd); +} + +int crypt_header_restore(struct crypt_device *cd, + const char *requested_type, + const char *backup_file) +{ + if (requested_type && !isLUKS(requested_type)) + return -EINVAL; + + log_dbg("Requested header restore to device %s (%s) from " + "file %s.", cd->device, requested_type, backup_file); + + return LUKS_hdr_restore(backup_file, cd->device, &cd->hdr, cd); +} + void crypt_free(struct crypt_device *cd) { if (cd) { diff --git a/luks/keymanage.c b/luks/keymanage.c index d9e386cc..6bcfdb30 100644 --- a/luks/keymanage.c +++ b/luks/keymanage.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -75,28 +76,179 @@ struct luks_masterkey *LUKS_generate_masterkey(int keylength) return mk; } -int LUKS_read_phdr(const char *device, - struct luks_phdr *hdr, - int require_luks_device, - struct crypt_device *ctx) +int LUKS_hdr_backup( + const char *backup_file, + const char *device, + struct luks_phdr *hdr, + struct crypt_device *ctx) { - int devfd = 0, r = 0; - unsigned int i; - uint64_t size; - char luksMagic[] = LUKS_MAGIC; + int r = 0, devfd = -1; + size_t buffer_size; + char *buffer = NULL; + struct stat st; - log_dbg("Reading LUKS header of size %d from device %s", - sizeof(struct luks_phdr), device); - - devfd = open(device,O_RDONLY | O_DIRECT | O_SYNC); - if(-1 == devfd) { - log_err(ctx, _("Cannot open device %s.\n"), device); - return -EINVAL; + if(stat(backup_file, &st) == 0) { + log_err(ctx, _("Requested file %s already exist.\n"), backup_file); + return -EINVAL; } - if(read_blockwise(devfd, hdr, sizeof(struct luks_phdr)) < sizeof(struct luks_phdr)) { + r = LUKS_read_phdr(device, hdr, 0, ctx); + if (r) + return r; + + buffer_size = hdr->payloadOffset << SECTOR_SHIFT; + buffer = safe_alloc(buffer_size); + if (!buffer || buffer_size < LUKS_ALIGN_KEYSLOTS) { + r = -ENOMEM; + goto out; + } + + log_dbg("Storing backup of header (%u bytes) and keyslot area (%u bytes).", + sizeof(*hdr), buffer_size - LUKS_ALIGN_KEYSLOTS); + + devfd = open(device, O_RDONLY | O_DIRECT | O_SYNC); + if(devfd == -1) { + log_err(ctx, _("Device %s is not LUKS device.\n"), device); + r = -EINVAL; + goto out; + } + + if(read_blockwise(devfd, buffer, buffer_size) < buffer_size) { r = -EIO; - } else if(memcmp(hdr->magic, luksMagic, LUKS_MAGIC_L)) { /* Check magic */ + goto out; + } + close(devfd); + + /* Wipe unused area, so backup cannot contain old signatures */ + memset(buffer + sizeof(*hdr), 0, LUKS_ALIGN_KEYSLOTS - sizeof(*hdr)); + + devfd = creat(backup_file, S_IRUSR); + if(devfd == -1) { + r = -EINVAL; + goto out; + } + if(write(devfd, buffer, buffer_size) < buffer_size) { + log_err(ctx, _("Cannot write header backup file %s.\n"), backup_file); + r = -EIO; + goto out; + } + close(devfd); + + r = 0; +out: + if (devfd != -1) + close(devfd); + safe_free(buffer); + return r; +} + +int LUKS_hdr_restore( + const char *backup_file, + const char *device, + struct luks_phdr *hdr, + struct crypt_device *ctx) +{ + int r = 0, devfd = -1, diff_uuid = 0; + size_t buffer_size; + char *buffer = NULL, msg[200]; + struct stat st; + struct luks_phdr hdr_file; + + if(stat(backup_file, &st) < 0) { + log_err(ctx, _("Backup file %s doesn't exist.\n"), backup_file); + return -EINVAL; + } + + r = LUKS_read_phdr_backup(backup_file, device, &hdr_file, 0, ctx); + buffer_size = hdr_file.payloadOffset << SECTOR_SHIFT; + + if (r || buffer_size < LUKS_ALIGN_KEYSLOTS) { + log_err(ctx, _("Backup file do not contain valid LUKS header.\n")); + r = -EINVAL; + goto out; + } + + buffer = safe_alloc(buffer_size); + if (!buffer) { + r = -ENOMEM; + goto out; + } + + devfd = open(backup_file, O_RDONLY); + if(devfd == -1) { + log_err(ctx, _("Cannot open header backup file %s.\n"), backup_file); + r = -EINVAL; + goto out; + } + + if(read(devfd, buffer, buffer_size) < buffer_size) { + log_err(ctx, _("Cannot read header backup file %s.\n"), backup_file); + r = -EIO; + goto out; + } + close(devfd); + + r = LUKS_read_phdr(device, hdr, 0, ctx); + if (r == 0) { + log_dbg("Device %s already contains LUKS header, checking UUID and offset.", device); + if(hdr->payloadOffset != hdr_file.payloadOffset || + hdr->keyBytes != hdr_file.keyBytes) { + log_err(ctx, _("Data offset or key size differs on device and backup, restore failed.\n")); + r = -EINVAL; + goto out; + } + if (memcmp(hdr->uuid, hdr_file.uuid, UUID_STRING_L)) + diff_uuid = 1; + } + + if (snprintf(msg, sizeof(msg), _("Device %s %s%s"), device, + r ? _("does not contain LUKS header. Replacing header can destroy data on that device.") : + _("already contains LUKS header. Replacing header will destroy existing keyslots."), + diff_uuid ? _("\nWARNING: real device header has different UUID than backup!") : "") < 0) { + r = -ENOMEM; + goto out; + } + + if (!crypt_confirm(ctx, msg)) { + r = -EINVAL; + goto out; + } + + log_dbg("Storing backup of header (%u bytes) and keyslot area (%u bytes) to device %s.", + sizeof(*hdr), buffer_size - LUKS_ALIGN_KEYSLOTS, device); + + devfd = open(device, O_WRONLY | O_DIRECT | O_SYNC); + if(devfd == -1) { + log_err(ctx, _("Cannot open device %s.\n"), device); + r = -EINVAL; + goto out; + } + + if(write_blockwise(devfd, buffer, buffer_size) < buffer_size) { + r = -EIO; + goto out; + } + close(devfd); + + /* Be sure to reload new data */ + r = LUKS_read_phdr(device, hdr, 0, ctx); +out: + if (devfd != -1) + close(devfd); + safe_free(buffer); + return r; +} + +static int _check_and_convert_hdr(const char *device, + struct luks_phdr *hdr, + int require_luks_device, + struct crypt_device *ctx) +{ + int r = 0; + unsigned int i; + char luksMagic[] = LUKS_MAGIC; + + if(memcmp(hdr->magic, luksMagic, LUKS_MAGIC_L)) { /* Check magic */ log_dbg("LUKS header not detected."); if (require_luks_device) log_err(ctx, _("%s is not a LUKS device.\n"), device); @@ -122,6 +274,57 @@ int LUKS_read_phdr(const char *device, } } + return r; +} + +int LUKS_read_phdr_backup(const char *backup_file, + const char *device, + struct luks_phdr *hdr, + int require_luks_device, + struct crypt_device *ctx) +{ + int devfd = 0, r = 0; + + log_dbg("Reading LUKS header of size %d from backup file %s", + sizeof(struct luks_phdr), backup_file); + + devfd = open(backup_file, O_RDONLY); + if(-1 == devfd) { + log_err(ctx, _("Cannot open file %s.\n"), device); + return -EINVAL; + } + + if(read(devfd, hdr, sizeof(struct luks_phdr)) < sizeof(struct luks_phdr)) + r = -EIO; + else + r = _check_and_convert_hdr(backup_file, hdr, require_luks_device, ctx); + + close(devfd); + return r; +} + +int LUKS_read_phdr(const char *device, + struct luks_phdr *hdr, + int require_luks_device, + struct crypt_device *ctx) +{ + int devfd = 0, r = 0; + uint64_t size; + + log_dbg("Reading LUKS header of size %d from device %s", + sizeof(struct luks_phdr), device); + + devfd = open(device,O_RDONLY | O_DIRECT | O_SYNC); + if(-1 == devfd) { + log_err(ctx, _("Cannot open device %s.\n"), device); + return -EINVAL; + } + + if(read_blockwise(devfd, hdr, sizeof(struct luks_phdr)) < sizeof(struct luks_phdr)) + r = -EIO; + else + r = _check_and_convert_hdr(device, hdr, require_luks_device, ctx); + #ifdef BLKGETSIZE64 if (r == 0 && (ioctl(devfd, BLKGETSIZE64, &size) < 0 || size < (uint64_t)hdr->payloadOffset)) { @@ -195,7 +398,7 @@ int LUKS_generate_phdr(struct luks_phdr *header, char luksMagic[] = LUKS_MAGIC; uuid_t partitionUuid; int currentSector; - int alignSectors = 4096/SECTOR_SIZE; + int alignSectors = LUKS_ALIGN_KEYSLOTS / SECTOR_SIZE; if (alignPayload == 0) alignPayload = alignSectors; diff --git a/luks/luks.h b/luks/luks.h index 046c365a..a395173b 100644 --- a/luks/luks.h +++ b/luks/luks.h @@ -38,6 +38,9 @@ /* Actually we need only 37, but we don't want struct autoaligning to kick in */ #define UUID_STRING_L 40 +/* Offset to align kesylot area */ +#define LUKS_ALIGN_KEYSLOTS 4096 + /* Any integer values are stored in network byte order on disk and must be converted */ @@ -98,6 +101,25 @@ int LUKS_read_phdr( int require_luks_device, struct crypt_device *ctx); +int LUKS_read_phdr_backup( + const char *backup_file, + const char *device, + struct luks_phdr *hdr, + int require_luks_device, + struct crypt_device *ctx); + +int LUKS_hdr_backup( + const char *backup_file, + const char *device, + struct luks_phdr *hdr, + struct crypt_device *ctx); + +int LUKS_hdr_restore( + const char *backup_file, + const char *device, + struct luks_phdr *hdr, + struct crypt_device *ctx); + int LUKS_write_phdr( const char *device, struct luks_phdr *hdr, diff --git a/man/cryptsetup.8 b/man/cryptsetup.8 index 39720bd1..3743d967 100644 --- a/man/cryptsetup.8 +++ b/man/cryptsetup.8 @@ -92,6 +92,23 @@ returns true, if is a LUKS partition. Otherwise, false. No options. .IP dumps the header information of a LUKS partition. No options. .PP +\fIluksHeaderBackup\fR --header-backup-file +.IP +Stores binary backup of LUKS header and keyslot areas. + +\fBWARNING:\fR Please note that with this backup file (and old passphrase knowledge) you can decrypt data even if old passphrase was wiped from real device. + +Also note that anti-forensic splitter is not used during manipulation with backup file. +.PP +\fIluksHeaderRestore\fR --header-backup-file +.IP + +Restores binary backup of LUKS header and keyslot areas from specified file. + +\fBWARNING:\fR All the keyslot areas are overwritten, only active keyslots form backup file are available after issuing this command. + +This command allows restoring header if device do not contain LUKS header or if the master key size and data offset in LUKS header on device match the backup file. +.PP For more information about LUKS, see \fBhttp://code.google.com/p/cryptsetup/wiki/Specification\fR diff --git a/src/cryptsetup.c b/src/cryptsetup.c index 48c59e4f..03659059 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -22,6 +22,7 @@ static char *opt_hash = NULL; static int opt_verify_passphrase = 0; static char *opt_key_file = NULL; static char *opt_master_key_file = NULL; +static char *opt_header_backup_file = NULL; static unsigned int opt_key_size = 0; static int opt_key_slot = CRYPT_ANY_SLOT; static uint64_t opt_size = 0; @@ -54,6 +55,8 @@ static int action_luksUUID(int arg); static int action_luksDump(int arg); static int action_luksSuspend(int arg); static int action_luksResume(int arg); +static int action_luksBackup(int arg); +static int action_luksRestore(int arg); static struct action_type { const char *type; @@ -81,6 +84,8 @@ static struct action_type { { "luksDump", action_luksDump, 0, 1, 0, 0, 1, N_(""), N_("dump LUKS partition information") }, { "luksSuspend",action_luksSuspend, 0, 1, 1, 1, 1, N_(""), N_("Suspend LUKS device and wipe key (all IOs are frozen).") }, { "luksResume", action_luksResume, 0, 1, 1, 1, 1, N_(""), N_("Resume suspended LUKS device.") }, + { "luksHeaderBackup",action_luksBackup, 0, 1, 1, 1, 1, N_(""), N_("Backup LUKS device header and keyslots") }, + { "luksHeaderRestore",action_luksRestore,0,1, 1, 1, 1, N_(""), N_("Restore LUKS device header and keyslots") }, { "luksDelKey", action_luksDelKey, 0, 2, 1, 1, 1, N_(" "), N_("identical to luksKillSlot - DEPRECATED - see man page") }, { "reload", action_create, 1, 2, 1, 1, 1, N_(" "), N_("modify active device - DEPRECATED - see man page") }, { NULL, NULL, 0, 0, 0, 0, 0, NULL, NULL } @@ -129,6 +134,16 @@ static struct interface_callbacks cmd_icb = { .log = cmdLineLog, }; +static void _log(int class, const char *msg, void *usrptr) +{ + cmdLineLog(class, (char *)msg); +} + +static int _yesDialog(const char *msg, void *usrptr) +{ + return yesDialog((char*)msg); +} + /* End ICBs */ static void show_status(int errcode) @@ -377,6 +392,7 @@ static int action_luksOpen(int arg) options.flags |= CRYPT_FLAG_READONLY; if (opt_non_exclusive) log_err(_("Obsolete option --non-exclusive is ignored.\n")); + return crypt_luksOpen(&options); } @@ -529,6 +545,49 @@ out: return r; } +static int action_luksBackup(int arg) +{ + struct crypt_device *cd = NULL; + int r; + + if (!opt_header_backup_file) { + log_err(_("Option --header-bakup-file is required.\n")); + return -EINVAL; + } + + if ((r = crypt_init(&cd, action_argv[0]))) + goto out; + + crypt_set_log_callback(cd, _log, NULL); + crypt_set_confirm_callback(cd, _yesDialog, NULL); + + r = crypt_header_backup(cd, CRYPT_LUKS1, opt_header_backup_file); +out: + crypt_free(cd); + return r; +} + +static int action_luksRestore(int arg) +{ + struct crypt_device *cd = NULL; + int r = 0; + + if (!opt_header_backup_file) { + log_err(_("Option --header-bakup-file is required.\n")); + return -EINVAL; + } + + if ((r = crypt_init(&cd, action_argv[0]))) + goto out; + + crypt_set_log_callback(cd, _log, NULL); + crypt_set_confirm_callback(cd, _yesDialog, NULL); + r = crypt_header_restore(cd, CRYPT_LUKS1, opt_header_backup_file); +out: + crypt_free(cd); + return r; +} + static void usage(poptContext popt_context, int exitcode, const char *error, const char *more) { @@ -638,6 +697,7 @@ int main(int argc, char **argv) { "tries", 'T', POPT_ARG_INT, &opt_tries, 0, N_("How often the input of the passphrase canbe retried"), NULL }, { "align-payload", '\0', POPT_ARG_INT, &opt_align_payload, 0, N_("Align payload at sector boundaries - for luksFormat"), N_("SECTORS") }, { "non-exclusive", '\0', POPT_ARG_NONE, &opt_non_exclusive, 0, N_("Allows non-exclusive access for luksOpen, WARNING see manpage."), NULL }, + { "header-backup-file",'\0', POPT_ARG_STRING, &opt_header_backup_file,0, N_("File with LUKS header and keyslots backup."), NULL }, POPT_TABLEEND }; poptContext popt_context;