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()) }