-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkey.go
183 lines (161 loc) · 4.76 KB
/
key.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
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
package otp
import (
"crypto/rand"
"encoding/base32"
"errors"
"io"
"net/url"
"strconv"
"strings"
xtp "github.com/pquerna/otp"
"github.com/pquerna/otp/hotp"
"github.com/pquerna/otp/totp"
)
const (
// AlgorithmSHA1 should be used for compatibility with Google Authenticator.
//
// See https://github.com/pquerna/otp/issues/55 for additional details.
AlgorithmSHA1 = xtp.AlgorithmSHA1
AlgorithmSHA256 = xtp.AlgorithmSHA256
AlgorithmSHA512 = xtp.AlgorithmSHA512
AlgorithmMD5 = xtp.AlgorithmMD5
)
var b32NoPadding = base32.StdEncoding.WithPadding(base32.NoPadding)
// KeyOpts provides options for Generate(). The default values
// are compatible with Google-Authenticator.
//
// Required: Issuer, AccountName, htop also need counter.
type KeyOpts struct {
// Name of the issuing Organization/Company.
Issuer string
// Name of the User's Account (eg, email address)
AccountName string
// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
Period uint
// Size in size of the generated Secret. Defaults to 20 bytes.
SecretSize uint
// Secret to store. Defaults to a randomly generated secret of SecretSize. You should generally leave this empty.
Secret []byte
// Digits to request. Defaults to 6.
Digits xtp.Digits
// Algorithm to use for HMAC. Defaults to SHA1.
Algorithm xtp.Algorithm
// Reader to use for generating TOTP Key.
Rand io.Reader
// Counter for HOTP. if type is hotp: The counter parameter is required when provisioning a key for use with HOTP. It will set the initial counter value.
Counter uint64
}
// RandomSecret generates a random secret of given length (number of bytes) without padding,
// if rand.Read failed returns empty string.
func RandomSecret(length int) (secret string) {
secretB := make([]byte, length)
gen, err := rand.Read(secretB)
if err != nil || gen != length {
return secret
}
secret = b32NoPadding.EncodeToString(secretB)
return
}
// VerifySecret verifies the secret is valid, support padding or NoPadding format.
func VerifySecret(secret string) bool {
secret = strings.TrimSpace(secret)
if n := len(secret) % 8; n != 0 {
secret = secret + strings.Repeat("=", 8-n)
}
_, err := base32.StdEncoding.DecodeString(secret)
return err == nil
}
// GenerateURLHOTP returns the HOTP URL as a string.
func GenerateURLHOTP(opts KeyOpts) (url string) {
if key, err := simpleURL(opts, "hotp"); err == nil {
key.Type()
url = key.URL()
}
return
}
// GenerateURLTOTP returns the TOTP URL as a string.
func GenerateURLTOTP(opts KeyOpts) (url string) {
if key, err := simpleURL(opts, "totp"); err == nil {
url = key.URL()
}
return
}
// KeyFromTOTPOpts creates a new TOTP Key.
func KeyFromTOTPOpts(opts KeyOpts) (*xtp.Key, error) {
return totp.Generate(totp.GenerateOpts{
Issuer: opts.Issuer,
AccountName: opts.AccountName,
Period: opts.Period,
Secret: opts.Secret,
SecretSize: opts.SecretSize,
Digits: opts.Digits,
Algorithm: opts.Algorithm,
Rand: opts.Rand,
})
}
// KeyFromHOTPOpts creates a new HOTP Key.
func KeyFromHOTPOpts(opts KeyOpts) (*xtp.Key, error) {
return hotp.Generate(hotp.GenerateOpts{
Issuer: opts.Issuer,
AccountName: opts.AccountName,
Secret: opts.Secret,
SecretSize: opts.SecretSize,
Digits: opts.Digits,
Algorithm: opts.Algorithm,
Rand: opts.Rand,
})
}
// KeyFromURL creates a new Key from an TOTP or HOTP url.
//
// The URL format is documented here:
//
// https://github.com/google/google-authenticator/wiki/Key-Uri-Format
func KeyFromURL(url string) (*xtp.Key, error) {
if len(url) == 0 {
return nil, errors.New("empty URL")
}
return xtp.NewKeyFromURL(url)
}
func simpleURL(opts KeyOpts, otpType string) (*xtp.Key, error) {
// url encode the Issuer/AccountName
if opts.Issuer == "" {
return nil, xtp.ErrGenerateMissingIssuer
}
if opts.AccountName == "" {
return nil, xtp.ErrGenerateMissingAccountName
}
if opts.SecretSize == 0 {
opts.SecretSize = 10
}
if opts.Rand == nil {
opts.Rand = rand.Reader
}
// otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example
v := url.Values{}
if len(opts.Secret) != 0 {
v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
} else {
secret := make([]byte, opts.SecretSize)
_, _ = opts.Rand.Read(secret)
v.Set("secret", b32NoPadding.EncodeToString(secret))
}
v.Set("issuer", opts.Issuer)
if opts.Digits == 0 {
opts.Digits = xtp.DigitsSix
} else {
v.Set("digits", opts.Digits.String())
}
if opts.Algorithm != xtp.AlgorithmSHA1 {
v.Set("algorithm", opts.Algorithm.String())
}
if otpType == "hotp" {
v.Set("counter", strconv.FormatUint(opts.Counter, 10))
}
u := url.URL{
Scheme: "otpauth",
Host: otpType,
Path: "/" + opts.Issuer + ":" + opts.AccountName,
RawQuery: v.Encode(),
}
return xtp.NewKeyFromURL(u.String())
}