Skip to content

Commit c6c829f

Browse files
authored
Enhanced auth token / remember me (#27606)
Closes #27455 > The mechanism responsible for long-term authentication (the 'remember me' cookie) uses a weak construction technique. It will hash the user's hashed password and the rands value; it will then call the secure cookie code, which will encrypt the user's name with the computed hash. If one were able to dump the database, they could extract those two values to rebuild that cookie and impersonate a user. That vulnerability exists from the date the dump was obtained until a user changed their password. > > To fix this security issue, the cookie could be created and verified using a different technique such as the one explained at https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#secure-remember-me-cookies. The PR removes the now obsolete setting `COOKIE_USERNAME`.
1 parent ee6a390 commit c6c829f

File tree

23 files changed

+418
-103
lines changed

23 files changed

+418
-103
lines changed

docs/content/administration/config-cheat-sheet.en-us.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -517,7 +517,6 @@ And the following unique queues:
517517
- `SECRET_KEY`: **\<random at every install\>**: Global secret key. This key is VERY IMPORTANT, if you lost it, the data encrypted by it (like 2FA secret) can't be decrypted anymore.
518518
- `SECRET_KEY_URI`: **_empty_**: Instead of defining SECRET_KEY, this option can be used to use the key stored in a file (example value: `file:/etc/gitea/secret_key`). It shouldn't be lost like SECRET_KEY.
519519
- `LOGIN_REMEMBER_DAYS`: **7**: Cookie lifetime, in days.
520-
- `COOKIE_USERNAME`: **gitea\_awesome**: Name of the cookie used to store the current username.
521520
- `COOKIE_REMEMBER_NAME`: **gitea\_incredible**: Name of cookie used to store authentication
522521
information.
523522
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**: Header name for reverse proxy

docs/content/administration/config-cheat-sheet.zh-cn.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,6 @@ Gitea 创建以下非唯一队列:
506506
- `SECRET_KEY`: **\<每次安装时随机生成\>**:全局服务器安全密钥。这个密钥非常重要,如果丢失将无法解密加密的数据(例如 2FA)。
507507
- `SECRET_KEY_URI`: **_empty_**:与定义 `SECRET_KEY` 不同,此选项可用于使用存储在文件中的密钥(示例值:`file:/etc/gitea/secret_key`)。它不应该像 `SECRET_KEY` 一样容易丢失。
508508
- `LOGIN_REMEMBER_DAYS`: **7**:Cookie 保存时间,单位为天。
509-
- `COOKIE_USERNAME`: **gitea\_awesome**:保存用户名的 Cookie 名称。
510509
- `COOKIE_REMEMBER_NAME`: **gitea\_incredible**:保存自动登录信息的 Cookie 名称。
511510
- `REVERSE_PROXY_AUTHENTICATION_USER`: **X-WEBAUTH-USER**:反向代理认证的 HTTP 头部名称,用于提供用户信息。
512511
- `REVERSE_PROXY_AUTHENTICATION_EMAIL`: **X-WEBAUTH-EMAIL**:反向代理认证的 HTTP 头部名称,用于提供邮箱信息。
File renamed without changes.
File renamed without changes.
File renamed without changes.

models/auth/auth_token.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package auth
5+
6+
import (
7+
"context"
8+
9+
"code.gitea.io/gitea/models/db"
10+
"code.gitea.io/gitea/modules/timeutil"
11+
"code.gitea.io/gitea/modules/util"
12+
13+
"xorm.io/builder"
14+
)
15+
16+
var ErrAuthTokenNotExist = util.NewNotExistErrorf("auth token does not exist")
17+
18+
type AuthToken struct { //nolint:revive
19+
ID string `xorm:"pk"`
20+
TokenHash string
21+
UserID int64 `xorm:"INDEX"`
22+
ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"`
23+
}
24+
25+
func init() {
26+
db.RegisterModel(new(AuthToken))
27+
}
28+
29+
func InsertAuthToken(ctx context.Context, t *AuthToken) error {
30+
_, err := db.GetEngine(ctx).Insert(t)
31+
return err
32+
}
33+
34+
func GetAuthTokenByID(ctx context.Context, id string) (*AuthToken, error) {
35+
at := &AuthToken{}
36+
37+
has, err := db.GetEngine(ctx).ID(id).Get(at)
38+
if err != nil {
39+
return nil, err
40+
}
41+
if !has {
42+
return nil, ErrAuthTokenNotExist
43+
}
44+
return at, nil
45+
}
46+
47+
func UpdateAuthTokenByID(ctx context.Context, t *AuthToken) error {
48+
_, err := db.GetEngine(ctx).ID(t.ID).Cols("token_hash", "expires_unix").Update(t)
49+
return err
50+
}
51+
52+
func DeleteAuthTokenByID(ctx context.Context, id string) error {
53+
_, err := db.GetEngine(ctx).ID(id).Delete(&AuthToken{})
54+
return err
55+
}
56+
57+
func DeleteExpiredAuthTokens(ctx context.Context) error {
58+
_, err := db.GetEngine(ctx).Where(builder.Lt{"expires_unix": timeutil.TimeStampNow()}).Delete(&AuthToken{})
59+
return err
60+
}

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,8 @@ var migrations = []Migration{
546546

547547
// v280 -> v281
548548
NewMigration("Rename user themes", v1_22.RenameUserThemes),
549+
// v281 -> v282
550+
NewMigration("Add auth_token table", v1_22.CreateAuthTokenTable),
549551
}
550552

551553
// GetCurrentDBVersion returns the current db version

models/migrations/v1_22/v281.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package v1_22 //nolint
5+
6+
import (
7+
"code.gitea.io/gitea/modules/timeutil"
8+
9+
"xorm.io/xorm"
10+
)
11+
12+
func CreateAuthTokenTable(x *xorm.Engine) error {
13+
type AuthToken struct {
14+
ID string `xorm:"pk"`
15+
TokenHash string
16+
UserID int64 `xorm:"INDEX"`
17+
ExpiresUnix timeutil.TimeStamp `xorm:"INDEX"`
18+
}
19+
20+
return x.Sync(new(AuthToken))
21+
}

modules/context/context_cookie.go

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,11 @@
44
package context
55

66
import (
7-
"encoding/hex"
87
"net/http"
98
"strings"
109

1110
"code.gitea.io/gitea/modules/setting"
12-
"code.gitea.io/gitea/modules/util"
1311
"code.gitea.io/gitea/modules/web/middleware"
14-
15-
"github.com/minio/sha256-simd"
16-
"golang.org/x/crypto/pbkdf2"
1712
)
1813

1914
const CookieNameFlash = "gitea_flash"
@@ -45,42 +40,3 @@ func (ctx *Context) DeleteSiteCookie(name string) {
4540
func (ctx *Context) GetSiteCookie(name string) string {
4641
return middleware.GetSiteCookie(ctx.Req, name)
4742
}
48-
49-
// GetSuperSecureCookie returns given cookie value from request header with secret string.
50-
func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) {
51-
val := ctx.GetSiteCookie(name)
52-
return ctx.CookieDecrypt(secret, val)
53-
}
54-
55-
// CookieDecrypt returns given value from with secret string.
56-
func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) {
57-
if val == "" {
58-
return "", false
59-
}
60-
61-
text, err := hex.DecodeString(val)
62-
if err != nil {
63-
return "", false
64-
}
65-
66-
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
67-
text, err = util.AESGCMDecrypt(key, text)
68-
return string(text), err == nil
69-
}
70-
71-
// SetSuperSecureCookie sets given cookie value to response header with secret string.
72-
func (ctx *Context) SetSuperSecureCookie(secret, name, value string, maxAge int) {
73-
text := ctx.CookieEncrypt(secret, value)
74-
ctx.SetSiteCookie(name, text, maxAge)
75-
}
76-
77-
// CookieEncrypt encrypts a given value using the provided secret
78-
func (ctx *Context) CookieEncrypt(secret, value string) string {
79-
key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New)
80-
text, err := util.AESGCMEncrypt(key, []byte(value))
81-
if err != nil {
82-
panic("error encrypting cookie: " + err.Error())
83-
}
84-
85-
return hex.EncodeToString(text)
86-
}

0 commit comments

Comments
 (0)