mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-31 14:31:02 +00:00 
			
		
		
		
	* Initial commit for 2FA support Signed-off-by: Andrew <write@imaginarycode.com> * Add vendored files * Add missing depends * A few clean ups * Added improvements, proper encryption * Better encryption key * Simplify "key" generation * Make 2FA enrollment page more robust * Fix typo * Rename twofa/2FA to TwoFactor * UNIQUE INDEX -> UNIQUE
		
			
				
	
	
		
			191 lines
		
	
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			191 lines
		
	
	
	
		
			5.1 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 totp
 | |
| 
 | |
| import (
 | |
| 	"github.com/pquerna/otp"
 | |
| 	"github.com/pquerna/otp/hotp"
 | |
| 
 | |
| 	"crypto/rand"
 | |
| 	"encoding/base32"
 | |
| 	"math"
 | |
| 	"net/url"
 | |
| 	"strconv"
 | |
| 	"time"
 | |
| )
 | |
| 
 | |
| // Validate a TOTP using the current time.
 | |
| // A shortcut for ValidateCustom, Validate uses a configuration
 | |
| // that is compatible with Google-Authenticator and most clients.
 | |
| func Validate(passcode string, secret string) bool {
 | |
| 	rv, _ := ValidateCustom(
 | |
| 		passcode,
 | |
| 		secret,
 | |
| 		time.Now().UTC(),
 | |
| 		ValidateOpts{
 | |
| 			Period:    30,
 | |
| 			Skew:      1,
 | |
| 			Digits:    otp.DigitsSix,
 | |
| 			Algorithm: otp.AlgorithmSHA1,
 | |
| 		},
 | |
| 	)
 | |
| 	return rv
 | |
| }
 | |
| 
 | |
| // GenerateCode creates a TOTP token using the current time.
 | |
| // A shortcut for GenerateCodeCustom, GenerateCode uses a configuration
 | |
| // that is compatible with Google-Authenticator and most clients.
 | |
| func GenerateCode(secret string, t time.Time) (string, error) {
 | |
| 	return GenerateCodeCustom(secret, t, ValidateOpts{
 | |
| 		Period:    30,
 | |
| 		Skew:      1,
 | |
| 		Digits:    otp.DigitsSix,
 | |
| 		Algorithm: otp.AlgorithmSHA1,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // ValidateOpts provides options for ValidateCustom().
 | |
| type ValidateOpts struct {
 | |
| 	// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
 | |
| 	Period uint
 | |
| 	// Periods before or after the current time to allow.  Value of 1 allows up to Period
 | |
| 	// of either side of the specified time.  Defaults to 0 allowed skews.  Values greater
 | |
| 	// than 1 are likely sketchy.
 | |
| 	Skew uint
 | |
| 	// Digits as part of the input. Defaults to 6.
 | |
| 	Digits otp.Digits
 | |
| 	// Algorithm to use for HMAC. Defaults to SHA1.
 | |
| 	Algorithm otp.Algorithm
 | |
| }
 | |
| 
 | |
| // GenerateCodeCustom takes a timepoint and produces a passcode using a
 | |
| // secret and the provided opts. (Under the hood, this is making an adapted
 | |
| // call to hotp.GenerateCodeCustom)
 | |
| func GenerateCodeCustom(secret string, t time.Time, opts ValidateOpts) (passcode string, err error) {
 | |
| 	if opts.Period == 0 {
 | |
| 		opts.Period = 30
 | |
| 	}
 | |
| 	counter := uint64(math.Floor(float64(t.Unix()) / float64(opts.Period)))
 | |
| 	passcode, err = hotp.GenerateCodeCustom(secret, counter, hotp.ValidateOpts{
 | |
| 		Digits:    opts.Digits,
 | |
| 		Algorithm: opts.Algorithm,
 | |
| 	})
 | |
| 	if err != nil {
 | |
| 		return "", err
 | |
| 	}
 | |
| 	return passcode, nil
 | |
| }
 | |
| 
 | |
| // ValidateCustom validates a TOTP given a user specified time and custom options.
 | |
| // Most users should use Validate() to provide an interpolatable TOTP experience.
 | |
| func ValidateCustom(passcode string, secret string, t time.Time, opts ValidateOpts) (bool, error) {
 | |
| 	if opts.Period == 0 {
 | |
| 		opts.Period = 30
 | |
| 	}
 | |
| 
 | |
| 	counters := []uint64{}
 | |
| 	counter := int64(math.Floor(float64(t.Unix()) / float64(opts.Period)))
 | |
| 
 | |
| 	counters = append(counters, uint64(counter))
 | |
| 	for i := 1; i <= int(opts.Skew); i++ {
 | |
| 		counters = append(counters, uint64(counter+int64(i)))
 | |
| 		counters = append(counters, uint64(counter-int64(i)))
 | |
| 	}
 | |
| 
 | |
| 	for _, counter := range counters {
 | |
| 		rv, err := hotp.ValidateCustom(passcode, counter, secret, hotp.ValidateOpts{
 | |
| 			Digits:    opts.Digits,
 | |
| 			Algorithm: opts.Algorithm,
 | |
| 		})
 | |
| 
 | |
| 		if err != nil {
 | |
| 			return false, err
 | |
| 		}
 | |
| 
 | |
| 		if rv == true {
 | |
| 			return true, nil
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return false, nil
 | |
| }
 | |
| 
 | |
| // GenerateOpts provides options for Generate().  The default values
 | |
| // are compatible with Google-Authenticator.
 | |
| type GenerateOpts struct {
 | |
| 	// Name of the issuing Organization/Company.
 | |
| 	Issuer string
 | |
| 	// Name of the User's Account (eg, email address)
 | |
| 	AccountName string
 | |
| 	// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
 | |
| 	Period uint
 | |
| 	// 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 a new TOTP 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.Period == 0 {
 | |
| 		opts.Period = 30
 | |
| 	}
 | |
| 
 | |
| 	if opts.SecretSize == 0 {
 | |
| 		opts.SecretSize = 10
 | |
| 	}
 | |
| 
 | |
| 	if opts.Digits == 0 {
 | |
| 		opts.Digits = otp.DigitsSix
 | |
| 	}
 | |
| 
 | |
| 	// 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("period", strconv.FormatUint(uint64(opts.Period), 10))
 | |
| 	v.Set("algorithm", opts.Algorithm.String())
 | |
| 	v.Set("digits", opts.Digits.String())
 | |
| 
 | |
| 	u := url.URL{
 | |
| 		Scheme:   "otpauth",
 | |
| 		Host:     "totp",
 | |
| 		Path:     "/" + opts.Issuer + ":" + opts.AccountName,
 | |
| 		RawQuery: v.Encode(),
 | |
| 	}
 | |
| 
 | |
| 	return otp.NewKeyFromURL(u.String())
 | |
| }
 |