-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathotpclient.go
66 lines (53 loc) · 1.7 KB
/
otpclient.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
package frothy
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base32"
"encoding/binary"
"fmt"
"math"
"strings"
"time"
)
type TOTP struct {
Code string
ExpiresAt time.Time
}
// NewTOTP Generates a TOTP from the given secret string
// currently only supports the most common type of TOTP:
// Six Digit, SHA1 based, 30 second intervals
//
// follows algo from https://tools.ietf.org/html/rfc4226
func NewTOTP(secret string) (*TOTP, error) {
hashFunc := sha1.New // TODO support mutliple hash functions
intervalSeconds := 30 // TODO support multiple intervals
codeLen := 6 // TODO support multiple code lengths
now := time.Now() // TODO support timestamps?
key := uint64(float64(now.Unix()) / float64(intervalSeconds))
// transform invalid but fixable secrets
// 1) trim secret
secret = strings.TrimSpace(secret)
// 2) ensure capitalization
secret = strings.ToUpper(secret)
// 3) ensure proper padding
secretBytes, err := base32.StdEncoding.WithPadding('=').DecodeString(secret)
if err != nil {
return nil, err
}
buf := make([]byte, 8)
mac := hmac.New(hashFunc, secretBytes)
binary.BigEndian.PutUint64(buf, key)
mac.Write(buf)
sum := mac.Sum(nil)
// https://tools.ietf.org/html/rfc4226#section-5.4
offset := sum[19] & 0xf
binCode := int64(((int(sum[offset]) & 0x7f) << 24) |
((int(sum[offset+1] & 0xff)) << 16) |
((int(sum[offset+2] & 0xff)) << 8) |
(int(sum[offset+3]) & 0xff))
code := int32(binCode % int64(math.Pow10(codeLen)))
return &TOTP{
Code: fmt.Sprintf(fmt.Sprintf("%%0%dd", codeLen), code), // pad beginning with zeros
ExpiresAt: now.Truncate(time.Second * time.Duration(intervalSeconds)).Add(time.Second * time.Duration(intervalSeconds)),
}, nil
}