Skip to content

Commit ecc3285

Browse files
committed
Allow calls to {htop,totp}.Generate() to specify the secret, rather than using a randomly generated one.
Fixes #37
1 parent 1e101fa commit ecc3285

File tree

4 files changed

+42
-10
lines changed

4 files changed

+42
-10
lines changed

hotp/hotp.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,8 @@ type GenerateOpts struct {
146146
AccountName string
147147
// Size in size of the generated Secret. Defaults to 10 bytes.
148148
SecretSize uint
149+
// Secret to store. Defaults to a randomly generated secret of SecretSize. You should generally leave this empty.
150+
Secret []byte
149151
// Digits to request. Defaults to 6.
150152
Digits otp.Digits
151153
// Algorithm to use for HMAC. Defaults to SHA1.
@@ -176,13 +178,17 @@ func Generate(opts GenerateOpts) (*otp.Key, error) {
176178
// otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example
177179

178180
v := url.Values{}
179-
secret := make([]byte, opts.SecretSize)
180-
_, err := rand.Read(secret)
181-
if err != nil {
182-
return nil, err
181+
if len(opts.Secret) != 0 {
182+
v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
183+
} else {
184+
secret := make([]byte, opts.SecretSize)
185+
_, err := rand.Read(secret)
186+
if err != nil {
187+
return nil, err
188+
}
189+
v.Set("secret", b32NoPadding.EncodeToString(secret))
183190
}
184191

185-
v.Set("secret", b32NoPadding.EncodeToString(secret))
186192
v.Set("issuer", opts.Issuer)
187193
v.Set("algorithm", opts.Algorithm.String())
188194
v.Set("digits", opts.Digits.String())

hotp/hotp_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,14 @@ func TestGenerate(t *testing.T) {
169169
})
170170
require.NoError(t, err, "Secret size is valid when length not divisable by 5.")
171171
require.NotContains(t, k.Secret(), "=", "Secret has no escaped characters.")
172+
173+
k, err = Generate(GenerateOpts{
174+
Issuer: "SnakeOil",
175+
AccountName: "[email protected]",
176+
Secret: []byte("helloworld"),
177+
})
178+
require.NoError(t, err, "Secret generation failed")
179+
sec, err := b32NoPadding.DecodeString(k.Secret())
180+
require.NoError(t, err, "Secret wa not valid base32")
181+
require.Equal(t, sec, []byte("helloworld"), "Specified Secret was not kept")
172182
}

totp/totp.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ type GenerateOpts struct {
136136
Period uint
137137
// Size in size of the generated Secret. Defaults to 20 bytes.
138138
SecretSize uint
139+
// Secret to store. Defaults to a randomly generated secret of SecretSize. You should generally leave this empty.
140+
Secret []byte
139141
// Digits to request. Defaults to 6.
140142
Digits otp.Digits
141143
// Algorithm to use for HMAC. Defaults to SHA1.
@@ -170,13 +172,17 @@ func Generate(opts GenerateOpts) (*otp.Key, error) {
170172
// otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP&issuer=Example
171173

172174
v := url.Values{}
173-
secret := make([]byte, opts.SecretSize)
174-
_, err := rand.Read(secret)
175-
if err != nil {
176-
return nil, err
175+
if len(opts.Secret) != 0 {
176+
v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
177+
} else {
178+
secret := make([]byte, opts.SecretSize)
179+
_, err := rand.Read(secret)
180+
if err != nil {
181+
return nil, err
182+
}
183+
v.Set("secret", b32NoPadding.EncodeToString(secret))
177184
}
178185

179-
v.Set("secret", b32NoPadding.EncodeToString(secret))
180186
v.Set("issuer", opts.Issuer)
181187
v.Set("period", strconv.FormatUint(uint64(opts.Period), 10))
182188
v.Set("algorithm", opts.Algorithm.String())

totp/totp_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,16 @@ func TestGenerate(t *testing.T) {
143143
})
144144
require.NoError(t, err, "Secret size is valid when length not divisable by 5.")
145145
require.NotContains(t, k.Secret(), "=", "Secret has no escaped characters.")
146+
147+
k, err = Generate(GenerateOpts{
148+
Issuer: "SnakeOil",
149+
AccountName: "[email protected]",
150+
Secret: []byte("helloworld"),
151+
})
152+
require.NoError(t, err, "Secret generation failed")
153+
sec, err := b32NoPadding.DecodeString(k.Secret())
154+
require.NoError(t, err, "Secret wa not valid base32")
155+
require.Equal(t, sec, []byte("helloworld"), "Specified Secret was not kept")
146156
}
147157

148158
func TestGoogleLowerCaseSecret(t *testing.T) {

0 commit comments

Comments
 (0)