// Package otp provides support for HOTP (RFC4226) and TOTP (RFC6238) One-Time Passwords package otp import ( "crypto/rand" "crypto/sha1" "encoding/base32" "net/url" "strconv" "strings" ) // Key is an interface that defines the charactaristics that specific implementations of OTP Keys must have type Key interface { Type() Type Label() string Issuer() string Length() uint AllowedSkew() uint64 EncodeSecret() string DecodeSecret(string) error Secret() []byte URL() string SetSkew(deltas uint64) // OTP generates and returns the next One-Time Password to be used OTP() string // Verify checks if the provided code(OTP) is acceptable. Takes an optional counter to bypass the counter set in the key Verify(code string, counter ...uint64) bool //IntegrityCheck returns the base (counter @ 0) OTP code and the current counter value IntegrityCheck() (string, uint64) } // KeyOptions stores the settings that can be used in creating keys type KeyOptions struct { Secret []byte Length uint Label string Issuer string AllowedSkew uint64 } // NewKeyOptions creates and returns KeyOptions using the default values and a random secret func NewKeyOptions() KeyOptions { o := KeyOptions{} o.Issuer = DefaultIssuer o.AllowedSkew = DefaultAllowedSkew o.Secret = make([]byte, sha1.Size) rand.Read(o.Secret) o.Length = DefaultLength o.Label = DefaultLabel return o } type baseKey struct { opts KeyOptions keyType Type } // Issuer returns the Issuer of the key func (k *baseKey) Issuer() string { return k.opts.Issuer } // Label returns the label or username of the key func (k *baseKey) Label() string { return k.opts.Label } // Type returns the Type value of the key - should be HOTP or TOTP func (k *baseKey) Type() Type { return k.keyType } // Secret returns the keys secret func (k *baseKey) Secret() []byte { return k.opts.Secret } // AllowedSkew returns the value of how many codes before and after the current code should be checked func (k *baseKey) AllowedSkew() uint64 { return k.opts.AllowedSkew } // SetSkew sets the value of how many codes both before and after the current code should be checked for code verification func (k *baseKey) SetSkew(delta uint64) { k.opts.AllowedSkew = delta } // Length returns the digit length of codes that will be generated func (k *baseKey) Length() uint { return k.opts.Length } // EncodedSecret converts the secret to base32 encoding func (k *baseKey) EncodeSecret() string { return base32.StdEncoding.EncodeToString(k.opts.Secret) } // DecodeSecret converts an encoded string to the secret byte value and sets it to the keys secret func (k *baseKey) DecodeSecret(s string) error { var err error k.opts.Secret, err = base32.StdEncoding.DecodeString(s) return err } func (k *baseKey) verify(counter uint64, code string) bool { counters := []uint64{counter} var i uint64 // Create slice of counters within the allowed skew range for i = 1; i <= k.AllowedSkew(); i++ { counters = append(counters, counter+i) counters = append(counters, counter-i) } // As soon as one of the counters generates the same code, the code is verified valid for _, c := range counters { if k.check(code, c) { return true } } return false } // URL converts a key to URL format as detailed at https://github.com/google/google-authenticator/wiki/Key-Uri-Format func (k *baseKey) URL() (url.URL, url.Values) { u := url.URL{} vals := url.Values{} u.Scheme = Scheme u.Host = k.keyType.String() u.Path = k.opts.Issuer + ":" + k.opts.Label vals.Add("secret", k.EncodeSecret()) vals.Add("digits", strconv.FormatUint(uint64(k.opts.Length), 10)) if len(k.opts.Issuer) != 0 { vals.Add("issuer", k.opts.Issuer) } return u, vals } // FromURL parses an OTP URL and creates the relevant Key // The URL/URI is specified at https://github.com/google/google-authenticator/wiki/Key-Uri-Format func FromURL(URL string) (k Key, err error) { u, err := url.Parse(URL) if err != nil { return nil, err } //Verify URL if u.Scheme != Scheme { return nil, Error("Invalid scheme found") } t := NewType(u.Host) if t == UnknownType { return nil, Error("Invalid type found") } vals := u.Query() b := baseKey{} b.keyType = t s := vals.Get("secret") if len(s) == 0 { return nil, Error("Secret not found") } err = b.DecodeSecret(s) d, err := strconv.ParseUint(vals.Get("digits"), 0, 0) if err == nil { b.opts.Length = uint(d) } else { b.opts.Length = DefaultLength } b.opts.Issuer = vals.Get("issuer") parts := strings.Split(u.Path, ":") b.opts.Label = parts[len(parts)-1] // Algorithm is an available parameter - maybe it will be used in the future switch t { case HOTP: h := HOTPKey{} h.baseKey = b h.counter, _ = strconv.ParseUint(vals.Get("counter"), 0, 0) k = &h case TOTP: t := TOTPKey{} t.baseKey = b t.period, _ = strconv.ParseUint(vals.Get("period"), 0, 0) k = &t } return k, nil }