mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 07:42:26 -05:00 
			
		
		
		
	[feature] add TOTP two-factor authentication (2FA) (#3960)
* [feature] add TOTP two-factor authentication (2FA) * use byteutil.S2B to avoid allocations when comparing + generating password hashes * don't bother with string conversion for consts * use io.ReadFull * use MustGenerateSecret for backup codes * rename util functions
This commit is contained in:
		
					parent
					
						
							
								6f24205a26
							
						
					
				
			
			
				commit
				
					
						365b575341
					
				
			
		
					 78 changed files with 5593 additions and 825 deletions
				
			
		
							
								
								
									
										216
									
								
								vendor/github.com/pquerna/otp/hotp/hotp.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								vendor/github.com/pquerna/otp/hotp/hotp.go
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,216 @@ | |||
| /** | ||||
|  *  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" | ||||
| 	"github.com/pquerna/otp/internal" | ||||
| 	"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) { | ||||
| 	//Set default value | ||||
| 	if opts.Digits == 0 { | ||||
| 		opts.Digits = otp.DigitsSix | ||||
| 	} | ||||
| 	// 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://hotp/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: internal.EncodeQuery(v), | ||||
| 	} | ||||
| 
 | ||||
| 	return otp.NewKeyFromURL(u.String()) | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue