diff --git a/debian/control b/debian/control index 5a675a109..703f9cdff 100644 --- a/debian/control +++ b/debian/control @@ -6,6 +6,7 @@ Priority: optional Build-Depends: debhelper (>= 10), po-debconf Build-Depends-Indep: libapache-session-perl , + libauth-yubikey-webclient-perl , libauthen-oath-perl , libcache-cache-perl , libclone-perl , diff --git a/lemonldap-ng-portal/t/79-2F-Yubikey.t b/lemonldap-ng-portal/t/79-2F-Yubikey.t new file mode 100644 index 000000000..d65f28272 --- /dev/null +++ b/lemonldap-ng-portal/t/79-2F-Yubikey.t @@ -0,0 +1,160 @@ +use lib 'inc'; +use Test::More; +use strict; +use IO::String; +use Plack::Request; +use JSON qw/from_json to_json/; + +require 't/test-lib.pm'; +require 't/test-yubikey.pm'; + +SKIP: { + eval "use Auth::Yubikey_WebClient"; + if ($@) { + skip 'Auth::Yubikey_WebClient not found', 0; + } + my $client = LLNG::Manager::Test->new( { + ini => { + logLevel => 'error', + yubikey2fActivation => 1, + yubikey2fClientID => "myid", + yubikey2fSecretKey => "cG9uZXk=", + yubikey2fSelfRegistration => 1, + authentication => 'Demo', + userDB => 'Same', + 'demoExportedVars' => { + 'cn' => 'cn', + 'mail' => 'mail', + 'uid' => 'uid', + '_2fDevices' => '_2fDevices', + }, + } + } + ); + + # Register ccccccdddwho as second factor of user dwho + $Lemonldap::NG::Portal::UserDB::Demo::demoAccounts{dwho}->{_2fDevices} = + to_json( [ { + "_yubikey" => "ccccccdddwho", + "epoch" => "1548016213", + "name" => "MyYubikey", + "type" => "UBK", + }, + ] + ); + + my $res; + + # Try to authenticate + # ------------------- + ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23, + accept => 'application/json', + ), + 'Auth query' + ); + count(1); + + my ( $host, $url, $query ) = + expectForm( $res, undef, '/yubikey2fcheck?skin=bootstrap', + 'token', 'code' ); + + # Authenticate with good OTP for wrong user + $query =~ s/code=/code=ccccccdddwho20000000000000000000/; + + ok( + $res = $client->_post( + '/yubikey2fcheck', + IO::String->new($query), + length => length($query), + accept => 'text/html', + ), + 'Post code' + ); + count(1); + + expectPortalError( $res, 96, "Bad OTP code" ); + + # Try to authenticate again + ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23, + accept => 'application/json', + ), + 'Auth query' + ); + count(1); + + my ( $host, $url, $query ) = + expectForm( $res, undef, '/yubikey2fcheck?skin=bootstrap', + 'token', 'code' ); + + # Authenticate with good OTP for wrong user + $query =~ s/code=/code=ccccccrtyler10000000000000000000/; + + ok( + $res = $client->_post( + '/yubikey2fcheck', + IO::String->new($query), + length => length($query), + accept => 'text/html', + ), + 'Post code' + ); + count(1); + expectPortalError( $res, 96, "Bad OTP code" ); + + # Try to authenticate again, again + ok( + $res = $client->_post( + '/', + IO::String->new('user=dwho&password=dwho'), + length => 23, + accept => 'application/json', + ), + 'Auth query' + ); + count(1); + + my ( $host, $url, $query ) = + expectForm( $res, undef, '/yubikey2fcheck?skin=bootstrap', + 'token', 'code' ); + + # Authenticate with good OTP for good user + $query =~ s/code=/code=ccccccdddwho10000000000000000000/; + + ok( + $res = $client->_post( + '/yubikey2fcheck', + IO::String->new($query), + length => length($query), + accept => 'text/html', + ), + 'Post code' + ); + count(1); + my $id = expectCookie($res); + + # This user has no UBK, the activation rule should not trigger + ok( + $res = $client->_post( + '/', + IO::String->new('user=msmith&password=msmith'), + length => 27, + accept => 'application/json', + ), + 'Auth query' + ); + count(1); + $id = expectCookie($res); + +} +clean_sessions(); + +done_testing( count() ); + diff --git a/lemonldap-ng-portal/t/test-yubikey.pm b/lemonldap-ng-portal/t/test-yubikey.pm new file mode 100644 index 000000000..e1fc780ff --- /dev/null +++ b/lemonldap-ng-portal/t/test-yubikey.pm @@ -0,0 +1,47 @@ +use LWP::Protocol::PSGI; +use MIME::Base64; +use Digest::HMAC_SHA1 qw(hmac_sha1 hmac_sha1_hex); + +# Fake yubikeyserver will succed for any OTP whose unique partbegins with 1 +# and fail when it begins with 2 +# eg of valid OTP +# cccccccccccc 10000000000000000000 +# ^ ^ +# \-token ID \- time-dependant code +# +my $fake_yubikey_server = sub { + my $req = Plack::Request->new(@_); + my $otp = $req->parameters->{otp}; + my $nonce = $req->parameters->{nonce}; + my $id = substr $otp, 0, 12; + my $unique = substr $otp, 12; + my $status; + + if ( $unique =~ /^1/ ) { + $status = "OK"; + } + + if ( $unique =~ /^2/ ) { + $status = "BAD_OTP"; + } + + my %res_without_hash = ( + status => $status, + nonce => $nonce, + otp => $otp, + ); + + my $str = join '&', + map { $_ . "=" . $res_without_hash{$_} } sort keys(%res_without_hash); + my $hmac = + encode_base64( hmac_sha1( $str, decode_base64("cG9uZXk=") ), '' ); + my %res = ( %res_without_hash, h => $hmac ); + + my $bytes = join "\r\n", map { $_ . "=" . $res{$_} } keys(%res); + return [ 200, [], [$bytes] ]; + +}; + +LWP::Protocol::PSGI->register($fake_yubikey_server); + +1;