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