forked from Versent/saml2aws
-
Notifications
You must be signed in to change notification settings - Fork 0
/
pinentry.go
122 lines (104 loc) · 3.63 KB
/
pinentry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
package prompter
import (
"bufio"
"fmt"
"io"
"os/exec"
"strings"
"sync"
)
const (
defaultPinentryDialog string = "Security token [%s]"
)
// PinentryRunner is the interface for pinentry to run itself
type PinentryRunner interface {
Run(string) (string, error)
}
// RealPinentryRunner is the concrete implementation of PinentryRunner
type RealPinentryRunner struct {
PinentryBin string
}
// PinentryPrompter is a concrete implementation of the Prompter interface.
// It uses the default Cli under the hood, except for RequestSecurityCode, where
// it uses any _pinentry_ binary to capture the security code.
// Its purpose is mainly to capture the TOTP code outside of the TTY, and thus
// making it possible to use TOTP with the credential process.
// https://github.com/Versent/saml2aws#using-saml2aws-as-credential-process
type PinentryPrompter struct {
Runner PinentryRunner
DefaultPrompter Prompter
}
// NewPinentryPrompter is a factory for PinentryPrompter
func NewPinentryPrompter(bin string) *PinentryPrompter {
return &PinentryPrompter{Runner: NewRealPinentryRunner(bin), DefaultPrompter: NewCli()}
}
// NewRealPinentryRunner is a factory for RealPinentryRunner
func NewRealPinentryRunner(bin string) *RealPinentryRunner {
return &RealPinentryRunner{PinentryBin: bin}
}
// RequestSecurityCode for PinentryPrompter is creating a query for pinentry
// and sends it to the pinentry bin.
func (p *PinentryPrompter) RequestSecurityCode(pattern string) (output string) {
commandTemplate := "SETPROMPT %s\nGETPIN\n"
prompt := fmt.Sprintf(defaultPinentryDialog, pattern)
command := fmt.Sprintf(commandTemplate, prompt)
if output, err := p.Runner.Run(command); err != nil {
return ""
} else {
return output
}
}
// ChooseWithDefault is running the default CLI ChooseWithDefault
func (p *PinentryPrompter) ChooseWithDefault(prompt string, def string, choices []string) (string, error) {
return p.DefaultPrompter.ChooseWithDefault(prompt, def, choices)
}
// Choose is running the default CLI Choose
func (p *PinentryPrompter) Choose(pr string, options []string) int {
return p.DefaultPrompter.Choose(pr, options)
}
// StringRequired is runniner the default Cli StringRequired
func (p *PinentryPrompter) StringRequired(pr string) string {
return p.DefaultPrompter.StringRequired(pr)
}
// String is runniner the default Cli String
func (p *PinentryPrompter) String(pr string, defaultValue string) string {
return p.DefaultPrompter.String(pr, defaultValue)
}
// Password is runniner the default Cli Password
func (p *PinentryPrompter) Password(pr string) string {
return p.DefaultPrompter.Password(pr)
}
// Run wraps a pinentry run. It sends the query to pinentry via stdin and
// reads its stdout to determine the user PIN.
// Pinentry uses an Assuan protocol
func (r *RealPinentryRunner) Run(command string) (output string, err error) {
cmd := exec.Command(r.PinentryBin, "--ttyname", "/dev/tty")
cmd.Stdin = strings.NewReader(command)
out, _ := cmd.StdoutPipe()
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
err = cmd.Run()
// fmt.Println(err)
wg.Done()
}()
output, err = ParseResults(out)
wg.Wait()
return output, err
}
// ParseResults parses the standard output of the pinentry command and determine the
// user input, or wheter the program yielded any error
func ParseResults(pinEntryOutput io.Reader) (output string, err error) {
scanner := bufio.NewScanner(pinEntryOutput)
for scanner.Scan() {
line := scanner.Text()
// fmt.Println(line)
if strings.HasPrefix(line, "D ") {
output = line[2:]
}
if strings.HasPrefix(line, "ERR ") {
return "", fmt.Errorf("Error while running pinentry: %s", line[4:])
}
}
return output, err
}