You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

otp.go 4.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  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. package otp
  18. import (
  19. "github.com/boombuler/barcode"
  20. "github.com/boombuler/barcode/qr"
  21. "crypto/md5"
  22. "crypto/sha1"
  23. "crypto/sha256"
  24. "crypto/sha512"
  25. "errors"
  26. "fmt"
  27. "hash"
  28. "image"
  29. "net/url"
  30. "strings"
  31. )
  32. // Error when attempting to convert the secret from base32 to raw bytes.
  33. var ErrValidateSecretInvalidBase32 = errors.New("Decoding of secret as base32 failed.")
  34. // The user provided passcode length was not expected.
  35. var ErrValidateInputInvalidLength = errors.New("Input length unexpected")
  36. // When generating a Key, the Issuer must be set.
  37. var ErrGenerateMissingIssuer = errors.New("Issuer must be set")
  38. // When generating a Key, the Account Name must be set.
  39. var ErrGenerateMissingAccountName = errors.New("AccountName must be set")
  40. // Key represents an TOTP or HTOP key.
  41. type Key struct {
  42. orig string
  43. url *url.URL
  44. }
  45. // NewKeyFromURL creates a new Key from an TOTP or HOTP url.
  46. //
  47. // The URL format is documented here:
  48. // https://github.com/google/google-authenticator/wiki/Key-Uri-Format
  49. //
  50. func NewKeyFromURL(orig string) (*Key, error) {
  51. s := strings.TrimSpace(orig)
  52. u, err := url.Parse(s)
  53. if err != nil {
  54. return nil, err
  55. }
  56. return &Key{
  57. orig: s,
  58. url: u,
  59. }, nil
  60. }
  61. func (k *Key) String() string {
  62. return k.orig
  63. }
  64. // Image returns an QR-Code image of the specified width and height,
  65. // suitable for use by many clients like Google-Authenricator
  66. // to enroll a user's TOTP/HOTP key.
  67. func (k *Key) Image(width int, height int) (image.Image, error) {
  68. b, err := qr.Encode(k.orig, qr.M, qr.Auto)
  69. if err != nil {
  70. return nil, err
  71. }
  72. b, err = barcode.Scale(b, width, height)
  73. if err != nil {
  74. return nil, err
  75. }
  76. return b, nil
  77. }
  78. // Type returns "hotp" or "totp".
  79. func (k *Key) Type() string {
  80. return k.url.Host
  81. }
  82. // Issuer returns the name of the issuing organization.
  83. func (k *Key) Issuer() string {
  84. q := k.url.Query()
  85. issuer := q.Get("issuer")
  86. if issuer != "" {
  87. return issuer
  88. }
  89. p := strings.TrimPrefix(k.url.Path, "/")
  90. i := strings.Index(p, ":")
  91. if i == -1 {
  92. return ""
  93. }
  94. return p[:i]
  95. }
  96. // AccountName returns the name of the user's account.
  97. func (k *Key) AccountName() string {
  98. p := strings.TrimPrefix(k.url.Path, "/")
  99. i := strings.Index(p, ":")
  100. if i == -1 {
  101. return p
  102. }
  103. return p[i+1:]
  104. }
  105. // Secret returns the opaque secret for this Key.
  106. func (k *Key) Secret() string {
  107. q := k.url.Query()
  108. return q.Get("secret")
  109. }
  110. // URL returns the OTP URL as a string
  111. func (k *Key) URL() string {
  112. return k.url.String()
  113. }
  114. // Algorithm represents the hashing function to use in the HMAC
  115. // operation needed for OTPs.
  116. type Algorithm int
  117. const (
  118. AlgorithmSHA1 Algorithm = iota
  119. AlgorithmSHA256
  120. AlgorithmSHA512
  121. AlgorithmMD5
  122. )
  123. func (a Algorithm) String() string {
  124. switch a {
  125. case AlgorithmSHA1:
  126. return "SHA1"
  127. case AlgorithmSHA256:
  128. return "SHA256"
  129. case AlgorithmSHA512:
  130. return "SHA512"
  131. case AlgorithmMD5:
  132. return "MD5"
  133. }
  134. panic("unreached")
  135. }
  136. func (a Algorithm) Hash() hash.Hash {
  137. switch a {
  138. case AlgorithmSHA1:
  139. return sha1.New()
  140. case AlgorithmSHA256:
  141. return sha256.New()
  142. case AlgorithmSHA512:
  143. return sha512.New()
  144. case AlgorithmMD5:
  145. return md5.New()
  146. }
  147. panic("unreached")
  148. }
  149. // Digits represents the number of digits present in the
  150. // user's OTP passcode. Six and Eight are the most common values.
  151. type Digits int
  152. const (
  153. DigitsSix Digits = 6
  154. DigitsEight Digits = 8
  155. )
  156. // Format converts an integer into the zero-filled size for this Digits.
  157. func (d Digits) Format(in int32) string {
  158. f := fmt.Sprintf("%%0%dd", d)
  159. return fmt.Sprintf(f, in)
  160. }
  161. // Length returns the number of characters for this Digits.
  162. func (d Digits) Length() int {
  163. return int(d)
  164. }
  165. func (d Digits) String() string {
  166. return fmt.Sprintf("%d", d)
  167. }