package otp import ( "bytes" "crypto/hmac" "crypto/sha1" "crypto/subtle" "encoding/binary" "fmt" "math/big" "strings" ) const ( Scheme = "otpauth" DefaultLength = 6 DefaultPeriod = uint64(30) DefaultIssuer = "justinjudd.org" DefaultAllowedSkew = 1 DefaultLabel = "test" DefaultCounter = uint64(0) ) // Error is a custom error type for this package type Error string // Error satisfies the error interface func (e Error) Error() string { return "otp: " + string(e) } // Type is used to differentiate different OTP types - HOTP and TOTP type Type int const ( UnknownType Type = iota HOTP TOTP ) // String satisfies the Stringer interface for Type func (t Type) String() string { switch t { case HOTP: return "hotp" case TOTP: return "totp" default: return "Unknown" } } // NewType creates a Type object from its string representation func NewType(t string) Type { switch strings.ToLower(t) { case "hotp": return HOTP case "totp": return TOTP default: return UnknownType } } // getOTP creates a Int representation of a OTP func getOTP(secret []byte, counter uint64, length uint) *big.Int { mac := hmac.New(sha1.New, secret) b := new(big.Int) b.SetUint64(counter) buf := new(bytes.Buffer) binary.Write(buf, binary.BigEndian, b.Uint64()) mac.Write(buf.Bytes()) s := mac.Sum(nil) i := dynamicTruncation(s) t := new(big.Int) t.Exp(big.NewInt(10), big.NewInt(int64(length)), nil) i.Mod(i, t) return i } // dynamicTruncation performs the dynamic Truncation for step 2 as defined in RFC4226 func dynamicTruncation(d []byte) *big.Int { offset := d[len(d)-1] & 0xf v := new(big.Int) v.SetBytes(d[offset : offset+4]) mask := big.NewInt(0x7fffffff) v.And(v, mask) return v } func (k *baseKey) check(code string, counter uint64) bool { i := getOTP(k.opts.Secret, counter, k.opts.Length) return subtle.ConstantTimeCompare([]byte(formatCode(i, uint(len(code)))), []byte(code)) == 1 } func (k *baseKey) formatCode(i *big.Int) string { return formatCode(i, k.opts.Length) } func formatCode(i *big.Int, length uint) string { return fmt.Sprintf(fmt.Sprintf("%%0%dd", length), i.Int64()) } // ValidateCustom allows validating an OTP type code without access to the key // Also allows using a custom counter value - counter for HOTP or time since epoch for TOTP func ValidateCustom(secret []byte, counter uint64, code string) bool { k := baseKey{} k.opts.Secret = secret k.opts.Length = uint(len(code)) return k.check(code, counter) } // CustomCode creates and returns an OTP code with the given func CustomCode(secret []byte, counter uint64, length uint) string { i := getOTP(secret, counter, length) return formatCode(i, length) }