mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-17 00:01:00 -04:00
Compare commits
No commits in common. "4c7786b3b6e5bf8b5798da3ce387624d4da63f7c" and "b2e58edd74f8d9bb530335e9b1ee2e356d4dacb4" have entirely different histories.
4c7786b3b6
...
b2e58edd74
37
CHANGELOG.md
37
CHANGELOG.md
@ -4,43 +4,6 @@ This changelog goes through all the changes that have been made in each release
|
|||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
been added to each release, please refer to the [blog](https://blog.gitea.io).
|
||||||
|
|
||||||
## [1.18.4](https://github.com/go-gitea/gitea/releases/tag/1.18.4) - 2023-02-20
|
|
||||||
|
|
||||||
* SECURITY
|
|
||||||
* Provide the ability to set password hash algorithm parameters (#22942) (#22943)
|
|
||||||
* Add command to bulk set must-change-password (#22823) (#22928)
|
|
||||||
* ENHANCEMENTS
|
|
||||||
* Use import of OCI structs (#22765) (#22805)
|
|
||||||
* Fix color of tertiary button on dark theme (#22739) (#22744)
|
|
||||||
* Link issue and pull requests status change in UI notifications directly to their event in the timelined view. (#22627) (#22642)
|
|
||||||
* BUGFIXES
|
|
||||||
* Notify on container image create (#22806) (#22965)
|
|
||||||
* Fix blame view missing lines (#22826) (#22929)
|
|
||||||
* Fix incorrect role labels for migrated issues and comments (#22914) (#22923)
|
|
||||||
* Fix PR file tree folders no longer collapsing (#22864) (#22872)
|
|
||||||
* Escape filename when assemble URL (#22850) (#22871)
|
|
||||||
* Fix isAllowed of escapeStreamer (#22814) (#22837)
|
|
||||||
* Load issue before accessing index in merge message (#22822) (#22830)
|
|
||||||
* Improve trace logging for pulls and processes (#22633) (#22812)
|
|
||||||
* Fix restore repo bug, clarify the problem of ForeignIndex (#22776) (#22794)
|
|
||||||
* Add default user visibility to cli command "admin user create" (#22750) (#22760)
|
|
||||||
* Escape path for the file list (#22741) (#22757)
|
|
||||||
* Fix bugs with WebAuthn preventing sign in and registration. (#22651) (#22721)
|
|
||||||
* Add missing close bracket in imagediff (#22710) (#22712)
|
|
||||||
* Move code comments to a standalone file and fix the bug when adding a reply to an outdated review appears to not post(#20821) (#22707)
|
|
||||||
* Fix line spacing for plaintext previews (#22699) (#22701)
|
|
||||||
* Fix wrong hint when deleting a branch successfully from pull request UI (#22673) (#22698)
|
|
||||||
* Fix README TOC links (#22577) (#22677)
|
|
||||||
* Fix missing message in git hook when pull requests disabled on fork (#22625) (#22658)
|
|
||||||
* Improve checkIfPRContentChanged (#22611) (#22644)
|
|
||||||
* Prevent duplicate labels when importing more than 99 (#22591) (#22598)
|
|
||||||
* Don't return duplicated users who can create org repo (#22560) (#22562)
|
|
||||||
* BUILD
|
|
||||||
* Upgrade golangcilint to v1.51.0 (#22764)
|
|
||||||
* MISC
|
|
||||||
* Use proxy for pull mirror (#22771) (#22772)
|
|
||||||
* Use `--index-url` in PyPi description (#22620) (#22636)
|
|
||||||
|
|
||||||
## [1.18.3](https://github.com/go-gitea/gitea/releases/tag/v1.18.3) - 2023-01-23
|
## [1.18.3](https://github.com/go-gitea/gitea/releases/tag/v1.18.3) - 2023-01-23
|
||||||
|
|
||||||
* SECURITY
|
* SECURITY
|
||||||
|
@ -9,7 +9,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
pwd "code.gitea.io/gitea/modules/auth/password"
|
pwd "code.gitea.io/gitea/modules/password"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
auth_model "code.gitea.io/gitea/models/auth"
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
pwd "code.gitea.io/gitea/modules/auth/password"
|
pwd "code.gitea.io/gitea/modules/password"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
@ -523,21 +523,7 @@ Certain queues have defaults that override the defaults set in `[queue]` (this o
|
|||||||
- `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server.
|
- `IMPORT_LOCAL_PATHS`: **false**: Set to `false` to prevent all users (including admin) from importing local path on server.
|
||||||
- `INTERNAL_TOKEN`: **\<random at every install if no uri set\>**: Secret used to validate communication within Gitea binary.
|
- `INTERNAL_TOKEN`: **\<random at every install if no uri set\>**: Secret used to validate communication within Gitea binary.
|
||||||
- `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining INTERNAL_TOKEN in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`)
|
- `INTERNAL_TOKEN_URI`: **<empty>**: Instead of defining INTERNAL_TOKEN in the configuration, this configuration option can be used to give Gitea a path to a file that contains the internal token (example value: `file:/etc/gitea/internal_token`)
|
||||||
- `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[argon2, pbkdf2, pbkdf2_v1, scrypt, bcrypt\], argon2 and scrypt will spend significant amounts of memory.
|
- `PASSWORD_HASH_ALGO`: **pbkdf2**: The hash algorithm to use \[argon2, pbkdf2, scrypt, bcrypt\], argon2 will spend more memory than others.
|
||||||
- Note: The default parameters for `pbkdf2` hashing have changed - the previous settings are available as `pbkdf2_v1` but are not recommended.
|
|
||||||
- The hash functions may be tuned by using `$` after the algorithm:
|
|
||||||
- `argon2$<time>$<memory>$<threads>$<key-length>`
|
|
||||||
- `bcrypt$<cost>`
|
|
||||||
- `pbkdf2$<iterations>$<key-length>`
|
|
||||||
- `scrypt$<n>$<r>$<p>$<key-length>`
|
|
||||||
- The defaults are:
|
|
||||||
- `argon2`: `argon2$2$65536$8$50`
|
|
||||||
- `bcrypt`: `bcrypt$10`
|
|
||||||
- `pbkdf2`: `pbkdf2$320000$50`
|
|
||||||
- `pbkdf2_v1`: `pbkdf2$10000$50`
|
|
||||||
- `pbkdf2_v2`: `pbkdf2$320000$50`
|
|
||||||
- `scrypt`: `scrypt$65536$16$2$50`
|
|
||||||
- Adjusting the algorithm parameters using this functionality is done at your own risk.
|
|
||||||
- `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie.
|
- `CSRF_COOKIE_HTTP_ONLY`: **true**: Set false to allow JavaScript to read CSRF cookie.
|
||||||
- `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users.
|
- `MIN_PASSWORD_LENGTH`: **6**: Minimum password length for new users.
|
||||||
- `PASSWORD_COMPLEXITY`: **off**: Comma separated list of character classes required to pass minimum complexity. If left empty or no valid values are specified, checking is disabled (off):
|
- `PASSWORD_COMPLEXITY`: **off**: Comma separated list of character classes required to pass minimum complexity. If left empty or no valid values are specified, checking is disabled (off):
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
email: user1@example.com
|
email: user1@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user1
|
login_name: user1
|
||||||
@ -45,8 +45,8 @@
|
|||||||
email: user2@example.com
|
email: user2@example.com
|
||||||
keep_email_private: true
|
keep_email_private: true
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user2
|
login_name: user2
|
||||||
@ -82,8 +82,8 @@
|
|||||||
email: user3@example.com
|
email: user3@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user3
|
login_name: user3
|
||||||
@ -119,8 +119,8 @@
|
|||||||
email: user4@example.com
|
email: user4@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user4
|
login_name: user4
|
||||||
@ -156,8 +156,8 @@
|
|||||||
email: user5@example.com
|
email: user5@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user5
|
login_name: user5
|
||||||
@ -193,8 +193,8 @@
|
|||||||
email: user6@example.com
|
email: user6@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user6
|
login_name: user6
|
||||||
@ -230,8 +230,8 @@
|
|||||||
email: user7@example.com
|
email: user7@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: disabled
|
email_notifications_preference: disabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user7
|
login_name: user7
|
||||||
@ -267,8 +267,8 @@
|
|||||||
email: user8@example.com
|
email: user8@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user8
|
login_name: user8
|
||||||
@ -304,8 +304,8 @@
|
|||||||
email: user9@example.com
|
email: user9@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user9
|
login_name: user9
|
||||||
@ -341,8 +341,8 @@
|
|||||||
email: user10@example.com
|
email: user10@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user10
|
login_name: user10
|
||||||
@ -378,8 +378,8 @@
|
|||||||
email: user11@example.com
|
email: user11@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user11
|
login_name: user11
|
||||||
@ -415,8 +415,8 @@
|
|||||||
email: user12@example.com
|
email: user12@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user12
|
login_name: user12
|
||||||
@ -452,8 +452,8 @@
|
|||||||
email: user13@example.com
|
email: user13@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user13
|
login_name: user13
|
||||||
@ -489,8 +489,8 @@
|
|||||||
email: user14@example.com
|
email: user14@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user14
|
login_name: user14
|
||||||
@ -526,8 +526,8 @@
|
|||||||
email: user15@example.com
|
email: user15@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user15
|
login_name: user15
|
||||||
@ -563,8 +563,8 @@
|
|||||||
email: user16@example.com
|
email: user16@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user16
|
login_name: user16
|
||||||
@ -600,8 +600,8 @@
|
|||||||
email: user17@example.com
|
email: user17@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user17
|
login_name: user17
|
||||||
@ -637,8 +637,8 @@
|
|||||||
email: user18@example.com
|
email: user18@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user18
|
login_name: user18
|
||||||
@ -674,8 +674,8 @@
|
|||||||
email: user19@example.com
|
email: user19@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user19
|
login_name: user19
|
||||||
@ -711,8 +711,8 @@
|
|||||||
email: user20@example.com
|
email: user20@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user20
|
login_name: user20
|
||||||
@ -748,8 +748,8 @@
|
|||||||
email: user21@example.com
|
email: user21@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user21
|
login_name: user21
|
||||||
@ -785,8 +785,8 @@
|
|||||||
email: limited_org@example.com
|
email: limited_org@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: limited_org
|
login_name: limited_org
|
||||||
@ -822,8 +822,8 @@
|
|||||||
email: privated_org@example.com
|
email: privated_org@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: privated_org
|
login_name: privated_org
|
||||||
@ -859,8 +859,8 @@
|
|||||||
email: user24@example.com
|
email: user24@example.com
|
||||||
keep_email_private: true
|
keep_email_private: true
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user24
|
login_name: user24
|
||||||
@ -896,8 +896,8 @@
|
|||||||
email: org25@example.com
|
email: org25@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: org25
|
login_name: org25
|
||||||
@ -933,8 +933,8 @@
|
|||||||
email: org26@example.com
|
email: org26@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: onmention
|
email_notifications_preference: onmention
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: org26
|
login_name: org26
|
||||||
@ -970,8 +970,8 @@
|
|||||||
email: user27@example.com
|
email: user27@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user27
|
login_name: user27
|
||||||
@ -1007,8 +1007,8 @@
|
|||||||
email: user28@example.com
|
email: user28@example.com
|
||||||
keep_email_private: true
|
keep_email_private: true
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user28
|
login_name: user28
|
||||||
@ -1044,8 +1044,8 @@
|
|||||||
email: user29@example.com
|
email: user29@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user29
|
login_name: user29
|
||||||
@ -1081,8 +1081,8 @@
|
|||||||
email: user30@example.com
|
email: user30@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user30
|
login_name: user30
|
||||||
@ -1118,8 +1118,8 @@
|
|||||||
email: user31@example.com
|
email: user31@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user31
|
login_name: user31
|
||||||
@ -1155,7 +1155,7 @@
|
|||||||
email: user32@example.com
|
email: user32@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f47017
|
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a
|
||||||
passwd_hash_algo: argon2
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
@ -1192,8 +1192,8 @@
|
|||||||
email: user33@example.com
|
email: user33@example.com
|
||||||
keep_email_private: false
|
keep_email_private: false
|
||||||
email_notifications_preference: enabled
|
email_notifications_preference: enabled
|
||||||
passwd: e82bc8ae42a53b98c3bd0f941aacc4aa2a264407534b0a11bf270137f67af912f694b67951f92148c45f91717e1478ca7889
|
passwd: a3d5fcd92bae586c2e3dbe72daea7a0d27833a8d0227aa1704f4bbd775c1f3b03535b76dd93b0d4d8d22a519dca47df1547b
|
||||||
passwd_hash_algo: pbkdf2$50000$50
|
passwd_hash_algo: argon2
|
||||||
must_change_password: false
|
must_change_password: false
|
||||||
login_source: 0
|
login_source: 0
|
||||||
login_name: user33
|
login_name: user33
|
||||||
|
@ -7,6 +7,8 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -20,7 +22,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/auth/openid"
|
"code.gitea.io/gitea/modules/auth/openid"
|
||||||
"code.gitea.io/gitea/modules/auth/password/hash"
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@ -29,6 +30,10 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/argon2"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"golang.org/x/crypto/pbkdf2"
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
"xorm.io/builder"
|
"xorm.io/builder"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -43,6 +48,21 @@ const (
|
|||||||
UserTypeOrganization
|
UserTypeOrganization
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
algoBcrypt = "bcrypt"
|
||||||
|
algoScrypt = "scrypt"
|
||||||
|
algoArgon2 = "argon2"
|
||||||
|
algoPbkdf2 = "pbkdf2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AvailableHashAlgorithms represents the available password hashing algorithms
|
||||||
|
var AvailableHashAlgorithms = []string{
|
||||||
|
algoPbkdf2,
|
||||||
|
algoArgon2,
|
||||||
|
algoScrypt,
|
||||||
|
algoBcrypt,
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own
|
// EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own
|
||||||
EmailNotificationsEnabled = "enabled"
|
EmailNotificationsEnabled = "enabled"
|
||||||
@ -348,6 +368,42 @@ func (u *User) NewGitSig() *git.Signature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hashPassword(passwd, salt, algo string) (string, error) {
|
||||||
|
var tempPasswd []byte
|
||||||
|
var saltBytes []byte
|
||||||
|
|
||||||
|
// There are two formats for the Salt value:
|
||||||
|
// * The new format is a (32+)-byte hex-encoded string
|
||||||
|
// * The old format was a 10-byte binary format
|
||||||
|
// We have to tolerate both here but Authenticate should
|
||||||
|
// regenerate the Salt following a successful validation.
|
||||||
|
if len(salt) == 10 {
|
||||||
|
saltBytes = []byte(salt)
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
saltBytes, err = hex.DecodeString(salt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch algo {
|
||||||
|
case algoBcrypt:
|
||||||
|
tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
|
||||||
|
return string(tempPasswd), nil
|
||||||
|
case algoScrypt:
|
||||||
|
tempPasswd, _ = scrypt.Key([]byte(passwd), saltBytes, 65536, 16, 2, 50)
|
||||||
|
case algoArgon2:
|
||||||
|
tempPasswd = argon2.IDKey([]byte(passwd), saltBytes, 2, 65536, 8, 50)
|
||||||
|
case algoPbkdf2:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
tempPasswd = pbkdf2.Key([]byte(passwd), saltBytes, 10000, 50, sha256.New)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%x", tempPasswd), nil
|
||||||
|
}
|
||||||
|
|
||||||
// SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO
|
// SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO
|
||||||
// change passwd, salt and passwd_hash_algo fields
|
// change passwd, salt and passwd_hash_algo fields
|
||||||
func (u *User) SetPassword(passwd string) (err error) {
|
func (u *User) SetPassword(passwd string) (err error) {
|
||||||
@ -361,7 +417,7 @@ func (u *User) SetPassword(passwd string) (err error) {
|
|||||||
if u.Salt, err = GetUserSalt(); err != nil {
|
if u.Salt, err = GetUserSalt(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if u.Passwd, err = hash.Parse(setting.PasswordHashAlgo).Hash(passwd, u.Salt); err != nil {
|
if u.Passwd, err = hashPassword(passwd, u.Salt, setting.PasswordHashAlgo); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
u.PasswdHashAlgo = setting.PasswordHashAlgo
|
u.PasswdHashAlgo = setting.PasswordHashAlgo
|
||||||
@ -369,9 +425,20 @@ func (u *User) SetPassword(passwd string) (err error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePassword checks if the given password matches the one belonging to the user.
|
// ValidatePassword checks if given password matches the one belongs to the user.
|
||||||
func (u *User) ValidatePassword(passwd string) bool {
|
func (u *User) ValidatePassword(passwd string) bool {
|
||||||
return hash.Parse(u.PasswdHashAlgo).VerifyPassword(passwd, u.Passwd, u.Salt)
|
tempHash, err := hashPassword(passwd, u.Salt, u.PasswdHashAlgo)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.PasswdHashAlgo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPasswordSet checks if the password is set or left empty
|
// IsPasswordSet checks if the password is set or left empty
|
||||||
|
@ -13,7 +13,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password/hash"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/structs"
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -163,7 +162,7 @@ func TestEmailNotificationPreferences(t *testing.T) {
|
|||||||
func TestHashPasswordDeterministic(t *testing.T) {
|
func TestHashPasswordDeterministic(t *testing.T) {
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
u := &user_model.User{}
|
u := &user_model.User{}
|
||||||
algos := hash.RecommendedHashAlgorithms
|
algos := []string{"argon2", "pbkdf2", "scrypt", "bcrypt"}
|
||||||
for j := 0; j < len(algos); j++ {
|
for j := 0; j < len(algos); j++ {
|
||||||
u.PasswdHashAlgo = algos[j]
|
u.PasswdHashAlgo = algos[j]
|
||||||
for i := 0; i < 50; i++ {
|
for i := 0; i < 50; i++ {
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/argon2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register("argon2", NewArgon2Hasher)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Argon2Hasher implements PasswordHasher
|
|
||||||
// and uses the Argon2 key derivation function, hybrant variant
|
|
||||||
type Argon2Hasher struct {
|
|
||||||
time uint32
|
|
||||||
memory uint32
|
|
||||||
threads uint8
|
|
||||||
keyLen uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashWithSaltBytes a provided password and salt
|
|
||||||
func (hasher *Argon2Hasher) HashWithSaltBytes(password string, salt []byte) string {
|
|
||||||
if hasher == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(argon2.IDKey([]byte(password), salt, hasher.time, hasher.memory, hasher.threads, hasher.keyLen))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewArgon2Hasher is a factory method to create an Argon2Hasher
|
|
||||||
// The provided config should be either empty or of the form:
|
|
||||||
// "<time>$<memory>$<threads>$<keyLen>", where <x> is the string representation
|
|
||||||
// of an integer
|
|
||||||
func NewArgon2Hasher(config string) *Argon2Hasher {
|
|
||||||
// This default configuration uses the following parameters:
|
|
||||||
// time=2, memory=64*1024, threads=8, keyLen=50.
|
|
||||||
// It will make two passes through the memory, using 64MiB in total.
|
|
||||||
hasher := &Argon2Hasher{
|
|
||||||
time: 2,
|
|
||||||
memory: 1 << 16,
|
|
||||||
threads: 8,
|
|
||||||
keyLen: 50,
|
|
||||||
}
|
|
||||||
|
|
||||||
if config == "" {
|
|
||||||
return hasher
|
|
||||||
}
|
|
||||||
|
|
||||||
vals := strings.SplitN(config, "$", 4)
|
|
||||||
if len(vals) != 4 {
|
|
||||||
log.Error("invalid argon2 hash spec %s", config)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, err := parseUIntParam(vals[0], "time", "argon2", config, nil)
|
|
||||||
hasher.time = uint32(parsed)
|
|
||||||
|
|
||||||
parsed, err = parseUIntParam(vals[1], "memory", "argon2", config, err)
|
|
||||||
hasher.memory = uint32(parsed)
|
|
||||||
|
|
||||||
parsed, err = parseUIntParam(vals[2], "threads", "argon2", config, err)
|
|
||||||
hasher.threads = uint8(parsed)
|
|
||||||
|
|
||||||
parsed, err = parseUIntParam(vals[3], "keyLen", "argon2", config, err)
|
|
||||||
hasher.keyLen = uint32(parsed)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasher
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register("bcrypt", NewBcryptHasher)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BcryptHasher implements PasswordHasher
|
|
||||||
// and uses the bcrypt password hash function.
|
|
||||||
type BcryptHasher struct {
|
|
||||||
cost int
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashWithSaltBytes a provided password and salt
|
|
||||||
func (hasher *BcryptHasher) HashWithSaltBytes(password string, salt []byte) string {
|
|
||||||
if hasher == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), hasher.cost)
|
|
||||||
return string(hashedPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hasher *BcryptHasher) VerifyPassword(password, hashedPassword, salt string) bool {
|
|
||||||
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBcryptHasher is a factory method to create an BcryptHasher
|
|
||||||
// The provided config should be either empty or the string representation of the "<cost>"
|
|
||||||
// as an integer
|
|
||||||
func NewBcryptHasher(config string) *BcryptHasher {
|
|
||||||
hasher := &BcryptHasher{
|
|
||||||
cost: 10, // cost=10. i.e. 2^10 rounds of key expansion.
|
|
||||||
}
|
|
||||||
|
|
||||||
if config == "" {
|
|
||||||
return hasher
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
hasher.cost, err = parseIntParam(config, "cost", "bcrypt", config, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasher
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseIntParam(value, param, algorithmName, config string, previousErr error) (int, error) {
|
|
||||||
parsed, err := strconv.Atoi(value)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUIntParam(value, param, algorithmName, config string, previousErr error) (uint64, error) {
|
|
||||||
parsed, err := strconv.ParseUint(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("invalid integer for %s representation in %s hash spec %s", param, algorithmName, config)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return parsed, previousErr // <- Keep the previous error as this function should still return an error once everything has been checked if any call failed
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync/atomic"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This package takes care of hashing passwords, verifying passwords, defining
|
|
||||||
// available password algorithms, defining recommended password algorithms and
|
|
||||||
// choosing the default password algorithm.
|
|
||||||
|
|
||||||
// PasswordSaltHasher will hash a provided password with the provided saltBytes
|
|
||||||
type PasswordSaltHasher interface {
|
|
||||||
HashWithSaltBytes(password string, saltBytes []byte) string
|
|
||||||
}
|
|
||||||
|
|
||||||
// PasswordHasher will hash a provided password with the salt
|
|
||||||
type PasswordHasher interface {
|
|
||||||
Hash(password, salt string) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PasswordVerifier will ensure that a providedPassword matches the hashPassword when hashed with the salt
|
|
||||||
type PasswordVerifier interface {
|
|
||||||
VerifyPassword(providedPassword, hashedPassword, salt string) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// PasswordHashAlgorithms are named PasswordSaltHashers with a default verifier and hash function
|
|
||||||
type PasswordHashAlgorithm struct {
|
|
||||||
PasswordSaltHasher
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hash the provided password with the salt and return the hash
|
|
||||||
func (algorithm *PasswordHashAlgorithm) Hash(password, salt string) (string, error) {
|
|
||||||
var saltBytes []byte
|
|
||||||
|
|
||||||
// There are two formats for the salt value:
|
|
||||||
// * The new format is a (32+)-byte hex-encoded string
|
|
||||||
// * The old format was a 10-byte binary format
|
|
||||||
// We have to tolerate both here.
|
|
||||||
if len(salt) == 10 {
|
|
||||||
saltBytes = []byte(salt)
|
|
||||||
} else {
|
|
||||||
var err error
|
|
||||||
saltBytes, err = hex.DecodeString(salt)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return algorithm.HashWithSaltBytes(password, saltBytes), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the provided password matches the hashPassword when hashed with the salt
|
|
||||||
func (algorithm *PasswordHashAlgorithm) VerifyPassword(providedPassword, hashedPassword, salt string) bool {
|
|
||||||
// The bcrypt package has its own specialized compare function that takes into
|
|
||||||
// account the stored password's bcrypt parameters.
|
|
||||||
if verifier, ok := algorithm.PasswordSaltHasher.(PasswordVerifier); ok {
|
|
||||||
return verifier.VerifyPassword(providedPassword, hashedPassword, salt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute the hash of the password.
|
|
||||||
providedPasswordHash, err := algorithm.Hash(providedPassword, salt)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("passwordhash: %v.Hash(): %v", algorithm.Name, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare it against the hashed password in constant-time.
|
|
||||||
return subtle.ConstantTimeCompare([]byte(hashedPassword), []byte(providedPasswordHash)) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
lastNonDefaultAlgorithm atomic.Value
|
|
||||||
availableHasherFactories = map[string]func(string) PasswordSaltHasher{}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Register registers a PasswordSaltHasher with the availableHasherFactories
|
|
||||||
// This is not thread safe.
|
|
||||||
func Register[T PasswordSaltHasher](name string, newFn func(config string) T) {
|
|
||||||
if _, has := availableHasherFactories[name]; has {
|
|
||||||
panic(fmt.Errorf("duplicate registration of password salt hasher: %s", name))
|
|
||||||
}
|
|
||||||
|
|
||||||
availableHasherFactories[name] = func(config string) PasswordSaltHasher {
|
|
||||||
n := newFn(config)
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In early versions of gitea the password hash algorithm field could be empty
|
|
||||||
// At that point the default was `pbkdf2` without configuration values
|
|
||||||
// Please note this is not the same as the DefaultAlgorithm
|
|
||||||
const defaultEmptyHashAlgorithmName = "pbkdf2"
|
|
||||||
|
|
||||||
func Parse(algorithm string) *PasswordHashAlgorithm {
|
|
||||||
if algorithm == "" {
|
|
||||||
algorithm = defaultEmptyHashAlgorithmName
|
|
||||||
}
|
|
||||||
|
|
||||||
if DefaultHashAlgorithm != nil && algorithm == DefaultHashAlgorithm.Name {
|
|
||||||
return DefaultHashAlgorithm
|
|
||||||
}
|
|
||||||
|
|
||||||
ptr := lastNonDefaultAlgorithm.Load()
|
|
||||||
if ptr != nil {
|
|
||||||
hashAlgorithm, ok := ptr.(*PasswordHashAlgorithm)
|
|
||||||
if ok && hashAlgorithm.Name == algorithm {
|
|
||||||
return hashAlgorithm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vals := strings.SplitN(algorithm, "$", 2)
|
|
||||||
var name string
|
|
||||||
var config string
|
|
||||||
if len(vals) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
name = vals[0]
|
|
||||||
if len(vals) > 1 {
|
|
||||||
config = vals[1]
|
|
||||||
}
|
|
||||||
newFn, has := availableHasherFactories[name]
|
|
||||||
if !has {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
ph := newFn(config)
|
|
||||||
if ph == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
hashAlgorithm := &PasswordHashAlgorithm{
|
|
||||||
PasswordSaltHasher: ph,
|
|
||||||
Name: algorithm,
|
|
||||||
}
|
|
||||||
|
|
||||||
lastNonDefaultAlgorithm.Store(hashAlgorithm)
|
|
||||||
|
|
||||||
return hashAlgorithm
|
|
||||||
}
|
|
@ -1,186 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
type testSaltHasher string
|
|
||||||
|
|
||||||
func (t testSaltHasher) HashWithSaltBytes(password string, salt []byte) string {
|
|
||||||
return password + "$" + string(salt) + "$" + string(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_registerHasher(t *testing.T) {
|
|
||||||
Register("Test_registerHasher", func(config string) testSaltHasher {
|
|
||||||
return testSaltHasher(config)
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Panics(t, func() {
|
|
||||||
Register("Test_registerHasher", func(config string) testSaltHasher {
|
|
||||||
return testSaltHasher(config)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Equal(t, "password$salt$",
|
|
||||||
Parse("Test_registerHasher").PasswordSaltHasher.HashWithSaltBytes("password", []byte("salt")))
|
|
||||||
|
|
||||||
assert.Equal(t, "password$salt$config",
|
|
||||||
Parse("Test_registerHasher$config").PasswordSaltHasher.HashWithSaltBytes("password", []byte("salt")))
|
|
||||||
|
|
||||||
delete(availableHasherFactories, "Test_registerHasher")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestParse(t *testing.T) {
|
|
||||||
hashAlgorithmsToTest := []string{}
|
|
||||||
for plainHashAlgorithmNames := range availableHasherFactories {
|
|
||||||
hashAlgorithmsToTest = append(hashAlgorithmsToTest, plainHashAlgorithmNames)
|
|
||||||
}
|
|
||||||
for _, aliased := range aliasAlgorithmNames {
|
|
||||||
if strings.Contains(aliased, "$") {
|
|
||||||
hashAlgorithmsToTest = append(hashAlgorithmsToTest, aliased)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, algorithmName := range hashAlgorithmsToTest {
|
|
||||||
t.Run(algorithmName, func(t *testing.T) {
|
|
||||||
algo := Parse(algorithmName)
|
|
||||||
assert.NotNil(t, algo, "Algorithm %s resulted in an empty algorithm", algorithmName)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHashing(t *testing.T) {
|
|
||||||
hashAlgorithmsToTest := []string{}
|
|
||||||
for plainHashAlgorithmNames := range availableHasherFactories {
|
|
||||||
hashAlgorithmsToTest = append(hashAlgorithmsToTest, plainHashAlgorithmNames)
|
|
||||||
}
|
|
||||||
for _, aliased := range aliasAlgorithmNames {
|
|
||||||
if strings.Contains(aliased, "$") {
|
|
||||||
hashAlgorithmsToTest = append(hashAlgorithmsToTest, aliased)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
runTests := func(password, salt string, shouldPass bool) {
|
|
||||||
for _, algorithmName := range hashAlgorithmsToTest {
|
|
||||||
t.Run(algorithmName, func(t *testing.T) {
|
|
||||||
output, err := Parse(algorithmName).Hash(password, salt)
|
|
||||||
if shouldPass {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.NotEmpty(t, output, "output for %s was empty", algorithmName)
|
|
||||||
} else {
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, Parse(algorithmName).VerifyPassword(password, output, salt), shouldPass)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test with new salt format.
|
|
||||||
runTests(strings.Repeat("a", 16), hex.EncodeToString([]byte{0x01, 0x02, 0x03}), true)
|
|
||||||
|
|
||||||
// Test with legacy salt format.
|
|
||||||
runTests(strings.Repeat("a", 16), strings.Repeat("b", 10), true)
|
|
||||||
|
|
||||||
// Test with invalid salt.
|
|
||||||
runTests(strings.Repeat("a", 16), "a", false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// vectors were generated using the current codebase.
|
|
||||||
var vectors = []struct {
|
|
||||||
algorithms []string
|
|
||||||
password string
|
|
||||||
salt string
|
|
||||||
output string
|
|
||||||
shouldfail bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
algorithms: []string{"bcrypt", "bcrypt$10"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: strings.Repeat("a", 10),
|
|
||||||
output: "$2a$10$fjtm8BsQ2crym01/piJroenO3oSVUBhSLKaGdTYJ4tG0ePVCrU0G2",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"scrypt", "scrypt$65536$16$2$50"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: strings.Repeat("a", 10),
|
|
||||||
output: "3b571d0c07c62d42b7bad3dbf18fb0cd67d4d8cd4ad4c6928e1090e5b2a4a84437c6fd2627d897c0e7e65025ca62b67a0002",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"argon2", "argon2$2$65536$8$50"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: strings.Repeat("a", 10),
|
|
||||||
output: "551f089f570f989975b6f7c6a8ff3cf89bc486dd7bbe87ed4d80ad4362f8ee599ec8dda78dac196301b98456402bcda775dc",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: strings.Repeat("a", 10),
|
|
||||||
output: "ab48d5471b7e6ed42d10001db88c852ff7303c788e49da5c3c7b63d5adf96360303724b74b679223a3dea8a242d10abb1913",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"bcrypt", "bcrypt$10"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
|
|
||||||
output: "$2a$10$qhgm32w9ZpqLygugWJsLjey8xRGcaq9iXAfmCeNBXxddgyoaOC3Gq",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"scrypt", "scrypt$65536$16$2$50"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
|
|
||||||
output: "25fe5f66b43fa4eb7b6717905317cd2223cf841092dc8e0a1e8c75720ad4846cb5d9387303e14bc3c69faa3b1c51ef4b7de1",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"argon2", "argon2$2$65536$8$50"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
|
|
||||||
output: "9c287db63a91d18bb1414b703216da4fc431387c1ae7c8acdb280222f11f0929831055dbfd5126a3b48566692e83ec750d2a",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
|
|
||||||
output: "45d6cdc843d65cf0eda7b90ab41435762a282f7df013477a1c5b212ba81dbdca2edf1ecc4b5cb05956bb9e0c37ab29315d78",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"pbkdf2$320000$50"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: hex.EncodeToString([]byte{0x01, 0x02, 0x03, 0x04}),
|
|
||||||
output: "84e233114499e8721da80e85568e5b7b5900b3e49a30845fcda9d1e1756da4547d70f8740ac2b4a5d82f88cebcd27f21bfe2",
|
|
||||||
shouldfail: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
algorithms: []string{"pbkdf2", "pbkdf2$10000$50"},
|
|
||||||
password: "abcdef",
|
|
||||||
salt: "",
|
|
||||||
output: "",
|
|
||||||
shouldfail: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that the current code will correctly verify against the test vectors.
|
|
||||||
func TestVectors(t *testing.T) {
|
|
||||||
for i, vector := range vectors {
|
|
||||||
for _, algorithm := range vector.algorithms {
|
|
||||||
t.Run(strconv.Itoa(i)+": "+algorithm, func(t *testing.T) {
|
|
||||||
pa := Parse(algorithm)
|
|
||||||
assert.Equal(t, !vector.shouldfail, pa.VerifyPassword(vector.password, vector.output, vector.salt))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,62 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/hex"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/pbkdf2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register("pbkdf2", NewPBKDF2Hasher)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PBKDF2Hasher implements PasswordHasher
|
|
||||||
// and uses the PBKDF2 key derivation function.
|
|
||||||
type PBKDF2Hasher struct {
|
|
||||||
iter, keyLen int
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashWithSaltBytes a provided password and salt
|
|
||||||
func (hasher *PBKDF2Hasher) HashWithSaltBytes(password string, salt []byte) string {
|
|
||||||
if hasher == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(pbkdf2.Key([]byte(password), salt, hasher.iter, hasher.keyLen, sha256.New))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPBKDF2Hasher is a factory method to create an PBKDF2Hasher
|
|
||||||
// config should be either empty or of the form:
|
|
||||||
// "<iter>$<keyLen>", where <x> is the string representation
|
|
||||||
// of an integer
|
|
||||||
func NewPBKDF2Hasher(config string) *PBKDF2Hasher {
|
|
||||||
hasher := &PBKDF2Hasher{
|
|
||||||
iter: 10_000,
|
|
||||||
keyLen: 50,
|
|
||||||
}
|
|
||||||
|
|
||||||
if config == "" {
|
|
||||||
return hasher
|
|
||||||
}
|
|
||||||
|
|
||||||
vals := strings.SplitN(config, "$", 2)
|
|
||||||
if len(vals) != 2 {
|
|
||||||
log.Error("invalid pbkdf2 hash spec %s", config)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
hasher.iter, err = parseIntParam(vals[0], "iter", "pbkdf2", config, nil)
|
|
||||||
hasher.keyLen, err = parseIntParam(vals[1], "keyLen", "pbkdf2", config, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasher
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/log"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/scrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Register("scrypt", NewScryptHasher)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScryptHasher implements PasswordHasher
|
|
||||||
// and uses the scrypt key derivation function.
|
|
||||||
type ScryptHasher struct {
|
|
||||||
n, r, p, keyLen int
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashWithSaltBytes a provided password and salt
|
|
||||||
func (hasher *ScryptHasher) HashWithSaltBytes(password string, salt []byte) string {
|
|
||||||
if hasher == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
hashedPassword, _ := scrypt.Key([]byte(password), salt, hasher.n, hasher.r, hasher.p, hasher.keyLen)
|
|
||||||
return hex.EncodeToString(hashedPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewScryptHasher is a factory method to create an ScryptHasher
|
|
||||||
// The provided config should be either empty or of the form:
|
|
||||||
// "<n>$<r>$<p>$<keyLen>", where <x> is the string representation
|
|
||||||
// of an integer
|
|
||||||
func NewScryptHasher(config string) *ScryptHasher {
|
|
||||||
hasher := &ScryptHasher{
|
|
||||||
n: 1 << 16,
|
|
||||||
r: 16,
|
|
||||||
p: 2, // 2 passes through memory - this default config will use 128MiB in total.
|
|
||||||
keyLen: 50,
|
|
||||||
}
|
|
||||||
|
|
||||||
if config == "" {
|
|
||||||
return hasher
|
|
||||||
}
|
|
||||||
|
|
||||||
vals := strings.SplitN(config, "$", 4)
|
|
||||||
if len(vals) != 4 {
|
|
||||||
log.Error("invalid scrypt hash spec %s", config)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
var err error
|
|
||||||
hasher.n, err = parseIntParam(vals[0], "n", "scrypt", config, nil)
|
|
||||||
hasher.r, err = parseIntParam(vals[1], "r", "scrypt", config, err)
|
|
||||||
hasher.p, err = parseIntParam(vals[2], "p", "scrypt", config, err)
|
|
||||||
hasher.keyLen, err = parseIntParam(vals[3], "keyLen", "scrypt", config, err)
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return hasher
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
const DefaultHashAlgorithmName = "pbkdf2"
|
|
||||||
|
|
||||||
var DefaultHashAlgorithm *PasswordHashAlgorithm
|
|
||||||
|
|
||||||
var aliasAlgorithmNames = map[string]string{
|
|
||||||
"argon2": "argon2$2$65536$8$50",
|
|
||||||
"bcrypt": "bcrypt$10",
|
|
||||||
"scrypt": "scrypt$65536$16$2$50",
|
|
||||||
"pbkdf2": "pbkdf2_v2", // pbkdf2 should default to pbkdf2_v2
|
|
||||||
"pbkdf2_v1": "pbkdf2$10000$50",
|
|
||||||
// The latest PBKDF2 password algorithm is used as the default since it doesn't
|
|
||||||
// use a lot of memory and is safer to use on less powerful devices.
|
|
||||||
"pbkdf2_v2": "pbkdf2$50000$50",
|
|
||||||
// The pbkdf2_hi password algorithm is offered as a stronger alternative to the
|
|
||||||
// slightly improved pbkdf2_v2 algorithm
|
|
||||||
"pbkdf2_hi": "pbkdf2$320000$50",
|
|
||||||
}
|
|
||||||
|
|
||||||
var RecommendedHashAlgorithms = []string{
|
|
||||||
"pbkdf2",
|
|
||||||
"argon2",
|
|
||||||
"bcrypt",
|
|
||||||
"scrypt",
|
|
||||||
"pbkdf2_hi",
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetDefaultPasswordHashAlgorithm(algorithmName string) (string, *PasswordHashAlgorithm) {
|
|
||||||
if algorithmName == "" {
|
|
||||||
algorithmName = DefaultHashAlgorithmName
|
|
||||||
}
|
|
||||||
alias, has := aliasAlgorithmNames[algorithmName]
|
|
||||||
for has {
|
|
||||||
algorithmName = alias
|
|
||||||
alias, has = aliasAlgorithmNames[algorithmName]
|
|
||||||
}
|
|
||||||
DefaultHashAlgorithm = Parse(algorithmName)
|
|
||||||
|
|
||||||
return algorithmName, DefaultHashAlgorithm
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
package hash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheckSettingPasswordHashAlgorithm(t *testing.T) {
|
|
||||||
t.Run("pbkdf2 is pbkdf2_v2", func(t *testing.T) {
|
|
||||||
pbkdf2v2Config, pbkdf2v2Algo := SetDefaultPasswordHashAlgorithm("pbkdf2_v2")
|
|
||||||
pbkdf2Config, pbkdf2Algo := SetDefaultPasswordHashAlgorithm("pbkdf2")
|
|
||||||
|
|
||||||
assert.Equal(t, pbkdf2v2Config, pbkdf2Config)
|
|
||||||
assert.Equal(t, pbkdf2v2Algo.Name, pbkdf2Algo.Name)
|
|
||||||
})
|
|
||||||
|
|
||||||
for a, b := range aliasAlgorithmNames {
|
|
||||||
t.Run(a+"="+b, func(t *testing.T) {
|
|
||||||
aConfig, aAlgo := SetDefaultPasswordHashAlgorithm(a)
|
|
||||||
bConfig, bAlgo := SetDefaultPasswordHashAlgorithm(b)
|
|
||||||
|
|
||||||
assert.Equal(t, bConfig, aConfig)
|
|
||||||
assert.Equal(t, aAlgo.Name, bAlgo.Name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("pbkdf2_v2 is the default when default password hash algorithm is empty", func(t *testing.T) {
|
|
||||||
emptyConfig, emptyAlgo := SetDefaultPasswordHashAlgorithm("")
|
|
||||||
pbkdf2v2Config, pbkdf2v2Algo := SetDefaultPasswordHashAlgorithm("pbkdf2_v2")
|
|
||||||
|
|
||||||
assert.Equal(t, pbkdf2v2Config, emptyConfig)
|
|
||||||
assert.Equal(t, pbkdf2v2Algo.Name, emptyAlgo.Name)
|
|
||||||
})
|
|
||||||
}
|
|
@ -12,8 +12,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// complexity contains information about a particular kind of password complexity
|
// complexity contains information about a particular kind of password complexity
|
||||||
@ -113,13 +113,13 @@ func Generate(n int) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BuildComplexityError builds the error message when password complexity checks fail
|
// BuildComplexityError builds the error message when password complexity checks fail
|
||||||
func BuildComplexityError(locale translation.Locale) string {
|
func BuildComplexityError(ctx *context.Context) string {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
buffer.WriteString(locale.Tr("form.password_complexity"))
|
buffer.WriteString(ctx.Tr("form.password_complexity"))
|
||||||
buffer.WriteString("<ul>")
|
buffer.WriteString("<ul>")
|
||||||
for _, c := range requiredList {
|
for _, c := range requiredList {
|
||||||
buffer.WriteString("<li>")
|
buffer.WriteString("<li>")
|
||||||
buffer.WriteString(locale.Tr(c.TrNameOne))
|
buffer.WriteString(ctx.Tr(c.TrNameOne))
|
||||||
buffer.WriteString("</li>")
|
buffer.WriteString("</li>")
|
||||||
}
|
}
|
||||||
buffer.WriteString("</ul>")
|
buffer.WriteString("</ul>")
|
@ -21,7 +21,6 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.gitea.io/gitea/modules/auth/password/hash"
|
|
||||||
"code.gitea.io/gitea/modules/container"
|
"code.gitea.io/gitea/modules/container"
|
||||||
"code.gitea.io/gitea/modules/generate"
|
"code.gitea.io/gitea/modules/generate"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
@ -965,14 +964,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
|
|||||||
DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true)
|
DisableGitHooks = sec.Key("DISABLE_GIT_HOOKS").MustBool(true)
|
||||||
DisableWebhooks = sec.Key("DISABLE_WEBHOOKS").MustBool(false)
|
DisableWebhooks = sec.Key("DISABLE_WEBHOOKS").MustBool(false)
|
||||||
OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true)
|
OnlyAllowPushIfGiteaEnvironmentSet = sec.Key("ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET").MustBool(true)
|
||||||
|
PasswordHashAlgo = sec.Key("PASSWORD_HASH_ALGO").MustString("pbkdf2")
|
||||||
// Ensure that the provided default hash algorithm is a valid hash algorithm
|
|
||||||
var algorithm *hash.PasswordHashAlgorithm
|
|
||||||
PasswordHashAlgo, algorithm = hash.SetDefaultPasswordHashAlgorithm(sec.Key("PASSWORD_HASH_ALGO").MustString(""))
|
|
||||||
if algorithm == nil {
|
|
||||||
log.Fatal("The provided password hash algorithm was invalid: %s", sec.Key("PASSWORD_HASH_ALGO").MustString(""))
|
|
||||||
}
|
|
||||||
|
|
||||||
CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true)
|
CSRFCookieHTTPOnly = sec.Key("CSRF_COOKIE_HTTP_ONLY").MustBool(true)
|
||||||
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
|
PasswordCheckPwn = sec.Key("PASSWORD_CHECK_PWN").MustBool(false)
|
||||||
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
|
SuccessfulTokensCacheSize = sec.Key("SUCCESSFUL_TOKENS_CACHE_SIZE").MustInt(20)
|
||||||
|
@ -16,10 +16,10 @@ import (
|
|||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password"
|
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/convert"
|
"code.gitea.io/gitea/modules/convert"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/password"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
@ -21,7 +21,6 @@ import (
|
|||||||
"code.gitea.io/gitea/models/migrations"
|
"code.gitea.io/gitea/models/migrations"
|
||||||
system_model "code.gitea.io/gitea/models/system"
|
system_model "code.gitea.io/gitea/models/system"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password/hash"
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/generate"
|
"code.gitea.io/gitea/modules/generate"
|
||||||
@ -81,7 +80,7 @@ func Init(ctx goctx.Context) func(next http.Handler) http.Handler {
|
|||||||
"AllLangs": translation.AllLangs(),
|
"AllLangs": translation.AllLangs(),
|
||||||
"PageStartTime": startTime,
|
"PageStartTime": startTime,
|
||||||
|
|
||||||
"PasswordHashAlgorithms": hash.RecommendedHashAlgorithms,
|
"PasswordHashAlgorithms": user_model.AvailableHashAlgorithms,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
defer ctx.Close()
|
defer ctx.Close()
|
||||||
|
@ -15,10 +15,10 @@ import (
|
|||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password"
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/password"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -14,13 +14,13 @@ import (
|
|||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password"
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/eventsource"
|
"code.gitea.io/gitea/modules/eventsource"
|
||||||
"code.gitea.io/gitea/modules/hcaptcha"
|
"code.gitea.io/gitea/modules/hcaptcha"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/mcaptcha"
|
"code.gitea.io/gitea/modules/mcaptcha"
|
||||||
|
"code.gitea.io/gitea/modules/password"
|
||||||
"code.gitea.io/gitea/modules/recaptcha"
|
"code.gitea.io/gitea/modules/recaptcha"
|
||||||
"code.gitea.io/gitea/modules/session"
|
"code.gitea.io/gitea/modules/session"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -10,10 +10,10 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models/auth"
|
"code.gitea.io/gitea/models/auth"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password"
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/password"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
@ -12,10 +12,10 @@ import (
|
|||||||
|
|
||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/auth/password"
|
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
"code.gitea.io/gitea/modules/password"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user