Users: transparently upgrade passwords to Argon2

Passwords that are sha1 will be transparently upgraded to argon2,
and future comparisons will use Argon2

Co-authored-by: Graham Christensen <graham@grahamc.com>
This commit is contained in:
Graham Christensen 2021-04-15 10:55:40 -04:00 committed by Graham Christensen
parent 29620df85e
commit 1da70030b7
3 changed files with 83 additions and 5 deletions

View file

@ -70,6 +70,47 @@
}; };
}; };
CryptArgon2 = final.perlPackages.buildPerlModule {
pname = "Crypt-Argon2";
version = "0.010";
src = final.fetchurl {
url = "mirror://cpan/authors/id/L/LE/LEONT/Crypt-Argon2-0.010.tar.gz";
sha256 = "3ea1c006f10ef66fd417e502a569df15c4cc1c776b084e35639751c41ce6671a";
};
nativeBuildInputs = [ pkgs.ld-is-cc-hook ];
meta = {
description = "Perl interface to the Argon2 key derivation functions";
license = final.lib.licenses.cc0;
};
};
CryptPassphrase = final.buildPerlPackage {
pname = "Crypt-Passphrase";
version = "0.003";
src = final.fetchurl {
url = "mirror://cpan/authors/id/L/LE/LEONT/Crypt-Passphrase-0.003.tar.gz";
sha256 = "685aa090f8179a86d6896212ccf8ccfde7a79cce857199bb14e2277a10d240ad";
};
meta = {
description = "A module for managing passwords in a cryptographically agile manner";
license = with final.lib.licenses; [ artistic1 gpl1Plus ];
};
};
CryptPassphraseArgon2 = final.buildPerlPackage {
pname = "Crypt-Passphrase-Argon2";
version = "0.002";
src = final.fetchurl {
url = "mirror://cpan/authors/id/L/LE/LEONT/Crypt-Passphrase-Argon2-0.002.tar.gz";
sha256 = "3906ff81697d13804ee21bd5ab78ffb1c4408b4822ce020e92ecf4737ba1f3a8";
};
propagatedBuildInputs = with final.perlPackages; [ CryptArgon2 CryptPassphrase ];
meta = {
description = "An Argon2 encoder for Crypt::Passphrase";
license = with final.lib.licenses; [ artistic1 gpl1Plus ];
};
};
DirSelf = final.buildPerlPackage { DirSelf = final.buildPerlPackage {
pname = "Dir-Self"; pname = "Dir-Self";
version = "0.11"; version = "0.11";
@ -267,6 +308,8 @@
CatalystViewTT CatalystViewTT
CatalystXScriptServerStarman CatalystXScriptServerStarman
CatalystXRoleApplicator CatalystXRoleApplicator
CryptPassphrase
CryptPassphraseArgon2
CryptRandPasswd CryptRandPasswd
DBDPg DBDPg
DBDSQLite DBDSQLite

View file

@ -195,6 +195,7 @@ __PACKAGE__->many_to_many("projects", "projectmembers", "project");
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2020-02-06 12:22:36 # Created by DBIx::Class::Schema::Loader v0.07049 @ 2020-02-06 12:22:36
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:4/WZ95asbnGmK+nEHb4sLQ # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:4/WZ95asbnGmK+nEHb4sLQ
use Crypt::Passphrase;
use Digest::SHA1 qw(sha1_hex); use Digest::SHA1 qw(sha1_hex);
use String::Compare::ConstantTime; use String::Compare::ConstantTime;
@ -216,7 +217,28 @@ sub json_hint {
sub check_password { sub check_password {
my ($self, $password) = @_; my ($self, $password) = @_;
return String::Compare::ConstantTime::equals($self->password, sha1_hex($password)); my $authenticator = Crypt::Passphrase->new(
encoder => 'Argon2',
validators => [
(sub {
my ($password, $hash) = @_;
return String::Compare::ConstantTime::equals($hash, sha1_hex($password));
})
],
);
if ($authenticator->verify_password($password, $self->password)) {
if ($authenticator->needs_rehash($self->password)) {
$self->update({
"password" => $authenticator->hash_password($password),
});
}
return 1;
} else {
return 0;
}
} }
1; 1;

View file

@ -11,11 +11,20 @@ use Test2::V0;
my $db = Hydra::Model::DB->new; my $db = Hydra::Model::DB->new;
hydra_setup($db); hydra_setup($db);
# Catalyst's default password checking is not constant time. To improve # Hydra used to store passwords, by default, as plain unsalted sha1 hashes.
# the security of the system, we replaced the check password routine. # We now upgrade these badly stored passwords with much stronger algorithms
# Verify comparing correct and incorrect passwords work. # when the user logs in. Implementing this meant reimplementing our password
# checking ourselves, so also ensure that basic password checking works.
#
# This test:
#
# 1. creates a user with the legacy password
# 2. validates that the wrong password is not considered valid
# 3. validates that the correct password is valid
# 4. checks that the checking of the correct password transparently upgraded
# the password's storage to a more secure algorithm.
# Starting the user with a sha1 password # Starting the user with an unsalted sha1 password
my $user = $db->resultset('Users')->create({ my $user = $db->resultset('Users')->create({
"username" => "alice", "username" => "alice",
"emailaddress" => 'alice@nixos.org', "emailaddress" => 'alice@nixos.org',
@ -24,6 +33,10 @@ my $user = $db->resultset('Users')->create({
isnt($user, undef, "My user was created."); isnt($user, undef, "My user was created.");
ok(!$user->check_password("barbaz"), "Checking the password, barbaz, is not right"); ok(!$user->check_password("barbaz"), "Checking the password, barbaz, is not right");
is($user->password, "8843d7f92416211de9ebb963ff4ce28125932878", "The unsalted sha1 is in the database.");
ok($user->check_password("foobar"), "Checking the password, foobar, is right"); ok($user->check_password("foobar"), "Checking the password, foobar, is right");
isnt($user->password, "8843d7f92416211de9ebb963ff4ce28125932878", "The user has had their password rehashed.");
ok($user->check_password("foobar"), "Checking the password, foobar, is still right");
done_testing; done_testing;