github.com/mika/distribution@v2.2.2-0.20160108133430-a75790e3d8e0+incompatible/registry/auth/htpasswd/htpasswd.go (about) 1 package htpasswd 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "strings" 8 9 "golang.org/x/crypto/bcrypt" 10 ) 11 12 // htpasswd holds a path to a system .htpasswd file and the machinery to parse 13 // it. Only bcrypt hash entries are supported. 14 type htpasswd struct { 15 entries map[string][]byte // maps username to password byte slice. 16 } 17 18 // newHTPasswd parses the reader and returns an htpasswd or an error. 19 func newHTPasswd(rd io.Reader) (*htpasswd, error) { 20 entries, err := parseHTPasswd(rd) 21 if err != nil { 22 return nil, err 23 } 24 25 return &htpasswd{entries: entries}, nil 26 } 27 28 // AuthenticateUser checks a given user:password credential against the 29 // receiving HTPasswd's file. If the check passes, nil is returned. 30 func (htpasswd *htpasswd) authenticateUser(username string, password string) error { 31 credentials, ok := htpasswd.entries[username] 32 if !ok { 33 // timing attack paranoia 34 bcrypt.CompareHashAndPassword([]byte{}, []byte(password)) 35 36 return ErrAuthenticationFailure 37 } 38 39 err := bcrypt.CompareHashAndPassword([]byte(credentials), []byte(password)) 40 if err != nil { 41 return ErrAuthenticationFailure 42 } 43 44 return nil 45 } 46 47 // parseHTPasswd parses the contents of htpasswd. This will read all the 48 // entries in the file, whether or not they are needed. An error is returned 49 // if an syntax errors are encountered or if the reader fails. 50 func parseHTPasswd(rd io.Reader) (map[string][]byte, error) { 51 entries := map[string][]byte{} 52 scanner := bufio.NewScanner(rd) 53 var line int 54 for scanner.Scan() { 55 line++ // 1-based line numbering 56 t := strings.TrimSpace(scanner.Text()) 57 58 if len(t) < 1 { 59 continue 60 } 61 62 // lines that *begin* with a '#' are considered comments 63 if t[0] == '#' { 64 continue 65 } 66 67 i := strings.Index(t, ":") 68 if i < 0 || i >= len(t) { 69 return nil, fmt.Errorf("htpasswd: invalid entry at line %d: %q", line, scanner.Text()) 70 } 71 72 entries[t[:i]] = []byte(t[i+1:]) 73 } 74 75 if err := scanner.Err(); err != nil { 76 return nil, err 77 } 78 79 return entries, nil 80 }