Documentation ¶
Overview ¶
Package gosecure provides hashing, checking and associated functions for passphrases and JWT tokens. Tokens allow for session data to be passed in by a client having been encoded earlier. With both hashes and tokens secure cryptographic functions are used. All functions can be called in a time boxed fashion with guarantees about minimum execution time using the Timebox function.
Index ¶
- Constants
- Variables
- func Compare(passphrase string, hash []byte) error
- func Handle(level, msg string, detail Detail)
- func Hash(passphrase string) ([]byte, error)
- func Timebox(duration time.Duration, task func())
- type API
- type Authenticator
- type Bcrypt
- type ConsoleErrorListener
- type Conversation
- type Crypto
- type Detail
- type ErrorListener
- type ErrorRecorder
- type Limiter
- type LogErrorListener
- type LogrusErrorListener
- type RateLimiter
- type Session
- type Signatory
Examples ¶
Constants ¶
const AuthHeader = "Authorization"
AuthHeader is the header used to store the bearer token.
const SessionKey = "session"
SessionKey is used to store a session in a Gorilla context.
Variables ¶
var ( // ErrEmptyPassphrase is returned if an attempt is made to hash an empty // passphrase. ErrEmptyPassphrase = errors.New("security: passphrase cannot be empty") // ErrMismatchedPassphrase is returned if the passphrase does not match the // provided hash. ErrMismatchedPassphrase = errors.New("security: passphrase does not match hash") )
var DefaultAlgorithm = Bcrypt{Cost: bcrypt.DefaultCost}
DefaultAlgorithm is the algorithm used by Hash and Compare.
var ErrNoCredentialsFound = errors.New("no credentials found")
ErrNoCredentialsFound is returned if an attempt is made to validate an empty bearer token.
var Exit = os.Exit
Exit is the exit function called when LogErrorListener outputs a Fatal log message.
var Fuse = 100000
Fuse is used to limit the number of hits stored against an action. If the fuse is trigger the action is thrown away. While it's theoretically possible to game the limited by carefully constructing the number of hits vs the limit and TTL in reality the Fuse will only be blown when a DOS style attack is happening and should be well over the limits set on the limiter.
Theoretical max memory usage is Fuse * size * ~60bytes.
Functions ¶
func Compare ¶
Compare a passphrase to a previously hashed passphrase using the default crypto algorithm. If the passphrase matches the hash then this function will return nil. A mismatched passphrase will result in ErrMismatchedPassphrase. All other errors indicate something went wrong comparing the hash and passphrase.
Example ¶
package main import ( "fmt" "bitbucket.org/idomdavis/gosecure" ) func main() { b := []byte{ 36, 50, 97, 36, 49, 48, 36, 100, 117, 66, 70, 85, 103, 120, 51, 68, 80, 106, 103, 108, 90, 74, 84, 51, 101, 56, 57, 122, 79, 87, 122, 74, 122, 51, 46, 108, 82, 73, 106, 56, 115, 101, 88, 112, 85, 46, 68, 67, 112, 78, 48, 121, 67, 97, 57, 109, 74, 98, 114, 67} if err := gosecure.Compare("passphrase", b); err != nil { fmt.Println("invalid passphrase") } else { fmt.Println("passphrase accepted") } }
Output: passphrase accepted
func Handle ¶ added in v1.1.0
Handle an error at the given level using the ErrorListener set on ErrorHandler. If this is nil, do nothing.
Example ¶
package main import ( "bytes" "fmt" "log" "bitbucket.org/idomdavis/gosecure" ) func main() { // Intercept log output, turn off exiting on fatal, and recover from panic var buf bytes.Buffer log.SetFlags(0) log.SetOutput(&buf) gosecure.Exit = func(int) { buf.WriteString("Exit\n") } defer func() { if r := recover(); r != nil { // Output the log here after we've panicked fmt.Println(buf.String()) } }() // Handle different scenarios gosecure.Handle("Error", "Something went wrong", gosecure.Detail{"k": "v1"}) gosecure.Handle("Fatal", "Something died", gosecure.Detail{"k": "v2"}) gosecure.Handle("Panic", "Something went bang", gosecure.Detail{"k": "v3"}) }
Output: [ERROR ] Something went wrong: {"k":"v1"} [FATAL ] Something died: {"k":"v2"} Exit [PANIC ] Something went bang: {"k":"v3"}
func Hash ¶
Hash a passphrase using the default crypto algorithm.
Example ¶
package main import ( "fmt" "bitbucket.org/idomdavis/gosecure" ) func main() { if h, err := gosecure.Hash("passphrase"); err == nil { fmt.Println(len(h)) } }
Output: 60
func Timebox ¶
Timebox will run the provided function, taking at least the given duration.
Example ¶
package main import ( "fmt" "os" "time" "bitbucket.org/idomdavis/gosecure" ) func main() { // Duration wants to be longer than the expected maximum of the provided // function. duration := time.Millisecond * 50 start := time.Now() // Use closures to send and retrieve values var err error msg := "running..." gosecure.Timebox(duration, func() { _, err = fmt.Fprintln(os.Stdout, msg) }) if err != nil { fmt.Printf("unexpected error running timeboxed function: %s\n", err) } end := time.Now() if (end.Sub(start)) > duration { fmt.Println("time boxed") } }
Output: running... time boxed
Types ¶
type API ¶ added in v1.1.0
type API interface { // RegisterSecure registers a secure endpoint on the given url for the given // method. RegisterSecure(method, url string, handler http.HandlerFunc) // RegisterInsecure registers an insecure endpoint on the given url for the // given method. RegisterInsecure(method, url string, handler http.HandlerFunc) // Handler returns an http.Handler for the currently registered endpoints. Handler(signatory Signatory) http.Handler }
API provides a method of building an http.Handler with secure and insecure endpoints.
func NewAPI ¶ added in v1.1.0
func NewAPI(source, user RateLimiter) API
NewAPI returns a new API type that uses Gorilla for it's underlying router. All patterns and rules that apply to Gorilla paths can be used here.
The limiters are applied to all routes, however the user limiter ignores any calls not setting an HTTP Basic Auth user.
Secure routes must have an Authorization header and must have a valid bearer token. Calling a secure route without the Authorization header will result in a 404 being returned. Calling a secure route with an invalid token will result in a 403 being returned.
Example ¶
package main import ( "fmt" "net/http" "net/http/httptest" "net/url" "time" "bitbucket.org/idomdavis/gosecure" ) func main() { const ( size = 1000 limit = 100 ttl = time.Minute ) source := gosecure.NewSourceLimiter(gosecure.NewLimiter(size, limit, ttl)) user := gosecure.NewUserLimiter(gosecure.NewLimiter(size, limit, ttl)) api := gosecure.NewAPI(source, user) signatory := gosecure.NewSignatory("secret", time.Hour) api.RegisterInsecure("GET", "/login", func(w http.ResponseWriter, _ *http.Request) { // Login logic here, bailing if we've not logged in fmt.Println("Logged in!") token, _ := signatory.Generate(gosecure.Session{}) w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(token)) }) api.RegisterSecure("GET", "/endpoint", func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("Logged in and verified!")) }) handler := api.Handler(signatory) // Simulate a call to the handler handler.ServeHTTP(httptest.NewRecorder(), &http.Request{ Method: "GET", URL: &url.URL{Path: "/login"}, }) }
Output: Logged in!
type Authenticator ¶
type Authenticator interface { // Authenticate returns a handler function used to authenticate requests. Authenticate(next http.Handler) http.Handler }
Authenticator types provide middleware that will authenticate requests.
func NewAuthenticator ¶
func NewAuthenticator(signatory Signatory) Authenticator
NewAuthenticator returns a type that can be used for authentication middleware.
type Bcrypt ¶
type Bcrypt struct {
Cost int
}
Bcrypt hashing algorithm. Should be all you need.
type ConsoleErrorListener ¶ added in v1.1.0
type ConsoleErrorListener struct{}
ConsoleErrorListener reports to stdout.
func (ConsoleErrorListener) Handle ¶ added in v1.1.0
func (ConsoleErrorListener) Handle(level, msg string, detail Detail)
Handle the error by printing the details to stdout.
Example ¶
package main import ( "bitbucket.org/idomdavis/gosecure" ) func main() { l := gosecure.ConsoleErrorListener{} l.Handle("level", "message", gosecure.Detail{"k": "v"}) }
Output: level message map[k:v]
type Conversation ¶ added in v1.1.0
type Conversation interface { // Respond to the conversation, using the default response for the given // code. Respond will close the conversation. Calling respond on a closed // conversation will do nothing. Respond(code int) // Reply to the conversation, either in plain text for []byte or string // bodies, or JSON for all other types. Reply will close the conversation. // Calling reply on a closed conversation will do nothing. Reply(body interface{}) }
Conversation provides helper functions when dealing with HTTP requests, gracefully handling errors to provide information without leaking unwanted data.
func NewConversation ¶ added in v1.1.0
func NewConversation(w http.ResponseWriter, r *http.Request) Conversation
NewConversation builds a new conversation for the given request and response, populating some basic headers. The conversation will use the ErrorListener set on ErrorHandler to report errors and warnings, throwing these away if ErrorHandler is nil. The conversation will report the request method, URL, and/or remote address where appropriate. No other request data is reported.
Example ¶
package main import ( "net/http" "net/http/httptest" "net/url" "bitbucket.org/idomdavis/gosecure" ) func main() { // Temporarily set the error handler to the console error listener handler := gosecure.ErrorHandler gosecure.ErrorHandler = gosecure.ConsoleErrorListener{} defer func() { gosecure.ErrorHandler = handler }() c := gosecure.NewConversation(httptest.NewRecorder(), &http.Request{ Method: "GET", URL: &url.URL{Path: "/url"}, RemoteAddr: "remote", }) c.Reply("Called") // Will warn about a call to a closed endpoint c.Respond(200) }
Output: warn Responding on a closed conversation map[code:200 endpoint:/url method:GET type:Response]
type Crypto ¶
type Crypto interface { Hash(passphrase string) ([]byte, error) Compare(passphrase string, hash []byte) error }
Crypto defines a type that can cryptographically hash and compare a passphrase. Implementations of Crypto want to implement strong hashing functions such as bcrypt. Implementing this for things like MD5 is a stunningly bad idea.
type Detail ¶ added in v1.1.0
type Detail map[string]interface{}
Detail is used to hold details of an error passed to an ErrorListener.
type ErrorListener ¶ added in v1.1.0
type ErrorListener interface { // Handle an error at the given level. Handle(level, msg string, detail Detail) }
An ErrorListener is used to handle errors produced by a Conversation.
var ErrorHandler ErrorListener = LogErrorListener{}
ErrorHandler is used by conversations to handle errors. By default it's set to a LogErrorListener. Set this to nil if you don't want errors to be handled/reported.
type ErrorRecorder ¶ added in v1.1.0
type ErrorRecorder struct {
Output string
}
ErrorRecorder records the last error as a string in Output
func (*ErrorRecorder) Handle ¶ added in v1.1.0
func (r *ErrorRecorder) Handle(level, msg string, detail Detail)
Handle an error by setting ErrorRecorder.Output to the error.
type Limiter ¶
type Limiter interface { // Limit checks if the action provided should be limited, returning true if // it should. Limit(action string) bool }
A Limiter is used to indicate if an action has occurred too often and should be limited.
func NewLimiter ¶
NewLimiter returns a limiter that uses a lazy LRU backing cache. The limiter prunes expired hits for an action on a call to Limit, and will drop the least recently used item from the cache if it exceeds the defined size.
TTL sizes want to be small (in the 1 second to 1 minute range) to avoid the number of hits in the cache getting too large in case of a DOS style attack. A Fuse value is used to prevent runaway memory consumption in this case.
type LogErrorListener ¶ added in v1.1.0
type LogErrorListener struct{}
LogErrorListener handles errors by reporting them via the log package in the standard library. "Panic" and "Fatal" levels behave like Panic() and Fatal() respectively. All other levels are logged using Print(). The log level and detail are embedded into the output.
func (LogErrorListener) Handle ¶ added in v1.1.0
func (LogErrorListener) Handle(level, msg string, detail Detail)
Handle errors using the log package.
type LogrusErrorListener ¶ added in v1.2.0
type LogrusErrorListener struct{}
LogrusErrorListener reports using the default logrus logger.
func (LogrusErrorListener) Handle ¶ added in v1.2.0
func (LogrusErrorListener) Handle(level, msg string, detail Detail)
Handle errors using logrus. Invalid levels are logged as Errors and the invalid level included in the output.
type RateLimiter ¶
type RateLimiter interface { // Limit returns a handler function used to rate limit requests. Limit(next http.Handler) http.Handler }
RateLimiter defines Gorilla middleware to be used to rate limit requests. If a limiter is triggered it returns "429 Too Many Requests".
func NewSourceLimiter ¶
func NewSourceLimiter(limiter Limiter) RateLimiter
NewSourceLimiter returns middleware that will limit requests based on the remote address, or the source of the request.
func NewUserLimiter ¶
func NewUserLimiter(limiter Limiter) RateLimiter
NewUserLimiter returns middleware that will limit requests based on the user in the basic auth header. If no user is set this limiter is skipped.
type Signatory ¶
type Signatory interface { // Generate a bearer token, embedding the given Session in the token. Generate(data Session) (string, error) // Validate a bearer token, returning the associated Session embedded in the // token. An error is returned if there is a problem validating the token. Validate(bearer string) (Session, error) }
A Signatory is used to sign and validate sessions via the use of JWT bearer tokens.
func NewSignatory ¶
NewSignatory constructs a signatory using the given secret string. If tokens are going to be shared between signatories they must use the same secret.
Example ¶
package main import ( "fmt" "time" "bitbucket.org/idomdavis/gosecure" ) const secret = "secretString" func main() { // Use the default TTL _ = gosecure.NewSignatory(secret, 0) // Specify TTL, generate, then validate a token. sig := gosecure.NewSignatory(secret, time.Hour) token, _ := sig.Generate(gosecure.Session{"key": "value"}) session, _ := sig.Validate(token) fmt.Println(session["key"]) }
Output: value