You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

94 lines
3.0 KiB

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