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 ¶
Examples ¶
Constants ¶
const ( ContentType = "Content-Type" ContentPlain = "text/plain;charset=utf-8" ContentJSON = "application/json;charset=UTF-8" ContentHTML = "text/html;charset=utf-8" )
Content types.
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") // ErrEmptyHash is returned if an attempt is made to compare a passphrase // against an empty hash slice. ErrEmptyHash = errors.New("security: hash 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 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 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) // Router returns a Gorilla router for the currently registered endpoints. // The optional set of middleware functions are applied to the secure router // that forms part of the router before it's returned. Router(signatory Signatory, f ...mux.MiddlewareFunc) *mux.Router // 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 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 with an HTTP OK and the given body. Content // type is determined based on the given body. A string/byte slice starting // with '<' will be treated as HTML, otherwise it is treated as plain text. // All other types are treated as JSON. Reply will close the conversation. // Calling Reply on a closed conversation will do nothing. Reply(body interface{}) // Response allows full control over the conversations response setting both // the body and HTTP response code. The body will either be in plain text // for []byte or string bodies, or JSON for all other types. Response will // close the conversation. Calling Response on a closed conversation will // do nothing. Response(code int, 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 DefaultListener set in the log package to report errors and warnings, throwing these away if DefaultListener 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 ( "fmt" "net/http" "net/http/httptest" "net/url" "bitbucket.org/idomdavis/gosecure" "bitbucket.org/idomdavis/gosecure/log" ) func main() { // Temporarily set the error handler to the console error listener listener := log.DefaultListener recorder := &log.Recorder{} log.DefaultListener = recorder defer func() { log.DefaultListener = listener }() 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) fmt.Println(recorder.Output) }
Output: warn Attempt to use 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 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 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.
If this limiter is triggered a warning log event is emitted.
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.
If this limiter is triggered a warning log event is emitted.
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