package service import ( "bufio" "context" "fmt" "os" "strconv" "strings" "github.com/keys-pub/keys-ext/auth/fido2" "github.com/pkg/errors" ) func fido2AuthSetup(ctx context.Context, client *Client, clientName string, device string, pin string) (string, error) { if device == "" { d, err := selectDevice(ctx, client) if err != nil { return "", err } device = d } if pin == "" { p, err := readPassword("Enter your PIN:", true) if err != nil { return "", err } pin = p } fmt.Fprintln(os.Stderr, "Let's create a credential, you may need to interact with the key...") if _, err := client.RPCClient().AuthSetup(ctx, &AuthSetupRequest{ Secret: pin, Type: FIDO2HMACSecretAuth, Device: device, }); err != nil { return "", err } fmt.Fprintln(os.Stderr, "Getting the credential, you may need to interact with the key...") unlockResp, err := client.RPCClient().AuthUnlock(ctx, &AuthUnlockRequest{ Secret: pin, Type: FIDO2HMACSecretAuth, Client: clientName, }) if err != nil { return "", err } return unlockResp.AuthToken, nil } func fido2AuthUnlock(ctx context.Context, client *Client, clientName string, pin string) (string, error) { if pin == "" { p, err := readPassword("Enter your PIN:", true) if err != nil { return "", err } pin = p } fmt.Fprintln(os.Stderr, "Getting the credential, you may need to interact with the key...") unlock, err := client.RPCClient().AuthUnlock(context.TODO(), &AuthUnlockRequest{ Secret: pin, Type: FIDO2HMACSecretAuth, Client: clientName, }) if err != nil { return "", err } return unlock.AuthToken, nil } func fido2AuthProvision(ctx context.Context, client *Client, device string, pin string) error { if device == "" { d, err := selectDevice(ctx, client) if err != nil { return err } device = d } if pin == "" { p, err := readPassword("Enter your PIN:", true) if err != nil { return err } pin = p } // Generate if err := fido2AuthGenerate(ctx, client, pin, device); err != nil { return err } // Credential if err := fido2AuthCredential(ctx, client, pin, device); err != nil { return err } fmt.Fprintln(os.Stderr, "Successfully provisioned (FIDO2 HMAC-Secret).") return nil } func fido2AuthGenerate(ctx context.Context, client *Client, pin string, device string) error { fmt.Fprintln(os.Stderr, "Let's create a credential, you may need to interact with the key...") if _, err := client.RPCClient().AuthProvision(ctx, &AuthProvisionRequest{ Device: device, Secret: pin, Type: FIDO2HMACSecretAuth, Generate: true, }); err != nil { return err } return nil } func fido2AuthCredential(ctx context.Context, client *Client, pin string, device string) error { fmt.Fprintln(os.Stderr, "Getting the credential, you may need to interact with the key (again)...") if _, err := client.RPCClient().AuthProvision(ctx, &AuthProvisionRequest{ Device: device, Secret: pin, Type: FIDO2HMACSecretAuth, }); err != nil { return err } return nil } func selectDevice(ctx context.Context, client *Client) (string, error) { devicesResp, err := client.FIDO2Client().Devices(ctx, &fido2.DevicesRequest{}) if err != nil { return "", err } if len(devicesResp.Devices) == 0 { return "", errors.Errorf("no devices found") } if len(devicesResp.Devices) == 1 { return devicesResp.Devices[0].Path, nil } fmt.Fprintf(os.Stderr, "Choose a device:\n") for i, d := range devicesResp.Devices { fmt.Fprintf(os.Stderr, "(%d) %s (%s)\n", i+1, d.Product, d.Manufacturer) } reader := bufio.NewReader(os.Stdin) read, err := reader.ReadString('\n') if err != nil { return "", err } n, err := strconv.Atoi(strings.TrimRight(read, " \n")) if err != nil { return "", errors.Errorf("invalid input: %s", read) } if n < 1 || n > len(devicesResp.Devices) { return "", errors.Errorf("invalid input (%d)", n) } return devicesResp.Devices[n-1].Path, nil }