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

292 lines
9.6 KiB
Perl
Raw Normal View History

2017-02-04 08:55:47 +01:00
# Self U2F registration
package Lemonldap::NG::Portal::2F::Register::U2F;
2017-02-02 22:48:32 +01:00
use strict;
use Mouse;
use JSON qw(from_json to_json);
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';
2017-02-02 22:48:32 +01:00
2017-02-04 08:55:47 +01:00
# INITIALIZATION
2017-02-03 18:14:13 +01:00
has prefix => ( is => 'rw', default => 'u' );
has template => ( is => 'ro', default => 'u2fregister' );
has logo => ( is => 'rw', default => 'u2f.png' );
2017-02-02 22:48:32 +01:00
sub init {
my ($self) = @_;
2017-02-04 08:55:47 +01:00
return 0 unless $self->SUPER::init;
2017-02-02 22:48:32 +01:00
return 1;
}
2017-02-04 08:55:47 +01:00
# RUNNING METHODS
# Main method
2017-02-02 22:48:32 +01:00
sub run {
my ( $self, $req, $action ) = @_;
2017-02-02 22:48:32 +01:00
if ( $action eq 'register' ) {
# Check if user can register one more device
2018-04-07 13:22:06 +02:00
my $_2fDevices = eval {
$self->logger->debug("Looking for 2F Devices ...");
# Read existing 2FDevices
2018-04-07 13:22:06 +02:00
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
};
2018-04-07 13:22:06 +02:00
unless ($_2fDevices) {
$self->logger->debug("No 2F Device found");
# Set default value
2018-04-07 13:22:06 +02:00
$_2fDevices = [];
}
2018-04-07 13:22:06 +02:00
my $size = @$_2fDevices;
my $maxSize = $self->conf->{max2FDevices};
2018-04-07 13:22:06 +02:00
$self->logger->debug("Registered 2F Device(s) : $size / $maxSize");
if ( $size >= $maxSize ) {
$self->userLogger->error("Max number of 2F devices is reached !!!");
return $self->p->sendError( $req, 'maxNumberof2FDevicesReached',
400 );
}
my $challenge = $self->crypter->registrationChallenge;
$self->logger->debug("Register challenge: $challenge");
return [
200,
[
'Content-Type' => 'application/json',
'Content-Length' => length($challenge),
],
[$challenge]
];
return [ 200, [ 'Content-Type' => 'application/json' ], [$challenge] ];
}
if ( $action eq 'registration' ) {
my ( $resp, $challenge );
$self->logger->debug('Registration response');
2018-03-06 22:50:40 +01:00
unless ($resp = $req->param('registration')
and $challenge = $req->param('challenge') )
2018-03-06 22:50:40 +01:00
{
return $self->p->sendError( $req, 'Missing registration parameter',
400 );
}
2017-02-15 07:41:50 +01:00
$self->logger->debug("Get registration data $resp");
2018-03-06 22:50:40 +01:00
$self->logger->debug("Get challenge $challenge");
eval { $challenge = from_json($challenge)->{challenge} };
2018-03-06 22:50:40 +01:00
if ($@) {
$self->userLogger->error("Bad challenge: $@");
return $self->p->sendError( $req, 'Bad challenge', 400 );
}
my $c = $self->crypter;
if ( $c->setChallenge($challenge) ) {
my ( $keyHandle, $userKey ) = $c->registrationVerify($resp);
if ( $keyHandle and $userKey ) {
2018-04-07 13:22:06 +02:00
my $_2fDevices = eval {
2018-03-30 21:24:34 +02:00
$self->logger->debug("Looking for 2F Devices ...");
# Read existing 2FDevices
2018-04-07 13:22:06 +02:00
from_json( $req->userData->{_2fDevices},
{ allow_nonref => 1 } );
};
2018-04-07 13:22:06 +02:00
unless ($_2fDevices) {
$self->logger->debug("No 2F Device found");
# Set default value
2018-04-07 13:22:06 +02:00
$_2fDevices = [];
}
my $keyName = $req->param('keyName');
my $epoch = time();
2018-04-05 19:43:06 +02:00
# Set default name if empty and troncate name if too long
$keyName ||= $epoch;
2018-04-05 19:43:06 +02:00
$keyName =
substr( $keyName, 0, $self->conf->{max2FDevicesNameLength} );
$self->logger->debug("Key name : $keyName");
2018-04-07 13:22:06 +02:00
push @{$_2fDevices},
{
type => 'U2F',
name => $keyName,
_userKey => $self->encode_base64url( $userKey, '' ),
_keyHandle => $keyHandle,
epoch => $epoch
};
$self->logger->debug(
"Append 2F Device : { type => 'U2F', name => $keyName }");
$self->p->updatePersistentSession( $req,
2018-04-07 13:22:06 +02:00
{ _2fDevices => to_json($_2fDevices) } );
2018-04-08 22:27:12 +02:00
#$self->p->updatePersistentSession(
#$req,
#{
#_u2fKeyHandle =>
#$self->encode_base64url( $keyHandle, '' ),
#_u2fUserKey => $self->encode_base64url( $userKey, '' )
#}
#);
2018-03-06 22:50:40 +01:00
return [
200,
[
'Content-Type' => 'application/json',
'Content-Length' => 12,
],
2018-03-06 22:50:40 +01:00
['{"result":1}']
];
}
2017-02-03 07:23:39 +01:00
}
my $err = Crypt::U2F::Server::Simple::lastError();
$self->userLogger->warn("U2F Registration failed: $err");
return $self->p->sendError( $req, $err, 200 );
2017-02-02 22:48:32 +01:00
}
2018-02-21 09:23:41 +01:00
elsif ( $action eq 'verify' ) {
$self->logger->debug('Verification challenge req');
my ( $err, $error ) = $self->loadUser($req);
if ( $err == -1 ) {
return $self->p->sendError( $req, "U2F error: $error", 200 );
}
elsif ( $err == 0 ) {
return $self->p->sendError( $req, "noU2FKeyFound" );
}
my $challenge = $req->datas->{crypter}->authenticationChallenge;
return [
200,
[
'Content-Type' => 'application/json',
'Content-Length' => length($challenge),
],
[$challenge]
];
}
2018-04-07 13:22:06 +02:00
elsif ( $action eq 'signature' ) {
$self->logger->debug('Verification response');
my ( $challenge, $resp );
unless ($challenge = $req->param('challenge')
and $resp = $req->param('signature') )
{
return $self->p->sendError( $req, 'Missing signature parameter',
400 );
}
my ( $err, $error ) = $self->loadUser($req);
if ( $err == -1 ) {
return $self->p->sendError( $req, "U2F error: $error", 200 );
}
elsif ( $err == 0 ) {
return $self->p->sendError( $req, "noU2FKeyFound" );
}
$self->logger->debug("Get verify response $resp");
$req->datas->{crypter}->setChallenge($challenge);
2018-03-06 22:50:40 +01:00
my $res =
( $req->datas->{crypter}->authenticationVerify($resp) ? 1 : 0 );
return [
200,
[ 'Content-Type' => 'application/json', 'Content-Length' => 12, ],
[qq'{"result":$res}']
];
}
# Check if unregistration is allowed
unless ( $self->conf->{u2fUserCanRemoveKey} ) {
2018-04-03 23:03:29 +02:00
return $self->p->sendError( $req, 'notAuthorized', 200 );
}
2018-04-03 23:03:29 +02:00
if ( $action eq 'unregister' ) {
my $challenge = $self->crypter->registrationChallenge;
return [
200,
[
'Content-Type' => 'application/json',
'Content-Length' => length($challenge),
],
[$challenge]
];
}
2018-04-08 22:27:12 +02:00
#elsif ( $action eq 'unregistration' ) {
#$self->p->updatePersistentSession(
#$req,
#{
#_u2fKeyHandle => '',
#_u2fUserKey => ''
#}
#);
#$self->userLogger->notice('U2F key unregistration succeed');
#return [
#200,
#[ 'Content-Type' => 'application/json', 'Content-Length' => 12, ],
#['{"result":1}']
#];
#my $err = Crypt::U2F::Server::Simple::lastError();
#$self->userLogger->warn("U2F Unregistration failed: $err");
#return $self->p->sendError( $req, $err, 200 );
#}
2018-04-02 23:19:56 +02:00
elsif ( $action eq 'delete' ) {
2018-04-04 23:16:36 +02:00
my $epoch = $req->param('epoch');
2018-04-07 13:22:06 +02:00
my $_2fDevices = eval {
$self->logger->debug("Loading 2F Devices ...");
2018-04-02 23:19:56 +02:00
# Read existing 2FDevices
2018-04-07 13:22:06 +02:00
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
2018-04-02 23:19:56 +02:00
};
my @keep = ();
2018-04-07 13:22:06 +02:00
while (@$_2fDevices) {
my $element = shift @$_2fDevices;
$self->logger->debug("Looking for 2F device to delete ...");
push @keep, $element unless ( $element->{epoch} eq $epoch );
}
$self->logger->debug(
"Delete 2F Device : { type => 'U2F', epoch => $epoch }");
2018-04-02 23:19:56 +02:00
$self->p->updatePersistentSession( $req,
2018-04-07 13:22:06 +02:00
{ _2fDevices => to_json( \@keep ) } );
2018-04-08 22:27:12 +02:00
#$self->p->updatePersistentSession(
#$req,
#{
#_u2fKeyHandle => '',
#_u2fUserKey => ''
#}
#);
$self->userLogger->notice('U2F key unregistration succeed');
return [
200,
[ 'Content-Type' => 'application/json', 'Content-Length' => 12, ],
['{"result":1}']
];
my $err = Crypt::U2F::Server::Simple::lastError();
$self->userLogger->warn("U2F Unregistration failed: $err");
return $self->p->sendError( $req, $err, 200 );
}
$self->logger->error("Unknown action $action");
2018-04-03 23:03:29 +02:00
return $self->p->sendError( $req, 'notAuthorized', 200 );
}
sub loadUser {
my ( $self, $req ) = @_;
my $uid = $req->userData->{ $self->conf->{whatToTrace} };
2017-02-08 19:10:06 +01:00
my $session = $self->p->getPersistentSession($uid);
my $kh = $session->data->{_u2fKeyHandle};
my $uk = $session->data->{_u2fUserKey};
unless ( $kh and $uk ) {
return 0;
}
$req->datas->{crypter} = $self->crypter(
keyHandle => $self->decode_base64url($kh),
publicKey => $self->decode_base64url($uk)
);
unless ( $req->datas->{crypter} ) {
my $error = Crypt::U2F::Server::Simple::lastError();
return ( -1, $error );
}
return 1;
2017-02-02 22:48:32 +01:00
}
1;