-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmcf.go
173 lines (144 loc) · 4.9 KB
/
mcf.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
// Copyright 2014 Gyepi Sam. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package mcf
import (
"bytes"
"crypto/rand"
"errors"
"fmt"
"io"
"github.com/gyepisam/mcf/encoder"
)
type instance struct {
id []byte
encoder.Encoder
}
var (
encoders [maxEncoding]*instance
defaultEncoding = maxEncoding
)
// ErrNoEncoder is returned if an encoded password does not match any known encoders.
// The encoded password is appended to the error message to aid in resolving the problem.
type ErrNoEncoder struct {
encoded string
}
func (e *ErrNoEncoder) Error() string {
return fmt.Sprintf("No matching encoder found for: %q", e.encoded)
}
// A SaltMiner is function that takes an int and produces that many random bytes.
// It exists to allow variation in the source of salt.
type SaltMiner func(int) ([]byte, error)
// Register adds an encoder implementation to the list.
// It is expected that each encoder will call Register from an init() function.
// The first encoder imported becomes the default and is used to create new passwords.
// Subsequent imported encoders, if any, are used for decoding, where necessary.
// See SetDefault() to set the default encoder manually.
func Register(encoding Encoding, enc encoder.Encoder) error {
if !encoding.IsValid() {
return encoding.errInvalid()
}
id := enc.Id()
if len(id) == 0 {
return fmt.Errorf("empty id: encoding=%s", encoding)
}
encoders[encoding] = &instance{id: id, Encoder: enc}
// default to first registered encoder.
if !defaultEncoding.IsValid() {
defaultEncoding = encoding
}
return nil
}
// SetDefault sets the default encoding used to create passwords.
// Since the first registered encoder is used as the default encoder,
// it is not necessary to call this routine unless you have multiple encoders
// imported, in which case, it is advisable to call this routine to avoid a dependency
// on the order of import statements.
func SetDefault(encoding Encoding) error {
if !encoding.IsValid() {
return encoding.errInvalid()
}
if encoders[encoding] == nil {
return &ErrUnregisteredEncoding{fmt.Sprintf("encoding [%s] not registered. Forgot to import?", encoding)}
}
defaultEncoding = encoding
return nil
}
// Create takes a plaintext password and uses the default encoder to
// create an encoded password in Modular Crypt Format, which it returns.
// The application is expected to store this password in order to subsequently
// verify the plaintext password.
func Create(plaintext string) (encoded string, err error) {
if !defaultEncoding.IsValid() {
err = errors.New("No encoders registered")
return
}
enc := encoders[defaultEncoding]
//This should not happen, but use suspenders anyway.
if enc == nil {
panic(fmt.Sprintf("missing implementation for encoding [%s]", defaultEncoding))
}
b, err := enc.Create([]byte(plaintext))
if err != nil {
return
}
return string(b), nil
}
func findInstance(encoded []byte) (Encoding, *instance) {
for i, e := range encoders {
if e == nil {
continue
}
if len(encoded) > 0 && bytes.HasPrefix(encoded[1:], e.id) {
return Encoding(i), e
}
}
return maxEncoding, nil
}
// Verify takes a plaintext password and a encoded password and returns true
// if the password, when encoded by the same encoder, using the same parameters,
// matches the encoded password.
func Verify(plaintext, encoded string) (isValid bool, err error) {
b := []byte(encoded)
_, enc := findInstance(b)
if enc == nil {
return false, &ErrNoEncoder{encoded}
}
return enc.Verify([]byte(plaintext), b)
}
// IsCurrent returns true if the encoded password was generated by the current encoder with the current parameters.
// If it returns false, then the encoded password should be regenerated and replaced.
// Assuming that policy changes are always to increase security by using stronger hashes or increasing work factors,
// IsCurrent presents a mechanism to query an encoded password and determine whether it needs to be re-created.
func IsCurrent(encoded string) (isCurrent bool, err error) {
b := []byte(encoded)
encoding, enc := findInstance(b)
if enc == nil {
err = &ErrNoEncoder{encoded}
} else {
isCurrent, err = enc.IsCurrent(b)
if err == nil && isCurrent {
// if the encoded password's scheme is not the default,
// then it is out of date.
isCurrent = encoding == defaultEncoding
}
}
return
}
// Salt produces the specified number of random bytes.
// If minerFn is nil, the function generates bytes from rand.Reader.
// Otherwise minerFn is called and its results validated and returned.
func Salt(size int, minerFn SaltMiner) (salt []byte, err error) {
if minerFn == nil {
salt = make([]byte, size)
_, err = io.ReadFull(rand.Reader, salt)
return
}
salt, err = minerFn(size)
if err == nil {
if m, n := size, len(salt); m != n {
err = fmt.Errorf("%s: short salt read. want: %d, got %d", defaultEncoding, m, n)
}
}
return
}