From c833cfcf36ab2b2be8e7c3e97072808add9e0e0b Mon Sep 17 00:00:00 2001 From: Alex Hornung Date: Sun, 16 Mar 2014 07:37:01 +0000 Subject: [PATCH] update tcplay to 2.0 --- lib/libtcplay/Makefile | 31 +- lib/libtcplay/crypto.c | 9 +- lib/libtcplay/hdr.c | 130 +- lib/libtcplay/io.c | 141 +- lib/libtcplay/safe_mem.c | 18 + lib/libtcplay/tcplay.3 | 615 ++++++-- lib/libtcplay/tcplay.c | 1324 ++++++++++++++--- lib/libtcplay/tcplay.h | 215 ++- lib/libtcplay/tcplay.map | 15 +- lib/libtcplay/tcplay_api.c | 769 +++++++++- lib/libtcplay/tcplay_api.h | 73 +- .../{tcplay_api.h => tcplay_api_internal.h} | 50 +- lib/libtcplay/tcplay_api_test.c | 58 - sbin/tcplay/Makefile | 12 +- sbin/tcplay/main.c | 301 +++- sbin/tcplay/tcplay.8 | 308 +++- 16 files changed, 3314 insertions(+), 755 deletions(-) copy lib/libtcplay/{tcplay_api.h => tcplay_api_internal.h} (61%) delete mode 100644 lib/libtcplay/tcplay_api_test.c diff --git a/lib/libtcplay/Makefile b/lib/libtcplay/Makefile index 67240b3836..1a6bf64ad5 100644 --- a/lib/libtcplay/Makefile +++ b/lib/libtcplay/Makefile @@ -1,22 +1,31 @@ LIB= tcplay -SHLIB_MAJOR?= 0 +MAJ_VER= 2 +MIN_VER= 0 +SHLIB_MAJOR?= ${MAJ_VER} +SHLIB_MINOR?= ${MIN_VER} WARNS?= 6 MAN= tcplay.3 MLINKS+= tcplay.3 tc_api_init.3 MLINKS+= tcplay.3 tc_api_uninit.3 -MLINKS+= tcplay.3 tc_api_create_volume.3 -MLINKS+= tcplay.3 tc_api_map_volume.3 -MLINKS+= tcplay.3 tc_api_unmap_volume.3 -MLINKS+= tcplay.3 tc_api_check_cipher.3 -MLINKS+= tcplay.3 tc_api_check_prf_hash.3 -MLINKS+= tcplay.3 tc_api_get_error_msg.3 -MLINKS+= tcplay.3 tc_api_get_summary.3 +MLINKS+= tcplay.3 tc_api_has.3 +MLINKS+= tcplay.3 tc_api_cipher_iterate.3 +MLINKS+= tcplay.3 tc_api_prf_iterate.3 +MLINKS+= tcplay.3 tc_api_task_init.3 +MLINKS+= tcplay.3 tc_api_task_uninit.3 +MLINKS+= tcplay.3 tc_api_task_set.3 +MLINKS+= tcplay.3 tc_api_task_do.3 +MLINKS+= tcplay.3 tc_api_task_info_get.3 +MLINKS+= tcplay.3 tc_api_task_get_error.3 +CFLAGS+= -DMAJ_VER=${MAJ_VER} -DMIN_VER=${MIN_VER} +CFLAGS+= -D_FILE_OFFSET_BITS=64 + +SRCS+= tcplay.c crc32.c safe_mem.c io.c hdr.c humanize.c +SRCS+= crypto.c generic_xts.c +SRCS+= crypto-dev.c pbkdf2-openssl.c +SRCS+= tcplay_api.c -SRCS+= tcplay_api.c tcplay.c crc32.c safe_mem.c \ - crypto.c generic_xts.c humanize.c pbkdf2-openssl.c \ - io.c crypto-dev.c hdr.c INCS+= tcplay_api.h LDFLAGS+= -Wl,-version-script=${.CURDIR}/tcplay.map diff --git a/lib/libtcplay/crypto.c b/lib/libtcplay/crypto.c index 1f6915bd68..8d479338eb 100644 --- a/lib/libtcplay/crypto.c +++ b/lib/libtcplay/crypto.c @@ -50,12 +50,7 @@ tc_cipher_chain_populate_keys(struct tc_cipher_chain *cipher_chain, * We need to determine the total key bytes as the key locations * depend on it. */ - total_key_bytes = 0; - for (dummy_chain = cipher_chain; - dummy_chain != NULL; - dummy_chain = dummy_chain->next) { - total_key_bytes += dummy_chain->cipher->klen; - } + total_key_bytes = tc_cipher_chain_klen(cipher_chain); /* * Now we need to get prepare the keys, as the keys are in @@ -214,7 +209,7 @@ apply_keyfiles(unsigned char *pass, size_t pass_memsz, const char *keyfiles[], uint32_t crc; if (pass_memsz < MAX_PASSSZ) { - tc_log(1, "Not enough memory for password manipulation\n"); + tc_log(1, "Not enough memory for password manipluation\n"); return ENOMEM; } diff --git a/lib/libtcplay/hdr.c b/lib/libtcplay/hdr.c index d420dda185..ee32c04938 100644 --- a/lib/libtcplay/hdr.c +++ b/lib/libtcplay/hdr.c @@ -100,7 +100,7 @@ verify_hdr(struct tchdr_dec *hdr) return 0; } - crc = crc32(&hdr->keys, 256); + crc = crc32((void *)&hdr->keys, 256); if (crc != hdr->crc_keys) { #ifdef DEBUG fprintf(stderr, "CRC32 mismatch (crc_keys)\n"); @@ -127,8 +127,8 @@ verify_hdr(struct tchdr_dec *hdr) struct tchdr_enc * create_hdr(unsigned char *pass, int passlen, struct pbkdf_prf_algo *prf_algo, struct tc_cipher_chain *cipher_chain, size_t sec_sz, - size_t total_blocks __unused, - off_t offset, size_t blocks, int hidden, struct tchdr_enc **backup_hdr) + disksz_t total_blocks __unused, + off_t offset, disksz_t blocks, int hidden, int weak, struct tchdr_enc **backup_hdr) { struct tchdr_enc *ehdr, *ehdr_backup; struct tchdr_dec *dhdr; @@ -169,12 +169,12 @@ create_hdr(unsigned char *pass, int passlen, struct pbkdf_prf_algo *prf_algo, goto error; } - if ((error = get_random(ehdr->salt, sizeof(ehdr->salt))) != 0) { + if ((error = get_random(ehdr->salt, sizeof(ehdr->salt), weak)) != 0) { tc_log(1, "could not get salt\n"); goto error; } - if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt))) + if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt), weak)) != 0) { tc_log(1, "could not get salt for backup header\n"); goto error; @@ -198,7 +198,7 @@ create_hdr(unsigned char *pass, int passlen, struct pbkdf_prf_algo *prf_algo, memset(dhdr, 0, sizeof(*dhdr)); - if ((error = get_random(dhdr->keys, sizeof(dhdr->keys))) != 0) { + if ((error = get_random(dhdr->keys, sizeof(dhdr->keys), weak)) != 0) { tc_log(1, "could not get key random bits\n"); goto error; } @@ -271,3 +271,121 @@ error: return NULL; } + +struct tchdr_enc *copy_reencrypt_hdr(unsigned char *pass, int passlen, + struct pbkdf_prf_algo *prf_algo, int weak, struct tcplay_info *info, + struct tchdr_enc **backup_hdr) +{ + struct tchdr_enc *ehdr, *ehdr_backup; + unsigned char *key, *key_backup; + unsigned char iv[128]; + int error; + + key = key_backup = NULL; + ehdr = ehdr_backup = NULL; + + /* By default stick to current PRF algo */ + if (prf_algo == NULL) + prf_algo = info->pbkdf_prf; + + if ((ehdr = (struct tchdr_enc *)alloc_safe_mem(sizeof(*ehdr))) == NULL) { + tc_log(1, "could not allocate safe ehdr memory\n"); + goto error; + } + + if ((ehdr_backup = (struct tchdr_enc *)alloc_safe_mem + (sizeof(*ehdr_backup))) == NULL) { + tc_log(1, "could not allocate safe ehdr_backup memory\n"); + goto error; + } + + if ((key = alloc_safe_mem(MAX_KEYSZ)) == NULL) { + tc_log(1, "could not allocate safe key memory\n"); + goto error; + } + + if ((key_backup = alloc_safe_mem(MAX_KEYSZ)) == NULL) { + tc_log(1, "could not allocate safe backup key memory\n"); + goto error; + } + + if ((error = get_random(ehdr->salt, sizeof(ehdr->salt), weak)) != 0) { + tc_log(1, "could not get salt\n"); + goto error; + } + + if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt), weak)) + != 0) { + tc_log(1, "could not get salt for backup header\n"); + goto error; + } + + error = pbkdf2(prf_algo, (char *)pass, passlen, + ehdr->salt, sizeof(ehdr->salt), + MAX_KEYSZ, key); + if (error) { + tc_log(1, "could not derive key\n"); + goto error; + } + + error = pbkdf2(prf_algo, (char *)pass, passlen, + ehdr_backup->salt, sizeof(ehdr_backup->salt), + MAX_KEYSZ, key_backup); + if (error) { + tc_log(1, "could not derive backup key\n"); + goto error; + } + + HOST_TO_BE(16, info->hdr->tc_ver); + HOST_TO_LE(16, info->hdr->tc_min_ver); + HOST_TO_BE(32, info->hdr->crc_keys); + HOST_TO_BE(64, info->hdr->vol_ctime); + HOST_TO_BE(64, info->hdr->hdr_ctime); + HOST_TO_BE(64, info->hdr->sz_vol); + HOST_TO_BE(64, info->hdr->sz_hidvol); + HOST_TO_BE(64, info->hdr->off_mk_scope); + HOST_TO_BE(64, info->hdr->sz_mk_scope); + HOST_TO_BE(32, info->hdr->sec_sz); + HOST_TO_BE(32, info->hdr->flags); + HOST_TO_BE(32, info->hdr->crc_dhdr); + + memset(iv, 0, sizeof(iv)); + error = tc_encrypt(info->cipher_chain, key, iv, + (unsigned char *)info->hdr, sizeof(struct tchdr_dec), ehdr->enc); + if (error) { + tc_log(1, "Header encryption failed\n"); + goto error; + } + + memset(iv, 0, sizeof(iv)); + error = tc_encrypt(info->cipher_chain, key_backup, iv, + (unsigned char *)info->hdr, + sizeof(struct tchdr_dec), ehdr_backup->enc); + if (error) { + tc_log(1, "Backup header encryption failed\n"); + goto error; + } + + free_safe_mem(key); + free_safe_mem(key_backup); + + if (backup_hdr != NULL) + *backup_hdr = ehdr_backup; + else + free_safe_mem(ehdr_backup); + + return ehdr; + /* NOT REACHED */ + +error: + if (key) + free_safe_mem(key); + if (key_backup) + free_safe_mem(key_backup); + if (ehdr) + free_safe_mem(ehdr); + if (ehdr_backup) + free_safe_mem(ehdr_backup); + + return NULL; +} diff --git a/lib/libtcplay/io.c b/lib/libtcplay/io.c index a19553f6f9..a862422b01 100644 --- a/lib/libtcplay/io.c +++ b/lib/libtcplay/io.c @@ -34,6 +34,7 @@ #include #include #endif +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include "tcplay.h" @@ -63,7 +65,7 @@ read_to_safe_mem(const char *file, off_t offset, size_t *sz) goto out; } - if ((lseek(fd, offset, SEEK_SET) < 0)) { + if ((lseek(fd, offset, (offset >= 0) ? SEEK_SET : SEEK_END) < 0)) { tc_log(1, "Error seeking on file %s\n", file); goto m_err; } @@ -88,19 +90,26 @@ m_err: static size_t get_random_total_bytes = 0; static size_t get_random_read_bytes = 0; + +float +get_random_read_progress(void) +{ + return (get_random_total_bytes == 0) ? 0.0 : + (1.0 * get_random_read_bytes) / + (1.0 * get_random_total_bytes) * 100.0; +} + static void get_random_summary(void) { - float pct_done; + float pct_done = get_random_read_progress(); - pct_done = (1.0 * get_random_read_bytes) / - (1.0 * get_random_total_bytes) * 100.0; tc_log(0, "Gathering true randomness, %.0f%% done.\n", pct_done); } int -get_random(unsigned char *buf, size_t len) +get_random(unsigned char *buf, size_t len, int weak) { int fd; ssize_t r; @@ -109,13 +118,14 @@ get_random(unsigned char *buf, size_t len) struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; /* 10 ms */ - if ((fd = open("/dev/random", O_RDONLY)) < 0) { + if ((fd = open((weak) ? "/dev/urandom" : "/dev/random", O_RDONLY)) < 0) { tc_log(1, "Error opening /dev/random\n"); return -1; } summary_fn = get_random_summary; get_random_total_bytes = len; + tc_internal_state = STATE_GET_RANDOM; /* Get random data in 16-byte chunks */ sz = 16; @@ -130,6 +140,7 @@ get_random(unsigned char *buf, size_t len) fd, strerror(errno)); close(fd); summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return -1; } rd += r; @@ -139,25 +150,31 @@ get_random(unsigned char *buf, size_t len) close(fd); summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return 0; } -static size_t secure_erase_total_bytes = 0; -static size_t secure_erase_erased_bytes = 0; +static disksz_t secure_erase_total_bytes = 0; +static disksz_t secure_erase_erased_bytes = 0; + +float +get_secure_erase_progress(void) +{ + return (secure_erase_total_bytes == 0) ? 0.0 : + (1.0 * secure_erase_erased_bytes) / + (1.0 * secure_erase_total_bytes) * 100.0; +} static void secure_erase_summary(void) { - float pct_done; - - pct_done = (1.0 * secure_erase_erased_bytes) / - (1.0 * secure_erase_total_bytes) * 100.0; + float pct_done = get_secure_erase_progress(); tc_log(0, "Securely erasing, %.0f%% done.\n", pct_done); } int -secure_erase(const char *dev, size_t bytes, size_t blksz) +secure_erase(const char *dev, disksz_t bytes, size_t blksz) { size_t erased = 0; int fd_rand, fd; @@ -184,6 +201,8 @@ secure_erase(const char *dev, size_t bytes, size_t blksz) summary_fn = secure_erase_summary; secure_erase_total_bytes = bytes; + tc_internal_state = STATE_ERASE; + sz = ERASE_BUFFER_SIZE; while (erased < bytes) { secure_erase_erased_bytes = erased; @@ -196,6 +215,7 @@ secure_erase(const char *dev, size_t bytes, size_t blksz) close(fd); close(fd_rand); summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return -1; } @@ -207,6 +227,7 @@ secure_erase(const char *dev, size_t bytes, size_t blksz) close(fd); close(fd_rand); summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return -1; } @@ -218,6 +239,7 @@ secure_erase(const char *dev, size_t bytes, size_t blksz) summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return 0; } @@ -248,7 +270,7 @@ get_disk_info(const char *dev, size_t *blocks, size_t *bsize) } #elif defined(__linux__) int -get_disk_info(const char *dev, size_t *blocks, size_t *bsize) +get_disk_info(const char *dev, disksz_t *blocks, size_t *bsize) { uint64_t nbytes; int blocksz; @@ -269,7 +291,7 @@ get_disk_info(const char *dev, size_t *blocks, size_t *bsize) return -1; } - *blocks = (size_t)(nbytes / blocksz); + *blocks = (disksz_t)(nbytes / blocksz); *bsize = (size_t)(blocksz); close(fd); @@ -317,7 +339,7 @@ write_to_disk(const char *dev, off_t offset, size_t blksz, void *mem, return -1; } - if ((lseek(fd, offset, SEEK_SET) < 0)) { + if ((lseek(fd, offset, (offset >= 0) ? SEEK_SET : SEEK_END) < 0)) { tc_log(1, "Error seeking on device %s\n", dev); close(fd); return -1; @@ -336,29 +358,73 @@ write_to_disk(const char *dev, off_t offset, size_t blksz, void *mem, return 0; } + +int +write_to_file(const char *file, void *mem, size_t bytes) +{ + int fd; + ssize_t w; + + if ((fd = open(file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) < 0) { + tc_log(1, "Error opening file %s\n", file); + return -1; + } + + if ((w = write(fd, mem, bytes)) < 0) { + tc_log(1, "Error writing to file %s\n", file); + close(fd); + return -1; + } + + close(fd); + return 0; +} + + +static struct termios termios_old; +static int tty_fd; + +static void sigint_termios(int sa) +{ + tcsetattr(tty_fd, TCSAFLUSH, &termios_old); + exit(sa); +} + int -read_passphrase(const char *prompt, char *pass, size_t passlen, time_t timeout) +read_passphrase(const char *prompt, char *pass, size_t passlen, size_t bufsz, time_t timeout) { - struct termios termios_old, termios_new; + struct termios termios_new; struct timeval to; fd_set fds; ssize_t n; - int fd, r = 0, cfd = 0, nready; + int fd = STDIN_FILENO, r = 0, nready; + struct sigaction act, old_act; + int is_tty = isatty(fd); - if ((fd = open("/dev/tty", O_RDONLY)) == -1) { - fd = STDIN_FILENO; - cfd = 1; - } + if (is_tty == 0) + errno = 0; + + memset(pass, 0, bufsz); printf("%s", prompt); fflush(stdout); - memset(pass, 0, passlen); + /* If input is being provided by something which is not a terminal, don't + * change the settings. */ + if (is_tty) { + tcgetattr(fd, &termios_old); + memcpy(&termios_new, &termios_old, sizeof(termios_new)); + termios_new.c_lflag &= ~ECHO; + + act.sa_handler = sigint_termios; + act.sa_flags = SA_RESETHAND; + sigemptyset(&act.sa_mask); - tcgetattr(fd, &termios_old); - memcpy(&termios_new, &termios_old, sizeof(termios_new)); - termios_new.c_lflag &= ~ECHO; - tcsetattr(fd, TCSAFLUSH, &termios_new); + tty_fd = fd; + sigaction(SIGINT, &act, &old_act); + + tcsetattr(fd, TCSAFLUSH, &termios_new); + } if (timeout > 0) { memset(&to, 0, sizeof(to)); @@ -373,19 +439,28 @@ read_passphrase(const char *prompt, char *pass, size_t passlen, time_t timeout) } } - n = read(fd, pass, passlen-1); + n = read(fd, pass, bufsz-1); if (n > 0) { pass[n-1] = '\0'; /* Strip trailing \n */ } else { r = EIO; } + /* Warn about passphrase trimming */ + if (strlen(pass) > MAX_PASSSZ) + tc_log(0, "WARNING: Passphrase is being trimmed to %zu " + "characters, discarding rest.\n", passlen); + + /* Cut off after passlen characters */ + pass[passlen] = '\0'; + out: - if (cfd) - close(fd); + if (is_tty) { + tcsetattr(fd, TCSAFLUSH, &termios_old); + putchar('\n'); - tcsetattr(fd, TCSAFLUSH, &termios_old); - putchar('\n'); + sigaction(SIGINT, &old_act, NULL); + } return r; } diff --git a/lib/libtcplay/safe_mem.c b/lib/libtcplay/safe_mem.c index 0dad2027e1..73ba370f6f 100644 --- a/lib/libtcplay/safe_mem.c +++ b/lib/libtcplay/safe_mem.c @@ -145,6 +145,24 @@ _free_safe_mem(void *mem_ptr, const char *file, int line) free(mem); } +void * +_strdup_safe_mem(const char *in, const char *file, int line) +{ + char *out; + size_t sz; + + sz = strlen(in)+1; + + if ((out = _alloc_safe_mem(sz, file, line)) == NULL) { + return NULL; + } + + memcpy(out, in, sz); + out[sz-1] = '\0'; + + return out; +} + void check_and_purge_safe_mem(void) { diff --git a/lib/libtcplay/tcplay.3 b/lib/libtcplay/tcplay.3 index dcec7f2b01..6be4481db5 100644 --- a/lib/libtcplay/tcplay.3 +++ b/lib/libtcplay/tcplay.3 @@ -28,91 +28,59 @@ .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 5, 2011 +.Dd January 20, 2014 .Dt TCPLAY 3 .Os .Sh NAME .Nm tc_api_init , .Nm tc_api_uninit , -.Nm tc_api_create_volume , -.Nm tc_api_map_volume , -.Nm tc_api_unmap_volume , -.Nm tc_api_check_cipher , -.Nm tc_api_check_prf_hash , -.Nm tc_api_get_error_msg , -.Nm tc_api_get_summary +.Nm tc_api_has , +.Nm tc_api_cipher_iterate , +.Nm tc_api_prf_iterate , +.Nm tc_api_task_init , +.Nm tc_api_task_uninit , +.Nm tc_api_task_set , +.Nm tc_api_task_do , +.Nm tc_api_task_info_get , +.Nm tc_api_task_get_error .Nd TrueCrypt volume management .Sh LIBRARY .Lb libtcplay .Sh SYNOPSIS -.In sys/types.h .In tcplay_api.h +.Ft typedef int +.Fn (*tc_api_cipher_iterator_fn) "void *priv" "const char *name" "int key_length_in_bits" "int ciphers_in_chain" +.Ft typedef int +.Fn (*tc_api_prf_iterator_fn) "void *priv" "const char *name" +.Ft typedef int +.Fn (*tc_api_state_change_fn) "void *priv" "const char *state" "int enter_state" .Ft int .Fn tc_api_init "int verbose" .Ft int .Fn tc_api_uninit "void" .Ft int -.Fn tc_api_create_volume "tc_api_opts *api_opts" +.Fn tc_api_has "const char *feature" .Ft int -.Fn tc_api_map_volume "tc_api_opts *api_opts" +.Fn tc_api_cipher_iterate "tc_api_cipher_iterator_fn fn" "void *priv" .Ft int -.Fn tc_api_unmap_volume "tc_api_opts *api_opts" +.Fn tc_api_prf_iterate "tc_api_prf_iterator_fn fn" "void *priv" +.Ft tc_api_task +.Fn tc_api_task_init "const char *op" .Ft int -.Fn tc_api_check_cipher "tc_api_opts *api_opts" +.Fn tc_api_task_uninit "tc_api_task task" .Ft int -.Fn tc_api_check_prf_hash "tc_api_opts *api_opts" -.Ft const char * -.Fn tc_api_get_error_msg "void" +.Fn tc_api_task_set "tc_api_task task" "const char *key" "..." +.Ft int +.Fn tc_api_task_do "tc_api_task task" +.Ft int +.Fn tc_api_task_info_get "tc_api_task task" "const char *key" "..." .Ft const char * -.Fn tc_api_get_summary "void" +.Fn tc_api_task_get_error "tc_api_task task" .Sh DESCRIPTION The .Nm tcplay -library provides an interface to create, query and map -TrueCrypt-compatible -volumes. -.Pp -The -.Fn tc_api_create_volume , -.Fn tc_api_map_volume , -.Fn tc_api_unmap_volume , -.Fn tc_api_check_cipher -and -.Fn tc_api_check_prf_hash -functions take a -.Vt tc_api_opts -data structure as only argument, which is defined as follows: -.Bd -literal -typedef struct tc_api_opts { - /* Common fields */ - char *tc_device; - char *tc_passphrase; - const char **tc_keyfiles; - - /* Fields for mapping */ - char *tc_map_name; - int tc_password_retries; - int tc_interactive_prompt; - unsigned long tc_prompt_timeout; - - /* Fields for creation */ - char *tc_cipher; - char *tc_prf_hash; - char *tc_cipher_hidden; - char *tc_prf_hash_hidden; - size_t tc_size_hidden_in_bytes; - char *tc_passphrase_hidden; - const char **tc_keyfiles_hidden; -} tc_api_opts; -.Ed -.Pp -where the keyfile fields, -.Fa tc_keyfiles -and -.Fa tc_keyfiles_hidden , -are -.Dv NULL -terminated arrays of key file strings. +library provides an interface to create, query, map and manage +TrueCrypt-compatible volumes. .Pp The .Fn tc_api_init @@ -130,124 +98,458 @@ function clears up all internal secure memory, such as memory used for decrypted headers, passphrases, keyfiles, etc. .Pp The -.Fn tc_api_create_volume -function creates a new volume using the parameters specified in the -.Fa api_opts +.Fn tc_api_has +function checks whether the loaded tcplay library has the feature +specified by the +.Fa feature argument. -The new volume will be created on the device specified by -.Fa tc_device -using the cipher specified by -.Fa tc_cipher -and the pbkdf2 prf hash algorithm specified by -.Fa tc_prf_hash -and using the passphrase and keyfiles specified by -.Fa tc_passphrase -and -.Fa tc_keyfiles -respectively. -If -.Fa tc_size_hidden_in_bytes -is not zero, a hidden volume of the given size will be created, using -the cipher specified by -.Fa tc_cipher_hidden -and the pbkdf2 prf hash algorithm specified by -.Fa tc_prf_hash_hidden . -If -.Fa tc_cipher_hidden -or -.Fa tc_prf_hash_hidden -are -.Dv NULL , -the same algorithm as for the outer volume will be used. -The passphrase and keyfiles used are specified by -.Fa tc_passphrase_hidden +The current version of the +.Nm tcplay +library supports the following features: +.Bl -column "some_long_feature" "Description" +.It Sy Feature Ta Sy Description +.It Li trim Ta Allows enabling discards/TRIM when mapping a volume +.El +.Pp +The +.Fn tc_api_cipher_iterate +function passes every available cipher chain to the callback provided in the +.Fa fn +argument. +The +.Fa priv +argument is passed on every call of the callback function. +The name of the cipher chain is passed to the callback function in the +.Fa name +argument. +Similarly, +the +.Fa ciphers_in_chain +argument holds the number of ciphers in the current chain, and -.Fa tc_keyfiles_hidden -respectively. +.Fa key_length_in_bits +holds the total key length for the cipher chain, +in bits. .Pp The -.Fn tc_api_map_volume -function maps a volume using the parameters specified in the -.Fa api_opts +.Fn tc_api_prf_iterate +function passes every available cipher chain to the callback provided in the +.Fa fn argument. -The volume, which will be mapped as -.Fa tc_map_name , -is specified in -.Fa tc_device . The -.Fa tc_interactive_prompt -field determines whether the user will be prompted to enter a passphrase -interactively or whether the passphrase in -.Fa tc_passphrase -will be used. -If an interactive prompt is used, the prompt will time out after -.Fa tc_prompt_timeout -seconds. -A value of 0 indicates that no timeout will occur. -The number of passphrase entry retries is defined by -.Fa tc_password_retries . -Note that the -.Fn tc_api_map_volume -function does not support accessing an outer volume while -protecting the hidden volume. -Depending on the passphrase/keyfiles used -either the outer or the hidden volume will be mapped. +.Fa priv +argument is passed on every call of the callback function. +The name of the PKBDF2 PRF algorithm is passed to the callback function in the +.Fa name +argument. .Pp The -.Fn tc_api_unmap_volume -unmaps / closes the volume specified in -.Fa tc_map_name . +.Fn tc_api_task_init +function initializes and returns a +.Ft tc_api_task +opaque pointer that can be used to run +.Nm tcplay +commands. +Each task can be used only for a single +.Fn tc_api_task_do +call, +and must be deallocated using +.Fn tc_api_task_uninit . +The +.Fa op +argument can be one of the following: +.Bl -tag -width indent +.It Sy create +Create a new encrypted TrueCrypt volume. +.It Sy map +Map an existing TrueCrypt volume. +.It Sy info +Request information about an encrypted TrueCrypt volume. +.It Sy info_mapped +Request information about a mapped TrueCrypt volume. +.It Sy unmap +Unmap a mapped TrueCrypt volume. +.It Sy modify +Modify the TrueCrypt volume by changing the passphrase, keyfiles, PRF algorithm, +restoring from a backup header, restoring from a header file or saving to a header file. +.It Sy restore +Modify the TrueCrypt volume as +.Sy modify +does, but without changing the passphrase, keyfiles or PRF algorithm. +.El .Pp The -.Fn tc_api_check_cipher -function checks whether the cipher specified in the -.Fa api_opts -argument field -.Fa tc_cipher -is valid. +.Fn tc_api_task_set +function allows settting a number of different options for the current task. +The following table shows which keys are available on calls to +.Fn tc_api_task_set +for each of the operations. +The letter +.Sy M +indicates the setting is mandatory, +whilst +.Sy * +indicates the setting is optional. +.Bl -column "hidden_header_from_filexxx" "createxxx" "infoxxx" "mapxxx" "unmapxxx" "info_mappedxxx" "modifyxxx" "restorexxx" +.It Sy Key Ta Sy create Ta Sy info Ta Sy map Ta Sy unmap Ta Sy info_mapped Ta Sy modify Ta Sy restore +.It Li dev Ta "M" Ta "M" Ta "M" Ta "*" Ta Ta "M" Ta "M" +.It Li map_name Ta Ta Ta "M" Ta "M" Ta "M" Ta Ta "" +.It Li interactive Ta "*" Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li retries Ta "*" Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li timeout Ta "*" Ta "*" Ta "*" Ta Ta Ta Ta "" +.It Li state_change_fn Ta "*" Ta Ta Ta Ta Ta "*" Ta "*" +.It Li weak_keys_and_salt Ta "*" Ta Ta Ta Ta Ta "*" Ta "*" +.It Li secure_erase Ta "*" Ta Ta Ta Ta Ta Ta "" +.It Li hidden_size_bytes Ta "*" Ta Ta Ta Ta Ta Ta "" +.It Li prf_algo Ta "*" Ta Ta Ta Ta Ta Ta "" +.It Li h_prf_algo Ta "*" Ta Ta Ta Ta Ta Ta "" +.It Li cipher_chain Ta "*" Ta Ta Ta Ta Ta Ta "" +.It Li h_cipher_chain Ta "*" Ta Ta Ta Ta Ta Ta "" +.It Li protect_hidden Ta Ta "*" Ta "*" Ta Ta Ta Ta "" +.It Li fde Ta Ta "*" Ta "*" Ta Ta Ta Ta "" +.It Li sys Ta Ta "*" Ta "*" Ta Ta Ta "?" Ta "?" +.It Li use_backup_header Ta Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li header_from_file Ta Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li hidden_header_from_file Ta Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li allow_trim Ta Ta Ta "*" Ta Ta Ta Ta "" +.It Li save_header_to_file Ta Ta Ta Ta Ta Ta "*" Ta "" +.It Li passphrase Ta "*" Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li h_passphrase Ta "*" Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li keyfiles Ta "*" Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li h_keyfiles Ta "*" Ta "*" Ta "*" Ta Ta Ta "*" Ta "*" +.It Li new_passphrase Ta Ta Ta Ta Ta Ta "*" Ta "" +.It Li new_keyfiles Ta Ta Ta Ta Ta Ta "*" Ta "" +.It Li new_prf_algo Ta Ta Ta Ta Ta Ta "*" Ta "" +.El +The varargs accepted by the +.Fn tc_api_task_set +function depend on the key being set. +.Bl -tag -width indent +.It dev +The vararg is of type +.Ft const char * . +It sets the device that contains the TrueCrypt volume to operate on. +.It map_name +The vararg is of type +.Ft const char * . +It set the name of the mapped volume. +.It interactive +The vararg is of type +.Ft int . +It determines whether the user will be prompted for a passphrase or whether +the settings are taken from the arguments set using +.Fn tc_api_task_set . +.It retries +The vararg is of type +.Ft int . +It determines the number of passphrase retries if +.Fa interactive +is set. +.It weak_keys_and_salt +The vararg is of type +.Ft int . +It determines whether to use a weak source of entropy for key material and/or +the salt. +.It secure_erase +The vararg is of type +.Ft int . +It determines whether a secure erase is performed as part of the volume creation. +.It hidden_size_bytes +The vararg is of type +.Ft int64_t . +If set to +.Fa 0 +it implies no hidden volume will be created. +A positive value implies a hidden volume of the specified size in bytes is created. +.It prf_algo +The vararg is of type +.Ft const char * +and must be a valid PBKDF2 PRF algorithm. +It determines which PBKDF2 PRF algorithm is used for the outer volume. +.It h_prf_algo +The vararg is of type +.Ft const char * +and must be a valid PBKDF2 PRF algorithm. +It determines which PBKDF2 PRF algorithm is used for the hidden volume. +.It cipher_chain +The vararg is of type +.Ft const char * +and must be a valid +.Nm tcplay +cipher chain. +It determines which cipher chain is used to encrypt the outer volume. +.It h_cipher_chain +The vararg is of type +.Ft const char * +and must be a valid +.Nm tcplay +cipher chain. +It determines which cipher chain is used to encrypt the hidden volume. +.It protect_hidden +The vararg is of type +.Ft int . +It determines whether the size of the outer volume should be adjusted to protect +any hidden volume. +Using this mode requires both outer and hidden passphrases and keyfiles. +.It sys +The vararg is of type +.Ft const char * . +It determines whether the volume is a system encrypted volume, +and if so, which disk is the system disk and hence contains the header. +If set to +.Dv NULL +the volume will implicitly be treated as a non-system encrypted volume. +.It fde +The vararg is of type +.Ft int . +It determines whether the disk uses full disk encryption or not. +If specified, the device pointed to by the +.Fa dev +setting should be a whole disk device, not any partition. +The device will be mapped or queried as a whole. +To access individual partitions, a utility such as +.Xr kpartx 8 +should be used, which will create additional individual mappings +for each partition in the decrypted mapped volume. +For more details on full disk encryption, see +.Xr tcplay 8 . +.It use_backup_header +The vararg is of type +.Ft int . +It determines whether the backup header should be used instead of the regular +header to access the volume. +.It header_from_file +The vararg is of type +.Ft const char * . +If not +.Dv NULL +it forces +.Nm tcplay +to use the header in the specified file instead of the regular outer volume header. +.It hidden_header_from_file +The vararg is of type +.Ft const char * . +If not +.Dv NULL +it forces +.Nm tcplay +to use the header in the specified file instead of the regular hidden volume header. +.It allow_trim +The vararg is of type +.Ft int . +It specifies whether the mapped volume should allow discards (TRIM). +.It save_header_to_file +The vararg is of type +.Ft const char * . +If not +.Dv NULL +it forces +.Nm tcplay +to write the (modified) header to the specified file instead of replacing the volume +headers. +.It passphrase +The vararg is of type +.Ft const char * . +It sets the passphrase that +.Nm tcplay +uses to access the volume. +.It h_passphrase +The vararg is of type +.Ft const char * . +It sets the passphrase that +.Nm tcplay +uses to unlock the hidden volume header. +This option is only used if a hidden volume is being created or the +.Fa protect_hidden +setting is set. +Otherwise +.Nm tcplay +will first use the regular passphrase to try to unlock the outer volume and then +try to unlock the hidden volume header with the same passphrase without ever +using +.Fa h_passphrase . +.It keyfiles +The vararg is of type +.Ft const char * . +If not +.Dv NULL +the given keyfile will be added to the keyfile pool. +Multiple calls to set this option with a non- +.Dv NULL +argument result add additional keyfiles. +If +.Dv NULL +all keyfiles are cleared. +.It h_keyfiles +The vararg is of type +.Ft const char * . +If not +.Dv NULL +the given keyfile will be added to the keyfile pool. +Multiple calls to set this option with a non- +.Dv NULL +argument result add additional keyfiles. +If +.Dv NULL +all keyfiles are cleared. +This option is only used if a hidden volume is being created or the +.Fa protect_hidden +setting is set. +Otherwise +.Nm tcplay +will first use the regular keyfiles to try to unlock the outer volume and then +try to unlock the hidden volume header with the same keyfiles without ever +using +.Fa h_keyfiles . +.It new_passphrase +The vararg is of type +.Fa const char * . +It specifies the new passphrase to use when modifying the volume header. +.It new_keyfiles +The vararg is of type +.Fa const char * . +If not +.Dv NULL +the given keyfile will be added to the new keyfile pool. +Multiple calls to set this option with a non- +.Dv NULL +argument result add additional keyfiles. +If +.Dv NULL +all new keyfiles are cleared. +When the volume header is modified, +it will be reencrypted using the new keyfiles. +.It new_prf_algo +The vararg is of type +.Ft const char * +and must be a valid PBKDF2 PRF algorithm. +It determines which PBKDF2 PRF algorithm is used when reencrypting the (modified) +volume header. +.It state_change_fn +The first vararg is of type +.Fa tc_api_state_change_fn +and the second vararg is of type +.Fa void * . +It allows the consumer to provide a callback function which will be called when +starting and stopping a time-intensive sub-operation such as collecting entropy +or erasing a volume. +The second vararg is passed as the +.Fa priv +argument to the callback. +The +.Fa enter_state +argument to the callback determines whether +.Nm tcplay +is starting or stopping the time-intensive sub-task specified in the +.Fa state +argument. +.El .Pp The -.Fn tc_api_check_prf_hash -function checks whether the prf hash algorithm specified in the -.Fa api_opts -argument field -.Fa tc_prf_hash -is valid. +.Fn tc_api_task_do +function runs the task specified in the +.Fa task +argument. +Before running the task, +.Fn tc_api_task_do +performs a simple sanity check of the arguments set previously using +.Fn tc_api_task_set +before performing the actual operation. +After a call to +.Fn tc_api_task_do +for the +.Sy info +or +.Sy info_mapped +operations, +the queried information is available to be accessed using +.Fn tc_api_task_info_get . .Pp The -.Fn tc_api_get_error_msg -function should be called whenver another API function returns -.Dv TC_ERR . -It returns a string containing a description of the error that -occurred. +.Fn tc_api_task_info_get +function can be used to query the result of a +.Sy info +or +.Sy info_mapped +operation. +The +.Fa task +argument is the task used in a previous +.Fn tc_api_task_do +call. +The +.Fa key +argument can be one of the following: +.Bl -column "some_long_feature" "very_long_type" "Description" +.It Sy Key Ta Sy type Ta Sy Description +.It Li device Ta Ft char * Ta Corresponding device node +.It Li cipher Ta Ft char * Ta Used cipher chain +.It Li prf Ta Ft char * Ta Used PBKDF2 PRF algorithm +.It Li key_bits Ta Ft int * Ta Number of key bits +.It Li size Ta Ft int64_t * Ta Volume size in bytes +.It Li iv_offset Ta Ft int64_t * Ta IV Offset of volume in bytes +.It Li block_offset Ta Ft int64_t * Ta Block Offset of volume in bytes +.El +.Pp +The second vararg argument must be of the type specified in the above table. +The first vararg argument is always the size of the storage provided +in the second argument. +For a +.Ft char * +argument, the size corresponds to the size of the buffer at the provided +location and must be of type +.Ft size_t . +For an +.Ft int * +or +.Ft int64_t * +argument, it should be the size of the underlying type. .Pp The -.Fn tc_api_get_summary -function returns a string containing a summary of the current -progress of a certain operation. -Currently only the volume erasing -part of creating a new volume can provide a summary. -When no summary is available, an empty string is returned. -The output otherwise is equivalent to that of a -.Dv SIGINFO -signal when using -.Xr tcplay 8 . +.Fn tc_api_task_get_error +function can be used to get a detailed error message after a failed +.Fa tc_api_task_do +call. +The +.Fa task +argument is the task used in a previous +.Fn tc_api_task_do +call. +.Sh NOTES +TrueCrypt limits passphrases to 64 characters (including the terminating +null character). +To be compatible with it, +.Nm tcplay +does the same. +All passphrases (exlcuding keyfiles) are trimmed to 64 characters. +Similarly, keyfiles are limited to a size of 1 MB, but up to 256 +keyfiles can be used. .Sh RETURN VALUES All functions except -.Fn tc_api_get_error_msg +.Fn tc_api_task_init and -.Fn tc_api_get_summary +.Fn tc_api_task_get_error return either .Dv TC_OK -to signal that the operation completed successfully, or +to indicate that the operation completed successfully, or +.Dv TC_ERR_UNIMPL +to indicate that the operation is not implemented +, or .Dv TC_ERR -to signal that an error occurred. +to indicate that any other error occured. .Pp The .Fn tc_api_get_error_msg -and -.Fn tc_api_get_summary -always return a valid, but possibly empty, string. +function always return a valid, but possibly empty (or irrelevant, +if not called after an error occurred) string. +.Pp +The +.Fn tc_api_task_init +function returns +.Dv NULL +if an error occurred and an opaque +.Ft tc_api_task +otherwise. .Sh COMPATIBILITY The .Nm tcplay @@ -255,7 +557,8 @@ library offers full compatibility with TrueCrypt volumes including hidden volumes, system encryption (map-only), keyfiles and cipher cascading. .Sh SEE ALSO -.Xr tcplay 8 +.Xr tcplay 8 , +.Xr kpartx 8 .Sh HISTORY The .Nm tcplay diff --git a/lib/libtcplay/tcplay.c b/lib/libtcplay/tcplay.c index d83a26523e..cd882cf19e 100644 --- a/lib/libtcplay/tcplay.c +++ b/lib/libtcplay/tcplay.c @@ -27,11 +27,9 @@ * SUCH DAMAGE. */ -#if defined(__linux__) -#define _GNU_SOURCE /* for asprintf */ -#endif - +#define _BSD_SOURCE #include +#include #if defined(__DragonFly__) #include @@ -54,6 +52,8 @@ #include #endif +#include + #include "crc32.h" #include "tcplay.h" #include "humanize.h" @@ -68,6 +68,7 @@ summary_fn_t summary_fn = NULL; int tc_internal_verbose = 1; char tc_internal_log_buffer[LOG_BUFFER_SZ]; +int tc_internal_state = STATE_UNKNOWN; void tc_log(int is_err, const char *fmt, ...) @@ -106,9 +107,9 @@ struct tc_crypto_algo tc_crypto_algos[] = { { "TWOFISH-128-XTS", "twofish-xts-plain", 32, 8 }, { "SERPENT-128-XTS", "serpent-xts-plain", 32, 8 }, #endif - { "AES-256-XTS", "aes-xts-plain", 64, 8 }, - { "TWOFISH-256-XTS", "twofish-xts-plain", 64, 8 }, - { "SERPENT-256-XTS", "serpent-xts-plain", 64, 8 }, + { "AES-256-XTS", "aes-xts-plain64", 64, 8 }, + { "TWOFISH-256-XTS", "twofish-xts-plain64", 64, 8 }, + { "SERPENT-256-XTS", "serpent-xts-plain64", 64, 8 }, { NULL, NULL, 0, 0 } }; @@ -195,6 +196,105 @@ tc_build_cipher_chains(void) return 0; } +static +struct tc_cipher_chain * +tc_dup_cipher_chain(struct tc_cipher_chain *src) +{ + struct tc_cipher_chain *first = NULL, *prev = NULL, *elem; + + for (; src != NULL; src = src->next) { + if ((elem = alloc_safe_mem(sizeof(*elem))) == NULL) { + tc_log(1, "Error allocating memory for " + "duplicate cipher chain\n"); + return NULL; + } + + memcpy(elem, src, sizeof(*elem)); + + if (src->key != NULL) { + if ((elem->key = alloc_safe_mem(src->cipher->klen)) == NULL) { + tc_log(1, "Error allocating memory for " + "duplicate key in cipher chain\n"); + return NULL; + } + + memcpy(elem->key, src->key, src->cipher->klen); + } + + if (first == NULL) + first = elem; + + elem->next = NULL; + elem->prev = prev; + + if (prev != NULL) + prev->next = elem; + + prev = elem; + } + + return first; +} + +static +int +tc_free_cipher_chain(struct tc_cipher_chain *chain) +{ + struct tc_cipher_chain *next = chain; + + while ((chain = next) != NULL) { + next = chain->next; + + if (chain->key != NULL) + free_safe_mem(chain->key); + free_safe_mem(chain); + } + + return 0; +} + +int +tc_cipher_chain_length(struct tc_cipher_chain *chain) +{ + int len = 0; + + for (; chain != NULL; chain = chain->next) + ++len; + + return len; +} + +int +tc_cipher_chain_klen(struct tc_cipher_chain *chain) +{ + int klen_bytes = 0; + + for (; chain != NULL; chain = chain->next) { + klen_bytes += chain->cipher->klen; + } + + return klen_bytes; +} + +char * +tc_cipher_chain_sprint(char *buf, size_t bufsz, struct tc_cipher_chain *chain) +{ + static char sbuf[256]; + int n = 0; + + if (buf == NULL) { + buf = sbuf; + bufsz = sizeof(sbuf); + } + + for (; chain != NULL; chain = chain->next) { + n += snprintf(buf+n, bufsz-n, "%s%s", chain->cipher->name, + (chain->next != NULL) ? "," : "\0"); + } + + return buf; +} + #ifdef DEBUG static void print_hex(unsigned char *buf, off_t start, size_t len) @@ -211,36 +311,45 @@ print_hex(unsigned char *buf, off_t start, size_t len) void print_info(struct tcplay_info *info) { - struct tc_cipher_chain *cipher_chain; - int klen = 0; - - printf("PBKDF2 PRF:\t\t%s\n", info->pbkdf_prf->name); - printf("PBKDF2 iterations:\t%d\n", info->pbkdf_prf->iteration_count); + printf("Device:\t\t\t%s\n", info->dev); - printf("Cipher:\t\t\t"); - for (cipher_chain = info->cipher_chain; - cipher_chain != NULL; - cipher_chain = cipher_chain->next) { - printf("%s%c", cipher_chain->cipher->name, - (cipher_chain->next != NULL) ? ',' : '\n'); - klen += cipher_chain->cipher->klen; + if (info->pbkdf_prf != NULL) { + printf("PBKDF2 PRF:\t\t%s\n", info->pbkdf_prf->name); + printf("PBKDF2 iterations:\t%d\n", + info->pbkdf_prf->iteration_count); } - printf("Key Length:\t\t%d bits\n", klen*8); - printf("CRC Key Data:\t\t%#x\n", info->hdr->crc_keys); - printf("Sector size:\t\t%d\n", info->hdr->sec_sz); - printf("Volume size:\t\t%zu sectors\n", info->size); + printf("Cipher:\t\t\t%s\n", + tc_cipher_chain_sprint(NULL, 0, info->cipher_chain)); + + printf("Key Length:\t\t%d bits\n", + 8*tc_cipher_chain_klen(info->cipher_chain)); + + if (info->hdr != NULL) { + printf("CRC Key Data:\t\t%#x\n", info->hdr->crc_keys); + printf("Sector size:\t\t%d\n", info->hdr->sec_sz); + } else { + printf("Sector size:\t\t512\n"); + } + printf("Volume size:\t\t%"DISKSZ_FMT" sectors\n", info->size); #if 0 /* Don't print this; it's always 0 and is rather confusing */ printf("Volume offset:\t\t%"PRIu64"\n", (uint64_t)info->start); #endif - printf("IV offset:\t\t%"PRIu64"\n", (uint64_t)info->skip); - printf("Block offset:\t\t%"PRIu64"\n", (uint64_t)info->offset); + +#ifdef DEBUG + printf("Vol Flags:\t\t%d\n", info->volflags); +#endif + + printf("IV offset:\t\t%"PRIu64" sectors\n", + (uint64_t)info->skip); + printf("Block offset:\t\t%"PRIu64" sectors\n", + (uint64_t)info->offset); } static struct tcplay_info * -new_info(const char *dev, struct tc_cipher_chain *cipher_chain, +new_info(const char *dev, int flags, struct tc_cipher_chain *cipher_chain, struct pbkdf_prf_algo *prf, struct tchdr_dec *hdr, off_t start) { struct tc_cipher_chain *chain_start; @@ -255,14 +364,22 @@ new_info(const char *dev, struct tc_cipher_chain *cipher_chain, return NULL; } - info->dev = dev; + strncpy(info->dev, dev, sizeof(info->dev)); info->cipher_chain = cipher_chain; info->pbkdf_prf = prf; info->start = start; info->hdr = hdr; + info->blk_sz = hdr->sec_sz; info->size = hdr->sz_mk_scope / hdr->sec_sz; /* volume size */ info->skip = hdr->off_mk_scope / hdr->sec_sz; /* iv skip */ - info->offset = hdr->off_mk_scope / hdr->sec_sz; /* block offset */ + + info->volflags = hdr->flags; + info->flags = flags; + + if (TC_FLAG_SET(flags, SYS)) + info->offset = 0; /* offset is 0 for system volumes */ + else + info->offset = hdr->off_mk_scope / hdr->sec_sz; /* block offset */ /* Associate a key out of the key pool with each cipher in the chain */ error = tc_cipher_chain_populate_keys(cipher_chain, hdr->keys); @@ -282,6 +399,19 @@ new_info(const char *dev, struct tc_cipher_chain *cipher_chain, return info; } +int +free_info(struct tcplay_info *info) +{ + if (info->cipher_chain) + tc_free_cipher_chain(info->cipher_chain); + if (info->hdr) + free_safe_mem(info->hdr); + + free_safe_mem(info); + + return 0; +} + int adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo) { @@ -293,11 +423,12 @@ adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo) } int -process_hdr(const char *dev, unsigned char *pass, int passlen, +process_hdr(const char *dev, int flags, unsigned char *pass, int passlen, struct tchdr_enc *ehdr, struct tcplay_info **pinfo) { struct tchdr_dec *dhdr; struct tcplay_info *info; + struct tc_cipher_chain *cipher_chain = NULL; unsigned char *key; int i, j, found, error; @@ -334,11 +465,12 @@ process_hdr(const char *dev, unsigned char *pass, int passlen, #endif for (j = 0; !found && tc_cipher_chains[j] != NULL; j++) { + cipher_chain = tc_dup_cipher_chain(tc_cipher_chains[j]); #ifdef DEBUG printf("\nTrying cipher chain %d\n", j); #endif - dhdr = decrypt_hdr(ehdr, tc_cipher_chains[j], key); + dhdr = decrypt_hdr(ehdr, cipher_chain, key); if (dhdr == NULL) { tc_log(1, "hdr decryption failed for cipher " "chain %d\n", j); @@ -360,6 +492,7 @@ process_hdr(const char *dev, unsigned char *pass, int passlen, found = 1; } else { free_safe_mem(dhdr); + tc_free_cipher_chain(cipher_chain); } } } @@ -369,8 +502,8 @@ process_hdr(const char *dev, unsigned char *pass, int passlen, if (!found) return EINVAL; - if ((info = new_info(dev, tc_cipher_chains[j-1], &pbkdf_prf_algos[i-1], - dhdr, 0)) == NULL) { + if ((info = new_info(dev, flags, cipher_chain, + &pbkdf_prf_algos[i-1], dhdr, 0)) == NULL) { free_safe_mem(dhdr); return ENOMEM; } @@ -381,16 +514,13 @@ process_hdr(const char *dev, unsigned char *pass, int passlen, } int -create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles, - const char *h_keyfiles[], int n_hkeyfiles, struct pbkdf_prf_algo *prf_algo, - struct tc_cipher_chain *cipher_chain, struct pbkdf_prf_algo *h_prf_algo, - struct tc_cipher_chain *h_cipher_chain, char *passphrase, - char *h_passphrase, size_t size_hidden_bytes_in, int interactive) +create_volume(struct tcplay_opts *opts) { char *pass, *pass_again; char *h_pass = NULL; char buf[1024]; - size_t blocks, blksz, hidden_blocks = 0; + disksz_t blocks, hidden_blocks = 0; + size_t blksz; struct tchdr_enc *ehdr, *hehdr; struct tchdr_enc *ehdr_backup, *hehdr_backup; uint64_t tmp; @@ -401,16 +531,16 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles ehdr_backup = hehdr_backup = NULL; ret = -1; /* Default to returning error */ - if (cipher_chain == NULL) - cipher_chain = tc_cipher_chains[0]; - if (prf_algo == NULL) - prf_algo = &pbkdf_prf_algos[0]; - if (h_cipher_chain == NULL) - h_cipher_chain = cipher_chain; - if (h_prf_algo == NULL) - h_prf_algo = prf_algo; + if (opts->cipher_chain == NULL) + opts->cipher_chain = tc_cipher_chains[0]; + if (opts->prf_algo == NULL) + opts->prf_algo = &pbkdf_prf_algos[0]; + if (opts->h_cipher_chain == NULL) + opts->h_cipher_chain = opts->cipher_chain; + if (opts->h_prf_algo == NULL) + opts->h_prf_algo = opts->prf_algo; - if ((error = get_disk_info(dev, &blocks, &blksz)) != 0) { + if ((error = get_disk_info(opts->dev, &blocks, &blksz)) != 0) { tc_log(1, "could not get disk info\n"); return -1; } @@ -421,16 +551,17 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles return -1; } - if (interactive) { - if (((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) || - ((pass_again = alloc_safe_mem(MAX_PASSSZ)) == NULL)) { + if (opts->interactive) { + if (((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) || + ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) { tc_log(1, "could not allocate safe passphrase memory\n"); goto out; } - if ((error = read_passphrase("Passphrase: ", pass, MAX_PASSSZ, 0) || - (read_passphrase("Repeat passphrase: ", pass_again, - MAX_PASSSZ, 0)))) { + if ((error = read_passphrase("Passphrase: ", pass, MAX_PASSSZ, + PASS_BUFSZ, 0) || + (read_passphrase("Repeat passphrase: ", pass_again, + MAX_PASSSZ, PASS_BUFSZ, 0)))) { tc_log(1, "could not read passphrase\n"); goto out; } @@ -444,38 +575,40 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles pass_again = NULL; } else { /* In batch mode, use provided passphrase */ - if ((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) { + if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { tc_log(1, "could not allocate safe " "passphrase memory"); goto out; } - if (passphrase != NULL) - strcpy(pass, passphrase); + if (opts->passphrase != NULL) { + strncpy(pass, opts->passphrase, MAX_PASSSZ); + pass[MAX_PASSSZ] = '\0'; + } } - if (nkeyfiles > 0) { + if (opts->nkeyfiles > 0) { /* Apply keyfiles to 'pass' */ - if ((error = apply_keyfiles((unsigned char *)pass, MAX_PASSSZ, - keyfiles, nkeyfiles))) { + if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ, + opts->keyfiles, opts->nkeyfiles))) { tc_log(1, "could not apply keyfiles\n"); goto out; } } - if (hidden) { - if (interactive) { - if (((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) || - ((pass_again = alloc_safe_mem(MAX_PASSSZ)) == NULL)) { + if (opts->hidden) { + if (opts->interactive) { + if (((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) || + ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) { tc_log(1, "could not allocate safe " "passphrase memory\n"); goto out; } if ((error = read_passphrase("Passphrase for hidden volume: ", - h_pass, MAX_PASSSZ, 0) || + h_pass, MAX_PASSSZ, PASS_BUFSZ, 0) || (read_passphrase("Repeat passphrase: ", pass_again, - MAX_PASSSZ, 0)))) { + MAX_PASSSZ, PASS_BUFSZ, 0)))) { tc_log(1, "could not read passphrase\n"); goto out; } @@ -490,36 +623,38 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles pass_again = NULL; } else { /* In batch mode, use provided passphrase */ - if ((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) { + if ((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { tc_log(1, "could not allocate safe " "passphrase memory"); goto out; } - if (h_passphrase != NULL) - strcpy(h_pass, h_passphrase); + if (opts->h_passphrase != NULL) { + strncpy(h_pass, opts->h_passphrase, MAX_PASSSZ); + h_pass[MAX_PASSSZ] = '\0'; + } } - if (n_hkeyfiles > 0) { + if (opts->n_hkeyfiles > 0) { /* Apply keyfiles to 'h_pass' */ if ((error = apply_keyfiles((unsigned char *)h_pass, - MAX_PASSSZ, h_keyfiles, n_hkeyfiles))) { + PASS_BUFSZ, opts->h_keyfiles, opts->n_hkeyfiles))) { tc_log(1, "could not apply keyfiles\n"); goto out; } } - if (interactive) { + if (opts->interactive) { hidden_blocks = 0; } else { - hidden_blocks = size_hidden_bytes_in/blksz; + hidden_blocks = opts->hidden_size_bytes/blksz; if (hidden_blocks == 0) { tc_log(1, "hidden_blocks to create volume " "cannot be zero!\n"); goto out; } - if (size_hidden_bytes_in >= + if (opts->hidden_size_bytes >= (blocks*blksz) - MIN_VOL_BYTES) { tc_log(1, "Hidden volume needs to be " "smaller than the outer volume\n"); @@ -531,10 +666,10 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles while (hidden_blocks == 0) { if ((r = _humanize_number(buf, sizeof(buf), (uint64_t)(blocks * blksz))) < 0) { - sprintf(buf, "%zu bytes", (blocks * blksz)); + sprintf(buf, "%"DISKSZ_FMT" bytes", (blocks * blksz)); } - printf("The total volume size of %s is %s (bytes)\n", dev, buf); + printf("The total volume size of %s is %s (bytes)\n", opts->dev, buf); memset(buf, 0, sizeof(buf)); printf("Size of hidden volume (e.g. 127M): "); fflush(stdout); @@ -564,13 +699,14 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles } } - if (interactive) { + if (opts->interactive) { /* Show summary and ask for confirmation */ printf("Summary of actions:\n"); - printf(" - Completely erase *EVERYTHING* on %s\n", dev); - printf(" - Create %svolume on %s\n", hidden?("outer "):"", dev); - if (hidden) { - printf(" - Create hidden volume of %zu bytes at end of " + if (opts->secure_erase) + printf(" - Completely erase *EVERYTHING* on %s\n", opts->dev); + printf(" - Create %svolume on %s\n", opts->hidden?("outer "):"", opts->dev); + if (opts->hidden) { + printf(" - Create hidden volume of %"DISKSZ_FMT" bytes at end of " "outer volume\n", hidden_blocks * blksz); } @@ -588,58 +724,78 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles } } - tc_log(0, "Securely erasing the volume...\nThis process may take " - "some time depending on the size of the volume\n"); - /* erase volume */ - if ((error = secure_erase(dev, blocks * blksz, blksz)) != 0) { - tc_log(1, "could not securely erase device %s\n", dev); - goto out; + if (opts->secure_erase) { + tc_log(0, "Securely erasing the volume...\nThis process may take " + "some time depending on the size of the volume\n"); + + if (opts->state_change_fn) + opts->state_change_fn(opts->api_ctx, "secure_erase", 1); + + if ((error = secure_erase(opts->dev, blocks * blksz, blksz)) != 0) { + tc_log(1, "could not securely erase device %s\n", opts->dev); + goto out; + } + + if (opts->state_change_fn) + opts->state_change_fn(opts->api_ctx, "secure_erase", 0); } tc_log(0, "Creating volume headers...\nDepending on your system, this " "process may take a few minutes as it uses true random data which " "might take a while to refill\n"); + if (opts->weak_keys_and_salt) { + tc_log(0, "WARNING: Using a weak random generator to get " + "entropy for the key material. Odds are this is NOT " + "what you want.\n"); + } + + if (opts->state_change_fn) + opts->state_change_fn(opts->api_ctx, "create_header", 1); + /* create encrypted headers */ ehdr = create_hdr((unsigned char *)pass, - (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), - prf_algo, cipher_chain, blksz, blocks, VOL_RSVD_BYTES_START/blksz, - blocks - (MIN_VOL_BYTES/blksz), 0, &ehdr_backup); + (opts->nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), + opts->prf_algo, opts->cipher_chain, blksz, blocks, VOL_RSVD_BYTES_START/blksz, + blocks - (MIN_VOL_BYTES/blksz), 0, opts->weak_keys_and_salt, &ehdr_backup); if (ehdr == NULL) { tc_log(1, "Could not create header\n"); goto out; } - if (hidden) { + if (opts->hidden) { hehdr = create_hdr((unsigned char *)h_pass, - (n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), h_prf_algo, - h_cipher_chain, + (opts->n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), opts->h_prf_algo, + opts->h_cipher_chain, blksz, blocks, blocks - (VOL_RSVD_BYTES_END/blksz) - hidden_blocks, - hidden_blocks, 1, &hehdr_backup); + hidden_blocks, 1, opts->weak_keys_and_salt, &hehdr_backup); if (hehdr == NULL) { tc_log(1, "Could not create hidden volume header\n"); goto out; } } + if (opts->state_change_fn) + opts->state_change_fn(opts->api_ctx, "create_header", 0); + tc_log(0, "Writing volume headers to disk...\n"); - if ((error = write_to_disk(dev, 0, blksz, ehdr, sizeof(*ehdr))) != 0) { + if ((error = write_to_disk(opts->dev, 0, blksz, ehdr, sizeof(*ehdr))) != 0) { tc_log(1, "Could not write volume header to device\n"); goto out; } /* Write backup header; it's offset is relative to the end */ - if ((error = write_to_disk(dev, (blocks*blksz - BACKUP_HDR_OFFSET_END), + if ((error = write_to_disk(opts->dev, (blocks*blksz - BACKUP_HDR_OFFSET_END), blksz, ehdr_backup, sizeof(*ehdr_backup))) != 0) { tc_log(1, "Could not write backup volume header to device\n"); goto out; } - if (hidden) { - if ((error = write_to_disk(dev, HDR_OFFSET_HIDDEN, blksz, hehdr, + if (opts->hidden) { + if ((error = write_to_disk(opts->dev, HDR_OFFSET_HIDDEN, blksz, hehdr, sizeof(*hehdr))) != 0) { tc_log(1, "Could not write hidden volume header to " "device\n"); @@ -647,7 +803,7 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles } /* Write backup hidden header; offset is relative to end */ - if ((error = write_to_disk(dev, + if ((error = write_to_disk(opts->dev, (blocks*blksz - BACKUP_HDR_HIDDEN_OFFSET_END), blksz, hehdr_backup, sizeof(*hehdr_backup))) != 0) { tc_log(1, "Could not write backup hidden volume " @@ -680,12 +836,8 @@ out: return ret; } -static struct tcplay_info * -info_map_common(const char *dev, int sflag, const char *sys_dev, - int protect_hidden, const char *keyfiles[], int nkeyfiles, - const char *h_keyfiles[], int n_hkeyfiles, char *passphrase, - char *passphrase_hidden, int interactive, int retries, time_t timeout) +info_map_common(struct tcplay_opts *opts, char *passphrase_out) { struct tchdr_enc *ehdr, *hehdr = NULL; struct tcplay_info *info, *hinfo = NULL; @@ -693,15 +845,30 @@ info_map_common(const char *dev, int sflag, const char *sys_dev, char *h_pass; int error, error2 = 0; size_t sz; - size_t blocks, blksz; + size_t blksz; + disksz_t blocks; + int is_hidden = 0; + int try_empty = 0; + int retries; - if ((error = get_disk_info(dev, &blocks, &blksz)) != 0) { + if ((error = get_disk_info(opts->dev, &blocks, &blksz)) != 0) { tc_log(1, "could not get disk information\n"); return NULL; } - if (retries < 1) + if (opts->retries < 1) retries = 1; + else + retries = opts->retries; + + /* + * Add one retry so we can do a first try without asking for + * a password if keyfiles are passed in. + */ + if (opts->interactive && (opts->nkeyfiles > 0)) { + try_empty = 1; + ++retries; + } info = NULL; @@ -714,56 +881,68 @@ info_map_common(const char *dev, int sflag, const char *sys_dev, ehdr = hehdr = NULL; info = hinfo = NULL; - if ((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) { + if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { tc_log(1, "could not allocate safe passphrase memory\n"); goto out; } - if (interactive) { + if (try_empty) { + pass[0] = '\0'; + } else if (opts->interactive) { if ((error = read_passphrase("Passphrase: ", pass, - MAX_PASSSZ, timeout))) { + MAX_PASSSZ, PASS_BUFSZ, opts->timeout))) { tc_log(1, "could not read passphrase\n"); /* XXX: handle timeout differently? */ goto out; } + pass[MAX_PASSSZ] = '\0'; } else { /* In batch mode, use provided passphrase */ - if (passphrase != NULL) - strcpy(pass, passphrase); + if (opts->passphrase != NULL) { + strncpy(pass, opts->passphrase, MAX_PASSSZ); + pass[MAX_PASSSZ] = '\0'; + } + } + + if (passphrase_out != NULL) { + strcpy(passphrase_out, pass); } - if (nkeyfiles > 0) { + if (opts->nkeyfiles > 0) { /* Apply keyfiles to 'pass' */ - if ((error = apply_keyfiles((unsigned char *)pass, MAX_PASSSZ, - keyfiles, nkeyfiles))) { + if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ, + opts->keyfiles, opts->nkeyfiles))) { tc_log(1, "could not apply keyfiles"); goto out; } } - if (protect_hidden) { - if ((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) { + if (opts->protect_hidden) { + if ((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { tc_log(1, "could not allocate safe passphrase memory\n"); goto out; } - if (interactive) { + if (opts->interactive) { if ((error = read_passphrase( "Passphrase for hidden volume: ", h_pass, - MAX_PASSSZ, timeout))) { + MAX_PASSSZ, PASS_BUFSZ, opts->timeout))) { tc_log(1, "could not read passphrase\n"); goto out; } + h_pass[MAX_PASSSZ] = '\0'; } else { /* In batch mode, use provided passphrase */ - if (passphrase_hidden != NULL) - strcpy(h_pass, passphrase_hidden); + if (opts->h_passphrase != NULL) { + strncpy(h_pass, opts->h_passphrase, MAX_PASSSZ); + h_pass[MAX_PASSSZ] = '\0'; + } } - if (n_hkeyfiles > 0) { + if (opts->n_hkeyfiles > 0) { /* Apply keyfiles to 'pass' */ - if ((error = apply_keyfiles((unsigned char *)h_pass, MAX_PASSSZ, - h_keyfiles, n_hkeyfiles))) { + if ((error = apply_keyfiles((unsigned char *)h_pass, PASS_BUFSZ, + opts->h_keyfiles, opts->n_hkeyfiles))) { tc_log(1, "could not apply keyfiles"); goto out; } @@ -773,29 +952,52 @@ info_map_common(const char *dev, int sflag, const char *sys_dev, /* Always read blksz-sized chunks */ sz = blksz; - ehdr = (struct tchdr_enc *)read_to_safe_mem((sflag) ? sys_dev : dev, - (sflag) ? HDR_OFFSET_SYS : 0, &sz); - if (ehdr == NULL) { - tc_log(1, "error read hdr_enc: %s", dev); - goto out; + if (TC_FLAG_SET(opts->flags, HDR_FROM_FILE)) { + ehdr = (struct tchdr_enc *)read_to_safe_mem( + opts->hdr_file_in, 0, &sz); + if (ehdr == NULL) { + tc_log(1, "error read hdr_enc: %s", opts->hdr_file_in); + goto out; + } + } else { + ehdr = (struct tchdr_enc *)read_to_safe_mem( + (TC_FLAG_SET(opts->flags, SYS)) ? opts->sys_dev : opts->dev, + (TC_FLAG_SET(opts->flags, SYS) || TC_FLAG_SET(opts->flags, FDE)) ? + HDR_OFFSET_SYS : + (!TC_FLAG_SET(opts->flags, BACKUP)) ? 0 : -BACKUP_HDR_OFFSET_END, + &sz); + if (ehdr == NULL) { + tc_log(1, "error read hdr_enc: %s", opts->dev); + goto out; + } } - if (!sflag) { + if (!TC_FLAG_SET(opts->flags, SYS)) { /* Always read blksz-sized chunks */ sz = blksz; - hehdr = (struct tchdr_enc *)read_to_safe_mem(dev, - HDR_OFFSET_HIDDEN, &sz); - if (hehdr == NULL) { - tc_log(1, "error read hdr_enc: %s", dev); - goto out; + if (TC_FLAG_SET(opts->flags, H_HDR_FROM_FILE)) { + hehdr = (struct tchdr_enc *)read_to_safe_mem( + opts->h_hdr_file_in, 0, &sz); + if (hehdr == NULL) { + tc_log(1, "error read hdr_enc: %s", opts->h_hdr_file_in); + goto out; + } + } else { + hehdr = (struct tchdr_enc *)read_to_safe_mem(opts->dev, + (!TC_FLAG_SET(opts->flags, BACKUP)) ? HDR_OFFSET_HIDDEN : + -BACKUP_HDR_HIDDEN_OFFSET_END, &sz); + if (hehdr == NULL) { + tc_log(1, "error read hdr_enc: %s", opts->dev); + goto out; + } } } else { hehdr = NULL; } - error = process_hdr(dev, (unsigned char *)pass, - (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), + error = process_hdr(opts->dev, opts->flags, (unsigned char *)pass, + (opts->nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), ehdr, &info); /* @@ -803,33 +1005,31 @@ info_map_common(const char *dev, int sflag, const char *sys_dev, * volume, or the decryption/verification of the main header * failed. */ - if (hehdr && (error || protect_hidden)) { + if (hehdr && (error || opts->protect_hidden)) { if (error) { - error2 = process_hdr(dev, (unsigned char *)pass, - (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), hehdr, + error2 = process_hdr(opts->dev, opts->flags, (unsigned char *)pass, + (opts->nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), hehdr, &info); - } else if (protect_hidden) { - error2 = process_hdr(dev, (unsigned char *)h_pass, - (n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), hehdr, + is_hidden = !error2; + } else if (opts->protect_hidden) { + error2 = process_hdr(opts->dev, opts->flags, (unsigned char *)h_pass, + (opts->n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), hehdr, &hinfo); } } /* We need both to protect a hidden volume */ - if ((protect_hidden && (error || error2)) || + if ((opts->protect_hidden && (error || error2)) || (error && error2)) { - tc_log(1, "Incorrect password or not a TrueCrypt volume\n"); + if (!try_empty) + tc_log(1, "Incorrect password or not a TrueCrypt volume\n"); if (info) { - if (info->hdr) - free_safe_mem(info->hdr); - free_safe_mem(info); + free_info(info); info = NULL; } if (hinfo) { - if (hinfo->hdr) - free_safe_mem(hinfo->hdr); - free_safe_mem(hinfo); + free_info(hinfo); hinfo = NULL; } @@ -849,36 +1049,36 @@ info_map_common(const char *dev, int sflag, const char *sys_dev, free_safe_mem(hehdr); hehdr = NULL; } + + try_empty = 0; continue; } - if (protect_hidden) { + if (opts->protect_hidden) { if (adjust_info(info, hinfo) != 0) { tc_log(1, "Could not protect hidden volume\n"); - if (info) { - if (info->hdr) - free_safe_mem(info->hdr); - free_safe_mem(info); - } + if (info) + free_info(info); info = NULL; - if (hinfo->hdr) - free_safe_mem(hinfo->hdr); - free_safe_mem(hinfo); + if (hinfo) + free_info(hinfo); hinfo = NULL; + goto out; } - if (hinfo->hdr) - free_safe_mem(hinfo->hdr); - free_safe_mem(hinfo); - hinfo = NULL; + if (hinfo) { + free_info(hinfo); + hinfo = NULL; + } } + try_empty = 0; } out: if (hinfo) - free_safe_mem(hinfo); + free_info(hinfo); if (pass) free_safe_mem(pass); if (h_pass) @@ -888,70 +1088,556 @@ out: if (hehdr) free_safe_mem(hehdr); + if (info != NULL) + info->hidden = is_hidden; + return info; } int -info_volume(const char *device, int sflag, const char *sys_dev, - int protect_hidden, const char *keyfiles[], int nkeyfiles, - const char *h_keyfiles[], int n_hkeyfiles, - char *passphrase, char *passphrase_hidden, int interactive, int retries, - time_t timeout) +info_mapped_volume(struct tcplay_opts *opts) { struct tcplay_info *info; - info = info_map_common(device, sflag, sys_dev, protect_hidden, - keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, - passphrase, passphrase_hidden, interactive, retries, timeout); - + info = dm_info_map(opts->map_name); if (info != NULL) { - if (interactive) + if (opts->interactive) print_info(info); - if (info->hdr) - free_safe_mem(info->hdr); - free_safe_mem(info); + + free_info(info); return 0; /* NOT REACHED */ + } else if (opts->interactive) { + tc_log(1, "Could not retrieve information about mapped " + "volume %s. Does it exist?\n", opts->map_name); } return -1; } int -map_volume(const char *map_name, const char *device, int sflag, - const char *sys_dev, int protect_hidden, const char *keyfiles[], - int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles, - char *passphrase, char *passphrase_hidden, int interactive, int retries, - time_t timeout) +info_volume(struct tcplay_opts *opts) +{ + struct tcplay_info *info; + + info = info_map_common(opts, NULL); + + if (info != NULL) { + if (opts->interactive) + print_info(info); + + free_info(info); + + return 0; + /* NOT REACHED */ + } + + return -1; +} +int +map_volume(struct tcplay_opts *opts) { struct tcplay_info *info; int error; - info = info_map_common(device, sflag, sys_dev, protect_hidden, - keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, - passphrase, passphrase_hidden, interactive, retries, timeout); + info = info_map_common(opts, NULL); if (info == NULL) return -1; - if ((error = dm_setup(map_name, info)) != 0) { - tc_log(1, "Could not set up mapping %s\n", map_name); - if (info->hdr) - free_safe_mem(info->hdr); - free_safe_mem(info); + if ((error = dm_setup(opts->map_name, info)) != 0) { + tc_log(1, "Could not set up mapping %s\n", opts->map_name); + free_info(info); return -1; } - if (interactive) + if (opts->interactive) printf("All ok!\n"); - free_safe_mem(info); + free_info(info); return 0; } +int +modify_volume(struct tcplay_opts *opts) +{ + struct tcplay_info *info; + struct tchdr_enc *ehdr, *ehdr_backup; + const char *new_passphrase = opts->new_passphrase; + const char **new_keyfiles = opts->new_keyfiles; + struct pbkdf_prf_algo *new_prf_algo = opts->new_prf_algo; + int n_newkeyfiles = opts->n_newkeyfiles; + char *pass, *pass_again; + int ret = -1; + off_t offset, offset_backup = 0; + const char *dev; + size_t blksz; + disksz_t blocks; + int error; + + ehdr = ehdr_backup = NULL; + pass = pass_again = NULL; + info = NULL; + + if (TC_FLAG_SET(opts->flags, ONLY_RESTORE)) { + if (opts->interactive) { + if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { + tc_log(1, "could not allocate safe " + "passphrase memory"); + goto out; + } + } else { + new_passphrase = opts->passphrase; + } + new_keyfiles = opts->keyfiles; + n_newkeyfiles = opts->nkeyfiles; + new_prf_algo = NULL; + } + + info = info_map_common(opts, pass); + if (info == NULL) + goto out; + + if (opts->interactive && !TC_FLAG_SET(opts->flags, ONLY_RESTORE)) { + if (((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) || + ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) { + tc_log(1, "could not allocate safe passphrase memory\n"); + goto out; + } + + if ((error = read_passphrase("New passphrase: ", pass, MAX_PASSSZ, + PASS_BUFSZ, 0) || + (read_passphrase("Repeat passphrase: ", pass_again, + MAX_PASSSZ, PASS_BUFSZ, 0)))) { + tc_log(1, "could not read passphrase\n"); + goto out; + } + + if (strcmp(pass, pass_again) != 0) { + tc_log(1, "Passphrases don't match\n"); + goto out; + } + + free_safe_mem(pass_again); + pass_again = NULL; + } else if (!opts->interactive) { + /* In batch mode, use provided passphrase */ + if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { + tc_log(1, "could not allocate safe " + "passphrase memory"); + goto out; + } + + if (new_passphrase != NULL) { + strncpy(pass, new_passphrase, MAX_PASSSZ); + pass[MAX_PASSSZ] = '\0'; + } + } + + if (n_newkeyfiles > 0) { + /* Apply keyfiles to 'pass' */ + if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ, + new_keyfiles, n_newkeyfiles))) { + tc_log(1, "could not apply keyfiles\n"); + goto out; + } + } + + ehdr = copy_reencrypt_hdr((unsigned char *)pass, + (opts->n_newkeyfiles > 0)?MAX_PASSSZ:strlen(pass), + new_prf_algo, opts->weak_keys_and_salt, info, &ehdr_backup); + if (ehdr == NULL) { + tc_log(1, "Could not create header\n"); + goto out; + } + + dev = (TC_FLAG_SET(opts->flags, SYS)) ? opts->sys_dev : opts->dev; + if (TC_FLAG_SET(opts->flags, SYS) || TC_FLAG_SET(opts->flags, FDE)) { + /* SYS and FDE don't have backup headers (as far as I understand) */ + if (info->hidden) { + offset = HDR_OFFSET_HIDDEN; + } else { + offset = HDR_OFFSET_SYS; + } + } else { + if (info->hidden) { + offset = HDR_OFFSET_HIDDEN; + offset_backup = -BACKUP_HDR_HIDDEN_OFFSET_END; + } else { + offset = 0; + offset_backup = -BACKUP_HDR_OFFSET_END; + } + } + + if ((error = get_disk_info(dev, &blocks, &blksz)) != 0) { + tc_log(1, "could not get disk information\n"); + goto out; + } + + tc_log(0, "Writing new volume headers to disk/file...\n"); + + if (TC_FLAG_SET(opts->flags, SAVE_TO_FILE)) { + if ((error = write_to_file(opts->hdr_file_out, ehdr, sizeof(*ehdr))) != 0) { + tc_log(1, "Could not write volume header to file\n"); + goto out; + } + } else { + if ((error = write_to_disk(dev, offset, blksz, ehdr, + sizeof(*ehdr))) != 0) { + tc_log(1, "Could not write volume header to device\n"); + goto out; + } + + if (!TC_FLAG_SET(opts->flags, SYS) && !TC_FLAG_SET(opts->flags, FDE)) { + if ((error = write_to_disk(dev, offset_backup, blksz, + ehdr_backup, sizeof(*ehdr_backup))) != 0) { + tc_log(1, "Could not write backup volume header to device\n"); + goto out; + } + } + } + + /* Everything went ok */ + tc_log(0, "All done!\n"); + + ret = 0; + +out: + if (pass) + free_safe_mem(pass); + if (pass_again) + free_safe_mem(pass_again); + if (ehdr) + free_safe_mem(ehdr); + if (ehdr_backup) + free_safe_mem(ehdr_backup); + if (info) + free_safe_mem(info); + + return ret; +} + +static +int +dm_get_info(const char *name, struct dm_info *dmi) +{ + struct dm_task *dmt = NULL; + int error = -1; + + if ((dmt = dm_task_create(DM_DEVICE_INFO)) == NULL) + goto out; + + if ((dm_task_set_name(dmt, name)) == 0) + goto out; + + if ((dm_task_run(dmt)) == 0) + goto out; + + if ((dm_task_get_info(dmt, dmi)) == 0) + goto out; + + error = 0; + +out: + if (dmt) + dm_task_destroy(dmt); + + return error; +} + +#if defined(__DragonFly__) +static +int +xlate_maj_min(const char *start_path __unused, int max_depth __unused, + char *buf, size_t bufsz, uint32_t maj, uint32_t min) +{ + dev_t dev = makedev(maj, min); + + snprintf(buf, bufsz, "/dev/%s", devname(dev, S_IFCHR)); + return 1; +} +#else +static +int +xlate_maj_min(const char *start_path, int max_depth, char *buf, size_t bufsz, + uint32_t maj, uint32_t min) +{ + dev_t dev = makedev(maj, min); + char path[PATH_MAX]; + struct stat sb; + struct dirent *ent; + DIR *dirp; + int found = 0; + + if (max_depth <= 0) + return -1; + + if ((dirp = opendir(start_path)) == NULL) + return -1; + + while ((ent = readdir(dirp)) != NULL) { + /* d_name, d_type, DT_BLK, DT_CHR, DT_DIR, DT_LNK */ + if (ent->d_name[0] == '.') + continue; + + /* Linux' /dev is littered with junk, so skip over it */ + /* + * The dm- devices seem to be the raw DM devices + * things in mapper/ link to. + */ + if (((strcmp(ent->d_name, "block")) == 0) || + ((strcmp(ent->d_name, "fd")) == 0) || + (((strncmp(ent->d_name, "dm-", 3) == 0) && strlen(ent->d_name) <= 5))) + continue; + + snprintf(path, PATH_MAX, "%s/%s", start_path, ent->d_name); + + if ((stat(path, &sb)) < 0) + continue; + + if (S_ISDIR(sb.st_mode)) { + found = !xlate_maj_min(path, max_depth-1, buf, bufsz, maj, min); + if (found) + break; + } + + if (!S_ISBLK(sb.st_mode)) + continue; + + if (sb.st_rdev != dev) + continue; + + snprintf(buf, bufsz, "%s", path); + found = 1; + break; + } + + if (dirp) + closedir(dirp); + + return found ? 0 : -ENOENT; +} +#endif + +static +struct tcplay_dm_table * +dm_get_table(const char *name) +{ + struct tcplay_dm_table *tc_table; + struct dm_task *dmt = NULL; + void *next = NULL; + uint64_t start, length; + char *target_type; + char *params; + char *p1; + int c = 0; + uint32_t maj, min; + + if ((tc_table = (struct tcplay_dm_table *)alloc_safe_mem(sizeof(*tc_table))) == NULL) { + tc_log(1, "could not allocate safe tc_table memory\n"); + return NULL; + } + + if ((dmt = dm_task_create(DM_DEVICE_TABLE)) == NULL) + goto error; + + if ((dm_task_set_name(dmt, name)) == 0) + goto error; + + if ((dm_task_run(dmt)) == 0) + goto error; + + tc_table->start = (off_t)0; + tc_table->size = (size_t)0; + + do { + next = dm_get_next_target(dmt, next, &start, &length, + &target_type, ¶ms); + + tc_table->size += (size_t)length; + strncpy(tc_table->target, target_type, + sizeof(tc_table->target)); + + /* Skip any leading whitespace */ + while (params && *params == ' ') + params++; + + if (strcmp(target_type, "crypt") == 0) { + while ((p1 = strsep(¶ms, " ")) != NULL) { + /* Skip any whitespace before the next strsep */ + while (params && *params == ' ') + params++; + + /* Process p1 */ + if (c == 0) { + /* cipher */ + strncpy(tc_table->cipher, p1, + sizeof(tc_table->cipher)); + } else if (c == 2) { + /* iv offset */ + tc_table->skip = (off_t)strtoll(p1, NULL, 10); + } else if (c == 3) { + /* major:minor */ + maj = strtoul(p1, NULL, 10); + while (*p1 != ':' && *p1 != '\0') + p1++; + min = strtoul(++p1, NULL, 10); + if ((xlate_maj_min("/dev", 2, tc_table->device, + sizeof(tc_table->device), maj, min)) != 0) + snprintf(tc_table->device, + sizeof(tc_table->device), + "%u:%u", maj, min); + } else if (c == 4) { + /* block offset */ + tc_table->offset = (off_t)strtoll(p1, + NULL, 10); + } + ++c; + } + + if (c < 5) { + tc_log(1, "could not get all the info required from " + "the table\n"); + goto error; + } + } + } while (next != NULL); + + if (dmt) + dm_task_destroy(dmt); + +#ifdef DEBUG + printf("device: %s\n", tc_table->device); + printf("target: %s\n", tc_table->target); + printf("cipher: %s\n", tc_table->cipher); + printf("size: %ju\n", tc_table->size); + printf("offset: %"PRId64"\n", tc_table->offset); + printf("skip: %"PRId64"\n", tc_table->skip); +#endif + + return tc_table; + +error: + if (dmt) + dm_task_destroy(dmt); + if (tc_table) + free_safe_mem(tc_table); + + return NULL; +} + +struct tcplay_info * +dm_info_map(const char *map_name) +{ + struct dm_task *dmt = NULL; + struct dm_info dmi[3]; + struct tcplay_dm_table *dm_table[3]; + struct tc_crypto_algo *crypto_algo; + struct tcplay_info *info; + char map[PATH_MAX]; + char ciphers[512]; + int i, outermost = -1; + + memset(dm_table, 0, sizeof(dm_table)); + + if ((info = (struct tcplay_info *)alloc_safe_mem(sizeof(*info))) == NULL) { + tc_log(1, "could not allocate safe info memory\n"); + return NULL; + } + + strncpy(map, map_name, PATH_MAX); + for (i = 0; i < 3; i++) { + if ((dm_get_info(map, &dmi[i])) != 0) + goto error; + + if (dmi[i].exists) + dm_table[i] = dm_get_table(map); + + snprintf(map, PATH_MAX, "%s.%d", map_name, i); + } + + if (dmt) + dm_task_destroy(dmt); + + if (dm_table[0] == NULL) + goto error; + + /* + * Process our dmi, dm_table fun into the info structure. + */ + /* First find which cipher chain we are using */ + ciphers[0] = '\0'; + for (i = 0; i < 3; i++) { + if (dm_table[i] == NULL) + continue; + + if (outermost < i) + outermost = i; + + crypto_algo = &tc_crypto_algos[0]; + while ((crypto_algo != NULL) && + (strcmp(dm_table[i]->cipher, crypto_algo->dm_crypt_str) != 0)) + ++crypto_algo; + if (crypto_algo == NULL) { + tc_log(1, "could not find corresponding cipher\n"); + goto error; + } + strcat(ciphers, crypto_algo->name); + strcat(ciphers, ","); + } + ciphers[strlen(ciphers)-1] = '\0'; + + info->cipher_chain = check_cipher_chain(ciphers, 1); + if (info->cipher_chain == NULL) { + tc_log(1, "could not find cipher chain\n"); + goto error; + } + + /* Copy over the name */ + strncpy(info->dev, dm_table[outermost]->device, sizeof(info->dev)); + + /* Other fields */ + info->hdr = NULL; + info->pbkdf_prf = NULL; + info->start = dm_table[outermost]->start; + info->size = dm_table[0]->size; + info->skip = dm_table[outermost]->skip; + info->offset = dm_table[outermost]->offset; + info->blk_sz = 512; + + return info; + +error: + if (dmt) + dm_task_destroy(dmt); + if (info) + free_safe_mem(info); + for (i = 0; i < 3; i++) + if (dm_table[i] != NULL) + free_safe_mem(dm_table[i]); + + return NULL; +} + +static +int +dm_exists_device(const char *name) +{ + struct dm_info dmi; + int exists = 0; + + if (dm_get_info(name, &dmi) != 0) + goto out; + + exists = dmi.exists; + +out: + return exists; +} + static int dm_remove_device(const char *name) @@ -983,14 +1669,14 @@ dm_setup(const char *mapname, struct tcplay_info *info) struct dm_task *dmt = NULL; struct dm_info dmi; char *params = NULL; - char *uu; + char *uu, *uu_temp; char *uu_stack[64]; int uu_stack_idx; #if defined(__DragonFly__) uint32_t status; #endif int r, ret = 0; - int j; + int j, len; off_t start, offset; char dev[PATH_MAX]; char map[PATH_MAX]; @@ -1004,29 +1690,35 @@ dm_setup(const char *mapname, struct tcplay_info *info) } strcpy(dev, info->dev); - start = info->start; - offset = info->offset; + + /* + * Device Mapper blocks are always 512-byte blocks, so convert + * from the "native" block size to the dm block size here. + */ + start = INFO_TO_DM_BLOCKS(info, start); + offset = INFO_TO_DM_BLOCKS(info, offset); uu_stack_idx = 0; + /* + * Find length of cipher chain. Could use the for below, but doesn't + * really matter. + */ + len = tc_cipher_chain_length(info->cipher_chain); + /* Get to the end of the chain */ for (cipher_chain = info->cipher_chain; cipher_chain->next != NULL; cipher_chain = cipher_chain->next) ; - for (j= 0; cipher_chain != NULL; - cipher_chain = cipher_chain->prev, j++) { + /* + * Start j at len-2, as we want to use .0, and the final one has no + * suffix. + */ + for (j = len-2; cipher_chain != NULL; + cipher_chain = cipher_chain->prev, j--) { cookie = 0; - /* aes-cbc-essiv:sha256 7997f8af... 0 /dev/ad0s0a 8 */ - /* iv off---^ block off--^ */ - snprintf(params, 512, "%s %s %"PRIu64 " %s %"PRIu64, - cipher_chain->cipher->dm_crypt_str, cipher_chain->dm_key, - (uint64_t)info->skip, dev, (uint64_t)offset); -#ifdef DEBUG - printf("Params: %s\n", params); -#endif - if ((dmt = dm_task_create(DM_DEVICE_CREATE)) == NULL) { tc_log(1, "dm_task_create failed\n"); ret = -1; @@ -1050,12 +1742,12 @@ dm_setup(const char *mapname, struct tcplay_info *info) #if defined(__linux__) uuid_generate(info->uuid); - if ((uu = malloc(1024)) == NULL) { + if ((uu_temp = malloc(1024)) == NULL) { tc_log(1, "uuid_unparse memory failed\n"); ret = -1; goto out; } - uuid_unparse(info->uuid, uu); + uuid_unparse(info->uuid, uu_temp); #elif defined(__DragonFly__) uuid_create(&info->uuid, &status); if (status != uuid_s_ok) { @@ -1064,14 +1756,24 @@ dm_setup(const char *mapname, struct tcplay_info *info) goto out; } - uuid_to_string(&info->uuid, &uu, &status); - if (uu == NULL) { + uuid_to_string(&info->uuid, &uu_temp, &status); + if (uu_temp == NULL) { tc_log(1, "uuid_to_string failed\n"); ret = -1; goto out; } #endif + if ((uu = malloc(1024)) == NULL) { + free(uu_temp); + tc_log(1, "uuid second malloc failed\n"); + ret = -1; + goto out; + } + + snprintf(uu, 1024, "CRYPT-TCPLAY-%s", uu_temp); + free(uu_temp); + if ((dm_task_set_uuid(dmt, uu)) == 0) { free(uu); tc_log(1, "dm_task_set_uuid failed\n"); @@ -1081,7 +1783,41 @@ dm_setup(const char *mapname, struct tcplay_info *info) free(uu); - if ((dm_task_add_target(dmt, start, info->size, "crypt", params)) == 0) { + if (TC_FLAG_SET(info->flags, FDE)) { + /* + * When the full disk encryption (FDE) flag is set, + * we map the first N sectors using a linear target + * as they aren't encrypted. + */ + + /* /dev/ad0s0a 0 */ + /* dev---^ block off --^ */ + snprintf(params, 512, "%s 0", dev); + + if ((dm_task_add_target(dmt, 0, + INFO_TO_DM_BLOCKS(info, offset), + "linear", params)) == 0) { + tc_log(1, "dm_task_add_target failed\n"); + ret = -1; + goto out; + } + + start = INFO_TO_DM_BLOCKS(info, offset); + } + + /* aes-cbc-essiv:sha256 7997f8af... 0 /dev/ad0s0a 8 */ + /* iv off---^ block off--^ */ + snprintf(params, 512, "%s %s %"PRIu64 " %s %"PRIu64 " %s", + cipher_chain->cipher->dm_crypt_str, cipher_chain->dm_key, + (uint64_t)INFO_TO_DM_BLOCKS(info, skip), dev, + (uint64_t)offset, + TC_FLAG_SET(info->flags, ALLOW_TRIM) ? "1 allow_discards" : ""); +#ifdef DEBUG + printf("Params: %s\n", params); +#endif + + if ((dm_task_add_target(dmt, start, + INFO_TO_DM_BLOCKS(info, size), "crypt", params)) == 0) { tc_log(1, "dm_task_add_target failed\n"); ret = -1; goto out; @@ -1095,7 +1831,7 @@ dm_setup(const char *mapname, struct tcplay_info *info) if ((dm_task_run(dmt)) == 0) { dm_udev_wait(cookie); - tc_log(1, "dm_task_task_run failed\n"); + tc_log(1, "dm_task_run failed\n"); ret = -1; goto out; } @@ -1109,7 +1845,10 @@ dm_setup(const char *mapname, struct tcplay_info *info) dm_udev_wait(cookie); - asprintf(&uu_stack[uu_stack_idx++], "%s", map); + if ((r = asprintf(&uu_stack[uu_stack_idx++], "%s", map)) < 0) + tc_log(1, "warning, asprintf failed. won't be able to " + "unroll changes\n"); + offset = 0; start = 0; @@ -1131,7 +1870,8 @@ out: printf("Unrolling dm changes! j = %d (%s)\n", j-1, uu_stack[j-1]); #endif - if ((r = dm_remove_device(uu_stack[--j])) != 0) { + if ((uu_stack[j-1] == NULL) || + ((r = dm_remove_device(uu_stack[--j])) != 0)) { tc_log(1, "Tried to unroll dm changes, " "giving up.\n"); break; @@ -1163,9 +1903,10 @@ dm_teardown(const char *mapname, const char *device __unused) } /* Try to remove other cascade devices */ - for (i = 2; i >= 0; i--) { + for (i = 0; i < 2; i++) { sprintf(map, "%s.%d", mapname, i); - dm_remove_device(map); + if (dm_exists_device(map)) + dm_remove_device(map); } return 0; @@ -1195,7 +1936,7 @@ check_cipher(const char *cipher, int quiet) } struct tc_cipher_chain * -check_cipher_chain(char *cipher_chain, int quiet) +check_cipher_chain(const char *cipher_chain, int quiet) { struct tc_cipher_chain *cipher = NULL; int i,k, nciphers = 0, mismatch = 0; @@ -1264,7 +2005,7 @@ check_cipher_chain(char *cipher_chain, int quiet) } struct pbkdf_prf_algo * -check_prf_algo(char *algo, int quiet) +check_prf_algo(const char *algo, int quiet) { int i, found = 0; @@ -1299,3 +2040,148 @@ tc_play_init(void) return 0; } + +struct tcplay_opts *opts_init(void) +{ + struct tcplay_opts *opts; + + if ((opts = (struct tcplay_opts *)alloc_safe_mem(sizeof(*opts))) == NULL) { + tc_log(1, "could not allocate safe opts memory\n"); + return NULL; + } + + memset(opts, 0, sizeof(*opts)); + + opts->retries = DEFAULT_RETRIES; + opts->secure_erase = 1; + + return opts; +} + +int +opts_add_keyfile(struct tcplay_opts *opts, const char *keyfile) +{ + const char *keyf; + + if (opts->nkeyfiles == MAX_KEYFILES) + return -1; + + if ((keyf = strdup_safe_mem(keyfile)) == NULL) { + return -1; + } + + opts->keyfiles[opts->nkeyfiles++] = keyf; + + return 0; +} + +int +opts_add_keyfile_hidden(struct tcplay_opts *opts, const char *keyfile) +{ + const char *keyf; + + if (opts->n_hkeyfiles == MAX_KEYFILES) + return -1; + + if ((keyf = strdup_safe_mem(keyfile)) == NULL) { + return -1; + } + + opts->h_keyfiles[opts->n_hkeyfiles++] = keyf; + + return 0; +} + +int +opts_add_keyfile_new(struct tcplay_opts *opts, const char *keyfile) +{ + const char *keyf; + + if (opts->n_newkeyfiles == MAX_KEYFILES) + return -1; + + if ((keyf = strdup_safe_mem(keyfile)) == NULL) { + return -1; + } + + opts->new_keyfiles[opts->n_newkeyfiles++] = keyf; + + return 0; +} + +void +opts_clear_keyfile(struct tcplay_opts *opts) +{ + int i; + + for (i = 0; i < opts->nkeyfiles; i++) { + free_safe_mem(opts->keyfiles[i]); + } + + opts->nkeyfiles = 0; +} + +void +opts_clear_keyfile_hidden(struct tcplay_opts *opts) +{ + int i; + + for (i = 0; i < opts->n_hkeyfiles; i++) { + free_safe_mem(opts->h_keyfiles[i]); + } + + opts->n_hkeyfiles = 0; +} + + +void +opts_clear_keyfile_new(struct tcplay_opts *opts) +{ + int i; + + for (i = 0; i < opts->n_newkeyfiles; i++) { + free_safe_mem(opts->new_keyfiles[i]); + } + + opts->n_newkeyfiles = 0; +} + + +void +opts_free(struct tcplay_opts *opts) +{ + int i; + + for (i = 0; i < opts->nkeyfiles; i++) { + free_safe_mem(opts->keyfiles[i]); + } + + for (i = 0; i < opts->n_hkeyfiles; i++) { + free_safe_mem(opts->h_keyfiles[i]); + } + + for (i = 0; i < opts->n_newkeyfiles; i++) { + free_safe_mem(opts->new_keyfiles[i]); + } + + if (opts->dev) + free_safe_mem(opts->dev); + if (opts->passphrase) + free_safe_mem(opts->passphrase); + if (opts->h_passphrase) + free_safe_mem(opts->h_passphrase); + if (opts->new_passphrase) + free_safe_mem(opts->new_passphrase); + if (opts->map_name) + free_safe_mem(opts->map_name); + if (opts->sys_dev) + free_safe_mem(opts->sys_dev); + if (opts->hdr_file_in) + free_safe_mem(opts->hdr_file_in); + if (opts->h_hdr_file_in) + free_safe_mem(opts->h_hdr_file_in); + if (opts->hdr_file_out) + free_safe_mem(opts->hdr_file_out); + + free_safe_mem(opts); +} diff --git a/lib/libtcplay/tcplay.h b/lib/libtcplay/tcplay.h index 64a2a69587..6f982d0ede 100644 --- a/lib/libtcplay/tcplay.h +++ b/lib/libtcplay/tcplay.h @@ -27,10 +27,10 @@ * SUCH DAMAGE. */ -/* Version of tcplay */ -#define MAJ_VER 0 -#define MIN_VER 9 +/* Version of tcplay specified during build (CMakeLists.txt, Makefile.classic) */ +#ifndef _TCPLAY_H +#define _TCPLAY_H #define MAX_BLKSZ 4096 #define MAX_KEYSZ 192 @@ -38,6 +38,7 @@ #define HDR_OFFSET_SYS 31744 /* 512 * (63 -1) */ #define TC_SIG "TRUE" #define MAX_PASSSZ 64 +#define PASS_BUFSZ 256 #define KPOOL_SZ 64 #define MAX_KFILE_SZ 1048576 /* 1 MB */ #define MAX_KEYFILES 256 @@ -57,11 +58,25 @@ #define TC_VOLFLAG_SYSTEM 0x01 /* system encryption */ #define TC_VOLFLAG_INPLACE 0x02 /* non-system in-place-encrypted volume */ +#define TC_VOLFLAG_SET(f, x) ((f & TC_VOLFLAG_##x) == TC_VOLFLAG_##x) + #define LOG_BUFFER_SZ 1024 #if 0 #define DEBUG 1 #endif +#define TC_FLAG_SYS 0x0001 +#define TC_FLAG_FDE 0x0002 +#define TC_FLAG_BACKUP 0x0004 +#define TC_FLAG_ONLY_RESTORE 0x0008 +#define TC_FLAG_ALLOW_TRIM 0x0010 +#define TC_FLAG_SAVE_TO_FILE 0x0020 +#define TC_FLAG_HDR_FROM_FILE 0x0040 +#define TC_FLAG_H_HDR_FROM_FILE 0x0080 + +#define TC_FLAG_SET(f, x) ((f & TC_FLAG_##x) == TC_FLAG_##x) + +#include #include #if defined(__DragonFly__) @@ -70,6 +85,11 @@ #include #endif + +typedef uint64_t disksz_t; +#define DISKSZ_FMT PRIu64 + + struct pbkdf_prf_algo { const char *name; int iteration_count; @@ -103,47 +123,131 @@ struct tchdr_dec { uint32_t crc_keys; /* CRC32 of the key section */ uint64_t vol_ctime; /* Volume creation time */ uint64_t hdr_ctime; /* Header creation time */ - uint64_t sz_hidvol; /* Size of hidden volume (set to zero - in non-hidden volumes) */ - uint64_t sz_vol; /* Size of volume */ - uint64_t off_mk_scope; /* Byte offset of the start of the - master key scope */ - uint64_t sz_mk_scope; /* Size of the encrypted area within - the master key scope */ - uint32_t flags; /* Flag bits - (bit 0: system encryption; - bit 1: non-system in-place-encrypted volume; - bits 2–31 are reserved) */ - uint32_t sec_sz; /* Sector size (in bytes) */ + uint64_t sz_hidvol; /* Size of hidden volume (set to zero + in non-hidden volumes) */ + uint64_t sz_vol; /* Size of volume */ + uint64_t off_mk_scope; /* Byte offset of the start of the + master key scope */ + uint64_t sz_mk_scope; /* Size of the encrypted area within + the master key scope */ + uint32_t flags; /* Flag bits + (bit 0: system encryption; + bit 1: non-system in-place-encrypted volume; + bits 2–31 are reserved) */ + uint32_t sec_sz; /* Sector size (in bytes) */ unsigned char unused3[120]; uint32_t crc_dhdr; /* CRC32 of dec. header (except keys) */ unsigned char keys[256]; } __attribute__((__packed__)); struct tcplay_info { - const char *dev; + char dev[PATH_MAX]; struct tchdr_dec *hdr; struct tc_cipher_chain *cipher_chain; struct pbkdf_prf_algo *pbkdf_prf; char key[MAX_KEYSZ*2 + 1]; - off_t start; /* Logical volume offset in table */ - size_t size; /* Volume size */ - off_t skip; /* IV offset */ - off_t offset; /* Block offset */ + int flags; + int volflags; + + uint32_t blk_sz; + + off_t start; /* Logical volume offset in table (in blk_sz blocks) */ + disksz_t size; /* Volume size (in blk_sz blocks) */ + + off_t skip; /* IV offset (in blk_sz blocks) */ + off_t offset; /* Block offset (in blk_sz blocks) */ /* Populated by dm_setup */ uuid_t uuid; + + int hidden; +}; + +#define INFO_TO_DM_BLOCKS(info, memb) \ + (((info)->memb * (uint64_t)((info)->blk_sz))/512) + +struct tcplay_dm_table { + char device[PATH_MAX]; /* Underlying device */ + char target[256]; /* DM Target type */ + off_t start; /* Logical volume offset in table */ + disksz_t size; /* Volume size */ + + char cipher[256]; /* Cipher */ + off_t skip; /* IV offset */ + off_t offset; /* Block offset */ }; + +typedef int (*tc_state_change_fn)(void *, const char *, int); + +struct tcplay_opts { + /* (Mostly) common options */ + const char *dev; + const char *keyfiles[MAX_KEYFILES]; + int nkeyfiles; + const char *h_keyfiles[MAX_KEYFILES]; + int n_hkeyfiles; + struct pbkdf_prf_algo *prf_algo; + struct tc_cipher_chain *cipher_chain; + struct pbkdf_prf_algo *h_prf_algo; + struct tc_cipher_chain *h_cipher_chain; + const char *passphrase; + const char *h_passphrase; + int interactive; + int weak_keys_and_salt; + + /* Options for create */ + int hidden; + disksz_t hidden_size_bytes; + int secure_erase; /* XXX: default to 1! */ + + /* Options for map, info_mapped */ + const char *map_name; + + /* Options for info, map, modify */ + int flags; + const char *sys_dev; + int protect_hidden; + int retries; /* XXX: default to DEFAULT_RETRIES */ + time_t timeout; + + const char *hdr_file_in; + const char *h_hdr_file_in; + + /* Options for modify only */ + struct pbkdf_prf_algo *new_prf_algo; + const char *new_passphrase; + const char *hdr_file_out; + const char *new_keyfiles[MAX_KEYFILES]; + int n_newkeyfiles; + + void *api_ctx; + tc_state_change_fn state_change_fn; +}; + + +struct tcplay_opts *opts_init(void); +int opts_add_keyfile(struct tcplay_opts *opts, const char *keyfile); +int opts_add_keyfile_hidden(struct tcplay_opts *opts, const char *keyfile); +int opts_add_keyfile_new(struct tcplay_opts *opts, const char *keyfile); +void opts_free(struct tcplay_opts *opts); +void opts_clear_keyfile(struct tcplay_opts *opts); +void opts_clear_keyfile_hidden(struct tcplay_opts *opts); +void opts_clear_keyfile_new(struct tcplay_opts *opts); + void *read_to_safe_mem(const char *file, off_t offset, size_t *sz); -int get_random(unsigned char *buf, size_t len); -int secure_erase(const char *dev, size_t bytes, size_t blksz); -int get_disk_info(const char *dev, size_t *blocks, size_t *bsize); +int get_random(unsigned char *buf, size_t len, int weak); +int secure_erase(const char *dev, disksz_t bytes, size_t blksz); +int get_disk_info(const char *dev, disksz_t *blocks, size_t *bsize); int write_to_disk(const char *dev, off_t offset, size_t blksz, void *mem, size_t bytes); +int write_to_file(const char *file, void *mem, size_t bytes); int read_passphrase(const char *prompt, char *pass, size_t passlen, - time_t timeout); + size_t bufsz, time_t timeout); +float get_random_read_progress(void); +float get_secure_erase_progress(void); + int tc_crypto_init(void); int tc_cipher_chain_populate_keys(struct tc_cipher_chain *cipher_chain, @@ -169,56 +273,73 @@ int apply_keyfiles(unsigned char *pass, size_t pass_memsz, const char *keyfiles[ struct tchdr_enc *create_hdr(unsigned char *pass, int passlen, struct pbkdf_prf_algo *prf_algo, struct tc_cipher_chain *cipher_chain, - size_t sec_sz, size_t total_blocks, - off_t offset, size_t blocks, int hidden, + size_t sec_sz, disksz_t total_blocks, + off_t offset, disksz_t blocks, int hidden, int weak, struct tchdr_enc **backup_hdr); struct tchdr_dec *decrypt_hdr(struct tchdr_enc *ehdr, struct tc_cipher_chain *cipher_chain, unsigned char *key); int verify_hdr(struct tchdr_dec *hdr); +struct tchdr_enc *copy_reencrypt_hdr(unsigned char *pass, int passlen, + struct pbkdf_prf_algo *prf_algo, int weak, struct tcplay_info *info, + struct tchdr_enc **backup_hdr); void *_alloc_safe_mem(size_t req_sz, const char *file, int line); +void *_strdup_safe_mem(const char *in, const char *file, int line); void _free_safe_mem(void *mem, const char *file, int line); void check_and_purge_safe_mem(void); struct tc_crypto_algo *check_cipher(const char *cipher, int quiet); -struct tc_cipher_chain *check_cipher_chain(char *cipher_chain, int quiet); -struct pbkdf_prf_algo *check_prf_algo(char *algo, int quiet); +struct tc_cipher_chain *check_cipher_chain(const char *cipher_chain, int quiet); +struct pbkdf_prf_algo *check_prf_algo(const char *algo, int quiet); int tc_play_init(void); -void tc_log(int err, const char *fmt, ...) __printflike(2, 3); +void tc_log(int err, const char *fmt, ...); +int tc_cipher_chain_klen(struct tc_cipher_chain *chain); +int tc_cipher_chain_length(struct tc_cipher_chain *chain); +char *tc_cipher_chain_sprint(char *buf, size_t bufsz, + struct tc_cipher_chain *chain); +int free_info(struct tcplay_info *info); void print_info(struct tcplay_info *info); int adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo); -int process_hdr(const char *dev, unsigned char *pass, int passlen, +int process_hdr(const char *dev, int flags, unsigned char *pass, int passlen, struct tchdr_enc *ehdr, struct tcplay_info **pinfo); -int create_volume(const char *dev, int hidden, const char *keyfiles[], - int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles, - struct pbkdf_prf_algo *prf_algo, struct tc_cipher_chain *cipher_chain, - struct pbkdf_prf_algo *h_prf_algo, struct tc_cipher_chain *h_cipher_chain, - char *passphrase, char *h_passphrase, size_t hidden_bytes_in, - int interactive); -int info_volume(const char *device, int sflag, const char *sys_dev, - int protect_hidden, const char *keyfiles[], int nkeyfiles, - const char *h_keyfiles[], int n_hkeyfiles, - char *passphrase, char *passphrase_hidden, int interactive, int retries, - time_t timeout); -int map_volume(const char *map_name, const char *device, int sflag, - const char *sys_dev, int protect_hidden, const char *keyfiles[], - int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles, - char *passphrase, char *passphrase_hidden, int interactive, int retries, - time_t timeout); +int create_volume(struct tcplay_opts *opts); +struct tcplay_info *info_map_common(struct tcplay_opts *opts, + char *passphrase_out); +int info_mapped_volume(struct tcplay_opts *opts); +int info_volume(struct tcplay_opts *opts); +int map_volume(struct tcplay_opts *opts); +int modify_volume(struct tcplay_opts *opts); int dm_setup(const char *mapname, struct tcplay_info *info); int dm_teardown(const char *mapname, const char *device); +struct tcplay_info *dm_info_map(const char *map_name); typedef void(*summary_fn_t)(void); extern int tc_internal_verbose; extern char tc_internal_log_buffer[]; extern summary_fn_t summary_fn; +extern struct pbkdf_prf_algo pbkdf_prf_algos[]; +extern struct tc_cipher_chain *tc_cipher_chains[MAX_CIPHER_CHAINS]; + +#define STATE_UNKNOWN 0 +#define STATE_GET_RANDOM 1 +#define STATE_ERASE 2 + +extern int tc_internal_state; +#ifndef __DECONST +#define __DECONST(type, var) ((type)(uintptr_t)(const void *)(var)) +#endif #define alloc_safe_mem(x) \ _alloc_safe_mem(x, __FILE__, __LINE__) +#define strdup_safe_mem(x) \ + _strdup_safe_mem(x, __FILE__, __LINE__) + #define free_safe_mem(x) \ - _free_safe_mem(x, __FILE__, __LINE__) + _free_safe_mem(__DECONST(void *, x), __FILE__, __LINE__) #define __unused __attribute__((__unused__)) + +#endif diff --git a/lib/libtcplay/tcplay.map b/lib/libtcplay/tcplay.map index d63cd0b177..6f8f377b7d 100644 --- a/lib/libtcplay/tcplay.map +++ b/lib/libtcplay/tcplay.map @@ -2,13 +2,14 @@ global: tc_api_init; tc_api_uninit; - tc_api_create_volume; - tc_api_map_volume; - tc_api_unmap_volume; - tc_api_check_cipher; - tc_api_check_prf_hash; - tc_api_get_error_msg; - tc_api_get_summary; + tc_api_has; + tc_api_cipher_iterate; + tc_api_prf_iterate; + tc_api_task_init; + tc_api_task_uninit; + tc_api_task_set; + tc_api_task_do; + tc_api_task_info_get; local: *; }; diff --git a/lib/libtcplay/tcplay_api.c b/lib/libtcplay/tcplay_api.c index c83c295f5b..1d7cba648f 100644 --- a/lib/libtcplay/tcplay_api.c +++ b/lib/libtcplay/tcplay_api.c @@ -30,9 +30,14 @@ #include #include #include +#include +#include +#include #include "tcplay.h" #include "tcplay_api.h" +#include "tcplay_api_internal.h" + int tc_api_init(int verbose) @@ -54,145 +59,745 @@ tc_api_uninit(void) return TC_OK; } -const char * -tc_api_get_error_msg(void) + +static const char *_caps[] = { + "trim", + NULL +}; + +int +tc_api_has(const char *feature) { - return tc_internal_log_buffer; + const char *cap; + int i; + + for (cap = _caps[0], i = 0; cap != NULL; cap = _caps[++i]) { + if ((strcmp(cap, feature)) == 0) + return TC_OK; + } + + return TC_ERR_UNIMPL; } -const char * -tc_api_get_summary(void) +int +tc_api_cipher_iterate(tc_api_cipher_iterator_fn fn, void *priv) { - if (summary_fn != NULL) { - summary_fn(); - return tc_internal_log_buffer; + int i; + struct tc_cipher_chain *chain; + int klen; + int length; + char buf[1024]; + + if (fn == NULL) { + errno = EFAULT; + return TC_ERR; + } + + for (i = 0, chain = tc_cipher_chains[0]; chain != NULL; + chain = tc_cipher_chains[++i]) { + tc_cipher_chain_sprint(buf, sizeof(buf), chain); + klen = tc_cipher_chain_klen(chain); + length = tc_cipher_chain_length(chain); + if ((fn(priv, buf, klen, length)) < 0) + break; } - return NULL; + return TC_OK; } int -tc_api_create_volume(tc_api_opts *api_opts) +tc_api_prf_iterate(tc_api_prf_iterator_fn fn, void *priv) { - int nkeyfiles, n_hkeyfiles = 0; - int create_hidden; - int err; + int i; - if ((api_opts == NULL) || - (api_opts->tc_device == NULL)) { + if (fn == NULL) { errno = EFAULT; return TC_ERR; } - if ((err = tc_api_check_cipher(api_opts)) != TC_OK) - return TC_ERR; + /* start at 1 due to RIPEMD weirdness... */ + for (i = 1; pbkdf_prf_algos[i].name != NULL; i++) { + if ((fn(priv, pbkdf_prf_algos[i].name)) < 0) + break; + } - if ((err = tc_api_check_prf_hash(api_opts)) != TC_OK) - return TC_ERR; + return TC_OK; +} - for (nkeyfiles = 0; (nkeyfiles < MAX_KEYFILES) && - (api_opts->tc_keyfiles != NULL) && - (api_opts->tc_keyfiles[nkeyfiles] != NULL); nkeyfiles++) - ; - create_hidden = 0; +const char * +tc_api_task_get_error(tc_api_task task __unused) +{ + return tc_internal_log_buffer; +} + - if (api_opts->tc_size_hidden_in_bytes > 0) { - create_hidden = 1; - for (n_hkeyfiles = 0; (n_hkeyfiles < MAX_KEYFILES) && - (api_opts->tc_keyfiles_hidden != NULL) && - (api_opts->tc_keyfiles_hidden[n_hkeyfiles] != NULL); - n_hkeyfiles++) - ; +#define _match(k, v) (strcmp(k, v) == 0) + +tc_api_task +tc_api_task_init(const char *op) +{ + tc_api_task task = NULL; + int fail = 1; + + if ((task = alloc_safe_mem(sizeof(*task))) == NULL) { + errno = ENOMEM; + goto out; } - err = create_volume(api_opts->tc_device, create_hidden, - api_opts->tc_keyfiles, nkeyfiles, - api_opts->tc_keyfiles_hidden, n_hkeyfiles, - check_prf_algo(api_opts->tc_prf_hash, 1), - check_cipher_chain(api_opts->tc_cipher, 1), - check_prf_algo(api_opts->tc_prf_hash_hidden, 1), - check_cipher_chain(api_opts->tc_cipher_hidden, 1), - api_opts->tc_passphrase, api_opts->tc_passphrase_hidden, - api_opts->tc_size_hidden_in_bytes, 0 /* non-interactive */); + if ((task->opts = opts_init()) == NULL) { + errno = ENOMEM; + goto out; + } - return (err) ? TC_ERR : TC_OK; + if (_match(op, "create")) { + task->op = TC_OP_CREATE; + } else if (_match(op, "map")) { + task->op = TC_OP_MAP; + } else if (_match(op, "unmap")) { + task->op = TC_OP_UNMAP; + } else if (_match(op, "info")) { + task->op = TC_OP_INFO; + } else if (_match(op, "info_mapped")) { + task->op = TC_OP_INFO_MAPPED; + } else if (_match(op, "modify")) { + task->op = TC_OP_MODIFY; + } else if (_match(op, "restore")) { + task->op = TC_OP_RESTORE; + } else { + errno = EINVAL; + goto out; + } + + fail = 0; + +out: + if (fail && task != NULL) { + if (task->opts != NULL) + opts_free(task->opts); + free_safe_mem(task); + } + + return fail ? NULL : task; } int -tc_api_map_volume(tc_api_opts *api_opts) +tc_api_task_uninit(tc_api_task task) { - int nkeyfiles; - int err; + if (task->last_info != NULL) + free_info(task->last_info); + opts_free(task->opts); + free_safe_mem(task); - if ((api_opts == NULL) || - (api_opts->tc_device == NULL)) { + return TC_OK; +} + + +#define _set_str(k) \ + do { \ + if ((opts->k = strdup_safe_mem(s)) == NULL) { \ + errno = ENOMEM; \ + r = TC_ERR; \ + goto out; \ + } \ + } while (0) + +#define _clr_str(k) \ + do { \ + if (opts->k) \ + free_safe_mem(opts->k); \ + opts->k = NULL; \ + } while (0) + +int +tc_api_task_set(tc_api_task task, const char *key, ...) +{ + struct tcplay_opts *opts; + va_list ap; + const char *s; + int64_t i64; + int i; + tc_api_state_change_fn sc_fn; + void *vp; + int r = TC_OK; + + if (task == NULL || ((opts = task->opts) == NULL)) { errno = EFAULT; return TC_ERR; } - for (nkeyfiles = 0; (nkeyfiles < MAX_KEYFILES) && - (api_opts->tc_keyfiles != NULL) && - (api_opts->tc_keyfiles[nkeyfiles] != NULL); nkeyfiles++) - ; + va_start(ap, key); + + if (_match(key, "interactive")) { + i = va_arg(ap, int); + opts->interactive = i; + } else if (_match(key, "weak_keys_and_salt")) { + i = va_arg(ap, int); + opts->weak_keys_and_salt = i; + } else if (_match(key, "secure_erase")) { + i = va_arg(ap, int); + opts->secure_erase = i; + } else if (_match(key, "protect_hidden")) { + i = va_arg(ap, int); + opts->protect_hidden = i; + } else if (_match(key, "fde")) { + i = va_arg(ap, int); + if (i) + opts->flags |= TC_FLAG_FDE; + else + opts->flags &= ~TC_FLAG_FDE; + } else if (_match(key, "use_backup_header")) { + i = va_arg(ap, int); + if (i) + opts->flags |= TC_FLAG_BACKUP; + else + opts->flags &= ~TC_FLAG_BACKUP; + } else if (_match(key, "allow_trim")) { + i = va_arg(ap, int); + if (i) + opts->flags |= TC_FLAG_ALLOW_TRIM; + else + opts->flags &= ~TC_FLAG_ALLOW_TRIM; + } else if (_match(key, "hidden_size_bytes")) { + i64 = va_arg(ap, int64_t); + opts->hidden_size_bytes = (disksz_t)i64; + opts->hidden = (i64 > 0); + } else if (_match(key, "retries")) { + i = va_arg(ap, int); + opts->retries = i; + } else if (_match(key, "timeout")) { + i = va_arg(ap, int); + opts->timeout = (time_t)i; + } else if (_match(key, "save_header_to_file")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(hdr_file_out); + opts->flags |= TC_FLAG_SAVE_TO_FILE; + } else { + _clr_str(hdr_file_out); + opts->flags &= ~TC_FLAG_SAVE_TO_FILE; + } + } else if (_match(key, "header_from_file")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(hdr_file_in); + opts->flags |= TC_FLAG_HDR_FROM_FILE; + } else { + _clr_str(hdr_file_in); + opts->flags &= ~TC_FLAG_HDR_FROM_FILE; + } + } else if (_match(key, "hidden_header_from_file")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(h_hdr_file_in); + opts->flags |= TC_FLAG_H_HDR_FROM_FILE; + } else { + _clr_str(h_hdr_file_in); + opts->flags &= ~TC_FLAG_H_HDR_FROM_FILE; + } + } else if (_match(key, "sys")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(sys_dev); + opts->flags |= TC_FLAG_SYS; + } else { + _clr_str(sys_dev); + opts->flags &= ~TC_FLAG_SYS; + } + } else if (_match(key, "passphrase")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(passphrase); + } else { + _clr_str(passphrase); + } + } else if (_match(key, "h_passphrase")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(h_passphrase); + } else { + _clr_str(h_passphrase); + } + } else if (_match(key, "new_passphrase")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(new_passphrase); + } else { + _clr_str(new_passphrase); + } + } else if (_match(key, "dev")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(dev); + } else { + _clr_str(dev); + } + } else if (_match(key, "map_name")) { + s = va_arg(ap, const char *); + if (s != NULL) { + _set_str(map_name); + } else { + _clr_str(map_name); + } + } else if (_match(key, "keyfiles")) { + s = va_arg(ap, const char *); + if (s != NULL) { + opts_add_keyfile(opts, s); + } else { + opts_clear_keyfile(opts); + } + } else if (_match(key, "h_keyfiles")) { + s = va_arg(ap, const char *); + if (s != NULL) { + opts_add_keyfile_hidden(opts, s); + } else { + opts_clear_keyfile_hidden(opts); + } + } else if (_match(key, "new_keyfiles")) { + s = va_arg(ap, const char *); + if (s != NULL) { + opts_add_keyfile_new(opts, s); + } else { + opts_clear_keyfile_new(opts); + } + } else if (_match(key, "prf_algo")) { + s = va_arg(ap, const char *); + if (s != NULL) { + if ((opts->prf_algo = check_prf_algo(s, 1)) == NULL) { + errno = ENOENT; + r = TC_ERR; + goto out; + } + } else { + opts->prf_algo = NULL; + } + } else if (_match(key, "h_prf_algo")) { + s = va_arg(ap, const char *); + if (s != NULL) { + if ((opts->h_prf_algo = check_prf_algo(s, 1)) == NULL) { + errno = ENOENT; + r = TC_ERR; + goto out; + } + } else { + opts->h_prf_algo = NULL; + } + } else if (_match(key, "new_prf_algo")) { + s = va_arg(ap, const char *); + if (s != NULL) { + if ((opts->new_prf_algo = check_prf_algo(s, 1)) == NULL) { + errno = ENOENT; + r = TC_ERR; + goto out; + } + } else { + opts->new_prf_algo = NULL; + } + } else if (_match(key, "cipher_chain")) { + s = va_arg(ap, const char *); + if (s != NULL) { + if ((opts->cipher_chain = check_cipher_chain(s, 1)) == NULL) { + errno = ENOENT; + r = TC_ERR; + goto out; + } + } else { + opts->cipher_chain = NULL; + } + } else if (_match(key, "h_cipher_chain")) { + s = va_arg(ap, const char *); + if (s != NULL) { + if ((opts->h_cipher_chain = check_cipher_chain(s, 1)) == NULL) { + errno = ENOENT; + r = TC_ERR; + goto out; + } + } else { + opts->h_cipher_chain = NULL; + } + } else if (_match(key, "state_change_fn")) { + sc_fn = va_arg(ap, tc_api_state_change_fn); + opts->state_change_fn = sc_fn; + vp = va_arg(ap, void *); + opts->api_ctx = vp; + } else { + r = TC_ERR_UNIMPL; + } - err = map_volume(api_opts->tc_map_name, api_opts->tc_device, - /* sflag */ 0, /* sys_dev */ NULL, - /* protect_hidden */ 0, api_opts->tc_keyfiles, nkeyfiles, - /* h_keyfiles[] */ NULL, /* n_hkeyfiles */ 0, - api_opts->tc_passphrase, /* passphrase_hidden */ NULL, - api_opts->tc_interactive_prompt, api_opts->tc_password_retries, - (time_t)api_opts->tc_prompt_timeout); +out: + va_end(ap); - return (err) ? TC_ERR : TC_OK; + return r; } +#define _not_null(x) \ + if (opts->x == NULL) { \ + return -1; \ + } + +#define _null(x) \ + if (opts->x != NULL) { \ + return -1; \ + } + +#define _zero(x) \ + if (opts->x != 0) { \ + return -1; \ + } + +#define _not_set(x) \ + if (TC_FLAG_SET(opts->flags, x)) { \ + return -1; \ + } + +static int -tc_api_unmap_volume(tc_api_opts *api_opts) +_opts_check_create(struct tcplay_opts *opts) { - int err; + _not_null(dev); + _not_set(SYS); + _not_set(FDE); + _not_set(BACKUP); + _not_set(ONLY_RESTORE); + _not_set(ALLOW_TRIM); + _not_set(SAVE_TO_FILE); + _not_set(HDR_FROM_FILE); + _not_set(H_HDR_FROM_FILE); + + _null(map_name); + _zero(protect_hidden); + _null(new_passphrase); + _null(new_prf_algo); + _zero(n_newkeyfiles); + + if (opts->hidden_size_bytes && !opts->hidden) { + return -1; + } - if ((api_opts == NULL) || - (api_opts->tc_map_name == NULL)) { - errno = EFAULT; - return TC_ERR; + return 0; +} + +static +int +_opts_check_map(struct tcplay_opts *opts) +{ + _not_null(dev); + _not_null(map_name); + _not_set(ONLY_RESTORE); + _not_set(SAVE_TO_FILE); + _zero(hidden); + _zero(hidden_size_bytes); + _null(new_passphrase); + _null(new_prf_algo); + _zero(n_newkeyfiles); + _null(prf_algo); + _null(h_prf_algo); + _null(cipher_chain); + _null(h_cipher_chain); + + if (!opts->protect_hidden) { + _zero(n_hkeyfiles); + //_null(h_passphrase); } - err = dm_teardown(api_opts->tc_map_name, api_opts->tc_device); - return (err) ? TC_ERR : TC_OK; + return 0; } +static int -tc_api_check_cipher(tc_api_opts *api_opts) +_opts_check_unmap(struct tcplay_opts *opts) { - struct tc_cipher_chain *chain; + _not_null(map_name); + /* XXX: _not_null(dev); ? */ + _zero(nkeyfiles); + _zero(n_hkeyfiles); + _null(prf_algo); + _null(cipher_chain); + _null(h_prf_algo); + _null(h_cipher_chain); + _null(passphrase); + _null(h_passphrase); + _zero(hidden); + _zero(protect_hidden); + _null(new_prf_algo); + _null(new_passphrase); + _zero(n_newkeyfiles); + _not_set(SYS); + _not_set(FDE); + _not_set(BACKUP); + _not_set(ONLY_RESTORE); + _not_set(ALLOW_TRIM); + _not_set(SAVE_TO_FILE); + _not_set(HDR_FROM_FILE); + _not_set(H_HDR_FROM_FILE); + + return 0; +} - if (api_opts == NULL || api_opts->tc_cipher == NULL) { - errno = EFAULT; - return TC_ERR; +static +int +_opts_check_info(struct tcplay_opts *opts) +{ + _not_null(dev); + _null(map_name); + _not_set(ONLY_RESTORE); + _not_set(SAVE_TO_FILE); + _zero(hidden); + _zero(hidden_size_bytes); + _null(new_passphrase); + _null(new_prf_algo); + _zero(n_newkeyfiles); + _null(prf_algo); + _null(h_prf_algo); + _null(cipher_chain); + _null(h_cipher_chain); + + if (!opts->protect_hidden) { + _zero(n_hkeyfiles); + //_null(h_passphrase); } - if ((chain = check_cipher_chain(api_opts->tc_cipher, 1)) != NULL) - return TC_OK; + return 0; +} + +static +int +_opts_check_info_mapped(struct tcplay_opts *opts) +{ + _not_null(map_name); + /* XXX: _not_null(dev); ? */ + _zero(nkeyfiles); + _zero(n_hkeyfiles); + _null(prf_algo); + _null(cipher_chain); + _null(h_prf_algo); + _null(h_cipher_chain); + _null(passphrase); + _null(h_passphrase); + _zero(hidden); + _zero(protect_hidden); + _null(new_prf_algo); + _null(new_passphrase); + _zero(n_newkeyfiles); + _not_set(SYS); + _not_set(FDE); + _not_set(BACKUP); + _not_set(ONLY_RESTORE); + _not_set(ALLOW_TRIM); + _not_set(SAVE_TO_FILE); + _not_set(HDR_FROM_FILE); + _not_set(H_HDR_FROM_FILE); + + return 0; +} + +static +int +_opts_check_modify(struct tcplay_opts *opts) +{ + _not_null(dev); + _null(map_name); + _zero(hidden); + _zero(hidden_size_bytes); + _null(prf_algo); + _null(h_prf_algo); + _null(cipher_chain); + _null(h_cipher_chain); + + if (!opts->protect_hidden) { + _zero(n_hkeyfiles); + _null(h_passphrase); + } - errno = ENOENT; - return TC_ERR; + return 0; } + +static int -tc_api_check_prf_hash(tc_api_opts *api_opts) +_opts_check_restore(struct tcplay_opts *opts) { - struct pbkdf_prf_algo *prf_hash; + if ((_opts_check_modify(opts)) < 0) + return -1; - if (api_opts == NULL || api_opts->tc_prf_hash == NULL) { + _null(new_prf_algo); + _zero(n_newkeyfiles); + _null(new_passphrase); + + return 0; +} + +int +tc_api_task_do(tc_api_task task) +{ + struct tcplay_opts *opts; + int r = TC_OK; + + if (task == NULL || ((opts = task->opts) == NULL)) { errno = EFAULT; return TC_ERR; } - if ((prf_hash = check_prf_algo(api_opts->tc_prf_hash, 1)) != NULL) - return TC_OK; + if (task->last_info != NULL) { + free_info(task->last_info); + } - errno = ENOENT; - return TC_ERR; + switch (task->op) { + case TC_OP_CREATE: + if ((r = _opts_check_create(task->opts)) != 0) { + errno = EINVAL; + return r; + } + r = create_volume(opts); + break; + + case TC_OP_MAP: + if ((r = _opts_check_map(task->opts)) != 0) { + errno = EINVAL; + return r; + } + r = map_volume(opts); + break; + + case TC_OP_UNMAP: + if ((r = _opts_check_unmap(task->opts)) != 0) { + errno = EINVAL; + return r; + } + r = dm_teardown(opts->map_name, opts->dev); + break; + + case TC_OP_INFO: + if ((r = _opts_check_info(task->opts)) != 0) { + errno = EINVAL; + return r; + } + if ((task->last_info = info_map_common(opts, NULL)) == NULL) { + r = TC_ERR; + } + break; + + case TC_OP_INFO_MAPPED: + if ((r = _opts_check_info_mapped(task->opts)) != 0) { + errno = EINVAL; + return r; + } + if ((task->last_info = dm_info_map(opts->map_name)) == NULL) { + r = TC_ERR; + } + break; + + case TC_OP_MODIFY: + if ((r = _opts_check_modify(task->opts)) != 0) { + errno = EINVAL; + return r; + } + r = modify_volume(opts); + break; + + case TC_OP_RESTORE: + if ((r = _opts_check_restore(task->opts)) != 0) { + errno = EINVAL; + return r; + } + opts->flags |= TC_FLAG_ONLY_RESTORE; + r = modify_volume(opts); + opts->flags &= ~TC_FLAG_ONLY_RESTORE; + break; + } + + return r; } + +int +tc_api_task_info_get(tc_api_task task, const char *key, ...) +{ + char buf[1024]; + va_list ap; + struct tcplay_info *info; + char *s; + int *ip; + int64_t *i64p; + int r = TC_OK; + size_t sz; + + if (task == NULL || ((info = task->last_info) == NULL)) { + errno = EFAULT; + return TC_ERR; + } + + va_start(ap, key); + sz = va_arg(ap, size_t); + if (sz < 1) { + errno = EINVAL; + r = TC_ERR; + goto out; + } + + if (_match(key, "device")) { + s = va_arg(ap, char *); + strncpy(s, info->dev, sz); + s[sz-1] = '\0'; + } else if (_match(key, "cipher")) { + s = va_arg(ap, char *); + tc_cipher_chain_sprint(buf, sizeof(buf), info->cipher_chain); + strncpy(s, buf, sz); + s[sz-1] = '\0'; + } else if (_match(key, "prf")) { + s = va_arg(ap, char *); + if (info->pbkdf_prf) + strncpy(s, info->pbkdf_prf->name, sz); + else + strncpy(s, "(unknown)", sz); + s[sz-1] = '\0'; + } else if (_match(key, "key_bits")) { + if (sz != sizeof(int)) { + errno = EFAULT; + r = TC_ERR; + goto out; + } + ip = va_arg(ap, int *); + *ip = 8*tc_cipher_chain_klen(info->cipher_chain); + } else if (_match(key, "size")) { + if (sz != sizeof(int64_t)) { + errno = EFAULT; + r = TC_ERR; + goto out; + } + i64p = va_arg(ap, int64_t *); + if (info->hdr) + *i64p = (int64_t)info->size * (int64_t)info->hdr->sec_sz; + else + *i64p = (int64_t)info->size * (int64_t)info->blk_sz; + } else if (_match(key, "iv_offset")) { + if (sz != sizeof(int64_t)) { + errno = EFAULT; + r = TC_ERR; + goto out; + } + i64p = va_arg(ap, int64_t *); + if (info->hdr) + *i64p = (int64_t)info->skip * (int64_t)info->hdr->sec_sz; + else + *i64p = (int64_t)info->skip * (int64_t)info->blk_sz; + } else if (_match(key, "block_offset")) { + if (sz != sizeof(int64_t)) { + errno = EFAULT; + r = TC_ERR; + goto out; + } + i64p = va_arg(ap, int64_t *); + if (info->hdr) + *i64p = (int64_t)info->offset * (int64_t)info->hdr->sec_sz; + else + *i64p = (int64_t)info->offset * (int64_t)info->blk_sz; + } else { + r = TC_ERR_UNIMPL; + } + +out: + va_end(ap); + + return r; +} diff --git a/lib/libtcplay/tcplay_api.h b/lib/libtcplay/tcplay_api.h index 4743643a1b..40a5ebc974 100644 --- a/lib/libtcplay/tcplay_api.h +++ b/lib/libtcplay/tcplay_api.h @@ -27,38 +27,49 @@ * SUCH DAMAGE. */ -#define TC_OK 0 -#define TC_ERR -1 - -typedef struct tc_api_opts { - /* Common fields */ - char *tc_device; - char *tc_passphrase; - const char **tc_keyfiles; - - /* Fields for mapping */ - char *tc_map_name; - int tc_password_retries; - int tc_interactive_prompt; - unsigned long tc_prompt_timeout; - - /* Fields for creation */ - char *tc_cipher; - char *tc_prf_hash; - char *tc_cipher_hidden; - char *tc_prf_hash_hidden; - size_t tc_size_hidden_in_bytes; - char *tc_passphrase_hidden; - const char **tc_keyfiles_hidden; -} tc_api_opts; +#ifndef _TCPLAY_API_H +#define _TCPLAY_API_H + +#include + +#define TC_OK 0 +#define TC_ERR -1 +#define TC_ERR_UNIMPL -255 + +#define TC_STATE_ENTER 1 +#define TC_STATE_EXIT 0 + +struct _tc_api_task; + +typedef struct _tc_api_task *tc_api_task; + +typedef int (*tc_api_cipher_iterator_fn)(void *, const char *, int /* klen */, int /* length */); +typedef int (*tc_api_prf_iterator_fn)(void *, const char *); +typedef int (*tc_api_state_change_fn)(void *, const char *, int); + +#ifdef __cplusplus +extern "C" { +#endif int tc_api_init(int verbose); int tc_api_uninit(void); -int tc_api_create_volume(tc_api_opts *api_opts); -int tc_api_map_volume(tc_api_opts *api_opts); -int tc_api_unmap_volume(tc_api_opts *api_opts); -int tc_api_check_cipher(tc_api_opts *api_opts); -int tc_api_check_prf_hash(tc_api_opts *api_opts); -const char *tc_api_get_error_msg(void); -const char *tc_api_get_summary(void); +int tc_api_has(const char *feature); + +int tc_api_cipher_iterate(tc_api_cipher_iterator_fn fn, void *priv); +int tc_api_prf_iterate(tc_api_prf_iterator_fn fn, void *priv); + + +tc_api_task tc_api_task_init(const char *op); +int tc_api_task_uninit(tc_api_task task); +int tc_api_task_set(tc_api_task task, const char *key, ...); +int tc_api_task_do(tc_api_task task); + +int tc_api_task_info_get(tc_api_task task, const char *key, ...); +const char *tc_api_task_get_error(tc_api_task task); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lib/libtcplay/tcplay_api.h b/lib/libtcplay/tcplay_api_internal.h similarity index 61% copy from lib/libtcplay/tcplay_api.h copy to lib/libtcplay/tcplay_api_internal.h index 4743643a1b..fb0717c695 100644 --- a/lib/libtcplay/tcplay_api.h +++ b/lib/libtcplay/tcplay_api_internal.h @@ -27,38 +27,26 @@ * SUCH DAMAGE. */ -#define TC_OK 0 -#define TC_ERR -1 +#ifndef _TCPLAY_API_INTERNAL_H +#define _TCPLAY_API_INTERNAL_H -typedef struct tc_api_opts { - /* Common fields */ - char *tc_device; - char *tc_passphrase; - const char **tc_keyfiles; +#include +#include "tcplay.h" - /* Fields for mapping */ - char *tc_map_name; - int tc_password_retries; - int tc_interactive_prompt; - unsigned long tc_prompt_timeout; +typedef enum tc_api_op { + TC_OP_CREATE, + TC_OP_MAP, + TC_OP_UNMAP, + TC_OP_INFO, + TC_OP_INFO_MAPPED, + TC_OP_MODIFY, + TC_OP_RESTORE +} tc_api_op; - /* Fields for creation */ - char *tc_cipher; - char *tc_prf_hash; - char *tc_cipher_hidden; - char *tc_prf_hash_hidden; - size_t tc_size_hidden_in_bytes; - char *tc_passphrase_hidden; - const char **tc_keyfiles_hidden; -} tc_api_opts; - -int tc_api_init(int verbose); -int tc_api_uninit(void); -int tc_api_create_volume(tc_api_opts *api_opts); -int tc_api_map_volume(tc_api_opts *api_opts); -int tc_api_unmap_volume(tc_api_opts *api_opts); -int tc_api_check_cipher(tc_api_opts *api_opts); -int tc_api_check_prf_hash(tc_api_opts *api_opts); -const char *tc_api_get_error_msg(void); -const char *tc_api_get_summary(void); +struct _tc_api_task { + tc_api_op op; + struct tcplay_opts *opts; + struct tcplay_info *last_info; +}; +#endif diff --git a/lib/libtcplay/tcplay_api_test.c b/lib/libtcplay/tcplay_api_test.c deleted file mode 100644 index 3be7ae6a8e..0000000000 --- a/lib/libtcplay/tcplay_api_test.c +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include -#include - -#include "tcplay_api.h" - -int -main(void) -{ - tc_api_opts api_opts; - int error; - - error = tc_api_init(/* verbose */1); - assert(error == 0); - - memset(&api_opts, 0, sizeof(api_opts)); - api_opts.tc_device = "/dev/vn1s0"; - api_opts.tc_passphrase = "apitest2"; - api_opts.tc_keyfiles = NULL; - api_opts.tc_keyfiles_hidden = NULL; - api_opts.tc_size_hidden_in_bytes = 12000*512; - api_opts.tc_passphrase_hidden = "apihidden"; - api_opts.tc_cipher = "AES-256-XTS,TWOFISH-256-XTS,SERPENT-256-XTS"; - api_opts.tc_cipher_hidden = "SERPENT-256-XTS,TWOFISH-256-XTS"; - api_opts.tc_prf_hash = "whirlpool"; - api_opts.tc_prf_hash_hidden = "RIPEMD160"; - - error = tc_api_create_volume(&api_opts); - if (error) - printf("API ERROR: %s\n", tc_api_get_error_msg()); - assert(error == 0); - - error = tc_api_uninit(); - assert(error == 0); - - error = tc_api_init(/*verbose */ 1); - assert(error == 0); - - api_opts.tc_passphrase = NULL; - api_opts.tc_map_name = "dragonfly-test"; - api_opts.tc_password_retries = 5; - api_opts.tc_interactive_prompt = 1; - api_opts.tc_prompt_timeout = 5; - error = tc_api_map_volume(&api_opts); - if (error) - printf("API MAP ERROR: %s\n", tc_api_get_error_msg()); - assert(error == 0); - - system("dmsetup ls"); - tc_api_unmap_volume(&api_opts); - - error = tc_api_uninit(); - assert(error == 0); - - return 0; -} - diff --git a/sbin/tcplay/Makefile b/sbin/tcplay/Makefile index de81a62b94..5ccca3077e 100644 --- a/sbin/tcplay/Makefile +++ b/sbin/tcplay/Makefile @@ -1,17 +1,23 @@ TCPLAY_DIR= ${.CURDIR}/../../lib/libtcplay +MAJ_VER= 2 +MIN_VER= 0 + PROG= tcplay MAN= tcplay.8 WARNS?= 6 -SRCS= main.c -SRCS+= tcplay.c crc32.c safe_mem.c io.c crypto-dev.c hdr.c -SRCS+= crypto.c generic_xts.c humanize.c pbkdf2-openssl.c +SRCS+= tcplay.c crc32.c safe_mem.c io.c hdr.c humanize.c +SRCS+= crypto.c generic_xts.c +SRCS+= crypto-dev.c pbkdf2-openssl.c +SRCS+= main.c LDADD= -lcrypto -ldm -lprop -lutil DPADD= ${LIBCRYPTO} ${LIBDM} ${LIBPROP} ${LIBUTIL} CFLAGS+= -I${TCPLAY_DIR} +CFLAGS+= -DMAJ_VER=${MAJ_VER} -DMIN_VER=${MIN_VER} +CFLAGS+= -D_FILE_OFFSET_BITS=64 .PATH: ${TCPLAY_DIR} diff --git a/sbin/tcplay/main.c b/sbin/tcplay/main.c index 4c085aeacc..1d15a7764e 100644 --- a/sbin/tcplay/main.c +++ b/sbin/tcplay/main.c @@ -43,6 +43,18 @@ #define SIGINFO SIGUSR1 #endif +#define FLAG_LONG_FDE 0xff01 +#define FLAG_LONG_USE_BACKUP 0xff02 +#define FLAG_LONG_MOD 0xff04 +#define FLAG_LONG_MOD_KF 0xff08 +#define FLAG_LONG_MOD_PRF 0xff10 +#define FLAG_LONG_MOD_NONE 0xff20 +#define FLAG_LONG_MOD_TO_FILE 0xff40 +#define FLAG_LONG_USE_HDR_FILE 0xfe01 +#define FLAG_LONG_USE_HHDR_FILE 0xfe02 +#define FLAG_LONG_NO_RETRIES 0xfabc + + static void sig_handler(int sig) @@ -56,12 +68,21 @@ void usage(void) { fprintf(stderr, - "usage: tcplay -c -d device [-g] [-a pbkdb_hash] [-b cipher]\n" + "usage: tcplay -c -d device [-g] [-z] [-w] [-a pbkdf_hash] [-b cipher]\n" " [-f keyfile_hidden] [-k keyfile] [-x pbkdf_hash] [-y cipher]\n" " tcplay -i -d device [-e] [-f keyfile_hidden] [-k keyfile]\n" - " [-s system_devcie]\n" + " [-s system_device] [--fde] [--use-backup]\n" + " [--use-hdr-file=hdr_file] [--use-hidden-hdr-file=hdr_file]\n" " tcplay -m mapping -d device [-e] [-f keyfile_hidden] [-k keyfile]\n" - " [-s system_device]\n" + " [-s system_device] [--fde] [--use-backup] [--allow-trim]\n" + " [--use-hdr-file=hdr_file] [--use-hidden-hdr-file=hdr_file]\n" + " tcplay --modify -d device [-k keyfile] [--new-keyfile=keyfile]\n" + " [--new-pbkdf-prf=pbkdf_hash] [-s system_device] [--fde]\n" + " [--use-backup] [--save-hdr-to-file=hdr_file] [-w]\n" + " [--use-hdr-file=hdr_file] [--use-hidden-hdr-file=hdr_file]\n" + " tcplay --modify -d device [-k keyfile] --restore-from-backup-hdr [-w]\n" + " tcplay -j mapping\n" + " tcplay -u mapping\n" " tcplay -h | -v\n" "\n" "Valid commands are:\n" @@ -71,9 +92,16 @@ usage(void) "\t Print help message and exit.\n" " -i, --info\n" "\t Gives information about the TC volume specified by -d or --device.\n" + " -j , --info-mapped=\n" + "\t Gives information about the mapped TC volume under the given mapping.\n" " -m , --map=\n" "\t Creates a dm-crypt mapping with the given name for the device\n" "\t specified by -d or --device.\n" + " -u , --unmap=\n" + "\t Removes a dm-crypt mapping with the given name.\n" + " --modify\n" + "\t Changes the volume's passphrase, keyfile and optionally the hashing\n" + "\t function used for the PBKDF password derivation.\n" " -v, --version\n" "\t Print version message and exit.\n" "\n" @@ -96,12 +124,67 @@ usage(void) "\t Specifies which cipher to use when creating a new hidden volume.\n" "\t By default, the same as for the outer volume will be used.\n" "\t To see valid options, specify '-y help'.\n" + " -z, --insecure-erase\n" + "\t Skips the erase of the disk. Possible security hazard.\n" + " -w, --weak-keys\n" + "\t Uses a weak source of entropy (urandom) for key material.\n" + "\t WARNING: This is a REALLY REALLY bad idea for anything but\n" + "\t testing.\n" + "\n" + "Valid options for --modify are:\n" + " --new-keyfile=\n" + "\t Specifies a key file to use for the password derivation, when\n" + "\t re-encrypting the header, can appear multiple times.\n" + " --new-pbkdf-prf=\n" + "\t Specifies which hashing function to use for the PBKDF password\n" + "\t derivation when re-encrypting the header.\n" + "\t To see valid options, specify '-a help'.\n" + " -s , --system-encryption=\n" + "\t Specifies that the disk (e.g. /dev/da0) is using system encryption.\n" + " --fde\n" + "\t Specifies that the disk (e.g. /dev/da0) is using full disk encryption.\n" + " --use-backup\n" + "\t Uses the backup headers (at the end of the volume) instead of the\n" + "\t primary headers. Both normal and backup headers will be modified!\n" + "\t This is useful when your primary headers have been corrupted.\n" + " --use-hdr-file=
\n" + "\t Use the header in the specified file instead of the main header on the\n" + "\t disk as source for the modify operation.\n" + " --use-hidden-hdr-file=
\n" + "\t Use the header in the specified file instead of the hidden header on the\n" + "\t disk as source for the modify operation.\n" + " --restore-from-backup-hdr\n" + "\t Implies --use-backup, no new PBKDF hashing function, no new keyfiles\n" + "\t and no new passphrase.\n" + "\t In other words, this will simply restore both headers from the backup\n" + "\t header. This option cannot be used to restore from a backup header file.\n" + " -w, --weak-keys\n" + "\t Uses a weak source of entropy (urandom) for salt material. The\n" + "\t key material is not affected, as the master keys are kept intact.\n" + "\t WARNING: This is a bad idea for anything but testing.\n" + " --save-hdr-backup=
\n" + "\t Saves the modified header in the specified file instead of updating\n" + "\t the header files on disk.\n" "\n" "Valid options for --info and --map are:\n" " -e, --protect-hidden\n" "\t Protect a hidden volume when mounting the outer volume.\n" " -s , --system-encryption=\n" "\t Specifies that the disk (e.g. /dev/da0) is using system encryption.\n" + " -t, --allow-trim\n" + "\t Allow discards (TRIM command) on mapped volume.\n" + " --fde\n" + "\t Specifies that the disk (e.g. /dev/da0) is using full disk encryption.\n" + " --use-backup\n" + "\t Uses the backup headers (at the end of the volume) instead of the\n" + "\t primary headers.\n" + "\t This is useful when your primary headers have been corrupted.\n" + " --use-hdr-file=
\n" + "\t Use the header in the specified file instead of the main header on the\n" + "\t disk.\n" + " --use-hidden-hdr-file=
\n" + "\t Use the header in the specified file instead of the hidden header on the\n" + "\t disk.\n" "\n" "Valid options common to all commands are:\n" " -d , --device=\n" @@ -115,7 +198,7 @@ usage(void) "\t multiple times.\n" ); - exit(1); + exit(EXIT_FAILURE); } static struct option longopts[] = { @@ -126,65 +209,85 @@ static struct option longopts[] = { { "pbkdf-prf", required_argument, NULL, 'a' }, { "pbkdf-prf-hidden", required_argument, NULL, 'x' }, { "info", no_argument, NULL, 'i' }, + { "info-mapped", required_argument, NULL, 'j' }, { "map", required_argument, NULL, 'm' }, { "keyfile", required_argument, NULL, 'k' }, { "keyfile-hidden", required_argument, NULL, 'f' }, { "protect-hidden", no_argument, NULL, 'e' }, { "device", required_argument, NULL, 'd' }, { "system-encryption", required_argument, NULL, 's' }, + { "allow-trim", no_argument, NULL, 't' }, + { "fde", no_argument, NULL, FLAG_LONG_FDE }, + { "use-backup", no_argument, NULL, FLAG_LONG_USE_BACKUP }, + { "use-hdr-file", required_argument, NULL, FLAG_LONG_USE_HDR_FILE }, + { "use-hidden-hdr-file",required_argument, NULL, FLAG_LONG_USE_HHDR_FILE }, + { "modify", no_argument, NULL, FLAG_LONG_MOD }, + { "new-keyfile", required_argument, NULL, FLAG_LONG_MOD_KF }, + { "new-pbkdf-prf", required_argument, NULL, FLAG_LONG_MOD_PRF }, + { "restore-from-backup-hdr", no_argument, NULL, FLAG_LONG_MOD_NONE }, + { "save-hdr-backup", required_argument, NULL, FLAG_LONG_MOD_TO_FILE }, + { "unmap", required_argument, NULL, 'u' }, { "version", no_argument, NULL, 'v' }, + { "weak-keys", no_argument, NULL, 'w' }, + { "insecure-erase", no_argument, NULL, 'z' }, { "help", no_argument, NULL, 'h' }, + { "no-retries", no_argument, NULL, FLAG_LONG_NO_RETRIES }, { NULL, 0, NULL, 0 }, }; +#define _set_str_opt(opt) \ + do { \ + if ((opts->opt = strdup_safe_mem(optarg)) == NULL) { \ + fprintf(stderr, "Could not allocate safe mem.\n"); \ + exit(EXIT_FAILURE); \ + } \ + } while(0) + int main(int argc, char *argv[]) { - const char *dev = NULL, *sys_dev = NULL, *map_name = NULL; - const char *keyfiles[MAX_KEYFILES]; - const char *h_keyfiles[MAX_KEYFILES]; - int nkeyfiles; - int n_hkeyfiles; + struct tcplay_opts *opts; int ch, error; - int sflag = 0, info_vol = 0, map_vol = 0, protect_hidden = 0, - create_vol = 0, contain_hidden = 0; - struct pbkdf_prf_algo *prf = NULL; - struct tc_cipher_chain *cipher_chain = NULL; - struct pbkdf_prf_algo *h_prf = NULL; - struct tc_cipher_chain *h_cipher_chain = NULL; + int info_vol = 0, map_vol = 0, + unmap_vol = 0, info_map = 0, + create_vol = 0, modify_vol = 0; if ((error = tc_play_init()) != 0) { fprintf(stderr, "Initialization failed, exiting."); - exit(1); + exit(EXIT_FAILURE); } atexit(check_and_purge_safe_mem); signal(SIGUSR1, sig_handler); signal(SIGINFO, sig_handler); - nkeyfiles = 0; - n_hkeyfiles = 0; + if ((opts = opts_init()) == NULL) { + fprintf(stderr, "Initialization failed (opts), exiting."); + exit(EXIT_FAILURE); + } + + opts->interactive = 1; - while ((ch = getopt_long(argc, argv, "a:b:cd:ef:ghik:m:s:vx:y:", + while ((ch = getopt_long(argc, argv, "a:b:cd:ef:ghij:k:m:s:tu:vwx:y:z", longopts, NULL)) != -1) { switch(ch) { case 'a': - if (prf != NULL) + if (opts->prf_algo != NULL) usage(); - if ((prf = check_prf_algo(optarg, 0)) == NULL) { + if ((opts->prf_algo = check_prf_algo(optarg, 0)) == NULL) { if (strcmp(optarg, "help") == 0) - exit(0); + exit(EXIT_SUCCESS); else usage(); /* NOT REACHED */ } break; case 'b': - if (cipher_chain != NULL) + if (opts->cipher_chain != NULL) usage(); - if ((cipher_chain = check_cipher_chain(optarg, 0)) == NULL) { + if ((opts->cipher_chain = check_cipher_chain(optarg, 0)) == NULL) { if (strcmp(optarg, "help") == 0) - exit(0); + exit(EXIT_SUCCESS); else usage(); /* NOT REACHED */ @@ -194,57 +297,128 @@ main(int argc, char *argv[]) create_vol = 1; break; case 'd': - dev = optarg; + _set_str_opt(dev); break; case 'e': - protect_hidden = 1; + opts->protect_hidden = 1; break; case 'f': - h_keyfiles[n_hkeyfiles++] = optarg; + if ((error = opts_add_keyfile_hidden(opts, optarg)) != 0) { + fprintf(stderr, "Could not add keyfile: %s\n", optarg); + exit(EXIT_FAILURE); + } break; case 'g': - contain_hidden = 1; + opts->hidden = 1; break; case 'i': info_vol = 1; break; + case 'j': + info_map = 1; + _set_str_opt(map_name); + break; case 'k': - keyfiles[nkeyfiles++] = optarg; + if ((error = opts_add_keyfile(opts, optarg)) != 0) { + fprintf(stderr, "Could not add keyfile: %s\n", optarg); + exit(EXIT_FAILURE); + } break; case 'm': map_vol = 1; - map_name = optarg; + _set_str_opt(map_name); break; case 's': - sflag = 1; - sys_dev = optarg; + opts->flags |= TC_FLAG_SYS; + _set_str_opt(sys_dev); + break; + case 't': + opts->flags |= TC_FLAG_ALLOW_TRIM; + break; + case 'u': + unmap_vol = 1; + _set_str_opt(map_name); break; case 'v': printf("tcplay v%d.%d\n", MAJ_VER, MIN_VER); - exit(0); + exit(EXIT_SUCCESS); /* NOT REACHED */ + case 'w': + fprintf(stderr, "WARNING: Using urandom as source of " + "entropy for key material is a really bad idea.\n"); + opts->weak_keys_and_salt = 1; + break; case 'x': - if (h_prf != NULL) + if (opts->h_prf_algo != NULL) usage(); - if ((h_prf = check_prf_algo(optarg, 0)) == NULL) { + if ((opts->h_prf_algo = check_prf_algo(optarg, 0)) == NULL) { if (strcmp(optarg, "help") == 0) - exit(0); + exit(EXIT_SUCCESS); else usage(); /* NOT REACHED */ } break; case 'y': - if (h_cipher_chain != NULL) + if (opts->h_cipher_chain != NULL) usage(); - if ((h_cipher_chain = check_cipher_chain(optarg, 0)) == NULL) { + if ((opts->h_cipher_chain = check_cipher_chain(optarg, 0)) == NULL) { if (strcmp(optarg, "help") == 0) - exit(0); + exit(EXIT_SUCCESS); else usage(); /* NOT REACHED */ } break; + case 'z': + opts->secure_erase = 0; + break; + case FLAG_LONG_FDE: + opts->flags |= TC_FLAG_FDE; + break; + case FLAG_LONG_USE_BACKUP: + opts->flags |= TC_FLAG_BACKUP; + break; + case FLAG_LONG_USE_HDR_FILE: + opts->flags |= TC_FLAG_HDR_FROM_FILE; + _set_str_opt(hdr_file_in); + break; + case FLAG_LONG_USE_HHDR_FILE: + opts->flags |= TC_FLAG_H_HDR_FROM_FILE; + _set_str_opt(h_hdr_file_in); + break; + case FLAG_LONG_MOD: + modify_vol = 1; + break; + case FLAG_LONG_MOD_KF: + if ((error = opts_add_keyfile_new(opts, optarg)) != 0) { + fprintf(stderr, "Could not add keyfile: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case FLAG_LONG_MOD_PRF: + if (opts->new_prf_algo != NULL) + usage(); + if ((opts->new_prf_algo = check_prf_algo(optarg, 0)) == NULL) { + if (strcmp(optarg, "help") == 0) + exit(EXIT_SUCCESS); + else + usage(); + /* NOT REACHED */ + } + break; + case FLAG_LONG_MOD_NONE: + opts->new_prf_algo = NULL; + opts->flags |= TC_FLAG_ONLY_RESTORE; + opts->flags |= TC_FLAG_BACKUP; + break; + case FLAG_LONG_MOD_TO_FILE: + opts->flags |= TC_FLAG_SAVE_TO_FILE; + _set_str_opt(hdr_file_out); + break; + case FLAG_LONG_NO_RETRIES: + opts->retries = 1; + break; case 'h': case '?': default: @@ -257,37 +431,44 @@ main(int argc, char *argv[]) argv += optind; /* Check arguments */ - if (!((map_vol || info_vol || create_vol) && dev != NULL) || - (map_vol && info_vol) || - (map_vol && create_vol) || - (create_vol && info_vol) || - (contain_hidden && !create_vol) || - (sflag && (sys_dev == NULL)) || - (map_vol && (map_name == NULL)) || - (!(protect_hidden || create_vol) && n_hkeyfiles > 0)) { + if (!(((map_vol || info_vol || create_vol || modify_vol) && opts->dev != NULL) || + ((unmap_vol || info_map) && opts->map_name != NULL)) || + (TC_FLAG_SET(opts->flags, SYS) && TC_FLAG_SET(opts->flags, FDE)) || + (map_vol + info_vol + create_vol + unmap_vol + info_map + modify_vol > 1) || + (opts->hidden && !create_vol) || + (TC_FLAG_SET(opts->flags, SYS) && (opts->sys_dev == NULL)) || + (TC_FLAG_SET(opts->flags, ONLY_RESTORE) && (opts->n_newkeyfiles > 0 || opts->new_prf_algo != NULL)) || + (TC_FLAG_SET(opts->flags, BACKUP) && (opts->sys_dev != NULL || TC_FLAG_SET(opts->flags, FDE))) || + (map_vol && (opts->map_name == NULL)) || + (unmap_vol && (opts->map_name == NULL)) || + (!modify_vol && opts->n_newkeyfiles > 0) || + (!modify_vol && opts->new_prf_algo != NULL) || + (!modify_vol && TC_FLAG_SET(opts->flags, ONLY_RESTORE)) || + (!modify_vol && TC_FLAG_SET(opts->flags, SAVE_TO_FILE)) || + (!(opts->protect_hidden || create_vol) && opts->n_hkeyfiles > 0)) { usage(); /* NOT REACHED */ } /* Create a new volume */ if (create_vol) { - error = create_volume(dev, contain_hidden, keyfiles, nkeyfiles, - h_keyfiles, n_hkeyfiles, prf, cipher_chain, h_prf, - h_cipher_chain, NULL, NULL, - 0, 1 /* interactive */); + error = create_volume(opts); if (error) { - tc_log(1, "could not create new volume on %s\n", dev); + tc_log(1, "could not create new volume on %s\n", opts->dev); } + } else if (info_map) { + error = info_mapped_volume(opts); } else if (info_vol) { - error = info_volume(dev, sflag, sys_dev, protect_hidden, - keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, NULL, NULL, - 1 /* interactive */, DEFAULT_RETRIES, 0); + error = info_volume(opts); } else if (map_vol) { - error = map_volume(map_name, - dev, sflag, sys_dev, protect_hidden, - keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, NULL, NULL, - 1 /* interactive */, DEFAULT_RETRIES, 0); + error = map_volume(opts); + } else if (unmap_vol) { + error = dm_teardown(opts->map_name, NULL); + } else if (modify_vol) { + error = modify_volume(opts); } return error; } + +#undef _set_str_opt diff --git a/sbin/tcplay/tcplay.8 b/sbin/tcplay/tcplay.8 index 54abdb3fdb..ee357f8b57 100644 --- a/sbin/tcplay/tcplay.8 +++ b/sbin/tcplay/tcplay.8 @@ -29,7 +29,7 @@ .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 5, 2011 +.Dd December 08, 2013 .Dt TCPLAY 8 .Os .Sh NAME @@ -40,6 +40,8 @@ .Fl c .Fl d Ar device .Op Fl g +.Op Fl z +.Op Fl w .Op Fl a Ar pbkdf_hash .Op Fl b Ar cipher .Op Fl f Ar keyfile_hidden @@ -53,6 +55,12 @@ .Op Fl f Ar keyfile_hidden .Op Fl k Ar keyfile .Op Fl s Ar system_device +.Op Fl -fde +.Op Fl -use-backup +.Op Fl -use-hdr-file Ar hdr_file +.Op Fl -use-hidden-hdr-file Ar hdr_file +.Nm +.Fl j Ar mapping .Nm .Fl m Ar mapping .Fl d Ar device @@ -60,6 +68,32 @@ .Op Fl f Ar keyfile_hidden .Op Fl k Ar keyfile .Op Fl s Ar system_device +.Op Fl t +.Op Fl -fde +.Op Fl -use-backup +.Op Fl -use-hdr-file Ar hdr_file +.Op Fl -use-hidden-hdr-file Ar hdr_file +.Nm +.Fl -modify +.Fl d Ar device +.Op Fl k Ar keyfile +.Op Fl -new-keyfile Ar new_keyfile +.Op Fl -new-pbkdf-prf Ar pbkdf_hash +.Op Fl s Ar system_device +.Op Fl -fde +.Op Fl -use-backup +.Op Fl -use-hdr-file Ar hdr_file +.Op Fl -use-hidden-hdr-file Ar hdr_file +.Op Fl -save-hdr-backup Ar hdr_file +.Op Fl w +.Nm +.Fl -modify +.Fl d Ar device +.Op Fl k Ar keyfile +.Fl -restore-from-backup-hdr +.Op Fl w +.Nm +.Fl u Ar mapping .Nm .Fl h | v .Sh DESCRIPTION @@ -79,6 +113,16 @@ Print help message and exit. .It Fl i , Fl -info Print out information about the encrypted device specified by .Fl -device . +.It Fl j Ar mapping , Fl -info-mapped Ns = Ns Ar mapping +Print out information about the mapped tcplay volume specified +by +.Ar mapping . +Information such as key CRC and the PBKDF2 PRF is not available +via this command. +.It Fl -modify +Modify the volume header. +This mode allows changing passphrase, keyfiles, PBKDF2 PRF as +well as restoring from a backup header. .It Fl m Ar mapping , Fl -map Ns = Ns Ar mapping Map the encrypted TrueCrypt volume on the device specified by .Fl -device @@ -86,6 +130,19 @@ as a .Xr dm 4 mapping called .Ar mapping . +The +.Ar mapping +argument should not contain any spaces or special characters. +.It Fl u Ar mapping , Fl -unmap Ns = Ns Ar mapping +Removes (unmaps) the +.Xr dm 4 +mapping specified by +.Ar mapping +as well as any related cascade mappings. +If you mapped a volume using full disk encryption and created +mapping for individual partitions using +.Xr kpartx 8 , +you must remove these prior to unmapping the volume. .It Fl v, Fl -version Print version message and exit. .El @@ -107,6 +164,9 @@ If you only intend to map a hidden volume, the option has to be used. This option can appear multiple times; if so, multiple keyfiles will be used. +This option is not valid in the +.Fl -modify +mode. .It Fl k Ar keyfile , Fl -keyfile Ns = Ns Ar keyfile Specifies a .Ar keyfile @@ -137,6 +197,12 @@ volume are those specified by .Fl -keyfile-hidden . The user will be prompted for the size of the hidden volume interactively. +.It Fl w, Fl -weak-keys +Use +.Xr urandom 4 +for key material instead of a strong entropy source. +This is in general a really bad idea and should only be used +for testing. .It Fl x Ar pbkdf_hash , Fl -pbkdf-prf-hidden Ns = Ns Ar pbkdf_hash Specifies which hash algorithm to use for the PBKDF2 password derivation for the hidden volume. @@ -155,12 +221,16 @@ If no cipher is specified, the same as for the outer volume will be used. To see which algorithms are supported, specify .Fl -cipher-hidden Ns = Ns Cm help . +.It Fl z, Fl -insecure-erase +Skips the secure erase of the disk. +Use this option carefully as it is a security risk! .El .Pp Additional options for the -.Fl -info -and +.Fl -info , .Fl -map +and +.Fl -modify commands are: .Bl -tag -width indent .It Fl e, Fl -protect-hidden @@ -169,18 +239,115 @@ its reported size will be adjusted accordingly to the size of the hidden volume contained in it. Both the hidden volume and outer volume passphrase and keyfiles will be required. +This option only applies to the +.Fl -info +and +.Fl -map +commands. .It Fl s Ar system_device , Fl -system-encryption Ns = Ns Ar system_device This option is required if you are attempting to access a device that uses system encryption, for example an encrypted .Tn Windows system partition. +It does not apply to disks using full disk encryption. The .Fl -device option will point at the actual encrypted partition, while the .Ar system_device argument will point to the parent device (i.e.\& underlying physical disk) of the encrypted partition. +.It Fl -fde +This option is intended to be used with disks using full disk encryption (FDE). +When a disk has been encrypted using TrueCrypt's FDE, the complete disk +is encrypted except for the first 63 sectors. +The +.Fl -device +option should point to the whole disk device, not to any particular +partition. +The resultant mapping will cover the whole disk, and will not appear as +separate partitions. +To access individual partitions after mapping, +.Xr kpartx 8 +can be used. +.It Fl -use-backup +This option is intended to be used when the primary headers of a volume +have been corrupted. +This option will force +.Nm +to use the backup headers, which are located at the end of the device, +to access the volume. +.El +.Pp +Additional options only for the +.Fl -map +command are: +.Bl -tag -width indent +.It Fl t , Fl -allow-trim +This option enables TRIM (discard) support on the mapped volume. +.El +.Pp +Additional options only for the +.Fl -modify +command are: +.Bl -tag -width indent +.It Fl -new-pbkdf-prf Ns = Ns Ar pbkdf_hash +Specifies which hash algorithm to use for the PBKDF2 password +derivation on reencrypting the volume header. +If this option is not specified, the reencrypted header will +use the current PRF. +To see which algorithms are supported, specify +.Fl -pbkdf-prf Ns = Ns Cm help . +.It Fl -new-keyfile Ns = Ns Ar keyfile +Specifies a +.Ar keyfile +to use in addition to the new passphrase on reencrypting the +volume header. +This option can appear multiple times; if so, multiple +keyfiles will be used. +.It Fl -restore-from-backup-hdr +If this option is specified, neither +.Fl -new-pbkdf-prf +nor +.Fl -new-keyfile +should be specified. +This option implies +.Fl -use-backup . +Use this option to restore the volume headers from the backup +header. .El +.Pp +Sending a +.Dv SIGINFO +or +.Dv SIGUSR1 +signal to a running +.Nm +process makes it print progress on slower tasks +such as gathering entropy or wiping the volume. +.Sh NOTES +TrueCrypt limits passphrases to 64 characters (including the terminating +null character). +To be compatible with it, +.Nm +does the same. +All passphrases (excluding keyfiles) are trimmed to 64 characters. +Similarly, keyfiles are limited to a size of 1 MB, but up to +256 keyfiles can be used. +.Sh PLAUSIBLE DENIABILITY +.Nm +offers plausible deniability. Hidden volumes are created within an outer +volume. +Which volume is accessed solely depends on the passphrase and keyfile(s) +used. +If the passphrase and keyfiles for the outer volume are specified, +no information about the existance of the hidden volume is exposed. +Without knowledge of the passphrase and keyfile(s) of the hidden volume +its existence remains unexposed. +The hidden volume can be protected when mapping the outer volume by +using the +.Fl -protect-hidden +option and specifying the passphrase and keyfiles for both the outer +and hidden volumes. .Sh EXAMPLES Create a new TrueCrypt volume on .Pa /dev/vn0 @@ -224,9 +391,142 @@ using the keyfile .Fl -device Ns = Ns Cm /dev/vn0 .Fl -keyfile Ns = Ns Cm hidden.key .Ed +.Pp +Map and mount the volume in the file +.Pa secvol +on Linux: +.Bd -ragged -offset indent +.Sy losetup Cm /dev/loop1 Cm secvol +.Ed +.Bd -ragged -offset indent +.Nm Fl -map Ns = Ns Cm secv +.Fl -device Ns = Ns Cm /dev/loop1 +.Ed +.Bd -ragged -offset indent +.Sy mount Cm /dev/mapper/secv Cm /mnt +.Ed +.Pp +Similarly on +.Dx : +.Bd -ragged -offset indent +.Sy vnconfig Cm vn1 Cm secvol +.Ed +.Bd -ragged -offset indent +.Nm Fl -map Ns = Ns Cm secv +.Fl -device Ns = Ns Cm /dev/vn1 +.Ed +.Bd -ragged -offset indent +.Sy mount Cm /dev/mapper/secv Cm /mnt +.Ed +.Pp +Unmapping the volume +.Sy truecrypt2 +on both Linux and +.Dx +after unmounting: +.Bd -ragged -offset indent +.Sy dmsetup Cm remove Cm truecrypt2 +.Ed +.Pp +Or alternatively: +.Bd -ragged -offset indent +.Nm Fl -unmap Ns = Ns Cm truecrypt2 +.Ed +.Pp +A hidden volume whose existance can be plausibly denied and its outer volume +can for example be created with +.Bd -ragged -offset indent +.Nm Fl -create +.Fl -hidden +.Fl -device Ns = Ns Cm /dev/loop0 +.Fl -cipher Ns = Ns Cm AES-256-XTS,TWOFISH-256-XTS +.Fl -pbkdf-prf Ns = Ns Cm whirlpool +.Fl -keyfile Ns = Ns Cm one.key +.Fl -cipher-hidden Ns = Ns Cm AES-256-XTS +.Fl -pbkdf-prf-hidden Ns = Ns Cm whirlpool +.Fl -keyfile-hidden Ns = Ns Cm hidden.key +.Ed +.Pp +.Nm +will prompt the user for the passphrase for both the outer and hidden volume +as well as the size of the hidden volume inside the outer volume. +The hidden volume will be created inside the area spanned by the outer volume. +The hidden volume can optionally use a different cipher and prf function +as specified by the +.Fl -cipher-hidden +and +.Fl -pbkdf-prf-hidden +options. +Which volume is later accessed depends only on which passphrase and keyfile(s) +are being used, +so that the existance of the hidden volume remains unknown without knowledge +of the passphrase and keyfile it is protected by since it is located within +the outer volume. +To map the outer volume without potentially damaging the hidden volume, +the passphrase and keyfile(s) of the hidden volume must be known and provided +alongside the +.Fl -protect-hidden +option. +.Pp +A disk encrypted using full disk encryption can be mapped using +.Bd -ragged -offset indent +.Nm Fl -map Ns = Ns Cm tcplay_sdb +.Fl -device Ns = Ns Cm /dev/sdb +.Fl -fde +.Ed +.Pp +To access individual partitions on the now mapped disk, +the following command will generate mappings for each +individual partition on the encrypted disk: +.Bd -ragged -offset indent +.Sy kpartx Fl -av Cm /dev/mapper/tcplay_sdb +.Ed +.Pp +To restore the main volume header from the backup header, the following +command can be used: +.Bd -ragged -offset indent +.Nm Fl -modify +.Fl -device Ns = Ns Cm /dev/sdb +.Fl -restore-from-backup-hdr +.Ed +.Pp +As with most other commands, which header is saved (used as source) depends +on the passphrase and keyfiles used. +.Pp +To save a backup copy of a header, the following command can be used: +.Bd -ragged -offset indent +.Nm Fl -modify +.Fl -device Ns = Ns Cm /dev/sdb +.Fl -save-hdr-backup Ns = Ns Cm /tmp/sdb_backup_header.hdr +.Ed +.Pp +As with most other commands, which header is saved (used as source) depends +on the passphrase and keyfiles used. +.Pp +To restore a header from a backup header file, the following command can be +used: +.Bd -ragged -offset indent +.Nm Fl -modify +.Nm -use-hdr-file Ns = Ns Cm /tmp/sdb_backup_header.hdr +.Ed +.Pp +Similarly, to restore a hidden header from a backup header file: +.Bd -ragged -offset indent +.Nm Fl -modify +.Nm -use-hidden-hdr-file Ns = Ns Cm /tmp/sdb_backup_hidden_header.hdr +.Ed +.Pp +Which header is used as the source of the operation will still depend on the +passphrase and keyfiles used. +Even if you use the +.Fl -use-hidden-hdr-file +option, if you specify the passphrase and keyfiles for the main header, the +main header will be used instead. .Sh SEE ALSO .Xr crypttab 5 , -.Xr cryptsetup 8 +.Xr cryptsetup 8 , +.Xr dmsetup 8 , +.Xr kpartx 8 .Sh HISTORY The .Nm -- 2.41.0