This repository has been archived by the owner on Jan 11, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Dominik Schulz <[email protected]>
- Loading branch information
1 parent
4abd555
commit 027b4ad
Showing
11 changed files
with
744 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
# pinentry | ||
|
||
Pinentry client in Go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package cli | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/gopasspw/gopass/pkg/termio" | ||
) | ||
|
||
// Client is pinentry CLI drop-in | ||
type Client struct{} | ||
|
||
// New creates a new client | ||
func New() (*Client, error) { | ||
return &Client{}, nil | ||
} | ||
|
||
// Close is a no-op | ||
func (c *Client) Close() {} | ||
|
||
// Confirm is a no-op | ||
func (c *Client) Confirm() bool { | ||
return true | ||
} | ||
|
||
// Set is a no-op | ||
func (c *Client) Set(string, string) error { | ||
return nil | ||
} | ||
|
||
// Option is a no-op | ||
func (c *Client) Option(string) error { | ||
return nil | ||
} | ||
|
||
// GetPin prompts for the pin in the termnial and returns the output | ||
func (c *Client) GetPin() ([]byte, error) { | ||
pw, err := termio.AskForPassword(context.TODO(), "Please enter your PIN") | ||
if err != nil { | ||
return nil, err | ||
} | ||
return []byte(pw), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module github.com/gopasspw/pinentry | ||
|
||
go 1.16 | ||
|
||
require github.com/gopasspw/gopass v1.12.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package gpgconf | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/gopasspw/gopass/pkg/debug" | ||
) | ||
|
||
// Path returns the path to a GPG component | ||
func Path(key string) (string, error) { | ||
buf := &bytes.Buffer{} | ||
cmd := exec.Command("gpgconf") | ||
cmd.Stdout = buf | ||
cmd.Stderr = os.Stderr | ||
|
||
debug.Log("%s %+v", cmd.Path, cmd.Args) | ||
if err := cmd.Run(); err != nil { | ||
return "", err | ||
} | ||
|
||
key = strings.TrimSpace(strings.ToLower(key)) | ||
sc := bufio.NewScanner(buf) | ||
for sc.Scan() { | ||
p := strings.Split(strings.TrimSpace(sc.Text()), ":") | ||
if len(p) < 3 { | ||
continue | ||
} | ||
if key == p[0] { | ||
return p[2], nil | ||
} | ||
} | ||
|
||
debug.Log("key %q not found", key) | ||
return "", nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// Package pinentry implements a cross platform pinentry client. It can be used | ||
// to obtain credentials from the user through a simple UI application. | ||
package pinentry | ||
|
||
import ( | ||
"bufio" | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"net/url" | ||
"os" | ||
"os/exec" | ||
"strings" | ||
|
||
"github.com/gopasspw/gopass/pkg/debug" | ||
) | ||
|
||
var ( | ||
// Unescape enables unescaping of percent encoded values, | ||
// disabled by default for backward compatibility. See #1621 | ||
Unescape bool | ||
) | ||
|
||
func init() { | ||
if sv := os.Getenv("GOPASS_PINENTRY_UNESCAPE"); sv == "on" || sv == "true" { | ||
Unescape = true | ||
} | ||
} | ||
|
||
// Client is a pinentry client | ||
type Client struct { | ||
cmd *exec.Cmd | ||
in io.WriteCloser | ||
out *bufio.Reader | ||
} | ||
|
||
// New creates a new pinentry client | ||
func New() (*Client, error) { | ||
cmd := exec.Command(GetBinary()) | ||
stdin, err := cmd.StdinPipe() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
stdout, err := cmd.StdoutPipe() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
br := bufio.NewReader(stdout) | ||
if err := cmd.Start(); err != nil { | ||
return nil, err | ||
} | ||
|
||
// check welcome message | ||
banner, _, err := br.ReadLine() | ||
if err != nil { | ||
return nil, err | ||
} | ||
if !bytes.HasPrefix(banner, []byte("OK")) { | ||
return nil, fmt.Errorf("wrong banner: %s", banner) | ||
} | ||
|
||
cl := &Client{ | ||
cmd: cmd, | ||
in: stdin, | ||
out: br, | ||
} | ||
|
||
return cl, nil | ||
} | ||
|
||
// Close closes the client | ||
func (c *Client) Close() { | ||
_ = c.in.Close() | ||
} | ||
|
||
// Confirm sends the confirm message | ||
func (c *Client) Confirm() bool { | ||
if err := c.Set("confirm", ""); err == nil { | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
// Set sets a key | ||
func (c *Client) Set(key, value string) error { | ||
key = strings.ToUpper(key) | ||
if value != "" { | ||
value = " " + value | ||
} | ||
val := "SET" + key + value + "\n" | ||
if _, err := c.in.Write([]byte(val)); err != nil { | ||
return err | ||
} | ||
line, _, _ := c.out.ReadLine() | ||
if string(line) != "OK" { | ||
return fmt.Errorf("error: %s", line) | ||
} | ||
return nil | ||
} | ||
|
||
// Option sets an option, e.g. "default-cancel=Abbruch" or "allow-external-password-cache" | ||
func (c *Client) Option(value string) error { | ||
val := "OPTION " + value + "\n" | ||
if _, err := c.in.Write([]byte(val)); err != nil { | ||
return err | ||
} | ||
line, _, _ := c.out.ReadLine() | ||
if string(line) != "OK" { | ||
return fmt.Errorf("error: %s", line) | ||
} | ||
return nil | ||
} | ||
|
||
// GetPin asks for the pin | ||
func (c *Client) GetPin() ([]byte, error) { | ||
if _, err := c.in.Write([]byte("GETPIN\n")); err != nil { | ||
return nil, err | ||
} | ||
buf, _, err := c.out.ReadLine() | ||
if err != nil { | ||
return nil, err | ||
} | ||
if bytes.HasPrefix(buf, []byte("OK")) { | ||
return nil, nil | ||
} | ||
// handle status messages | ||
for bytes.HasPrefix(buf, []byte("S ")) { | ||
debug.Log("message: %q", string(buf)) | ||
buf, _, err = c.out.ReadLine() | ||
if err != nil { | ||
return nil, err | ||
} | ||
} | ||
// now there should be some data | ||
if !bytes.HasPrefix(buf, []byte("D ")) { | ||
return nil, fmt.Errorf("unexpected response: %s", buf) | ||
} | ||
|
||
pin := make([]byte, len(buf)) | ||
if n := copy(pin, buf); n != len(buf) { | ||
return nil, fmt.Errorf("failed to copy pin: expected %d bytes only copied %d", len(buf), n) | ||
} | ||
|
||
ok, _, err := c.out.ReadLine() | ||
if err != nil { | ||
return nil, err | ||
} | ||
if !bytes.HasPrefix(ok, []byte("OK")) { | ||
return nil, fmt.Errorf("unexpected response: %s", ok) | ||
} | ||
pin = pin[2:] | ||
|
||
// Handle unescaping, if enabled | ||
if bytes.Contains(pin, []byte("%")) && Unescape { | ||
sv, err := url.QueryUnescape(string(pin)) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to unescape pin: %q", err) | ||
} | ||
pin = []byte(sv) | ||
} | ||
return pin, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// +build darwin | ||
|
||
package pinentry | ||
|
||
import "github.com/gopasspw/pinentry/gpgconf" | ||
|
||
// GetBinary always returns pinentry-mac | ||
func GetBinary() string { | ||
if p, err := gpgconf.Path("pinentry"); err == nil && p != "" { | ||
return p | ||
} | ||
return "pinentry-mac" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// +build !darwin,!windows | ||
|
||
package pinentry | ||
|
||
import "github.com/gopasspw/pinentry/gpgconf" | ||
|
||
// GetBinary returns the binary name | ||
func GetBinary() string { | ||
if p, err := gpgconf.Path("pinentry"); err == nil && p != "" { | ||
return p | ||
} | ||
return "pinentry" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package pinentry | ||
|
||
import "fmt" | ||
|
||
func ExampleClient() { | ||
pi, err := New() | ||
if err != nil { | ||
panic(err) | ||
} | ||
_ = pi.Set("title", "Agent Pinentry") | ||
_ = pi.Set("desc", "Asking for a passphrase") | ||
_ = pi.Set("prompt", "Please enter your passphrase:") | ||
_ = pi.Set("ok", "OK") | ||
pin, err := pi.GetPin() | ||
if err != nil { | ||
panic(err) | ||
} | ||
fmt.Println(string(pin)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// +build windows | ||
|
||
package pinentry | ||
|
||
import "github.com/gopasspw/pinentry/gpgconf" | ||
|
||
// GetBinary always returns pinentry.exe | ||
func GetBinary() string { | ||
if p, err := gpgconf.Path("pinentry"); err == nil && p != "" { | ||
return p | ||
} | ||
return "pinentry.exe" | ||
} |