otp/totp.go

92 lines
2.3 KiB
Go

package otp
import (
"strconv"
"time"
)
// TOTPKeyOptions represents the settings or values to use when creating a TOTP Key
type TOTPKeyOptions struct {
KeyOptions
Period uint64
}
// NewTOTPKeyOptions returns a TOTPKeyOptions using sane default values and a secret key
func NewTOTPKeyOptions() TOTPKeyOptions {
return TOTPKeyOptions{
NewKeyOptions(),
DefaultPeriod,
}
}
// TOTPKey represents the TOTP family of OTP as defined in RFC6238
type TOTPKey struct {
baseKey
period uint64
}
// NewTOTPKey creates and returns a new TOTP key
// If no KeyOptions are provided, sane defaults and a random secret will be used
func NewTOTPKey(o ...TOTPKeyOptions) Key {
var opts TOTPKeyOptions
if len(o) == 0 {
opts = NewTOTPKeyOptions()
} else {
opts = o[0]
}
t := &TOTPKey{}
t.baseKey = baseKey{}
t.opts = opts.KeyOptions
t.keyType = TOTP
t.period = opts.Period
return t
}
// Period returns the time period this TOTP Key uses - default is 30 seconds
func (k *TOTPKey) Period() uint64 {
return k.period
}
func (k *TOTPKey) timeToCounter(t time.Time) uint64 {
return TimeToCounter(t, k.period)
}
// TimeToCounter converts a provided time to a counter to be used for the OTP
func TimeToCounter(t time.Time, period uint64) uint64 {
return uint64(t.Unix() / int64(period))
}
// OTP produces a one-time use code
func (k *TOTPKey) OTP() string {
i := getOTP(k.Secret(), k.timeToCounter(time.Now()), k.Length())
return k.formatCode(i)
}
// URL creates a relevant URL to distribute/share the key as detailed at https://github.com/google/google-authenticator/wiki/Key-Uri-Format
func (k *TOTPKey) URL() string {
u, vals := k.baseKey.URL()
vals.Add("period", strconv.FormatUint(k.period, 10))
u.RawQuery = vals.Encode()
return u.String()
}
// Verify compares the provided code with different potential codes within the allowed skew range. Counter should be Seconds since the Unix Epoch
func (k *TOTPKey) Verify(code string, counter ...uint64) bool {
c := k.timeToCounter(time.Now())
if len(counter) > 0 {
c = counter[0] / k.period
}
return k.verify(c, code)
}
// IntegrityCheck provides information to verify the key is the same one used in Google Authenticator - doesn't appear to be used for TOTP keys though
func (k *TOTPKey) IntegrityCheck() (string, uint64) {
i := getOTP(k.Secret(), 0, k.Length())
return k.formatCode(i), k.timeToCounter(time.Now())
}