diff -Nru tcplay-0.11/.gitignore tcplay-1.1/.gitignore --- tcplay-0.11/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/.gitignore 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,7 @@ +*~ +*.swp +*.o +*.a +*.so +*.so.* +tcplay diff -Nru tcplay-0.11/CHANGELOG tcplay-1.1/CHANGELOG --- tcplay-0.11/CHANGELOG 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/CHANGELOG 2013-07-31 06:01:56.000000000 +0000 @@ -1,3 +1,51 @@ +2013-07-31 Version 1.1 - Alex Hornung + +Special thanks to Mike Baker, Joshua Escamilla and Alon Bar-Lev +for contributing fixes/improvements! + + * Added command to request information on mapped volumes. + + * Added support for full disk encryption. + + * Added support to query/map volumes using the backup headers + instead of the primary headers. + + * Prefixed dm(4) uuids with CRYPT-TCPLAY-. + + * Added the 'device' field to all info commands. + + * Added command to modify/reencrypt a header, allowing passphrase + and keyfile changes, as well as restoring from the backup + header. + + * Improvements to Makefile.classic. + + +2013-05-15 Version 1.0 - Alex Hornung + + * Fixed bug in hidden volume protection (issue #28). + + * Added cmake build infrastructure (old single-Makefile option + is also still available, using Makefile.classic). + + * Changed IV gen in mapped devices to plain64 from plain. + + * Fixed bug in passphrase logic - Truecrypt limits passphrases + to 64 bytes (issue #24). + + * Added unmap option to command line tool (issue #17). + + * Added option to use /dev/urandom for key material, although + this should only ever be used for testing purposes (issue + #19). + + * Complete API overhaul. See man page for details. + + * Added test framework and tests. + + * Other minor bugfixes and tweaks. + + 2012-11-16 Version 0.11 - Alex Hornung Special thanks to Cody Schafer(jmesmon) for providing some of the diff -Nru tcplay-0.11/CMakeLists.txt tcplay-1.1/CMakeLists.txt --- tcplay-0.11/CMakeLists.txt 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/CMakeLists.txt 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,153 @@ +cmake_minimum_required (VERSION 2.6) + +project (tcplay) +set (VERSION_MAJOR 1) +set (VERSION_MINOR 1) + +find_package (PkgConfig) +include (CheckLibraryExists) + +set (SRCS_COMMON tcplay.c crc32.c safe_mem.c io.c hdr.c humanize.c crypto.c generic_xts.c) +set (SRCS_LINUX crypto-gcrypt.c) + +set (CFLAGS_LINUX "-D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE") +set (CFLAGS_WARN "-Wsystem-headers -Wall -W -Wno-unused-parameter -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wold-style-definition -Wreturn-type -Wcast-qual -Wwrite-strings -Wswitch -Wshadow -Wcast-align -Wunused-parameter -Wchar-subscripts -Winline -Wnested-externs") +set (CFLAGS_DEBUG "-O0 -g3 -DDEBUG") +set (CFLAGS_OPT "-O3") +set (CFLAGS_VER "-DMAJ_VER=${VERSION_MAJOR} -DMIN_VER=${VERSION_MINOR}") +set (CFLAGS_COMMON "-std=c99 -fPIE ${CFLAGS_LINUX} ${CFLAGS_WARN} ${CFLAGS_VER}") +if ($ENV{DEBUG}) + set (CFLAGS_COMMON "${CFLAGS_COMMON} ${CFLAGS_DEBUG}") +else() + set (CFLAGS_COMMON "${CFLAGS_COMMON} ${CFLAGS_OPT}") +endif() + +pkg_check_modules(DEVMAPPER devmapper) +pkg_check_modules(UUID uuid) +find_library(GCRYPT_LIB gcrypt) + +if (NOT DEVMAPPER_FOUND) + message(FATAL_ERROR "Could not find the devmapper library") +endif() + +if (NOT UUID_FOUND) + message(FATAL_ERROR "Could not find the uuid library") +endif() + +if (NOT GCRYPT_LIB) + message(FATAL_ERROR "Could not find the gcrypt library") +else() + set (GCRYPT_LDFLAGS "-lgcrypt -lgpg-error") + set (GCRYPT_CFLAGS "") +endif() + +set (CMAKE_REQUIRED_LIBRARIES gpg-error) +# gcrypt>=1.5.0 required +check_library_exists(gcrypt gcry_kdf_derive "" HAVE_GCRYPT_PBKDF) +set (CMAKE_REQUIRED_LIBRARIES "") + +if (HAVE_GCRYPT_PBKDF) + set (SRCS_PBKDF pbkdf2-gcrypt.c) + set (LDFLAGS_PBKDF "") + set (CFLAGS_PBKDF "") + set (PBKDF_BACKEND gcrypt) +else() + pkg_check_modules(OPENSSL openssl>=1.0.0a) + if (NOT OPENSSL_FOUND) + message(FATAL_ERROR "Could not find a gcrypt with gcry_kdf_derive() nor OpenSSL >= 1.0.0a") + endif() + set (SRCS_PBKDF pbkdf2-openssl.c) + set (LDFLAGS_PBKDF ${OPENSSL_LDFLAGS}) + set (CFLAGS_PBKDF ${OPENSSL_CFLAGS}) + set (PBKDF_BACKEND openssl) +endif() + + +if (NOT LIB_SUFFIX) + message(STATUS "") + message(STATUS "LIB_SUFFIX variable is not defined. It will be autodetected now.") + message(STATUS "You can set it manually with -DLIB_SUFFIX= (e.g. 64).") + if (CMAKE_SIZEOF_VOID_P EQUAL 8) + message(STATUS "\nSetting LIB_SUFFIX=64\n") + set (LIB_SUFFIX "64") + else() + message(STATUS "\nSetting LIB_SUFFIX=\n") + set (LIB_SUFFIX "") + endif() +endif() + + + +add_executable(tcplay-bin main.c ${SRCS_COMMON} ${SRCS_LINUX} ${SRCS_PBKDF}) +set_target_properties(tcplay-bin PROPERTIES OUTPUT_NAME tcplay) +set_target_properties(tcplay-bin PROPERTIES COMPILE_FLAGS "${CFLAGS_COMMON} ${DEVMAPPER_CFLAGS} ${UUID_CFLAGS} ${GCRYPT_CFLAGS}") +target_link_libraries(tcplay-bin ${DEVMAPPER_LDFLAGS} ${UUID_LDFLAGS} ${GCRYPT_LDFLAGS} ${LDFLAGS_PBKDF}) + + +add_library(tcplay-so SHARED tcplay_api.c ${SRCS_COMMON} ${SRCS_LINUX} ${SRCS_PBKDF}) +set_target_properties(tcplay-so PROPERTIES OUTPUT_NAME tcplay) +set_target_properties(tcplay-so PROPERTIES SOVERSION ${VERSION_MAJOR}.${VERSION_MINOR}) +set_target_properties(tcplay-so PROPERTIES COMPILE_FLAGS "${CFLAGS_COMMON} ${DEVMAPPER_CFLAGS} ${UUID_CFLAGS} ${GCRYPT_CFLAGS}") +set_target_properties(tcplay-so PROPERTIES LINK_FLAGS "-Wl,--version-script=${PROJECT_SOURCE_DIR}/tcplay.map") +# XXX: revist linking libraries against so. Seems to be more common practice nowadays +target_link_libraries(tcplay-so ${DEVMAPPER_LDFLAGS} ${UUID_LDFLAGS} ${GCRYPT_LDFLAGS} ${LDFLAGS_PBKDF}) + + +add_library(tcplay-static STATIC tcplay_api.c ${SRCS_COMMON} ${SRCS_LINUX} ${SRCS_PBKDF}) +set_target_properties(tcplay-static PROPERTIES OUTPUT_NAME tcplay) +set_target_properties(tcplay-static PROPERTIES VERSION ${VERSION_MAJOR}.${VERSION_MINOR}) +set_target_properties(tcplay-static PROPERTIES COMPILE_FLAGS "${CFLAGS_COMMON} ${DEVMAPPER_CFLAGS} ${UUID_CFLAGS} ${GCRYPT_CFLAGS}") + + +# Generate pkg-config file tcplay.pc +file(WRITE ${PROJECT_BINARY_DIR}/tcplay.pc "prefix=${CMAKE_INSTALL_PREFIX} +exec_prefix=${CMAKE_INSTALL_PREFIX} +libdir=${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX} +includedir=${CMAKE_INSTALL_PREFIX}/include + +Name: libtcplay +Description: tcplay as a library +Version: ${VERSION_MAJOR}.${VERSION_MINOR} +Requires.private: devmapper uuid +Libs: -L\${libdir} -ltcplay +Libs.private: -lgpg-error -lgcrypt +Cflags: -I\${includedir} +") + + +# Installation targets +install(TARGETS tcplay-bin tcplay-static tcplay-so + RUNTIME DESTINATION sbin COMPONENT bin + LIBRARY DESTINATION lib${LIB_SUFFIX} COMPONENT lib + ARCHIVE DESTINATION lib${LIB_SUFFIX} COMPONENT lib-dev +) + +install(FILES ${PROJECT_BINARY_DIR}/tcplay.pc DESTINATION lib${LIB_SUFFIX}/pkgconfig COMPONENT lib-dev) + +install(FILES tcplay_api.h DESTINATION include COMPONENT lib-dev) +install(FILES tcplay.3 DESTINATION share/man/man3 COMPONENT lib-dev) + +install(FILES tcplay.8 DESTINATION share/man/man8 COMPONENT bin) + + + + +# Optional CPack magic +set (CPACK_RPM_COMPONENT_INSTALL 1) +set (CPACK_DEB_COMPONENT_INSTALL 1) + +set (CPACK_PACKAGE_NAME "tcplay") +set (CPACK_PACKAGE_CONTACT "Alex Hornung ") +set (CPACK_PACKAGE_VENDOR "Alex Hornung ") +set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "tcplay is a free (BSD-licensed), pretty much fully featured (including multiple keyfiles, cipher cascades, hidden volumes, etc) and stable TrueCrypt implementation.") +set (CPACK_PACAKGE_VERSION "${VERSION_MAJOR}.${VERSION_MINOR}") +set (CPACK_PACKAGE_VERSION_MAJOR "${VERSION_MAJOR}") +set (CPACK_PACKAGE_VERSION_MINOR "${VERSION_MINOR}") +set (CPACK_PACKAGE_VERSION_PATCH "0") +set (CPACK_PACKAGE_LICENSE "2-clause BSD") + +include(CPack) +cpack_add_component(bin DISPLAY_NAME bin REQUIRED INSTALL_TYPES all) +cpack_add_component(lib DISPLAY_NAME lib REQUIRED INSTALL_TYPES all) +cpack_add_component(lib-dev DISPLAY_NAME lib-dev REQUIRED INSTALL_TYPES all DEPENDS lib) + diff -Nru tcplay-0.11/Makefile tcplay-1.1/Makefile --- tcplay-0.11/Makefile 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/Makefile 1970-01-01 00:00:00.000000000 +0000 @@ -1,70 +0,0 @@ -# either linux or dragonfly -SYSTEM?=linux - -# either openssl or gcrypt -PBKDF_BACKEND?=openssl - -# system compiler, normally gcc -CC?=gcc - -RM?=rm -f - -# whether to enable debugging or not -DEBUG?=no - -# I've given up on -Werror for now; many Linux distros have warnings even in -# their standard header files. -WARNFLAGS= -Wsystem-headers -Wall -W -Wno-unused-parameter \ - -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith \ - -Wold-style-definition -Wreturn-type -Wcast-qual -Wwrite-strings \ - -Wswitch -Wshadow -Wcast-align -Wunused-parameter -Wchar-subscripts \ - -Winline -Wnested-externs - -SRCS= tcplay.c crc32.c safe_mem.c io.c hdr.c humanize.c -SRCS+= crypto.c generic_xts.c -OBJS= tcplay.o crc32.o safe_mem.o io.o hdr.o humanize.o -OBJS+= crypto.o generic_xts.o - -CFLAGS+= $(WARNFLAGS) - -ifeq (${DEBUG}, yes) - CFLAGS+= -O0 -g -DDEBUG -else - CFLAGS+= -O3 -endif - -ifeq (${SYSTEM}, linux) - CFLAGS+= -D_GNU_SOURCE - LIBS+= -lgcrypt -ldevmapper -luuid - SRCS+= crypto-gcrypt.c - OBJS+= crypto-gcrypt.o - ifeq (${PBKDF_BACKEND}, gcrypt) - SRCS+= pbkdf2-gcrypt.c - OBJS+= pbkdf2-gcrypt.o - endif - ifeq (${PBKDF_BACKEND}, openssl) - SRCS+= pbkdf2-openssl.c - OBJS+= pbkdf2-openssl.o - LIBS+= -lcrypto - endif -endif - -ifeq (${SYSTEM}, dragonfly) - LIBS+= -lcrypto -ldm -lprop - SRCS+= crypto-dev.c - OBJS+= crypto-dev.o - SRCS+= pbkdf2-openssl.c - OBJS+= pbkdf2-openssl.o -endif - -program: - $(CC) $(CFLAGS) -o tcplay main.c $(SRCS) $(LIBS) -lib: - $(CC) $(CFLAGS) -c -fPIC tcplay_api.c $(SRCS) - $(CC) -shared -Wl,-version-script=tcplay.map -o libtcplay.so tcplay_api.o $(OBJS) - -test: - $(CC) -O0 -g -L. -I. tcplay_api_test.c -ltcplay -lcrypto -ldm -lprop -clean: - $(RM) tcplay libtcplay.so tcplay.core *.o ktrace.out - diff -Nru tcplay-0.11/Makefile.classic tcplay-1.1/Makefile.classic --- tcplay-0.11/Makefile.classic 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/Makefile.classic 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,142 @@ +# either linux or dragonfly +SYSTEM?=linux + +DESTDIR?= +PREFIX?=/usr/local +LIBSUFFIX?= +LIBDIR?=$(PREFIX)/lib$(LIBSUFFIX) +MANDIR?=$(PREFIX)/share/man +SBINDIR?=$(PREFIX)/sbin +INCLUDEDIR?=$(PREFIX)/include + +# either openssl or gcrypt +PBKDF_BACKEND?=openssl + +# system compiler, normally gcc +CC?=gcc + +INSTALL?=install + +RM?=rm -f + +LN?=ln + +# whether to enable debugging or not +DEBUG?=no + +MAJ_VER=1 +MIN_VER=1 + + +# I've given up on -Werror for now; many Linux distros have warnings even in +# their standard header files. +WARNFLAGS= -Wsystem-headers -Wall -W -Wno-unused-parameter \ + -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith \ + -Wold-style-definition -Wreturn-type -Wcast-qual -Wwrite-strings \ + -Wswitch -Wshadow -Wcast-align -Wunused-parameter -Wchar-subscripts \ + -Winline -Wnested-externs + +VER_FLAGS= -DMAJ_VER=$(MAJ_VER) -DMIN_VER=$(MIN_VER) + +SRCS= tcplay.c crc32.c safe_mem.c io.c hdr.c humanize.c +SRCS+= crypto.c generic_xts.c +OBJS= tcplay.o crc32.o safe_mem.o io.o hdr.o humanize.o +OBJS+= crypto.o generic_xts.o + +COMMON_CFLAGS= $(WARNFLAGS) -fPIC $(VER_FLAGS) + +ifeq ($(DEBUG), yes) + COMMON_CFLAGS+= -O0 -g -DDEBUG +else + COMMON_CFLAGS+= -O3 +endif + +ifeq (${SYSTEM}, linux) + COMMON_CFLAGS+= -D_GNU_SOURCE + LIBS+= -lgcrypt -lgpg-error -ldevmapper -luuid + SRCS+= crypto-gcrypt.c + OBJS+= crypto-gcrypt.o + ifeq (${PBKDF_BACKEND}, gcrypt) + SRCS+= pbkdf2-gcrypt.c + OBJS+= pbkdf2-gcrypt.o + endif + ifeq (${PBKDF_BACKEND}, openssl) + SRCS+= pbkdf2-openssl.c + OBJS+= pbkdf2-openssl.o + LIBS+= -lcrypto + endif +endif + +ifeq (${SYSTEM}, dragonfly) + LIBS+= -lcrypto -ldm -lprop + SRCS+= crypto-dev.c + OBJS+= crypto-dev.o + SRCS+= pbkdf2-openssl.c + OBJS+= pbkdf2-openssl.o +endif + +OBJS_PROG= $(OBJS) main.o +OBJS_LIB= $(OBJS) tcplay_api.o + +all: tcplay libtcplay.so libtcplay.a + +%.o: %.c + $(CC) $(COMMON_CFLAGS) $(CFLAGS) -c -o $@ $< + +tcplay: $(OBJS_PROG) + $(CC) $(COMMON_CFLAGS) $(CFLAGS) -o tcplay $(OBJS_PROG) $(LIBS) + +libtcplay.so.$(MAJ_VER).$(MIN_VER): $(OBJS_LIB) + $(CC) -shared -Wl,-version-script=tcplay.map -Wl,-soname=libtcplay.so.$(MAJ_VER).$(MIN_VER) $(LDFLAGS) \ + -o libtcplay.so.$(MAJ_VER).$(MIN_VER) $(OBJS_LIB) $(LIBS) + +libtcplay.so: libtcplay.so.$(MAJ_VER).$(MIN_VER) + $(LN) -sf libtcplay.so.$(MAJ_VER).$(MIN_VER) libtcplay.so + +libtcplay.a: $(OBJS_LIB) + $(AR) -rs libtcplay.a $(OBJS_LIB) + + +install: install_program install_lib install_lib_static + +install_program: tcplay install_man8 + $(INSTALL) -d "$(DESTDIR)$(SBINDIR)" + $(INSTALL) tcplay "$(DESTDIR)$(SBINDIR)" + +install_lib: libtcplay.so.$(MAJ_VER).$(MIN_VER) libtcplay.so install_h install_man3 + $(INSTALL) -d "$(DESTDIR)$(LIBDIR)" + $(INSTALL) libtcplay.so.$(MAJ_VER).$(MIN_VER) "$(DESTDIR)$(LIBDIR)" + $(LN) -sf libtcplay.so.$(MAJ_VER).$(MIN_VER) "$(DESTDIR)$(LIBDIR)/libtcplay.so" + +install_lib_static: libtcplay.a install_h install_man3 + $(INSTALL) -d "$(DESTDIR)$(LIBDIR)" + $(INSTALL) libtcplay.a "$(DESTDIR)$(LIBDIR)" + +install_h: tcplay_api.h + $(INSTALL) -d "$(DESTDIR)$(INCLUDEDIR)" + $(INSTALL) tcplay_api.h "$(DESTDIR)$(INCLUDEDIR)" + +install_man3: tcplay.3 + $(INSTALL) -d "$(DESTDIR)$(MANDIR)/man3" + $(INSTALL) tcplay.3 "$(DESTDIR)$(MANDIR)/man3" + +install_man8: tcplay.8 + $(INSTALL) -d "$(DESTDIR)$(MANDIR)/man8" + $(INSTALL) tcplay.8 "$(DESTDIR)$(MANDIR)/man8" + + +clean_cmake_mess: + $(RM) CMakeCache.txt + $(RM) -r CMakeFiles + $(RM) CPackConfig.cmake + $(RM) CPackSourceConfig.cmake + $(RM) Makefile + $(RM) cmake_install.cmake + $(RM) tcplay.pc + +clean: clean_cmake_mess + $(RM) $(OBJS_PROG) + $(RM) $(OBJS_LIB) + $(RM) tcplay libtcplay.so* libtcplay.a tcplay.core ktrace.out + +.PHONY: install install_program install_lib install_lib_static install_man3 install_man8 install_h diff -Nru tcplay-0.11/README.md tcplay-1.1/README.md --- tcplay-0.11/README.md 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/README.md 2013-07-31 06:01:56.000000000 +0000 @@ -6,7 +6,9 @@ This implementation supports mapping (opening) both system and normal TrueCrypt volumes, as well as opening hidden volumes and opening an outer volume while protecting a hidden volume. There is also support to create volumes, including -hidden volumes, etc. +hidden volumes, etc. Since version 1.1, there is also support for restoring +from the backup header (if present), change passphrase, keyfile and PBKDF2 +PRF function. Since tcplay uses dm-crypt (or dm_target_crypt on DragonFly) it makes full use of any available hardware encryption/decryption support once the volume has been @@ -14,7 +16,7 @@ It is based solely on the documentation available on the TrueCrypt website, many hours of trial and error and the output of the Linux' TrueCrypt client. -As it turns out, most technical documents on TrueCrypt contains mistakes, hence +As it turns out, most technical documents on TrueCrypt contain mistakes, hence the trial and error approach. @@ -33,21 +35,84 @@ The crypto options can be chosen with make/Makefile parameters. Building on Linux is as easy as doing - make SYSTEM=linux + make -f Makefile.classic SYSTEM=linux you can even skip the SYSTEM=linux, since that's the default. To choose the PBKDF backend, you can use either, - make PBKDF_BACKEND=openssl + make -f Makefile.classic PBKDF_BACKEND=openssl or - make PBKDF_BACKEND=gcrypt + make -f Makefile.classic PBKDF_BACKEND=gcrypt The interface to device mapper is libdevmapper on Linux and libdm on DragonFly. libdm is a BSD-licensed version of libdevmapper that I hacked together in a few hours. +On Ubuntu, the following dev packages are needed to build tcplay: + + apt-get install libdevmapper-dev libgcrypt11-dev uuid-dev + + +cmake +---------- +New in version 1.0 is a cmake build system. tcplay can now be built using: + + mkdir objdir + cd objdir + cmake .. + make + +NOTE: building inside the source directory is discouraged, so please do +build inside an "objdir" directory. If you happen to do it anyway, you can +clean up behind cmake using: + + make -f Makefile.classic clean_cmake_mess + +This process will check for dependencies and automatically select whether to +use OpenSSL or gcrypt as PBKDF backend. + +In addition, this process will also generate a .pc file (pkg-config) for the +tcplay library. + +The classic single-file Makefile can still be used for building, however, using + + make -f Makefile.classic + +Or, if you only want the command line tool: + + make -f Makefile.classic tcplay + + + +Documentation +========== +Please refer to the man pages bundled with tcplay. + + + +Download for packaging +========== +Latest release can be found as a (source) zip at: + +https://github.com/bwalex/tc-play/archive/v1.0.zip + + + +Bugs +========== +Please report all bugs on the github issue tracker. If appropriate, please +attach a small test volume which you think tcplay isn't handling correctly. +The reduce_test_vol.sh script in test/ can significantly reduce the size +of a volume when compressed by stripping out all the unnecessary data, +leaving only the headers. After that, just bzip2 it and it should be fairly +tiny. + +What would be even better is if you could write a small test case to +reproduce the issue. The README in the test/ directory has information on +how to write tests for tcplay. + OS Support @@ -75,9 +140,6 @@ distro's bugtracker). Please make sure those bugs are also reported upstream on github, otherwise odds are they will never reach me. -Coming features: - - restoring header from backup header - Bugs in the TrueCrypt documentation @@ -88,7 +150,7 @@ Some notable differences between actual implementation and documentation: - PBKDF using RIPEMD160 only uses 2000 iterations if the volume isn't a system volume. - - The keyfile pool is not XOR'ed with the passphrase but modulo-8 summed. + - The keyfile pool is not XOR'ed with the passphrase but modulo-256 summed. - Every field *except* the minimum version field of the volume header are in big endian. - Some volume header fields (creation time of volume and header) are missing diff -Nru tcplay-0.11/crypto-gcrypt.c tcplay-1.1/crypto-gcrypt.c --- tcplay-0.11/crypto-gcrypt.c 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/crypto-gcrypt.c 2013-07-31 06:01:56.000000000 +0000 @@ -46,6 +46,8 @@ #include "tcplay.h" +static int gcrypt_inited = 0; + static int gcrypt_encrypt(void *ctx, size_t blk_len, const uint8_t *src, uint8_t *dst) { @@ -195,6 +197,11 @@ int tc_crypto_init(void) { + if (gcrypt_inited) + return 0; + + gcrypt_inited = 1; + gcry_control(GCRYCTL_SUSPEND_SECMEM_WARN); gcry_control(GCRYCTL_INIT_SECMEM, 16384, 0); gcry_control(GCRYCTL_RESUME_SECMEM_WARN); diff -Nru tcplay-0.11/crypto.c tcplay-1.1/crypto.c --- tcplay-0.11/crypto.c 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/crypto.c 2013-07-31 06:01:56.000000000 +0000 @@ -50,12 +50,7 @@ * We need to determine the total key bytes as the key locations * depend on it. */ - total_key_bytes = 0; - for (dummy_chain = cipher_chain; - dummy_chain != NULL; - dummy_chain = dummy_chain->next) { - total_key_bytes += dummy_chain->cipher->klen; - } + total_key_bytes = tc_cipher_chain_klen(cipher_chain); /* * Now we need to get prepare the keys, as the keys are in diff -Nru tcplay-0.11/debian/changelog tcplay-1.1/debian/changelog --- tcplay-0.11/debian/changelog 2012-11-17 02:06:39.000000000 +0000 +++ tcplay-1.1/debian/changelog 2013-09-01 09:40:44.000000000 +0000 @@ -1,3 +1,9 @@ +tcplay (1.1-1) unstable; urgency=low + + * New upstream release (closes: #719836). + + -- Laszlo Boszormenyi (GCS) Sun, 01 Sep 2013 11:36:00 +0200 + tcplay (0.11-1) unstable; urgency=low * New upstream release, fixing 64 bit archs compilation. diff -Nru tcplay-0.11/debian/control tcplay-1.1/debian/control --- tcplay-0.11/debian/control 2012-10-29 21:47:44.000000000 +0000 +++ tcplay-1.1/debian/control 2013-09-01 10:01:37.000000000 +0000 @@ -1,9 +1,9 @@ Source: tcplay Section: admin Priority: optional -Maintainer: Laszlo Boszormenyi (GCS) -Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1~), libdevmapper-dev, uuid-dev, libgcrypt11-dev -Standards-Version: 3.9.3 +Maintainer: Laszlo Boszormenyi (GCS) +Build-Depends: debhelper (>= 9), dpkg-dev (>= 1.16.1~), cmake, pkg-config, libdevmapper-dev, libudev-dev, uuid-dev, libgcrypt11-dev +Standards-Version: 3.9.4 Homepage: https://github.com/bwalex/tc-play Package: tcplay @@ -20,3 +20,17 @@ . Since tcplay uses dm-crypt it makes full use of any available hardware encryption/decryption support once the volume has been mapped. + +Package: libtcplay +Architecture: linux-any +Section: libs +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: Free and simple TrueCrypt Implementation based on dm-crypt + tcplay is a free (BSD-licensed), pretty much fully featured (including + +Package: libtcplay-dev +Architecture: linux-any +Section: libdevel +Depends: ${shlibs:Depends}, ${misc:Depends}, libtcplay (= ${binary:Version}) +Description: Free and simple TrueCrypt Implementation based on dm-crypt + tcplay is a free (BSD-licensed), pretty much fully featured (including diff -Nru tcplay-0.11/debian/copyright tcplay-1.1/debian/copyright --- tcplay-0.11/debian/copyright 2012-10-29 22:19:52.000000000 +0000 +++ tcplay-1.1/debian/copyright 2013-09-01 09:40:02.000000000 +0000 @@ -24,7 +24,7 @@ License: BSD-3-clause Files: debian/* -Copyright: Copyright (C) 2012- Laszlo Boszormenyi (GCS) +Copyright: Copyright (C) 2012- Laszlo Boszormenyi (GCS) License: GPL-3+ License: BSD-2-clause diff -Nru tcplay-0.11/debian/libtcplay-dev.install tcplay-1.1/debian/libtcplay-dev.install --- tcplay-0.11/debian/libtcplay-dev.install 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/debian/libtcplay-dev.install 2013-07-15 19:30:15.000000000 +0000 @@ -0,0 +1,4 @@ +usr/include/ +usr/lib*/libtcplay.a +usr/lib*/libtcplay.so +usr/lib*/pkgconfig/ diff -Nru tcplay-0.11/debian/libtcplay.install tcplay-1.1/debian/libtcplay.install --- tcplay-0.11/debian/libtcplay.install 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/debian/libtcplay.install 2013-07-15 19:25:12.000000000 +0000 @@ -0,0 +1 @@ +usr/lib*/libtcplay.so.* diff -Nru tcplay-0.11/debian/patches/do_not_add_lib_suffix.patch tcplay-1.1/debian/patches/do_not_add_lib_suffix.patch --- tcplay-0.11/debian/patches/do_not_add_lib_suffix.patch 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/debian/patches/do_not_add_lib_suffix.patch 2013-09-01 09:50:52.000000000 +0000 @@ -0,0 +1,23 @@ +Description: use /usr/lib on all architectures +Author: Laszlo Boszormenyi (GCS) +Last-Update: 2013-09-01 + +--- + +--- tcplay-1.1.orig/CMakeLists.txt ++++ tcplay-1.1/CMakeLists.txt +@@ -65,11 +65,10 @@ endif() + + if (NOT LIB_SUFFIX) + message(STATUS "") +- message(STATUS "LIB_SUFFIX variable is not defined. It will be autodetected now.") +- message(STATUS "You can set it manually with -DLIB_SUFFIX= (e.g. 64).") ++ message(STATUS "LIB_SUFFIX variable is not defined. It will remain as is.") + if (CMAKE_SIZEOF_VOID_P EQUAL 8) +- message(STATUS "\nSetting LIB_SUFFIX=64\n") +- set (LIB_SUFFIX "64") ++ message(STATUS "\nSetting LIB_SUFFIX=\n") ++ set (LIB_SUFFIX "") + else() + message(STATUS "\nSetting LIB_SUFFIX=\n") + set (LIB_SUFFIX "") diff -Nru tcplay-0.11/debian/patches/series tcplay-1.1/debian/patches/series --- tcplay-0.11/debian/patches/series 2012-11-17 02:04:57.000000000 +0000 +++ tcplay-1.1/debian/patches/series 2013-09-01 09:50:05.000000000 +0000 @@ -1 +1,2 @@ -use_ldflags.patch +#use_ldflags.patch +do_not_add_lib_suffix.patch diff -Nru tcplay-0.11/debian/rules tcplay-1.1/debian/rules --- tcplay-0.11/debian/rules 2012-11-17 02:01:55.000000000 +0000 +++ tcplay-1.1/debian/rules 2013-07-15 19:19:08.000000000 +0000 @@ -15,7 +15,7 @@ CPPFLAGS += $(HARDENING_CFLAGS) LDFLAGS += $(HARDENING_LDFLAGS) -DESTDIR = $(CURDIR)/debian/tcplay +DESTDIR = $(CURDIR)/debian/tmp/ ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) DEBUG = DEBUG=yes @@ -24,12 +24,12 @@ INSTALL_PROGRAM += -s endif -# configure doesn't exists and it doesn't needed -override_dh_autoreconf: +export DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +export DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) override_dh_auto_build: - make SYSTEM=linux PBKDF_BACKEND=gcrypt $(DEBUG) -# make SYSTEM=linux PBKDF_BACKEND=gcrypt $(DEBUG) lib + cd $(CURDIR)/obj-$(DEB_BUILD_GNU_TYPE)/ && \ + $(MAKE) SYSTEM=linux PBKDF_BACKEND=gcrypt $(DEBUG) # test needs the library, which can't be built ATM override_dh_auto_test: diff -Nru tcplay-0.11/debian/tcplay.install tcplay-1.1/debian/tcplay.install --- tcplay-0.11/debian/tcplay.install 2012-10-29 18:04:42.000000000 +0000 +++ tcplay-1.1/debian/tcplay.install 2013-07-15 19:20:15.000000000 +0000 @@ -1 +1 @@ -tcplay usr/bin +usr/sbin diff -Nru tcplay-0.11/hdr.c tcplay-1.1/hdr.c --- tcplay-0.11/hdr.c 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/hdr.c 2013-07-31 06:01:56.000000000 +0000 @@ -128,7 +128,7 @@ create_hdr(unsigned char *pass, int passlen, struct pbkdf_prf_algo *prf_algo, struct tc_cipher_chain *cipher_chain, size_t sec_sz, size_t total_blocks __unused, - off_t offset, size_t blocks, int hidden, struct tchdr_enc **backup_hdr) + off_t offset, size_t blocks, int hidden, int weak, struct tchdr_enc **backup_hdr) { struct tchdr_enc *ehdr, *ehdr_backup; struct tchdr_dec *dhdr; @@ -169,12 +169,12 @@ goto error; } - if ((error = get_random(ehdr->salt, sizeof(ehdr->salt))) != 0) { + if ((error = get_random(ehdr->salt, sizeof(ehdr->salt), weak)) != 0) { tc_log(1, "could not get salt\n"); goto error; } - if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt))) + if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt), weak)) != 0) { tc_log(1, "could not get salt for backup header\n"); goto error; @@ -198,7 +198,7 @@ memset(dhdr, 0, sizeof(*dhdr)); - if ((error = get_random(dhdr->keys, sizeof(dhdr->keys))) != 0) { + if ((error = get_random(dhdr->keys, sizeof(dhdr->keys), weak)) != 0) { tc_log(1, "could not get key random bits\n"); goto error; } @@ -271,3 +271,121 @@ return NULL; } + +struct tchdr_enc *copy_reencrypt_hdr(unsigned char *pass, int passlen, + struct pbkdf_prf_algo *prf_algo, int weak, struct tcplay_info *info, + struct tchdr_enc **backup_hdr) +{ + struct tchdr_enc *ehdr, *ehdr_backup; + unsigned char *key, *key_backup; + unsigned char iv[128]; + int error; + + key = key_backup = NULL; + ehdr = ehdr_backup = NULL; + + /* By default stick to current PRF algo */ + if (prf_algo == NULL) + prf_algo = info->pbkdf_prf; + + if ((ehdr = (struct tchdr_enc *)alloc_safe_mem(sizeof(*ehdr))) == NULL) { + tc_log(1, "could not allocate safe ehdr memory\n"); + goto error; + } + + if ((ehdr_backup = (struct tchdr_enc *)alloc_safe_mem + (sizeof(*ehdr_backup))) == NULL) { + tc_log(1, "could not allocate safe ehdr_backup memory\n"); + goto error; + } + + if ((key = alloc_safe_mem(MAX_KEYSZ)) == NULL) { + tc_log(1, "could not allocate safe key memory\n"); + goto error; + } + + if ((key_backup = alloc_safe_mem(MAX_KEYSZ)) == NULL) { + tc_log(1, "could not allocate safe backup key memory\n"); + goto error; + } + + if ((error = get_random(ehdr->salt, sizeof(ehdr->salt), weak)) != 0) { + tc_log(1, "could not get salt\n"); + goto error; + } + + if ((error = get_random(ehdr_backup->salt, sizeof(ehdr_backup->salt), weak)) + != 0) { + tc_log(1, "could not get salt for backup header\n"); + goto error; + } + + error = pbkdf2(prf_algo, (char *)pass, passlen, + ehdr->salt, sizeof(ehdr->salt), + MAX_KEYSZ, key); + if (error) { + tc_log(1, "could not derive key\n"); + goto error; + } + + error = pbkdf2(prf_algo, (char *)pass, passlen, + ehdr_backup->salt, sizeof(ehdr_backup->salt), + MAX_KEYSZ, key_backup); + if (error) { + tc_log(1, "could not derive backup key\n"); + goto error; + } + + HOST_TO_BE(16, info->hdr->tc_ver); + HOST_TO_LE(16, info->hdr->tc_min_ver); + HOST_TO_BE(32, info->hdr->crc_keys); + HOST_TO_BE(64, info->hdr->vol_ctime); + HOST_TO_BE(64, info->hdr->hdr_ctime); + HOST_TO_BE(64, info->hdr->sz_vol); + HOST_TO_BE(64, info->hdr->sz_hidvol); + HOST_TO_BE(64, info->hdr->off_mk_scope); + HOST_TO_BE(64, info->hdr->sz_mk_scope); + HOST_TO_BE(32, info->hdr->sec_sz); + HOST_TO_BE(32, info->hdr->flags); + HOST_TO_BE(32, info->hdr->crc_dhdr); + + memset(iv, 0, sizeof(iv)); + error = tc_encrypt(info->cipher_chain, key, iv, + (unsigned char *)info->hdr, sizeof(struct tchdr_dec), ehdr->enc); + if (error) { + tc_log(1, "Header encryption failed\n"); + goto error; + } + + memset(iv, 0, sizeof(iv)); + error = tc_encrypt(info->cipher_chain, key_backup, iv, + (unsigned char *)info->hdr, + sizeof(struct tchdr_dec), ehdr_backup->enc); + if (error) { + tc_log(1, "Backup header encryption failed\n"); + goto error; + } + + free_safe_mem(key); + free_safe_mem(key_backup); + + if (backup_hdr != NULL) + *backup_hdr = ehdr_backup; + else + free_safe_mem(ehdr_backup); + + return ehdr; + /* NOT REACHED */ + +error: + if (key) + free_safe_mem(key); + if (key_backup) + free_safe_mem(key_backup); + if (ehdr) + free_safe_mem(ehdr); + if (ehdr_backup) + free_safe_mem(ehdr_backup); + + return NULL; +} diff -Nru tcplay-0.11/io.c tcplay-1.1/io.c --- tcplay-0.11/io.c 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/io.c 2013-07-31 06:01:56.000000000 +0000 @@ -64,7 +64,7 @@ goto out; } - if ((lseek(fd, offset, SEEK_SET) < 0)) { + if ((lseek(fd, offset, (offset >= 0) ? SEEK_SET : SEEK_END) < 0)) { tc_log(1, "Error seeking on file %s\n", file); goto m_err; } @@ -89,19 +89,26 @@ static size_t get_random_total_bytes = 0; static size_t get_random_read_bytes = 0; + +float +get_random_read_progress(void) +{ + return (get_random_total_bytes == 0) ? 0.0 : + (1.0 * get_random_read_bytes) / + (1.0 * get_random_total_bytes) * 100.0; +} + static void get_random_summary(void) { - float pct_done; + float pct_done = get_random_read_progress(); - pct_done = (1.0 * get_random_read_bytes) / - (1.0 * get_random_total_bytes) * 100.0; tc_log(0, "Gathering true randomness, %.0f%% done.\n", pct_done); } int -get_random(unsigned char *buf, size_t len) +get_random(unsigned char *buf, size_t len, int weak) { int fd; ssize_t r; @@ -110,13 +117,14 @@ struct timespec ts = { .tv_sec = 0, .tv_nsec = 10000000 }; /* 10 ms */ - if ((fd = open("/dev/random", O_RDONLY)) < 0) { + if ((fd = open((weak) ? "/dev/urandom" : "/dev/random", O_RDONLY)) < 0) { tc_log(1, "Error opening /dev/random\n"); return -1; } summary_fn = get_random_summary; get_random_total_bytes = len; + tc_internal_state = STATE_GET_RANDOM; /* Get random data in 16-byte chunks */ sz = 16; @@ -131,6 +139,7 @@ fd, strerror(errno)); close(fd); summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return -1; } rd += r; @@ -140,20 +149,26 @@ close(fd); summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return 0; } static size_t secure_erase_total_bytes = 0; static size_t secure_erase_erased_bytes = 0; +float +get_secure_erase_progress(void) +{ + return (secure_erase_total_bytes == 0) ? 0.0 : + (1.0 * secure_erase_erased_bytes) / + (1.0 * secure_erase_total_bytes) * 100.0; +} + static void secure_erase_summary(void) { - float pct_done; - - pct_done = (1.0 * secure_erase_erased_bytes) / - (1.0 * secure_erase_total_bytes) * 100.0; + float pct_done = get_secure_erase_progress(); tc_log(0, "Securely erasing, %.0f%% done.\n", pct_done); } @@ -185,6 +200,8 @@ summary_fn = secure_erase_summary; secure_erase_total_bytes = bytes; + tc_internal_state = STATE_ERASE; + sz = ERASE_BUFFER_SIZE; while (erased < bytes) { secure_erase_erased_bytes = erased; @@ -197,6 +214,7 @@ close(fd); close(fd_rand); summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return -1; } @@ -208,6 +226,7 @@ close(fd); close(fd_rand); summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return -1; } @@ -219,6 +238,7 @@ summary_fn = NULL; + tc_internal_state = STATE_UNKNOWN; return 0; } @@ -318,7 +338,7 @@ return -1; } - if ((lseek(fd, offset, SEEK_SET) < 0)) { + if ((lseek(fd, offset, (offset >= 0) ? SEEK_SET : SEEK_END) < 0)) { tc_log(1, "Error seeking on device %s\n", dev); close(fd); return -1; @@ -348,7 +368,7 @@ } int -read_passphrase(const char *prompt, char *pass, size_t passlen, time_t timeout) +read_passphrase(const char *prompt, char *pass, size_t passlen, size_t bufsz, time_t timeout) { struct termios termios_new; struct timeval to; @@ -361,14 +381,14 @@ if (is_tty == 0) errno = 0; - memset(pass, 0, passlen); + memset(pass, 0, bufsz); + + printf("%s", prompt); + fflush(stdout); /* If input is being provided by something which is not a terminal, don't * change the settings. */ if (is_tty) { - printf("%s", prompt); - fflush(stdout); - tcgetattr(fd, &termios_old); memcpy(&termios_new, &termios_old, sizeof(termios_new)); termios_new.c_lflag &= ~ECHO; @@ -396,13 +416,20 @@ } } - n = read(fd, pass, passlen-1); + n = read(fd, pass, bufsz-1); if (n > 0) { pass[n-1] = '\0'; /* Strip trailing \n */ } else { r = EIO; } + /* Warn about passphrase trimming */ + if (strlen(pass) > MAX_PASSSZ) + tc_log(0, "WARNING: Passphrase is being trimmed to %zu " + "characters, discarding rest.\n", passlen); + + /* Cut off after passlen characters */ + pass[passlen] = '\0'; out: if (is_tty) { diff -Nru tcplay-0.11/main.c tcplay-1.1/main.c --- tcplay-0.11/main.c 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/main.c 2013-07-31 06:01:56.000000000 +0000 @@ -43,6 +43,14 @@ #define SIGINFO SIGUSR1 #endif +#define FLAG_LONG_FDE 0xff01 +#define FLAG_LONG_USE_BACKUP 0xff02 +#define FLAG_LONG_MOD 0xff04 +#define FLAG_LONG_MOD_KF 0xff08 +#define FLAG_LONG_MOD_PRF 0xff10 +#define FLAG_LONG_MOD_NONE 0xff20 + + static void sig_handler(int sig) @@ -56,12 +64,18 @@ usage(void) { fprintf(stderr, - "usage: tcplay -c -d device [-g] [-z] [-a pbkdb_hash] [-b cipher]\n" + "usage: tcplay -c -d device [-g] [-z] [-w] [-a pbkdf_hash] [-b cipher]\n" " [-f keyfile_hidden] [-k keyfile] [-x pbkdf_hash] [-y cipher]\n" " tcplay -i -d device [-e] [-f keyfile_hidden] [-k keyfile]\n" - " [-s system_device]\n" + " [-s system_device] [--fde] [--use-backup]\n" " tcplay -m mapping -d device [-e] [-f keyfile_hidden] [-k keyfile]\n" - " [-s system_device]\n" + " [-s system_device] [--fde] [--use-backup]\n" + " tcplay --modify -d device [-k keyfile] [--new-keyfile=keyfile]\n" + " [--new-pbkdf-prf=pbkdf_hash] [-s system_device] [--fde]\n" + " [--use-backup] [-w]\n" + " tcplay --modify -d device [-k keyfile] --restore-from-backup-hdr [-w]\n" + " tcplay -j mapping\n" + " tcplay -u mapping\n" " tcplay -h | -v\n" "\n" "Valid commands are:\n" @@ -71,9 +85,16 @@ "\t Print help message and exit.\n" " -i, --info\n" "\t Gives information about the TC volume specified by -d or --device.\n" + " -j , --info-mapped=\n" + "\t Gives information about the mapped TC volume under the given mapping.\n" " -m , --map=\n" "\t Creates a dm-crypt mapping with the given name for the device\n" "\t specified by -d or --device.\n" + " -u , --unmap=\n" + "\t Removes a dm-crypt mapping with the given name.\n" + " --modify\n" + "\t Changes the volume's passphrase, keyfile and optionally the hashing\n" + "\t function used for the PBKDF password derivation.\n" " -v, --version\n" "\t Print version message and exit.\n" "\n" @@ -98,12 +119,48 @@ "\t To see valid options, specify '-y help'.\n" " -z, --insecure-erase\n" "\t Skips the erase of the disk. Possible security hazard.\n" + " -w, --weak-keys\n" + "\t Uses a weak source of entropy (urandom) for key material.\n" + "\t WARNING: This is a REALLY REALLY bad idea for anything but\n" + "\t testing.\n" + "\n" + "Valid options for --modify are:\n" + " --new-keyfile=\n" + "\t Specifies a key file to use for the password derivation, when\n" + "\t re-encrypting the header, can appear multiple times.\n" + " --new-pbkdf-prf=\n" + "\t Specifies which hashing function to use for the PBKDF password\n" + "\t derivation when re-encrypting the header.\n" + "\t To see valid options, specify '-a help'.\n" + " -s , --system-encryption=\n" + "\t Specifies that the disk (e.g. /dev/da0) is using system encryption.\n" + " --fde\n" + "\t Specifies that the disk (e.g. /dev/da0) is using full disk encryption.\n" + " --use-backup\n" + "\t Uses the backup headers (at the end of the volume) instead of the\n" + "\t primary headers. Both normal and backup headers will be modified!\n" + "\t This is useful when your primary headers have been corrupted.\n" + " --restore-from-backup-hdr\n" + "\t Implies --use-backup, no new PBKDF hashing function, no new keyfiles\n" + "\t and no new passphrase.\n" + "\t In other words, this will simply restore both headers from the backup\n" + "\t header." + " -w, --weak-keys\n" + "\t Uses a weak source of entropy (urandom) for salt material. The\n" + "\t key material is not affected, as the master keys are kept intact.\n" + "\t WARNING: This is a bad idea for anything but testing.\n" "\n" "Valid options for --info and --map are:\n" " -e, --protect-hidden\n" "\t Protect a hidden volume when mounting the outer volume.\n" " -s , --system-encryption=\n" "\t Specifies that the disk (e.g. /dev/da0) is using system encryption.\n" + " --fde\n" + "\t Specifies that the disk (e.g. /dev/da0) is using full disk encryption.\n" + " --use-backup\n" + "\t Uses the backup headers (at the end of the volume) instead of the\n" + "\t primary headers.\n" + "\t This is useful when your primary headers have been corrupted.\n" "\n" "Valid options common to all commands are:\n" " -d , --device=\n" @@ -117,7 +174,7 @@ "\t multiple times.\n" ); - exit(1); + exit(EXIT_FAILURE); } static struct option longopts[] = { @@ -128,13 +185,22 @@ { "pbkdf-prf", required_argument, NULL, 'a' }, { "pbkdf-prf-hidden", required_argument, NULL, 'x' }, { "info", no_argument, NULL, 'i' }, + { "info-mapped", required_argument, NULL, 'j' }, { "map", required_argument, NULL, 'm' }, { "keyfile", required_argument, NULL, 'k' }, { "keyfile-hidden", required_argument, NULL, 'f' }, { "protect-hidden", no_argument, NULL, 'e' }, { "device", required_argument, NULL, 'd' }, { "system-encryption", required_argument, NULL, 's' }, + { "fde", no_argument, NULL, FLAG_LONG_FDE }, + { "use-backup", no_argument, NULL, FLAG_LONG_USE_BACKUP }, + { "modify", no_argument, NULL, FLAG_LONG_MOD }, + { "new-keyfile", required_argument, NULL, FLAG_LONG_MOD_KF }, + { "new-pbkdf-prf", required_argument, NULL, FLAG_LONG_MOD_PRF }, + { "restore-from-backup-hdr", no_argument, NULL, FLAG_LONG_MOD_NONE }, + { "unmap", required_argument, NULL, 'u' }, { "version", no_argument, NULL, 'v' }, + { "weak-keys", no_argument, NULL, 'w' }, { "insecure-erase", no_argument, NULL, 'z' }, { "help", no_argument, NULL, 'h' }, { NULL, 0, NULL, 0 }, @@ -146,19 +212,26 @@ const char *dev = NULL, *sys_dev = NULL, *map_name = NULL; const char *keyfiles[MAX_KEYFILES]; const char *h_keyfiles[MAX_KEYFILES]; + const char *new_keyfiles[MAX_KEYFILES]; int nkeyfiles; int n_hkeyfiles; + int n_newkeyfiles; int ch, error; - int sflag = 0, info_vol = 0, map_vol = 0, protect_hidden = 0, - create_vol = 0, contain_hidden = 0, use_secure_erase = 1; + int flags = 0; + int info_vol = 0, map_vol = 0, protect_hidden = 0, + unmap_vol = 0, info_map = 0, + create_vol = 0, modify_vol = 0, + contain_hidden = 0, use_secure_erase = 1, + use_weak_keys = 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; + struct pbkdf_prf_algo *new_prf = NULL; if ((error = tc_play_init()) != 0) { fprintf(stderr, "Initialization failed, exiting."); - exit(1); + exit(EXIT_FAILURE); } atexit(check_and_purge_safe_mem); @@ -167,8 +240,9 @@ nkeyfiles = 0; n_hkeyfiles = 0; + n_newkeyfiles = 0; - while ((ch = getopt_long(argc, argv, "a:b:cd:ef:ghik:m:s:vx:y:z", + while ((ch = getopt_long(argc, argv, "a:b:cd:ef:ghij:k:m:s:u:vwx:y:z", longopts, NULL)) != -1) { switch(ch) { case 'a': @@ -176,7 +250,7 @@ usage(); if ((prf = check_prf_algo(optarg, 0)) == NULL) { if (strcmp(optarg, "help") == 0) - exit(0); + exit(EXIT_SUCCESS); else usage(); /* NOT REACHED */ @@ -187,7 +261,7 @@ usage(); if ((cipher_chain = check_cipher_chain(optarg, 0)) == NULL) { if (strcmp(optarg, "help") == 0) - exit(0); + exit(EXIT_SUCCESS); else usage(); /* NOT REACHED */ @@ -211,6 +285,10 @@ case 'i': info_vol = 1; break; + case 'j': + info_map = 1; + map_name = optarg; + break; case 'k': keyfiles[nkeyfiles++] = optarg; break; @@ -219,19 +297,28 @@ map_name = optarg; break; case 's': - sflag = 1; + flags |= TC_FLAG_SYS; sys_dev = optarg; break; + case 'u': + unmap_vol = 1; + map_name = optarg; + break; case 'v': printf("tcplay v%d.%d\n", MAJ_VER, MIN_VER); - exit(0); + exit(EXIT_SUCCESS); /* NOT REACHED */ + case 'w': + fprintf(stderr, "WARNING: Using urandom as source of " + "entropy for key material is a really bad idea.\n"); + use_weak_keys = 1; + break; case 'x': if (h_prf != NULL) usage(); if ((h_prf = check_prf_algo(optarg, 0)) == NULL) { if (strcmp(optarg, "help") == 0) - exit(0); + exit(EXIT_SUCCESS); else usage(); /* NOT REACHED */ @@ -242,16 +329,44 @@ usage(); if ((h_cipher_chain = check_cipher_chain(optarg, 0)) == NULL) { if (strcmp(optarg, "help") == 0) - exit(0); + exit(EXIT_SUCCESS); else usage(); /* NOT REACHED */ } break; - case 'z': use_secure_erase = 0; break; + case FLAG_LONG_FDE: + flags |= TC_FLAG_FDE; + break; + case FLAG_LONG_USE_BACKUP: + flags |= TC_FLAG_BACKUP; + break; + case FLAG_LONG_MOD: + modify_vol = 1; + break; + case FLAG_LONG_MOD_KF: + new_keyfiles[n_newkeyfiles++] = optarg; + break; + case FLAG_LONG_MOD_PRF: + if (new_prf != NULL) + usage(); + if ((new_prf = check_prf_algo(optarg, 0)) == NULL) { + if (strcmp(optarg, "help") == 0) + exit(EXIT_SUCCESS); + else + usage(); + /* NOT REACHED */ + } + break; + case FLAG_LONG_MOD_NONE: + new_prf = NULL; + n_newkeyfiles = 0; + flags |= TC_FLAG_ONLY_RESTORE; + flags |= TC_FLAG_BACKUP; + break; case 'h': case '?': default: @@ -264,13 +379,19 @@ 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) || + if (!(((map_vol || info_vol || create_vol || modify_vol) && dev != NULL) || + ((unmap_vol || info_map) && map_name != NULL)) || + (TC_FLAG_SET(flags, SYS) && TC_FLAG_SET(flags, FDE)) || + (map_vol + info_vol + create_vol + unmap_vol + info_map + modify_vol > 1) || (contain_hidden && !create_vol) || - (sflag && (sys_dev == NULL)) || + (TC_FLAG_SET(flags, SYS) && (sys_dev == NULL)) || + (TC_FLAG_SET(flags, ONLY_RESTORE) && (n_newkeyfiles > 0 || new_prf != NULL)) || + (TC_FLAG_SET(flags, BACKUP) && (sys_dev != NULL || TC_FLAG_SET(flags, FDE))) || (map_vol && (map_name == NULL)) || + (unmap_vol && (map_name == NULL)) || + (!modify_vol && n_newkeyfiles > 0) || + (!modify_vol && new_prf != NULL) || + (!modify_vol && TC_FLAG_SET(flags, ONLY_RESTORE)) || (!(protect_hidden || create_vol) && n_hkeyfiles > 0)) { usage(); /* NOT REACHED */ @@ -282,19 +403,27 @@ h_keyfiles, n_hkeyfiles, prf, cipher_chain, h_prf, h_cipher_chain, NULL, NULL, 0, 1 /* interactive */, - use_secure_erase); + use_secure_erase, use_weak_keys); if (error) { tc_log(1, "could not create new volume on %s\n", dev); } + } else if (info_map) { + error = info_mapped_volume(map_name, 1 /* interactive */); } else if (info_vol) { - error = info_volume(dev, sflag, sys_dev, protect_hidden, + error = info_volume(dev, flags, 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, + dev, flags, sys_dev, protect_hidden, keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, NULL, NULL, 1 /* interactive */, DEFAULT_RETRIES, 0); + } else if (unmap_vol) { + error = dm_teardown(map_name, NULL); + } else if (modify_vol) { + error = modify_volume(dev, flags, sys_dev, keyfiles, nkeyfiles, + new_keyfiles, n_newkeyfiles, new_prf, NULL, NULL, + 1 /* interactive */, DEFAULT_RETRIES, 0, use_weak_keys); } return error; diff -Nru tcplay-0.11/tcplay.3 tcplay-1.1/tcplay.3 --- tcplay-0.11/tcplay.3 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/tcplay.3 2013-07-31 06:01:56.000000000 +0000 @@ -28,19 +28,23 @@ .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 5, 2011 +.Dd July 30, 2013 .Dt TCPLAY 3 .Os .Sh NAME .Nm tc_api_init , .Nm tc_api_uninit , +.Nm tc_api_info_volume , +.Nm tc_api_info_mapped_volume , .Nm tc_api_create_volume , +.Nm tc_api_modify_volume , .Nm tc_api_map_volume , .Nm tc_api_unmap_volume , .Nm tc_api_check_cipher , .Nm tc_api_check_prf_hash , .Nm tc_api_get_error_msg , -.Nm tc_api_get_summary +.Nm tc_api_get_summary , +.Nm tc_api_get_state .Nd TrueCrypt volume management .Sh LIBRARY .Lb libtcplay @@ -51,8 +55,14 @@ .Ft int .Fn tc_api_uninit "void" .Ft int +.Fn tc_api_info_volume "tc_api_opts *api_opts" "tc_api_volinfo *info" +.Ft int +.Fn tc_api_info_mapped_volume "tc_api_opts *api_opts" "tc_api_volinfo *info" +.Ft int .Fn tc_api_create_volume "tc_api_opts *api_opts" .Ft int +.Fn tc_api_modify_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" @@ -64,6 +74,8 @@ .Fn tc_api_get_error_msg "void" .Ft const char * .Fn tc_api_get_summary "void" +.Ft tc_api_state +.Fn tc_api_get_state "float *progress_pct" .Sh DESCRIPTION The .Nm tcplay @@ -73,6 +85,7 @@ .Pp The .Fn tc_api_create_volume , +.Fn tc_api_modify_volume , .Fn tc_api_map_volume , .Fn tc_api_unmap_volume , .Fn tc_api_check_cipher @@ -84,36 +97,73 @@ .Bd -literal typedef struct tc_api_opts { /* Common fields */ - char *tc_device; - char *tc_passphrase; + const char *tc_device; + const char *tc_passphrase; const char **tc_keyfiles; + const char **tc_passphrase_hidden; + const char **tc_keyfiles_hidden; + + /* Fields for mapping / info */ + const char *tc_map_name; + int tc_protect_hidden; - /* Fields for mapping */ - char *tc_map_name; + /* Fields for mapping / info / modify */ int tc_password_retries; int tc_interactive_prompt; unsigned long tc_prompt_timeout; + int tc_use_system_encryption; + const char *tc_system_device; + int tc_use_fde; + int tc_use_backup; + + /* Fields for modify */ + const char *tc_new_passphrase; + const char **tc_new_keyfiles; + const char *tc_new_prf_hash; + int tc_use_weak_salt; /* Fields for creation */ - char *tc_cipher; - char *tc_prf_hash; - char *tc_cipher_hidden; - char *tc_prf_hash_hidden; + const char *tc_cipher; + const char *tc_prf_hash; + const char *tc_cipher_hidden; + const char *tc_prf_hash_hidden; size_t tc_size_hidden_in_bytes; - char *tc_passphrase_hidden; - const char **tc_keyfiles_hidden; + int tc_no_secure_erase; + int tc_use_weak_keys; } tc_api_opts; .Ed .Pp where the keyfile fields, -.Fa tc_keyfiles +.Fa tc_keyfiles , +.Fa tc_keyfiles_hidden and -.Fa tc_keyfiles_hidden , +.Fa tc_new_keyfiles are .Dv NULL terminated arrays of key file strings. .Pp The +.Fn tc_api_info_volume +and +.Fn tc_api_info_mapped_volume +functions take a +.Vt tc_api_volinfo +data structure as second argument, which is defined as follows: +.Bd -literal +typedef struct tc_api_volinfo { + char tc_device[1024]; + char tc_cipher[256]; + char tc_prf[64]; + + int tc_key_bits; + + size_t tc_size; /* in bytes */ + off_t tc_iv_offset; /* in bytes */ + off_t tc_block_offset; /* in bytes */ +} tc_api_volinfo; +.Ed +.Pp +The .Fn tc_api_init function initializes the library internals and prepares it for use via the API. @@ -129,6 +179,61 @@ decrypted headers, passphrases, keyfiles, etc. .Pp The +.Fn tc_api_info_volume +function retrieves information about a volume using the parameters +specified in the +.Fa api_opts +argument. +All fields except +.Fa tc_map_name +are used in the same way as for +.Fn tc_api_map_volume . +The retrieved information is placed into the +.Fa info +argument. +The +.Fa tc_cipher +and +.Fa tc_prf +fields will contain a string describing the block cipher (chain) +and PBKDF2 PRF algorithm respectively. +The +.Fa tc_key_bits +field gives the total key size used for the block cipher(s). +The +.Fa tc_size +field gives the size of the volume in bytes. +The +.Fa tc_iv_offset +and +.Fa tc_block_offset +fields give the IV and block offset in bytes of the volume, +respectively. +The +.Fa tc_device +field contains the path to the raw encrypted block device. +.Pp +The +.Fn tc_api_info_mapped_volume +is similar to the +.Fn tc_api_info_volume +function, but is to be used on already mapped volumes and +doesn't require any passphrase or keyfiles. +The +.Fa tc_map_name +field in the +.Fa api_opts +parameter determines which mapped volume is to be queried. +The retrieved information is placed into the +.Fa info +argument. +All fields will be populated as when calling +.Fn tc_api_info_volume +except for the +.Fa tc_prf +field, which will contain the string "(unknown)". +.Pp +The .Fn tc_api_create_volume function creates a new volume using the parameters specified in the .Fa api_opts @@ -163,6 +268,87 @@ and .Fa tc_keyfiles_hidden respectively. +If +.Fa tc_no_secure_erase +is specified, no erase will be performed. +If +.Fa tc_use_weak_keys +is specified, the key material for the master key will be taken from +.Pa /dev/urandom +instead of +.Pa /dev/random . +This option should never be used other than for testing. +.Pp +The +.Fn tc_api_modify_volume +function modifies a volume (header) according to the parameters specified +in the +.Fa api_opts +parameter. +The volume 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 . +Depending on the passphrase/keyfiles used +either the outer or the hidden volume header will be modified. +Any keyfiles that are needed to unlock the volume are specified in +.Fa tc_keyfiles . +If +.Fa tc_use_system_encryption +is specified, a device using system encryption can be accessed. +The +.Fa tc_system_device +should point to the parent device (i.e. underlying physical disk), +while the +.Fa tc_device +argument should point to the actual encrypted partition. +If +.Fa tc_use_fde +is specified, the device pointed to by +.Fa tc_device +should be a whole disk device, not any partition. +If +.Fa tc_use_backup +is specified, +.Nm tcplay +will use the backup headers at the end of a volume +instead of the primary headers as template for the modification. +Both the backup and the main header will always be written as part +of a +.Fn tc_api_modify_volume +call. +The +.Fa tc_new_passphrase +and +.Fa tc_new_keyfiles +arguments specify the new passphrase and keyfile(s) to be used, respectively. +The +.Fa tc_new_passphrase +argument will only be used if +.Fa tc_interactive_prompt +is not set, otherwise the user will be prompted for the new passphrase. +The +.Fa tc_new_prf_hash +specifies the PBKDF2 PRF hash algorithm to be used when reencrypting the header. +If it is +.Dv NULL , +the same PBKDF2 PRF hash function will be used that the header already uses. +If +.Fa tc_use_weak_salt +is set, a weak source of entropy will be used for the salt of both the +main and backup headers. +This option does not affect the entropy of the master volume keys, as these +are not modified. .Pp The .Fn tc_api_map_volume @@ -185,12 +371,47 @@ 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. +If +.Fa tc_protect_hidden +is specified, the outer volume will be mapped, but its size will be +adjusted so that it does not map over the hidden volume - the hidden +volume will hence be protected from any accidental overwriting. +If +.Fa tc_protect_hidden +is specified, the passphrase and keyfiles for the hidden volume +must be specified in +.Fa tc_passphrase_hidden +and +.Fa tc_keyfiles_hidden . +If +.Fa tc_use_system_encryption +is specified, a device using system encryption can be accessed. +The +.Fa tc_system_device +should point to the parent device (i.e. underlying physical disk), +while the +.Fa tc_device +argument should point to the actual encrypted partition. +If +.Fa tc_use_fde +is specified, the device pointed to by +.Fa tc_device +should be a whole disk device, not any partition. +The device will be mapped or queried as a whole. +To access individual partitions, a utility such as +.Xr kpartx 8 +should be used, which will create additional individual mappings +for each partition in the decrypted mapped volume. +For more details on full disk encryption, see +.Xr tcplay 8 . +If +.Fa tc_use_backup +is specified, +.Nm tcplay +will use the backup headers at the end of a volume +instead of the primary headers to access it. .Pp The .Fn tc_api_unmap_volume @@ -231,6 +452,43 @@ .Dv SIGINFO signal when using .Xr tcplay 8 . +.Pp +The +.Fn tc_api_get_state +function returns information on the current state of +.Nm tcplay . +Three states are currently reported: +.Bl -tag -width indent +.It Dv TC_STATE_GET_RANDOM +is reported when +.Nm tcplay +is gathering entropy for key material. +.It Dv TC_STATE_ERASE +is reported when the volume is being erased. +.It Dv TC_STATE_UNKNOWN +is reported in all other cases. +.El +.Pp +The two states +.Dv TC_STATE_GET_RANDOM +and +.Dv TC_STATE_ERASE +are reported separately, as they are the only lengthy steps +during the use of tcplay. +The +.Fa progress_pct +parameter, if not +.Dv NULL , +will be set to the percentage completed so far of these operations. +.Sh NOTES +TrueCrypt limits passphrases to 64 characters (including the terminating +null character). +To be compatible with it, +.Nm tcplay +does the same. +All passphrases (exlcuding keyfiles) are trimmed to 64 characters. +Similarly, keyfiles are limited to a size of 1 MB, but up to 256 +keyfiles can be used. .Sh RETURN VALUES All functions except .Fn tc_api_get_error_msg @@ -246,7 +504,16 @@ .Fn tc_api_get_error_msg and .Fn tc_api_get_summary -always return a valid, but possibly empty, string. +functions always return a valid, but possibly empty, string. +.Pp +The +.Fn tc_api_get_state +function always returns one of: +.Dv TC_STATE_UNKNOWN , +.Dv TC_STATE_ERASE +or +.Dv TC_STATE_GET_RANDOM . + .Sh COMPATIBILITY The .Nm tcplay @@ -254,7 +521,8 @@ hidden volumes, system encryption (map-only), keyfiles and cipher cascading. .Sh SEE ALSO -.Xr tcplay 8 +.Xr tcplay 8 , +.Xr kpartx 8 .Sh HISTORY The .Nm tcplay diff -Nru tcplay-0.11/tcplay.8 tcplay-1.1/tcplay.8 --- tcplay-0.11/tcplay.8 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/tcplay.8 2013-07-31 06:01:56.000000000 +0000 @@ -29,7 +29,7 @@ .\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 5, 2011 +.Dd July 29, 2013 .Dt TCPLAY 8 .Os .Sh NAME @@ -41,6 +41,7 @@ .Fl d Ar device .Op Fl g .Op Fl z +.Op Fl w .Op Fl a Ar pbkdf_hash .Op Fl b Ar cipher .Op Fl f Ar keyfile_hidden @@ -54,6 +55,10 @@ .Op Fl f Ar keyfile_hidden .Op Fl k Ar keyfile .Op Fl s Ar system_device +.Op Fl -fde +.Op Fl -use-backup +.Nm +.Fl j Ar mapping .Nm .Fl m Ar mapping .Fl d Ar device @@ -61,6 +66,26 @@ .Op Fl f Ar keyfile_hidden .Op Fl k Ar keyfile .Op Fl s Ar system_device +.Op Fl -fde +.Op Fl -use-backup +.Nm +.Fl -modify +.Fl d Ar device +.Op Fl k Ar keyfile +.Op Fl -new-keyfile Ar new_keyfile +.Op Fl -new-pbkdf-prf Ar pbkdf_hash +.Op Fl s Ar system_device +.Op Fl -fde +.Op Fl -use-backup +.Op Fl w +.Nm +.Fl -modify +.Fl d Ar device +.Op Fl k Ar keyfile +.Fl -restore-from-backup-hdr +.Op Fl w +.Nm +.Fl u Ar mapping .Nm .Fl h | v .Sh DESCRIPTION @@ -80,6 +105,16 @@ .It Fl i , Fl -info Print out information about the encrypted device specified by .Fl -device . +.It Fl j Ar mapping , Fl -info-mapped Ns = Ns Ar mapping +Print out information about the mapped tcplay volume specified +by +.Ar mapping . +Information such as key CRC and the PBKDF2 PRF is not available +via this command. +.It Fl -modify +Modify the volume header. +This mode allows changing passphrase, keyfiles, PBKDF2 PRF as +well as restoring from a backup header. .It Fl m Ar mapping , Fl -map Ns = Ns Ar mapping Map the encrypted TrueCrypt volume on the device specified by .Fl -device @@ -87,6 +122,19 @@ .Xr dm 4 mapping called .Ar mapping . +The +.Ar mapping +argument should not contain any spaces or special characters. +.It Fl u Ar mapping , Fl -unmap Ns = Ns Ar mapping +Removes (unmaps) the +.Xr dm 4 +mapping specified by +.Ar mapping +as well as any related cascade mappings. +If you mapped a volume using full disk encryption and created +mapping for individual partitions using +.Xr kpartx 8 , +you must remove these prior to unmapping the volume. .It Fl v, Fl -version Print version message and exit. .El @@ -108,6 +156,9 @@ option has to be used. This option can appear multiple times; if so, multiple keyfiles will be used. +This option is not valid in the +.Fl -modify +mode. .It Fl k Ar keyfile , Fl -keyfile Ns = Ns Ar keyfile Specifies a .Ar keyfile @@ -138,6 +189,12 @@ .Fl -keyfile-hidden . The user will be prompted for the size of the hidden volume interactively. +.It Fl w, Fl -weak-keys +Use +.Xr urandom 4 +for key material instead of a strong entropy source. +This is in general a really bad idea and should only be used +for testing. .It Fl x Ar pbkdf_hash , Fl -pbkdf-prf-hidden Ns = Ns Ar pbkdf_hash Specifies which hash algorithm to use for the PBKDF2 password derivation for the hidden volume. @@ -162,9 +219,10 @@ .El .Pp Additional options for the -.Fl -info -and +.Fl -info , .Fl -map +and +.Fl -modify commands are: .Bl -tag -width indent .It Fl e, Fl -protect-hidden @@ -173,23 +231,98 @@ the hidden volume contained in it. Both the hidden volume and outer volume passphrase and keyfiles will be required. +This option only applies to the +.Fl -info +and +.Fl -map +commands. .It Fl s Ar system_device , Fl -system-encryption Ns = Ns Ar system_device This option is required if you are attempting to access a device that uses system encryption, for example an encrypted .Tn Windows system partition. +It does not apply to disks using full disk encryption. The .Fl -device option will point at the actual encrypted partition, while the .Ar system_device argument will point to the parent device (i.e.\& underlying physical disk) of the encrypted partition. +.It Fl -fde +This option is intended to be used with disks using full disk encryption (FDE). +When a disk has been encrypted using TrueCrypt's FDE, the complete disk +is encrypted except for the first 63 sectors. +The +.Fl -device +option should point to the whole disk device, not to any particular +partition. +The resultant mapping will cover the whole disk, and will not appear as +separate partitions. +To access individual partitions after mapping, +.Xr kpartx 8 +can be used. +.It Fl -use-backup +This option is intended to be used when the primary headers of a volume +have been corrupted. +This option will force +.Nm +to use the backup headers, which are located at the end of the device, +to access the volume. .El .Pp -To unmap volumes, -the -.Xr dmsetup 8 -utility is used. +Additional options only for the +.Fl -modify +command are: +.Bl -tag -width indent +.It Fl -new-pbkdf-prf Ns = Ns Ar pbkdf_hash +Specifies which hash algorithm to use for the PBKDF2 password +derivation on reencrypting the volume header. +If this option is not specified, the reencrypted header will +use the current PRF. +To see which algorithms are supported, specify +.Fl -pbkdf-prf Ns = Ns Cm help . +.It Fl -new-keyfile Ns = Ns Ar keyfile +Specifies a +.Ar keyfile +to use in addition to the new passphrase on reencrypting the +volume header. +This option can appear multiple times; if so, multiple +keyfiles will be used. +.It Fl -restore-from-backup-hdr +If this option is specified, neither +.Fl -new-pbkdf-prf +nor +.Fl -new-keyfile +should be specified. +This option implies +.Fl -use-backup . +Use this option to restore the volume headers from the backup +header. +.El +.Sh NOTES +TrueCrypt limits passphrases to 64 characters (including the terminating +null character). +To be compatible with it, +.Nm +does the same. +All passphrases (excluding keyfiles) are trimmed to 64 characters. +Similarly, keyfiles are limited to a size of 1 MB, but up to +256 keyfiles can be used. +.Sh PLAUSIBLE DENIABILITY +.Nm +offers plausible deniability. Hidden volumes are created within an outer +volume. +Which volume is accessed solely depends on the passphrase and keyfile(s) +used. +If the passphrase and keyfiles for the outer volume are specified, +no information about the existance of the hidden volume is exposed. +Without knowledge of the passphrase and keyfile(s) of the hidden volume +its existence remains unexposed. +The hidden volume can be protected when mapping the outer volume by +using the +.Fl -protect-hidden +option and specifying the passphrase and keyfiles for both the outer +and hidden volumes. .Sh EXAMPLES Create a new TrueCrypt volume on .Pa /dev/vn0 @@ -269,9 +402,65 @@ .Bd -ragged -offset indent .Sy dmsetup Cm remove Cm truecrypt2 .Ed +.Pp +Or alternatively: +.Bd -ragged -offset indent +.Nm Fl -unmap Ns = Ns Cm truecrypt2 +.Ed +.Pp +A hidden volume whose existance can be plausibly denied and its outer volume +can for example be created with +.Bd -ragged -offset indent +.Nm Fl -create +.Fl -hidden +.Fl -device Ns = Ns Cm /dev/loop0 +.Fl -cipher Ns = Ns Cm AES-256-XTS,TWOFISH-256-XTS +.Fl -pbkdf-prf Ns = Ns Cm whirlpool +.Fl -keyfile Ns = Ns Cm one.key +.Fl -cipher-hidden Ns = Ns Cm AES-256-XTS +.Fl -pbkdf-prf-hidden Ns = Ns Cm whirlpool +.Fl -keyfile-hidden Ns = Ns Cm hidden.key +.Ed +.Pp +.Nm +will prompt the user for the passphrase for both the outer and hidden volume +as well as the size of the hidden volume inside the outer volume. +The hidden volume will be created inside the area spanned by the outer volume. +The hidden volume can optionally use a different cipher and prf function +as specified by the +.Fl -cipher-hidden +and +.Fl -pbkdf-prf-hidden +options. +Which volume is later accessed depends only on which passphrase and keyfile(s) +are being used, +so that the existance of the hidden volume remains unknown without knowledge +of the passphrase and keyfile it is protected by since it is located within +the outer volume. +To map the outer volume without potentially damaging the hidden volume, +the passphrase and keyfile(s) of the hidden volume must be known and provided +alongside the +.Fl -protect-hidden +option. +.Pp +A disk encrypted using full disk encryption can be mapped using +.Bd -ragged -offset indent +.Nm Fl -map Ns = Ns Cm tcplay_sdb +.Fl -device Ns = Ns Cm /dev/sdb +.Fl -fde +.Ed +.Pp +To access individual partitions on the now mapped disk, +the following command will generate mappings for each +individual partition on the encrypted disk: +.Bd -ragged -offset indent +.Sy kpartx Fl -av Cm /dev/mapper/tcplay_sdb +.Ed .Sh SEE ALSO .Xr crypttab 5 , -.Xr cryptsetup 8 +.Xr cryptsetup 8 , +.Xr dmsetup 8 , +.Xr kpartx 8 .Sh HISTORY The .Nm diff -Nru tcplay-0.11/tcplay.c tcplay-1.1/tcplay.c --- tcplay-0.11/tcplay.c 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/tcplay.c 2013-07-31 06:01:56.000000000 +0000 @@ -27,7 +27,9 @@ * SUCH DAMAGE. */ +#define _BSD_SOURCE #include +#include #if defined(__DragonFly__) #include @@ -50,6 +52,8 @@ #include #endif +#include + #include "crc32.h" #include "tcplay.h" #include "humanize.h" @@ -64,6 +68,7 @@ summary_fn_t summary_fn = NULL; int tc_internal_verbose = 1; char tc_internal_log_buffer[LOG_BUFFER_SZ]; +int tc_internal_state = STATE_UNKNOWN; void tc_log(int is_err, const char *fmt, ...) @@ -102,9 +107,9 @@ { "TWOFISH-128-XTS", "twofish-xts-plain", 32, 8 }, { "SERPENT-128-XTS", "serpent-xts-plain", 32, 8 }, #endif - { "AES-256-XTS", "aes-xts-plain", 64, 8 }, - { "TWOFISH-256-XTS", "twofish-xts-plain", 64, 8 }, - { "SERPENT-256-XTS", "serpent-xts-plain", 64, 8 }, + { "AES-256-XTS", "aes-xts-plain64", 64, 8 }, + { "TWOFISH-256-XTS", "twofish-xts-plain64", 64, 8 }, + { "SERPENT-256-XTS", "serpent-xts-plain64", 64, 8 }, { NULL, NULL, 0, 0 } }; @@ -191,6 +196,106 @@ return 0; } +static +struct tc_cipher_chain * +tc_dup_cipher_chain(struct tc_cipher_chain *src) +{ + struct tc_cipher_chain *first = NULL, *prev = NULL, *elem; + + for (; src != NULL; src = src->next) { + if ((elem = alloc_safe_mem(sizeof(*elem))) == NULL) { + tc_log(1, "Error allocating memory for " + "duplicate cipher chain\n"); + return NULL; + } + + memcpy(elem, src, sizeof(*elem)); + + if (src->key != NULL) { + if ((elem->key = alloc_safe_mem(src->cipher->klen)) == NULL) { + tc_log(1, "Error allocating memory for " + "duplicate key in cipher chain\n"); + return NULL; + } + + memcpy(elem->key, src->key, src->cipher->klen); + } + + if (first == NULL) + first = elem; + + elem->next = NULL; + elem->prev = prev; + + if (prev != NULL) + prev->next = elem; + + prev = elem; + } + + return first; +} + +static +int +tc_free_cipher_chain(struct tc_cipher_chain *chain) +{ + struct tc_cipher_chain *next = chain; + + while ((chain = next) != NULL) { + next = chain->next; + + if (chain->key != NULL) + free_safe_mem(chain->key); + free_safe_mem(chain); + } + + return 0; +} + +static +int +tc_cipher_chain_length(struct tc_cipher_chain *chain) +{ + int len = 0; + + for (; chain != NULL; chain = chain->next) + ++len; + + return len; +} + +int +tc_cipher_chain_klen(struct tc_cipher_chain *chain) +{ + int klen_bytes = 0; + + for (; chain != NULL; chain = chain->next) { + klen_bytes += chain->cipher->klen; + } + + return klen_bytes; +} + +char * +tc_cipher_chain_sprint(char *buf, size_t bufsz, struct tc_cipher_chain *chain) +{ + static char sbuf[256]; + int n = 0; + + if (buf == NULL) { + buf = sbuf; + bufsz = sizeof(sbuf); + } + + for (; chain != NULL; chain = chain->next) { + n += snprintf(buf+n, bufsz-n, "%s%s", chain->cipher->name, + (chain->next != NULL) ? "," : "\0"); + } + + return buf; +} + #ifdef DEBUG static void print_hex(unsigned char *buf, off_t start, size_t len) @@ -207,36 +312,43 @@ void print_info(struct tcplay_info *info) { - struct tc_cipher_chain *cipher_chain; - int klen = 0; + printf("Device:\t\t\t%s\n", info->dev); - printf("PBKDF2 PRF:\t\t%s\n", info->pbkdf_prf->name); - printf("PBKDF2 iterations:\t%d\n", info->pbkdf_prf->iteration_count); + if (info->pbkdf_prf != NULL) { + printf("PBKDF2 PRF:\t\t%s\n", info->pbkdf_prf->name); + printf("PBKDF2 iterations:\t%d\n", + info->pbkdf_prf->iteration_count); + } + + printf("Cipher:\t\t\t%s\n", + tc_cipher_chain_sprint(NULL, 0, info->cipher_chain)); + + printf("Key Length:\t\t%d bits\n", + 8*tc_cipher_chain_klen(info->cipher_chain)); - 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); + if (info->hdr != NULL) { + printf("CRC Key Data:\t\t%#x\n", info->hdr->crc_keys); + printf("Sector size:\t\t%d\n", info->hdr->sec_sz); + } else { + printf("Sector size:\t\t512\n"); + } printf("Volume size:\t\t%zu sectors\n", info->size); #if 0 /* Don't print this; it's always 0 and is rather confusing */ printf("Volume offset:\t\t%"PRIu64"\n", (uint64_t)info->start); #endif + +#ifdef DEBUG + printf("Vol Flags:\t\t%d\n", info->volflags); +#endif + printf("IV offset:\t\t%"PRIu64"\n", (uint64_t)info->skip); printf("Block offset:\t\t%"PRIu64"\n", (uint64_t)info->offset); } static struct tcplay_info * -new_info(const char *dev, int sflag, struct tc_cipher_chain *cipher_chain, +new_info(const char *dev, int flags, struct tc_cipher_chain *cipher_chain, struct pbkdf_prf_algo *prf, struct tchdr_dec *hdr, off_t start) { struct tc_cipher_chain *chain_start; @@ -251,7 +363,7 @@ return NULL; } - info->dev = dev; + strncpy(info->dev, dev, sizeof(info->dev)); info->cipher_chain = cipher_chain; info->pbkdf_prf = prf; info->start = start; @@ -259,7 +371,10 @@ info->size = hdr->sz_mk_scope / hdr->sec_sz; /* volume size */ info->skip = hdr->off_mk_scope / hdr->sec_sz; /* iv skip */ - if (sflag) + info->volflags = hdr->flags; + info->flags = flags; + + if (TC_FLAG_SET(flags, SYS)) info->offset = 0; /* offset is 0 for system volumes */ else info->offset = hdr->off_mk_scope / hdr->sec_sz; /* block offset */ @@ -282,6 +397,20 @@ return info; } +static +int +free_info(struct tcplay_info *info) +{ + if (info->cipher_chain) + tc_free_cipher_chain(info->cipher_chain); + if (info->hdr) + free_safe_mem(info->hdr); + + free_safe_mem(info); + + return 0; +} + int adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo) { @@ -293,11 +422,12 @@ } int -process_hdr(const char *dev, int sflag, unsigned char *pass, int passlen, +process_hdr(const char *dev, int flags, unsigned char *pass, int passlen, struct tchdr_enc *ehdr, struct tcplay_info **pinfo) { struct tchdr_dec *dhdr; struct tcplay_info *info; + struct tc_cipher_chain *cipher_chain = NULL; unsigned char *key; int i, j, found, error; @@ -334,11 +464,12 @@ #endif for (j = 0; !found && tc_cipher_chains[j] != NULL; j++) { + cipher_chain = tc_dup_cipher_chain(tc_cipher_chains[j]); #ifdef DEBUG printf("\nTrying cipher chain %d\n", j); #endif - dhdr = decrypt_hdr(ehdr, tc_cipher_chains[j], key); + dhdr = decrypt_hdr(ehdr, cipher_chain, key); if (dhdr == NULL) { tc_log(1, "hdr decryption failed for cipher " "chain %d\n", j); @@ -360,6 +491,7 @@ found = 1; } else { free_safe_mem(dhdr); + tc_free_cipher_chain(cipher_chain); } } } @@ -369,7 +501,7 @@ if (!found) return EINVAL; - if ((info = new_info(dev, sflag, tc_cipher_chains[j-1], + if ((info = new_info(dev, flags, cipher_chain, &pbkdf_prf_algos[i-1], dhdr, 0)) == NULL) { free_safe_mem(dhdr); return ENOMEM; @@ -384,9 +516,9 @@ create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles, struct pbkdf_prf_algo *prf_algo, struct tc_cipher_chain *cipher_chain, struct pbkdf_prf_algo *h_prf_algo, - struct tc_cipher_chain *h_cipher_chain, char *passphrase, - char *h_passphrase, size_t size_hidden_bytes_in, int interactive, - int use_secure_erase) + struct tc_cipher_chain *h_cipher_chain, const char *passphrase, + const char *h_passphrase, size_t size_hidden_bytes_in, int interactive, + int use_secure_erase, int weak_keys) { char *pass, *pass_again; char *h_pass = NULL; @@ -423,15 +555,16 @@ } if (interactive) { - if (((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) || - ((pass_again = alloc_safe_mem(MAX_PASSSZ)) == NULL)) { + if (((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) || + ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) { tc_log(1, "could not allocate safe passphrase memory\n"); goto out; } - if ((error = read_passphrase("Passphrase: ", pass, MAX_PASSSZ, 0) || - (read_passphrase("Repeat passphrase: ", pass_again, - MAX_PASSSZ, 0)))) { + if ((error = read_passphrase("Passphrase: ", pass, MAX_PASSSZ, + PASS_BUFSZ, 0) || + (read_passphrase("Repeat passphrase: ", pass_again, + MAX_PASSSZ, PASS_BUFSZ, 0)))) { tc_log(1, "could not read passphrase\n"); goto out; } @@ -445,19 +578,21 @@ pass_again = NULL; } else { /* In batch mode, use provided passphrase */ - if ((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) { + if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { tc_log(1, "could not allocate safe " "passphrase memory"); goto out; } - if (passphrase != NULL) - strcpy(pass, passphrase); + if (passphrase != NULL) { + strncpy(pass, passphrase, MAX_PASSSZ); + pass[MAX_PASSSZ] = '\0'; + } } if (nkeyfiles > 0) { /* Apply keyfiles to 'pass' */ - if ((error = apply_keyfiles((unsigned char *)pass, MAX_PASSSZ, + if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ, keyfiles, nkeyfiles))) { tc_log(1, "could not apply keyfiles\n"); goto out; @@ -466,17 +601,17 @@ if (hidden) { if (interactive) { - if (((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) || - ((pass_again = alloc_safe_mem(MAX_PASSSZ)) == NULL)) { + if (((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) || + ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) { tc_log(1, "could not allocate safe " "passphrase memory\n"); goto out; } if ((error = read_passphrase("Passphrase for hidden volume: ", - h_pass, MAX_PASSSZ, 0) || + h_pass, MAX_PASSSZ, PASS_BUFSZ, 0) || (read_passphrase("Repeat passphrase: ", pass_again, - MAX_PASSSZ, 0)))) { + MAX_PASSSZ, PASS_BUFSZ, 0)))) { tc_log(1, "could not read passphrase\n"); goto out; } @@ -491,20 +626,22 @@ pass_again = NULL; } else { /* In batch mode, use provided passphrase */ - if ((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) { + if ((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { tc_log(1, "could not allocate safe " "passphrase memory"); goto out; } - if (h_passphrase != NULL) - strcpy(h_pass, h_passphrase); + if (h_passphrase != NULL) { + strncpy(h_pass, h_passphrase, MAX_PASSSZ); + h_pass[MAX_PASSSZ] = '\0'; + } } if (n_hkeyfiles > 0) { /* Apply keyfiles to 'h_pass' */ if ((error = apply_keyfiles((unsigned char *)h_pass, - MAX_PASSSZ, h_keyfiles, n_hkeyfiles))) { + PASS_BUFSZ, h_keyfiles, n_hkeyfiles))) { tc_log(1, "could not apply keyfiles\n"); goto out; } @@ -603,11 +740,17 @@ "process may take a few minutes as it uses true random data which " "might take a while to refill\n"); + if (weak_keys) { + tc_log(0, "WARNING: Using a weak random generator to get " + "entropy for the key material. Odds are this is NOT " + "what you want.\n"); + } + /* create encrypted headers */ ehdr = create_hdr((unsigned char *)pass, (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), prf_algo, cipher_chain, blksz, blocks, VOL_RSVD_BYTES_START/blksz, - blocks - (MIN_VOL_BYTES/blksz), 0, &ehdr_backup); + blocks - (MIN_VOL_BYTES/blksz), 0, weak_keys, &ehdr_backup); if (ehdr == NULL) { tc_log(1, "Could not create header\n"); goto out; @@ -619,7 +762,7 @@ h_cipher_chain, blksz, blocks, blocks - (VOL_RSVD_BYTES_END/blksz) - hidden_blocks, - hidden_blocks, 1, &hehdr_backup); + hidden_blocks, 1, weak_keys, &hehdr_backup); if (hehdr == NULL) { tc_log(1, "Could not create hidden volume header\n"); goto out; @@ -682,12 +825,12 @@ return ret; } -static struct tcplay_info * -info_map_common(const char *dev, int sflag, const char *sys_dev, +info_map_common(const char *dev, int flags, 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) + const char *h_keyfiles[], int n_hkeyfiles, const char *passphrase, + const char *passphrase_hidden, int interactive, int retries, + time_t timeout, char *passphrase_out) { struct tchdr_enc *ehdr, *hehdr = NULL; struct tcplay_info *info, *hinfo = NULL; @@ -696,6 +839,7 @@ int error, error2 = 0; size_t sz; size_t blocks, blksz; + int is_hidden = 0; if ((error = get_disk_info(dev, &blocks, &blksz)) != 0) { tc_log(1, "could not get disk information\n"); @@ -716,27 +860,34 @@ ehdr = hehdr = NULL; info = hinfo = NULL; - if ((pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) { + if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { tc_log(1, "could not allocate safe passphrase memory\n"); goto out; } if (interactive) { if ((error = read_passphrase("Passphrase: ", pass, - MAX_PASSSZ, timeout))) { + MAX_PASSSZ, PASS_BUFSZ, timeout))) { tc_log(1, "could not read passphrase\n"); /* XXX: handle timeout differently? */ goto out; } + pass[MAX_PASSSZ] = '\0'; } else { /* In batch mode, use provided passphrase */ - if (passphrase != NULL) - strcpy(pass, passphrase); + if (passphrase != NULL) { + strncpy(pass, passphrase, MAX_PASSSZ); + pass[MAX_PASSSZ] = '\0'; + } + } + + if (passphrase_out != NULL) { + strcpy(passphrase_out, pass); } if (nkeyfiles > 0) { /* Apply keyfiles to 'pass' */ - if ((error = apply_keyfiles((unsigned char *)pass, MAX_PASSSZ, + if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ, keyfiles, nkeyfiles))) { tc_log(1, "could not apply keyfiles"); goto out; @@ -744,7 +895,7 @@ } if (protect_hidden) { - if ((h_pass = alloc_safe_mem(MAX_PASSSZ)) == NULL) { + if ((h_pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { tc_log(1, "could not allocate safe passphrase memory\n"); goto out; } @@ -752,19 +903,22 @@ if (interactive) { if ((error = read_passphrase( "Passphrase for hidden volume: ", h_pass, - MAX_PASSSZ, timeout))) { + MAX_PASSSZ, PASS_BUFSZ, timeout))) { tc_log(1, "could not read passphrase\n"); goto out; } + h_pass[MAX_PASSSZ] = '\0'; } else { /* In batch mode, use provided passphrase */ - if (passphrase_hidden != NULL) - strcpy(h_pass, passphrase_hidden); + if (passphrase_hidden != NULL) { + strncpy(h_pass, passphrase_hidden, MAX_PASSSZ); + h_pass[MAX_PASSSZ] = '\0'; + } } if (n_hkeyfiles > 0) { /* Apply keyfiles to 'pass' */ - if ((error = apply_keyfiles((unsigned char *)h_pass, MAX_PASSSZ, + if ((error = apply_keyfiles((unsigned char *)h_pass, PASS_BUFSZ, h_keyfiles, n_hkeyfiles))) { tc_log(1, "could not apply keyfiles"); goto out; @@ -775,19 +929,24 @@ /* Always read blksz-sized chunks */ sz = blksz; - ehdr = (struct tchdr_enc *)read_to_safe_mem((sflag) ? sys_dev : dev, - (sflag) ? HDR_OFFSET_SYS : 0, &sz); + ehdr = (struct tchdr_enc *)read_to_safe_mem( + (TC_FLAG_SET(flags, SYS)) ? sys_dev : dev, + (TC_FLAG_SET(flags, SYS) || TC_FLAG_SET(flags, FDE)) ? + HDR_OFFSET_SYS : + (!TC_FLAG_SET(flags, BACKUP)) ? 0 : -BACKUP_HDR_OFFSET_END, + &sz); if (ehdr == NULL) { tc_log(1, "error read hdr_enc: %s", dev); goto out; } - if (!sflag) { + if (!TC_FLAG_SET(flags, SYS)) { /* Always read blksz-sized chunks */ sz = blksz; hehdr = (struct tchdr_enc *)read_to_safe_mem(dev, - HDR_OFFSET_HIDDEN, &sz); + (!TC_FLAG_SET(flags, BACKUP)) ? HDR_OFFSET_HIDDEN : + -BACKUP_HDR_HIDDEN_OFFSET_END, &sz); if (hehdr == NULL) { tc_log(1, "error read hdr_enc: %s", dev); goto out; @@ -796,7 +955,7 @@ hehdr = NULL; } - error = process_hdr(dev, sflag, (unsigned char *)pass, + error = process_hdr(dev, flags, (unsigned char *)pass, (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), ehdr, &info); @@ -807,11 +966,12 @@ */ if (hehdr && (error || protect_hidden)) { if (error) { - error2 = process_hdr(dev, sflag, (unsigned char *)pass, + error2 = process_hdr(dev, flags, (unsigned char *)pass, (nkeyfiles > 0)?MAX_PASSSZ:strlen(pass), hehdr, &info); + is_hidden = !error2; } else if (protect_hidden) { - error2 = process_hdr(dev, sflag, (unsigned char *)h_pass, + error2 = process_hdr(dev, flags, (unsigned char *)h_pass, (n_hkeyfiles > 0)?MAX_PASSSZ:strlen(h_pass), hehdr, &hinfo); } @@ -823,15 +983,11 @@ tc_log(1, "Incorrect password or not a TrueCrypt volume\n"); if (info) { - if (info->hdr) - free_safe_mem(info->hdr); - free_safe_mem(info); + free_info(info); info = NULL; } if (hinfo) { - if (hinfo->hdr) - free_safe_mem(hinfo->hdr); - free_safe_mem(hinfo); + free_info(hinfo); hinfo = NULL; } @@ -857,30 +1013,27 @@ if (protect_hidden) { if (adjust_info(info, hinfo) != 0) { tc_log(1, "Could not protect hidden volume\n"); - if (info) { - if (info->hdr) - free_safe_mem(info->hdr); - free_safe_mem(info); - } + if (info) + free_info(info); info = NULL; - if (hinfo->hdr) - free_safe_mem(hinfo->hdr); - free_safe_mem(hinfo); + if (hinfo) + free_info(hinfo); hinfo = NULL; + goto out; } - if (hinfo->hdr) - free_safe_mem(hinfo->hdr); - free_safe_mem(hinfo); - hinfo = NULL; + if (hinfo) { + free_info(hinfo); + hinfo = NULL; + } } } out: if (hinfo) - free_safe_mem(hinfo); + free_info(hinfo); if (pass) free_safe_mem(pass); if (h_pass) @@ -890,28 +1043,54 @@ if (hehdr) free_safe_mem(hehdr); + if (info != NULL) + info->hidden = is_hidden; + return info; } int -info_volume(const char *device, int sflag, const char *sys_dev, +info_mapped_volume(const char *map_name, int interactive) +{ + struct tcplay_info *info; + + info = dm_info_map(map_name); + if (info != NULL) { + if (interactive) + print_info(info); + + free_info(info); + + return 0; + /* NOT REACHED */ + } else if (interactive) { + tc_log(1, "Could not retrieve information about mapped " + "volume %s. Does it exist?\n", map_name); + } + + return -1; +} + +int +info_volume(const char *device, int flags, 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, + const char *passphrase, const char *passphrase_hidden, + int interactive, int retries, time_t timeout) { struct tcplay_info *info; - info = info_map_common(device, sflag, sys_dev, protect_hidden, + info = info_map_common(device, flags, sys_dev, protect_hidden, keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, - passphrase, passphrase_hidden, interactive, retries, timeout); + passphrase, passphrase_hidden, interactive, retries, timeout, + NULL); if (info != NULL) { if (interactive) print_info(info); - if (info->hdr) - free_safe_mem(info->hdr); - free_safe_mem(info); + + free_info(info); return 0; /* NOT REACHED */ @@ -921,39 +1100,511 @@ } int -map_volume(const char *map_name, const char *device, int sflag, +map_volume(const char *map_name, const char *device, int flags, 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, + const char *passphrase, const 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, + info = info_map_common(device, flags, sys_dev, protect_hidden, keyfiles, nkeyfiles, h_keyfiles, n_hkeyfiles, - passphrase, passphrase_hidden, interactive, retries, timeout); + passphrase, passphrase_hidden, interactive, retries, timeout, + NULL); if (info == NULL) return -1; if ((error = dm_setup(map_name, info)) != 0) { tc_log(1, "Could not set up mapping %s\n", map_name); - if (info->hdr) - free_safe_mem(info->hdr); - free_safe_mem(info); + free_info(info); return -1; } if (interactive) printf("All ok!\n"); - free_safe_mem(info); + free_info(info); return 0; } +int +modify_volume(const char *device, int flags, const char *sys_dev, + const char *keyfiles[], int nkeyfiles, const char *new_keyfiles[], + int n_newkeyfiles, struct pbkdf_prf_algo *new_prf_algo, + const char *passphrase, const char *new_passphrase, int interactive, + int retries, time_t timeout, int weak_salt) +{ + struct tcplay_info *info; + struct tchdr_enc *ehdr, *ehdr_backup; + char *pass, *pass_again; + int ret = -1; + off_t offset, offset_backup; + const char *dev; + size_t blocks, blksz; + int error; + + ehdr = ehdr_backup = NULL; + pass = pass_again = NULL; + info = NULL; + + if (TC_FLAG_SET(flags, ONLY_RESTORE)) { + if (interactive) { + if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { + tc_log(1, "could not allocate safe " + "passphrase memory"); + goto out; + } + } else { + new_passphrase = passphrase; + } + new_keyfiles = keyfiles; + n_newkeyfiles = nkeyfiles; + new_prf_algo = NULL; + } + + info = info_map_common(device, flags, sys_dev, 0, + keyfiles, nkeyfiles, NULL, 0, + passphrase, NULL, interactive, retries, timeout, pass); + + + if (info == NULL) + goto out; + + if (interactive && !TC_FLAG_SET(flags, ONLY_RESTORE)) { + if (((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) || + ((pass_again = alloc_safe_mem(PASS_BUFSZ)) == NULL)) { + tc_log(1, "could not allocate safe passphrase memory\n"); + goto out; + } + + if ((error = read_passphrase("New passphrase: ", pass, MAX_PASSSZ, + PASS_BUFSZ, 0) || + (read_passphrase("Repeat passphrase: ", pass_again, + MAX_PASSSZ, PASS_BUFSZ, 0)))) { + tc_log(1, "could not read passphrase\n"); + goto out; + } + + if (strcmp(pass, pass_again) != 0) { + tc_log(1, "Passphrases don't match\n"); + goto out; + } + + free_safe_mem(pass_again); + pass_again = NULL; + } else if (!interactive) { + /* In batch mode, use provided passphrase */ + if ((pass = alloc_safe_mem(PASS_BUFSZ)) == NULL) { + tc_log(1, "could not allocate safe " + "passphrase memory"); + goto out; + } + + if (new_passphrase != NULL) { + strncpy(pass, new_passphrase, MAX_PASSSZ); + pass[MAX_PASSSZ] = '\0'; + } + } + + if (n_newkeyfiles > 0) { + /* Apply keyfiles to 'pass' */ + if ((error = apply_keyfiles((unsigned char *)pass, PASS_BUFSZ, + new_keyfiles, n_newkeyfiles))) { + tc_log(1, "could not apply keyfiles\n"); + goto out; + } + } + + ehdr = copy_reencrypt_hdr((unsigned char *)pass, + (n_newkeyfiles > 0)?MAX_PASSSZ:strlen(pass), + new_prf_algo, weak_salt, info, &ehdr_backup); + if (ehdr == NULL) { + tc_log(1, "Could not create header\n"); + goto out; + } + + dev = (TC_FLAG_SET(flags, SYS)) ? sys_dev : device; + if (TC_FLAG_SET(flags, SYS) || TC_FLAG_SET(flags, FDE)) { + /* SYS and FDE don't have backup headers (as far as I understand) */ + if (info->hidden) { + offset = HDR_OFFSET_HIDDEN; + } else { + offset = HDR_OFFSET_SYS; + } + } else { + if (info->hidden) { + offset = HDR_OFFSET_HIDDEN; + offset_backup = -BACKUP_HDR_HIDDEN_OFFSET_END; + } else { + offset = 0; + offset_backup = -BACKUP_HDR_OFFSET_END; + } + } + + if ((error = get_disk_info(dev, &blocks, &blksz)) != 0) { + tc_log(1, "could not get disk information\n"); + goto out; + } + + tc_log(0, "Writing new volume headers to disk...\n"); + + if ((error = write_to_disk(dev, offset, blksz, ehdr, + sizeof(*ehdr))) != 0) { + tc_log(1, "Could not write volume header to device\n"); + goto out; + } + + if (!TC_FLAG_SET(flags, SYS) && !TC_FLAG_SET(flags, FDE)) { + if ((error = write_to_disk(dev, offset_backup, blksz, + ehdr_backup, sizeof(*ehdr_backup))) != 0) { + tc_log(1, "Could not write backup volume header to device\n"); + goto out; + } + } + + /* Everything went ok */ + tc_log(0, "All done!\n"); + + ret = 0; + +out: + if (pass) + free_safe_mem(pass); + if (pass_again) + free_safe_mem(pass_again); + if (ehdr) + free_safe_mem(ehdr); + if (ehdr_backup) + free_safe_mem(ehdr_backup); + if (info) + free_safe_mem(info); + + return ret; +} + +static +int +dm_get_info(const char *name, struct dm_info *dmi) +{ + struct dm_task *dmt = NULL; + int error = -1; + + if ((dmt = dm_task_create(DM_DEVICE_INFO)) == NULL) + goto out; + + if ((dm_task_set_name(dmt, name)) == 0) + goto out; + + if ((dm_task_run(dmt)) == 0) + goto out; + + if ((dm_task_get_info(dmt, dmi)) == 0) + goto out; + + error = 0; + +out: + if (dmt) + dm_task_destroy(dmt); + + return error; +} + +#if defined(__DragonFly__) +static +int +xlate_maj_min(char *start_path __unused, int max_depth __unused, + char *buf, size_t bufsz, uint32_t maj, uint32_t min) +{ + dev_t dev = makedev(maj, min); + + snprintf(buf, bufsz, "/dev/%s", devname(dev, S_IFCHR)); + return 1; +} +#else +static +int +xlate_maj_min(const char *start_path, int max_depth, char *buf, size_t bufsz, + uint32_t maj, uint32_t min) +{ + dev_t dev = makedev(maj, min); + char path[PATH_MAX]; + struct stat sb; + struct dirent *ent; + DIR *dirp; + int found = 0; + + if (max_depth <= 0) + return -1; + + if ((dirp = opendir(start_path)) == NULL) + return -1; + + while ((ent = readdir(dirp)) != NULL) { + /* d_name, d_type, DT_BLK, DT_CHR, DT_DIR, DT_LNK */ + if (ent->d_name[0] == '.') + continue; + + /* Linux' /dev is littered with junk, so skip over it */ + /* + * The dm- devices seem to be the raw DM devices + * things in mapper/ link to. + */ + if (((strcmp(ent->d_name, "block")) == 0) || + ((strcmp(ent->d_name, "fd")) == 0) || + (((strncmp(ent->d_name, "dm-", 3) == 0) && strlen(ent->d_name) <= 5))) + continue; + + snprintf(path, PATH_MAX, "%s/%s", start_path, ent->d_name); + + if ((stat(path, &sb)) < 0) + continue; + + if (S_ISDIR(sb.st_mode)) { + found = !xlate_maj_min(path, max_depth-1, buf, bufsz, maj, min); + if (found) + break; + } + + if (!S_ISBLK(sb.st_mode)) + continue; + + if (sb.st_rdev != dev) + continue; + + snprintf(buf, bufsz, "%s", path); + found = 1; + break; + } + + if (dirp) + closedir(dirp); + + return found ? 0 : -ENOENT; +} +#endif + +static +struct tcplay_dm_table * +dm_get_table(const char *name) +{ + struct tcplay_dm_table *tc_table; + struct dm_task *dmt = NULL; + void *next = NULL; + uint64_t start, length; + char *target_type; + char *params; + char *p1; + int c = 0; + uint32_t maj, min; + + if ((tc_table = (struct tcplay_dm_table *)alloc_safe_mem(sizeof(*tc_table))) == NULL) { + tc_log(1, "could not allocate safe tc_table memory\n"); + return NULL; + } + + if ((dmt = dm_task_create(DM_DEVICE_TABLE)) == NULL) + goto error; + + if ((dm_task_set_name(dmt, name)) == 0) + goto error; + + if ((dm_task_run(dmt)) == 0) + goto error; + + tc_table->start = (off_t)0; + tc_table->size = (size_t)0; + + do { + next = dm_get_next_target(dmt, next, &start, &length, + &target_type, ¶ms); + + tc_table->size += (size_t)length; + strncpy(tc_table->target, target_type, + sizeof(tc_table->target)); + + /* Skip any leading whitespace */ + while (params && *params == ' ') + params++; + + if (strcmp(target_type, "crypt") == 0) { + while ((p1 = strsep(¶ms, " ")) != NULL) { + /* Skip any whitespace before the next strsep */ + while (params && *params == ' ') + params++; + + /* Process p1 */ + if (c == 0) { + /* cipher */ + strncpy(tc_table->cipher, p1, + sizeof(tc_table->cipher)); + } else if (c == 2) { + /* iv offset */ + tc_table->skip = (off_t)strtoll(p1, NULL, 10); + } else if (c == 3) { + /* major:minor */ + maj = strtoul(p1, NULL, 10); + while (*p1 != ':' && *p1 != '\0') + p1++; + min = strtoul(++p1, NULL, 10); + if ((xlate_maj_min("/dev", 2, tc_table->device, + sizeof(tc_table->device), maj, min)) != 0) + snprintf(tc_table->device, + sizeof(tc_table->device), + "%u:%u", maj, min); + } else if (c == 4) { + /* block offset */ + tc_table->offset = (off_t)strtoll(p1, + NULL, 10); + } + ++c; + } + + if (c != 5) { + tc_log(1, "could not get all the info required from " + "the table\n"); + goto error; + } + } + } while (next != NULL); + + if (dmt) + dm_task_destroy(dmt); + +#ifdef DEBUG + printf("device: %s\n", tc_table->device); + printf("target: %s\n", tc_table->target); + printf("cipher: %s\n", tc_table->cipher); + printf("size: %ju\n", tc_table->size); + printf("offset: %"PRId64"\n", tc_table->offset); + printf("skip: %"PRId64"\n", tc_table->skip); +#endif + + return tc_table; + +error: + if (dmt) + dm_task_destroy(dmt); + if (tc_table) + free_safe_mem(tc_table); + + return NULL; +} + +struct tcplay_info * +dm_info_map(const char *map_name) +{ + struct dm_task *dmt = NULL; + struct dm_info dmi[3]; + struct tcplay_dm_table *dm_table[3]; + struct tc_crypto_algo *crypto_algo; + struct tcplay_info *info; + char map[PATH_MAX]; + char ciphers[512]; + int i, outermost = -1; + + memset(dm_table, 0, sizeof(dm_table)); + + if ((info = (struct tcplay_info *)alloc_safe_mem(sizeof(*info))) == NULL) { + tc_log(1, "could not allocate safe info memory\n"); + return NULL; + } + + strncpy(map, map_name, PATH_MAX); + for (i = 0; i < 3; i++) { + if ((dm_get_info(map, &dmi[i])) != 0) + goto error; + + if (dmi[i].exists) + dm_table[i] = dm_get_table(map); + + snprintf(map, PATH_MAX, "%s.%d", map_name, i); + } + + if (dmt) + dm_task_destroy(dmt); + + if (dm_table[0] == NULL) + goto error; + + /* + * Process our dmi, dm_table fun into the info structure. + */ + /* First find which cipher chain we are using */ + ciphers[0] = '\0'; + for (i = 0; i < 3; i++) { + if (dm_table[i] == NULL) + continue; + + if (outermost < i) + outermost = i; + + crypto_algo = &tc_crypto_algos[0]; + while ((crypto_algo != NULL) && + (strcmp(dm_table[i]->cipher, crypto_algo->dm_crypt_str) != 0)) + ++crypto_algo; + if (crypto_algo == NULL) { + tc_log(1, "could not find corresponding cipher\n"); + goto error; + } + strcat(ciphers, crypto_algo->name); + strcat(ciphers, ","); + } + ciphers[strlen(ciphers)-1] = '\0'; + + info->cipher_chain = check_cipher_chain(ciphers, 1); + if (info->cipher_chain == NULL) { + tc_log(1, "could not find cipher chain\n"); + goto error; + } + + /* Copy over the name */ + strncpy(info->dev, dm_table[outermost]->device, sizeof(info->dev)); + + /* Other fields */ + info->hdr = NULL; + info->pbkdf_prf = NULL; + info->start = dm_table[outermost]->start; + info->size = dm_table[0]->size; + info->skip = dm_table[outermost]->skip; + info->offset = dm_table[outermost]->offset; + + return info; + +error: + if (dmt) + dm_task_destroy(dmt); + if (info) + free_safe_mem(info); + for (i = 0; i < 3; i++) + if (dm_table[i] != NULL) + free_safe_mem(dm_table[i]); + + return NULL; +} + +static +int +dm_exists_device(const char *name) +{ + struct dm_info dmi; + int exists = 0; + + if (dm_get_info(name, &dmi) != 0) + goto out; + + exists = dmi.exists; + +out: + return exists; +} + static int dm_remove_device(const char *name) @@ -985,14 +1636,14 @@ struct dm_task *dmt = NULL; struct dm_info dmi; char *params = NULL; - char *uu; + char *uu, *uu_temp; char *uu_stack[64]; int uu_stack_idx; #if defined(__DragonFly__) uint32_t status; #endif int r, ret = 0; - int j; + int j, len; off_t start, offset; char dev[PATH_MAX]; char map[PATH_MAX]; @@ -1010,25 +1661,26 @@ offset = info->offset; uu_stack_idx = 0; + /* + * Find length of cipher chain. Could use the for below, but doesn't + * really matter. + */ + len = tc_cipher_chain_length(info->cipher_chain); + /* Get to the end of the chain */ for (cipher_chain = info->cipher_chain; cipher_chain->next != NULL; cipher_chain = cipher_chain->next) ; - for (j= 0; cipher_chain != NULL; - cipher_chain = cipher_chain->prev, j++) { + /* + * Start j at len-2, as we want to use .0, and the final one has no + * suffix. + */ + for (j = len-2; cipher_chain != NULL; + cipher_chain = cipher_chain->prev, j--) { cookie = 0; - /* aes-cbc-essiv:sha256 7997f8af... 0 /dev/ad0s0a 8 */ - /* iv off---^ block off--^ */ - snprintf(params, 512, "%s %s %"PRIu64 " %s %"PRIu64, - cipher_chain->cipher->dm_crypt_str, cipher_chain->dm_key, - (uint64_t)info->skip, dev, (uint64_t)offset); -#ifdef DEBUG - printf("Params: %s\n", params); -#endif - if ((dmt = dm_task_create(DM_DEVICE_CREATE)) == NULL) { tc_log(1, "dm_task_create failed\n"); ret = -1; @@ -1052,12 +1704,12 @@ #if defined(__linux__) uuid_generate(info->uuid); - if ((uu = malloc(1024)) == NULL) { + if ((uu_temp = malloc(1024)) == NULL) { tc_log(1, "uuid_unparse memory failed\n"); ret = -1; goto out; } - uuid_unparse(info->uuid, uu); + uuid_unparse(info->uuid, uu_temp); #elif defined(__DragonFly__) uuid_create(&info->uuid, &status); if (status != uuid_s_ok) { @@ -1066,14 +1718,24 @@ goto out; } - uuid_to_string(&info->uuid, &uu, &status); - if (uu == NULL) { + uuid_to_string(&info->uuid, &uu_temp, &status); + if (uu_temp == NULL) { tc_log(1, "uuid_to_string failed\n"); ret = -1; goto out; } #endif + if ((uu = malloc(1024)) == NULL) { + free(uu_temp); + tc_log(1, "uuid second malloc failed\n"); + ret = -1; + goto out; + } + + snprintf(uu, 1024, "CRYPT-TCPLAY-%s", uu_temp); + free(uu_temp); + if ((dm_task_set_uuid(dmt, uu)) == 0) { free(uu); tc_log(1, "dm_task_set_uuid failed\n"); @@ -1083,6 +1745,35 @@ free(uu); + if (TC_FLAG_SET(info->flags, FDE)) { + /* + * When the full disk encryption (FDE) flag is set, + * we map the first N sectors using a linear target + * as they aren't encrypted. + */ + + /* /dev/ad0s0a 0 */ + /* dev---^ block off --^ */ + snprintf(params, 512, "%s 0", dev); + + if ((dm_task_add_target(dmt, 0, info->offset, "linear", params)) == 0) { + tc_log(1, "dm_task_add_target failed\n"); + ret = -1; + goto out; + } + + start = info->offset; + } + + /* aes-cbc-essiv:sha256 7997f8af... 0 /dev/ad0s0a 8 */ + /* iv off---^ block off--^ */ + snprintf(params, 512, "%s %s %"PRIu64 " %s %"PRIu64, + cipher_chain->cipher->dm_crypt_str, cipher_chain->dm_key, + (uint64_t)info->skip, dev, (uint64_t)offset); +#ifdef DEBUG + printf("Params: %s\n", params); +#endif + if ((dm_task_add_target(dmt, start, info->size, "crypt", params)) == 0) { tc_log(1, "dm_task_add_target failed\n"); ret = -1; @@ -1169,9 +1860,10 @@ } /* Try to remove other cascade devices */ - for (i = 2; i >= 0; i--) { + for (i = 0; i < 2; i++) { sprintf(map, "%s.%d", mapname, i); - dm_remove_device(map); + if (dm_exists_device(map)) + dm_remove_device(map); } return 0; @@ -1201,7 +1893,7 @@ } struct tc_cipher_chain * -check_cipher_chain(char *cipher_chain, int quiet) +check_cipher_chain(const char *cipher_chain, int quiet) { struct tc_cipher_chain *cipher = NULL; int i,k, nciphers = 0, mismatch = 0; @@ -1270,7 +1962,7 @@ } struct pbkdf_prf_algo * -check_prf_algo(char *algo, int quiet) +check_prf_algo(const char *algo, int quiet) { int i, found = 0; diff -Nru tcplay-0.11/tcplay.h tcplay-1.1/tcplay.h --- tcplay-0.11/tcplay.h 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/tcplay.h 2013-07-31 06:01:56.000000000 +0000 @@ -27,9 +27,7 @@ * SUCH DAMAGE. */ -/* Version of tcplay */ -#define MAJ_VER 0 -#define MIN_VER 10 +/* Version of tcplay specified during build (CMakeLists.txt, Makefile.classic) */ #define MAX_BLKSZ 4096 @@ -38,6 +36,7 @@ #define HDR_OFFSET_SYS 31744 /* 512 * (63 -1) */ #define TC_SIG "TRUE" #define MAX_PASSSZ 64 +#define PASS_BUFSZ 256 #define KPOOL_SZ 64 #define MAX_KFILE_SZ 1048576 /* 1 MB */ #define MAX_KEYFILES 256 @@ -57,11 +56,21 @@ #define TC_VOLFLAG_SYSTEM 0x01 /* system encryption */ #define TC_VOLFLAG_INPLACE 0x02 /* non-system in-place-encrypted volume */ +#define TC_VOLFLAG_SET(f, x) ((f & TC_VOLFLAG_##x) == TC_VOLFLAG_##x) + #define LOG_BUFFER_SZ 1024 #if 0 #define DEBUG 1 #endif +#define TC_FLAG_SYS 0x0001 +#define TC_FLAG_FDE 0x0002 +#define TC_FLAG_BACKUP 0x0004 +#define TC_FLAG_ONLY_RESTORE 0x0008 + +#define TC_FLAG_SET(f, x) ((f & TC_FLAG_##x) == TC_FLAG_##x) + +#include #include #if defined(__DragonFly__) @@ -103,29 +112,33 @@ uint32_t crc_keys; /* CRC32 of the key section */ uint64_t vol_ctime; /* Volume creation time */ uint64_t hdr_ctime; /* Header creation time */ - uint64_t sz_hidvol; /* Size of hidden volume (set to zero - in non-hidden volumes) */ - uint64_t sz_vol; /* Size of volume */ - uint64_t off_mk_scope; /* Byte offset of the start of the - master key scope */ - uint64_t sz_mk_scope; /* Size of the encrypted area within - the master key scope */ - uint32_t flags; /* Flag bits - (bit 0: system encryption; - bit 1: non-system in-place-encrypted volume; - bits 2–31 are reserved) */ - uint32_t sec_sz; /* Sector size (in bytes) */ + uint64_t sz_hidvol; /* Size of hidden volume (set to zero + in non-hidden volumes) */ + uint64_t sz_vol; /* Size of volume */ + uint64_t off_mk_scope; /* Byte offset of the start of the + master key scope */ + uint64_t sz_mk_scope; /* Size of the encrypted area within + the master key scope */ + uint32_t flags; /* Flag bits + (bit 0: system encryption; + bit 1: non-system in-place-encrypted volume; + bits 2–31 are reserved) */ + uint32_t sec_sz; /* Sector size (in bytes) */ unsigned char unused3[120]; uint32_t crc_dhdr; /* CRC32 of dec. header (except keys) */ unsigned char keys[256]; } __attribute__((__packed__)); struct tcplay_info { - const char *dev; + char dev[PATH_MAX]; struct tchdr_dec *hdr; struct tc_cipher_chain *cipher_chain; struct pbkdf_prf_algo *pbkdf_prf; char key[MAX_KEYSZ*2 + 1]; + + int flags; + int volflags; + off_t start; /* Logical volume offset in table */ size_t size; /* Volume size */ @@ -134,16 +147,32 @@ /* Populated by dm_setup */ uuid_t uuid; + + int hidden; +}; + +struct tcplay_dm_table { + char device[PATH_MAX]; /* Underlying device */ + char target[256]; /* DM Target type */ + off_t start; /* Logical volume offset in table */ + size_t size; /* Volume size */ + + char cipher[256]; /* Cipher */ + off_t skip; /* IV offset */ + off_t offset; /* Block offset */ }; void *read_to_safe_mem(const char *file, off_t offset, size_t *sz); -int get_random(unsigned char *buf, size_t len); +int get_random(unsigned char *buf, size_t len, int weak); 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_to_disk(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); + size_t bufsz, time_t timeout); +float get_random_read_progress(void); +float get_secure_erase_progress(void); + int tc_crypto_init(void); int tc_cipher_chain_populate_keys(struct tc_cipher_chain *cipher_chain, @@ -170,44 +199,62 @@ 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, + off_t offset, size_t blocks, int hidden, int weak, struct tchdr_enc **backup_hdr); struct tchdr_dec *decrypt_hdr(struct tchdr_enc *ehdr, struct tc_cipher_chain *cipher_chain, unsigned char *key); int verify_hdr(struct tchdr_dec *hdr); +struct tchdr_enc *copy_reencrypt_hdr(unsigned char *pass, int passlen, + struct pbkdf_prf_algo *prf_algo, int weak, struct tcplay_info *info, + struct tchdr_enc **backup_hdr); void *_alloc_safe_mem(size_t req_sz, const char *file, int line); void _free_safe_mem(void *mem, const char *file, int line); void check_and_purge_safe_mem(void); struct tc_crypto_algo *check_cipher(const char *cipher, int quiet); -struct tc_cipher_chain *check_cipher_chain(char *cipher_chain, int quiet); -struct pbkdf_prf_algo *check_prf_algo(char *algo, int quiet); +struct tc_cipher_chain *check_cipher_chain(const char *cipher_chain, int quiet); +struct pbkdf_prf_algo *check_prf_algo(const char *algo, int quiet); int tc_play_init(void); void tc_log(int err, const char *fmt, ...); +int tc_cipher_chain_klen(struct tc_cipher_chain *chain); +char *tc_cipher_chain_sprint(char *buf, size_t bufsz, + struct tc_cipher_chain *chain); void print_info(struct tcplay_info *info); int adjust_info(struct tcplay_info *info, struct tcplay_info *hinfo); -int process_hdr(const char *dev, int sflag, unsigned char *pass, int passlen, +int process_hdr(const char *dev, int flags, unsigned char *pass, int passlen, struct tchdr_enc *ehdr, struct tcplay_info **pinfo); int create_volume(const char *dev, int hidden, const char *keyfiles[], int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles, struct pbkdf_prf_algo *prf_algo, struct tc_cipher_chain *cipher_chain, struct pbkdf_prf_algo *h_prf_algo, struct tc_cipher_chain *h_cipher_chain, - char *passphrase, char *h_passphrase, size_t hidden_bytes_in, - int interactive, int secure_erase); -int info_volume(const char *device, int sflag, const char *sys_dev, + const char *passphrase, const char *h_passphrase, size_t hidden_bytes_in, + int interactive, int secure_erase, int weak_keys); +struct tcplay_info *info_map_common(const char *dev, int flags, + const char *sys_dev, int protect_hidden, const char *keyfiles[], + int nkeyfiles, const char *h_keyfiles[], int n_hkeyfiles, + const char *passphrase, const char *passphrase_hidden, int interactive, + int retries, time_t timeout, char *passphrase_out); +int info_mapped_volume(const char *map_name, int interactive); +int info_volume(const char *device, int flags, 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 *passphrase, const char *passphrase_hidden, int interactive, + int retries, time_t timeout); +int map_volume(const char *map_name, const char *device, int flags, 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); + const char *passphrase, const char *passphrase_hidden, int interactive, + int retries, time_t timeout); +int modify_volume(const char *device, int flags, const char *sys_dev, + const char *keyfiles[], int nkeyfiles, const char *new_keyfiles[], + int n_newkeyfiles, struct pbkdf_prf_algo *new_prf_algo, + const char *passphrase, const char *new_passphrase, int interactive, + int retries, time_t timeout, int weak_salt); int dm_setup(const char *mapname, struct tcplay_info *info); int dm_teardown(const char *mapname, const char *device); +struct tcplay_info *dm_info_map(const char *map_name); typedef void(*summary_fn_t)(void); @@ -215,6 +262,12 @@ extern char tc_internal_log_buffer[]; extern summary_fn_t summary_fn; +#define STATE_UNKNOWN 0 +#define STATE_GET_RANDOM 1 +#define STATE_ERASE 2 + +extern int tc_internal_state; + #define alloc_safe_mem(x) \ _alloc_safe_mem(x, __FILE__, __LINE__) diff -Nru tcplay-0.11/tcplay.map tcplay-1.1/tcplay.map --- tcplay-0.11/tcplay.map 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/tcplay.map 2013-07-31 06:01:56.000000000 +0000 @@ -9,6 +9,10 @@ tc_api_check_prf_hash; tc_api_get_error_msg; tc_api_get_summary; + tc_api_get_state; + tc_api_info_volume; + tc_api_info_mapped_volume; + tc_api_modify_volume; local: *; }; diff -Nru tcplay-0.11/tcplay_api.c tcplay-1.1/tcplay_api.c --- tcplay-0.11/tcplay_api.c 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/tcplay_api.c 2013-07-31 06:01:56.000000000 +0000 @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include "tcplay.h" #include "tcplay_api.h" @@ -71,6 +73,29 @@ return NULL; } +tc_api_state +tc_api_get_state(float *progress) +{ + switch (tc_internal_state) { + case STATE_UNKNOWN: + return TC_STATE_UNKNOWN; + + case STATE_ERASE: + if (progress != NULL) + *progress = get_secure_erase_progress(); + return TC_STATE_ERASE; + + case STATE_GET_RANDOM: + if (progress != NULL) + *progress = get_random_read_progress(); + return TC_STATE_GET_RANDOM; + + default: + return TC_STATE_UNKNOWN; + } + +} + int tc_api_create_volume(tc_api_opts *api_opts) { @@ -111,11 +136,11 @@ 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_prf_hash_hidden ? check_prf_algo(api_opts->tc_prf_hash_hidden, 1) : NULL, + api_opts->tc_cipher_hidden ? check_cipher_chain(api_opts->tc_cipher_hidden, 1) : NULL, api_opts->tc_passphrase, api_opts->tc_passphrase_hidden, api_opts->tc_size_hidden_in_bytes, 0 /* non-interactive */, - 0 /* non-secure erase */); + !api_opts->tc_no_secure_erase, api_opts->tc_use_weak_keys); return (err) ? TC_ERR : TC_OK; } @@ -123,10 +148,12 @@ int tc_api_map_volume(tc_api_opts *api_opts) { - int nkeyfiles; + int nkeyfiles, n_hkeyfiles = 0; int err; + int flags = 0; if ((api_opts == NULL) || + (api_opts->tc_map_name == NULL) || (api_opts->tc_device == NULL)) { errno = EFAULT; return TC_ERR; @@ -137,11 +164,26 @@ (api_opts->tc_keyfiles[nkeyfiles] != NULL); nkeyfiles++) ; + if (api_opts->tc_protect_hidden) { + for (n_hkeyfiles = 0; (n_hkeyfiles < MAX_KEYFILES) && + (api_opts->tc_keyfiles_hidden != NULL) && + (api_opts->tc_keyfiles_hidden[n_hkeyfiles] != NULL); + n_hkeyfiles++) + ; + } + + if (api_opts->tc_use_system_encryption) + flags |= TC_FLAG_SYS; + if (api_opts->tc_use_fde) + flags |= TC_FLAG_FDE; + if (api_opts->tc_use_backup) + flags |= TC_FLAG_BACKUP; + 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, + flags, api_opts->tc_system_device, + api_opts->tc_protect_hidden, api_opts->tc_keyfiles, nkeyfiles, + api_opts->tc_keyfiles_hidden, n_hkeyfiles, + api_opts->tc_passphrase, api_opts->tc_passphrase_hidden, api_opts->tc_interactive_prompt, api_opts->tc_password_retries, (time_t)api_opts->tc_prompt_timeout); @@ -149,6 +191,151 @@ } int +tc_api_info_volume(tc_api_opts *api_opts, tc_api_volinfo *vol_info) +{ + struct tcplay_info *info; + int nkeyfiles, n_hkeyfiles = 0; + int flags = 0; + + if ((api_opts == NULL) || + (vol_info == 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++) + ; + + if (api_opts->tc_protect_hidden) { + for (n_hkeyfiles = 0; (n_hkeyfiles < MAX_KEYFILES) && + (api_opts->tc_keyfiles_hidden != NULL) && + (api_opts->tc_keyfiles_hidden[n_hkeyfiles] != NULL); + n_hkeyfiles++) + ; + } + + if (api_opts->tc_use_system_encryption) + flags |= TC_FLAG_SYS; + if (api_opts->tc_use_fde) + flags |= TC_FLAG_FDE; + if (api_opts->tc_use_backup) + flags |= TC_FLAG_BACKUP; + + info = info_map_common(api_opts->tc_device, + flags, api_opts->tc_system_device, + api_opts->tc_protect_hidden, api_opts->tc_keyfiles, nkeyfiles, + api_opts->tc_keyfiles_hidden, n_hkeyfiles, + api_opts->tc_passphrase, api_opts->tc_passphrase_hidden, + api_opts->tc_interactive_prompt, api_opts->tc_password_retries, + (time_t)api_opts->tc_prompt_timeout, NULL); + + if (info == NULL || info->hdr == NULL) + return TC_ERR; + + tc_cipher_chain_sprint(vol_info->tc_cipher, sizeof(vol_info->tc_cipher), + info->cipher_chain); + vol_info->tc_key_bits = 8*tc_cipher_chain_klen(info->cipher_chain); + strncpy(vol_info->tc_prf, info->pbkdf_prf->name, sizeof(vol_info->tc_prf)); + vol_info->tc_size = info->size * (off_t)info->hdr->sec_sz; + vol_info->tc_iv_offset = info->skip * (off_t)info->hdr->sec_sz; + vol_info->tc_block_offset = info->offset * (off_t)info->hdr->sec_sz; + strncpy(vol_info->tc_device, info->dev, sizeof(vol_info->tc_device)); + vol_info->tc_device[sizeof(vol_info->tc_device)-1] = '\0'; + + free_safe_mem(info->hdr); + free_safe_mem(info); + + return TC_OK; +} + +int +tc_api_info_mapped_volume(tc_api_opts *api_opts, tc_api_volinfo *vol_info) +{ + struct tcplay_info *info; + + if ((api_opts == NULL) || + (vol_info == NULL) || + (api_opts->tc_map_name == NULL)) { + errno = EFAULT; + return TC_ERR; + } + + info = dm_info_map(api_opts->tc_map_name); + if (info == NULL) + return TC_ERR; + + tc_cipher_chain_sprint(vol_info->tc_cipher, sizeof(vol_info->tc_cipher), + info->cipher_chain); + vol_info->tc_key_bits = 8*tc_cipher_chain_klen(info->cipher_chain); + strncpy(vol_info->tc_prf, "(unknown)", sizeof(vol_info->tc_prf)); + vol_info->tc_size = info->size * (size_t)512; + vol_info->tc_iv_offset = info->skip * (off_t)512; + vol_info->tc_block_offset = info->offset * (off_t)512; + strncpy(vol_info->tc_device, info->dev, sizeof(vol_info->tc_device)); + vol_info->tc_device[sizeof(vol_info->tc_device)-1] = '\0'; + + free_safe_mem(info); + + return TC_OK; +} + +int +tc_api_modify_volume(tc_api_opts *api_opts) +{ + struct pbkdf_prf_algo *prf_hash = NULL; + int nkeyfiles, n_newkeyfiles = 0; + int flags = 0; + int error; + + if ((api_opts == NULL) || + (api_opts->tc_device == NULL)) { + errno = EFAULT; + return TC_ERR; + } + + if (api_opts->tc_new_prf_hash != NULL) { + if ((prf_hash = check_prf_algo(api_opts->tc_new_prf_hash, 1)) == NULL) { + errno = EINVAL; + return TC_ERR; + } + } + + for (nkeyfiles = 0; (nkeyfiles < MAX_KEYFILES) && + (api_opts->tc_keyfiles != NULL) && + (api_opts->tc_keyfiles[nkeyfiles] != NULL); nkeyfiles++) + ; + + for (n_newkeyfiles = 0; (n_newkeyfiles < MAX_KEYFILES) && + (api_opts->tc_new_keyfiles != NULL) && + (api_opts->tc_new_keyfiles[n_newkeyfiles] != NULL); n_newkeyfiles++) + ; + + if (api_opts->tc_use_system_encryption) + flags |= TC_FLAG_SYS; + if (api_opts->tc_use_fde) + flags |= TC_FLAG_FDE; + if (api_opts->tc_use_backup) + flags |= TC_FLAG_BACKUP; + + error = modify_volume(api_opts->tc_device, + flags, api_opts->tc_system_device, + api_opts->tc_keyfiles, nkeyfiles, + api_opts->tc_new_keyfiles, n_newkeyfiles, + prf_hash, + api_opts->tc_passphrase, api_opts->tc_new_passphrase, + api_opts->tc_interactive_prompt, api_opts->tc_password_retries, + (time_t)api_opts->tc_prompt_timeout, api_opts->tc_use_weak_salt); + + if (error) + return TC_ERR; + + return TC_OK; +} + +int tc_api_unmap_volume(tc_api_opts *api_opts) { int err; diff -Nru tcplay-0.11/tcplay_api.h tcplay-1.1/tcplay_api.h --- tcplay-0.11/tcplay_api.h 2012-11-16 07:28:22.000000000 +0000 +++ tcplay-1.1/tcplay_api.h 2013-07-31 06:01:56.000000000 +0000 @@ -27,38 +27,89 @@ * SUCH DAMAGE. */ +#ifndef _TCPLAY_API_H +#define _TCPLAY_API_H + +#include + #define TC_OK 0 #define TC_ERR -1 +typedef enum tc_api_state { + TC_STATE_UNKNOWN, + TC_STATE_ERASE, + TC_STATE_GET_RANDOM +} tc_api_state; + typedef struct tc_api_opts { /* Common fields */ - char *tc_device; - char *tc_passphrase; + const char *tc_device; + const char *tc_passphrase; const char **tc_keyfiles; + const char *tc_passphrase_hidden; + const char **tc_keyfiles_hidden; - /* Fields for mapping */ - char *tc_map_name; + /* Fields for mapping / info */ + const char *tc_map_name; + int tc_protect_hidden; + + /* Fields for mapping / info / modify */ int tc_password_retries; int tc_interactive_prompt; unsigned long tc_prompt_timeout; + int tc_use_system_encryption; + const char *tc_system_device; + int tc_use_fde; + int tc_use_backup; + + /* Fields for modify */ + const char *tc_new_passphrase; + const char **tc_new_keyfiles; + const char *tc_new_prf_hash; + int tc_use_weak_salt; /* Fields for creation */ - char *tc_cipher; - char *tc_prf_hash; - char *tc_cipher_hidden; - char *tc_prf_hash_hidden; + const char *tc_cipher; + const char *tc_prf_hash; + const char *tc_cipher_hidden; + const char *tc_prf_hash_hidden; size_t tc_size_hidden_in_bytes; - char *tc_passphrase_hidden; - const char **tc_keyfiles_hidden; + int tc_no_secure_erase; + int tc_use_weak_keys; } tc_api_opts; +typedef struct tc_api_volinfo { + char tc_device[1024]; + char tc_cipher[256]; + char tc_prf[64]; + + int tc_key_bits; + + size_t tc_size; + off_t tc_iv_offset; + off_t tc_block_offset; +} tc_api_volinfo; + +#ifdef __cplusplus +extern "C" { +#endif + int tc_api_init(int verbose); int tc_api_uninit(void); +int tc_api_info_volume(tc_api_opts *api_opts, tc_api_volinfo *vol_info); +int tc_api_info_mapped_volume(tc_api_opts *api_opts, tc_api_volinfo *vol_info); int tc_api_create_volume(tc_api_opts *api_opts); +int tc_api_modify_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); +tc_api_state tc_api_get_state(float *progress_pct); + +#ifdef __cplusplus +} +#endif +#endif diff -Nru tcplay-0.11/test/Gemfile tcplay-1.1/test/Gemfile --- tcplay-0.11/test/Gemfile 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/Gemfile 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,6 @@ +source "http://rubygems.org" + +gem "json", "~> 1.7.7" +gem "cucumber", "~> 1.3.1" +gem "rspec" , "~> 2.13.0" +gem "ffi" , "~> 1.8.1" diff -Nru tcplay-0.11/test/Makefile tcplay-1.1/test/Makefile --- tcplay-0.11/test/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/Makefile 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,5 @@ +test: + sudo bundle exec cucumber + +bootstrap: + bundle install diff -Nru tcplay-0.11/test/README tcplay-1.1/test/README --- tcplay-0.11/test/README 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/README 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,162 @@ +Introduction +============ +The tcplay tests use cucumber as test driver. Test cases are known as Scenarios +and are grouped by Feature. + +The .feature files in features/ contain the Scenarios. What each step in a +scenario does is defined in the step_definitions files. + + + +Running the tests +============ +To run the tests, you first need to bootstrap the environment. Running the +following will install all required ruby gems: + + make bootstrap + +You only have to do that once. After that, you can run tests using: + + make test + +The tests must run as root, so sudo will prompt you for your password before +running the actual tests. + +To be able to run the tests, you need to have 'libtcplay.so' and 'tcplay' in +the source directory. To get them, just change to the source directory and run + + make -f Makefile.classic clean all + + +Steps +============ +The tcplay tests have the following step definitions, some of which use the command line +tool and some of which use the library (API): + +* "Given I map volume as using the following settings:" +* "Given I map volume as with the API using the following settings:" + +Where is a file name containing a volume and is the +name of the mapped device. +The settings are specified in a table - the following are all possible settings: +passphrase | Any passphrase +keyfiles | path_to_kf1, path_to_kf2, ... +protect_hidden | yes/no +passphrase_hidden | Any passphrase +keyfiles_hidden | path_to_kf1, path_to_kf2, ... +use_backup | yes/no + + + +* "Given I request information about volume using the following settings:" +* "Given I request information about volume with the API using the following settings:" + +Where is a file name containing a volume. +The settings are specified in a table - the following are all possible settings: +passphrase | Any passphrase +keyfiles | path_to_kf1, path_to_kf2, ... +protect_hidden | yes/no +passphrase_hidden | Any passphrase +keyfiles_hidden | path_to_kf1, path_to_kf2, ... +use_backup | yes/no + + + +* "Given I request information about mapped volume " +* "Given I request information about mapped volume with the API" + +Where is the mapped name of a volume previously mapped. + + + +* "Given I create a volume of size M with the following parameters:" +* "Given I create a volume of size M using the API with the following parameters:" + +Where is a file name that will be created to contain the newly +created volume. Its size will be MB. +The parameters are specified in a table - the following are all possible parameters: +passphrase | Any passphrase +keyfiles | path_to_kf1, path_to_kf2, ... +pbkdf_prf | Any valid tcplay PBKDF PRF +cipher | Any valid tcplay cipher chain +create_hidden | yes/no +hidden_mb | A numeric size, e.g. 5.5 (meaning 5.5 MB) +passphrase_hidden | Any passphrase +keyfiles_hidden | path_to_kf1, path_to_kf2, ... +pbkdf_prf_hidden | Any valid tcplay PBKDF PRF +cipher_hidden | Any valid tcplay cipher chain + + + +* "Given I modify volume using the following settings:" +* "Given I modify volume with the API using the following settings:" + +Where is a file name containing a volume. +The settings are specified in a table - the following are all possible settings: +passphrase | Any passphrase +keyfiles | path_to_kf1, path_to_kf2, ... +new_passphrase | Any passphrase +new_keyfiles | path_to_kf1, path_to_kf2, ... +new_pbkdf_prf | Any valid tcplay PBKDF PRF +use_backup | yes/no + + + +* "Given I modify volume by restoring from the backup header using the following settings:" + +Where is a file name containing a volume. +The settings are specified in a table - the following are all possible settings: +passphrase | Any passphrase +keyfiles | path_to_kf1, path_to_kf2, ... + + + +* "Then I expect dmsetup to have the following tables:" + +The tables that should have been created by a previous "Given I map..." are +specified as a table with the following headers: +name | Name of the mapped volume +begin | First sector of the table +end | Last sector of the table +algo | Algorithm used for dm-crypt +key | The master key, in hexadecimal +offset | The offset on the raw device at which the volume starts +iv_offset | The IV offset + + + +* "Then I expect tcplay to report the following:" + +The information tcplay should report after a previous "Given I request +information" is specified as a table with the following headers (which +are the same as the keys output by the --info command to tcplay): +PBKDF2 PRF | Any valid tcplay PBKDF PRF +PBKDF2 iterations | A numeric value +Cipher | Any valid tcplay cipher chain +Key Length | A string such as "512 bits" +CRC Key Data | A hexadecimal value such as "0xdeadc0de" +Sector size | A numeric value such as 512 +Volume size | A string such as "40448 sectors" +IV offset | A numeric value (in blocks) +Block offset | A numeric value (in blocks) + +The output of the API information request has the following fields +instead: +PBKDF2 PRF | Any valid tcplay PBKDF PRF +Cipher | Any valid tcplay cipher chain +Key Length | A string such as "512 bits" +Volume size | A string such as "20709376 bytes" +IV offset | A string such as "131072 bytes" +Block offset | A string such as "131072 bytes" + +The "Given I request information about mapped volume" steps will +generate the same information, except the PBKDF2 and CRC Key Data +fields will be missing. + + + +* "Then I expect tcplay to succeed" + + + +* "Then I expect tcplay to fail" diff -Nru tcplay-0.11/test/features/create_vol_api.feature tcplay-1.1/test/features/create_vol_api.feature --- tcplay-0.11/test/features/create_vol_api.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/create_vol_api.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,103 @@ +@api +Feature: API volume creation + + Scenario: Simple volume #1 + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 10223616 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + Scenario: Cascade volume #1 + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | AES-256-XTS,SERPENT-256-XTS | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test.0 | 0 | 19968 | serpent-xts-plain64 | 256 | 256 | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 0 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | Cipher | AES-256-XTS,SERPENT-256-XTS | + | Key Length | 1024 bits | + | Volume size | 10223616 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + Scenario: Hidden volume #1 + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | twofish-xts-plain64 | 256 | 256 | + + Scenario: Hidden volume #2 + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | keyfiles | key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + + Scenario: Hidden volume #3 + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | passphrase_hidden | hiddenhidden | + | keyfiles_hidden | key.2, key.1 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | hiddenhidden | + | keyfiles | key.1, key.2 | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | hiddenhidden | + | keyfiles | key.1, key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + And I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | Cipher | SERPENT-256-XTS | + | Key Length | 512 bits | + | Volume size | 2097152 bytes | + | IV offset | 8257536 bytes | + | Block offset | 8257536 bytes | diff -Nru tcplay-0.11/test/features/create_vol_cli.feature tcplay-1.1/test/features/create_vol_cli.feature --- tcplay-0.11/test/features/create_vol_cli.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/create_vol_cli.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,131 @@ +@cmdline +Feature: Command line volume creation + + Scenario: Simple volume #1 + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | PBKDF2 iterations | 1000 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Cascade volume #1 + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | AES-256-XTS,SERPENT-256-XTS | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test.0 | 0 | 19968 | serpent-xts-plain64 | 256 | 256 | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 0 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | PBKDF2 iterations | 1000 | + | Cipher | AES-256-XTS,SERPENT-256-XTS | + | Key Length | 1024 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Hidden volume #1 + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | twofish-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | PBKDF2 iterations | 1000 | + | Cipher | TWOFISH-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Hidden volume #2 + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I map volume tmpvol1 as tcplay_test using the following settings: + | keyfiles | key.2 | + And I request information about volume tmpvol1 using the following settings: + | keyfiles | key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + And I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | PBKDF2 iterations | 2000 | + | Cipher | SERPENT-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 4096 sectors | + | IV offset | 16128 | + | Block offset | 16128 | + + Scenario: Hidden volume #3 + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | passphrase_hidden | hiddenhidden | + | keyfiles_hidden | key.2, key.1 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | hiddenhidden | + | keyfiles | key.1, key.2 | + And I request information about volume tmpvol1 using the following settings: + | passphrase | hiddenhidden | + | keyfiles | key.1, key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + And I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | PBKDF2 iterations | 2000 | + | Cipher | SERPENT-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 4096 sectors | + | IV offset | 16128 | + | Block offset | 16128 | diff -Nru tcplay-0.11/test/features/modify_vol_api.feature tcplay-1.1/test/features/modify_vol_api.feature --- tcplay-0.11/test/features/modify_vol_api.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/modify_vol_api.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,177 @@ +@api +Feature: API volume modification + + Scenario: Simple volume #1 (change passphrase, neg) + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I modify volume tmpvol1 with the API using the following settings: + | passphrase | test | + | new_passphrase | new | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | test | + Then I expect tcplay to fail + + Scenario: Simple volume #1 (change passphrase, pos) + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I modify volume tmpvol1 with the API using the following settings: + | passphrase | test | + | new_passphrase | new | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | new | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | new | + Then I expect tcplay to succeed + And I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 10223616 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + Scenario: Simple volume #1 (change PBKDF PRF) + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I modify volume tmpvol1 with the API using the following settings: + | passphrase | test | + | new_passphrase | test | + | new_pbkdf_prf | whirlpool | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | test | + Then I expect tcplay to succeed + And I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 10223616 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + Scenario: Cascade volume #1 (change passphrase) + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | AES-256-XTS,SERPENT-256-XTS | + And I modify volume tmpvol1 with the API using the following settings: + | passphrase | test | + | new_passphrase | new | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | new | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | new | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test.0 | 0 | 19968 | serpent-xts-plain64 | 256 | 256 | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 0 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | Cipher | AES-256-XTS,SERPENT-256-XTS | + | Key Length | 1024 bits | + | Volume size | 10223616 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + Scenario: Hidden volume #1 (change passphrase) + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I modify volume tmpvol1 with the API using the following settings: + | keyfiles | key.2 | + | new_passphrase | new_hidden | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | twofish-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | Cipher | TWOFISH-256-XTS | + | Key Length | 512 bits | + | Volume size | 10223616 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + Scenario: Hidden volume #2 (change passphrase, keyfiles) + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I modify volume tmpvol1 with the API using the following settings: + | keyfiles | key.2 | + | new_passphrase | new_hidden | + | new_keyfiles | key.1, key.2 | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | new_hidden | + | keyfiles | key.1, key.2 | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | new_hidden | + | keyfiles | key.1, key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + And I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | Cipher | SERPENT-256-XTS | + | Key Length | 512 bits | + | Volume size | 2097152 bytes | + | IV offset | 8257536 bytes | + | Block offset | 8257536 bytes | + + Scenario: Hidden volume #3 (change keyfiles, PBKDF PRF) + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I modify volume tmpvol1 with the API using the following settings: + | keyfiles | key.2 | + | new_keyfiles | key.1, key.2 | + | new_pbkdf_prf | SHA512 | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | keyfiles | key.1, key.2 | + And I request information about volume tmpvol1 with the API using the following settings: + | keyfiles | key.1, key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + And I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | Cipher | SERPENT-256-XTS | + | Key Length | 512 bits | + | Volume size | 2097152 bytes | + | IV offset | 8257536 bytes | + | Block offset | 8257536 bytes | diff -Nru tcplay-0.11/test/features/modify_vol_cli.feature tcplay-1.1/test/features/modify_vol_cli.feature --- tcplay-0.11/test/features/modify_vol_cli.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/modify_vol_cli.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,235 @@ +@cmdline +Feature: Command line volume modification + + Scenario: Simple volume #1 (change passphrase, pos) + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I modify volume tmpvol1 using the following settings: + | passphrase | test | + | new_passphrase | new | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | new | + And I request information about volume tmpvol1 using the following settings: + | passphrase | new | + Then I expect tcplay to succeed + And I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | PBKDF2 iterations | 1000 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Simple volume #1 (change PBKDF PRF) + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I modify volume tmpvol1 using the following settings: + | passphrase | test | + | new_passphrase | test | + | new_pbkdf_prf | whirlpool | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 using the following settings: + | passphrase | test | + Then I expect tcplay to succeed + And I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | PBKDF2 iterations | 1000 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Cascade volume #1 (change passphrase) + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | AES-256-XTS,SERPENT-256-XTS | + And I modify volume tmpvol1 using the following settings: + | passphrase | test | + | new_passphrase | new | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | new | + And I request information about volume tmpvol1 using the following settings: + | passphrase | new | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test.0 | 0 | 19968 | serpent-xts-plain64 | 256 | 256 | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 0 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | PBKDF2 iterations | 1000 | + | Cipher | AES-256-XTS,SERPENT-256-XTS | + | Key Length | 1024 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Hidden volume #1 (change passphrase) + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I modify volume tmpvol1 using the following settings: + | keyfiles | key.2 | + | new_passphrase | new_hidden | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | twofish-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | PBKDF2 iterations | 1000 | + | Cipher | TWOFISH-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Hidden volume #2 (change passphrase, keyfiles) + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I modify volume tmpvol1 using the following settings: + | keyfiles | key.2 | + | new_passphrase | new_hidden | + | new_keyfiles | key.1, key.2 | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | new_hidden | + | keyfiles | key.1, key.2 | + And I request information about volume tmpvol1 using the following settings: + | passphrase | new_hidden | + | keyfiles | key.1, key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + And I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | PBKDF2 iterations | 2000 | + | Cipher | SERPENT-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 4096 sectors | + | IV offset | 16128 | + | Block offset | 16128 | + + Scenario: Hidden volume #3 (change keyfiles, PBKDF PRF) + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I modify volume tmpvol1 using the following settings: + | keyfiles | key.2 | + | new_keyfiles | key.1, key.2 | + | new_pbkdf_prf | SHA512 | + And I map volume tmpvol1 as tcplay_test using the following settings: + | keyfiles | key.1, key.2 | + And I request information about volume tmpvol1 using the following settings: + | keyfiles | key.1, key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + And I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | PBKDF2 iterations | 1000 | + | Cipher | SERPENT-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 4096 sectors | + | IV offset | 16128 | + | Block offset | 16128 | + + Scenario: Hidden volume #4 (restore from backup, outer) + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I corrupt sector 0 of volume tmpvol1 + And I modify volume tmpvol1 by restoring from the backup header using the following settings: + | passphrase | test | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | test | + And I request information about volume tmpvol1 using the following settings: + | passphrase | test | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | twofish-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | PBKDF2 iterations | 1000 | + | Cipher | TWOFISH-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Hidden volume #5 (restore from backup, hidden) + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | test | + | pbkdf_prf | whirlpool | + | cipher | TWOFISH-256-XTS | + | create_hidden | yes | + | hidden_mb | 2 | + | keyfiles_hidden | key.2 | + | pbkdf_prf_hidden | RIPEMD160 | + | cipher_hidden | SERPENT-256-XTS | + And I corrupt sector 128 of volume tmpvol1 + And I modify volume tmpvol1 by restoring from the backup header using the following settings: + | keyfiles | key.2 | + And I map volume tmpvol1 as tcplay_test using the following settings: + | keyfiles | key.2 | + And I request information about volume tmpvol1 using the following settings: + | keyfiles | key.2 | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 4096 | serpent-xts-plain64 | 16128 | 16128 | + And I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | PBKDF2 iterations | 2000 | + | Cipher | SERPENT-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 4096 sectors | + | IV offset | 16128 | + | Block offset | 16128 | diff -Nru tcplay-0.11/test/features/passphrase_64_api.feature tcplay-1.1/test/features/passphrase_64_api.feature --- tcplay-0.11/test/features/passphrase_64_api.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/passphrase_64_api.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,58 @@ +@api +Feature: Passphrase 64-byte limitation using API + + Scenario: Long passphrase TrueCrypt-created volume mapping + Given I map volume test_long.tc as tcplay_test with the API using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 1536 | aes-xts-plain64 | 256 | 256 | + + Scenario: Long passphrase TrueCrypt-created volume info + Given I request information about volume test_long.tc with the API using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest | + Then I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 786432 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + Scenario: Long passphrase TrueCrypt-created volume mapping with trimming + Given I map volume test_long.tc as tcplay_test with the API using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttestexceedslimitation | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 1536 | aes-xts-plain64 | 256 | 256 | + + Scenario: Long passphrase TrueCrypt-created volume info with trimming + Given I request information about volume test_long.tc with the API using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttestexceedslimitation | + Then I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 786432 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + Scenario: Long passphrase volume creation + Given I create a volume tmpvol1 of size 10M using the API with the following parameters: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttestexceeds64chars | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I map volume tmpvol1 as tcplay_test with the API using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest | + And I request information about volume tmpvol1 with the API using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 10223616 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | diff -Nru tcplay-0.11/test/features/passphrase_64_cli.feature tcplay-1.1/test/features/passphrase_64_cli.feature --- tcplay-0.11/test/features/passphrase_64_cli.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/passphrase_64_cli.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,64 @@ +@cmdline +Feature: Passphrase 64-byte limitation using command line + + Scenario: Long passphrase TrueCrypt-created volume mapping + Given I map volume test_long.tc as tcplay_test using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 1536 | aes-xts-plain64 | 256 | 256 | + + Scenario: Long passphrase TrueCrypt-created volume info + Given I request information about volume test_long.tc using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest | + Then I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | PBKDF2 iterations | 2000 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 1536 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Long passphrase TrueCrypt-created volume mapping with trimming + Given I map volume test_long.tc as tcplay_test using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttestexceedslimitation | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 1536 | aes-xts-plain64 | 256 | 256 | + + Scenario: Long passphrase TrueCrypt-created volume info with trimming + Given I request information about volume test_long.tc using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttestexceedslimitation | + Then I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | PBKDF2 iterations | 2000 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 1536 sectors | + | IV offset | 256 | + | Block offset | 256 | + + Scenario: Long passphrase volume creation + Given I create a volume tmpvol1 of size 10M with the following parameters: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttestexceeds64chars | + | pbkdf_prf | SHA512 | + | cipher | AES-256-XTS | + And I map volume tmpvol1 as tcplay_test using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest | + And I request information about volume tmpvol1 using the following settings: + | passphrase | testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttest | + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | + | tcplay_test | 0 | 19968 | aes-xts-plain64 | 256 | 256 | + And I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | PBKDF2 iterations | 1000 | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Sector size | 512 | + | Volume size | 19968 sectors | + | IV offset | 256 | + | Block offset | 256 | diff -Nru tcplay-0.11/test/features/step_definitions/libtcplay_steps.rb tcplay-1.1/test/features/step_definitions/libtcplay_steps.rb --- tcplay-0.11/test/features/step_definitions/libtcplay_steps.rb 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/step_definitions/libtcplay_steps.rb 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,227 @@ +require 'rspec/expectations' +require 'expect' + + +Given /^I map volume ([^\s]+) as ([^\s]+) with the API using the following settings:$/ do |vol,map,settings| + protect_hidden = false + s = settings.rows_hash + + loop_dev = @losetup.get_device("volumes/#{vol}") + + opts = TCplayLib::TCApiOpts.new + + opts[:tc_map_name] = FFI::MemoryPointer.from_string(map) + opts[:tc_device] = FFI::MemoryPointer.from_string(loop_dev) + + unless s['keyfiles'].nil? + keyfiles = ParseHelper.csv_parse(s['keyfiles']) { |kf| "keyfiles/#{kf}" } + opts[:tc_keyfiles] = FFIHelper.str_array_to_p(keyfiles) + end + + unless s['keyfiles_hidden'].nil? + keyfiles = ParseHelper.csv_parse(s['keyfiles_hidden']) { |kf| "keyfiles/#{kf}" } + opts[:tc_keyfiles_hidden] = FFIHelper.str_array_to_p(keyfiles) + end + + if ParseHelper.is_yes(s['protect_hidden']) + opts[:tc_protect_hidden] = 1 + protect_hidden = true + end + + opts[:tc_passphrase] = FFI::MemoryPointer.from_string(s['passphrase'] || '') + opts[:tc_passphrase_hidden] = FFI::MemoryPointer.from_string(s['passphrase_hidden'] || '') + opts[:tc_interactive_prompt] = 0 + opts[:tc_use_system_encryption] = 0 + opts[:tc_use_backup] = (s['use_backup'].nil? ? 0 : ParseHelper.is_yes(s['use_backup'])) + + @error = (TCplayLib.tc_api_map_volume(opts) == TCplayLib::TC_ERR) + if (@error) + @err_str = TCplayLib.tc_api_get_error_msg() + end + + @mappings << map unless @error + @maps = DMSetupHelper.get_crypt_mappings("#{map}") +end + + +Given /^I create a volume ([^\s]+) of size (\d+)M using the API with the following parameters:$/ do |vol,size_mb,params| + create_hidden = false + s = params.rows_hash + + IO.popen("dd if=/dev/zero of=\"volumes/#{vol}\" bs=1M count=#{size_mb.to_i} status=none") { |io| Process.wait(io.pid) } + loop_dev = @losetup.get_device("volumes/#{vol}") + + opts = TCplayLib::TCApiOpts.new + + opts[:tc_device] = FFI::MemoryPointer.from_string(loop_dev) + + unless s['keyfiles'].nil? + keyfiles = ParseHelper.csv_parse(s['keyfiles']) { |kf| "keyfiles/#{kf}" } + opts[:tc_keyfiles] = FFIHelper.str_array_to_p(keyfiles) + end + + unless s['keyfiles_hidden'].nil? + keyfiles = ParseHelper.csv_parse(s['keyfiles_hidden']) { |kf| "keyfiles/#{kf}" } + opts[:tc_keyfiles_hidden] = FFIHelper.str_array_to_p(keyfiles) + end + + if ParseHelper.is_yes(s['create_hidden']) + opts[:tc_size_hidden_in_bytes] = 1024*1024*s['hidden_mb'].to_i + create_hidden = true + end + + opts[:tc_passphrase] = FFI::MemoryPointer.from_string(s['passphrase'] || '') + opts[:tc_passphrase_hidden] = FFI::MemoryPointer.from_string(s['passphrase_hidden'] || '') + opts[:tc_interactive_prompt] = 0 + opts[:tc_use_system_encryption] = 0 + opts[:tc_no_secure_erase] = 1 + opts[:tc_use_weak_keys] = 1 + + opts[:tc_prf_hash] = FFI::MemoryPointer.from_string(s['pbkdf_prf'].strip) unless s['pbkdf_prf'].nil? + opts[:tc_cipher] = FFI::MemoryPointer.from_string(s['cipher'].strip) unless s['cipher'].nil? + opts[:tc_prf_hash_hidden] = FFI::MemoryPointer.from_string(s['pbkdf_prf_hidden'].strip) unless s['pbkdf_prf_hidden'].nil? + opts[:tc_cipher_hidden] = FFI::MemoryPointer.from_string(s['cipher_hidden'].strip) unless s['cipher_hidden'].nil? + + s['passphrase'] ||= '' + s['passphrase_hidden'] ||= '' + + @files_to_delete << "volumes/#{vol}" + + @error = (TCplayLib.tc_api_create_volume(opts) == TCplayLib::TC_ERR) + if (@error) + @err_str = TCplayLib.tc_api_get_error_msg() + end +end + + + +Given /^I request information about volume ([^\s]+) with the API using the following settings:$/ do |vol,settings| + protect_hidden = false + s = settings.rows_hash + + loop_dev = @losetup.get_device("volumes/#{vol}") + + opts = TCplayLib::TCApiOpts.new + + opts[:tc_device] = FFI::MemoryPointer.from_string(loop_dev) + + unless s['keyfiles'].nil? + keyfiles = ParseHelper.csv_parse(s['keyfiles']) { |kf| "keyfiles/#{kf}" } + opts[:tc_keyfiles] = FFIHelper.str_array_to_p(keyfiles) + end + + unless s['keyfiles_hidden'].nil? + keyfiles = ParseHelper.csv_parse(s['keyfiles_hidden']) { |kf| "keyfiles/#{kf}" } + opts[:tc_keyfiles_hidden] = FFIHelper.str_array_to_p(keyfiles) + end + + if ParseHelper.is_yes(s['protect_hidden']) + opts[:tc_protect_hidden] = 1 + protect_hidden = true + end + + opts[:tc_passphrase] = FFI::MemoryPointer.from_string(s['passphrase'] || '') + opts[:tc_passphrase_hidden] = FFI::MemoryPointer.from_string(s['passphrase_hidden'] || '') + opts[:tc_interactive_prompt] = 0 + opts[:tc_use_system_encryption] = 0 + opts[:tc_use_backup] = (s['use_backup'].nil? ? 0 : ParseHelper.is_yes(s['use_backup'])) + + @info = {} + + api_info = TCplayLib::TCApiVolinfo.new + + @error = (TCplayLib.tc_api_info_volume(opts, api_info) == TCplayLib::TC_ERR) + if (@error) + @err_str = TCplayLib.tc_api_get_error_msg() + end + + @info['device'] = api_info[:tc_device].to_ptr.get_string(0) + @info['pbkdf2 prf'] = api_info[:tc_prf].to_ptr.get_string(0).downcase + @info['cipher'] = api_info[:tc_cipher].to_ptr.get_string(0).downcase + @info['key length'] = "#{api_info[:tc_key_bits].to_i} bits" + @info['volume size'] = "#{api_info[:tc_size]} bytes" + @info['iv offset'] = "#{api_info[:tc_iv_offset]} bytes" + @info['block offset'] = "#{api_info[:tc_block_offset]} bytes" +end + + +Given /^I request information about mapped volume ([^\s]+) with the API$/ do |map| + opts = TCplayLib::TCApiOpts.new + + opts[:tc_map_name] = FFI::MemoryPointer.from_string(map) + + @info = {} + + api_info = TCplayLib::TCApiVolinfo.new + + @error = (TCplayLib.tc_api_info_mapped_volume(opts, api_info) == TCplayLib::TC_ERR) + if (@error) + @err_str = TCplayLib.tc_api_get_error_msg() + end + + @info['device'] = api_info[:tc_device].to_ptr.get_string(0) + @info['pbkdf2 prf'] = api_info[:tc_prf].to_ptr.get_string(0).downcase + @info['cipher'] = api_info[:tc_cipher].to_ptr.get_string(0).downcase + @info['key length'] = "#{api_info[:tc_key_bits].to_i} bits" + @info['volume size'] = "#{api_info[:tc_size]} bytes" + @info['iv offset'] = "#{api_info[:tc_iv_offset]} bytes" + @info['block offset'] = "#{api_info[:tc_block_offset]} bytes" +end + + +Given /^I modify volume ([^\s]+) with the API using the following settings:$/ do |vol,settings| + s = settings.rows_hash + + loop_dev = @losetup.get_device("volumes/#{vol}") + + opts = TCplayLib::TCApiOpts.new + + opts[:tc_device] = FFI::MemoryPointer.from_string(loop_dev) + + unless s['keyfiles'].nil? + keyfiles = ParseHelper.csv_parse(s['keyfiles']) { |kf| "keyfiles/#{kf}" } + opts[:tc_keyfiles] = FFIHelper.str_array_to_p(keyfiles) + end + + unless s['new_keyfiles'].nil? + new_keyfiles = ParseHelper.csv_parse(s['new_keyfiles']) { |kf| "keyfiles/#{kf}" } + opts[:tc_new_keyfiles] = FFIHelper.str_array_to_p(new_keyfiles) + end + + opts[:tc_passphrase] = FFI::MemoryPointer.from_string(s['passphrase'] || '') + opts[:tc_new_passphrase] = FFI::MemoryPointer.from_string(s['new_passphrase'] || '') + opts[:tc_new_prf_hash] = FFI::MemoryPointer.from_string(s['new_pbkdf_prf'].strip) unless s['new_pbkdf_prf'].nil? + opts[:tc_interactive_prompt] = 0 + opts[:tc_use_system_encryption] = 0 + opts[:tc_use_weak_salt] = 1 + opts[:tc_use_backup] = (s['use_backup'].nil? ? 0 : ParseHelper.is_yes(s['use_backup'])) + + @error = (TCplayLib.tc_api_modify_volume(opts) == TCplayLib::TC_ERR) + if (@error) + @err_str = TCplayLib.tc_api_get_error_msg() + end +end + + +Before('@api') do + r = TCplayLib.tc_api_init(1) + r.should == TCplayLib::TC_OK +end + + +After('@api') do + opts = TCplayLib::TCApiOpts.new + + @mappings.each do |m| + opts[:tc_map_name] = FFI::MemoryPointer.from_string("#{m}") + r = TCplayLib.tc_api_unmap_volume(opts) + r.should == TCplayLib::TC_OK + end + + @losetup.detach_all + + r = TCplayLib.tc_api_uninit() + r.should == TCplayLib::TC_OK + + @files_to_delete.each { |f| File.unlink(f) } +end diff -Nru tcplay-0.11/test/features/step_definitions/tcplay_steps.rb tcplay-1.1/test/features/step_definitions/tcplay_steps.rb --- tcplay-0.11/test/features/step_definitions/tcplay_steps.rb 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/step_definitions/tcplay_steps.rb 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,286 @@ +require 'rspec/expectations' +require 'expect' + + +Given /^I corrupt sectors ([^\s]+) to ([^\s]+) of volume ([^\s]+)$/ do |first,last,vol| + loop_dev = @losetup.get_device("volumes/#{vol}") + first = first.to_i + last = last.to_i + count = last-first+1 + + IO.popen("dd if=/dev/urandom of=\"#{loop_dev}\" bs=512 count=#{count} seek=#{first} status=none") { |io| Process.wait(io.pid) } +end + + +Given /^I corrupt sector ([^\s]+) of volume ([^\s]+)$/ do |first,vol| + loop_dev = @losetup.get_device("volumes/#{vol}") + first = first.to_i + + IO.popen("dd if=/dev/urandom of=\"#{loop_dev}\" bs=512 count=1 seek=#{first} status=none") { |io| Process.wait(io.pid) } +end + + +Given /^I map volume ([^\s]+) as ([^\s]+) using the following settings:$/ do |vol,map,settings| + protect_hidden = false + s = settings.rows_hash + + loop_dev = @losetup.get_device("volumes/#{vol}") + @args = [ + "-m #{map}", + "-d #{loop_dev}" + ] + + ParseHelper.csv_parse(s['keyfiles']) { |kf| @args << "-k \"keyfiles/#{kf}\"" } unless s['keyfiles'].nil? + ParseHelper.csv_parse(s['keyfiles_hidden']) { |kf| @args << "-f \"keyfiles/#{kf}\"" } unless s['keyfiles_hidden'].nil? + + if ParseHelper.is_yes(s['protect_hidden']) + @args << "-e" + protect_hidden = true + end + + @args << "--use-backup" if ParseHelper.is_yes(s['use_backup']) + + s['passphrase'] ||= '' + s['passphrase_hidden'] ||= '' + + IO.popen("#{@tcplay} #{@args.join(' ')}", mode='r+') do |tcplay_io| + tcplay_io.expect /Passphrase/, 10 do + tcplay_io.write("#{s['passphrase']}\n") + end + if protect_hidden == true + tcplay_io.expect /Passphrase for hidden volume/, 10 do + tcplay_io.write("#{s['passphrase_hidden']}\n") + end + end + end + + @error = ($? != 0) + + @mappings << map + @maps = DMSetupHelper.get_crypt_mappings("#{map}") +end + + +Given /^I create a volume ([^\s]+) of size (\d+)M with the following parameters:$/ do |vol,size_mb,params| + create_hidden = false + s = params.rows_hash + + IO.popen("dd if=/dev/zero of=\"volumes/#{vol}\" bs=1M count=#{size_mb.to_i} status=none") { |io| Process.wait(io.pid) } + loop_dev = @losetup.get_device("volumes/#{vol}") + @args = [ + "-c", + "-d #{loop_dev}", + "-w", # We don't want to wait for /dev/random to have enough entropy + ] + + ParseHelper.csv_parse(s['keyfiles']) { |kf| @args << "-k \"keyfiles/#{kf}\"" } unless s['keyfiles'].nil? + ParseHelper.csv_parse(s['keyfiles_hidden']) { |kf| @args << "-f \"keyfiles/#{kf}\"" } unless s['keyfiles_hidden'].nil? + + if ParseHelper.is_yes(s['create_hidden']) + @args << "-g" + create_hidden = true + end + + @args << "-a #{s['pbkdf_prf'].strip}" unless s['pbkdf_prf'].nil? + @args << "-b #{s['cipher'].strip}" unless s['cipher'].nil? + @args << "-x #{s['pbkdf_prf_hidden'].strip}" unless s['pbkdf_prf_hidden'].nil? + @args << "-y #{s['cipher_hidden'].strip}" unless s['cipher_hidden'].nil? + + s['passphrase'] ||= '' + s['passphrase_hidden'] ||= '' + + @files_to_delete << "volumes/#{vol}" + + IO.popen("#{@tcplay} #{@args.join(' ')}", mode='r+') do |tcplay_io| + tcplay_io.expect /Passphrase/, 10 do + tcplay_io.write("#{s['passphrase']}\n") + end + + tcplay_io.expect /Repeat/, 10 do + tcplay_io.write("#{s['passphrase']}\n") + end + + if create_hidden == true + tcplay_io.expect /Passphrase for hidden volume/, 10 do + tcplay_io.write("#{s['passphrase_hidden']}\n") + end + + tcplay_io.expect /Repeat/, 10 do + tcplay_io.write("#{s['passphrase_hidden']}\n") + end + + tcplay_io.expect /Size of hidden volume/, 10 do + tcplay_io.write("#{s['hidden_mb']}M\n") + end + end + tcplay_io.expect /Are you sure/, 10 do + tcplay_io.write("y\n") + end + end + @error = ($? != 0) +end + + + +Given /^I request information about volume ([^\s]+) using the following settings:$/ do |vol,settings| + protect_hidden = false + s = settings.rows_hash + + loop_dev = @losetup.get_device("volumes/#{vol}") + @args = [ + "-i", + "-d #{loop_dev}" + ] + + ParseHelper.csv_parse(s['keyfiles']) { |kf| @args << "-k \"keyfiles/#{kf}\"" } unless s['keyfiles'].nil? + ParseHelper.csv_parse(s['keyfiles_hidden']) { |kf| @args << "-f \"keyfiles/#{kf}\"" } unless s['keyfiles_hidden'].nil? + + if ParseHelper.is_yes(s['protect_hidden']) + @args << "-e" + protect_hidden = true + end + + @args << "--use-backup" if ParseHelper.is_yes(s['use_backup']) + + s['passphrase'] ||= '' + s['passphrase_hidden'] ||= '' + + @info = {} + + IO.popen("#{@tcplay} #{@args.join(' ')}", mode='r+') do |tcplay_io| + tcplay_io.expect /Passphrase:/, 10 do + tcplay_io.write("#{s['passphrase']}\n") + end + if protect_hidden == true + tcplay_io.expect /Passphrase for hidden volume:/, 10 do + tcplay_io.write("#{s['passphrase_hidden']}\n") + end + end + tcplay_io.each do |line| + line.match(/^(.*):\s+(.*)$/) do |m| + c = m.captures + @info[c[0].downcase.strip] = c[1].downcase + end + end + end + @error = ($? != 0) +end + + +Given /^I request information about mapped volume ([^\s]+)$/ do |map| + @args = [ + "-j #{map}" + ] + + @info = {} + + IO.popen("#{@tcplay} #{@args.join(' ')}", mode='r+') do |tcplay_io| + tcplay_io.each do |line| + line.match(/^(.*):\s+(.*)$/) do |m| + c = m.captures + @info[c[0].downcase.strip] = c[1].downcase + end + end + end + @error = ($? != 0) +end + + +Given /^I modify volume ([^\s]+) using the following settings:$/ do |vol,settings| + s = settings.rows_hash + + loop_dev = @losetup.get_device("volumes/#{vol}") + @args = [ + "--modify", + "-d #{loop_dev}", + "-w", # We don't want to wait for /dev/random to have enough entropy + ] + + ParseHelper.csv_parse(s['keyfiles']) { |kf| @args << "-k \"keyfiles/#{kf}\"" } unless s['keyfiles'].nil? + ParseHelper.csv_parse(s['new_keyfiles']) { |kf| @args << "--new-keyfile=\"keyfiles/#{kf}\"" } unless s['new_keyfiles'].nil? + + @args << "--new-pbkdf-prf=#{s['new_pbkdf_prf'].strip}" unless s['new_pbkdf_prf'].nil? + @args << "--use-backup" if ParseHelper.is_yes(s['use_backup']) + + s['passphrase'] ||= '' + s['new_passphrase'] ||= '' + + IO.popen("#{@tcplay} #{@args.join(' ')}", mode='r+') do |tcplay_io| + tcplay_io.expect /Passphrase:/, 10 do + tcplay_io.write("#{s['passphrase']}\n") + end + + tcplay_io.expect /New passphrase/, 10 do + tcplay_io.write("#{s['new_passphrase']}\n") + end + + tcplay_io.expect /Repeat/, 10 do + tcplay_io.write("#{s['new_passphrase']}\n") + end + end + @error = ($? != 0) +end + + +Given /^I modify volume ([^\s]+) by restoring from the backup header using the following settings:$/ do |vol,settings| + s = settings.rows_hash + + loop_dev = @losetup.get_device("volumes/#{vol}") + @args = [ + "--modify", + "--restore-from-backup-hdr", + "-d #{loop_dev}", + "-w", # We don't want to wait for /dev/random to have enough entropy + ] + + ParseHelper.csv_parse(s['keyfiles']) { |kf| @args << "-k \"keyfiles/#{kf}\"" } unless s['keyfiles'].nil? + + s['passphrase'] ||= '' + + IO.popen("#{@tcplay} #{@args.join(' ')}", mode='r+') do |tcplay_io| + tcplay_io.expect /Passphrase:/, 10 do + tcplay_io.write("#{s['passphrase']}\n") + end + end + @error = ($? != 0) +end + + +Then /^I expect dmsetup to have the following tables:$/ do |tables| + tables.map_headers! { |h| h.to_sym } + tables.diff!(@maps) +end + + +Then /^I expect tcplay to report the following:$/ do |expected_info| + expected_info.rows_hash.each_pair do |k,v| + @info[k.downcase.strip].should == v.downcase + end +end + +Then /^I expect tcplay to succeed$/ do + @error.should == false +end + +Then /^I expect tcplay to fail$/ do + @error.should == true +end + + +Before do + @tcplay = "../tcplay" + @maps = [] + @mappings = [] + @info = {} + @files_to_delete = [] + @losetup = LOSetupHelper.new +end + + +After('@cmdline') do + @mappings.each { |m| IO.popen("#{@tcplay} -u #{m}") { |io| Process.wait(io.pid) } } + @losetup.detach_all + + @files_to_delete.each { |f| File.unlink(f) } +end + diff -Nru tcplay-0.11/test/features/support/env.rb tcplay-1.1/test/features/support/env.rb --- tcplay-0.11/test/features/support/env.rb 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/support/env.rb 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,165 @@ +require 'ffi' + +module TCplayLib + extend FFI::Library + + ffi_lib [ "../libtcplay.so" ] + + TC_OK = 0 + TC_ERR = -1 + + class TCApiOpts < FFI::Struct + # Common fields + layout :tc_device, :pointer, + :tc_passphrase, :pointer, + :tc_keyfiles, :pointer, + :tc_passphrase_hidden, :pointer, + :tc_keyfiles_hidden, :pointer, + + # Fields for mapping / info + :tc_map_name, :pointer, + :tc_protect_hidden, :int, + + # Fields for mapping / info / modify + :tc_password_retries, :int, + :tc_interactive_prompt, :int, + :tc_prompt_timeout, :ulong, + :tc_use_system_encryption, :int, + :tc_system_device, :pointer, + :tc_use_fde, :int, + :tc_use_backup, :int, + + # Fields for modify + :tc_new_passphrase, :pointer, + :tc_new_keyfiles, :pointer, + :tc_new_prf_hash, :pointer, + :tc_use_weak_salt, :int, + + # Fields for creation + :tc_cipher, :pointer, + :tc_prf_hash, :pointer, + :tc_cipher_hidden, :pointer, + :tc_prf_hash_hidden, :pointer, + :tc_size_hidden_in_bytes, :size_t, + :tc_no_secure_erase, :int, + :tc_use_weak_keys, :int + end + + class TCApiVolinfo < FFI::Struct + layout :tc_device, [:char, 1024], + :tc_cipher, [:char, 256], + :tc_prf, [:char, 64], + :tc_key_bits, :int, + :tc_size, :size_t, + :tc_iv_offset, :off_t, + :tc_block_offset, :off_t + end + + attach_function :tc_api_init, [ :int ], :int + attach_function :tc_api_uninit, [ ], :int + attach_function :tc_api_info_volume, [ TCApiOpts.by_ref, TCApiVolinfo.by_ref ], :int + attach_function :tc_api_info_mapped_volume, [ TCApiOpts.by_ref, TCApiVolinfo.by_ref ], :int + attach_function :tc_api_create_volume, [ TCApiOpts.by_ref ], :int + attach_function :tc_api_map_volume, [ TCApiOpts.by_ref ], :int + attach_function :tc_api_unmap_volume, [ TCApiOpts.by_ref ], :int + attach_function :tc_api_modify_volume, [ TCApiOpts.by_ref ], :int + attach_function :tc_api_check_cipher, [ TCApiOpts.by_ref ], :int + attach_function :tc_api_check_prf_hash, [ TCApiOpts.by_ref ], :int + attach_function :tc_api_get_error_msg, [ ], :string +end + + +module FFIHelper + # String Array to null-terminated pointer array + def self.str_array_to_p(a) + pointers = [] + a.each { |v| pointers << FFI::MemoryPointer.from_string(v) } + pointers << nil + + ptr = FFI::MemoryPointer.new(:pointer, pointers.length) + pointers.each_with_index { |p,i| ptr[i].write_pointer(p) } + return ptr + end +end + + +module ParseHelper + def self.csv_parse(field) + a = [] + field.split(%r{\s*,\s*}).each do |str| + v = str.strip + v = yield v if block_given? + a << v + end + return a + end + + def self.is_yes(field) + return false if field.nil? + return true if field.casecmp("yes") or field.casecmp("y") + return false + end +end + + +module DMSetupHelper + def self.get_crypt_mappings(name) + maps = [] + IO.popen("dmsetup table --showkeys") do |dmsetup_io| + dmsetup_io.each do |line| + line.match(/^(#{name}.*):\s+(\d+)\s+(\d+)\s+crypt\s+([^\s]+)\s+([a-fA-F0-9]+)\s+(\d+)\s+[^\s]+\s+(\d+)/) do |m| + c = m.captures + mapping = { + :name => c[0], + :begin => c[1], + :end => c[2], + :algo => c[3], + :key => c[4], + :offset => c[5], + :iv_offset => c[6] + } + maps << mapping + end + end + maps.sort! { |x,y| y[:name] <=> x[:name] } + end + + return maps + end +end + +class LOSetupHelper + def initialize + @loopdevs = {} + end + + def find_free + free_dev = nil + IO.popen("losetup -f") { |losetup_io| free_dev = losetup_io.read.chomp } + return free_dev + end + + def get_device(path) + if @loopdevs[path].nil? + @loopdevs[path] = find_free() + IO.popen("losetup #{@loopdevs[path]} \"#{path}\"") { |io| Process.wait(io.pid) } + end + + return @loopdevs[path] + end + + def detach(path) + unless @loopdevs[path].nil? + IO.popen("losetup -d #{@loopdevs[path]}") { |io| Process.wait(io.pid) } + @loopdevs.delete(path) + end + end + + def detach_all + @loopdevs.each do |p,d| + IO.popen("losetup -d #{d}") { |io| Process.wait(io.pid) } + @loopdevs.delete(p) + end + end +end + diff -Nru tcplay-0.11/test/features/vol_test1_api.feature tcplay-1.1/test/features/vol_test1_api.feature --- tcplay-0.11/test/features/vol_test1_api.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/vol_test1_api.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,92 @@ +@api +Feature: API mapping/info tests using volume test1.tc + + Scenario: Map outer volume + Given I map volume test1.tc as tcplay_test with the API using the following settings: + | passphrase | test | + And I request information about mapped volume tcplay_test with the API + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test | 0 | 40448 | aes-xts-plain64 | 256 | 256 | b736aebe0ba9e67f58f110769955a19d1351b07f99cbda9f59249e0aaed72f9e672365dee6d2bf00a34bdd1b8aafa31b274103b9e925ac568e60562ce0c6ff0e | + And I expect tcplay to report the following: + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 20709376 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + + + Scenario: Map hidden volume + Given I map volume test1.tc as tcplay_test with the API using the following settings: + | passphrase | hidden | + And I request information about mapped volume tcplay_test with the API + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test | 0 | 10240 | aes-xts-plain64 | 30464 | 30464 | 7dd9086e92b756f55465723a9b4594bf61040a6bea8b2291a45821ff80d0676dd86caf6595d57beb7902e952706fbb7bad8b69048c47cced1aba08ebac847dd9 | + And I expect tcplay to report the following: + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 5242880 bytes | + | IV offset | 15597568 bytes | + | Block offset | 15597568 bytes | + + + + Scenario: Map outer volume protecting hidden volume + Given I map volume test1.tc as tcplay_test with the API using the following settings: + | passphrase | test | + | passphrase_hidden | hidden | + | protect_hidden | yes | + And I request information about mapped volume tcplay_test with the API + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test | 0 | 30208 | aes-xts-plain64 | 256 | 256 | b736aebe0ba9e67f58f110769955a19d1351b07f99cbda9f59249e0aaed72f9e672365dee6d2bf00a34bdd1b8aafa31b274103b9e925ac568e60562ce0c6ff0e | + And I expect tcplay to report the following: + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 15466496 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + + + Scenario: Info on outer volume + Given I request information about volume test1.tc with the API using the following settings: + | passphrase | test | + Then I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 20709376 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + + + Scenario: Info on hidden volume + Given I request information about volume test1.tc with the API using the following settings: + | passphrase | hidden | + Then I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 5242880 bytes | + | IV offset | 15597568 bytes | + | Block offset | 15597568 bytes | + + + + Scenario: Info on outer volume protecting hidden volume + Given I request information about volume test1.tc with the API using the following settings: + | passphrase | test | + | passphrase_hidden | hidden | + | protect_hidden | yes | + Then I expect tcplay to report the following: + | PBKDF2 PRF | whirlpool | + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 15466496 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + diff -Nru tcplay-0.11/test/features/vol_test1_cli.feature tcplay-1.1/test/features/vol_test1_cli.feature --- tcplay-0.11/test/features/vol_test1_cli.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/vol_test1_cli.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,50 @@ +@cmdline +Feature: Command line mapping/info tests using volume test1.tc + + Scenario: Map outer volume + Given I map volume test1.tc as tcplay_test using the following settings: + | passphrase | test | + And I request information about mapped volume tcplay_test + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test | 0 | 40448 | aes-xts-plain64 | 256 | 256 | b736aebe0ba9e67f58f110769955a19d1351b07f99cbda9f59249e0aaed72f9e672365dee6d2bf00a34bdd1b8aafa31b274103b9e925ac568e60562ce0c6ff0e | + And I expect tcplay to report the following: + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 40448 sectors | + | IV offset | 256 | + | Block offset | 256 | + + + + Scenario: Map hidden volume + Given I map volume test1.tc as tcplay_test using the following settings: + | passphrase | hidden | + And I request information about mapped volume tcplay_test + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test | 0 | 10240 | aes-xts-plain64 | 30464 | 30464 | 7dd9086e92b756f55465723a9b4594bf61040a6bea8b2291a45821ff80d0676dd86caf6595d57beb7902e952706fbb7bad8b69048c47cced1aba08ebac847dd9 | + And I expect tcplay to report the following: + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 10240 sectors | + | IV offset | 30464 | + | Block offset | 30464 | + + + + Scenario: Map outer volume protecting hidden volume + Given I map volume test1.tc as tcplay_test using the following settings: + | passphrase | test | + | passphrase_hidden | hidden | + | protect_hidden | yes | + And I request information about mapped volume tcplay_test + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test | 0 | 30208 | aes-xts-plain64 | 256 | 256 | b736aebe0ba9e67f58f110769955a19d1351b07f99cbda9f59249e0aaed72f9e672365dee6d2bf00a34bdd1b8aafa31b274103b9e925ac568e60562ce0c6ff0e | + And I expect tcplay to report the following: + | Cipher | AES-256-XTS | + | Key Length | 512 bits | + | Volume size | 30208 sectors | + | IV offset | 256 | + | Block offset | 256 | diff -Nru tcplay-0.11/test/features/vol_test2_api.feature tcplay-1.1/test/features/vol_test2_api.feature --- tcplay-0.11/test/features/vol_test2_api.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/vol_test2_api.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,100 @@ +@api +Feature: API mapping/info tests using volume test2.tc + + Scenario: Map outer volume + Given I map volume test2.tc as tcplay_test with the API using the following settings: + | passphrase | test | + And I request information about mapped volume tcplay_test with the API + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test.1 | 0 | 9728 | aes-xts-plain64 | 256 | 256 | c0c4f02fd1f099388e80e387186b341ee90dec22083d1bcfdb402db247f5e829d84da25ed03101645953c4d265fc4e9be56a8c313527a6be49c82b67304e5844 | + | tcplay_test.0 | 0 | 9728 | twofish-xts-plain64 | 256 | 0 | 48aefeb402d01bffcd1aaf6db82f92c5951faefd6abd4ed26f10b0c0cd5bfc189c3a34ece236a2c614d39b8e97d2bc42495382dca757b03c9bfb26bb9d4f3dbb | + | tcplay_test | 0 | 9728 | serpent-xts-plain64 | 256 | 0 | ab2ac0b2007a0ead7a26d5560fb4d385ab4b59d38a7794f211ae7a6f91e97cd388d5bda1bf57be14be1b39bf514873470006c687c03041b5fbeb47aa5cc3a065 | + And I expect tcplay to report the following: + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS,AES-256-XTS | + | Key Length | 1536 bits | + | Volume size | 4980736 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + + + Scenario: Map hidden volume + Given I map volume test2.tc as tcplay_test with the API using the following settings: + | passphrase | hidden | + | keyfiles | key.1, key.2 | + And I request information about mapped volume tcplay_test with the API + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test.0 | 0 | 2040 | twofish-xts-plain64 | 7936 | 7936 | 9063947314787d4645bfb609461e8636f7f103dc82631897ed35e2aacf5d651266fb409395d337ecf91578d446cedb5f3979058c79399416850c512060bfaf20 | + | tcplay_test | 0 | 2040 | serpent-xts-plain64 | 7936 | 0 | fbdfcc05d46e450accb6c7b17c58de8807a39ef1b61bb5347f21b9fc65e9bd3c13e7c0818df5d76a56055abafb8fa2dd3878140996172e418970857a20509509 | + And I expect tcplay to report the following: + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS | + | Key Length | 1024 bits | + | Volume size | 1044480 bytes | + | IV offset | 4063232 bytes | + | Block offset | 4063232 bytes | + + + + Scenario: Map outer volume protecting hidden volume + Given I map volume test2.tc as tcplay_test with the API using the following settings: + | passphrase | test | + | passphrase_hidden | hidden | + | keyfiles_hidden | key.1, key.2 | + | protect_hidden | yes | + And I request information about mapped volume tcplay_test with the API + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test.1 | 0 | 7688 | aes-xts-plain64 | 256 | 256 | c0c4f02fd1f099388e80e387186b341ee90dec22083d1bcfdb402db247f5e829d84da25ed03101645953c4d265fc4e9be56a8c313527a6be49c82b67304e5844 | + | tcplay_test.0 | 0 | 7688 | twofish-xts-plain64 | 256 | 0 | 48aefeb402d01bffcd1aaf6db82f92c5951faefd6abd4ed26f10b0c0cd5bfc189c3a34ece236a2c614d39b8e97d2bc42495382dca757b03c9bfb26bb9d4f3dbb | + | tcplay_test | 0 | 7688 | serpent-xts-plain64 | 256 | 0 | ab2ac0b2007a0ead7a26d5560fb4d385ab4b59d38a7794f211ae7a6f91e97cd388d5bda1bf57be14be1b39bf514873470006c687c03041b5fbeb47aa5cc3a065 | + And I expect tcplay to report the following: + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS,AES-256-XTS | + | Key Length | 1536 bits | + | Volume size | 3936256 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + + + Scenario: Info on outer volume + Given I request information about volume test2.tc with the API using the following settings: + | passphrase | test | + Then I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS,AES-256-XTS | + | Key Length | 1536 bits | + | Volume size | 4980736 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | + + + + Scenario: Map hidden volume + Given I request information about volume test2.tc with the API using the following settings: + | passphrase | hidden | + | keyfiles | key.1, key.2 | + Then I expect tcplay to report the following: + | PBKDF2 PRF | SHA512 | + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS | + | Key Length | 1024 bits | + | Volume size | 1044480 bytes | + | IV offset | 4063232 bytes | + | Block offset | 4063232 bytes | + + + + Scenario: Map outer volume protecting hidden volume + Given I request information about volume test2.tc with the API using the following settings: + | passphrase | test | + | passphrase_hidden | hidden | + | keyfiles_hidden | key.1, key.2 | + | protect_hidden | yes | + Then I expect tcplay to report the following: + | PBKDF2 PRF | RIPEMD160 | + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS,AES-256-XTS | + | Key Length | 1536 bits | + | Volume size | 3936256 bytes | + | IV offset | 131072 bytes | + | Block offset | 131072 bytes | diff -Nru tcplay-0.11/test/features/vol_test2_cli.feature tcplay-1.1/test/features/vol_test2_cli.feature --- tcplay-0.11/test/features/vol_test2_cli.feature 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/features/vol_test2_cli.feature 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,57 @@ +@cmdline +Feature: Command line mapping/info tests using volume test2.tc + + Scenario: Map outer volume in test2.tc + Given I map volume test2.tc as tcplay_test using the following settings: + | passphrase | test | + And I request information about mapped volume tcplay_test + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test.1 | 0 | 9728 | aes-xts-plain64 | 256 | 256 | c0c4f02fd1f099388e80e387186b341ee90dec22083d1bcfdb402db247f5e829d84da25ed03101645953c4d265fc4e9be56a8c313527a6be49c82b67304e5844 | + | tcplay_test.0 | 0 | 9728 | twofish-xts-plain64 | 256 | 0 | 48aefeb402d01bffcd1aaf6db82f92c5951faefd6abd4ed26f10b0c0cd5bfc189c3a34ece236a2c614d39b8e97d2bc42495382dca757b03c9bfb26bb9d4f3dbb | + | tcplay_test | 0 | 9728 | serpent-xts-plain64 | 256 | 0 | ab2ac0b2007a0ead7a26d5560fb4d385ab4b59d38a7794f211ae7a6f91e97cd388d5bda1bf57be14be1b39bf514873470006c687c03041b5fbeb47aa5cc3a065 | + And I expect tcplay to report the following: + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS,AES-256-XTS | + | Key Length | 1536 bits | + | Volume size | 9728 sectors | + | IV offset | 256 | + | Block offset | 256 | + + + + Scenario: Map hidden volume in test2.tc + Given I map volume test2.tc as tcplay_test using the following settings: + | passphrase | hidden | + | keyfiles | key.1, key.2 | + And I request information about mapped volume tcplay_test + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test.0 | 0 | 2040 | twofish-xts-plain64 | 7936 | 7936 | 9063947314787d4645bfb609461e8636f7f103dc82631897ed35e2aacf5d651266fb409395d337ecf91578d446cedb5f3979058c79399416850c512060bfaf20 | + | tcplay_test | 0 | 2040 | serpent-xts-plain64 | 7936 | 0 | fbdfcc05d46e450accb6c7b17c58de8807a39ef1b61bb5347f21b9fc65e9bd3c13e7c0818df5d76a56055abafb8fa2dd3878140996172e418970857a20509509 | + And I expect tcplay to report the following: + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS | + | Key Length | 1024 bits | + | Volume size | 2040 sectors | + | IV offset | 7936 | + | Block offset | 7936 | + + + + Scenario: Map outer volume protecting hidden volume in test2.tc + Given I map volume test2.tc as tcplay_test using the following settings: + | passphrase | test | + | passphrase_hidden | hidden | + | keyfiles_hidden | key.1, key.2 | + | protect_hidden | yes | + And I request information about mapped volume tcplay_test + Then I expect dmsetup to have the following tables: + | name | begin | end | algo | offset | iv_offset | key | + | tcplay_test.1 | 0 | 7688 | aes-xts-plain64 | 256 | 256 | c0c4f02fd1f099388e80e387186b341ee90dec22083d1bcfdb402db247f5e829d84da25ed03101645953c4d265fc4e9be56a8c313527a6be49c82b67304e5844 | + | tcplay_test.0 | 0 | 7688 | twofish-xts-plain64 | 256 | 0 | 48aefeb402d01bffcd1aaf6db82f92c5951faefd6abd4ed26f10b0c0cd5bfc189c3a34ece236a2c614d39b8e97d2bc42495382dca757b03c9bfb26bb9d4f3dbb | + | tcplay_test | 0 | 7688 | serpent-xts-plain64 | 256 | 0 | ab2ac0b2007a0ead7a26d5560fb4d385ab4b59d38a7794f211ae7a6f91e97cd388d5bda1bf57be14be1b39bf514873470006c687c03041b5fbeb47aa5cc3a065 | + And I expect tcplay to report the following: + | Cipher | SERPENT-256-XTS,TWOFISH-256-XTS,AES-256-XTS | + | Key Length | 1536 bits | + | Volume size | 7688 sectors | + | IV offset | 256 | + | Block offset | 256 | Binary files /tmp/tGQliFY7cw/tcplay-0.11/test/keyfiles/key.1 and /tmp/qtTDj5nBo4/tcplay-1.1/test/keyfiles/key.1 differ Binary files /tmp/tGQliFY7cw/tcplay-0.11/test/keyfiles/key.2 and /tmp/qtTDj5nBo4/tcplay-1.1/test/keyfiles/key.2 differ diff -Nru tcplay-0.11/test/reduce_test_vol.sh tcplay-1.1/test/reduce_test_vol.sh --- tcplay-0.11/test/reduce_test_vol.sh 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/reduce_test_vol.sh 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,47 @@ +#!/bin/sh + +############################################################# +# This script takes a volume file and reduces its effective +# compressed size by creating a new zero-filled volume +# and only copying over the header, hidden header and the +# respective backup headers into the new volume. +# +# Since the new file consists mostly of zeros, it compresses +# rather well. +############################################################# + +if [ $# -lt 2 ]; then + echo "Usage: $0 " + exit 1 +fi + +SRC_VOL=$1 +DST_VOL=$2 + +# Find the total size of the source volume, in bytes. +SZ=`stat -c "%s" $SRC_VOL` + +# Find the total size of the source volume, in blocks. +SZ_BLOCKS=`echo $SZ / 512 | bc` + +# Find the block at which the backup header area starts. +BCK_HDR_START=`echo $SZ_BLOCKS - 256 | bc` + +# Define the sizes of the header and backup header areas, +# in blocks. +HDR_AREA_BLOCKS=256 +BCK_HDR_AREA_BLOCKS=256 + + +# Create new zero-filled volume with the same size as the +# source volume. +dd if=/dev/zero of=$DST_VOL bs=512 count=$SZ_BLOCKS + +# Copy over the header area. +dd if=$SRC_VOL of=$DST_VOL bs=512 count=$HDR_AREA_BLOCKS \ + conv=notrunc + +# Copy over the backup header area. +dd if=$SRC_VOL of=$DST_VOL bs=512 count=$BCK_HDR_AREA_BLOCKS \ + conv=notrunc seek=$BCK_HDR_START skip=$BCK_HDR_START + Binary files /tmp/tGQliFY7cw/tcplay-0.11/test/volumes/test1.tc and /tmp/qtTDj5nBo4/tcplay-1.1/test/volumes/test1.tc differ diff -Nru tcplay-0.11/test/volumes/test2.desc tcplay-1.1/test/volumes/test2.desc --- tcplay-0.11/test/volumes/test2.desc 1970-01-01 00:00:00.000000000 +0000 +++ tcplay-1.1/test/volumes/test2.desc 2013-07-31 06:01:56.000000000 +0000 @@ -0,0 +1,123 @@ +1 % Main/truecrypt -t -c +Volume type: + 1) Normal + 2) Hidden +Select [1]: 1 + +Enter volume path: test2.tc + +Enter volume size (sizeK/size[M]/sizeG): 5M + +Encryption algorithm: + 1) AES + 2) Serpent + 3) Twofish + 4) AES-Twofish + 5) AES-Twofish-Serpent + 6) Serpent-AES + 7) Serpent-Twofish-AES + 8) Twofish-Serpent +Select [1]: 5 + +Hash algorithm: + 1) RIPEMD-160 + 2) SHA-512 + 3) Whirlpool +Select [1]: 1 + +Filesystem: + 1) None + 2) FAT + 3) Linux Ext2 + 4) Linux Ext3 + 5) Linux Ext4 +Select [2]: 1 + +Enter password: +WARNING: Short passwords are easy to crack using brute force techniques! + +We recommend choosing a password consisting of more than 20 characters. Are you sure you want to use a short password? (y=Yes/n=No) [No]: y + +Re-enter password: + +Enter keyfile path [none]: + +Please type at least 320 randomly chosen characters and then press Enter: +Characters remaining: 172 +Characters remaining: 33 + + +Done: 100.000% Speed: 11 MB/s Left: 0 s + +The TrueCrypt volume has been successfully created. +21:21:29 alex-laptop:~/src/truecrypt-7.1a-source +% Main/truecrypt -t -c +Volume type: + 1) Normal + 2) Hidden +Select [1]: 2 + +IMPORTANT: Inexperienced users should use the graphical user interface to create a hidden volume. When using the text interface, the procedure described in the command line help must be followed to create a hidden volume. + +Enter volume path: test2.tc + +Enter hidden volume size (sizeK/size[M]/sizeG): 1M + +Encryption algorithm: + 1) AES + 2) Serpent + 3) Twofish + 4) AES-Twofish + 5) AES-Twofish-Serpent + 6) Serpent-AES + 7) Serpent-Twofish-AES + 8) Twofish-Serpent +Select [1]: 8 + +Hash algorithm: + 1) RIPEMD-160 + 2) SHA-512 + 3) Whirlpool +Select [1]: 2 + +Filesystem: + 1) None + 2) FAT + 3) Linux Ext2 + 4) Linux Ext3 + 5) Linux Ext4 +Select [2]: 1 + +Enter password: +WARNING: Short passwords are easy to crack using brute force techniques! + +We recommend choosing a password consisting of more than 20 characters. Are you sure you want to use a short password? (y=Yes/n=No) [No]: yes + +Re-enter password: + +Enter keyfile path [none]: /home/alex/src/tc-play/test/key.1 +Enter keyfile path [finish]: /home/alex/src/tc-play/test/key.2 +Enter keyfile path [finish]: + + + + + + + + + + +Outer: +---------------- + +truecrypt1_1: 0 9728 crypt aes-xts-plain64 c0c4f02fd1f099388e80e387186b341ee90dec22083d1bcfdb402db247f5e829d84da25ed03101645953c4d265fc4e9be56a8c313527a6be49c82b67304e5844 256 7:5 256 +truecrypt1_0: 0 9728 crypt twofish-xts-plain64 48aefeb402d01bffcd1aaf6db82f92c5951faefd6abd4ed26f10b0c0cd5bfc189c3a34ece236a2c614d39b8e97d2bc42495382dca757b03c9bfb26bb9d4f3dbb 256 252:2 0 +truecrypt1: 0 9728 crypt serpent-xts-plain64 ab2ac0b2007a0ead7a26d5560fb4d385ab4b59d38a7794f211ae7a6f91e97cd388d5bda1bf57be14be1b39bf514873470006c687c03041b5fbeb47aa5cc3a065 256 252:3 0 + + + +Hidden: +---------------- +truecrypt1_0: 0 2040 crypt twofish-xts-plain64 9063947314787d4645bfb609461e8636f7f103dc82631897ed35e2aacf5d651266fb409395d337ecf91578d446cedb5f3979058c79399416850c512060bfaf20 7936 7:5 7936 +truecrypt1: 0 2040 crypt serpent-xts-plain64 fbdfcc05d46e450accb6c7b17c58de8807a39ef1b61bb5347f21b9fc65e9bd3c13e7c0818df5d76a56055abafb8fa2dd3878140996172e418970857a20509509 7936 252:2 0 Binary files /tmp/tGQliFY7cw/tcplay-0.11/test/volumes/test2.tc and /tmp/qtTDj5nBo4/tcplay-1.1/test/volumes/test2.tc differ Binary files /tmp/tGQliFY7cw/tcplay-0.11/test/volumes/test_long.tc and /tmp/qtTDj5nBo4/tcplay-1.1/test/volumes/test_long.tc differ