2018-02-19 14:23:33 +01:00
|
|
|
package Lemonldap::NG::Portal::2F::TOTP;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
|
|
|
PE_BADCREDENTIALS
|
|
|
|
PE_ERROR
|
|
|
|
PE_FORMEMPTY
|
|
|
|
PE_OK
|
|
|
|
PE_SENDRESPONSE
|
|
|
|
);
|
|
|
|
|
|
|
|
our $VERSION = '2.0.0';
|
|
|
|
|
|
|
|
extends 'Lemonldap::NG::Portal::Main::SecondFactor';
|
|
|
|
|
|
|
|
# INITIALIZATION
|
|
|
|
|
2018-02-19 22:15:06 +01:00
|
|
|
has prefix => ( is => 'ro', default => 'totp' );
|
|
|
|
|
2018-02-19 14:23:33 +01:00
|
|
|
sub init {
|
|
|
|
my ($self) = @_;
|
2018-02-19 22:15:06 +01:00
|
|
|
if ( $self->conf->{totp2fActivation} eq '1' ) {
|
|
|
|
$self->conf->{totp2fActivation} = '$_totp2fSecret';
|
2018-02-19 14:23:33 +01:00
|
|
|
}
|
2018-02-19 22:15:06 +01:00
|
|
|
$self->conf->{totp2fInterval} ||= 30;
|
|
|
|
$self->conf->{totp2fRange} ||= 1;
|
2018-02-19 14:23:33 +01:00
|
|
|
return $self->SUPER::init();
|
|
|
|
}
|
|
|
|
|
|
|
|
# RUNNING METHODS
|
|
|
|
|
|
|
|
sub run {
|
|
|
|
my ( $self, $req, $token ) = @_;
|
|
|
|
|
|
|
|
# Prepare form
|
|
|
|
my $tmp = $self->p->sendHtml(
|
|
|
|
$req,
|
|
|
|
'ext2fcheck',
|
|
|
|
params => {
|
|
|
|
SKIN => $self->conf->{portalSkin},
|
|
|
|
TOKEN => $token
|
|
|
|
}
|
|
|
|
);
|
|
|
|
$self->logger->debug("Prepare TOTP 2F verification");
|
|
|
|
|
|
|
|
$req->response($tmp);
|
|
|
|
return PE_SENDRESPONSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub verify {
|
|
|
|
my ( $self, $req, $session ) = @_;
|
|
|
|
my $code;
|
|
|
|
unless ( $code = $req->param('code') ) {
|
|
|
|
$self->userLogger->error('TOTP 2F: no code');
|
|
|
|
return PE_FORMEMPTY;
|
|
|
|
}
|
|
|
|
|
2018-02-19 22:15:06 +01:00
|
|
|
my $s = eval { decode_base32( $session->{_totp2fSecret} ) };
|
2018-02-19 14:23:33 +01:00
|
|
|
if ($@) {
|
|
|
|
$self->logger->error("Bad characters in secret, aborting");
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
2018-02-19 22:15:06 +01:00
|
|
|
for ( 0 .. $self->conf->{totp2fRange} ) {
|
2018-02-19 14:23:33 +01:00
|
|
|
if ( $code eq $self->code( $s, $_ ) ) {
|
|
|
|
$self->userLogger->info('TOTP verified');
|
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$self->userLogger->notice(
|
|
|
|
'Invalid TOTP for ' . $session->{ $self->conf->{whatToTrace} } . ')' );
|
|
|
|
return PE_BADCREDENTIALS;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub code {
|
|
|
|
my ( $self, $secret, $r ) = @_;
|
|
|
|
my $hmac = hmac_sha1_hex(
|
|
|
|
pack(
|
|
|
|
'H*',
|
|
|
|
sprintf(
|
|
|
|
'%016x',
|
|
|
|
int(
|
2018-02-19 22:15:06 +01:00
|
|
|
( time + $r * $self->conf->{totp2fInterval} ) /
|
|
|
|
$self->conf->{totp2fInterval}
|
2018-02-19 14:23:33 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
),
|
|
|
|
_decode_base32($secret),
|
|
|
|
);
|
|
|
|
|
|
|
|
return sprintf(
|
|
|
|
'%06d',
|
|
|
|
(
|
|
|
|
hex( substr( $hmac, hex( substr( $hmac, -1 ) ) * 2, 8 ) ) &
|
|
|
|
0x7fffffff
|
|
|
|
) % 1000000
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|