code.gitea.io/gitea@v1.19.3/modules/context/xsrf.go (about)

     1  // Copyright 2012 Google Inc. All Rights Reserved.
     2  // Copyright 2014 The Macaron Authors
     3  // Copyright 2020 The Gitea Authors
     4  //
     5  // Licensed under the Apache License, Version 2.0 (the "License");
     6  // you may not use this file except in compliance with the License.
     7  // You may obtain a copy of the License at
     8  //
     9  //     http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  // Unless required by applicable law or agreed to in writing, software
    12  // distributed under the License is distributed on an "AS IS" BASIS,
    13  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  // See the License for the specific language governing permissions and
    15  // limitations under the License.
    16  // SPDX-License-Identifier: Apache-2.0
    17  
    18  package context
    19  
    20  import (
    21  	"bytes"
    22  	"crypto/hmac"
    23  	"crypto/sha1"
    24  	"crypto/subtle"
    25  	"encoding/base64"
    26  	"fmt"
    27  	"strconv"
    28  	"strings"
    29  	"time"
    30  )
    31  
    32  // CsrfTokenTimeout represents the duration that XSRF tokens are valid.
    33  // It is exported so clients may set cookie timeouts that match generated tokens.
    34  const CsrfTokenTimeout = 24 * time.Hour
    35  
    36  // CsrfTokenRegenerationInterval is the interval between token generations, old tokens are still valid before CsrfTokenTimeout
    37  var CsrfTokenRegenerationInterval = 10 * time.Minute
    38  
    39  var csrfTokenSep = []byte(":")
    40  
    41  // GenerateCsrfToken returns a URL-safe secure XSRF token that expires in CsrfTokenTimeout hours.
    42  // key is a secret key for your application.
    43  // userID is a unique identifier for the user.
    44  // actionID is the action the user is taking (e.g. POSTing to a particular path).
    45  func GenerateCsrfToken(key, userID, actionID string, now time.Time) string {
    46  	nowUnixNano := now.UnixNano()
    47  	nowUnixNanoStr := strconv.FormatInt(nowUnixNano, 10)
    48  	h := hmac.New(sha1.New, []byte(key))
    49  	h.Write([]byte(strings.ReplaceAll(userID, ":", "_")))
    50  	h.Write(csrfTokenSep)
    51  	h.Write([]byte(strings.ReplaceAll(actionID, ":", "_")))
    52  	h.Write(csrfTokenSep)
    53  	h.Write([]byte(nowUnixNanoStr))
    54  	tok := fmt.Sprintf("%s:%s", h.Sum(nil), nowUnixNanoStr)
    55  	return base64.RawURLEncoding.EncodeToString([]byte(tok))
    56  }
    57  
    58  func ParseCsrfToken(token string) (issueTime time.Time, ok bool) {
    59  	data, err := base64.RawURLEncoding.DecodeString(token)
    60  	if err != nil {
    61  		return time.Time{}, false
    62  	}
    63  
    64  	pos := bytes.LastIndex(data, csrfTokenSep)
    65  	if pos == -1 {
    66  		return time.Time{}, false
    67  	}
    68  	nanos, err := strconv.ParseInt(string(data[pos+1:]), 10, 64)
    69  	if err != nil {
    70  		return time.Time{}, false
    71  	}
    72  	return time.Unix(0, nanos), true
    73  }
    74  
    75  // ValidCsrfToken returns true if token is a valid and unexpired token returned by Generate.
    76  func ValidCsrfToken(token, key, userID, actionID string, now time.Time) bool {
    77  	issueTime, ok := ParseCsrfToken(token)
    78  	if !ok {
    79  		return false
    80  	}
    81  
    82  	// Check that the token is not expired.
    83  	if now.Sub(issueTime) >= CsrfTokenTimeout {
    84  		return false
    85  	}
    86  
    87  	// Check that the token is not from the future.
    88  	// Allow 1-minute grace period in case the token is being verified on a
    89  	// machine whose clock is behind the machine that issued the token.
    90  	if issueTime.After(now.Add(1 * time.Minute)) {
    91  		return false
    92  	}
    93  
    94  	expected := GenerateCsrfToken(key, userID, actionID, issueTime)
    95  
    96  	// Check that the token matches the expected value.
    97  	// Use constant time comparison to avoid timing attacks.
    98  	return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
    99  }