argon2 password hashing (#406)

Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/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` - Prometheus metrics exporting from `/api/v1/akkoma/metrics`
- Ability to alter http pool size - Ability to alter http pool size
- Translation of statuses via ArgosTranslate - Translation of statuses via ArgosTranslate
- Argon2 password hashing
- Ability to "verify" links in profile fields via rel=me - Ability to "verify" links in profile fields via rel=me
- Mix tasks to dump/load config to/from json for bulk editing - 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 - Non-finch HTTP adapters
- Legacy redirect from /api/pleroma/admin to /api/v1/pleroma/admin - Legacy redirect from /api/pleroma/admin to /api/v1/pleroma/admin
- Legacy redirects from /api/pleroma to /api/v1/pleroma - Legacy redirects from /api/pleroma to /api/v1/pleroma
- :crypt dependency
### Changed ### Changed
- Return HTTP error 413 when uploading an avatar or banner that's above the configured upload limit instead of a 500. - Return HTTP error 413 when uploading an avatar or banner that's above the configured upload limit instead of a 500.

View file

@ -87,10 +87,12 @@ def run(["dump_to_file", group, key, fname]) do
key = maybe_atomize(key) key = maybe_atomize(key)
config = ConfigDB.get_by_group_and_key(group, key) config = ConfigDB.get_by_group_and_key(group, key)
json = %{
json =
%{
group: ConfigDB.to_json_types(config.group), group: ConfigDB.to_json_types(config.group),
key: ConfigDB.to_json_types(config.key), key: ConfigDB.to_json_types(config.key),
value: ConfigDB.to_json_types(config.value), value: ConfigDB.to_json_types(config.value)
} }
|> Jason.encode!() |> Jason.encode!()
|> Jason.Formatter.pretty_print() |> Jason.Formatter.pretty_print()

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( defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
) do ) do
change(changeset, password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt(password)) change(changeset, password_hash: Pleroma.Password.hash_pwd_salt(password))
end end
defp put_password_hash(changeset), do: changeset defp put_password_hash(changeset), do: changeset

View file

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

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.Auth.TOTPAuthenticator do
alias Pleroma.MFA alias Pleroma.MFA
alias Pleroma.MFA.TOTP alias Pleroma.MFA.TOTP
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.Plugs.AuthenticationPlug
@doc "Verify code or check backup code." @doc "Verify code or check backup code."
@spec verify(String.t(), User.t()) :: @spec verify(String.t(), User.t()) ::
@ -31,7 +30,7 @@ def verify_recovery_code(
code code
) )
when is_list(codes) and is_binary(code) do 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 if hash_code do
MFA.invalidate_backup_code(user, hash_code) 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.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI.ActivityDraft alias Pleroma.Web.CommonAPI.ActivityDraft
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
alias Pleroma.Web.Plugs.AuthenticationPlug
alias Pleroma.Web.Utils.Params alias Pleroma.Web.Utils.Params
require Logger require Logger
@ -356,7 +355,7 @@ defp shortname(name) do
@spec confirm_current_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, String.t()} @spec confirm_current_password(User.t(), String.t()) :: {:ok, User.t()} | {:error, String.t()}
def confirm_current_password(user, password) do def confirm_current_password(user, password) do
with %User{local: true} = db_user <- User.get_cached_by_id(user.id), 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} {:ok, db_user}
else else
_ -> {:error, dgettext("errors", "Invalid password.")} _ -> {:error, dgettext("errors", "Invalid password.")}

View file

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

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do
alias Pleroma.Helpers.AuthHelper alias Pleroma.Helpers.AuthHelper
alias Pleroma.User alias Pleroma.User
alias Pleroma.Password
import Plug.Conn import Plug.Conn
@ -25,8 +26,8 @@ def call(
} = conn, } = conn,
_ _
) do ) do
if checkpw(password, password_hash) do if Password.checkpw(password, password_hash) do
{:ok, auth_user} = maybe_update_password(auth_user, password) {:ok, auth_user} = Password.maybe_update_password(auth_user, password)
conn conn
|> assign(:user, auth_user) |> assign(:user, auth_user)
@ -38,35 +39,6 @@ def call(
def call(conn, _), do: conn def call(conn, _), do: conn
def checkpw(password, "$6" <> _ = password_hash) do @spec checkpw(String.t(), String.t()) :: boolean
:crypt.crypt(password, password_hash) == password_hash defdelegate checkpw(password, hash), to: Password
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
end end

View file

@ -143,9 +143,7 @@ defp deps do
{:sweet_xml, "~> 0.7.2"}, {:sweet_xml, "~> 0.7.2"},
{:earmark, "~> 1.4.15"}, {:earmark, "~> 1.4.15"},
{:bbcode_pleroma, "~> 0.2.0"}, {:bbcode_pleroma, "~> 0.2.0"},
{:crypt, {:argon2_elixir, "~> 3.0.0"},
git: "https://github.com/msantos/crypt.git",
ref: "f75cd55325e33cbea198fb41fe41871392f8fb76"},
{:cors_plug, "~> 2.0"}, {:cors_plug, "~> 2.0"},
{:web_push_encryption, "~> 0.3.1"}, {:web_push_encryption, "~> 0.3.1"},
{:swoosh, "~> 1.0"}, {: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"}, "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"}, "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"}, "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"}, "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"}, "cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
"credo": {:git, "https://github.com/rrrene/credo.git", "1c1b99ea41a457761383d81aaf6a606913996fe7", [ref: "1c1b99ea41a457761383d81aaf6a606913996fe7"]}, "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"}, "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"}, "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"}, "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) {:ok, [code1, code2]} = MFA.generate_backup_codes(user)
updated_user = refresh_record(user) updated_user = refresh_record(user)
[hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes [hash1, hash2] = updated_user.multi_factor_authentication_settings.backup_codes
assert Pleroma.Password.Pbkdf2.verify_pass(code1, hash1) assert Pleroma.Password.checkpw(code1, hash1)
assert Pleroma.Password.Pbkdf2.verify_pass(code2, hash2) assert Pleroma.Password.checkpw(code2, hash2)
end end
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 conn: conn
} do } do
user = insert(:user) user = insert(:user)
assert Pleroma.Password.Pbkdf2.verify_pass("test", user.password_hash) assert Pleroma.Password.checkpw("test", user.password_hash)
basic_auth_contents = basic_auth_contents =
(URI.encode_www_form(user.nickname) <> ":" <> URI.encode_www_form("test")) (URI.encode_www_form(user.nickname) <> ":" <> URI.encode_www_form("test"))

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.MFAControllerTest do
insert(:user, insert(:user,
multi_factor_authentication_settings: %MFA.Settings{ multi_factor_authentication_settings: %MFA.Settings{
enabled: true, 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} 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 = hashed_codes =
backup_codes backup_codes
|> Enum.map(&Pleroma.Password.Pbkdf2.hash_pwd_salt(&1)) |> Enum.map(&Pleroma.Password.hash_pwd_salt(&1))
user = user =
insert(:user, insert(:user,

View file

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

View file

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

View file

@ -96,7 +96,7 @@ test "it returns HTTP 200", %{conn: conn} do
assert response =~ "<h2>Password changed!</h2>" assert response =~ "<h2>Password changed!</h2>"
user = refresh_record(user) 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)) assert Enum.empty?(Token.get_user_tokens(user))
end 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"} assert json_response_and_validate_schema(conn, 200) == %{"status" => "success"}
fetched_user = User.get_cached_by_id(user.id) 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
end end

View file

@ -7,7 +7,7 @@ def build(data \\ %{}) do
email: "test@example.org", email: "test@example.org",
name: "Test Name", name: "Test Name",
nickname: "testname", nickname: "testname",
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("test"), password_hash: Pleroma.Password.hash_pwd_salt("test"),
bio: "A tester.", bio: "A tester.",
ap_id: "some id", ap_id: "some id",
last_digest_emailed_at: NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second), 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 def user_factory(attrs \\ %{}) do
pem = Enum.random(@rsa_keys) 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{ user = %User{
name: sequence(:name, &"Test テスト User #{&1}"), name: sequence(:name, &"Test テスト User #{&1}"),
email: sequence(:email, &"user#{&1}@example.com"), email: sequence(:email, &"user#{&1}@example.com"),
nickname: sequence(:nickname, &"nick#{&1}"), nickname: sequence(:nickname, &"nick#{&1}"),
password_hash: Pleroma.Password.Pbkdf2.hash_pwd_salt("test"), password_hash: password_hash,
bio: sequence(:bio, &"Tester Number #{&1}"), bio: sequence(:bio, &"Tester Number #{&1}"),
is_discoverable: true, is_discoverable: true,
last_digest_emailed_at: NaiveDateTime.utc_now(), last_digest_emailed_at: NaiveDateTime.utc_now(),