forked from yawn/ykoath
-
Notifications
You must be signed in to change notification settings - Fork 2
/
calculate.go
164 lines (129 loc) · 3.51 KB
/
calculate.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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
package ykoath
import (
"encoding/binary"
"fmt"
"math"
)
const (
errNoValuesFound = "no values found in response (% x)"
errUnknownName = "no such name configued (%s)"
errInvalidDigits = "invalid digits (%d)"
errMultipleMatches = "multiple matches found (%s)"
errAuthentication = "authentication failed"
touchRequired = "touch-required"
hotpNoResponse = "hotp-no-response"
)
//// Calculate is a high-level function that first identifies all TOTP credentials
//// that are configured and returns the matching one (if no touch is required) or
//// fires the callback and then fetches the name again while blocking during
//// the device awaiting touch
//func (o *OATH) Calculate(name string, touchRequiredCallback func(string) error) (string, error) {
//
// res, err := o.CalculateAll()
//
// if err != nil {
// return "", nil
// }
//
// // support matching by name without issuer in the same way that ykman does
// // https://github.com/Yubico/yubikey-manager/blob/f493008d78a0ad09016f23dabd1cb658929d9c0e/ykman/cli/oath.py#L543
// var key, code string
// var matches []string
// for k, c := range res {
// if strings.Contains(strings.ToLower(k), strings.ToLower(name)) {
// key = k
// code = c
// matches = append(matches, k)
// }
// }
// if len(matches) > 1 {
// return "", fmt.Errorf(errMultipleMatches, strings.Join(matches, ","))
// }
//
// if key == "" {
// return "", fmt.Errorf(errUnknownName, name)
// }
//
// if code == touchRequired {
//
// if err := touchRequiredCallback(name); err != nil {
// return "", err
// }
//
// return o.calculate(key)
//
// }
//
// return code, nil
//
//}
// Calculate implements the "CALCULATE" instruction to fetch a single
// truncated TOTP response
func (o *OATH) Calculate(name string, touchRequiredCallback func(string) error) (string, error) {
var (
buf = make([]byte, 8)
timestamp = o.Clock().Unix() / 30
)
binary.BigEndian.PutUint64(buf, uint64(timestamp))
res, err := o.send(0x00, 0xa2, 0x00, 0x01,
write(0x71, []byte(name)),
write(0x74, buf),
)
if err != nil {
return "", err
}
for _, tv := range res {
switch tv.tag {
case 0x76:
return otp(tv.value), nil
default:
return "", fmt.Errorf(errUnknownTag, tv.tag)
}
}
return "", fmt.Errorf(errNoValuesFound, res)
}
// CalculateAll implements the "CALCULATE ALL" instruction to fetch all TOTP
// tokens and their codes (or a constant indicating a touch requirement)
func (o *OATH) CalculateAll() (map[string]string, error) {
var (
buf = make([]byte, 8)
codes []string
names []string
timestamp = o.Clock().Unix() / 30
)
binary.BigEndian.PutUint64(buf, uint64(timestamp))
res, err := o.send(0x00, 0xa4, 0x00, 0x01,
write(0x74, buf),
)
if err != nil {
return nil, err
}
for _, tv := range res {
switch tv.tag {
case 0x71:
names = append(names, string(tv.value))
case 0x7c:
codes = append(codes, touchRequired)
case 0x76:
codes = append(codes, otp(tv.value))
case 0x77:
codes = append(codes, hotpNoResponse)
default:
return nil, fmt.Errorf(errUnknownTag, tv.tag)
}
}
all := make(map[string]string, len(names))
for idx, name := range names {
all[name] = codes[idx]
}
return all, nil
}
// otp converts a value into a (6 or 8 digits) one-time password
func otp(value []byte) string {
digits := value[0]
if digits != 6 && digits != 8 {
return fmt.Sprintf(errInvalidDigits, digits)
}
code := binary.BigEndian.Uint32(value[1:]) % uint32(math.Pow10(int(digits)))
return fmt.Sprintf(fmt.Sprintf("%%0%dd", digits), code)
}