// Copyright 2015 Matthew Holt and The Caddy Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package caddyauth import ( "crypto/sha256" "crypto/subtle" "encoding/json" "fmt" "net/http" "github.com/caddyserver/caddy/v2" ) func init() { caddy.RegisterModule(HTTPBasicAuth{}) } // HTTPBasicAuth facilitates HTTP basic authentication. type HTTPBasicAuth struct { HashRaw json.RawMessage `json:"hash,omitempty"` AccountList []Account `json:"accounts,omitempty"` Realm string `json:"realm,omitempty"` Accounts map[string]Account `json:"-"` Hash Comparer `json:"-"` } // CaddyModule returns the Caddy module information. func (HTTPBasicAuth) CaddyModule() caddy.ModuleInfo { return caddy.ModuleInfo{ Name: "http.handlers.authentication.providers.http_basic", New: func() caddy.Module { return new(HTTPBasicAuth) }, } } // Provision provisions the HTTP basic auth provider. func (hba *HTTPBasicAuth) Provision(ctx caddy.Context) error { if hba.HashRaw == nil { return fmt.Errorf("passwords must be hashed, so a hash must be defined") } // load password hasher hashIface, err := ctx.LoadModuleInline("algorithm", "http.handlers.authentication.hashes", hba.HashRaw) if err != nil { return fmt.Errorf("loading password hasher module: %v", err) } hba.Hash = hashIface.(Comparer) hba.HashRaw = nil // allow GC to deallocate if hba.Hash == nil { return fmt.Errorf("hash is required") } // load account list hba.Accounts = make(map[string]Account) for _, acct := range hba.AccountList { if _, ok := hba.Accounts[acct.Username]; ok { return fmt.Errorf("username is not unique: %s", acct.Username) } hba.Accounts[acct.Username] = acct } hba.AccountList = nil // allow GC to deallocate return nil } // Authenticate validates the user credentials in req and returns the user, if valid. func (hba HTTPBasicAuth) Authenticate(w http.ResponseWriter, req *http.Request) (User, bool, error) { username, plaintextPasswordStr, ok := req.BasicAuth() // if basic auth is missing or invalid, prompt for credentials if !ok { // browsers show a message that says something like: // "The website says: " // which is kinda dumb, but whatever. realm := hba.Realm if realm == "" { realm = "restricted" } w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s"`, realm)) return User{}, false, nil } plaintextPassword := []byte(plaintextPasswordStr) account, accountExists := hba.Accounts[username] // don't return early if account does not exist; we want // to try to avoid side-channels that leak existence same, err := hba.Hash.Compare(account.Password, plaintextPassword, account.Salt) if err != nil { return User{}, false, err } if !same || !accountExists { return User{}, false, nil } return User{ID: username}, true, nil } // Comparer is a type that can securely compare // a plaintext password with a hashed password // in constant-time. Comparers should hash the // plaintext password and then use constant-time // comparison. type Comparer interface { // Compare returns true if the result of hashing // plaintextPassword with salt is hashedPassword, // false otherwise. An error is returned only if // there is a technical/configuration error. Compare(hashedPassword, plaintextPassword, salt []byte) (bool, error) } type quickComparer struct{} func (quickComparer) Compare(theirHash, plaintext, _ []byte) (bool, error) { ourHash := quickHash(plaintext) return hashesMatch(ourHash, theirHash), nil } func hashesMatch(pwdHash1, pwdHash2 []byte) bool { return subtle.ConstantTimeCompare(pwdHash1, pwdHash2) == 1 } // quickHash returns the SHA-256 of v. It // is not secure for password storage, but // it is useful for efficiently normalizing // the length of plaintext passwords for // constant-time comparisons. // // Errors are discarded. func quickHash(v []byte) []byte { h := sha256.New() h.Write([]byte(v)) return h.Sum(nil) } // Account contains a username, password, and salt (if applicable). type Account struct { Username string `json:"username"` Password []byte `json:"password"` Salt []byte `json:"salt,omitempty"` // for algorithms where external salt is needed } // Interface guards var ( _ caddy.Provisioner = (*HTTPBasicAuth)(nil) _ Authenticator = (*HTTPBasicAuth)(nil) )