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

resolves #702 add fido2 up/uv/pin support #705

Closed
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
13 changes: 12 additions & 1 deletion Documentation/MANPAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,12 +475,23 @@ BUG: In `-extpass -X`, the `-X` will be interpreted as `--X`. Please use
`-extpass=-X` to prevent that. See **Dash duplication** in the **BUGS** section
for details.

#### -fido2 DEVICE_PATH
#### -fido2-device DEVICE_PATH
Use a FIDO2 token to initialize and unlock the filesystem.
Use "fido2-token -L" to obtain the FIDO2 token device path.

Applies to: all actions that ask for a password.

### -fido2-with-user-presence bool
Request user presence, e.g. a press of a button.

### -fido2-with-user-presence bool
Request user verification, e.g. fingerprint.
Not supported by all tokens.
Use "fido2-token -I" to check supported options.

### -fido2-with-client-pin bool
Request token pin to be entered.

#### -masterkey string
Use a explicit master key specified on the command line or, if the special
value "stdin" is used, read the masterkey from stdin, instead of reading
Expand Down
16 changes: 11 additions & 5 deletions cli_args.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ type argContainer struct {
sharedstorage, fsck, one_file_system, deterministic_names,
xchacha bool
// Mount options with opposites
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl bool
dev, nodev, suid, nosuid, exec, noexec, rw, ro, kernel_cache, acl, fido2_with_pin, fido2_with_uv, fido2_with_up bool
masterkey, mountpoint, cipherdir, cpuprofile,
memprofile, ko, ctlsock, fsname, force_owner, trace, fido2 string
memprofile, ko, ctlsock, fsname, force_owner, trace, fido2_device string
// -extpass, -badname, -passfile can be passed multiple times
extpass, badname, passfile []string
// For reverse mode, several ways to specify exclusions. All can be specified multiple times.
Expand Down Expand Up @@ -207,7 +207,13 @@ func parseCliOpts(osArgs []string) (args argContainer) {
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.trace, "trace", "", "Write execution trace to file")
flagSet.StringVar(&args.fido2, "fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
flagSet.StringVar(&args.fido2_device, "fido2-device", "", "Protect the masterkey using a FIDO2 token instead of a password")
flagSet.BoolVar(&args.fido2_with_up, "fido2-with-user-presence", true, "Request user presence before using the FIDO2 token")
flagSet.Lookup("fido2-with-user-presence").NoOptDefVal = "true"
flagSet.BoolVar(&args.fido2_with_uv, "fido2-with-user-verification", false, "Request user verification before using the FIDO2 token")
flagSet.Lookup("fido2-with-user-verification").NoOptDefVal = "true"
flagSet.BoolVar(&args.fido2_with_pin, "fido2-with-client-pin", true, "Request token PIN before using the FIDO2 token")
flagSet.Lookup("fido2-with-client-pin").NoOptDefVal = "true"

// Exclusion options
flagSet.StringSliceVar(&args.exclude, "e", nil, "Alias for -exclude")
Expand Down Expand Up @@ -284,8 +290,8 @@ func parseCliOpts(osArgs []string) (args argContainer) {
tlog.Fatal.Printf("The options -extpass and -masterkey cannot be used at the same time")
os.Exit(exitcodes.Usage)
}
if len(args.extpass) > 0 && args.fido2 != "" {
tlog.Fatal.Printf("The options -extpass and -fido2 cannot be used at the same time")
if len(args.extpass) > 0 && args.fido2_device != "" {
tlog.Fatal.Printf("The options -extpass and -fido2-device cannot be used at the same time")
os.Exit(exitcodes.Usage)
}
if args.idle < 0 {
Expand Down
10 changes: 5 additions & 5 deletions gocryptfs-xray/xray_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ type argContainer struct {
aessiv *bool
xchacha *bool
sep0 *bool
fido2 *string
fido2Device *string
version *bool
}

Expand All @@ -94,7 +94,7 @@ func main() {
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.xchacha = flag.Bool("xchacha", false, "Assume XChaCha20-Poly1305 mode instead of AES-GCM")
args.fido2 = flag.String("fido2", "", "Protect the masterkey using a FIDO2 token instead of a password")
args.fido2Device = flag.String("fido2-device", "", "Protect the masterkey using a FIDO2 token instead of a password")
args.version = flag.Bool("version", false, "Print version information")

flag.Usage = usage
Expand Down Expand Up @@ -127,7 +127,7 @@ func main() {
}
defer f.Close()
if *args.dumpmasterkey {
dumpMasterKey(fn, *args.fido2)
dumpMasterKey(fn, *args.fido2Device)
} else {
inspectCiphertext(&args, f)
}
Expand All @@ -143,10 +143,10 @@ func dumpMasterKey(fn string, fido2Path string) {
var pw []byte
if cf.IsFeatureFlagSet(configfile.FlagFIDO2) {
if fido2Path == "" {
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2 option.")
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2-device option.")
os.Exit(exitcodes.Usage)
}
pw = fido2.Secret(fido2Path, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
pw = fido2.Secret(fido2Path, cf.FIDO2.UseFlags, cf.FIDO2.UpRequired, cf.FIDO2.UvRequired, cf.FIDO2.PinRequired, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
} else {
pw, err = readpassword.Once(nil, nil, "")
if err != nil {
Expand Down
11 changes: 7 additions & 4 deletions init_dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,16 @@ func initDir(args *argContainer) {
}
}
// Choose password for config file
if len(args.extpass) == 0 && args.fido2 == "" {
if len(args.extpass) == 0 && args.fido2_device == "" {
tlog.Info.Printf("Choose a password for protecting your files.")
}
{
var password []byte
var fido2CredentialID, fido2HmacSalt []byte
if args.fido2 != "" {
fido2CredentialID = fido2.Register(args.fido2, filepath.Base(args.cipherdir))
if args.fido2_device != "" {
fido2CredentialID = fido2.Register(args.fido2_device, filepath.Base(args.cipherdir))
fido2HmacSalt = cryptocore.RandBytes(32)
password = fido2.Secret(args.fido2, fido2CredentialID, fido2HmacSalt)
password = fido2.Secret(args.fido2_device, true, args.fido2_with_up, args.fido2_with_uv, args.fido2_with_pin, fido2CredentialID, fido2HmacSalt)
} else {
// normal password entry
password, err = readpassword.Twice([]string(args.extpass), []string(args.passfile))
Expand All @@ -105,6 +105,9 @@ func initDir(args *argContainer) {
AESSIV: args.aessiv,
Fido2CredentialID: fido2CredentialID,
Fido2HmacSalt: fido2HmacSalt,
Fido2Up: args.fido2_with_up,
Fido2Uv: args.fido2_with_uv,
Fido2Pin: args.fido2_with_pin,
DeterministicNames: args.deterministic_names,
XChaCha20Poly1305: args.xchacha,
LongNameMax: args.longnamemax,
Expand Down
12 changes: 12 additions & 0 deletions internal/configfile/config_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ type FIDO2Params struct {
CredentialID []byte
// FIDO2 hmac-secret salt
HMACSalt []byte
// to detect if config was created with up/uv/pin flags
UseFlags bool
UpRequired bool
UvRequired bool
PinRequired bool
}

// ConfFile is the content of a config file.
Expand Down Expand Up @@ -71,6 +76,9 @@ type CreateArgs struct {
AESSIV bool
Fido2CredentialID []byte
Fido2HmacSalt []byte
Fido2Up bool
Fido2Uv bool
Fido2Pin bool
DeterministicNames bool
XChaCha20Poly1305 bool
LongNameMax uint8
Expand Down Expand Up @@ -118,6 +126,10 @@ func Create(args *CreateArgs) error {
cf.FIDO2 = &FIDO2Params{
CredentialID: args.Fido2CredentialID,
HMACSalt: args.Fido2HmacSalt,
UseFlags: true,
UpRequired: args.Fido2Up,
UvRequired: args.Fido2Uv,
PinRequired: args.Fido2Pin,
}
}
// Catch bugs and invalid cli flag combinations early
Expand Down
17 changes: 12 additions & 5 deletions internal/fido2/fido2.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,20 @@ func (fc fidoCommand) String() string {

const relyingPartyID = "gocryptfs"

func callFidoCommand(command fidoCommand, device string, stdin []string) ([]string, error) {
func callFidoCommand(command fidoCommand, useFlags bool, userPresence bool, userVerification bool, clientPin bool, device string, stdin []string) ([]string, error) {
var cmd *exec.Cmd
switch command {
case cred:
cmd = exec.Command("fido2-cred", "-M", "-h", device)
case assert:
cmd = exec.Command("fido2-assert", "-G", "-h", device)
if useFlags {
up := fmt.Sprintf("up=%v", userPresence)
uv := fmt.Sprintf("uv=%v", userVerification)
pin := fmt.Sprintf("pin=%v", clientPin);
cmd = exec.Command("fido2-assert", "-G", "-h", "-t", up, "-t", uv, "-t", pin, device)
} else {
cmd = exec.Command("fido2-assert", "-G", "-h", device)
}
}
tlog.Debug.Printf("callFidoCommand %s: executing %q with args %q", command, cmd.Path, cmd.Args)
cmd.Stderr = os.Stderr
Expand All @@ -67,7 +74,7 @@ func Register(device string, userName string) (credentialID []byte) {
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)
out, err := callFidoCommand(cred, false, false, false, false, device, stdin)
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.FIDO2Error)
Expand All @@ -81,14 +88,14 @@ func Register(device string, userName string) (credentialID []byte) {
}

// Secret generates a HMAC secret using a FIDO2 token
func Secret(device string, credentialID []byte, salt []byte) (secret []byte) {
func Secret(device string, useFlags bool, userPresence bool, userVerification bool, clientPin bool, 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}
// call fido2-assert
out, err := callFidoCommand(assert, device, stdin)
out, err := callFidoCommand(assert, useFlags, userPresence, userVerification, clientPin, device, stdin)
if err != nil {
tlog.Fatal.Println(err)
os.Exit(exitcodes.FIDO2Error)
Expand Down
4 changes: 2 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ func loadConfig(args *argContainer) (masterkey []byte, cf *configfile.ConfFile,
}
var pw []byte
if cf.IsFeatureFlagSet(configfile.FlagFIDO2) {
if args.fido2 == "" {
if args.fido2_device == "" {
tlog.Fatal.Printf("Masterkey encrypted using FIDO2 token; need to use the --fido2 option.")
return nil, nil, exitcodes.NewErr("", exitcodes.Usage)
}
pw = fido2.Secret(args.fido2, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
pw = fido2.Secret(args.fido2_device, cf.FIDO2.UseFlags, cf.FIDO2.UpRequired, cf.FIDO2.UvRequired, cf.FIDO2.PinRequired, cf.FIDO2.CredentialID, cf.FIDO2.HMACSalt)
} else {
pw, err = readpassword.Once([]string(args.extpass), []string(args.passfile), "")
if err != nil {
Expand Down