Add support for FIDO2 tokens
This commit is contained in:
parent
6a9c49e9cf
commit
1e624a4cc3
|
@ -130,6 +130,10 @@ to your program, use `"--"`, which is accepted by most programs:
|
||||||
Stay in the foreground instead of forking away. Implies "-nosyslog".
|
Stay in the foreground instead of forking away. Implies "-nosyslog".
|
||||||
For compatibility, "-f" is also accepted, but "-fg" is preferred.
|
For compatibility, "-f" is also accepted, but "-fg" is preferred.
|
||||||
|
|
||||||
|
#### -fido2 DEVICE_PATH
|
||||||
|
Use a FIDO2 token to initialize and unlock the filesystem.
|
||||||
|
Use "fido2-token -L" to obtain the FIDO2 token device path.
|
||||||
|
|
||||||
#### -force_owner string
|
#### -force_owner string
|
||||||
If given a string of the form "uid:gid" (where both "uid" and "gid" are
|
If given a string of the form "uid:gid" (where both "uid" and "gid" are
|
||||||
substituted with positive integers), presents all files as owned by the given
|
substituted with positive integers), presents all files as owned by the given
|
||||||
|
|
|
@ -32,7 +32,7 @@ type argContainer struct {
|
||||||
// Mount options with opposites
|
// Mount options with opposites
|
||||||
dev, nodev, suid, nosuid, exec, noexec, rw, ro bool
|
dev, nodev, suid, nosuid, exec, noexec, rw, ro bool
|
||||||
masterkey, mountpoint, cipherdir, cpuprofile,
|
masterkey, mountpoint, cipherdir, cpuprofile,
|
||||||
memprofile, ko, ctlsock, fsname, force_owner, trace string
|
memprofile, ko, ctlsock, fsname, force_owner, trace, fido2 string
|
||||||
// -extpass, -badname, -passfile can be passed multiple times
|
// -extpass, -badname, -passfile can be passed multiple times
|
||||||
extpass, badname, passfile multipleStrings
|
extpass, badname, passfile multipleStrings
|
||||||
// For reverse mode, several ways to specify exclusions. All can be specified multiple times.
|
// For reverse mode, several ways to specify exclusions. All can be specified multiple times.
|
||||||
|
@ -189,6 +189,7 @@ func parseCliOpts() (args argContainer) {
|
||||||
flagSet.StringVar(&args.fsname, "fsname", "", "Override the filesystem name")
|
flagSet.StringVar(&args.fsname, "fsname", "", "Override the filesystem name")
|
||||||
flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership")
|
flagSet.StringVar(&args.force_owner, "force_owner", "", "uid:gid pair to coerce ownership")
|
||||||
flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file")
|
flagSet.StringVar(&args.trace, "trace", "", "Write execution trace to file")
|
||||||
|
flagSet.StringVar(&args.fido2, "fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
|
||||||
|
|
||||||
// Exclusion options
|
// Exclusion options
|
||||||
flagSet.Var(&args.exclude, "e", "Alias for -exclude")
|
flagSet.Var(&args.exclude, "e", "Alias for -exclude")
|
||||||
|
@ -279,6 +280,10 @@ func parseCliOpts() (args argContainer) {
|
||||||
tlog.Fatal.Printf("The options -extpass and -masterkey cannot be used at the same time")
|
tlog.Fatal.Printf("The options -extpass and -masterkey cannot be used at the same time")
|
||||||
os.Exit(exitcodes.Usage)
|
os.Exit(exitcodes.Usage)
|
||||||
}
|
}
|
||||||
|
if !args.extpass.Empty() && args.fido2 != "" {
|
||||||
|
tlog.Fatal.Printf("The options -extpass and -fido2 cannot be used at the same time")
|
||||||
|
os.Exit(exitcodes.Usage)
|
||||||
|
}
|
||||||
if args.idle < 0 {
|
if args.idle < 0 {
|
||||||
tlog.Fatal.Printf("Idle timeout cannot be less than 0")
|
tlog.Fatal.Printf("Idle timeout cannot be less than 0")
|
||||||
os.Exit(exitcodes.Usage)
|
os.Exit(exitcodes.Usage)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/fido2"
|
||||||
"github.com/rfjakob/gocryptfs/internal/readpassword"
|
"github.com/rfjakob/gocryptfs/internal/readpassword"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
)
|
)
|
||||||
|
@ -67,12 +68,14 @@ func main() {
|
||||||
encryptPaths *bool
|
encryptPaths *bool
|
||||||
aessiv *bool
|
aessiv *bool
|
||||||
sep0 *bool
|
sep0 *bool
|
||||||
|
fido2 *string
|
||||||
}
|
}
|
||||||
args.dumpmasterkey = flag.Bool("dumpmasterkey", false, "Decrypt and dump the master key")
|
args.dumpmasterkey = flag.Bool("dumpmasterkey", false, "Decrypt and dump the master key")
|
||||||
args.decryptPaths = flag.Bool("decrypt-paths", false, "Decrypt file paths using gocryptfs control socket")
|
args.decryptPaths = flag.Bool("decrypt-paths", false, "Decrypt file paths using gocryptfs control socket")
|
||||||
args.encryptPaths = flag.Bool("encrypt-paths", false, "Encrypt file paths using gocryptfs control socket")
|
args.encryptPaths = flag.Bool("encrypt-paths", false, "Encrypt file paths using gocryptfs control socket")
|
||||||
args.sep0 = flag.Bool("0", false, "Use \\0 instead of \\n as separator")
|
args.sep0 = flag.Bool("0", false, "Use \\0 instead of \\n as separator")
|
||||||
args.aessiv = flag.Bool("aessiv", false, "Assume AES-SIV mode instead of AES-GCM")
|
args.aessiv = flag.Bool("aessiv", false, "Assume AES-SIV mode instead of AES-GCM")
|
||||||
|
args.fido2 = flag.String("fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
|
||||||
flag.Usage = usage
|
flag.Usage = usage
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
s := sum(args.dumpmasterkey, args.decryptPaths, args.encryptPaths)
|
s := sum(args.dumpmasterkey, args.decryptPaths, args.encryptPaths)
|
||||||
|
@ -97,20 +100,30 @@ func main() {
|
||||||
}
|
}
|
||||||
defer fd.Close()
|
defer fd.Close()
|
||||||
if *args.dumpmasterkey {
|
if *args.dumpmasterkey {
|
||||||
dumpMasterKey(fn)
|
dumpMasterKey(fn, *args.fido2)
|
||||||
} else {
|
} else {
|
||||||
inspectCiphertext(fd, *args.aessiv)
|
inspectCiphertext(fd, *args.aessiv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func dumpMasterKey(fn string) {
|
func dumpMasterKey(fn string, fido2Path string) {
|
||||||
tlog.Info.Enabled = false
|
tlog.Info.Enabled = false
|
||||||
pw := readpassword.Once(nil, nil, "")
|
cf, err := configfile.Load(fn)
|
||||||
masterkey, _, err := configfile.LoadAndDecrypt(fn, pw)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
exitcodes.Exit(err)
|
exitcodes.Exit(err)
|
||||||
}
|
}
|
||||||
|
var pw []byte
|
||||||
|
if cf.IsFeatureFlagSet(configfile.FlagFIDO2) {
|
||||||
|
if fido2Path == "" {
|
||||||
|
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2 option.")
|
||||||
|
os.Exit(exitcodes.Usage)
|
||||||
|
}
|
||||||
|
pw = fido2.Secret(fido2Path, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
|
||||||
|
} else {
|
||||||
|
pw = readpassword.Once(nil, nil, "")
|
||||||
|
}
|
||||||
|
masterkey, err := cf.DecryptMasterKey(pw)
|
||||||
fmt.Println(hex.EncodeToString(masterkey))
|
fmt.Println(hex.EncodeToString(masterkey))
|
||||||
for i := range pw {
|
for i := range pw {
|
||||||
pw[i] = 0
|
pw[i] = 0
|
||||||
|
|
19
init_dir.go
19
init_dir.go
|
@ -9,7 +9,9 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/fido2"
|
||||||
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
"github.com/rfjakob/gocryptfs/internal/nametransform"
|
||||||
"github.com/rfjakob/gocryptfs/internal/readpassword"
|
"github.com/rfjakob/gocryptfs/internal/readpassword"
|
||||||
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
"github.com/rfjakob/gocryptfs/internal/syscallcompat"
|
||||||
|
@ -67,14 +69,25 @@ func initDir(args *argContainer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Choose password for config file
|
// Choose password for config file
|
||||||
if args.extpass.Empty() {
|
if args.extpass.Empty() && args.fido2 == "" {
|
||||||
tlog.Info.Printf("Choose a password for protecting your files.")
|
tlog.Info.Printf("Choose a password for protecting your files.")
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
password := readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
var password []byte
|
||||||
|
var fido2CredentialID, fido2HmacSalt []byte
|
||||||
|
if args.fido2 != "" {
|
||||||
|
fido2CredentialID = fido2.Register(args.fido2, filepath.Base(args.cipherdir))
|
||||||
|
fido2HmacSalt = cryptocore.RandBytes(32)
|
||||||
|
password = fido2.Secret(args.fido2, fido2CredentialID, fido2HmacSalt)
|
||||||
|
} else {
|
||||||
|
// normal password entry
|
||||||
|
password = readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
||||||
|
fido2CredentialID = nil
|
||||||
|
fido2HmacSalt = nil
|
||||||
|
}
|
||||||
creator := tlog.ProgramName + " " + GitVersion
|
creator := tlog.ProgramName + " " + GitVersion
|
||||||
err = configfile.Create(args.config, password, args.plaintextnames,
|
err = configfile.Create(args.config, password, args.plaintextnames,
|
||||||
args.scryptn, creator, args.aessiv, args.devrandom)
|
args.scryptn, creator, args.aessiv, args.devrandom, fido2CredentialID, fido2HmacSalt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Fatal.Println(err)
|
tlog.Fatal.Println(err)
|
||||||
os.Exit(exitcodes.WriteConf)
|
os.Exit(exitcodes.WriteConf)
|
||||||
|
|
|
@ -10,12 +10,13 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||||
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
)
|
)
|
||||||
import "os"
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ConfDefaultName is the default configuration file name.
|
// ConfDefaultName is the default configuration file name.
|
||||||
|
@ -28,6 +29,14 @@ const (
|
||||||
ConfReverseName = ".gocryptfs.reverse.conf"
|
ConfReverseName = ".gocryptfs.reverse.conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// FIDO2Params is a structure for storing FIDO2 parameters.
|
||||||
|
type FIDO2Params struct {
|
||||||
|
// FIDO2 credential
|
||||||
|
CredentialID []byte
|
||||||
|
// FIDO2 hmac-secret salt
|
||||||
|
HMACSalt []byte
|
||||||
|
}
|
||||||
|
|
||||||
// ConfFile is the content of a config file.
|
// ConfFile is the content of a config file.
|
||||||
type ConfFile struct {
|
type ConfFile struct {
|
||||||
// Creator is the gocryptfs version string.
|
// Creator is the gocryptfs version string.
|
||||||
|
@ -46,6 +55,8 @@ type ConfFile struct {
|
||||||
// mounting. This mechanism is analogous to the ext4 feature flags that are
|
// mounting. This mechanism is analogous to the ext4 feature flags that are
|
||||||
// stored in the superblock.
|
// stored in the superblock.
|
||||||
FeatureFlags []string
|
FeatureFlags []string
|
||||||
|
// FIDO2 parameters
|
||||||
|
FIDO2 FIDO2Params
|
||||||
// Filename is the name of the config file. Not exported to JSON.
|
// Filename is the name of the config file. Not exported to JSON.
|
||||||
filename string
|
filename string
|
||||||
}
|
}
|
||||||
|
@ -69,7 +80,7 @@ func randBytesDevRandom(n int) []byte {
|
||||||
// "password" and write it to "filename".
|
// "password" and write it to "filename".
|
||||||
// Uses scrypt with cost parameter logN.
|
// Uses scrypt with cost parameter logN.
|
||||||
func Create(filename string, password []byte, plaintextNames bool,
|
func Create(filename string, password []byte, plaintextNames bool,
|
||||||
logN int, creator string, aessiv bool, devrandom bool) error {
|
logN int, creator string, aessiv bool, devrandom bool, fido2CredentialID []byte, fido2HmacSalt []byte) error {
|
||||||
var cf ConfFile
|
var cf ConfFile
|
||||||
cf.filename = filename
|
cf.filename = filename
|
||||||
cf.Creator = creator
|
cf.Creator = creator
|
||||||
|
@ -89,6 +100,11 @@ func Create(filename string, password []byte, plaintextNames bool,
|
||||||
if aessiv {
|
if aessiv {
|
||||||
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV])
|
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagAESSIV])
|
||||||
}
|
}
|
||||||
|
if len(fido2CredentialID) > 0 {
|
||||||
|
cf.FeatureFlags = append(cf.FeatureFlags, knownFlags[FlagFIDO2])
|
||||||
|
cf.FIDO2.CredentialID = fido2CredentialID
|
||||||
|
cf.FIDO2.HMACSalt = fido2HmacSalt
|
||||||
|
}
|
||||||
{
|
{
|
||||||
// Generate new random master key
|
// Generate new random master key
|
||||||
var key []byte
|
var key []byte
|
||||||
|
|
|
@ -62,7 +62,7 @@ func TestLoadV2StrangeFeature(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateConfDefault(t *testing.T) {
|
func TestCreateConfDefault(t *testing.T) {
|
||||||
err := Create("config_test/tmp.conf", testPw, false, 10, "test", false, false)
|
err := Create("config_test/tmp.conf", testPw, false, 10, "test", false, false, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -83,14 +83,14 @@ func TestCreateConfDefault(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateConfDevRandom(t *testing.T) {
|
func TestCreateConfDevRandom(t *testing.T) {
|
||||||
err := Create("config_test/tmp.conf", testPw, false, 10, "test", false, true)
|
err := Create("config_test/tmp.conf", testPw, false, 10, "test", false, true, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateConfPlaintextnames(t *testing.T) {
|
func TestCreateConfPlaintextnames(t *testing.T) {
|
||||||
err := Create("config_test/tmp.conf", testPw, true, 10, "test", false, false)
|
err := Create("config_test/tmp.conf", testPw, true, 10, "test", false, false, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,7 @@ func TestCreateConfPlaintextnames(t *testing.T) {
|
||||||
|
|
||||||
// Reverse mode uses AESSIV
|
// Reverse mode uses AESSIV
|
||||||
func TestCreateConfFileAESSIV(t *testing.T) {
|
func TestCreateConfFileAESSIV(t *testing.T) {
|
||||||
err := Create("config_test/tmp.conf", testPw, false, 10, "test", true, false)
|
err := Create("config_test/tmp.conf", testPw, false, 10, "test", true, false, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,9 @@ const (
|
||||||
// Note that this flag does not change the password hashing algorithm
|
// Note that this flag does not change the password hashing algorithm
|
||||||
// which always is scrypt.
|
// which always is scrypt.
|
||||||
FlagHKDF
|
FlagHKDF
|
||||||
|
// FlagFIDO2 means that "-fido2" was used when creating the filesystem.
|
||||||
|
// The masterkey is protected using a FIDO2 token instead of a password.
|
||||||
|
FlagFIDO2
|
||||||
)
|
)
|
||||||
|
|
||||||
// knownFlags stores the known feature flags and their string representation
|
// knownFlags stores the known feature flags and their string representation
|
||||||
|
@ -37,6 +40,7 @@ var knownFlags = map[flagIota]string{
|
||||||
FlagAESSIV: "AESSIV",
|
FlagAESSIV: "AESSIV",
|
||||||
FlagRaw64: "Raw64",
|
FlagRaw64: "Raw64",
|
||||||
FlagHKDF: "HKDF",
|
FlagHKDF: "HKDF",
|
||||||
|
FlagFIDO2: "FIDO2",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Filesystems that do not have these feature flags set are deprecated.
|
// Filesystems that do not have these feature flags set are deprecated.
|
||||||
|
|
|
@ -70,6 +70,8 @@ const (
|
||||||
ExcludeError = 29
|
ExcludeError = 29
|
||||||
// DevNull means that /dev/null could not be opened
|
// DevNull means that /dev/null could not be opened
|
||||||
DevNull = 30
|
DevNull = 30
|
||||||
|
// FIDO2Error - an error was encountered while interacting with a FIDO2 token
|
||||||
|
FIDO2Error = 31
|
||||||
)
|
)
|
||||||
|
|
||||||
// Err wraps an error with an associated numeric exit code
|
// Err wraps an error with an associated numeric exit code
|
||||||
|
|
|
@ -0,0 +1,110 @@
|
||||||
|
package fido2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/cryptocore"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fidoCommand int
|
||||||
|
|
||||||
|
const (
|
||||||
|
cred fidoCommand = iota
|
||||||
|
assert fidoCommand = iota
|
||||||
|
assertWithPIN fidoCommand = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
const relyingPartyID = "gocryptfs"
|
||||||
|
|
||||||
|
func callFidoCommand(command fidoCommand, device string, stdin []string) ([]string, error) {
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
switch command {
|
||||||
|
case cred:
|
||||||
|
cmd = exec.Command("fido2-cred", "-M", "-h", "-v", device)
|
||||||
|
case assert:
|
||||||
|
cmd = exec.Command("fido2-assert", "-G", "-h", device)
|
||||||
|
case assertWithPIN:
|
||||||
|
cmd = exec.Command("fido2-assert", "-G", "-h", "-v", device)
|
||||||
|
}
|
||||||
|
tlog.Debug.Printf("callFidoCommand: executing %q with args %v", cmd.Path, cmd.Args)
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
in, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, s := range stdin {
|
||||||
|
// This does not deadlock because the pipe buffer is big enough (64kiB on Linux)
|
||||||
|
io.WriteString(in, s+"\n")
|
||||||
|
}
|
||||||
|
in.Close()
|
||||||
|
out, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%s failed with %v", cmd.Args[0], err)
|
||||||
|
}
|
||||||
|
return strings.Split(string(out), "\n"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register registers a credential using a FIDO2 token
|
||||||
|
func Register(device string, userName string) (credentialID []byte) {
|
||||||
|
tlog.Info.Printf("FIDO2 Register: interact with your device ...")
|
||||||
|
cdh := base64.StdEncoding.EncodeToString(cryptocore.RandBytes(32))
|
||||||
|
userID := base64.StdEncoding.EncodeToString(cryptocore.RandBytes(32))
|
||||||
|
stdin := []string{cdh, relyingPartyID, userName, userID}
|
||||||
|
out, err := callFidoCommand(cred, device, stdin)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
os.Exit(exitcodes.FIDO2Error)
|
||||||
|
}
|
||||||
|
credentialID, err = base64.StdEncoding.DecodeString(out[4])
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
os.Exit(exitcodes.FIDO2Error)
|
||||||
|
}
|
||||||
|
return credentialID
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secret generates a HMAC secret using a FIDO2 token
|
||||||
|
func Secret(device string, credentialID []byte, salt []byte) (secret []byte) {
|
||||||
|
tlog.Info.Printf("FIDO2 Secret: interact with your device ...")
|
||||||
|
cdh := base64.StdEncoding.EncodeToString(cryptocore.RandBytes(32))
|
||||||
|
crid := base64.StdEncoding.EncodeToString(credentialID)
|
||||||
|
hmacsalt := base64.StdEncoding.EncodeToString(salt)
|
||||||
|
stdin := []string{cdh, relyingPartyID, crid, hmacsalt}
|
||||||
|
// try asserting without PIN first
|
||||||
|
out, err := callFidoCommand(assert, device, stdin)
|
||||||
|
if err != nil {
|
||||||
|
// if that fails, let's assert with PIN
|
||||||
|
out, err = callFidoCommand(assertWithPIN, device, stdin)
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
os.Exit(exitcodes.FIDO2Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secret, err = base64.StdEncoding.DecodeString(out[4])
|
||||||
|
if err != nil {
|
||||||
|
tlog.Fatal.Println(err)
|
||||||
|
os.Exit(exitcodes.FIDO2Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity checks
|
||||||
|
secretLen := len(secret)
|
||||||
|
if secretLen < 32 {
|
||||||
|
tlog.Fatal.Printf("FIDO2 HMACSecret too short (%d)!\n", secretLen)
|
||||||
|
os.Exit(exitcodes.FIDO2Error)
|
||||||
|
}
|
||||||
|
zero := make([]byte, secretLen)
|
||||||
|
if bytes.Equal(zero, secret) {
|
||||||
|
tlog.Fatal.Printf("FIDO2 HMACSecret is all zero!")
|
||||||
|
os.Exit(exitcodes.FIDO2Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return secret
|
||||||
|
}
|
16
main.go
16
main.go
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/rfjakob/gocryptfs/internal/configfile"
|
"github.com/rfjakob/gocryptfs/internal/configfile"
|
||||||
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
"github.com/rfjakob/gocryptfs/internal/contentenc"
|
||||||
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
"github.com/rfjakob/gocryptfs/internal/exitcodes"
|
||||||
|
"github.com/rfjakob/gocryptfs/internal/fido2"
|
||||||
"github.com/rfjakob/gocryptfs/internal/readpassword"
|
"github.com/rfjakob/gocryptfs/internal/readpassword"
|
||||||
"github.com/rfjakob/gocryptfs/internal/speed"
|
"github.com/rfjakob/gocryptfs/internal/speed"
|
||||||
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
|
||||||
|
@ -50,7 +51,16 @@ func loadConfig(args *argContainer) (masterkey []byte, cf *configfile.ConfFile,
|
||||||
if masterkey != nil {
|
if masterkey != nil {
|
||||||
return masterkey, cf, nil
|
return masterkey, cf, nil
|
||||||
}
|
}
|
||||||
pw := readpassword.Once([]string(args.extpass), []string(args.passfile), "")
|
var pw []byte
|
||||||
|
if cf.IsFeatureFlagSet(configfile.FlagFIDO2) {
|
||||||
|
if args.fido2 == "" {
|
||||||
|
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2 option.")
|
||||||
|
os.Exit(exitcodes.Usage)
|
||||||
|
}
|
||||||
|
pw = fido2.Secret(args.fido2, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
|
||||||
|
} else {
|
||||||
|
pw = readpassword.Once([]string(args.extpass), []string(args.passfile), "")
|
||||||
|
}
|
||||||
tlog.Info.Println("Decrypting master key")
|
tlog.Info.Println("Decrypting master key")
|
||||||
masterkey, err = cf.DecryptMasterKey(pw)
|
masterkey, err = cf.DecryptMasterKey(pw)
|
||||||
for i := range pw {
|
for i := range pw {
|
||||||
|
@ -78,6 +88,10 @@ func changePassword(args *argContainer) {
|
||||||
if len(masterkey) == 0 {
|
if len(masterkey) == 0 {
|
||||||
log.Panic("empty masterkey")
|
log.Panic("empty masterkey")
|
||||||
}
|
}
|
||||||
|
if confFile.IsFeatureFlagSet(configfile.FlagFIDO2) {
|
||||||
|
tlog.Fatal.Printf("Password change is not supported on FIDO2-enabled filesystems.")
|
||||||
|
os.Exit(exitcodes.Usage)
|
||||||
|
}
|
||||||
tlog.Info.Println("Please enter your new password.")
|
tlog.Info.Println("Please enter your new password.")
|
||||||
newPw := readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
newPw := readpassword.Twice([]string(args.extpass), []string(args.passfile))
|
||||||
logN := confFile.ScryptObject.LogN()
|
logN := confFile.ScryptObject.LogN()
|
||||||
|
|
Loading…
Reference in New Issue