Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support of (open)hardware cryptographic module "Trezor One" #243

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,31 @@ LS: 1.75
RM: 4.42
```

Secure hardware encryption
--------------------------

gocryptfs supports openhardware cryptographic device
[Trezor One](https://github.com/trezor/trezor-mcu)

$ mkdir cipher plain
$ ./gocryptfs -init -cryptowallet_encrypt_masterkey cipher
$ ./gocryptfs cipher plain

Notes:
* Flag `-cryptowallet_encrypt_masterkey` encrypts/decrypts masterkey using trezor
so there's a decrypted masterkey in RAM while you're working with
the decrypted directory.
* ATM, the only supported Trezor device is "Trezor One"
* There was no security audit of the code quality (related to the Trezor
devices support)

Changelog
---------

vNEXT, in progress
* Add a support of encrypting the master key using (open)hardware device "Trezor One"
(`-cryptowallet_encrypt_masterkey`)

v1.5, 2018-06-12
* **Support extended attributes (xattr)** in forward mode
([#217](https://github.com/rfjakob/gocryptfs/issues/217)). Older gocryptfs versions
Expand Down
6 changes: 4 additions & 2 deletions cli_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import (
type argContainer struct {
debug, init, zerokey, fusedebug, openssl, passwd, fg, version,
plaintextnames, quiet, nosyslog, wpanic,
longnames, allow_other, reverse, aessiv, nonempty, raw64,
longnames, allow_other, reverse, aessiv, cryptowalletencryptmasterkey, nonempty, raw64,
noprealloc, speed, hkdf, serialize_reads, forcedecode, hh, info,
sharedstorage, devrandom, fsck bool
// Mount options with opposites
dev, nodev, suid, nosuid, exec, noexec, rw, ro bool
masterkey, mountpoint, cipherdir, cpuprofile, extpass,
masterkey, mountpoint, cipherdir, cpuprofile, extpass, cryptowalletkeyname,
memprofile, ko, passfile, ctlsock, fsname, force_owner, trace string
// Configuration file name override
config string
Expand Down Expand Up @@ -125,6 +125,7 @@ func parseCliOpts() (args argContainer) {
"Only works if user_allow_other is set in /etc/fuse.conf.")
flagSet.BoolVar(&args.reverse, "reverse", false, "Reverse mode")
flagSet.BoolVar(&args.aessiv, "aessiv", false, "AES-SIV encryption")
flagSet.BoolVar(&args.cryptowalletencryptmasterkey, "cryptowallet_encrypt_masterkey", false, `Encrypt master key through a hardware cryptowallet device.`)
flagSet.BoolVar(&args.nonempty, "nonempty", false, "Allow mounting over non-empty directories")
flagSet.BoolVar(&args.raw64, "raw64", true, "Use unpadded base64 for file names")
flagSet.BoolVar(&args.noprealloc, "noprealloc", false, "Disable preallocation before writing")
Expand Down Expand Up @@ -154,6 +155,7 @@ func parseCliOpts() (args argContainer) {
flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file")
flagSet.StringVar(&args.config, "config", "", "Use specified config file instead of CIPHERDIR/gocryptfs.conf")
flagSet.StringVar(&args.extpass, "extpass", "", "Use external program for the password prompt")
flagSet.StringVar(&args.cryptowalletkeyname, "cryptowallet_keyname", "gocryptfs", "A name of the key for a cryptowallet device")
flagSet.StringVar(&args.passfile, "passfile", "", "Read password from file")
flagSet.StringVar(&args.ko, "ko", "", "Pass additional options directly to the kernel, comma-separated list")
flagSet.StringVar(&args.ctlsock, "ctlsock", "", "Create control socket at specified path")
Expand Down
7 changes: 1 addition & 6 deletions gocryptfs-xray/xray_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/exitcodes"
"github.com/rfjakob/gocryptfs/internal/readpassword"
"github.com/rfjakob/gocryptfs/internal/tlog"
)

Expand Down Expand Up @@ -60,16 +59,12 @@ func main() {

func dumpMasterKey(fn string) {
tlog.Info.Enabled = false
pw := readpassword.Once("", "")
masterkey, _, err := configfile.LoadConfFile(fn, pw)
masterkey, _, err := configfile.LoadConfFile(fn, true, "")
if err != nil {
fmt.Fprintln(os.Stderr, err)
exitcodes.Exit(err)
}
fmt.Println(hex.EncodeToString(masterkey))
for i := range pw {
pw[i] = 0
}
}

func inspectCiphertext(fd *os.File) {
Expand Down
2 changes: 2 additions & 0 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ Common Options (use -hh to show all):
-allow_other Allow other users to access the mount
-config Custom path to config file
-ctlsock Create control socket at location
-cryptowallet_encrypt_masterkey Encrypt master key through a hardware cryptowallet device (with -init)
-cryptowallet_keyname Set the key name for a cryptowallet device (default: "gocryptfs")
-extpass Call external program to prompt for the password
-fg Stay in the foreground
-fusedebug Debug FUSE calls
Expand Down
18 changes: 11 additions & 7 deletions init_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,19 @@ func initDir(args *argContainer) {
os.Exit(exitcodes.Init)
}
}
// Choose password for config file
if args.extpass == "" {
tlog.Info.Printf("Choose a password for protecting your files.")
}
{
creator := tlog.ProgramName + " " + GitVersion
password := readpassword.Twice(args.extpass)
readpassword.CheckTrailingGarbage()
err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator, args.aessiv, args.devrandom)
var password []byte
if !args.cryptowalletencryptmasterkey {
// Choose password for config file
if args.extpass == "" {
tlog.Info.Printf("Choose a password for protecting your files.")
}
password = readpassword.Twice(args.extpass)
readpassword.CheckTrailingGarbage()
}
err = configfile.CreateConfFile(args.config, password, args.plaintextnames, args.scryptn, creator,
args.aessiv, args.cryptowalletencryptmasterkey, args.cryptowalletkeyname, args.devrandom)
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.WriteConf)
Expand Down
66 changes: 55 additions & 11 deletions internal/configfile/config_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import (
"github.com/rfjakob/gocryptfs/internal/contentenc"
"github.com/rfjakob/gocryptfs/internal/cryptocore"
"github.com/rfjakob/gocryptfs/internal/exitcodes"
"github.com/rfjakob/gocryptfs/internal/readpassword"
"github.com/rfjakob/gocryptfs/internal/tlog"
"github.com/xaionaro-go/cryptoWallet"
)
import "os"

Expand Down Expand Up @@ -40,6 +42,8 @@ type ConfFile struct {
ScryptObject ScryptKDF
// Version is the On-Disk-Format version this filesystem uses
Version uint16
// CryptowalletKeyname is a string that is passed to a cryptowallet device as a key name
CryptowalletKeyname string
// FeatureFlags is a list of feature flags this filesystem has enabled.
// If gocryptfs encounters a feature flag it does not support, it will refuse
// mounting. This mechanism is analogous to the ext4 feature flags that are
Expand Down Expand Up @@ -67,7 +71,7 @@ func randBytesDevRandom(n int) []byte {
// CreateConfFile - create a new config with a random key encrypted with
// "password" and write it to "filename".
// Uses scrypt with cost parameter logN.
func CreateConfFile(filename string, password []byte, plaintextNames bool, logN int, creator string, aessiv bool, devrandom bool) error {
func CreateConfFile(filename string, password []byte, plaintextNames bool, logN int, creator string, aessiv bool, cryptowalletEncryptMasterkey bool, cryptowalletKeyname string, devrandom bool) error {
var cf ConfFile
cf.filename = filename
cf.Creator = creator
Expand All @@ -87,6 +91,11 @@ func CreateConfFile(filename string, password []byte, plaintextNames bool, logN
if aessiv {
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV])
}
if cryptowalletEncryptMasterkey {
cf.CryptowalletKeyname = cryptowalletKeyname
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagCryptowalletEncryptMasterkey])
}

{
// Generate new random master key
var key []byte
Expand All @@ -95,10 +104,15 @@ func CreateConfFile(filename string, password []byte, plaintextNames bool, logN
} else {
key = cryptocore.RandBytes(cryptocore.KeyLen)
}
// Encrypt it using the password
// This sets ScryptObject and EncryptedKey
// Note: this looks at the FeatureFlags, so call it AFTER setting them.
cf.EncryptKey(key, password, logN)
if cryptowalletEncryptMasterkey {
// Encrypt it using a cryptowallet device
cf.EncryptKeyByCryptowallet(key)
} else {
// Encrypt it using the password
// This sets ScryptObject and EncryptedKey
// Note: this looks at the FeatureFlags, so call it AFTER setting them.
cf.EncryptKeyByPassword(key, password, logN)
}
for i := range key {
key[i] = 0
}
Expand All @@ -108,13 +122,17 @@ func CreateConfFile(filename string, password []byte, plaintextNames bool, logN
return cf.WriteFile()
}

func getPin(title, description, ok, cancel string) ([]byte, error) {
return readpassword.Once("", title), nil
}

// LoadConfFile - read config file from disk and decrypt the
// contained key using "password".
// Returns the decrypted key and the ConfFile object
//
// If "password" is empty, the config file is read
// but the key is not decrypted (returns nil in its place).
func LoadConfFile(filename string, password []byte) ([]byte, *ConfFile, error) {
func LoadConfFile(filename string, retrieveMasterKey bool, extpass string) ([]byte, *ConfFile, error) {
var cf ConfFile
cf.filename = filename

Expand Down Expand Up @@ -171,12 +189,28 @@ func LoadConfFile(filename string, password []byte) ([]byte, *ConfFile, error) {

return nil, nil, exitcodes.NewErr("Deprecated filesystem", exitcodes.DeprecatedFS)
}
if len(password) == 0 {
// We have validated the config file, but without a password we cannot
// decrypt the master key. Return only the parsed config.

if !retrieveMasterKey {
return nil, &cf, nil
}

if cf.IsFeatureFlagSet(FlagCryptowalletEncryptMasterkey) {
// if `-cryptowallet_encrypt_masterkey` is enabled then the password is passed to a cryptowallet device
// directly (via pinentry) and we should ask for it here
wallet := cryptoWallet.FindAny()
wallet.SetGetPinFunc(getPin)
var key []byte
key, err = wallet.DecryptKey(cryptocore.CryptowalletBIPPath, cf.EncryptedKey, []byte{}, cf.CryptowalletKeyname)
return key, &cf, err
}

password := readpassword.Once(extpass, "")
defer func() {
for i := range password {
password[i] = 0
}
}()

// Generate derived key from password
scryptHash := cf.ScryptObject.DeriveKey(password)

Expand All @@ -195,11 +229,21 @@ func LoadConfFile(filename string, password []byte) ([]byte, *ConfFile, error) {
return key, &cf, err
}

// EncryptKey - encrypt "key" using an scrypt hash generated from "password"

func (cf *ConfFile) EncryptKeyByCryptowallet(key []byte) {
var err error
wallet := cryptoWallet.FindAny()
wallet.SetGetPinFunc(getPin)
cf.EncryptedKey, err = wallet.EncryptKey(cryptocore.CryptowalletBIPPath, key, []byte{}, cf.CryptowalletKeyname)
if err != nil {
log.Fatal(err)
}
}
// EncryptKeyByPassword - encrypt "key" using an scrypt hash generated from "password"
// and store it in cf.EncryptedKey.
// Uses scrypt with cost parameter logN and stores the scrypt parameters in
// cf.ScryptObject.
func (cf *ConfFile) EncryptKey(key []byte, password []byte, logN int) {
func (cf *ConfFile) EncryptKeyByPassword(key []byte, password []byte, logN int) {
// Generate scrypt-derived key from password
cf.ScryptObject = NewScryptKDF(logN)
scryptHash := cf.ScryptObject.DeriveKey(password)
Expand Down
26 changes: 13 additions & 13 deletions internal/configfile/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
"github.com/rfjakob/gocryptfs/internal/tlog"
)

var testPw = []byte("test")
var testPw = "test"

func TestLoadV1(t *testing.T) {
_, _, err := LoadConfFile("config_test/v1.conf", testPw)
_, _, err := LoadConfFile("config_test/v1.conf", true, "/bin/echo "+testPw)
if err == nil {
t.Errorf("Outdated v1 config file must fail to load but it didn't")
} else if testing.Verbose() {
Expand All @@ -24,7 +24,7 @@ func TestLoadV1(t *testing.T) {
func TestLoadV2(t *testing.T) {
t1 := time.Now()

_, _, err := LoadConfFile("config_test/v2.conf", testPw)
_, _, err := LoadConfFile("config_test/v2.conf", true, "/bin/echo "+testPw)
if err != nil {
t.Errorf("Could not load v2 config file: %v", err)
}
Expand All @@ -39,21 +39,21 @@ func TestLoadV2PwdError(t *testing.T) {
if !testing.Verbose() {
tlog.Warn.Enabled = false
}
_, _, err := LoadConfFile("config_test/v2.conf", []byte("wrongpassword"))
_, _, err := LoadConfFile("config_test/v2.conf", true, "/bin/echo wrongpassword")
if err == nil {
t.Errorf("Loading with wrong password must fail but it didn't")
}
}

func TestLoadV2Feature(t *testing.T) {
_, _, err := LoadConfFile("config_test/PlaintextNames.conf", testPw)
_, _, err := LoadConfFile("config_test/PlaintextNames.conf", true, "/bin/echo "+testPw)
if err != nil {
t.Errorf("Could not load v2 PlaintextNames config file: %v", err)
}
}

func TestLoadV2StrangeFeature(t *testing.T) {
_, _, err := LoadConfFile("config_test/StrangeFeature.conf", testPw)
_, _, err := LoadConfFile("config_test/StrangeFeature.conf", true, "/bin/echo "+testPw)
if err == nil {
t.Errorf("Loading unknown feature must fail but it didn't")
} else if testing.Verbose() {
Expand All @@ -62,11 +62,11 @@ func TestLoadV2StrangeFeature(t *testing.T) {
}

func TestCreateConfDefault(t *testing.T) {
err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", false, false)
err := CreateConfFile("config_test/tmp.conf", []byte(testPw), false, 10, "test", false, false, "", false)
if err != nil {
t.Fatal(err)
}
_, c, err := LoadConfFile("config_test/tmp.conf", testPw)
_, c, err := LoadConfFile("config_test/tmp.conf", true, "/bin/echo "+testPw)
if err != nil {
t.Fatal(err)
}
Expand All @@ -83,18 +83,18 @@ func TestCreateConfDefault(t *testing.T) {
}

func TestCreateConfDevRandom(t *testing.T) {
err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", false, true)
err := CreateConfFile("config_test/tmp.conf", []byte(testPw), false, 10, "test", false, false, "", true)
if err != nil {
t.Fatal(err)
}
}

func TestCreateConfPlaintextnames(t *testing.T) {
err := CreateConfFile("config_test/tmp.conf", testPw, true, 10, "test", false, false)
err := CreateConfFile("config_test/tmp.conf", []byte(testPw), true, 10, "test", false, false, "", false)
if err != nil {
t.Fatal(err)
}
_, c, err := LoadConfFile("config_test/tmp.conf", testPw)
_, c, err := LoadConfFile("config_test/tmp.conf", true, "/bin/echo "+testPw)
if err != nil {
t.Fatal(err)
}
Expand All @@ -111,11 +111,11 @@ func TestCreateConfPlaintextnames(t *testing.T) {

// Reverse mode uses AESSIV
func TestCreateConfFileAESSIV(t *testing.T) {
err := CreateConfFile("config_test/tmp.conf", testPw, false, 10, "test", true, false)
err := CreateConfFile("config_test/tmp.conf", []byte(testPw), false, 10, "test", true, false, "", false)
if err != nil {
t.Fatal(err)
}
_, c, err := LoadConfFile("config_test/tmp.conf", testPw)
_, c, err := LoadConfFile("config_test/tmp.conf", true, "/bin/echo "+testPw)
if err != nil {
t.Fatal(err)
}
Expand Down
19 changes: 11 additions & 8 deletions internal/configfile/feature_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const (
FlagLongNames
// FlagAESSIV selects an AES-SIV based crypto backend.
FlagAESSIV
// FlagCryptowalletEncrypEncrypttMasterkey additionally encrypt master key using "Cryptowallet"
FlagCryptowalletEncryptMasterkey
// FlagRaw64 enables raw (unpadded) base64 encoding for file names
FlagRaw64
// FlagHKDF enables HKDF-derived keys for use with GCM, EME and SIV
Expand All @@ -29,14 +31,15 @@ const (

// knownFlags stores the known feature flags and their string representation
var knownFlags = map[flagIota]string{
FlagPlaintextNames: "PlaintextNames",
FlagDirIV: "DirIV",
FlagEMENames: "EMENames",
FlagGCMIV128: "GCMIV128",
FlagLongNames: "LongNames",
FlagAESSIV: "AESSIV",
FlagRaw64: "Raw64",
FlagHKDF: "HKDF",
FlagPlaintextNames: "PlaintextNames",
FlagDirIV: "DirIV",
FlagEMENames: "EMENames",
FlagGCMIV128: "GCMIV128",
FlagLongNames: "LongNames",
FlagAESSIV: "AESSIV",
FlagCryptowalletEncryptMasterkey: "CryptowalletEncryptMasterkey",
FlagRaw64: "Raw64",
FlagHKDF: "HKDF",
}

// Filesystems that do not have these feature flags set are deprecated.
Expand Down
2 changes: 1 addition & 1 deletion internal/configfile/scrypt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ ok github.com/rfjakob/gocryptfs/cryptfs 18.772s
func benchmarkScryptN(n int, b *testing.B) {
kdf := NewScryptKDF(n)
for i := 0; i < b.N; i++ {
kdf.DeriveKey(testPw)
kdf.DeriveKey([]byte(testPw))
}
}

Expand Down
Loading