Skip to content

Commit

Permalink
feat: Handle KeePassXC prompts for YubiKeys
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Aug 26, 2024
1 parent 82ebb6a commit 700164f
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,20 @@ If your database is not password protected, add `--no-password` to

```toml title="~/.config/chezmoi/chezmoi.toml"
[keepassxc]
database = "/home/user/Passwords.kdbx"
args = ["--no-password"]
prompt = false
```

## YubiKey support

chezmoi includes an experimental mode to support using KeePassXC with YubiKeys.
Set `keepassxc.mode` to `open` and `keepassxc.args` to the arguments required to
set your YubiKey, for example:
Set `keepassxc.mode` to `open` and `keepassxc.openArgs` to the arguments
required to set your YubiKey, for example:

```toml title="~/.config/chezmoi/chezmoi.toml"
[keepassxc]
args = ["--yubikey", "1:7370001"]
database = "/home/user/Passwords.kdbx"
openArgs = ["--no-password", "--yubikey", "2:7370001"]
mode = "open"
```
73 changes: 43 additions & 30 deletions internal/cmd/keepassxctemplatefuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,13 @@ type keepassxcConfig struct {
var (
keepassxcMinVersion = semver.Version{Major: 2, Minor: 7, Patch: 0}

keepassxcEnterPasswordToUnlockDatabaseRx = regexp.MustCompile(`^Enter password to unlock .*: `)
keepassxcAnyResponseRx = regexp.MustCompile(`(?m)\A.*\r\n`)
keepassxcPairRx = regexp.MustCompile(`^([A-Z]\w*):\s*(.*)$`)
keepassxcPromptRx = regexp.MustCompile(`^.*> `)
keepassxcEnterPasswordToUnlockDatabaseRx = regexp.MustCompile(`^Enter password to unlock .*: `)
keepassxcPleasePresentOrTouchYourYubiKeyToContinueRx = regexp.MustCompile(
"^Please present or touch your \\S+ to continue\\.\r\n",
)
keepassxcAnyResponseRx = regexp.MustCompile(`(?m)\A.*\r\n`)
keepassxcPairRx = regexp.MustCompile(`^([A-Z]\w*):\s*(.*)$`)
keepassxcPromptRx = regexp.MustCompile(`^.*> `)
)

func (c *Config) keepassxcAttachmentTemplateFunc(entry, name string) string {
Expand Down Expand Up @@ -220,55 +223,65 @@ func (c *Config) keepassxcOutputOpen(command string, args ...string) ([]byte, er
}

if c.Keepassxc.Prompt {
// Expect the password prompt, e.g. "Enter password to unlock $HOME/Passwords.kdbx: ".
enterPasswordToUnlockPrompt, err := console.Expect(
// Expect the password or YubiKey response.
response, err := console.Expect(
expect.Regexp(keepassxcEnterPasswordToUnlockDatabaseRx),
expect.Regexp(keepassxcPleasePresentOrTouchYourYubiKeyToContinueRx),
expect.Regexp(keepassxcAnyResponseRx),
)
if err != nil {
return nil, err
}
if !keepassxcEnterPasswordToUnlockDatabaseRx.MatchString(enterPasswordToUnlockPrompt) {
return nil, errors.New(strings.TrimSpace(enterPasswordToUnlockPrompt))
}

// Read the password from the user, if necessary.
var password string
if c.Keepassxc.password != "" {
password = c.Keepassxc.password
} else {
password, err = c.readPassword(enterPasswordToUnlockPrompt)
if err != nil {
return nil, err
switch {
case keepassxcEnterPasswordToUnlockDatabaseRx.MatchString(response):
// Read the password from the user, if necessary.
var password string
if c.Keepassxc.password != "" {
password = c.Keepassxc.password
} else {
password, err = c.readPassword(response)
if err != nil {
return nil, err
}
}
}

// Send the password.
if _, err := console.SendLine(password); err != nil {
return nil, err
}
// Send the password.
if _, err := console.SendLine(password); err != nil {
return nil, err
}

// Wait for the end of the password prompt.
if _, err := console.ExpectString("\r\n"); err != nil {
return nil, err
// Wait for the end of the password prompt.
if _, err := console.ExpectString("\r\n"); err != nil {
return nil, err
}
case keepassxcPleasePresentOrTouchYourYubiKeyToContinueRx.MatchString(response):
if _, err := console.ExpectString("\r\n"); err != nil {
return nil, err
}
if _, err := c.stderr.Write([]byte(strings.TrimSpace(response) + "\n")); err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("%q: unexpected response", response)
}
}

// Read the prompt, e.g "Passwords> ", so we can expect it later.
output, err := console.Expect(
// Read the response, e.g "Passwords> ", so we can expect it later.
response, err := console.Expect(
expect.Regexp(keepassxcPromptRx),
expect.Regexp(keepassxcAnyResponseRx),
)
if err != nil {
return nil, err
}
if !keepassxcPromptRx.MatchString(output) {
return nil, errors.New(strings.TrimSpace(output))
if !keepassxcPromptRx.MatchString(response) {
return nil, fmt.Errorf("%q: unexpected response", response)
}

c.Keepassxc.cmd = cmd
c.Keepassxc.console = console
c.Keepassxc.prompt = keepassxcPromptRx.FindString(output)
c.Keepassxc.prompt = keepassxcPromptRx.FindString(response)
}

// Build the command line. Strings with spaces and other non-word characters
Expand Down

0 comments on commit 700164f

Please sign in to comment.