* update code.gitea.io/sdk/gitea v0.13.1 -> v0.13.2 * update github.com/go-swagger/go-swagger v0.25.0 -> v0.26.0 * update github.com/google/uuid v1.1.2 -> v1.2.0 * update github.com/klauspost/compress v1.11.3 -> v1.11.7 * update github.com/lib/pq 083382b7e6fc -> v1.9.0 * update github.com/markbates/goth v1.65.0 -> v1.66.1 * update github.com/mattn/go-sqlite3 v1.14.4 -> v1.14.6 * update github.com/mgechev/revive 246eac737dc7 -> v1.0.3 * update github.com/minio/minio-go/v7 v7.0.6 -> v7.0.7 * update github.com/niklasfasching/go-org v1.3.2 -> v1.4.0 * update github.com/olivere/elastic/v7 v7.0.21 -> v7.0.22 * update github.com/pquerna/otp v1.2.0 -> v1.3.0 * update github.com/xanzy/go-gitlab v0.39.0 -> v0.42.0 * update github.com/yuin/goldmark v1.2.1 -> v1.3.1
		
			
				
	
	
		
			211 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/**
 | 
						|
 *  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"
 | 
						|
	"io"
 | 
						|
 | 
						|
	"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) {
 | 
						|
	// As noted in issue #10 and #17 this adds support for TOTP secrets that are
 | 
						|
	// missing their padding.
 | 
						|
	secret = strings.TrimSpace(secret)
 | 
						|
	if n := len(secret) % 8; n != 0 {
 | 
						|
		secret = secret + strings.Repeat("=", 8-n)
 | 
						|
	}
 | 
						|
 | 
						|
	// As noted in issue #24 Google has started producing base32 in lower case,
 | 
						|
	// but the StdEncoding (and the RFC), expect a dictionary of only upper case letters.
 | 
						|
	secret = strings.ToUpper(secret)
 | 
						|
 | 
						|
	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
 | 
						|
	// Secret to store. Defaults to a randomly generated secret of SecretSize.  You should generally leave this empty.
 | 
						|
	Secret []byte
 | 
						|
	// Digits to request. Defaults to 6.
 | 
						|
	Digits otp.Digits
 | 
						|
	// Algorithm to use for HMAC. Defaults to SHA1.
 | 
						|
	Algorithm otp.Algorithm
 | 
						|
	// Reader to use for generating HOTP Key.
 | 
						|
	Rand io.Reader
 | 
						|
}
 | 
						|
 | 
						|
var b32NoPadding = base32.StdEncoding.WithPadding(base32.NoPadding)
 | 
						|
 | 
						|
// 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
 | 
						|
	}
 | 
						|
 | 
						|
	if opts.Digits == 0 {
 | 
						|
		opts.Digits = otp.DigitsSix
 | 
						|
	}
 | 
						|
 | 
						|
	if opts.Rand == nil {
 | 
						|
		opts.Rand = rand.Reader
 | 
						|
	}
 | 
						|
 | 
						|
	// otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
 | 
						|
 | 
						|
	v := url.Values{}
 | 
						|
	if len(opts.Secret) != 0 {
 | 
						|
		v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
 | 
						|
	} else {
 | 
						|
		secret := make([]byte, opts.SecretSize)
 | 
						|
		_, err := opts.Rand.Read(secret)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		v.Set("secret", b32NoPadding.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())
 | 
						|
}
 |