Mercurial > go > multipass
comparison file/auth.go @ 18:00d30c67b56d
Put all the library stuff into multipass/file.
author | Paul Fisher <paul@pfish.zone> |
---|---|
date | Sun, 01 Nov 2015 12:16:51 -0500 |
parents | auth/auth.go@4368a377ff64 |
children |
comparison
equal
deleted
inserted
replaced
17:342f63116bfd | 18:00d30c67b56d |
---|---|
1 package file | |
2 | |
3 import ( | |
4 "crypto/rand" | |
5 "encoding/base64" | |
6 "errors" | |
7 "math/big" | |
8 "strconv" | |
9 "strings" | |
10 | |
11 "golang.org/x/crypto/bcrypt" | |
12 ) | |
13 | |
14 const ( | |
15 // default cost stolen from python bcrypt | |
16 bcryptCost = 12 | |
17 // we only generate passwords from lowercases for non-ambiguity | |
18 lowercases = "abcdefghijklmnopqrstuvwxyz" | |
19 template = "????-????-????-????" | |
20 ) | |
21 | |
22 var ( | |
23 lowercaseLen *big.Int = big.NewInt(int64(len(lowercases))) | |
24 maxID = big.NewInt(0) | |
25 ) | |
26 | |
27 var ( | |
28 WrongLengthError error = errors.New("multipass/auth: password entry must have 3 fields") | |
29 BadIDError = errors.New("multipass/auth: ID field invalid") | |
30 Base64Error = errors.New("multipass/auth: can't decode base64 data") | |
31 LongDescriptionError = errors.New("multipass/auth: description must be less than 255 bytes") | |
32 ) | |
33 | |
34 func init() { | |
35 one := big.NewInt(1) | |
36 maxID.Lsh(one, 64) | |
37 } | |
38 | |
39 // Entry represents a single entry in the a multipass file. | |
40 type Entry struct { | |
41 id uint64 | |
42 hash string | |
43 description string | |
44 } | |
45 | |
46 // EntryFromShadow creates a new entry from a line in a multipass shadow file. | |
47 // The line should not end in a newline. | |
48 func EntryFromShadow(shadow string) (*Entry, error) { | |
49 segments := strings.Split(shadow, ":") | |
50 if len(segments) != 3 { | |
51 return nil, WrongLengthError | |
52 } | |
53 entry := new(Entry) | |
54 id, err := strconv.ParseUint(segments[0], 10, 64) | |
55 if err != nil { | |
56 return nil, BadIDError | |
57 } | |
58 entry.id = id | |
59 entry.hash = segments[1] | |
60 description, err := base64.StdEncoding.DecodeString(segments[2]) | |
61 if err != nil { | |
62 return nil, Base64Error | |
63 } | |
64 entry.description = string(description) | |
65 return entry, nil | |
66 } | |
67 | |
68 // NewEntry creates an Entry for the given description. | |
69 // It returns the Entry itself and a generated password. | |
70 func NewEntry(description string) (entry *Entry, password string, err error) { | |
71 if len(description) > 255 { | |
72 return nil, "", LongDescriptionError | |
73 } | |
74 passBytes := genPassword() | |
75 password = string(passBytes) | |
76 hashBytes, err := bcrypt.GenerateFromPassword(passBytes, bcryptCost) | |
77 if err != nil { | |
78 // This is very unexpected. | |
79 return nil, "", err | |
80 } | |
81 e := new(Entry) | |
82 e.id = newID() | |
83 e.hash = string(hashBytes) | |
84 e.description = description | |
85 return e, password, nil | |
86 } | |
87 | |
88 // ID is a unique 64-bit integer which identifies the entry. | |
89 func (e *Entry) ID() uint64 { | |
90 return e.id | |
91 } | |
92 | |
93 // Description is the user's description of their password. | |
94 func (e *Entry) Description() string { | |
95 return e.description | |
96 } | |
97 | |
98 // Authenticate tests whether the password is correct. | |
99 func (e *Entry) Authenticate(password string) bool { | |
100 err := bcrypt.CompareHashAndPassword([]byte(e.hash), []byte(password)) | |
101 return err == nil | |
102 } | |
103 | |
104 // Encode encodes this Entry to a bytestring for writing to a multipass shadow file. | |
105 func (e *Entry) Encode() string { | |
106 segments := []string{ | |
107 strconv.FormatUint(e.id, 10), | |
108 e.hash, | |
109 base64.StdEncoding.EncodeToString([]byte(e.description)), | |
110 } | |
111 return strings.Join(segments, ":") | |
112 } | |
113 | |
114 func genPassword() []byte { | |
115 password := []byte(template) | |
116 for i, chr := range password { | |
117 if chr == '?' { | |
118 password[i] = randChr() | |
119 } | |
120 } | |
121 return password | |
122 } | |
123 | |
124 func randChr() byte { | |
125 bigIdx, err := rand.Int(rand.Reader, lowercaseLen) | |
126 if err != nil { | |
127 panic("multipass/auth: can't get a random number") | |
128 } | |
129 idx := bigIdx.Int64() | |
130 return byte(lowercases[idx]) | |
131 } | |
132 | |
133 func newID() uint64 { | |
134 bigID, err := rand.Int(rand.Reader, maxID) | |
135 if err != nil { | |
136 panic("multipass/auth: can't get a random number") | |
137 } | |
138 return bigID.Uint64() | |
139 } |