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