...

Source file src/github.com/pquerna/otp/totp/totp.go

Documentation: github.com/pquerna/otp/totp

     1  /**
     2   *  Copyright 2014 Paul Querna
     3   *
     4   *  Licensed under the Apache License, Version 2.0 (the "License");
     5   *  you may not use this file except in compliance with the License.
     6   *  You may obtain a copy of the License at
     7   *
     8   *      http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   *  Unless required by applicable law or agreed to in writing, software
    11   *  distributed under the License is distributed on an "AS IS" BASIS,
    12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   *  See the License for the specific language governing permissions and
    14   *  limitations under the License.
    15   *
    16   */
    17  
    18  package totp
    19  
    20  import (
    21  	"github.com/pquerna/otp"
    22  	"github.com/pquerna/otp/hotp"
    23  	"io"
    24  
    25  	"crypto/rand"
    26  	"encoding/base32"
    27  	"math"
    28  	"net/url"
    29  	"strconv"
    30  	"time"
    31  )
    32  
    33  // Validate a TOTP using the current time.
    34  // A shortcut for ValidateCustom, Validate uses a configuration
    35  // that is compatible with Google-Authenticator and most clients.
    36  func Validate(passcode string, secret string) bool {
    37  	rv, _ := ValidateCustom(
    38  		passcode,
    39  		secret,
    40  		time.Now().UTC(),
    41  		ValidateOpts{
    42  			Period:    30,
    43  			Skew:      1,
    44  			Digits:    otp.DigitsSix,
    45  			Algorithm: otp.AlgorithmSHA1,
    46  		},
    47  	)
    48  	return rv
    49  }
    50  
    51  // GenerateCode creates a TOTP token using the current time.
    52  // A shortcut for GenerateCodeCustom, GenerateCode uses a configuration
    53  // that is compatible with Google-Authenticator and most clients.
    54  func GenerateCode(secret string, t time.Time) (string, error) {
    55  	return GenerateCodeCustom(secret, t, ValidateOpts{
    56  		Period:    30,
    57  		Skew:      1,
    58  		Digits:    otp.DigitsSix,
    59  		Algorithm: otp.AlgorithmSHA1,
    60  	})
    61  }
    62  
    63  // ValidateOpts provides options for ValidateCustom().
    64  type ValidateOpts struct {
    65  	// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
    66  	Period uint
    67  	// Periods before or after the current time to allow.  Value of 1 allows up to Period
    68  	// of either side of the specified time.  Defaults to 0 allowed skews.  Values greater
    69  	// than 1 are likely sketchy.
    70  	Skew uint
    71  	// Digits as part of the input. Defaults to 6.
    72  	Digits otp.Digits
    73  	// Algorithm to use for HMAC. Defaults to SHA1.
    74  	Algorithm otp.Algorithm
    75  }
    76  
    77  // GenerateCodeCustom takes a timepoint and produces a passcode using a
    78  // secret and the provided opts. (Under the hood, this is making an adapted
    79  // call to hotp.GenerateCodeCustom)
    80  func GenerateCodeCustom(secret string, t time.Time, opts ValidateOpts) (passcode string, err error) {
    81  	if opts.Period == 0 {
    82  		opts.Period = 30
    83  	}
    84  	counter := uint64(math.Floor(float64(t.Unix()) / float64(opts.Period)))
    85  	passcode, err = hotp.GenerateCodeCustom(secret, counter, hotp.ValidateOpts{
    86  		Digits:    opts.Digits,
    87  		Algorithm: opts.Algorithm,
    88  	})
    89  	if err != nil {
    90  		return "", err
    91  	}
    92  	return passcode, nil
    93  }
    94  
    95  // ValidateCustom validates a TOTP given a user specified time and custom options.
    96  // Most users should use Validate() to provide an interpolatable TOTP experience.
    97  func ValidateCustom(passcode string, secret string, t time.Time, opts ValidateOpts) (bool, error) {
    98  	if opts.Period == 0 {
    99  		opts.Period = 30
   100  	}
   101  
   102  	counters := []uint64{}
   103  	counter := int64(math.Floor(float64(t.Unix()) / float64(opts.Period)))
   104  
   105  	counters = append(counters, uint64(counter))
   106  	for i := 1; i <= int(opts.Skew); i++ {
   107  		counters = append(counters, uint64(counter+int64(i)))
   108  		counters = append(counters, uint64(counter-int64(i)))
   109  	}
   110  
   111  	for _, counter := range counters {
   112  		rv, err := hotp.ValidateCustom(passcode, counter, secret, hotp.ValidateOpts{
   113  			Digits:    opts.Digits,
   114  			Algorithm: opts.Algorithm,
   115  		})
   116  
   117  		if err != nil {
   118  			return false, err
   119  		}
   120  
   121  		if rv == true {
   122  			return true, nil
   123  		}
   124  	}
   125  
   126  	return false, nil
   127  }
   128  
   129  // GenerateOpts provides options for Generate().  The default values
   130  // are compatible with Google-Authenticator.
   131  type GenerateOpts struct {
   132  	// Name of the issuing Organization/Company.
   133  	Issuer string
   134  	// Name of the User's Account (eg, email address)
   135  	AccountName string
   136  	// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
   137  	Period uint
   138  	// Size in size of the generated Secret. Defaults to 20 bytes.
   139  	SecretSize uint
   140  	// Secret to store. Defaults to a randomly generated secret of SecretSize.  You should generally leave this empty.
   141  	Secret []byte
   142  	// Digits to request. Defaults to 6.
   143  	Digits otp.Digits
   144  	// Algorithm to use for HMAC. Defaults to SHA1.
   145  	Algorithm otp.Algorithm
   146  	// Reader to use for generating TOTP Key.
   147  	Rand io.Reader
   148  }
   149  
   150  var b32NoPadding = base32.StdEncoding.WithPadding(base32.NoPadding)
   151  
   152  // Generate a new TOTP Key.
   153  func Generate(opts GenerateOpts) (*otp.Key, error) {
   154  	// url encode the Issuer/AccountName
   155  	if opts.Issuer == "" {
   156  		return nil, otp.ErrGenerateMissingIssuer
   157  	}
   158  
   159  	if opts.AccountName == "" {
   160  		return nil, otp.ErrGenerateMissingAccountName
   161  	}
   162  
   163  	if opts.Period == 0 {
   164  		opts.Period = 30
   165  	}
   166  
   167  	if opts.SecretSize == 0 {
   168  		opts.SecretSize = 20
   169  	}
   170  
   171  	if opts.Digits == 0 {
   172  		opts.Digits = otp.DigitsSix
   173  	}
   174  
   175  	if opts.Rand == nil {
   176  		opts.Rand = rand.Reader
   177  	}
   178  
   179  	// otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
   180  
   181  	v := url.Values{}
   182  	if len(opts.Secret) != 0 {
   183  		v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
   184  	} else {
   185  		secret := make([]byte, opts.SecretSize)
   186  		_, err := opts.Rand.Read(secret)
   187  		if err != nil {
   188  			return nil, err
   189  		}
   190  		v.Set("secret", b32NoPadding.EncodeToString(secret))
   191  	}
   192  
   193  	v.Set("issuer", opts.Issuer)
   194  	v.Set("period", strconv.FormatUint(uint64(opts.Period), 10))
   195  	v.Set("algorithm", opts.Algorithm.String())
   196  	v.Set("digits", opts.Digits.String())
   197  
   198  	u := url.URL{
   199  		Scheme:   "otpauth",
   200  		Host:     "totp",
   201  		Path:     "/" + opts.Issuer + ":" + opts.AccountName,
   202  		RawQuery: v.Encode(),
   203  	}
   204  
   205  	return otp.NewKeyFromURL(u.String())
   206  }
   207  

View as plain text