lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/U2F.pm

265 lines
7.8 KiB
Perl
Raw Normal View History

2017-02-04 08:55:47 +01:00
# U2F second factor authentication
#
# This plugin handle authentications to ask U2F second factor for users that
# have registered their U2F key
package Lemonldap::NG::Portal::2F::U2F;
2017-02-02 22:48:32 +01:00
use strict;
use Mouse;
2018-04-10 11:06:06 +02:00
use JSON qw(from_json to_json);
use MIME::Base64 qw(decode_base64url);
2017-02-07 13:52:56 +01:00
use Lemonldap::NG::Portal::Main::Constants qw(
PE_OK
2020-10-31 23:43:08 +01:00
PE_ERROR
PE_U2FFAILED
2020-10-31 23:43:08 +01:00
PE_SENDRESPONSE
PE_BADCREDENTIALS
2017-02-07 13:52:56 +01:00
);
2017-02-02 22:48:32 +01:00
2021-06-28 15:36:29 +02:00
our $VERSION = '2.0.12';
2017-02-02 22:48:32 +01:00
2020-10-31 23:43:08 +01:00
extends qw(
Lemonldap::NG::Portal::Main::SecondFactor
Lemonldap::NG::Portal::Lib::U2F
);
2017-02-04 08:55:47 +01:00
# INITIALIZATION
has rule => ( is => 'rw' );
2017-03-23 12:17:01 +01:00
has prefix => ( is => 'ro', default => 'u' );
has logo => ( is => 'rw', default => 'u2f.png' );
2018-03-09 16:51:15 +01:00
2017-02-02 22:48:32 +01:00
sub init {
2017-02-04 08:55:47 +01:00
my ($self) = @_;
2018-02-21 06:28:42 +01:00
2018-08-18 18:29:45 +02:00
# If self registration is enabled and "activation" is just set to
2018-04-06 16:38:07 +02:00
# "enabled", replace the rule to detect if user has registered its key
2018-02-21 06:28:42 +01:00
if ( $self->conf->{u2fSelfRegistration}
and $self->conf->{u2fActivation} eq '1' )
{
2018-09-02 17:31:58 +02:00
$self->conf->{u2fActivation} =
'$_2fDevices && $_2fDevices =~ /"type":\s*"U2F"/s';
}
2017-03-23 12:17:01 +01:00
return 0
unless ( $self->Lemonldap::NG::Portal::Main::SecondFactor::init()
and $self->Lemonldap::NG::Portal::Lib::U2F::init() );
2020-02-16 22:42:10 +01:00
2017-02-02 22:48:32 +01:00
1;
}
2017-02-04 08:55:47 +01:00
# RUNNING METHODS
# Main method
sub run {
2017-03-23 12:17:01 +01:00
my ( $self, $req, $token ) = @_;
2018-06-12 17:56:19 +02:00
my $checkLogins = $req->param('checkLogins');
$self->logger->debug("U2F: checkLogins set") if $checkLogins;
my $stayconnected = $req->param('stayconnected');
$self->logger->debug("U2F: stayconnected set") if $stayconnected;
2017-03-23 07:20:06 +01:00
2017-02-07 13:52:56 +01:00
# Check if user is registered
if ( my $res = $self->loadUser( $req, $req->sessionInfo ) ) {
2018-03-18 20:52:50 +01:00
return PE_ERROR if ( $res == -1 );
return PE_U2FFAILED if ( $res == 0 );
2017-02-07 13:52:56 +01:00
2018-04-17 18:08:30 +02:00
# Get a challenge (from first key)
my $data = eval {
from_json( $req->data->{crypter}->[0]->authenticationChallenge );
2018-04-17 18:08:30 +02:00
};
2018-04-17 21:48:16 +02:00
2018-04-17 18:08:30 +02:00
if ($@) {
$self->logger->error( Crypt::U2F::Server::u2fclib_getError() );
return PE_ERROR;
}
# Get registered keys
2020-02-16 22:42:10 +01:00
my @rk =
map { { keyHandle => $_->{keyHandle}, version => $data->{version} } }
@{ $req->data->{crypter} };
2018-04-17 18:08:30 +02:00
2018-04-17 21:36:34 +02:00
$self->ott->updateToken( $token, __ch => $data->{challenge} );
2018-09-02 17:31:58 +02:00
$self->logger->debug("Prepare U2F verification");
$self->logger->debug( " -> Send challenge: " . $data->{challenge} );
# Serialize data
2019-02-07 09:27:56 +01:00
$data = to_json( {
2018-04-17 18:08:30 +02:00
challenge => $data->{challenge},
appId => $data->{appId},
registeredKeys => \@rk
}
);
my $tmp = $self->p->sendHtml(
$req,
'u2fcheck',
params => {
MAIN_LOGO => $self->conf->{portalMainLogo},
SKIN => $self->p->getSkin($req),
DATA => $data,
TOKEN => $token,
CHECKLOGINS => $checkLogins,
STAYCONNECTED => $stayconnected
}
);
$req->response($tmp);
return PE_SENDRESPONSE;
2017-02-07 13:52:56 +01:00
}
return PE_U2FFAILED;
2017-02-07 13:52:56 +01:00
}
sub verify {
2017-03-23 12:17:01 +01:00
my ( $self, $req, $session ) = @_;
2020-10-01 21:49:00 +02:00
my $crypter;
2017-03-22 23:18:28 +01:00
# Check U2F signature
2018-03-06 22:50:40 +01:00
if ( my $resp = $req->param('signature')
and my $challenge = $req->param('challenge') )
{
unless ( $self->loadUser( $req, $session ) == 1 ) {
$req->error(PE_ERROR);
return $self->fail($req);
2017-02-07 13:52:56 +01:00
}
2018-03-13 07:14:01 +01:00
2018-04-10 16:15:14 +02:00
$self->logger->debug("Get challenge: $challenge");
2018-04-17 21:36:34 +02:00
unless ( $session->{__ch} and $session->{__ch} eq $challenge ) {
$self->userLogger->error(
"U2F challenge changed by user: $session->{__ch} / $challenge");
2018-04-17 21:36:34 +02:00
$req->error(PE_BADCREDENTIALS);
return $self->fail($req);
}
delete $session->{__ch};
2018-04-17 18:08:30 +02:00
$self->logger->debug("Get signature: $resp");
my $data = eval { JSON::from_json($resp) };
if ($@) {
$self->logger->error("U2F response error: $@");
$req->error(PE_ERROR);
return $self->fail($req);
}
2020-10-01 21:49:00 +02:00
$crypter = $_
foreach grep { $_->{keyHandle} eq $data->{keyHandle} }
@{ $req->data->{crypter} };
2018-04-17 18:08:30 +02:00
unless ($crypter) {
$self->userLogger->error("Unregistered U2F key");
$req->error(PE_BADCREDENTIALS);
return $self->fail($req);
}
2018-04-17 18:08:30 +02:00
if ( not $crypter->setChallenge($challenge) ) {
2018-03-06 22:50:40 +01:00
$self->logger->error(
$@ ? $@ : Crypt::U2F::Server::Simple::lastError() );
$req->error(PE_ERROR);
return $self->fail($req);
}
2018-04-17 18:08:30 +02:00
if ( $crypter->authenticationVerify($resp) ) {
2017-03-23 12:17:01 +01:00
$self->userLogger->info('U2F signature verified');
return PE_OK;
2017-02-07 13:52:56 +01:00
}
else {
$self->userLogger->notice( 'Invalid U2F signature for '
2017-03-23 12:17:01 +01:00
. $session->{ $self->conf->{whatToTrace} } . ' ('
. Crypt::U2F::Server::u2fclib_getError()
. ')' );
$req->error(PE_U2FFAILED);
return $self->fail($req);
2017-02-07 13:52:56 +01:00
}
}
else {
2018-03-06 22:50:40 +01:00
$self->userLogger->notice( 'No valid U2F response for user'
2017-03-23 12:17:01 +01:00
. $session->{ $self->conf->{whatToTrace} } );
2017-02-20 22:59:31 +01:00
$req->authResult(PE_U2FFAILED);
return $self->fail($req);
2017-02-07 13:52:56 +01:00
}
}
sub fail {
my ( $self, $req ) = @_;
2017-03-23 12:17:01 +01:00
$req->response(
$self->p->sendHtml(
$req,
'u2fcheck',
params => {
2018-10-12 19:41:13 +02:00
MAIN_LOGO => $self->conf->{portalMainLogo},
2017-03-23 12:17:01 +01:00
AUTH_ERROR => $req->error,
AUTH_ERROR_TYPE => $req->error_type,
AUTH_ERROR_ROLE => $req->error_role,
2019-06-28 15:56:57 +02:00
SKIN => $self->p->getSkin($req),
2017-03-23 12:17:01 +01:00
FAILED => 1
}
)
);
2017-03-23 12:17:01 +01:00
return PE_SENDRESPONSE;
}
2017-02-07 13:52:56 +01:00
sub loadUser {
my ( $self, $req, $session ) = @_;
2018-04-12 14:20:28 +02:00
my ( $kh, $uk, $_2fDevices );
my @u2fs = ();
2018-04-10 16:15:14 +02:00
2018-04-12 14:20:28 +02:00
if ( $session->{_2fDevices} ) {
2018-04-10 16:15:14 +02:00
$self->logger->debug("Loading 2F Devices ...");
# Read existing 2FDevices
2018-04-12 14:20:28 +02:00
$_2fDevices =
eval { from_json( $session->{_2fDevices}, { allow_nonref => 1 } ); };
if ($@) {
$self->logger->error("Bad encoding in _2fDevices: $@");
return PE_ERROR;
}
$self->logger->debug("2F Device(s) found");
2018-09-02 17:31:58 +02:00
2018-08-21 17:37:14 +02:00
$self->logger->debug("Looking for registered U2F key(s) ...");
2018-04-12 14:20:28 +02:00
foreach (@$_2fDevices) {
if ( $_->{type} eq 'U2F' ) {
2018-04-20 16:15:26 +02:00
unless ( $_->{_userKey} and $_->{_keyHandle} ) {
2018-04-17 18:08:30 +02:00
$self->logger->error(
"Missing required U2F attributes in storage ($session->{_2fDevices})"
2018-04-17 18:08:30 +02:00
);
next;
}
2018-04-17 21:36:34 +02:00
$self->logger->debug( "Found U2F key -> _userKey = "
. $_->{_userKey}
2018-08-21 17:37:14 +02:00
. " / _keyHandle = "
2018-04-17 21:36:34 +02:00
. $_->{_keyHandle} );
$_->{_userKey} = decode_base64url( $_->{_userKey} );
2018-04-12 14:20:28 +02:00
push @u2fs, $_;
}
2018-04-10 11:06:06 +02:00
}
2018-04-10 16:15:14 +02:00
}
2018-04-17 21:48:16 +02:00
# Manage multi u2f keys
2018-04-17 18:08:30 +02:00
my @crypters;
if (@u2fs) {
2018-08-21 17:37:14 +02:00
$self->logger->debug("Generating crypter(s) with uk & kh");
2018-04-17 18:08:30 +02:00
foreach (@u2fs) {
$kh = $_->{_keyHandle};
$uk = $_->{_userKey};
2018-08-21 17:37:14 +02:00
$self->logger->debug("Append crypter with kh -> $kh");
2018-04-17 18:08:30 +02:00
my $c = $self->crypter( keyHandle => $kh, publicKey => $uk );
if ($c) {
push @crypters, $c;
}
else {
$self->logger->error(
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
}
}
2020-10-01 21:49:00 +02:00
return -1 unless @crypters;
2020-02-16 22:42:10 +01:00
$req->data->{crypter} = \@crypters;
2017-02-07 13:52:56 +01:00
return 1;
}
else {
$self->userLogger->info("U2F : user not registered");
return 0;
2017-02-07 13:52:56 +01:00
}
2017-02-04 08:55:47 +01:00
}
2017-02-02 22:48:32 +01:00
1;