| package htpasswd |
| |
| import ( |
| "bufio" |
| "fmt" |
| "io" |
| "strings" |
| |
| "github.com/docker/distribution/registry/auth" |
| |
| "golang.org/x/crypto/bcrypt" |
| ) |
| |
| // htpasswd holds a path to a system .htpasswd file and the machinery to parse |
| // it. Only bcrypt hash entries are supported. |
| type htpasswd struct { |
| entries map[string][]byte // maps username to password byte slice. |
| } |
| |
| // newHTPasswd parses the reader and returns an htpasswd or an error. |
| func newHTPasswd(rd io.Reader) (*htpasswd, error) { |
| entries, err := parseHTPasswd(rd) |
| if err != nil { |
| return nil, err |
| } |
| |
| return &htpasswd{entries: entries}, nil |
| } |
| |
| // AuthenticateUser checks a given user:password credential against the |
| // receiving HTPasswd's file. If the check passes, nil is returned. |
| func (htpasswd *htpasswd) authenticateUser(username string, password string) error { |
| credentials, ok := htpasswd.entries[username] |
| if !ok { |
| // timing attack paranoia |
| bcrypt.CompareHashAndPassword([]byte{}, []byte(password)) |
| |
| return auth.ErrAuthenticationFailure |
| } |
| |
| err := bcrypt.CompareHashAndPassword(credentials, []byte(password)) |
| if err != nil { |
| return auth.ErrAuthenticationFailure |
| } |
| |
| return nil |
| } |
| |
| // parseHTPasswd parses the contents of htpasswd. This will read all the |
| // entries in the file, whether or not they are needed. An error is returned |
| // if a syntax errors are encountered or if the reader fails. |
| func parseHTPasswd(rd io.Reader) (map[string][]byte, error) { |
| entries := map[string][]byte{} |
| scanner := bufio.NewScanner(rd) |
| var line int |
| for scanner.Scan() { |
| line++ // 1-based line numbering |
| t := strings.TrimSpace(scanner.Text()) |
| |
| if len(t) < 1 { |
| continue |
| } |
| |
| // lines that *begin* with a '#' are considered comments |
| if t[0] == '#' { |
| continue |
| } |
| |
| i := strings.Index(t, ":") |
| if i < 0 || i >= len(t) { |
| return nil, fmt.Errorf("htpasswd: invalid entry at line %d: %q", line, scanner.Text()) |
| } |
| |
| entries[t[:i]] = []byte(t[i+1:]) |
| } |
| |
| if err := scanner.Err(); err != nil { |
| return nil, err |
| } |
| |
| return entries, nil |
| } |