Reformat files, replace autoconf with CMake, and RLog with GLog.

git-svn-id: http://encfs.googlecode.com/svn/trunk@92 db9cf616-1c43-0410-9cb8-a902689de0d6
This commit is contained in:
Valient Gough 2013-01-29 03:07:54 +00:00
parent efe358c2d5
commit fb9a8ff879
145 changed files with 10139 additions and 11730 deletions

1068
ABOUT-NLS

File diff suppressed because it is too large Load Diff

14
AUTHORS
View File

@ -2,7 +2,15 @@
Valient Gough <vgough@pobox.com>
Also, thanks to the work of many contributors, encfs as of 1.1.11 now has
full or partial translations for many languages.
See README-NLS and TRANSLATORS for more details.
With significant contributions from:
Csaba Henk
David Rosenstrauch
Gerald Klix
Janne Hellsten
p.kosseff
Also, thanks to the work of many contributors, encfs has full or partial
translations for many languages. See README-NLS for more details.

67
CMakeLists.txt Normal file
View File

@ -0,0 +1,67 @@
cmake_minimum_required(VERSION 2.8)
project(Encfs)
set (ENCFS_MAJOR 2)
set (ENCFS_MINOR 0)
set (ENCFS_PATCH 0)
set (ENCFS_VERSION "${ENCFS_MAJOR}.${ENCFS_MINOR}.${ENCFS_PATCH}")
option (BUILD_SHARED_LIBS "Build dynamic link libraries" OFF)
set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_SOURCE_DIR}/CMakeModules/")
set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall")
set (CPACK_PACKAGE_NAME "Encfs")
set (CPACK_PACKAGE_VERSION_MAJOR ${ENCFS_MAJOR})
set (CPACK_PACKAGE_VERSION_MINOR ${ENCFS_MINOR})
set (CPACK_PACKAGE_VERSION_PATCH ${ENCFS_PATCH})
set (CPACK_SOURCE_GENERATOR TGZ)
set (CPACK_SOURCE_IGNORE_FILES
"/_darcs/"
"/build/")
include (CPack)
include (CheckIncludeFileCXX)
check_include_file_cxx (attr/xattr.h HAVE_ATTR_XATTR_H)
check_include_file_cxx (sys/xattr.h HAVE_SYS_XATTR_H)
check_include_file_cxx (tr1/memory HAVE_TR1_MEMORY)
check_include_file_cxx (tr1/unordered_map HAVE_TR1_UNORDERED_MAP)
check_include_file_cxx (tr1/unordered_set HAVE_TR1_UNORDERED_SET)
check_include_file_cxx (tr1/tuple HAVE_TR1_TUPLE)
check_include_file_cxx (valgrind/valgrind.h HAVE_VALGRIND_VALGRIND_H)
check_include_file_cxx (valgrind/memcheck.h HAVE_VALGRIND_MEMCHECK_H)
# TODO: move this to cipher directory.
find_package (OpenSSL REQUIRED)
include (OpenSSLTests)
# Check if xattr functions take extra argument.
include (CheckCXXSourceCompiles)
CHECK_CXX_SOURCE_COMPILES ("#include <sys/types.h>
#include <sys/xattr.h>
int main() { getxattr(0,0,0,0,0,0); return 1; } " XATTR_ADD_OPT)
add_definitions (-D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=26)
if (APPLE)
add_definitions (-D__FreeBSD__=10)
endif (APPLE)
find_package (GLog REQUIRED)
include_directories (${GLOG_INCLUDE_DIRS})
find_program (POD2MAN pod2man)
include_directories (${Encfs_BINARY_DIR})
include_directories (${Encfs_SOURCE_DIR})
add_subdirectory(base)
add_subdirectory(cipher)
add_subdirectory(fs)
add_subdirectory(encfs)
add_subdirectory(util)
add_subdirectory(po)

View File

@ -0,0 +1,35 @@
# Find the FUSE includes and library
#
# FUSE_INCLUDE_DIR - where to find fuse.h, etc.
# FUSE_LIBRARIES - List of libraries when using FUSE.
# FUSE_FOUND - True if FUSE lib is found.
# check if already in cache, be silent
IF (FUSE_INCLUDE_DIR)
SET (FUSE_FIND_QUIETLY TRUE)
ENDIF (FUSE_INCLUDE_DIR)
# find includes
FIND_PATH (FUSE_INCLUDE_DIR fuse.h
/usr/local/include/osxfuse
/usr/local/include
/usr/include
)
# find lib
if (APPLE)
SET(FUSE_NAMES libosxfuse.dylib fuse)
else (APPLE)
SET(FUSE_NAMES fuse)
endif (APPLE)
FIND_LIBRARY(FUSE_LIBRARIES
NAMES ${FUSE_NAMES}
PATHS /lib64 /lib /usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib
)
include ("FindPackageHandleStandardArgs")
find_package_handle_standard_args ("FUSE" DEFAULT_MSG
FUSE_INCLUDE_DIR FUSE_LIBRARIES)
mark_as_advanced (FUSE_INCLUDE_DIR FUSE_LIBRARIES)

View File

@ -0,0 +1,59 @@
# Try to find the libglog libraries
# Once done this will define :
#
# Glog_FOUND - system has libglog
# Glog_INCLUDE_DIRS - the libglog include directory
# Glog_LIBRARIES - libglog library
# Inputs to this module:
# GLOG_ROOT The preferred installation prefix for searching for glog. Set
# this if the module has problems finding the proper glog installation.
# If GLOG_ROOT was defined in the environment, use it.
IF (NOT GLOG_ROOT AND NOT $ENV{GLOG_ROOT} STREQUAL "")
SET(GLOG_ROOT $ENV{GLOG_ROOT})
ENDIF(NOT GLOG_ROOT AND NOT $ENV{GLOG_ROOT} STREQUAL "")
IF( GLOG_ROOT )
file(TO_CMAKE_PATH ${GLOG_ROOT} GLOG_ROOT)
ENDIF( GLOG_ROOT )
SET (GLOG_INCLUDE_DIRS)
SET (GLOG_LIBRARIES)
IF(WIN32)
IF(MSVC)
FIND_PATH(GLOG_INCLUDE_DIRS NAMES src/windows/glog/logging.h HINTS ${GLOG_ROOT})
IF(GLOG_INCLUDE_DIRS)
SET(GLOG_INCLUDE_DIRS ${GLOG_INCLUDE_DIRS}/src/windows)
ENDIF(GLOG_INCLUDE_DIRS)
IF (CMAKE_BUILD_TYPE STREQUAL "Release")
message (STATUS " searching ${GLOG_ROOT}/Release/libglog.lib ...")
FIND_LIBRARY(GLOG_LIBRARIES NAMES libglog.lib HINTS ${GLOG_ROOT}/Release $ENV{LIB} PATH_SUFFIXES ".lib")
ELSE (CMAKE_BUILD_TYPE STREQUAL "Release")
message (STATUS " searching ${GLOG_ROOT}/Debug/libglog.lib ...")
FIND_LIBRARY(GLOG_LIBRARIES NAMES libglog.lib HINTS ${GLOG_ROOT}/Debug $ENV{LIB} PATH_SUFFIXES ".lib")
ENDIF (CMAKE_BUILD_TYPE STREQUAL "Release")
ELSE(MSVC)
SET(Glog_FOUND FALSE)
message (STATUS " Crap. this module supports only MSVC in Windows.")
ENDIF(MSVC)
ELSE(WIN32)
FIND_PATH(GLOG_INCLUDE_DIRS NAMES glog/logging.h HINTS ${GLOG_ROOT}/include ${GLOG_ROOT} /include/ /usr/include/ /usr/local/include/ /opt/local/include/)
FIND_LIBRARY(GLOG_LIBRARIES NAMES glog HINTS ${GLOG_ROOT}/lib ${GLOG_ROOT} /lib /usr/lib /usr/local/lib /opt/local/lib)
ENDIF(WIN32)
IF(GLOG_INCLUDE_DIRS AND GLOG_LIBRARIES)
SET(Glog_FOUND TRUE)
message (STATUS " glog found in include=${GLOG_INCLUDE_DIRS},lib=${GLOG_LIBRARIES}")
ELSE(GLOG_INCLUDE_DIRS AND GLOG_LIBRARIES)
SET(Glog_FOUND FALSE)
message (STATUS " glog not found. Please set GLOG_ROOT to the root directory containing glog.")
IF(GLOG_INCLUDE_DIRS)
message (STATUS " include=${GLOG_INCLUDE_DIRS}, but lib not found")
ENDIF(GLOG_INCLUDE_DIRS)
IF(GLOG_LIBRARIES)
message (STATUS " lib=${GLOG_LIBRARIES}, but include not found")
ENDIF(GLOG_LIBRARIES)
ENDIF(GLOG_INCLUDE_DIRS AND GLOG_LIBRARIES)
MARK_AS_ADVANCED(GLOG_INCLUDE_DIRS GLOG_LIBRARIES)

View File

@ -0,0 +1,26 @@
# - Find TinyXML
# Find the native TinyXML includes and library
#
# TINYXML_FOUND - True if TinyXML found.
# TINYXML_INCLUDE_DIR - where to find tinyxml.h, etc.
# TINYXML_LIBRARIES - List of libraries when using TinyXML.
#
IF( TINYXML_INCLUDE_DIR )
# Already in cache, be silent
SET( TinyXML_FIND_QUIETLY TRUE )
ENDIF( TINYXML_INCLUDE_DIR )
FIND_PATH( TINYXML_INCLUDE_DIR "tinyxml.h"
PATH_SUFFIXES "tinyxml" )
FIND_LIBRARY( TINYXML_LIBRARIES
NAMES "tinyxml"
PATH_SUFFIXES "tinyxml" )
# handle the QUIETLY and REQUIRED arguments and set TINYXML_FOUND to TRUE if
# all listed variables are TRUE
INCLUDE( "FindPackageHandleStandardArgs" )
FIND_PACKAGE_HANDLE_STANDARD_ARGS( "TinyXML" DEFAULT_MSG TINYXML_INCLUDE_DIR TINYXML_LIBRARIES )
MARK_AS_ADVANCED( TINYXML_INCLUDE_DIR TINYXML_LIBRARIES )

View File

@ -0,0 +1,287 @@
# Copyright (c) 2012, Jarryd Beck
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# This module creates build rules for updating translation files made
# with gettext
# In your top level CMakeLists.txt, do
# include(GettextTranslate)
# then in any po directory where you want things to be translated, write
# GettextTranslate()
#
# This module also finds the gettext binaries. If these are in a non-standard
# location, you can define the following variables to provide paths to search
# in
# GettextTranslate_BINARIES --- a path in which to look for every program
# GettextTranslate_XGETTEXT --- the xgettext program
# GettextTranslate_MSGINIT --- the msginit program
# GettextTranslate_MSGFILTER --- the msgfilter program
# GettextTranslate_MSGCONV --- the msgconv program
# GettextTranslate_MSGMERGE --- the msgmerge program
# GettextTranslate_MSGFMT --- the msgfmt program
# these are searched first before $PATH, so set this if you have your own
# version that overrides the system version
#
# it reads variables from Makevars, one of the most important being DOMAIN
# it reads the languages to generate from LINGUAS
#
# it adds the following targets
# update-po
# update-gmo
# ${DOMAIN}-pot.update
# generate-${DOMAIN}-${lang}-po
# generate-${DOMAIN}-${lang}-gmo
#
# where ${DOMAIN} is the DOMAIN variable read from Makevars
# and ${lang} is each language mentioned in LINGUAS
#
# if you want update-gmo to be added to the "all" target, then define the
# variable GettextTranslate_ALL before including this file
#
# by default, the gmo files are built in the source directory. If you want
# them to be built in the binary directory, then define the variable
# GettextTranslate_GMO_BINARY
# add the update-po and update-gmo targets, the actual files that need to
# depend on this will be added as we go
if (DEFINED GettextTranslate_ALL)
set(_addToALL "ALL")
endif()
add_custom_target(update-po)
add_custom_target(update-gmo ${_addToALL})
#look for all the programs
#xgettext, msginit, msgfilter, msgconv, msgmerge, msgfmt
function(REQUIRE_BINARY binname varname)
if (defined ${${varname}-NOTFOUND})
message(FATAL_ERROR "Could not find " binname)
endif()
endfunction()
find_program(GettextTranslate_XGETTEXT_EXECUTABLE xgettext
HINTS ${GettextTranslate_XGETTEXT} ${GettextTranslate_BINARIES}
)
REQUIRE_BINARY(xgettext GettextTranslate_XGETTEXT_EXECUTABLE)
find_program(GettextTranslate_MSGINIT_EXECUTABLE msginit
HINTS ${GettextTranslate_MSGINIT} ${GettextTranslate_BINARIES}
)
REQUIRE_BINARY(msginit GettextTranslate_MSGINIT_EXECUTABLE)
find_program(GettextTranslate_MSGFILTER_EXECUTABLE msgfilter
HINTS ${GettextTranslate_MSGFILTER} ${GettextTranslate_BINARIES}
)
REQUIRE_BINARY(msgfilter GettextTranslate_MSGFILTER_EXECUTABLE)
find_program(GettextTranslate_MSGCONV_EXECUTABLE msgconv
HINTS ${GettextTranslate_MSGCONV} ${GettextTranslate_BINARIES}
)
REQUIRE_BINARY(msgconv GettextTranslate_MSGCONV_EXECUTABLE)
find_program(GettextTranslate_MSGMERGE_EXECUTABLE msgmerge
HINTS ${GettextTranslate_MSGMERGE} ${GettextTranslate_BINARIES}
)
REQUIRE_BINARY(msgmerge GettextTranslate_MSGMERGE_EXECUTABLE)
find_program(GettextTranslate_MSGFMT_EXECUTABLE msgfmt
HINTS ${GettextTranslate_MSGFMT} ${GettextTranslate_BINARIES}
)
REQUIRE_BINARY(msgfmt GettextTranslate_MSGFMT_EXECUTABLE)
mark_as_advanced(
GettextTranslate_MSGCONV_EXECUTABLE
GettextTranslate_MSGFILTER_EXECUTABLE
GettextTranslate_MSGFMT_EXECUTABLE
GettextTranslate_MSGINIT_EXECUTABLE
GettextTranslate_MSGMERGE_EXECUTABLE
GettextTranslate_XGETTEXT_EXECUTABLE
)
macro(GettextTranslate)
if(GettextTranslate_GMO_BINARY)
set (GMO_BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR})
else()
set (GMO_BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR})
endif()
if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in)
message(FATAL_ERROR "There is no POTFILES.in in
${CMAKE_CURRENT_SOURCE_DIR}")
endif()
if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Makevars)
message(FATAL_ERROR "There is no Makevars in ${CMAKE_CURRENT_SOURCE_DIR}")
endif()
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/Makevars makevars
REGEX "^[^=]+=(.*)$"
)
foreach(makevar ${makevars})
string(REGEX REPLACE "^([^= ]+) =[ ]?(.*)$" "\\1" MAKEVAR_KEY ${makevar})
string(REGEX REPLACE "^([^= ]+) =[ ]?(.*)$" "\\2"
MAKEVAR_${MAKEVAR_KEY} ${makevar})
endforeach()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in
${CMAKE_CURRENT_BINARY_DIR}/POTFILES
COPYONLY
)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LINGUAS
${CMAKE_CURRENT_BINARY_DIR}/LINGUAS
COPYONLY
)
#set the directory to not clean
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
PROPERTY CLEAN_NO_CUSTOM true)
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in potfiles
REGEX "^[^#].*"
)
foreach(potfile ${potfiles})
list(APPEND source_translatable
${CMAKE_CURRENT_SOURCE_DIR}/${MAKEVAR_top_builddir}/${potfile})
endforeach()
set(TEMPLATE_FILE ${MAKEVAR_DOMAIN}.pot)
set(TEMPLATE_FILE_ABS ${CMAKE_CURRENT_SOURCE_DIR}/${TEMPLATE_FILE})
string(REGEX MATCHALL "[^ ]+" XGETTEXT_OPTS ${MAKEVAR_XGETTEXT_OPTIONS})
#add_custom_target(${MAKEVAR_DOMAIN}.pot-update DEPENDS
# ${TEMPLATE_FILE_ABS}
#)
add_custom_target(${MAKEVAR_DOMAIN}.pot-update
COMMAND ${GettextTranslate_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTS}
-o ${TEMPLATE_FILE_ABS}
--default-domain=${MAKEVAR_DOMAIN}
--add-comments=TRANSLATORS:
--copyright-holder=${MAKEVAR_COPYRIGHT_HOLDER}
--msgid-bugs-address="${MAKEVAR_MSGID_BUGS_ADDRESS}"
--directory=${MAKEVAR_top_builddir}
--files-from=${CMAKE_CURRENT_BINARY_DIR}/POTFILES
--package-version=${VERSION}
--package-name=${CMAKE_PROJECT_NAME}
DEPENDS ${source_translatable}
${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
#add_custom_command(OUTPUT ${TEMPLATE_FILE_ABS}
# COMMAND ${GettextTranslate_XGETTEXT_EXECUTABLE} ${XGETTEXT_OPTS}
# -o ${TEMPLATE_FILE_ABS}
# --default-domain=${MAKEVAR_DOMAIN}
# --add-comments=TRANSLATORS:
# --copyright-holder=${MAKEVAR_COPYRIGHT_HOLDER}
# --msgid-bugs-address="${MAKEVAR_MSGID_BUGS_ADDRESS}"
# --directory=${MAKEVAR_top_builddir}
# --files-from=${CMAKE_CURRENT_BINARY_DIR}/POTFILES
# --package-version=${VERSION}
# --package-name=${CMAKE_PROJECT_NAME}
# DEPENDS ${source_translatable}
# ${CMAKE_CURRENT_SOURCE_DIR}/POTFILES.in
# WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
#)
#add_dependencies(update-po ${MAKEVAR_DOMAIN}.pot-update)
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/LINGUAS LINGUAS
REGEX "^[^#].*")
string(REGEX MATCHALL "[^ ]+" languages ${LINGUAS})
foreach(lang ${languages})
set(PO_FILE_NAME "${CMAKE_CURRENT_SOURCE_DIR}/${lang}.po")
set(GMO_FILE_NAME "${GMO_BUILD_DIR}/${lang}.gmo")
set(PO_TARGET "generate-${MAKEVAR_DOMAIN}-${lang}-po")
set(GMO_TARGET "generate-${MAKEVAR_DOMAIN}-${lang}-gmo")
list(APPEND po_files ${PO_TARGET})
list(APPEND gmo_files ${GMO_TARGET})
if(${lang} MATCHES "en@(.*)quot")
add_custom_command(OUTPUT ${lang}.insert-header
COMMAND
sed -e "'/^#/d'" -e 's/HEADER/${lang}.header/g'
${CMAKE_CURRENT_SOURCE_DIR}/insert-header.sin > ${lang}.insert-header
)
#generate the en@quot files
add_custom_command(OUTPUT ${PO_FILE_NAME}
COMMAND
${GettextTranslate_MSGINIT_EXECUTABLE} -i ${TEMPLATE_FILE_ABS}
--no-translator -l ${lang}
-o - 2>/dev/null
| sed -f ${CMAKE_CURRENT_BINARY_DIR}/${lang}.insert-header
| ${GettextTranslate_MSGCONV_EXECUTABLE} -t UTF-8
| ${GettextTranslate_MSGFILTER_EXECUTABLE} sed -f
${CMAKE_CURRENT_SOURCE_DIR}/`echo ${lang}
| sed -e 's/.*@//'`.sed 2>/dev/null >
${PO_FILE_NAME}
DEPENDS ${lang}.insert-header ${TEMPLATE_FILE_ABS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
else()
add_custom_command(OUTPUT ${PO_FILE_NAME}
COMMAND ${GettextTranslate_MSGMERGE_EXECUTABLE} --lang=${lang}
${PO_FILE_NAME} ${TEMPLATE_FILE_ABS}
-o ${PO_FILE_NAME}.new
COMMAND mv ${PO_FILE_NAME}.new ${PO_FILE_NAME}
DEPENDS ${TEMPLATE_FILE_ABS}
)
endif()
add_custom_command(OUTPUT ${GMO_FILE_NAME}
COMMAND ${GettextTranslate_MSGFMT_EXECUTABLE} -c --statistics --verbose
-o ${GMO_FILE_NAME} ${PO_FILE_NAME}
DEPENDS ${PO_TARGET}
)
add_custom_target(${GMO_TARGET} DEPENDS ${GMO_FILE_NAME})
add_custom_target(${PO_TARGET} DEPENDS ${PO_FILE_NAME})
add_dependencies(${PO_TARGET} ${MAKEVAR_DOMAIN}.pot-update)
install(FILES ${GMO_FILE_NAME} DESTINATION
${LOCALEDIR}/${lang}/LC_MESSAGES
RENAME ${MAKEVAR_DOMAIN}.mo
)
endforeach()
add_dependencies(update-po ${po_files})
add_dependencies(update-gmo ${gmo_files})
#string(REGEX MATCH "^[^=]+=(.*)$" parsed_variables ${makevars})
endmacro()

View File

@ -0,0 +1,24 @@
include (CheckFunctionExists)
set (CMAKE_REQUIRED_LIBRARIES ${OPENSSL_LIBRARIES})
set (CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR})
check_function_exists (EVP_bf_cbc HAVE_EVP_BF)
if (NOT HAVE_EVP_BF)
message (STATUS " Blowfish support disabled.")
endif (NOT HAVE_EVP_BF)
check_function_exists (EVP_aes_128_cbc HAVE_EVP_AES)
if (NOT HAVE_EVP_AES)
message (STATUS " AES support disabled.")
endif (NOT HAVE_EVP_AES)
check_function_exists (EVP_aes_128_xts HAVE_EVP_AES_XTS)
if (NOT HAVE_EVP_AES_XTS)
message (STATUS " AES/XTS support disabled.")
endif (NOT HAVE_EVP_AES_XTS)
set (CMAKE_REQUIRED_LIBRARIES)
set (CMAKE_REQUIRED_INCLUDES)

View File

@ -1,17 +0,0 @@
if BUILD_NLS
NLS_DIR = po
endif
SUBDIRS = encfs m4 $(NLS_DIR)
EXTRA_DIST = config.rpath mkinstalldirs encfs.spec makedist.sh makedist2.sh \
intl/autosprintf.h intl/autosprintf.cpp intl/gettext.h
AUTOMAKE_OPTIONS = foreign
MAINTAINERCLEANFILES = aclocal.m4
ACLOCAL_AMFLAGS = -I m4

View File

@ -1,2 +0,0 @@
KDE_OPTIONS = qtonly

View File

@ -1,5 +0,0 @@
default: all
all:
autoreconf -if

11
README
View File

@ -35,12 +35,13 @@ Usage:
Technology:
- Encfs uses algorithms from third-party libraries (OpenSSL is the default) to
encrypt data and filenames.
- Encfs uses algorithms from the third-party library OpenSSL to encrypt data
and filenames.
- a user supplied password is used to decrypt a volume key, and the volume key
is used for encrypting all file names and contents. This makes it possible
to change the password without needing to re-encrypt all files.
- a user supplied password is used to decrypt a randomly generated volume key,
and the volume key is used for encrypting all file names and contents. This
makes it possible to change the password without needing to re-encrypt all
files.
- EncFS has two encryption modes, which are used in different places:
- Stream encryption:

View File

@ -1,67 +1,6 @@
Quick configuration advice
==========================
The configuration script will automatically find and make use of your installed
'gettext' package. If you do not have gettext installed, or do not want
internationalization support included in the build, then you can disable native
language support using
./configu --disable-nls
Using This Package
==================
As a user, if your language has been installed for this package, you
only have to set the `LANG' environment variable to the appropriate
`LL_CC' combination. Here `LL' is an ISO 639 two-letter language code,
and `CC' is an ISO 3166 two-letter country code. For example, let's
suppose that you speak German and live in Germany. At the shell
prompt, merely execute `setenv LANG de_DE' (in `csh'),
`export LANG; LANG=de_DE' (in `sh') or `export LANG=de_DE' (in `bash').
This can be done from your `.login' or `.profile' file, once and for
all.
You might think that the country code specification is redundant.
But in fact, some languages have dialects in different countries. For
example, `de_AT' is used for Austria, and `pt_BR' for Brazil. The
country code serves to distinguish the dialects.
The locale naming convention of `LL_CC', with `LL' denoting the
language and `CC' denoting the country, is the one use on systems based
on GNU libc. On other systems, some variations of this scheme are
used, such as `LL' or `LL_CC.ENCODING'. You can get the list of
locales supported by your system for your country by running the command
`locale -a | grep '^LL''.
Not all programs have translations for all languages. By default, an
English message is shown in place of a nonexistent translation. If you
understand other languages, you can set up a priority list of languages.
This is done through a different environment variable, called
`LANGUAGE'. GNU `gettext' gives preference to `LANGUAGE' over `LANG'
for the purpose of message handling, but you still need to have `LANG'
set to the primary language; this is required by other parts of the
system libraries. For example, some Swedish users who would rather
read translations in German than English for when Swedish is not
available, set `LANGUAGE' to `sv:de' while leaving `LANG' to `sv_SE'.
Special advice for Norwegian users: The language code for Norwegian
bokma*l changed from `no' to `nb' recently (in 2003). During the
transition period, while some message catalogs for this language are
installed under `nb' and some older ones under `no', it's recommended
for Norwegian users to set `LANGUAGE' to `nb:no' so that both newer and
older translations are used.
In the `LANGUAGE' environment variable, but not in the `LANG'
environment variable, `LL_CC' combinations can be abbreviated as `LL'
to denote the language's main dialect. For example, `de' is equivalent
to `de_DE' (German as spoken in Germany), and `pt' to `pt_PT'
(Portuguese as spoken in Portugal) in this context.
Translating
===========
EncFS is registered with Rosetta - an online interface for supplying
translations. See https://launchpad.ubuntu.com/rosetta/products/encfs
translations. See https://translations.launchpad.net/encfs
If your language is not included in this distribution, you may want
to check if translated text is already available online in Rosetta.

View File

@ -1,7 +0,0 @@
Many people have contributed translations for EncFS. Thank you for making
EncFS easier for everyone to use!
If you would like to help with translations, please use the online
interface provided by Canonical Ltd (the makers of Ubuntu Linux):
https://translations.launchpad.net/encfs/main/

29
base/CMakeLists.txt Normal file
View File

@ -0,0 +1,29 @@
find_package (Protobuf REQUIRED)
include_directories (${PROTOBUF_INCLUDE_DIR})
find_package (TinyXML REQUIRED)
include_directories (${TINYXML_INCLUDE_DIR})
set (LIBS ${LIBS} ${TINYXML_LIBRARIES})
protobuf_generate_cpp (PROTO_SRCS PROTO_HDRS config.proto)
configure_file (${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake
${CMAKE_CURRENT_BINARY_DIR}/config.h)
add_library (encfs-base
autosprintf.cpp
base64.cpp
ConfigReader.cpp
ConfigVar.cpp
Error.cpp
Interface.cpp
XmlReader.cpp
${PROTO_SRCS}
${PROTO_HDRS}
)
target_link_libraries (encfs-base
${PROTOBUF_LIBRARY}
${TINYXML_LIBRARIES}
)

157
base/ConfigReader.cpp Normal file
View File

@ -0,0 +1,157 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004-2013, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "base/ConfigReader.h"
#include <glog/logging.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
using namespace std;
ConfigReader::ConfigReader()
{
}
ConfigReader::~ConfigReader()
{
}
// read the entire file into a ConfigVar instance and then use that to decode
// into mapped variables.
bool ConfigReader::load(const char *fileName)
{
struct stat stbuf;
memset( &stbuf, 0, sizeof(struct stat));
if( lstat( fileName, &stbuf ) != 0)
return false;
int size = stbuf.st_size;
int fd = open( fileName, O_RDONLY );
if(fd < 0)
return false;
char *buf = new char[size];
int res = ::read( fd, buf, size );
close( fd );
if( res != size )
{
LOG(WARNING) << "Partial read of config file, expecting "
<< size << " bytes, got " << res;
delete[] buf;
return false;
}
ConfigVar in;
in.write( (unsigned char *)buf, size );
delete[] buf;
return loadFromVar( in );
}
bool ConfigReader::loadFromVar(ConfigVar &in)
{
in.resetOffset();
// parse.
int numEntries = in.readInt();
for(int i=0; i<numEntries; ++i)
{
string key, value;
in >> key >> value;
if(key.length() == 0)
{
LOG(ERROR) << "Invalid key encoding in buffer";
return false;
}
ConfigVar newVar( value );
vars.insert( make_pair( key, newVar ) );
}
return true;
}
bool ConfigReader::save(const char *fileName) const
{
// write everything to a ConfigVar, then output to disk
ConfigVar out = toVar();
int fd = ::open( fileName, O_RDWR | O_CREAT, 0640 );
if(fd >= 0)
{
int retVal = ::write( fd, out.buffer(), out.size() );
close( fd );
if(retVal != out.size())
{
LOG(ERROR) << "Error writing to config file " << fileName;
return false;
}
} else
{
LOG(ERROR) << "Unable to open or create file " << fileName;
return false;
}
return true;
}
ConfigVar ConfigReader::toVar() const
{
// write everything to a ConfigVar, then output to disk
ConfigVar out;
out.writeInt( vars.size() );
map<string, ConfigVar>::const_iterator it;
for(it = vars.begin(); it != vars.end(); ++it)
{
out.writeInt( it->first.size() );
out.write( (unsigned char*)it->first.data(), it->first.size() );
out.writeInt( it->second.size() );
out.write( (unsigned char*)it->second.buffer(), it->second.size() );
}
return out;
}
ConfigVar ConfigReader::operator[] ( const std::string &varName ) const
{
// read only
map<string, ConfigVar>::const_iterator it = vars.find( varName );
if( it == vars.end() )
return ConfigVar();
else
return it->second;
}
ConfigVar &ConfigReader::operator[] ( const std::string &varName )
{
return vars[ varName ];
}

View File

@ -24,7 +24,7 @@
#include <string>
#include <map>
#include "ConfigVar.h"
#include "base/ConfigVar.h"
/*
handles Configuration load / store for Encfs filesystems.

253
base/ConfigVar.cpp Normal file
View File

@ -0,0 +1,253 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "base/ConfigVar.h"
#include "base/Error.h"
#include <glog/logging.h>
#include <cstring>
#ifndef MIN
inline int MIN(int a, int b)
{
return (a < b) ? a : b;
}
#endif
ConfigVar::ConfigVar()
: pd( new ConfigVarData )
{
pd->offset = 0;
}
ConfigVar::ConfigVar(const std::string &buf)
: pd( new ConfigVarData )
{
pd->buffer = buf;
pd->offset = 0;
}
ConfigVar::ConfigVar(const ConfigVar &src)
{
pd = src.pd;
}
ConfigVar::~ConfigVar()
{
pd.reset();
}
ConfigVar & ConfigVar::operator = (const ConfigVar &src)
{
if(src.pd == pd)
return *this;
else
pd = src.pd;
return *this;
}
void ConfigVar::resetOffset()
{
pd->offset = 0;
}
int ConfigVar::read(unsigned char *buffer_, int bytes) const
{
int toCopy = MIN( bytes, pd->buffer.size() - pd->offset );
if(toCopy > 0)
memcpy( buffer_, pd->buffer.data() + pd->offset, toCopy );
pd->offset += toCopy;
return toCopy;
}
int ConfigVar::write(const unsigned char *data, int bytes)
{
if(pd->buffer.size() == (unsigned int)pd->offset)
{
pd->buffer.append( (const char *)data, bytes );
} else
{
pd->buffer.insert( pd->offset, (const char *)data, bytes );
}
pd->offset += bytes;
return bytes;
}
int ConfigVar::size() const
{
return pd->buffer.size();
}
const char *ConfigVar::buffer() const
{
return pd->buffer.data();
}
int ConfigVar::at() const
{
return pd->offset;
}
void ConfigVar::writeString(const char *data, int bytes)
{
writeInt( bytes );
write( (const unsigned char *)data, bytes );
}
// convert integer to BER encoded integer
void ConfigVar::writeInt(int val)
{
// we can represent 7 bits per char output, so a 32bit number may take up
// to 5 bytes.
// first byte: 0x0000007f 0111,1111
// second byte: 0x00003f80 0011,1111 1000,0000
// third byte: 0x001fb000 0000,0000 0001,1111 1100,0000 0000,0000
// fourth byte: 0x0fe00000 0000,1111 1110,0000
// fifth byte: 0xf0000000 1111,0000
unsigned char digit[5];
digit[4] = (unsigned char)((val & 0x0000007f));
digit[3] = 0x80 | (unsigned char)((val & 0x00003f80) >> 7);
digit[2] = 0x80 | (unsigned char)((val & 0x001fc000) >> 14);
digit[1] = 0x80 | (unsigned char)((val & 0x0fe00000) >> 21);
digit[0] = 0x80 | (unsigned char)((val & 0xf0000000) >> 28);
// find the starting point - we only need to output starting at the most
// significant non-zero digit..
int start = 0;
while(digit[start] == 0x80)
++start;
write( digit + start, 5-start );
}
int ConfigVar::readInt() const
{
const unsigned char * buf = (const unsigned char *)buffer();
int bytes = this->size();
int offset = at();
int value = 0;
bool highBitSet;
rAssert( offset < bytes );
do
{
unsigned char tmp = buf[offset++];
highBitSet = tmp & 0x80;
value = (value << 7) | (int)(tmp & 0x7f);
} while(highBitSet && offset < bytes);
pd->offset = offset;
// should never end up with a negative number..
rAssert( value >= 0 );
return value;
}
int ConfigVar::readInt( int defaultValue ) const
{
int bytes = this->size();
int offset = at();
if(offset >= bytes)
return defaultValue;
else
return readInt();
}
bool ConfigVar::readBool( bool defaultValue ) const
{
int tmp = readInt( defaultValue ? 1 : 0 );
return (tmp != 0);
}
ConfigVar & operator << (ConfigVar &src, bool value)
{
src.writeInt( value ? 1 : 0 );
return src;
}
ConfigVar & operator << (ConfigVar &src, int var)
{
src.writeInt( var );
return src;
}
ConfigVar & operator << (ConfigVar &src, const std::string &str)
{
src.writeString( str.data(), str.length() );
return src;
}
const ConfigVar & operator >> (const ConfigVar &src, bool &result)
{
int tmp = src.readInt();
result = (tmp != 0);
return src;
}
const ConfigVar & operator >> (const ConfigVar &src, int &result)
{
result = src.readInt();
return src;
}
const ConfigVar & operator >> (const ConfigVar &src, std::string &result)
{
int length = src.readInt();
LOG_IF(WARNING, length <= 0) << "Invalid config length " << length;
int readLen;
unsigned char tmpBuf[32];
if(length > (int)sizeof(tmpBuf))
{
unsigned char *ptr = new unsigned char[length];
readLen = src.read( ptr, length );
result.assign( (char*)ptr, length );
delete[] ptr;
} else
{
readLen = src.read( tmpBuf, length );
result.assign( (char*)tmpBuf, length );
}
if(readLen != length)
{
VLOG(1) << "string encoded as size " << length
<< " bytes, read " << readLen;
}
rAssert(readLen == length);
return src;
}

View File

@ -22,7 +22,7 @@
#define _ConfigVar_incl_
#include <string>
#include "shared_ptr.h"
#include "base/shared_ptr.h"
class ConfigVar
{

7
base/Error.cpp Normal file
View File

@ -0,0 +1,7 @@
#include "base/Error.h"
Error::Error(const char *msg)
: runtime_error(msg)
{
}

25
base/Error.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef _Error_incl_
#define _Error_incl_
#include <glog/logging.h>
#include <stdexcept>
class Error : public std::runtime_error
{
public:
Error(const char *msg);
};
#define STR(X) #X
#define rAssert( cond ) \
do { \
if( (cond) == false) \
{ LOG(ERROR) << "Assert failed: " << STR(cond); \
throw Error(STR(cond)); \
} \
} while(0)
#endif

View File

@ -2,7 +2,7 @@
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
* Copyright (c) 2004-2013, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
@ -18,22 +18,23 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Interface.h"
#include "base/Interface.h"
#include "ConfigVar.h"
#include "base/ConfigVar.h"
#include <rlog/rlog.h>
#include <rlog/RLogChannel.h>
#include <glog/logging.h>
#include <ostream>
using namespace rlog;
static RLogChannel * Info = DEF_CHANNEL( "info/iface", Log_Info );
std::ostream& operator << (std::ostream& out, const Interface &iface)
{
out << iface.name() << "(" << iface.major()
<< ":" << iface.minor() << ":" << iface.age() << ")";
return out;
}
bool implements(const Interface &A, const Interface &B)
{
rLog(Info, "checking if %s(%i:%i:%i) implements %s(%i:%i:%i)",
A.name().c_str(), A.major(), A.minor(), A.age(),
B.name().c_str(), B.major(), B.minor(), B.age());
VLOG(1) << "checking if " << A << " implements " << B;
if( A.name() != B.name() )
return false;
@ -54,7 +55,8 @@ Interface makeInterface(const char *name, int major, int minor, int age)
ConfigVar & operator << (ConfigVar &dst, const Interface &iface)
{
dst << iface.name() << (int)iface.major() << (int)iface.minor() << (int)iface.age();
dst << iface.name() << (int)iface.major() << (int)iface.minor()
<< (int)iface.age();
return dst;
}

View File

@ -22,7 +22,7 @@
#define _Interface_incl_
#include <string>
#include "config.pb.h"
#include "base/config.pb.h"
// check if A implements the interface described by B.
// Note that implements(A, B) is not the same as implements(B, A)
@ -32,7 +32,7 @@
bool implements( const Interface &a, const Interface &b );
Interface makeInterface( const char *name, int major, int minor, int age );
// Reae operation
// Read operation
class ConfigVar;
const ConfigVar & operator >> (const ConfigVar &, Interface &);

View File

@ -26,40 +26,41 @@
namespace rel
{
class Lock
{
public:
Lock( pthread_mutex_t &mutex );
~Lock();
class Lock
{
public:
Lock( pthread_mutex_t &mutex );
~Lock();
// leave the lock as it is. When the Lock wrapper is destroyed, it
// will do nothing with the pthread mutex.
void leave();
// leave the lock as it is. When the Lock wrapper is destroyed, it
// will do nothing with the pthread mutex.
void leave();
private:
Lock(const Lock &src); // not allowed
Lock &operator = (const Lock &src); // not allowed
private:
Lock(const Lock &src); // not allowed
Lock &operator = (const Lock &src); // not allowed
pthread_mutex_t *_mutex;
};
pthread_mutex_t *_mutex;
};
inline Lock::Lock( pthread_mutex_t &mutex )
: _mutex( &mutex )
{
pthread_mutex_lock( _mutex );
}
inline Lock::~Lock( )
{
if(_mutex)
pthread_mutex_unlock( _mutex );
}
inline void Lock::leave()
{
_mutex = 0;
}
inline Lock::Lock( pthread_mutex_t &mutex )
: _mutex( &mutex )
{
pthread_mutex_lock( _mutex );
}
inline Lock::~Lock( )
{
if(_mutex)
pthread_mutex_unlock( _mutex );
}
inline void Lock::leave()
{
_mutex = 0;
}
} // namespace rel
#endif

View File

@ -66,6 +66,8 @@ inline Range::Range()
inline bool Range::allowed(int value) const
{
if(minVal < 0 && maxVal < 0)
return true;
if(value >= minVal && value <= maxVal)
{
int tmp = value - minVal;

View File

@ -2,7 +2,7 @@
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2012, Valient Gough
* Copyright (c) 2012-2013, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "XmlReader.h"
#include "base/XmlReader.h"
#include <sys/types.h>
#include <sys/stat.h>
@ -35,12 +35,11 @@
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <rlog/rlog.h>
#include "base64.h"
#include "Interface.h"
#include <glog/logging.h>
#include "base/base64.h"
#include "base/Interface.h"
using namespace std;
using namespace rlog;
XmlValue::~XmlValue()
{
@ -53,7 +52,7 @@ XmlValuePtr XmlValue::operator[] (const char *path) const
XmlValuePtr XmlValue::find(const char *path) const
{
rError("in XmlValue::find(%s)", path);
LOG_FIRST_N(ERROR, 1) << "in XmlValue::find( " << path << ")";
return XmlValuePtr();
}
@ -127,8 +126,9 @@ bool XmlValue::readB64(const char *path, unsigned char *data, int length) const
if (decodedSize != length)
{
rError("decoding bytes len %i, expecting output len %i, got %i",
(int)s.size(), length, decodedSize);
LOG(ERROR) << "decoding bytes len " << s.size()
<< ", expecting output len " << length
<< ", got " << decodedSize;
return false;
}
@ -231,14 +231,15 @@ XmlValuePtr XmlReader::operator[] ( const char *name ) const
TiXmlNode *node = pd->doc->FirstChild(name);
if (node == NULL)
{
rError("Xml node %s not found", name);
LOG(ERROR) << "Xml node " << name << " not found";
return XmlValuePtr(new XmlValue());
}
TiXmlElement *element = node->ToElement();
if (element == NULL)
{
rError("Xml node %s not element, type = %i", name, node->Type());
LOG(ERROR) << "Xml node " << name
<< " not element, type = " << node->Type();
return XmlValuePtr(new XmlValue());
}

View File

@ -22,7 +22,7 @@
#define _XmlReader_incl_
#include <string>
#include "shared_ptr.h"
#include "base/shared_ptr.h"
class XmlValue;
typedef shared_ptr<XmlValue> XmlValuePtr;

View File

@ -25,7 +25,7 @@
#endif
/* Specification. */
#include "autosprintf.h"
#include "base/autosprintf.h"
#include <stdarg.h>
#include <stdlib.h>

View File

@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "base64.h"
#include "base/base64.h"
#include <ctype.h>

19
base/config.h.cmake Normal file
View File

@ -0,0 +1,19 @@
#cmakedefine HAVE_ATTR_XATTR_H
#cmakedefine HAVE_SYS_XATTR_H
#cmakedefine XATTR_ADD_OPT
#cmakedefine HAVE_COMMON_CRYPTO
#cmakedefine HAVE_TR1_MEMORY
#cmakedefine HAVE_TR1_UNORDERED_MAP
#cmakedefine HAVE_TR1_UNORDERED_SET
#cmakedefine HAVE_TR1_TUPLE
#cmakedefine HAVE_EVP_BF
#cmakedefine HAVE_EVP_AES
#cmakedefine HAVE_EVP_AES_XTS
#cmakedefine HAVE_VALGRIND_VALGRIND_H
#cmakedefine HAVE_VALGRIND_MEMCHECK_H
#define VERSION "@ENCFS_VERSION@"

View File

@ -2,9 +2,13 @@
message EncfsConfig
{
optional string creator = 1;
optional string writer = 11;
optional int32 revision = 2 [default=0];
required Interface cipher = 3;
// added for FileIO/Cipher 3.0 (encfs 1.8)
// Use only block encryption, no stream encryption.
optional bool block_mode_only = 31;
required EncryptedKey key = 4;
optional Interface naming = 5;
@ -16,6 +20,7 @@ message EncfsConfig
optional int32 block_mac_bytes = 61 [default=0];
optional int32 block_mac_rand_bytes = 611 [default=0];
optional bool allow_holes = 62 [default = false];
}
message EncryptedKey

View File

@ -23,11 +23,11 @@
#if defined(LOCALEDIR)
# include "gettext.h"
# include "base/gettext.h"
// make shortcut for gettext
# define _(STR) gettext (STR)
# include "autosprintf.h"
# include "base/autosprintf.h"
using gnu::autosprintf;
#else

View File

@ -22,7 +22,7 @@
#ifndef _SHARED_PTR_incl_
#define _SHARED_PTR_incl_
#include "config.h"
#include "base/config.h"
#ifdef HAVE_TR1_MEMORY
#include <tr1/memory>

41
cipher/CMakeLists.txt Normal file
View File

@ -0,0 +1,41 @@
include_directories (${OPENSSL_INCLUDE_DIR})
link_directories (${Encfs_BINARY_DIR}/base)
enable_testing ()
find_package (GTest REQUIRED)
add_library (encfs-cipher
readpassphrase.cpp
Cipher.cpp
CipherKey.cpp
MemoryPool.cpp
NullCipher.cpp
openssl.cpp
SSL_Cipher.cpp
)
target_link_libraries (encfs-cipher
${OPENSSL_LIBRARIES}
)
#include_directories (${GTEST_INCLUDE_DIR})
#add_executable (unittests
#MemBlockFileIO.cpp
#MemFileIO.cpp
#testing.cpp
#test_IO.cpp
#test_BlockIO.cpp
#)
#target_link_libraries (unittests
#${GTEST_BOTH_LIBRARIES}
#encfs-fs
#encfs-base
#${GLOG_LIBRARIES}
#)
#add_test (UnitTests unittests)
#GTEST_ADD_TESTS (unittests "${UnitTestArgs}" test_IO.cpp test_BlockIO.cpp)
#add_custom_target (test COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS unittests)

View File

@ -18,12 +18,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "base/config.h"
#include "cipher/Cipher.h"
#include "Cipher.h"
#include "Interface.h"
#include "Range.h"
#include "base64.h"
#include "base/Interface.h"
#include "base/Range.h"
#include "base/base64.h"
#include <map>
#include <list>
@ -57,6 +57,7 @@ struct CipherAlg
Interface iface;
Range keyLength;
Range blockSize;
bool hasStreamMode;
};
typedef multimap< string, CipherAlg> CipherMap_t;
@ -84,6 +85,7 @@ Cipher::GetAlgorithmList( bool includeHidden )
tmp.iface = it->second.iface;
tmp.keyLength = it->second.keyLength;
tmp.blockSize = it->second.blockSize;
tmp.hasStreamMode = it->second.hasStreamMode;
result.push_back( tmp );
}
@ -93,18 +95,21 @@ Cipher::GetAlgorithmList( bool includeHidden )
}
bool Cipher::Register(const char *name, const char *description,
const Interface &iface, CipherConstructor fn, bool hidden)
const Interface &iface, CipherConstructor fn,
bool hasStreamMode, bool hidden)
{
Range keyLength(-1,-1,1);
Range blockSize(-1,-1,1);
return Cipher::Register( name, description, iface,
keyLength, blockSize, fn, hidden );
keyLength, blockSize, fn, hasStreamMode, hidden );
}
bool Cipher::Register(const char *name, const char *description,
const Interface &iface, const Range &keyLength,
const Range &blockSize,
CipherConstructor fn, bool hidden)
CipherConstructor fn,
bool hasStreamMode,
bool hidden)
{
if(!gCipherMap)
gCipherMap = new CipherMap_t;
@ -116,6 +121,7 @@ bool Cipher::Register(const char *name, const char *description,
ca.iface = iface;
ca.keyLength = keyLength;
ca.blockSize = blockSize;
ca.hasStreamMode = hasStreamMode;
gCipherMap->insert( make_pair(string(name), ca) );
return true;
@ -195,26 +201,13 @@ unsigned int Cipher::MAC_16( const unsigned char *src, int len,
return mac16;
}
bool Cipher::nameEncode( unsigned char *data, int len,
uint64_t iv64, const CipherKey &key ) const
{
return streamEncode( data, len, iv64, key );
}
bool Cipher::nameDecode( unsigned char *data, int len,
uint64_t iv64, const CipherKey &key ) const
{
return streamDecode( data, len, iv64, key );
}
string Cipher::encodeAsString(const CipherKey &key,
const CipherKey &encodingKey )
{
int encodedKeySize = this->encodedKeySize();
unsigned char *keyBuf = new unsigned char[ encodedKeySize ];
// write the key, encoding it with itself.
this->writeKey( key, keyBuf, key );
this->writeKey( key, keyBuf, encodingKey );
int b64Len = B256ToB64Bytes( encodedKeySize );
unsigned char *b64Key = new unsigned char[ b64Len + 1 ];
@ -226,3 +219,9 @@ string Cipher::encodeAsString(const CipherKey &key,
return string( (const char *)b64Key );
}
bool Cipher::hasStreamMode() const
{
return true;
}

View File

@ -21,11 +21,9 @@
#ifndef _Cipher_incl_
#define _Cipher_incl_
#include "encfs.h"
#include "Range.h"
#include "Interface.h"
#include "CipherKey.h"
#include "cipher/CipherKey.h"
#include "base/Interface.h"
#include "base/Range.h"
#include <string>
#include <list>
@ -52,6 +50,7 @@ public:
Interface iface;
Range keyLength;
Range blockSize;
bool hasStreamMode;
};
@ -60,15 +59,16 @@ public:
static shared_ptr<Cipher> New( const Interface &iface,
int keyLen = -1);
int keyLen = -1);
static shared_ptr<Cipher> New( const std::string &cipherName,
int keyLen = -1 );
int keyLen = -1 );
static bool Register(const char *cipherName,
const char *description,
const Interface &iface,
CipherConstructor constructor,
bool hasStreamMode,
bool hidden = false);
static bool Register(const char *cipherName,
@ -76,6 +76,7 @@ public:
const Interface &iface,
const Range &keyLength, const Range &blockSize,
CipherConstructor constructor,
bool hasStreamMode,
bool hidden = false);
Cipher();
@ -117,6 +118,8 @@ public:
virtual int encodedKeySize() const=0; // size
virtual int cipherBlockSize() const=0; // size of a cipher block
virtual bool hasStreamMode() const;
// fill the supplied buffer with random data
// The data may be pseudo random and might not be suitable for key
// generation. For generating keys, uses newRandomKey() instead.
@ -143,17 +146,6 @@ public:
virtual bool streamDecode( unsigned char *data, int len,
uint64_t iv64, const CipherKey &key) const=0;
/*
These are just aliases of streamEncode / streamDecode, but there are
provided here for backward compatibility for earlier ciphers that has
effectively two stream modes - one for encoding partial blocks and
another for encoding filenames.
*/
virtual bool nameEncode( unsigned char *data, int len,
uint64_t iv64, const CipherKey &key) const;
virtual bool nameDecode( unsigned char *data, int len,
uint64_t iv64, const CipherKey &key) const;
/*
Block encoding of data in-place. The data size should be a multiple of
the cipher block size.

View File

@ -18,7 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CipherKey.h"
#include "cipher/CipherKey.h"
AbstractCipherKey::AbstractCipherKey()
{

View File

@ -21,7 +21,7 @@
#ifndef _CipherKey_incl_
#define _CipherKey_incl_
#include "shared_ptr.h"
#include "base/shared_ptr.h"
class AbstractCipherKey
{

164
cipher/MemoryPool.cpp Normal file
View File

@ -0,0 +1,164 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2003-2013, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cipher/MemoryPool.h"
#include <cstdlib>
#include <cstring>
#include "base/config.h"
#include "base/Error.h"
#include <pthread.h>
#include <sys/mman.h>
#include <glog/logging.h>
#ifdef HAVE_VALGRIND_MEMCHECK_H
#include <valgrind/memcheck.h>
#else
#define VALGRIND_MAKE_MEM_NOACCESS( a, b )
#define VALGRIND_MAKE_MEM_UNDEFINED( a, b )
#endif
#include <map>
#include <list>
using namespace std;
# include <openssl/crypto.h>
# include <openssl/buffer.h>
static BUF_MEM *allocBlock( int size )
{
BUF_MEM *block = BUF_MEM_new( );
BUF_MEM_grow( block, size );
VALGRIND_MAKE_MEM_NOACCESS( block->data, block->max );
return block;
}
static void freeBlock( BUF_MEM *block )
{
VALGRIND_MAKE_MEM_UNDEFINED( block->data, block->max );
BUF_MEM_free( block );
}
static pthread_mutex_t gMPoolMutex = PTHREAD_MUTEX_INITIALIZER;
typedef std::map<int, std::list<BUF_MEM* > > FreeBlockMap;
static FreeBlockMap gFreeBlocks;
void MemBlock::allocate(int size)
{
rAssert(size > 0);
pthread_mutex_lock( &gMPoolMutex );
list<BUF_MEM*> &freeList = gFreeBlocks[size];
BUF_MEM *mem;
if (!freeList.empty())
{
mem = freeList.front();
freeList.pop_front();
pthread_mutex_unlock( &gMPoolMutex );
} else
{
pthread_mutex_unlock( &gMPoolMutex );
mem = allocBlock( size );
}
internalData = mem;
data = reinterpret_cast<unsigned char *>(mem->data);
VALGRIND_MAKE_MEM_UNDEFINED( data, size );
}
MemBlock::~MemBlock()
{
BUF_MEM *block = (BUF_MEM*)internalData;
data = NULL;
internalData = NULL;
if (block)
{
// wipe the buffer..
VALGRIND_MAKE_MEM_UNDEFINED( block->data, block->max );
memset( block->data , 0, block->max);
VALGRIND_MAKE_MEM_NOACCESS( block->data, block->max );
pthread_mutex_lock( &gMPoolMutex );
gFreeBlocks[ block->max ].push_front(block);
pthread_mutex_unlock( &gMPoolMutex );
}
}
void MemoryPool::destroyAll()
{
pthread_mutex_lock( &gMPoolMutex );
for (FreeBlockMap::const_iterator it = gFreeBlocks.begin();
it != gFreeBlocks.end(); it++)
{
for (list<BUF_MEM*>::const_iterator bIt = it->second.begin();
bIt != it->second.end(); bIt++)
{
freeBlock( *bIt );
}
}
gFreeBlocks.clear();
pthread_mutex_unlock( &gMPoolMutex );
}
SecureMem::SecureMem(int len)
{
rAssert(len > 0);
data = (char *)OPENSSL_malloc(len);
if (data)
{
size = len;
mlock(data, size);
memset(data, '\0', size);
VALGRIND_MAKE_MEM_UNDEFINED( data, size );
} else
{
size = 0;
}
}
SecureMem::~SecureMem()
{
if (size)
{
memset(data, '\0', size);
OPENSSL_cleanse(data, size);
munlock(data, size);
OPENSSL_free(data);
VALGRIND_MAKE_MEM_NOACCESS( data, size );
data = NULL;
size = 0;
}
}

View File

@ -21,14 +21,25 @@
#ifndef _MemoryPool_incl_
#define _MemoryPool_incl_
/*
Memory Pool for fixed sized objects.
Usage:
MemBlock mb( size );
// do things with storage in mb.data
unsigned char *buffer = mb.data;
// memblock freed when destructed
*/
struct MemBlock
{
unsigned char *data;
void *internalData;
MemBlock();
~MemBlock();
void allocate(int size);
};
inline MemBlock::MemBlock()
@ -36,19 +47,8 @@ inline MemBlock::MemBlock()
{
}
/*
Memory Pool for fixed sized objects.
Usage:
MemBlock mb = MemoryPool::allocate( size );
// do things with storage in mb.data
unsigned char *buffer = mb.data;
MemoryPool::release( mb );
*/
namespace MemoryPool
{
MemBlock allocate( int size );
void release( const MemBlock &el );
void destroyAll();
}

View File

@ -18,18 +18,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NullCipher.h"
#include "cipher/NullCipher.h"
#include "Range.h"
#include "Interface.h"
#include "shared_ptr.h"
#include <rlog/rlog.h>
#include "base/Range.h"
#include "base/Interface.h"
#include "base/shared_ptr.h"
#include <cstring>
using namespace std;
using namespace rlog;
static Interface NullInterface = makeInterface( "nullCipher", 1, 0, 0 );
@ -38,40 +35,40 @@ static Range NullBlockRange(1,4096,1);
static shared_ptr<Cipher> NewNullCipher(const Interface &iface, int keyLen)
{
(void)keyLen;
return shared_ptr<Cipher>( new NullCipher( iface ) );
(void)keyLen;
return shared_ptr<Cipher>( new NullCipher( iface ) );
}
const bool HiddenCipher = true;
static bool NullCipher_registered = Cipher::Register("Null",
"Non encrypting cipher. For testing only!",
NullInterface, NullKeyRange, NullBlockRange, NewNullCipher,
HiddenCipher);
"Non encrypting cipher. For testing only!",
NullInterface, NullKeyRange, NullBlockRange, NewNullCipher,
HiddenCipher);
class NullKey : public AbstractCipherKey
{
public:
NullKey() {}
virtual ~NullKey() {}
NullKey() {}
virtual ~NullKey() {}
};
class NullDestructor
{
public:
NullDestructor() {}
NullDestructor(const NullDestructor &) {}
~NullDestructor() {}
NullDestructor() {}
NullDestructor(const NullDestructor &) {}
~NullDestructor() {}
NullDestructor &operator = (const NullDestructor &){ return *this; }
void operator ()(NullKey *&) {}
NullDestructor &operator = (const NullDestructor &){ return *this; }
void operator ()(NullKey *&) {}
};
shared_ptr<AbstractCipherKey> gNullKey( new NullKey(), NullDestructor() );
NullCipher::NullCipher(const Interface &iface_)
{
this->iface = iface_;
this->iface = iface_;
}
NullCipher::~NullCipher()
@ -80,105 +77,105 @@ NullCipher::~NullCipher()
Interface NullCipher::interface() const
{
return iface;
return iface;
}
CipherKey NullCipher::newKey(const char *, int,
int &, long, const unsigned char *, int )
{
return gNullKey;
return gNullKey;
}
CipherKey NullCipher::newKey(const char *, int)
{
return gNullKey;
return gNullKey;
}
CipherKey NullCipher::newRandomKey()
{
return gNullKey;
return gNullKey;
}
bool NullCipher::randomize( unsigned char *buf, int len, bool ) const
{
memset( buf, 0, len );
return true;
memset( buf, 0, len );
return true;
}
uint64_t NullCipher::MAC_64(const unsigned char *, int ,
const CipherKey &, uint64_t *) const
const CipherKey &, uint64_t *) const
{
return 0;
return 0;
}
CipherKey NullCipher::readKey( const unsigned char *,
const CipherKey &, bool)
const CipherKey &, bool)
{
return gNullKey;
return gNullKey;
}
void NullCipher::writeKey(const CipherKey &, unsigned char *,
const CipherKey &)
const CipherKey &)
{
}
bool NullCipher::compareKey(const CipherKey &A_,
const CipherKey &B_) const
const CipherKey &B_) const
{
shared_ptr<NullKey> A = dynamic_pointer_cast<NullKey>(A_);
shared_ptr<NullKey> B = dynamic_pointer_cast<NullKey>(B_);
return A.get() == B.get();
shared_ptr<NullKey> A = dynamic_pointer_cast<NullKey>(A_);
shared_ptr<NullKey> B = dynamic_pointer_cast<NullKey>(B_);
return A.get() == B.get();
}
int NullCipher::encodedKeySize() const
{
return 0;
return 0;
}
int NullCipher::keySize() const
{
return 0;
return 0;
}
int NullCipher::cipherBlockSize() const
{
return 1;
return 1;
}
bool NullCipher::streamEncode( unsigned char *src, int len,
uint64_t iv64, const CipherKey &key) const
uint64_t iv64, const CipherKey &key) const
{
(void)src;
(void)len;
(void)iv64;
(void)key;
return true;
(void)src;
(void)len;
(void)iv64;
(void)key;
return true;
}
bool NullCipher::streamDecode( unsigned char *src, int len,
uint64_t iv64, const CipherKey &key) const
uint64_t iv64, const CipherKey &key) const
{
(void)src;
(void)len;
(void)iv64;
(void)key;
return true;
(void)src;
(void)len;
(void)iv64;
(void)key;
return true;
}
bool NullCipher::blockEncode( unsigned char *, int , uint64_t,
const CipherKey & ) const
const CipherKey & ) const
{
return true;
return true;
}
bool NullCipher::blockDecode( unsigned char *, int, uint64_t,
const CipherKey & ) const
const CipherKey & ) const
{
return true;
return true;
}
bool NullCipher::Enabled()
{
return true;
return true;
}

View File

@ -21,8 +21,8 @@
#ifndef _NullCipher_incl_
#define _NullCipher_incl_
#include "Cipher.h"
#include "Interface.h"
#include "cipher/Cipher.h"
#include "base/Interface.h"
/*
Implements Cipher interface for a pass-through mode. May be useful for

973
cipher/SSL_Cipher.cpp Normal file
View File

@ -0,0 +1,973 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "base/config.h"
#include <openssl/blowfish.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include "cipher/SSL_Cipher.h"
#include "cipher/MemoryPool.h"
#include "base/Error.h"
#include "base/Mutex.h"
#include "base/Range.h"
#include <cstring>
#include <ctime>
#include <sys/mman.h>
#include <sys/time.h>
#include <glog/logging.h>
#include "base/i18n.h"
using namespace std;
using namespace rel;
const int MAX_KEYLENGTH = 64; // in bytes (256 bit)
const int MAX_IVLENGTH = 16;
const int KEY_CHECKSUM_BYTES = 4;
#ifndef MIN
inline int MIN(int a, int b)
{
return (a < b) ? a : b;
}
#endif
/*
This produces the same result as OpenSSL's EVP_BytesToKey. The difference
is that here we can explicitly specify the key size, instead of relying on
the state of EVP_CIPHER struct. EVP_BytesToKey will only produce 128 bit
keys for the EVP Blowfish interface, which is not what we want.
DEPRECATED: this is here for backward compatibilty only. Use PBKDF
*/
int BytesToKey( int keyLen, int ivLen, const EVP_MD *md,
const unsigned char *data, int dataLen,
unsigned int rounds, unsigned char *key, unsigned char *iv)
{
if( data == NULL || dataLen == 0 )
return 0; // OpenSSL returns nkey here, but why? It is a failure..
unsigned char mdBuf[ EVP_MAX_MD_SIZE ];
unsigned int mds=0;
int addmd =0;
int nkey = key ? keyLen : 0;
int niv = iv ? ivLen : 0;
EVP_MD_CTX cx;
EVP_MD_CTX_init( &cx );
for(;;)
{
EVP_DigestInit_ex( &cx, md, NULL );
if( addmd++ )
EVP_DigestUpdate( &cx, mdBuf, mds );
EVP_DigestUpdate( &cx, data, dataLen );
EVP_DigestFinal_ex( &cx, mdBuf, &mds );
for(unsigned int i=1; i < rounds; ++i)
{
EVP_DigestInit_ex( &cx, md, NULL );
EVP_DigestUpdate( &cx, mdBuf, mds );
EVP_DigestFinal_ex( &cx, mdBuf, &mds );
}
int offset = 0;
int toCopy = MIN( nkey, (int)mds - offset );
if( toCopy )
{
memcpy( key, mdBuf+offset, toCopy );
key += toCopy;
nkey -= toCopy;
offset += toCopy;
}
toCopy = MIN( niv, (int)mds - offset );
if( toCopy )
{
memcpy( iv, mdBuf+offset, toCopy );
iv += toCopy;
niv -= toCopy;
offset += toCopy;
}
if((nkey == 0) && (niv == 0)) break;
}
EVP_MD_CTX_cleanup( &cx );
OPENSSL_cleanse( mdBuf, sizeof(mdBuf) );
return keyLen;
}
long time_diff(const timeval &end, const timeval &start)
{
return (end.tv_sec - start.tv_sec) * 1000 * 1000 +
(end.tv_usec - start.tv_usec);
}
int SSL_Cipher::TimedPBKDF2(const char *pass, int passlen,
const unsigned char *salt, int saltlen,
int keylen, unsigned char *out,
long desiredPDFTime)
{
int iter = 1000;
timeval start, end;
for(;;)
{
gettimeofday( &start, 0 );
int res = PKCS5_PBKDF2_HMAC_SHA1(
pass, passlen, const_cast<unsigned char*>(salt), saltlen,
iter, keylen, out);
if(res != 1)
return -1;
gettimeofday( &end, 0 );
long delta = time_diff(end, start);
if(delta < desiredPDFTime / 8)
{
iter *= 4;
} else if(delta < (5 * desiredPDFTime / 6))
{
// estimate number of iterations to get close to desired time
iter = (int)((double)iter * (double)desiredPDFTime
/ (double)delta);
} else
return iter;
}
}
// - Version 1:0 used EVP_BytesToKey, which didn't do the right thing for
// Blowfish key lengths > 128 bit.
// - Version 2:0 uses BytesToKey.
// We support both 2:0 and 1:0, hence current:revision:age = 2:0:1
// - Version 2:1 adds support for Message Digest function interface
// - Version 2:2 adds PBKDF2 for password derivation
// - Version 3:0 adds a new IV mechanism
// - Version 3:1 adds ssl/aes_xts
static Interface BlowfishInterface = makeInterface( "ssl/blowfish", 3, 0, 2 );
static Interface AESInterface = makeInterface( "ssl/aes", 3, 0, 2 );
static Interface AesXtsInterface = makeInterface( "ssl/aes_xts", 3, 1, 2 );
#if defined(HAVE_EVP_BF)
static Range BFKeyRange(128,256,32);
static Range BFBlockRange(64,4096,8);
static shared_ptr<Cipher> NewBFCipher( const Interface &iface, int keyLen )
{
if( keyLen <= 0 )
keyLen = 160;
keyLen = BFKeyRange.closest( keyLen );
const EVP_CIPHER *blockCipher = EVP_bf_cbc();
const EVP_CIPHER *streamCipher = EVP_bf_cfb();
return shared_ptr<Cipher>( new SSL_Cipher(iface, BlowfishInterface,
blockCipher, streamCipher, keyLen / 8) );
}
static bool BF_Cipher_registered = Cipher::Register(
"Blowfish",
// xgroup(setup)
gettext_noop("8 byte block cipher"),
BlowfishInterface, BFKeyRange, BFBlockRange, NewBFCipher, true);
#endif
#if defined(HAVE_EVP_AES)
static Range AESKeyRange(128,256,64);
static Range AESBlockRange(64,4096,16);
static shared_ptr<Cipher> NewAESCipher( const Interface &iface, int keyLen )
{
if( keyLen <= 0 )
keyLen = 192;
keyLen = AESKeyRange.closest( keyLen );
const EVP_CIPHER *blockCipher = 0;
const EVP_CIPHER *streamCipher = 0;
switch(keyLen)
{
case 128:
blockCipher = EVP_aes_128_cbc();
streamCipher = EVP_aes_128_cfb();
break;
case 192:
blockCipher = EVP_aes_192_cbc();
streamCipher = EVP_aes_192_cfb();
break;
case 256:
default:
blockCipher = EVP_aes_256_cbc();
streamCipher = EVP_aes_256_cfb();
break;
}
return shared_ptr<Cipher>( new SSL_Cipher(iface, AESInterface,
blockCipher, streamCipher, keyLen / 8) );
}
static bool AES_Cipher_registered = Cipher::Register(
"AES", "16 byte block cipher",
AESInterface, AESKeyRange, AESBlockRange, NewAESCipher, true);
#endif
#if defined(HAVE_EVP_AES_XTS)
static Range AesXtsKeyRange(128,256,128);
static Range AesXtsBlockRange(1024,8192,256);
static shared_ptr<Cipher> NewAesXtsCipher( const Interface &iface, int keyLen )
{
if( keyLen <= 0 )
keyLen = 256;
keyLen = AesXtsKeyRange.closest( keyLen );
const EVP_CIPHER *blockCipher = 0;
switch(keyLen)
{
case 128:
blockCipher = EVP_aes_128_xts();
break;
case 256:
default:
blockCipher = EVP_aes_256_xts();
break;
}
// XTS uses 2 keys, so the key size is doubled here.
// Eg XTS-AES-256 uses two 256 bit keys.
return shared_ptr<Cipher>( new SSL_Cipher(iface, AesXtsInterface,
blockCipher, NULL, 2 * keyLen / 8) );
}
static bool AES_XTS_Cipher_registered = Cipher::Register(
"AES_XTS", "Tweakable wide-block cipher",
AesXtsInterface, AesXtsKeyRange, AesXtsBlockRange, NewAesXtsCipher, false);
#endif
class SSLKey : public AbstractCipherKey
{
public:
pthread_mutex_t mutex;
unsigned int keySize; // in bytes
unsigned int ivLength;
// key data is first _keySize bytes,
// followed by iv of _ivLength bytes,
SecureMem buf;
EVP_CIPHER_CTX block_enc;
EVP_CIPHER_CTX block_dec;
EVP_CIPHER_CTX stream_enc;
EVP_CIPHER_CTX stream_dec;
HMAC_CTX mac_ctx;
SSLKey(int keySize, int ivLength);
~SSLKey();
};
SSLKey::SSLKey(int keySize_, int ivLength_)
: buf(keySize_ + ivLength_)
{
rAssert(keySize_ >= 8);
rAssert(ivLength_ >= 8);
this->keySize = keySize_;
this->ivLength = ivLength_;
pthread_mutex_init( &mutex, 0 );
}
SSLKey::~SSLKey()
{
keySize = 0;
ivLength = 0;
EVP_CIPHER_CTX_cleanup( &block_enc );
EVP_CIPHER_CTX_cleanup( &block_dec );
EVP_CIPHER_CTX_cleanup( &stream_enc );
EVP_CIPHER_CTX_cleanup( &stream_dec );
HMAC_CTX_cleanup( &mac_ctx );
pthread_mutex_destroy( &mutex );
}
inline unsigned char* KeyData( const shared_ptr<SSLKey> &key )
{
return (unsigned char *)key->buf.data;
}
inline unsigned char* IVData( const shared_ptr<SSLKey> &key )
{
return (unsigned char *)key->buf.data + key->keySize;
}
void initKey(const shared_ptr<SSLKey> &key, const EVP_CIPHER *_blockCipher,
const EVP_CIPHER *_streamCipher, int _keySize)
{
Lock lock( key->mutex );
// initialize the cipher context once so that we don't have to do it for
// every block..
EVP_CIPHER_CTX_init( &key->block_enc );
EVP_CIPHER_CTX_init( &key->block_dec );
EVP_EncryptInit_ex( &key->block_enc, _blockCipher, NULL, NULL, NULL);
EVP_DecryptInit_ex( &key->block_dec, _blockCipher, NULL, NULL, NULL);
EVP_CIPHER_CTX_set_key_length( &key->block_enc, _keySize );
EVP_CIPHER_CTX_set_key_length( &key->block_dec, _keySize );
EVP_CIPHER_CTX_set_padding( &key->block_enc, 0 );
EVP_CIPHER_CTX_set_padding( &key->block_dec, 0 );
EVP_EncryptInit_ex( &key->block_enc, NULL, NULL, KeyData(key), NULL);
EVP_DecryptInit_ex( &key->block_dec, NULL, NULL, KeyData(key), NULL);
EVP_CIPHER_CTX_init( &key->stream_enc );
EVP_CIPHER_CTX_init( &key->stream_dec );
if (_streamCipher != NULL)
{
EVP_EncryptInit_ex( &key->stream_enc, _streamCipher, NULL, NULL, NULL);
EVP_DecryptInit_ex( &key->stream_dec, _streamCipher, NULL, NULL, NULL);
EVP_CIPHER_CTX_set_key_length( &key->stream_enc, _keySize );
EVP_CIPHER_CTX_set_key_length( &key->stream_dec, _keySize );
EVP_CIPHER_CTX_set_padding( &key->stream_enc, 0 );
EVP_CIPHER_CTX_set_padding( &key->stream_dec, 0 );
EVP_EncryptInit_ex( &key->stream_enc, NULL, NULL, KeyData(key), NULL);
EVP_DecryptInit_ex( &key->stream_dec, NULL, NULL, KeyData(key), NULL);
}
HMAC_CTX_init( &key->mac_ctx );
HMAC_Init_ex( &key->mac_ctx, KeyData(key), _keySize, EVP_sha1(), 0 );
}
SSL_Cipher::SSL_Cipher(const Interface &iface_,
const Interface &realIface_,
const EVP_CIPHER *blockCipher,
const EVP_CIPHER *streamCipher,
int keySize_)
{
this->iface = iface_;
this->realIface = realIface_;
this->_blockCipher = blockCipher;
this->_streamCipher = streamCipher;
this->_keySize = keySize_;
this->_ivLength = EVP_CIPHER_iv_length( _blockCipher );
rAssert(_ivLength == 8 || _ivLength == 16);
rAssert(_ivLength <= _keySize);
VLOG(1) << "allocated cipher " << iface.name()
<< ", keySize " << _keySize
<< ", ivlength " << _ivLength;
// EVP_CIPHER_key_length isn't useful for variable-length ciphers like
// Blowfish. Version 1 relied upon it incorrectly.
if( (EVP_CIPHER_key_length( _blockCipher ) != (int )_keySize)
&& iface.major() == 1)
{
LOG(WARNING) << "Running in backward compatibilty mode for 1.0 - \n"
<< "key is really " << EVP_CIPHER_key_length( _blockCipher ) * 8
<< " bits, not " << _keySize * 8;
}
}
SSL_Cipher::~SSL_Cipher()
{
}
Interface SSL_Cipher::interface() const
{
return realIface;
}
/*
Create a key from the password.
Use SHA to distribute entropy from the password into the key.
This algorithm must remain constant for backward compatibility, as this key
is used to encipher/decipher the master key.
*/
CipherKey SSL_Cipher::newKey(const char *password, int passwdLength,
int &iterationCount, long desiredDuration,
const unsigned char *salt, int saltLen)
{
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
if(iterationCount == 0)
{
// timed run, fills in iteration count
int res = TimedPBKDF2(password, passwdLength,
salt, saltLen,
_keySize+_ivLength, KeyData(key),
1000 * desiredDuration);
if(res <= 0)
{
LOG(ERROR) << "openssl error, PBKDF2 failed";
return CipherKey();
} else
iterationCount = res;
} else
{
// known iteration length
if(PKCS5_PBKDF2_HMAC_SHA1(
password, passwdLength,
const_cast<unsigned char*>(salt), saltLen,
iterationCount, _keySize + _ivLength, KeyData(key)) != 1)
{
LOG(ERROR) << "openssl error, PBKDF2 failed";
return CipherKey();
}
}
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
CipherKey SSL_Cipher::newKey(const char *password, int passwdLength)
{
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
int bytes = 0;
if( iface.major() > 1 )
{
// now we use BytesToKey, which can deal with Blowfish keys larger then
// 128 bits.
bytes = BytesToKey( _keySize, _ivLength, EVP_sha1(),
(unsigned char *)password, passwdLength, 16,
KeyData(key), IVData(key) );
// the reason for moving from EVP_BytesToKey to BytesToKey function..
if(bytes != (int)_keySize)
{
LOG(WARNING) << "newKey: BytesToKey returned " << bytes
<< ", expecting " << _keySize << " key bytes";
}
} else
{
// for backward compatibility with filesystems created with 1:0
bytes = EVP_BytesToKey( _blockCipher, EVP_sha1(), NULL,
(unsigned char *)password, passwdLength, 16,
KeyData(key), IVData(key) );
}
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
/*
Create a random key.
We use the OpenSSL library to generate random bytes, then take the hash of
those bytes to use as the key.
This algorithm can change at any time without affecting backward
compatibility.
*/
CipherKey SSL_Cipher::newRandomKey()
{
const int bufLen = MAX_KEYLENGTH;
unsigned char tmpBuf[ bufLen ];
int saltLen = 20;
unsigned char saltBuf[ saltLen ];
if(!randomize(tmpBuf, bufLen, true) ||
!randomize(saltBuf, saltLen, true))
return CipherKey();
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
// doesn't need to be versioned, because a random key is a random key..
// Doesn't need to be reproducable..
if(PKCS5_PBKDF2_HMAC_SHA1((char*)tmpBuf, bufLen, saltBuf, saltLen,
1000, _keySize + _ivLength, KeyData(key)) != 1)
{
LOG(ERROR) << "openssl error, PBKDF2 failed";
return CipherKey();
}
OPENSSL_cleanse(tmpBuf, bufLen);
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
/*
Compute a 64-bit check value for the data using HMAC.
*/
static uint64_t _checksum_64(SSLKey *key,
const unsigned char *data,
int dataLen,
uint64_t *chainedIV)
{
rAssert( dataLen > 0 );
Lock lock( key->mutex );
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdLen = EVP_MAX_MD_SIZE;
HMAC_Init_ex( &key->mac_ctx, 0, 0, 0, 0 );
HMAC_Update( &key->mac_ctx, data, dataLen );
if(chainedIV)
{
// toss in the chained IV as well
uint64_t tmp = *chainedIV;
unsigned char h[8];
for(unsigned int i=0; i<8; ++i)
{
h[i] = tmp & 0xff;
tmp >>= 8;
}
HMAC_Update( &key->mac_ctx, h, 8 );
}
HMAC_Final( &key->mac_ctx, md, &mdLen );
rAssert(mdLen >= 8);
// chop this down to a 64bit value..
unsigned char h[8] = {0,0,0,0,0,0,0,0};
for(unsigned int i=0; i<(mdLen-1); ++i)
h[i%8] ^= (unsigned char)(md[i]);
uint64_t value = (uint64_t)h[0];
for(int i=1; i<8; ++i)
value = (value << 8) | (uint64_t)h[i];
return value;
}
bool SSL_Cipher::randomize( unsigned char *buf, int len,
bool strongRandom ) const
{
// to avoid warnings of uninitialized data from valgrind
memset(buf, 0, len);
int result;
if(strongRandom)
result = RAND_bytes( buf, len );
else
result = RAND_pseudo_bytes( buf, len );
if(result != 1)
{
char errStr[120]; // specs require string at least 120 bytes long..
unsigned long errVal = 0;
if((errVal = ERR_get_error()) != 0)
LOG(ERROR) << "openssl error: " << ERR_error_string( errVal, errStr );
return false;
} else
return true;
}
uint64_t SSL_Cipher::MAC_64( const unsigned char *data, int len,
const CipherKey &key, uint64_t *chainedIV ) const
{
shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(key);
uint64_t tmp = _checksum_64( mk.get(), data, len, chainedIV );
if(chainedIV)
*chainedIV = tmp;
return tmp;
}
CipherKey SSL_Cipher::readKey(const unsigned char *data,
const CipherKey &masterKey, bool checkKey)
{
shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(masterKey);
rAssert(mk->keySize == _keySize);
unsigned char tmpBuf[ 2 * MAX_KEYLENGTH ];
// First N bytes are checksum bytes.
unsigned int checksum = 0;
for(int i=0; i<KEY_CHECKSUM_BYTES; ++i)
checksum = (checksum << 8) | (unsigned int)data[i];
if (_streamCipher != NULL)
{
memcpy( tmpBuf, data+KEY_CHECKSUM_BYTES, _keySize + _ivLength );
streamDecode(tmpBuf, _keySize + _ivLength, checksum, masterKey);
} else
{
memcpy( tmpBuf, data+KEY_CHECKSUM_BYTES, 2 * _keySize );
blockDecode(tmpBuf, 2 * _keySize, checksum, masterKey);
}
// check for success
unsigned int checksum2 = MAC_32( tmpBuf, _keySize + _ivLength, masterKey );
if(checksum2 != checksum && checkKey)
{
VLOG(1) << "checksum mismatch: expected " << checksum
<< ", got " << checksum2
<< "on decode of " << _keySize + _ivLength << " bytes";
OPENSSL_cleanse(tmpBuf, sizeof(tmpBuf));
return CipherKey();
}
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
rAssert(_keySize + _ivLength == (unsigned int)key->buf.size );
memcpy( key->buf.data, tmpBuf, key->buf.size );
OPENSSL_cleanse(tmpBuf, sizeof(tmpBuf));
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
void SSL_Cipher::writeKey(const CipherKey &ckey, unsigned char *data,
const CipherKey &masterKey)
{
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(masterKey);
rAssert(mk->keySize == _keySize);
rAssert(mk->ivLength == _ivLength);
unsigned char tmpBuf[ 2 * MAX_KEYLENGTH ];
unsigned int bufLen = key->buf.size;
rAssert(_keySize + _ivLength == bufLen );
memcpy( tmpBuf, key->buf.data, bufLen );
unsigned int checksum = MAC_32( tmpBuf, bufLen, masterKey );
if (_streamCipher != NULL)
streamEncode(tmpBuf, bufLen, checksum, masterKey);
else
{
bufLen = 2 * _keySize;
blockEncode(tmpBuf, bufLen, checksum, masterKey);
}
memcpy( data+KEY_CHECKSUM_BYTES, tmpBuf, bufLen );
// first N bytes contain HMAC derived checksum..
for(int i=1; i<=KEY_CHECKSUM_BYTES; ++i)
{
data[KEY_CHECKSUM_BYTES-i] = checksum & 0xff;
checksum >>= 8;
}
OPENSSL_cleanse(tmpBuf, sizeof(tmpBuf));
}
bool SSL_Cipher::compareKey( const CipherKey &A, const CipherKey &B) const
{
shared_ptr<SSLKey> key1 = dynamic_pointer_cast<SSLKey>(A);
shared_ptr<SSLKey> key2 = dynamic_pointer_cast<SSLKey>(B);
rAssert(key1->buf.size == key2->buf.size);
if(memcmp(key1->buf.data, key2->buf.data, key1->buf.size) != 0)
return false;
else
return true;
}
int SSL_Cipher::encodedKeySize() const
{
if (_streamCipher != NULL)
return _keySize + _ivLength + KEY_CHECKSUM_BYTES;
else
return 2 * _keySize + KEY_CHECKSUM_BYTES;
}
int SSL_Cipher::keySize() const
{
return _keySize;
}
int SSL_Cipher::cipherBlockSize() const
{
int size = EVP_CIPHER_block_size( _blockCipher );
// OpenSSL (1.0.1-4ubuntu5.5) reports a block size of 1 for aes_xts.
// If this happens, use a single key width (ie 32 bytes for aes-xts-256).
if (size == 1)
size = _keySize / 2;
return size;
}
void SSL_Cipher::setIVec(unsigned char *ivec, uint64_t seed,
const shared_ptr<SSLKey> &key) const
{
if (iface.major() >= 3)
{
memcpy( ivec, IVData(key), _ivLength );
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdLen = EVP_MAX_MD_SIZE;
for(int i=0; i<8; ++i)
{
md[i] = (unsigned char)(seed & 0xff);
seed >>= 8;
}
// combine ivec and seed with HMAC
HMAC_Init_ex( &key->mac_ctx, 0, 0, 0, 0 );
HMAC_Update( &key->mac_ctx, ivec, _ivLength );
HMAC_Update( &key->mac_ctx, md, 8 );
HMAC_Final( &key->mac_ctx, md, &mdLen );
rAssert(mdLen >= _ivLength);
memcpy( ivec, md, _ivLength );
} else
{
setIVec_old(ivec, seed, key);
}
}
// Deprecated: For backward compatibility only.
// A watermark attack was discovered against this IV setup. If an attacker
// could get a victim to store a carefully crafted file, they could later
// determine if the victim had the file in encrypted storage (without decrypting
// the file).
void SSL_Cipher::setIVec_old(unsigned char *ivec,
unsigned int seed,
const shared_ptr<SSLKey> &key) const
{
unsigned int var1 = 0x060a4011 * seed;
unsigned int var2 = 0x0221040d * (seed ^ 0xD3FEA11C);
memcpy( ivec, IVData(key), _ivLength );
ivec[0] ^= (var1 >> 24) & 0xff;
ivec[1] ^= (var2 >> 16) & 0xff;
ivec[2] ^= (var1 >> 8 ) & 0xff;
ivec[3] ^= (var2 ) & 0xff;
ivec[4] ^= (var2 >> 24) & 0xff;
ivec[5] ^= (var1 >> 16) & 0xff;
ivec[6] ^= (var2 >> 8 ) & 0xff;
ivec[7] ^= (var1 ) & 0xff;
if(_ivLength > 8)
{
ivec[8+0] ^= (var1 ) & 0xff;
ivec[8+1] ^= (var2 >> 8 ) & 0xff;
ivec[8+2] ^= (var1 >> 16) & 0xff;
ivec[8+3] ^= (var2 >> 24) & 0xff;
ivec[8+4] ^= (var1 >> 24) & 0xff;
ivec[8+5] ^= (var2 >> 16) & 0xff;
ivec[8+6] ^= (var1 >> 8 ) & 0xff;
ivec[8+7] ^= (var2 ) & 0xff;
}
}
static void flipBytes(unsigned char *buf, int size)
{
unsigned char revBuf[64];
int bytesLeft = size;
while(bytesLeft)
{
int toFlip = MIN( (int)sizeof(revBuf), bytesLeft );
for(int i=0; i<toFlip; ++i)
revBuf[i] = buf[toFlip - (i+1)];
memcpy( buf, revBuf, toFlip );
bytesLeft -= toFlip;
buf += toFlip;
}
memset(revBuf, 0, sizeof(revBuf));
}
static void shuffleBytes(unsigned char *buf, int size)
{
for(int i=0; i<size-1; ++i)
buf[i+1] ^= buf[i];
}
static void unshuffleBytes(unsigned char *buf, int size)
{
for(int i=size-1; i; --i)
buf[i] ^= buf[i-1];
}
/* Partial blocks are encoded with a stream cipher. We make multiple passes on
the data to ensure that the ends of the data depend on each other.
*/
bool SSL_Cipher::streamEncode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &ckey) const
{
rAssert( size > 0 );
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
rAssert( key->stream_enc.key_len > 0 );
Lock lock( key->mutex );
unsigned char ivec[ MAX_IVLENGTH ];
int dstLen=0, tmpLen=0;
shuffleBytes( buf, size );
setIVec( ivec, iv64, key );
EVP_EncryptInit_ex( &key->stream_enc, NULL, NULL, NULL, ivec);
EVP_EncryptUpdate( &key->stream_enc, buf, &dstLen, buf, size );
EVP_EncryptFinal_ex( &key->stream_enc, buf+dstLen, &tmpLen );
flipBytes( buf, size );
shuffleBytes( buf, size );
setIVec( ivec, iv64 + 1, key );
EVP_EncryptInit_ex( &key->stream_enc, NULL, NULL, NULL, ivec);
EVP_EncryptUpdate( &key->stream_enc, buf, &dstLen, buf, size );
EVP_EncryptFinal_ex( &key->stream_enc, buf+dstLen, &tmpLen );
dstLen += tmpLen;
LOG_IF(ERROR, dstLen != size) << "encoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return true;
}
bool SSL_Cipher::streamDecode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &ckey) const
{
rAssert( size > 0 );
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
rAssert( key->stream_dec.key_len > 0 );
Lock lock( key->mutex );
unsigned char ivec[ MAX_IVLENGTH ];
int dstLen=0, tmpLen=0;
setIVec( ivec, iv64 + 1, key );
EVP_DecryptInit_ex( &key->stream_dec, NULL, NULL, NULL, ivec);
EVP_DecryptUpdate( &key->stream_dec, buf, &dstLen, buf, size );
EVP_DecryptFinal_ex( &key->stream_dec, buf+dstLen, &tmpLen );
unshuffleBytes( buf, size );
flipBytes( buf, size );
setIVec( ivec, iv64, key );
EVP_DecryptInit_ex( &key->stream_dec, NULL, NULL, NULL, ivec);
EVP_DecryptUpdate( &key->stream_dec, buf, &dstLen, buf, size );
EVP_DecryptFinal_ex( &key->stream_dec, buf+dstLen, &tmpLen );
unshuffleBytes( buf, size );
dstLen += tmpLen;
LOG_IF(ERROR, dstLen != size) << "encoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return true;
}
bool SSL_Cipher::blockEncode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &ckey ) const
{
rAssert( size > 0 );
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
// data must be integer number of blocks
const int blockMod = size % EVP_CIPHER_CTX_block_size( &key->block_enc );
rAssert(blockMod == 0);
Lock lock( key->mutex );
unsigned char ivec[ MAX_IVLENGTH ];
int dstLen = 0, tmpLen = 0;
setIVec( ivec, iv64, key );
EVP_EncryptInit_ex( &key->block_enc, NULL, NULL, NULL, ivec);
EVP_EncryptUpdate( &key->block_enc, buf, &dstLen, buf, size );
EVP_EncryptFinal_ex( &key->block_enc, buf+dstLen, &tmpLen );
dstLen += tmpLen;
LOG_IF(ERROR, dstLen != size) << "encoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return true;
}
bool SSL_Cipher::blockDecode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &ckey ) const
{
rAssert( size > 0 );
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
// data must be integer number of blocks
const int blockMod = size % EVP_CIPHER_CTX_block_size( &key->block_dec );
rAssert(blockMod == 0);
Lock lock( key->mutex );
unsigned char ivec[ MAX_IVLENGTH ];
int dstLen = 0, tmpLen = 0;
setIVec( ivec, iv64, key );
EVP_DecryptInit_ex( &key->block_dec, NULL, NULL, NULL, ivec);
EVP_DecryptUpdate( &key->block_dec, buf, &dstLen, buf, size );
EVP_DecryptFinal_ex( &key->block_dec, buf+dstLen, &tmpLen );
dstLen += tmpLen;
LOG_IF(ERROR, dstLen != size) << "decoding " << size
<< " bytes, got back " << dstLen << " (" << tmpLen << " in final_ex)";
return true;
}
bool SSL_Cipher::Enabled()
{
return true;
}
bool SSL_Cipher::hasStreamMode() const
{
return false;
}

159
cipher/SSL_Cipher.h Normal file
View File

@ -0,0 +1,159 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _SSL_Cipher_incl_
#define _SSL_Cipher_incl_
#include "cipher/Cipher.h"
#include "base/Interface.h"
class SSLKey;
#ifndef EVP_CIPHER
struct evp_cipher_st;
typedef struct evp_cipher_st EVP_CIPHER;
#endif
/*
Implements Cipher interface for OpenSSL's ciphers.
Design:
Variable algorithm, key size, and block size.
Partial blocks, keys, and names are encrypted using the cipher in a pseudo
stream mode (CFB).
Keys are encrypted with 2-4 (KEY_CHECKSUM_BYTES define) checksum bytes
derived from an HMAC over both they key data and the initial value vector
associated with the key. This allows a good chance at detecting an
incorrect password when we try and decrypt the master key.
File names are encrypted in the same way, with 2 checksum bytes derived
from an HMAC over the filename. This is done not to allow checking the
results, but to make the output much more random. Changing one letter in a
filename should result in a completely different encrypted filename, to
help frustrate any attempt to guess information about files from their
encrypted names.
Stream encryption involves two encryption passes over the data, implemented
as:
1. shuffle
2. encrypt
3. reverse
4. shuffle
5. encrypt
The reason for the shuffle and reverse steps (and the second encrypt pass)
is to try and propogate any changed bits to a larger set. If only a single
pass was made with the stream cipher in CFB mode, then a change to one byte
may only affect one byte of output, allowing some XOR attacks.
The shuffle/encrypt is used as above in filename encryption as well,
although it is not necessary as they have checksum bytes which augment the
initial value vector to randomize the output. But it makes the code
simpler to reuse the encryption algorithm as is.
*/
class SSL_Cipher : public Cipher
{
Interface iface;
Interface realIface;
const EVP_CIPHER *_blockCipher;
const EVP_CIPHER *_streamCipher;
unsigned int _keySize; // in bytes
unsigned int _ivLength;
public:
SSL_Cipher(const Interface &iface, const Interface &realIface,
const EVP_CIPHER *blockCipher, const EVP_CIPHER *streamCipher,
int keyLength);
virtual ~SSL_Cipher();
// returns the real interface, not the one we're emulating (if any)..
virtual Interface interface() const;
// create a new key based on a password
virtual CipherKey newKey(const char *password, int passwdLength,
int &iterationCount, long desiredDuration,
const unsigned char *salt, int saltLen);
// deprecated - for backward compatibility
virtual CipherKey newKey(const char *password, int passwdLength);
// create a new random key
virtual CipherKey newRandomKey();
// data must be len keySize()
virtual CipherKey readKey(const unsigned char *data,
const CipherKey &encodingKey,
bool checkKey);
virtual void writeKey(const CipherKey &key, unsigned char *data,
const CipherKey &encodingKey);
virtual bool compareKey( const CipherKey &A,
const CipherKey &B ) const;
// meta-data about the cypher
virtual int keySize() const;
virtual int encodedKeySize() const;
virtual int cipherBlockSize() const;
virtual bool hasStreamMode() const;
virtual bool randomize( unsigned char *buf, int len,
bool strongRandom ) const;
virtual uint64_t MAC_64( const unsigned char *src, int len,
const CipherKey &key, uint64_t *augment ) const;
// functional interfaces
/*
Stream encoding in-place.
*/
virtual bool streamEncode(unsigned char *in, int len,
uint64_t iv64, const CipherKey &key) const;
virtual bool streamDecode(unsigned char *in, int len,
uint64_t iv64, const CipherKey &key) const;
/*
Block encoding is done in-place. Partial blocks are supported, but
blocks are always expected to begin on a block boundary. See
blockSize().
*/
virtual bool blockEncode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &key) const;
virtual bool blockDecode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &key) const;
// hack to help with static builds
static bool Enabled();
// Password-based key derivation function which determines the
// number of iterations based on a desired execution time (in microseconds).
// Returns the number of iterations applied.
static int TimedPBKDF2(const char *pass, int passLen,
const unsigned char *salt, int saltLen,
int keyLen, unsigned char *out,
long desiredPDFTimeMicroseconds);
private:
void setIVec( unsigned char *ivec, uint64_t seed,
const shared_ptr<SSLKey> &key ) const;
// deprecated - for backward compatibility
void setIVec_old( unsigned char *ivec, unsigned int seed,
const shared_ptr<SSLKey> &key ) const;
};
#endif

109
cipher/openssl.cpp Normal file
View File

@ -0,0 +1,109 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2007, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "cipher/openssl.h"
#include <pthread.h>
#include <glog/logging.h>
#define NO_DES
#include <openssl/ssl.h>
#include <openssl/rand.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif
unsigned long pthreads_thread_id()
{
return (unsigned long)pthread_self();
}
static pthread_mutex_t *crypto_locks = NULL;
void pthreads_locking_callback( int mode, int n,
const char *caller_file, int caller_line )
{
(void)caller_file;
(void)caller_line;
if(!crypto_locks)
{
VLOG(1) << "Allocating " << CRYPTO_num_locks() << " locks for OpenSSL";
crypto_locks = new pthread_mutex_t[ CRYPTO_num_locks() ];
for(int i=0; i<CRYPTO_num_locks(); ++i)
pthread_mutex_init( crypto_locks+i, 0 );
}
if(mode & CRYPTO_LOCK)
{
pthread_mutex_lock( crypto_locks + n );
} else
{
pthread_mutex_unlock( crypto_locks + n );
}
}
void pthreads_locking_cleanup()
{
if(crypto_locks)
{
for(int i=0; i<CRYPTO_num_locks(); ++i)
pthread_mutex_destroy( crypto_locks+i );
delete[] crypto_locks;
crypto_locks = NULL;
}
}
void openssl_init(bool threaded)
{
// initialize the SSL library
SSL_load_error_strings();
SSL_library_init();
unsigned int randSeed = 0;
RAND_bytes( (unsigned char*)&randSeed, sizeof(randSeed) );
srand( randSeed );
#ifndef OPENSSL_NO_ENGINE
/* Load all bundled ENGINEs into memory and make them visible */
ENGINE_load_builtin_engines();
/* Register all of them for every algorithm they collectively implement */
ENGINE_register_all_complete();
#endif // NO_ENGINE
if(threaded)
{
// provide locking functions to OpenSSL since we'll be running with
// threads accessing openssl in parallel.
CRYPTO_set_id_callback( pthreads_thread_id );
CRYPTO_set_locking_callback( pthreads_locking_callback );
}
}
void openssl_shutdown(bool threaded)
{
#ifndef OPENSSL_NO_ENGINE
ENGINE_cleanup();
#endif
if(threaded)
pthreads_locking_cleanup();
}

View File

@ -47,7 +47,7 @@ static const char rcsid[] = "$OpenBSD: readpassphrase.c,v 1.12 2001/12/15 05:41:
#include <cctype>
#include <termios.h>
#include <readpassphrase.h>
#include "cipher/readpassphrase.h"
#ifdef TCSASOFT
# define _T_FLUSH (TCSAFLUSH|TCSASOFT)

1
configure vendored Normal file
View File

@ -0,0 +1 @@
cmake . -DCMAKE_BUILD_TYPE=Release $@

View File

@ -1,235 +0,0 @@
dnl Process this file with autoconf to produce a configure script.
AC_INIT(encfs/encfs.h) dnl a source file from your sub dir
AC_CONFIG_AUX_DIR([build-aux])
AM_INIT_AUTOMAKE(encfs, 1.8.0) dnl searches for some needed programs
AC_CONFIG_MACRO_DIR([m4])
AC_CANONICAL_HOST
AM_CONDITIONAL([DARWIN],
[case $host_os in darwin*) true;; *) false;; esac])
dnl without this order in this file, automake will be confused!
dnl
AM_CONFIG_HEADER(config.h)
dnl This ksh/zsh feature conflicts with `cd blah ; pwd`
unset CDPATH
AC_LANG_CPLUSPLUS
AC_PROG_CXX
dnl almost the same like KDE_SET_PEFIX but the path is /usr/local
dnl
unset CDPATH
dnl make /usr/local the default for the installation
AC_PREFIX_DEFAULT(/usr/local)
AM_GNU_GETTEXT([external])
AM_GNU_GETTEXT_VERSION([0.17])
dnl AC_LIB_LINKFLAGS([asprintf]) # use internal copy
LIBINTL=-lgettextlib
dnl create only shared libtool-libraries
dnl These can be overridden by command line arguments
AC_ENABLE_SHARED(yes)
AC_ENABLE_STATIC(no)
AM_CONDITIONAL( BUILD_STATIC, test "x$enable_static" = "xyes" )
dnl only build either static or shared, not both..
if test "x$enable_static" = "xyes"; then
enable_shared=no
AC_DEFINE(BUILD_STATIC, [1], [Building static library])
fi
AC_PROG_LIBTOOL
AX_PTHREAD
AC_CHECK_HEADERS([tr1/memory tr1/unordered_map tr1/unordered_set tr1/tuple])
dnl Need to include any user specified flags in the tests below, as they might
dnl specify required include directories..
FUSE_FLAGS="-D_FILE_OFFSET_BITS=64 -DFUSE_USE_VERSION=26"
CPPFLAGS="$CPPFLAGS $USER_INCLUDES $FUSE_FLAGS -D__STDC_FORMAT_MACROS"
CXXFLAGS="$CXXFLAGS $PTHREAD_CFLAGS $USER_INCLUDES"
LDFLAGS="$LDFLAGS $PTHREAD_LIBS $USER_LDFLAGS $FUSE_LIBS"
dnl Look for fuse headers.
AX_EXT_HAVE_HEADER(fuse.h, /usr/include/fuse /usr/local/include/fuse \
/opt/include/fuse /opt/local/include/fuse \
/usr/include/osxfuse /usr/local/include/osxfuse)
dnl Ensure the necessary paths are added to LDPATH
AX_EXT_HAVE_LIB(/usr/lib /usr/local/lib /opt/lib /opt/local/lib, fuse,
fuse_new, [])
AX_EXT_HAVE_LIB(/usr/lib /usr/local/lib /opt/lib /opt/local/lib, osxfuse,
fuse_new, [])
if test "$GXX" = "yes"; then
CXXFLAGS="-W -Wall -Wpointer-arith -Wwrite-strings $CXXFLAGS"
dnl CXXFLAGS="$CXXFLAGS -Wformat=2 -Wconversion"
fi
if test -z "${DARWIN_TRUE}"; then
dnl Prefer OSXFuse, but fall back to libfuse.
AC_CHECK_LIB(osxfuse, fuse_new, [FUSE_LIBS="$FUSE_LIBS -losxfuse"],
AC_CHECK_LIB(fuse, fuse_new, [FUSE_LIBS="$FUSE_LIBS -lfuse"],
AC_MSG_ERROR([Unable to find libfuse or libosxfuse.])))
else
AC_CHECK_LIB(fuse, fuse_new, [FUSE_LIBS="$FUSE_LIBS -lfuse"],
AC_MSG_ERROR([Unable to find libfuse.]))
fi
# check for a supported FUSE_MAJOR_VERSION.
AC_MSG_CHECKING([For supported FUSE API version])
AC_RUN_IFELSE([
AC_LANG_PROGRAM([[#include "fuse.h"]],
[[
if(FUSE_MAJOR_VERSION == 2 && FUSE_MINOR_VERSION >= 5)
{
return 0;
} else
return -1;
]])],
[AC_MSG_RESULT([yes])],
[AC_MSG_RESULT([no])
AC_MSG_FAILURE([
Encfs 1.3 requires FUSE 2.5 or newer. Please check config.log for errors. If
you cannot determine the problem, mail encfs-users@lists.sourceforge.net
and include the config.log file])
]
)
dnl fuse_operations.setxattr was added 2004-03-31
dnl only enable it if setxattr function is found..
AC_CHECK_HEADERS([attr/xattr.h sys/xattr.h])
dnl xattr functions take additional arguments on some systems (eg Darwin).
AC_CACHE_CHECK([whether xattr interface takes additional options],
smb_attr_cv_xattr_add_opt, [
old_LIBS=$LIBS
LIBS="$LIBS $ACL_LIBS"
AC_TRY_COMPILE([
#include <sys/types.h>
#if HAVE_ATTR_XATTR_H
#include <attr/xattr.h>
#elif HAVE_SYS_XATTR_H
#include <sys/xattr.h>
#endif
],[
getxattr(0, 0, 0, 0, 0, 0);
],
[smb_attr_cv_xattr_add_opt=yes],
[smb_attr_cv_xattr_add_opt=no;LIBS=$old_LIBS])
])
if test x"$smb_attr_cv_xattr_add_opt" = x"yes"; then
AC_DEFINE(XATTR_ADD_OPT, 1, [xattr functions have additional options])
fi
dnl Check for valgrind headers..
AC_ARG_ENABLE(valgrind,
AC_HELP_STRING([--enable-valgrind],
[build with valgrind support.]),
AC_CHECK_HEADERS([valgrind/valgrind.h valgrind/memcheck.h])
)
# allow user option of not using ssl..
AC_ARG_ENABLE(openssl,
AC_HELP_STRING([--disable-openssl],
[disables openssl library usage.]),
with_openssl=$enableval, with_openssl="yes" )
# try checking for openssl using
if test "x$with_openssl" = "xyes"; then
# look for openssl using pkg-config first..
PKG_CHECK_MODULES(OPENSSL, openssl >= 0.9.7,
with_openssl="yes", with_openssl="old-test")
# If that fails, try checking via old methods - which isn't as robust when
# it comes to extra include paths, etc..
if test "x$with_openssl" = "xold-test"; then
AC_CHECK_HEADER(openssl/ssl.h,
AC_CHECK_LIB(ssl,SSL_new,
[with_openssl="yes"]))
OPENSSL_LIBS="-lssl"
fi
# if we have openssl, then examine available interfaces.
if test "x$with_openssl" = "xyes"; then
AC_DEFINE(HAVE_SSL, [1], [Linking with OpenSSL])
# add in the libs just for the test..
oldflags=$CXXFLAGS
oldlibs=$LIBS
CXXFLAGS="$CXXFLAGS $OPENSSL_CFLAGS"
LIBS="$LIBS $OPENSSL_LIBS"
AC_CHECK_FUNCS(EVP_aes_128_cbc EVP_aes_192_cbc EVP_aes_256_cbc,
AC_DEFINE(HAVE_EVP_AES, [1], [Have EVP AES interfaces]))
AC_CHECK_FUNCS(EVP_bf_cbc,
AC_DEFINE(HAVE_EVP_BF, [1], [Have EVP Blowfish interfaces]))
AC_CHECK_FUNCS(EVP_CIPHER_CTX_set_padding,
with_opensslevp=yes,
AC_MSG_WARN([New SSL cipher code only enabled for OpenSSL 0.9.7 or later]))
AC_CHECK_FUNCS(HMAC_Init_ex,
AC_DEFINE(HAVE_HMAC_INIT_EX, [1], [Have HMAC_Init_ex function]))
CXXFLAGS="$oldflags"
LIBS="$oldlibs"
AC_SUBST(HAVE_EVP_AES)
AC_SUBST(HAVE_EVP_BF)
AC_SUBST(HAVE_HMAC_INIT_EX)
fi
fi
AM_CONDITIONAL( BUILD_OPENSSL, test "x$with_openssl" = "xyes" )
AM_CONDITIONAL( BUILD_SSLCIPHER, test "x$with_opensslevp" = "xyes" )
AC_SUBST(HAVE_SSL)
if test "x$with_openssl" != "xyes"; then
AC_MSG_ERROR( [Encfs requires OpenSSL])
fi
# check for RLOG
PKG_CHECK_MODULES(RLOG, librlog >= 1.3, with_rlog="yes", with_rlog="test")
# manual check for rlog, unless environment variable already set
if test "$with_rlog" = "test" && test "x$RLOG_LIBS" = "x"; then
AC_MSG_WARN([Checking for librlog the hard way])
AC_CHECK_LIB(rlog, RLogVersion, [RLOG_LIBS="-lrlog"],
[AC_MSG_ERROR([EncFS depends on librlog])])
fi
# find Protocol Buffers
PKG_CHECK_MODULES(PROTOBUF, protobuf >= 2.0)
AC_PATH_PROG(PROTOC, protoc, [no])
if test "$PROTOC" == "no"; then
AC_MSG_FAILURE([Protocol Buffers compiler 'protoc' is required to build.])
fi
# find TinyXML
AC_LANG_PUSH([C++])
CPPFLAGS="$CPPFLAGS -DTIXML_USE_STL"
AC_CHECK_HEADER([tinyxml.h],,[AC_MSG_ERROR([tinyxml.h not found])])
AC_CHECK_LIB([tinyxml],[main],,
[AC_MSG_ERROR([you must install libtinyxml dev])])
AC_LANG_POP([C++])
# look for pod2man program for building man pages
AC_PATH_PROG(POD2MAN, pod2man, [no])
AC_PATH_PROG(POD2HTML, pod2html, [no])
AM_CONDITIONAL( BUILD_MAN, test "x$POD2MAN" != "xno" )
AM_CONDITIONAL( BUILD_MANHTML, test "x$POD2HTML" != "xno" )
AM_CONDITIONAL( BUILD_NLS, test "x$USE_NLS" != "xno" )
AC_CONFIG_FILES([Makefile] \
[encfs/Makefile] \
[encfs.spec] \
[makedist2.sh] \
[m4/Makefile] \
[po/Makefile.in] \
[po/Makefile])
AC_OUTPUT

2
devmode Normal file
View File

@ -0,0 +1,2 @@
mkdir build
cd build && cmake .. -DCMAKE_BUILD_TYPE=Debug $@

View File

@ -1,207 +0,0 @@
Name: encfs
Summary: Encrypted pass-thru filesystem for Linux
Version: @VERSION@
Release: @RELEASE@
License: GPL
Group: System/Filesystems
Source: %{name}-%{version}-%{release}.tgz
BuildRoot: %{_tmppath}/build-root-%{name}
Packager: Valient Gough <vgough at pobox dot com>
#Distribution: Suse 9.1
Prefix: /usr
Url: http://pobox.com/~vgough/encfs
Provides: encfs
Provides: encfsctl
Provides: libencfs.1
Requires: rlog >= 1.3
Requires: openssl
Requires: fuse >= 2.2
%description
EncFS implements an encrypted filesystem in userspace using FUSE. FUSE
provides a Linux kernel module which allows virtual filesystems to be written
in userspace. EncFS encrypts all data and filenames in the filesystem and
passes access through to the underlying filesystem. Similar to CFS except that
it does not use NFS.
%changelog
* Fri Nov 11 2005 Valient Gough <vgough@pobox.com>
- Release 1.2.5
- Fix race condition when using newer versions of GCC. Fixes problem reported
by Chris at x.nu.
- add encfssh script, thanks to David Rosenstrauch
* Fri Aug 26 2005 Valient Gough <vgough@pobox.com>
- Release 1.2.4
- fix segfault if small invalid filenames were encountered in the encrypted
directory, reported by paulgfx.
- try and detect if user tries to mount the filesystem over the top of the
encrypted directory, problem reported by paulgfx.
- environment variable ENCFS5_CONFIG can be used to override the location of
the .encfs5 configuration file.
- add encfsctl 'export' command, patch from Janne Hellsten
* Tue Apr 19 2005 Valient Gough <vgough@pobox.com>
- Release 1.2.1
- add --public mount option
- add --stdinpass option to read password from stdin for scripting
- import latest rosetta translation updates
* Thu Feb 10 2005 Valient Gough <vgough@pobox.com>
- Release 1.2.0
- Fix bug with MAC headers and files > 2GB, reported by Damian Frank
- Fix bug with external password interface which could result in problems
communicating with external password program. Found by Olivier Dournaux.
- Switch to FUSE 2.2 API -- support for FUSE 1.x has been dropped.
- Add support for inode numbering pass-thru (when used 'use_ino' option to
fuse). This allows encoded filesystem to use the same inode numbers as the
underlying filesystem.
* Wed Jan 12 2005 Valient Gough <vgough@pobox.com>
- Release 1.1.11
- add internationalization support. Thanks to lots of contributors, there are
translations for serveral languages.
- added workaround for libfuse mount failure with FUSE 1.4
- fix compile failure with FUSE 1.4
* Mon Nov 8 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.10
- fix problems with recursive rename
- fix incorrect error codes from xattr functions
* Tue Aug 15 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.9
- fix another rename bug (affected filesystems with 'paranoia' configuration)
* Mon Aug 14 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.8
- Improve MAC block header processing.
* Sat Aug 12 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.7
- fix bug in truncate() for unopened files.
* Mon Aug 9 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.6
- fix header IV creation when truncate() used to create files.
- add support for IV chaining to old 0.x filesystem support code (useful for
systems with old OpenSSL, like RedHat 7.x).
* Tue Jul 22 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.5
* Sat Jul 10 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.4
- add external password prompt support.
* Thu Jun 24 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.3
* Fri May 28 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.2
- Fix bug affecting filesystems with small empty directories (like XFS)
- Updates to recursive rename code to undo all changes on failure.
- Fix OpenSSL dependency path inclusion in build.
* Wed May 19 2004 Valient Gough <vgough@pobox.com>
- Release 1.1.1
- Fix MAC header memory size allocation error.
- Add file rename-while-open support needed for Evolution.
* Thu May 13 2004 Valient Gough <vgough@pobox.com>
- Second release candidate for version 1.1
- Add support for block mode filename encryption.
- Add support for per-file initialization vectors.
- Add support for directory IV chaining for per-directory initialization
vectors.
- Add support for per-block MAC headers for file contents.
- Backward compatibility support dropped for filesystems created by version
0.x. Maintains backward compatible support for versions 1.0.x.
* Sun Apr 4 2004 Valient Gough <vgough@pobox.com>
- Release 1.0.5
- Allow truncate call to extend file (only shrinking was supported)
* Fri Mar 26 2004 Valient Gough <vgough@pobox.com>
- Release 1.0.4
- Large speed improvement.
- Add support for FUSE major version 2 API.
* Thu Mar 18 2004 Valient Gough <vgough@pobox.com>
- Release 1.0.3
- Fix bugs in truncation and padding code.
* Sat Mar 13 2004 Valient Gough <vgough@pobox.com>
- Release 1.0.2
- Use pkg-config to check for OpenSSL and RLog build settings
- Add support for '--' argument to encfs to pass arbitrary options to FUSE /
fusermount.
- Add man pages.
* Tue Mar 2 2004 Valient Gough <vgough@pobox.com>
- Release 1.0.1
- Fix problem with using OpenSSL's EVP_BytesToKey function with variable
key length ciphers like Blowfish, as it would only generate 128 bit keys.
- Some configure script changes to make it possible to use --with-extra-include
configure option to pick up any necessary directories for OpenSSL.
* Fri Feb 27 2004 Valient Gough <vgough@pobox.com>
- Release 1.0
- Added some pre-defined configuration options at startup to make filesystem
creation a bit more user friendly.
* Mon Feb 23 2004 Valient Gough <vgough@pobox.com>
- Merge development branch to mainline. Source modularized to make it easier
to support different algorithms.
- Added encfsctl program which can show information about an encrypted
directory and can change the user password used to store the volume key.
- Added support for AES and BlowFish with user specified keys and block sizes
(when building with OpenSSL >= 0.9.7).
- Backward compatible with old format, but new filesystems store configuration
information in a new format which is not readable by old encfs versions.
* Sat Feb 7 2004 Valient Gough <vgough@pobox.com>
- Improved performance by fixing cache bug which caused cached data to not be
used as often as it could have been. Random seek performance improved by
600% according to Bonnie++ benchmark.
- Fixed bugs preventing files larger then 2GB. Limit should now be around
128GB (untested - I don't have that much drive space). > 2GB also requires
recent version of FUSE module (from Feb 6 or later) and an underlying
filesystem which supports large files.
- Release 0.6
%prep
rm -rf $RPM_BUILD_ROOT
mkdir $RPM_BUILD_ROOT
%setup -q
%build
CFLAGS="$RPM_OPT_FLAGS" CXXFLAGS="$RPM_OPT_FLAGS" \
./configure --enable-debug=no --prefix=%{prefix} --mandir=%{_mandir}
make SED=/usr/bin/sed -j 2
%install
make DESTDIR=$RPM_BUILD_ROOT install-strip
cd $RPM_BUILD_ROOT
find . -type d -fprint $RPM_BUILD_DIR/file.list.%{name}.dirs
find . -type f -fprint $RPM_BUILD_DIR/file.list.%{name}.files.tmp
sed '/\/man\//s/$/.gz/g' $RPM_BUILD_DIR/file.list.%{name}.files.tmp > $RPM_BUILD_DIR/file.list.%{name}.files
find . -type l -fprint $RPM_BUILD_DIR/file.list.%{name}.libs
sed '1,2d;s,^\.,\%attr(-\,root\,root) \%dir ,' $RPM_BUILD_DIR/file.list.%{name}.dirs > $RPM_BUILD_DIR/file.list.%{name}
sed 's,^\.,\%attr(-\,root\,root) ,' $RPM_BUILD_DIR/file.list.%{name}.files >> $RPM_BUILD_DIR/file.list.%{name}
sed 's,^\.,\%attr(-\,root\,root) ,' $RPM_BUILD_DIR/file.list.%{name}.libs >> $RPM_BUILD_DIR/file.list.%{name}
%clean
case "$RPM_BUILD_ROOT" in build-root-*) rm -rf $RPM_BUILD_ROOT ;; esac
rm -f $RPM_BUILD_DIR/file.list.%{name}
rm -f $RPM_BUILD_DIR/file.list.%{name}.libs
rm -f $RPM_BUILD_DIR/file.list.%{name}.files
rm -f $RPM_BUILD_DIR/file.list.%{name}.files.tmp
rm -f $RPM_BUILD_DIR/file.list.%{name}.dirs
%files -f ../file.list.%{name}
%defattr(-,root,root,0755)

View File

@ -1,433 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BlockFileIO.h"
#include "MemoryPool.h"
#include "config.pb.h"
#include <cstring>
#include <rlog/rlog.h>
#include "i18n.h"
template<typename Type>
inline Type min( Type A, Type B )
{
return (B < A) ? B : A;
}
static void clearCache( IORequest &req, int blockSize )
{
memset( req.data, 0, blockSize );
req.dataLen = 0;
}
BlockFileIO::BlockFileIO( int blockSize, const FSConfigPtr &cfg )
: _blockSize( blockSize )
, _allowHoles( cfg->config->allow_holes() )
{
rAssert( _blockSize > 1 );
_cache.data = new unsigned char [ _blockSize ];
}
BlockFileIO::~BlockFileIO()
{
clearCache( _cache, _blockSize );
delete[] _cache.data;
}
ssize_t BlockFileIO::cacheReadOneBlock( const IORequest &req ) const
{
// we can satisfy the request even if _cache.dataLen is too short, because
// we always request a full block during reads..
if((req.offset == _cache.offset) && (_cache.dataLen != 0))
{
// satisfy request from cache
int len = req.dataLen;
if(_cache.dataLen < len)
len = _cache.dataLen;
memcpy( req.data, _cache.data, len );
return len;
} else
{
if(_cache.dataLen > 0)
clearCache( _cache, _blockSize );
// cache results of read -- issue reads for full blocks
IORequest tmp;
tmp.offset = req.offset;
tmp.data = _cache.data;
tmp.dataLen = _blockSize;
ssize_t result = readOneBlock( tmp );
if(result > 0)
{
_cache.offset = req.offset;
_cache.dataLen = result; // the amount we really have
if(result > req.dataLen)
result = req.dataLen; // only as much as requested
memcpy( req.data, _cache.data, result );
}
return result;
}
}
bool BlockFileIO::cacheWriteOneBlock( const IORequest &req )
{
// cache results of write (before pass-thru, because it may be modified
// in-place)
memcpy( _cache.data, req.data, req.dataLen );
_cache.offset = req.offset;
_cache.dataLen = req.dataLen;
bool ok = writeOneBlock( req );
if(!ok)
clearCache( _cache, _blockSize );
return ok;
}
ssize_t BlockFileIO::read( const IORequest &req ) const
{
rAssert( _blockSize != 0 );
int partialOffset = req.offset % _blockSize;
off_t blockNum = req.offset / _blockSize;
ssize_t result = 0;
if(partialOffset == 0 && req.dataLen <= _blockSize)
{
// read completely within a single block -- can be handled as-is by
// readOneBloc().
return cacheReadOneBlock( req );
} else
{
size_t size = req.dataLen;
// if the request is larger then a block, then request each block
// individually
MemBlock mb; // in case we need to allocate a temporary block..
IORequest blockReq; // for requests we may need to make
blockReq.dataLen = _blockSize;
blockReq.data = NULL;
unsigned char *out = req.data;
while( size )
{
blockReq.offset = blockNum * _blockSize;
// if we're reading a full block, then read directly into the
// result buffer instead of using a temporary
if(partialOffset == 0 && size >= (size_t)_blockSize)
blockReq.data = out;
else
{
if(!mb.data)
mb = MemoryPool::allocate( _blockSize );
blockReq.data = mb.data;
}
ssize_t readSize = cacheReadOneBlock( blockReq );
if(unlikely(readSize <= partialOffset))
break; // didn't get enough bytes
int cpySize = min( (size_t)(readSize - partialOffset), size );
rAssert(cpySize <= readSize);
// if we read to a temporary buffer, then move the data
if(blockReq.data != out)
memcpy( out, blockReq.data + partialOffset, cpySize );
result += cpySize;
size -= cpySize;
out += cpySize;
++blockNum;
partialOffset = 0;
if(unlikely(readSize < _blockSize))
break;
}
if(mb.data)
MemoryPool::release( mb );
}
return result;
}
bool BlockFileIO::write( const IORequest &req )
{
rAssert( _blockSize != 0 );
off_t fileSize = getSize();
// where write request begins
off_t blockNum = req.offset / _blockSize;
int partialOffset = req.offset % _blockSize;
// last block of file (for testing write overlaps with file boundary)
off_t lastFileBlock = fileSize / _blockSize;
ssize_t lastBlockSize = fileSize % _blockSize;
off_t lastNonEmptyBlock = lastFileBlock;
if(lastBlockSize == 0)
--lastNonEmptyBlock;
if( req.offset > fileSize )
{
// extend file first to fill hole with 0's..
const bool forceWrite = false;
padFile( fileSize, req.offset, forceWrite );
}
// check against edge cases where we can just let the base class handle the
// request as-is..
if(partialOffset == 0 && req.dataLen <= _blockSize)
{
// if writing a full block.. pretty safe..
if( req.dataLen == _blockSize )
return cacheWriteOneBlock( req );
// if writing a partial block, but at least as much as what is
// already there..
if(blockNum == lastFileBlock && req.dataLen >= lastBlockSize)
return cacheWriteOneBlock( req );
}
// have to merge data with existing block(s)..
MemBlock mb;
IORequest blockReq;
blockReq.data = NULL;
blockReq.dataLen = _blockSize;
bool ok = true;
size_t size = req.dataLen;
unsigned char *inPtr = req.data;
while( size )
{
blockReq.offset = blockNum * _blockSize;
int toCopy = min((size_t)(_blockSize - partialOffset), size);
// if writing an entire block, or writing a partial block that requires
// no merging with existing data..
if( (toCopy == _blockSize)
||(partialOffset == 0 && blockReq.offset + toCopy >= fileSize))
{
// write directly from buffer
blockReq.data = inPtr;
blockReq.dataLen = toCopy;
} else
{
// need a temporary buffer, since we have to either merge or pad
// the data.
if(!mb.data)
mb = MemoryPool::allocate( _blockSize );
memset( mb.data, 0, _blockSize );
blockReq.data = mb.data;
if(blockNum > lastNonEmptyBlock)
{
// just pad..
blockReq.dataLen = toCopy + partialOffset;
} else
{
// have to merge with existing block data..
blockReq.dataLen = _blockSize;
blockReq.dataLen = cacheReadOneBlock( blockReq );
// extend data if necessary..
if( partialOffset + toCopy > blockReq.dataLen )
blockReq.dataLen = partialOffset + toCopy;
}
// merge in the data to be written..
memcpy( blockReq.data + partialOffset, inPtr, toCopy );
}
// Finally, write the damn thing!
if(!cacheWriteOneBlock( blockReq ))
{
ok = false;
break;
}
// prepare to start all over with the next block..
size -= toCopy;
inPtr += toCopy;
++blockNum;
partialOffset = 0;
}
if(mb.data)
MemoryPool::release( mb );
return ok;
}
int BlockFileIO::blockSize() const
{
return _blockSize;
}
void BlockFileIO::padFile( off_t oldSize, off_t newSize, bool forceWrite )
{
off_t oldLastBlock = oldSize / _blockSize;
off_t newLastBlock = newSize / _blockSize;
int newBlockSize = newSize % _blockSize;
IORequest req;
MemBlock mb;
if(oldLastBlock == newLastBlock)
{
// when the real write occurs, it will have to read in the existing
// data and pad it anyway, so we won't do it here (unless we're
// forced).
if( forceWrite )
{
mb = MemoryPool::allocate( _blockSize );
req.data = mb.data;
req.offset = oldLastBlock * _blockSize;
req.dataLen = oldSize % _blockSize;
int outSize = newSize % _blockSize; // outSize > req.dataLen
if(outSize)
{
memset( mb.data, 0, outSize );
cacheReadOneBlock( req );
req.dataLen = outSize;
cacheWriteOneBlock( req );
}
} else
rDebug("optimization: not padding last block");
} else
{
mb = MemoryPool::allocate( _blockSize );
req.data = mb.data;
// 1. extend the first block to full length
// 2. write the middle empty blocks
// 3. write the last block
req.offset = oldLastBlock * _blockSize;
req.dataLen = oldSize % _blockSize;
// 1. req.dataLen == 0, iff oldSize was already a multiple of blocksize
if(req.dataLen != 0)
{
rDebug("padding block %" PRIi64, oldLastBlock);
memset( mb.data, 0, _blockSize );
cacheReadOneBlock( req );
req.dataLen = _blockSize; // expand to full block size
cacheWriteOneBlock( req );
++oldLastBlock;
}
// 2, pad zero blocks unless holes are allowed
if(!_allowHoles)
{
for(; oldLastBlock != newLastBlock; ++oldLastBlock)
{
rDebug("padding block %" PRIi64, oldLastBlock);
req.offset = oldLastBlock * _blockSize;
req.dataLen = _blockSize;
memset( mb.data, 0, req.dataLen );
cacheWriteOneBlock( req );
}
}
// 3. only necessary if write is forced and block is non 0 length
if(forceWrite && newBlockSize)
{
req.offset = newLastBlock * _blockSize;
req.dataLen = newBlockSize;
memset( mb.data, 0, req.dataLen );
cacheWriteOneBlock( req );
}
}
if(mb.data)
MemoryPool::release( mb );
}
int BlockFileIO::truncate( off_t size, FileIO *base )
{
int partialBlock = size % _blockSize;
int res = 0;
off_t oldSize = getSize();
if( size > oldSize )
{
// truncate can be used to extend a file as well. truncate man page
// states that it will pad with 0's.
// do the truncate so that the underlying filesystem can allocate
// the space, and then we'll fill it in padFile..
if(base)
base->truncate( size );
const bool forceWrite = true;
padFile( oldSize, size, forceWrite );
} else
if( size == oldSize )
{
// the easiest case, but least likely....
} else
if( partialBlock )
{
// partial block after truncate. Need to read in the block being
// truncated before the truncate. Then write it back out afterwards,
// since the encoding will change..
off_t blockNum = size / _blockSize;
MemBlock mb = MemoryPool::allocate( _blockSize );
IORequest req;
req.offset = blockNum * _blockSize;
req.dataLen = _blockSize;
req.data = mb.data;
ssize_t rdSz = cacheReadOneBlock( req );
// do the truncate
if(base)
res = base->truncate( size );
// write back out partial block
req.dataLen = partialBlock;
bool wrRes = cacheWriteOneBlock( req );
if((rdSz < 0) || (!wrRes))
{
// rwarning - unlikely to ever occur..
rWarning(_("truncate failure: read %i bytes, partial block of %i"),
(int)rdSz, partialBlock );
}
MemoryPool::release( mb );
} else
{
// truncating on a block bounday. No need to re-encode the last
// block..
if(base)
res = base->truncate( size );
}
return res;
}

View File

@ -1,267 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004-2011, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "BlockNameIO.h"
#include "Cipher.h"
#include "base64.h"
#include <cstring>
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include <rlog/RLogChannel.h>
#include "i18n.h"
using namespace rlog;
static RLogChannel * Info = DEF_CHANNEL( "info/nameio", Log_Info );
static shared_ptr<NameIO> NewBlockNameIO( const Interface &iface,
const shared_ptr<Cipher> &cipher, const CipherKey &key )
{
int blockSize = 8;
if(cipher)
blockSize = cipher->cipherBlockSize();
return shared_ptr<NameIO>(
new BlockNameIO( iface, cipher, key, blockSize, false));
}
static shared_ptr<NameIO> NewBlockNameIO32( const Interface &iface,
const shared_ptr<Cipher> &cipher, const CipherKey &key )
{
int blockSize = 8;
if(cipher)
blockSize = cipher->cipherBlockSize();
return shared_ptr<NameIO>(
new BlockNameIO( iface, cipher, key, blockSize, true));
}
static bool BlockIO_registered = NameIO::Register("Block",
// description of block name encoding algorithm..
// xgroup(setup)
gettext_noop("Block encoding, hides file name size somewhat"),
BlockNameIO::CurrentInterface(false),
NewBlockNameIO);
static bool BlockIO32_registered = NameIO::Register("Block32",
// description of block name encoding algorithm..
// xgroup(setup)
gettext_noop("Block encoding with base32 output for case-sensitive systems"),
BlockNameIO::CurrentInterface(true),
NewBlockNameIO32);
/*
- Version 1.0 computed MAC over the filename, but not the padding bytes.
This version was from pre-release 1.1, never publically released, so no
backward compatibility necessary.
- Version 2.0 includes padding bytes in MAC computation. This way the MAC
computation uses the same number of bytes regardless of the number of
padding bytes.
- Version 3.0 uses full 64 bit initialization vector during IV chaining.
Prior versions used only the output from the MAC_16 call, giving a 1 in
2^16 chance of the same name being produced. Using the full 64 bit IV
changes that to a 1 in 2^64 chance..
- Version 4.0 adds support for base32, creating names more suitable for
case-insensitive filesystems (eg Mac).
*/
Interface BlockNameIO::CurrentInterface(bool caseSensitive)
{
// implement major version 4 plus support for two prior versions
if (caseSensitive)
return makeInterface("nameio/block32", 4, 0, 2);
else
return makeInterface("nameio/block", 4, 0, 2);
}
BlockNameIO::BlockNameIO( const Interface &iface,
const shared_ptr<Cipher> &cipher,
const CipherKey &key, int blockSize,
bool caseSensitiveEncoding )
: _interface( iface.major() )
, _bs( blockSize )
, _cipher( cipher )
, _key( key )
, _caseSensitive( caseSensitiveEncoding )
{
// just to be safe..
rAssert( blockSize < 128 );
}
BlockNameIO::~BlockNameIO()
{
}
Interface BlockNameIO::interface() const
{
return CurrentInterface(_caseSensitive);
}
int BlockNameIO::maxEncodedNameLen( int plaintextNameLen ) const
{
// number of blocks, rounded up.. Only an estimate at this point, err on
// the size of too much space rather then too little.
int numBlocks = ( plaintextNameLen + _bs ) / _bs;
int encodedNameLen = numBlocks * _bs + 2; // 2 checksum bytes
if (_caseSensitive)
return B256ToB32Bytes( encodedNameLen );
else
return B256ToB64Bytes( encodedNameLen );
}
int BlockNameIO::maxDecodedNameLen( int encodedNameLen ) const
{
int decLen256 = _caseSensitive ?
B32ToB256Bytes( encodedNameLen ) :
B64ToB256Bytes( encodedNameLen );
return decLen256 - 2; // 2 checksum bytes removed..
}
int BlockNameIO::encodeName( const char *plaintextName, int length,
uint64_t *iv, char *encodedName ) const
{
// copy the data into the encoding buffer..
memcpy( encodedName+2, plaintextName, length );
// Pad encryption buffer to block boundary..
int padding = _bs - length % _bs;
if(padding == 0)
padding = _bs; // padding a full extra block!
memset( encodedName+length+2, (unsigned char)padding, padding );
// store the IV before it is modified by the MAC call.
uint64_t tmpIV = 0;
if( iv && _interface >= 3 )
tmpIV = *iv;
// include padding in MAC computation
unsigned int mac = _cipher->MAC_16( (unsigned char *)encodedName+2,
length+padding, _key, iv );
// add checksum bytes
encodedName[0] = (mac >> 8) & 0xff;
encodedName[1] = (mac ) & 0xff;
_cipher->blockEncode( (unsigned char *)encodedName+2, length+padding,
(uint64_t)mac ^ tmpIV, _key);
// convert to base 64 ascii
int encodedStreamLen = length + 2 + padding;
int encLen;
if (_caseSensitive)
{
encLen = B256ToB32Bytes( encodedStreamLen );
changeBase2Inline( (unsigned char *)encodedName, encodedStreamLen,
8, 5, true );
B32ToAscii( (unsigned char *)encodedName, encLen );
} else
{
encLen = B256ToB64Bytes( encodedStreamLen );
changeBase2Inline( (unsigned char *)encodedName, encodedStreamLen,
8, 6, true );
B64ToAscii( (unsigned char *)encodedName, encLen );
}
return encLen;
}
int BlockNameIO::decodeName( const char *encodedName, int length,
uint64_t *iv, char *plaintextName ) const
{
int decLen256 = _caseSensitive ?
B32ToB256Bytes( length ) :
B64ToB256Bytes( length );
int decodedStreamLen = decLen256 - 2;
// don't bother trying to decode files which are too small
if(decodedStreamLen < _bs)
throw ERROR("Filename too small to decode");
BUFFER_INIT( tmpBuf, 32, (unsigned int)length );
// decode into tmpBuf,
if (_caseSensitive)
{
AsciiToB32((unsigned char *)tmpBuf, (unsigned char *)encodedName, length);
changeBase2Inline((unsigned char *)tmpBuf, length, 5, 8, false);
} else
{
AsciiToB64((unsigned char *)tmpBuf, (unsigned char *)encodedName, length);
changeBase2Inline((unsigned char *)tmpBuf, length, 6, 8, false);
}
// pull out the header information
unsigned int mac = ((unsigned int)((unsigned char)tmpBuf[0])) << 8
| ((unsigned int)((unsigned char)tmpBuf[1]));
uint64_t tmpIV = 0;
if( iv && _interface >= 3 )
tmpIV = *iv;
_cipher->blockDecode( (unsigned char *)tmpBuf+2, decodedStreamLen,
(uint64_t)mac ^ tmpIV, _key);
// find out true string length
int padding = (unsigned char)tmpBuf[2+decodedStreamLen-1];
int finalSize = decodedStreamLen - padding;
// might happen if there is an error decoding..
if(padding > _bs || finalSize < 0)
{
rDebug("padding, _bx, finalSize = %i, %i, %i", padding,
_bs, finalSize);
throw ERROR( "invalid padding size" );
}
// copy out the result..
memcpy(plaintextName, tmpBuf+2, finalSize);
plaintextName[finalSize] = '\0';
// check the mac
unsigned int mac2 = _cipher->MAC_16((const unsigned char *)tmpBuf+2,
decodedStreamLen, _key, iv);
BUFFER_RESET( tmpBuf );
if(mac2 != mac)
{
rDebug("checksum mismatch: expected %u, got %u", mac, mac2);
rDebug("on decode of %i bytes", finalSize);
throw ERROR( "checksum mismatch in filename decode" );
}
return finalSize;
}
bool BlockNameIO::Enabled()
{
return true;
}

39
encfs/CMakeLists.txt Normal file
View File

@ -0,0 +1,39 @@
include_directories (${Encfs_SOURCE_DIR}/base)
link_directories (${Encfs_BINARY_DIR}/base)
include_directories (${Encfs_SOURCE_DIR}/cipher)
link_directories (${Encfs_BINARY_DIR}/cipher)
include_directories (${Encfs_SOURCE_DIR}/fs)
link_directories (${Encfs_BINARY_DIR}/fs)
# TODO: move FUSE code into encfs-fs.
find_package (FUSE REQUIRED)
include_directories (${FUSE_INCLUDE_DIR})
include_directories (${CMAKE_BINARY_DIR}/base)
add_executable (encfs
main.cpp)
target_link_libraries (encfs
encfs-fs
encfs-cipher
encfs-base
${GLOG_LIBRARIES}
${FUSE_LIBRARIES}
)
if (POD2MAN)
add_custom_target(man ALL
COMMAND ${POD2MAN} -u --section=1 --release=${ENCFS_VERSION}
--center="Encrypted Filesystem"
${CMAKE_CURRENT_SOURCE_DIR}/encfs.pod
encfs.1)
install (FILES ${CMAKE_CURRENT_BINARY_DIR}/encfs.1
DESTINATION
share/man/man1)
endif (POD2MAN)
install (TARGETS encfs DESTINATION bin)

View File

@ -1,438 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "CipherFileIO.h"
#include "Cipher.h"
#include "MemoryPool.h"
#include "config.pb.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include <fcntl.h>
#include <cerrno>
/*
- Version 2:0 adds support for a per-file initialization vector with a
fixed 8 byte header. The headers are enabled globally within a
filesystem at the filesystem configuration level.
When headers are disabled, 2:0 is compatible with version 1:0.
*/
static Interface CipherFileIO_iface = makeInterface("FileIO/Cipher", 2, 0, 1);
const int HEADER_SIZE = 8; // 64 bit initialization vector..
static bool checkSize( int fsBlockSize, int cipherBlockSize )
{
int blockBoundary = fsBlockSize % cipherBlockSize ;
if(blockBoundary != 0)
{
rError("CipherFileIO: blocks should be multiple of cipher block size");
return true;
} else
return false;
}
CipherFileIO::CipherFileIO( const shared_ptr<FileIO> &_base,
const FSConfigPtr &cfg)
: BlockFileIO( cfg->config->block_size(), cfg )
, base( _base )
, haveHeader( cfg->config->unique_iv() )
, externalIV( 0 )
, fileIV( 0 )
, lastFlags( 0 )
{
fsConfig = cfg;
cipher = cfg->cipher;
key = cfg->key;
static bool warnOnce = false;
if(!warnOnce)
warnOnce = checkSize( fsConfig->config->block_size(),
fsConfig->cipher->cipherBlockSize() );
}
CipherFileIO::~CipherFileIO()
{
}
Interface CipherFileIO::interface() const
{
return CipherFileIO_iface;
}
int CipherFileIO::open( int flags )
{
int res = base->open( flags );
if( res >= 0 )
lastFlags = flags;
return res;
}
void CipherFileIO::setFileName( const char *fileName )
{
base->setFileName( fileName );
}
const char *CipherFileIO::getFileName() const
{
return base->getFileName();
}
bool CipherFileIO::setIV( uint64_t iv )
{
rDebug("in setIV, current IV = %" PRIu64 ", new IV = %" PRIu64
", fileIV = %" PRIu64,
externalIV, iv, fileIV);
if(externalIV == 0)
{
// we're just being told about which IV to use. since we haven't
// initialized the fileIV, there is no need to just yet..
externalIV = iv;
if(fileIV != 0)
rWarning("fileIV initialized before externalIV! (%" PRIu64
", %" PRIu64 ")", fileIV, externalIV);
} else
if(haveHeader)
{
// we have an old IV, and now a new IV, so we need to update the fileIV
// on disk.
if(fileIV == 0)
{
// ensure the file is open for read/write..
int newFlags = lastFlags | O_RDWR;
int res = base->open( newFlags );
if(res < 0)
{
if(res == -EISDIR)
{
// duh -- there are no file headers for directories!
externalIV = iv;
return base->setIV( iv );
} else
{
rDebug("writeHeader failed to re-open for write");
return false;
}
}
initHeader();
}
uint64_t oldIV = externalIV;
externalIV = iv;
if(!writeHeader())
{
externalIV = oldIV;
return false;
}
}
return base->setIV( iv );
}
int CipherFileIO::getAttr( struct stat *stbuf ) const
{
int res = base->getAttr( stbuf );
// adjust size if we have a file header
if((res == 0) && haveHeader &&
S_ISREG(stbuf->st_mode) && (stbuf->st_size > 0))
{
rAssert(stbuf->st_size >= HEADER_SIZE);
stbuf->st_size -= HEADER_SIZE;
}
return res;
}
off_t CipherFileIO::getSize() const
{
off_t size = base->getSize();
// No check on S_ISREG here -- don't call getSize over getAttr unless this
// is a normal file!
if(haveHeader && size > 0)
{
rAssert(size >= HEADER_SIZE);
size -= HEADER_SIZE;
}
return size;
}
void CipherFileIO::initHeader( )
{
// check if the file has a header, and read it if it does.. Otherwise,
// create one.
off_t rawSize = base->getSize();
if(rawSize >= HEADER_SIZE)
{
rDebug("reading existing header, rawSize = %" PRIi64, rawSize);
// has a header.. read it
unsigned char buf[8] = {0};
IORequest req;
req.offset = 0;
req.data = buf;
req.dataLen = 8;
base->read( req );
cipher->streamDecode( buf, sizeof(buf),
externalIV, key );
fileIV = 0;
for(int i=0; i<8; ++i)
fileIV = (fileIV << 8) | (uint64_t)buf[i];
rAssert(fileIV != 0); // 0 is never used..
} else
{
rDebug("creating new file IV header");
unsigned char buf[8] = {0};
do
{
if(!cipher->randomize( buf, 8, false ))
throw ERROR("Unable to generate a random file IV");
fileIV = 0;
for(int i=0; i<8; ++i)
fileIV = (fileIV << 8) | (uint64_t)buf[i];
if(fileIV == 0)
rWarning("Unexpected result: randomize returned 8 null bytes!");
} while(fileIV == 0); // don't accept 0 as an option..
if( base->isWritable() )
{
cipher->streamEncode( buf, sizeof(buf), externalIV, key );
IORequest req;
req.offset = 0;
req.data = buf;
req.dataLen = 8;
base->write( req );
} else
rDebug("base not writable, IV not written..");
}
rDebug("initHeader finished, fileIV = %" PRIu64 , fileIV);
}
bool CipherFileIO::writeHeader( )
{
if( !base->isWritable() )
{
// open for write..
int newFlags = lastFlags | O_RDWR;
if( base->open( newFlags ) < 0 )
{
rDebug("writeHeader failed to re-open for write");
return false;
}
}
if(fileIV == 0)
rError("Internal error: fileIV == 0 in writeHeader!!!");
rDebug("writing fileIV %" PRIu64 , fileIV);
unsigned char buf[8] = {0};
for(int i=0; i<8; ++i)
{
buf[sizeof(buf)-1-i] = (unsigned char)(fileIV & 0xff);
fileIV >>= 8;
}
cipher->streamEncode( buf, sizeof(buf), externalIV, key );
IORequest req;
req.offset = 0;
req.data = buf;
req.dataLen = 8;
base->write( req );
return true;
}
ssize_t CipherFileIO::readOneBlock( const IORequest &req ) const
{
// read raw data, then decipher it..
int bs = blockSize();
off_t blockNum = req.offset / bs;
ssize_t readSize = 0;
IORequest tmpReq = req;
if(haveHeader)
tmpReq.offset += HEADER_SIZE;
readSize = base->read( tmpReq );
bool ok;
if(readSize > 0)
{
if(haveHeader && fileIV == 0)
const_cast<CipherFileIO*>(this)->initHeader();
if(readSize != bs)
{
ok = streamRead( tmpReq.data, (int)readSize, blockNum ^ fileIV);
} else
{
ok = blockRead( tmpReq.data, (int)readSize, blockNum ^ fileIV);
}
if(!ok)
{
rDebug("decodeBlock failed for block %" PRIi64 ", size %i",
blockNum, (int)readSize );
readSize = -1;
}
} else
rDebug("readSize zero for offset %" PRIi64, req.offset);
return readSize;
}
bool CipherFileIO::writeOneBlock( const IORequest &req )
{
int bs = blockSize();
off_t blockNum = req.offset / bs;
if(haveHeader && fileIV == 0)
initHeader();
bool ok;
if( req.dataLen != bs )
{
ok = streamWrite( req.data, (int)req.dataLen,
blockNum ^ fileIV );
} else
{
ok = blockWrite( req.data, (int)req.dataLen,
blockNum ^ fileIV );
}
if( ok )
{
if(haveHeader)
{
IORequest tmpReq = req;
tmpReq.offset += HEADER_SIZE;
ok = base->write( tmpReq );
} else
ok = base->write( req );
} else
{
rDebug("encodeBlock failed for block %" PRIi64 ", size %i",
blockNum, req.dataLen);
ok = false;
}
return ok;
}
bool CipherFileIO::blockWrite( unsigned char *buf, int size,
uint64_t _iv64 ) const
{
if (!fsConfig->reverseEncryption)
return cipher->blockEncode( buf, size, _iv64, key );
else
return cipher->blockDecode( buf, size, _iv64, key );
}
bool CipherFileIO::streamWrite( unsigned char *buf, int size,
uint64_t _iv64 ) const
{
if (!fsConfig->reverseEncryption)
return cipher->streamEncode( buf, size, _iv64, key );
else
return cipher->streamDecode( buf, size, _iv64, key );
}
bool CipherFileIO::blockRead( unsigned char *buf, int size,
uint64_t _iv64 ) const
{
if (fsConfig->reverseEncryption)
return cipher->blockEncode( buf, size, _iv64, key );
else
{
if(_allowHoles)
{
// special case - leave all 0's alone
for(int i=0; i<size; ++i)
if(buf[i] != 0)
return cipher->blockDecode( buf, size, _iv64, key );
return true;
} else
return cipher->blockDecode( buf, size, _iv64, key );
}
}
bool CipherFileIO::streamRead( unsigned char *buf, int size,
uint64_t _iv64 ) const
{
if (fsConfig->reverseEncryption)
return cipher->streamEncode( buf, size, _iv64, key );
else
return cipher->streamDecode( buf, size, _iv64, key );
}
int CipherFileIO::truncate( off_t size )
{
int res = 0;
if(!haveHeader)
{
res = BlockFileIO::truncate( size, base.get() );
} else
{
if(0 == fileIV)
{
// empty file.. create the header..
if( !base->isWritable() )
{
// open for write..
int newFlags = lastFlags | O_RDWR;
if( base->open( newFlags ) < 0 )
rDebug("writeHeader failed to re-open for write");
}
initHeader();
}
// can't let BlockFileIO call base->truncate(), since it would be using
// the wrong size..
res = BlockFileIO::truncate( size, 0 );
if(res == 0)
base->truncate( size + HEADER_SIZE );
}
return res;
}
bool CipherFileIO::isWritable() const
{
return base->isWritable();
}

View File

@ -1,162 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ConfigReader.h"
#include <rlog/rlog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cstring>
using namespace std;
using namespace rlog;
ConfigReader::ConfigReader()
{
}
ConfigReader::~ConfigReader()
{
}
// read the entire file into a ConfigVar instance and then use that to decode
// into mapped variables.
bool
ConfigReader::load(const char *fileName)
{
struct stat stbuf;
memset( &stbuf, 0, sizeof(struct stat));
if( lstat( fileName, &stbuf ) != 0)
return false;
int size = stbuf.st_size;
int fd = open( fileName, O_RDONLY );
if(fd < 0)
return false;
char *buf = new char[size];
int res = ::read( fd, buf, size );
close( fd );
if( res != size )
{
rWarning("Partial read of config file, expecting %i bytes, got %i",
size, res);
delete[] buf;
return false;
}
ConfigVar in;
in.write( (unsigned char *)buf, size );
delete[] buf;
return loadFromVar( in );
}
bool
ConfigReader::loadFromVar(ConfigVar &in)
{
in.resetOffset();
// parse.
int numEntries = in.readInt();
for(int i=0; i<numEntries; ++i)
{
string key, value;
in >> key >> value;
if(key.length() == 0)
{
rError("Invalid key encoding in buffer");
return false;
}
ConfigVar newVar( value );
vars.insert( make_pair( key, newVar ) );
}
return true;
}
bool
ConfigReader::save(const char *fileName) const
{
// write everything to a ConfigVar, then output to disk
ConfigVar out = toVar();
int fd = ::open( fileName, O_RDWR | O_CREAT, 0640 );
if(fd >= 0)
{
int retVal = ::write( fd, out.buffer(), out.size() );
close( fd );
if(retVal != out.size())
{
rError("Error writing to config file %s", fileName);
return false;
}
} else
{
rError("Unable to open or create file %s", fileName);
return false;
}
return true;
}
ConfigVar
ConfigReader::toVar() const
{
// write everything to a ConfigVar, then output to disk
ConfigVar out;
out.writeInt( vars.size() );
map<string, ConfigVar>::const_iterator it;
for(it = vars.begin(); it != vars.end(); ++it)
{
out.writeInt( it->first.size() );
out.write( (unsigned char*)it->first.data(), it->first.size() );
out.writeInt( it->second.size() );
out.write( (unsigned char*)it->second.buffer(), it->second.size() );
}
return out;
}
ConfigVar ConfigReader::operator[] ( const std::string &varName ) const
{
// read only
map<string, ConfigVar>::const_iterator it = vars.find( varName );
if( it == vars.end() )
return ConfigVar();
else
return it->second;
}
ConfigVar &ConfigReader::operator[] ( const std::string &varName )
{
return vars[ varName ];
}

View File

@ -1,252 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ConfigVar.h"
#include <rlog/rlog.h>
#include <cstring>
using namespace rlog;
#ifndef MIN
inline int MIN(int a, int b)
{
return (a < b) ? a : b;
}
#endif
ConfigVar::ConfigVar()
: pd( new ConfigVarData )
{
pd->offset = 0;
}
ConfigVar::ConfigVar(const std::string &buf)
: pd( new ConfigVarData )
{
pd->buffer = buf;
pd->offset = 0;
}
ConfigVar::ConfigVar(const ConfigVar &src)
{
pd = src.pd;
}
ConfigVar::~ConfigVar()
{
pd.reset();
}
ConfigVar & ConfigVar::operator = (const ConfigVar &src)
{
if(src.pd == pd)
return *this;
else
pd = src.pd;
return *this;
}
void ConfigVar::resetOffset()
{
pd->offset = 0;
}
int ConfigVar::read(unsigned char *buffer_, int bytes) const
{
int toCopy = MIN( bytes, pd->buffer.size() - pd->offset );
if(toCopy > 0)
memcpy( buffer_, pd->buffer.data() + pd->offset, toCopy );
pd->offset += toCopy;
return toCopy;
}
int ConfigVar::write(const unsigned char *data, int bytes)
{
if(pd->buffer.size() == (unsigned int)pd->offset)
{
pd->buffer.append( (const char *)data, bytes );
} else
{
pd->buffer.insert( pd->offset, (const char *)data, bytes );
}
pd->offset += bytes;
return bytes;
}
int ConfigVar::size() const
{
return pd->buffer.size();
}
const char *ConfigVar::buffer() const
{
return pd->buffer.data();
}
int ConfigVar::at() const
{
return pd->offset;
}
void ConfigVar::writeString(const char *data, int bytes)
{
writeInt( bytes );
write( (const unsigned char *)data, bytes );
}
// convert integer to BER encoded integer
void ConfigVar::writeInt(int val)
{
// we can represent 7 bits per char output, so a 32bit number may take up
// to 5 bytes.
// first byte: 0x0000007f 0111,1111
// second byte: 0x00003f80 0011,1111 1000,0000
// third byte: 0x001fb000 0000,0000 0001,1111 1100,0000 0000,0000
// fourth byte: 0x0fe00000 0000,1111 1110,0000
// fifth byte: 0xf0000000 1111,0000
unsigned char digit[5];
digit[4] = (unsigned char)((val & 0x0000007f));
digit[3] = 0x80 | (unsigned char)((val & 0x00003f80) >> 7);
digit[2] = 0x80 | (unsigned char)((val & 0x001fc000) >> 14);
digit[1] = 0x80 | (unsigned char)((val & 0x0fe00000) >> 21);
digit[0] = 0x80 | (unsigned char)((val & 0xf0000000) >> 28);
// find the starting point - we only need to output starting at the most
// significant non-zero digit..
int start = 0;
while(digit[start] == 0x80)
++start;
write( digit + start, 5-start );
}
int ConfigVar::readInt() const
{
const unsigned char * buf = (const unsigned char *)buffer();
int bytes = this->size();
int offset = at();
int value = 0;
bool highBitSet;
rAssert( offset < bytes );
do
{
unsigned char tmp = buf[offset++];
highBitSet = tmp & 0x80;
value = (value << 7) | (int)(tmp & 0x7f);
} while(highBitSet && offset < bytes);
pd->offset = offset;
// should never end up with a negative number..
rAssert( value >= 0 );
return value;
}
int ConfigVar::readInt( int defaultValue ) const
{
int bytes = this->size();
int offset = at();
if(offset >= bytes)
return defaultValue;
else
return readInt();
}
bool ConfigVar::readBool( bool defaultValue ) const
{
int tmp = readInt( defaultValue ? 1 : 0 );
return (tmp != 0);
}
ConfigVar & operator << (ConfigVar &src, bool value)
{
src.writeInt( value ? 1 : 0 );
return src;
}
ConfigVar & operator << (ConfigVar &src, int var)
{
src.writeInt( var );
return src;
}
ConfigVar & operator << (ConfigVar &src, const std::string &str)
{
src.writeString( str.data(), str.length() );
return src;
}
const ConfigVar & operator >> (const ConfigVar &src, bool &result)
{
int tmp = src.readInt();
result = (tmp != 0);
return src;
}
const ConfigVar & operator >> (const ConfigVar &src, int &result)
{
result = src.readInt();
return src;
}
const ConfigVar & operator >> (const ConfigVar &src, std::string &result)
{
int length = src.readInt();
//rAssert(length > 0);
int readLen;
unsigned char tmpBuf[32];
if(length > (int)sizeof(tmpBuf))
{
unsigned char *ptr = new unsigned char[length];
readLen = src.read( ptr, length );
result.assign( (char*)ptr, length );
delete[] ptr;
} else
{
readLen = src.read( tmpBuf, length );
result.assign( (char*)tmpBuf, length );
}
if(readLen != length)
{
rDebug("string encoded as size %i bytes, read %i", length, readLen );
}
rAssert(readLen == length);
return src;
}

View File

@ -1,177 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2007, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FileNode.h"
#include "Context.h"
#include "Mutex.h"
#include "FileUtils.h"
#include "DirNode.h"
#include <rlog/rlog.h>
using namespace rel;
using namespace rlog;
EncFS_Context::EncFS_Context()
{
pthread_cond_init( &wakeupCond, 0 );
pthread_mutex_init( &wakeupMutex, 0 );
pthread_mutex_init( &contextMutex, 0 );
usageCount = 0;
}
EncFS_Context::~EncFS_Context()
{
pthread_mutex_destroy( &contextMutex );
pthread_mutex_destroy( &wakeupMutex );
pthread_cond_destroy( &wakeupCond );
// release all entries from map
openFiles.clear();
}
shared_ptr<DirNode> EncFS_Context::getRoot(int *errCode)
{
shared_ptr<DirNode> ret;
do
{
{
Lock lock( contextMutex );
ret = root;
++usageCount;
}
if(!ret)
{
int res = remountFS( this );
if(res != 0)
{
*errCode = res;
break;
}
}
} while(!ret);
return ret;
}
void EncFS_Context::setRoot(const shared_ptr<DirNode> &r)
{
Lock lock( contextMutex );
root = r;
if(r)
rootCipherDir = r->rootDirectory();
}
bool EncFS_Context::isMounted()
{
return root;
}
int EncFS_Context::getAndResetUsageCounter()
{
Lock lock( contextMutex );
int count = usageCount;
usageCount = 0;
return count;
}
int EncFS_Context::openFileCount() const
{
Lock lock( contextMutex );
return openFiles.size();
}
shared_ptr<FileNode> EncFS_Context::lookupNode(const char *path)
{
Lock lock( contextMutex );
FileMap::iterator it = openFiles.find( std::string(path) );
if(it != openFiles.end())
{
// all the items in the set point to the same node.. so just use the
// first
return (*it->second.begin())->node;
} else
{
return shared_ptr<FileNode>();
}
}
void EncFS_Context::renameNode(const char *from, const char *to)
{
Lock lock( contextMutex );
FileMap::iterator it = openFiles.find( std::string(from) );
if(it != openFiles.end())
{
std::set<Placeholder *> val = it->second;
openFiles.erase(it);
openFiles[ std::string(to) ] = val;
}
}
shared_ptr<FileNode> EncFS_Context::getNode(void *pl)
{
Placeholder *ph = (Placeholder*)pl;
return ph->node;
}
void *EncFS_Context::putNode(const char *path,
const shared_ptr<FileNode> &node)
{
Lock lock( contextMutex );
Placeholder *pl = new Placeholder( node );
openFiles[ std::string(path) ].insert(pl);
return (void *)pl;
}
void EncFS_Context::eraseNode(const char *path, void *pl)
{
Lock lock( contextMutex );
Placeholder *ph = (Placeholder *)pl;
FileMap::iterator it = openFiles.find( std::string(path) );
rAssert(it != openFiles.end());
int rmCount = it->second.erase( ph );
rAssert(rmCount == 1);
// if no more references to this file, remove the record all together
if(it->second.empty())
{
// attempts to make use of shallow copy to clear memory used to hold
// unencrypted filenames.. not sure this does any good..
std::string storedName = it->first;
openFiles.erase( it );
storedName.assign( storedName.length(), '\0' );
}
delete ph;
}

View File

@ -1,831 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2003-2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "encfs.h"
#include "DirNode.h"
#include "FileUtils.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#ifdef linux
#include <sys/fsuid.h>
#endif
#include <cstring>
#include "Context.h"
#include "Cipher.h"
#include "Mutex.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include <iostream>
using namespace std;
using namespace rel;
using namespace rlog;
static RLogChannel *Info = DEF_CHANNEL( "info/DirNode", Log_Info );
class DirDeleter
{
public:
void operator () ( DIR *d )
{
::closedir( d );
}
};
DirTraverse::DirTraverse(const shared_ptr<DIR> &_dirPtr,
uint64_t _iv, const shared_ptr<NameIO> &_naming)
: dir( _dirPtr )
, iv( _iv )
, naming( _naming )
{
}
DirTraverse::DirTraverse(const DirTraverse &src)
: dir( src.dir )
, iv( src.iv )
, naming( src.naming )
{
}
DirTraverse &DirTraverse::operator = (const DirTraverse &src)
{
dir = src.dir;
iv = src.iv;
naming = src.naming;
return *this;
}
DirTraverse::~DirTraverse()
{
dir.reset();
iv = 0;
naming.reset();
}
static
bool _nextName(struct dirent *&de, const shared_ptr<DIR> &dir,
int *fileType, ino_t *inode)
{
de = ::readdir( dir.get() );
if(de)
{
if(fileType)
{
#if defined(_DIRENT_HAVE_D_TYPE) || defined(__FreeBSD__)
*fileType = de->d_type;
#else
#warning "struct dirent.d_type not supported"
*fileType = 0;
#endif
}
if(inode)
*inode = de->d_ino;
return true;
} else
{
if(fileType)
*fileType = 0;
return false;
}
}
std::string DirTraverse::nextPlaintextName(int *fileType, ino_t *inode)
{
struct dirent *de=0;
while(_nextName(de, dir, fileType, inode))
{
try
{
uint64_t localIv = iv;
return naming->decodePath( de->d_name, &localIv );
} catch ( rlog::Error &ex )
{
// .. .problem decoding, ignore it and continue on to next name..
rDebug("error decoding filename: %s", de->d_name);
}
}
return string();
}
std::string DirTraverse::nextInvalid()
{
struct dirent *de=0;
// find the first name which produces a decoding error...
while(_nextName(de, dir, (int*)0, (ino_t*)0))
{
try
{
uint64_t localIv = iv;
naming->decodePath( de->d_name, &localIv );
continue;
} catch( rlog::Error &ex )
{
return string( de->d_name );
}
}
return string();
}
struct RenameEl
{
// ciphertext names
string oldCName;
string newCName; // intermediate name (not final cname)
// plaintext names
string oldPName;
string newPName;
bool isDirectory;
};
class RenameOp
{
private:
DirNode *dn;
shared_ptr< list<RenameEl> > renameList;
list<RenameEl>::const_iterator last;
public:
RenameOp( DirNode *_dn, const shared_ptr< list<RenameEl> > &_renameList )
: dn(_dn), renameList(_renameList)
{
last = renameList->begin();
}
RenameOp(const RenameOp &src)
: dn(src.dn)
, renameList(src.renameList)
, last(src.last)
{
}
~RenameOp();
operator bool () const
{
return renameList;
}
bool apply();
void undo();
};
RenameOp::~RenameOp()
{
if(renameList)
{
// got a bunch of decoded filenames sitting in memory.. do a little
// cleanup before leaving..
list<RenameEl>::iterator it;
for(it = renameList->begin(); it != renameList->end(); ++it)
{
it->oldPName.assign( it->oldPName.size(), ' ' );
it->newPName.assign( it->newPName.size(), ' ' );
}
}
}
bool RenameOp::apply()
{
try
{
while(last != renameList->end())
{
// backing store rename.
rDebug("renaming %s -> %s",
last->oldCName.c_str(), last->newCName.c_str());
struct stat st;
bool preserve_mtime = ::stat(last->oldCName.c_str(), &st) == 0;
// internal node rename..
dn->renameNode( last->oldPName.c_str(),
last->newPName.c_str() );
// rename on disk..
if(::rename( last->oldCName.c_str(),
last->newCName.c_str() ) == -1)
{
rWarning("Error renaming %s: %s",
last->oldCName.c_str(), strerror( errno ));
dn->renameNode( last->newPName.c_str(),
last->oldPName.c_str(), false );
return false;
}
if(preserve_mtime)
{
struct utimbuf ut;
ut.actime = st.st_atime;
ut.modtime = st.st_mtime;
::utime(last->newCName.c_str(), &ut);
}
++last;
}
return true;
} catch( rlog::Error &err )
{
err.log( _RLWarningChannel );
return false;
}
}
void RenameOp::undo()
{
rDebug("in undoRename");
if(last == renameList->begin())
{
rDebug("nothing to undo");
return; // nothing to undo
}
// list has to be processed backwards, otherwise we may rename
// directories and directory contents in the wrong order!
int undoCount = 0;
list<RenameEl>::const_iterator it = last;
while( it != renameList->begin() )
{
--it;
rDebug("undo: renaming %s -> %s",
it->newCName.c_str(), it->oldCName.c_str());
::rename( it->newCName.c_str(), it->oldCName.c_str() );
try
{
dn->renameNode( it->newPName.c_str(),
it->oldPName.c_str(), false );
} catch( rlog::Error &err )
{
err.log( _RLWarningChannel );
// continue on anyway...
}
++undoCount;
};
rWarning("Undo rename count: %i", undoCount);
}
DirNode::DirNode(EncFS_Context *_ctx,
const string &sourceDir,
const FSConfigPtr &_config)
{
pthread_mutex_init( &mutex, 0 );
Lock _lock( mutex );
ctx = _ctx;
rootDir = sourceDir;
fsConfig = _config;
// make sure rootDir ends in '/', so that we can form a path by appending
// the rest..
if( rootDir[ rootDir.length()-1 ] != '/' )
rootDir.append( 1, '/');
naming = fsConfig->nameCoding;
}
DirNode::~DirNode()
{
}
bool
DirNode::hasDirectoryNameDependency() const
{
return naming ? naming->getChainedNameIV() : false;
}
string
DirNode::rootDirectory()
{
// don't update last access here, otherwise 'du' would cause lastAccess to
// be reset.
// chop off '/' terminator from root dir.
return string( rootDir, 0, rootDir.length()-1 );
}
string
DirNode::cipherPath( const char *plaintextPath )
{
return rootDir + naming->encodePath( plaintextPath );
}
string
DirNode::cipherPathWithoutRoot( const char *plaintextPath )
{
return naming->encodePath( plaintextPath );
}
string
DirNode::plainPath( const char *cipherPath_ )
{
try
{
if( !strncmp( cipherPath_, rootDir.c_str(),
rootDir.length() ) )
{
return naming->decodePath( cipherPath_ + rootDir.length() );
} else
{
if ( cipherPath_[0] == '+' )
{
// decode as fully qualified path
return string("/") + naming->decodeName( cipherPath_+1,
strlen(cipherPath_+1) );
} else
{
return naming->decodePath( cipherPath_ );
}
}
} catch( rlog::Error &err )
{
rError("decode err: %s", err.message());
err.log( _RLWarningChannel );
return string();
}
}
string
DirNode::relativeCipherPath( const char *plaintextPath )
{
try
{
if(plaintextPath[0] == '/')
{
// mark with '+' to indicate special decoding..
return string("+") + naming->encodeName(plaintextPath+1,
strlen(plaintextPath+1));
} else
{
return naming->encodePath( plaintextPath );
}
} catch( rlog::Error &err )
{
rError("encode err: %s", err.message());
err.log( _RLWarningChannel );
return string();
}
}
DirTraverse DirNode::openDir(const char *plaintextPath)
{
string cyName = rootDir + naming->encodePath( plaintextPath );
//rDebug("openDir on %s", cyName.c_str() );
DIR *dir = ::opendir( cyName.c_str() );
if(dir == NULL)
{
rDebug("opendir error %s", strerror(errno));
return DirTraverse( shared_ptr<DIR>(), 0, shared_ptr<NameIO>() );
} else
{
shared_ptr<DIR> dp( dir, DirDeleter() );
uint64_t iv = 0;
// if we're using chained IV mode, then compute the IV at this
// directory level..
try
{
if( naming->getChainedNameIV() )
naming->encodePath( plaintextPath, &iv );
} catch( rlog::Error &err )
{
rError("encode err: %s", err.message());
err.log( _RLWarningChannel );
}
return DirTraverse( dp, iv, naming );
}
}
bool DirNode::genRenameList( list<RenameEl> &renameList,
const char *fromP, const char *toP )
{
uint64_t fromIV = 0, toIV = 0;
// compute the IV for both paths
string fromCPart = naming->encodePath( fromP, &fromIV );
string toCPart = naming->encodePath( toP, &toIV );
// where the files live before the rename..
string sourcePath = rootDir + fromCPart;
// ok..... we wish it was so simple.. should almost never happen
if(fromIV == toIV)
return true;
// generate the real destination path, where we expect to find the files..
rDebug("opendir %s", sourcePath.c_str() );
shared_ptr<DIR> dir = shared_ptr<DIR>(
opendir( sourcePath.c_str() ), DirDeleter() );
if(!dir)
return false;
struct dirent *de = NULL;
while((de = ::readdir( dir.get() )) != NULL)
{
// decode the name using the oldIV
uint64_t localIV = fromIV;
string plainName;
if((de->d_name[0] == '.') &&
((de->d_name[1] == '\0')
|| ((de->d_name[1] == '.') && (de->d_name[2] == '\0'))))
{
// skip "." and ".."
continue;
}
try
{
plainName = naming->decodePath( de->d_name, &localIV );
} catch( rlog::Error &ex )
{
// if filename can't be decoded, then ignore it..
continue;
}
// any error in the following will trigger a rename failure.
try
{
// re-encode using the new IV..
localIV = toIV;
string newName = naming->encodePath( plainName.c_str(), &localIV );
// store rename information..
string oldFull = sourcePath + '/' + de->d_name;
string newFull = sourcePath + '/' + newName;
RenameEl ren;
ren.oldCName = oldFull;
ren.newCName = newFull;
ren.oldPName = string(fromP) + '/' + plainName;
ren.newPName = string(toP) + '/' + plainName;
bool isDir;
#if defined(_DIRENT_HAVE_D_TYPE)
if(de->d_type != DT_UNKNOWN)
{
isDir = (de->d_type == DT_DIR);
} else
#endif
{
isDir = isDirectory( oldFull.c_str() );
}
ren.isDirectory = isDir;
if(isDir)
{
// recurse.. We want to add subdirectory elements before the
// parent, as that is the logical rename order..
if(!genRenameList( renameList,
ren.oldPName.c_str(),
ren.newPName.c_str()))
{
return false;
}
}
rDebug("adding file %s to rename list",
oldFull.c_str());
renameList.push_back( ren );
} catch( rlog::Error &err )
{
// We can't convert this name, because we don't have a valid IV for
// it (or perhaps a valid key).. It will be inaccessible..
rWarning("Aborting rename: error on file: %s",
fromCPart.append(1, '/').append(de->d_name).c_str());
err.log( _RLDebugChannel );
// abort.. Err on the side of safety and disallow rename, rather
// then loosing files..
return false;
}
}
return true;
}
/*
A bit of a pain.. If a directory is renamed in a filesystem with
directory initialization vector chaining, then we have to recursively
rename every descendent of this directory, as all initialization vectors
will have changed..
Returns a list of renamed items on success, a null list on failure.
*/
shared_ptr<RenameOp>
DirNode::newRenameOp( const char *fromP, const char *toP )
{
// Do the rename in two stages to avoid chasing our tail
// Undo everything if we encounter an error!
shared_ptr< list<RenameEl> > renameList(new list<RenameEl>);
if(!genRenameList( *renameList.get(), fromP, toP ))
{
rWarning("Error during generation of recursive rename list");
return shared_ptr<RenameOp>();
} else
return shared_ptr<RenameOp>( new RenameOp(this, renameList) );
}
int DirNode::mkdir(const char *plaintextPath, mode_t mode,
uid_t uid, gid_t gid)
{
string cyName = rootDir + naming->encodePath( plaintextPath );
rAssert( !cyName.empty() );
rLog( Info, "mkdir on %s", cyName.c_str() );
// if uid or gid are set, then that should be the directory owner
int olduid = -1;
int oldgid = -1;
if(uid != 0)
olduid = setfsuid( uid );
if(gid != 0)
oldgid = setfsgid( gid );
int res = ::mkdir( cyName.c_str(), mode );
if(olduid >= 0)
setfsuid( olduid );
if(oldgid >= 0)
setfsgid( oldgid );
if(res == -1)
{
int eno = errno;
rWarning("mkdir error on %s mode %i: %s", cyName.c_str(),
mode, strerror(eno));
res = -eno;
} else
res = 0;
return res;
}
int
DirNode::rename( const char *fromPlaintext, const char *toPlaintext )
{
Lock _lock( mutex );
string fromCName = rootDir + naming->encodePath( fromPlaintext );
string toCName = rootDir + naming->encodePath( toPlaintext );
rAssert( !fromCName.empty() );
rAssert( !toCName.empty() );
rLog( Info, "rename %s -> %s", fromCName.c_str(), toCName.c_str() );
shared_ptr<FileNode> toNode = findOrCreate( toPlaintext );
shared_ptr<RenameOp> renameOp;
if( hasDirectoryNameDependency() && isDirectory( fromCName.c_str() ))
{
rLog( Info, "recursive rename begin" );
renameOp = newRenameOp( fromPlaintext, toPlaintext );
if(!renameOp || !renameOp->apply())
{
if(renameOp)
renameOp->undo();
rWarning("rename aborted");
return -EACCES;
}
rLog( Info, "recursive rename end" );
}
int res = 0;
try
{
struct stat st;
bool preserve_mtime = ::stat(fromCName.c_str(), &st) == 0;
renameNode( fromPlaintext, toPlaintext );
res = ::rename( fromCName.c_str(), toCName.c_str() );
if(res == -1)
{
// undo
res = -errno;
renameNode( toPlaintext, fromPlaintext, false );
if(renameOp)
renameOp->undo();
} else if(preserve_mtime)
{
struct utimbuf ut;
ut.actime = st.st_atime;
ut.modtime = st.st_mtime;
::utime(toCName.c_str(), &ut);
}
} catch( rlog::Error &err )
{
// exception from renameNode, just show the error and continue..
err.log( _RLWarningChannel );
res = -EIO;
}
if(res != 0)
{
rLog( Info, "rename failed: %s", strerror( errno ));
res = -errno;
}
return res;
}
int DirNode::link( const char *from, const char *to )
{
Lock _lock( mutex );
string fromCName = rootDir + naming->encodePath( from );
string toCName = rootDir + naming->encodePath( to );
rAssert( !fromCName.empty() );
rAssert( !toCName.empty() );
rLog(Info, "link %s -> %s", fromCName.c_str(), toCName.c_str());
int res = -EPERM;
if( fsConfig->config->external_iv() )
{
rLog(Info, "hard links not supported with external IV chaining!");
} else
{
res = ::link( fromCName.c_str(), toCName.c_str() );
if(res == -1)
res = -errno;
else
res = 0;
}
return res;
}
/*
The node is keyed by filename, so a rename means the internal node names
must be changed.
*/
shared_ptr<FileNode> DirNode::renameNode( const char *from, const char *to )
{
return renameNode( from, to, true );
}
shared_ptr<FileNode> DirNode::renameNode( const char *from, const char *to,
bool forwardMode )
{
shared_ptr<FileNode> node = findOrCreate( from );
if(node)
{
uint64_t newIV = 0;
string cname = rootDir + naming->encodePath( to, &newIV );
rLog(Info, "renaming internal node %s -> %s",
node->cipherName(), cname.c_str());
if(node->setName( to, cname.c_str(), newIV, forwardMode ))
{
if(ctx)
ctx->renameNode( from, to );
} else
{
// rename error! - put it back
rError("renameNode failed");
throw ERROR("Internal node name change failed!");
}
}
return node;
}
shared_ptr<FileNode> DirNode::findOrCreate( const char *plainName)
{
shared_ptr<FileNode> node;
if(ctx)
node = ctx->lookupNode( plainName );
if(!node)
{
uint64_t iv = 0;
string cipherName = naming->encodePath( plainName, &iv );
node.reset( new FileNode( this, fsConfig,
plainName,
(rootDir + cipherName).c_str()));
if(fsConfig->config->external_iv())
node->setName(0, 0, iv);
rLog(Info, "created FileNode for %s", node->cipherName());
}
return node;
}
shared_ptr<FileNode>
DirNode::lookupNode( const char *plainName, const char * requestor )
{
(void)requestor;
Lock _lock( mutex );
shared_ptr<FileNode> node = findOrCreate( plainName );
return node;
}
/*
Similar to lookupNode, except that we also call open() and only return a
node on sucess.. This is done in one step to avoid any race conditions
with the stored state of the file.
*/
shared_ptr<FileNode>
DirNode::openNode( const char *plainName, const char * requestor, int flags,
int *result )
{
(void)requestor;
rAssert( result != NULL );
Lock _lock( mutex );
shared_ptr<FileNode> node = findOrCreate( plainName );
if( node && (*result = node->open( flags )) >= 0 )
return node;
else
return shared_ptr<FileNode>();
}
int DirNode::unlink( const char *plaintextName )
{
string cyName = naming->encodePath( plaintextName );
rLog( Info, "unlink %s", cyName.c_str() );
Lock _lock( mutex );
int res = 0;
if(ctx && ctx->lookupNode( plaintextName ))
{
// If FUSE is running with "hard_remove" option where it doesn't
// hide open files for us, then we can't allow an unlink of an open
// file..
rWarning("Refusing to unlink open file: %s, hard_remove option "
"is probably in effect", cyName.c_str() );
res = -EBUSY;
} else
{
string fullName = rootDir + cyName;
res = ::unlink( fullName.c_str() );
if(res == -1)
{
res = -errno;
rDebug("unlink error: %s", strerror(errno));
}
}
return res;
}

View File

@ -1,306 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2003-2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Include encfs first, because we need to include fuse.h before any inclusion
// of sys/stat.h or other system headers (to be safe)
#include "encfs.h"
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef linux
#include <sys/fsuid.h>
#endif
#include <cstring>
#include "config.h"
#include "FileNode.h"
#include "FileUtils.h"
#include "Cipher.h"
#include "CipherFileIO.h"
#include "RawFileIO.h"
#include "MACFileIO.h"
#include "DirNode.h"
#include "FileIO.h"
#include "MemoryPool.h"
#include "Mutex.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
using namespace std;
using namespace rel;
using namespace rlog;
/*
TODO: locking at the FileNode level is inefficient, since this precludes
multiple IO operations from going on concurrently within the same file.
There is no reason why simultainous reads cannot be satisfied, or why one
read has to wait for the decoding of the previous read before it can be
sent to the IO subsystem!
*/
static RLogChannel *Info = DEF_CHANNEL("info/FileNode", Log_Info);
FileNode::FileNode(DirNode *parent_, const FSConfigPtr &cfg,
const char *plaintextName_, const char *cipherName_)
{
pthread_mutex_init( &mutex, 0 );
Lock _lock( mutex );
this->_pname = plaintextName_;
this->_cname = cipherName_;
this->parent = parent_;
this->fsConfig = cfg;
// chain RawFileIO & CipherFileIO
shared_ptr<FileIO> rawIO( new RawFileIO( _cname ) );
io = shared_ptr<FileIO>( new CipherFileIO( rawIO, fsConfig ));
if(cfg->config->block_mac_bytes() || cfg->config->block_mac_rand_bytes())
io = shared_ptr<FileIO>(new MACFileIO(io, fsConfig));
}
FileNode::~FileNode()
{
// FileNode mutex should be locked before the destructor is called
//pthread_mutex_lock( &mutex );
_pname.assign( _pname.length(), '\0' );
_cname.assign( _cname.length(), '\0' );
io.reset();
pthread_mutex_destroy( &mutex );
}
const char *FileNode::cipherName() const
{
return _cname.c_str();
}
const char *FileNode::plaintextName() const
{
return _pname.c_str();
}
string FileNode::plaintextParent() const
{
return parentDirectory( _pname );
}
static bool setIV(const shared_ptr<FileIO> &io, uint64_t iv)
{
struct stat stbuf;
if((io->getAttr(&stbuf) < 0) || S_ISREG(stbuf.st_mode))
return io->setIV( iv );
else
return true;
}
bool FileNode::setName( const char *plaintextName_, const char *cipherName_,
uint64_t iv, bool setIVFirst )
{
//Lock _lock( mutex );
rDebug("calling setIV on %s", cipherName_);
if(setIVFirst)
{
if(fsConfig->config->external_iv() && !setIV(io, iv))
return false;
// now change the name..
if(plaintextName_)
this->_pname = plaintextName_;
if(cipherName_)
{
this->_cname = cipherName_;
io->setFileName( cipherName_ );
}
} else
{
std::string oldPName = _pname;
std::string oldCName = _cname;
if(plaintextName_)
this->_pname = plaintextName_;
if(cipherName_)
{
this->_cname = cipherName_;
io->setFileName( cipherName_ );
}
if(fsConfig->config->external_iv() && !setIV(io, iv))
{
_pname = oldPName;
_cname = oldCName;
return false;
}
}
return true;
}
int FileNode::mknod(mode_t mode, dev_t rdev, uid_t uid, gid_t gid)
{
Lock _lock( mutex );
int res;
int olduid = -1;
int oldgid = -1;
if(uid != 0)
{
olduid = setfsuid( uid );
if(olduid == -1)
{
rInfo("setfsuid error: %s", strerror(errno));
return -EPERM;
}
}
if(gid != 0)
{
oldgid = setfsgid( gid );
if(oldgid == -1)
{
rInfo("setfsgid error: %s", strerror(errno));
return -EPERM;
}
}
/*
* cf. xmp_mknod() in fusexmp.c
* The regular file stuff could be stripped off if there
* were a create method (advised to have)
*/
if (S_ISREG( mode )) {
res = ::open( _cname.c_str(), O_CREAT | O_EXCL | O_WRONLY, mode );
if (res >= 0)
res = ::close( res );
} else if (S_ISFIFO( mode ))
res = ::mkfifo( _cname.c_str(), mode );
else
res = ::mknod( _cname.c_str(), mode, rdev );
if(olduid >= 0)
setfsuid( olduid );
if(oldgid >= 0)
setfsgid( oldgid );
if(res == -1)
{
int eno = errno;
rDebug("mknod error: %s", strerror(eno));
res = -eno;
}
return res;
}
int FileNode::open(int flags) const
{
Lock _lock( mutex );
int res = io->open( flags );
return res;
}
int FileNode::getAttr(struct stat *stbuf) const
{
Lock _lock( mutex );
int res = io->getAttr( stbuf );
return res;
}
off_t FileNode::getSize() const
{
Lock _lock( mutex );
int res = io->getSize();
return res;
}
ssize_t FileNode::read( off_t offset, unsigned char *data, ssize_t size ) const
{
IORequest req;
req.offset = offset;
req.dataLen = size;
req.data = data;
Lock _lock( mutex );
return io->read( req );
}
bool FileNode::write(off_t offset, unsigned char *data, ssize_t size)
{
rLog(Info, "FileNode::write offset %" PRIi64 ", data size %i",
offset, (int)size);
IORequest req;
req.offset = offset;
req.dataLen = size;
req.data = data;
Lock _lock( mutex );
return io->write( req );
}
int FileNode::truncate( off_t size )
{
Lock _lock( mutex );
return io->truncate( size );
}
int FileNode::sync(bool datasync)
{
Lock _lock( mutex );
int fh = io->open( O_RDONLY );
if(fh >= 0)
{
int res = -EIO;
#ifdef linux
if(datasync)
res = fdatasync( fh );
else
res = fsync( fh );
#else
// no fdatasync support
// TODO: use autoconfig to check for it..
res = fsync(fh);
#endif
if(res == -1)
res = -errno;
return res;
} else
return fh;
}

View File

@ -1,304 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MACFileIO.h"
#include "MemoryPool.h"
#include "FileUtils.h"
#include "config.pb.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include <rlog/RLogChannel.h>
#include <cstring>
#include "i18n.h"
using namespace rlog;
using namespace std;
static RLogChannel *Info = DEF_CHANNEL("info/MACFileIO", Log_Info);
//
// Version 1.0 worked on blocks of size (blockSize + headerSize).
// That is, it took [blockSize] worth of user data and added headers.
// Version 2.0 takes [blockSize - headerSize] worth of user data and writes
// [blockSize] bytes. That way the size going into the crypto engine is
// valid from what was selected based on the crypto module allowed ranges!
// Version 2.1 allows per-block rand bytes to be used without enabling MAC.
//
// The information about MACFileIO currently does not make its way into the
// configuration file, so there is no easy way to make this backward
// compatible, except at a high level by checking a revision number for the
// filesystem...
//
static Interface MACFileIO_iface = makeInterface("FileIO/MAC", 2, 1, 0);
int dataBlockSize(const FSConfigPtr &cfg)
{
return cfg->config->block_size()
- cfg->config->block_mac_bytes()
- cfg->config->block_mac_rand_bytes();
}
MACFileIO::MACFileIO( const shared_ptr<FileIO> &_base,
const FSConfigPtr &cfg )
: BlockFileIO( dataBlockSize( cfg ), cfg )
, base( _base )
, cipher( cfg->cipher )
, key( cfg->key )
, macBytes( cfg->config->block_mac_bytes() )
, randBytes( cfg->config->block_mac_rand_bytes() )
, warnOnly( cfg->opts->forceDecode )
{
rAssert( macBytes >= 0 && macBytes <= 8 );
rAssert( randBytes >= 0 );
rLog(Info, "fs block size = %i, macBytes = %i, randBytes = %i",
cfg->config->block_size(),
cfg->config->block_mac_bytes(),
cfg->config->block_mac_rand_bytes());
}
MACFileIO::~MACFileIO()
{
}
Interface MACFileIO::interface() const
{
return MACFileIO_iface;
}
int MACFileIO::open( int flags )
{
return base->open( flags );
}
void MACFileIO::setFileName( const char *fileName )
{
base->setFileName( fileName );
}
const char *MACFileIO::getFileName() const
{
return base->getFileName();
}
bool MACFileIO::setIV( uint64_t iv )
{
return base->setIV( iv );
}
inline static off_t roundUpDivide( off_t numerator, int denominator )
{
// integer arithmetic always rounds down, so we can round up by adding
// enough so that any value other then a multiple of denominator gets
// rouned to the next highest value.
return ( numerator + denominator - 1 ) / denominator;
}
// Convert from a location in the raw file to a location when MAC headers are
// interleved with the data.
// So, if the filesystem stores/encrypts [blockSize] bytes per block, then
// [blockSize - headerSize] of those bytes will contain user-supplied data,
// and the rest ([headerSize]) will contain the MAC header for this block.
// Example, offset points to second block (of user-data)
// offset = blockSize - headerSize
// ... blockNum = 1
// ... partialBlock = 0
// ... adjLoc = 1 * blockSize
static off_t locWithHeader( off_t offset, int blockSize, int headerSize )
{
off_t blockNum = roundUpDivide( offset , blockSize - headerSize );
return offset + blockNum * headerSize;
}
// convert from a given location in the stream containing headers, and return a
// location in the user-data stream (which doesn't contain MAC headers)..
// The output value will always be less then the input value, because the
// headers are stored at the beginning of the block, so even the first data is
// offset by the size of the header.
static off_t locWithoutHeader( off_t offset, int blockSize, int headerSize )
{
off_t blockNum = roundUpDivide( offset , blockSize );
return offset - blockNum * headerSize;
}
int MACFileIO::getAttr( struct stat *stbuf ) const
{
int res = base->getAttr( stbuf );
if(res == 0 && S_ISREG(stbuf->st_mode))
{
// have to adjust size field..
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
stbuf->st_size = locWithoutHeader( stbuf->st_size, bs, headerSize );
}
return res;
}
off_t MACFileIO::getSize() const
{
// adjust the size to hide the header overhead we tack on..
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
off_t size = base->getSize();
if(size > 0)
size = locWithoutHeader( size, bs, headerSize );
return size;
}
ssize_t MACFileIO::readOneBlock( const IORequest &req ) const
{
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
MemBlock mb = MemoryPool::allocate( bs );
IORequest tmp;
tmp.offset = locWithHeader( req.offset, bs, headerSize );
tmp.data = mb.data;
tmp.dataLen = headerSize + req.dataLen;
// get the data from the base FileIO layer
ssize_t readSize = base->read( tmp );
// don't store zeros if configured for zero-block pass-through
bool skipBlock = true;
if( _allowHoles )
{
for(int i=0; i<readSize; ++i)
if(tmp.data[i] != 0)
{
skipBlock = false;
break;
}
} else if(macBytes > 0)
skipBlock = false;
if(readSize > headerSize)
{
if(!skipBlock)
{
// At this point the data has been decoded. So, compute the MAC of
// the block and check against the checksum stored in the header..
uint64_t mac = cipher->MAC_64( tmp.data + macBytes,
readSize - macBytes, key );
for(int i=0; i<macBytes; ++i, mac >>= 8)
{
int test = mac & 0xff;
int stored = tmp.data[i];
if(test != stored)
{
// uh oh..
long blockNum = req.offset / bs;
rWarning(_("MAC comparison failure in block %li"),
blockNum);
if( !warnOnly )
{
MemoryPool::release( mb );
throw ERROR(
_("MAC comparison failure, refusing to read"));
}
break;
}
}
}
// now copy the data to the output buffer
readSize -= headerSize;
memcpy( req.data, tmp.data + headerSize, readSize );
} else
{
rDebug("readSize %i at offset %" PRIi64, (int)readSize, req.offset);
if(readSize > 0)
readSize = 0;
}
MemoryPool::release( mb );
return readSize;
}
bool MACFileIO::writeOneBlock( const IORequest &req )
{
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
// we have the unencrypted data, so we need to attach a header to it.
MemBlock mb = MemoryPool::allocate( bs );
IORequest newReq;
newReq.offset = locWithHeader( req.offset, bs, headerSize );
newReq.data = mb.data;
newReq.dataLen = headerSize + req.dataLen;
memset( newReq.data, 0, headerSize );
memcpy( newReq.data + headerSize, req.data, req.dataLen );
if(randBytes > 0)
{
if(!cipher->randomize( newReq.data+macBytes, randBytes, false ))
return false;
}
if(macBytes > 0)
{
// compute the mac (which includes the random data) and fill it in
uint64_t mac = cipher->MAC_64( newReq.data+macBytes,
req.dataLen + randBytes, key );
for(int i=0; i<macBytes; ++i)
{
newReq.data[i] = mac & 0xff;
mac >>= 8;
}
}
// now, we can let the next level have it..
bool ok = base->write( newReq );
MemoryPool::release( mb );
return ok;
}
int MACFileIO::truncate( off_t size )
{
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
int res = BlockFileIO::truncate( size, 0 );
if(res == 0)
base->truncate( locWithHeader( size, bs, headerSize ) );
return res;
}
bool MACFileIO::isWritable() const
{
return base->isWritable();
}

View File

@ -1,163 +0,0 @@
include $(top_srcdir)/Makefile.common
ALL_INCLUDES = @RLOG_CFLAGS@ @OPENSSL_CFLAGS@
ALL_INCLUDES += @PROTOBUF_CFLAGS@
ALL_LDFLAGS = @RLOG_LIBS@ @OPENSSL_LIBS@
ALL_LDFLAGS += @PROTOBUF_LIBS@
INCLUDES = $(all_includes) -I../intl
AM_CXXFLAGS = -DRLOG_COMPONENT="encfs" $(ALL_INCLUDES)
if DARWIN
# needed to select correct API in fuse.h
AM_CXXFLAGS += -D__FreeBSD__=10
endif
if BUILD_NLS
# define a C macro LOCALEDIR indicating where catalogs will be installed
#localedir = $(datadir)/locale
AM_CXXFLAGS += -DLOCALEDIR=\"$(localedir)\"
ALL_LDFLAGS += @LIBINTL@
endif
lib_LTLIBRARIES = libencfs.la
bin_PROGRAMS = encfs encfsctl
dist_bin_SCRIPTS = encfssh
noinst_PROGRAMS = test makeKey
all-local: encfs-man.html
encfs_LDADD = libencfs.la $(ALL_LDFLAGS)
encfsctl_LDADD = libencfs.la $(ALL_LDFLAGS)
test_LDADD = libencfs.la $(ALL_LDFLAGS)
makeKey_LDADD = libencfs.la $(ALL_LDFLAGS)
if BUILD_STATIC
encfs_LDFLAGS = -all-static
encfsctl_LDFLAGS = -all-static
test_LDFLAGS = -all-static
makeKey_LDFLAGS = -all-static
endif
# CURRENT : REVISION : AGE
# +1 : 0 : +1 => new interface that does not break old one
# +1 : 0 : 0 => new interface that breaks old one
# : : 0 => no new interfaces, but breaks old apps
# : +1 : => internal changes, nothing breaks
#
libencfs_la_LDFLAGS = -version-info 7:0:0
libencfs_la_LIBADD = @RLOG_LIBS@ @OPENSSL_LIBS@
EXTRASRC = ../intl/autosprintf.cpp
if BUILD_OPENSSL
if BUILD_SSLCIPHER
EXTRASRC += SSL_Cipher.cpp
endif
endif
libencfs_la_SOURCES = \
readpassphrase.cpp \
base64.cpp \
config.pb.cc \
ConfigReader.cpp \
ConfigVar.cpp \
Context.cpp \
Cipher.cpp \
CipherKey.cpp \
FileIO.cpp \
RawFileIO.cpp \
BlockFileIO.cpp \
CipherFileIO.cpp \
MACFileIO.cpp \
NameIO.cpp \
StreamNameIO.cpp \
BlockNameIO.cpp \
NullNameIO.cpp \
Interface.cpp \
MemoryPool.cpp \
NullCipher.cpp \
DirNode.cpp \
FileNode.cpp \
FileUtils.cpp \
openssl.cpp \
XmlReader.cpp \
${EXTRASRC}
encfs_SOURCES = \
encfs.cpp \
main.cpp
test_SOURCES = \
test.cpp
makeKey_SOURCES = \
makeKey.cpp
encfsctl_SOURCES = \
encfsctl.cpp
noinst_HEADERS = \
base64.h \
BlockFileIO.h \
BlockNameIO.h \
CipherFileIO.h \
Cipher.h \
CipherKey.h \
ConfigReader.h \
ConfigVar.h \
config.pb.h \
Context.h \
DirNode.h \
encfs.h \
FileIO.h \
FileNode.h \
FileUtils.h \
FSConfig.h \
Interface.h \
i18n.h \
MACFileIO.h \
MemoryPool.h \
Mutex.h \
NameIO.h \
NullCipher.h \
NullNameIO.h \
openssl.h \
Range.h \
RawFileIO.h \
readpassphrase.h \
SSL_Cipher.h \
StreamNameIO.h
man_MANS=encfs.1 encfsctl.1
EXTRA_DIST = encfs.pod encfsctl.pod encfs.1 encfsctl.1 encfs-man.html
SUFFIXES = .1 .pod .proto
config.pb.cc: config.pb.h
config.pb.h: config.proto
@PROTOC@ --cpp_out=. config.proto
if BUILD_MAN
# since we have POD2MAN, we can specify how to rebuild encfs.1 if necessary
.pod.1:
@POD2MAN@ --section=1 --release=@VERSION@ --center="Encrypted Filesystem" $< $@
CLEANFILES = encfs.1 encfsctl.1
endif
if BUILD_MANHTML
encfs-man.html: encfs.pod
@POD2HTML@ encfs.pod > $@
endif
tests:
perl -MTest::Harness -e '$$Test::Harness::verbose=0; runtests @ARGV;' *.t
tests-verbose:
perl -MTest::Harness -e '$$Test::Harness::verbose=1; runtests @ARGV;' *.t

View File

@ -1,178 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2003, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "MemoryPool.h"
#include <rlog/rlog.h>
#include <cstdlib>
#include <cstring>
#include "config.h"
#include <pthread.h>
#include <sys/mman.h>
#ifdef HAVE_VALGRIND_MEMCHECK_H
#include <valgrind/memcheck.h>
#else
#define VALGRIND_MAKE_MEM_NOACCESS( a, b )
#define VALGRIND_MAKE_MEM_UNDEFINED( a, b )
#endif
using namespace rlog;
# include <openssl/crypto.h>
# include <openssl/buffer.h>
#define BLOCKDATA( BLOCK ) (unsigned char*)BLOCK->data->data
struct BlockList
{
BlockList *next;
int size;
BUF_MEM *data;
};
static BlockList *allocBlock( int size )
{
BlockList *block = new BlockList;
block->size = size;
block->data = BUF_MEM_new( );
BUF_MEM_grow( block->data, size );
VALGRIND_MAKE_MEM_NOACCESS( block->data->data, block->data->max );
return block;
}
static void freeBlock( BlockList *el )
{
VALGRIND_MAKE_MEM_UNDEFINED( el->data->data, el->data->max );
BUF_MEM_free( el->data );
delete el;
}
static pthread_mutex_t gMPoolMutex = PTHREAD_MUTEX_INITIALIZER;
static BlockList *gMemPool = NULL;
MemBlock MemoryPool::allocate( int size )
{
pthread_mutex_lock( &gMPoolMutex );
BlockList *parent = NULL;
BlockList *block = gMemPool;
// check if we already have a large enough block available..
while(block != NULL && block->size < size)
{
parent = block;
block = block->next;
}
// unlink block from list
if(block)
{
if(!parent)
gMemPool = block->next;
else
parent->next = block->next;
}
pthread_mutex_unlock( &gMPoolMutex );
if(!block)
block = allocBlock( size );
block->next = NULL;
MemBlock result;
result.data = BLOCKDATA(block);
result.internalData = block;
VALGRIND_MAKE_MEM_UNDEFINED( result.data, size );
return result;
}
void MemoryPool::release( const MemBlock &mb )
{
pthread_mutex_lock( &gMPoolMutex );
BlockList *block = (BlockList*)mb.internalData;
// just to be sure there's nothing important left in buffers..
VALGRIND_MAKE_MEM_UNDEFINED( block->data->data, block->size );
memset( BLOCKDATA(block) , 0, block->size);
VALGRIND_MAKE_MEM_NOACCESS( block->data->data, block->data->max );
block->next = gMemPool;
gMemPool = block;
pthread_mutex_unlock( &gMPoolMutex );
}
void MemoryPool::destroyAll()
{
pthread_mutex_lock( &gMPoolMutex );
BlockList *block = gMemPool;
gMemPool = NULL;
pthread_mutex_unlock( &gMPoolMutex );
while(block != NULL)
{
BlockList *next = block->next;
freeBlock( block );
block = next;
}
}
SecureMem::SecureMem(int len)
{
data = (char *)OPENSSL_malloc(len);
if (data)
{
size = len;
mlock(data, size);
memset(data, '\0', size);
VALGRIND_MAKE_MEM_UNDEFINED( data, size );
} else
{
size = 0;
}
}
SecureMem::~SecureMem()
{
if (size)
{
memset(data, '\0', size);
munlock(data, size);
OPENSSL_free(data);
VALGRIND_MAKE_MEM_NOACCESS( data, size );
data = NULL;
size = 0;
}
}

View File

@ -1,351 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NameIO.h"
#include "config.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include <map>
#include <cstring>
// for static build. Need to reference the modules which are registered at
// run-time, to ensure that the linker doesn't optimize them away.
#include <iostream>
#include "BlockNameIO.h"
#include "StreamNameIO.h"
#include "NullNameIO.h"
using namespace std;
using namespace rlog;
#define REF_MODULE(TYPE) \
if(!TYPE::Enabled() ) \
cerr << "referenceModule: should never happen\n";
static
void AddSymbolReferences()
{
REF_MODULE(BlockNameIO)
REF_MODULE(StreamNameIO)
REF_MODULE(NullNameIO)
}
struct NameIOAlg
{
bool hidden;
NameIO::Constructor constructor;
string description;
Interface iface;
};
typedef multimap< string, NameIOAlg > NameIOMap_t;
static NameIOMap_t *gNameIOMap = 0;
list< NameIO::Algorithm >
NameIO::GetAlgorithmList( bool includeHidden )
{
AddSymbolReferences();
list< Algorithm > result;
if(gNameIOMap)
{
NameIOMap_t::const_iterator it;
NameIOMap_t::const_iterator end = gNameIOMap->end();
for(it = gNameIOMap->begin(); it != end; ++it)
{
if(includeHidden || !it->second.hidden)
{
Algorithm tmp;
tmp.name = it->first;
tmp.description = it->second.description;
tmp.iface = it->second.iface;
result.push_back( tmp );
}
}
}
return result;
}
bool NameIO::Register( const char *name, const char *description,
const Interface &iface, Constructor constructor,
bool hidden )
{
if( !gNameIOMap )
gNameIOMap = new NameIOMap_t;
NameIOAlg alg;
alg.hidden = hidden;
alg.constructor = constructor;
alg.description = description;
alg.iface = iface;
gNameIOMap->insert( make_pair( string(name), alg ));
return true;
}
shared_ptr<NameIO> NameIO::New( const string &name,
const shared_ptr<Cipher> &cipher, const CipherKey &key)
{
shared_ptr<NameIO> result;
if(gNameIOMap)
{
NameIOMap_t::const_iterator it = gNameIOMap->find( name );
if(it != gNameIOMap->end())
{
Constructor fn = it->second.constructor;
result = (*fn)( it->second.iface, cipher, key );
}
}
return result;
}
shared_ptr<NameIO> NameIO::New( const Interface &iface,
const shared_ptr<Cipher> &cipher, const CipherKey &key )
{
shared_ptr<NameIO> result;
if(gNameIOMap)
{
NameIOMap_t::const_iterator it;
NameIOMap_t::const_iterator end = gNameIOMap->end();
for(it = gNameIOMap->begin(); it != end; ++it)
{
if( implements(it->second.iface, iface ))
{
Constructor fn = it->second.constructor;
result = (*fn)( iface, cipher, key );
break;
}
}
}
return result;
}
NameIO::NameIO()
: chainedNameIV( false ), reverseEncryption( false )
{
}
NameIO::~NameIO()
{
}
void NameIO::setChainedNameIV( bool enable )
{
chainedNameIV = enable;
}
bool NameIO::getChainedNameIV() const
{
return chainedNameIV;
}
void NameIO::setReverseEncryption( bool enable )
{
reverseEncryption = enable;
}
bool NameIO::getReverseEncryption() const
{
return reverseEncryption;
}
std::string NameIO::recodePath( const char *path,
int (NameIO::*_length)(int) const,
int (NameIO::*_code)(const char*, int, uint64_t *, char*) const,
uint64_t *iv ) const
{
string output;
while( *path )
{
if( *path == '/' )
{
if( !output.empty() ) // don't start the string with '/'
output += '/';
++path;
} else
{
bool isDotFile = (*path == '.');
const char *next = strchr( path, '/' );
int len = next ? next - path : strlen( path );
// at this point we know that len > 0
if( isDotFile && (path[len-1] == '.') && (len <= 2) )
{
output.append(len, '.'); // append [len] copies of '.'
path += len;
continue;
}
// figure out buffer sizes
int approxLen = (this->*_length)( len );
if(approxLen <= 0)
throw ERROR("Filename too small to decode");
BUFFER_INIT( codeBuf, 32, (unsigned int)approxLen+1 )
// code the name
int codedLen = (this->*_code)( path, len, iv, codeBuf );
rAssert( codedLen <= approxLen );
rAssert( codeBuf[codedLen] == '\0' );
path += len;
// append result to string
output += (char*)codeBuf;
BUFFER_RESET( codeBuf )
}
}
return output;
}
std::string NameIO::encodePath( const char *plaintextPath ) const
{
uint64_t iv = 0;
return encodePath( plaintextPath, &iv);
}
std::string NameIO::decodePath( const char *cipherPath ) const
{
uint64_t iv = 0;
return decodePath( cipherPath, &iv );
}
std::string NameIO::_encodePath( const char *plaintextPath, uint64_t *iv ) const
{
// if chaining is not enabled, then the iv pointer is not used..
if(!chainedNameIV)
iv = 0;
return recodePath( plaintextPath,
&NameIO::maxEncodedNameLen, &NameIO::encodeName, iv);
}
std::string NameIO::_decodePath( const char *cipherPath, uint64_t *iv ) const
{
// if chaining is not enabled, then the iv pointer is not used..
if(!chainedNameIV)
iv = 0;
return recodePath( cipherPath,
&NameIO::maxDecodedNameLen, &NameIO::decodeName, iv);
}
std::string NameIO::encodePath( const char *path, uint64_t *iv ) const
{
return getReverseEncryption() ?
_decodePath( path, iv ) :
_encodePath( path, iv );
}
std::string NameIO::decodePath( const char *path, uint64_t *iv ) const
{
return getReverseEncryption() ?
_encodePath( path, iv ) :
_decodePath( path, iv );
}
int NameIO::encodeName( const char *input, int length, char *output ) const
{
return encodeName( input, length, (uint64_t*)0, output );
}
int NameIO::decodeName( const char *input, int length, char *output ) const
{
return decodeName( input, length, (uint64_t*)0, output );
}
std::string NameIO::_encodeName( const char *plaintextName, int length ) const
{
int approxLen = maxEncodedNameLen( length );
BUFFER_INIT( codeBuf, 32, (unsigned int)approxLen+1 )
// code the name
int codedLen = encodeName( plaintextName, length, 0, codeBuf );
rAssert( codedLen <= approxLen );
rAssert( codeBuf[codedLen] == '\0' );
// append result to string
std::string result = (char*)codeBuf;
BUFFER_RESET( codeBuf )
return result;
}
std::string NameIO::_decodeName( const char *encodedName, int length ) const
{
int approxLen = maxDecodedNameLen( length );
BUFFER_INIT( codeBuf, 32, (unsigned int)approxLen+1 )
// code the name
int codedLen = decodeName( encodedName, length, 0, codeBuf );
rAssert( codedLen <= approxLen );
rAssert( codeBuf[codedLen] == '\0' );
// append result to string
std::string result = (char*)codeBuf;
BUFFER_RESET( codeBuf )
return result;
}
std::string NameIO::encodeName( const char *path, int length ) const
{
return getReverseEncryption() ?
_decodeName( path, length ) :
_encodeName( path, length );
}
std::string NameIO::decodeName( const char *path, int length ) const
{
return getReverseEncryption() ?
_encodeName( path, length ) :
_decodeName( path, length );
}
/*
int NameIO::encodeName( const char *path, int length,
char *output ) const
{
return getReverseEncryption() ?
_decodeName( path, length, output ) :
_encodeName( path, length, output );
}
int NameIO::decodeName( const char *path, int length,
char *output ) const
{
return getReverseEncryption() ?
_encodeName( path, length, output ) :
_decodeName( path, length, output );
}
*/

View File

@ -1,325 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef linux
#define _XOPEN_SOURCE 500 // pick up pread , pwrite
#endif
#include <unistd.h>
#include "RawFileIO.h"
#include <rlog/rlog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstring>
#include <cerrno>
using namespace std;
static Interface RawFileIO_iface = makeInterface("FileIO/Raw", 1, 0, 0);
FileIO *NewRawFileIO( const Interface &iface )
{
(void)iface;
return new RawFileIO();
}
inline void swap( int &x, int &y )
{
int tmp = x;
x = y;
y = tmp;
}
RawFileIO::RawFileIO( )
: knownSize( false )
, fileSize(0)
, fd( -1 )
, oldfd( -1 )
, canWrite( false )
{
}
RawFileIO::RawFileIO( const std::string &fileName )
: name( fileName )
, knownSize( false )
, fileSize( 0 )
, fd( -1 )
, oldfd( -1 )
, canWrite( false )
{
}
RawFileIO::~RawFileIO()
{
int _fd = -1;
int _oldfd = -1;
swap( _fd, fd );
swap( _oldfd, oldfd );
if( _oldfd != -1 )
close( _oldfd );
if( _fd != -1 )
close( _fd );
}
Interface RawFileIO::interface() const
{
return RawFileIO_iface;
}
/*
Workaround for opening a file for write when permissions don't allow.
Since the kernel has already checked permissions, we can assume it is ok to
provide access. So force it by changing permissions temporarily. Should
be called with a lock around it so that there won't be a race condition
with calls to lstat picking up the wrong permissions.
*/
static int open_readonly_workaround(const char *path, int flags)
{
int fd = -1;
struct stat stbuf;
memset(&stbuf, 0, sizeof(struct stat));
if(lstat( path, &stbuf ) != -1)
{
// make sure user has read/write permission..
chmod( path , stbuf.st_mode | 0600 );
fd = ::open( path , flags );
chmod( path , stbuf.st_mode );
} else
{
rInfo("can't stat file %s", path );
}
return fd;
}
/*
We shouldn't have to support all possible open flags, so untaint the flags
argument by only taking ones we understand and accept.
- Since the kernel has already done permission tests before calling us, we
shouldn't have to worry about access control.
- Basically we just need to distinguish between read and write flags
- Also keep the O_LARGEFILE flag, in case the underlying filesystem needs
it..
*/
int RawFileIO::open(int flags)
{
bool requestWrite = ((flags & O_RDWR) || (flags & O_WRONLY));
rDebug("open call for %s file", requestWrite ? "writable" : "read only");
int result = 0;
// if we have a descriptor and it is writable, or we don't need writable..
if((fd >= 0) && (canWrite || !requestWrite))
{
rDebug("using existing file descriptor");
result = fd; // success
} else
{
int finalFlags = requestWrite ? O_RDWR : O_RDONLY;
#if defined(O_LARGEFILE)
if( flags & O_LARGEFILE )
finalFlags |= O_LARGEFILE;
#else
#warning O_LARGEFILE not supported
#endif
int newFd = ::open( name.c_str(), finalFlags );
rDebug("open file with flags %i, result = %i", finalFlags, newFd);
if((newFd == -1) && (errno == EACCES))
{
rDebug("using readonly workaround for open");
newFd = open_readonly_workaround( name.c_str(), finalFlags );
}
if(newFd >= 0)
{
if(oldfd >= 0)
{
rError("leaking FD?: oldfd = %i, fd = %i, newfd = %i",
oldfd, fd, newFd);
}
// the old fd might still be in use, so just keep it around for
// now.
canWrite = requestWrite;
oldfd = fd;
result = fd = newFd;
} else
{
result = -errno;
rInfo("::open error: %s", strerror(errno));
}
}
if(result < 0)
rInfo("file %s open failure: %i", name.c_str(), -result);
return result;
}
int RawFileIO::getAttr( struct stat *stbuf ) const
{
int res = lstat( name.c_str(), stbuf );
int eno = errno;
if(res < 0)
rInfo("getAttr error on %s: %s", name.c_str(), strerror( eno ));
return ( res < 0 ) ? -eno : 0;
}
void RawFileIO::setFileName( const char *fileName )
{
name = fileName;
}
const char *RawFileIO::getFileName() const
{
return name.c_str();
}
off_t RawFileIO::getSize() const
{
if(!knownSize)
{
struct stat stbuf;
memset( &stbuf, 0, sizeof( struct stat ));
int res = lstat( name.c_str(), &stbuf );
if(res == 0)
{
const_cast<RawFileIO*>(this)->fileSize = stbuf.st_size;
const_cast<RawFileIO*>(this)->knownSize = true;
return fileSize;
} else
return -1;
} else
{
return fileSize;
}
}
ssize_t RawFileIO::read( const IORequest &req ) const
{
rAssert( fd >= 0 );
ssize_t readSize = pread( fd, req.data, req.dataLen, req.offset );
if(readSize < 0)
{
rInfo("read failed at offset %" PRIi64 " for %i bytes: %s",
req.offset, req.dataLen, strerror( errno ));
}
return readSize;
}
bool RawFileIO::write( const IORequest &req )
{
rAssert( fd >= 0 );
rAssert( true == canWrite );
int retrys = 10;
void *buf = req.data;
ssize_t bytes = req.dataLen;
off_t offset = req.offset;
while( bytes && retrys > 0 )
{
ssize_t writeSize = ::pwrite( fd, buf, bytes, offset );
if( writeSize < 0 )
{
knownSize = false;
rInfo("write failed at offset %" PRIi64 " for %i bytes: %s",
offset, (int)bytes, strerror( errno ));
return false;
}
bytes -= writeSize;
offset += writeSize;
buf = (void*)((char*)buf + writeSize);
--retrys;
}
if(bytes != 0)
{
rError("Write error: wrote %i bytes of %i, max retries reached\n",
(int)(req.dataLen - bytes), req.dataLen );
knownSize = false;
return false;
} else
{
if(knownSize)
{
off_t last = req.offset + req.dataLen;
if(last > fileSize)
fileSize = last;
}
return true;
}
}
int RawFileIO::truncate( off_t size )
{
int res;
if(fd >= 0 && canWrite)
{
res = ::ftruncate( fd, size );
#ifndef __FreeBSD__
::fdatasync( fd );
#endif
} else
res = ::truncate( name.c_str(), size );
if(res < 0)
{
int eno = errno;
rInfo("truncate failed for %s (%i) size %" PRIi64 ", error %s",
name.c_str(), fd, size, strerror(eno));
res = -eno;
knownSize = false;
} else
{
res = 0;
fileSize = size;
knownSize = true;
}
return res;
}
bool RawFileIO::isWritable() const
{
return canWrite;
}

View File

@ -1,927 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "encfs.h"
#include "config.h"
#include <openssl/blowfish.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#include <openssl/err.h>
#include <openssl/hmac.h>
#include "SSL_Cipher.h"
#include "Range.h"
#include "MemoryPool.h"
#include "Mutex.h"
#include <cstring>
#include <ctime>
#include <sys/mman.h>
#include <sys/time.h>
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include "i18n.h"
using namespace std;
using namespace rel;
using namespace rlog;
const int MAX_KEYLENGTH = 32; // in bytes (256 bit)
const int MAX_IVLENGTH = 16;
const int KEY_CHECKSUM_BYTES = 4;
#ifndef MIN
inline int MIN(int a, int b)
{
return (a < b) ? a : b;
}
#endif
/*
This produces the same result as OpenSSL's EVP_BytesToKey. The difference
is that here we can explicitly specify the key size, instead of relying on
the state of EVP_CIPHER struct. EVP_BytesToKey will only produce 128 bit
keys for the EVP Blowfish interface, which is not what we want.
DEPRECATED: this is here for backward compatibilty only. Use PBKDF
*/
int BytesToKey( int keyLen, int ivLen, const EVP_MD *md,
const unsigned char *data, int dataLen,
unsigned int rounds, unsigned char *key, unsigned char *iv)
{
if( data == NULL || dataLen == 0 )
return 0; // OpenSSL returns nkey here, but why? It is a failure..
unsigned char mdBuf[ EVP_MAX_MD_SIZE ];
unsigned int mds=0;
int addmd =0;
int nkey = key ? keyLen : 0;
int niv = iv ? ivLen : 0;
EVP_MD_CTX cx;
EVP_MD_CTX_init( &cx );
for(;;)
{
EVP_DigestInit_ex( &cx, md, NULL );
if( addmd++ )
EVP_DigestUpdate( &cx, mdBuf, mds );
EVP_DigestUpdate( &cx, data, dataLen );
EVP_DigestFinal_ex( &cx, mdBuf, &mds );
for(unsigned int i=1; i < rounds; ++i)
{
EVP_DigestInit_ex( &cx, md, NULL );
EVP_DigestUpdate( &cx, mdBuf, mds );
EVP_DigestFinal_ex( &cx, mdBuf, &mds );
}
int offset = 0;
int toCopy = MIN( nkey, (int)mds - offset );
if( toCopy )
{
memcpy( key, mdBuf+offset, toCopy );
key += toCopy;
nkey -= toCopy;
offset += toCopy;
}
toCopy = MIN( niv, (int)mds - offset );
if( toCopy )
{
memcpy( iv, mdBuf+offset, toCopy );
iv += toCopy;
niv -= toCopy;
offset += toCopy;
}
if((nkey == 0) && (niv == 0)) break;
}
EVP_MD_CTX_cleanup( &cx );
OPENSSL_cleanse( mdBuf, sizeof(mdBuf) );
return keyLen;
}
long time_diff(const timeval &end, const timeval &start)
{
return (end.tv_sec - start.tv_sec) * 1000 * 1000 +
(end.tv_usec - start.tv_usec);
}
int TimedPBKDF2(const char *pass, int passlen,
const unsigned char *salt, int saltlen,
int keylen, unsigned char *out,
long desiredPDFTime)
{
int iter = 1000;
timeval start, end;
for(;;)
{
gettimeofday( &start, 0 );
int res = PKCS5_PBKDF2_HMAC_SHA1(
pass, passlen, const_cast<unsigned char*>(salt), saltlen,
iter, keylen, out);
if(res != 1)
return -1;
gettimeofday( &end, 0 );
long delta = time_diff(end, start);
if(delta < desiredPDFTime / 8)
{
iter *= 4;
} else if(delta < (5 * desiredPDFTime / 6))
{
// estimate number of iterations to get close to desired time
iter = (int)((double)iter * (double)desiredPDFTime
/ (double)delta);
} else
return iter;
}
}
// - Version 1:0 used EVP_BytesToKey, which didn't do the right thing for
// Blowfish key lengths > 128 bit.
// - Version 2:0 uses BytesToKey.
// We support both 2:0 and 1:0, hence current:revision:age = 2:0:1
// - Version 2:1 adds support for Message Digest function interface
// - Version 2:2 adds PBKDF2 for password derivation
// - Version 3:0 adds a new IV mechanism
static Interface BlowfishInterface = makeInterface( "ssl/blowfish", 3, 0, 2 );
static Interface AESInterface = makeInterface( "ssl/aes", 3, 0, 2 );
#if defined(HAVE_EVP_BF)
static Range BFKeyRange(128,256,32);
static Range BFBlockRange(64,4096,8);
static shared_ptr<Cipher> NewBFCipher( const Interface &iface, int keyLen )
{
if( keyLen <= 0 )
keyLen = 160;
keyLen = BFKeyRange.closest( keyLen );
const EVP_CIPHER *blockCipher = EVP_bf_cbc();
const EVP_CIPHER *streamCipher = EVP_bf_cfb();
return shared_ptr<Cipher>( new SSL_Cipher(iface, BlowfishInterface,
blockCipher, streamCipher, keyLen / 8) );
}
static bool BF_Cipher_registered = Cipher::Register("Blowfish",
// xgroup(setup)
gettext_noop("8 byte block cipher"),
BlowfishInterface, BFKeyRange, BFBlockRange, NewBFCipher);
#endif
#if defined(HAVE_EVP_AES)
static Range AESKeyRange(128,256,64);
static Range AESBlockRange(64,4096,16);
static shared_ptr<Cipher> NewAESCipher( const Interface &iface, int keyLen )
{
if( keyLen <= 0 )
keyLen = 192;
keyLen = AESKeyRange.closest( keyLen );
const EVP_CIPHER *blockCipher = 0;
const EVP_CIPHER *streamCipher = 0;
switch(keyLen)
{
case 128:
blockCipher = EVP_aes_128_cbc();
streamCipher = EVP_aes_128_cfb();
break;
case 192:
blockCipher = EVP_aes_192_cbc();
streamCipher = EVP_aes_192_cfb();
break;
case 256:
default:
blockCipher = EVP_aes_256_cbc();
streamCipher = EVP_aes_256_cfb();
break;
}
return shared_ptr<Cipher>( new SSL_Cipher(iface, AESInterface,
blockCipher, streamCipher, keyLen / 8) );
}
static bool AES_Cipher_registered = Cipher::Register("AES",
"16 byte block cipher",
AESInterface, AESKeyRange, AESBlockRange, NewAESCipher);
#endif
class SSLKey : public AbstractCipherKey
{
public:
pthread_mutex_t mutex;
unsigned int keySize; // in bytes
unsigned int ivLength;
// key data is first _keySize bytes,
// followed by iv of _ivLength bytes,
unsigned char *buffer;
EVP_CIPHER_CTX block_enc;
EVP_CIPHER_CTX block_dec;
EVP_CIPHER_CTX stream_enc;
EVP_CIPHER_CTX stream_dec;
HMAC_CTX mac_ctx;
SSLKey(int keySize, int ivLength);
~SSLKey();
};
SSLKey::SSLKey(int keySize_, int ivLength_)
{
this->keySize = keySize_;
this->ivLength = ivLength_;
pthread_mutex_init( &mutex, 0 );
buffer = (unsigned char *)OPENSSL_malloc( keySize + ivLength );
// most likely fails unless we're running as root, or a user-page-lock
// kernel patch is applied..
mlock( buffer, keySize + ivLength );
memset( buffer, 0, keySize + ivLength );
}
SSLKey::~SSLKey()
{
memset( buffer, 0, keySize + ivLength );
munlock( buffer, keySize + ivLength );
OPENSSL_free( buffer );
keySize = 0;
ivLength = 0;
buffer = 0;
EVP_CIPHER_CTX_cleanup( &block_enc );
EVP_CIPHER_CTX_cleanup( &block_dec );
EVP_CIPHER_CTX_cleanup( &stream_enc );
EVP_CIPHER_CTX_cleanup( &stream_dec );
HMAC_CTX_cleanup( &mac_ctx );
pthread_mutex_destroy( &mutex );
}
inline unsigned char* KeyData( const shared_ptr<SSLKey> &key )
{
return key->buffer;
}
inline unsigned char* IVData( const shared_ptr<SSLKey> &key )
{
return key->buffer + key->keySize;
}
void initKey(const shared_ptr<SSLKey> &key, const EVP_CIPHER *_blockCipher,
const EVP_CIPHER *_streamCipher, int _keySize)
{
Lock lock( key->mutex );
// initialize the cipher context once so that we don't have to do it for
// every block..
EVP_CIPHER_CTX_init( &key->block_enc );
EVP_CIPHER_CTX_init( &key->block_dec );
EVP_CIPHER_CTX_init( &key->stream_enc );
EVP_CIPHER_CTX_init( &key->stream_dec );
EVP_EncryptInit_ex( &key->block_enc, _blockCipher, NULL, NULL, NULL);
EVP_DecryptInit_ex( &key->block_dec, _blockCipher, NULL, NULL, NULL);
EVP_EncryptInit_ex( &key->stream_enc, _streamCipher, NULL, NULL, NULL);
EVP_DecryptInit_ex( &key->stream_dec, _streamCipher, NULL, NULL, NULL);
EVP_CIPHER_CTX_set_key_length( &key->block_enc, _keySize );
EVP_CIPHER_CTX_set_key_length( &key->block_dec, _keySize );
EVP_CIPHER_CTX_set_key_length( &key->stream_enc, _keySize );
EVP_CIPHER_CTX_set_key_length( &key->stream_dec, _keySize );
EVP_CIPHER_CTX_set_padding( &key->block_enc, 0 );
EVP_CIPHER_CTX_set_padding( &key->block_dec, 0 );
EVP_CIPHER_CTX_set_padding( &key->stream_enc, 0 );
EVP_CIPHER_CTX_set_padding( &key->stream_dec, 0 );
EVP_EncryptInit_ex( &key->block_enc, NULL, NULL, KeyData(key), NULL);
EVP_DecryptInit_ex( &key->block_dec, NULL, NULL, KeyData(key), NULL);
EVP_EncryptInit_ex( &key->stream_enc, NULL, NULL, KeyData(key), NULL);
EVP_DecryptInit_ex( &key->stream_dec, NULL, NULL, KeyData(key), NULL);
HMAC_CTX_init( &key->mac_ctx );
HMAC_Init_ex( &key->mac_ctx, KeyData(key), _keySize, EVP_sha1(), 0 );
}
static RLogChannel * CipherInfo = DEF_CHANNEL( "info/cipher", Log_Info );
SSL_Cipher::SSL_Cipher(const Interface &iface_,
const Interface &realIface_,
const EVP_CIPHER *blockCipher,
const EVP_CIPHER *streamCipher,
int keySize_)
{
this->iface = iface_;
this->realIface = realIface_;
this->_blockCipher = blockCipher;
this->_streamCipher = streamCipher;
this->_keySize = keySize_;
this->_ivLength = EVP_CIPHER_iv_length( _blockCipher );
rAssert(_ivLength == 8 || _ivLength == 16);
rLog(CipherInfo, "allocated cipher %s, keySize %i, ivlength %i",
iface.name().c_str(), _keySize, _ivLength);
if( (EVP_CIPHER_key_length( _blockCipher ) != (int )_keySize)
&& iface.major() == 1)
{
rWarning("Running in backward compatibilty mode for 1.0 - \n"
"key is really %i bits, not %i.\n"
"Consider creating a new filesystem and moving your data.",
EVP_CIPHER_key_length( _blockCipher ) * 8,
_keySize * 8 );
}
}
SSL_Cipher::~SSL_Cipher()
{
}
Interface SSL_Cipher::interface() const
{
return realIface;
}
/*
create a key from the password.
Use SHA to distribute entropy from the password into the key.
This algorithm must remain constant for backward compatibility, as this key
is used to encipher/decipher the master key.
*/
CipherKey SSL_Cipher::newKey(const char *password, int passwdLength,
int &iterationCount, long desiredDuration,
const unsigned char *salt, int saltLen)
{
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
if(iterationCount == 0)
{
// timed run, fills in iteration count
int res = TimedPBKDF2(password, passwdLength,
salt, saltLen,
_keySize+_ivLength, KeyData(key),
1000 * desiredDuration);
if(res <= 0)
{
rWarning("openssl error, PBKDF2 failed");
return CipherKey();
} else
iterationCount = res;
} else
{
// known iteration length
if(PKCS5_PBKDF2_HMAC_SHA1(
password, passwdLength,
const_cast<unsigned char*>(salt), saltLen,
iterationCount, _keySize + _ivLength, KeyData(key)) != 1)
{
rWarning("openssl error, PBKDF2 failed");
return CipherKey();
}
}
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
CipherKey SSL_Cipher::newKey(const char *password, int passwdLength)
{
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
int bytes = 0;
if( iface.major() > 1 )
{
// now we use BytesToKey, which can deal with Blowfish keys larger then
// 128 bits.
bytes = BytesToKey( _keySize, _ivLength, EVP_sha1(),
(unsigned char *)password, passwdLength, 16,
KeyData(key), IVData(key) );
// the reason for moving from EVP_BytesToKey to BytesToKey function..
if(bytes != (int)_keySize)
{
rWarning("newKey: BytesToKey returned %i, expecting %i key bytes",
bytes, _keySize);
}
} else
{
// for backward compatibility with filesystems created with 1:0
bytes = EVP_BytesToKey( _blockCipher, EVP_sha1(), NULL,
(unsigned char *)password, passwdLength, 16,
KeyData(key), IVData(key) );
}
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
/*
Create a random key.
We use the OpenSSL library to generate random bytes, then take the hash of
those bytes to use as the key.
This algorithm can change at any time without affecting backward
compatibility.
*/
CipherKey SSL_Cipher::newRandomKey()
{
const int bufLen = MAX_KEYLENGTH;
unsigned char tmpBuf[ bufLen ];
int saltLen = 20;
unsigned char saltBuf[ saltLen ];
if(!randomize(tmpBuf, bufLen, true) ||
!randomize(saltBuf, saltLen, true))
return CipherKey();
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
// doesn't need to be versioned, because a random key is a random key..
// Doesn't need to be reproducable..
if(PKCS5_PBKDF2_HMAC_SHA1((char*)tmpBuf, bufLen, saltBuf, saltLen,
1000, _keySize + _ivLength, KeyData(key)) != 1)
{
rWarning("openssl error, PBKDF2 failed");
return CipherKey();
}
OPENSSL_cleanse(tmpBuf, bufLen);
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
/*
compute a 64-bit check value for the data using HMAC.
*/
static uint64_t _checksum_64( SSLKey *key,
const unsigned char *data, int dataLen, uint64_t *chainedIV)
{
rAssert( dataLen > 0 );
Lock lock( key->mutex );
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdLen = EVP_MAX_MD_SIZE;
HMAC_Init_ex( &key->mac_ctx, 0, 0, 0, 0 );
HMAC_Update( &key->mac_ctx, data, dataLen );
if(chainedIV)
{
// toss in the chained IV as well
uint64_t tmp = *chainedIV;
unsigned char h[8];
for(unsigned int i=0; i<8; ++i)
{
h[i] = tmp & 0xff;
tmp >>= 8;
}
HMAC_Update( &key->mac_ctx, h, 8 );
}
HMAC_Final( &key->mac_ctx, md, &mdLen );
rAssert(mdLen >= 8);
// chop this down to a 64bit value..
unsigned char h[8] = {0,0,0,0,0,0,0,0};
for(unsigned int i=0; i<(mdLen-1); ++i)
h[i%8] ^= (unsigned char)(md[i]);
uint64_t value = (uint64_t)h[0];
for(int i=1; i<8; ++i)
value = (value << 8) | (uint64_t)h[i];
return value;
}
bool SSL_Cipher::randomize( unsigned char *buf, int len,
bool strongRandom ) const
{
// to avoid warnings of uninitialized data from valgrind
memset(buf, 0, len);
int result;
if(strongRandom)
result = RAND_bytes( buf, len );
else
result = RAND_pseudo_bytes( buf, len );
if(result != 1)
{
char errStr[120]; // specs require string at least 120 bytes long..
unsigned long errVal = 0;
if((errVal = ERR_get_error()) != 0)
rWarning("openssl error: %s", ERR_error_string( errVal, errStr ));
return false;
} else
return true;
}
uint64_t SSL_Cipher::MAC_64( const unsigned char *data, int len,
const CipherKey &key, uint64_t *chainedIV ) const
{
shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(key);
uint64_t tmp = _checksum_64( mk.get(), data, len, chainedIV );
if(chainedIV)
*chainedIV = tmp;
return tmp;
}
CipherKey SSL_Cipher::readKey(const unsigned char *data,
const CipherKey &masterKey, bool checkKey)
{
shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(masterKey);
rAssert(mk->keySize == _keySize);
unsigned char tmpBuf[ MAX_KEYLENGTH + MAX_IVLENGTH ];
// First N bytes are checksum bytes.
unsigned int checksum = 0;
for(int i=0; i<KEY_CHECKSUM_BYTES; ++i)
checksum = (checksum << 8) | (unsigned int)data[i];
memcpy( tmpBuf, data+KEY_CHECKSUM_BYTES, _keySize + _ivLength );
streamDecode(tmpBuf, _keySize + _ivLength, checksum, masterKey);
// check for success
unsigned int checksum2 = MAC_32( tmpBuf, _keySize + _ivLength, masterKey );
if(checksum2 != checksum && checkKey)
{
rDebug("checksum mismatch: expected %u, got %u", checksum, checksum2);
rDebug("on decode of %i bytes", _keySize + _ivLength);
memset( tmpBuf, 0, sizeof(tmpBuf) );
return CipherKey();
}
shared_ptr<SSLKey> key( new SSLKey( _keySize, _ivLength) );
memcpy( key->buffer, tmpBuf, _keySize + _ivLength );
memset( tmpBuf, 0, sizeof(tmpBuf) );
initKey( key, _blockCipher, _streamCipher, _keySize );
return key;
}
void SSL_Cipher::writeKey(const CipherKey &ckey, unsigned char *data,
const CipherKey &masterKey)
{
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
shared_ptr<SSLKey> mk = dynamic_pointer_cast<SSLKey>(masterKey);
rAssert(mk->keySize == _keySize);
rAssert(mk->ivLength == _ivLength);
unsigned char tmpBuf[ MAX_KEYLENGTH + MAX_IVLENGTH ];
int bufLen = _keySize + _ivLength;
memcpy( tmpBuf, key->buffer, bufLen );
unsigned int checksum = MAC_32( tmpBuf, bufLen, masterKey );
streamEncode(tmpBuf, bufLen, checksum, masterKey);
memcpy( data+KEY_CHECKSUM_BYTES, tmpBuf, bufLen );
// first N bytes contain HMAC derived checksum..
for(int i=1; i<=KEY_CHECKSUM_BYTES; ++i)
{
data[KEY_CHECKSUM_BYTES-i] = checksum & 0xff;
checksum >>= 8;
}
memset( tmpBuf, 0, sizeof(tmpBuf) );
}
bool SSL_Cipher::compareKey( const CipherKey &A, const CipherKey &B) const
{
shared_ptr<SSLKey> key1 = dynamic_pointer_cast<SSLKey>(A);
shared_ptr<SSLKey> key2 = dynamic_pointer_cast<SSLKey>(B);
rAssert(key1->keySize == _keySize);
rAssert(key2->keySize == _keySize);
if(memcmp(key1->buffer, key2->buffer, _keySize + _ivLength) != 0)
return false;
else
return true;
}
int SSL_Cipher::encodedKeySize() const
{
return _keySize + _ivLength + KEY_CHECKSUM_BYTES;
}
int SSL_Cipher::keySize() const
{
return _keySize;
}
int SSL_Cipher::cipherBlockSize() const
{
return EVP_CIPHER_block_size( _blockCipher );
}
void SSL_Cipher::setIVec( unsigned char *ivec, uint64_t seed,
const shared_ptr<SSLKey> &key) const
{
if (iface.major() >= 3)
{
memcpy( ivec, IVData(key), _ivLength );
unsigned char md[EVP_MAX_MD_SIZE];
unsigned int mdLen = EVP_MAX_MD_SIZE;
for(int i=0; i<8; ++i)
{
md[i] = (unsigned char)(seed & 0xff);
seed >>= 8;
}
// combine ivec and seed with HMAC
HMAC_Init_ex( &key->mac_ctx, 0, 0, 0, 0 );
HMAC_Update( &key->mac_ctx, ivec, _ivLength );
HMAC_Update( &key->mac_ctx, md, 8 );
HMAC_Final( &key->mac_ctx, md, &mdLen );
rAssert(mdLen >= _ivLength);
memcpy( ivec, md, _ivLength );
} else
{
setIVec_old(ivec, seed, key);
}
}
/** Deprecated: For backward compatibility only.
A watermark attack was discovered against this IV setup. If an attacker
could get a victim to store a carefully crafted file, they could later
determine if the victim had the file in encrypted storage (without
decrypting the file).
*/
void SSL_Cipher::setIVec_old(unsigned char *ivec,
unsigned int seed,
const shared_ptr<SSLKey> &key) const
{
unsigned int var1 = 0x060a4011 * seed;
unsigned int var2 = 0x0221040d * (seed ^ 0xD3FEA11C);
memcpy( ivec, IVData(key), _ivLength );
ivec[0] ^= (var1 >> 24) & 0xff;
ivec[1] ^= (var2 >> 16) & 0xff;
ivec[2] ^= (var1 >> 8 ) & 0xff;
ivec[3] ^= (var2 ) & 0xff;
ivec[4] ^= (var2 >> 24) & 0xff;
ivec[5] ^= (var1 >> 16) & 0xff;
ivec[6] ^= (var2 >> 8 ) & 0xff;
ivec[7] ^= (var1 ) & 0xff;
if(_ivLength > 8)
{
ivec[8+0] ^= (var1 ) & 0xff;
ivec[8+1] ^= (var2 >> 8 ) & 0xff;
ivec[8+2] ^= (var1 >> 16) & 0xff;
ivec[8+3] ^= (var2 >> 24) & 0xff;
ivec[8+4] ^= (var1 >> 24) & 0xff;
ivec[8+5] ^= (var2 >> 16) & 0xff;
ivec[8+6] ^= (var1 >> 8 ) & 0xff;
ivec[8+7] ^= (var2 ) & 0xff;
}
}
static void flipBytes(unsigned char *buf, int size)
{
unsigned char revBuf[64];
int bytesLeft = size;
while(bytesLeft)
{
int toFlip = MIN( sizeof(revBuf), bytesLeft );
for(int i=0; i<toFlip; ++i)
revBuf[i] = buf[toFlip - (i+1)];
memcpy( buf, revBuf, toFlip );
bytesLeft -= toFlip;
buf += toFlip;
}
memset(revBuf, 0, sizeof(revBuf));
}
static void shuffleBytes(unsigned char *buf, int size)
{
for(int i=0; i<size-1; ++i)
buf[i+1] ^= buf[i];
}
static void unshuffleBytes(unsigned char *buf, int size)
{
for(int i=size-1; i; --i)
buf[i] ^= buf[i-1];
}
/* Partial blocks are encoded with a stream cipher. We make multiple passes on
the data to ensure that the ends of the data depend on each other.
*/
bool SSL_Cipher::streamEncode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &ckey) const
{
rAssert( size > 0 );
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
Lock lock( key->mutex );
unsigned char ivec[ MAX_IVLENGTH ];
int dstLen=0, tmpLen=0;
shuffleBytes( buf, size );
setIVec( ivec, iv64, key );
EVP_EncryptInit_ex( &key->stream_enc, NULL, NULL, NULL, ivec);
EVP_EncryptUpdate( &key->stream_enc, buf, &dstLen, buf, size );
EVP_EncryptFinal_ex( &key->stream_enc, buf+dstLen, &tmpLen );
flipBytes( buf, size );
shuffleBytes( buf, size );
setIVec( ivec, iv64 + 1, key );
EVP_EncryptInit_ex( &key->stream_enc, NULL, NULL, NULL, ivec);
EVP_EncryptUpdate( &key->stream_enc, buf, &dstLen, buf, size );
EVP_EncryptFinal_ex( &key->stream_enc, buf+dstLen, &tmpLen );
dstLen += tmpLen;
if(dstLen != size)
{
rError("encoding %i bytes, got back %i (%i in final_ex)",
size, dstLen, tmpLen);
}
return true;
}
bool SSL_Cipher::streamDecode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &ckey) const
{
rAssert( size > 0 );
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
Lock lock( key->mutex );
unsigned char ivec[ MAX_IVLENGTH ];
int dstLen=0, tmpLen=0;
setIVec( ivec, iv64 + 1, key );
EVP_DecryptInit_ex( &key->stream_dec, NULL, NULL, NULL, ivec);
EVP_DecryptUpdate( &key->stream_dec, buf, &dstLen, buf, size );
EVP_DecryptFinal_ex( &key->stream_dec, buf+dstLen, &tmpLen );
unshuffleBytes( buf, size );
flipBytes( buf, size );
setIVec( ivec, iv64, key );
EVP_DecryptInit_ex( &key->stream_dec, NULL, NULL, NULL, ivec);
EVP_DecryptUpdate( &key->stream_dec, buf, &dstLen, buf, size );
EVP_DecryptFinal_ex( &key->stream_dec, buf+dstLen, &tmpLen );
unshuffleBytes( buf, size );
dstLen += tmpLen;
if(dstLen != size)
{
rError("encoding %i bytes, got back %i (%i in final_ex)",
size, dstLen, tmpLen);
}
return true;
}
bool SSL_Cipher::blockEncode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &ckey ) const
{
rAssert( size > 0 );
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
// data must be integer number of blocks
const int blockMod = size % EVP_CIPHER_CTX_block_size( &key->block_enc );
if(blockMod != 0)
throw ERROR("Invalid data size, not multiple of block size");
Lock lock( key->mutex );
unsigned char ivec[ MAX_IVLENGTH ];
int dstLen = 0, tmpLen = 0;
setIVec( ivec, iv64, key );
EVP_EncryptInit_ex( &key->block_enc, NULL, NULL, NULL, ivec);
EVP_EncryptUpdate( &key->block_enc, buf, &dstLen, buf, size );
EVP_EncryptFinal_ex( &key->block_enc, buf+dstLen, &tmpLen );
dstLen += tmpLen;
if(dstLen != size)
{
rError("encoding %i bytes, got back %i (%i in final_ex)",
size, dstLen, tmpLen);
}
return true;
}
bool SSL_Cipher::blockDecode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &ckey ) const
{
rAssert( size > 0 );
shared_ptr<SSLKey> key = dynamic_pointer_cast<SSLKey>(ckey);
rAssert(key->keySize == _keySize);
rAssert(key->ivLength == _ivLength);
// data must be integer number of blocks
const int blockMod = size % EVP_CIPHER_CTX_block_size( &key->block_dec );
if(blockMod != 0)
throw ERROR("Invalid data size, not multiple of block size");
Lock lock( key->mutex );
unsigned char ivec[ MAX_IVLENGTH ];
int dstLen = 0, tmpLen = 0;
setIVec( ivec, iv64, key );
EVP_DecryptInit_ex( &key->block_dec, NULL, NULL, NULL, ivec);
EVP_DecryptUpdate( &key->block_dec, buf, &dstLen, buf, size );
EVP_DecryptFinal_ex( &key->block_dec, buf+dstLen, &tmpLen );
dstLen += tmpLen;
if(dstLen != size)
{
rError("decoding %i bytes, got back %i (%i in final_ex)",
size, dstLen, tmpLen);
}
return true;
}
bool SSL_Cipher::Enabled()
{
return true;
}

View File

@ -1,150 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _SSL_Cipher_incl_
#define _SSL_Cipher_incl_
#include "Cipher.h"
#include "Interface.h"
struct SSLKey;
#ifndef EVP_CIPHER
struct evp_cipher_st;
typedef struct evp_cipher_st EVP_CIPHER;
#endif
/*
Implements Cipher interface for OpenSSL's ciphers.
Design:
Variable algorithm, key size, and block size.
Partial blocks, keys, and names are encrypted using the cipher in a pseudo
stream mode (CFB).
Keys are encrypted with 2-4 (KEY_CHECKSUM_BYTES define) checksum bytes
derived from an HMAC over both they key data and the initial value vector
associated with the key. This allows a good chance at detecting an
incorrect password when we try and decrypt the master key.
File names are encrypted in the same way, with 2 checksum bytes derived
from an HMAC over the filename. This is done not to allow checking the
results, but to make the output much more random. Changing one letter in a
filename should result in a completely different encrypted filename, to
help frustrate any attempt to guess information about files from their
encrypted names.
Stream encryption involves two encryption passes over the data, implemented
as:
1. shuffle
2. encrypt
3. reverse
4. shuffle
5. encrypt
The reason for the shuffle and reverse steps (and the second encrypt pass)
is to try and propogate any changed bits to a larger set. If only a single
pass was made with the stream cipher in CFB mode, then a change to one byte
may only affect one byte of output, allowing some XOR attacks.
The shuffle/encrypt is used as above in filename encryption as well,
although it is not necessary as they have checksum bytes which augment the
initial value vector to randomize the output. But it makes the code
simpler to reuse the encryption algorithm as is.
*/
class SSL_Cipher : public Cipher
{
Interface iface;
Interface realIface;
const EVP_CIPHER *_blockCipher;
const EVP_CIPHER *_streamCipher;
unsigned int _keySize; // in bytes
unsigned int _ivLength;
public:
SSL_Cipher(const Interface &iface, const Interface &realIface,
const EVP_CIPHER *blockCipher, const EVP_CIPHER *streamCipher,
int keyLength);
virtual ~SSL_Cipher();
// returns the real interface, not the one we're emulating (if any)..
virtual Interface interface() const;
// create a new key based on a password
virtual CipherKey newKey(const char *password, int passwdLength,
int &iterationCount, long desiredDuration,
const unsigned char *salt, int saltLen);
// deprecated - for backward compatibility
virtual CipherKey newKey(const char *password, int passwdLength);
// create a new random key
virtual CipherKey newRandomKey();
// data must be len keySize()
virtual CipherKey readKey(const unsigned char *data,
const CipherKey &encodingKey,
bool checkKey);
virtual void writeKey(const CipherKey &key, unsigned char *data,
const CipherKey &encodingKey);
virtual bool compareKey( const CipherKey &A,
const CipherKey &B ) const;
// meta-data about the cypher
virtual int keySize() const;
virtual int encodedKeySize() const;
virtual int cipherBlockSize() const;
virtual bool randomize( unsigned char *buf, int len,
bool strongRandom ) const;
virtual uint64_t MAC_64( const unsigned char *src, int len,
const CipherKey &key, uint64_t *augment ) const;
// functional interfaces
/*
Stream encoding in-place.
*/
virtual bool streamEncode(unsigned char *in, int len,
uint64_t iv64, const CipherKey &key) const;
virtual bool streamDecode(unsigned char *in, int len,
uint64_t iv64, const CipherKey &key) const;
/*
Block encoding is done in-place. Partial blocks are supported, but
blocks are always expected to begin on a block boundary. See
blockSize().
*/
virtual bool blockEncode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &key) const;
virtual bool blockDecode(unsigned char *buf, int size,
uint64_t iv64, const CipherKey &key) const;
// hack to help with static builds
static bool Enabled();
private:
void setIVec( unsigned char *ivec, uint64_t seed,
const shared_ptr<SSLKey> &key ) const;
// deprecated - for backward compatibility
void setIVec_old( unsigned char *ivec, unsigned int seed,
const shared_ptr<SSLKey> &key ) const;
};
#endif

View File

@ -1,208 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "StreamNameIO.h"
#include "Cipher.h"
#include "base64.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include "i18n.h"
#include <cstring>
using namespace std;
static shared_ptr<NameIO> NewStreamNameIO( const Interface &iface,
const shared_ptr<Cipher> &cipher, const CipherKey &key)
{
return shared_ptr<NameIO>( new StreamNameIO( iface, cipher, key ) );
}
static bool StreamIO_registered = NameIO::Register("Stream",
gettext_noop("Stream encoding, keeps filenames as short as possible"),
StreamNameIO::CurrentInterface(),
NewStreamNameIO);
/*
- Version 0.1 is for EncFS 0.x support. The difference to 1.0 is that 0.x
stores the file checksums at the end of the encoded name, where 1.0
stores them at the beginning.
- Version 1.0 is the basic stream encoding mode used since the beginning of
EncFS. There is a slight difference in filename encodings from EncFS 0.x
to 1.0.x. This implements just the 1.0.x method.
- Version 1.1 adds support for IV chaining. This is transparently
backward compatible, since older filesystems do not use IV chaining.
- Version 2.0 uses full 64 bit IV during IV chaining mode. Prior versions
used only the 16 bit output from MAC_16. This reduces the theoretical
possibility (unlikely to make any difference in practice) of two files
with the same name in different directories ending up with the same
encrypted name. Added because there is no good reason to chop to 16
bits.
- Version 2.1 adds support for version 0 for EncFS 0.x compatibility.
*/
Interface StreamNameIO::CurrentInterface()
{
// implement major version 2, 1, and 0
return makeInterface("nameio/stream", 2, 1, 2);
}
StreamNameIO::StreamNameIO( const Interface &iface,
const shared_ptr<Cipher> &cipher,
const CipherKey &key )
: _interface( iface.major() )
, _cipher( cipher )
, _key( key )
{
}
StreamNameIO::~StreamNameIO()
{
}
Interface StreamNameIO::interface() const
{
return CurrentInterface();
}
int StreamNameIO::maxEncodedNameLen( int plaintextStreamLen ) const
{
int encodedStreamLen = 2 + plaintextStreamLen;
return B256ToB64Bytes( encodedStreamLen );
}
int StreamNameIO::maxDecodedNameLen( int encodedStreamLen ) const
{
int decLen256 = B64ToB256Bytes( encodedStreamLen );
return decLen256 - 2;
}
int StreamNameIO::encodeName( const char *plaintextName, int length,
uint64_t *iv, char *encodedName ) const
{
uint64_t tmpIV = 0;
if( iv && _interface >= 2 )
tmpIV = *iv;
unsigned int mac = _cipher->MAC_16( (const unsigned char *)plaintextName,
length, _key, iv );
// add on checksum bytes
unsigned char *encodeBegin;
if(_interface >= 1)
{
// current versions store the checksum at the beginning
encodedName[0] = (mac >> 8) & 0xff;
encodedName[1] = (mac ) & 0xff;
encodeBegin = (unsigned char *)encodedName+2;
} else
{
// encfs 0.x stored checksums at the end.
encodedName[length] = (mac >> 8) & 0xff;
encodedName[length+1] = (mac ) & 0xff;
encodeBegin = (unsigned char *)encodedName;
}
// stream encode the plaintext bytes
memcpy( encodeBegin, plaintextName, length );
_cipher->nameEncode( encodeBegin, length, (uint64_t)mac ^ tmpIV, _key);
// convert the entire thing to base 64 ascii..
int encodedStreamLen = length + 2;
int encLen64 = B256ToB64Bytes( encodedStreamLen );
changeBase2Inline( (unsigned char *)encodedName, encodedStreamLen,
8, 6, true );
B64ToAscii( (unsigned char *)encodedName, encLen64 );
return encLen64;
}
int StreamNameIO::decodeName( const char *encodedName, int length,
uint64_t *iv, char *plaintextName ) const
{
rAssert(length > 2);
int decLen256 = B64ToB256Bytes( length );
int decodedStreamLen = decLen256 - 2;
if(decodedStreamLen <= 0)
throw ERROR("Filename too small to decode");
BUFFER_INIT( tmpBuf, 32, (unsigned int)length );
// decode into tmpBuf, because this step produces more data then we can fit
// into the result buffer..
AsciiToB64( (unsigned char *)tmpBuf, (unsigned char *)encodedName, length );
changeBase2Inline((unsigned char *)tmpBuf, length, 6, 8, false);
// pull out the checksum value which is used as an initialization vector
uint64_t tmpIV = 0;
unsigned int mac;
if(_interface >= 1)
{
// current versions store the checksum at the beginning
mac = ((unsigned int)((unsigned char)tmpBuf[0])) << 8
| ((unsigned int)((unsigned char)tmpBuf[1]));
// version 2 adds support for IV chaining..
if( iv && _interface >= 2 )
tmpIV = *iv;
memcpy( plaintextName, tmpBuf+2, decodedStreamLen );
} else
{
// encfs 0.x stored checksums at the end.
mac = ((unsigned int)((unsigned char)tmpBuf[decodedStreamLen])) << 8
| ((unsigned int)((unsigned char)tmpBuf[decodedStreamLen+1]));
memcpy( plaintextName, tmpBuf, decodedStreamLen );
}
// use nameDeocde instead of streamDecode for backward compatibility
_cipher->nameDecode( (unsigned char *)plaintextName, decodedStreamLen,
(uint64_t)mac ^ tmpIV, _key);
// compute MAC to check with stored value
unsigned int mac2 = _cipher->MAC_16((const unsigned char *)plaintextName,
decodedStreamLen, _key, iv);
BUFFER_RESET( tmpBuf );
if(mac2 != mac)
{
rDebug("checksum mismatch: expected %u, got %u", mac, mac2);
rDebug("on decode of %i bytes", decodedStreamLen);
throw ERROR( "checksum mismatch in filename decode" );
}
return decodedStreamLen;
}
bool StreamNameIO::Enabled()
{
return true;
}

View File

@ -1,4 +0,0 @@
####### kdevelop will overwrite this part!!! (begin)##########
####### kdevelop will overwrite this part!!! (end)############

View File

@ -1,4 +0,0 @@
####### kdevelop will overwrite this part!!! (begin)##########
####### kdevelop will overwrite this part!!! (end)############

View File

@ -1,807 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2003-2007, Valient Gough
*
* This program is free software; you can distribute it and/or modify it under
* the terms of the GNU General Public License (GPL), as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include "encfs.h"
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <cerrno>
#include <sys/statvfs.h>
#include <sys/time.h>
#include <sys/types.h>
#ifdef linux
#include <sys/fsuid.h>
#endif
#ifdef HAVE_ATTR_XATTR_H
#include <attr/xattr.h>
#elif HAVE_SYS_XATTR_H
#include <sys/xattr.h>
#endif
#include <string>
#include <map>
#if HAVE_TR1_TUPLE
#include <tr1/tuple>
using namespace std;
using namespace std::tr1;
#else
#include <tuple>
using namespace std;
#endif
#include "DirNode.h"
#include "MemoryPool.h"
#include "FileUtils.h"
#include "Mutex.h"
#include "Context.h"
#include "shared_ptr.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
#ifndef MIN
#define MIN(a,b) (((a)<(b)) ? (a): (b))
#endif
#define ESUCCESS 0
using namespace rlog;
using rel::Lock;
#define GET_FN(ctx, finfo) ctx->getNode((void*)(uintptr_t)finfo->fh)
static RLogChannel *Info = DEF_CHANNEL("info", Log_Info);
static EncFS_Context * context()
{
return (EncFS_Context*)fuse_get_context()->private_data;
}
// helper function -- apply a functor to a cipher path, given the plain path
template<typename T>
static int withCipherPath( const char *opName, const char *path,
int (*op)(EncFS_Context *, const string &name, T data ), T data,
bool passReturnCode = false )
{
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
string cyName = FSRoot->cipherPath( path );
rLog(Info, "%s %s", opName, cyName.c_str());
res = op( ctx, cyName, data );
if(res == -1)
{
int eno = errno;
rInfo("%s error: %s", opName, strerror(eno));
res = -eno;
} else if(!passReturnCode)
res = ESUCCESS;
} catch( rlog::Error &err )
{
rError("error caught in %s", opName);
err.log( _RLWarningChannel );
}
return res;
}
// helper function -- apply a functor to a node
template<typename T>
static int withFileNode( const char *opName,
const char *path, struct fuse_file_info *fi,
int (*op)(FileNode *, T data ), T data )
{
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
shared_ptr<FileNode> fnode;
if(fi != NULL)
fnode = GET_FN(ctx, fi);
else
fnode = FSRoot->lookupNode( path, opName );
rAssert(fnode != NULL);
rLog(Info, "%s %s", opName, fnode->cipherName());
res = op( fnode.get(), data );
if(res < 0)
rInfo("%s error: %s", opName, strerror(-res));
} catch( rlog::Error &err )
{
rError("error caught in %s", opName);
err.log( _RLWarningChannel );
}
return res;
}
/*
The rLog messages below always print out encrypted filenames, not
plaintext. The reason is so that it isn't possible to leak information
about the encrypted data through rlog interfaces.
The purpose of this layer of code is to take the FUSE request and dispatch
to the internal interfaces. Any marshaling of arguments and return types
can be done here.
*/
int _do_getattr(FileNode *fnode, struct stat *stbuf)
{
int res = fnode->getAttr(stbuf);
if(res == ESUCCESS && S_ISLNK(stbuf->st_mode))
{
EncFS_Context *ctx = context();
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(FSRoot)
{
// determine plaintext link size.. Easiest to read and decrypt..
vector<char> buf(stbuf->st_size+1, 0);
res = ::readlink( fnode->cipherName(), &buf[0], stbuf->st_size );
if(res >= 0)
{
// other functions expect c-strings to be null-terminated, which
// readlink doesn't provide
buf[res] = '\0';
stbuf->st_size = FSRoot->plainPath( &buf[0] ).length();
res = ESUCCESS;
} else
res = -errno;
}
}
return res;
}
int encfs_getattr(const char *path, struct stat *stbuf)
{
return withFileNode( "getattr", path, NULL, _do_getattr, stbuf );
}
int encfs_fgetattr(const char *path, struct stat *stbuf,
struct fuse_file_info *fi)
{
return withFileNode( "fgetattr", path, fi, _do_getattr, stbuf );
}
int encfs_getdir(const char *path, fuse_dirh_t h, fuse_dirfil_t filler)
{
EncFS_Context *ctx = context();
int res = ESUCCESS;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
DirTraverse dt = FSRoot->openDir( path );
rLog(Info, "getdir on %s", FSRoot->cipherPath(path).c_str());
if(dt.valid())
{
int fileType = 0;
ino_t inode = 0;
std::string name = dt.nextPlaintextName( &fileType, &inode );
while( !name.empty() )
{
res = filler( h, name.c_str(), fileType, inode );
if(res != ESUCCESS)
break;
name = dt.nextPlaintextName( &fileType, &inode );
}
} else
{
rInfo("getdir request invalid, path: '%s'", path);
}
return res;
} catch( rlog::Error &err )
{
rError("Error caught in getdir");
err.log( _RLWarningChannel );
return -EIO;
}
}
int encfs_mknod(const char *path, mode_t mode, dev_t rdev)
{
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
shared_ptr<FileNode> fnode = FSRoot->lookupNode( path, "mknod" );
rLog(Info, "mknod on %s, mode %i, dev %" PRIi64,
fnode->cipherName(), mode, (int64_t)rdev);
uid_t uid = 0;
gid_t gid = 0;
if(ctx->publicFilesystem)
{
fuse_context *context = fuse_get_context();
uid = context->uid;
gid = context->gid;
}
res = fnode->mknod( mode, rdev, uid, gid );
// Is this error due to access problems?
if(ctx->publicFilesystem && -res == EACCES)
{
// try again using the parent dir's group
string parent = fnode->plaintextParent();
rInfo("trying public filesystem workaround for %s", parent.c_str());
shared_ptr<FileNode> dnode =
FSRoot->lookupNode( parent.c_str(), "mknod" );
struct stat st;
if(dnode->getAttr( &st ) == 0)
res = fnode->mknod( mode, rdev, uid, st.st_gid );
}
} catch( rlog::Error &err )
{
rError("error caught in mknod");
err.log( _RLWarningChannel );
}
return res;
}
int encfs_mkdir(const char *path, mode_t mode)
{
fuse_context *fctx = fuse_get_context();
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
uid_t uid = 0;
gid_t gid = 0;
if(ctx->publicFilesystem)
{
uid = fctx->uid;
gid = fctx->gid;
}
res = FSRoot->mkdir( path, mode, uid, gid );
// Is this error due to access problems?
if(ctx->publicFilesystem && -res == EACCES)
{
// try again using the parent dir's group
string parent = parentDirectory( path );
shared_ptr<FileNode> dnode =
FSRoot->lookupNode( parent.c_str(), "mkdir" );
struct stat st;
if(dnode->getAttr( &st ) == 0)
res = FSRoot->mkdir( path, mode, uid, st.st_gid );
}
} catch( rlog::Error &err )
{
rError("error caught in mkdir");
err.log( _RLWarningChannel );
}
return res;
}
int encfs_unlink(const char *path)
{
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
// let DirNode handle it atomically so that it can handle race
// conditions
res = FSRoot->unlink( path );
} catch( rlog::Error &err )
{
rError("error caught in unlink");
err.log( _RLWarningChannel );
}
return res;
}
int _do_rmdir(EncFS_Context *, const string &cipherPath, int )
{
return rmdir( cipherPath.c_str() );
}
int encfs_rmdir(const char *path)
{
return withCipherPath( "rmdir", path, _do_rmdir, 0 );
}
int _do_readlink(EncFS_Context *ctx, const string &cyName,
tuple<char *, size_t> data )
{
char *buf = get<0>(data);
size_t size = get<1>(data);
int res = ESUCCESS;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
res = ::readlink( cyName.c_str(), buf, size-1 );
if(res == -1)
return -errno;
buf[res] = '\0'; // ensure null termination
string decodedName;
try
{
decodedName = FSRoot->plainPath( buf );
} catch(...) { }
if(!decodedName.empty())
{
strncpy(buf, decodedName.c_str(), size-1);
buf[size-1] = '\0';
return ESUCCESS;
} else
{
rWarning("Error decoding link");
return -1;
}
}
int encfs_readlink(const char *path, char *buf, size_t size)
{
return withCipherPath( "readlink", path, _do_readlink,
make_tuple(buf, size) );
}
int encfs_symlink(const char *from, const char *to)
{
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
// allow fully qualified names in symbolic links.
string fromCName = FSRoot->relativeCipherPath( from );
string toCName = FSRoot->cipherPath( to );
rLog(Info, "symlink %s -> %s", fromCName.c_str(), toCName.c_str());
// use setfsuid / setfsgid so that the new link will be owned by the
// uid/gid provided by the fuse_context.
int olduid = -1;
int oldgid = -1;
if(ctx->publicFilesystem)
{
fuse_context *context = fuse_get_context();
olduid = setfsuid( context->uid );
oldgid = setfsgid( context->gid );
}
res = ::symlink( fromCName.c_str(), toCName.c_str() );
if(olduid >= 0)
setfsuid( olduid );
if(oldgid >= 0)
setfsgid( oldgid );
if(res == -1)
res = -errno;
else
res = ESUCCESS;
} catch( rlog::Error &err )
{
rError("error caught in symlink");
err.log( _RLWarningChannel );
}
return res;
}
int encfs_link(const char *from, const char *to)
{
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
res = FSRoot->link( from, to );
} catch( rlog::Error &err )
{
rError("error caught in link");
err.log( _RLWarningChannel );
}
return res;
}
int encfs_rename(const char *from, const char *to)
{
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
res = FSRoot->rename( from, to );
} catch( rlog::Error &err )
{
rError("error caught in rename");
err.log( _RLWarningChannel );
}
return res;
}
int _do_chmod(EncFS_Context *, const string &cipherPath, mode_t mode)
{
return chmod( cipherPath.c_str(), mode );
}
int encfs_chmod(const char *path, mode_t mode)
{
return withCipherPath( "chmod", path, _do_chmod, mode );
}
int _do_chown(EncFS_Context *, const string &cyName,
tuple<uid_t, gid_t> data)
{
int res = lchown( cyName.c_str(), get<0>(data), get<1>(data) );
return (res == -1) ? -errno : ESUCCESS;
}
int encfs_chown(const char *path, uid_t uid, gid_t gid)
{
return withCipherPath( "chown", path, _do_chown, make_tuple(uid, gid));
}
int _do_truncate( FileNode *fnode, off_t size )
{
return fnode->truncate( size );
}
int encfs_truncate(const char *path, off_t size)
{
return withFileNode( "truncate", path, NULL, _do_truncate, size );
}
int encfs_ftruncate(const char *path, off_t size, struct fuse_file_info *fi)
{
return withFileNode( "ftruncate", path, fi, _do_truncate, size );
}
int _do_utime(EncFS_Context *, const string &cyName, struct utimbuf *buf)
{
int res = utime( cyName.c_str(), buf);
return (res == -1) ? -errno : ESUCCESS;
}
int encfs_utime(const char *path, struct utimbuf *buf)
{
return withCipherPath( "utime", path, _do_utime, buf );
}
int _do_utimens(EncFS_Context *, const string &cyName,
const struct timespec ts[2])
{
struct timeval tv[2];
tv[0].tv_sec = ts[0].tv_sec;
tv[0].tv_usec = ts[0].tv_nsec / 1000;
tv[1].tv_sec = ts[1].tv_sec;
tv[1].tv_usec = ts[1].tv_nsec / 1000;
int res = lutimes( cyName.c_str(), tv);
return (res == -1) ? -errno : ESUCCESS;
}
int encfs_utimens(const char *path, const struct timespec ts[2] )
{
return withCipherPath( "utimens", path, _do_utimens, ts );
}
int encfs_open(const char *path, struct fuse_file_info *file)
{
EncFS_Context *ctx = context();
int res = -EIO;
shared_ptr<DirNode> FSRoot = ctx->getRoot(&res);
if(!FSRoot)
return res;
try
{
shared_ptr<FileNode> fnode =
FSRoot->openNode( path, "open", file->flags, &res );
if(fnode)
{
rLog(Info, "encfs_open for %s, flags %i", fnode->cipherName(),
file->flags);
if( res >= 0 )
{
file->fh = (uintptr_t)ctx->putNode(path, fnode);
res = ESUCCESS;
}
}
} catch( rlog::Error &err )
{
rError("error caught in open");
err.log( _RLWarningChannel );
}
return res;
}
int _do_flush(FileNode *fnode, int )
{
/* Flush can be called multiple times for an open file, so it doesn't
close the file. However it is important to call close() for some
underlying filesystems (like NFS).
*/
int res = fnode->open( O_RDONLY );
if(res >= 0)
{
int fh = res;
res = close(dup(fh));
if(res == -1)
res = -errno;
}
return res;
}
int encfs_flush(const char *path, struct fuse_file_info *fi)
{
return withFileNode( "flush", path, fi, _do_flush, 0 );
}
/*
Note: This is advisory -- it might benefit us to keep file nodes around for a
bit after they are released just in case they are reopened soon. But that
requires a cache layer.
*/
int encfs_release(const char *path, struct fuse_file_info *finfo)
{
EncFS_Context *ctx = context();
try
{
ctx->eraseNode( path, (void*)(uintptr_t)finfo->fh );
return ESUCCESS;
} catch( rlog::Error &err )
{
rError("error caught in release");
err.log( _RLWarningChannel );
return -EIO;
}
}
int _do_read(FileNode *fnode, tuple<unsigned char *, size_t, off_t> data)
{
return fnode->read( get<2>(data), get<0>(data), get<1>(data));
}
int encfs_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *file)
{
return withFileNode( "read", path, file, _do_read,
make_tuple((unsigned char *)buf, size, offset));
}
int _do_fsync(FileNode *fnode, int dataSync)
{
return fnode->sync( dataSync != 0 );
}
int encfs_fsync(const char *path, int dataSync,
struct fuse_file_info *file)
{
return withFileNode( "fsync", path, file, _do_fsync, dataSync );
}
int _do_write(FileNode *fnode, tuple<const char *, size_t, off_t> data)
{
size_t size = get<1>(data);
if(fnode->write( get<2>(data), (unsigned char *)get<0>(data), size ))
return size;
else
return -EIO;
}
int encfs_write(const char *path, const char *buf, size_t size,
off_t offset, struct fuse_file_info *file)
{
return withFileNode("write", path, file, _do_write,
make_tuple(buf, size, offset));
}
// statfs works even if encfs is detached..
int encfs_statfs(const char *path, struct statvfs *st)
{
EncFS_Context *ctx = context();
int res = -EIO;
try
{
(void)path; // path should always be '/' for now..
rAssert( st != NULL );
string cyName = ctx->rootCipherDir;
rLog(Info, "doing statfs of %s", cyName.c_str());
res = statvfs( cyName.c_str(), st );
if(!res)
{
// adjust maximum name length..
st->f_namemax = 6 * (st->f_namemax - 2) / 8; // approx..
}
if(res == -1)
res = -errno;
} catch( rlog::Error &err )
{
rError("error caught in statfs");
err.log( _RLWarningChannel );
}
return res;
}
#ifdef HAVE_XATTR
#ifdef XATTR_ADD_OPT
int _do_setxattr(EncFS_Context *, const string &cyName,
tuple<const char *, const char *, size_t, uint32_t> data)
{
int options = 0;
return ::setxattr( cyName.c_str(), get<0>(data), get<1>(data),
get<2>(data), get<3>(data), options );
}
int encfs_setxattr( const char *path, const char *name,
const char *value, size_t size, int flags, uint32_t position )
{
(void)flags;
return withCipherPath( "setxattr", path, _do_setxattr,
make_tuple(name, value, size, position) );
}
#else
int _do_setxattr(EncFS_Context *, const string &cyName,
tuple<const char *, const char *, size_t, int> data)
{
return ::setxattr( cyName.c_str(), get<0>(data), get<1>(data),
get<2>(data), get<3>(data) );
}
int encfs_setxattr( const char *path, const char *name,
const char *value, size_t size, int flags )
{
return withCipherPath( "setxattr", path, _do_setxattr,
make_tuple(name, value, size, flags) );
}
#endif
#ifdef XATTR_ADD_OPT
int _do_getxattr(EncFS_Context *, const string &cyName,
tuple<const char *, void *, size_t, uint32_t> data)
{
int options = 0;
return ::getxattr( cyName.c_str(), get<0>(data),
get<1>(data), get<2>(data), get<3>(data), options );
}
int encfs_getxattr( const char *path, const char *name,
char *value, size_t size, uint32_t position )
{
return withCipherPath( "getxattr", path, _do_getxattr,
make_tuple(name, (void *)value, size, position), true );
}
#else
int _do_getxattr(EncFS_Context *, const string &cyName,
tuple<const char *, void *, size_t> data)
{
return ::getxattr( cyName.c_str(), get<0>(data),
get<1>(data), get<2>(data));
}
int encfs_getxattr( const char *path, const char *name,
char *value, size_t size )
{
return withCipherPath( "getxattr", path, _do_getxattr,
make_tuple(name, (void *)value, size), true );
}
#endif
int _do_listxattr(EncFS_Context *, const string &cyName,
tuple<char *, size_t> data)
{
#ifdef XATTR_ADD_OPT
int options = 0;
int res = ::listxattr( cyName.c_str(), get<0>(data), get<1>(data),
options );
#else
int res = ::listxattr( cyName.c_str(), get<0>(data), get<1>(data) );
#endif
return (res == -1) ? -errno : res;
}
int encfs_listxattr( const char *path, char *list, size_t size )
{
return withCipherPath( "listxattr", path, _do_listxattr,
make_tuple(list, size), true );
}
int _do_removexattr(EncFS_Context *, const string &cyName, const char *name)
{
#ifdef XATTR_ADD_OPT
int options = 0;
int res = ::removexattr( cyName.c_str(), name, options );
#else
int res = ::removexattr( cyName.c_str(), name );
#endif
return (res == -1) ? -errno : res;
}
int encfs_removexattr( const char *path, const char *name )
{
return withCipherPath( "removexattr", path, _do_removexattr, name );
}
#endif // HAVE_XATTR

View File

@ -1,12 +1,3 @@
=cut
Copyright (c) 2003-2008, Valient Gough <vgough@pobox.com>
All rights reserved.
EncFS is free software; you can distribute it and/or modify it under the terms
of the GNU General Public License (GPL), as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.
=pod
=head1 NAME
@ -174,9 +165,8 @@ the B<fusermount> help page for information on available commands.
=item B<--no-default-flags>
B<Encfs> adds the FUSE flags "use_ino" and "default_permissions" by default, as
of version 1.2.2, because that improves compatibility with some programs.. If
for some reason you need to disable one or both of these flags, use the option
B<--no-default-flags>.
that improves compatibility with some programs.. If you need to disable one or
both of these flags, use the option B<--no-default-flags>.
The following command lines produce the same result:
@ -196,10 +186,6 @@ for a trailing newline (\n) which will be removed.
For example, specifying B<--extpass>=I</usr/lib/ssh/ssh-askpass> will cause
B<EncFS> to use ssh's password prompt program.
B<Note>: B<EncFS> reads at most 2k of data from the password program, and it
removes any trailing newline. Versions before 1.4.x accepted only 64 bytes of
text.
=item B<-S>, B<--stdinpass>
Read password from standard input, without prompting. This may be useful for
@ -209,6 +195,10 @@ Note that you should make sure the filesystem and mount points exist first.
Otherwise encfs will prompt for the filesystem creation options, which may
interfere with your script.
B<Note>: B<EncFS> reads a limited amount of data from the console (roughly 2k
bytes), and it removes any trailing newline. If your password is larger than
this, use --extpass.
=item B<--anykey>
Turn off key validation checking. This allows B<EncFS> to be used with
@ -275,10 +265,10 @@ to decode filenames if desired.
=head1 CAVEATS
B<EncFS> is not a true filesystem. It does not deal with any of the actual
storage or maintenance of files. It simply translates requests (encrypting or
storage or maintenance of files. It translates requests (encrypting or
decrypting as necessary) and passes the requests through to the underlying
host filesystem. Therefor any limitations of the host filesystem will likely
be inherited by B<EncFS> (or possibly be further limited).
host filesystem. Therefor any limitations of the host filesystem will be
inherited by B<EncFS> (or possibly be further limited).
One such limitation is filename length. If your underlying filesystem limits
you to N characters in a filename, then B<EncFS> will limit you to approximately
@ -328,20 +318,20 @@ they mean:
=head1 Key Derivation Function
As of version 1.5, B<EncFS> now uses PBKDF2 as the default key derivation
function. The number of iterations in the keying function is selected based on
wall clock time to generate the key. In standard mode, a target time of 0.5
seconds is used, and in paranoia mode a target of 3.0 seconds is used.
B<EncFS> uses PBKDF2 as the key derivation function. The number of iterations
in the keying function is selected based on wall clock time to generate the
key. In standard mode, a target time of 0.5 seconds is used, and in paranoia
mode a target of 3.0 seconds is used.
On a 1.6Ghz AMD 64 system, it rougly 64k iterations of the key derivation
function can be handled in half a second. The exact number of iterations to
use is stored in the configuration file, as it is needed to remount the
filesystem.
If an B<EncFS> filesystem configuration from 1.4.x is modified with version 1.5
(such as when using encfsctl to change the password), then the new PBKDF2
function will be used and the filesystem will no longer be readable by older
versions.
If an B<EncFS> filesystem configuration from 1.4.x is modified with a later
version (such as when using encfsctl to change the password), then the new
PBKDF2 function will be used and the filesystem will no longer be readable by
older versions.
=over 4
@ -375,18 +365,16 @@ read and decoded, so a large block size adds overhead to small requests. With
write calls it is even worse, as a block must be read and decoded, the change
applied and the block encoded and written back out.
The default is 512 bytes as of version 1.0. It was hard coded to 64 bytes in
version 0.x, which was not as efficient as the current setting for general
usage.
The default block size is currently 2k.
=item I<Filename Encoding>
B<New in 1.1>. A choice is given between stream encoding of filename and block
encoding. The advantage of stream encoding is that the encoded filenames will
be as short as possible. If you have a filename with a single letter, it will
be very short in the encoded form, where as block encoded filenames are always
rounded up to the block size of the encryption cipher (8 bytes for Blowfish and
16 bytes for AES).
A choice is given between stream encoding of filename and block encoding. The
advantage of stream encoding is that the encoded filenames will be as short as
possible. If you have a filename with a single letter, it will be very short
in the encoded form, where as block encoded filenames are always rounded up to
the block size of the encryption cipher (8 bytes for Blowfish and 16 bytes for
AES).
The advantage of block encoding mode is that filename lenths all come out as a
multiple of the cipher block size. This means that someone looking at your
@ -395,16 +383,14 @@ on by default, as it takes a similar amount of time to using the stream cipher.
However stream cipher mode may be useful if you want shorter encrypted
filenames for some reason.
Prior to version 1.1, only stream encoding was supported.
=item I<Filename Initialization Vector Chaining>
B<New in 1.1>. In previous versions of B<EncFS>, each filename element in
a path was encoded separately. So if "foo" encoded to "XXX", then it would
always encode that way (given the same encryption key), no matter if the path
was "a/b/foo", or "aa/foo/cc", etc. That meant it was possible for someone
looking at the encrypted data to see if two files in different directories had
the same name, even though they wouldn't know what that name decoded to.
In previous versions of B<EncFS>, each filename element in a path was encoded
separately. So if "foo" encoded to "XXX", then it would always encode that way
(given the same encryption key), no matter if the path was "a/b/foo", or
"aa/foo/cc", etc. That meant it was possible for someone looking at the
encrypted data to see if two files in different directories had the same name,
even though they wouldn't know what that name decoded to.
With initialization vector chaining, each directory gets its own initialization
vector. So "a/foo" and "b/foo" will have completely different encoded names
@ -422,11 +408,11 @@ rename will fail.
=item I<Per-File Initialization Vectors>
B<New in 1.1>. In previous versions of B<EncFS>, each file was encoded in the
same way. Each block in a file has always had its own initialization vector,
but in a deterministic way so that block N in one file is encoded in the same
was as block N in another file. That made it possible for someone to tell if
two files were identical (or parts of the file were identical) by comparing the
In previous versions of B<EncFS>, each file was encoded in the same way. Each
block in a file has always had its own initialization vector, but in a
deterministic way so that block N in one file is encoded in the same was as
block N in another file. That made it possible for someone to tell if two
files were identical (or parts of the file were identical) by comparing the
encoded data.
With per-file initialization vectors, each file gets its own 64bit random
@ -436,10 +422,9 @@ This option is enabled by default.
=item I<External IV Chaining>
B<New in 1.1.3>. This option is closely related to Per-File Initialization
Vectors and Filename Initialization Vector Chaining. Basically it extends the
initialization vector chaining from filenames to the per-file initialization
vector.
This option is closely related to Per-File Initialization Vectors and Filename
Initialization Vector Chaining. Basically it extends the initialization vector
chaining from filenames to the per-file initialization vector.
When this option is enabled, the per-file initialization vector is encoded
using the initialization vector derived from the filename initialization vector
@ -461,11 +446,11 @@ Because of these limits, this option is disabled by default for standard mode
=item I<Block MAC headers>
B<New to 1.1>. If this is enabled, every block in every file is stored along
with a cryptographic checksum (Message Authentication Code). This makes it
virtually impossible to modify a file without the change being detected by
B<EncFS>. B<EncFS> will refuse to read data which does not pass the checksum,
and will log the error and return an IO error to the application.
If this is enabled, every block in every file is stored along with a
cryptographic checksum (Message Authentication Code). This makes it virtually
impossible to modify a file without the change being detected by B<EncFS>.
B<EncFS> will refuse to read data which does not pass the checksum, and will
log the error and return an IO error to the application.
This adds substantial overhead (default being 8 bytes per filesystem block),
plus computational overhead, and is not enabled by default except in paranoia
@ -498,28 +483,6 @@ filesystem contents along with the algorithms B<EncFS> supports to thwart them:
=over 4
=item B<Attack>: modifying a few bytes of an encrypted file (without knowing
what they will decode to).
B<EncFS> does not use any form of XOR encryption which would allow
single bytes to be modified without affecting others. Most modifications
would affect dozens or more bytes. Additionally, MAC Block headers can be
used to identify any changes to files.
=item B<Attack>: copying a random block of one file to a random block of another file.
Each block has its own [deterministic] initialization vector.
=item B<Attack>: copying block N to block N of another file.
When the Per-File Initialization Vector support is enabled (default
in 1.1.x filesystems), a copied block will not decode properly when copied to
another file.
=item B<Attack>: copying an entire file to another file.
Can be prevented by enabling External IV Chaining mode.
=item B<Attack>: determine if two filenames are the same by looking at encrypted names.
Filename Initialization Vector chaining prevents this by giving each file a
@ -529,8 +492,39 @@ Filename Initialization Vector chaining prevents this by giving each file a
Per-File Initialization Vector support prevents this.
=item B<Attack>: copying an entire file to another file.
Can be prevented by enabling External IV Chaining mode.
=item B<Attack>: copying a random block of one file to a random block of another file.
Each block has its own [deterministic] initialization vector.
=item B<Attack>: copying block N to block N of another file.
When the Per-File Initialization Vector support is enabled (the default), a
copied block will not decode properly when copied to another file.
=item B<Attack>: modifying a few bytes of an encrypted file (without knowing
what they will decode to).
B<EncFS> does not use any form of XOR encryption which would allow
single bytes to be modified without affecting others. Most modifications
would affect dozens or more bytes. Additionally, MAC Block headers can be
used to identify any changes to files.
=back
=head1 LICENSE
EncFS is free software; you can distribute it and/or modify it under the terms
of the GNU General Public License (GPL), as published by the Free Software
Foundation; either version 3 of the License, or (at your option) any later
version.
The library portion of EncFS is licensed under the LGPL version 3. See the
COPYING files in the source distribution for details.
=head1 DISCLAIMER
This library is distributed in the hope that it will be useful, but WITHOUT ANY

View File

@ -1,853 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software; you can distribute it and/or modify it under
* the terms of the GNU General Public License (GPL), as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#include "encfs.h"
#include "autosprintf.h"
#include "config.h"
#include "FileUtils.h"
#include "Cipher.h"
#include "Context.h"
#include "FileNode.h"
#include "DirNode.h"
#include <rlog/rlog.h>
#include <rlog/StdioNode.h>
#include <rlog/RLogChannel.h>
#include <iostream>
#include <string>
#include <getopt.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef __FreeBSD__
#include <libintl.h>
#endif
#include "i18n.h"
#ifdef HAVE_SSL
#define NO_DES
#include <openssl/ssl.h>
#endif
using namespace rlog;
using namespace std;
using namespace gnu;
static int showInfo( int argc, char **argv );
static int showVersion( int argc, char **argv );
static int chpasswd( int argc, char **argv );
static int chpasswdAutomaticly( int argc, char **argv );
static int cmd_ls( int argc, char **argv );
static int cmd_decode( int argc, char **argv );
static int cmd_encode( int argc, char **argv );
static int cmd_showcruft( int argc, char **argv );
static int cmd_cat( int argc, char **argv );
static int cmd_export( int argc, char **argv );
static int cmd_showKey( int argc, char **argv );
struct CommandOpts
{
const char *name;
int minOptions;
int maxOptions;
int (*func)(int argc, char **argv);
const char *argStr;
const char *usageStr;
} commands[] =
{
{"info", 1, 1, showInfo, "(root dir)",
// xgroup(usage)
gettext_noop(" -- show information (Default command)")},
{"showKey", 1, 1, cmd_showKey, "(root dir)",
// xgroup(usage)
gettext_noop(" -- show key")},
{"passwd", 1, 1, chpasswd, "(root dir)",
// xgroup(usage)
gettext_noop(" -- change password for volume")},
{"autopasswd", 1, 1, chpasswdAutomaticly, "(root dir)",
// xgroup(usage)
gettext_noop(" -- change password for volume, taking password"
" from standard input.\n\tNo prompts are issued.")},
{"ls", 1, 2, cmd_ls, 0,0},
{"showcruft", 1, 1, cmd_showcruft, "(root dir)",
// xgroup(usage)
gettext_noop(" -- show undecodable filenames in the volume")},
{"cat", 2, 2, cmd_cat, "(root dir) path",
// xgroup(usage)
gettext_noop(" -- decodes the file and cats it to standard out")},
{"decode", 1, 100, cmd_decode, "[--extpass=prog] (root dir) [encoded-name ...]",
// xgroup(usage)
gettext_noop(" -- decodes name and prints plaintext version")},
{"encode", 1, 100, cmd_encode, "[--extpass=prog] (root dir) [plaintext-name ...]",
// xgroup(usage)
gettext_noop(" -- encodes a filename and print result")},
{"export", 2, 2, cmd_export, "(root dir) path",
// xgroup(usage)
gettext_noop(" -- decrypts a volume and writes results to path")},
{"--version", 0, 0, showVersion, "",
// xgroup(usage)
gettext_noop(" -- print version number and exit")},
{0,0,0,0,0,0}
};
static
void usage(const char *name)
{
cerr << autosprintf(_("encfsctl version %s"), VERSION) << "\n"
<< _("Usage:\n")
// displays usage commands, eg "./encfs (root dir) ..."
// xgroup(usage)
<< autosprintf(_("%s (root dir)\n"
" -- displays information about the filesystem, or \n"), name);
int offset = 0;
while(commands[offset].name != 0)
{
if( commands[offset].argStr != 0 )
{
cerr << "encfsctl " << commands[offset].name << " "
<< commands[offset].argStr << "\n"
<< gettext( commands[offset].usageStr ) << "\n";
}
++offset;
}
cerr << "\n"
// xgroup(usage)
<< autosprintf(_("Example: \n%s info ~/.crypt\n"), name)
<< "\n";
}
static bool checkDir( string &rootDir )
{
if( !isDirectory( rootDir.c_str() ))
{
cerr << autosprintf(_("directory %s does not exist.\n"),
rootDir.c_str());
return false;
}
if(rootDir[ rootDir.length()-1 ] != '/')
rootDir.append("/");
return true;
}
static int showVersion( int argc, char **argv )
{
(void)argc;
(void)argv;
// xgroup(usage)
cerr << autosprintf(_("encfsctl version %s"), VERSION) << "\n";
return EXIT_SUCCESS;
}
static int showInfo( int argc, char **argv )
{
(void)argc;
string rootDir = argv[1];
if( !checkDir( rootDir ))
return EXIT_FAILURE;
EncfsConfig config;
ConfigType type = readConfig( rootDir, config );
// show information stored in config..
switch(type)
{
case Config_None:
// xgroup(diag)
cout << _("Unable to load or parse config file\n");
return EXIT_FAILURE;
case Config_Prehistoric:
// xgroup(diag)
cout << _("A really old EncFS filesystem was found. \n"
"It is not supported in this EncFS build.\n");
return EXIT_FAILURE;
case Config_V3:
// xgroup(diag)
cout << "\n" << autosprintf(_("Version 3 configuration; "
"created by %s\n"), config.creator().c_str());
break;
case Config_V4:
// xgroup(diag)
cout << "\n" << autosprintf(_("Version 4 configuration; "
"created by %s\n"), config.creator().c_str());
break;
case Config_V5:
case Config_V6:
case Config_V7:
// xgroup(diag)
cout << "\n" << autosprintf(_("Version %i configuration; "
"created by %s (revision %i)\n"),
type,
config.creator().c_str(),
config.revision());
break;
}
showFSInfo( config );
return EXIT_SUCCESS;
}
static RootPtr initRootInfo(int &argc, char ** &argv)
{
RootPtr result;
shared_ptr<EncFS_Opts> opts( new EncFS_Opts() );
opts->createIfNotFound = false;
opts->checkKey = false;
static struct option long_options[] = {
{"extpass", 1, 0, 'p'},
{0,0,0,0}
};
for(;;)
{
int option_index = 0;
int res = getopt_long( argc, argv, "",
long_options, &option_index);
if(res == -1)
break;
switch(res)
{
case 'p':
opts->passwordProgram.assign(optarg);
break;
default:
rWarning(_("getopt error: %i"), res);
break;
}
}
argc -= optind;
argv += optind;
if(argc == 0)
{
cerr << _("Incorrect number of arguments") << "\n";
} else
{
opts->rootDir = string( argv[0] );
--argc;
++argv;
if(checkDir( opts->rootDir ))
result = initFS( NULL, opts );
if(!result)
cerr << _("Unable to initialize encrypted filesystem - check path.\n");
}
return result;
}
static RootPtr initRootInfo(const char* crootDir)
{
string rootDir(crootDir);
RootPtr result;
if(checkDir( rootDir ))
{
shared_ptr<EncFS_Opts> opts( new EncFS_Opts() );
opts->rootDir = rootDir;
opts->createIfNotFound = false;
opts->checkKey = false;
result = initFS( NULL, opts );
}
if(!result)
cerr << _("Unable to initialize encrypted filesystem - check path.\n");
return result;
}
static int cmd_showKey( int argc, char **argv )
{
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
else
{
// encode with itself
string b64Key = rootInfo->cipher->encodeAsString(
rootInfo->volumeKey, rootInfo->volumeKey );
cout << b64Key << "\n";
return EXIT_SUCCESS;
}
}
static int cmd_decode( int argc, char **argv )
{
RootPtr rootInfo = initRootInfo(argc, argv);
if(!rootInfo)
return EXIT_FAILURE;
if(argc > 0)
{
for(int i=0; i<argc; ++i)
{
string name = rootInfo->root->plainPath( argv[i] );
cout << name << "\n";
}
} else
{
char buf[PATH_MAX+1];
while(cin.getline(buf,PATH_MAX))
{
cout << rootInfo->root->plainPath( buf ) << "\n";
}
}
return EXIT_SUCCESS;
}
static int cmd_encode( int argc, char **argv )
{
RootPtr rootInfo = initRootInfo(argc, argv);
if(!rootInfo)
return EXIT_FAILURE;
if(argc > 0)
{
for(int i=0; i<argc; ++i)
{
string name = rootInfo->root->cipherPathWithoutRoot(argv[i]);
cout << name << "\n";
}
} else
{
char buf[PATH_MAX+1];
while(cin.getline(buf,PATH_MAX))
{
cout << rootInfo->root->cipherPathWithoutRoot( buf ) << "\n";
}
}
return EXIT_SUCCESS;
}
static int cmd_ls( int argc, char **argv )
{
(void)argc;
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
// show files in directory
{
DirTraverse dt = rootInfo->root->openDir("/");
if(dt.valid())
{
for(string name = dt.nextPlaintextName(); !name.empty();
name = dt.nextPlaintextName())
{
shared_ptr<FileNode> fnode =
rootInfo->root->lookupNode( name.c_str(), "encfsctl-ls" );
struct stat stbuf;
fnode->getAttr( &stbuf );
struct tm stm;
localtime_r( &stbuf.st_mtime, &stm );
stm.tm_year += 1900;
// TODO: when I add "%s" to the end and name.c_str(), I get a
// seg fault from within strlen. Why ???
printf("%11i %4i-%02i-%02i %02i:%02i:%02i %s\n",
int(stbuf.st_size),
int(stm.tm_year), int(stm.tm_mon), int(stm.tm_mday),
int(stm.tm_hour), int(stm.tm_min), int(stm.tm_sec),
name.c_str());
}
}
}
return EXIT_SUCCESS;
}
// apply an operation to every block in the file
template<typename T>
int processContents( const shared_ptr<EncFS_Root> &rootInfo,
const char *path, T &op )
{
int errCode = 0;
shared_ptr<FileNode> node = rootInfo->root->openNode( path, "encfsctl",
O_RDONLY, &errCode );
if(!node)
{
// try treating filename as an enciphered path
string plainName = rootInfo->root->plainPath( path );
node = rootInfo->root->lookupNode( plainName.c_str(), "encfsctl" );
if(node)
{
errCode = node->open( O_RDONLY );
if(errCode < 0)
node.reset();
}
}
if(!node)
{
cerr << "unable to open " << path << "\n";
return errCode;
} else
{
unsigned char buf[512];
int blocks = (node->getSize() + sizeof(buf)-1) / sizeof(buf);
// read all the data in blocks
for(int i=0; i<blocks; ++i)
{
int bytes = node->read(i*sizeof(buf), buf, sizeof(buf));
int res = op(buf, bytes);
if(res < 0)
return res;
}
}
return 0;
}
class WriteOutput
{
int _fd;
public:
WriteOutput(int fd) { _fd = fd; }
~WriteOutput() { close(_fd); }
int operator()(const void *buf, int count)
{
return (int)write(_fd, buf, count);
}
};
static int cmd_cat( int argc, char **argv )
{
(void)argc;
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
const char *path = argv[2];
WriteOutput output(STDOUT_FILENO);
int errCode = processContents( rootInfo, path, output );
return errCode;
}
static int copyLink(const struct stat &stBuf,
const shared_ptr<EncFS_Root> &rootInfo,
const string &cpath, const string &destName )
{
vector<char> buf(stBuf.st_size+1, 0);
int res = ::readlink( cpath.c_str(), &buf[0], stBuf.st_size );
if(res == -1)
{
cerr << "unable to readlink of " << cpath << "\n";
return EXIT_FAILURE;
}
buf[res] = '\0';
string decodedLink = rootInfo->root->plainPath(&buf[0]);
res = ::symlink( decodedLink.c_str(), destName.c_str() );
if(res == -1)
{
cerr << "unable to create symlink for " << cpath
<< " to " << decodedLink << "\n";
}
return EXIT_SUCCESS;
}
static int copyContents(const shared_ptr<EncFS_Root> &rootInfo,
const char* encfsName, const char* targetName)
{
shared_ptr<FileNode> node =
rootInfo->root->lookupNode( encfsName, "encfsctl" );
if(!node)
{
cerr << "unable to open " << encfsName << "\n";
return EXIT_FAILURE;
} else
{
struct stat st;
if(node->getAttr(&st) != 0)
return EXIT_FAILURE;
if((st.st_mode & S_IFLNK) == S_IFLNK)
{
string d = rootInfo->root->cipherPath(encfsName);
char linkContents[PATH_MAX+2];
if(readlink (d.c_str(), linkContents, PATH_MAX + 1) <= 0)
{
cerr << "unable to read link " << encfsName << "\n";
return EXIT_FAILURE;
}
symlink(rootInfo->root->plainPath(linkContents).c_str(),
targetName);
} else
{
int outfd = creat(targetName, st.st_mode);
WriteOutput output(outfd);
processContents( rootInfo, encfsName, output );
}
}
return EXIT_SUCCESS;
}
static bool endsWith(const string &str, char ch)
{
if(str.empty())
return false;
else
return str[str.length()-1] == ch;
}
static int traverseDirs(const shared_ptr<EncFS_Root> &rootInfo,
string volumeDir, string destDir)
{
if(!endsWith(volumeDir, '/'))
volumeDir.append("/");
if(!endsWith(destDir, '/'))
destDir.append("/");
// Lookup directory node so we can create a destination directory
// with the same permissions
{
struct stat st;
shared_ptr<FileNode> dirNode =
rootInfo->root->lookupNode( volumeDir.c_str(), "encfsctl" );
if(dirNode->getAttr(&st))
return EXIT_FAILURE;
mkdir(destDir.c_str(), st.st_mode);
}
// show files in directory
DirTraverse dt = rootInfo->root->openDir(volumeDir.c_str());
if(dt.valid())
{
for(string name = dt.nextPlaintextName(); !name.empty();
name = dt.nextPlaintextName())
{
// Recurse to subdirectories
if(name != "." && name != "..")
{
string plainPath = volumeDir + name;
string cpath = rootInfo->root->cipherPath(plainPath.c_str());
string destName = destDir + name;
int r = EXIT_SUCCESS;
struct stat stBuf;
if( !lstat( cpath.c_str(), &stBuf ))
{
if( S_ISDIR( stBuf.st_mode ) )
{
traverseDirs(rootInfo, (plainPath + '/').c_str(),
destName + '/');
} else if( S_ISLNK( stBuf.st_mode ))
{
r = copyLink( stBuf, rootInfo, cpath, destName );
} else
{
r = copyContents(rootInfo, plainPath.c_str(),
destName.c_str());
}
} else
{
r = EXIT_FAILURE;
}
if(r != EXIT_SUCCESS)
return r;
}
}
}
return EXIT_SUCCESS;
}
static int cmd_export( int argc, char **argv )
{
(void)argc;
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
string destDir = argv[2];
// if the dir doesn't exist, then create it (with user permission)
if(!checkDir(destDir) && !userAllowMkdir(destDir.c_str(), 0700))
return EXIT_FAILURE;
return traverseDirs(rootInfo, "/", destDir);
}
int showcruft( const shared_ptr<EncFS_Root> &rootInfo, const char *dirName )
{
int found = 0;
DirTraverse dt = rootInfo->root->openDir( dirName );
if(dt.valid())
{
bool showedDir = false;
for(string name = dt.nextInvalid(); !name.empty();
name = dt.nextInvalid())
{
string cpath = rootInfo->root->cipherPath( dirName );
cpath += '/';
cpath += name;
if(!showedDir)
{
// just before showing a list of files in a directory
cout << autosprintf(_("In directory %s: \n"), dirName);
showedDir = true;
}
++found;
cout << cpath << "\n";
}
// now go back and look for directories to recurse into..
dt = rootInfo->root->openDir( dirName );
if(dt.valid())
{
for(string name = dt.nextPlaintextName(); !name.empty();
name = dt.nextPlaintextName())
{
if( name == "." || name == "..")
continue;
string plainPath = dirName;
plainPath += '/';
plainPath += name;
string cpath = rootInfo->root->cipherPath( plainPath.c_str() );
if(isDirectory( cpath.c_str() ))
found += showcruft( rootInfo, plainPath.c_str() );
}
}
}
return found;
}
/*
iterate recursively through the filesystem and print out names of files
which have filenames which cannot be decoded with the given key..
*/
static int cmd_showcruft( int argc, char **argv )
{
(void)argc;
RootPtr rootInfo = initRootInfo(argv[1]);
if(!rootInfo)
return EXIT_FAILURE;
int filesFound = showcruft( rootInfo, "/" );
cerr << autosprintf("Found %i invalid file(s).", filesFound) << "\n";
return EXIT_SUCCESS;
}
static int do_chpasswd( bool useStdin, bool annotate, int argc, char **argv )
{
(void)argc;
string rootDir = argv[1];
if( !checkDir( rootDir ))
return EXIT_FAILURE;
EncfsConfig config;
ConfigType cfgType = readConfig( rootDir, config );
if(cfgType == Config_None)
{
cout << _("Unable to load or parse config file\n");
return EXIT_FAILURE;
}
// instanciate proper cipher
shared_ptr<Cipher> cipher = getCipher(config);
if(!cipher)
{
cout << autosprintf(_("Unable to find specified cipher \"%s\"\n"),
config.cipher().name().c_str());
return EXIT_FAILURE;
}
// ask for existing password
cout << _("Enter current Encfs password\n");
if (annotate)
cerr << "$PROMPT$ passwd" << endl;
CipherKey userKey = getUserKey( config, useStdin );
if(!userKey)
return EXIT_FAILURE;
// decode volume key using user key -- at this point we detect an incorrect
// password if the key checksum does not match (causing readKey to fail).
CipherKey volumeKey = cipher->readKey(
(const unsigned char *)config.key().ciphertext().data(), userKey );
if(!volumeKey)
{
cout << _("Invalid password\n");
return EXIT_FAILURE;
}
// Now, get New user key..
userKey.reset();
cout << _("Enter new Encfs password\n");
// create new key
if( useStdin )
{
if (annotate)
cerr << "$PROMPT$ new_passwd" << endl;
}
userKey = getNewUserKey( config, useStdin, string(), string() );
// re-encode the volume key using the new user key and write it out..
int result = EXIT_FAILURE;
if(userKey)
{
int encodedKeySize = cipher->encodedKeySize();
unsigned char *keyBuf = new unsigned char[ encodedKeySize ];
// encode volume key with new user key
cipher->writeKey( volumeKey, keyBuf, userKey );
userKey.reset();
EncryptedKey *key = config.mutable_key();
key->set_ciphertext( keyBuf, encodedKeySize );
delete[] keyBuf;
if(saveConfig( rootDir, config ))
{
// password modified -- changes volume key of filesystem..
cout << _("Volume Key successfully updated.\n");
result = EXIT_SUCCESS;
} else
{
cout << _("Error saving modified config file.\n");
}
} else
{
cout << _("Error creating key\n");
}
volumeKey.reset();
return result;
}
static int chpasswd( int argc, char **argv )
{
return do_chpasswd( false, false, argc, argv );
}
static int chpasswdAutomaticly( int argc, char **argv )
{
return do_chpasswd( true, false, argc, argv );
}
int main(int argc, char **argv)
{
RLogInit( argc, argv );
#ifdef LOCALEDIR
setlocale( LC_ALL, "" );
bindtextdomain( PACKAGE, LOCALEDIR );
textdomain( PACKAGE );
#endif
#ifdef HAVE_SSL
SSL_load_error_strings();
SSL_library_init();
#endif
StdioNode *slog = new StdioNode( STDERR_FILENO );
slog->subscribeTo( GetGlobalChannel("error") );
slog->subscribeTo( GetGlobalChannel("warning") );
#ifndef NO_DEBUG
slog->subscribeTo( GetGlobalChannel("debug") );
#endif
if(argc < 2)
{
usage( argv[0] );
return EXIT_FAILURE;
}
if(argc == 2 && !(*argv[1] == '-' && *(argv[1]+1) == '-'))
{
// default command when only 1 argument given -- treat the argument as
// a directory..
return showInfo( argc, argv );
} else
{
// find the specified command
int offset = 0;
while(commands[offset].name != 0)
{
if(!strcmp( argv[1], commands[offset].name ))
break;
++offset;
}
if(commands[offset].name == 0)
{
cerr << autosprintf(_("invalid command: \"%s\""), argv[1]) << "\n";
} else
{
if((argc-2 < commands[offset].minOptions) ||
(argc-2 > commands[offset].maxOptions))
{
cerr << autosprintf(
_("Incorrect number of arguments for command \"%s\""),
argv[1]) << "\n";
} else
return (*commands[offset].func)( argc-1, argv+1 );
}
}
return EXIT_FAILURE;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,109 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2007, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "openssl.h"
#include <pthread.h>
#include <rlog/rlog.h>
#define NO_DES
#include <openssl/ssl.h>
#include <openssl/rand.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif
unsigned long pthreads_thread_id()
{
return (unsigned long)pthread_self();
}
static pthread_mutex_t *crypto_locks = NULL;
void pthreads_locking_callback( int mode, int n,
const char *caller_file, int caller_line )
{
(void)caller_file;
(void)caller_line;
if(!crypto_locks)
{
rDebug("Allocating %i locks for OpenSSL", CRYPTO_num_locks() );
crypto_locks = new pthread_mutex_t[ CRYPTO_num_locks() ];
for(int i=0; i<CRYPTO_num_locks(); ++i)
pthread_mutex_init( crypto_locks+i, 0 );
}
if(mode & CRYPTO_LOCK)
{
pthread_mutex_lock( crypto_locks + n );
} else
{
pthread_mutex_unlock( crypto_locks + n );
}
}
void pthreads_locking_cleanup()
{
if(crypto_locks)
{
for(int i=0; i<CRYPTO_num_locks(); ++i)
pthread_mutex_destroy( crypto_locks+i );
delete[] crypto_locks;
crypto_locks = NULL;
}
}
void openssl_init(bool threaded)
{
// initialize the SSL library
SSL_load_error_strings();
SSL_library_init();
unsigned int randSeed = 0;
RAND_bytes( (unsigned char*)&randSeed, sizeof(randSeed) );
srand( randSeed );
#ifndef OPENSSL_NO_ENGINE
/* Load all bundled ENGINEs into memory and make them visible */
ENGINE_load_builtin_engines();
/* Register all of them for every algorithm they collectively implement */
ENGINE_register_all_complete();
#endif // NO_ENGINE
if(threaded)
{
// provide locking functions to OpenSSL since we'll be running with
// threads accessing openssl in parallel.
CRYPTO_set_id_callback( pthreads_thread_id );
CRYPTO_set_locking_callback( pthreads_locking_callback );
}
}
void openssl_shutdown(bool threaded)
{
#ifndef OPENSSL_NO_ENGINE
ENGINE_cleanup();
#endif
if(threaded)
pthreads_locking_cleanup();
}

View File

@ -1,563 +0,0 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2003, Valient Gough
*
* This library is free software; you can distribute it and/or modify it under
* the terms of the GNU General Public License (GPL), as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GPL in the file COPYING for more
* details.
*
*/
#include "encfs.h"
#include "config.h"
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <sstream>
#include "Cipher.h"
#include "DirNode.h"
#include "MemoryPool.h"
#include "Interface.h"
#include "FileUtils.h"
#include "StreamNameIO.h"
#include "BlockNameIO.h"
#include "NullNameIO.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include <rlog/StdioNode.h>
#include <rlog/RLogChannel.h>
#ifdef HAVE_SSL
#define NO_DES
#include <openssl/ssl.h>
#ifndef OPENSSL_NO_ENGINE
#include <openssl/engine.h>
#endif
#endif
#include <google/protobuf/text_format.h>
#if HAVE_TR1_UNORDERED_SET
#include <tr1/unordered_set>
using std::tr1::unordered_set;
#else
#include <unordered_set>
using std::unordered_set;
#endif
using namespace std;
using namespace rlog;
const int FSBlockSize = 256;
static
int checkErrorPropogation( const shared_ptr<Cipher> &cipher,
int size, int byteToChange, const CipherKey &key )
{
MemBlock orig = MemoryPool::allocate(size);
MemBlock data = MemoryPool::allocate(size);
for(int i=0; i<size; ++i)
{
unsigned char tmp = rand();
orig.data[i] = tmp;
data.data[i] = tmp;
}
if(size != FSBlockSize)
cipher->streamEncode( data.data, size, 0, key );
else
cipher->blockEncode( data.data, size, 0, key );
// intoduce an error in the encoded data, so we can check error propogation
if(byteToChange >= 0 && byteToChange < size)
{
unsigned char previousValue = data.data[byteToChange];
do
{
data.data[byteToChange] = rand();
} while(data.data[byteToChange] == previousValue);
}
if(size != FSBlockSize)
cipher->streamDecode( data.data, size, 0, key );
else
cipher->blockDecode( data.data, size, 0, key );
int numByteErrors = 0;
for(int i=0; i<size; ++i)
{
if( data.data[i] != orig.data[i] )
++numByteErrors;
}
MemoryPool::release( data );
MemoryPool::release( orig );
return numByteErrors;
}
const char TEST_ROOTDIR[] = "/foo";
static
bool testNameCoding( DirNode &dirNode, bool verbose,
bool collisionTest = false )
{
// encrypt a name
const char *name[] = {
"1234567",
"12345678",
"123456789",
"123456789ABCDEF",
"123456789ABCDEF0",
"123456789ABCDEF01",
"test-name",
"test-name2",
"test",
"../test",
"/foo/bar/blah",
"test-name.21",
"test-name.22",
"test-name.o",
"1.test",
"2.test",
"a/b/c/d",
"a/c/d/e",
"b/c/d/e",
"b/a/c/d",
NULL
};
const char **orig = name;
while(*orig)
{
if(verbose)
cerr << " coding name \"" << *orig << "\"";
string encName = dirNode.relativeCipherPath( *orig );
if(verbose)
cerr << " -> \"" << encName.c_str() << "\"";
// decrypt name
string decName = dirNode.plainPath( encName.c_str() );
if(decName == *orig)
{
if(verbose)
cerr << " OK\n";
} else
{
if(verbose)
cerr << " FAILED (got " << decName << ")\n";
return false;
}
orig++;
}
if (collisionTest)
{
if (verbose)
cerr << "Checking for name collections, this will take a while..\n";
// check for collision rate
char buf[64];
unordered_set<string> encryptedNames;
for (long i=0; i < 10000000; i++)
{
snprintf(buf, sizeof(buf), "%li", i);
string encName = dirNode.relativeCipherPath( buf );
// simulate a case-insisitive filesystem..
std::transform(encName.begin(), encName.end(), encName.begin(),
::toupper);
if (encryptedNames.insert(encName).second == false) {
cerr << "collision detected after " << i << " iterations";
break;
}
}
cerr << "NO collisions detected";
}
return true;
}
bool runTests(const shared_ptr<Cipher> &cipher, bool verbose)
{
// create a random key
if(verbose)
cerr << "Generating new key, output will be different on each run\n\n";
CipherKey key = cipher->newRandomKey();
if(verbose)
cerr << "Testing key save / restore :";
{
CipherKey encodingKey = cipher->newRandomKey();
int encodedKeySize = cipher->encodedKeySize();
unsigned char *keyBuf = new unsigned char [ encodedKeySize ];
cipher->writeKey( key, keyBuf, encodingKey );
CipherKey key2 = cipher->readKey( keyBuf, encodingKey );
if(!key2)
{
if(verbose)
cerr << " FAILED (decode error)\n";
return false;
}
if(cipher->compareKey( key, key2 ))
{
if(verbose)
cerr << " OK\n";
} else
{
if(verbose)
cerr << " FAILED\n";
return false;
}
}
if(verbose)
cerr << "Testing Config interface load / store :";
{
CipherKey encodingKey = cipher->newRandomKey();
int encodedKeySize = cipher->encodedKeySize();
unsigned char *keyBuf = new unsigned char [ encodedKeySize ];
cipher->writeKey( key, keyBuf, encodingKey );
// store in config struct..
EncfsConfig cfg;
cfg.mutable_cipher()->MergeFrom(cipher->interface());
EncryptedKey *encryptedKey = cfg.mutable_key();
encryptedKey->set_size(8 * cipher->keySize());
encryptedKey->set_ciphertext( keyBuf, encodedKeySize );
cfg.set_block_size(FSBlockSize);
// save config
string data;
google::protobuf::TextFormat::PrintToString(cfg, &data);
// read back in and check everything..
EncfsConfig cfg2;
google::protobuf::TextFormat::ParseFromString(data, &cfg2);
// check..
rAssert( implements(cfg.cipher(),cfg2.cipher()) );
rAssert( cfg.key().size() == cfg2.key().size() );
rAssert( cfg.block_size() == cfg2.block_size() );
// try decoding key..
CipherKey key2 = cipher->readKey( (unsigned char *)cfg2.key().ciphertext().data(), encodingKey );
if(!key2)
{
if(verbose)
cerr << " FAILED (decode error)\n";
return false;
}
if(cipher->compareKey( key, key2 ))
{
if(verbose)
cerr << " OK\n";
} else
{
if(verbose)
cerr << " FAILED\n";
return false;
}
}
FSConfigPtr fsCfg = FSConfigPtr(new FSConfig);
fsCfg->cipher = cipher;
fsCfg->key = key;
fsCfg->config.reset(new EncfsConfig);
fsCfg->config->set_block_size(FSBlockSize);
if(verbose)
cerr << "Testing name encode/decode (stream coding w/ IV chaining)\n";
{
fsCfg->opts.reset(new EncFS_Opts);
fsCfg->opts->idleTracking = false;
fsCfg->config->set_unique_iv(false);
fsCfg->nameCoding.reset( new StreamNameIO(
StreamNameIO::CurrentInterface(), cipher, key ) );
fsCfg->nameCoding->setChainedNameIV( true );
DirNode dirNode( NULL, TEST_ROOTDIR, fsCfg );
if(!testNameCoding( dirNode, verbose ))
return false;
}
if(verbose)
cerr << "Testing name encode/decode (block coding w/ IV chaining)\n";
{
fsCfg->opts->idleTracking = false;
fsCfg->config->set_unique_iv(false);
fsCfg->nameCoding.reset( new BlockNameIO(
BlockNameIO::CurrentInterface(), cipher, key,
cipher->cipherBlockSize() ) );
fsCfg->nameCoding->setChainedNameIV( true );
DirNode dirNode( NULL, TEST_ROOTDIR, fsCfg );
if(!testNameCoding( dirNode, verbose ))
return false;
}
if(verbose)
cerr << "Testing name encode/decode (block coding w/ IV chaining, base32)\n";
{
fsCfg->opts->idleTracking = false;
fsCfg->config->set_unique_iv(false);
fsCfg->nameCoding.reset( new BlockNameIO(
BlockNameIO::CurrentInterface(), cipher, key,
cipher->cipherBlockSize(), true ) );
fsCfg->nameCoding->setChainedNameIV( true );
DirNode dirNode( NULL, TEST_ROOTDIR, fsCfg );
if(!testNameCoding( dirNode, verbose ))
return false;
}
if(!verbose)
{
{
// test stream mode, this time without IV chaining
fsCfg->nameCoding =
shared_ptr<NameIO>( new StreamNameIO(
StreamNameIO::CurrentInterface(), cipher, key ) );
fsCfg->nameCoding->setChainedNameIV( false );
DirNode dirNode( NULL, TEST_ROOTDIR, fsCfg );
if(!testNameCoding( dirNode, verbose ))
return false;
}
{
// test block mode, this time without IV chaining
fsCfg->nameCoding = shared_ptr<NameIO>( new BlockNameIO(
BlockNameIO::CurrentInterface(), cipher, key,
cipher->cipherBlockSize() ) );
fsCfg->nameCoding->setChainedNameIV( false );
DirNode dirNode( NULL, TEST_ROOTDIR, fsCfg );
if(!testNameCoding( dirNode, verbose ))
return false;
}
}
if(verbose)
cerr << "Testing block encode/decode on full block - ";
{
int numErrors = checkErrorPropogation( cipher,
FSBlockSize, -1, key );
if(numErrors)
{
if(verbose)
cerr << " FAILED!\n";
return false;
} else
{
if(verbose)
cerr << " OK\n";
}
}
if(verbose)
cerr << "Testing block encode/decode on partial block - ";
{
int numErrors = checkErrorPropogation( cipher,
FSBlockSize-1, -1, key );
if(numErrors)
{
if(verbose)
cerr << " FAILED!\n";
return false;
} else
{
if(verbose)
cerr << " OK\n";
}
}
if(verbose)
cerr << "Checking error propogation in partial block:\n";
{
int minChanges = FSBlockSize-1;
int maxChanges = 0;
int minAt = 0;
int maxAt = 0;
for(int i=0; i<FSBlockSize-1; ++i)
{
int numErrors = checkErrorPropogation( cipher,
FSBlockSize-1, i, key );
if(numErrors < minChanges)
{
minChanges = numErrors;
minAt = i;
}
if(numErrors > maxChanges)
{
maxChanges = numErrors;
maxAt = i;
}
}
if(verbose)
{
cerr << "modification of 1 byte affected between " << minChanges
<< " and " << maxChanges << " decoded bytes\n";
cerr << "minimum change at byte " << minAt
<< " and maximum at byte " << maxAt << "\n";
}
}
if(verbose)
cerr << "Checking error propogation on full block:\n";
{
int minChanges = FSBlockSize;
int maxChanges = 0;
int minAt = 0;
int maxAt = 0;
for(int i=0; i<FSBlockSize; ++i)
{
int numErrors = checkErrorPropogation( cipher,
FSBlockSize, i, key );
if(numErrors < minChanges)
{
minChanges = numErrors;
minAt = i;
}
if(numErrors > maxChanges)
{
maxChanges = numErrors;
maxAt = i;
}
}
if(verbose)
{
cerr << "modification of 1 byte affected between " << minChanges
<< " and " << maxChanges << " decoded bytes\n";
cerr << "minimum change at byte " << minAt
<< " and maximum at byte " << maxAt << "\n";
}
}
return true;
}
int main(int argc, char *argv[])
{
RLogInit( argc, argv );
StdioNode stdLog( STDERR_FILENO );
stdLog.subscribeTo( RLOG_CHANNEL("error") );
stdLog.subscribeTo( RLOG_CHANNEL("warning") );
#ifndef NO_DEBUG
stdLog.subscribeTo( RLOG_CHANNEL("debug") );
#endif
#ifdef HAVE_SSL
SSL_load_error_strings();
SSL_library_init();
#ifndef OPENSSL_NO_ENGINE
ENGINE_load_builtin_engines();
ENGINE_register_all_ciphers();
ENGINE_register_all_digests();
ENGINE_register_all_RAND();
#endif
#endif
srand( time(0) );
// get a list of the available algorithms
std::list<Cipher::CipherAlgorithm> algorithms =
Cipher::GetAlgorithmList();
std::list<Cipher::CipherAlgorithm>::const_iterator it;
cerr << "Supported Crypto interfaces:\n";
for(it = algorithms.begin(); it != algorithms.end(); ++it)
{
cerr << it->name
<< " ( " << it->iface.name() << " "
<< it->iface.major() << ":"
<< it->iface.minor() << ":"
<< it->iface.age() << " ) : " << it->description << "\n";
cerr << " - key length " << it->keyLength.min() << " to "
<< it->keyLength.max() << " , block size " << it->blockSize.min()
<< " to " << it->blockSize.max() << "\n";
}
cerr << "\n";
cerr << "Testing interfaces\n";
for(it = algorithms.begin(); it != algorithms.end(); ++it)
{
int blockSize = it->blockSize.closest( 256 );
for(int keySize = it->keyLength.min(); keySize <= it->keyLength.max();
keySize += it->keyLength.inc())
{
cerr << it->name << ", key length " << keySize
<< ", block size " << blockSize << ": ";
shared_ptr<Cipher> cipher = Cipher::New( it->name, keySize );
if(!cipher)
{
cerr << "FAILED TO CREATE\n";
} else
{
try
{
if(runTests( cipher, false ))
cerr << "OK\n";
else
cerr << "FAILED\n";
} catch( rlog::Error &er )
{
cerr << "Error: " << er.what() << "\n";
}
}
}
}
// run one test with verbose output too..
shared_ptr<Cipher> cipher = Cipher::New("AES", 192);
if(!cipher)
{
cerr << "\nNo AES cipher found, skipping verbose test.\n";
} else
{
cerr << "\nVerbose output for " << cipher->interface().name()
<< " test, key length " << cipher->keySize()*8 << ", block size "
<< FSBlockSize << ":\n";
runTests( cipher, true );
}
MemoryPool::destroyAll();
return 0;
}

View File

@ -1,213 +0,0 @@
#!/usr/bin/perl -w
use Test::More qw( no_plan );
use File::Path;
use IO::Handle;
use Digest::MD5;
my $tempDir = $ENV{'TMPDIR'} || "/tmp";
my $raw = "$tempDir/crypt-raw-$$";
my $crypt = "$tempDir/crypt-$$";
# test filesystem in standard config mode
&runTests('standard');
# test in paranoia mode
&runTests('paranoia');
sub runTests
{
my $mode = shift;
my $hardlinks = 1;
if($mode eq 'standard')
{
&mount("--standard");
} elsif($mode eq 'paranoia')
{
&mount("--paranoia");
$hardlinks = 0; # no hardlinks in paranoia mode
} else
{
die "invalid test mode";
}
# tests..
&fileCreation;
&links($hardlinks);
&truncate;
&renames;
&cleanup;
}
sub renames
{
ok( open(F, ">$crypt/orig-name") && close F, "create file for rename test");
ok( -f "$crypt/orig-name", "file exists");
ok( rename("$crypt/orig-name", "$crypt/2nd-name"), "rename");
ok( ! -f "$crypt/orig-name", "file exists");
ok( -f "$crypt/2nd-name", "file exists");
# rename directory with contents
ok( mkpath("$crypt/orig-dir/foo"), "mkdir for rename test");
ok( open(F, ">$crypt/orig-dir/foo/bar") && close F, "make file");
ok( rename("$crypt/orig-dir", "$crypt/new-dir"), "rename dir");
ok( -f "$crypt/new-dir/foo/bar", "dir rename contents");
# TODO: rename failure? (check undo works)
# check time stamps of files on rename
my $mtime = (stat "$crypt/2nd-name")[9];
# change time to 60 seconds earlier
my $olderTime = $mtime - 60;
ok( utime($olderTime, $olderTime, "$crypt/2nd-name"), "change time");
ok( rename("$crypt/2nd-name", "$crypt/3rd-name"), "rename");
is( (stat "$crypt/3rd-name")[9], $olderTime, "time unchanged by rename");
}
sub truncate
{
# write to file, then truncate it
ok( open(OUT, "+> $crypt/trunc"), "create truncate-test file");
autoflush OUT 1;
print OUT "12345678901234567890";
is( -s "$crypt/trunc", 20, "initial file size" );
ok( truncate(OUT, 10), "truncate" );
is( -s "$crypt/trunc", 10, "truncated file size");
is( qx(cat "$crypt/trunc"), "1234567890", "truncated file contents");
# try growing the file as well.
ok( truncate(OUT, 30), "truncate extend");
is( -s "$crypt/trunc", 30, "truncated file size");
seek(OUT, 30, 0);
print OUT "12345";
is( -s "$crypt/trunc", 35, "truncated file size");
seek(OUT, 0, 0);
is( Digest::MD5->new->addfile(*OUT)->hexdigest,
"5f170cc34b1944d75d86cc01496292df", "content digest");
# try crossing block boundaries
seek(OUT, 10000,0);
print OUT "abcde";
seek(OUT, 0, 0);
is( Digest::MD5->new->addfile(*OUT)->hexdigest,
"117a51c980b64dcd21df097d02206f98", "content digest");
# then truncate back to 35 chars
truncate(OUT, 35);
seek(OUT, 0, 0);
is( Digest::MD5->new->addfile(*OUT)->hexdigest,
"5f170cc34b1944d75d86cc01496292df", "content digest");
close OUT;
}
sub fileCreation
{
# create a file
qx(df -ah > "$crypt/df.txt");
ok( -f "$crypt/df.txt", "file created" );
# ensure there is an encrypted version.
my $c = qx(./encfsctl encode --extpass="echo test" $raw df.txt);
chomp($c);
cmp_ok( length($c), '>', 8, "encrypted name ok" );
ok( -f "$raw/$c", "encrypted file created" );
# check contents
my $count = qx(grep -c crypt-$$ "$crypt/df.txt");
isnt(scalar($count), 0, "encrypted file readable");
unlink "$crypt/df.txt";
ok( ! -f "$crypt/df.txt", "file removal" );
ok( ! -f "$raw/$c", "file removal" );
}
sub checkContents
{
my ($file, $expected, $testName) = @_;
open(IN, "< $file");
my $line = <IN>;
is( $line, $expected, $testName );
close IN;
}
sub links
{
my $hardlinkTests = shift;
my $contents = "hello world\n";
ok( open(OUT, "> $crypt/data"), "create file for link test" );
print OUT $contents;
close OUT;
# symlinks
ok( symlink("$crypt/data", "$crypt/data-fqn") , "fqn symlink");
checkContents("$crypt/data-fqn", $contents, "fqn link traversal");
is( readlink("$crypt/data-fqn"), "$crypt/data", "read fqn symlink");
ok( symlink("data", "$crypt/data-rel"), "local symlink");
checkContents("$crypt/data-rel", $contents, "rel link traversal");
is( readlink("$crypt/data-rel"), "data", "read rel symlink");
SKIP: {
skip "No hardlink support" unless $hardlinkTests;
ok( link("$crypt/data", "$crypt/data.2"), "hard link");
checkContents("$crypt/data.2", $contents, "hardlink read");
};
}
sub mount
{
my $args = shift;
ok( ! -d $raw, "no existing dir");
ok( ! -d $crypt, "no existing dir");
mkdir $raw;
ok( -d $raw, "created dir" );
mkdir $crypt;
ok( -d $crypt, "created dir" );
qx(./encfs --extpass="echo test" $args $raw $crypt);
ok( -f "$raw/.encfs6.xml", "created control file");
}
sub cleanup
{
my $fusermount = qx(which fusermount);
if(-f $fusermount)
{
qx($fusermount -u "$crypt");
} else
{
qx(umount "$crypt");
}
rmdir $crypt;
ok( ! -d $crypt, "unmount ok, mount point removed");
if(-d $raw)
{
rmtree($raw);
}
ok( ! -d $raw, "encrypted directory removed");
}

426
fs/BlockFileIO.cpp Normal file
View File

@ -0,0 +1,426 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fs/BlockFileIO.h"
#include "base/config.pb.h"
#include "base/Error.h"
#include "base/i18n.h"
#include "cipher/MemoryPool.h"
#include <cstring>
#include <glog/logging.h>
template<typename Type>
inline Type min( Type A, Type B )
{
return (B < A) ? B : A;
}
static void clearCache( IORequest &req, int blockSize )
{
memset( req.data, 0, blockSize );
req.dataLen = 0;
}
BlockFileIO::BlockFileIO( int blockSize, const FSConfigPtr &cfg )
: _blockSize( blockSize )
, _allowHoles( cfg->config->allow_holes() )
{
rAssert( _blockSize > 1 );
_cache.data = new unsigned char [ _blockSize ];
}
BlockFileIO::~BlockFileIO()
{
clearCache( _cache, _blockSize );
delete[] _cache.data;
}
ssize_t BlockFileIO::cacheReadOneBlock( const IORequest &req ) const
{
// we can satisfy the request even if _cache.dataLen is too short, because
// we always request a full block during reads..
if((req.offset == _cache.offset) && (_cache.dataLen != 0))
{
// satisfy request from cache
int len = req.dataLen;
if(_cache.dataLen < len)
len = _cache.dataLen;
memcpy( req.data, _cache.data, len );
return len;
} else
{
if(_cache.dataLen > 0)
clearCache( _cache, _blockSize );
// cache results of read -- issue reads for full blocks
IORequest tmp;
tmp.offset = req.offset;
tmp.data = _cache.data;
tmp.dataLen = _blockSize;
ssize_t result = readOneBlock( tmp );
if(result > 0)
{
_cache.offset = req.offset;
_cache.dataLen = result; // the amount we really have
if(result > req.dataLen)
result = req.dataLen; // only as much as requested
memcpy( req.data, _cache.data, result );
}
return result;
}
}
bool BlockFileIO::cacheWriteOneBlock( const IORequest &req )
{
// cache results of write (before pass-thru, because it may be modified
// in-place)
memcpy( _cache.data, req.data, req.dataLen );
_cache.offset = req.offset;
_cache.dataLen = req.dataLen;
bool ok = writeOneBlock( req );
if(!ok)
clearCache( _cache, _blockSize );
return ok;
}
ssize_t BlockFileIO::read( const IORequest &req ) const
{
rAssert( _blockSize != 0 );
int partialOffset = req.offset % _blockSize;
off_t blockNum = req.offset / _blockSize;
ssize_t result = 0;
if(partialOffset == 0 && req.dataLen <= _blockSize)
{
// read completely within a single block -- can be handled as-is by
// readOneBloc().
return cacheReadOneBlock( req );
} else
{
size_t size = req.dataLen;
// if the request is larger then a block, then request each block
// individually
MemBlock mb; // in case we need to allocate a temporary block..
IORequest blockReq; // for requests we may need to make
blockReq.dataLen = _blockSize;
blockReq.data = NULL;
unsigned char *out = req.data;
while( size )
{
blockReq.offset = blockNum * _blockSize;
// if we're reading a full block, then read directly into the
// result buffer instead of using a temporary
if(partialOffset == 0 && size >= (size_t)_blockSize)
blockReq.data = out;
else
{
if(!mb.data)
mb.allocate( _blockSize );
blockReq.data = mb.data;
}
ssize_t readSize = cacheReadOneBlock( blockReq );
if(readSize <= partialOffset)
break; // didn't get enough bytes
int cpySize = min( (size_t)(readSize - partialOffset), size );
rAssert(cpySize <= readSize);
// if we read to a temporary buffer, then move the data
if(blockReq.data != out)
memcpy( out, blockReq.data + partialOffset, cpySize );
result += cpySize;
size -= cpySize;
out += cpySize;
++blockNum;
partialOffset = 0;
if(readSize < _blockSize)
break;
}
return result;
}
}
bool BlockFileIO::write( const IORequest &req )
{
rAssert( _blockSize != 0 );
off_t fileSize = getSize();
// where write request begins
off_t blockNum = req.offset / _blockSize;
int partialOffset = req.offset % _blockSize;
// last block of file (for testing write overlaps with file boundary)
off_t lastFileBlock = fileSize / _blockSize;
ssize_t lastBlockSize = fileSize % _blockSize;
off_t lastNonEmptyBlock = lastFileBlock;
if(lastBlockSize == 0)
--lastNonEmptyBlock;
if( req.offset > fileSize )
{
// extend file first to fill hole with 0's..
const bool forceWrite = false;
padFile( fileSize, req.offset, forceWrite );
}
// check against edge cases where we can just let the base class handle the
// request as-is..
if(partialOffset == 0 && req.dataLen <= _blockSize)
{
// if writing a full block.. pretty safe..
if( req.dataLen == _blockSize )
return cacheWriteOneBlock( req );
// if writing a partial block, but at least as much as what is
// already there..
if(blockNum == lastFileBlock && req.dataLen >= lastBlockSize)
return cacheWriteOneBlock( req );
}
// have to merge data with existing block(s)..
MemBlock mb;
IORequest blockReq;
blockReq.data = NULL;
blockReq.dataLen = _blockSize;
bool ok = true;
size_t size = req.dataLen;
unsigned char *inPtr = req.data;
while( size )
{
blockReq.offset = blockNum * _blockSize;
int toCopy = min((size_t)(_blockSize - partialOffset), size);
// if writing an entire block, or writing a partial block that requires
// no merging with existing data..
if( (toCopy == _blockSize)
||(partialOffset == 0 && blockReq.offset + toCopy >= fileSize))
{
// write directly from buffer
blockReq.data = inPtr;
blockReq.dataLen = toCopy;
} else
{
// need a temporary buffer, since we have to either merge or pad
// the data.
if(!mb.data)
mb.allocate( _blockSize );
memset( mb.data, 0, _blockSize );
blockReq.data = mb.data;
if(blockNum > lastNonEmptyBlock)
{
// just pad..
blockReq.dataLen = toCopy + partialOffset;
} else
{
// have to merge with existing block data..
blockReq.dataLen = _blockSize;
blockReq.dataLen = cacheReadOneBlock( blockReq );
// extend data if necessary..
if( partialOffset + toCopy > blockReq.dataLen )
blockReq.dataLen = partialOffset + toCopy;
}
// merge in the data to be written..
memcpy( blockReq.data + partialOffset, inPtr, toCopy );
}
// Finally, write the damn thing!
if(!cacheWriteOneBlock( blockReq ))
{
ok = false;
break;
}
// prepare to start all over with the next block..
size -= toCopy;
inPtr += toCopy;
++blockNum;
partialOffset = 0;
}
return ok;
}
int BlockFileIO::blockSize() const
{
return _blockSize;
}
void BlockFileIO::padFile( off_t oldSize, off_t newSize, bool forceWrite )
{
off_t oldLastBlock = oldSize / _blockSize;
off_t newLastBlock = newSize / _blockSize;
int lastBlockSize = newSize % _blockSize;
IORequest req;
MemBlock mb;
if(oldLastBlock == newLastBlock)
{
// when the real write occurs, it will have to read in the existing
// data and pad it anyway, so we won't do it here (unless we're
// forced).
if( forceWrite )
{
mb.allocate( _blockSize );
req.data = mb.data;
req.offset = oldLastBlock * _blockSize;
req.dataLen = oldSize % _blockSize;
int outSize = newSize % _blockSize; // outSize > req.dataLen
if(outSize)
{
memset( mb.data, 0, outSize );
cacheReadOneBlock( req );
req.dataLen = outSize;
cacheWriteOneBlock( req );
}
} else
VLOG(1) << "optimization: not padding last block";
} else
{
mb.allocate( _blockSize );
req.data = mb.data;
// 1. extend the first block to full length
// 2. write the middle empty blocks
// 3. write the last block
req.offset = oldLastBlock * _blockSize;
req.dataLen = oldSize % _blockSize;
// 1. req.dataLen == 0, iff oldSize was already a multiple of blocksize
if(req.dataLen != 0)
{
VLOG(1) << "padding block " << oldLastBlock;
memset( mb.data, 0, _blockSize );
cacheReadOneBlock( req );
req.dataLen = _blockSize; // expand to full block size
cacheWriteOneBlock( req );
++oldLastBlock;
}
// 2, pad zero blocks unless holes are allowed
if(!_allowHoles)
{
for(; oldLastBlock != newLastBlock; ++oldLastBlock)
{
VLOG(1) << "padding block " << oldLastBlock;
req.offset = oldLastBlock * _blockSize;
req.dataLen = _blockSize;
memset( mb.data, 0, req.dataLen );
cacheWriteOneBlock( req );
}
}
// 3. only necessary if write is forced and block is non 0 length
if(forceWrite && lastBlockSize)
{
req.offset = newLastBlock * _blockSize;
req.dataLen = lastBlockSize;
memset( mb.data, 0, req.dataLen );
cacheWriteOneBlock( req );
}
}
}
int BlockFileIO::blockTruncate( off_t size, FileIO *base )
{
rAssert(size >= 0);
int partialBlock = size % _blockSize;
int res = 0;
off_t oldSize = getSize();
if( size > oldSize )
{
// truncate can be used to extend a file as well. truncate man page
// states that it will pad with 0's.
// do the truncate so that the underlying filesystem can allocate
// the space, and then we'll fill it in padFile..
if(base)
base->truncate( size );
const bool forceWrite = true;
padFile( oldSize, size, forceWrite );
} else
if( size == oldSize )
{
// the easiest case, but least likely....
} else
if( partialBlock )
{
// partial block after truncate. Need to read in the block being
// truncated before the truncate. Then write it back out afterwards,
// since the encoding will change..
off_t blockNum = size / _blockSize;
MemBlock mb;
mb.allocate( _blockSize );
IORequest req;
req.offset = blockNum * _blockSize;
req.dataLen = _blockSize;
req.data = mb.data;
ssize_t rdSz = cacheReadOneBlock( req );
// do the truncate
if(base)
res = base->truncate( size );
// write back out partial block
req.dataLen = partialBlock;
bool wrRes = cacheWriteOneBlock( req );
if((rdSz < 0) || (!wrRes))
{
LOG(ERROR) << "truncate failure: read size " << rdSz
<< ", partial block of " << partialBlock;
}
} else
{
// truncating on a block bounday. No need to re-encode the last
// block..
if(base)
res = base->truncate( size );
}
return res;
}

View File

@ -46,7 +46,7 @@ public:
protected:
int truncate( off_t size, FileIO *base );
int blockTruncate( off_t size, FileIO *base );
void padFile( off_t oldSize, off_t newSize, bool forceWrite );
// same as read(), except that the request.offset field is guarenteed to be

250
fs/BlockNameIO.cpp Normal file
View File

@ -0,0 +1,250 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004-2011, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fs/BlockNameIO.h"
#include "base/base64.h"
#include "base/Error.h"
#include "base/i18n.h"
#include "cipher/Cipher.h"
#include <cstring>
#include <glog/logging.h>
static shared_ptr<NameIO> NewBlockNameIO( const Interface &iface,
const shared_ptr<Cipher> &cipher, const CipherKey &key )
{
return shared_ptr<NameIO>(
new BlockNameIO( iface, cipher, key, false));
}
static shared_ptr<NameIO> NewBlockNameIO32( const Interface &iface,
const shared_ptr<Cipher> &cipher, const CipherKey &key )
{
return shared_ptr<NameIO>(
new BlockNameIO( iface, cipher, key, true));
}
static bool BlockIO_registered = NameIO::Register("Block",
// description of block name encoding algorithm..
// xgroup(setup)
gettext_noop("Block encoding, hides file name size somewhat"),
BlockNameIO::CurrentInterface(false),
NewBlockNameIO, false);
static bool BlockIO32_registered = NameIO::Register("Block32",
// description of block name encoding algorithm..
// xgroup(setup)
gettext_noop("Block encoding with base32 output for case-sensitive systems"),
BlockNameIO::CurrentInterface(true),
NewBlockNameIO32, false);
/*
- Version 1.0 computed MAC over the filename, but not the padding bytes.
This version was from pre-release 1.1, never publically released, so no
backward compatibility necessary.
- Version 2.0 includes padding bytes in MAC computation. This way the MAC
computation uses the same number of bytes regardless of the number of
padding bytes.
- Version 3.0 uses full 64 bit initialization vector during IV chaining.
Prior versions used only the output from the MAC_16 call, giving a 1 in
2^16 chance of the same name being produced. Using the full 64 bit IV
changes that to a 1 in 2^64 chance..
- Version 4.0 adds support for base32, creating names more suitable for
case-insensitive filesystems (eg Mac).
*/
Interface BlockNameIO::CurrentInterface(bool caseSensitive)
{
// implement major version 4 plus support for two prior versions
if (caseSensitive)
return makeInterface("nameio/block32", 4, 0, 2);
else
return makeInterface("nameio/block", 4, 0, 2);
}
BlockNameIO::BlockNameIO( const Interface &iface,
const shared_ptr<Cipher> &cipher,
const CipherKey &key, bool caseSensitiveEncoding )
: _interface( iface.major() )
, _bs( cipher->cipherBlockSize() )
, _cipher( cipher )
, _key( key )
, _caseSensitive( caseSensitiveEncoding )
{
rAssert( _bs < 128 );
}
BlockNameIO::~BlockNameIO()
{
}
Interface BlockNameIO::interface() const
{
return CurrentInterface(_caseSensitive);
}
int BlockNameIO::maxEncodedNameLen( int plaintextNameLen ) const
{
// number of blocks, rounded up.. Only an estimate at this point, err on
// the size of too much space rather then too little.
int numBlocks = ( plaintextNameLen + _bs ) / _bs;
int encodedNameLen = numBlocks * _bs + 2; // 2 checksum bytes
if (_caseSensitive)
return B256ToB32Bytes( encodedNameLen );
else
return B256ToB64Bytes( encodedNameLen );
}
int BlockNameIO::maxDecodedNameLen( int encodedNameLen ) const
{
int decLen256 = _caseSensitive ?
B32ToB256Bytes( encodedNameLen ) :
B64ToB256Bytes( encodedNameLen );
return decLen256 - 2; // 2 checksum bytes removed..
}
int BlockNameIO::encodeName( const char *plaintextName, int length,
uint64_t *iv, char *encodedName ) const
{
// copy the data into the encoding buffer..
memcpy( encodedName+2, plaintextName, length );
// Pad encryption buffer to block boundary..
int padding = _bs - length % _bs;
if(padding == 0)
padding = _bs; // padding a full extra block!
memset( encodedName+length+2, (unsigned char)padding, padding );
// store the IV before it is modified by the MAC call.
uint64_t tmpIV = 0;
if( iv && _interface >= 3 )
tmpIV = *iv;
// include padding in MAC computation
unsigned int mac = _cipher->MAC_16( (unsigned char *)encodedName+2,
length+padding, _key, iv );
// add checksum bytes
encodedName[0] = (mac >> 8) & 0xff;
encodedName[1] = (mac ) & 0xff;
_cipher->blockEncode( (unsigned char *)encodedName+2, length+padding,
(uint64_t)mac ^ tmpIV, _key);
// convert to base 64 ascii
int encodedStreamLen = length + 2 + padding;
int encLen;
if (_caseSensitive)
{
encLen = B256ToB32Bytes( encodedStreamLen );
changeBase2Inline( (unsigned char *)encodedName, encodedStreamLen,
8, 5, true );
B32ToAscii( (unsigned char *)encodedName, encLen );
} else
{
encLen = B256ToB64Bytes( encodedStreamLen );
changeBase2Inline( (unsigned char *)encodedName, encodedStreamLen,
8, 6, true );
B64ToAscii( (unsigned char *)encodedName, encLen );
}
return encLen;
}
int BlockNameIO::decodeName( const char *encodedName, int length,
uint64_t *iv, char *plaintextName ) const
{
int decLen256 = _caseSensitive ?
B32ToB256Bytes( length ) :
B64ToB256Bytes( length );
int decodedStreamLen = decLen256 - 2;
// don't bother trying to decode files which are too small
if(decodedStreamLen < _bs)
throw Error("Filename too small to decode");
BUFFER_INIT( tmpBuf, 32, (unsigned int)length );
// decode into tmpBuf,
if (_caseSensitive)
{
AsciiToB32((unsigned char *)tmpBuf, (unsigned char *)encodedName, length);
changeBase2Inline((unsigned char *)tmpBuf, length, 5, 8, false);
} else
{
AsciiToB64((unsigned char *)tmpBuf, (unsigned char *)encodedName, length);
changeBase2Inline((unsigned char *)tmpBuf, length, 6, 8, false);
}
// pull out the header information
unsigned int mac = ((unsigned int)((unsigned char)tmpBuf[0])) << 8
| ((unsigned int)((unsigned char)tmpBuf[1]));
uint64_t tmpIV = 0;
if( iv && _interface >= 3 )
tmpIV = *iv;
_cipher->blockDecode( (unsigned char *)tmpBuf+2, decodedStreamLen,
(uint64_t)mac ^ tmpIV, _key);
// find out true string length
int padding = (unsigned char)tmpBuf[2+decodedStreamLen-1];
int finalSize = decodedStreamLen - padding;
// might happen if there is an error decoding..
if(padding > _bs || finalSize < 0)
{
VLOG(1) << "padding, _bx, finalSize = " << padding
<< ", " << _bs << ", " << finalSize;
throw Error( "invalid padding size" );
}
// copy out the result..
memcpy(plaintextName, tmpBuf+2, finalSize);
plaintextName[finalSize] = '\0';
// check the mac
unsigned int mac2 = _cipher->MAC_16((const unsigned char *)tmpBuf+2,
decodedStreamLen, _key, iv);
BUFFER_RESET( tmpBuf );
if(mac2 != mac)
{
LOG(INFO) << "checksum mismatch: expected " << mac << ", got "
<< mac2 << " on decode of " << finalSize << " bytes";
throw Error( "checksum mismatch in filename decode" );
}
return finalSize;
}
bool BlockNameIO::Enabled()
{
return true;
}

View File

@ -21,8 +21,8 @@
#ifndef _BlockNameIO_incl_
#define _BlockNameIO_incl_
#include "NameIO.h"
#include "CipherKey.h"
#include "cipher/CipherKey.h"
#include "fs/NameIO.h"
#include <memory>
@ -40,7 +40,7 @@ public:
BlockNameIO( const Interface &iface,
const shared_ptr<Cipher> &cipher,
const CipherKey &key, int blockSize,
const CipherKey &key,
bool caseSensitiveEncoding = false );
virtual ~BlockNameIO();

56
fs/CMakeLists.txt Normal file
View File

@ -0,0 +1,56 @@
find_package (FUSE REQUIRED)
include_directories (${FUSE_INCLUDE_DIR})
enable_testing ()
find_package (GTest)
add_library (encfs-fs
encfs.cpp
Context.cpp
FileIO.cpp
RawFileIO.cpp
BlockFileIO.cpp
CipherFileIO.cpp
MACFileIO.cpp
NameIO.cpp
StreamNameIO.cpp
BlockNameIO.cpp
NullNameIO.cpp
DirNode.cpp
FileNode.cpp
FileUtils.cpp
${PROTO_SRCS}
${PROTO_HDRS}
)
target_link_libraries (encfs-fs
${PROTOBUF_LIBRARY}
)
# Unit tests are optional, depends on libgtest (Google's C++ test framework).
if (GTEST_FOUND)
link_directories (${Encfs_BINARY_DIR}/base)
link_directories (${Encfs_BINARY_DIR}/cipher)
include_directories (${GTEST_INCLUDE_DIR})
add_executable (unittests
MemBlockFileIO.cpp
MemFileIO.cpp
testing.cpp
test_IO.cpp
test_BlockIO.cpp
)
target_link_libraries (unittests
${GTEST_BOTH_LIBRARIES}
encfs-fs
encfs-cipher
encfs-base
${GLOG_LIBRARIES}
)
add_test (UnitTests unittests)
GTEST_ADD_TESTS (unittests "${UnitTestArgs}" test_IO.cpp test_BlockIO.cpp)
add_custom_target (test COMMAND ${CMAKE_CTEST_COMMAND} DEPENDS unittests)
endif (GTEST_FOUND)

513
fs/CipherFileIO.cpp Normal file
View File

@ -0,0 +1,513 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004-2013, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fs/CipherFileIO.h"
#include "base/config.pb.h"
#include "base/Error.h"
#include "cipher/Cipher.h"
#include "cipher/MemoryPool.h"
#include <glog/logging.h>
#include <fcntl.h>
#include <cerrno>
/*
Version 3:0 adds support for block-only encryption by adding space for
a full block to the file header.
Version 2:0 adds support for a per-file initialization vector with a
fixed 8 byte header. The headers are enabled globally within a
filesystem at the filesystem configuration level.
When headers are disabled, 2:0 is compatible with version 1:0.
*/
static Interface CipherFileIO_iface = makeInterface("FileIO/Cipher", 3, 0, 2);
CipherFileIO::CipherFileIO( const shared_ptr<FileIO> &_base,
const FSConfigPtr &cfg)
: BlockFileIO( cfg->config->block_size(), cfg )
, base( _base )
, headerLen( 0 )
, blockOnlyMode( cfg->config->block_mode_only() )
, perFileIV( cfg->config->unique_iv() )
, externalIV( 0 )
, fileIV( 0 )
, lastFlags( 0 )
{
fsConfig = cfg;
cipher = cfg->cipher;
key = cfg->key;
if ( blockOnlyMode )
{
headerLen += blockSize();
if ( perFileIV )
headerLen += cipher->cipherBlockSize();
} else
{
if ( perFileIV )
headerLen += sizeof(uint64_t); // 64bit IV per file
}
int blockBoundary = fsConfig->config->block_size() %
fsConfig->cipher->cipherBlockSize();
if(blockBoundary != 0)
{
LOG_FIRST_N(ERROR, 1)
<< "CipherFileIO: blocks should be multiple of cipher block size";
}
}
CipherFileIO::~CipherFileIO()
{
}
Interface CipherFileIO::interface() const
{
return CipherFileIO_iface;
}
int CipherFileIO::open( int flags )
{
int res = base->open( flags );
if( res >= 0 )
lastFlags = flags;
return res;
}
void CipherFileIO::setFileName( const char *fileName )
{
base->setFileName( fileName );
}
const char *CipherFileIO::getFileName() const
{
return base->getFileName();
}
bool CipherFileIO::setIV( uint64_t iv )
{
VLOG(1) << "in setIV, current IV = " << externalIV
<< ", new IV = " << iv << ", fileIV = " << fileIV;
if(externalIV == 0)
{
// we're just being told about which IV to use. since we haven't
// initialized the fileIV, there is no need to just yet..
externalIV = iv;
LOG_IF(WARNING, fileIV != 0)
<< "fileIV initialized before externalIV! (" << fileIV
<< ", " << externalIV << ")";
} else if(perFileIV)
{
// we have an old IV, and now a new IV, so we need to update the fileIV
// on disk.
if(fileIV == 0)
{
// ensure the file is open for read/write..
int newFlags = lastFlags | O_RDWR;
int res = base->open( newFlags );
if(res < 0)
{
if(res == -EISDIR)
{
// duh -- there are no file headers for directories!
externalIV = iv;
return base->setIV( iv );
} else
{
VLOG(1) << "writeHeader failed to re-open for write";
return false;
}
}
initHeader();
}
uint64_t oldIV = externalIV;
externalIV = iv;
if(!writeHeader())
{
externalIV = oldIV;
return false;
}
}
return base->setIV( iv );
}
off_t CipherFileIO::adjustedSize(off_t rawSize) const
{
off_t size = rawSize;
if (rawSize >= headerLen)
size -= headerLen;
return size;
}
int CipherFileIO::getAttr( struct stat *stbuf ) const
{
int res = base->getAttr( stbuf );
// adjust size if we have a file header
if((res == 0) && S_ISREG(stbuf->st_mode))
stbuf->st_size = adjustedSize(stbuf->st_size);
return res;
}
off_t CipherFileIO::getSize() const
{
// No check on S_ISREG here -- getSize only for normal files!
off_t size = base->getSize();
return adjustedSize(size);
}
void CipherFileIO::initHeader( )
{
int cbs = cipher->cipherBlockSize();
MemBlock mb;
mb.allocate(cbs);
// check if the file has a header, and read it if it does.. Otherwise,
// create one.
off_t rawSize = base->getSize();
if(rawSize >= headerLen)
{
VLOG(1) << "reading existing header, rawSize = " << rawSize;
IORequest req;
req.offset = 0;
if (blockOnlyMode)
req.offset += blockSize();
req.data = mb.data;
req.dataLen = blockOnlyMode ? cbs : sizeof(uint64_t);
base->read( req );
if (perFileIV)
{
if (blockOnlyMode)
cipher->blockDecode( mb.data, cbs, externalIV, key );
else
cipher->streamDecode( mb.data, sizeof(uint64_t), externalIV, key );
fileIV = 0;
for(unsigned int i=0; i<sizeof(uint64_t); ++i)
fileIV = (fileIV << 8) | (uint64_t)mb.data[i];
rAssert(fileIV != 0); // 0 is never used..
}
} else if (perFileIV)
{
VLOG(1) << "creating new file IV header";
do
{
if(!cipher->randomize( mb.data, 8, false ))
throw Error("Unable to generate a random file IV");
fileIV = 0;
for(unsigned int i=0; i<sizeof(uint64_t); ++i)
fileIV = (fileIV << 8) | (uint64_t)mb.data[i];
LOG_IF(WARNING, fileIV == 0)
<< "Unexpected result: randomize returned 8 null bytes!";
} while(fileIV == 0); // don't accept 0 as an option..
if (blockOnlyMode)
cipher->blockEncode( mb.data, cbs, externalIV, key );
else
cipher->streamEncode( mb.data, sizeof(uint64_t), externalIV, key );
if( base->isWritable() )
{
IORequest req;
req.offset = 0;
if (blockOnlyMode)
req.offset += blockSize();
req.data = mb.data;
req.dataLen = blockOnlyMode ? cbs : sizeof(uint64_t);
base->write( req );
} else
VLOG(1) << "base not writable, IV not written..";
}
VLOG(1) << "initHeader finished, fileIV = " << fileIV;
}
bool CipherFileIO::writeHeader( )
{
if( !base->isWritable() )
{
// open for write..
int newFlags = lastFlags | O_RDWR;
if( base->open( newFlags ) < 0 )
{
VLOG(1) << "writeHeader failed to re-open for write";
return false;
}
}
LOG_IF(ERROR, fileIV == 0)
<< "Internal error: fileIV == 0 in writeHeader!!!";
VLOG(1) << "writing fileIV " << fileIV;
MemBlock mb;
mb.allocate(headerLen);
if (perFileIV)
{
int cbs = cipher->cipherBlockSize();
unsigned char *buf = mb.data + (blockOnlyMode ? blockSize() : 0);
for(int i=sizeof(buf)-1; i>=0; --i)
{
buf[i] = (unsigned char)(fileIV & 0xff);
fileIV >>= 8;
}
if (blockOnlyMode)
cipher->blockEncode( buf, cbs, externalIV, key );
else
cipher->streamEncode( buf, sizeof(uint64_t), externalIV, key);
}
IORequest req;
req.offset = 0;
req.data = mb.data;
req.dataLen = headerLen;
base->write( req );
return true;
}
ssize_t CipherFileIO::readOneBlock( const IORequest &req ) const
{
// read raw data, then decipher it..
int bs = blockSize();
rAssert(req.dataLen <= bs);
off_t blockNum = req.offset / bs;
ssize_t readSize = 0;
IORequest tmpReq = req;
MemBlock mb;
if (headerLen != 0)
tmpReq.offset += headerLen;
int maxReadSize = req.dataLen;
if (blockOnlyMode)
{
off_t size = getSize();
if (req.offset + req.dataLen > size)
{
// Last block written as full block at front of the file header.
mb.allocate(bs);
tmpReq.offset = 0;
tmpReq.dataLen = bs;
tmpReq.data = mb.data;
// TODO: what is the expected behavior if req.offset >= size?
maxReadSize = size - req.offset;
if (maxReadSize <= 0)
return 0;
}
}
readSize = base->read( tmpReq );
bool ok;
if(readSize > 0)
{
if(headerLen != 0 && fileIV == 0)
const_cast<CipherFileIO*>(this)->initHeader();
if(blockOnlyMode || readSize == bs)
{
ok = blockRead( tmpReq.data, bs, blockNum ^ fileIV);
} else
{
ok = streamRead( tmpReq.data, (int)readSize, blockNum ^ fileIV);
}
if(!ok)
{
VLOG(1) << "decodeBlock failed for block " << blockNum
<< ", size " << readSize;
readSize = -1;
} else if (tmpReq.data != req.data)
{
if (readSize > maxReadSize)
readSize = maxReadSize;
memcpy(req.data, tmpReq.data, readSize);
}
} else
VLOG(1) << "readSize zero for offset " << req.offset;
return readSize;
}
bool CipherFileIO::writeOneBlock( const IORequest &req )
{
int bs = blockSize();
int cbs = cipher->cipherBlockSize();
off_t blockNum = req.offset / bs;
if(headerLen != 0 && fileIV == 0)
initHeader();
MemBlock mb;
bool ok;
if (req.dataLen == bs)
{
ok = blockWrite( req.data, bs, blockNum ^ fileIV );
} else if (blockOnlyMode)
{
mb.allocate(bs);
cipher->randomize(mb.data + bs - cbs, cbs, false);
memcpy(mb.data, req.data, req.dataLen);
ok = blockWrite( mb.data, bs, blockNum ^ fileIV );
} else
{
ok = streamWrite( req.data, (int)req.dataLen,
blockNum ^ fileIV );
}
if( ok )
{
if(headerLen != 0)
{
IORequest nreq = req;
if (mb.data == NULL)
{
nreq.offset += headerLen;
} else
{
// Partial block is stored at front of file.
nreq.offset = 0;
nreq.data = mb.data;
nreq.dataLen = bs;
base->truncate(req.offset + req.dataLen + headerLen);
}
ok = base->write( nreq );
} else
ok = base->write( req );
} else
{
VLOG(1) << "encodeBlock failed for block " << blockNum
<< ", size " << req.dataLen;
ok = false;
}
return ok;
}
bool CipherFileIO::blockWrite( unsigned char *buf, int size,
uint64_t _iv64 ) const
{
if (!fsConfig->reverseEncryption)
return cipher->blockEncode( buf, size, _iv64, key );
else
return cipher->blockDecode( buf, size, _iv64, key );
}
bool CipherFileIO::streamWrite( unsigned char *buf, int size,
uint64_t _iv64 ) const
{
if (!fsConfig->reverseEncryption)
return cipher->streamEncode( buf, size, _iv64, key );
else
return cipher->streamDecode( buf, size, _iv64, key );
}
bool CipherFileIO::blockRead( unsigned char *buf, int size,
uint64_t _iv64 ) const
{
if (fsConfig->reverseEncryption)
return cipher->blockEncode( buf, size, _iv64, key );
else if(_allowHoles)
{
// special case - leave all 0's alone
for(int i=0; i<size; ++i)
if(buf[i] != 0)
return cipher->blockDecode( buf, size, _iv64, key );
return true;
} else
return cipher->blockDecode( buf, size, _iv64, key );
}
bool CipherFileIO::streamRead( unsigned char *buf, int size,
uint64_t _iv64 ) const
{
if (fsConfig->reverseEncryption)
return cipher->streamEncode( buf, size, _iv64, key );
else
return cipher->streamDecode( buf, size, _iv64, key );
}
int CipherFileIO::truncate( off_t size )
{
rAssert(size >= 0);
if(headerLen == 0)
{
return blockTruncate( size, base.get() );
} else if(0 == fileIV)
{
// empty file.. create the header..
if( !base->isWritable() )
{
// open for write..
int newFlags = lastFlags | O_RDWR;
if( base->open( newFlags ) < 0 )
VLOG(1) << "writeHeader failed to re-open for write";
}
initHeader();
}
// can't let BlockFileIO call base->truncate(), since it would be using
// the wrong size..
int res = blockTruncate( size, 0 );
if(res == 0)
base->truncate( size + headerLen );
return res;
}
bool CipherFileIO::isWritable() const
{
return base->isWritable();
}

View File

@ -21,9 +21,9 @@
#ifndef _CipherFileIO_incl_
#define _CipherFileIO_incl_
#include "BlockFileIO.h"
#include "CipherKey.h"
#include "FileUtils.h"
#include "cipher/CipherKey.h"
#include "fs/BlockFileIO.h"
#include "fs/FileUtils.h"
#include <inttypes.h>
@ -52,6 +52,9 @@ public:
virtual int getAttr( struct stat *stbuf ) const;
virtual off_t getSize() const;
// NOTE: if truncate is used to extend the file, the extended plaintext is
// not 0. The extended ciphertext may be 0, resulting in non-zero
// plaintext.
virtual int truncate( off_t size );
virtual bool isWritable() const;
@ -71,13 +74,18 @@ private:
bool streamWrite( unsigned char *buf, int size,
uint64_t iv64 ) const;
off_t adjustedSize(off_t size) const;
shared_ptr<FileIO> base;
FSConfigPtr fsConfig;
// if haveHeader is true, then we have a transparent file header which
// contains a 64 bit initialization vector.
bool haveHeader;
int headerLen;
// Use block only encryption, no stream encryption.
bool blockOnlyMode;
bool perFileIV;
bool externalIVChaining;
uint64_t externalIV;
uint64_t fileIV;

175
fs/Context.cpp Normal file
View File

@ -0,0 +1,175 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2007, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "base/Mutex.h"
#include "base/Error.h"
#include "fs/FileNode.h"
#include "fs/Context.h"
#include "fs/FileUtils.h"
#include "fs/DirNode.h"
using namespace rel;
EncFS_Context::EncFS_Context()
{
pthread_cond_init( &wakeupCond, 0 );
pthread_mutex_init( &wakeupMutex, 0 );
pthread_mutex_init( &contextMutex, 0 );
usageCount = 0;
}
EncFS_Context::~EncFS_Context()
{
pthread_mutex_destroy( &contextMutex );
pthread_mutex_destroy( &wakeupMutex );
pthread_cond_destroy( &wakeupCond );
// release all entries from map
openFiles.clear();
}
shared_ptr<DirNode> EncFS_Context::getRoot(int *errCode)
{
shared_ptr<DirNode> ret;
do
{
{
Lock lock( contextMutex );
ret = root;
++usageCount;
}
if(!ret)
{
int res = remountFS( this );
if(res != 0)
{
*errCode = res;
break;
}
}
} while(!ret);
return ret;
}
void EncFS_Context::setRoot(const shared_ptr<DirNode> &r)
{
Lock lock( contextMutex );
root = r;
if(r)
rootCipherDir = r->rootDirectory();
}
bool EncFS_Context::isMounted()
{
return root;
}
int EncFS_Context::getAndResetUsageCounter()
{
Lock lock( contextMutex );
int count = usageCount;
usageCount = 0;
return count;
}
int EncFS_Context::openFileCount() const
{
Lock lock( contextMutex );
return openFiles.size();
}
shared_ptr<FileNode> EncFS_Context::lookupNode(const char *path)
{
Lock lock( contextMutex );
FileMap::iterator it = openFiles.find( std::string(path) );
if(it != openFiles.end())
{
// all the items in the set point to the same node.. so just use the
// first
return (*it->second.begin())->node;
} else
{
return shared_ptr<FileNode>();
}
}
void EncFS_Context::renameNode(const char *from, const char *to)
{
Lock lock( contextMutex );
FileMap::iterator it = openFiles.find( std::string(from) );
if(it != openFiles.end())
{
std::set<Placeholder *> val = it->second;
openFiles.erase(it);
openFiles[ std::string(to) ] = val;
}
}
shared_ptr<FileNode> EncFS_Context::getNode(void *pl)
{
Placeholder *ph = (Placeholder*)pl;
return ph->node;
}
void *EncFS_Context::putNode(const char *path,
const shared_ptr<FileNode> &node)
{
Lock lock( contextMutex );
Placeholder *pl = new Placeholder( node );
openFiles[ std::string(path) ].insert(pl);
return (void *)pl;
}
void EncFS_Context::eraseNode(const char *path, void *pl)
{
Lock lock( contextMutex );
Placeholder *ph = (Placeholder *)pl;
FileMap::iterator it = openFiles.find( std::string(path) );
rAssert(it != openFiles.end());
int rmCount = it->second.erase( ph );
rAssert(rmCount == 1);
// if no more references to this file, remove the record all together
if(it->second.empty())
{
// attempts to make use of shallow copy to clear memory used to hold
// unencrypted filenames.. not sure this does any good..
std::string storedName = it->first;
openFiles.erase( it );
storedName.assign( storedName.length(), '\0' );
}
delete ph;
}

View File

@ -21,11 +21,11 @@
#ifndef _Context_incl_
#define _Context_incl_
#include "encfs.h"
#include "shared_ptr.h"
#include "base/shared_ptr.h"
#include "fs/encfs.h"
#include <set>
#if HAVE_TR1_UNORDERED_MAP
#ifdef HAVE_TR1_UNORDERED_MAP
#include <tr1/unordered_map>
using std::tr1::unordered_map;
#else

816
fs/DirNode.cpp Normal file
View File

@ -0,0 +1,816 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2003-2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fs/encfs.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#ifdef linux
#include <sys/fsuid.h>
#endif
#include <cstring>
#include "cipher/Cipher.h"
#include "base/Error.h"
#include "base/Mutex.h"
#include "fs/Context.h"
#include "fs/DirNode.h"
#include "fs/FileUtils.h"
#include <glog/logging.h>
#include <iostream>
using namespace std;
using namespace rel;
class DirDeleter
{
public:
void operator () ( DIR *d )
{
::closedir( d );
}
};
DirTraverse::DirTraverse(const shared_ptr<DIR> &_dirPtr,
uint64_t _iv, const shared_ptr<NameIO> &_naming)
: dir( _dirPtr )
, iv( _iv )
, naming( _naming )
{
}
DirTraverse::DirTraverse(const DirTraverse &src)
: dir( src.dir )
, iv( src.iv )
, naming( src.naming )
{
}
DirTraverse &DirTraverse::operator = (const DirTraverse &src)
{
dir = src.dir;
iv = src.iv;
naming = src.naming;
return *this;
}
DirTraverse::~DirTraverse()
{
dir.reset();
iv = 0;
naming.reset();
}
static
bool _nextName(struct dirent *&de, const shared_ptr<DIR> &dir,
int *fileType, ino_t *inode)
{
de = ::readdir( dir.get() );
if(de)
{
if(fileType)
{
#if defined(_DIRENT_HAVE_D_TYPE) || defined(__FreeBSD__)
*fileType = de->d_type;
#else
#warning "struct dirent.d_type not supported"
*fileType = 0;
#endif
}
if(inode)
*inode = de->d_ino;
return true;
} else
{
if(fileType)
*fileType = 0;
return false;
}
}
std::string DirTraverse::nextPlaintextName(int *fileType, ino_t *inode)
{
struct dirent *de=0;
while(_nextName(de, dir, fileType, inode))
{
try
{
uint64_t localIv = iv;
return naming->decodePath( de->d_name, &localIv );
} catch ( Error &ex )
{
// .. .problem decoding, ignore it and continue on to next name..
VLOG(1) << "error decoding filename " << de->d_name
<< " : " << ex.what();
}
}
return string();
}
std::string DirTraverse::nextInvalid()
{
struct dirent *de=0;
// find the first name which produces a decoding error...
while(_nextName(de, dir, (int*)0, (ino_t*)0))
{
try
{
uint64_t localIv = iv;
naming->decodePath( de->d_name, &localIv );
continue;
} catch( Error &ex )
{
return string( de->d_name );
}
}
return string();
}
struct RenameEl
{
// ciphertext names
string oldCName;
string newCName; // intermediate name (not final cname)
// plaintext names
string oldPName;
string newPName;
bool isDirectory;
};
class RenameOp
{
private:
DirNode *dn;
shared_ptr< list<RenameEl> > renameList;
list<RenameEl>::const_iterator last;
public:
RenameOp( DirNode *_dn, const shared_ptr< list<RenameEl> > &_renameList )
: dn(_dn), renameList(_renameList)
{
last = renameList->begin();
}
RenameOp(const RenameOp &src)
: dn(src.dn)
, renameList(src.renameList)
, last(src.last)
{
}
~RenameOp();
operator bool () const
{
return renameList;
}
bool apply();
void undo();
};
RenameOp::~RenameOp()
{
if(renameList)
{
// got a bunch of decoded filenames sitting in memory.. do a little
// cleanup before leaving..
list<RenameEl>::iterator it;
for(it = renameList->begin(); it != renameList->end(); ++it)
{
it->oldPName.assign( it->oldPName.size(), ' ' );
it->newPName.assign( it->newPName.size(), ' ' );
}
}
}
bool RenameOp::apply()
{
try
{
while(last != renameList->end())
{
// backing store rename.
VLOG(2) << "renaming " << last->oldCName << "-> " << last->newCName;
struct stat st;
bool preserve_mtime = ::stat(last->oldCName.c_str(), &st) == 0;
// internal node rename..
dn->renameNode( last->oldPName.c_str(),
last->newPName.c_str() );
// rename on disk..
if(::rename( last->oldCName.c_str(),
last->newCName.c_str() ) == -1)
{
LOG(WARNING) << "Error renaming " << last->oldCName << ": " <<
strerror(errno);
dn->renameNode( last->newPName.c_str(),
last->oldPName.c_str(), false );
return false;
}
if(preserve_mtime)
{
struct utimbuf ut;
ut.actime = st.st_atime;
ut.modtime = st.st_mtime;
::utime(last->newCName.c_str(), &ut);
}
++last;
}
return true;
} catch( Error &err )
{
LOG(WARNING) << "caught error in rename application: " << err.what();
return false;
}
}
void RenameOp::undo()
{
VLOG(1) << "in undoRename";
if(last == renameList->begin())
{
VLOG(1) << "nothing to undo";
return; // nothing to undo
}
// list has to be processed backwards, otherwise we may rename
// directories and directory contents in the wrong order!
int undoCount = 0;
int errorCount = 0;
list<RenameEl>::const_iterator it = last;
while( it != renameList->begin() )
{
--it;
VLOG(1) << "undo: renaming " << it->newCName << " -> " << it->oldCName;
::rename( it->newCName.c_str(), it->oldCName.c_str() );
try
{
dn->renameNode( it->newPName.c_str(),
it->oldPName.c_str(), false );
} catch( Error &err )
{
if (++errorCount == 1)
LOG(WARNING) << "error in rename und: " << err.what();
// continue on anyway...
}
++undoCount;
};
LOG(WARNING) << "Undo rename count: " << undoCount;
}
DirNode::DirNode(EncFS_Context *_ctx,
const string &sourceDir,
const FSConfigPtr &_config)
{
pthread_mutex_init( &mutex, 0 );
Lock _lock( mutex );
ctx = _ctx;
rootDir = sourceDir;
fsConfig = _config;
// make sure rootDir ends in '/', so that we can form a path by appending
// the rest..
if( rootDir[ rootDir.length()-1 ] != '/' )
rootDir.append( 1, '/');
naming = fsConfig->nameCoding;
}
DirNode::~DirNode()
{
}
bool DirNode::hasDirectoryNameDependency() const
{
return naming ? naming->getChainedNameIV() : false;
}
string DirNode::rootDirectory()
{
// don't update last access here, otherwise 'du' would cause lastAccess to
// be reset.
// chop off '/' terminator from root dir.
return string( rootDir, 0, rootDir.length()-1 );
}
string DirNode::cipherPath( const char *plaintextPath )
{
return rootDir + naming->encodePath( plaintextPath );
}
string DirNode::cipherPathWithoutRoot( const char *plaintextPath )
{
return naming->encodePath( plaintextPath );
}
string DirNode::plainPath( const char *cipherPath_ )
{
try
{
if( !strncmp( cipherPath_, rootDir.c_str(),
rootDir.length() ) )
{
return naming->decodePath( cipherPath_ + rootDir.length() );
} else
{
if ( cipherPath_[0] == '+' )
{
// decode as fully qualified path
return string("/") + naming->decodeName( cipherPath_+1,
strlen(cipherPath_+1) );
} else
{
return naming->decodePath( cipherPath_ );
}
}
} catch( Error &err )
{
LOG(ERROR) << "decode err: " << err.what();
return string();
}
}
string DirNode::relativeCipherPath( const char *plaintextPath )
{
try
{
if(plaintextPath[0] == '/')
{
// mark with '+' to indicate special decoding..
return string("+") + naming->encodeName(plaintextPath+1,
strlen(plaintextPath+1));
} else
{
return naming->encodePath( plaintextPath );
}
} catch( Error &err )
{
LOG(ERROR) << "encode err: " << err.what();
return string();
}
}
DirTraverse DirNode::openDir(const char *plaintextPath)
{
string cyName = rootDir + naming->encodePath( plaintextPath );
//rDebug("openDir on %s", cyName.c_str() );
DIR *dir = ::opendir( cyName.c_str() );
if(dir == NULL)
{
VLOG(1) << "opendir error " << strerror(errno);
return DirTraverse( shared_ptr<DIR>(), 0, shared_ptr<NameIO>() );
} else
{
shared_ptr<DIR> dp( dir, DirDeleter() );
uint64_t iv = 0;
// if we're using chained IV mode, then compute the IV at this
// directory level..
try
{
if( naming->getChainedNameIV() )
naming->encodePath( plaintextPath, &iv );
} catch( Error &err )
{
LOG(ERROR) << "encode err: " << err.what();
}
return DirTraverse( dp, iv, naming );
}
}
bool DirNode::genRenameList( list<RenameEl> &renameList,
const char *fromP, const char *toP )
{
uint64_t fromIV = 0, toIV = 0;
// compute the IV for both paths
string fromCPart = naming->encodePath( fromP, &fromIV );
string toCPart = naming->encodePath( toP, &toIV );
// where the files live before the rename..
string sourcePath = rootDir + fromCPart;
// ok..... we wish it was so simple.. should almost never happen
if(fromIV == toIV)
return true;
// generate the real destination path, where we expect to find the files..
VLOG(1) << "opendir " << sourcePath;
shared_ptr<DIR> dir = shared_ptr<DIR>(
opendir( sourcePath.c_str() ), DirDeleter() );
if(!dir)
return false;
struct dirent *de = NULL;
while((de = ::readdir( dir.get() )) != NULL)
{
// decode the name using the oldIV
uint64_t localIV = fromIV;
string plainName;
if((de->d_name[0] == '.') &&
((de->d_name[1] == '\0')
|| ((de->d_name[1] == '.') && (de->d_name[2] == '\0'))))
{
// skip "." and ".."
continue;
}
try
{
plainName = naming->decodePath( de->d_name, &localIV );
} catch( Error &ex )
{
// if filename can't be decoded, then ignore it..
continue;
}
// any error in the following will trigger a rename failure.
try
{
// re-encode using the new IV..
localIV = toIV;
string newName = naming->encodePath( plainName.c_str(), &localIV );
// store rename information..
string oldFull = sourcePath + '/' + de->d_name;
string newFull = sourcePath + '/' + newName;
RenameEl ren;
ren.oldCName = oldFull;
ren.newCName = newFull;
ren.oldPName = string(fromP) + '/' + plainName;
ren.newPName = string(toP) + '/' + plainName;
bool isDir;
#if defined(_DIRENT_HAVE_D_TYPE)
if(de->d_type != DT_UNKNOWN)
{
isDir = (de->d_type == DT_DIR);
} else
#endif
{
isDir = isDirectory( oldFull.c_str() );
}
ren.isDirectory = isDir;
if(isDir)
{
// recurse.. We want to add subdirectory elements before the
// parent, as that is the logical rename order..
if(!genRenameList( renameList,
ren.oldPName.c_str(),
ren.newPName.c_str()))
{
return false;
}
}
VLOG(1) << "adding file " << oldFull << " to rename list";
renameList.push_back( ren );
} catch( Error &err )
{
// We can't convert this name, because we don't have a valid IV for
// it (or perhaps a valid key).. It will be inaccessible..
LOG(WARNING) << "Aborting rename: error on file " <<
fromCPart.append(1, '/').append(de->d_name) << ":" << err.what();
// abort.. Err on the side of safety and disallow rename, rather
// then loosing files..
return false;
}
}
return true;
}
/*
A bit of a pain.. If a directory is renamed in a filesystem with
directory initialization vector chaining, then we have to recursively
rename every descendent of this directory, as all initialization vectors
will have changed..
Returns a list of renamed items on success, a null list on failure.
*/
shared_ptr<RenameOp>
DirNode::newRenameOp( const char *fromP, const char *toP )
{
// Do the rename in two stages to avoid chasing our tail
// Undo everything if we encounter an error!
shared_ptr< list<RenameEl> > renameList(new list<RenameEl>);
if(!genRenameList( *renameList.get(), fromP, toP ))
{
LOG(WARNING) << "Error during generation of recursive rename list";
return shared_ptr<RenameOp>();
} else
return shared_ptr<RenameOp>( new RenameOp(this, renameList) );
}
int DirNode::mkdir(const char *plaintextPath, mode_t mode,
uid_t uid, gid_t gid)
{
string cyName = rootDir + naming->encodePath( plaintextPath );
rAssert( !cyName.empty() );
VLOG(1) << "mkdir on " << cyName;
// if uid or gid are set, then that should be the directory owner
int olduid = -1;
int oldgid = -1;
if(uid != 0)
olduid = setfsuid( uid );
if(gid != 0)
oldgid = setfsgid( gid );
int res = ::mkdir( cyName.c_str(), mode );
if(olduid >= 0)
setfsuid( olduid );
if(oldgid >= 0)
setfsgid( oldgid );
if(res == -1)
{
int eno = errno;
LOG(WARNING) << "mkdir error on " << cyName
<< " mode " << mode << ": " << strerror(eno);
res = -eno;
} else
res = 0;
return res;
}
int
DirNode::rename( const char *fromPlaintext, const char *toPlaintext )
{
Lock _lock( mutex );
string fromCName = rootDir + naming->encodePath( fromPlaintext );
string toCName = rootDir + naming->encodePath( toPlaintext );
rAssert( !fromCName.empty() );
rAssert( !toCName.empty() );
VLOG(1) << "rename " << fromCName << " -> " << toCName;
shared_ptr<FileNode> toNode = findOrCreate( toPlaintext );
shared_ptr<RenameOp> renameOp;
if( hasDirectoryNameDependency() && isDirectory( fromCName.c_str() ))
{
VLOG(1) << "recursive rename begin";
renameOp = newRenameOp( fromPlaintext, toPlaintext );
if(!renameOp || !renameOp->apply())
{
if(renameOp)
renameOp->undo();
LOG(WARNING) << "rename aborted";
return -EACCES;
}
VLOG(1) << "recursive rename end";
}
int res = 0;
try
{
struct stat st;
bool preserve_mtime = ::stat(fromCName.c_str(), &st) == 0;
renameNode( fromPlaintext, toPlaintext );
res = ::rename( fromCName.c_str(), toCName.c_str() );
if(res == -1)
{
// undo
res = -errno;
renameNode( toPlaintext, fromPlaintext, false );
if(renameOp)
renameOp->undo();
} else if(preserve_mtime)
{
struct utimbuf ut;
ut.actime = st.st_atime;
ut.modtime = st.st_mtime;
::utime(toCName.c_str(), &ut);
}
} catch( Error &err )
{
// exception from renameNode, just show the error and continue..
LOG(ERROR) << "rename err: " << err.what();
res = -EIO;
}
if(res != 0)
{
VLOG(1) << "rename failed: " << strerror( errno );
res = -errno;
}
return res;
}
int DirNode::link( const char *from, const char *to )
{
Lock _lock( mutex );
string fromCName = rootDir + naming->encodePath( from );
string toCName = rootDir + naming->encodePath( to );
rAssert( !fromCName.empty() );
rAssert( !toCName.empty() );
VLOG(1) << "link " << fromCName << " -> " << toCName;
int res = -EPERM;
if( fsConfig->config->external_iv() )
{
VLOG(1) << "hard links not supported with external IV chaining!";
} else
{
res = ::link( fromCName.c_str(), toCName.c_str() );
if(res == -1)
res = -errno;
else
res = 0;
}
return res;
}
/*
The node is keyed by filename, so a rename means the internal node names
must be changed.
*/
shared_ptr<FileNode> DirNode::renameNode( const char *from, const char *to )
{
return renameNode( from, to, true );
}
shared_ptr<FileNode> DirNode::renameNode( const char *from, const char *to,
bool forwardMode )
{
shared_ptr<FileNode> node = findOrCreate( from );
if(node)
{
uint64_t newIV = 0;
string cname = rootDir + naming->encodePath( to, &newIV );
VLOG(1) << "renaming internal node " << node->cipherName()
<< " -> " << cname.c_str();
if(node->setName( to, cname.c_str(), newIV, forwardMode ))
{
if(ctx)
ctx->renameNode( from, to );
} else
{
// rename error! - put it back
LOG(ERROR) << "renameNode failed";
throw Error("Internal node name change failed!");
}
}
return node;
}
shared_ptr<FileNode> DirNode::findOrCreate( const char *plainName)
{
shared_ptr<FileNode> node;
if(ctx)
node = ctx->lookupNode( plainName );
if(!node)
{
uint64_t iv = 0;
string cipherName = naming->encodePath( plainName, &iv );
node.reset( new FileNode( this, fsConfig,
plainName,
(rootDir + cipherName).c_str()));
if(fsConfig->config->external_iv())
node->setName(0, 0, iv);
VLOG(1) << "created FileNode for " << node->cipherName();
}
return node;
}
shared_ptr<FileNode>
DirNode::lookupNode( const char *plainName, const char * requestor )
{
(void)requestor;
Lock _lock( mutex );
shared_ptr<FileNode> node = findOrCreate( plainName );
return node;
}
/*
Similar to lookupNode, except that we also call open() and only return a
node on sucess.. This is done in one step to avoid any race conditions
with the stored state of the file.
*/
shared_ptr<FileNode>
DirNode::openNode( const char *plainName, const char * requestor, int flags,
int *result )
{
(void)requestor;
rAssert( result != NULL );
Lock _lock( mutex );
shared_ptr<FileNode> node = findOrCreate( plainName );
if( node && (*result = node->open( flags )) >= 0 )
return node;
else
return shared_ptr<FileNode>();
}
int DirNode::unlink( const char *plaintextName )
{
string cyName = naming->encodePath( plaintextName );
VLOG(1) << "unlink " << cyName;
Lock _lock( mutex );
int res = 0;
if(ctx && ctx->lookupNode( plaintextName ))
{
// If FUSE is running with "hard_remove" option where it doesn't
// hide open files for us, then we can't allow an unlink of an open
// file..
LOG(WARNING) << "Refusing to unlink open file: "
<< cyName << ", hard_remove option is probably in effect";
res = -EBUSY;
} else
{
string fullName = rootDir + cyName;
res = ::unlink( fullName.c_str() );
if(res == -1)
{
res = -errno;
VLOG(1) << "unlink error: " << strerror(errno);
}
}
return res;
}

View File

@ -30,11 +30,11 @@
#include <vector>
#include <string>
#include "FileNode.h"
#include "NameIO.h"
#include "CipherKey.h"
#include "FSConfig.h"
#include "shared_ptr.h"
#include "base/shared_ptr.h"
#include "cipher/CipherKey.h"
#include "fs/FileNode.h"
#include "fs/NameIO.h"
#include "fs/FSConfig.h"
class Cipher;
class RenameOp;

View File

@ -21,10 +21,10 @@
#ifndef _FSConfig_incl_
#define _FSConfig_incl_
#include "encfs.h"
#include "Interface.h"
#include "CipherKey.h"
#include "shared_ptr.h"
#include "base/Interface.h"
#include "base/shared_ptr.h"
#include "cipher/CipherKey.h"
#include "fs/encfs.h"
#include <vector>
@ -39,7 +39,7 @@ enum ConfigType
Config_V7 = 7
};
class EncFS_Opts;
struct EncFS_Opts;
class Cipher;
class NameIO;
class EncfsConfig;

View File

@ -21,12 +21,11 @@
#ifndef _FileIO_incl_
#define _FileIO_incl_
#include "encfs.h"
#include "base/Interface.h"
#include "fs/encfs.h"
#include <inttypes.h>
#include "Interface.h"
struct IORequest
{
off_t offset;

303
fs/FileNode.cpp Normal file
View File

@ -0,0 +1,303 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2003-2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Include encfs first, because we need to include fuse.h before any inclusion
// of sys/stat.h or other system headers (to be safe)
#include "fs/encfs.h"
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef linux
#include <sys/fsuid.h>
#endif
#include <cstring>
#include "base/config.h"
#include "base/Error.h"
#include "base/Mutex.h"
#include "cipher/Cipher.h"
#include "cipher/MemoryPool.h"
#include "fs/FileNode.h"
#include "fs/FileUtils.h"
#include "fs/CipherFileIO.h"
#include "fs/RawFileIO.h"
#include "fs/MACFileIO.h"
#include "fs/DirNode.h"
#include "fs/FileIO.h"
#include <glog/logging.h>
using namespace std;
using namespace rel;
/*
TODO: locking at the FileNode level is inefficient, since this precludes
multiple concurrent IO operations within the same file.
There is no reason why simultainous reads cannot be satisfied, or why one
read has to wait for the decoding of the previous read before it can be
sent to the IO subsystem!
*/
FileNode::FileNode(DirNode *parent_, const FSConfigPtr &cfg,
const char *plaintextName_, const char *cipherName_)
{
pthread_mutex_init( &mutex, 0 );
Lock _lock( mutex );
this->_pname = plaintextName_;
this->_cname = cipherName_;
this->parent = parent_;
this->fsConfig = cfg;
// chain RawFileIO & CipherFileIO
shared_ptr<FileIO> rawIO( new RawFileIO( _cname ) );
io = shared_ptr<FileIO>( new CipherFileIO( rawIO, fsConfig ));
if(cfg->config->block_mac_bytes() || cfg->config->block_mac_rand_bytes())
io = shared_ptr<FileIO>(new MACFileIO(io, fsConfig));
}
FileNode::~FileNode()
{
// FileNode mutex should be locked before the destructor is called
//pthread_mutex_lock( &mutex );
_pname.assign( _pname.length(), '\0' );
_cname.assign( _cname.length(), '\0' );
io.reset();
pthread_mutex_destroy( &mutex );
}
const char *FileNode::cipherName() const
{
return _cname.c_str();
}
const char *FileNode::plaintextName() const
{
return _pname.c_str();
}
string FileNode::plaintextParent() const
{
return parentDirectory( _pname );
}
static bool setIV(const shared_ptr<FileIO> &io, uint64_t iv)
{
struct stat stbuf;
if((io->getAttr(&stbuf) < 0) || S_ISREG(stbuf.st_mode))
return io->setIV( iv );
else
return true;
}
bool FileNode::setName( const char *plaintextName_, const char *cipherName_,
uint64_t iv, bool setIVFirst )
{
//Lock _lock( mutex );
VLOG(1) << "calling setIV on " << cipherName_;
if(setIVFirst)
{
if(fsConfig->config->external_iv() && !setIV(io, iv))
return false;
// now change the name..
if(plaintextName_)
this->_pname = plaintextName_;
if(cipherName_)
{
this->_cname = cipherName_;
io->setFileName( cipherName_ );
}
} else
{
std::string oldPName = _pname;
std::string oldCName = _cname;
if(plaintextName_)
this->_pname = plaintextName_;
if(cipherName_)
{
this->_cname = cipherName_;
io->setFileName( cipherName_ );
}
if(fsConfig->config->external_iv() && !setIV(io, iv))
{
_pname = oldPName;
_cname = oldCName;
return false;
}
}
return true;
}
int FileNode::mknod(mode_t mode, dev_t rdev, uid_t uid, gid_t gid)
{
Lock _lock( mutex );
int res;
int olduid = -1;
int oldgid = -1;
if(uid != 0)
{
olduid = setfsuid( uid );
if(olduid == -1)
{
LOG(INFO) << "setfsuid error: " << strerror(errno);
return -EPERM;
}
}
if(gid != 0)
{
oldgid = setfsgid( gid );
if(oldgid == -1)
{
LOG(INFO) << "setfsgid error: " << strerror(errno);
return -EPERM;
}
}
/*
* cf. xmp_mknod() in fusexmp.c
* The regular file stuff could be stripped off if there
* were a create method (advised to have)
*/
if (S_ISREG( mode )) {
res = ::open( _cname.c_str(), O_CREAT | O_EXCL | O_WRONLY, mode );
if (res >= 0)
res = ::close( res );
} else if (S_ISFIFO( mode ))
res = ::mkfifo( _cname.c_str(), mode );
else
res = ::mknod( _cname.c_str(), mode, rdev );
if(olduid >= 0)
setfsuid( olduid );
if(oldgid >= 0)
setfsgid( oldgid );
if(res == -1)
{
int eno = errno;
VLOG(1) << "mknod error: " << strerror(eno);
res = -eno;
}
return res;
}
int FileNode::open(int flags) const
{
Lock _lock( mutex );
int res = io->open( flags );
return res;
}
int FileNode::getAttr(struct stat *stbuf) const
{
Lock _lock( mutex );
int res = io->getAttr( stbuf );
return res;
}
off_t FileNode::getSize() const
{
Lock _lock( mutex );
int res = io->getSize();
return res;
}
ssize_t FileNode::read( off_t offset, unsigned char *data, ssize_t size ) const
{
IORequest req;
req.offset = offset;
req.dataLen = size;
req.data = data;
Lock _lock( mutex );
return io->read( req );
}
bool FileNode::write(off_t offset, unsigned char *data, ssize_t size)
{
VLOG(1) << "FileNode::write offset " << offset
<< ", data size " << size;
IORequest req;
req.offset = offset;
req.dataLen = size;
req.data = data;
Lock _lock( mutex );
return io->write( req );
}
int FileNode::truncate( off_t size )
{
Lock _lock( mutex );
return io->truncate( size );
}
int FileNode::sync(bool datasync)
{
Lock _lock( mutex );
int fh = io->open( O_RDONLY );
if(fh >= 0)
{
int res = -EIO;
#ifdef linux
if(datasync)
res = fdatasync( fh );
else
res = fsync( fh );
#else
// no fdatasync support
// TODO: use autoconfig to check for it..
res = fsync(fh);
#endif
if(res == -1)
res = -errno;
return res;
} else
return fh;
}

View File

@ -21,9 +21,9 @@
#ifndef _FileNode_incl_
#define _FileNode_incl_
#include "encfs.h"
#include "CipherKey.h"
#include "FileUtils.h"
#include "cipher/CipherKey.h"
#include "fs/encfs.h"
#include "fs/FileUtils.h"
#include <inttypes.h>
#include <sys/types.h>

View File

@ -24,28 +24,29 @@
#endif
#define _BSD_SOURCE // pick up setenv on RH7.3
#include "encfs.h"
#include "config.h"
#include "config.pb.h"
#include "fs/encfs.h"
#include "readpassphrase.h"
#include "autosprintf.h"
#include "base/autosprintf.h"
#include "base/config.h"
#include "base/config.pb.h"
#include "base/ConfigReader.h"
#include "base/Error.h"
#include "base/i18n.h"
#include "base/XmlReader.h"
#include "FileUtils.h"
#include "ConfigReader.h"
#include "XmlReader.h"
#include "FSConfig.h"
#include "cipher/Cipher.h"
#include "cipher/MemoryPool.h"
#include "cipher/readpassphrase.h"
#include "DirNode.h"
#include "Cipher.h"
#include "StreamNameIO.h"
#include "BlockNameIO.h"
#include "NullNameIO.h"
#include "Context.h"
#include "MemoryPool.h"
#include "fs/BlockNameIO.h"
#include "fs/Context.h"
#include "fs/DirNode.h"
#include "fs/FileUtils.h"
#include "fs/FSConfig.h"
#include "fs/NullNameIO.h"
#include "fs/StreamNameIO.h"
#include <rlog/rlog.h>
#include <rlog/Error.h>
#include <glog/logging.h>
#include <sys/types.h>
#include <sys/stat.h>
@ -62,23 +63,16 @@
#include <iostream>
#include <sstream>
#include "i18n.h"
#include <google/protobuf/text_format.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
// disable rlog section grouping for this file.. seems to cause problems
#undef RLOG_SECTION
#define RLOG_SECTION
using namespace rlog;
using namespace std;
using namespace gnu;
static const int DefaultBlockSize = 1024;
static const int DefaultBlockSize = 2048;
// The maximum length of text passwords. If longer are needed,
// use the extpass option, as extpass can return arbitrary length binary data.
static const int MaxPassBuf = 512;
static const int MaxPassBuf = 2048;
static const int NormalKDFDuration = 500; // 1/2 a second
static const int ParanoiaKDFDuration = 3000; // 3 seconds
@ -90,7 +84,7 @@ static const char ENCFS_ENV_STDOUT[] = "encfs_stdout";
static const char ENCFS_ENV_STDERR[] = "encfs_stderr";
const int V5Latest = 20040813; // fix MACFileIO block size issues
const int ProtoSubVersion = 20120819;
const int ProtoSubVersion = 20120902;
const char ConfigFileName[] = ".encfs.txt";
@ -223,12 +217,12 @@ ConfigType readConfig_load( ConfigInfo *nm, const char *path,
{
if( (*nm->loadFunc)( path, config, nm ))
return nm->type;
} catch( rlog::Error & err )
} catch( Error &err )
{
err.log( _RLWarningChannel );
LOG(WARNING) << "readConfig failed: " << err.what();
}
rError( _("Found config file %s, but failed to load"), path);
LOG(ERROR) << "Found config file " << path << ", but failed to load";
return Config_None;
} else
{
@ -269,7 +263,7 @@ bool readV6Config( const char *configFile,
XmlReader rdr;
if (!rdr.load(configFile))
{
rError("Failed to load config file %s", configFile);
LOG(ERROR) << "Failed to load config file " << configFile;
return false;
}
@ -279,39 +273,40 @@ bool readV6Config( const char *configFile,
config = (*serialization)["config"];
}
if (!config) {
rError("Unable to find XML configuration in file %s", configFile);
LOG(ERROR) << "Unable to find XML configuration in file " << configFile;
return false;
}
int version;
if (!config->read("version", &version) &&
!config->read("@version", &version)) {
rError("Unable to find version in config file");
LOG(ERROR) << "Unable to find version in config file";
return false;
}
// version numbering was complicated by boost::archive
if (version == 20 || version >= 20100713)
{
rInfo("found new serialization format");
VLOG(1) << "found new serialization format";
cfg.set_revision(version);
} else if (version == 26800)
{
rInfo("found 20080816 version");
VLOG(1) << "found 20080816 version";
cfg.set_revision(20080816);
} else if (version == 26797)
{
rInfo("found 20080813");
VLOG(1) << "found 20080813";
cfg.set_revision(20080813);
} else if (version < V5Latest)
{
rError("Invalid version %i - please fix config file", version);
LOG(ERROR) << "Invalid version " << version
<< " - please fix config file";
} else
{
rInfo("Boost <= 1.41 compatibility mode");
LOG(INFO) << "Boost <= 1.41 compatibility mode";
cfg.set_revision(version);
}
rInfo("subVersion = %i", cfg.revision());
VLOG(1) << "subVersion = " << cfg.revision();
config->read("creator", cfg.mutable_creator());
config->read("cipherAlg", cfg.mutable_cipher());
@ -390,15 +385,15 @@ bool readV5Config( const char *configFile,
{
/* config file specifies a version outside our supported
range.. */
rWarning(_("Config subversion %i found, but this version of"
" encfs only supports up to version %i."),
config.revision(), V5Latest);
LOG(ERROR) << "Config subversion " << config.revision()
<< " found, but this version of encfs only supports up to version "
<< V5Latest;
return false;
}
if( config.revision() < V5Latest )
{
rError(_("This version of EncFS doesn't support "
"filesystems created before 2004-08-13"));
LOG(ERROR) << "This version of EncFS doesn't support "
<< "filesystems created with EncFS releases before 2004-08-13";
return false;
}
@ -423,10 +418,10 @@ bool readV5Config( const char *configFile,
config.set_block_mac_rand_bytes( cfgRdr["blockMACRandBytes"].readInt(0) );
ok = true;
} catch( rlog::Error &err)
} catch( Error &err)
{
err.log( _RLWarningChannel );
rDebug("Error parsing data in config file %s", configFile);
LOG(WARNING) << "Error parsing data in config file " << configFile
<< "; " << err.what();
ok = false;
}
}
@ -458,10 +453,10 @@ bool readV4Config( const char *configFile,
config.set_creator( "EncFS 1.0.x" );
ok = true;
} catch( rlog::Error &err)
} catch( Error &err)
{
err.log( _RLWarningChannel );
rDebug("Error parsing config file %s", configFile);
LOG(WARNING) << "Error parsing config file " << configFile
<< ": " << err.what();
ok = false;
}
}
@ -474,7 +469,7 @@ bool writeTextConfig( const char *fileName, const EncfsConfig &cfg )
int fd = ::open( fileName, O_RDWR | O_CREAT, 0640 );
if (fd < 0)
{
rError("Unable to open or create file %s", fileName);
LOG(ERROR) << "Unable to open or create file " << fileName;
return false;
}
@ -503,10 +498,11 @@ bool saveConfig( const string &rootDir, const EncfsConfig &config )
try
{
const_cast<EncfsConfig &>(config).set_writer("EncFS " VERSION );
ok = writeTextConfig( path.c_str(), config );
} catch( rlog::Error &err )
} catch( Error &err )
{
err.log( _RLWarningChannel );
LOG(WARNING) << "saveConfig failed: " << err.what();
ok = false;
}
@ -519,7 +515,7 @@ bool readProtoConfig( const char *fileName, EncfsConfig &config,
int fd = ::open( fileName, O_RDONLY, 0640 );
if (fd < 0)
{
rError("Unable to open file %s", fileName);
LOG(ERROR) << "Unable to open file " << fileName;
return false;
}
@ -623,7 +619,7 @@ Cipher::CipherAlgorithm selectCipherAlgorithm()
}
static
Interface selectNameCoding()
Interface selectNameCoding(const Cipher::CipherAlgorithm &alg)
{
for(;;)
{
@ -634,10 +630,15 @@ Interface selectNameCoding()
NameIO::AlgorithmList algorithms = NameIO::GetAlgorithmList();
NameIO::AlgorithmList::const_iterator it;
int optNum = 1;
for(it = algorithms.begin(); it != algorithms.end(); ++it, ++optNum)
map<int, NameIO::AlgorithmList::const_iterator> algMap;
for(it = algorithms.begin(); it != algorithms.end(); ++it)
{
if (it->needsStreamMode && !alg.hasStreamMode)
continue;
cout << optNum << ". " << it->name
<< " : " << gettext(it->description.c_str()) << "\n";
algMap[optNum++] = it;
}
// xgroup(setup)
@ -647,15 +648,13 @@ Interface selectNameCoding()
int algNum = (res == 0 ? 0 : atoi( answer ));
cout << "\n";
if( algNum < 1 || algNum > (int)algorithms.size() )
if( algNum < 1 || algNum >= optNum )
{
cerr << _("Invalid selection.") << "\n";
continue;
}
it = algorithms.begin();
while(--algNum) // numbering starts at 1
++it;
it = algMap[algNum];
// xgroup(setup)
cout << autosprintf(_("Selected algorithm \"%s\""), it->name.c_str())
@ -940,7 +939,7 @@ RootPtr createConfig( EncFS_Context *ctx,
{
if (reverseEncryption)
{
rError(_("Paranoia configuration not supported for --reverse"));
LOG(ERROR) << "Paranoia configuration not supported for --reverse";
return rootInfo;
}
@ -953,7 +952,7 @@ RootPtr createConfig( EncFS_Context *ctx,
// Enable filename initialization vector chaning
keySize = 256;
blockSize = DefaultBlockSize;
alg = findCipherAlgorithm("AES", keySize);
alg = findCipherAlgorithm("AES_XTS", keySize);
nameIOIface = BlockNameIO::CurrentInterface();
blockMACBytes = 8;
blockMACRandBytes = 0; // using uniqueIV, so this isn't necessary
@ -1004,7 +1003,7 @@ RootPtr createConfig( EncFS_Context *ctx,
alg = selectCipherAlgorithm();
keySize = selectKeySize( alg );
blockSize = selectBlockSize( alg );
nameIOIface = selectNameCoding();
nameIOIface = selectNameCoding( alg );
if (reverseEncryption)
{
cout << _("--reverse specified, not using unique/chained IV") << "\n";
@ -1030,18 +1029,22 @@ RootPtr createConfig( EncFS_Context *ctx,
shared_ptr<Cipher> cipher = Cipher::New( alg.name, keySize );
if(!cipher)
{
rError(_("Unable to instanciate cipher %s, key size %i, block size %i"),
alg.name.c_str(), keySize, blockSize);
LOG(ERROR) << "Unable to instanciate cipher " << alg.name
<< ", key size " << keySize << ", block size " << blockSize;
return rootInfo;
} else
{
rDebug("Using cipher %s, key size %i, block size %i",
alg.name.c_str(), keySize, blockSize);
VLOG(1) << "Using cipher " << alg.name
<< ", key size " << keySize << ", block size " << blockSize;
}
EncfsConfig config;
config.mutable_cipher()->MergeFrom( cipher->interface() );
// TODO: allow user config
if (!cipher->hasStreamMode())
config.set_block_mode_only(true);
config.set_block_size( blockSize );
config.mutable_naming()->MergeFrom( nameIOIface );
config.set_creator( "EncFS " VERSION );
@ -1093,7 +1096,7 @@ RootPtr createConfig( EncFS_Context *ctx,
// get user key and use it to encode volume key
CipherKey userKey;
rDebug( "useStdin: %i", useStdin );
VLOG(1) << "useStdin: " << useStdin;
if(useStdin)
{
if (annotate)
@ -1109,8 +1112,8 @@ RootPtr createConfig( EncFS_Context *ctx,
if(!volumeKey)
{
rWarning(_("Failure generating new volume key! "
"Please report this error."));
LOG(ERROR) << "Failure generating new volume key! "
<< "Please report this error.";
return rootInfo;
}
@ -1122,7 +1125,7 @@ RootPtr createConfig( EncFS_Context *ctx,
cipher, volumeKey );
if(!nameCoder)
{
rWarning(_("Name coding interface not supported"));
LOG(WARNING) << "Name coding interface not supported";
cout << _("The filename encoding interface requested is not available")
<< endl;
return rootInfo;
@ -1175,12 +1178,17 @@ void showFSInfo( const EncfsConfig &config )
cout << "\n";
}
}
{
// xgroup(diag)
cout << autosprintf(_("Filename encoding: \"%s\", version %i:%i:%i"),
config.naming().name().c_str(), config.naming().major(),
config.naming().minor(), config.naming().age());
// xgroup(diag)
cout << autosprintf(_("Filename encoding: \"%s\", version %i:%i:%i"),
config.naming().name().c_str(), config.naming().major(),
config.naming().minor(), config.naming().age());
if (!cipher)
{
cout << "\n";
} else
{
// check if we support the filename encoding interface..
shared_ptr<NameIO> nameCoder = NameIO::New( config.naming(),
cipher, CipherKey() );
@ -1312,8 +1320,9 @@ CipherKey decryptKey(const EncfsConfig &config, const char *password, int passwd
iterations, key.kdf_duration(),
(const unsigned char *)key.salt().data(), key.salt().size());
if (iterations != key.kdf_iterations()) {
rError("Error in KDF, iteration mismatch");
if (iterations != key.kdf_iterations())
{
LOG(ERROR) << "Error in KDF, iteration mismatch";
return userKey;
}
} else
@ -1366,7 +1375,7 @@ SecureMem *passwordFromProgram(const std::string &passProg,
perror(_("Internal error: socketpair() failed"));
return NULL;
}
rDebug("getUserKey: fds = %i, %i", fds[0], fds[1]);
VLOG(1) << "getUserKey: fds = " << fds[0] << ", " << fds[1];
pid = fork();
if(pid == -1)
@ -1570,11 +1579,10 @@ RootPtr initFS( EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts )
shared_ptr<Cipher> cipher = getCipher(config);
if(!cipher)
{
rError(_("Unable to find cipher %s, version %i:%i:%i"),
config.cipher().name().c_str(),
config.cipher().major(),
config.cipher().minor(),
config.cipher().age());
Interface iface = config.cipher();
LOG(ERROR) << "Unable to find cipher " << iface.name()
<< ", version " << iface.major()
<< ":" << iface.minor() << ":" << iface.age();
// xgroup(diag)
cout << _("The requested cipher interface is not available\n");
return rootInfo;
@ -1594,7 +1602,7 @@ RootPtr initFS( EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts )
if(!userKey)
return rootInfo;
rDebug("cipher encoded key size = %i", cipher->encodedKeySize());
VLOG(1) << "cipher encoded key size = " << cipher->encodedKeySize();
// decode volume key..
CipherKey volumeKey = cipher->readKey(
(const unsigned char *)config.key().ciphertext().data(), userKey, opts->checkKey);
@ -1611,11 +1619,10 @@ RootPtr initFS( EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts )
cipher, volumeKey );
if(!nameCoder)
{
rError(_("Unable to find nameio interface %s, version %i:%i:%i"),
config.naming().name().c_str(),
config.naming().major(),
config.naming().minor(),
config.naming().age());
Interface iface = config.naming();
LOG(ERROR) << "Unable to find nameio interface " << iface.name()
<< ", version " << iface.major()
<< ":" << iface.minor() << ":" << iface.age();
// xgroup(diag)
cout << _("The requested filename coding interface is "
"not available\n");
@ -1653,7 +1660,7 @@ RootPtr initFS( EncFS_Context *ctx, const shared_ptr<EncFS_Opts> &opts )
int remountFS(EncFS_Context *ctx)
{
rDebug("Attempting to reinitialize filesystem");
VLOG(1) << "Attempting to reinitialize filesystem";
RootPtr rootInfo = initFS( ctx, ctx->opts );
if(rootInfo)
@ -1662,7 +1669,7 @@ int remountFS(EncFS_Context *ctx)
return 0;
} else
{
rInfo(_("Remount failed"));
LOG(WARNING) << "Remount failed";
return -EACCES;
}
}

View File

@ -21,10 +21,10 @@
#ifndef _FileUtils_incl_
#define _FileUtils_incl_
#include "encfs.h"
#include "Interface.h"
#include "CipherKey.h"
#include "FSConfig.h"
#include "base/Interface.h"
#include "cipher/CipherKey.h"
#include "fs/encfs.h"
#include "fs/FSConfig.h"
// true if the path points to an existing node (of any type)
bool fileExists( const char *fileName );

295
fs/MACFileIO.cpp Normal file
View File

@ -0,0 +1,295 @@
/*****************************************************************************
* Author: Valient Gough <vgough@pobox.com>
*
*****************************************************************************
* Copyright (c) 2004, Valient Gough
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fs/MACFileIO.h"
#include "base/config.pb.h"
#include "base/Error.h"
#include "base/i18n.h"
#include "cipher/MemoryPool.h"
#include "fs/FileUtils.h"
#include <glog/logging.h>
#include <cstring>
using namespace std;
//
// Version 1.0 worked on blocks of size (blockSize + headerSize).
// That is, it took [blockSize] worth of user data and added headers.
// Version 2.0 takes [blockSize - headerSize] worth of user data and writes
// [blockSize] bytes. That way the size going into the crypto engine is
// valid from what was selected based on the crypto module allowed ranges!
// Version 2.1 allows per-block rand bytes to be used without enabling MAC.
//
// The information about MACFileIO currently does not make its way into the
// configuration file, so there is no easy way to make this backward
// compatible, except at a high level by checking a revision number for the
// filesystem...
//
static Interface MACFileIO_iface = makeInterface("FileIO/MAC", 2, 1, 0);
int dataBlockSize(const FSConfigPtr &cfg)
{
return cfg->config->block_size()
- cfg->config->block_mac_bytes()
- cfg->config->block_mac_rand_bytes();
}
MACFileIO::MACFileIO( const shared_ptr<FileIO> &_base,
const FSConfigPtr &cfg )
: BlockFileIO( dataBlockSize( cfg ), cfg )
, base( _base )
, cipher( cfg->cipher )
, key( cfg->key )
, macBytes( cfg->config->block_mac_bytes() )
, randBytes( cfg->config->block_mac_rand_bytes() )
, warnOnly( cfg->opts->forceDecode )
{
rAssert( macBytes >= 0 && macBytes <= 8 );
rAssert( randBytes >= 0 );
VLOG(1) << "fs block size = " << cfg->config->block_size()
<< ", macBytes = " << cfg->config->block_mac_bytes()
<< ", randBytes = " << cfg->config->block_mac_rand_bytes();
}
MACFileIO::~MACFileIO()
{
}
Interface MACFileIO::interface() const
{
return MACFileIO_iface;
}
int MACFileIO::open( int flags )
{
return base->open( flags );
}
void MACFileIO::setFileName( const char *fileName )
{
base->setFileName( fileName );
}
const char *MACFileIO::getFileName() const
{
return base->getFileName();
}
bool MACFileIO::setIV( uint64_t iv )
{
return base->setIV( iv );
}
inline static off_t roundUpDivide( off_t numerator, int denominator )
{
// integer arithmetic always rounds down, so we can round up by adding
// enough so that any value other then a multiple of denominator gets
// rouned to the next highest value.
return ( numerator + denominator - 1 ) / denominator;
}
// Convert from a location in the raw file to a location when MAC headers are
// interleved with the data.
// So, if the filesystem stores/encrypts [blockSize] bytes per block, then
// [blockSize - headerSize] of those bytes will contain user-supplied data,
// and the rest ([headerSize]) will contain the MAC header for this block.
// Example, offset points to second block (of user-data)
// offset = blockSize - headerSize
// ... blockNum = 1
// ... partialBlock = 0
// ... adjLoc = 1 * blockSize
static off_t locWithHeader( off_t offset, int blockSize, int headerSize )
{
off_t blockNum = roundUpDivide( offset , blockSize - headerSize );
return offset + blockNum * headerSize;
}
// convert from a given location in the stream containing headers, and return a
// location in the user-data stream (which doesn't contain MAC headers)..
// The output value will always be less then the input value, because the
// headers are stored at the beginning of the block, so even the first data is
// offset by the size of the header.
static off_t locWithoutHeader( off_t offset, int blockSize, int headerSize )
{
off_t blockNum = roundUpDivide( offset , blockSize );
return offset - blockNum * headerSize;
}
int MACFileIO::getAttr( struct stat *stbuf ) const
{
int res = base->getAttr( stbuf );
if(res == 0 && S_ISREG(stbuf->st_mode))
{
// have to adjust size field..
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
stbuf->st_size = locWithoutHeader( stbuf->st_size, bs, headerSize );
}
return res;
}
off_t MACFileIO::getSize() const
{
// adjust the size to hide the header overhead we tack on..
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
off_t size = base->getSize();
if(size > 0)
size = locWithoutHeader( size, bs, headerSize );
return size;
}
ssize_t MACFileIO::readOneBlock( const IORequest &req ) const
{
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
MemBlock mb;
mb.allocate( bs );
IORequest tmp;
tmp.offset = locWithHeader( req.offset, bs, headerSize );
tmp.data = mb.data;
tmp.dataLen = headerSize + req.dataLen;
// get the data from the base FileIO layer
ssize_t readSize = base->read( tmp );
// don't store zeros if configured for zero-block pass-through
bool skipBlock = true;
if( _allowHoles )
{
for(int i=0; i<readSize; ++i)
if(tmp.data[i] != 0)
{
skipBlock = false;
break;
}
} else if(macBytes > 0)
skipBlock = false;
if(readSize > headerSize)
{
if(!skipBlock)
{
// At this point the data has been decoded. So, compute the MAC of
// the block and check against the checksum stored in the header..
uint64_t mac = cipher->MAC_64( tmp.data + macBytes,
readSize - macBytes, key );
for(int i=0; i<macBytes; ++i, mac >>= 8)
{
int test = mac & 0xff;
int stored = tmp.data[i];
if(test != stored)
{
// uh oh..
long blockNum = req.offset / bs;
LOG(WARNING) << "MAC comparison failure in block " << blockNum;
if( !warnOnly )
{
throw Error(
_("MAC comparison failure, refusing to read"));
}
break;
}
}
}
// now copy the data to the output buffer
readSize -= headerSize;
memcpy( req.data, tmp.data + headerSize, readSize );
} else
{
VLOG(1) << "readSize " << readSize << " at offset " << req.offset;
if(readSize > 0)
readSize = 0;
}
return readSize;
}
bool MACFileIO::writeOneBlock( const IORequest &req )
{
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
// we have the unencrypted data, so we need to attach a header to it.
MemBlock mb;
mb.allocate( bs );
IORequest newReq;
newReq.offset = locWithHeader( req.offset, bs, headerSize );
newReq.data = mb.data;
newReq.dataLen = headerSize + req.dataLen;
memset( newReq.data, 0, headerSize );
memcpy( newReq.data + headerSize, req.data, req.dataLen );
if(randBytes > 0)
{
if(!cipher->randomize( newReq.data+macBytes, randBytes, false ))
return false;
}
if(macBytes > 0)
{
// compute the mac (which includes the random data) and fill it in
uint64_t mac = cipher->MAC_64( newReq.data+macBytes,
req.dataLen + randBytes, key );
for(int i=0; i<macBytes; ++i)
{
newReq.data[i] = mac & 0xff;
mac >>= 8;
}
}
// now, we can let the next level have it..
bool ok = base->write( newReq );
return ok;
}
int MACFileIO::truncate( off_t size )
{
int headerSize = macBytes + randBytes;
int bs = blockSize() + headerSize;
int res = blockTruncate( size, 0 );
if(res == 0)
base->truncate( locWithHeader( size, bs, headerSize ) );
return res;
}
bool MACFileIO::isWritable() const
{
return base->isWritable();
}

Some files were not shown because too many files have changed in this diff Show More