Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile.PL
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ requires(
'Apache::Request' => 0,
'AppConfig' => 0,
'Clone' => 0,
'Crypt::Eksblowfish::Bcrypt' => 0,
'DBI' => 0,
'DBD::Pg' => 1.22,
'Data::ICal' => '0.16', # Data::ICal::Entry::Event
Expand Down
5 changes: 1 addition & 4 deletions lib/Act/Auth.pm
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package Act::Auth;
use strict;
use Apache::AuthCookie;
use Apache::Constants qw(OK);
use Digest::MD5 ();

use Act::Config;
use Act::User;
Expand Down Expand Up @@ -55,9 +54,7 @@ sub authen_cred ($$\@)
$user or do { $r->log_error("$prefix Unknown user"); return undef; };

# compare passwords
my $digest = Digest::MD5->new;
$digest->add(lc $sent_pw);
$digest->b64digest() eq $user->{passwd}
Act::Util::verify_password(lc $sent_pw, $user->{passwd})
or do { $r->log_error("$prefix Bad password"); return undef; };

# user is authenticated - create a session
Expand Down
7 changes: 2 additions & 5 deletions lib/Act/Handler/User/ChangePassword.pm
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use Act::Template::HTML;
use Act::User;
use Act::Util;
use Act::TwoStep;
use Digest::MD5 ();

my $form = Act::Form->new(
required => [qw(newpassword1 newpassword2)],
Expand Down Expand Up @@ -64,9 +63,7 @@ sub handler
my ($token, $token_data);
if ($Request{user}) { #
# compare passwords
my $digest = Digest::MD5->new;
$digest->add(lc $fields->{oldpassword});
$digest->b64digest() eq $Request{user}{passwd}
Act::Util::verify_password(lc $fields->{oldpassword}, $Request{user}{passwd})
or do { $ok = 0; $form->{invalid}{oldpassword} = 1; };
}
else { # must have a valid twostep token if not logged in
Expand All @@ -91,7 +88,7 @@ sub handler
}
# update user
$Request{user}->update(
passwd => Act::Util::crypt_password( $fields->{newpassword1} )
passwd => Act::Util::crypt_password( $fields->{newpassword1}, Act::Util::gen_salt() )
);

# redirect to user's main page
Expand Down
41 changes: 37 additions & 4 deletions lib/Act/Util.pm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use Apache::AuthCookie;
use DateTime::Format::Pg;
use DBI;
use Digest::MD5 ();
use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64);
use Unicode::Normalize ();
use URI::Escape ();

Expand Down Expand Up @@ -148,15 +149,47 @@ sub gen_password
{
my $clear_passwd = $pass[ rand @pass ];
$clear_passwd =~ s/([vc])/$grams{$1}[rand@{$grams{$1}}]/g;
return ($clear_passwd, crypt_password( $clear_passwd ));
return ($clear_passwd, crypt_password( $clear_passwd, gen_salt() ));
}

sub gen_salt
{
# bcrypt cost is between 1 and 31
my $cost = 10;

# salt must be 16 bytes long at most.
my $salt;
$salt .= ('.', '/', 0..9, 'A'..'Z', 'a'..'z')[rand 64] for 1..16;

# bcrypt uses a non-standard base64 encoding and cost is padded
return join('$', '', '2a', sprintf("%02d", $cost), en_base64($salt));
}

sub crypt_password
{
my $digest = Digest::MD5->new;
$digest->add(shift);
return $digest->b64digest();
my ($plaintext, $salt) = @_;

# crypt using MD5 digest for old passwords
if (!defined $salt || $salt !~ /^\$\d/) {
my $digest = Digest::MD5->new;
$digest->add($plaintext);
return $digest->b64digest();
}

# Eksblowfish
return bcrypt($plaintext, $salt)
if $salt =~ /^\$2a?\$\d+\$/;

# crypt(3)
return crypt($plaintext, $salt);
}

sub verify_password
{
my ($clear_passwd, $crypt_passwd) = @_;
return crypt_password( $clear_passwd, $crypt_passwd ) eq $crypt_passwd;
}

sub create_session
{
my $user = shift;
Expand Down
19 changes: 16 additions & 3 deletions t/05util.t
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use utf8;
use DateTime;
use Test::MockObject;
use constant NBPASS => 100;
use Test::More tests => 79 + 5 * NBPASS;
use Test::More tests => 80 + (6+2) * NBPASS;
use Act::Config;

BEGIN { use_ok('Act::Util') }
Expand Down Expand Up @@ -62,16 +62,29 @@ while (my ($u, $args, $expected) = splice(@t, 0, 3)) {
is(self_uri(%$args), $expected);
}

# gen_password
# gen_password and verify_password
my %seen;
for (1..NBPASS) {
my ($clear, $crypted) = Act::Util::gen_password();
ok($clear);
ok(!$seen{$clear}++);
ok($crypted);
like($clear, qr/^[a-z]+$/);
like($crypted, qr/^\S+$/);
like($crypted, qr/^\$\S+\$\S+$/);
ok( Act::Util::verify_password($clear, $crypted) );
}

# gen_salt
%seen = ();
for (1..NBPASS) {
my $salt = Act::Util::gen_salt();
ok($salt);
ok(!$seen{$salt}++);
}

# verify_password and crypt_password without salt
ok( Act::Util::verify_password('f00bar', Act::Util::crypt_password('f00bar')) );

# date_format
use utf8;
$Request{language} = 'fr';
Expand Down