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 }