...

Source file src/github.com/pquerna/otp/otp.go

Documentation: github.com/pquerna/otp

     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  	"github.com/boombuler/barcode"
    22  	"github.com/boombuler/barcode/qr"
    23  
    24  	"crypto/md5"
    25  	"crypto/sha1"
    26  	"crypto/sha256"
    27  	"crypto/sha512"
    28  	"errors"
    29  	"fmt"
    30  	"hash"
    31  	"image"
    32  	"net/url"
    33  	"strings"
    34  	"strconv"
    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  
    65  	if err != nil {
    66  		return nil, err
    67  	}
    68  
    69  	return &Key{
    70  		orig: s,
    71  		url:  u,
    72  	}, nil
    73  }
    74  
    75  func (k *Key) String() string {
    76  	return k.orig
    77  }
    78  
    79  // Image returns an QR-Code image of the specified width and height,
    80  // suitable for use by many clients like Google-Authenricator
    81  // to enroll a user's TOTP/HOTP key.
    82  func (k *Key) Image(width int, height int) (image.Image, error) {
    83  	b, err := qr.Encode(k.orig, qr.M, qr.Auto)
    84  
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	b, err = barcode.Scale(b, width, height)
    90  
    91  	if err != nil {
    92  		return nil, err
    93  	}
    94  
    95  	return b, nil
    96  }
    97  
    98  // Type returns "hotp" or "totp".
    99  func (k *Key) Type() string {
   100  	return k.url.Host
   101  }
   102  
   103  // Issuer returns the name of the issuing organization.
   104  func (k *Key) Issuer() string {
   105  	q := k.url.Query()
   106  
   107  	issuer := q.Get("issuer")
   108  
   109  	if issuer != "" {
   110  		return issuer
   111  	}
   112  
   113  	p := strings.TrimPrefix(k.url.Path, "/")
   114  	i := strings.Index(p, ":")
   115  
   116  	if i == -1 {
   117  		return ""
   118  	}
   119  
   120  	return p[:i]
   121  }
   122  
   123  // AccountName returns the name of the user's account.
   124  func (k *Key) AccountName() string {
   125  	p := strings.TrimPrefix(k.url.Path, "/")
   126  	i := strings.Index(p, ":")
   127  
   128  	if i == -1 {
   129  		return p
   130  	}
   131  
   132  	return p[i+1:]
   133  }
   134  
   135  // Secret returns the opaque secret for this Key.
   136  func (k *Key) Secret() string {
   137  	q := k.url.Query()
   138  
   139  	return q.Get("secret")
   140  }
   141  
   142  // Period returns a tiny int representing the rotation time in seconds.
   143  func (k *Key) Period() uint64 {
   144  	q := k.url.Query()
   145  
   146  	if u, err := strconv.ParseUint(q.Get("period"), 10, 64); err == nil {
   147  		return u
   148  	}
   149  	
   150  	// If no period is defined 30 seconds is the default per (rfc6238)
   151  	return 30
   152  }
   153  
   154  // URL returns the OTP URL as a string
   155  func (k *Key) URL() string {
   156  	return k.url.String()
   157  }
   158  
   159  // Algorithm represents the hashing function to use in the HMAC
   160  // operation needed for OTPs.
   161  type Algorithm int
   162  
   163  const (
   164  	// AlgorithmSHA1 should be used for compatibility with Google Authenticator.
   165  	//
   166  	// See https://github.com/pquerna/otp/issues/55 for additional details.
   167  	AlgorithmSHA1 Algorithm = iota
   168  	AlgorithmSHA256
   169  	AlgorithmSHA512
   170  	AlgorithmMD5
   171  )
   172  
   173  func (a Algorithm) String() string {
   174  	switch a {
   175  	case AlgorithmSHA1:
   176  		return "SHA1"
   177  	case AlgorithmSHA256:
   178  		return "SHA256"
   179  	case AlgorithmSHA512:
   180  		return "SHA512"
   181  	case AlgorithmMD5:
   182  		return "MD5"
   183  	}
   184  	panic("unreached")
   185  }
   186  
   187  func (a Algorithm) Hash() hash.Hash {
   188  	switch a {
   189  	case AlgorithmSHA1:
   190  		return sha1.New()
   191  	case AlgorithmSHA256:
   192  		return sha256.New()
   193  	case AlgorithmSHA512:
   194  		return sha512.New()
   195  	case AlgorithmMD5:
   196  		return md5.New()
   197  	}
   198  	panic("unreached")
   199  }
   200  
   201  // Digits represents the number of digits present in the
   202  // user's OTP passcode. Six and Eight are the most common values.
   203  type Digits int
   204  
   205  const (
   206  	DigitsSix   Digits = 6
   207  	DigitsEight Digits = 8
   208  )
   209  
   210  // Format converts an integer into the zero-filled size for this Digits.
   211  func (d Digits) Format(in int32) string {
   212  	f := fmt.Sprintf("%%0%dd", d)
   213  	return fmt.Sprintf(f, in)
   214  }
   215  
   216  // Length returns the number of characters for this Digits.
   217  func (d Digits) Length() int {
   218  	return int(d)
   219  }
   220  
   221  func (d Digits) String() string {
   222  	return fmt.Sprintf("%d", d)
   223  }
   224  

View as plain text