208 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			208 lines
		
	
	
		
			4.2 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 otp
 | 
						|
 | 
						|
import (
 | 
						|
	"github.com/boombuler/barcode"
 | 
						|
	"github.com/boombuler/barcode/qr"
 | 
						|
 | 
						|
	"crypto/md5"
 | 
						|
	"crypto/sha1"
 | 
						|
	"crypto/sha256"
 | 
						|
	"crypto/sha512"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"hash"
 | 
						|
	"image"
 | 
						|
	"net/url"
 | 
						|
	"strings"
 | 
						|
)
 | 
						|
 | 
						|
// Error when attempting to convert the secret from base32 to raw bytes.
 | 
						|
var ErrValidateSecretInvalidBase32 = errors.New("Decoding of secret as base32 failed.")
 | 
						|
 | 
						|
// The user provided passcode length was not expected.
 | 
						|
var ErrValidateInputInvalidLength = errors.New("Input length unexpected")
 | 
						|
 | 
						|
// When generating a Key, the Issuer must be set.
 | 
						|
var ErrGenerateMissingIssuer = errors.New("Issuer must be set")
 | 
						|
 | 
						|
// When generating a Key, the Account Name must be set.
 | 
						|
var ErrGenerateMissingAccountName = errors.New("AccountName must be set")
 | 
						|
 | 
						|
// Key represents an TOTP or HTOP key.
 | 
						|
type Key struct {
 | 
						|
	orig string
 | 
						|
	url  *url.URL
 | 
						|
}
 | 
						|
 | 
						|
// NewKeyFromURL creates a new Key from an TOTP or HOTP url.
 | 
						|
//
 | 
						|
// The URL format is documented here:
 | 
						|
//   https://github.com/google/google-authenticator/wiki/Key-Uri-Format
 | 
						|
//
 | 
						|
func NewKeyFromURL(orig string) (*Key, error) {
 | 
						|
	s := strings.TrimSpace(orig)
 | 
						|
 | 
						|
	u, err := url.Parse(s)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return &Key{
 | 
						|
		orig: s,
 | 
						|
		url:  u,
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
func (k *Key) String() string {
 | 
						|
	return k.orig
 | 
						|
}
 | 
						|
 | 
						|
// Image returns an QR-Code image of the specified width and height,
 | 
						|
// suitable for use by many clients like Google-Authenricator
 | 
						|
// to enroll a user's TOTP/HOTP key.
 | 
						|
func (k *Key) Image(width int, height int) (image.Image, error) {
 | 
						|
	b, err := qr.Encode(k.orig, qr.M, qr.Auto)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	b, err = barcode.Scale(b, width, height)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return b, nil
 | 
						|
}
 | 
						|
 | 
						|
// Type returns "hotp" or "totp".
 | 
						|
func (k *Key) Type() string {
 | 
						|
	return k.url.Host
 | 
						|
}
 | 
						|
 | 
						|
// Issuer returns the name of the issuing organization.
 | 
						|
func (k *Key) Issuer() string {
 | 
						|
	q := k.url.Query()
 | 
						|
 | 
						|
	issuer := q.Get("issuer")
 | 
						|
 | 
						|
	if issuer != "" {
 | 
						|
		return issuer
 | 
						|
	}
 | 
						|
 | 
						|
	p := strings.TrimPrefix(k.url.Path, "/")
 | 
						|
	i := strings.Index(p, ":")
 | 
						|
 | 
						|
	if i == -1 {
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
 | 
						|
	return p[:i]
 | 
						|
}
 | 
						|
 | 
						|
// AccountName returns the name of the user's account.
 | 
						|
func (k *Key) AccountName() string {
 | 
						|
	p := strings.TrimPrefix(k.url.Path, "/")
 | 
						|
	i := strings.Index(p, ":")
 | 
						|
 | 
						|
	if i == -1 {
 | 
						|
		return p
 | 
						|
	}
 | 
						|
 | 
						|
	return p[i+1:]
 | 
						|
}
 | 
						|
 | 
						|
// Secret returns the opaque secret for this Key.
 | 
						|
func (k *Key) Secret() string {
 | 
						|
	q := k.url.Query()
 | 
						|
 | 
						|
	return q.Get("secret")
 | 
						|
}
 | 
						|
 | 
						|
// URL returns the OTP URL as a string
 | 
						|
func (k *Key) URL() string {
 | 
						|
	return k.url.String()
 | 
						|
}
 | 
						|
 | 
						|
// Algorithm represents the hashing function to use in the HMAC
 | 
						|
// operation needed for OTPs.
 | 
						|
type Algorithm int
 | 
						|
 | 
						|
const (
 | 
						|
	AlgorithmSHA1 Algorithm = iota
 | 
						|
	AlgorithmSHA256
 | 
						|
	AlgorithmSHA512
 | 
						|
	AlgorithmMD5
 | 
						|
)
 | 
						|
 | 
						|
func (a Algorithm) String() string {
 | 
						|
	switch a {
 | 
						|
	case AlgorithmSHA1:
 | 
						|
		return "SHA1"
 | 
						|
	case AlgorithmSHA256:
 | 
						|
		return "SHA256"
 | 
						|
	case AlgorithmSHA512:
 | 
						|
		return "SHA512"
 | 
						|
	case AlgorithmMD5:
 | 
						|
		return "MD5"
 | 
						|
	}
 | 
						|
	panic("unreached")
 | 
						|
}
 | 
						|
 | 
						|
func (a Algorithm) Hash() hash.Hash {
 | 
						|
	switch a {
 | 
						|
	case AlgorithmSHA1:
 | 
						|
		return sha1.New()
 | 
						|
	case AlgorithmSHA256:
 | 
						|
		return sha256.New()
 | 
						|
	case AlgorithmSHA512:
 | 
						|
		return sha512.New()
 | 
						|
	case AlgorithmMD5:
 | 
						|
		return md5.New()
 | 
						|
	}
 | 
						|
	panic("unreached")
 | 
						|
}
 | 
						|
 | 
						|
// Digits represents the number of digits present in the
 | 
						|
// user's OTP passcode. Six and Eight are the most common values.
 | 
						|
type Digits int
 | 
						|
 | 
						|
const (
 | 
						|
	DigitsSix   Digits = 6
 | 
						|
	DigitsEight Digits = 8
 | 
						|
)
 | 
						|
 | 
						|
// Format converts an integer into the zero-filled size for this Digits.
 | 
						|
func (d Digits) Format(in int32) string {
 | 
						|
	f := fmt.Sprintf("%%0%dd", d)
 | 
						|
	return fmt.Sprintf(f, in)
 | 
						|
}
 | 
						|
 | 
						|
// Length returns the number of characters for this Digits.
 | 
						|
func (d Digits) Length() int {
 | 
						|
	return int(d)
 | 
						|
}
 | 
						|
 | 
						|
func (d Digits) String() string {
 | 
						|
	return fmt.Sprintf("%d", d)
 | 
						|
}
 |