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 184 185 186 187 188 189 190 191 192 193
|
/**
* Copyright 2014 Paul Querna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package totp
import (
"strings"
"github.com/pquerna/otp"
"github.com/pquerna/otp/hotp"
"crypto/rand"
"encoding/base32"
"math"
"net/url"
"strconv"
"time"
)
// Validate a TOTP using the current time.
// A shortcut for ValidateCustom, Validate uses a configuration
// that is compatible with Google-Authenticator and most clients.
func Validate(passcode string, secret string) bool {
rv, _ := ValidateCustom(
passcode,
secret,
time.Now().UTC(),
ValidateOpts{
Period: 30,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
},
)
return rv
}
// GenerateCode creates a TOTP token using the current time.
// A shortcut for GenerateCodeCustom, GenerateCode uses a configuration
// that is compatible with Google-Authenticator and most clients.
func GenerateCode(secret string, t time.Time) (string, error) {
return GenerateCodeCustom(secret, t, ValidateOpts{
Period: 30,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
}
// ValidateOpts provides options for ValidateCustom().
type ValidateOpts struct {
// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
Period uint
// Periods before or after the current time to allow. Value of 1 allows up to Period
// of either side of the specified time. Defaults to 0 allowed skews. Values greater
// than 1 are likely sketchy.
Skew uint
// Digits as part of the input. Defaults to 6.
Digits otp.Digits
// Algorithm to use for HMAC. Defaults to SHA1.
Algorithm otp.Algorithm
}
// GenerateCodeCustom takes a timepoint and produces a passcode using a
// secret and the provided opts. (Under the hood, this is making an adapted
// call to hotp.GenerateCodeCustom)
func GenerateCodeCustom(secret string, t time.Time, opts ValidateOpts) (passcode string, err error) {
if opts.Period == 0 {
opts.Period = 30
}
counter := uint64(math.Floor(float64(t.Unix()) / float64(opts.Period)))
passcode, err = hotp.GenerateCodeCustom(secret, counter, hotp.ValidateOpts{
Digits: opts.Digits,
Algorithm: opts.Algorithm,
})
if err != nil {
return "", err
}
return passcode, nil
}
// ValidateCustom validates a TOTP given a user specified time and custom options.
// Most users should use Validate() to provide an interpolatable TOTP experience.
func ValidateCustom(passcode string, secret string, t time.Time, opts ValidateOpts) (bool, error) {
if opts.Period == 0 {
opts.Period = 30
}
counters := []uint64{}
counter := int64(math.Floor(float64(t.Unix()) / float64(opts.Period)))
counters = append(counters, uint64(counter))
for i := 1; i <= int(opts.Skew); i++ {
counters = append(counters, uint64(counter+int64(i)))
counters = append(counters, uint64(counter-int64(i)))
}
for _, counter := range counters {
rv, err := hotp.ValidateCustom(passcode, counter, secret, hotp.ValidateOpts{
Digits: opts.Digits,
Algorithm: opts.Algorithm,
})
if err != nil {
return false, err
}
if rv == true {
return true, nil
}
}
return false, nil
}
// GenerateOpts provides options for Generate(). The default values
// are compatible with Google-Authenticator.
type GenerateOpts 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 10 bytes.
SecretSize uint
// Digits to request. Defaults to 6.
Digits otp.Digits
// Algorithm to use for HMAC. Defaults to SHA1.
Algorithm otp.Algorithm
}
// Generate a new TOTP Key.
func Generate(opts GenerateOpts) (*otp.Key, error) {
// url encode the Issuer/AccountName
if opts.Issuer == "" {
return nil, otp.ErrGenerateMissingIssuer
}
if opts.AccountName == "" {
return nil, otp.ErrGenerateMissingAccountName
}
if opts.Period == 0 {
opts.Period = 30
}
if opts.SecretSize == 0 {
opts.SecretSize = 10
}
if opts.Digits == 0 {
opts.Digits = otp.DigitsSix
}
// otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
v := url.Values{}
secret := make([]byte, opts.SecretSize)
_, err := rand.Read(secret)
if err != nil {
return nil, err
}
v.Set("secret", strings.TrimRight(base32.StdEncoding.EncodeToString(secret), "="))
v.Set("issuer", opts.Issuer)
v.Set("period", strconv.FormatUint(uint64(opts.Period), 10))
v.Set("algorithm", opts.Algorithm.String())
v.Set("digits", opts.Digits.String())
u := url.URL{
Scheme: "otpauth",
Host: "totp",
Path: "/" + opts.Issuer + ":" + opts.AccountName,
RawQuery: v.Encode(),
}
return otp.NewKeyFromURL(u.String())
}
|