2016-11-04 01:16:01 +03:00
// Copyright 2012 Google Inc. All Rights Reserved.
// Copyright 2014 The Macaron Authors
2021-01-26 18:36:53 +03:00
// Copyright 2020 The Gitea Authors
2016-11-04 01:16:01 +03:00
//
// 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.
2022-12-02 17:14:57 +03:00
// SPDX-License-Identifier: Apache-2.0
2016-11-04 01:16:01 +03:00
2021-01-26 18:36:53 +03:00
package context
2016-11-04 01:16:01 +03:00
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"crypto/subtle"
"encoding/base64"
"fmt"
"strconv"
"strings"
"time"
)
2022-04-08 08:21:05 +03:00
// CsrfTokenTimeout represents the duration that XSRF tokens are valid.
2016-11-04 01:16:01 +03:00
// It is exported so clients may set cookie timeouts that match generated tokens.
2022-04-08 08:21:05 +03:00
const CsrfTokenTimeout = 24 * time . Hour
2016-11-04 01:16:01 +03:00
2022-04-08 08:21:05 +03:00
// CsrfTokenRegenerationInterval is the interval between token generations, old tokens are still valid before CsrfTokenTimeout
var CsrfTokenRegenerationInterval = 10 * time . Minute
2016-11-04 01:16:01 +03:00
2022-04-08 08:21:05 +03:00
var csrfTokenSep = [ ] byte ( ":" )
// GenerateCsrfToken returns a URL-safe secure XSRF token that expires in CsrfTokenTimeout hours.
2016-11-04 01:16:01 +03:00
// 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).
2022-04-08 08:21:05 +03:00
func GenerateCsrfToken ( key , userID , actionID string , now time . Time ) string {
nowUnixNano := now . UnixNano ( )
nowUnixNanoStr := strconv . FormatInt ( nowUnixNano , 10 )
2016-11-04 01:16:01 +03:00
h := hmac . New ( sha1 . New , [ ] byte ( key ) )
2022-04-08 08:21:05 +03:00
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 )
2019-07-12 16:57:31 +03:00
return base64 . RawURLEncoding . EncodeToString ( [ ] byte ( tok ) )
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
func ParseCsrfToken ( token string ) ( issueTime time . Time , ok bool ) {
2019-07-12 16:57:31 +03:00
data , err := base64 . RawURLEncoding . DecodeString ( token )
2016-11-04 01:16:01 +03:00
if err != nil {
2022-04-08 08:21:05 +03:00
return time . Time { } , false
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
pos := bytes . LastIndex ( data , csrfTokenSep )
if pos == - 1 {
return time . Time { } , false
2016-11-04 01:16:01 +03:00
}
2022-04-08 08:21:05 +03:00
nanos , err := strconv . ParseInt ( string ( data [ pos + 1 : ] ) , 10 , 64 )
2016-11-04 01:16:01 +03:00
if err != nil {
2022-04-08 08:21:05 +03:00
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 {
2016-11-04 01:16:01 +03:00
return false
}
// Check that the token is not expired.
2022-04-08 08:21:05 +03:00
if now . Sub ( issueTime ) >= CsrfTokenTimeout {
2016-11-04 01:16:01 +03:00
return false
}
// Check that the token is not from the future.
2022-04-08 08:21:05 +03:00
// Allow 1-minute grace period in case the token is being verified on a
2016-11-04 01:16:01 +03:00
// machine whose clock is behind the machine that issued the token.
if issueTime . After ( now . Add ( 1 * time . Minute ) ) {
return false
}
2022-04-08 08:21:05 +03:00
expected := GenerateCsrfToken ( key , userID , actionID , issueTime )
2016-11-04 01:16:01 +03:00
// Check that the token matches the expected value.
// Use constant time comparison to avoid timing attacks.
return subtle . ConstantTimeCompare ( [ ] byte ( token ) , [ ] byte ( expected ) ) == 1
}