-
Notifications
You must be signed in to change notification settings - Fork 1
/
ykman.go
132 lines (106 loc) · 3.07 KB
/
ykman.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
123
124
125
126
127
128
129
130
131
132
package ykmangoath
import (
"bytes"
"context"
"fmt"
"os/exec"
"strings"
"syscall"
)
type Ykman struct {
ctx context.Context
serial string
password string
}
func NewYkman(ctx context.Context, serial string) *Ykman {
return &Ykman{ctx: ctx, serial: serial}
}
func (y *Ykman) Execute(args []string) (string, error) {
var finalArgs []string
// only apply device argument if an id is given
if y.serial != "" {
finalArgs = append(finalArgs, "--device", y.serial)
finalArgs = append(finalArgs, args...)
}
// define the ykman command to be run
cmd := exec.CommandContext(y.ctx, "ykman", finalArgs...)
// in case a password is provided, provide it to ykman via stdin
// it's better to pass it in via stdin as it will fail on empty string immediately
// if the oath is password protected
var b bytes.Buffer
b.Write([]byte(fmt.Sprintf("%s\n", y.password)))
cmd.Stdin = &b
// redirect stdout & stderr into byte buffer
var outb, errb bytes.Buffer
cmd.Stdout = &outb
cmd.Stderr = &errb
// execute the ykman command
err := cmd.Run()
err = processYkmanErrors(err, errb.String(), y.password)
// finally return the ykman output
return outb.String(), err
}
func (y *Ykman) ExecuteWithPrompt(args []string, prompt func(ctx context.Context) (string, error)) (string, error) {
result, err := y.Execute(args)
if err != ErrOathAccountPasswordProtected {
return result, err
}
password, err := prompt(y.ctx)
if err != nil {
return "", err
}
y.password = password
return y.Execute(args)
}
func processYkmanErrors(err error, outputStderr string, password string) error {
if err != nil {
// check for ykman process existance
if execErr, ok := err.(*exec.Error); ok {
if execErr.Err == exec.ErrNotFound {
return ErrCommandNotFound
}
}
// check for ykman process interuption
if exitErr, ok := err.(*exec.ExitError); ok {
status := exitErr.Sys().(syscall.WaitStatus).Signal()
if status == syscall.SIGINT {
fmt.Println("SIGINT")
return ErrCommandInterrupted
}
if status == syscall.SIGTERM {
fmt.Println("SIGTERM")
return ErrCommandInterrupted
}
}
// check for yubikey device connection
if strings.Contains(outputStderr, "Failed connecting to the YubiKey") {
return ErrDeviceNotFound
}
if strings.Contains(outputStderr, "Failed to open device for communication") {
return ErrDeviceNotFound
}
// check for yubikey device removal
if strings.Contains(outputStderr, "Failed to transmit with protocol") {
return ErrDeviceRemoved
}
// check for yubikey device timeout
if strings.Contains(outputStderr, "Touch account timed out!") {
return ErrDeviceTimeout
}
// check for oath password protection
if strings.Contains(outputStderr, "Authentication to the YubiKey failed. Wrong password?") {
if password == "" {
return ErrOathAccountPasswordProtected
} else {
return ErrOathAccountPasswordIncorrect
}
}
// check for oath account mismatch
if strings.Contains(outputStderr, "No matching account found.") {
return ErrOathAccountNotFound
}
// catch-all error
return err
}
return nil
}