104 lines
2.2 KiB
Go
104 lines
2.2 KiB
Go
package twofactor
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/sha1" // #nosec G505 - required by RFC
|
|
"encoding/base32"
|
|
"io"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// HOTP represents an RFC-4226 Hash-based One Time Password instance.
|
|
type HOTP struct {
|
|
*OATH
|
|
}
|
|
|
|
// NewHOTP takes the key, the initial counter value, and the number
|
|
// of digits (typically 6 or 8) and returns a new HOTP instance.
|
|
func NewHOTP(key []byte, counter uint64, digits int) *HOTP {
|
|
return &HOTP{
|
|
OATH: &OATH{
|
|
key: key,
|
|
counter: counter,
|
|
size: digits,
|
|
hash: sha1.New,
|
|
algo: crypto.SHA1,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Type returns OATH_HOTP.
|
|
func (otp *HOTP) Type() Type {
|
|
return OATH_HOTP
|
|
}
|
|
|
|
// OTP returns the next OTP and increments the counter.
|
|
func (otp *HOTP) OTP() string {
|
|
code := otp.OATH.OTP(otp.counter)
|
|
otp.counter++
|
|
return code
|
|
}
|
|
|
|
// URL returns an HOTP URL (i.e. for putting in a QR code).
|
|
func (otp *HOTP) URL(label string) string {
|
|
return otp.OATH.URL(otp.Type(), label)
|
|
}
|
|
|
|
// SetProvider sets up the provider component of the OTP URL.
|
|
func (otp *HOTP) SetProvider(provider string) {
|
|
otp.provider = provider
|
|
}
|
|
|
|
// GenerateGoogleHOTP generates a new HOTP instance as used by
|
|
// Google Authenticator.
|
|
func GenerateGoogleHOTP() *HOTP {
|
|
key := make([]byte, sha1.Size)
|
|
if _, err := io.ReadFull(PRNG, key); err != nil {
|
|
return nil
|
|
}
|
|
return NewHOTP(key, 0, 6)
|
|
}
|
|
|
|
func hotpFromURL(u *url.URL) (*HOTP, string, error) {
|
|
label := u.Path[1:]
|
|
v := u.Query()
|
|
|
|
secret := strings.ToUpper(v.Get("secret"))
|
|
if secret == "" {
|
|
return nil, "", ErrInvalidURL
|
|
}
|
|
|
|
var digits = 6
|
|
if sdigit := v.Get("digits"); sdigit != "" {
|
|
tmpDigits, err := strconv.ParseInt(sdigit, 10, 8)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
digits = int(tmpDigits)
|
|
}
|
|
|
|
var counter uint64
|
|
if scounter := v.Get("counter"); scounter != "" {
|
|
var err error
|
|
counter, err = strconv.ParseUint(scounter, 10, 64)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
}
|
|
|
|
key, err := base32.StdEncoding.DecodeString(Pad(secret))
|
|
if err != nil {
|
|
// assume secret isn't base32 encoded
|
|
key = []byte(secret)
|
|
}
|
|
otp := NewHOTP(key, counter, digits)
|
|
return otp, label, nil
|
|
}
|
|
|
|
// QR generates a new QR code for the HOTP.
|
|
func (otp *HOTP) QR(label string) ([]byte, error) {
|
|
return otp.OATH.QR(otp.Type(), label)
|
|
}
|