argon2 password hashing (#406)

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#406
This commit is contained in:
floatingghost 2022-12-30 02:46:58 +00:00
parent a5e98083f2
commit 9be6caf125
25 changed files with 188 additions and 99 deletions

View file

@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Prometheus metrics exporting from `/api/v1/akkoma/metrics`
- Ability to alter http pool size
- Translation of statuses via ArgosTranslate
- Argon2 password hashing
- Ability to "verify" links in profile fields via rel=me
- Mix tasks to dump/load config to/from json for bulk editing
@ -17,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Non-finch HTTP adapters
- Legacy redirect from /api/pleroma/admin to /api/v1/pleroma/admin
- Legacy redirects from /api/pleroma to /api/v1/pleroma
- :crypt dependency
### Changed
- Return HTTP error 413 when uploading an avatar or banner that's above the configured upload limit instead of a 500.

View file

@ -87,13 +87,15 @@ def run(["dump_to_file", group, key, fname]) do
key = maybe_atomize(key)
config = ConfigDB.get_by_group_and_key(group, key)
json = %{
group: ConfigDB.to_json_types(config.group),
key: ConfigDB.to_json_types(config.key),
value: ConfigDB.to_json_types(config.value),
}
|> Jason.encode!()
|> Jason.Formatter.pretty_print()
json =
%{
group: ConfigDB.to_json_types(config.group),
key: ConfigDB.to_json_types(config.key),
value: ConfigDB.to_json_types(config.value)
}
|> Jason.encode!()
|> Jason.Formatter.pretty_print()
File.write(fname, json)
shell_info("Wrote #{group}_#{key}.json")

54
lib/pleroma/password.ex Normal file
View file

@ -0,0 +1,54 @@
defmodule Pleroma.Password do
@moduledoc """
This module handles password hashing and verification.
It will delegate to the appropriate module based on the password hash.
It also handles upgrading of password hashes.
"""
alias Pleroma.User
alias Pleroma.Password.Pbkdf2
require Logger
@hashing_module Argon2
@spec hash_pwd_salt(String.t()) :: String.t()
defdelegate hash_pwd_salt(pass), to: @hashing_module
@spec checkpw(String.t(), String.t()) :: boolean()
def checkpw(password, "$2" <> _ = password_hash) do
# Handle bcrypt passwords for Mastodon migration
Bcrypt.verify_pass(password, password_hash)
end
def checkpw(password, "$pbkdf2" <> _ = password_hash) do
Pbkdf2.verify_pass(password, password_hash)
end
def checkpw(password, "$argon2" <> _ = password_hash) do
Argon2.verify_pass(password, password_hash)
end
def checkpw(_password, _password_hash) do
Logger.error("Password hash not recognized")
false
end
@spec maybe_update_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
do_update_password(user, password)
end
def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
do_update_password(user, password)
end
def maybe_update_password(%User{password_hash: "$pbkdf2" <> _} = user, password) do
do_update_password(user, password)
end
def maybe_update_password(user, _), do: {:ok, user}
defp do_update_password(user, password) do
User.reset_password(user, %{password: password, password_confirmation: password})
end
end

View file

@ -2277,7 +2277,7 @@ def get_ap_ids_by_nicknames(nicknames) do
defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
) do
change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
change(changeset, password_hash: Pleroma.Password.hash_pwd_salt(password))
end
defp put_password_hash(changeset), do: changeset

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.Plugs.AuthenticationPlug
import Pleroma.Web.Auth.Helpers, only: [fetch_credentials: 1, fetch_user: 1]
@ -15,8 +14,8 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do
def get_user(%Plug.Conn{} = conn) do
with {:ok, {name, password}} <- fetch_credentials(conn),
{_, %User{} = user} <- {:user, fetch_user(name)},
{_, true} <- {:checkpw, AuthenticationPlug.checkpw(password, user.password_hash)},
{:ok, user} <- AuthenticationPlug.maybe_update_password(user, password) do
{_, true} <- {:checkpw, Pleroma.Password.checkpw(password, user.password_hash)},
{:ok, user} <- Pleroma.Password.maybe_update_password(user, password) do
{:ok, user}
else
{:error, _reason} = error -> error

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.Auth.TOTPAuthenticator do
alias Pleroma.MFA
alias Pleroma.MFA.TOTP
alias Pleroma.User
alias Pleroma.Web.Plugs.AuthenticationPlug
@doc "Verify code or check backup code."
@spec verify(String.t(), User.t()) ::
@ -31,7 +30,7 @@ def verify_recovery_code(
code
)
when is_list(codes) and is_binary(code) do
hash_code = Enum.find(codes, fn hash -> AuthenticationPlug.checkpw(code, hash) end)
hash_code = Enum.find(codes, fn hash -> Pleroma.Password.checkpw(code, hash) end)
if hash_code do
MFA.invalidate_backup_code(user, hash_code)

View file

@ -17,7 +17,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI.ActivityDraft
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Plugs.AuthenticationPlug
alias Pleroma.Web.Utils.Params
require Logger
@ -356,7 +355,7 @@ defp shortname(name) do
@spec confirm_current_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, String.t()}
def confirm_current_password(user, password) do
with %User{local: true} = db_user <- User.get_cached_by_id(user.id),
true <- AuthenticationPlug.checkpw(password, db_user.password_hash) do
true <- Pleroma.Password.checkpw(password, db_user.password_hash) do
{:ok, db_user}
else
_ -> {:error, dgettext("errors", "Invalid password.")}

View file

@ -7,7 +7,6 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.Plugs.AuthenticationPlug
alias Pleroma.Web.Plugs.RateLimiter
plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password])
@ -28,7 +27,7 @@ def user_exists(conn, %{"user" => username}) do
def check_password(conn, %{"user" => username, "pass" => password}) do
with %User{password_hash: password_hash, is_active: true} <-
Repo.get_by(User, nickname: username, local: true),
true <- AuthenticationPlug.checkpw(password, password_hash) do
true <- Pleroma.Password.checkpw(password, password_hash) do
conn
|> json(true)
else

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
alias Pleroma.Helpers.AuthHelper
alias Pleroma.User
alias Pleroma.Password
import Plug.Conn
@ -25,8 +26,8 @@ def call(
} = conn,
_
) do
if checkpw(password, password_hash) do
{:ok, auth_user} = maybe_update_password(auth_user, password)
if Password.checkpw(password, password_hash) do
{:ok, auth_user} = Password.maybe_update_password(auth_user, password)
conn
|> assign(:user, auth_user)
@ -38,35 +39,6 @@ def call(
def call(conn, _), do: conn
def checkpw(password, "$6" <> _ = password_hash) do
:crypt.crypt(password, password_hash) == password_hash
end
def checkpw(password, "$2" <> _ = password_hash) do
# Handle bcrypt passwords for Mastodon migration
Bcrypt.verify_pass(password, password_hash)
end
def checkpw(password, "$pbkdf2" <> _ = password_hash) do
Pleroma.Password.Pbkdf2.verify_pass(password, password_hash)
end
def checkpw(_password, _password_hash) do
Logger.error("Password hash not recognized")
false
end
def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
do_update_password(user, password)
end
def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
do_update_password(user, password)
end
def maybe_update_password(user, _), do: {:ok, user}
defp do_update_password(user, password) do
User.reset_password(user, %{password: password, password_confirmation: password})
end
@spec checkpw(String.t(), String.t()) :: boolean
defdelegate checkpw(password, hash), to: Password
end

View file

@ -143,9 +143,7 @@ defp deps do
{:sweet_xml, "~> 0.7.2"},
{:earmark, "~> 1.4.15"},
{:bbcode_pleroma, "~> 0.2.0"},
{:crypt,
git: "https://github.com/msantos/crypt.git",
ref: "f75cd55325e33cbea198fb41fe41871392f8fb76"},
{:argon2_elixir, "~> 3.0.0"},
{:cors_plug, "~> 2.0"},
{:web_push_encryption, "~> 0.3.1"},
{:swoosh, "~> 1.0"},

View file

@ -1,4 +1,5 @@
%{
"argon2_elixir": {:hex, :argon2_elixir, "3.0.0", "fd4405f593e77b525a5c667282172dd32772d7c4fa58cdecdaae79d2713b6c5f", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "8b753b270af557d51ba13fcdebc0f0ab27a2a6792df72fd5a6cf9cfaffcedc57"},
"base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.1", "5114d780459a04f2b4aeef52307de23de961b69e13a5cd98a911e39fda13f420", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "42182d5f46764def15bf9af83739e3bf4ad22661b1c34fc3e88558efced07279"},
@ -18,7 +19,6 @@
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"},
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"credo": {:git, "https://github.com/rrrene/credo.git", "1c1b99ea41a457761383d81aaf6a606913996fe7", [ref: "1c1b99ea41a457761383d81aaf6a606913996fe7"]},
"crypt": {:git, "https://github.com/msantos/crypt.git", "f75cd55325e33cbea198fb41fe41871392f8fb76", [ref: "f75cd55325e33cbea198fb41fe41871392f8fb76"]},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},

View file

@ -30,8 +30,8 @@ test "returns backup codes" do
{:ok, [code1, code2]} = MFA.generate_backup_codes(user)
updated_user = refresh_record(user)
[hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes
assert Pleroma.Password.Pbkdf2.verify_pass(code1, hash1)
assert Pleroma.Password.Pbkdf2.verify_pass(code2, hash2)
assert Pleroma.Password.checkpw(code1, hash1)
assert Pleroma.Password.checkpw(code2, hash2)
end
end

View file

@ -0,0 +1,65 @@
defmodule Pleroma.PasswordTest do
use Pleroma.DataCase, async: true
import Pleroma.Factory
import ExUnit.CaptureLog
alias Pleroma.Password
describe "hash_pwd_salt/1" do
test "returns a hash" do
assert "$argon2id" <> _ = Password.hash_pwd_salt("test")
end
end
describe "maybe_update_password/2" do
test "with a bcrypt hash, it updates to an argon2 hash" do
user = insert(:user, password_hash: Bcrypt.hash_pwd_salt("123"))
assert "$2" <> _ = user.password_hash
{:ok, user} = Password.maybe_update_password(user, "123")
assert "$argon2" <> _ = user.password_hash
end
test "with a pbkdf2 hash, it updates to an argon2 hash" do
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("123"))
assert "$pbkdf2" <> _ = user.password_hash
{:ok, user} = Password.maybe_update_password(user, "123")
assert "$argon2" <> _ = user.password_hash
end
end
describe "checkpw/2" do
test "check pbkdf2 hash" do
hash =
"$pbkdf2-sha512$160000$loXqbp8GYls43F0i6lEfIw$AY.Ep.2pGe57j2hAPY635sI/6w7l9Q9u9Bp02PkPmF3OrClDtJAI8bCiivPr53OKMF7ph6iHhN68Rom5nEfC2A"
assert Password.checkpw("test-password", hash)
refute Password.checkpw("test-password1", hash)
end
test "check bcrypt hash" do
hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
assert Password.checkpw("password", hash)
refute Password.checkpw("password1", hash)
end
test "check argon2 hash" do
hash =
"$argon2id$v=19$m=65536,t=8,p=2$zEMMsTuK5KkL5AFWbX7jyQ$VyaQD7PF6e9btz0oH1YiAkWwIGZ7WNDZP8l+a/O171g"
assert Password.checkpw("password", hash)
refute Password.checkpw("password1", hash)
end
test "it returns false when hash invalid" do
hash =
"psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
assert capture_log(fn ->
refute Password.checkpw("password", hash)
end) =~ "[error] Password hash not recognized"
end
end
end

View file

@ -11,7 +11,7 @@ test "with HTTP Basic Auth used, grants access to OAuth scope-restricted endpoin
conn: conn
} do
user = insert(:user)
assert Pleroma.Password.Pbkdf2.verify_pass("test", user.password_hash)
assert Pleroma.Password.checkpw("test", user.password_hash)
basic_auth_contents =
(URI.encode_www_form(user.nickname) <> ":" <> URI.encode_www_form("test"))

View file

@ -15,7 +15,7 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticatorTest do
user =
insert(:user,
nickname: name,
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password)
password_hash: Pleroma.Password.hash_pwd_salt(password)
)
{:ok, [user: user, name: name, password: password]}
@ -30,7 +30,7 @@ test "get_user/authorization", %{name: name, password: password} do
assert {:ok, returned_user} = res
assert returned_user.id == user.id
assert "$pbkdf2" <> _ = returned_user.password_hash
assert "$argon2" <> _ = returned_user.password_hash
end
test "get_user/authorization with invalid password", %{name: name} do

View file

@ -34,7 +34,7 @@ test "checks backup codes" do
hashed_codes =
backup_codes
|> Enum.map(&Pleroma.Password.Pbkdf2.hash_pwd_salt(&1))
|> Enum.map(&Pleroma.Password.hash_pwd_salt(&1))
user =
insert(:user,

View file

@ -41,13 +41,13 @@ test "/user_exists", %{conn: conn} do
end
test "/check_password", %{conn: conn} do
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("cool"))
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt("cool"))
_deactivated_user =
insert(:user,
nickname: "konata",
is_active: false,
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("cool")
password_hash: Pleroma.Password.hash_pwd_salt("cool")
)
res =

View file

@ -18,7 +18,7 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do
@tag @skip
test "authorizes the existing user using LDAP credentials" do
password = "testpassword"
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password))
app = insert(:oauth_app, scopes: ["read", "write"])
host = Pleroma.Config.get([:ldap, :host]) |> to_charlist
@ -101,7 +101,7 @@ test "creates a new user after successful LDAP authorization" do
@tag @skip
test "disallow authorization for wrong LDAP credentials" do
password = "testpassword"
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password))
app = insert(:oauth_app, scopes: ["read", "write"])
host = Pleroma.Config.get([:ldap, :host]) |> to_charlist

View file

@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
backup_codes: [Pleroma.Password.Pbkdf2.hash_pwd_salt("test-code")],
backup_codes: [Pleroma.Password.hash_pwd_salt("test-code")],
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
}
)
@ -246,7 +246,7 @@ test "returns access token with valid code", %{conn: conn, app: app} do
hashed_codes =
backup_codes
|> Enum.map(&Pleroma.Password.Pbkdf2.hash_pwd_salt(&1))
|> Enum.map(&Pleroma.Password.hash_pwd_salt(&1))
user =
insert(:user,

View file

@ -316,7 +316,7 @@ test "with valid params, POST /oauth/register?op=connect redirects to `redirect_
app: app,
conn: conn
} do
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("testpassword"))
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt("testpassword"))
registration = insert(:registration, user: nil)
redirect_uri = OAuthController.default_redirect_uri(app)
@ -347,7 +347,7 @@ test "with unlisted `redirect_uri`, POST /oauth/register?op=connect results in H
app: app,
conn: conn
} do
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("testpassword"))
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt("testpassword"))
registration = insert(:registration, user: nil)
unlisted_redirect_uri = "http://cross-site-request.com"
@ -917,7 +917,7 @@ test "issues a token for an all-body request" do
test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do
password = "testpassword"
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
user = insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password))
app = insert(:oauth_app, scopes: ["read", "write"])
@ -947,7 +947,7 @@ test "issues a mfa token for `password` grant_type, when MFA enabled" do
user =
insert(:user,
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
password_hash: Pleroma.Password.hash_pwd_salt(password),
multi_factor_authentication_settings: %MFA.Settings{
enabled: true,
totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true}
@ -1056,7 +1056,7 @@ test "rejects token exchange for valid credentials belonging to unconfirmed user
password = "testpassword"
{:ok, user} =
insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password))
insert(:user, password_hash: Pleroma.Password.hash_pwd_salt(password))
|> User.confirmation_changeset(set_confirmation: false)
|> User.update_and_set_cache()
@ -1084,7 +1084,7 @@ test "rejects token exchange for valid credentials belonging to deactivated user
user =
insert(:user,
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
password_hash: Pleroma.Password.hash_pwd_salt(password),
is_active: false
)
@ -1112,7 +1112,7 @@ test "rejects token exchange for user with password_reset_pending set to true" d
user =
insert(:user,
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
password_hash: Pleroma.Password.hash_pwd_salt(password),
password_reset_pending: true
)
@ -1141,7 +1141,7 @@ test "rejects token exchange for user with confirmation_pending set to true" do
user =
insert(:user,
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
password_hash: Pleroma.Password.hash_pwd_salt(password),
is_confirmed: false
)
@ -1169,7 +1169,7 @@ test "rejects token exchange for valid credentials belonging to an unapproved us
user =
insert(:user,
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password),
password_hash: Pleroma.Password.hash_pwd_salt(password),
is_approved: false
)

View file

@ -17,7 +17,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do
user = %User{
id: 1,
name: "dude",
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("guy")
password_hash: Pleroma.Password.hash_pwd_salt("guy")
}
conn =
@ -52,7 +52,7 @@ test "with a correct password in the credentials, " <>
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
end
test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
test "with a bcrypt hash, it updates to an argon2 hash", %{conn: conn} do
user = insert(:user, password_hash: Bcrypt.hash_pwd_salt("123"))
assert "$2" <> _ = user.password_hash
@ -67,21 +67,17 @@ test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
user = User.get_by_id(user.id)
assert "$pbkdf2" <> _ = user.password_hash
assert "$argon2" <> _ = user.password_hash
end
@tag :skip_on_mac
test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
user =
insert(:user,
password_hash:
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
)
test "with a pbkdf2 hash, it updates to an argon2 hash", %{conn: conn} do
user = insert(:user, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("123"))
assert "$pbkdf2" <> _ = user.password_hash
conn =
conn
|> assign(:auth_user, user)
|> assign(:auth_credentials, %{password: "password"})
|> assign(:auth_credentials, %{password: "123"})
|> AuthenticationPlug.call(%{})
assert conn.assigns.user.id == conn.assigns.auth_user.id
@ -89,7 +85,7 @@ test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
user = User.get_by_id(user.id)
assert "$pbkdf2" <> _ = user.password_hash
assert "$argon2" <> _ = user.password_hash
end
describe "checkpw/2" do
@ -101,14 +97,6 @@ test "check pbkdf2 hash" do
refute AuthenticationPlug.checkpw("test-password1", hash)
end
@tag :skip_on_mac
test "check sha512-crypt hash" do
hash =
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
assert AuthenticationPlug.checkpw("password", hash)
end
test "check bcrypt hash" do
hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
@ -116,6 +104,14 @@ test "check bcrypt hash" do
refute AuthenticationPlug.checkpw("password1", hash)
end
test "check argon2 hash" do
hash =
"$argon2id$v=19$m=65536,t=8,p=2$zEMMsTuK5KkL5AFWbX7jyQ$VyaQD7PF6e9btz0oH1YiAkWwIGZ7WNDZP8l+a/O171g"
assert AuthenticationPlug.checkpw("password", hash)
refute AuthenticationPlug.checkpw("password1", hash)
end
test "it returns false when hash invalid" do
hash =
"psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"

View file

@ -96,7 +96,7 @@ test "it returns HTTP 200", %{conn: conn} do
assert response =~ "<h2>Password changed!</h2>"
user = refresh_record(user)
assert Pleroma.Password.Pbkdf2.verify_pass("test", user.password_hash)
assert Pleroma.Password.checkpw("test", user.password_hash)
assert Enum.empty?(Token.get_user_tokens(user))
end

View file

@ -553,7 +553,7 @@ test "with proper permissions, valid password and matching new password and conf
assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
fetched_user = User.get_cached_by_id(user.id)
assert Pleroma.Password.Pbkdf2.verify_pass("newpass", fetched_user.password_hash) == true
assert Pleroma.Password.checkpw("newpass", fetched_user.password_hash) == true
end
end

View file

@ -7,7 +7,7 @@ def build(data \\ %{}) do
email: "test@example.org",
name: "Test Name",
nickname: "testname",
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("test"),
password_hash: Pleroma.Password.hash_pwd_salt("test"),
bio: "A tester.",
ap_id: "some id",
last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second),

View file

@ -47,12 +47,16 @@ def instance_factory(attrs \\ %{}) do
def user_factory(attrs \\ %{}) do
pem = Enum.random(@rsa_keys)
# Argon2.hash_pwd_salt("test")
# it really eats CPU time, so we use a precomputed hash
password_hash =
"$argon2id$v=19$m=65536,t=8,p=2$FEAarFuiOsROO24NHIHMYw$oxdaz2fTPpuU+dYCl60FsqE65T1Tjy6lGikKfmql4xo"
user = %User{
name: sequence(:name, &"Test テスト User #{&1}"),
email: sequence(:email, &"user#{&1}@example.com"),
nickname: sequence(:nickname, &"nick#{&1}"),
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("test"),
password_hash: password_hash,
bio: sequence(:bio, &"Tester Number #{&1}"),
is_discoverable: true,
last_digest_emailed_at: NaiveDateTime.utc_now(),