91 lines
2.2 KiB
Go
91 lines
2.2 KiB
Go
package auth
|
|
|
|
import "crypto/hmac"
|
|
import "crypto/rand"
|
|
import "crypto/sha1"
|
|
import "encoding/base32"
|
|
import "encoding/binary"
|
|
import "fmt"
|
|
import "net/url"
|
|
import "strconv"
|
|
import "strings"
|
|
import "time"
|
|
|
|
const TOTPPeriod int64 = 30
|
|
const TOTPDigits int = 6
|
|
|
|
func NewTOTPSecret() (string, error) {
|
|
var raw []byte
|
|
var err error
|
|
var enc *base32.Encoding
|
|
raw = make([]byte, 20)
|
|
_, err = rand.Read(raw)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
enc = base32.StdEncoding.WithPadding(base32.NoPadding)
|
|
return enc.EncodeToString(raw), nil
|
|
}
|
|
|
|
func TOTPProvisioningURL(issuer string, account string, secret string) string {
|
|
var label string
|
|
var values url.Values
|
|
label = strings.TrimSpace(issuer) + ":" + strings.TrimSpace(account)
|
|
values = url.Values{}
|
|
values.Set("secret", strings.TrimSpace(secret))
|
|
values.Set("issuer", strings.TrimSpace(issuer))
|
|
values.Set("algorithm", "SHA1")
|
|
values.Set("digits", strconv.Itoa(TOTPDigits))
|
|
values.Set("period", strconv.FormatInt(TOTPPeriod, 10))
|
|
return "otpauth://totp/" + url.PathEscape(label) + "?" + values.Encode()
|
|
}
|
|
|
|
func ValidateTOTP(code string, secret string, now time.Time) bool {
|
|
var value string
|
|
var counter int64
|
|
var i int64
|
|
value = strings.TrimSpace(code)
|
|
if len(value) != TOTPDigits {
|
|
return false
|
|
}
|
|
counter = now.Unix() / TOTPPeriod
|
|
for i = -1; i <= 1; i++ {
|
|
if totpCode(secret, counter+i) == value {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func totpCode(secret string, counter int64) string {
|
|
var key []byte
|
|
var msg []byte
|
|
var mac hashHMAC
|
|
var sum []byte
|
|
var offset byte
|
|
var binaryCode uint32
|
|
var code uint32
|
|
var enc *base32.Encoding
|
|
var err error
|
|
|
|
enc = base32.StdEncoding.WithPadding(base32.NoPadding)
|
|
key, err = enc.DecodeString(strings.ToUpper(strings.TrimSpace(secret)))
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
msg = make([]byte, 8)
|
|
binary.BigEndian.PutUint64(msg, uint64(counter))
|
|
mac = hmac.New(sha1.New, key)
|
|
_, _ = mac.Write(msg)
|
|
sum = mac.Sum(nil)
|
|
offset = sum[len(sum)-1] & 0x0f
|
|
binaryCode = (uint32(sum[offset])&0x7f)<<24 | (uint32(sum[offset+1])&0xff)<<16 | (uint32(sum[offset+2])&0xff)<<8 | (uint32(sum[offset+3]) & 0xff)
|
|
code = binaryCode % 1000000
|
|
return fmt.Sprintf("%06d", code)
|
|
}
|
|
|
|
type hashHMAC interface {
|
|
Write([]byte) (int, error)
|
|
Sum([]byte) []byte
|
|
}
|