Repository: jesseduffield/lazygit (language: go)

Issue:
Add prompts for FIDO/U2F hardware device-backed SSH keys
Somewhat hesitant as to whether this should be considered a bug or a feature request. Erring on the safe side that it's the latter.

**Is your feature request related to a problem? Please describe.**
Recently, I've changed my git setup a bit, and am trying out a new set of SSH keys. Newer versions of OpenSSH ([>=8.2](https://www.openssh.com/txt/release-8.2), to be more precise) support SSH keys that are backed by FIDO/U2F hardware authenticators (like a [YubiKey](https://www.yubico.com/)). Those keys have the type of `ecdsa-sk` or `ed25519-sk`.

When you initiate a pull/push operation with such a key, the first prompt is:

```
Enter PIN for ${key_type} key ${path_to_key}:
```

at which point the user is supposed to enter a numerical PIN, set for the particular FIDO/U2F key, using the computer keyboard. Upon confirmation that the PIN is correct, the second prompt is:

```
Confirm user presence for key ${key_type} ${key_fingerprint}
```

at which point the user is supposed to interact with the hardware key (i.e. touch it). Some keys will also start to physically blink with a LED to indicate that they're waiting for input.

Touching the key concludes the authentication operation, with a message of

```
User presence confirmed
```

Currently, lazygit does not handle this flow, and as such push/pull operations with such a key just hang waiting for PIN input that will never come.

**Describe the solution you'd like**
The minimal patch that makes lazygit usable for me with such a key without workarounds is something like the following (briefly tested to work):

```diff
diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go
index 01cc0f46..498ef4ba 100644
--- a/pkg/commands/oscommands/cmd_obj_runner.go
+++ b/pkg/commands/oscommands/cmd_obj_runner.go
@@ -25,6 +25,7 @@ const (
 	Password CredentialType = iota
 	Username
 	Passphrase
+	PIN
 )
 
 type cmdObjRunner struct {
@@ -335,6 +336,7 @@ func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (Crede
 			`Password\s*for\s*'.+':`:                 Password,
 			`Username\s*for\s*'.+':`:                 Username,
 			`Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase,
+			`Enter\s*PIN\s*for\s*.+\s*key\s*.+:`:     PIN,
 		}
 
 		for pattern, askFor := range prompts {
diff --git a/pkg/gui/controllers/helpers/credentials_helper.go b/pkg/gui/controllers/helpers/credentials_helper.go
index 9da3a46a..10679d7a 100644
--- a/pkg/gui/controllers/helpers/credentials_helper.go
+++ b/pkg/gui/controllers/helpers/credentials_helper.go
@@ -61,6 +61,8 @@ func (self *CredentialsHelper) getTitleAndMask(passOrUname oscommands.Credential
 		return self.c.Tr.CredentialsPassword, true
 	case oscommands.Passphrase:
 		return self.c.Tr.CredentialsPassphrase, true
+	case oscommands.PIN:
+		return self.c.Tr.CredentialsPIN, true
 	}
 
 	// should never land here
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index 75e12154..0f4d5f32 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -29,6 +29,7 @@ type TranslationSet struct {
 	CredentialsUsername                 string
 	CredentialsPassword                 string
 	CredentialsPassphrase               string
+	CredentialsPIN                      string
 	PassUnameWrong                      string
 	CommitChanges                       string
 	AmendLastCommit                     string
@@ -674,6 +675,7 @@ func EnglishTranslationSet() TranslationSet {
 		CredentialsUsername:                 "Username",
 		CredentialsPassword:                 "Password",
 		CredentialsPassphrase:               "Enter passphrase for SSH key",
+		CredentialsPIN:                      "Enter PIN for SSH key",
 		PassUnameWrong:                      "Password, passphrase and/or username wrong",
 		CommitChanges:                       "commit changes",
 		AmendLastCommit:                     "amend last commit",
```

This makes it so that I can interactively enter the PIN at least. I still have to know to touch my U2F key to conclude the flow afterwards, but at least I can complete pushes/pulls this way.

I'm willing to PR the patch above, but as per contribution guidelines I figure it's better to ask first.

Making it so that lazygit shows a prompt that user interaction with the key is required is probably bound to be much more difficult, but I don't think I actually need this.

**Describe alternatives you've considered**
I can work around the lack of support for these types of key with the custom command <kbd>:</kbd> feature to escape to shell and manually type the push/pull commands, but it'd be nice to have this working natively.

**Additional context**
Not sure what else to put there, but if there's anything unclear in the description above, please do let me know and I'll attempt to elaborate.


Discussion / hints:

Fix patch:
diff --git a/pkg/commands/oscommands/cmd_obj_runner.go b/pkg/commands/oscommands/cmd_obj_runner.go
index 01cc0f46098..498ef4babe2 100644
--- a/pkg/commands/oscommands/cmd_obj_runner.go
+++ b/pkg/commands/oscommands/cmd_obj_runner.go
@@ -25,6 +25,7 @@ const (
 	Password CredentialType = iota
 	Username
 	Passphrase
+	PIN
 )
 
 type cmdObjRunner struct {
@@ -335,6 +336,7 @@ func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (Crede
 			`Password\s*for\s*'.+':`:                 Password,
 			`Username\s*for\s*'.+':`:                 Username,
 			`Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase,
+			`Enter\s*PIN\s*for\s*.+\s*key\s*.+:`:     PIN,
 		}
 
 		for pattern, askFor := range prompts {
diff --git a/pkg/gui/controllers/helpers/credentials_helper.go b/pkg/gui/controllers/helpers/credentials_helper.go
index 9da3a46a2d6..10679d7a428 100644
--- a/pkg/gui/controllers/helpers/credentials_helper.go
+++ b/pkg/gui/controllers/helpers/credentials_helper.go
@@ -61,6 +61,8 @@ func (self *CredentialsHelper) getTitleAndMask(passOrUname oscommands.Credential
 		return self.c.Tr.CredentialsPassword, true
 	case oscommands.Passphrase:
 		return self.c.Tr.CredentialsPassphrase, true
+	case oscommands.PIN:
+		return self.c.Tr.CredentialsPIN, true
 	}
 
 	// should never land here
diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go
index e152b62a449..c20022174b9 100644
--- a/pkg/i18n/english.go
+++ b/pkg/i18n/english.go
@@ -29,6 +29,7 @@ type TranslationSet struct {
 	CredentialsUsername                 string
 	CredentialsPassword                 string
 	CredentialsPassphrase               string
+	CredentialsPIN                      string
 	PassUnameWrong                      string
 	CommitChanges                       string
 	AmendLastCommit                     string
@@ -676,6 +677,7 @@ func EnglishTranslationSet() TranslationSet {
 		CredentialsUsername:                 "Username",
 		CredentialsPassword:                 "Password",
 		CredentialsPassphrase:               "Enter passphrase for SSH key",
+		CredentialsPIN:                      "Enter PIN for SSH key",
 		PassUnameWrong:                      "Password, passphrase and/or username wrong",
 		CommitChanges:                       "commit changes",
 		AmendLastCommit:                     "amend last commit",


Test patch: