44package base
55
66import (
7+ "crypto/hmac"
78 "crypto/sha1"
89 "crypto/sha256"
10+ "crypto/subtle"
911 "encoding/base64"
1012 "encoding/hex"
1113 "errors"
1214 "fmt"
15+ "hash"
1316 "os"
1417 "path/filepath"
1518 "runtime"
@@ -25,13 +28,6 @@ import (
2528 "github.com/dustin/go-humanize"
2629)
2730
28- // EncodeSha1 string to sha1 hex value.
29- func EncodeSha1 (str string ) string {
30- h := sha1 .New ()
31- _ , _ = h .Write ([]byte (str ))
32- return hex .EncodeToString (h .Sum (nil ))
33- }
34-
3531// EncodeSha256 string to sha256 hex value.
3632func EncodeSha256 (str string ) string {
3733 h := sha256 .New ()
@@ -62,63 +58,65 @@ func BasicAuthDecode(encoded string) (string, string, error) {
6258}
6359
6460// VerifyTimeLimitCode verify time limit code
65- func VerifyTimeLimitCode (data string , minutes int , code string ) bool {
61+ func VerifyTimeLimitCode (now time. Time , data string , minutes int , code string ) bool {
6662 if len (code ) <= 18 {
6763 return false
6864 }
6965
7066 // split code
7167 start := code [:12 ]
72- lives := code [12 :18 ]
73- if d , err := strconv .ParseInt (lives , 10 , 0 ); err == nil {
74- minutes = int (d )
75- }
76-
77- // right active code
78- retCode := CreateTimeLimitCode (data , minutes , start )
79- if retCode == code && minutes > 0 {
80- // check time is expired or not
81- before , _ := time .ParseInLocation ("200601021504" , start , time .Local )
82- now := time .Now ()
83- if before .Add (time .Minute * time .Duration (minutes )).Unix () > now .Unix () {
84- return true
68+ // only use the passed-in "minutes", no need to parse the "alive time" in the code (code[12:18])
69+ if minutes <= 0 {
70+ return false
71+ }
72+
73+ // check code
74+ retCode := CreateTimeLimitCode (data , minutes , start , nil )
75+ if subtle .ConstantTimeCompare ([]byte (retCode ), []byte (code )) != 1 {
76+ retCode = CreateTimeLimitCode (data , minutes , start , sha1 .New ()) // TODO: this is only for the support of legacy codes, remove this in/after 1.23
77+ if subtle .ConstantTimeCompare ([]byte (retCode ), []byte (code )) != 1 {
78+ return false
8579 }
8680 }
8781
88- return false
82+ // check time is expired or not: startTime <= now && now < startTime + minutes
83+ startTime , _ := time .ParseInLocation ("200601021504" , start , time .Local )
84+ return (startTime .Before (now ) || startTime .Equal (now )) && now .Before (startTime .Add (time .Minute * time .Duration (minutes )))
8985}
9086
9187// TimeLimitCodeLength default value for time limit code
9288const TimeLimitCodeLength = 12 + 6 + 40
9389
94- // CreateTimeLimitCode create a time limit code
95- // code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
96- func CreateTimeLimitCode (data string , minutes int , startInf any ) string {
97- format := "200601021504"
90+ // CreateTimeLimitCode create a time-limited code.
91+ // Format: 12 length date time string + 6 minutes string (not used) + 40 hash string, some other code depends on this fixed length
92+ // If h is nil, then use the default hmac hash.
93+ func CreateTimeLimitCode [T time.Time | string ](data string , minutes int , startTimeGeneric T , h hash.Hash ) string {
94+ const format = "200601021504"
9895
99- var start , end time.Time
100- var startStr , endStr string
101-
102- if startInf == nil {
103- // Use now time create code
104- start = time .Now ()
105- startStr = start .Format (format )
96+ var start time.Time
97+ var startTimeAny any = startTimeGeneric
98+ if t , ok := startTimeAny .(time.Time ); ok {
99+ start = t
106100 } else {
107- // use start string create code
108- startStr = startInf .(string )
109- start , _ = time .ParseInLocation (format , startStr , time .Local )
110- startStr = start .Format (format )
101+ var err error
102+ start , err = time .ParseInLocation (format , startTimeAny .(string ), time .Local )
103+ if err != nil {
104+ return "" // return an invalid code because the "parse" failed
105+ }
111106 }
107+ startStr := start .Format (format )
108+ end := start .Add (time .Minute * time .Duration (minutes ))
112109
113- end = start .Add (time .Minute * time .Duration (minutes ))
114- endStr = end .Format (format )
115-
116- // create sha1 encode string
117- sh := sha1 .New ()
118- _ , _ = sh .Write ([]byte (fmt .Sprintf ("%s%s%s%s%d" , data , hex .EncodeToString (setting .GetGeneralTokenSigningSecret ()), startStr , endStr , minutes )))
119- encoded := hex .EncodeToString (sh .Sum (nil ))
110+ if h == nil {
111+ h = hmac .New (sha1 .New , setting .GetGeneralTokenSigningSecret ())
112+ }
113+ _ , _ = h .Write ([]byte (fmt .Sprintf ("%s%s%s%s%d" , data , hex .EncodeToString (setting .GetGeneralTokenSigningSecret ()), startStr , end .Format (format ), minutes )))
114+ encoded := hex .EncodeToString (h .Sum (nil ))
120115
121116 code := fmt .Sprintf ("%s%06d%s" , startStr , minutes , encoded )
117+ if len (code ) != TimeLimitCodeLength {
118+ panic ("there is a hard requirement for the length of time-limited code" ) // it shouldn't happen
119+ }
122120 return code
123121}
124122
0 commit comments