digest

package
v0.0.0-...-3eca13d Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 1, 2014 License: MIT Imports: 16 Imported by: 0

README

auth/digest GoDoc

Authenticate using HTTP Digest Authentication for Martini or similar HTTP request routers.

Overview

HTTP Digest Authentication is one of the mechanisms described in RFC-2617, along with Basic Authentication. Digest Authentication is more secure because the password is never transmitted across the network and need not even be stored on the servers. Despite this advantage, Digest Authentication is less popular than Basic and less well supported.

This package implements the server side authentication of Digest Authentication using client provided credentials, Apache-style htdigest files, or a custom client provided mechanism.

As might be dreaded, Digest authentication comes with a suite of options to be combined into implementation taxing cross products. Not all of Digest Authentication is supported by this package. It stops implementing where libcurl and most web browsers stop on the theory that you'd have no one to talk to anyway.

Feature Support
algorithm asks for MD5-sess, accepts MD5 or MD5-sess or none
qop asks for auth, accepts auth or none, no auth-int

Usage

This package is designed to be used by Martini or a similar URL router. It provides a .ServeHTTP() method which matches the http.ServeHTTP. On successful authentication it does nothing to the response. With missing, invalid, or uncheckable credentials to responds with the appropriate HTTP status and a brief diagnostic body. If this happens the request has been fully satisfied and you should stop handling the request.

To specify your account information in a map, one might do this...

import (
  "github.com/codegangsta/martini"
  "github.com/jimstudt/http-authentication/digest"
  "log"
)

func main() {
  m := martini.Classic()
  myUserStore := digest.NewSimpleUserStore(  map[string]string{
			"foo:mortal": "3791e8e14a10b3666ba15d9e78e4b359",    // pw is 'bar'
			"Mufasa:testrealm@host.com": "939e7578ed9e3c518a452acee763bce9",   // pw is 'Circle Of Life'
                 })

  digester := digest.NewDigestHandler( "mortal", nil, nil, myUserStore )
  m.Use( digester.ServeHTTP )   // this will force authentication of all requests, you can be more specific.

  //...
}

To use an Apache style htdigest file, one would instead...

     m := martini.Classic()
     ...
     // Read file: hint, the nil is standing in for a malformed line reporter function.
     myUserFile,err := digest.NewHtdigestUserStore("path/to/my/htdigest/file", nil)
     if err != nil {
		log.Fatalf("Unable to load password file: %s", err.Error())
     }
     ...
     myUserFile.ReloadOn(syscall.SIGHUP, nil)   // optional, that nil is the bad line reporter again
     ...
     digester := digest.NewDigestHandler( "My Realm", nil, nil, myUserFile )
     ...
     m.Post("/my-sensitive-uri", digester.ServeHTTP, mySensitiveHandler)  // just protect this one, notice chained handlers.

Documentation

The API documentation is available using godoc and at godoc.org

Documentation

Overview

Package digest provides HTTP Digest authentication.

You may use Apache style htdigest files, a simple map of userXrealm->MD5(user:realm:password) or your own implementation of a user lookup function (if perhaps you use a database).

The API is designed for HTTP request routers like Martini. It provides a handler, which will do nothing to the HTTP response for valid authentication. For invalid authentication it will make an http.Error(), which will usually be a 401 (Unauthorized) to request authentication from a user.

You might use digest like this if you were getting passwords from your own source:

     m := martini.Classic()
     ...
     myUserStore := auth.NewSimpleUserStore(  map[string]string{
			"foo:mortal": "3791e8e14a10b3666ba15d9e78e4b359",    // pw is 'bar'
			"Mufasa:testrealm@host.com": "939e7578ed9e3c518a452acee763bce9",   // pw is 'Circle Of Life'
                    })
     ...
     digester := auth.NewDigestHandler( "mortal", nil, nil, myUserStore )
     m.Use( digester.ServeHTTP )   // this will force authentication of all requests, you can be more specific.

If you want to use htdigest files (because I know you thought about passing credentials on the command line or in environment variables and you should know better):

     m := martini.Classic()
     ...
     // Read file: hint, the nil is standing in for a malformed line reporter function.
     myUserFile,err := auth.NewHtdigestUserStore("path/to/my/htdigest/file", nil)
     if err != nil {
		log.Fatalf("Unable to load password file '%s': %s", digestfile, err.Error())
     }
     ...
     digester := auth.NewDigestHandler( "My Realm", nil, nil, myUserFile )
     ...
     m.Post("/my-sensitive-uri", digester.ServeHTTP, mySensitiveHandler)  // just protect this one, notice chained handlers.

You will have noticed that both New*Handler calls included a pair of nils. If you don't like the way nonces are created, tracked, and expired, then you will want to replace one or both of these with your own implementations of Nonce and NonceStore. Otherwise, ignore those interfaces. The default is a 64 bit random nonce which will last for about 100 uses before going stale. The NonceStore expires nonces which are unused for about 5 to 10 minutes.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type BadLineHandler

type BadLineHandler func(err error)

A BadLineHandler is used to notice bad lines in an htdigest file. If not nil, it will be called for each bad line with a descriptive error. Think about what you do with these, they will sometimes contain hashed passwords.

type Digest

type Digest interface {
	http.Handler

	// Turn on logging
	Log(*log.Logger)
}

The Digest interface exposes a ServeHTTP method from net/http. On successful authentication, nothing happens. On a failure or missing credentials then it will invoke http.Error(), afterwhich point you should consider the request finished.

func NewDigestHandler

func NewDigestHandler(realm string, nonces NonceStore, nonceMaker NonceMaker, users UserStore) Digest

Create a new Digest.

realm - Your HTTP Digest realm. Must match that used in your UserStore.Lookup() function or htdigest file.

nonces - Pass nil for the default NonceStore, or implement your own.

nonceMaker - Pass nil for the default, or implement your own.

users - A UserStore. A simple one that uses a map, and a slightly more complicated one that reads Apache style htdigest files are included for you to choose from. Or write your own.

type HtdigestUserStore

type HtdigestUserStore struct {
	// contains filtered or unexported fields
}

Use an htdigest file from apache's htdigest command as a source of user authentication As provided it will read the file once. You may invoke .Reload() if you wish to track changes, perhaps in response to fsnotify.

func NewHtdigestUserStore

func NewHtdigestUserStore(filename string, onbad BadLineHandler) (*HtdigestUserStore, error)

Create a new UserStore loaded from an Apache style htdigest file.

func (*HtdigestUserStore) Lookup

func (us *HtdigestUserStore) Lookup(user string, realm string) (string, bool, error)

func (*HtdigestUserStore) Reload

func (us *HtdigestUserStore) Reload(onbad BadLineHandler) error

Reload the htdigest's file. If there is an error, the old data will be kept instead. This function is thread safe.

func (*HtdigestUserStore) ReloadOn

func (us *HtdigestUserStore) ReloadOn(when os.Signal, onbad BadLineHandler)

Reload the htdigest's file on a signal. If there is an error, the old data will be kept instead. Typically you would use syscall.SIGHUP for the value of "when"

type Nonce

type Nonce interface {
	// Return the nonce string to be passed to the client
	Value() string

	// This is used when a nonce is expiring to get a new one in order
	// to chain in a sane manner.
	Next() Nonce

	// Returns true iff the nonce should be marked as stale to force a replacement,
	// ideally without ever rejecting a request.
	Stale() bool

	// Check is a particular 'nc' is acceptable at this time, and consume it if it
	// is. The function is not idempotent!
	AcceptCounter(uint) bool

	// Mark this Nonce as worn out. It should get a Next() in place and
	// after an interval, identify itself as Stale() in future calls.
	// The interval is to allow any inflight pipelined requests to clear.
	Expire()
}

Each nonce created is tracked with a Nonce. The default lasts about 100 'nc' uses before going stale.

type NonceMaker

type NonceMaker func() (Nonce, error)

A NonceMaker creates a new Nonce.

type NonceStore

type NonceStore interface {
	Add(nonce Nonce) error
	Lookup(value string) (Nonce, bool, error)
}

A NonceStore keeps track of currently valid nonces. It needs to handle expiration.

type UserStore

type UserStore interface {
	// Look up a user in a realm. Valid is xxx,true,nil
	// bool false means no such user.
	// error not nil means something when wrong in the store
	Lookup(user string, realm string) (string, bool, error)
}

func NewSimpleUserStore

func NewSimpleUserStore(users map[string]string) UserStore

Create a simple UserStore. You will pass in a map of the form { "username:realm": "md5(username:realm:password)", ... } It will be used to answer .Lookup() queries. The map is NOT copied. You could alter it if you wished to track new accounts or password changes, though I suggest you make your own implementation of UserStore instead of going that route. Spare your code readers.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL