mirror of
https://github.com/vgough/encfs.git
synced 2024-11-21 23:43:26 +01:00
1779 lines
54 KiB
C++
1779 lines
54 KiB
C++
/*****************************************************************************
|
|
* 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/>.
|
|
*/
|
|
|
|
// defines needed for RedHat 7.3...
|
|
#ifdef __linux__
|
|
#define _XOPEN_SOURCE 500 // make sure pwrite() is pulled in
|
|
#endif
|
|
#define _BSD_SOURCE // pick up setenv on RH7.3
|
|
#define _DEFAULT_SOURCE // Replaces _BSD_SOURCE
|
|
|
|
#include "easylogging++.h"
|
|
#include <cctype>
|
|
#include <cerrno>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <fcntl.h>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <list>
|
|
#ifdef __APPLE__
|
|
#include <sys/mount.h>
|
|
#include <sys/param.h>
|
|
#endif
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
#include <tinyxml2.h>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
#include "BlockNameIO.h"
|
|
#include "Cipher.h"
|
|
#include "CipherKey.h"
|
|
#include "ConfigReader.h"
|
|
#include "ConfigVar.h"
|
|
#include "Context.h"
|
|
#include "DirNode.h"
|
|
#include "Error.h"
|
|
#include "FSConfig.h"
|
|
#include "FileUtils.h"
|
|
#include "Interface.h"
|
|
#include "NameIO.h"
|
|
#include "Range.h"
|
|
#include "XmlReader.h"
|
|
#include "autosprintf.h"
|
|
#include "base64.h"
|
|
#include "config.h"
|
|
#include "i18n.h"
|
|
#include "intl/gettext.h"
|
|
#include "readpassphrase.h"
|
|
|
|
using namespace std;
|
|
using gnu::autosprintf;
|
|
|
|
namespace encfs {
|
|
|
|
static const int DefaultBlockSize = 1024;
|
|
// 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 NormalKDFDuration = 500; // 1/2 a second
|
|
static const int ParanoiaKDFDuration = 3000; // 3 seconds
|
|
|
|
// environment variable names for values encfs stores in the environment when
|
|
// calling an external password program.
|
|
static const char ENCFS_ENV_ROOTDIR[] = "encfs_root";
|
|
static const char ENCFS_ENV_STDOUT[] = "encfs_stdout";
|
|
static const char ENCFS_ENV_STDERR[] = "encfs_stderr";
|
|
|
|
// static int V5SubVersion = 20040518;
|
|
// static int V5SubVersion = 20040621; // add external IV chaining
|
|
static int V5SubVersion = 20040813; // fix MACFileIO block size issues
|
|
static int V5SubVersionDefault = 0;
|
|
|
|
// 20080813 was really made on 20080413 -- typo on date..
|
|
// const int V6SubVersion = 20080813; // switch to v6/XML, add allowHoles option
|
|
// const int V6SubVersion = 20080816; // add salt and iteration count
|
|
/*
|
|
* In boost 1.42+, serial numbers change to 8 bit, which means the date
|
|
* numbering scheme does not work any longer.
|
|
* boost-versioning.h implements a workaround that sets the version to
|
|
* 20 for boost 1.42+. */
|
|
const int V6SubVersion = 20100713; // add version field for boost 1.42+
|
|
|
|
struct ConfigInfo {
|
|
const char *fileName;
|
|
ConfigType type;
|
|
const char *environmentOverride;
|
|
bool (*loadFunc)(const char *fileName, EncFSConfig *config, ConfigInfo *cfg);
|
|
bool (*saveFunc)(const char *fileName, const EncFSConfig *config);
|
|
int currentSubVersion;
|
|
int defaultSubVersion;
|
|
} ConfigFileMapping[] = {
|
|
// current format
|
|
{".encfs6.xml", Config_V6, "ENCFS6_CONFIG", readV6Config, writeV6Config,
|
|
V6SubVersion, 0},
|
|
// backward compatible support for older versions
|
|
{".encfs5", Config_V5, "ENCFS5_CONFIG", readV5Config, writeV5Config,
|
|
V5SubVersion, V5SubVersionDefault},
|
|
{".encfs4", Config_V4, nullptr, readV4Config, writeV4Config, 0, 0},
|
|
// no longer support earlier versions
|
|
{".encfs3", Config_V3, nullptr, nullptr, nullptr, 0, 0},
|
|
{".encfs2", Config_Prehistoric, nullptr, nullptr, nullptr, 0, 0},
|
|
{".encfs", Config_Prehistoric, nullptr, nullptr, nullptr, 0, 0},
|
|
{nullptr, Config_None, nullptr, nullptr, nullptr, 0, 0}};
|
|
|
|
EncFS_Root::EncFS_Root() = default;
|
|
|
|
EncFS_Root::~EncFS_Root() = default;
|
|
|
|
bool fileExists(const char *fileName) {
|
|
struct stat buf;
|
|
return lstat(fileName, &buf) == 0;
|
|
}
|
|
|
|
bool isDirectory(const char *fileName) {
|
|
struct stat buf;
|
|
if (lstat(fileName, &buf) == 0) {
|
|
return S_ISDIR(buf.st_mode);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isAbsolutePath(const char *fileName) {
|
|
return (fileName != nullptr) && fileName[0] != '\0' && fileName[0] == '/';
|
|
}
|
|
|
|
const char *lastPathElement(const char *name) {
|
|
const char *loc = strrchr(name, '/');
|
|
return loc != nullptr ? loc + 1 : name;
|
|
}
|
|
|
|
std::string parentDirectory(const std::string &path) {
|
|
size_t last = path.find_last_of('/');
|
|
if (last == string::npos) {
|
|
return string("");
|
|
}
|
|
return path.substr(0, last);
|
|
}
|
|
|
|
bool userAllowMkdir(const char *path, mode_t mode) {
|
|
return userAllowMkdir(0, path, mode);
|
|
}
|
|
|
|
bool userAllowMkdir(int promptno, const char *path, mode_t mode) {
|
|
// TODO: can we internationalize the y/n names? Seems strange to prompt in
|
|
// their own language but then have to respond 'y' or 'n'.
|
|
// xgroup(setup)
|
|
cerr << autosprintf(
|
|
_("The directory \"%s\" does not exist. Should it be created? "
|
|
"(y,N) "),
|
|
path);
|
|
char answer[10];
|
|
char *res;
|
|
|
|
switch (promptno) {
|
|
case 1:
|
|
cerr << endl << "$PROMPT$ create_root_dir" << endl;
|
|
break;
|
|
case 2:
|
|
cerr << endl << "$PROMPT$ create_mount_point" << endl;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
res = fgets(answer, sizeof(answer), stdin);
|
|
|
|
if (res != nullptr && toupper(answer[0]) == 'Y') {
|
|
int result = mkdir(path, mode);
|
|
if (result < 0) {
|
|
perror(_("Unable to create directory: "));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
// Directory not created, by user request
|
|
cerr << _("Directory not created.") << "\n";
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Load config file by calling the load function on the filename
|
|
*/
|
|
ConfigType readConfig_load(ConfigInfo *nm, const char *path,
|
|
EncFSConfig *config) {
|
|
if (nm->loadFunc != nullptr) {
|
|
try {
|
|
if ((*nm->loadFunc)(path, config, nm)) {
|
|
config->cfgType = nm->type;
|
|
return nm->type;
|
|
}
|
|
} catch (encfs::Error &err) {
|
|
RLOG(ERROR) << "readConfig error: " << err.what();
|
|
}
|
|
|
|
RLOG(ERROR) << "Found config file " << path
|
|
<< ", but failed to load - exiting";
|
|
exit(1);
|
|
} else {
|
|
// No load function - must be an unsupported type..
|
|
config->cfgType = nm->type;
|
|
return nm->type;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Try to locate the config file
|
|
* Tries the most recent format first, then looks for older versions
|
|
*/
|
|
ConfigType readConfig(const string &rootDir, EncFSConfig *config, const string &cmdConfig) {
|
|
ConfigInfo *nm = ConfigFileMapping;
|
|
while (nm->fileName != nullptr) {
|
|
// allow command line argument to override default config path
|
|
if (!cmdConfig.empty()) {
|
|
if (!fileExists(cmdConfig.c_str())) {
|
|
RLOG(ERROR)
|
|
<< "fatal: config file specified on command line does not exist: "
|
|
<< cmdConfig;
|
|
exit(1);
|
|
}
|
|
return readConfig_load(nm, cmdConfig.c_str(), config);
|
|
} // allow environment variable to override default config path
|
|
if (nm->environmentOverride != nullptr) {
|
|
char *envFile = getenv(nm->environmentOverride);
|
|
if (envFile != nullptr) {
|
|
if (!fileExists(envFile)) {
|
|
RLOG(ERROR)
|
|
<< "fatal: config file specified by environment does not exist: "
|
|
<< envFile;
|
|
exit(1);
|
|
}
|
|
return readConfig_load(nm, envFile, config);
|
|
}
|
|
}
|
|
// the standard place to look is in the root directory
|
|
string path = rootDir + nm->fileName;
|
|
if (fileExists(path.c_str())) {
|
|
return readConfig_load(nm, path.c_str(), config);
|
|
}
|
|
|
|
++nm;
|
|
}
|
|
|
|
return Config_None;
|
|
}
|
|
|
|
/**
|
|
* Read config file in current "V6" XML format, normally named ".encfs6.xml"
|
|
* This format is in use since Apr 13, 2008 (commit 6d081f5c)
|
|
*/
|
|
// Read a boost::serialization config file using an Xml reader..
|
|
bool readV6Config(const char *configFile, EncFSConfig *cfg, ConfigInfo *info) {
|
|
(void)info;
|
|
|
|
XmlReader rdr;
|
|
if (!rdr.load(configFile)) {
|
|
RLOG(ERROR) << "Failed to load config file " << configFile;
|
|
return false;
|
|
}
|
|
|
|
XmlValuePtr serialization = rdr["boost_serialization"];
|
|
XmlValuePtr config = (*serialization)["cfg"];
|
|
if (!config) {
|
|
config = (*serialization)["config"];
|
|
}
|
|
if (!config) {
|
|
RLOG(ERROR) << "Unable to find XML configuration in file " << configFile;
|
|
return false;
|
|
}
|
|
|
|
int version;
|
|
if (!config->read("version", &version) &&
|
|
!config->read("@version", &version)) {
|
|
RLOG(ERROR) << "Unable to find version in config file";
|
|
return false;
|
|
}
|
|
|
|
// version numbering was complicated by boost::archive
|
|
if (version == 20 || version >= 20100713) {
|
|
VLOG(1) << "found new serialization format";
|
|
cfg->subVersion = version;
|
|
} else if (version == 26800) {
|
|
VLOG(1) << "found 20080816 version";
|
|
cfg->subVersion = 20080816;
|
|
} else if (version == 26797) {
|
|
VLOG(1) << "found 20080813";
|
|
cfg->subVersion = 20080813;
|
|
} else if (version < V5SubVersion) {
|
|
RLOG(ERROR) << "Invalid version " << version << " - please fix config file";
|
|
} else {
|
|
VLOG(1) << "Boost <= 1.41 compatibility mode";
|
|
cfg->subVersion = version;
|
|
}
|
|
VLOG(1) << "subVersion = " << cfg->subVersion;
|
|
|
|
config->read("creator", &cfg->creator);
|
|
config->read("cipherAlg", &cfg->cipherIface);
|
|
config->read("nameAlg", &cfg->nameIface);
|
|
|
|
config->read("keySize", &cfg->keySize);
|
|
|
|
config->read("blockSize", &cfg->blockSize);
|
|
config->read("plainData", &cfg->plainData);
|
|
config->read("uniqueIV", &cfg->uniqueIV);
|
|
config->read("chainedNameIV", &cfg->chainedNameIV);
|
|
config->read("externalIVChaining", &cfg->externalIVChaining);
|
|
config->read("blockMACBytes", &cfg->blockMACBytes);
|
|
config->read("blockMACRandBytes", &cfg->blockMACRandBytes);
|
|
config->read("allowHoles", &cfg->allowHoles);
|
|
|
|
int encodedSize;
|
|
config->read("encodedKeySize", &encodedSize);
|
|
auto *key = new unsigned char[encodedSize];
|
|
config->readB64("encodedKeyData", key, encodedSize);
|
|
cfg->assignKeyData(key, encodedSize);
|
|
delete[] key;
|
|
|
|
if (cfg->subVersion >= 20080816) {
|
|
int saltLen;
|
|
config->read("saltLen", &saltLen);
|
|
auto *salt = new unsigned char[saltLen];
|
|
config->readB64("saltData", salt, saltLen);
|
|
cfg->assignSaltData(salt, saltLen);
|
|
delete[] salt;
|
|
|
|
config->read("kdfIterations", &cfg->kdfIterations);
|
|
config->read("desiredKDFDuration", &cfg->desiredKDFDuration);
|
|
} else {
|
|
cfg->kdfIterations = 16;
|
|
cfg->desiredKDFDuration = NormalKDFDuration;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Read config file in deprecated "V5" format, normally named ".encfs5"
|
|
* This format has been used before Apr 13, 2008
|
|
*/
|
|
bool readV5Config(const char *configFile, EncFSConfig *config,
|
|
ConfigInfo *info) {
|
|
bool ok = false;
|
|
|
|
// use Config to parse the file and query it..
|
|
ConfigReader cfgRdr;
|
|
if (cfgRdr.load(configFile)) {
|
|
try {
|
|
config->subVersion =
|
|
cfgRdr["subVersion"].readInt(info->defaultSubVersion);
|
|
if (config->subVersion > info->currentSubVersion) {
|
|
/* config file specifies a version outside our supported
|
|
range.. */
|
|
RLOG(WARNING) << "Config subversion " << config->subVersion
|
|
<< " found, which is newer than supported version "
|
|
<< info->currentSubVersion;
|
|
return false;
|
|
}
|
|
if (config->subVersion < 20040813) {
|
|
RLOG(ERROR) << "This version of EncFS doesn't support "
|
|
"filesystems created before 2004-08-13";
|
|
return false;
|
|
}
|
|
|
|
cfgRdr["creator"] >> config->creator;
|
|
cfgRdr["cipher"] >> config->cipherIface;
|
|
cfgRdr["naming"] >> config->nameIface;
|
|
cfgRdr["keySize"] >> config->keySize;
|
|
cfgRdr["blockSize"] >> config->blockSize;
|
|
|
|
string data;
|
|
cfgRdr["keyData"] >> data;
|
|
config->assignKeyData(data);
|
|
config->uniqueIV = cfgRdr["uniqueIV"].readBool(false);
|
|
config->chainedNameIV = cfgRdr["chainedIV"].readBool(false);
|
|
config->externalIVChaining = cfgRdr["externalIV"].readBool(false);
|
|
config->blockMACBytes = cfgRdr["blockMACBytes"].readInt(0);
|
|
config->blockMACRandBytes = cfgRdr["blockMACRandBytes"].readInt(0);
|
|
|
|
ok = true;
|
|
} catch (encfs::Error &err) {
|
|
RLOG(WARNING) << err.what();
|
|
VLOG(1) << "Error parsing data in config file " << configFile;
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
/**
|
|
* Read config file in deprecated "V4" format, normally named ".encfs4"
|
|
* This format has been used before Jan 7, 2008
|
|
*/
|
|
bool readV4Config(const char *configFile, EncFSConfig *config,
|
|
ConfigInfo *info) {
|
|
bool ok = false;
|
|
|
|
// use Config to parse the file and query it..
|
|
ConfigReader cfgRdr;
|
|
if (cfgRdr.load(configFile)) {
|
|
try {
|
|
cfgRdr["cipher"] >> config->cipherIface;
|
|
cfgRdr["keySize"] >> config->keySize;
|
|
cfgRdr["blockSize"] >> config->blockSize;
|
|
string data;
|
|
cfgRdr["keyData"] >> data;
|
|
config->assignKeyData(data);
|
|
|
|
// fill in default for V4
|
|
config->nameIface = Interface("nameio/stream", 1, 0, 0);
|
|
config->creator = "EncFS 1.0.x";
|
|
config->subVersion = info->defaultSubVersion;
|
|
config->blockMACBytes = 0;
|
|
config->blockMACRandBytes = 0;
|
|
config->uniqueIV = false;
|
|
config->externalIVChaining = false;
|
|
config->chainedNameIV = false;
|
|
|
|
ok = true;
|
|
} catch (encfs::Error &err) {
|
|
RLOG(WARNING) << err.what();
|
|
VLOG(1) << "Error parsing config file " << configFile;
|
|
ok = false;
|
|
}
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool saveConfig(ConfigType type, const string &rootDir,
|
|
const EncFSConfig *config, const string &cmdConfig) {
|
|
bool ok = false;
|
|
|
|
ConfigInfo *nm = ConfigFileMapping;
|
|
while (nm->fileName != nullptr) {
|
|
if (nm->type == type && (nm->saveFunc != nullptr)) {
|
|
string path = rootDir + nm->fileName;
|
|
if (!cmdConfig.empty()) {
|
|
// use command line argument if specified
|
|
path.assign(cmdConfig);
|
|
}
|
|
else if (nm->environmentOverride != nullptr) {
|
|
// use environment file if specified..
|
|
const char *envFile = getenv(nm->environmentOverride);
|
|
if (envFile != nullptr) {
|
|
path.assign(envFile);
|
|
}
|
|
}
|
|
|
|
try {
|
|
ok = (*nm->saveFunc)(path.c_str(), config);
|
|
} catch (encfs::Error &err) {
|
|
RLOG(WARNING) << err.what();
|
|
ok = false;
|
|
}
|
|
break;
|
|
}
|
|
++nm;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
template <typename T>
|
|
tinyxml2::XMLElement *addEl(tinyxml2::XMLDocument &doc,
|
|
tinyxml2::XMLNode *parent, const char *name,
|
|
const T &value) {
|
|
auto el = doc.NewElement(name);
|
|
el->SetText(value);
|
|
parent->InsertEndChild(el);
|
|
return el;
|
|
}
|
|
|
|
template <>
|
|
tinyxml2::XMLElement *addEl<>(tinyxml2::XMLDocument &doc,
|
|
tinyxml2::XMLNode *parent, const char *name,
|
|
const Interface &iface) {
|
|
auto el = doc.NewElement(name);
|
|
|
|
auto n = doc.NewElement("name");
|
|
n->SetText(iface.name().c_str());
|
|
el->InsertEndChild(n);
|
|
|
|
auto major = doc.NewElement("major");
|
|
major->SetText(iface.current());
|
|
el->InsertEndChild(major);
|
|
|
|
auto minor = doc.NewElement("minor");
|
|
minor->SetText(iface.revision());
|
|
el->InsertEndChild(minor);
|
|
|
|
parent->InsertEndChild(el);
|
|
return el;
|
|
}
|
|
|
|
template <>
|
|
tinyxml2::XMLElement *addEl<>(tinyxml2::XMLDocument &doc,
|
|
tinyxml2::XMLNode *parent, const char *name,
|
|
const std::vector<unsigned char> &data) {
|
|
string v = string("\n") + B64StandardEncode(data) + "\n";
|
|
return addEl(doc, parent, name, v.c_str());
|
|
}
|
|
|
|
bool writeV6Config(const char *configFile, const EncFSConfig *cfg) {
|
|
tinyxml2::XMLDocument doc;
|
|
|
|
// Various static tags are included to make the output compatible with
|
|
// older boost-based readers.
|
|
doc.InsertEndChild(doc.NewDeclaration(nullptr));
|
|
doc.InsertEndChild(doc.NewUnknown("DOCTYPE boost_serialization"));
|
|
|
|
auto header = doc.NewElement("boost_serialization");
|
|
header->SetAttribute("signature", "serialization::archive");
|
|
header->SetAttribute("version", "7");
|
|
doc.InsertEndChild(header);
|
|
|
|
auto config = doc.NewElement("cfg");
|
|
config->SetAttribute("class_id", "0");
|
|
config->SetAttribute("tracking_level", "0");
|
|
config->SetAttribute("version", "20");
|
|
header->InsertEndChild(config);
|
|
|
|
addEl(doc, config, "version", V6SubVersion);
|
|
addEl(doc, config, "creator", cfg->creator.c_str());
|
|
auto cipherAlg = addEl(doc, config, "cipherAlg", cfg->cipherIface);
|
|
cipherAlg->SetAttribute("class_id", "1");
|
|
cipherAlg->SetAttribute("tracking_level", "0");
|
|
cipherAlg->SetAttribute("version", "0");
|
|
addEl(doc, config, "nameAlg", cfg->nameIface);
|
|
addEl(doc, config, "keySize", cfg->keySize);
|
|
addEl(doc, config, "blockSize", cfg->blockSize);
|
|
addEl(doc, config, "plainData", (int)cfg->plainData);
|
|
addEl(doc, config, "uniqueIV", (int)cfg->uniqueIV);
|
|
addEl(doc, config, "chainedNameIV", (int)cfg->chainedNameIV);
|
|
addEl(doc, config, "externalIVChaining", (int)cfg->externalIVChaining);
|
|
addEl(doc, config, "blockMACBytes", cfg->blockMACBytes);
|
|
addEl(doc, config, "blockMACRandBytes", cfg->blockMACRandBytes);
|
|
addEl(doc, config, "allowHoles", (int)cfg->allowHoles);
|
|
addEl(doc, config, "encodedKeySize", (int)cfg->keyData.size());
|
|
addEl(doc, config, "encodedKeyData", cfg->keyData);
|
|
addEl(doc, config, "saltLen", (int)cfg->salt.size());
|
|
addEl(doc, config, "saltData", cfg->salt);
|
|
addEl(doc, config, "kdfIterations", cfg->kdfIterations);
|
|
addEl(doc, config, "desiredKDFDuration", (int)cfg->desiredKDFDuration);
|
|
|
|
auto err = doc.SaveFile(configFile, false);
|
|
return err == tinyxml2::XML_SUCCESS;
|
|
}
|
|
|
|
bool writeV5Config(const char *configFile, const EncFSConfig *config) {
|
|
ConfigReader cfg;
|
|
|
|
cfg["creator"] << config->creator;
|
|
cfg["subVersion"] << config->subVersion;
|
|
cfg["cipher"] << config->cipherIface;
|
|
cfg["naming"] << config->nameIface;
|
|
cfg["keySize"] << config->keySize;
|
|
cfg["blockSize"] << config->blockSize;
|
|
string key;
|
|
key.assign((char *)config->getKeyData(), config->keyData.size());
|
|
cfg["keyData"] << key;
|
|
cfg["blockMACBytes"] << config->blockMACBytes;
|
|
cfg["blockMACRandBytes"] << config->blockMACRandBytes;
|
|
cfg["uniqueIV"] << config->uniqueIV;
|
|
cfg["chainedIV"] << config->chainedNameIV;
|
|
cfg["externalIV"] << config->externalIVChaining;
|
|
|
|
return cfg.save(configFile);
|
|
}
|
|
|
|
bool writeV4Config(const char *configFile, const EncFSConfig *config) {
|
|
ConfigReader cfg;
|
|
|
|
cfg["cipher"] << config->cipherIface;
|
|
cfg["keySize"] << config->keySize;
|
|
cfg["blockSize"] << config->blockSize;
|
|
string key;
|
|
key.assign((char *)config->getKeyData(), config->keyData.size());
|
|
cfg["keyData"] << key;
|
|
|
|
return cfg.save(configFile);
|
|
}
|
|
|
|
static Cipher::CipherAlgorithm findCipherAlgorithm(const char *name,
|
|
int keySize) {
|
|
Cipher::AlgorithmList algorithms = Cipher::GetAlgorithmList();
|
|
Cipher::AlgorithmList::const_iterator it;
|
|
for (it = algorithms.begin(); it != algorithms.end(); ++it) {
|
|
if ((strcmp(name, it->name.c_str()) == 0) &&
|
|
it->keyLength.allowed(keySize)) {
|
|
return *it;
|
|
}
|
|
}
|
|
|
|
Cipher::CipherAlgorithm result;
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Ask the user which cipher to use
|
|
*/
|
|
static Cipher::CipherAlgorithm selectCipherAlgorithm() {
|
|
for (;;) {
|
|
// figure out what cipher they want to use..
|
|
// xgroup(setup)
|
|
cout << _("The following cipher algorithms are available:") << "\n";
|
|
Cipher::AlgorithmList algorithms = Cipher::GetAlgorithmList();
|
|
Cipher::AlgorithmList::const_iterator it;
|
|
int optNum = 1;
|
|
for (it = algorithms.begin(); it != algorithms.end(); ++it, ++optNum) {
|
|
cout << optNum << ". " << it->name << " : "
|
|
<< gettext(it->description.c_str()) << "\n";
|
|
if (it->keyLength.min() == it->keyLength.max()) {
|
|
// shown after algorithm name and description.
|
|
// xgroup(setup)
|
|
cout << autosprintf(_(" -- key length %i bits"), it->keyLength.min())
|
|
<< "\n";
|
|
} else {
|
|
cout << autosprintf(
|
|
// shown after algorithm name and description.
|
|
// xgroup(setup)
|
|
_(" -- Supports key lengths of %i to %i bits"),
|
|
it->keyLength.min(), it->keyLength.max())
|
|
<< "\n";
|
|
}
|
|
|
|
if (it->blockSize.min() == it->blockSize.max()) {
|
|
cout << autosprintf(
|
|
// shown after algorithm name and description.
|
|
// xgroup(setup)
|
|
_(" -- block size %i bytes"), it->blockSize.min())
|
|
<< "\n";
|
|
} else {
|
|
cout << autosprintf(
|
|
// shown after algorithm name and description.
|
|
// xgroup(setup)
|
|
_(" -- Supports block sizes of %i to %i bytes"),
|
|
it->blockSize.min(), it->blockSize.max())
|
|
<< "\n";
|
|
}
|
|
}
|
|
|
|
// xgroup(setup)
|
|
cout << "\n" << _("Enter the number corresponding to your choice: ");
|
|
char answer[10];
|
|
char *res = fgets(answer, sizeof(answer), stdin);
|
|
int cipherNum = (res == nullptr ? 0 : (int)strtol(answer, nullptr, 10));
|
|
cout << "\n";
|
|
|
|
if (cipherNum < 1 || cipherNum > (int)algorithms.size()) {
|
|
cerr << _("Invalid selection.") << "\n";
|
|
continue;
|
|
}
|
|
|
|
it = algorithms.begin();
|
|
while (--cipherNum != 0) { // numbering starts at 1
|
|
++it;
|
|
}
|
|
|
|
Cipher::CipherAlgorithm alg = *it;
|
|
|
|
// xgroup(setup)
|
|
cout << autosprintf(_("Selected algorithm \"%s\""), alg.name.c_str())
|
|
<< "\n\n";
|
|
|
|
return alg;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ask the user which encoding to use for file names
|
|
*/
|
|
static Interface selectNameCoding() {
|
|
for (;;) {
|
|
// figure out what cipher they want to use..
|
|
// xgroup(setup)
|
|
cout << _("The following filename encoding algorithms are available:")
|
|
<< "\n";
|
|
NameIO::AlgorithmList algorithms = NameIO::GetAlgorithmList();
|
|
NameIO::AlgorithmList::const_iterator it;
|
|
int optNum = 1;
|
|
for (it = algorithms.begin(); it != algorithms.end(); ++it, ++optNum) {
|
|
cout << optNum << ". " << it->name << " : "
|
|
<< gettext(it->description.c_str()) << "\n";
|
|
}
|
|
|
|
// xgroup(setup)
|
|
cout << "\n" << _("Enter the number corresponding to your choice: ");
|
|
char answer[10];
|
|
char *res = fgets(answer, sizeof(answer), stdin);
|
|
int algNum = (res == nullptr ? 0 : (int)strtol(answer, nullptr, 10));
|
|
cout << "\n";
|
|
|
|
if (algNum < 1 || algNum > (int)algorithms.size()) {
|
|
cerr << _("Invalid selection.") << "\n";
|
|
continue;
|
|
}
|
|
|
|
it = algorithms.begin();
|
|
while (--algNum != 0) { // numbering starts at 1
|
|
++it;
|
|
}
|
|
|
|
// xgroup(setup)
|
|
cout << autosprintf(_("Selected algorithm \"%s\""), it->name.c_str())
|
|
<< "\"\n\n";
|
|
|
|
return it->iface;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ask the user which key size to use
|
|
*/
|
|
static int selectKeySize(const Cipher::CipherAlgorithm &alg) {
|
|
if (alg.keyLength.min() == alg.keyLength.max()) {
|
|
cout << autosprintf(_("Using key size of %i bits"), alg.keyLength.min())
|
|
<< "\n";
|
|
return alg.keyLength.min();
|
|
}
|
|
|
|
cout
|
|
<< autosprintf(
|
|
// xgroup(setup)
|
|
_("Please select a key size in bits. The cipher you have chosen\n"
|
|
"supports sizes from %i to %i bits in increments of %i bits.\n"
|
|
"For example: "),
|
|
alg.keyLength.min(), alg.keyLength.max(), alg.keyLength.inc())
|
|
<< "\n";
|
|
|
|
int numAvail =
|
|
(alg.keyLength.max() - alg.keyLength.min()) / alg.keyLength.inc();
|
|
|
|
if (numAvail < 5) {
|
|
// show them all
|
|
for (int i = 0; i <= numAvail; ++i) {
|
|
if (i != 0) {
|
|
cout << ", ";
|
|
}
|
|
cout << alg.keyLength.min() + i * alg.keyLength.inc();
|
|
}
|
|
} else {
|
|
// partial
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (i != 0) {
|
|
cout << ", ";
|
|
}
|
|
cout << alg.keyLength.min() + i * alg.keyLength.inc();
|
|
}
|
|
cout << " ... " << alg.keyLength.max() - alg.keyLength.inc();
|
|
cout << ", " << alg.keyLength.max();
|
|
}
|
|
// xgroup(setup)
|
|
cout << "\n" << _("Selected key size: ");
|
|
|
|
char answer[10];
|
|
char *res = fgets(answer, sizeof(answer), stdin);
|
|
int keySize = (res == nullptr ? 0 : (int)strtol(answer, nullptr, 10));
|
|
cout << "\n";
|
|
|
|
keySize = alg.keyLength.closest(keySize);
|
|
|
|
// xgroup(setup)
|
|
cout << autosprintf(_("Using key size of %i bits"), keySize) << "\n\n";
|
|
|
|
return keySize;
|
|
}
|
|
|
|
/**
|
|
* Ask the user which block size to use
|
|
*/
|
|
static int selectBlockSize(const Cipher::CipherAlgorithm &alg) {
|
|
if (alg.blockSize.min() == alg.blockSize.max()) {
|
|
cout << autosprintf(
|
|
// xgroup(setup)
|
|
_("Using filesystem block size of %i bytes"),
|
|
alg.blockSize.min())
|
|
<< "\n";
|
|
return alg.blockSize.min();
|
|
}
|
|
|
|
cout << autosprintf(
|
|
// xgroup(setup)
|
|
_("Select a block size in bytes. The cipher you have chosen\n"
|
|
"supports sizes from %i to %i bytes in increments of %i.\n"
|
|
"Or just hit enter for the default (%i bytes)\n"),
|
|
alg.blockSize.min(), alg.blockSize.max(), alg.blockSize.inc(),
|
|
DefaultBlockSize);
|
|
|
|
// xgroup(setup)
|
|
cout << "\n" << _("filesystem block size: ");
|
|
|
|
int blockSize = DefaultBlockSize;
|
|
char answer[10];
|
|
char *res = fgets(answer, sizeof(answer), stdin);
|
|
cout << "\n";
|
|
|
|
if (res != nullptr && (int)strtol(answer, nullptr, 10) >= alg.blockSize.min()) {
|
|
blockSize = (int)strtol(answer, nullptr, 10);
|
|
}
|
|
|
|
blockSize = alg.blockSize.closest(blockSize);
|
|
|
|
// xgroup(setup)
|
|
cout << autosprintf(_("Using filesystem block size of %i bytes"), blockSize)
|
|
<< "\n\n";
|
|
|
|
return blockSize;
|
|
}
|
|
|
|
/**
|
|
* Prompt the user for a "y" or "n" answer.
|
|
* An empty answer returns defaultValue.
|
|
*/
|
|
static bool boolDefault(const char *prompt, bool defaultValue) {
|
|
|
|
cout << prompt;
|
|
cout << "\n";
|
|
|
|
string yesno;
|
|
|
|
if (defaultValue) {
|
|
yesno = "[y]/n: ";
|
|
} else {
|
|
yesno = "y/[n]: ";
|
|
}
|
|
|
|
string response;
|
|
bool value;
|
|
|
|
while (true) {
|
|
cout << yesno;
|
|
getline(cin, response);
|
|
|
|
if (cin.fail() || response == "") {
|
|
value = defaultValue;
|
|
break;
|
|
}
|
|
if (response == "y") {
|
|
value = true;
|
|
break;
|
|
}
|
|
if (response == "n") {
|
|
value = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
cout << "\n";
|
|
return value;
|
|
}
|
|
|
|
static bool boolDefaultNo(const char *prompt) {
|
|
return boolDefault(prompt, false);
|
|
}
|
|
|
|
static bool boolDefaultYes(const char *prompt) {
|
|
return boolDefault(prompt, true);
|
|
}
|
|
|
|
/**
|
|
* Ask the user to select plain data
|
|
*/
|
|
static bool selectPlainData(bool insecure) {
|
|
bool plainData = false;
|
|
if (insecure) {
|
|
plainData = boolDefaultNo(
|
|
_("You used --insecure, you can then disable file data encryption\n"
|
|
"which is of course abolutely discouraged.\n"
|
|
"Disable file data encryption?"));
|
|
}
|
|
return plainData;
|
|
}
|
|
|
|
/**
|
|
* Ask the user whether to enable block MAC and random header bytes
|
|
*/
|
|
static void selectBlockMAC(int *macBytes, int *macRandBytes, bool forceMac) {
|
|
bool addMAC = false;
|
|
if (!forceMac) {
|
|
// xgroup(setup)
|
|
addMAC = boolDefaultNo(
|
|
_("Enable block authentication code headers\n"
|
|
"on every block in a file? This adds about 8 bytes per block\n"
|
|
"to the storage requirements for a file, and significantly affects\n"
|
|
"performance but it also means [almost] any modifications or errors\n"
|
|
"within a block will be caught and will cause a read error."));
|
|
} else {
|
|
cout << "\n\n"
|
|
<< _("You specified --require-macs. "
|
|
"Enabling block authentication code headers...")
|
|
<< "\n\n";
|
|
addMAC = true;
|
|
}
|
|
|
|
if (addMAC) {
|
|
*macBytes = 8;
|
|
} else {
|
|
*macBytes = 0;
|
|
}
|
|
|
|
// xgroup(setup)
|
|
cout << _(
|
|
"Add random bytes to each block header?\n"
|
|
"This adds a performance penalty, but ensures that blocks\n"
|
|
"have different authentication codes. Note that you can\n"
|
|
"have the same benefits by enabling per-file initialization\n"
|
|
"vectors, which does not come with as great of performance\n"
|
|
"penalty. \n"
|
|
"Select a number of bytes, from 0 (no random bytes) to 8: ");
|
|
|
|
char answer[10];
|
|
int randSize = 0;
|
|
char *res = fgets(answer, sizeof(answer), stdin);
|
|
cout << "\n";
|
|
|
|
randSize = (res == nullptr ? 0 : (int)strtol(answer, nullptr, 10));
|
|
if (randSize < 0) {
|
|
randSize = 0;
|
|
}
|
|
if (randSize > 8) {
|
|
randSize = 8;
|
|
}
|
|
|
|
*macRandBytes = randSize;
|
|
}
|
|
|
|
/**
|
|
* Ask the user if per-file unique IVs should be used
|
|
*/
|
|
static bool selectUniqueIV(bool default_answer) {
|
|
// xgroup(setup)
|
|
return boolDefault(
|
|
_("Enable per-file initialization vectors?\n"
|
|
"This adds about 8 bytes per file to the storage requirements.\n"
|
|
"It should not affect performance except possibly with applications\n"
|
|
"which rely on block-aligned file io for performance."),
|
|
default_answer);
|
|
}
|
|
|
|
/**
|
|
* Ask the user if the filename IV should depend on the complete path
|
|
*/
|
|
static bool selectChainedIV() {
|
|
// xgroup(setup)
|
|
return boolDefaultYes(
|
|
_("Enable filename initialization vector chaining?\n"
|
|
"This makes filename encoding dependent on the complete path, \n"
|
|
"rather then encoding each path element individually."));
|
|
}
|
|
|
|
/**
|
|
* Ask the user if the file IV should depend on the file path
|
|
*/
|
|
static bool selectExternalChainedIV() {
|
|
// xgroup(setup)
|
|
return boolDefaultNo(
|
|
_("Enable filename to IV header chaining?\n"
|
|
"This makes file data encoding dependent on the complete file path.\n"
|
|
"If a file is renamed, it will not decode sucessfully unless it\n"
|
|
"was renamed by encfs with the proper key.\n"
|
|
"If this option is enabled, then hard links will not be supported\n"
|
|
"in the filesystem."));
|
|
}
|
|
|
|
/**
|
|
* Ask the user if file holes should be passed through
|
|
*/
|
|
static bool selectZeroBlockPassThrough() {
|
|
// xgroup(setup)
|
|
return boolDefaultYes(
|
|
_("Enable file-hole pass-through?\n"
|
|
"This avoids writing encrypted blocks when file holes are created."));
|
|
}
|
|
|
|
RootPtr createV6Config(EncFS_Context *ctx,
|
|
const std::shared_ptr<EncFS_Opts> &opts) {
|
|
const std::string rootDir = opts->rootDir;
|
|
bool enableIdleTracking = opts->idleTracking;
|
|
bool forceDecode = opts->forceDecode;
|
|
const std::string passwordProgram = opts->passwordProgram;
|
|
bool useStdin = opts->useStdin;
|
|
bool reverseEncryption = opts->reverseEncryption;
|
|
ConfigMode configMode = opts->configMode;
|
|
bool annotate = opts->annotate;
|
|
|
|
RootPtr rootInfo;
|
|
|
|
// creating new volume key.. should check that is what the user is
|
|
// expecting...
|
|
// xgroup(setup)
|
|
cout << _("Creating new encrypted volume.") << endl;
|
|
|
|
char answer[10] = {0};
|
|
if (configMode == Config_Prompt) {
|
|
// xgroup(setup)
|
|
cout << _(
|
|
"Please choose from one of the following options:\n"
|
|
" enter \"x\" for expert configuration mode,\n"
|
|
" enter \"p\" for pre-configured paranoia mode,\n"
|
|
" anything else, or an empty line will select standard mode.\n"
|
|
"?> ");
|
|
|
|
if (annotate) {
|
|
cerr << "$PROMPT$ config_option" << endl;
|
|
}
|
|
|
|
char *res = fgets(answer, sizeof(answer), stdin);
|
|
(void)res;
|
|
cout << "\n";
|
|
}
|
|
|
|
// documented in ...
|
|
int keySize = 0; // selectKeySize()
|
|
int blockSize = 0; // selectBlockSize()
|
|
Cipher::CipherAlgorithm alg; // selectCipherAlgorithm()
|
|
Interface nameIOIface; // selectNameCoding()
|
|
int blockMACBytes = 0; // selectBlockMAC()
|
|
int blockMACRandBytes = 0; // selectBlockMAC()
|
|
bool plainData = false; // selectPlainData()
|
|
bool uniqueIV = true; // selectUniqueIV()
|
|
bool chainedIV = true; // selectChainedIV()
|
|
bool externalIV = false; // selectExternalChainedIV()
|
|
bool allowHoles = true; // selectZeroBlockPassThrough()
|
|
long desiredKDFDuration = NormalKDFDuration;
|
|
|
|
if (reverseEncryption) {
|
|
chainedIV = false;
|
|
externalIV = false;
|
|
uniqueIV = false;
|
|
blockMACBytes = 0;
|
|
blockMACRandBytes = 0;
|
|
}
|
|
|
|
if (configMode == Config_Paranoia || answer[0] == 'p') {
|
|
if (reverseEncryption) {
|
|
cerr << _("Paranoia configuration not supported for reverse encryption");
|
|
return rootInfo;
|
|
}
|
|
|
|
// xgroup(setup)
|
|
cout << _("Paranoia configuration selected.") << "\n";
|
|
// look for AES with 256 bit key..
|
|
// Use block filename encryption mode.
|
|
// Enable per-block HMAC headers at substantial performance penalty..
|
|
// Enable per-file initialization vector headers.
|
|
// Enable filename initialization vector chaning
|
|
keySize = 256;
|
|
blockSize = DefaultBlockSize;
|
|
alg = findCipherAlgorithm("AES", keySize);
|
|
|
|
// If case-insensitive system, opt for Block32 filename encoding
|
|
#if defined(DEFAULT_CASE_INSENSITIVE)
|
|
nameIOIface = BlockNameIO::CurrentInterface(true);
|
|
#else
|
|
nameIOIface = BlockNameIO::CurrentInterface();
|
|
#endif
|
|
|
|
blockMACBytes = 8;
|
|
blockMACRandBytes = 0; // using uniqueIV, so this isn't necessary
|
|
externalIV = true;
|
|
desiredKDFDuration = ParanoiaKDFDuration;
|
|
} else if (configMode == Config_Standard || answer[0] != 'x') {
|
|
// xgroup(setup)
|
|
cout << _("Standard configuration selected.") << "\n";
|
|
// AES w/ 192 bit key, block name encoding, per-file initialization
|
|
// vectors are all standard.
|
|
keySize = 192;
|
|
blockSize = DefaultBlockSize;
|
|
alg = findCipherAlgorithm("AES", keySize);
|
|
|
|
// If case-insensitive system, opt for Block32 filename encoding
|
|
#if defined(DEFAULT_CASE_INSENSITIVE)
|
|
nameIOIface = BlockNameIO::CurrentInterface(true);
|
|
#else
|
|
nameIOIface = BlockNameIO::CurrentInterface();
|
|
#endif
|
|
|
|
if (opts->requireMac) {
|
|
blockMACBytes = 8;
|
|
}
|
|
}
|
|
|
|
if (answer[0] == 'x' || alg.name.empty()) {
|
|
if (answer[0] != 'x') {
|
|
// xgroup(setup)
|
|
cout << _(
|
|
"Sorry, unable to locate cipher for predefined "
|
|
"configuration...\n"
|
|
"Falling through to Manual configuration mode.");
|
|
} else {
|
|
// xgroup(setup)
|
|
cout << _("Manual configuration mode selected.");
|
|
}
|
|
cout << endl;
|
|
|
|
// query user for settings..
|
|
alg = selectCipherAlgorithm();
|
|
keySize = selectKeySize(alg);
|
|
blockSize = selectBlockSize(alg);
|
|
plainData = selectPlainData(opts->insecure);
|
|
nameIOIface = selectNameCoding();
|
|
if (plainData) {
|
|
cout << _("plain data - IV, MAC and file-hole disabled") << "\n";
|
|
allowHoles = false;
|
|
chainedIV = false;
|
|
externalIV = false;
|
|
uniqueIV = false;
|
|
blockMACBytes = 0;
|
|
blockMACRandBytes = 0;
|
|
}
|
|
else {
|
|
if (reverseEncryption) {
|
|
cout << _("reverse encryption - chained IV and MAC disabled") << "\n";
|
|
uniqueIV = selectUniqueIV(false);
|
|
/* If uniqueIV is off, writing can be allowed, because there
|
|
* is no header that could be overwritten.
|
|
* So if it is on, enforce readOnly. */
|
|
if (uniqueIV) {
|
|
opts->readOnly = true;
|
|
}
|
|
} else {
|
|
chainedIV = selectChainedIV();
|
|
uniqueIV = selectUniqueIV(true);
|
|
if (chainedIV && uniqueIV) {
|
|
externalIV = selectExternalChainedIV();
|
|
} else {
|
|
// xgroup(setup)
|
|
cout << _("External chained IV disabled, as both 'IV chaining'\n"
|
|
"and 'unique IV' features are required for this option.")
|
|
<< "\n";
|
|
externalIV = false;
|
|
}
|
|
selectBlockMAC(&blockMACBytes, &blockMACRandBytes, opts->requireMac);
|
|
allowHoles = selectZeroBlockPassThrough();
|
|
}
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<Cipher> cipher = Cipher::New(alg.name, keySize);
|
|
if (!cipher) {
|
|
cerr << autosprintf(
|
|
_("Unable to instanciate cipher %s, key size %i, block size %i"),
|
|
alg.name.c_str(), keySize, blockSize);
|
|
return rootInfo;
|
|
}
|
|
VLOG(1) << "Using cipher " << alg.name << ", key size " << keySize
|
|
<< ", block size " << blockSize;
|
|
|
|
std::shared_ptr<EncFSConfig> config(new EncFSConfig);
|
|
|
|
config->cfgType = Config_V6;
|
|
config->cipherIface = cipher->interface();
|
|
config->keySize = keySize;
|
|
config->blockSize = blockSize;
|
|
config->plainData = plainData;
|
|
config->nameIface = nameIOIface;
|
|
config->creator = "EncFS " VERSION;
|
|
config->subVersion = V6SubVersion;
|
|
config->blockMACBytes = blockMACBytes;
|
|
config->blockMACRandBytes = blockMACRandBytes;
|
|
config->uniqueIV = uniqueIV;
|
|
config->chainedNameIV = chainedIV;
|
|
config->externalIVChaining = externalIV;
|
|
config->allowHoles = allowHoles;
|
|
|
|
config->salt.clear();
|
|
config->kdfIterations = 0; // filled in by keying function
|
|
config->desiredKDFDuration = desiredKDFDuration;
|
|
|
|
cout << "\n";
|
|
// xgroup(setup)
|
|
cout << _("Configuration finished. The filesystem to be created has\n"
|
|
"the following properties:")
|
|
<< endl;
|
|
showFSInfo(config.get());
|
|
|
|
if (config->externalIVChaining) {
|
|
cout << _("-------------------------- WARNING --------------------------\n")
|
|
<< _("The external initialization-vector chaining option has been\n"
|
|
"enabled. This option disables the use of hard links on the\n"
|
|
"filesystem. Without hard links, some programs may not work.\n"
|
|
"The programs 'mutt' and 'procmail' are known to fail. For\n"
|
|
"more information, please see the encfs mailing list.\n"
|
|
"If you would like to choose another configuration setting,\n"
|
|
"please press CTRL-C now to abort and start over.")
|
|
<< endl;
|
|
cout << endl;
|
|
}
|
|
|
|
// xgroup(setup)
|
|
cout << _(
|
|
"Now you will need to enter a password for your filesystem.\n"
|
|
"You will need to remember this password, as there is absolutely\n"
|
|
"no recovery mechanism. However, the password can be changed\n"
|
|
"later using encfsctl.\n\n");
|
|
|
|
int encodedKeySize = cipher->encodedKeySize();
|
|
auto *encodedKey = new unsigned char[encodedKeySize];
|
|
|
|
CipherKey volumeKey = cipher->newRandomKey();
|
|
|
|
// get user key and use it to encode volume key
|
|
CipherKey userKey;
|
|
VLOG(1) << "useStdin: " << useStdin;
|
|
if (useStdin) {
|
|
if (annotate) {
|
|
cerr << "$PROMPT$ new_passwd" << endl;
|
|
}
|
|
userKey = config->getUserKey(useStdin);
|
|
} else if (!passwordProgram.empty()) {
|
|
userKey = config->getUserKey(passwordProgram, rootDir);
|
|
} else {
|
|
userKey = config->getNewUserKey();
|
|
}
|
|
|
|
cipher->writeKey(volumeKey, encodedKey, userKey);
|
|
userKey.reset();
|
|
|
|
config->assignKeyData(encodedKey, encodedKeySize);
|
|
delete[] encodedKey;
|
|
|
|
if (!volumeKey) {
|
|
cerr << _(
|
|
"Failure generating new volume key! "
|
|
"Please report this error.");
|
|
return rootInfo;
|
|
}
|
|
|
|
if (!saveConfig(Config_V6, rootDir, config.get(), opts->config)) {
|
|
return rootInfo;
|
|
}
|
|
|
|
// fill in config struct
|
|
std::shared_ptr<NameIO> nameCoder =
|
|
NameIO::New(config->nameIface, cipher, volumeKey);
|
|
if (!nameCoder) {
|
|
cerr << _("Name coding interface not supported");
|
|
cout << _("The filename encoding interface requested is not available")
|
|
<< endl;
|
|
return rootInfo;
|
|
}
|
|
|
|
nameCoder->setChainedNameIV(config->chainedNameIV);
|
|
nameCoder->setReverseEncryption(reverseEncryption);
|
|
|
|
FSConfigPtr fsConfig(new FSConfig);
|
|
if (plainData) {
|
|
static Interface NullInterface("nullCipher", 1, 0, 0);
|
|
fsConfig->cipher = Cipher::New(NullInterface, 0);
|
|
}
|
|
else {
|
|
fsConfig->cipher = cipher;
|
|
}
|
|
fsConfig->key = volumeKey;
|
|
fsConfig->nameCoding = nameCoder;
|
|
fsConfig->config = config;
|
|
fsConfig->forceDecode = forceDecode;
|
|
fsConfig->reverseEncryption = reverseEncryption;
|
|
fsConfig->idleTracking = enableIdleTracking;
|
|
fsConfig->opts = opts;
|
|
|
|
rootInfo = std::make_shared<encfs::EncFS_Root>();
|
|
rootInfo->cipher = cipher;
|
|
rootInfo->volumeKey = volumeKey;
|
|
rootInfo->root = std::make_shared<DirNode>(ctx, rootDir, fsConfig);
|
|
|
|
return rootInfo;
|
|
}
|
|
|
|
void showFSInfo(const EncFSConfig *config) {
|
|
std::shared_ptr<Cipher> cipher = Cipher::New(config->cipherIface, -1);
|
|
{
|
|
cout << autosprintf(
|
|
// xgroup(diag)
|
|
_("Filesystem cipher: \"%s\", version %i:%i:%i"),
|
|
config->cipherIface.name().c_str(), config->cipherIface.current(),
|
|
config->cipherIface.revision(), config->cipherIface.age());
|
|
// check if we support this interface..
|
|
if (!cipher) {
|
|
cout << _(" (NOT supported)\n");
|
|
} else {
|
|
// if we're using a newer interface, show the version number
|
|
if (config->cipherIface != cipher->interface()) {
|
|
Interface iface = cipher->interface();
|
|
// xgroup(diag)
|
|
cout << autosprintf(_(" (using %i:%i:%i)\n"), iface.current(),
|
|
iface.revision(), iface.age());
|
|
} else {
|
|
cout << "\n";
|
|
}
|
|
}
|
|
}
|
|
{
|
|
// xgroup(diag)
|
|
cout << autosprintf(_("Filename encoding: \"%s\", version %i:%i:%i"),
|
|
config->nameIface.name().c_str(),
|
|
config->nameIface.current(),
|
|
config->nameIface.revision(), config->nameIface.age());
|
|
|
|
// check if we support the filename encoding interface..
|
|
std::shared_ptr<NameIO> nameCoder =
|
|
NameIO::New(config->nameIface, cipher, CipherKey());
|
|
if (!nameCoder) {
|
|
// xgroup(diag)
|
|
cout << _(" (NOT supported)\n");
|
|
} else {
|
|
// if we're using a newer interface, show the version number
|
|
if (config->nameIface != nameCoder->interface()) {
|
|
Interface iface = nameCoder->interface();
|
|
cout << autosprintf(_(" (using %i:%i:%i)\n"), iface.current(),
|
|
iface.revision(), iface.age());
|
|
} else {
|
|
cout << "\n";
|
|
}
|
|
}
|
|
}
|
|
{
|
|
cout << autosprintf(_("Key Size: %i bits"), config->keySize);
|
|
cipher = config->getCipher();
|
|
if (!cipher) {
|
|
// xgroup(diag)
|
|
cout << _(" (NOT supported)\n");
|
|
} else {
|
|
cout << "\n";
|
|
}
|
|
}
|
|
if (config->kdfIterations > 0 && !config->salt.empty()) {
|
|
cout << autosprintf(_("Using PBKDF2, with %i iterations"),
|
|
config->kdfIterations)
|
|
<< "\n";
|
|
cout << autosprintf(_("Salt Size: %i bits"), (int)(8 * config->salt.size()))
|
|
<< "\n";
|
|
}
|
|
if ((config->blockMACBytes != 0) || (config->blockMACRandBytes != 0)) {
|
|
if (config->subVersion < 20040813) {
|
|
cout << autosprintf(
|
|
// xgroup(diag)
|
|
_("Block Size: %i bytes + %i byte MAC header"),
|
|
config->blockSize,
|
|
config->blockMACBytes + config->blockMACRandBytes)
|
|
<< endl;
|
|
} else {
|
|
// new version stores the header as part of that block size..
|
|
cout << autosprintf(
|
|
// xgroup(diag)
|
|
_("Block Size: %i bytes, including %i byte MAC header"),
|
|
config->blockSize,
|
|
config->blockMACBytes + config->blockMACRandBytes)
|
|
<< endl;
|
|
}
|
|
} else {
|
|
// xgroup(diag)
|
|
cout << autosprintf(_("Block Size: %i bytes"), config->blockSize);
|
|
cout << "\n";
|
|
}
|
|
|
|
if (config->uniqueIV) {
|
|
// xgroup(diag)
|
|
cout << _("Each file contains 8 byte header with unique IV data.\n");
|
|
}
|
|
if (config->chainedNameIV) {
|
|
// xgroup(diag)
|
|
cout << _("Filenames encoded using IV chaining mode.\n");
|
|
}
|
|
if (config->externalIVChaining) {
|
|
// xgroup(diag)
|
|
cout << _("File data IV is chained to filename IV.\n");
|
|
}
|
|
if (config->allowHoles) {
|
|
// xgroup(diag)
|
|
cout << _("File holes passed through to ciphertext.\n");
|
|
}
|
|
cout << "\n";
|
|
}
|
|
std::shared_ptr<Cipher> EncFSConfig::getCipher() const {
|
|
return Cipher::New(cipherIface, keySize);
|
|
}
|
|
|
|
void EncFSConfig::assignKeyData(const std::string &in) {
|
|
keyData.assign(in.data(), in.data() + in.length());
|
|
}
|
|
|
|
void EncFSConfig::assignKeyData(unsigned char *data, int len) {
|
|
keyData.assign(data, data + len);
|
|
}
|
|
|
|
void EncFSConfig::assignSaltData(unsigned char *data, int len) {
|
|
salt.assign(data, data + len);
|
|
}
|
|
|
|
unsigned char *EncFSConfig::getKeyData() const {
|
|
return const_cast<unsigned char *>(&keyData.front());
|
|
}
|
|
|
|
unsigned char *EncFSConfig::getSaltData() const {
|
|
return const_cast<unsigned char *>(&salt.front());
|
|
}
|
|
|
|
CipherKey EncFSConfig::makeKey(const char *password, int passwdLen) {
|
|
CipherKey userKey;
|
|
std::shared_ptr<Cipher> cipher = getCipher();
|
|
|
|
if (passwdLen == 0) {
|
|
cerr << _("fatal: zero-length passwords are not allowed\n");
|
|
exit(1);
|
|
}
|
|
|
|
// if no salt is set and we're creating a new password for a new
|
|
// FS type, then initialize salt..
|
|
if (salt.empty() && kdfIterations == 0 && cfgType >= Config_V6) {
|
|
// upgrade to using salt
|
|
salt.resize(20);
|
|
}
|
|
|
|
if (!salt.empty()) {
|
|
// if iterations isn't known, then we're creating a new key, so
|
|
// randomize the salt..
|
|
if (kdfIterations == 0 &&
|
|
!cipher->randomize(getSaltData(), salt.size(), true)) {
|
|
cout << _("Error creating salt\n");
|
|
return userKey;
|
|
}
|
|
|
|
userKey = cipher->newKey(password, passwdLen, kdfIterations,
|
|
desiredKDFDuration, getSaltData(), salt.size());
|
|
} else {
|
|
userKey = cipher->newKey(password, passwdLen);
|
|
}
|
|
|
|
return userKey;
|
|
}
|
|
|
|
CipherKey EncFSConfig::getUserKey(bool useStdin) {
|
|
char passBuf[MaxPassBuf];
|
|
char *res;
|
|
|
|
if (useStdin) {
|
|
res = fgets(passBuf, sizeof(passBuf), stdin);
|
|
// Kill the trailing newline.
|
|
if (passBuf[strlen(passBuf) - 1] == '\n') {
|
|
passBuf[strlen(passBuf) - 1] = '\0';
|
|
}
|
|
} else {
|
|
// xgroup(common)
|
|
res = readpassphrase(_("EncFS Password: "), passBuf, sizeof(passBuf),
|
|
RPP_ECHO_OFF);
|
|
}
|
|
|
|
CipherKey userKey;
|
|
if (res == nullptr) {
|
|
cerr << _("fatal: error reading password\n");
|
|
exit(1);
|
|
} else {
|
|
userKey = makeKey(passBuf, strlen(passBuf));
|
|
}
|
|
|
|
memset(passBuf, 0, sizeof(passBuf));
|
|
|
|
return userKey;
|
|
}
|
|
|
|
std::string readPassword(int FD) {
|
|
char buffer[1024];
|
|
string result;
|
|
|
|
while (true) {
|
|
ssize_t rdSize = recv(FD, buffer, sizeof(buffer), 0);
|
|
|
|
if (rdSize > 0) {
|
|
result.append(buffer, rdSize);
|
|
memset(buffer, 0, sizeof(buffer));
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// chop off trailing "\n" if present..
|
|
// This is done so that we can use standard programs like ssh-askpass
|
|
// without modification, as it returns trailing newline..
|
|
if (!result.empty() && result[result.length() - 1] == '\n') {
|
|
result.resize(result.length() - 1);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
CipherKey EncFSConfig::getUserKey(const std::string &passProg,
|
|
const std::string &rootDir) {
|
|
// have a child process run the command and get the result back to us.
|
|
int fds[2], pid;
|
|
int res;
|
|
CipherKey result;
|
|
|
|
res = socketpair(PF_UNIX, SOCK_STREAM, 0, fds);
|
|
if (res == -1) {
|
|
perror(_("Internal error: socketpair() failed"));
|
|
return result;
|
|
}
|
|
VLOG(1) << "getUserKey: fds = " << fds[0] << ", " << fds[1];
|
|
|
|
pid = fork();
|
|
if (pid == -1) {
|
|
perror(_("Internal error: fork() failed"));
|
|
close(fds[0]);
|
|
close(fds[1]);
|
|
return result;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
const char *argv[4];
|
|
argv[0] = "/bin/sh";
|
|
argv[1] = "-c";
|
|
argv[2] = passProg.c_str();
|
|
argv[3] = nullptr;
|
|
|
|
// child process.. run the command and send output to fds[0]
|
|
close(fds[1]); // we don't use the other half..
|
|
|
|
// make a copy of stdout and stderr descriptors, and set an environment
|
|
// variable telling where to find them, in case a child wants it..
|
|
int stdOutCopy = dup(STDOUT_FILENO);
|
|
int stdErrCopy = dup(STDERR_FILENO);
|
|
// replace STDOUT with our socket, which we'll used to receive the
|
|
// password..
|
|
dup2(fds[0], STDOUT_FILENO);
|
|
|
|
// ensure that STDOUT_FILENO and stdout/stderr are not closed on exec..
|
|
fcntl(STDOUT_FILENO, F_SETFD, 0); // don't close on exec..
|
|
fcntl(stdOutCopy, F_SETFD, 0);
|
|
fcntl(stdErrCopy, F_SETFD, 0);
|
|
|
|
char tmpBuf[8];
|
|
|
|
setenv(ENCFS_ENV_ROOTDIR, rootDir.c_str(), 1);
|
|
|
|
snprintf(tmpBuf, sizeof(tmpBuf) - 1, "%i", stdOutCopy);
|
|
setenv(ENCFS_ENV_STDOUT, tmpBuf, 1);
|
|
|
|
snprintf(tmpBuf, sizeof(tmpBuf) - 1, "%i", stdErrCopy);
|
|
setenv(ENCFS_ENV_STDERR, tmpBuf, 1);
|
|
|
|
execvp(argv[0], (char *const *)argv); // returns only on error..
|
|
|
|
perror(_("Internal error: failed to exec program"));
|
|
exit(1);
|
|
}
|
|
|
|
close(fds[0]);
|
|
string password = readPassword(fds[1]);
|
|
close(fds[1]);
|
|
|
|
waitpid(pid, nullptr, 0);
|
|
|
|
// convert to key..
|
|
result = makeKey(password.c_str(), password.length());
|
|
|
|
// clear buffer..
|
|
password.assign(password.length(), '\0');
|
|
|
|
return result;
|
|
}
|
|
|
|
CipherKey EncFSConfig::getNewUserKey() {
|
|
CipherKey userKey;
|
|
char passBuf[MaxPassBuf];
|
|
char passBuf2[MaxPassBuf];
|
|
|
|
do {
|
|
// xgroup(common)
|
|
char *res1 = readpassphrase(_("New Encfs Password: "), passBuf,
|
|
sizeof(passBuf) - 1, RPP_ECHO_OFF);
|
|
// xgroup(common)
|
|
char *res2 = readpassphrase(_("Verify Encfs Password: "), passBuf2,
|
|
sizeof(passBuf2) - 1, RPP_ECHO_OFF);
|
|
|
|
if ((res1 != nullptr) && (res2 != nullptr) &&
|
|
(strcmp(passBuf, passBuf2) == 0)) {
|
|
userKey = makeKey(passBuf, strlen(passBuf));
|
|
} else {
|
|
// xgroup(common) -- probably not common, but group with the others
|
|
cerr << _("Passwords did not match, please try again\n");
|
|
}
|
|
|
|
memset(passBuf, 0, sizeof(passBuf));
|
|
memset(passBuf2, 0, sizeof(passBuf2));
|
|
} while (!userKey);
|
|
|
|
return userKey;
|
|
}
|
|
|
|
RootPtr initFS(EncFS_Context *ctx, const std::shared_ptr<EncFS_Opts> &opts) {
|
|
RootPtr rootInfo;
|
|
std::shared_ptr<EncFSConfig> config(new EncFSConfig);
|
|
|
|
if (readConfig(opts->rootDir, config.get(), opts->config) != Config_None) {
|
|
if (config->blockMACBytes == 0 && opts->requireMac) {
|
|
cout << _(
|
|
"The configuration disabled MAC, but you passed --require-macs\n");
|
|
return rootInfo;
|
|
}
|
|
|
|
if (opts->reverseEncryption) {
|
|
if (config->blockMACBytes != 0 || config->blockMACRandBytes != 0 ||
|
|
config->externalIVChaining || config->chainedNameIV) {
|
|
cout << _(
|
|
"The configuration loaded is not compatible with --reverse\n");
|
|
return rootInfo;
|
|
}
|
|
/* If uniqueIV is off, writing can be allowed, because there
|
|
* is no header that could be overwritten.
|
|
* So if it is on, enforce readOnly. */
|
|
if (config->uniqueIV) {
|
|
opts->readOnly = true;
|
|
}
|
|
}
|
|
|
|
// first, instanciate the cipher.
|
|
std::shared_ptr<Cipher> cipher = config->getCipher();
|
|
if (!cipher) {
|
|
cerr << autosprintf(
|
|
_("Unable to find cipher %s, version %i:%i:%i"),
|
|
config->cipherIface.name().c_str(), config->cipherIface.current(),
|
|
config->cipherIface.revision(), config->cipherIface.age());
|
|
// xgroup(diag)
|
|
cout << _("The requested cipher interface is not available\n");
|
|
return rootInfo;
|
|
}
|
|
|
|
if (opts->delayMount) {
|
|
rootInfo = std::make_shared<encfs::EncFS_Root>();
|
|
rootInfo->cipher = cipher;
|
|
rootInfo->root = std::shared_ptr<DirNode>();
|
|
return rootInfo;
|
|
}
|
|
|
|
// get user key
|
|
CipherKey userKey;
|
|
|
|
if (opts->passwordProgram.empty()) {
|
|
VLOG(1) << "useStdin: " << opts->useStdin;
|
|
if (opts->annotate) {
|
|
cerr << "$PROMPT$ passwd" << endl;
|
|
}
|
|
userKey = config->getUserKey(opts->useStdin);
|
|
} else {
|
|
userKey = config->getUserKey(opts->passwordProgram, opts->rootDir);
|
|
}
|
|
|
|
if (!userKey) {
|
|
return rootInfo;
|
|
}
|
|
|
|
VLOG(1) << "cipher key size = " << cipher->encodedKeySize();
|
|
// decode volume key..
|
|
CipherKey volumeKey =
|
|
cipher->readKey(config->getKeyData(), userKey, opts->checkKey);
|
|
userKey.reset();
|
|
|
|
if (!volumeKey) {
|
|
// xgroup(diag)
|
|
cout << _("Error decoding volume key, password incorrect\n");
|
|
return rootInfo;
|
|
}
|
|
|
|
std::shared_ptr<NameIO> nameCoder =
|
|
NameIO::New(config->nameIface, cipher, volumeKey);
|
|
if (!nameCoder) {
|
|
cerr << autosprintf(
|
|
_("Unable to find nameio interface '%s', version %i:%i:%i"),
|
|
config->nameIface.name().c_str(), config->nameIface.current(),
|
|
config->nameIface.revision(), config->nameIface.age());
|
|
// xgroup(diag)
|
|
cout << _(
|
|
"The requested filename coding interface is "
|
|
"not available\n");
|
|
return rootInfo;
|
|
}
|
|
|
|
nameCoder->setChainedNameIV(config->chainedNameIV);
|
|
nameCoder->setReverseEncryption(opts->reverseEncryption);
|
|
|
|
FSConfigPtr fsConfig(new FSConfig);
|
|
if (config->plainData) {
|
|
if (! opts->insecure) {
|
|
cout << _("Configuration use plainData but you did not use --insecure\n");
|
|
return rootInfo;
|
|
}
|
|
static Interface NullInterface("nullCipher", 1, 0, 0);
|
|
fsConfig->cipher = Cipher::New(NullInterface, 0);
|
|
}
|
|
else {
|
|
fsConfig->cipher = cipher;
|
|
}
|
|
fsConfig->key = volumeKey;
|
|
fsConfig->nameCoding = nameCoder;
|
|
fsConfig->config = config;
|
|
fsConfig->forceDecode = opts->forceDecode;
|
|
fsConfig->reverseEncryption = opts->reverseEncryption;
|
|
fsConfig->opts = opts;
|
|
|
|
rootInfo = std::make_shared<encfs::EncFS_Root>();
|
|
rootInfo->cipher = cipher;
|
|
rootInfo->volumeKey = volumeKey;
|
|
rootInfo->root = std::make_shared<DirNode>(ctx, opts->rootDir, fsConfig);
|
|
} else {
|
|
if (opts->createIfNotFound) {
|
|
// creating a new encrypted filesystem
|
|
rootInfo = createV6Config(ctx, opts);
|
|
}
|
|
}
|
|
|
|
return rootInfo;
|
|
}
|
|
|
|
void unmountFS(const char *mountPoint) {
|
|
// fuse_unmount succeeds and returns void
|
|
fuse_unmount(mountPoint, nullptr);
|
|
#ifdef __APPLE__
|
|
// fuse_unmount does not work on Mac OS, see #428
|
|
unmount(mountPoint, MNT_FORCE);
|
|
#endif
|
|
#ifdef __CYGWIN__
|
|
if(fork() == 0)
|
|
{
|
|
execl("/usr/bin/pkill", "/usr/bin/pkill", "-f", string("(^|/)encfs .*/.* ").append(mountPoint).append("?( |$)").c_str(), (char *)0);
|
|
}
|
|
int status;
|
|
wait(&status);
|
|
#endif
|
|
}
|
|
|
|
int remountFS(EncFS_Context *ctx) {
|
|
VLOG(1) << "Attempting to reinitialize filesystem";
|
|
|
|
RootPtr rootInfo = initFS(ctx, ctx->opts);
|
|
if (rootInfo) {
|
|
ctx->setRoot(rootInfo->root);
|
|
return 0;
|
|
}
|
|
RLOG(WARNING) << "Remount failed";
|
|
return -EACCES;
|
|
}
|
|
|
|
bool unmountFS(EncFS_Context *ctx) {
|
|
if (ctx->opts->mountOnDemand) {
|
|
VLOG(1) << "Detaching filesystem due to inactivity: "
|
|
<< ctx->opts->mountPoint;
|
|
|
|
ctx->setRoot(std::shared_ptr<DirNode>());
|
|
return false;
|
|
}
|
|
// Time to unmount!
|
|
unmountFS(ctx->opts->mountPoint.c_str());
|
|
RLOG(INFO) << "Filesystem inactive, unmounted: " << ctx->opts->mountPoint;
|
|
return true;
|
|
}
|
|
|
|
} // namespace encfs
|