golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/xsrftoken/xsrf.go (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package xsrftoken provides methods for generating and validating secure XSRF tokens.
     6  package xsrftoken // import "golang.org/x/net/xsrftoken"
     7  
     8  import (
     9  	"crypto/hmac"
    10  	"crypto/sha1"
    11  	"crypto/subtle"
    12  	"encoding/base64"
    13  	"fmt"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  )
    18  
    19  // Timeout is the duration for which XSRF tokens are valid.
    20  // It is exported so clients may set cookie timeouts that match generated tokens.
    21  const Timeout = 24 * time.Hour
    22  
    23  // clean sanitizes a string for inclusion in a token by replacing all ":" with "::".
    24  func clean(s string) string {
    25  	return strings.Replace(s, `:`, `::`, -1)
    26  }
    27  
    28  // Generate returns a URL-safe secure XSRF token that expires in 24 hours.
    29  //
    30  // key is a secret key for your application; it must be non-empty.
    31  // userID is an optional unique identifier for the user.
    32  // actionID is an optional action the user is taking (e.g. POSTing to a particular path).
    33  func Generate(key, userID, actionID string) string {
    34  	return generateTokenAtTime(key, userID, actionID, time.Now())
    35  }
    36  
    37  // generateTokenAtTime is like Generate, but returns a token that expires 24 hours from now.
    38  func generateTokenAtTime(key, userID, actionID string, now time.Time) string {
    39  	if len(key) == 0 {
    40  		panic("zero length xsrf secret key")
    41  	}
    42  	// Round time up and convert to milliseconds.
    43  	milliTime := (now.UnixNano() + 1e6 - 1) / 1e6
    44  
    45  	h := hmac.New(sha1.New, []byte(key))
    46  	fmt.Fprintf(h, "%s:%s:%d", clean(userID), clean(actionID), milliTime)
    47  
    48  	// Get the padded base64 string then removing the padding.
    49  	tok := string(h.Sum(nil))
    50  	tok = base64.URLEncoding.EncodeToString([]byte(tok))
    51  	tok = strings.TrimRight(tok, "=")
    52  
    53  	return fmt.Sprintf("%s:%d", tok, milliTime)
    54  }
    55  
    56  // Valid reports whether a token is a valid, unexpired token returned by Generate.
    57  // The token is considered to be expired and invalid if it is older than the default Timeout.
    58  func Valid(token, key, userID, actionID string) bool {
    59  	return validTokenAtTime(token, key, userID, actionID, time.Now(), Timeout)
    60  }
    61  
    62  // ValidFor reports whether a token is a valid, unexpired token returned by Generate.
    63  // The token is considered to be expired and invalid if it is older than the timeout duration.
    64  func ValidFor(token, key, userID, actionID string, timeout time.Duration) bool {
    65  	return validTokenAtTime(token, key, userID, actionID, time.Now(), timeout)
    66  }
    67  
    68  // validTokenAtTime reports whether a token is valid at the given time.
    69  func validTokenAtTime(token, key, userID, actionID string, now time.Time, timeout time.Duration) bool {
    70  	if len(key) == 0 {
    71  		panic("zero length xsrf secret key")
    72  	}
    73  	// Extract the issue time of the token.
    74  	sep := strings.LastIndex(token, ":")
    75  	if sep < 0 {
    76  		return false
    77  	}
    78  	millis, err := strconv.ParseInt(token[sep+1:], 10, 64)
    79  	if err != nil {
    80  		return false
    81  	}
    82  	issueTime := time.Unix(0, millis*1e6)
    83  
    84  	// Check that the token is not expired.
    85  	if now.Sub(issueTime) >= timeout {
    86  		return false
    87  	}
    88  
    89  	// Check that the token is not from the future.
    90  	// Allow 1 minute grace period in case the token is being verified on a
    91  	// machine whose clock is behind the machine that issued the token.
    92  	if issueTime.After(now.Add(1 * time.Minute)) {
    93  		return false
    94  	}
    95  
    96  	expected := generateTokenAtTime(key, userID, actionID, issueTime)
    97  
    98  	// Check that the token matches the expected value.
    99  	// Use constant time comparison to avoid timing attacks.
   100  	return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
   101  }