-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhotp.go
104 lines (89 loc) · 2.27 KB
/
hotp.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
package otp
import (
"crypto/hmac"
"crypto/subtle"
"encoding/binary"
"fmt"
"math"
"net/url"
)
// HOTP represents HOTP codes generator and validator.
type HOTP struct {
cfg HOTPConfig
}
type HOTPConfig struct {
Algo Algorithm
Digits uint
Issuer string
}
func (cfg HOTPConfig) Validate() error {
switch {
case cfg.Algo == 0 || cfg.Algo >= algorithmMax:
return ErrUnsupportedAlgorithm
case cfg.Digits == 0:
return ErrNoDigits
case cfg.Issuer == "":
return ErrEmptyIssuer
default:
return nil
}
}
// NewHOTP creates new HOTP.
func NewHOTP(cfg HOTPConfig) (*HOTP, error) {
if err := cfg.Validate(); err != nil {
return nil, err
}
return &HOTP{cfg: cfg}, nil
}
// GenerateURL for the account for a given secret.
func (h *HOTP) GenerateURL(account string, secret []byte) string {
v := url.Values{}
v.Set("algorithm", h.cfg.Algo.String())
v.Set("digits", atoi(uint64(h.cfg.Digits)))
v.Set("issuer", h.cfg.Issuer)
v.Set("secret", b32Enc(secret))
u := url.URL{
Scheme: "otpauth",
Host: "hotp",
Path: "/" + h.cfg.Issuer + ":" + account,
RawQuery: v.Encode(),
}
return u.String()
}
// GenerateCode for the given counter and secret.
func (h *HOTP) GenerateCode(counter uint64, secret string) (string, error) {
secretBytes, err := b32Dec(secret)
if err != nil {
return "", ErrEncodingNotValid
}
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, counter)
mac := hmac.New(h.cfg.Algo.Hash, secretBytes)
mac.Write(buf)
sum := mac.Sum(nil)
// See: http://tools.ietf.org/html/rfc4226#section-5.4
offset := sum[len(sum)-1] & 0xf
var value int64
value |= int64(sum[offset]&0x7f) << 24
value |= int64(sum[offset+1]&0xff) << 16
value |= int64(sum[offset+2]&0xff) << 8
value |= int64(sum[offset+3] & 0xff)
length := int64(math.Pow10(int(h.cfg.Digits)))
code := fmt.Sprintf(fmt.Sprintf("%%0%dd", h.cfg.Digits), value%length)
return code, nil
}
// Validate the given passcode, counter and secret.
func (h *HOTP) Validate(passcode string, counter uint64, secret string) error {
if len(passcode) != int(h.cfg.Digits) {
return ErrCodeLengthMismatch
}
code, err := h.GenerateCode(counter, secret)
if err != nil {
return err
}
ok := subtle.ConstantTimeCompare([]byte(code), []byte(passcode))
if ok != 1 {
return ErrCodeIsNotValid
}
return nil
}