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
|
2017-02-02 22:48:32 +01:00
|
|
|
package Lemonldap::NG::Portal::Plugins::U2F;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
2017-02-07 13:52:56 +01:00
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
|
|
|
PE_ERROR
|
2017-02-07 23:04:49 +01:00
|
|
|
PE_NOTOKEN
|
2017-02-07 13:52:56 +01:00
|
|
|
PE_OK
|
2017-02-07 23:04:49 +01:00
|
|
|
PE_SENDRESPONSE
|
|
|
|
PE_TOKENEXPIRED
|
|
|
|
PE_U2FFAILED
|
2017-02-07 13:52:56 +01:00
|
|
|
);
|
2017-02-02 22:48:32 +01:00
|
|
|
|
|
|
|
our $VERSION = '2.0.0';
|
|
|
|
|
2017-02-04 08:55:47 +01:00
|
|
|
extends 'Lemonldap::NG::Portal::Lib::U2F';
|
|
|
|
|
|
|
|
# INTERFACE
|
|
|
|
|
2017-02-20 22:59:31 +01:00
|
|
|
sub afterDatas { 'run' }
|
2017-02-04 08:55:47 +01:00
|
|
|
|
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
has ott => (
|
|
|
|
is => 'rw',
|
|
|
|
default => sub {
|
|
|
|
my $ott =
|
|
|
|
$_[0]->{p}->loadModule('Lemonldap::NG::Portal::Lib::OneTimeToken');
|
|
|
|
$ott->timeout( $_[0]->conf->{formTimeout} );
|
|
|
|
return $ott;
|
|
|
|
}
|
|
|
|
);
|
2017-02-02 22:48:32 +01:00
|
|
|
|
|
|
|
sub init {
|
2017-02-04 08:55:47 +01:00
|
|
|
my ($self) = @_;
|
2017-02-07 13:52:56 +01:00
|
|
|
$self->addUnauthRoute( u2fcheck => 'verify', ['POST'] );
|
2017-02-04 08:55:47 +01:00
|
|
|
return 0 unless $self->SUPER::init;
|
2017-02-02 22:48:32 +01:00
|
|
|
1;
|
|
|
|
}
|
|
|
|
|
2017-02-04 08:55:47 +01:00
|
|
|
# RUNNING METHODS
|
|
|
|
|
|
|
|
# Main method
|
|
|
|
sub run {
|
2017-02-07 13:52:56 +01:00
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
my ( $kh, $uk );
|
|
|
|
|
|
|
|
# Check if user is registered
|
|
|
|
if ( my $res = $self->loadUser($req) ) {
|
|
|
|
return PE_ERROR if ( $res == -1 );
|
|
|
|
|
2017-02-20 22:59:31 +01:00
|
|
|
$req->sessionInfo->{_u2fRealSession} = $req->id;
|
|
|
|
my $token = $self->ott->createToken( $req->sessionInfo );
|
|
|
|
$req->id(0);
|
|
|
|
$self->p->rebuildCookies($req);
|
2017-02-07 23:04:49 +01:00
|
|
|
|
2017-02-07 13:52:56 +01:00
|
|
|
my $challenge = $self->crypter->authenticationChallenge;
|
2017-02-07 23:04:49 +01:00
|
|
|
my $tmp = $self->p->sendHtml(
|
|
|
|
$req,
|
|
|
|
'u2fcheck',
|
|
|
|
params => {
|
2017-02-19 08:17:45 +01:00
|
|
|
SKIN => $self->conf->{portalSkin},
|
|
|
|
CHALLENGE => $challenge,
|
|
|
|
TOKEN => $token
|
2017-02-07 23:04:49 +01:00
|
|
|
}
|
|
|
|
);
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug( 'Prepare U2F verification for '
|
|
|
|
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
|
2017-02-07 23:04:49 +01:00
|
|
|
|
|
|
|
$req->response($tmp);
|
2017-02-20 22:59:31 +01:00
|
|
|
delete $req->{authResult};
|
2017-02-07 23:04:49 +01:00
|
|
|
return PE_SENDRESPONSE;
|
2017-02-07 13:52:56 +01:00
|
|
|
}
|
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub verify {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
|
|
|
|
# TODO: set sessionInfo with token
|
2017-02-07 23:04:49 +01:00
|
|
|
my $token;
|
|
|
|
unless ( $token = $req->param('token') ) {
|
2017-02-15 15:16:59 +01:00
|
|
|
$self->userLogger->error('U2F access without token');
|
2017-02-07 23:04:49 +01:00
|
|
|
$req->error(PE_NOTOKEN);
|
|
|
|
return $self->fail($req);
|
|
|
|
}
|
2017-02-20 22:59:31 +01:00
|
|
|
unless ( $req->sessionInfo( $self->ott->getToken($token) ) ) {
|
2017-02-15 15:16:59 +01:00
|
|
|
$self->userLogger->info('Token expired');
|
2017-02-07 23:04:49 +01:00
|
|
|
$req->error(PE_TOKENEXPIRED);
|
|
|
|
return $self->fail($req);
|
|
|
|
}
|
|
|
|
if ( my $resp = $req->param('signature') ) {
|
2017-02-20 22:59:31 +01:00
|
|
|
unless ( $self->loadUser($req) == 1 ) {
|
2017-02-07 23:04:49 +01:00
|
|
|
$req->error(PE_ERROR);
|
|
|
|
return $self->fail($req);
|
2017-02-07 13:52:56 +01:00
|
|
|
}
|
|
|
|
if ( $self->crypter->authenticationVerify($resp) ) {
|
2017-02-07 23:04:49 +01:00
|
|
|
$req->id( $req->sessionInfo->{_u2fRealSession} );
|
|
|
|
delete $req->sessionInfo->{_u2fRealSession};
|
|
|
|
$self->p->rebuildCookies($req);
|
|
|
|
$req->mustRedirect(1);
|
2017-02-15 15:16:59 +01:00
|
|
|
$self->userLogger->info( 'U2F signature verified for '
|
2017-02-07 23:04:49 +01:00
|
|
|
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
|
2017-02-20 22:59:31 +01:00
|
|
|
return $self->p->do( $req, [ sub { PE_OK } ] );
|
2017-02-07 13:52:56 +01:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 15:16:59 +01:00
|
|
|
$self->userLogger->notice( 'Invalid U2F signature for '
|
2017-02-07 23:04:49 +01:00
|
|
|
. $req->sessionInfo->{ $self->conf->{whatToTrace} } . ' ('
|
|
|
|
. Crypt::U2F::Server::u2fclib_getError()
|
|
|
|
. ')' );
|
|
|
|
$req->error(PE_U2FFAILED);
|
2017-02-20 22:59:31 +01:00
|
|
|
$req->authResult(PE_U2FFAILED);
|
2017-02-07 23:04:49 +01:00
|
|
|
return $self->fail($req);
|
2017-02-07 13:52:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 15:16:59 +01:00
|
|
|
$self->userLogger->notice( 'No U2F response for user'
|
2017-02-07 13:52:56 +01:00
|
|
|
. $req->sessionInfo->{ $self->conf->{whatToTrace} } );
|
2017-02-20 22:59:31 +01:00
|
|
|
$req->authResult(PE_U2FFAILED);
|
2017-02-07 23:04:49 +01:00
|
|
|
return $self->fail($req);
|
2017-02-07 13:52:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-07 23:04:49 +01:00
|
|
|
sub fail {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
return $self->p->sendHtml(
|
|
|
|
$req,
|
|
|
|
'u2fcheck',
|
|
|
|
params => {
|
|
|
|
AUTH_ERROR => $req->error,
|
|
|
|
AUTH_ERROR_TYPE => $req->error_type,
|
|
|
|
SKIN => $self->conf->{portalSkin},
|
|
|
|
FAILED => 1
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2017-02-07 13:52:56 +01:00
|
|
|
sub loadUser {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
my ( $kh, $uk );
|
|
|
|
if ( ( $kh = $req->sessionInfo->{_u2fKeyHandle} )
|
|
|
|
and ( $uk = $req->sessionInfo->{_u2fUserKey} ) )
|
|
|
|
{
|
2017-02-08 19:10:06 +01:00
|
|
|
$self->crypter->{keyHandle} = $self->decode_base64url($kh);
|
|
|
|
$self->crypter->{publicKey} = $self->decode_base64url($uk);
|
2017-02-20 22:59:31 +01:00
|
|
|
unless ( $self->crypter->setKeyHandle and $self->crypter->setPublicKey )
|
2017-02-07 13:52:56 +01:00
|
|
|
{
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
|
|
|
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
|
2017-02-07 13:52:56 +01:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return 0;
|
|
|
|
}
|
2017-02-04 08:55:47 +01:00
|
|
|
}
|
|
|
|
|
2017-02-02 22:48:32 +01:00
|
|
|
1;
|