Merge pull request #10937 from reasonerjt/csrf-2.0

Update CSRF mechanism
This commit is contained in:
Daniel Jiang 2020-03-09 12:31:08 +08:00 committed by GitHub
commit 6d89553c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 2568 additions and 36 deletions

View File

@ -4,6 +4,3 @@ enablegzip = true
[dev]
httpport = 8080
EnableXSRF = true
XSRFKey = {{xsrf_key}}
XSRFExpire = 3600

View File

@ -47,6 +47,7 @@ REGISTRY_CONTROLLER_URL={{registry_controller_url}}
WITH_CHARTMUSEUM={{with_chartmuseum}}
REGISTRY_CREDENTIAL_USERNAME={{registry_username}}
REGISTRY_CREDENTIAL_PASSWORD={{registry_password}}
CSRF_KEY={{csrf_key}}
HTTP_PROXY={{core_http_proxy}}
HTTPS_PROXY={{core_https_proxy}}

View File

@ -32,14 +32,14 @@ def prepare_core(config_dict, with_notary, with_clair, with_trivy, with_chartmus
with_clair=with_clair,
with_trivy=with_trivy,
with_chartmuseum=with_chartmuseum,
csrf_key=generate_random_string(32),
**config_dict)
render_jinja(
core_conf_template_path,
core_conf,
uid=DEFAULT_UID,
gid=DEFAULT_GID,
xsrf_key=generate_random_string(40))
gid=DEFAULT_GID)
def copy_core_config(core_templates_path, core_config_path):

View File

@ -83,10 +83,6 @@ func (b *BaseController) Prepare() {
return
}
b.ProjectMgr = pm
if !filter.ReqCarriesSession(b.Ctx.Request) {
b.EnableXSRF = false
}
}
// RequireAuthenticated returns true when the request is authenticated
@ -96,7 +92,6 @@ func (b *BaseController) RequireAuthenticated() bool {
b.SendError(internal_errors.UnauthorizedError(errors.New("Unauthorized")))
return false
}
return true
}

View File

@ -16,6 +16,7 @@ package controllers
import (
"context"
"fmt"
"github.com/goharbor/harbor/src/core/middlewares"
"net/http"
"net/http/httptest"
"os"
@ -38,7 +39,6 @@ func init() {
dir := filepath.Dir(file)
dir = filepath.Join(dir, "..")
apppath, _ := filepath.Abs(dir)
beego.BConfig.WebConfig.EnableXSRF = true
beego.BConfig.WebConfig.Session.SessionOn = true
beego.TestBeegoInit(apppath)
beego.AddTemplateExt("htm")
@ -100,30 +100,38 @@ func TestRedirectForOIDC(t *testing.T) {
func TestAll(t *testing.T) {
config.InitWithSettings(utilstest.GetUnitTestConfig())
assert := assert.New(t)
handler := http.Handler(beego.BeeApp.Handlers)
mws := middlewares.MiddleWares()
for i := len(mws) - 1; i >= 0; i-- {
if mws[i] == nil {
continue
}
handler = mws[i](handler)
}
r, _ := http.NewRequest("POST", "/c/login", nil)
w := httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(http.StatusUnprocessableEntity, w.Code, "'/c/login' httpStatusCode should be 422")
handler.ServeHTTP(w, r)
assert.Equal(http.StatusForbidden, w.Code, "'/c/login' httpStatusCode should be 403")
r, _ = http.NewRequest("GET", "/c/log_out", nil)
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
handler.ServeHTTP(w, r)
assert.Equal(int(200), w.Code, "'/c/log_out' httpStatusCode should be 200")
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), ""), "http respond should be empty")
r, _ = http.NewRequest("POST", "/c/reset", nil)
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(http.StatusUnprocessableEntity, w.Code, "'/c/reset' httpStatusCode should be 422")
handler.ServeHTTP(w, r)
assert.Equal(http.StatusForbidden, w.Code, "'/c/reset' httpStatusCode should be 403")
r, _ = http.NewRequest("POST", "/c/userExists", nil)
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
assert.Equal(http.StatusUnprocessableEntity, w.Code, "'/c/userExists' httpStatusCode should be 422")
handler.ServeHTTP(w, r)
assert.Equal(http.StatusForbidden, w.Code, "'/c/userExists' httpStatusCode should be 403")
r, _ = http.NewRequest("GET", "/c/sendEmail", nil)
w = httptest.NewRecorder()
beego.BeeApp.Handlers.ServeHTTP(w, r)
handler.ServeHTTP(w, r)
assert.Equal(int(400), w.Code, "'/c/sendEmail' httpStatusCode should be 400")
}

View File

@ -18,11 +18,8 @@ func SessionCheck(ctx *beegoctx.Context) {
req := ctx.Request
_, err := req.Cookie(config.SessionCookieName)
if err == nil {
// This is a temp workaround for beego bug: https://github.com/goharbor/harbor/issues/10446
// After we upgrading beego to the latest version and moving the filter to middleware,
// this workaround can be removed
*(ctx.Request) = *(req.WithContext(context.WithValue(req.Context(), SessionReqKey, true)))
log.Debug("Mark the request as no-session")
ctx.Request = req.WithContext(context.WithValue(req.Context(), SessionReqKey, true))
log.Debugf("Mark the request as with-session: %s %s", req.Method, req.URL.RawPath)
}
}

View File

@ -15,6 +15,7 @@
package middlewares
import (
"github.com/goharbor/harbor/src/server/middleware/csrf"
"github.com/goharbor/harbor/src/server/middleware/readonly"
"net/http"
"path"
@ -71,6 +72,7 @@ func legacyAPISkipper(r *http.Request) bool {
// MiddleWares returns global middlewares
func MiddleWares() []beego.MiddleWare {
return []beego.MiddleWare{
csrf.Middleware(),
requestid.Middleware(),
readonly.Middleware(readonlySkippers...),
orm.Middleware(legacyAPISkipper),

View File

@ -6,8 +6,3 @@ import "github.com/goharbor/harbor/src/core/api"
type BaseHandler struct {
api.BaseController
}
// Prepare disable the xsrf as the request is from other components and do not require the xsrf token
func (bh *BaseHandler) Prepare() {
bh.EnableXSRF = false
}

View File

@ -27,13 +27,6 @@ type Handler struct {
beego.Controller
}
// Prepare disables xsrf for /service/token endpoint.
// This is done on purpose b/c containerd will try to send POST and fallback to GET
// more details see #10305
func (h *Handler) Prepare() {
h.EnableXSRF = false
}
// Get handles GET request, it checks the http header for user credentials
// and parse service and scope based on docker registry v2 standard,
// checks the permission against local DB and generates jwt token.

View File

@ -44,6 +44,7 @@ require (
github.com/google/certificate-transparency-go v1.0.21 // indirect
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect
github.com/google/uuid v1.1.1
github.com/gorilla/csrf v1.6.2
github.com/gorilla/handlers v1.3.0
github.com/gorilla/mux v1.7.2
github.com/graph-gophers/dataloader v5.0.0+incompatible

View File

@ -342,6 +342,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/csrf v1.6.2 h1:QqQ/OWwuFp4jMKgBFAzJVW3FMULdyUW7JoM4pEWuqKg=
github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI=
github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
@ -349,6 +351,8 @@ github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=

View File

@ -0,0 +1,89 @@
package csrf
import (
"github.com/goharbor/harbor/src/common/utils"
"github.com/goharbor/harbor/src/common/utils/log"
"github.com/goharbor/harbor/src/core/config"
ierror "github.com/goharbor/harbor/src/internal/error"
serror "github.com/goharbor/harbor/src/server/error"
"github.com/goharbor/harbor/src/server/middleware"
"github.com/gorilla/csrf"
"net/http"
"os"
"strings"
"sync"
)
const (
csrfKeyEnv = "CSRF_KEY"
tokenHeader = "X-Harbor-CSRF-Token"
tokenCookie = "__csrf"
)
var (
once sync.Once
protect func(handler http.Handler) http.Handler
)
// attachToken makes sure if csrf generate a new token it will be included in the response header
func attachToken(w http.ResponseWriter, r *http.Request) {
if t := csrf.Token(r); len(t) > 0 {
http.SetCookie(w, &http.Cookie{
Name: tokenCookie,
Secure: true,
Value: t,
Path: "/",
SameSite: http.SameSiteStrictMode,
})
} else {
log.Warningf("token not found in context, skip attaching")
}
}
func handleError(w http.ResponseWriter, r *http.Request) {
attachToken(w, r)
serror.SendError(w, ierror.New(csrf.FailureReason(r)).WithCode(ierror.ForbiddenCode))
return
}
func attach(handler http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
attachToken(rw, req)
handler.ServeHTTP(rw, req)
})
}
// Middleware initialize the middleware to apply csrf selectively
func Middleware() func(handler http.Handler) http.Handler {
once.Do(func() {
key := os.Getenv(csrfKeyEnv)
if len(key) != 32 {
log.Warningf("Invalid CSRF key from environment: %s, generating random key...", key)
key = utils.GenerateRandomString()
}
protect = csrf.Protect([]byte(key), csrf.RequestHeader(tokenHeader),
csrf.ErrorHandler(http.HandlerFunc(handleError)),
csrf.SameSite(csrf.SameSiteStrictMode),
csrf.Path("/"))
})
return middleware.New(func(rw http.ResponseWriter, req *http.Request, next http.Handler) {
protect(attach(next)).ServeHTTP(rw, req)
}, csrfSkipper)
}
// csrfSkipper makes sure only some of the uris accessed by non-UI client can skip the csrf check
func csrfSkipper(req *http.Request) bool {
path := req.URL.Path
// We can check the cookie directly b/c the filter and controllerRegistry is executed after middleware, so no session
// cookie is added by beego.
_, err := req.Cookie(config.SessionCookieName)
hasSession := err == nil
if (strings.HasPrefix(path, "/v2/") ||
strings.HasPrefix(path, "/api/") ||
strings.HasPrefix(path, "/chartrepo/") ||
strings.HasPrefix(path, "/service/")) && !hasSession {
return true
}
return false
}

View File

@ -0,0 +1,60 @@
package csrf
import (
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
type handler struct {
}
func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
}
func TestMiddleware(t *testing.T) {
srv := Middleware()(&handler{})
cases := []struct {
req *http.Request
statusCode int
returnToken bool
}{
{
req: httptest.NewRequest(http.MethodGet, "/", nil),
statusCode: http.StatusOK,
returnToken: true,
},
{
req: httptest.NewRequest(http.MethodDelete, "/", nil),
statusCode: http.StatusForbidden,
returnToken: true,
},
{
req: httptest.NewRequest(http.MethodGet, "/api/2.0/projects", nil), // should be skipped
statusCode: http.StatusOK,
returnToken: false,
},
{
req: httptest.NewRequest(http.MethodDelete, "/v2/library/hello-world/manifests/latest", nil), // should be skipped
statusCode: http.StatusOK,
returnToken: false,
},
}
for _, c := range cases {
rec := httptest.NewRecorder()
srv.ServeHTTP(rec, c.req)
assert.Equal(t, c.statusCode, rec.Result().StatusCode)
assert.Equal(t, c.returnToken, hasCookie(rec.Result(), tokenCookie))
}
}
func hasCookie(resp *http.Response, name string) bool {
for _, c := range resp.Cookies() {
if c != nil && c.Name == name {
return true
}
}
return false
}

20
src/vendor/github.com/gorilla/csrf/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,20 @@
# This is the official list of gorilla/csrf authors for copyright purposes.
# Please keep the list sorted.
adiabatic <adiabatic@users.noreply.github.com>
Google LLC (https://opensource.google.com)
jamesgroat <james@groat.com>
Joshua Carp <jm.carp@gmail.com>
Kamil Kisiel <kamil@kamilkisiel.net>
Kevin Burke <kev@inburke.com>
Kévin Dunglas <dunglas@gmail.com>
Kristoffer Berdal <web@flexd.net>
Martin Angers <martin.n.angers@gmail.com>
Matt Silverlock <matt@eatsleeprepeat.net>
Philip I. Thomas <mail@philipithomas.com>
Richard Musiol <mail@richard-musiol.de>
Seth Hoenig <seth.a.hoenig@gmail.com>
Stefano Vettorazzi <stefanovc@gmail.com>
Wayne Ashley Berry <wayneashleyberry@gmail.com>
田浩浩 <llitfkitfk@gmail.com>
陈东海 <cdh_cjx@163.com>

27
src/vendor/github.com/gorilla/csrf/Gopkg.lock generated vendored Normal file
View File

@ -0,0 +1,27 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/gorilla/context"
packages = ["."]
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
version = "v1.1"
[[projects]]
name = "github.com/gorilla/securecookie"
packages = ["."]
revision = "667fe4e3466a040b780561fe9b51a83a3753eefc"
version = "v1.1"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "1695686bc8fa0eb76df9fe8c5ca473686071ddcf795a0595a9465a03e8ac9bef"
solver-name = "gps-cdcl"
solver-version = 1

34
src/vendor/github.com/gorilla/csrf/Gopkg.toml generated vendored Normal file
View File

@ -0,0 +1,34 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/gorilla/context"
version = "1.1.0"
[[constraint]]
name = "github.com/gorilla/securecookie"
version = "1.1.0"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"

26
src/vendor/github.com/gorilla/csrf/LICENSE generated vendored Normal file
View File

@ -0,0 +1,26 @@
Copyright (c) 2015-2018, The Gorilla Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

352
src/vendor/github.com/gorilla/csrf/README.md generated vendored Normal file
View File

@ -0,0 +1,352 @@
# gorilla/csrf
[![GoDoc](https://godoc.org/github.com/gorilla/csrf?status.svg)](https://godoc.org/github.com/gorilla/csrf)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/csrf/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/csrf?badge)
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
[![CircleCI](https://circleci.com/gh/gorilla/csrf.svg?style=svg)](https://circleci.com/gh/gorilla/csrf)
gorilla/csrf is a HTTP middleware library that provides [cross-site request
forgery](http://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/) (CSRF)
protection. It includes:
- The `csrf.Protect` middleware/handler provides CSRF protection on routes
attached to a router or a sub-router.
- A `csrf.Token` function that provides the token to pass into your response,
whether that be a HTML form or a JSON response body.
- ... and a `csrf.TemplateField` helper that you can pass into your `html/template`
templates to replace a `{{ .csrfField }}` template tag with a hidden input
field.
gorilla/csrf is designed to work with any Go web framework, including:
- The [Gorilla](https://www.gorillatoolkit.org/) toolkit
- Go's built-in [net/http](http://golang.org/pkg/net/http/) package
- [Goji](https://goji.io) - see the [tailored fork](https://github.com/goji/csrf)
- [Gin](https://github.com/gin-gonic/gin)
- [Echo](https://github.com/labstack/echo)
- ... and any other router/framework that rallies around Go's `http.Handler` interface.
gorilla/csrf is also compatible with middleware 'helper' libraries like
[Alice](https://github.com/justinas/alice) and [Negroni](https://github.com/codegangsta/negroni).
## Install
With a properly configured Go toolchain:
```sh
go get github.com/gorilla/csrf
```
## Examples
- [HTML Forms](#html-forms)
- [JavaScript Apps](#javascript-applications)
- [Google App Engine](#google-app-engine)
- [Setting SameSite](#setting-samesite)
- [Setting Options](#setting-options)
gorilla/csrf is easy to use: add the middleware to your router with
the below:
```go
CSRF := csrf.Protect([]byte("32-byte-long-auth-key"))
http.ListenAndServe(":8000", CSRF(r))
```
...and then collect the token with `csrf.Token(r)` in your handlers before
passing it to the template, JSON body or HTTP header (see below).
Note that the authentication key passed to `csrf.Protect([]byte(key))` should be
32-bytes long and persist across application restarts. Generating a random key
won't allow you to authenticate existing cookies and will break your CSRF
validation.
gorilla/csrf inspects the HTTP headers (first) and form body (second) on
subsequent POST/PUT/PATCH/DELETE/etc. requests for the token.
### HTML Forms
Here's the common use-case: HTML forms you want to provide CSRF protection for,
in order to protect malicious POST requests being made:
```go
package main
import (
"net/http"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/signup", ShowSignupForm)
// All POST requests without a valid token will return HTTP 403 Forbidden.
// We should also ensure that our mutating (non-idempotent) handler only
// matches on POST requests. We can check that here, at the router level, or
// within the handler itself via r.Method.
r.HandleFunc("/signup/post", SubmitSignupForm).Methods("POST")
// Add the middleware to your router by wrapping it.
http.ListenAndServe(":8000",
csrf.Protect([]byte("32-byte-long-auth-key"))(r))
// PS: Don't forget to pass csrf.Secure(false) if you're developing locally
// over plain HTTP (just don't leave it on in production).
}
func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
// signup_form.tmpl just needs a {{ .csrfField }} template tag for
// csrf.TemplateField to inject the CSRF token into. Easy!
t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
csrf.TemplateTag: csrf.TemplateField(r),
})
// We could also retrieve the token directly from csrf.Token(r) and
// set it in the request header - w.Header.Set("X-CSRF-Token", token)
// This is useful if you're sending JSON to clients or a front-end JavaScript
// framework.
}
func SubmitSignupForm(w http.ResponseWriter, r *http.Request) {
// We can trust that requests making it this far have satisfied
// our CSRF protection requirements.
}
```
Note that the CSRF middleware will (by necessity) consume the request body if the
token is passed via POST form values. If you need to consume this in your
handler, insert your own middleware earlier in the chain to capture the request
body.
### JavaScript Applications
This approach is useful if you're using a front-end JavaScript framework like
React, Ember or Angular, and are providing a JSON API. Specifically, we need
to provide a way for our front-end fetch/AJAX calls to pass the token on each
fetch (AJAX/XMLHttpRequest) request. We achieve this by:
- Parsing the token from the `<input>` field generated by the
`csrf.TemplateField(r)` helper, or passing it back in a response header.
- Sending this token back on every request
- Ensuring our cookie is attached to the request so that the form/header
value can be compared to the cookie value.
We'll also look at applying selective CSRF protection using
[gorilla/mux's](https://www.gorillatoolkit.org/pkg/mux) sub-routers,
as we don't handle any POST/PUT/DELETE requests with our top-level router.
```go
package main
import (
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))
api := r.PathPrefix("/api").Subrouter()
api.Use(csrfMiddleware)
api.HandleFunc("/user/{id}", GetUser).Methods("GET")
http.ListenAndServe(":8000", r)
}
func GetUser(w http.ResponseWriter, r *http.Request) {
// Authenticate the request, get the id from the route params,
// and fetch the user from the DB, etc.
// Get the token and pass it in the CSRF header. Our JSON-speaking client
// or JavaScript framework can now read the header and return the token in
// in its own "X-CSRF-Token" request header on the subsequent POST.
w.Header().Set("X-CSRF-Token", csrf.Token(r))
b, err := json.Marshal(user)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(b)
}
```
In our JavaScript application, we should read the token from the response
headers and pass it in a request header for all requests. Here's what that
looks like when using [Axios](https://github.com/axios/axios), a popular
JavaScript HTTP client library:
```js
// You can alternatively parse the response header for the X-CSRF-Token, and
// store that instead, if you followed the steps above to write the token to a
// response header.
let csrfToken = document.getElementsByName("gorilla.csrf.Token")[0].value
// via https://github.com/axios/axios#creating-an-instance
const instance = axios.create({
baseURL: "https://example.com/api/",
timeout: 1000,
headers: { "X-CSRF-Token": csrfToken }
})
// Now, any HTTP request you make will include the csrfToken from the page,
// provided you update the csrfToken variable for each render.
try {
let resp = await instance.post(endpoint, formData)
// Do something with resp
} catch (err) {
// Handle the exception
}
```
If you plan to host your JavaScript application on another domain, you can use the Trusted Origins
feature to allow the host of your JavaScript application to make requests to your Go application. Observe the example below:
```go
package main
import (
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"), csrf.TrustedOrigin([]string{"ui.domain.com"}))
api := r.PathPrefix("/api").Subrouter()
api.Use(csrfMiddleware)
api.HandleFunc("/user/{id}", GetUser).Methods("GET")
http.ListenAndServe(":8000", r)
}
func GetUser(w http.ResponseWriter, r *http.Request) {
// Authenticate the request, get the id from the route params,
// and fetch the user from the DB, etc.
// Get the token and pass it in the CSRF header. Our JSON-speaking client
// or JavaScript framework can now read the header and return the token in
// in its own "X-CSRF-Token" request header on the subsequent POST.
w.Header().Set("X-CSRF-Token", csrf.Token(r))
b, err := json.Marshal(user)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(b)
}
```
On the example above, you're authorizing requests from `ui.domain.com` to make valid CSRF requests to your application, so you can have your API server on another domain without problems.
### Google App Engine
If you're using [Google App
Engine](https://cloud.google.com/appengine/docs/go/how-requests-are-handled#Go_Requests_and_HTTP),
(first-generation) which doesn't allow you to hook into the default `http.ServeMux` directly,
you can still use gorilla/csrf (and gorilla/mux):
```go
package app
// Remember: appengine has its own package main
func init() {
r := mux.NewRouter()
r.HandleFunc("/", IndexHandler)
// ...
// We pass our CSRF-protected router to the DefaultServeMux
http.Handle("/", csrf.Protect([]byte(your-key))(r))
}
```
Note: You can ignore this if you're using the
[second-generation](https://cloud.google.com/appengine/docs/go/) Go runtime
on App Engine (Go 1.11 and above).
### Setting SameSite
Go 1.11 introduced the option to set the SameSite attribute in cookies. This is
valuable if a developer wants to instruct a browser to not include cookies during
a cross site request. SameSiteStrictMode prevents all cross site requests from including
the cookie. SameSiteLaxMode prevents CSRF prone requests (POST) from including the cookie
but allows the cookie to be included in GET requests to support external linking.
```go
func main() {
CSRF := csrf.Protect(
[]byte("a-32-byte-long-key-goes-here"),
// instruct the browser to never send cookies during cross site requests
csrf.SameSite(csrf.SameSiteStrictMode),
)
r := mux.NewRouter()
r.HandleFunc("/signup", GetSignupForm)
r.HandleFunc("/signup/post", PostSignupForm)
http.ListenAndServe(":8000", CSRF(r))
}
```
### Setting Options
What about providing your own error handler and changing the HTTP header the
package inspects on requests? (i.e. an existing API you're porting to Go). Well,
gorilla/csrf provides options for changing these as you see fit:
```go
func main() {
CSRF := csrf.Protect(
[]byte("a-32-byte-long-key-goes-here"),
csrf.RequestHeader("Authenticity-Token"),
csrf.FieldName("authenticity_token"),
csrf.ErrorHandler(http.HandlerFunc(serverError(403))),
)
r := mux.NewRouter()
r.HandleFunc("/signup", GetSignupForm)
r.HandleFunc("/signup/post", PostSignupForm)
http.ListenAndServe(":8000", CSRF(r))
}
```
Not too bad, right?
If there's something you're confused about or a feature you would like to see
added, open an issue.
## Design Notes
Getting CSRF protection right is important, so here's some background:
- This library generates unique-per-request (masked) tokens as a mitigation
against the [BREACH attack](http://breachattack.com/).
- The 'base' (unmasked) token is stored in the session, which means that
multiple browser tabs won't cause a user problems as their per-request token
is compared with the base token.
- Operates on a "whitelist only" approach where safe (non-mutating) HTTP methods
(GET, HEAD, OPTIONS, TRACE) are the _only_ methods where token validation is not
enforced.
- The design is based on the battle-tested
[Django](https://docs.djangoproject.com/en/1.8/ref/csrf/) and [Ruby on
Rails](http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html)
approaches.
- Cookies are authenticated and based on the [securecookie](https://github.com/gorilla/securecookie)
library. They're also Secure (issued over HTTPS only) and are HttpOnly
by default, because sane defaults are important.
- Cookie SameSite attribute (prevents cookies from being sent by a browser
during cross site requests) are not set by default to maintain backwards compatibility
for legacy systems. The SameSite attribute can be set with the SameSite option.
- Go's `crypto/rand` library is used to generate the 32 byte (256 bit) tokens
and the one-time-pad used for masking them.
This library does not seek to be adventurous.
## License
BSD licensed. See the LICENSE file for details.

29
src/vendor/github.com/gorilla/csrf/context.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
// +build go1.7
package csrf
import (
"context"
"net/http"
"github.com/pkg/errors"
)
func contextGet(r *http.Request, key string) (interface{}, error) {
val := r.Context().Value(key)
if val == nil {
return nil, errors.Errorf("no value exists in the context for key %q", key)
}
return val, nil
}
func contextSave(r *http.Request, key string, val interface{}) *http.Request {
ctx := r.Context()
ctx = context.WithValue(ctx, key, val)
return r.WithContext(ctx)
}
func contextClear(r *http.Request) {
// no-op for go1.7+
}

309
src/vendor/github.com/gorilla/csrf/csrf.go generated vendored Normal file
View File

@ -0,0 +1,309 @@
package csrf
import (
"fmt"
"net/http"
"net/url"
"github.com/pkg/errors"
"github.com/gorilla/securecookie"
)
// CSRF token length in bytes.
const tokenLength = 32
// Context/session keys & prefixes
const (
tokenKey string = "gorilla.csrf.Token"
formKey string = "gorilla.csrf.Form"
errorKey string = "gorilla.csrf.Error"
skipCheckKey string = "gorilla.csrf.Skip"
cookieName string = "_gorilla_csrf"
errorPrefix string = "gorilla/csrf: "
)
var (
// The name value used in form fields.
fieldName = tokenKey
// defaultAge sets the default MaxAge for cookies.
defaultAge = 3600 * 12
// The default HTTP request header to inspect
headerName = "X-CSRF-Token"
// Idempotent (safe) methods as defined by RFC7231 section 4.2.2.
safeMethods = []string{"GET", "HEAD", "OPTIONS", "TRACE"}
)
// TemplateTag provides a default template tag - e.g. {{ .csrfField }} - for use
// with the TemplateField function.
var TemplateTag = "csrfField"
var (
// ErrNoReferer is returned when a HTTPS request provides an empty Referer
// header.
ErrNoReferer = errors.New("referer not supplied")
// ErrBadReferer is returned when the scheme & host in the URL do not match
// the supplied Referer header.
ErrBadReferer = errors.New("referer invalid")
// ErrNoToken is returned if no CSRF token is supplied in the request.
ErrNoToken = errors.New("CSRF token not found in request")
// ErrBadToken is returned if the CSRF token in the request does not match
// the token in the session, or is otherwise malformed.
ErrBadToken = errors.New("CSRF token invalid")
)
// SameSiteMode allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests. The main
// goal is to mitigate the risk of cross-origin information leakage, and provide
// some protection against cross-site request forgery attacks.
//
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
type SameSiteMode int
// SameSite options
const (
SameSiteDefaultMode SameSiteMode = iota + 1
SameSiteLaxMode
SameSiteStrictMode
SameSiteNoneMode
)
type csrf struct {
h http.Handler
sc *securecookie.SecureCookie
st store
opts options
}
// options contains the optional settings for the CSRF middleware.
type options struct {
MaxAge int
Domain string
Path string
// Note that the function and field names match the case of the associated
// http.Cookie field instead of the "correct" HTTPOnly name that golint suggests.
HttpOnly bool
Secure bool
SameSite SameSiteMode
RequestHeader string
FieldName string
ErrorHandler http.Handler
CookieName string
TrustedOrigins []string
}
// Protect is HTTP middleware that provides Cross-Site Request Forgery
// protection.
//
// It securely generates a masked (unique-per-request) token that
// can be embedded in the HTTP response (e.g. form field or HTTP header).
// The original (unmasked) token is stored in the session, which is inaccessible
// by an attacker (provided you are using HTTPS). Subsequent requests are
// expected to include this token, which is compared against the session token.
// Requests that do not provide a matching token are served with a HTTP 403
// 'Forbidden' error response.
//
// Example:
// package main
//
// import (
// "html/template"
//
// "github.com/gorilla/csrf"
// "github.com/gorilla/mux"
// )
//
// var t = template.Must(template.New("signup_form.tmpl").Parse(form))
//
// func main() {
// r := mux.NewRouter()
//
// r.HandleFunc("/signup", GetSignupForm)
// // POST requests without a valid token will return a HTTP 403 Forbidden.
// r.HandleFunc("/signup/post", PostSignupForm)
//
// // Add the middleware to your router.
// http.ListenAndServe(":8000",
// // Note that the authentication key provided should be 32 bytes
// // long and persist across application restarts.
// csrf.Protect([]byte("32-byte-long-auth-key"))(r))
// }
//
// func GetSignupForm(w http.ResponseWriter, r *http.Request) {
// // signup_form.tmpl just needs a {{ .csrfField }} template tag for
// // csrf.TemplateField to inject the CSRF token into. Easy!
// t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
// csrf.TemplateTag: csrf.TemplateField(r),
// })
// // We could also retrieve the token directly from csrf.Token(r) and
// // set it in the request header - w.Header.Set("X-CSRF-Token", token)
// // This is useful if you're sending JSON to clients or a front-end JavaScript
// // framework.
// }
//
func Protect(authKey []byte, opts ...Option) func(http.Handler) http.Handler {
return func(h http.Handler) http.Handler {
cs := parseOptions(h, opts...)
// Set the defaults if no options have been specified
if cs.opts.ErrorHandler == nil {
cs.opts.ErrorHandler = http.HandlerFunc(unauthorizedHandler)
}
if cs.opts.MaxAge < 0 {
// Default of 12 hours
cs.opts.MaxAge = defaultAge
}
if cs.opts.FieldName == "" {
cs.opts.FieldName = fieldName
}
if cs.opts.CookieName == "" {
cs.opts.CookieName = cookieName
}
if cs.opts.RequestHeader == "" {
cs.opts.RequestHeader = headerName
}
// Create an authenticated securecookie instance.
if cs.sc == nil {
cs.sc = securecookie.New(authKey, nil)
// Use JSON serialization (faster than one-off gob encoding)
cs.sc.SetSerializer(securecookie.JSONEncoder{})
// Set the MaxAge of the underlying securecookie.
cs.sc.MaxAge(cs.opts.MaxAge)
}
if cs.st == nil {
// Default to the cookieStore
cs.st = &cookieStore{
name: cs.opts.CookieName,
maxAge: cs.opts.MaxAge,
secure: cs.opts.Secure,
httpOnly: cs.opts.HttpOnly,
sameSite: cs.opts.SameSite,
path: cs.opts.Path,
domain: cs.opts.Domain,
sc: cs.sc,
}
}
return cs
}
}
// Implements http.Handler for the csrf type.
func (cs *csrf) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Skip the check if directed to. This should always be a bool.
if val, err := contextGet(r, skipCheckKey); err == nil {
if skip, ok := val.(bool); ok {
if skip {
cs.h.ServeHTTP(w, r)
return
}
}
}
// Retrieve the token from the session.
// An error represents either a cookie that failed HMAC validation
// or that doesn't exist.
realToken, err := cs.st.Get(r)
if err != nil || len(realToken) != tokenLength {
// If there was an error retrieving the token, the token doesn't exist
// yet, or it's the wrong length, generate a new token.
// Note that the new token will (correctly) fail validation downstream
// as it will no longer match the request token.
realToken, err = generateRandomBytes(tokenLength)
if err != nil {
r = envError(r, err)
cs.opts.ErrorHandler.ServeHTTP(w, r)
return
}
// Save the new (real) token in the session store.
err = cs.st.Save(realToken, w)
if err != nil {
r = envError(r, err)
cs.opts.ErrorHandler.ServeHTTP(w, r)
return
}
}
// Save the masked token to the request context
r = contextSave(r, tokenKey, mask(realToken, r))
// Save the field name to the request context
r = contextSave(r, formKey, cs.opts.FieldName)
// HTTP methods not defined as idempotent ("safe") under RFC7231 require
// inspection.
if !contains(safeMethods, r.Method) {
// Enforce an origin check for HTTPS connections. As per the Django CSRF
// implementation (https://goo.gl/vKA7GE) the Referer header is almost
// always present for same-domain HTTP requests.
if r.URL.Scheme == "https" {
// Fetch the Referer value. Call the error handler if it's empty or
// otherwise fails to parse.
referer, err := url.Parse(r.Referer())
if err != nil || referer.String() == "" {
r = envError(r, ErrNoReferer)
cs.opts.ErrorHandler.ServeHTTP(w, r)
return
}
valid := sameOrigin(r.URL, referer)
if !valid {
for _, trustedOrigin := range cs.opts.TrustedOrigins {
if referer.Host == trustedOrigin {
valid = true
break
}
}
}
if valid == false {
r = envError(r, ErrBadReferer)
cs.opts.ErrorHandler.ServeHTTP(w, r)
return
}
}
// If the token returned from the session store is nil for non-idempotent
// ("unsafe") methods, call the error handler.
if realToken == nil {
r = envError(r, ErrNoToken)
cs.opts.ErrorHandler.ServeHTTP(w, r)
return
}
// Retrieve the combined token (pad + masked) token and unmask it.
requestToken := unmask(cs.requestToken(r))
// Compare the request token against the real token
if !compareTokens(requestToken, realToken) {
r = envError(r, ErrBadToken)
cs.opts.ErrorHandler.ServeHTTP(w, r)
return
}
}
// Set the Vary: Cookie header to protect clients from caching the response.
w.Header().Add("Vary", "Cookie")
// Call the wrapped handler/router on success.
cs.h.ServeHTTP(w, r)
// Clear the request context after the handler has completed.
contextClear(r)
}
// unauthorizedhandler sets a HTTP 403 Forbidden status and writes the
// CSRF failure reason to the response.
func unauthorizedHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, fmt.Sprintf("%s - %s",
http.StatusText(http.StatusForbidden), FailureReason(r)),
http.StatusForbidden)
return
}

176
src/vendor/github.com/gorilla/csrf/doc.go generated vendored Normal file
View File

@ -0,0 +1,176 @@
/*
Package csrf (gorilla/csrf) provides Cross Site Request Forgery (CSRF)
prevention middleware for Go web applications & services.
It includes:
* The `csrf.Protect` middleware/handler provides CSRF protection on routes
attached to a router or a sub-router.
* A `csrf.Token` function that provides the token to pass into your response,
whether that be a HTML form or a JSON response body.
* ... and a `csrf.TemplateField` helper that you can pass into your `html/template`
templates to replace a `{{ .csrfField }}` template tag with a hidden input
field.
gorilla/csrf is easy to use: add the middleware to individual handlers with
the below:
CSRF := csrf.Protect([]byte("32-byte-long-auth-key"))
http.HandlerFunc("/route", CSRF(YourHandler))
... and then collect the token with `csrf.Token(r)` before passing it to the
template, JSON body or HTTP header (you pick!). gorilla/csrf inspects the form body
(first) and HTTP headers (second) on subsequent POST/PUT/PATCH/DELETE/etc. requests
for the token.
Note that the authentication key passed to `csrf.Protect([]byte(key))` should be
32-bytes long and persist across application restarts. Generating a random key
won't allow you to authenticate existing cookies and will break your CSRF
validation.
Here's the common use-case: HTML forms you want to provide CSRF protection for,
in order to protect malicious POST requests being made:
package main
import (
"fmt"
"html/template"
"net/http"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
var form = `
<html>
<head>
<title>Sign Up!</title>
</head>
<body>
<form method="POST" action="/signup/post" accept-charset="UTF-8">
<input type="text" name="name">
<input type="text" name="email">
<!--
The default template tag used by the CSRF middleware .
This will be replaced with a hidden <input> field containing the
masked CSRF token.
-->
{{ .csrfField }}
<input type="submit" value="Sign up!">
</form>
</body>
</html>
`
var t = template.Must(template.New("signup_form.tmpl").Parse(form))
func main() {
r := mux.NewRouter()
r.HandleFunc("/signup", ShowSignupForm)
// All POST requests without a valid token will return HTTP 403 Forbidden.
// We should also ensure that our mutating (non-idempotent) handler only
// matches on POST requests. We can check that here, at the router level, or
// within the handler itself via r.Method.
r.HandleFunc("/signup/post", SubmitSignupForm).Methods("POST")
// Add the middleware to your router by wrapping it.
http.ListenAndServe(":8000",
csrf.Protect([]byte("32-byte-long-auth-key"))(r))
// PS: Don't forget to pass csrf.Secure(false) if you're developing locally
// over plain HTTP (just don't leave it on in production).
}
func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
// signup_form.tmpl just needs a {{ .csrfField }} template tag for
// csrf.TemplateField to inject the CSRF token into. Easy!
t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
csrf.TemplateTag: csrf.TemplateField(r),
})
}
func SubmitSignupForm(w http.ResponseWriter, r *http.Request) {
// We can trust that requests making it this far have satisfied
// our CSRF protection requirements.
fmt.Fprintf(w, "%v\n", r.PostForm)
}
Note that the CSRF middleware will (by necessity) consume the request body if the
token is passed via POST form values. If you need to consume this in your
handler, insert your own middleware earlier in the chain to capture the request
body.
You can also send the CSRF token in the response header. This approach is useful
if you're using a front-end JavaScript framework like Ember or Angular, or are
providing a JSON API:
package main
import (
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
api := r.PathPrefix("/api").Subrouter()
api.HandleFunc("/user/:id", GetUser).Methods("GET")
http.ListenAndServe(":8000",
csrf.Protect([]byte("32-byte-long-auth-key"))(r))
}
func GetUser(w http.ResponseWriter, r *http.Request) {
// Authenticate the request, get the id from the route params,
// and fetch the user from the DB, etc.
// Get the token and pass it in the CSRF header. Our JSON-speaking client
// or JavaScript framework can now read the header and return the token in
// in its own "X-CSRF-Token" request header on the subsequent POST.
w.Header().Set("X-CSRF-Token", csrf.Token(r))
b, err := json.Marshal(user)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(b)
}
If you're writing a client that's supposed to mimic browser behavior, make sure to
send back the CSRF cookie (the default name is _gorilla_csrf, but this can be changed
with the CookieName Option) along with either the X-CSRF-Token header or the gorilla.csrf.Token form field.
In addition: getting CSRF protection right is important, so here's some background:
* This library generates unique-per-request (masked) tokens as a mitigation
against the BREACH attack (http://breachattack.com/).
* The 'base' (unmasked) token is stored in the session, which means that
multiple browser tabs won't cause a user problems as their per-request token
is compared with the base token.
* Operates on a "whitelist only" approach where safe (non-mutating) HTTP methods
(GET, HEAD, OPTIONS, TRACE) are the *only* methods where token validation is not
enforced.
* The design is based on the battle-tested Django
(https://docs.djangoproject.com/en/1.8/ref/csrf/) and Ruby on Rails
(http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html)
approaches.
* Cookies are authenticated and based on the securecookie
(https://github.com/gorilla/securecookie) library. They're also Secure (issued
over HTTPS only) and are HttpOnly by default, because sane defaults are
important.
* Go's `crypto/rand` library is used to generate the 32 byte (256 bit) tokens
and the one-time-pad used for masking them.
This library does not seek to be adventurous.
*/
package csrf

6
src/vendor/github.com/gorilla/csrf/go.mod generated vendored Normal file
View File

@ -0,0 +1,6 @@
module github.com/gorilla/csrf
require (
github.com/gorilla/securecookie v1.1.1
github.com/pkg/errors v0.8.0
)

4
src/vendor/github.com/gorilla/csrf/go.sum generated vendored Normal file
View File

@ -0,0 +1,4 @@
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=

203
src/vendor/github.com/gorilla/csrf/helpers.go generated vendored Normal file
View File

@ -0,0 +1,203 @@
package csrf
import (
"crypto/rand"
"crypto/subtle"
"encoding/base64"
"fmt"
"html/template"
"net/http"
"net/url"
)
// Token returns a masked CSRF token ready for passing into HTML template or
// a JSON response body. An empty token will be returned if the middleware
// has not been applied (which will fail subsequent validation).
func Token(r *http.Request) string {
if val, err := contextGet(r, tokenKey); err == nil {
if maskedToken, ok := val.(string); ok {
return maskedToken
}
}
return ""
}
// FailureReason makes CSRF validation errors available in the request context.
// This is useful when you want to log the cause of the error or report it to
// client.
func FailureReason(r *http.Request) error {
if val, err := contextGet(r, errorKey); err == nil {
if err, ok := val.(error); ok {
return err
}
}
return nil
}
// UnsafeSkipCheck will skip the CSRF check for any requests. This must be
// called before the CSRF middleware.
//
// Note: You should not set this without otherwise securing the request from
// CSRF attacks. The primary use-case for this function is to turn off CSRF
// checks for non-browser clients using authorization tokens against your API.
func UnsafeSkipCheck(r *http.Request) *http.Request {
return contextSave(r, skipCheckKey, true)
}
// TemplateField is a template helper for html/template that provides an <input> field
// populated with a CSRF token.
//
// Example:
//
// // The following tag in our form.tmpl template:
// {{ .csrfField }}
//
// // ... becomes:
// <input type="hidden" name="gorilla.csrf.Token" value="<token>">
//
func TemplateField(r *http.Request) template.HTML {
if name, err := contextGet(r, formKey); err == nil {
fragment := fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`,
name, Token(r))
return template.HTML(fragment)
}
return template.HTML("")
}
// mask returns a unique-per-request token to mitigate the BREACH attack
// as per http://breachattack.com/#mitigations
//
// The token is generated by XOR'ing a one-time-pad and the base (session) CSRF
// token and returning them together as a 64-byte slice. This effectively
// randomises the token on a per-request basis without breaking multiple browser
// tabs/windows.
func mask(realToken []byte, r *http.Request) string {
otp, err := generateRandomBytes(tokenLength)
if err != nil {
return ""
}
// XOR the OTP with the real token to generate a masked token. Append the
// OTP to the front of the masked token to allow unmasking in the subsequent
// request.
return base64.StdEncoding.EncodeToString(append(otp, xorToken(otp, realToken)...))
}
// unmask splits the issued token (one-time-pad + masked token) and returns the
// unmasked request token for comparison.
func unmask(issued []byte) []byte {
// Issued tokens are always masked and combined with the pad.
if len(issued) != tokenLength*2 {
return nil
}
// We now know the length of the byte slice.
otp := issued[tokenLength:]
masked := issued[:tokenLength]
// Unmask the token by XOR'ing it against the OTP used to mask it.
return xorToken(otp, masked)
}
// requestToken returns the issued token (pad + masked token) from the HTTP POST
// body or HTTP header. It will return nil if the token fails to decode.
func (cs *csrf) requestToken(r *http.Request) []byte {
// 1. Check the HTTP header first.
issued := r.Header.Get(cs.opts.RequestHeader)
// 2. Fall back to the POST (form) value.
if issued == "" {
issued = r.PostFormValue(cs.opts.FieldName)
}
// 3. Finally, fall back to the multipart form (if set).
if issued == "" && r.MultipartForm != nil {
vals := r.MultipartForm.Value[cs.opts.FieldName]
if len(vals) > 0 {
issued = vals[0]
}
}
// Decode the "issued" (pad + masked) token sent in the request. Return a
// nil byte slice on a decoding error (this will fail upstream).
decoded, err := base64.StdEncoding.DecodeString(issued)
if err != nil {
return nil
}
return decoded
}
// generateRandomBytes returns securely generated random bytes.
// It will return an error if the system's secure random number generator
// fails to function correctly.
func generateRandomBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
// err == nil only if len(b) == n
if err != nil {
return nil, err
}
return b, nil
}
// sameOrigin returns true if URLs a and b share the same origin. The same
// origin is defined as host (which includes the port) and scheme.
func sameOrigin(a, b *url.URL) bool {
return (a.Scheme == b.Scheme && a.Host == b.Host)
}
// compare securely (constant-time) compares the unmasked token from the request
// against the real token from the session.
func compareTokens(a, b []byte) bool {
// This is required as subtle.ConstantTimeCompare does not check for equal
// lengths in Go versions prior to 1.3.
if len(a) != len(b) {
return false
}
return subtle.ConstantTimeCompare(a, b) == 1
}
// xorToken XORs tokens ([]byte) to provide unique-per-request CSRF tokens. It
// will return a masked token if the base token is XOR'ed with a one-time-pad.
// An unmasked token will be returned if a masked token is XOR'ed with the
// one-time-pad used to mask it.
func xorToken(a, b []byte) []byte {
n := len(a)
if len(b) < n {
n = len(b)
}
res := make([]byte, n)
for i := 0; i < n; i++ {
res[i] = a[i] ^ b[i]
}
return res
}
// contains is a helper function to check if a string exists in a slice - e.g.
// whether a HTTP method exists in a list of safe methods.
func contains(vals []string, s string) bool {
for _, v := range vals {
if v == s {
return true
}
}
return false
}
// envError stores a CSRF error in the request context.
func envError(r *http.Request, err error) *http.Request {
return contextSave(r, errorKey, err)
}

170
src/vendor/github.com/gorilla/csrf/options.go generated vendored Normal file
View File

@ -0,0 +1,170 @@
package csrf
import (
"net/http"
)
// Option describes a functional option for configuring the CSRF handler.
type Option func(*csrf)
// MaxAge sets the maximum age (in seconds) of a CSRF token's underlying cookie.
// Defaults to 12 hours. Call csrf.MaxAge(0) to explicitly set session-only
// cookies.
func MaxAge(age int) Option {
return func(cs *csrf) {
cs.opts.MaxAge = age
}
}
// Domain sets the cookie domain. Defaults to the current domain of the request
// only (recommended).
//
// This should be a hostname and not a URL. If set, the domain is treated as
// being prefixed with a '.' - e.g. "example.com" becomes ".example.com" and
// matches "www.example.com" and "secure.example.com".
func Domain(domain string) Option {
return func(cs *csrf) {
cs.opts.Domain = domain
}
}
// Path sets the cookie path. Defaults to the path the cookie was issued from
// (recommended).
//
// This instructs clients to only respond with cookie for that path and its
// subpaths - i.e. a cookie issued from "/register" would be included in requests
// to "/register/step2" and "/register/submit".
func Path(p string) Option {
return func(cs *csrf) {
cs.opts.Path = p
}
}
// Secure sets the 'Secure' flag on the cookie. Defaults to true (recommended).
// Set this to 'false' in your development environment otherwise the cookie won't
// be sent over an insecure channel. Setting this via the presence of a 'DEV'
// environmental variable is a good way of making sure this won't make it to a
// production environment.
func Secure(s bool) Option {
return func(cs *csrf) {
cs.opts.Secure = s
}
}
// HttpOnly sets the 'HttpOnly' flag on the cookie. Defaults to true (recommended).
func HttpOnly(h bool) Option {
return func(cs *csrf) {
// Note that the function and field names match the case of the
// related http.Cookie field instead of the "correct" HTTPOnly name
// that golint suggests.
cs.opts.HttpOnly = h
}
}
// SameSite sets the cookie SameSite attribute. Defaults to blank to maintain
// backwards compatibility, however, Strict is recommended.
//
// SameSite(SameSiteStrictMode) will prevent the cookie from being sent by the
// browser to the target site in all cross-site browsing context, even when
// following a regular link (GET request).
//
// SameSite(SameSiteLaxMode) provides a reasonable balance between security and
// usability for websites that want to maintain user's logged-in session after
// the user arrives from an external link. The session cookie would be allowed
// when following a regular link from an external website while blocking it in
// CSRF-prone request methods (e.g. POST).
//
// This option is only available for go 1.11+.
func SameSite(s SameSiteMode) Option {
return func(cs *csrf) {
cs.opts.SameSite = s
}
}
// ErrorHandler allows you to change the handler called when CSRF request
// processing encounters an invalid token or request. A typical use would be to
// provide a handler that returns a static HTML file with a HTTP 403 status. By
// default a HTTP 403 status and a plain text CSRF failure reason are served.
//
// Note that a custom error handler can also access the csrf.FailureReason(r)
// function to retrieve the CSRF validation reason from the request context.
func ErrorHandler(h http.Handler) Option {
return func(cs *csrf) {
cs.opts.ErrorHandler = h
}
}
// RequestHeader allows you to change the request header the CSRF middleware
// inspects. The default is X-CSRF-Token.
func RequestHeader(header string) Option {
return func(cs *csrf) {
cs.opts.RequestHeader = header
}
}
// FieldName allows you to change the name attribute of the hidden <input> field
// inspected by this package. The default is 'gorilla.csrf.Token'.
func FieldName(name string) Option {
return func(cs *csrf) {
cs.opts.FieldName = name
}
}
// CookieName changes the name of the CSRF cookie issued to clients.
//
// Note that cookie names should not contain whitespace, commas, semicolons,
// backslashes or control characters as per RFC6265.
func CookieName(name string) Option {
return func(cs *csrf) {
cs.opts.CookieName = name
}
}
// TrustedOrigins configures a set of origins (Referers) that are considered as trusted.
// This will allow cross-domain CSRF use-cases - e.g. where the front-end is served
// from a different domain than the API server - to correctly pass a CSRF check.
//
// You should only provide origins you own or have full control over.
func TrustedOrigins(origins []string) Option {
return func(cs *csrf) {
cs.opts.TrustedOrigins = origins
}
}
// setStore sets the store used by the CSRF middleware.
// Note: this is private (for now) to allow for internal API changes.
func setStore(s store) Option {
return func(cs *csrf) {
cs.st = s
}
}
// parseOptions parses the supplied options functions and returns a configured
// csrf handler.
func parseOptions(h http.Handler, opts ...Option) *csrf {
// Set the handler to call after processing.
cs := &csrf{
h: h,
}
// Default to true. See Secure & HttpOnly function comments for rationale.
// Set here to allow package users to override the default.
cs.opts.Secure = true
cs.opts.HttpOnly = true
// Default to blank to maintain backwards compatibility
cs.opts.SameSite = SameSiteDefaultMode
// Default; only override this if the package user explicitly calls MaxAge(0)
cs.opts.MaxAge = defaultAge
// Range over each options function and apply it
// to our csrf type to configure it. Options functions are
// applied in order, with any conflicting options overriding
// earlier calls.
for _, option := range opts {
option(cs)
}
return cs
}

86
src/vendor/github.com/gorilla/csrf/store.go generated vendored Normal file
View File

@ -0,0 +1,86 @@
// +build go1.11
package csrf
import (
"net/http"
"time"
"github.com/gorilla/securecookie"
)
// store represents the session storage used for CSRF tokens.
type store interface {
// Get returns the real CSRF token from the store.
Get(*http.Request) ([]byte, error)
// Save stores the real CSRF token in the store and writes a
// cookie to the http.ResponseWriter.
// For non-cookie stores, the cookie should contain a unique (256 bit) ID
// or key that references the token in the backend store.
// csrf.GenerateRandomBytes is a helper function for generating secure IDs.
Save(token []byte, w http.ResponseWriter) error
}
// cookieStore is a signed cookie session store for CSRF tokens.
type cookieStore struct {
name string
maxAge int
secure bool
httpOnly bool
path string
domain string
sc *securecookie.SecureCookie
sameSite SameSiteMode
}
// Get retrieves a CSRF token from the session cookie. It returns an empty token
// if decoding fails (e.g. HMAC validation fails or the named cookie doesn't exist).
func (cs *cookieStore) Get(r *http.Request) ([]byte, error) {
// Retrieve the cookie from the request
cookie, err := r.Cookie(cs.name)
if err != nil {
return nil, err
}
token := make([]byte, tokenLength)
// Decode the HMAC authenticated cookie.
err = cs.sc.Decode(cs.name, cookie.Value, &token)
if err != nil {
return nil, err
}
return token, nil
}
// Save stores the CSRF token in the session cookie.
func (cs *cookieStore) Save(token []byte, w http.ResponseWriter) error {
// Generate an encoded cookie value with the CSRF token.
encoded, err := cs.sc.Encode(cs.name, token)
if err != nil {
return err
}
cookie := &http.Cookie{
Name: cs.name,
Value: encoded,
MaxAge: cs.maxAge,
HttpOnly: cs.httpOnly,
Secure: cs.secure,
SameSite: http.SameSite(cs.sameSite),
Path: cs.path,
Domain: cs.domain,
}
// Set the Expires field on the cookie based on the MaxAge
// If MaxAge <= 0, we don't set the Expires attribute, making the cookie
// session-only.
if cs.maxAge > 0 {
cookie.Expires = time.Now().Add(
time.Duration(cs.maxAge) * time.Second)
}
// Write the authenticated cookie to the response.
http.SetCookie(w, cookie)
return nil
}

86
src/vendor/github.com/gorilla/csrf/store_legacy.go generated vendored Normal file
View File

@ -0,0 +1,86 @@
// +build !go1.11
// file for compatibility with go versions prior to 1.11
package csrf
import (
"net/http"
"time"
"github.com/gorilla/securecookie"
)
// store represents the session storage used for CSRF tokens.
type store interface {
// Get returns the real CSRF token from the store.
Get(*http.Request) ([]byte, error)
// Save stores the real CSRF token in the store and writes a
// cookie to the http.ResponseWriter.
// For non-cookie stores, the cookie should contain a unique (256 bit) ID
// or key that references the token in the backend store.
// csrf.GenerateRandomBytes is a helper function for generating secure IDs.
Save(token []byte, w http.ResponseWriter) error
}
// cookieStore is a signed cookie session store for CSRF tokens.
type cookieStore struct {
name string
maxAge int
secure bool
httpOnly bool
path string
domain string
sc *securecookie.SecureCookie
sameSite SameSiteMode
}
// Get retrieves a CSRF token from the session cookie. It returns an empty token
// if decoding fails (e.g. HMAC validation fails or the named cookie doesn't exist).
func (cs *cookieStore) Get(r *http.Request) ([]byte, error) {
// Retrieve the cookie from the request
cookie, err := r.Cookie(cs.name)
if err != nil {
return nil, err
}
token := make([]byte, tokenLength)
// Decode the HMAC authenticated cookie.
err = cs.sc.Decode(cs.name, cookie.Value, &token)
if err != nil {
return nil, err
}
return token, nil
}
// Save stores the CSRF token in the session cookie.
func (cs *cookieStore) Save(token []byte, w http.ResponseWriter) error {
// Generate an encoded cookie value with the CSRF token.
encoded, err := cs.sc.Encode(cs.name, token)
if err != nil {
return err
}
cookie := &http.Cookie{
Name: cs.name,
Value: encoded,
MaxAge: cs.maxAge,
HttpOnly: cs.httpOnly,
Secure: cs.secure,
Path: cs.path,
Domain: cs.domain,
}
// Set the Expires field on the cookie based on the MaxAge
// If MaxAge <= 0, we don't set the Expires attribute, making the cookie
// session-only.
if cs.maxAge > 0 {
cookie.Expires = time.Now().Add(
time.Duration(cs.maxAge) * time.Second)
}
// Write the authenticated cookie to the response.
http.SetCookie(w, cookie)
return nil
}

19
src/vendor/github.com/gorilla/securecookie/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,19 @@
language: go
sudo: false
matrix:
include:
- go: 1.3
- go: 1.4
- go: 1.5
- go: 1.6
- go: 1.7
- go: tip
allow_failures:
- go: tip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go vet $(go list ./... | grep -v /vendor/)
- go test -v -race ./...

27
src/vendor/github.com/gorilla/securecookie/LICENSE generated vendored Normal file
View File

@ -0,0 +1,27 @@
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

80
src/vendor/github.com/gorilla/securecookie/README.md generated vendored Normal file
View File

@ -0,0 +1,80 @@
securecookie
============
[![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/securecookie/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/securecookie?badge)
securecookie encodes and decodes authenticated and optionally encrypted
cookie values.
Secure cookies can't be forged, because their values are validated using HMAC.
When encrypted, the content is also inaccessible to malicious eyes. It is still
recommended that sensitive data not be stored in cookies, and that HTTPS be used
to prevent cookie [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
## Examples
To use it, first create a new SecureCookie instance:
```go
// Hash keys should be at least 32 bytes long
var hashKey = []byte("very-secret")
// Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
// Shorter keys may weaken the encryption used.
var blockKey = []byte("a-lot-secret")
var s = securecookie.New(hashKey, blockKey)
```
The hashKey is required, used to authenticate the cookie value using HMAC.
It is recommended to use a key with 32 or 64 bytes.
The blockKey is optional, used to encrypt the cookie value -- set it to nil
to not use encryption. If set, the length must correspond to the block size
of the encryption algorithm. For AES, used by default, valid lengths are
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
Strong keys can be created using the convenience function GenerateRandomKey().
Once a SecureCookie instance is set, use it to encode a cookie value:
```go
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
value := map[string]string{
"foo": "bar",
}
if encoded, err := s.Encode("cookie-name", value); err == nil {
cookie := &http.Cookie{
Name: "cookie-name",
Value: encoded,
Path: "/",
Secure: true,
HttpOnly: true,
}
http.SetCookie(w, cookie)
}
}
```
Later, use the same SecureCookie instance to decode and validate a cookie
value:
```go
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err == nil {
value := make(map[string]string)
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
}
}
}
```
We stored a map[string]string, but secure cookies can hold any value that
can be encoded using `encoding/gob`. To store custom types, they must be
registered first using gob.Register(). For basic types this is not needed;
it works out of the box. An optional JSON encoder that uses `encoding/json` is
available for types compatible with JSON.
## License
BSD licensed. See the LICENSE file for details.

61
src/vendor/github.com/gorilla/securecookie/doc.go generated vendored Normal file
View File

@ -0,0 +1,61 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
/*
Package securecookie encodes and decodes authenticated and optionally
encrypted cookie values.
Secure cookies can't be forged, because their values are validated using HMAC.
When encrypted, the content is also inaccessible to malicious eyes.
To use it, first create a new SecureCookie instance:
var hashKey = []byte("very-secret")
var blockKey = []byte("a-lot-secret")
var s = securecookie.New(hashKey, blockKey)
The hashKey is required, used to authenticate the cookie value using HMAC.
It is recommended to use a key with 32 or 64 bytes.
The blockKey is optional, used to encrypt the cookie value -- set it to nil
to not use encryption. If set, the length must correspond to the block size
of the encryption algorithm. For AES, used by default, valid lengths are
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
Strong keys can be created using the convenience function GenerateRandomKey().
Once a SecureCookie instance is set, use it to encode a cookie value:
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
value := map[string]string{
"foo": "bar",
}
if encoded, err := s.Encode("cookie-name", value); err == nil {
cookie := &http.Cookie{
Name: "cookie-name",
Value: encoded,
Path: "/",
}
http.SetCookie(w, cookie)
}
}
Later, use the same SecureCookie instance to decode and validate a cookie
value:
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err == nil {
value := make(map[string]string)
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
}
}
}
We stored a map[string]string, but secure cookies can hold any value that
can be encoded using encoding/gob. To store custom types, they must be
registered first using gob.Register(). For basic types this is not needed;
it works out of the box.
*/
package securecookie

25
src/vendor/github.com/gorilla/securecookie/fuzz.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
// +build gofuzz
package securecookie
var hashKey = []byte("very-secret12345")
var blockKey = []byte("a-lot-secret1234")
var s = New(hashKey, blockKey)
type Cookie struct {
B bool
I int
S string
}
func Fuzz(data []byte) int {
datas := string(data)
var c Cookie
if err := s.Decode("fuzz", datas, &c); err != nil {
return 0
}
if _, err := s.Encode("fuzz", c); err != nil {
panic(err)
}
return 1
}

View File

@ -0,0 +1,646 @@
// Copyright 2012 The Gorilla Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package securecookie
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"crypto/subtle"
"encoding/base64"
"encoding/gob"
"encoding/json"
"fmt"
"hash"
"io"
"strconv"
"strings"
"time"
)
// Error is the interface of all errors returned by functions in this library.
type Error interface {
error
// IsUsage returns true for errors indicating the client code probably
// uses this library incorrectly. For example, the client may have
// failed to provide a valid hash key, or may have failed to configure
// the Serializer adequately for encoding value.
IsUsage() bool
// IsDecode returns true for errors indicating that a cookie could not
// be decoded and validated. Since cookies are usually untrusted
// user-provided input, errors of this type should be expected.
// Usually, the proper action is simply to reject the request.
IsDecode() bool
// IsInternal returns true for unexpected errors occurring in the
// securecookie implementation.
IsInternal() bool
// Cause, if it returns a non-nil value, indicates that this error was
// propagated from some underlying library. If this method returns nil,
// this error was raised directly by this library.
//
// Cause is provided principally for debugging/logging purposes; it is
// rare that application logic should perform meaningfully different
// logic based on Cause. See, for example, the caveats described on
// (MultiError).Cause().
Cause() error
}
// errorType is a bitmask giving the error type(s) of an cookieError value.
type errorType int
const (
usageError = errorType(1 << iota)
decodeError
internalError
)
type cookieError struct {
typ errorType
msg string
cause error
}
func (e cookieError) IsUsage() bool { return (e.typ & usageError) != 0 }
func (e cookieError) IsDecode() bool { return (e.typ & decodeError) != 0 }
func (e cookieError) IsInternal() bool { return (e.typ & internalError) != 0 }
func (e cookieError) Cause() error { return e.cause }
func (e cookieError) Error() string {
parts := []string{"securecookie: "}
if e.msg == "" {
parts = append(parts, "error")
} else {
parts = append(parts, e.msg)
}
if c := e.Cause(); c != nil {
parts = append(parts, " - caused by: ", c.Error())
}
return strings.Join(parts, "")
}
var (
errGeneratingIV = cookieError{typ: internalError, msg: "failed to generate random iv"}
errNoCodecs = cookieError{typ: usageError, msg: "no codecs provided"}
errHashKeyNotSet = cookieError{typ: usageError, msg: "hash key is not set"}
errBlockKeyNotSet = cookieError{typ: usageError, msg: "block key is not set"}
errEncodedValueTooLong = cookieError{typ: usageError, msg: "the value is too long"}
errValueToDecodeTooLong = cookieError{typ: decodeError, msg: "the value is too long"}
errTimestampInvalid = cookieError{typ: decodeError, msg: "invalid timestamp"}
errTimestampTooNew = cookieError{typ: decodeError, msg: "timestamp is too new"}
errTimestampExpired = cookieError{typ: decodeError, msg: "expired timestamp"}
errDecryptionFailed = cookieError{typ: decodeError, msg: "the value could not be decrypted"}
errValueNotByte = cookieError{typ: decodeError, msg: "value not a []byte."}
errValueNotBytePtr = cookieError{typ: decodeError, msg: "value not a pointer to []byte."}
// ErrMacInvalid indicates that cookie decoding failed because the HMAC
// could not be extracted and verified. Direct use of this error
// variable is deprecated; it is public only for legacy compatibility,
// and may be privatized in the future, as it is rarely useful to
// distinguish between this error and other Error implementations.
ErrMacInvalid = cookieError{typ: decodeError, msg: "the value is not valid"}
)
// Codec defines an interface to encode and decode cookie values.
type Codec interface {
Encode(name string, value interface{}) (string, error)
Decode(name, value string, dst interface{}) error
}
// New returns a new SecureCookie.
//
// hashKey is required, used to authenticate values using HMAC. Create it using
// GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes.
//
// blockKey is optional, used to encrypt values. Create it using
// GenerateRandomKey(). The key length must correspond to the block size
// of the encryption algorithm. For AES, used by default, valid lengths are
// 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
// The default encoder used for cookie serialization is encoding/gob.
//
// Note that keys created using GenerateRandomKey() are not automatically
// persisted. New keys will be created when the application is restarted, and
// previously issued cookies will not be able to be decoded.
func New(hashKey, blockKey []byte) *SecureCookie {
s := &SecureCookie{
hashKey: hashKey,
blockKey: blockKey,
hashFunc: sha256.New,
maxAge: 86400 * 30,
maxLength: 4096,
sz: GobEncoder{},
}
if hashKey == nil {
s.err = errHashKeyNotSet
}
if blockKey != nil {
s.BlockFunc(aes.NewCipher)
}
return s
}
// SecureCookie encodes and decodes authenticated and optionally encrypted
// cookie values.
type SecureCookie struct {
hashKey []byte
hashFunc func() hash.Hash
blockKey []byte
block cipher.Block
maxLength int
maxAge int64
minAge int64
err error
sz Serializer
// For testing purposes, the function that returns the current timestamp.
// If not set, it will use time.Now().UTC().Unix().
timeFunc func() int64
}
// Serializer provides an interface for providing custom serializers for cookie
// values.
type Serializer interface {
Serialize(src interface{}) ([]byte, error)
Deserialize(src []byte, dst interface{}) error
}
// GobEncoder encodes cookie values using encoding/gob. This is the simplest
// encoder and can handle complex types via gob.Register.
type GobEncoder struct{}
// JSONEncoder encodes cookie values using encoding/json. Users who wish to
// encode complex types need to satisfy the json.Marshaller and
// json.Unmarshaller interfaces.
type JSONEncoder struct{}
// NopEncoder does not encode cookie values, and instead simply accepts a []byte
// (as an interface{}) and returns a []byte. This is particularly useful when
// you encoding an object upstream and do not wish to re-encode it.
type NopEncoder struct{}
// MaxLength restricts the maximum length, in bytes, for the cookie value.
//
// Default is 4096, which is the maximum value accepted by Internet Explorer.
func (s *SecureCookie) MaxLength(value int) *SecureCookie {
s.maxLength = value
return s
}
// MaxAge restricts the maximum age, in seconds, for the cookie value.
//
// Default is 86400 * 30. Set it to 0 for no restriction.
func (s *SecureCookie) MaxAge(value int) *SecureCookie {
s.maxAge = int64(value)
return s
}
// MinAge restricts the minimum age, in seconds, for the cookie value.
//
// Default is 0 (no restriction).
func (s *SecureCookie) MinAge(value int) *SecureCookie {
s.minAge = int64(value)
return s
}
// HashFunc sets the hash function used to create HMAC.
//
// Default is crypto/sha256.New.
func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie {
s.hashFunc = f
return s
}
// BlockFunc sets the encryption function used to create a cipher.Block.
//
// Default is crypto/aes.New.
func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie {
if s.blockKey == nil {
s.err = errBlockKeyNotSet
} else if block, err := f(s.blockKey); err == nil {
s.block = block
} else {
s.err = cookieError{cause: err, typ: usageError}
}
return s
}
// Encoding sets the encoding/serialization method for cookies.
//
// Default is encoding/gob. To encode special structures using encoding/gob,
// they must be registered first using gob.Register().
func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie {
s.sz = sz
return s
}
// Encode encodes a cookie value.
//
// It serializes, optionally encrypts, signs with a message authentication code,
// and finally encodes the value.
//
// The name argument is the cookie name. It is stored with the encoded value.
// The value argument is the value to be encoded. It can be any value that can
// be encoded using the currently selected serializer; see SetSerializer().
//
// It is the client's responsibility to ensure that value, when encoded using
// the current serialization/encryption settings on s and then base64-encoded,
// is shorter than the maximum permissible length.
func (s *SecureCookie) Encode(name string, value interface{}) (string, error) {
if s.err != nil {
return "", s.err
}
if s.hashKey == nil {
s.err = errHashKeyNotSet
return "", s.err
}
var err error
var b []byte
// 1. Serialize.
if b, err = s.sz.Serialize(value); err != nil {
return "", cookieError{cause: err, typ: usageError}
}
// 2. Encrypt (optional).
if s.block != nil {
if b, err = encrypt(s.block, b); err != nil {
return "", cookieError{cause: err, typ: usageError}
}
}
b = encode(b)
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
b = []byte(fmt.Sprintf("%s|%d|%s|", name, s.timestamp(), b))
mac := createMac(hmac.New(s.hashFunc, s.hashKey), b[:len(b)-1])
// Append mac, remove name.
b = append(b, mac...)[len(name)+1:]
// 4. Encode to base64.
b = encode(b)
// 5. Check length.
if s.maxLength != 0 && len(b) > s.maxLength {
return "", errEncodedValueTooLong
}
// Done.
return string(b), nil
}
// Decode decodes a cookie value.
//
// It decodes, verifies a message authentication code, optionally decrypts and
// finally deserializes the value.
//
// The name argument is the cookie name. It must be the same name used when
// it was stored. The value argument is the encoded cookie value. The dst
// argument is where the cookie will be decoded. It must be a pointer.
func (s *SecureCookie) Decode(name, value string, dst interface{}) error {
if s.err != nil {
return s.err
}
if s.hashKey == nil {
s.err = errHashKeyNotSet
return s.err
}
// 1. Check length.
if s.maxLength != 0 && len(value) > s.maxLength {
return errValueToDecodeTooLong
}
// 2. Decode from base64.
b, err := decode([]byte(value))
if err != nil {
return err
}
// 3. Verify MAC. Value is "date|value|mac".
parts := bytes.SplitN(b, []byte("|"), 3)
if len(parts) != 3 {
return ErrMacInvalid
}
h := hmac.New(s.hashFunc, s.hashKey)
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])-1]...)
if err = verifyMac(h, b, parts[2]); err != nil {
return err
}
// 4. Verify date ranges.
var t1 int64
if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
return errTimestampInvalid
}
t2 := s.timestamp()
if s.minAge != 0 && t1 > t2-s.minAge {
return errTimestampTooNew
}
if s.maxAge != 0 && t1 < t2-s.maxAge {
return errTimestampExpired
}
// 5. Decrypt (optional).
b, err = decode(parts[1])
if err != nil {
return err
}
if s.block != nil {
if b, err = decrypt(s.block, b); err != nil {
return err
}
}
// 6. Deserialize.
if err = s.sz.Deserialize(b, dst); err != nil {
return cookieError{cause: err, typ: decodeError}
}
// Done.
return nil
}
// timestamp returns the current timestamp, in seconds.
//
// For testing purposes, the function that generates the timestamp can be
// overridden. If not set, it will return time.Now().UTC().Unix().
func (s *SecureCookie) timestamp() int64 {
if s.timeFunc == nil {
return time.Now().UTC().Unix()
}
return s.timeFunc()
}
// Authentication -------------------------------------------------------------
// createMac creates a message authentication code (MAC).
func createMac(h hash.Hash, value []byte) []byte {
h.Write(value)
return h.Sum(nil)
}
// verifyMac verifies that a message authentication code (MAC) is valid.
func verifyMac(h hash.Hash, value []byte, mac []byte) error {
mac2 := createMac(h, value)
// Check that both MACs are of equal length, as subtle.ConstantTimeCompare
// does not do this prior to Go 1.4.
if len(mac) == len(mac2) && subtle.ConstantTimeCompare(mac, mac2) == 1 {
return nil
}
return ErrMacInvalid
}
// Encryption -----------------------------------------------------------------
// encrypt encrypts a value using the given block in counter mode.
//
// A random initialization vector (http://goo.gl/zF67k) with the length of the
// block size is prepended to the resulting ciphertext.
func encrypt(block cipher.Block, value []byte) ([]byte, error) {
iv := GenerateRandomKey(block.BlockSize())
if iv == nil {
return nil, errGeneratingIV
}
// Encrypt it.
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(value, value)
// Return iv + ciphertext.
return append(iv, value...), nil
}
// decrypt decrypts a value using the given block in counter mode.
//
// The value to be decrypted must be prepended by a initialization vector
// (http://goo.gl/zF67k) with the length of the block size.
func decrypt(block cipher.Block, value []byte) ([]byte, error) {
size := block.BlockSize()
if len(value) > size {
// Extract iv.
iv := value[:size]
// Extract ciphertext.
value = value[size:]
// Decrypt it.
stream := cipher.NewCTR(block, iv)
stream.XORKeyStream(value, value)
return value, nil
}
return nil, errDecryptionFailed
}
// Serialization --------------------------------------------------------------
// Serialize encodes a value using gob.
func (e GobEncoder) Serialize(src interface{}) ([]byte, error) {
buf := new(bytes.Buffer)
enc := gob.NewEncoder(buf)
if err := enc.Encode(src); err != nil {
return nil, cookieError{cause: err, typ: usageError}
}
return buf.Bytes(), nil
}
// Deserialize decodes a value using gob.
func (e GobEncoder) Deserialize(src []byte, dst interface{}) error {
dec := gob.NewDecoder(bytes.NewBuffer(src))
if err := dec.Decode(dst); err != nil {
return cookieError{cause: err, typ: decodeError}
}
return nil
}
// Serialize encodes a value using encoding/json.
func (e JSONEncoder) Serialize(src interface{}) ([]byte, error) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
if err := enc.Encode(src); err != nil {
return nil, cookieError{cause: err, typ: usageError}
}
return buf.Bytes(), nil
}
// Deserialize decodes a value using encoding/json.
func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error {
dec := json.NewDecoder(bytes.NewReader(src))
if err := dec.Decode(dst); err != nil {
return cookieError{cause: err, typ: decodeError}
}
return nil
}
// Serialize passes a []byte through as-is.
func (e NopEncoder) Serialize(src interface{}) ([]byte, error) {
if b, ok := src.([]byte); ok {
return b, nil
}
return nil, errValueNotByte
}
// Deserialize passes a []byte through as-is.
func (e NopEncoder) Deserialize(src []byte, dst interface{}) error {
if dat, ok := dst.(*[]byte); ok {
*dat = src
return nil
}
return errValueNotBytePtr
}
// Encoding -------------------------------------------------------------------
// encode encodes a value using base64.
func encode(value []byte) []byte {
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
base64.URLEncoding.Encode(encoded, value)
return encoded
}
// decode decodes a cookie using base64.
func decode(value []byte) ([]byte, error) {
decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
b, err := base64.URLEncoding.Decode(decoded, value)
if err != nil {
return nil, cookieError{cause: err, typ: decodeError, msg: "base64 decode failed"}
}
return decoded[:b], nil
}
// Helpers --------------------------------------------------------------------
// GenerateRandomKey creates a random key with the given length in bytes.
// On failure, returns nil.
//
// Callers should explicitly check for the possibility of a nil return, treat
// it as a failure of the system random number generator, and not continue.
func GenerateRandomKey(length int) []byte {
k := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, k); err != nil {
return nil
}
return k
}
// CodecsFromPairs returns a slice of SecureCookie instances.
//
// It is a convenience function to create a list of codecs for key rotation. Note
// that the generated Codecs will have the default options applied: callers
// should iterate over each Codec and type-assert the underlying *SecureCookie to
// change these.
//
// Example:
//
// codecs := securecookie.CodecsFromPairs(
// []byte("new-hash-key"),
// []byte("new-block-key"),
// []byte("old-hash-key"),
// []byte("old-block-key"),
// )
//
// // Modify each instance.
// for _, s := range codecs {
// if cookie, ok := s.(*securecookie.SecureCookie); ok {
// cookie.MaxAge(86400 * 7)
// cookie.SetSerializer(securecookie.JSONEncoder{})
// cookie.HashFunc(sha512.New512_256)
// }
// }
//
func CodecsFromPairs(keyPairs ...[]byte) []Codec {
codecs := make([]Codec, len(keyPairs)/2+len(keyPairs)%2)
for i := 0; i < len(keyPairs); i += 2 {
var blockKey []byte
if i+1 < len(keyPairs) {
blockKey = keyPairs[i+1]
}
codecs[i/2] = New(keyPairs[i], blockKey)
}
return codecs
}
// EncodeMulti encodes a cookie value using a group of codecs.
//
// The codecs are tried in order. Multiple codecs are accepted to allow
// key rotation.
//
// On error, may return a MultiError.
func EncodeMulti(name string, value interface{}, codecs ...Codec) (string, error) {
if len(codecs) == 0 {
return "", errNoCodecs
}
var errors MultiError
for _, codec := range codecs {
encoded, err := codec.Encode(name, value)
if err == nil {
return encoded, nil
}
errors = append(errors, err)
}
return "", errors
}
// DecodeMulti decodes a cookie value using a group of codecs.
//
// The codecs are tried in order. Multiple codecs are accepted to allow
// key rotation.
//
// On error, may return a MultiError.
func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error {
if len(codecs) == 0 {
return errNoCodecs
}
var errors MultiError
for _, codec := range codecs {
err := codec.Decode(name, value, dst)
if err == nil {
return nil
}
errors = append(errors, err)
}
return errors
}
// MultiError groups multiple errors.
type MultiError []error
func (m MultiError) IsUsage() bool { return m.any(func(e Error) bool { return e.IsUsage() }) }
func (m MultiError) IsDecode() bool { return m.any(func(e Error) bool { return e.IsDecode() }) }
func (m MultiError) IsInternal() bool { return m.any(func(e Error) bool { return e.IsInternal() }) }
// Cause returns nil for MultiError; there is no unique underlying cause in the
// general case.
//
// Note: we could conceivably return a non-nil Cause only when there is exactly
// one child error with a Cause. However, it would be brittle for client code
// to rely on the arity of causes inside a MultiError, so we have opted not to
// provide this functionality. Clients which really wish to access the Causes
// of the underlying errors are free to iterate through the errors themselves.
func (m MultiError) Cause() error { return nil }
func (m MultiError) Error() string {
s, n := "", 0
for _, e := range m {
if e != nil {
if n == 0 {
s = e.Error()
}
n++
}
}
switch n {
case 0:
return "(0 errors)"
case 1:
return s
case 2:
return s + " (and 1 other error)"
}
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
}
// any returns true if any element of m is an Error for which pred returns true.
func (m MultiError) any(pred func(Error) bool) bool {
for _, e := range m {
if ourErr, ok := e.(Error); ok && pred(ourErr) {
return true
}
}
return false
}

View File

@ -189,10 +189,14 @@ github.com/google/go-querystring/query
github.com/google/gofuzz
# github.com/google/uuid v1.1.1
github.com/google/uuid
# github.com/gorilla/csrf v1.6.2
github.com/gorilla/csrf
# github.com/gorilla/handlers v1.3.0
github.com/gorilla/handlers
# github.com/gorilla/mux v1.7.2
github.com/gorilla/mux
# github.com/gorilla/securecookie v1.1.1
github.com/gorilla/securecookie
# github.com/graph-gophers/dataloader v5.0.0+incompatible
github.com/graph-gophers/dataloader
# github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af