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.

200 lines
4.1 KiB

  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://code.google.com/p/google-authenticator/wiki/KeyUriFormat
  49. //
  50. func NewKeyFromURL(orig string) (*Key, error) {
  51. u, err := url.Parse(orig)
  52. if err != nil {
  53. return nil, err
  54. }
  55. return &Key{
  56. orig: orig,
  57. url: u,
  58. }, nil
  59. }
  60. func (k *Key) String() string {
  61. return k.orig
  62. }
  63. // Image returns an QR-Code image of the specified width and height,
  64. // suitable for use by many clients like Google-Authenricator
  65. // to enroll a user's TOTP/HOTP key.
  66. func (k *Key) Image(width int, height int) (image.Image, error) {
  67. b, err := qr.Encode(k.orig, qr.M, qr.Auto)
  68. if err != nil {
  69. return nil, err
  70. }
  71. b, err = barcode.Scale(b, width, height)
  72. if err != nil {
  73. return nil, err
  74. }
  75. return b, nil
  76. }
  77. // Type returns "hotp" or "totp".
  78. func (k *Key) Type() string {
  79. return k.url.Host
  80. }
  81. // Issuer returns the name of the issuing organization.
  82. func (k *Key) Issuer() string {
  83. q := k.url.Query()
  84. issuer := q.Get("issuer")
  85. if issuer != "" {
  86. return issuer
  87. }
  88. p := strings.TrimPrefix(k.url.Path, "/")
  89. i := strings.Index(p, ":")
  90. if i == -1 {
  91. return ""
  92. }
  93. return p[:i]
  94. }
  95. // AccountName returns the name of the user's account.
  96. func (k *Key) AccountName() string {
  97. p := strings.TrimPrefix(k.url.Path, "/")
  98. i := strings.Index(p, ":")
  99. if i == -1 {
  100. return p
  101. }
  102. return p[i+1:]
  103. }
  104. // Secret returns the opaque secret for this Key.
  105. func (k *Key) Secret() string {
  106. q := k.url.Query()
  107. return q.Get("secret")
  108. }
  109. // Algorithm represents the hashing function to use in the HMAC
  110. // operation needed for OTPs.
  111. type Algorithm int
  112. const (
  113. AlgorithmSHA1 Algorithm = iota
  114. AlgorithmSHA256
  115. AlgorithmSHA512
  116. AlgorithmMD5
  117. )
  118. func (a Algorithm) String() string {
  119. switch a {
  120. case AlgorithmSHA1:
  121. return "SHA1"
  122. case AlgorithmSHA256:
  123. return "SHA256"
  124. case AlgorithmSHA512:
  125. return "SHA512"
  126. case AlgorithmMD5:
  127. return "MD5"
  128. }
  129. panic("unreached")
  130. }
  131. func (a Algorithm) Hash() hash.Hash {
  132. switch a {
  133. case AlgorithmSHA1:
  134. return sha1.New()
  135. case AlgorithmSHA256:
  136. return sha256.New()
  137. case AlgorithmSHA512:
  138. return sha512.New()
  139. case AlgorithmMD5:
  140. return md5.New()
  141. }
  142. panic("unreached")
  143. }
  144. // Digits represents the number of digits present in the
  145. // user's OTP passcode. Six and Eight are the most common values.
  146. type Digits int
  147. const (
  148. DigitsSix Digits = 6
  149. DigitsEight Digits = 8
  150. )
  151. // Format converts an integer into the zero-filled size for this Digits.
  152. func (d Digits) Format(in int32) string {
  153. f := fmt.Sprintf("%%0%dd", d)
  154. return fmt.Sprintf(f, in)
  155. }
  156. // Length returns the number of characters for this Digits.
  157. func (d Digits) Length() int {
  158. return int(d)
  159. }
  160. func (d Digits) String() string {
  161. return fmt.Sprintf("%d", d)
  162. }