diff options
Diffstat (limited to 'services/context/xsrf.go')
-rw-r--r-- | services/context/xsrf.go | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/services/context/xsrf.go b/services/context/xsrf.go new file mode 100644 index 0000000..15e36d1 --- /dev/null +++ b/services/context/xsrf.go @@ -0,0 +1,99 @@ +// Copyright 2012 Google Inc. All Rights Reserved. +// Copyright 2014 The Macaron Authors +// Copyright 2020 The Gitea Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// SPDX-License-Identifier: Apache-2.0 + +package context + +import ( + "bytes" + "crypto/hmac" + "crypto/sha1" + "crypto/subtle" + "encoding/base64" + "fmt" + "strconv" + "strings" + "time" +) + +// CsrfTokenTimeout represents the duration that XSRF tokens are valid. +// It is exported so clients may set cookie timeouts that match generated tokens. +const CsrfTokenTimeout = 24 * time.Hour + +// CsrfTokenRegenerationInterval is the interval between token generations, old tokens are still valid before CsrfTokenTimeout +var CsrfTokenRegenerationInterval = 10 * time.Minute + +var csrfTokenSep = []byte(":") + +// GenerateCsrfToken returns a URL-safe secure XSRF token that expires in CsrfTokenTimeout hours. +// key is a secret key for your application. +// userID is a unique identifier for the user. +// actionID is the action the user is taking (e.g. POSTing to a particular path). +func GenerateCsrfToken(key, userID, actionID string, now time.Time) string { + nowUnixNano := now.UnixNano() + nowUnixNanoStr := strconv.FormatInt(nowUnixNano, 10) + h := hmac.New(sha1.New, []byte(key)) + h.Write([]byte(strings.ReplaceAll(userID, ":", "_"))) + h.Write(csrfTokenSep) + h.Write([]byte(strings.ReplaceAll(actionID, ":", "_"))) + h.Write(csrfTokenSep) + h.Write([]byte(nowUnixNanoStr)) + tok := fmt.Sprintf("%s:%s", h.Sum(nil), nowUnixNanoStr) + return base64.RawURLEncoding.EncodeToString([]byte(tok)) +} + +func ParseCsrfToken(token string) (issueTime time.Time, ok bool) { + data, err := base64.RawURLEncoding.DecodeString(token) + if err != nil { + return time.Time{}, false + } + + pos := bytes.LastIndex(data, csrfTokenSep) + if pos == -1 { + return time.Time{}, false + } + nanos, err := strconv.ParseInt(string(data[pos+1:]), 10, 64) + if err != nil { + return time.Time{}, false + } + return time.Unix(0, nanos), true +} + +// ValidCsrfToken returns true if token is a valid and unexpired token returned by Generate. +func ValidCsrfToken(token, key, userID, actionID string, now time.Time) bool { + issueTime, ok := ParseCsrfToken(token) + if !ok { + return false + } + + // Check that the token is not expired. + if now.Sub(issueTime) >= CsrfTokenTimeout { + return false + } + + // Check that the token is not from the future. + // Allow 1-minute grace period in case the token is being verified on a + // machine whose clock is behind the machine that issued the token. + if issueTime.After(now.Add(1 * time.Minute)) { + return false + } + + expected := GenerateCsrfToken(key, userID, actionID, issueTime) + + // Check that the token matches the expected value. + // Use constant time comparison to avoid timing attacks. + return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1 +} |