otp.go raw

   1  /**
   2   *  Copyright 2014 Paul Querna
   3   *
   4   *  Licensed under the Apache License, Version 2.0 (the "License");
   5   *  you may not use this file except in compliance with the License.
   6   *  You may obtain a copy of the License at
   7   *
   8   *      http://www.apache.org/licenses/LICENSE-2.0
   9   *
  10   *  Unless required by applicable law or agreed to in writing, software
  11   *  distributed under the License is distributed on an "AS IS" BASIS,
  12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13   *  See the License for the specific language governing permissions and
  14   *  limitations under the License.
  15   *
  16   */
  17  
  18  package otp
  19  
  20  import (
  21  	"crypto/md5"
  22  	"crypto/sha1"
  23  	"crypto/sha256"
  24  	"crypto/sha512"
  25  	"errors"
  26  	"fmt"
  27  	"hash"
  28  	"image"
  29  	"net/url"
  30  	"strconv"
  31  	"strings"
  32  
  33  	"github.com/boombuler/barcode"
  34  	"github.com/boombuler/barcode/qr"
  35  )
  36  
  37  // Error when attempting to convert the secret from base32 to raw bytes.
  38  var ErrValidateSecretInvalidBase32 = errors.New("Decoding of secret as base32 failed.")
  39  
  40  // The user provided passcode length was not expected.
  41  var ErrValidateInputInvalidLength = errors.New("Input length unexpected")
  42  
  43  // When generating a Key, the Issuer must be set.
  44  var ErrGenerateMissingIssuer = errors.New("Issuer must be set")
  45  
  46  // When generating a Key, the Account Name must be set.
  47  var ErrGenerateMissingAccountName = errors.New("AccountName must be set")
  48  
  49  // Key represents an TOTP or HTOP key.
  50  type Key struct {
  51  	orig string
  52  	url  *url.URL
  53  }
  54  
  55  // NewKeyFromURL creates a new Key from an TOTP or HOTP url.
  56  //
  57  // The URL format is documented here:
  58  //   https://github.com/google/google-authenticator/wiki/Key-Uri-Format
  59  //
  60  func NewKeyFromURL(orig string) (*Key, error) {
  61  	s := strings.TrimSpace(orig)
  62  
  63  	u, err := url.Parse(s)
  64  	if err != nil {
  65  		return nil, err
  66  	}
  67  
  68  	return &Key{
  69  		orig: s,
  70  		url:  u,
  71  	}, nil
  72  }
  73  
  74  func (k *Key) String() string {
  75  	return k.orig
  76  }
  77  
  78  // Image returns an QR-Code image of the specified width and height,
  79  // suitable for use by many clients like Google-Authenricator
  80  // to enroll a user's TOTP/HOTP key.
  81  func (k *Key) Image(width int, height int) (image.Image, error) {
  82  	b, err := qr.Encode(k.orig, qr.M, qr.Auto)
  83  	if err != nil {
  84  		return nil, err
  85  	}
  86  
  87  	b, err = barcode.Scale(b, width, height)
  88  
  89  	if err != nil {
  90  		return nil, err
  91  	}
  92  
  93  	return b, nil
  94  }
  95  
  96  // Type returns "hotp" or "totp".
  97  func (k *Key) Type() string {
  98  	return k.url.Host
  99  }
 100  
 101  // Issuer returns the name of the issuing organization.
 102  func (k *Key) Issuer() string {
 103  	q := k.url.Query()
 104  
 105  	issuer := q.Get("issuer")
 106  
 107  	if issuer != "" {
 108  		return issuer
 109  	}
 110  
 111  	p := strings.TrimPrefix(k.url.Path, "/")
 112  	i := strings.Index(p, ":")
 113  
 114  	if i == -1 {
 115  		return ""
 116  	}
 117  
 118  	return p[:i]
 119  }
 120  
 121  // AccountName returns the name of the user's account.
 122  func (k *Key) AccountName() string {
 123  	p := strings.TrimPrefix(k.url.Path, "/")
 124  	i := strings.Index(p, ":")
 125  
 126  	if i == -1 {
 127  		return p
 128  	}
 129  
 130  	return p[i+1:]
 131  }
 132  
 133  // Secret returns the opaque secret for this Key.
 134  func (k *Key) Secret() string {
 135  	q := k.url.Query()
 136  
 137  	return q.Get("secret")
 138  }
 139  
 140  // Period returns a tiny int representing the rotation time in seconds.
 141  func (k *Key) Period() uint64 {
 142  	q := k.url.Query()
 143  
 144  	if u, err := strconv.ParseUint(q.Get("period"), 10, 64); err == nil {
 145  		return u
 146  	}
 147  
 148  	// If no period is defined 30 seconds is the default per (rfc6238)
 149  	return 30
 150  }
 151  
 152  // Digits returns a tiny int representing the number of OTP digits.
 153  func (k *Key) Digits() Digits {
 154  	q := k.url.Query()
 155  
 156  	if u, err := strconv.ParseUint(q.Get("digits"), 10, 64); err == nil {
 157  		return Digits(u)
 158  	}
 159  
 160  	// Six is the most common value.
 161  	return DigitsSix
 162  }
 163  
 164  // Algorithm returns the algorithm used or the default (SHA1).
 165  func (k *Key) Algorithm() Algorithm {
 166  	q := k.url.Query()
 167  
 168  	a := strings.ToLower(q.Get("algorithm"))
 169  	switch a {
 170  	case "md5":
 171  		return AlgorithmMD5
 172  	case "sha256":
 173  		return AlgorithmSHA256
 174  	case "sha512":
 175  		return AlgorithmSHA512
 176  	default:
 177  		return AlgorithmSHA1
 178  	}
 179  }
 180  
 181  // Encoder returns the encoder used or the default ("")
 182  func (k *Key) Encoder() Encoder {
 183  	q := k.url.Query()
 184  
 185  	a := strings.ToLower(q.Get("encoder"))
 186  	switch a {
 187  	case "steam":
 188  		return EncoderSteam
 189  	default:
 190  		return EncoderDefault
 191  	}
 192  }
 193  
 194  // URL returns the OTP URL as a string
 195  func (k *Key) URL() string {
 196  	return k.url.String()
 197  }
 198  
 199  // Algorithm represents the hashing function to use in the HMAC
 200  // operation needed for OTPs.
 201  type Algorithm int
 202  
 203  const (
 204  	// AlgorithmSHA1 should be used for compatibility with Google Authenticator.
 205  	//
 206  	// See https://github.com/pquerna/otp/issues/55 for additional details.
 207  	AlgorithmSHA1 Algorithm = iota
 208  	AlgorithmSHA256
 209  	AlgorithmSHA512
 210  	AlgorithmMD5
 211  )
 212  
 213  func (a Algorithm) String() string {
 214  	switch a {
 215  	case AlgorithmSHA1:
 216  		return "SHA1"
 217  	case AlgorithmSHA256:
 218  		return "SHA256"
 219  	case AlgorithmSHA512:
 220  		return "SHA512"
 221  	case AlgorithmMD5:
 222  		return "MD5"
 223  	}
 224  	panic("unreached")
 225  }
 226  
 227  func (a Algorithm) Hash() hash.Hash {
 228  	switch a {
 229  	case AlgorithmSHA1:
 230  		return sha1.New()
 231  	case AlgorithmSHA256:
 232  		return sha256.New()
 233  	case AlgorithmSHA512:
 234  		return sha512.New()
 235  	case AlgorithmMD5:
 236  		return md5.New()
 237  	}
 238  	panic("unreached")
 239  }
 240  
 241  // Digits represents the number of digits present in the
 242  // user's OTP passcode. Six and Eight are the most common values.
 243  type Digits int
 244  
 245  const (
 246  	DigitsSix   Digits = 6
 247  	DigitsEight Digits = 8
 248  )
 249  
 250  // Format converts an integer into the zero-filled size for this Digits.
 251  func (d Digits) Format(in int32) string {
 252  	f := fmt.Sprintf("%%0%dd", d)
 253  	return fmt.Sprintf(f, in)
 254  }
 255  
 256  // Length returns the number of characters for this Digits.
 257  func (d Digits) Length() int {
 258  	return int(d)
 259  }
 260  
 261  func (d Digits) String() string {
 262  	return fmt.Sprintf("%d", d)
 263  }
 264  
 265  type Encoder string
 266  
 267  const (
 268  	EncoderDefault Encoder = ""
 269  	EncoderSteam   Encoder = "steam"
 270  )
 271