90 lines
1.8 KiB
Go
90 lines
1.8 KiB
Go
|
package totpext
|
||
|
|
||
|
import (
|
||
|
"crypto/hmac"
|
||
|
"crypto/rand"
|
||
|
"crypto/sha1"
|
||
|
"encoding/base32"
|
||
|
"encoding/binary"
|
||
|
"fmt"
|
||
|
"hash"
|
||
|
"net/url"
|
||
|
"strconv"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
// https://datatracker.ietf.org/doc/html/rfc6238
|
||
|
// https://datatracker.ietf.org/doc/html/rfc4226
|
||
|
// https://datatracker.ietf.org/doc/html/rfc2104
|
||
|
|
||
|
// https://en.wikipedia.org/wiki/Universal_2nd_Factor
|
||
|
// https://en.wikipedia.org/wiki/HMAC-based_one-time_password
|
||
|
// https://en.wikipedia.org/wiki/HMAC
|
||
|
|
||
|
func TOTP(key []byte) string {
|
||
|
t := time.Now().Unix() / 30
|
||
|
return generateTOTP(sha1.New, key, t, 6)
|
||
|
}
|
||
|
|
||
|
func Validate(key []byte, totp string) bool {
|
||
|
t := time.Now().Unix() / 30
|
||
|
|
||
|
if generateTOTP(sha1.New, key, t, 6) == totp {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
if generateTOTP(sha1.New, key, t-1, 6) == totp {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
if generateTOTP(sha1.New, key, t+1, 6) == totp {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func GenerateSecret() ([]byte, error) {
|
||
|
secret := make([]byte, 20)
|
||
|
_, err := rand.Read(secret)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return secret, nil
|
||
|
}
|
||
|
|
||
|
func generateTOTP(algo func() hash.Hash, secret []byte, time int64, returnDigits int) string {
|
||
|
msg := make([]byte, 8)
|
||
|
binary.BigEndian.PutUint64(msg, uint64(time))
|
||
|
|
||
|
mac := hmac.New(algo, secret)
|
||
|
mac.Write(msg)
|
||
|
hmacResult := mac.Sum(nil)
|
||
|
|
||
|
offsetBits := hmacResult[len(hmacResult)-1] & 0x0F
|
||
|
|
||
|
p := hmacResult[offsetBits : offsetBits+4]
|
||
|
|
||
|
truncated := binary.BigEndian.Uint32(p) & 0x7FFFFFFF // Last 31 bits
|
||
|
|
||
|
val := strconv.Itoa(int(truncated))
|
||
|
for len(val) < returnDigits {
|
||
|
val = "0" + val
|
||
|
}
|
||
|
val = val[len(val)-returnDigits:]
|
||
|
|
||
|
return val
|
||
|
}
|
||
|
|
||
|
func GenerateOTPAuth(ccn string, key []byte, accountmail string, issuer string) string {
|
||
|
|
||
|
return fmt.Sprintf("otpauth://totp/%v:%v?secret=%v&issuer=%v&algorithm=%v&period=%v&digits=%v",
|
||
|
ccn,
|
||
|
url.QueryEscape(accountmail),
|
||
|
base32.StdEncoding.EncodeToString(key),
|
||
|
issuer,
|
||
|
"SHA1",
|
||
|
"30",
|
||
|
"6")
|
||
|
}
|