-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathotp.go
111 lines (103 loc) · 3.4 KB
/
otp.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 implemnts HOTP and TOTP one-time passwords.
package otp // import "code.soquee.net/otp"
import (
"crypto"
"crypto/hmac"
"encoding/base32"
"encoding/binary"
"hash"
"math"
"net/url"
"strconv"
"time"
)
// URL returns a URL that is compatible with many popular OTP apps such as
// FreeOTP, Yubico Authenticator, and Google Authenticator.
//
// Supported hashes are SHA1, SHA256, and SHA512.
// Anything else will default to SHA1.
func URL(key []byte, step time.Duration, l int, hash crypto.Hash, domain, email string) *url.URL {
secret := base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(key)
u := &url.URL{
Scheme: "otpauth",
Host: "totp",
Path: domain + ":" + email,
}
// TODO: Is it safe to do this as a string and avoid the heap allocations?
// Domain looks like the only thing that would need to be explicitly URL
// encoded.
v := url.Values{}
switch hash {
case crypto.SHA1:
v.Add("algorithm", "SHA1")
case crypto.SHA256:
v.Add("algorithm", "SHA256")
case crypto.SHA512:
v.Add("algorithm", "SHA512")
default:
v.Add("algorithm", "SHA1")
}
v.Add("secret", secret)
v.Add("issuer", domain)
v.Add("digits", strconv.Itoa(l))
v.Add("period", strconv.FormatFloat(math.Floor(step.Seconds()), 'f', 0, 64))
u.RawQuery = v.Encode()
return u
}
// CounterFunc is a function that is called when generating a one-time password
// and returns a seed value.
// In HOTP this will be an incrementing counter, in TOTP it is a function of the
// current time.
// Offset indicates that we want the token relative to the current token by
// offset (eg. -1 for the previous token).
type CounterFunc func(offset int) uint64
// TOTP returns a counter function that can be used to generate HOTP tokens
// compatible with the Time-Based One-Time Password Algorithm (TOTP) defined in
// RFC 6238.
//
// If a zero duration is provided, a default of 30 seconds is used.
// If no time function is provided, time.Now is used.
func TOTP(step time.Duration, t func() time.Time) CounterFunc {
if step == 0 {
step = 30 * time.Second
}
if t == nil {
t = time.Now
}
return func(offset int) uint64 {
return uint64(math.Floor(float64(t().Add(time.Duration(offset)*step).Unix()) / step.Seconds()))
}
}
// NewOTP returns a function that generates hmac-based one-time passwords.
// Each time the returned function is called it calls c and appends the one-time
// password to dst. It also returns a 31-bit representation of the value.
// The key is the shared secret, l is the length of the output number (if l is
// less than or equal to 0, NewOTP panics), h is a function that returns the
// inner and outer hash mechanisms for the HMAC, and c returns the seed used to
// generate the key.
func NewOTP(key []byte, l int, h func() hash.Hash, c CounterFunc) func(offset int, dst []byte) int32 {
if l <= 0 {
panic("otp: l must be greater than 0")
}
if c == nil {
panic("otp: counter func must not be nil")
}
if len(key) == 0 {
panic("otp: key must not be empty")
}
hs := hmac.New(h, key)
return func(offset int, dst []byte) int32 {
hs.Reset()
err := binary.Write(hs, binary.BigEndian, c(offset))
if err != nil {
panic(err)
}
dst = hs.Sum(dst)
dstOffset := dst[len(dst)-1] & 0xf
value := int64(((int(dst[dstOffset]))&0x7f)<<24 |
((int(dst[dstOffset+1] & 0xff)) << 16) |
((int(dst[dstOffset+2] & 0xff)) << 8) |
(int(dst[dstOffset+3]) & 0xff))
return int32(value % int64(math.Pow10(l)))
}
}