diff options
Diffstat (limited to 'vendor/github.com/pquerna/otp/hotp/hotp.go')
-rw-r--r-- | vendor/github.com/pquerna/otp/hotp/hotp.go | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/vendor/github.com/pquerna/otp/hotp/hotp.go b/vendor/github.com/pquerna/otp/hotp/hotp.go new file mode 100644 index 0000000000..62cdc87f41 --- /dev/null +++ b/vendor/github.com/pquerna/otp/hotp/hotp.go @@ -0,0 +1,181 @@ +/** + * Copyright 2014 Paul Querna + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +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 + +// Validate a HOTP passcode given a counter and secret. +// This is a shortcut for ValidateCustom, with parameters that +// are compataible with Google-Authenticator. +func Validate(passcode string, counter uint64, secret string) bool { + rv, _ := ValidateCustom( + passcode, + counter, + secret, + ValidateOpts{ + Digits: otp.DigitsSix, + Algorithm: otp.AlgorithmSHA1, + }, + ) + return rv +} + +// ValidateOpts provides options for ValidateCustom(). +type ValidateOpts struct { + // Digits as part of the input. Defaults to 6. + Digits otp.Digits + // Algorithm to use for HMAC. Defaults to SHA1. + Algorithm otp.Algorithm +} + +// GenerateCode creates a HOTP passcode given a counter and secret. +// This is a shortcut for GenerateCodeCustom, with parameters that +// are compataible with Google-Authenticator. +func GenerateCode(secret string, counter uint64) (string, error) { + return GenerateCodeCustom(secret, counter, ValidateOpts{ + Digits: otp.DigitsSix, + Algorithm: otp.AlgorithmSHA1, + }) +} + +// GenerateCodeCustom uses a counter and secret value and options struct to +// create a passcode. +func GenerateCodeCustom(secret string, counter uint64, opts ValidateOpts) (passcode string, err error) { + 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) + + // "Dynamic truncation" in RFC 4226 + // http://tools.ietf.org/html/rfc4226#section-5.4 + 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 +} + +// ValidateCustom validates an HOTP with customizable options. Most users should +// use Validate(). +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 +} + +// GenerateOpts provides options for .Generate() +type GenerateOpts struct { + // Name of the issuing Organization/Company. + Issuer string + // Name of the User's Account (eg, email address) + AccountName string + // Size in size of the generated Secret. Defaults to 10 bytes. + SecretSize uint + // Digits to request. Defaults to 6. + Digits otp.Digits + // Algorithm to use for HMAC. Defaults to SHA1. + Algorithm otp.Algorithm +} + +// Generate creates a new HOTP Key. +func Generate(opts GenerateOpts) (*otp.Key, error) { + // url encode the Issuer/AccountName + if opts.Issuer == "" { + return nil, otp.ErrGenerateMissingIssuer + } + + if opts.AccountName == "" { + return nil, otp.ErrGenerateMissingAccountName + } + + if opts.SecretSize == 0 { + opts.SecretSize = 10 + } + + // otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example + + 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()) +} |