tcplay - bring in
authorAlex Hornung <ahornung@gmail.com>
Tue, 12 Jul 2011 14:09:32 +0000 (15:09 +0100)
committerAlex Hornung <ahornung@gmail.com>
Sat, 16 Jul 2011 17:36:30 +0000 (18:36 +0100)
 - tcplay is a fully-compatible TrueCrypt implementation, including
   cascaded ciphers, creating volumes, hidden volumes, protecting hidden
   volumes, mapping system volumes, etc... written from scratch.

19 files changed:
lib/Makefile
lib/libtcplay/Makefile [new file with mode: 0644]
lib/libtcplay/crc32.c [new file with mode: 0644]
lib/libtcplay/crc32.h [new file with mode: 0644]
lib/libtcplay/crypto-dev.c [new file with mode: 0644]
lib/libtcplay/hdr.c [new file with mode: 0644]
lib/libtcplay/io.c [new file with mode: 0644]
lib/libtcplay/safe_mem.c [new file with mode: 0644]
lib/libtcplay/tcplay.3 [new file with mode: 0644]
lib/libtcplay/tcplay.c [new file with mode: 0644]
lib/libtcplay/tcplay.h [new file with mode: 0644]
lib/libtcplay/tcplay.map [new file with mode: 0644]
lib/libtcplay/tcplay_api.c [new file with mode: 0644]
lib/libtcplay/tcplay_api.h [new file with mode: 0644]
lib/libtcplay/tcplay_api_test.c [new file with mode: 0644]
sbin/Makefile
sbin/tcplay/Makefile [new file with mode: 0644]
sbin/tcplay/main.c [new file with mode: 0644]
sbin/tcplay/tcplay.8 [new file with mode: 0644]

index dbd8f25..cd4bff3 100644 (file)
@@ -79,6 +79,7 @@ SUBDIR=       ${SUBDIR_ORDERED} \
        ${_libsmdb} \
        ${_libsmutil} \
        libstand \
+       libtcplay \
        libtelnet \
        libusbhid \
        libvgl \
diff --git a/lib/libtcplay/Makefile b/lib/libtcplay/Makefile
new file mode 100644 (file)
index 0000000..fddccc6
--- /dev/null
@@ -0,0 +1,23 @@
+LIB=           tcplay
+SHLIB_MAJOR?=  0
+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
+
+
+SRCS+=         tcplay_api.c tcplay.c crc32.c safe_mem.c \
+               io.c crypto-dev.c hdr.c
+INCS+=         tcplay_api.h
+
+LDFLAGS+=      -Wl,-version-script=${.CURDIR}/tcplay.map
+
+.include <bsd.lib.mk>
diff --git a/lib/libtcplay/crc32.c b/lib/libtcplay/crc32.c
new file mode 100644 (file)
index 0000000..a18468d
--- /dev/null
@@ -0,0 +1,108 @@
+/*-
+ *  COPYRIGHT (C) 1986 Gary S. Brown.  You may use this program, or
+ *  code or tables extracted from it, as desired without restriction.
+ *
+ *  First, the polynomial itself and its table of feedback terms.  The
+ *  polynomial is
+ *  X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0
+ *
+ *  Note that we take it "backwards" and put the highest-order term in
+ *  the lowest-order bit.  The X^32 term is "implied"; the LSB is the
+ *  X^31 term, etc.  The X^0 term (usually shown as "+1") results in
+ *  the MSB being 1
+ *
+ *  Note that the usual hardware shift register implementation, which
+ *  is what we're using (we're merely optimizing it by doing eight-bit
+ *  chunks at a time) shifts bits into the lowest-order term.  In our
+ *  implementation, that means shifting towards the right.  Why do we
+ *  do it this way?  Because the calculated CRC must be transmitted in
+ *  order from highest-order term to lowest-order term.  UARTs transmit
+ *  characters in order from LSB to MSB.  By storing the CRC this way
+ *  we hand it to the UART in the order low-byte to high-byte; the UART
+ *  sends each low-bit to hight-bit; and the result is transmission bit
+ *  by bit from highest- to lowest-order term without requiring any bit
+ *  shuffling on our part.  Reception works similarly
+ *
+ *  The feedback terms table consists of 256, 32-bit entries.  Notes
+ *
+ *      The table can be generated at runtime if desired; code to do so
+ *      is shown later.  It might not be obvious, but the feedback
+ *      terms simply represent the results of eight shift/xor opera
+ *      tions for all combinations of data and CRC register values
+ *
+ *      The values must be right-shifted by eight bits by the "updcrc
+ *      logic; the shift must be unsigned (bring in zeroes).  On some
+ *      hardware you could probably optimize the shift in assembler by
+ *      using byte-swap instructions
+ *      polynomial $edb88320
+ */
+
+#include <stdlib.h>
+#include "crc32.h"
+
+uint32_t crc32_tab[] = {
+       0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+       0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+       0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+       0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+       0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+       0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+       0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+       0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+       0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+       0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+       0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+       0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+       0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+       0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+       0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+       0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+       0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+       0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+       0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+       0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+       0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+       0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+       0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+       0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+       0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+       0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+       0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+       0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+       0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+       0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+       0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+       0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+       0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+       0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+       0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+       0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+       0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+       0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+       0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+       0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+       0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+       0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+       0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
+uint32_t
+crc32(const void *buf, size_t size)
+{
+       const uint8_t *p;
+       uint32_t crc;
+
+       p = buf;
+       crc = ~0U;
+
+       while (size--)
+               crc = crc32_tab[(crc ^ *p++) & 0xFF] ^ (crc >> 8);
+
+       return crc ^ ~0U;
+}
+
+uint32_t
+crc32_intermediate(uint32_t crc, uint8_t d)
+{
+       return crc32_tab[(crc ^ d) & 0xFF] ^ (crc >> 8);
+}
diff --git a/lib/libtcplay/crc32.h b/lib/libtcplay/crc32.h
new file mode 100644 (file)
index 0000000..2761f5d
--- /dev/null
@@ -0,0 +1,2 @@
+uint32_t crc32(const void *buf, size_t size);
+uint32_t crc32_intermediate(uint32_t crc, uint8_t d);
\ No newline at end of file
diff --git a/lib/libtcplay/crypto-dev.c b/lib/libtcplay/crypto-dev.c
new file mode 100644 (file)
index 0000000..f4c493f
--- /dev/null
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <sys/sysctl.h>
+#include <crypto/cryptodev.h>
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <openssl/evp.h>
+
+#include "crc32.h"
+#include "tcplay.h"
+
+static
+int
+getallowsoft(void)
+{
+       int old;
+       size_t olen;
+
+       olen = sizeof(old);
+
+       if (sysctlbyname("kern.cryptodevallowsoft", &old, &olen, NULL, 0) < 0) {
+               perror("accessing sysctl kern.cryptodevallowsoft failed");
+       }
+
+       return old;
+}
+
+static
+void
+setallowsoft(int new)
+{
+       int old;
+       size_t olen, nlen;
+
+       olen = nlen = sizeof(new);
+
+       if (sysctlbyname("kern.cryptodevallowsoft", &old, &olen, &new, nlen) < 0) {
+               perror("accessing sysctl kern.cryptodevallowsoft failed");
+       }
+}
+
+static
+int
+syscrypt(int cipher, unsigned char *key, size_t klen, unsigned char *iv,
+    unsigned char *in, unsigned char *out, size_t len, int do_encrypt)
+{
+       struct session_op session;
+       struct crypt_op cryp;
+       int cryptodev_fd = -1, fd = -1;
+
+       if ((cryptodev_fd = open("/dev/crypto", O_RDWR, 0)) < 0) {
+               perror("Could not open /dev/crypto");
+               goto err;
+       }
+       if (ioctl(cryptodev_fd, CRIOGET, &fd) == -1) {
+               perror("CRIOGET failed");
+               goto err;
+       }
+       memset(&session, 0, sizeof(session));
+       session.cipher = cipher;
+       session.key = (caddr_t) key;
+       session.keylen = klen;
+       if (ioctl(fd, CIOCGSESSION, &session) == -1) {
+               perror("CIOCGSESSION failed");
+               goto err;
+       }
+       memset(&cryp, 0, sizeof(cryp));
+       cryp.ses = session.ses;
+       cryp.op = do_encrypt ? COP_ENCRYPT : COP_DECRYPT;
+       cryp.flags = 0;
+       cryp.len = len;
+       cryp.src = (caddr_t) in;
+       cryp.dst = (caddr_t) out;
+       cryp.iv = (caddr_t) iv;
+       cryp.mac = 0;
+       if (ioctl(fd, CIOCCRYPT, &cryp) == -1) {
+               perror("CIOCCRYPT failed");
+               goto err;
+       }
+       if (ioctl(fd, CIOCFSESSION, &session.ses) == -1) {
+               perror("CIOCFSESSION failed");
+               goto err;
+       }
+       close(fd);
+       close(cryptodev_fd);
+       return (0);
+
+err:
+       if (fd != -1)
+               close(fd);
+       if (cryptodev_fd != -1)
+               close(cryptodev_fd);
+       return (-1);
+}
+
+static
+int
+get_cryptodev_cipher_id(struct tc_crypto_algo *cipher)
+{
+       if      (strcmp(cipher->name, "AES-128-XTS") == 0)
+               return CRYPTO_AES_XTS;
+       else if (strcmp(cipher->name, "AES-256-XTS") == 0)
+               return CRYPTO_AES_XTS;
+       else if (strcmp(cipher->name, "TWOFISH-128-XTS") == 0)
+               return CRYPTO_TWOFISH_XTS;
+       else if (strcmp(cipher->name, "TWOFISH-256-XTS") == 0)
+               return CRYPTO_TWOFISH_XTS;
+       else if (strcmp(cipher->name, "SERPENT-128-XTS") == 0)
+               return CRYPTO_SERPENT_XTS;
+       else if (strcmp(cipher->name, "SERPENT-256-XTS") == 0)
+               return CRYPTO_SERPENT_XTS;
+       else
+               return -1;
+}
+
+int
+tc_crypto_init(void)
+{
+       int allowed;
+
+       OpenSSL_add_all_algorithms();
+
+       allowed = getallowsoft();
+       if (allowed == 0)
+               setallowsoft(1);
+
+       return 0;
+}
+
+int
+tc_cipher_chain_populate_keys(struct tc_cipher_chain *cipher_chain,
+    unsigned char *key)
+{
+       int total_key_bytes, used_key_bytes;
+       struct tc_cipher_chain *dummy_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;
+       }
+
+       /*
+        * Now we need to get prepare the keys, as the keys are in
+        * forward order with respect to the cipher cascade, but
+        * the actual decryption is in reverse cipher cascade order.
+        */
+       used_key_bytes = 0;
+       for (dummy_chain = cipher_chain;
+           dummy_chain != NULL;
+           dummy_chain = dummy_chain->next) {
+               dummy_chain->key = alloc_safe_mem(dummy_chain->cipher->klen);
+               if (dummy_chain->key == NULL) {
+                       tc_log(1, "tc_decrypt: Could not allocate key "
+                           "memory\n");
+                       return ENOMEM;
+               }
+
+               /* XXX: here we assume XTS operation! */
+               memcpy(dummy_chain->key,
+                   key + used_key_bytes/2,
+                   dummy_chain->cipher->klen/2);
+               memcpy(dummy_chain->key + dummy_chain->cipher->klen/2,
+                   key + (total_key_bytes/2) + used_key_bytes/2,
+                   dummy_chain->cipher->klen/2);
+
+               /* Remember how many key bytes we've seen */
+               used_key_bytes += dummy_chain->cipher->klen;
+       }
+
+       return 0;
+}
+
+int
+tc_encrypt(struct tc_cipher_chain *cipher_chain, unsigned char *key,
+    unsigned char *iv,
+    unsigned char *in, int in_len, unsigned char *out)
+{
+       int cipher_id;
+       int err;
+
+       if ((err = tc_cipher_chain_populate_keys(cipher_chain, key)))
+               return err;
+
+#ifdef DEBUG
+       printf("tc_encrypt: starting chain\n");
+#endif
+
+       /*
+        * Now process the actual decryption, in forward cascade order.
+        */
+       for (;
+           cipher_chain != NULL;
+           cipher_chain = cipher_chain->next) {
+               cipher_id = get_cryptodev_cipher_id(cipher_chain->cipher);
+               if (cipher_id < 0) {
+                       tc_log(1, "Cipher %s not found\n",
+                           cipher_chain->cipher->name);
+                       return ENOENT;
+               }
+
+#ifdef DEBUG
+               printf("tc_encrypt: Currently using cipher %s\n",
+                   cipher_chain->cipher->name);
+#endif
+
+               err = syscrypt(cipher_id, cipher_chain->key,
+                   cipher_chain->cipher->klen, iv, in, out, in_len, 1);
+
+               /* Deallocate this key, since we won't need it anymore */
+               free_safe_mem(cipher_chain->key);
+
+               if (err != 0)
+                       return err;
+
+               /* Set next input buffer as current output buffer */
+               in = out;
+       }
+
+       return 0;
+}
+
+int
+tc_decrypt(struct tc_cipher_chain *cipher_chain, unsigned char *key,
+    unsigned char *iv,
+    unsigned char *in, int in_len, unsigned char *out)
+{
+       int cipher_id;
+       int err;
+
+       if ((err = tc_cipher_chain_populate_keys(cipher_chain, key)))
+               return err;
+
+#ifdef DEBUG
+       printf("tc_decrypt: starting chain!\n");
+#endif
+
+       /*
+        * Now process the actual decryption, in reverse cascade order; so
+        * first find the last element in the chain.
+        */
+       for (; cipher_chain->next != NULL; cipher_chain = cipher_chain->next)
+               ;
+       for (;
+           cipher_chain != NULL;
+           cipher_chain = cipher_chain->prev) {
+               cipher_id = get_cryptodev_cipher_id(cipher_chain->cipher);
+               if (cipher_id < 0) {
+                       tc_log(1, "Cipher %s not found\n",
+                           cipher_chain->cipher->name);
+                       return ENOENT;
+               }
+
+#ifdef DEBUG
+               printf("tc_decrypt: Currently using cipher %s\n",
+                   cipher_chain->cipher->name);
+#endif
+
+               err = syscrypt(cipher_id, cipher_chain->key,
+                   cipher_chain->cipher->klen, iv, in, out, in_len, 0);
+
+               /* Deallocate this key, since we won't need it anymore */
+               free_safe_mem(cipher_chain->key);
+
+               if (err != 0)
+                       return err;
+
+               /* Set next input buffer as current output buffer */
+               in = out;
+       }
+
+       return 0;
+}
+
+int
+pbkdf2(const char *pass, int passlen, const unsigned char *salt, int saltlen,
+    int iter, const char *hash_name, int keylen, unsigned char *out)
+{
+       const EVP_MD *md;
+       int r;
+
+       md = EVP_get_digestbyname(hash_name);
+       if (md == NULL) {
+               printf("Hash %s not found\n", hash_name);
+               return ENOENT;
+       }
+       r = PKCS5_PBKDF2_HMAC(pass, passlen, salt, saltlen, iter, md,
+           keylen, out);
+
+       if (r == 0) {
+               printf("Error in PBKDF2\n");
+               return EINVAL;
+       }
+
+       return 0;
+}
+
+int
+apply_keyfiles(unsigned char *pass, size_t pass_memsz, const char *keyfiles[],
+    int nkeyfiles)
+{
+       int pl, k;
+       unsigned char *kpool;
+       unsigned char *kdata;
+       int kpool_idx;
+       size_t i, kdata_sz;
+       uint32_t crc;
+
+       if (pass_memsz < MAX_PASSSZ) {
+               tc_log(1, "Not enough memory for password manipluation\n");
+               return ENOMEM;
+       }
+
+       pl = strlen(pass);
+       memset(pass+pl, 0, MAX_PASSSZ-pl);
+
+       if ((kpool = alloc_safe_mem(KPOOL_SZ)) == NULL) {
+               tc_log(1, "Error allocating memory for keyfile pool\n");
+               return ENOMEM;
+       }
+
+       memset(kpool, 0, KPOOL_SZ);
+
+       for (k = 0; k < nkeyfiles; k++) {
+#ifdef DEBUG
+               printf("Loading keyfile %s into kpool\n", keyfiles[k]);
+#endif
+               kpool_idx = 0;
+               crc = ~0U;
+               kdata_sz = MAX_KFILE_SZ;
+
+               if ((kdata = read_to_safe_mem(keyfiles[k], 0, &kdata_sz)) == NULL) {
+                       tc_log(1, "Error reading keyfile %s content\n",
+                           keyfiles[k]);
+                       free_safe_mem(kpool);
+                       return EIO;
+               }
+
+               for (i = 0; i < kdata_sz; i++) {
+                       crc = crc32_intermediate(crc, kdata[i]);
+
+                       kpool[kpool_idx++] += (unsigned char)(crc >> 24);
+                       kpool[kpool_idx++] += (unsigned char)(crc >> 16);
+                       kpool[kpool_idx++] += (unsigned char)(crc >> 8);
+                       kpool[kpool_idx++] += (unsigned char)(crc);
+
+                       /* Wrap around */
+                       if (kpool_idx == KPOOL_SZ)
+                               kpool_idx = 0;
+               }
+
+               free_safe_mem(kdata);
+       }
+
+#ifdef DEBUG
+       printf("Applying kpool to passphrase\n");
+#endif
+       /* Apply keyfile pool to passphrase */
+       for (i = 0; i < KPOOL_SZ; i++)
+               pass[i] += kpool[i];
+
+       free_safe_mem(kpool);
+
+       return 0;
+}
diff --git a/lib/libtcplay/hdr.c b/lib/libtcplay/hdr.c
new file mode 100644 (file)
index 0000000..fab20f2
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <sys/endian.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "crc32.h"
+#include "tcplay.h"
+
+/* Endianess macros */
+#define BE_TO_HOST(n, v) v = be ## n ## toh(v)
+#define LE_TO_HOST(n, v) v = le ## n ## toh(v)
+#define HOST_TO_BE(n, v) v = htobe ## n (v)
+#define HOST_TO_LE(n, v) v = htole ## n (v)
+
+struct tchdr_dec *
+decrypt_hdr(struct tchdr_enc *ehdr, struct tc_cipher_chain *cipher_chain,
+    unsigned char *key)
+{
+       struct tchdr_dec *dhdr;
+       unsigned char iv[128];
+       int error;
+
+       if ((dhdr = alloc_safe_mem(sizeof(struct tchdr_dec))) == NULL) {
+               tc_log(1, "Error allocating safe tchdr_dec memory\n");
+               return NULL;
+       }
+
+       memset(iv, 0, sizeof(iv));
+
+       error = tc_decrypt(cipher_chain, key, iv, ehdr->enc,
+           sizeof(struct tchdr_dec), (unsigned char *)dhdr);
+       if (error) {
+               tc_log(1, "Header decryption failed\n");
+               free_safe_mem(dhdr);
+               return NULL;
+       }
+
+       BE_TO_HOST(16, dhdr->tc_ver);
+       LE_TO_HOST(16, dhdr->tc_min_ver);
+       BE_TO_HOST(32, dhdr->crc_keys);
+       BE_TO_HOST(64, dhdr->vol_ctime);
+       BE_TO_HOST(64, dhdr->hdr_ctime);
+       BE_TO_HOST(64, dhdr->sz_hidvol);
+       BE_TO_HOST(64, dhdr->sz_vol);
+       BE_TO_HOST(64, dhdr->off_mk_scope);
+       BE_TO_HOST(64, dhdr->sz_mk_scope);
+       BE_TO_HOST(32, dhdr->flags);
+       BE_TO_HOST(32, dhdr->sec_sz);
+       BE_TO_HOST(32, dhdr->crc_dhdr);
+
+       return dhdr;
+}
+
+int
+verify_hdr(struct tchdr_dec *hdr)
+{
+       uint32_t crc;
+
+       if (memcmp(hdr->tc_str, TC_SIG, sizeof(hdr->tc_str)) != 0) {
+#ifdef DEBUG
+               fprintf(stderr, "Signature mismatch\n");
+#endif
+               return 0;
+       }
+
+       crc = crc32((void *)&hdr->keys, 256);
+       if (crc != hdr->crc_keys) {
+#ifdef DEBUG
+               fprintf(stderr, "CRC32 mismatch (crc_keys)\n");
+#endif
+               return 0;
+       }
+
+       switch(hdr->tc_ver) {
+       case 1:
+       case 2:
+               /* Unsupported header version */
+               tc_log(1, "Header version %d unsupported\n", hdr->tc_ver);
+               return 0;
+
+       case 3:
+       case 4:
+               hdr->sec_sz = 512;
+               break;
+       }
+
+       return 1;
+}
+
+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 *ehdr;
+       struct tchdr_dec *dhdr;
+       unsigned char *key;
+       unsigned char iv[128];
+       int error;
+
+       if ((dhdr = (struct tchdr_dec *)alloc_safe_mem(sizeof(*dhdr))) == NULL) {
+               tc_log(1, "could not allocate safe dhdr memory\n");
+               return NULL;
+       }
+
+       if ((ehdr = (struct tchdr_enc *)alloc_safe_mem(sizeof(*ehdr))) == NULL) {
+               tc_log(1, "could not allocate safe ehdr memory\n");
+               return NULL;
+       }
+
+       if ((key = alloc_safe_mem(MAX_KEYSZ)) == NULL) {
+               tc_log(1, "could not allocate safe key memory\n");
+               return NULL;
+       }
+
+       if ((error = get_random(ehdr->salt, sizeof(ehdr->salt))) != 0) {
+               tc_log(1, "could not get salt\n");
+               return NULL;
+       }
+
+       error = pbkdf2(pass, passlen,
+           ehdr->salt, sizeof(ehdr->salt),
+           prf_algo->iteration_count,
+           prf_algo->name, MAX_KEYSZ, key);
+       if (error) {
+               tc_log(1, "could not derive key\n");
+               return NULL;
+       }
+
+       memset(dhdr, 0, sizeof(*dhdr));
+
+       if ((error = get_random(dhdr->keys, sizeof(dhdr->keys))) != 0) {
+               tc_log(1, "could not get key random bits\n");
+               return NULL;
+       }
+
+       memcpy(dhdr->tc_str, "TRUE", 4);
+       dhdr->tc_ver = 5;
+       dhdr->tc_min_ver = 7;
+       dhdr->crc_keys = crc32((void *)&dhdr->keys, 256);
+       dhdr->sz_vol = blocks * sec_sz;
+       if (hidden)
+               dhdr->sz_hidvol = dhdr->sz_vol;
+       dhdr->off_mk_scope = offset * sec_sz;
+       dhdr->sz_mk_scope = blocks * sec_sz;
+       dhdr->sec_sz = sec_sz;
+       dhdr->flags = 0;
+
+       HOST_TO_BE(16, dhdr->tc_ver);
+       HOST_TO_LE(16, dhdr->tc_min_ver);
+       HOST_TO_BE(32, dhdr->crc_keys);
+       HOST_TO_BE(64, dhdr->sz_vol);
+       HOST_TO_BE(64, dhdr->sz_hidvol);
+       HOST_TO_BE(64, dhdr->off_mk_scope);
+       HOST_TO_BE(64, dhdr->sz_mk_scope);
+       HOST_TO_BE(32, dhdr->sec_sz);
+       HOST_TO_BE(32, dhdr->flags);
+
+       dhdr->crc_dhdr = crc32((void *)dhdr, 188);
+       HOST_TO_BE(32, dhdr->crc_dhdr);
+
+       memset(iv, 0, sizeof(iv));
+       error = tc_encrypt(cipher_chain, key, iv, (unsigned char *)dhdr,
+           sizeof(struct tchdr_dec), ehdr->enc);
+       if (error) {
+               tc_log(1, "Header encryption failed\n");
+               free_safe_mem(dhdr);
+               return NULL;
+       }
+
+       free_safe_mem(dhdr);
+       return ehdr;
+}
diff --git a/lib/libtcplay/io.c b/lib/libtcplay/io.c
new file mode 100644 (file)
index 0000000..7d96fa5
--- /dev/null
@@ -0,0 +1,294 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <sys/diskslice.h>
+#include <sys/uio.h>
+#include <sys/select.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "tcplay.h"
+
+void *
+read_to_safe_mem(const char *file, off_t offset, size_t *sz)
+{
+       void *mem = NULL;
+       ssize_t r = 0;
+       int fd;
+
+       if ((fd = open(file, O_RDONLY)) < 0) {
+               tc_log(1, "Error opening file %s\n", file);
+               return NULL;
+       }
+
+       if ((mem = alloc_safe_mem(*sz)) == NULL) {
+               tc_log(1, "Error allocating memory\n");
+               goto out;
+       }
+
+       if ((lseek(fd, offset, SEEK_SET) < 0)) {
+               tc_log(1, "Error seeking on file %s\n", file);
+               goto m_err;
+       }
+
+       if ((r = read(fd, mem, *sz)) <= 0) {
+               tc_log(1, "Error reading from file %s\n", file);
+               goto m_err;
+       }
+
+out:
+       *sz = r;
+       close(fd);
+       return mem;
+       /* NOT REACHED */
+
+m_err:
+       free_safe_mem(mem);
+       close(fd);
+       return NULL;
+}
+
+int
+get_random(unsigned char *buf, size_t len)
+{
+       int fd;
+       ssize_t r;
+       size_t rd = 0;
+       struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; /* 10 ms */
+
+
+       if ((fd = open("/dev/random", O_RDONLY)) < 0) {
+               tc_log(1, "Error opening /dev/random\n");
+               return -1;
+       }
+
+       while (rd < len) {
+               if ((r = read(fd, buf+rd, len-rd)) < 0) {
+                       tc_log(1, "Error reading from /dev/random\n");
+                       close(fd);
+                       return -1;
+               }
+               rd += r;
+               nanosleep(&ts, NULL);
+       }
+
+       close(fd);
+       return 0;
+}
+
+static size_t secure_erase_total_bytes = 0;
+static size_t secure_erase_erased_bytes = 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;
+       tc_log(0, "Securely erasing, %.0f%% done.\n", pct_done);
+}
+
+int
+secure_erase(const char *dev, size_t bytes, size_t blksz)
+{
+       size_t erased = 0;
+       int fd_rand, fd;
+       char buf[ERASE_BUFFER_SIZE];
+       ssize_t r, w;
+       size_t sz;
+
+       if (blksz > MAX_BLKSZ) {
+               tc_log(1, "blksz > MAX_BLKSZ\n");
+               return -1;
+       }
+
+       if ((fd_rand = open("/dev/urandom", O_RDONLY)) < 0) {
+               tc_log(1, "Error opening /dev/urandom\n");
+               return -1;
+       }
+
+       if ((fd = open(dev, O_WRONLY)) < 0) {
+               close(fd_rand);
+               tc_log(1, "Error opening %s\n", dev);
+               return -1;
+       }
+
+       summary_fn = secure_erase_summary;
+       secure_erase_total_bytes = bytes;
+
+       sz = ERASE_BUFFER_SIZE;
+       while (erased < bytes) {
+               secure_erase_erased_bytes = erased;
+               /* Switch to block size when not much is remaining */
+               if ((bytes - erased) <= ERASE_BUFFER_SIZE)
+                       sz = blksz;
+
+               if ((r = read(fd_rand, buf, sz)) < 0) {
+                       tc_log(1, "Error reading from /dev/urandom\n");
+                       close(fd);
+                       close(fd_rand);
+                       summary_fn = NULL;
+                       return -1;
+               }
+
+               if (r < (ssize_t)blksz)
+                       continue;
+
+               if ((w = write(fd, buf, r)) < 0) {
+                       tc_log(1, "Error writing to %s\n", dev);
+                       close(fd);
+                       close(fd_rand);
+                       summary_fn = NULL;
+                       return -1;
+               }
+
+               erased += (size_t)w;
+       }
+
+       close(fd);
+       close(fd_rand);
+
+       summary_fn = NULL;
+
+       return 0;
+}
+
+int
+get_disk_info(const char *dev, size_t *blocks, size_t *bsize)
+{
+       struct partinfo pinfo;
+       int fd;
+
+       if ((fd = open(dev, O_RDONLY)) < 0) {
+               tc_log(1, "Error opening %s\n", dev);
+               return -1;
+       }
+
+       memset(&pinfo, 0, sizeof(struct partinfo));
+
+       if (ioctl(fd, DIOCGPART, &pinfo) < 0) {
+               close(fd);
+               return -1;
+       }
+
+       *blocks = pinfo.media_blocks;
+       *bsize = pinfo.media_blksize;
+
+       close(fd);
+       return 0;
+}
+
+int
+write_mem(const char *dev, off_t offset, size_t blksz __unused, void *mem,
+    size_t bytes)
+{
+       ssize_t w;
+       int fd;
+
+       if ((fd = open(dev, O_WRONLY)) < 0) {
+               tc_log(1, "Error opening device %s\n", dev);
+               return -1;
+       }
+
+       if ((lseek(fd, offset, SEEK_SET) < 0)) {
+               tc_log(1, "Error seeking on device %s\n", dev);
+               close(fd);
+               return -1;
+       }
+
+       if ((w = write(fd, mem, bytes)) <= 0) {
+               tc_log(1, "Error writing to device %s\n", dev);
+               close(fd);
+               return -1;
+       }
+
+       close(fd);
+       return 0;
+}
+
+int
+read_passphrase(const char *prompt, char *pass, size_t passlen, time_t timeout)
+{
+       struct termios termios_old, termios_new;
+       struct timeval to;
+       fd_set fds;
+       ssize_t n;
+       int fd, r = 0, cfd = 0, nready;
+
+       if ((fd = open("/dev/tty", O_RDONLY)) == -1) {
+               fd = STDIN_FILENO;
+               cfd = 1;
+       }
+
+       printf(prompt);
+       fflush(stdout);
+
+       memset(pass, 0, passlen);
+
+       tcgetattr(fd, &termios_old);
+       memcpy(&termios_new, &termios_old, sizeof(termios_new));
+       termios_new.c_lflag &= ~ECHO;
+       tcsetattr(fd, TCSAFLUSH, &termios_new);
+
+       if (timeout > 0) {
+               memset(&to, 0, sizeof(to));
+               to.tv_sec = timeout;
+
+               FD_ZERO(&fds);
+               FD_SET(fd, &fds);
+               nready = select(fd + 1, &fds, NULL, NULL, &to);
+               if (nready <= 0) {
+                       r = EINTR;
+                       goto out;
+               }
+       }
+
+       n = read(fd, pass, passlen-1);
+       if (n > 0) {
+               pass[n-1] = '\0'; /* Strip trailing \n */
+       } else {
+               r = EIO;
+       }
+
+out:
+       if (cfd)
+               close(fd);
+
+       tcsetattr(fd, TCSAFLUSH, &termios_old);
+       putchar('\n');
+
+       return r;
+}
diff --git a/lib/libtcplay/safe_mem.c b/lib/libtcplay/safe_mem.c
new file mode 100644 (file)
index 0000000..eb557bb
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "tcplay.h"
+
+struct safe_mem_hdr {
+       struct safe_mem_hdr     *prev;
+       struct safe_mem_hdr     *next;
+       struct safe_mem_tail    *tail;
+       const char      *file;
+       int             line;
+       size_t          alloc_sz;
+       char            sig[8]; /* SAFEMEM */
+};
+
+struct safe_mem_tail {
+       char sig[8]; /* SAFEMEM */
+};
+
+static struct safe_mem_hdr *safe_mem_hdr_first = NULL;
+
+void *
+_alloc_safe_mem(size_t req_sz, const char *file, int line)
+{
+       struct safe_mem_hdr *hdr, *hdrp;
+       struct safe_mem_tail *tail;
+       size_t alloc_sz;
+       char *mem, *user_mem;
+
+       alloc_sz = req_sz + sizeof(*hdr) + sizeof(*tail);
+       if ((mem = malloc(alloc_sz)) == NULL)
+               return NULL;
+
+       if (mlock(mem, alloc_sz) < 0) {
+               free(mem);
+               return NULL;
+       }
+
+       memset(mem, 0, alloc_sz);
+
+       hdr = (struct safe_mem_hdr *)mem;
+       tail = (struct safe_mem_tail *)(mem + alloc_sz - sizeof(*tail));
+       user_mem = mem + sizeof(*hdr);
+
+       strcpy(hdr->sig, "SAFEMEM");
+       strcpy(tail->sig, "SAFEMEM");
+       hdr->tail = tail;
+       hdr->alloc_sz = alloc_sz;
+       hdr->file = file;
+       hdr->line = line;
+       hdr->next = NULL;
+
+       if (safe_mem_hdr_first == NULL) {
+               safe_mem_hdr_first = hdr;
+       } else {
+               hdrp = safe_mem_hdr_first;
+               while (hdrp->next != NULL)
+                       hdrp = hdrp->next;
+               hdr->prev = hdrp;
+               hdrp->next = hdr;
+       }
+
+       return user_mem;
+}
+
+void
+_free_safe_mem(void *mem_ptr, const char *file, int line)
+{
+       struct safe_mem_hdr *hdr;
+       struct safe_mem_tail *tail;
+       size_t alloc_sz;
+       char *mem = mem_ptr;
+
+       mem -= sizeof(*hdr);
+       hdr = (struct safe_mem_hdr *)mem;
+       tail = (struct safe_mem_tail *)(mem + hdr->alloc_sz - sizeof(*tail));
+
+#ifdef DEBUG
+       fprintf(stderr, "freeing safe_mem (hdr): %#lx (%s:%d)\n",
+                   (unsigned long)(void *)hdr, hdr->file, hdr->line);
+#endif
+
+       if (hdr->alloc_sz == 0) {
+               fprintf(stderr, "BUG: double-free at %s:%d !!!\n", file, line);
+               return;
+       }
+
+       /* Integrity checks */
+       if ((memcmp(hdr->sig, "SAFEMEM\0", 8) != 0) ||
+           (memcmp(tail->sig, "SAFEMEM\0", 8) != 0)) {
+               fprintf(stderr, "BUG: safe_mem buffer under- or overflow at "
+                   "%s:%d !!!\n", file, line);
+               return;
+       }
+
+       if (safe_mem_hdr_first == NULL) {
+               fprintf(stderr, "BUG: safe_mem list should not be empty at "
+                   "%s:%d !!!\n", file, line);
+               return;
+       }
+
+       if (hdr->prev != NULL)
+               hdr->prev->next = hdr->next;
+       if (hdr->next != NULL)
+               hdr->next->prev = hdr->prev;
+       if (safe_mem_hdr_first == hdr)
+               safe_mem_hdr_first = hdr->next;
+
+       alloc_sz = hdr->alloc_sz;
+       memset(mem, 0xFF, alloc_sz);
+       memset(mem, 0, alloc_sz);
+
+       free(mem);
+}
+
+void
+check_and_purge_safe_mem(void)
+{
+       struct safe_mem_hdr *hdr;
+       char *mem;
+       int ok;
+
+       if (safe_mem_hdr_first == NULL)
+               return;
+
+       hdr = safe_mem_hdr_first;
+       while ((hdr = safe_mem_hdr_first) != NULL) {
+               if ((hdr->alloc_sz > 0) &&
+                   (memcmp(hdr->sig, "SAFEMEM\0", 8) == 0) &&
+                   (memcmp(hdr->tail->sig, "SAFEMEM\0", 8) == 0))
+                       ok = 1;
+               else
+                       ok = 0;
+
+#ifdef DEBUG
+               fprintf(stderr, "un-freed safe_mem: %#lx (%s:%d) [integrity=%s]\n",
+                   (unsigned long)(void *)hdr, hdr->file, hdr->line,
+                   ok? "ok" : "failed");
+#endif
+               mem = (void *)hdr;
+               mem += sizeof(*hdr);
+               _free_safe_mem(mem, "check_and_purge_safe_mem", 0);
+       }
+}
diff --git a/lib/libtcplay/tcplay.3 b/lib/libtcplay/tcplay.3
new file mode 100644 (file)
index 0000000..ddea610
--- /dev/null
@@ -0,0 +1,265 @@
+.\"
+.\" Copyright (c) 2011 The DragonFly Project.  All rights reserved.
+.\" 
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in
+.\"    the documentation and/or other materials provided with the
+.\"    distribution.
+.\" 3. Neither the name of The DragonFly Project nor the names of its
+.\"    contributors may be used to endorse or promote products derived
+.\"    from this software without specific, prior written permission.
+.\" 
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd July 5, 2011
+.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
+.Nd TrueCrypt volume management
+.Sh LIBRARY
+.Lb libtcplay
+.Sh SYNOPSIS
+.In tcplay_api.h
+.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"
+.Ft int
+.Fn tc_api_map_volume "tc_api_opts *api_opts"
+.Ft int
+.Fn tc_api_unmap_volume "tc_api_opts *api_opts"
+.Ft int
+.Fn tc_api_check_cipher "tc_api_opts *api_opts"
+.Ft int
+.Fn tc_api_check_prf_hash "tc_api_opts *api_opts"
+.Ft const char *
+.Fn tc_api_get_error_msg "void"
+.Ft const char *
+.Fn tc_api_get_summary "void"
+.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_blocks;
+       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.
+.Pp
+The
+.Fn tc_api_init
+function initializes the library internals and prepares it for use via
+the API.
+This function has to be called before using any other API function.
+If the
+.Fa verbose
+argument is non-zero, then the library will output information such as
+errors via stdout and stderr.
+.Pp
+The
+.Fn tc_api_uninit
+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
+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_blocks
+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
+and
+.Fa tc_keyfiles_hidden
+respectively.
+.Pp
+The
+.Fn tc_api_map_volume
+function maps a volume using the parameters specified in the
+.Fa api_opts
+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.
+.Pp
+The
+.Fn tc_api_unmap_volume
+unmaps / closes the volume specified in
+.Fa tc_map_name .
+.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.
+.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.
+.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
+occured.
+.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 .
+.Sh RETURN VALUES
+All functions except
+.Fn tc_api_get_error_msg
+and
+.Fn tc_api_get_summary
+return either
+.Dv TC_OK
+to signal that the operation completed successfully, or
+.Dv TC_ERR
+to signal that an error occured.
+.Pp
+The
+.Fn tc_api_get_error_msg
+and
+.Fn tc_api_get_summary
+always return a valid, but possibly empty, string.
+.Sh COMPATIBILITY
+The
+.Nm tcplay
+library offers full compatibility with TrueCrypt volumes including 
+hidden
+volumes, system encryption (map-only), keyfiles and cipher cascading.
+.Sh SEE ALSO
+.Xr tcplay 8
+.Sh HISTORY
+The
+.Nm tcplay
+library appeared in
+.Dx 2.11 .
+.Sh AUTHORS
+.An Alex Hornung
+
diff --git a/lib/libtcplay/tcplay.c b/lib/libtcplay/tcplay.c
new file mode 100644 (file)
index 0000000..edfedef
--- /dev/null
@@ -0,0 +1,1101 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <err.h>
+#include <time.h>
+#include <libdevmapper.h>
+#include <libutil.h>
+
+#include "crc32.h"
+#include "tcplay.h"
+
+
+/* XXX TODO:
+ *  - LRW-benbi support? needs further work in dm-crypt and even opencrypto
+ *  - secure buffer review (i.e: is everything that needs it using secure mem?)
+ *  - mlockall? (at least MCL_FUTURE, which is the only one we support)
+ */
+
+summary_fn_t summary_fn = NULL;
+int tc_internal_verbose = 1;
+char tc_internal_log_buffer[LOG_BUFFER_SZ];
+
+void
+tc_log(int is_err, const char *fmt, ...)
+{
+       __va_list ap;
+       FILE *fp;
+
+       if (is_err)
+               fp = stderr;
+       else
+               fp = stdout;
+
+        __va_start(ap, fmt);
+
+       vsnprintf(tc_internal_log_buffer, LOG_BUFFER_SZ, fmt, ap);
+
+       if (tc_internal_verbose)
+                vfprintf(fp, fmt, ap);
+
+       __va_end(ap);
+}
+
+/* Supported algorithms */
+struct pbkdf_prf_algo pbkdf_prf_algos[] = {
+       { "RIPEMD160",  2000 }, /* needs to come before the other RIPEMD160 */
+       { "RIPEMD160",  1000 },
+       { "SHA512",     1000 },
+       { "whirlpool",  1000 },
+       { NULL,         0    }
+};
+
+struct tc_crypto_algo tc_crypto_algos[] = {
+#if 0
+       /* XXX: turns out TC doesn't support AES-128-XTS */
+       { "AES-128-XTS",        "aes-xts-plain",        32,     8 },
+       { "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 },
+       { NULL,                 NULL,                   0,      0 }
+};
+
+const char *valid_cipher_chains[][MAX_CIPHER_CHAINS] = {
+       { "AES-256-XTS", NULL },
+       { "TWOFISH-256-XTS", NULL },
+       { "SERPENT-256-XTS", NULL },
+       { "AES-256-XTS", "TWOFISH-256-XTS", "SERPENT-256-XTS", NULL },
+       { "SERPENT-256-XTS", "TWOFISH-256-XTS", "AES-256-XTS", NULL },
+#if 0
+       /* It seems that all the two-way cascades are the other way round... */
+       { "AES-256-XTS", "TWOFISH-256-XTS", NULL },
+       { "SERPENT-256-XTS", "AES-256-XTS", NULL },
+       { "TWOFISH-256-XTS", "SERPENT-256-XTS", NULL },
+
+#endif
+       { "TWOFISH-256-XTS", "AES-256-XTS", NULL },
+       { "AES-256-XTS", "SERPENT-256-XTS", NULL },
+       { "SERPENT-256-XTS", "TWOFISH-256-XTS", NULL },
+       { NULL }
+};
+
+struct tc_cipher_chain *tc_cipher_chains[MAX_CIPHER_CHAINS];
+
+static
+int
+tc_build_cipher_chains(void)
+{
+       struct tc_cipher_chain *chain, *elem, *prev;
+       int i = 0;
+       int k;
+
+       while (valid_cipher_chains[i][0] != NULL) {
+               chain = NULL;
+               prev = NULL;
+               k = 0;
+
+               while (valid_cipher_chains[i][k] != NULL) {
+                       if ((elem = alloc_safe_mem(sizeof(*elem))) == NULL) {
+                               tc_log(1, "Error allocating memory for "
+                                  "cipher chain\n");
+                               return -1;
+                       }
+
+                       /* Initialize first element of chain */
+                       if (chain == NULL) {
+                               chain = elem;
+                               elem->prev = NULL;
+                       }
+
+                       /* Populate previous element */
+                       if (prev != NULL) {
+                               prev->next = elem;
+                               elem->prev = prev;
+                       }
+
+                       /* Assume we are the last element in the chain */
+                       elem->next = NULL;
+
+                       /* Initialize other fields */
+                       elem->cipher = check_cipher(valid_cipher_chains[i][k], 0);
+                       if (elem->cipher == NULL)
+                               return -1;
+
+                       elem->key = NULL;
+
+                       prev = elem;
+                       ++k;
+               }
+
+               /* Store cipher chain */
+               tc_cipher_chains[i++] = chain;
+
+               /* Integrity check */
+               if (i >= MAX_CIPHER_CHAINS) {
+                       tc_log(1, "FATAL: tc_cipher_chains is full!!\n");
+                       return -1;
+               }
+
+               /* Make sure array is NULL terminated */
+               tc_cipher_chains[i] = NULL;
+       }
+
+       return 0;
+}
+
+#ifdef DEBUG
+static void
+print_hex(unsigned char *buf, off_t start, size_t len)
+{
+       size_t i;
+
+       for (i = start; i < start+len; i++)
+               printf("%02x", buf[i]);
+
+       printf("\n");
+}
+#endif
+
+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("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;
+       }
+
+       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);
+}
+
+static
+struct tcplay_info *
+new_info(const char *dev, struct tc_cipher_chain *cipher_chain,
+    struct pbkdf_prf_algo *prf, struct tchdr_dec *hdr, off_t start)
+{
+       struct tcplay_info *info;
+       int i;
+       int error;
+
+       if ((info = (struct tcplay_info *)alloc_safe_mem(sizeof(*info))) == NULL) {
+               tc_log(1, "could not allocate safe info memory\n");
+               return NULL;
+       }
+
+       info->dev = dev;
+       info->cipher_chain = cipher_chain;
+       info->pbkdf_prf = prf;
+       info->start = start;
+       info->hdr = hdr;
+       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 */
+
+       /* Associate a key out of the key pool with each cipher in the chain */
+       error = tc_cipher_chain_populate_keys(cipher_chain, hdr->keys);
+       if (error) {
+               tc_log(1, "could not populate keys in cipher chain\n");
+               return NULL;
+       }
+
+       for (; cipher_chain != NULL; cipher_chain = cipher_chain->next) {
+               for (i = 0; i < cipher_chain->cipher->klen; i++)
+                       sprintf(&cipher_chain->dm_key[i*2], "%02x",
+                           cipher_chain->key[i]);
+       }
+
+       return info;
+}
+
+int
+adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo)
+{
+       if (hinfo->hdr->sz_hidvol == 0)
+               return 1;
+
+       info->size -= hinfo->hdr->sz_hidvol / hinfo->hdr->sec_sz;
+       return 0;
+}
+
+int
+process_hdr(const char *dev, unsigned char *pass, int passlen,
+    struct tchdr_enc *ehdr, struct tcplay_info **pinfo)
+{
+       struct tchdr_dec *dhdr;
+       struct tcplay_info *info;
+       unsigned char *key;
+       int i, j, found, error;
+
+       *pinfo = NULL;
+
+       if ((key = alloc_safe_mem(MAX_KEYSZ)) == NULL) {
+               tc_log(1, "could not allocate safe key memory\n");
+               return ENOMEM;
+       }
+
+       /* Start search for correct algorithm combination */
+       found = 0;
+       for (i = 0; !found && pbkdf_prf_algos[i].name != NULL; i++) {
+#ifdef DEBUG
+               printf("\nTrying PRF algo %s (%d)\n", pbkdf_prf_algos[i].name,
+                   pbkdf_prf_algos[i].iteration_count);
+               printf("Salt: ");
+               print_hex(ehdr->salt, 0, sizeof(ehdr->salt));
+#endif
+               error = pbkdf2(pass, passlen,
+                   ehdr->salt, sizeof(ehdr->salt),
+                   pbkdf_prf_algos[i].iteration_count,
+                   pbkdf_prf_algos[i].name, MAX_KEYSZ, key);
+
+               if (error) {
+                       tc_log(1, "pbkdf failed for algorithm %s\n",
+                           pbkdf_prf_algos[i].name);
+                       return EINVAL;
+               }
+
+#if 0
+               printf("Derived Key: ");
+               print_hex(key, 0, MAX_KEYSZ);
+#endif
+
+               for (j = 0; !found && tc_cipher_chains[j] != NULL; j++) {
+#ifdef DEBUG
+                       printf("\nTrying cipher chain %d\n", j);
+#endif
+
+                       dhdr = decrypt_hdr(ehdr, tc_cipher_chains[j], key);
+                       if (dhdr == NULL) {
+                               tc_log(1, "hdr decryption failed for cipher "
+                                   "chain %d\n", j);
+                               return EINVAL;
+                       }
+
+                       if (verify_hdr(dhdr)) {
+#ifdef DEBUG
+                               printf("tc_str: %.4s, tc_ver: %zd, tc_min_ver: %zd, "
+                                   "crc_keys: %d, sz_vol: %"PRIu64", "
+                                   "off_mk_scope: %"PRIu64", sz_mk_scope: %"PRIu64", "
+                                   "flags: %d, sec_sz: %d crc_dhdr: %d\n",
+                                   dhdr->tc_str, dhdr->tc_ver, dhdr->tc_min_ver,
+                                   dhdr->crc_keys, dhdr->sz_vol, dhdr->off_mk_scope,
+                                   dhdr->sz_mk_scope, dhdr->flags, dhdr->sec_sz,
+                                   dhdr->crc_dhdr);
+#endif
+                               found = 1;
+                       } else {
+                               free_safe_mem(dhdr);
+                       }
+               }
+       }
+
+       free_safe_mem(key);
+
+       if (!found)
+               return EINVAL;
+
+       if ((info = new_info(dev, tc_cipher_chains[j-1], &pbkdf_prf_algos[i-1],
+           dhdr, 0)) == NULL) {
+               return ENOMEM;
+       }
+
+       *pinfo = info;
+       return 0;
+}
+
+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_blocks_in, int interactive)
+{
+       char *pass, *pass_again;
+       char *h_pass = NULL;
+       char buf[1024];
+       size_t blocks, blksz, hidden_blocks = 0;
+       struct tchdr_enc *ehdr, *hehdr = NULL;
+       int64_t tmp;
+       int error, r;
+
+       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 ((error = get_disk_info(dev, &blocks, &blksz)) != 0) {
+               tc_log(1, "could not get disk info\n");
+               return -1;
+       }
+
+       if (blocks <= MIN_VOL_BLOCKS) {
+               tc_log(1, "Cannot create volumes on devices with less "
+                   "than %d blocks/sectors\n", MIN_VOL_BLOCKS);
+               return -1;
+       }
+
+       if (interactive) {
+               if (((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) ||
+                  ((pass_again = alloc_safe_mem(MAX_PASSSZ)) == NULL)) {
+                       tc_log(1, "could not allocate safe passphrase memory\n");
+                       return -1;
+               }
+
+               if ((error = read_passphrase("Passphrase: ", pass, MAX_PASSSZ, 0) ||
+                  (read_passphrase("Repeat passphrase: ", pass_again,
+                  MAX_PASSSZ, 0)))) {
+                       tc_log(1, "could not read passphrase\n");
+                       return -1;
+               }
+
+               if (strcmp(pass, pass_again) != 0) {
+                       tc_log(1, "Passphrases don't match\n");
+                       return -1;
+               }
+
+               free_safe_mem(pass_again);
+       } else {
+               /* In batch mode, use provided passphrase */
+               if ((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) {
+                       tc_log(1, "could not allocate safe "
+                           "passphrase memory");
+                       return -1;
+               }
+
+               if (passphrase != NULL)
+                       strcpy(pass, passphrase);
+       }
+
+       if (nkeyfiles > 0) {
+               /* Apply keyfiles to 'pass' */
+               if ((error = apply_keyfiles(pass, MAX_PASSSZ, keyfiles,
+                   nkeyfiles))) {
+                       tc_log(1, "could not apply keyfiles\n");
+               }
+       }
+
+       if (hidden) {
+               if (interactive) {
+                       if (((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) ||
+                          ((pass_again = alloc_safe_mem(MAX_PASSSZ)) == NULL)) {
+                               tc_log(1, "could not allocate safe "
+                                   "passphrase memory\n");
+                               return -1;
+                       }
+
+                       if ((error = read_passphrase("Passphrase for hidden volume: ",
+                          h_pass, MAX_PASSSZ, 0) ||
+                          (read_passphrase("Repeat passphrase: ", pass_again,
+                          MAX_PASSSZ, 0)))) {
+                               tc_log(1, "could not read passphrase\n");
+                               return -1;
+                       }
+
+                       if (strcmp(h_pass, pass_again) != 0) {
+                               tc_log(1, "Passphrases for hidden volume don't "
+                                   "match\n");
+                               return -1;
+                       }
+
+                       free_safe_mem(pass_again);
+               } else {
+                       /* In batch mode, use provided passphrase */
+                       if ((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) {
+                               tc_log(1, "could not allocate safe "
+                                   "passphrase memory");
+                               return -1;
+                       }
+
+                       if (h_passphrase != NULL)
+                               strcpy(h_pass, h_passphrase);
+               }
+
+               if (n_hkeyfiles > 0) {
+                       /* Apply keyfiles to 'h_pass' */
+                       if ((error = apply_keyfiles(h_pass, MAX_PASSSZ, h_keyfiles,
+                       n_hkeyfiles))) {
+                               tc_log(1, "could not apply keyfiles\n");
+                               return -1;
+                       }
+               }
+
+               if (interactive) {
+                       hidden_blocks = 0;
+               } else {
+                       hidden_blocks = hidden_blocks_in;
+                       if (hidden_blocks == 0) {
+                               tc_log(1, "hidden_blocks to create volume "
+                                   "cannot be zero!\n");
+                               return -1;
+                       }
+               }
+
+               /* This only happens in interactive mode */
+               while (hidden_blocks == 0) {
+                       if ((r = humanize_number(buf, strlen("XXX MB"),
+                           (int64_t)(blocks * blksz), "B", 0, 0)) < 0) {
+                               sprintf(buf, "%zu bytes", (blocks * blksz));
+                       }
+
+                       printf("The total volume size of %s is %s (bytes)\n", dev, buf);
+                       memset(buf, 0, sizeof(buf));
+                       printf("Size of hidden volume (e.g. 127M): ");
+                       fflush(stdout);
+
+                       if ((fgets(buf, sizeof(buf), stdin)) == NULL) {
+                               tc_log(1, "Could not read from stdin\n");
+                               return -1;
+                       }
+
+                       /* get rid of trailing newline */
+                       buf[strlen(buf)-1] = '\0';
+                       if ((error = dehumanize_number(buf,
+                           &tmp)) != 0) {
+                               tc_log(1, "Could not interpret input: %s\n", buf);
+                               return -1;
+                       }
+
+                       hidden_blocks = (size_t)tmp;
+                       hidden_blocks /= blksz;
+                       if (hidden_blocks >= blocks - MIN_VOL_BLOCKS) {
+                               tc_log(1, "Hidden volume needs to be "
+                                   "smaller than the outer volume\n");
+                               hidden_blocks = 0;
+                               continue;
+                       }
+               }
+       }
+
+       if (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 "
+                           "outer volume\n",
+                           hidden_blocks * blksz);
+               }
+
+               printf("\n Are you sure you want to proceed? (y/n) ");
+               fflush(stdout);
+               if ((fgets(buf, sizeof(buf), stdin)) == NULL) {
+                       tc_log(1, "Could not read from stdin\n");
+                       return -1;
+               }
+
+               if ((buf[0] != 'y') && (buf[0] != 'Y')) {
+                       tc_log(1, "User cancelled action(s)\n");
+                       return -1;
+               }
+       }
+
+       /* erase volume */
+       if ((error = secure_erase(dev, blocks * blksz, blksz)) != 0) {
+               tc_log(1, "could not securely erase device %s\n", dev);
+               return -1;
+       }
+
+       /* create encrypted headers */
+       ehdr = create_hdr(pass, (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass),
+           prf_algo, cipher_chain, blksz, blocks, MIN_VOL_BLOCKS,
+           blocks-MIN_VOL_BLOCKS, 0);
+       if (ehdr == NULL) {
+               tc_log(1, "Could not create header\n");
+               return -1;
+       }
+
+       if (hidden) {
+               hehdr = create_hdr(h_pass,
+                   (n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), h_prf_algo,
+                   h_cipher_chain,
+                   blksz, blocks, blocks - hidden_blocks, hidden_blocks, 1);
+               if (hehdr == NULL) {
+                       tc_log(1, "Could not create hidden volume header\n");
+                       return -1;
+               }
+       }
+
+       if ((error = write_mem(dev, 0, blksz, ehdr, sizeof(*ehdr))) != 0) {
+               tc_log(1, "Could not write volume header to device\n");
+               return -1;
+       }
+
+       if (hidden) {
+               if ((error = write_mem(dev, HDR_OFFSET_HIDDEN, blksz, hehdr,
+                   sizeof(*hehdr))) != 0) {
+                       tc_log(1, "Could not write hidden volume header to "
+                           "device\n");
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+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)
+{
+       struct tchdr_enc *ehdr, *hehdr = NULL;
+       struct tcplay_info *info, *hinfo = NULL;
+       char *pass;
+       char *h_pass;
+       int error, error2 = 0;
+       size_t sz;
+
+       info = NULL;
+       if (retries < 1)
+               retries = 1;
+
+       while ((info == NULL) && retries-- > 0)
+       {
+               h_pass = NULL;
+               ehdr = NULL;
+               hehdr = NULL;
+
+               if ((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) {
+                       tc_log(1, "could not allocate safe passphrase memory\n");
+                       return NULL;
+               }
+
+               if (interactive) {
+                       if ((error = read_passphrase("Passphrase: ", pass,
+                           MAX_PASSSZ, timeout))) {
+                               tc_log(1, "could not read passphrase\n");
+                               return NULL;
+                       }
+               } else {
+                       /* In batch mode, use provided passphrase */
+                       if (passphrase != NULL)
+                               strcpy(pass, passphrase);
+               }
+
+               if (nkeyfiles > 0) {
+                       /* Apply keyfiles to 'pass' */
+                       if ((error = apply_keyfiles(pass, MAX_PASSSZ, keyfiles,
+                           nkeyfiles))) {
+                               tc_log(1, "could not apply keyfiles");
+                               return NULL;
+                       }
+               }
+
+               if (protect_hidden) {
+                       if ((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) {
+                               tc_log(1, "could not allocate safe passphrase memory\n");
+                               return NULL;
+                       }
+
+                       if (interactive) {
+                               if ((error = read_passphrase(
+                                   "Passphrase for hidden volume: ", h_pass,
+                                   MAX_PASSSZ, timeout))) {
+                                       tc_log(1, "could not read passphrase\n");
+                                       return NULL;
+                               }
+                       } else {
+                               /* In batch mode, use provided passphrase */
+                               if (passphrase_hidden != NULL)
+                                       strcpy(h_pass, passphrase_hidden);
+                       }
+
+                       if (n_hkeyfiles > 0) {
+                               /* Apply keyfiles to 'pass' */
+                               if ((error = apply_keyfiles(h_pass, MAX_PASSSZ, h_keyfiles,
+                                   n_hkeyfiles))) {
+                                       tc_log(1, "could not apply keyfiles");
+                                       return NULL;
+                               }
+                       }
+               }
+
+               sz = HDRSZ;
+               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);
+                       return NULL;
+               }
+
+               if (!sflag) {
+                       sz = HDRSZ;
+                       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);
+                               return NULL;
+                       }
+               } else {
+                       hehdr = NULL;
+               }
+
+               error = process_hdr(dev, pass, (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass),
+                   ehdr, &info);
+
+               /*
+                * Try to process hidden header if we have to protect the hidden
+                * volume, or the decryption/verification of the main header
+                * failed.
+                */
+               if (hehdr && (error || protect_hidden)) {
+                       if (error) {
+                               error2 = process_hdr(dev, pass,
+                                   (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), hehdr,
+                                   &info);
+                       } else if (protect_hidden) {
+                               error2 = process_hdr(dev, h_pass,
+                                   (n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), hehdr,
+                                   &hinfo);
+                       }
+               }
+
+               /* We need both to protect a hidden volume */
+               if ((protect_hidden && (error || error2)) ||
+                   (error && error2)) {
+                       tc_log(1, "Incorrect password or not a TrueCrypt volume\n");
+                       info = NULL;
+                       hinfo = NULL;
+
+                       /* Try again (or finish) */
+                       free_safe_mem(pass);
+                       if (h_pass)
+                               free_safe_mem(h_pass);
+                       if (ehdr)
+                               free_safe_mem(ehdr);
+                       if (hehdr)
+                               free_safe_mem(hehdr);
+                       continue;
+               }
+
+               if (protect_hidden) {
+                       if (adjust_info(info, hinfo) != 0) {
+                               tc_log(1, "Could not protect hidden volume\n");
+                               return NULL;
+                       }
+               }
+        }
+
+       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)
+{
+       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);
+
+       if (info != NULL) {
+               if (interactive)
+                       print_info(info);
+       }
+
+       return (info != NULL) ? 0 : -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)
+
+{
+       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);
+
+       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);
+               return -1;
+       }
+
+       if (interactive)
+               printf("All ok!");
+
+       return 0;
+}
+
+static
+int
+dm_remove_device(const char *name)
+{
+       struct dm_task *dmt = NULL;
+       int ret = EINVAL;
+
+       if ((dmt = dm_task_create(DM_DEVICE_REMOVE)) == NULL)
+               goto out;
+
+       if ((dm_task_set_name(dmt, name)) == 0)
+               goto out;
+
+       if ((dm_task_run(dmt)) == 0)
+               goto out;
+
+       ret = 0;
+out:
+       if (dmt)
+               dm_task_destroy(dmt);
+
+       return ret;
+}
+
+int
+dm_setup(const char *mapname, struct tcplay_info *info)
+{
+       struct tc_cipher_chain *cipher_chain;
+       struct dm_task *dmt = NULL;
+       struct dm_info dmi;
+       char *params = NULL;
+       char *uu;
+       char *uu_stack[64];
+       int uu_stack_idx;
+       uint32_t status;
+       int ret = 0;
+       int j;
+       off_t start, offset;
+       char dev[PATH_MAX];
+       char map[PATH_MAX];
+
+       if ((params = alloc_safe_mem(512)) == NULL) {
+               tc_log(1, "could not allocate safe parameters memory");
+               return ENOMEM;
+       }
+
+       strcpy(dev, info->dev);
+       start = info->start;
+       offset = info->offset;
+       uu_stack_idx = 0;
+
+       /* 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++) {
+               /* 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,
+                   info->skip, dev, 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;
+                       goto out;
+               }
+
+               /*
+                * If this is the last element in the cipher chain, use the
+                * final map name. Otherwise pick a secondary name...
+                */
+               if (cipher_chain->prev == NULL)
+                       strcpy(map, mapname);
+               else
+                       sprintf(map, "%s.%d", mapname, j);
+
+               if ((dm_task_set_name(dmt, map)) == 0) {
+                       tc_log(1, "dm_task_set_name failed\n");
+                       ret = -1;
+                       goto out;
+               }
+
+               uuid_create(&info->uuid, &status);
+               if (status != uuid_s_ok) {
+                       tc_log(1, "uuid_create failed\n");
+                       ret = -1;
+                       goto out;
+               }
+
+               uuid_to_string(&info->uuid, &uu, &status);
+               if (uu == NULL) {
+                       tc_log(1, "uuid_to_string failed\n");
+                       ret = -1;
+                       goto out;
+               }
+
+               if ((dm_task_set_uuid(dmt, uu)) == 0) {
+                       free(uu);
+                       tc_log(1, "dm_task_set_uuid failed\n");
+                       ret = -1;
+                       goto out;
+               }
+
+               free(uu);
+
+               if ((dm_task_add_target(dmt, start, info->size, "crypt", params)) == 0) {
+                       tc_log(1, "dm_task_add_target failed\n");
+                       ret = -1;
+                       goto out;
+               }
+
+               if ((dm_task_run(dmt)) == 0) {
+                       tc_log(1, "dm_task_task_run failed\n");
+                       ret = -1;
+                       goto out;
+               }
+
+               if ((dm_task_get_info(dmt, &dmi)) == 0) {
+                       tc_log(1, "dm_task_get info failed\n");
+                       /* XXX: probably do more than just erroring out... */
+                       ret = -1;
+                       goto out;
+               }
+
+               asprintf(&uu_stack[uu_stack_idx++], "%s", map);
+
+               offset = 0;
+               start = 0;
+               sprintf(dev, "/dev/mapper/%s.%d", mapname, j);
+
+               dm_task_destroy(dmt);
+       }
+
+out:
+       /*
+        * If an error occured, try to unroll changes made before it
+        * happened.
+        */
+       if (ret) {
+               j = uu_stack_idx;
+               while (j > 0) {
+#ifdef DEBUG
+                       printf("Unrolling dm changes! j = %d (%s)\n", j-1,
+                           uu_stack[j-1]);
+#endif
+                       if ((ret = dm_remove_device(uu_stack[--j])) != 0) {
+                               tc_log(1, "Tried to unroll dm changes, "
+                                   "giving up.\n");
+                               break;
+                       }
+               }
+       }
+
+       while (uu_stack_idx > 0)
+               free(uu_stack[--uu_stack_idx]);
+
+       free_safe_mem(params);
+
+       return ret;
+}
+
+int
+dm_teardown(const char *mapname, const char *device __unused)
+{
+#if 0
+       struct dm_task *dmt = NULL;
+       struct dm_info dmi;
+#endif
+       char map[PATH_MAX];
+       int i, error;
+
+       if ((error = dm_remove_device(mapname)) != 0) {
+               tc_log(1, "Could not remove mapping %s\n", mapname);
+               return error;
+       }
+
+       /* Try to remove other cascade devices */
+       for (i = 2; i >= 0; i--) {
+               sprintf(map, "%s.%d", mapname, i);
+               dm_remove_device(map);
+       }
+
+       return 0;
+}
+
+struct tc_crypto_algo *
+check_cipher(const char *cipher, int quiet)
+{
+       int i, found = 0;
+
+       for (i = 0; tc_crypto_algos[i].name != NULL; i++) {
+               if (strcmp(cipher, tc_crypto_algos[i].name) == 0) {
+                       found = 1;
+                       break;
+               }
+       }
+
+       if (!found && !quiet) {
+               fprintf(stderr, "Valid ciphers are: ");
+               for (i = 0; tc_crypto_algos[i].name != NULL; i++)
+                       fprintf(stderr, "%s ", tc_crypto_algos[i].name);
+               fprintf(stderr, "\n");
+               return NULL;
+       }
+
+       return &tc_crypto_algos[i];
+}
+
+struct tc_cipher_chain *
+check_cipher_chain(char *cipher_chain, int quiet)
+{
+       struct tc_cipher_chain *cipher = NULL;
+       int i,k, nciphers = 0, mismatch = 0;
+       char *ciphers[8];
+       char *tmp_chain, *tmp_chain_free;
+       char *token;
+
+       if ((tmp_chain = strdup(cipher_chain)) == NULL) {
+               tc_log(1, "Could not allocate strdup memory\n");
+               return NULL;
+       }
+
+       tmp_chain_free = tmp_chain;
+
+       while ((token = strsep(&tmp_chain, ",")) != NULL)
+               ciphers[nciphers++] = token;
+
+       cipher = NULL;
+
+       for (i = 0; valid_cipher_chains[i][0] != NULL; i++) {
+               mismatch = 0;
+
+               for (k = 0; (valid_cipher_chains[i][k] != NULL); k++) {
+                       /*
+                        * If there are more ciphers in the chain than in the
+                        * ciphers[] variable this is not the right chain.
+                        */
+                       if (k == nciphers) {
+                               mismatch = 1;
+                               break;
+                       }
+
+                       if (strcmp(ciphers[k], valid_cipher_chains[i][k]) != 0)
+                               mismatch = 1;
+               }
+
+               /*
+                * If all ciphers matched and there are exactly nciphers,
+                * then we found the right cipher chain.
+                */
+               if ((k == nciphers) && !mismatch) {
+                       cipher = tc_cipher_chains[i];
+                       break;
+               }
+       }
+
+       if (cipher == NULL) {
+               tc_log(1, "Invalid cipher: %s\n", cipher_chain);
+               if (!quiet) {
+                       fprintf(stderr, "Valid cipher chains are:\n");
+                       for (i = 0; valid_cipher_chains[i][0] != NULL; i++) {
+                               for (k = 0; valid_cipher_chains[i][k] != NULL;
+                                   k++) {
+                                       fprintf(stderr, "%s%c",
+                                           valid_cipher_chains[i][k],
+                                           (valid_cipher_chains[i][k+1] != NULL) ?
+                                           ',' : '\0');
+                               }
+                               fprintf(stderr, "\n");
+                       }
+               }
+       }
+
+       free(tmp_chain_free);
+       return cipher;
+}
+
+struct pbkdf_prf_algo *
+check_prf_algo(char *algo, int quiet)
+{
+       int i, found = 0;
+
+       for (i = 0; pbkdf_prf_algos[i].name != NULL; i++) {
+               if (strcmp(algo, pbkdf_prf_algos[i].name) == 0) {
+                       found = 1;
+                       break;
+               }
+       }
+
+       if (!found && !quiet) {
+               fprintf(stderr, "Valid PBKDF PRF algorithms are: ");
+               for (i = 0; pbkdf_prf_algos[i].name != NULL; i++)
+                       fprintf(stderr, "%s ", pbkdf_prf_algos[i].name);
+               fprintf(stderr, "\n");
+               return NULL;
+       }
+
+       return &pbkdf_prf_algos[i];
+}
+
+int
+tc_play_init(void)
+{
+       int error;
+
+       if ((error = tc_build_cipher_chains()) != 0)
+               return error;
+
+       if ((error = tc_crypto_init()) != 0)
+               return error;
+
+       return 0;
+}
diff --git a/lib/libtcplay/tcplay.h b/lib/libtcplay/tcplay.h
new file mode 100644 (file)
index 0000000..4cebb0e
--- /dev/null
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* Version of tcplay */
+#define MAJ_VER                        0
+#define MIN_VER                        8
+
+
+#define MAX_BLKSZ              4096
+#define MAX_KEYSZ              192
+#define HDRSZ                  512
+#define HDR_OFFSET_SYS         31744   /* 512 * (63 -1) */
+#define TC_SIG                 "TRUE"
+#define MAX_PASSSZ             64
+#define KPOOL_SZ               64
+#define MAX_KFILE_SZ           1048576 /* 1 MB */
+#define MAX_KEYFILES           256
+#define HDR_OFFSET_HIDDEN      65536
+#define SALT_LEN               64
+#define MIN_VOL_BLOCKS         256
+#define MAX_CIPHER_CHAINS      64
+#define DEFAULT_RETRIES                3
+#define ERASE_BUFFER_SIZE      4*1024*1024 /* 4 MB */
+
+/* TrueCrypt Volume flags */
+#define TC_VOLFLAG_SYSTEM      0x01    /* system encryption */
+#define TC_VOLFLAG_INPLACE     0x02    /* non-system in-place-encrypted volume */
+
+#define LOG_BUFFER_SZ          1024
+#if 0
+#define DEBUG 1
+#endif
+
+#include <uuid.h>
+
+struct pbkdf_prf_algo {
+       const char *name;
+       int iteration_count;
+};
+
+struct tc_crypto_algo {
+       const char *name;
+       const char *dm_crypt_str;
+       int klen;
+       int ivlen;
+};
+
+struct tc_cipher_chain {
+       struct tc_crypto_algo *cipher;
+       unsigned char *key;
+       char dm_key[MAX_KEYSZ*2 + 1];
+
+       struct tc_cipher_chain *prev;
+       struct tc_cipher_chain *next;
+};
+
+struct tchdr_enc {
+       unsigned char salt[SALT_LEN];   /* Salt for PBKDF */
+       unsigned char enc[448];         /* Encrypted part of the header */
+} __attribute__((__packed__));
+
+struct tchdr_dec {
+       char            tc_str[4];      /* ASCII string "TRUE" */
+       uint16_t        tc_ver;         /* Volume header format version */
+       uint16_t        tc_min_ver;
+       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) */
+       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;
+       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 */
+
+       /* Populated by dm_setup */
+       uuid_t uuid;
+};
+
+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 write_mem(const char *dev, off_t offset, size_t blksz, void *mem, size_t bytes);
+int read_passphrase(const char *prompt, char *pass, size_t passlen,
+    time_t timeout);
+
+int tc_crypto_init(void);
+int tc_cipher_chain_populate_keys(struct tc_cipher_chain *cipher_chain,
+    unsigned char *key);
+int tc_encrypt(struct tc_cipher_chain *cipher_chain, unsigned char *key,
+    unsigned char *iv,
+    unsigned char *in, int in_len, unsigned char *out);
+int tc_decrypt(struct tc_cipher_chain *cipher_chain, unsigned char *key,
+    unsigned char *iv,
+    unsigned char *in, int in_len, unsigned char *out);
+int pbkdf2(const char *pass, int passlen, const unsigned char *salt, int saltlen,
+    int iter, const char *hash_name, int keylen, unsigned char *out);
+int apply_keyfiles(unsigned char *pass, size_t pass_memsz, const char *keyfiles[],
+    int nkeyfiles);
+
+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);
+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);
+
+void *_alloc_safe_mem(size_t req_sz, 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);
+
+int tc_play_init(void);
+void tc_log(int err, const char *fmt, ...);
+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,
+    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_blocks_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 dm_setup(const char *mapname, struct tcplay_info *info);
+int dm_teardown(const char *mapname, const char *device);
+
+typedef void(*summary_fn_t)(void);
+
+extern int tc_internal_verbose;
+extern char tc_internal_log_buffer[];
+extern summary_fn_t summary_fn;
+
+#define alloc_safe_mem(x) \
+       _alloc_safe_mem(x, __FILE__, __LINE__)
+
+#define free_safe_mem(x) \
+       _free_safe_mem(x, __FILE__, __LINE__)
diff --git a/lib/libtcplay/tcplay.map b/lib/libtcplay/tcplay.map
new file mode 100644 (file)
index 0000000..d63cd0b
--- /dev/null
@@ -0,0 +1,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;
+local:
+       *;
+};
diff --git a/lib/libtcplay/tcplay_api.c b/lib/libtcplay/tcplay_api.c
new file mode 100644 (file)
index 0000000..79f0ea1
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "tcplay.h"
+#include "tcplay_api.h"
+
+int
+tc_api_init(int verbose)
+{
+       int error;
+
+       tc_internal_verbose = verbose;
+
+       if ((error = tc_play_init()) != 0)
+               return TC_ERR;
+       else
+               return TC_OK;
+}
+
+int
+tc_api_uninit(void)
+{
+       check_and_purge_safe_mem();
+       return TC_OK;
+}
+
+const char *
+tc_api_get_error_msg(void)
+{
+       return tc_internal_log_buffer;
+}
+
+const char *
+tc_api_get_summary(void)
+{
+       if (summary_fn != NULL) {
+               summary_fn();
+               return tc_internal_log_buffer;
+       }
+
+       return NULL;
+}
+
+int
+tc_api_create_volume(tc_api_opts *api_opts)
+{
+       int nkeyfiles, n_hkeyfiles = 0;
+       int create_hidden;
+       int err;
+
+       if ((api_opts == NULL) ||
+           (api_opts->tc_device == NULL)) {
+               errno = EFAULT;
+               return TC_ERR;
+       }
+
+       if ((err = tc_api_check_cipher(api_opts)) != TC_OK)
+               return TC_ERR;
+
+       if ((err = tc_api_check_prf_hash(api_opts)) != TC_OK)
+               return TC_ERR;
+
+       for (nkeyfiles = 0; (nkeyfiles < MAX_KEYFILES) &&
+           (api_opts->tc_keyfiles != NULL) &&
+           (api_opts->tc_keyfiles[nkeyfiles] != NULL); nkeyfiles++)
+               ;
+
+       create_hidden = 0;
+
+       if (api_opts->tc_size_hidden_in_blocks > 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++)
+                       ;
+       }
+
+       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_blocks, 0 /* non-interactive */);
+
+       return (err) ? TC_ERR : TC_OK;
+}
+
+int
+tc_api_map_volume(tc_api_opts *api_opts)
+{
+       int nkeyfiles;
+       int err;
+
+       if ((api_opts == NULL) ||
+           (api_opts->tc_device == NULL)) {
+               errno = EFAULT;
+               return TC_ERR;
+       }
+
+       for (nkeyfiles = 0; (nkeyfiles < MAX_KEYFILES) &&
+           (api_opts->tc_keyfiles != NULL) &&
+           (api_opts->tc_keyfiles[nkeyfiles] != NULL); nkeyfiles++)
+               ;
+
+       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);
+
+       return (err) ? TC_ERR : TC_OK;
+}
+
+int
+tc_api_unmap_volume(tc_api_opts *api_opts)
+{
+       int err;
+
+       if ((api_opts == NULL) ||
+           (api_opts->tc_map_name == NULL)) {
+               errno = EFAULT;
+               return TC_ERR;
+       }
+
+       err = dm_teardown(api_opts->tc_map_name, api_opts->tc_device);
+       return (err) ? TC_ERR : TC_OK;
+}
+
+int
+tc_api_check_cipher(tc_api_opts *api_opts)
+{
+       struct tc_cipher_chain *chain;
+
+       if (api_opts == NULL || api_opts->tc_cipher == NULL) {
+               errno = EFAULT;
+               return TC_ERR;
+       }
+
+       if ((chain = check_cipher_chain(api_opts->tc_cipher, 1)) != NULL)
+               return TC_OK;
+
+       errno = ENOENT;
+       return TC_ERR;
+}
+
+int
+tc_api_check_prf_hash(tc_api_opts *api_opts)
+{
+       struct pbkdf_prf_algo *prf_hash;
+
+       if (api_opts == NULL || api_opts->tc_prf_hash == NULL) {
+               errno = EFAULT;
+               return TC_ERR;
+       }
+
+       if ((prf_hash = check_prf_algo(api_opts->tc_prf_hash, 1)) != NULL)
+               return TC_OK;
+
+       errno = ENOENT;
+       return TC_ERR;
+}
+
diff --git a/lib/libtcplay/tcplay_api.h b/lib/libtcplay/tcplay_api.h
new file mode 100644 (file)
index 0000000..3a0fe4a
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * 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_blocks;
+       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);
+
diff --git a/lib/libtcplay/tcplay_api_test.c b/lib/libtcplay/tcplay_api_test.c
new file mode 100644 (file)
index 0000000..a8776ea
--- /dev/null
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#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_blocks = 12000;
+       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;
+}
+
index ab9741e..a5d58ad 100644 (file)
@@ -92,6 +92,7 @@ SUBDIR=       adjkerntz \
        startslip \
        swapon \
        sysctl \
+       tcplay \
        tunefs \
        udevd \
        umount \
diff --git a/sbin/tcplay/Makefile b/sbin/tcplay/Makefile
new file mode 100644 (file)
index 0000000..d3d1be3
--- /dev/null
@@ -0,0 +1,16 @@
+TCPLAY_DIR=     ${.CURDIR}/../../lib/libtcplay
+
+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
+
+LDADD= -lcrypto -ldevmapper -lprop -lutil
+
+CFLAGS+=       -I${TCPLAY_DIR}
+
+.PATH: ${TCPLAY_DIR}
+
+.include <bsd.prog.mk>
diff --git a/sbin/tcplay/main.c b/sbin/tcplay/main.c
new file mode 100644 (file)
index 0000000..024f62d
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#include <sys/types.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <err.h>
+#include <time.h>
+#include <libutil.h>
+
+#include "tcplay.h"
+
+static
+void
+sig_handler(int sig)
+{
+       if ((sig == SIGUSR1 || sig == SIGINFO) && (summary_fn != NULL))
+               summary_fn();
+}
+
+static
+void
+usage(void)
+{
+       fprintf(stderr,
+           "Usage: tcplay <command> [options]\n"
+           "Valid commands and its arguments are:\n"
+           " -c, --create\n"
+           "\t Creates a new TC volume on the device specified by -d or --device\n"
+           " -i, --info\n"
+           "\t Gives information about the TC volume specified by -d or --device\n"
+           " -m <mapping name>, --map=<mapping name>\n"
+           "\t Creates a dm-crypt mapping with the given name for the device\n"
+           "\t specified by -d or --device\n"
+           "\n"
+           "Valid options and its arguments for 'create' are:\n"
+           " -a <pbkdf prf algorithm>, --pbkdf-prf=<pbkdf prf algorithm>\n"
+           "\t specifies which hashing function to use for the PBKDF password\n"
+           "\t derivation when creating a new volume\n"
+           "\t To see valid options, specify -a help\n"
+           " -b <cipher>, --cipher=<cipher>\n"
+           "\t specifies which cipher to use when creating a new TC volume\n"
+           "\t To see valid options, specify -b help\n"
+           " -x <pbkdf prf algorithm>, --pbkdf-prf=<pbkdf prf algorithm>\n"
+           "\t specifies which hashing function to use for the PBKDF password\n"
+           "\t derivation when creating a new hidden volume. By default, the\n"
+           "\t same as for the outer volume will be used\n"
+           "\t To see valid options, specify -a help\n"
+           " -y <cipher>, --cipher=<cipher>\n"
+           "\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 -b help\n"
+           " -g, --hidden\n"
+           "\t specifies that the newly created volume will contain a hidden volume\n"
+           "\n"
+           "Valid options and its arguments for 'info' and 'map' are:\n"
+           " -e, --protect-hidden\n"
+           "\t protect a hidden volume when mounting the outer volume\n"
+           " -s <disk path>, --system-encryption=<disk path>\n"
+           "\t specifies that the disk (e.g. /dev/da0) is using system encryption\n"
+           "\n"
+           "Valid options and its arguments common to all commands are:\n"
+           " -d <device path>, --device=<device path>\n"
+           "\t specifies the path to the volume to operate on (e.g. /dev/da0s1)\n"
+           " -k <key file>, --keyfile=<key file>\n"
+           "\t specifies a key file to use for the password derivation, can appear\n"
+           "\t multiple times\n"
+           " -f <key file>, --keyfile-hidden=<key file>\n"
+           "\t specifies a key file to use for the hidden volume password derivation\n"
+           "\t This option is only valid in combination with -e, --protect-hidden\n"
+           "\t or -g, --hidden\n"
+           );
+
+       exit(1);
+}
+
+static struct option longopts[] = {
+       { "create",             no_argument,            NULL, 'c' },
+       { "cipher",             required_argument,      NULL, 'b' },
+       { "cipher-hidden",      required_argument,      NULL, 'y' },
+       { "hidden",             no_argument,            NULL, 'g' },
+       { "pbkdf-prf",          required_argument,      NULL, 'a' },
+       { "pbkdf-prf-hidden",   required_argument,      NULL, 'x' },
+       { "info",               no_argument,            NULL, 'i' },
+       { "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' },
+       { "version",            no_argument,            NULL, 'v' },
+       { "help",               no_argument,            NULL, 'h' },
+       { NULL,                 0,                      NULL, 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;
+       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;
+
+       if ((error = tc_play_init()) != 0) {
+               fprintf(stderr, "Initialization failed, exiting.");
+               exit(1);
+       }
+
+       atexit(check_and_purge_safe_mem);
+       signal(SIGUSR1, sig_handler);
+       signal(SIGINFO, sig_handler);
+
+       nkeyfiles = 0;
+       n_hkeyfiles = 0;
+
+       while ((ch = getopt_long(argc, argv, "a:b:cd:efgh:ik:m:s:vx:y:",
+           longopts, NULL)) != -1) {
+               switch(ch) {
+               case 'a':
+                       if (prf != NULL)
+                               usage();
+                       if ((prf = check_prf_algo(optarg, 0)) == NULL) {
+                               if (strcmp(optarg, "help") == 0)
+                                       exit(0);
+                               else
+                                       usage();
+                               /* NOT REACHED */
+                       }
+                       break;
+               case 'b':
+                       if (cipher_chain != NULL)
+                               usage();
+                       if ((cipher_chain = check_cipher_chain(optarg, 0)) == NULL) {
+                               if (strcmp(optarg, "help") == 0)
+                                       exit(0);
+                               else
+                                       usage();
+                               /* NOT REACHED */
+                       }
+                       break;
+               case 'c':
+                       create_vol = 1;
+                       break;
+               case 'd':
+                       dev = optarg;
+                       break;
+               case 'e':
+                       protect_hidden = 1;
+                       break;
+               case 'f':
+                       h_keyfiles[n_hkeyfiles++] = optarg;
+                       break;
+               case 'g':
+                       contain_hidden = 1;
+                       break;
+               case 'i':
+                       info_vol = 1;
+                       break;
+               case 'k':
+                       keyfiles[nkeyfiles++] = optarg;
+                       break;
+               case 'm':
+                       map_vol = 1;
+                       map_name = optarg;
+                       break;
+               case 's':
+                       sflag = 1;
+                       sys_dev = optarg;
+                       break;
+               case 'v':
+                       printf("tcplay v%d.%d\n", MAJ_VER, MIN_VER);
+                       exit(0);
+                       /* NOT REACHED */
+               case 'x':
+                       if (h_prf != NULL)
+                               usage();
+                       if ((h_prf = check_prf_algo(optarg, 0)) == NULL) {
+                               if (strcmp(optarg, "help") == 0)
+                                       exit(0);
+                               else
+                                       usage();
+                               /* NOT REACHED */
+                       }
+                       break;
+               case 'y':
+                       if (h_cipher_chain != NULL)
+                               usage();
+                       if ((h_cipher_chain = check_cipher_chain(optarg, 0)) == NULL) {
+                               if (strcmp(optarg, "help") == 0)
+                                       exit(0);
+                               else
+                                       usage();
+                               /* NOT REACHED */
+                       }
+                       break;
+               case 'h':
+               case '?':
+               default:
+                       usage();
+                       /* NOT REACHED */
+               }
+       }
+
+       argc -= optind;
+       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)) {
+               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 */);
+               if (error) {
+                       tc_log(1, "could not create new volume on %s\n", dev);
+               }
+       } 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);
+       } 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);
+       }
+
+       return error;
+}
diff --git a/sbin/tcplay/tcplay.8 b/sbin/tcplay/tcplay.8
new file mode 100644 (file)
index 0000000..328c5cf
--- /dev/null
@@ -0,0 +1,227 @@
+.\"
+.\" Copyright (c) 2011
+.\"    The DragonFly Project.  All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in
+.\"    the documentation and/or other materials provided with the
+.\"    distribution.
+.\" 3. Neither the name of The DragonFly Project nor the names of its
+.\"    contributors may be used to endorse or promote products derived
+.\"    from this software without specific, prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.Dd July 5, 2011
+.Dt TCPLAY 8
+.Os
+.Sh NAME
+.Nm tcplay
+.Nd tool to manage TrueCrypt volumes
+.Sh SYNOPSIS
+.Nm
+.Fl c
+.Fl d Ar device
+.Op Fl g
+.Op Fl a Ar pbkdf_hash
+.Op Fl b Ar cipher
+.Op Fl k Ar keyfile
+.Op Fl f Ar keyfile_hidden
+.Nm
+.Fl i
+.Fl d Ar device
+.Op Fl e
+.Op Fl s Ar system_device
+.Nm
+.Fl m Ar mapping
+.Fl d Ar device
+.Op Fl e
+.Op Fl s Ar system_device
+.Sh DESCRIPTION
+The
+.Nm
+utility provides full support for creating and opening/mapping
+TrueCrypt-compatible volumes.
+It supports the following commands, each with a set of options
+detailed further below:
+.Bl -tag -width indent
+.It Fl c , Fl -create
+Create a new encrypted TrueCrypt volume on the device
+specified by
+.Fl -device .
+.It Fl i , Fl -info
+Print out information about the encrypted device specified by
+.Fl -device .
+.It Fl m Ar mapping , Fl -map Ns = Ns Ar mapping
+Map the encrypted TrueCrypt volume on the device specified by
+.Fl -device
+as a
+.Xr dm 4
+mapping called
+.Ar mapping .
+.El
+.Pp
+Options common to all commands are:
+.Bl -tag -width indent
+.It Fl d Ar device , Fl -device Ns = Ns Ar device
+Specifies the disk
+.Ar device
+on which the TrueCrypt volume resides/will reside.
+This option is mandatory for all commands.
+.It Fl k Ar keyfile , Fl -keyfile Ns = Ns Ar keyfile
+Specifies a
+.Ar keyfile
+to use in addition to the passphrase.
+This option can appear multiple times; if so, multiple
+keyfiles will be used.
+.It Fl f Ar keyfile , Fl -keyfile-hidden Ns = Ns Ar keyfile
+Specifies a
+.Ar keyfile
+to use in addition to the passphrase when either creating a
+hidden volume or when protecting a hidden volume while mapping
+or querying the outer volume.
+If you only intend to map a hidden volume, the
+.Fl -keyfile
+option has to be used.
+This option can appear multiple times; if so, multiple
+keyfiles will be used.
+.El
+.Pp
+Additional options for the
+.Fl -create
+command are:
+.Bl -tag -width indent
+.It Fl a Ar pbkdf_hash , Fl -pbkdf-prf Ns = Ns Ar pbkdf_hash
+Specifies with hash algorithm to use for the PBKDF2 password
+derivation.
+To see which algorithms are supported, specify
+.Fl -pbkdf-prf Ns = Ns Ar help .
+.It Fl b Ar cipher , Fl -cipher Ns = Ns Ar cipher
+Specifies with cipher algorithm or cascade of ciphers to use
+to encrypt the new volume.
+To see which algorithms are supported, specify
+.Fl -cipher Ns = Ns Ar help .
+.It Fl x Ar pbkdf_hash , Fl -pbkdf-prf-hidden Ns = Ns Ar pbkdf_hash
+Specifies with hash algorithm to use for the PBKDF2 password
+derivation for the hidden volume.
+Only valid in conjunction with
+.Fl -hidden .
+If no algorithm is specified, the same as for the outer volume
+will be used.
+To see which algorithms are supported, specify
+.Fl -pbkdf-prf-hidden Ns = Ns Ar help .
+.It Fl y Ar cipher , Fl -cipher-hidden Ns = Ns Ar cipher
+Specifies with cipher algorithm or cascade of ciphers to use
+to encrypt the hidden volume on the new TrueCrypt volume.
+Only valid in conjunction with
+.Fl -hidden .
+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 Ar help .
+.It Fl g, Fl -hidden
+Specifies that the newly created volume will contain a hidden
+volume.
+The keyfiles applied to the passphrase for the hidden
+volume are those specified by
+.Fl -keyfile-hidden .
+The user will be prompted for the size of the hidden volume
+interactively.
+.El
+.Pp
+Additional options for the
+.Fl -info
+and
+.Fl -map
+commands are:
+.Bl -tag -width indent
+.It Fl e, Fl -protect-hidden
+Specifies that an outer volume will be queried or mapped, but
+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.
+.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.
+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.
+.El
+.Sh EXAMPLES
+Create a new TrueCrypt volume on
+.Pa /dev/vn0
+using the cipher cascade
+of AES and Twofish and the Whirlpool hash algorithm for
+PBKDF2 password derivation and two keyfiles,
+.Pa one.key
+and
+.Pa two.key :
+.Bd -ragged -offset indent
+.Nm Fl -create
+.Fl -device Ns = Ns Ar /dev/vn0
+.Fl -cipher Ns = Ns Ar AES-256-XTS,TWOFISH-256-XTS
+.Fl -pbkdf-prf Ns = Ns Ar whirlpool
+.Fl -keyfile Ns = Ns Ar one.key
+.Fl -keyfile Ns = Ns Ar two.key
+.Ed
+.Pp
+Map the outer volume on the TrueCrypt volume on
+.Pa /dev/vn0
+as
+.Sy truecrypt1 ,
+but protect the hidden volume, using the keyfile
+.Pa hidden.key ,
+from being overwritten:
+.Bd -ragged -offset indent
+.Nm Fl -map Ns = Ns Ar truecrypt1
+.Fl -device Ns = Ns Ar /dev/vn0
+.Fl -protect-hidden
+.Fl -keyfile-hidden Ns = Ns Ar hidden.key
+.Ed
+.Pp
+Map the hidden volume on the TrueCrypt volume on
+.Pa /dev/vn0
+as
+.Sy truecrypt2 ,
+using the keyfile
+.Pa hidden.key :
+.Bd -ragged -offset indent
+.Nm Fl -map Ns = Ns Ar truecrypt2
+.Fl -device Ns = Ns Ar /dev/vn0
+.Fl -keyfile Ns = Ns Ar hidden.key
+.Ed
+.Sh SEE ALSO
+.Xr crypttab 5 ,
+.Xr cryptsetup 8
+.Sh HISTORY
+The
+.Nm
+utility appeared in
+.Dx 2.11 .
+.Sh AUTHORS
+.An Alex Hornung
+