diff options
Diffstat (limited to 'internal/fido2/fido2.go')
-rw-r--r-- | internal/fido2/fido2.go | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/internal/fido2/fido2.go b/internal/fido2/fido2.go new file mode 100644 index 0000000..ea8ffd8 --- /dev/null +++ b/internal/fido2/fido2.go @@ -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 +} |