123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- package hotp
- import (
- "github.com/pquerna/otp"
- "crypto/hmac"
- "crypto/rand"
- "crypto/subtle"
- "encoding/base32"
- "encoding/binary"
- "fmt"
- "math"
- "net/url"
- "strings"
- )
- const debug = false
- func Validate(passcode string, counter uint64, secret string) bool {
- rv, _ := ValidateCustom(
- passcode,
- counter,
- secret,
- ValidateOpts{
- Digits: otp.DigitsSix,
- Algorithm: otp.AlgorithmSHA1,
- },
- )
- return rv
- }
- type ValidateOpts struct {
-
- Digits otp.Digits
-
- Algorithm otp.Algorithm
- }
- func GenerateCode(secret string, counter uint64) (string, error) {
- return GenerateCodeCustom(secret, counter, ValidateOpts{
- Digits: otp.DigitsSix,
- Algorithm: otp.AlgorithmSHA1,
- })
- }
- func GenerateCodeCustom(secret string, counter uint64, opts ValidateOpts) (passcode string, err error) {
-
-
- if n := len(secret) % 8; n != 0 {
- secret = secret + strings.Repeat("=", 8-n)
- }
- secretBytes, err := base32.StdEncoding.DecodeString(secret)
- if err != nil {
- return "", otp.ErrValidateSecretInvalidBase32
- }
- buf := make([]byte, 8)
- mac := hmac.New(opts.Algorithm.Hash, secretBytes)
- binary.BigEndian.PutUint64(buf, counter)
- if debug {
- fmt.Printf("counter=%v\n", counter)
- fmt.Printf("buf=%v\n", buf)
- }
- mac.Write(buf)
- sum := mac.Sum(nil)
-
-
- offset := sum[len(sum)-1] & 0xf
- value := int64(((int(sum[offset]) & 0x7f) << 24) |
- ((int(sum[offset+1] & 0xff)) << 16) |
- ((int(sum[offset+2] & 0xff)) << 8) |
- (int(sum[offset+3]) & 0xff))
- l := opts.Digits.Length()
- mod := int32(value % int64(math.Pow10(l)))
- if debug {
- fmt.Printf("offset=%v\n", offset)
- fmt.Printf("value=%v\n", value)
- fmt.Printf("mod'ed=%v\n", mod)
- }
- return opts.Digits.Format(mod), nil
- }
- func ValidateCustom(passcode string, counter uint64, secret string, opts ValidateOpts) (bool, error) {
- passcode = strings.TrimSpace(passcode)
- if len(passcode) != opts.Digits.Length() {
- return false, otp.ErrValidateInputInvalidLength
- }
- otpstr, err := GenerateCodeCustom(secret, counter, opts)
- if err != nil {
- return false, err
- }
- if subtle.ConstantTimeCompare([]byte(otpstr), []byte(passcode)) == 1 {
- return true, nil
- }
- return false, nil
- }
- type GenerateOpts struct {
-
- Issuer string
-
- AccountName string
-
- SecretSize uint
-
- Digits otp.Digits
-
- Algorithm otp.Algorithm
- }
- func Generate(opts GenerateOpts) (*otp.Key, error) {
-
- if opts.Issuer == "" {
- return nil, otp.ErrGenerateMissingIssuer
- }
- if opts.AccountName == "" {
- return nil, otp.ErrGenerateMissingAccountName
- }
- if opts.SecretSize == 0 {
- opts.SecretSize = 10
- }
-
- v := url.Values{}
- secret := make([]byte, opts.SecretSize)
- _, err := rand.Read(secret)
- if err != nil {
- return nil, err
- }
- v.Set("secret", base32.StdEncoding.EncodeToString(secret))
- v.Set("issuer", opts.Issuer)
- v.Set("algorithm", opts.Algorithm.String())
- v.Set("digits", opts.Digits.String())
- u := url.URL{
- Scheme: "otpauth",
- Host: "hotp",
- Path: "/" + opts.Issuer + ":" + opts.AccountName,
- RawQuery: v.Encode(),
- }
- return otp.NewKeyFromURL(u.String())
- }
|