Mercurial > go > multipass
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/auth/auth.go Sat Oct 24 21:32:03 2015 -0400 @@ -0,0 +1,147 @@ +// Package auth contains data structures for authenticating users. + +package auth + +import ( + "crypto/rand" + "encoding/base64" + "errors" + "math/big" + "strconv" + "strings" + + "golang.org/x/crypto/bcrypt" +) + +const ( + // default cost stolen from python bcrypt + bcryptCost = 12 + // we only generate passwords from lowercases for non-ambiguity + lowercases = "abcdefghijklmnopqrstuvwxyz" + template = "____-____-____-____" +) + +var ( + lowercaseLen *big.Int = big.NewInt(int64(len(lowercases))) + maxID = big.NewInt(0) +) + +var ( + ShortEntryError error = errors.New("multipass/auth: password entry must have 3 or more fields") + BadIDError = errors.New("multipass/auth: ID field invalid") + Base64Error = errors.New("multipass/auth: can't decode base64 data") + LongDescriptionError = errors.New("multipass/auth: description must be less than 255 bytes") +) + +func init() { + one := big.NewInt(1) + maxID.Lsh(one, 64) +} + +// Entry represents a single entry in the a multipass file. +type Entry struct { + id uint64 + hash string + description string + rest []string +} + +// EntryFromShadow creates a new entry from a line in a multipass shadow file. +// The line should not end in a newline. +func EntryFromShadow(shadow string) (*Entry, error) { + segments := strings.Split(shadow, ":") + if len(segments) < 2 { + return nil, ShortEntryError + } + entry := new(Entry) + id, err := strconv.ParseUint(segments[0], 10, 64) + if err != nil { + return nil, BadIDError + } + entry.id = id + entry.hash = segments[1] + if len(segments) > 2 { + description, err := base64.StdEncoding.DecodeString(segments[2]) + if err != nil { + return nil, Base64Error + } + entry.description = string(description) + entry.rest = segments[3:] + } + return entry, nil +} + +// NewEntry creates an Entry for the given description. +// It returns the Entry itself and a generated password. +func NewEntry(description string) (entry *Entry, password string, err error) { + if len(description) > 255 { + return nil, "", LongDescriptionError + } + passBytes := genPassword() + password = string(passBytes) + hashBytes, err := bcrypt.GenerateFromPassword(passBytes, bcryptCost) + if err != nil { + // This is very unexpected. + return nil, "", err + } + e := new(Entry) + e.id = newID() + e.hash = string(hashBytes) + e.description = description + return e, password, nil +} + +// ID is a unique 64-bit integer which identifies the entry. +func (e *Entry) ID() uint64 { + return e.id +} + +// Description is the user's description of their password. +func (e *Entry) Description() string { + return e.description +} + +// Authenticate tests whether the password is correct. +func (e *Entry) Authenticate(password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(e.hash), []byte(password)) + return err == nil +} + +// Encode encodes this Entry to a bytestring for writing to a multipass shadow file. +func (e *Entry) Encode() string { + segments := []string{ + strconv.FormatUint(e.id, 10), + e.hash, + base64.StdEncoding.EncodeToString([]byte(e.description)), + } + segments = append(segments, e.rest...) + return strings.Join(segments, ":") +} + +func genPassword() []byte { + password := []byte(template) + for group := 0; group < 4; group++ { + base := group * 5 + for chr := 0; chr < 4; chr++ { + password[base+chr] = randChr() + } + } + return password +} + +func randChr() byte { + bigIdx, err := rand.Int(rand.Reader, lowercaseLen) + if err != nil { + panic("multipass/auth: can't get a random number") + } + idx := bigIdx.Int64() + return byte(lowercases[idx]) +} + +func newID() uint64 { + bigID, err := rand.Int(rand.Reader, maxID) + if err != nil { + panic("multipass/auth: can't get a random number") + } + return bigID.Uint64() +}