Unverified Commit fa87dfd2 authored by Francesc Guasch's avatar Francesc Guasch Committed by GitHub
Browse files

Refactor #1208 ldap (#1210)

refactor(auth): use PBKDF2 to store encripted data in LDAP

* test(auth): check with MD5 and PBKDF2
* refactor(auth): improved LDAP support
* enabled PBKDF2 and SHA-256
* suggest bind by default
* test old rfc2307 and new format and hashing

issue #1208
parent d940e311
......@@ -1434,6 +1434,12 @@ sub _check_config($config_orig = {} , $valid_config = \%VALID_CONFIG ) {
warn "Error: Unknown config entry \n".Dumper(\%config) if ! $0 =~ /\.t$/;
return 0;
}
warn "Warning: LDAP authentication with match is discouraged. Try bind.\n"
if exists $config_orig->{ldap}
&& exists $config_orig->{ldap}->{auth}
&& $config_orig->{ldap}->{auth} =~ /match/
&& $0 !~ /\.t$/;
return 1;
}
......
......@@ -13,7 +13,10 @@ use Authen::Passphrase;
use Authen::Passphrase::SaltedDigest;
use Carp qw(carp);
use Data::Dumper;
use Digest::SHA qw(sha1_hex);
use Digest::SHA qw(sha1_hex sha256_hex);
use Encode;
use PBKDF2::Tiny qw/derive/;
use MIME::Base64;
use Moose;
use Net::LDAP;
use Net::LDAPS;
......@@ -43,6 +46,11 @@ our $STATUS_EOF = 1;
our $STATUS_DISCONNECTED = 81;
our $STATUS_BAD_FILTER = 89;
our $PBKDF2_SALT_LENGTH = 64;
our $PBKDF2_ITERATIONS_LENGTH = 4;
our $PBKDF2_HASH_LENGTH = 256;
our $PBKDF2_LENGTH = $PBKDF2_SALT_LENGTH + $PBKDF2_ITERATIONS_LENGTH + $PBKDF2_HASH_LENGTH;
=head2 BUILD
Internal OO build
......@@ -64,8 +72,7 @@ Adds a new user in the LDAP directory
=cut
sub add_user {
my ($name, $password, $is_admin) = @_;
sub add_user($name, $password, $storage='rfc2307', $algorithm=undef ) {
_init_ldap_admin();
......@@ -76,8 +83,6 @@ sub add_user {
if !_dc_base();
my ($givenName, $sn) = $name =~ m{(\w+)\.(.*)};
my $apr=Authen::Passphrase::SaltedDigest->new(passphrase => $password, algorithm => "MD5");
my %entry = (
cn => $name
, uid => $name
......@@ -87,7 +92,7 @@ sub add_user {
, givenName => ($givenName or $name)
, sn => ($sn or $name)
# , homeDirectory => "/home/$name"
,userPassword => $apr->as_rfc2307()
,userPassword => _password_store($password, $storage, $algorithm)
);
my $dn = "cn=$name,"._dc_base();
......@@ -97,6 +102,51 @@ sub add_user {
}
}
sub _password_store($password, $storage, $algorithm) {
return _password_rfc2307($password, $algorithm) if lc($storage) eq 'rfc2307';
return _password_pbkdf2($password, $algorithm) if lc($storage) eq 'pbkdf2';
confess "Error: Unknown storage '$storage'";
}
sub _password_pbkdf2($password, $algorithm='SHA-256') {
$algorithm = 'SHA-256' if ! defined $algorithm;
my $salt = encode('ascii',Ravada::Utils::random_name($PBKDF2_SALT_LENGTH));
die "wrong salt length ".length($salt)." != $PBKDF2_SALT_LENGTH"
if length($salt) != $PBKDF2_SALT_LENGTH;
my $iterations = 1024;
my $derive = derive($algorithm
, encode('ascii',$password)
, $salt
, $iterations
, $PBKDF2_HASH_LENGTH);
my $iterations_n = pack('N', $iterations);
die "wrong iterations length ".length($iterations_n)." != $PBKDF2_ITERATIONS_LENGTH"
if length($iterations_n) != $PBKDF2_ITERATIONS_LENGTH;
my $pbkdf2 = $iterations_n.$salt.$derive;
die "wrong pass length ".length($pbkdf2)." != $PBKDF2_LENGTH"
if length($pbkdf2) != $PBKDF2_LENGTH;
$algorithm =~ s/-//;
return "\{PBKDF2_$algorithm}"
.encode_base64($pbkdf2,"");
}
sub _password_rfc2307($password, $algorithm='MD5') {
my $apr=Authen::Passphrase::SaltedDigest->new(passphrase => $password
, algorithm => ($algorithm or 'MD5'));
return $apr->as_rfc2307();
}
=head2 remove_user
Removes the user
......@@ -348,10 +398,11 @@ sub login($self) {
}
$user_ok = $self->_login_bind()
if !exists $$CONFIG->{ldap}->{auth}
|| !$$CONFIG->{ldap}->{auth}
|| $$CONFIG->{ldap}->{auth} =~ /bind|all/i;
$user_ok = $self->_login_match() if !$user_ok;
if !exists $$CONFIG->{ldap}->{auth} || $$CONFIG->{ldap}->{auth} =~ /bind|all/i;
$user_ok = $self->_login_match()
if !$user_ok && exists $$CONFIG->{ldap}->{auth}
&& $$CONFIG->{ldap}->{auth} =~ /match|all/i;
$self->_check_user_profile($self->name) if $user_ok;
$LDAP_ADMIN->unbind if $LDAP_ADMIN && exists $self->{_auth} && $self->{_auth} eq 'bind';
......@@ -454,7 +505,47 @@ sub _match_password {
# ."\n"
# .sha1_hex($password);
return Authen::Passphrase->from_rfc2307($password_ldap)->match($password);
my ($storage) = $password_ldap =~ /^{([a-z0-9]+)[_}]/i;
my ($password_ldap_hex) = $password_ldap =~ /.*?}(.*)/;
return Authen::Passphrase->from_rfc2307($password_ldap)->match($password)
if $storage =~ /rfc2307|md5/i;
return _match_pbkdf2($password_ldap,$password) if $storage =~ /pbkdf2|SSHA/i;
confess "Error: storage $storage can't do match. Use bind.";
}
sub _ntohl {
return unless defined wantarray;
confess "Wrong number of arguments ($#_) to " . __PACKAGE__ . "::ntohl, called"
if @_ != 1 and !wantarray;
unpack('L*', pack('N*', @_));
}
sub _match_pbkdf2($password_db_64, $password) {
my ($sign,$password_db) = $password_db_64 =~ /(\{.*?})(.*)/;
$password_db=decode_base64($password_db);
my ($algorithm,$n) = $sign =~ /_(.*?)(\d+)}/;
die "password_db length wrong: ".length($password_db)
." != $PBKDF2_LENGTH"
if length($password_db) != $PBKDF2_LENGTH;
my ($iterations_db) = substr($password_db, 0, $PBKDF2_ITERATIONS_LENGTH);
my $iterations = unpack 'V', $iterations_db;
($iterations) = _ntohl($iterations);
my ($salt)
= substr($password_db, $PBKDF2_ITERATIONS_LENGTH, $PBKDF2_SALT_LENGTH);
my $derive = derive("$algorithm-$n", encode('ascii',$password), $salt
, $iterations, $PBKDF2_HASH_LENGTH);
my $match = $sign.encode_base64($iterations_db.$salt.$derive,"");
return $password_db_64 eq $match;
}
sub _dc_base {
......
......@@ -47,11 +47,7 @@ sub _remove_user_ldap($name) {
}
}
sub test_user{
my $name = (shift or 'jimmy.mcnulty');
my $with_posix_group = ( shift or 0);
my $password = 'jameson';
sub test_user($name, $with_posix_group=0, $password='jameson', $storage=undef, $algorithm=undef) {
if ( Ravada::Auth::LDAP::search_user($name) ) {
diag("Removing $name");
Ravada::Auth::LDAP::remove_user($name)
......@@ -72,7 +68,11 @@ sub test_user{
ok(!$row->{name},"I shouldn't find $name in the SQL db ".Dumper($row));
eval { Ravada::Auth::LDAP::add_user($name,$password) };
my @options;
push @options, ( $storage ) if defined $storage;
push @options, ( $algorithm ) if defined $algorithm;
eval { Ravada::Auth::LDAP::add_user($name,$password, @options) };
push @USERS,($name);
ok(!$@, $@) or return;
......@@ -155,6 +155,7 @@ sub remove_users {
sub test_add_group {
Ravada::Auth::LDAP::init();
my $name = "grup.test";
Ravada::Auth::LDAP::remove_group($name)
......@@ -202,6 +203,7 @@ sub test_manage_group {
ok(!$@,$@);
ok(!$is_admin,"User $uid should not be admin");
Ravada::Auth::LDAP::init();
Ravada::Auth::LDAP::add_to_group($uid, $name);
if ($with_admin) {
......@@ -492,6 +494,35 @@ sub test_login_fields($data) {
}
}
sub test_pass_storage($with_posix_group) {
my %data = (
rfc2307 => 'MD5'
,PBKDF2 => 'SHA-256'
);
for my $storage ( keys %data ) {
for my $algorithm ( undef, $data{$storage} ) {
my $name = "tst_".lc($storage)."_".lc($algorithm or 'none');
my @args = ( $name, $with_posix_group, $$, $storage);
push @args, ($algorithm) if $algorithm;
Ravada::Auth::LDAP::init();
my $user = test_user(@args);
my $sign = $storage;
$sign = $data{$storage} if $sign eq 'rfc2307';
like($user->{_ldap_entry}->get_value('userPassword'), qr/^{$sign/);
$user->_login_match();
$user->_login_bind();
$user->_login_match();
$user->_login_bind();
_remove_user_ldap($name);
}
}
Ravada::Auth::LDAP::init();
}
SKIP: {
test_filter();
my $file_config = "t/etc/ravada_ldap.conf";
......@@ -524,6 +555,8 @@ SKIP: {
ok($ldap) and do {
test_user_fail();
test_pass_storage($with_posix_group);
my $user = test_user( 'pepe.mcnulty', $with_posix_group );
test_add_group();
......@@ -532,7 +565,6 @@ SKIP: {
test_user_bind($user, $fly_config, $with_posix_group);
remove_users();
};
unlink($fly_config) if -e $fly_config;
......
......@@ -4,6 +4,5 @@ ldap:
admin_user:
dn: cn=Directory Manager
password: 12345678
auth: match
base: 'dc=example,dc=com'
ravada_posix_group: rvd_posix_group
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment