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