-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathtotp.go
111 lines (96 loc) · 2.33 KB
/
totp.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
package otp
import (
"math"
"net/url"
"time"
)
// TOTP represents TOTP codes generator and validator.
type TOTP struct {
hotp HOTP
cfg TOTPConfig
}
type TOTPConfig struct {
Algo Algorithm
Digits uint
Issuer string
Period uint64
Skew uint
}
func (cfg TOTPConfig) Validate() error {
switch {
case cfg.Algo == 0 || cfg.Algo >= algorithmMax:
return ErrUnsupportedAlgorithm
case cfg.Digits == 0:
return ErrNoDigits
case cfg.Issuer == "":
return ErrEmptyIssuer
case cfg.Period == 0:
return ErrPeriodNotValid
case cfg.Skew == 0:
return ErrSkewNotValid
default:
return nil
}
}
// NewTOTP creates new TOTP.
func NewTOTP(cfg TOTPConfig) (*TOTP, error) {
if err := cfg.Validate(); err != nil {
return nil, err
}
hotp, err := NewHOTP(HOTPConfig{
Algo: cfg.Algo,
Digits: cfg.Digits,
Issuer: cfg.Issuer,
})
if err != nil {
return nil, err
}
return &TOTP{
hotp: *hotp,
cfg: cfg,
}, nil
}
// GenerateURL for the account for a given secret.
func (t *TOTP) GenerateURL(account string, secret []byte) string {
v := url.Values{}
v.Set("algorithm", t.cfg.Algo.String())
v.Set("digits", atoi(uint64(t.cfg.Digits)))
v.Set("issuer", t.cfg.Issuer)
v.Set("secret", b32Enc(secret))
v.Set("period", atoi(t.cfg.Period))
u := url.URL{
Scheme: "otpauth",
Host: "totp",
Path: "/" + t.cfg.Issuer + ":" + account,
RawQuery: v.Encode(),
}
return u.String()
}
// GenerateCode for the given counter and secret.
func (t *TOTP) GenerateCode(secret string, at time.Time) (string, error) {
counter := uint64(math.Floor(float64(at.Unix()) / float64(t.cfg.Period)))
code, err := t.hotp.GenerateCode(counter, secret)
if err != nil {
return "", err
}
return code, nil
}
// Validate the given passcode, time and secret.
func (t *TOTP) Validate(passcode string, at time.Time, secret string) error {
if len(passcode) != int(t.cfg.Digits) {
return ErrCodeLengthMismatch
}
counter := int64(math.Floor(float64(at.Unix()) / float64(t.cfg.Period)))
if err := t.hotp.Validate(passcode, uint64(counter), secret); err == nil {
return nil
}
for i := uint(1); i <= t.cfg.Skew; i++ {
if err := t.hotp.Validate(passcode, uint64(counter+int64(i)), secret); err == nil {
return nil
}
if err := t.hotp.Validate(passcode, uint64(counter-int64(i)), secret); err == nil {
return nil
}
}
return ErrCodeIsNotValid
}