Enabled new 2F engine (#1386)

This commit is contained in:
Christophe Maudoux 2018-04-10 11:06:06 +02:00
parent 1bc83a242d
commit c291cfced9
9 changed files with 168 additions and 60 deletions

View File

@ -27,7 +27,7 @@ sub types {
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval "$s $val";
my $err = join(
@ -662,7 +662,7 @@ sub attributes {
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval "$s $val";
my $err = join(
@ -1026,7 +1026,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval $s;
my $err = join(
@ -1111,7 +1111,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval "$s $val";
my $err = join(
@ -1134,7 +1134,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval "$s $val";
my $err = join(
@ -1489,7 +1489,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval $s;
my $err = join(
@ -1526,7 +1526,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval "$s $val";
my $err = join(
@ -1885,7 +1885,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval "$s $val";
my $err = join(
@ -2222,7 +2222,7 @@ qr/^(?:\*\.)?(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval "$s $val";
my $err = join(
@ -2925,7 +2925,7 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
BEGIN {
${^WARNING_BITS} =
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x01";
"\x54\x55\x55\x55\x15\x55\x55\x55\x55\x55\x51\x55\x55\x55\x55\x55\x55\x05";
}
eval "$s $val";
my $err = join(
@ -3004,19 +3004,19 @@ qr/(?:(?:https?):\/\/(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.]
'default' => 0,
'select' => [
{
'k' => '0',
'k' => 0,
'v' => 'unsecuredCookie'
},
{
'k' => '1',
'k' => 1,
'v' => 'securedCookie'
},
{
'k' => '2',
'k' => 2,
'v' => 'doubleCookie'
},
{
'k' => '3',
'k' => 3,
'v' => 'doubleCookieForSingleSession'
}
],

View File

@ -98,20 +98,64 @@ sub run {
$self->logger->debug('TOTP code verified');
# Now code is verified, let's store the master key in persistent data
#$self->p->updatePersistentSession( $req,
#{ _totp2fSecret => $token->{_totp2fSecret} } );
my $secret = '';
my $_2fDevices = eval {
$self->logger->debug("Looking for 2F Devices ...");
# Read existing 2FDevices
from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
};
unless ($_2fDevices) {
$self->logger->debug("No 2F Device found");
$_2fDevices = [];
};
#my $_2fDevices = eval {
#$self->logger->debug("Loading 2F Devices ...");
## Read existing 2FDevices
#from_json( $req->userData->{_2fDevices}, { allow_nonref => 1 } );
#};
my @totp2f = grep { $_->{type} eq "TOTP" } @$_2fDevices;
unless ( @totp2f ) {
$self->logger->debug("No TOTP Device found");
# Set default value
push @totp2f, { _secret => '' } ;
}
# Loading TOTP secret
foreach ( @totp2f ) {
$self->logger->debug("Reading TOTP secret if exists ...");
$secret = $_->{_secret};
}
if ( $token->{_totp2fSecret} eq $secret ) {
return $self->p->sendError( $req, 'totpExistingKey', 200 );
}
### USER CAN ONLY REGISTER ONE TOTP ###
# Delete TOTP 2F devices registered
my @keep = ();
while (@$_2fDevices) {
my $element = shift @$_2fDevices;
$self->logger->debug("Looking for TOTP to delete ...");
push @keep, $element unless ( $element->{type} eq "TOTP" );
}
#$self->logger->debug("Delete previously registered TOTP");
#$self->p->updatePersistentSession( $req,
#{ _2fDevices => to_json( \@keep ) } );
# Check if user can register one more device
my $size = @$_2fDevices;
#my $size = @$_2fDevices;
my $size = @keep;
my $maxSize = $self->conf->{max2FDevices};
$self->logger->debug("Nbr 2FDevices = $size / $maxSize");
if ( $size >= $maxSize ) {
@ -120,7 +164,9 @@ sub run {
400 );
}
push @{$_2fDevices},
# Store TOTP secret
#push @{$_2fDevices},
push @keep,
{
type => 'TOTP',
name => $TOTPName,
@ -131,7 +177,8 @@ sub run {
$self->logger->debug(
"Append 2F Device : { type => 'TOTP', name => $TOTPName }");
$self->p->updatePersistentSession( $req,
{ _2fDevices => to_json($_2fDevices) } );
#{ _2fDevices => to_json($_2fDevices) } );
{ _2fDevices => to_json( \@keep ) } );
$self->userLogger->notice('TOTP registration succeed');
return [ 200, [ 'Content-Type' => 'application/json' ],
@ -153,42 +200,37 @@ sub run {
my @totp2f = grep { $_->{type} eq "TOTP" } @$_2fDevices;
unless ( @totp2f ) {
$self->logger->debug("No 2F Device found");
$self->logger->debug("No TOTP found");
# Set default value
push @totp2f, { _secret => '' } ;
}
};
# Loading TOTP secret
foreach ( @totp2f ) {
$self->logger->debug("Reading TOTP secret ...");
$secret = $_->{_secret};
$self->logger->debug("Reading TOTP secret if exists ...");
$secret = $_->{_secret};
};
if ( ( $req->param('newkey') and $self->conf->{totp2fUserCanChangeKey} )
#or not $req->userData->{_totp2fSecret} )
or not $secret )
{
$secret = $self->newSecret;
$self->logger->debug("Generating new secret = $secret");
$nk = 1;
}
elsif ( $req->param('newkey') ) {
return $self->p->sendError( $req, 'notAuthorized', 200 );
}
elsif ( $self->conf->{totp2fDisplayExistingSecret} ) {
#$secret = $req->userData->{_totp2fSecret};
$self->logger->debug("User secret = $secret");
}
else {
return $self->p->sendError( $req, 'totpExistingKey', 200 );
}
};
# Secret is stored in a token: we choose to not accept secret returned
# by Ajax request to avoid some attacks
@ -203,14 +245,6 @@ sub run {
$issuer = $self->conf->{portal};
$issuer =~ s#^https?://([^/:]+).*$#$1#;
}
if ( $token eq $secret ) {
return $self->p->sendError( $req, 'notAuthorized', 200 );
}
# QR-code will be generated by a javascript, here we just send data
return $self->p->sendJSONresponse(
@ -225,21 +259,14 @@ sub run {
interval => $self->conf->{totp2fInterval}
}
);
}
};
# Check if unregistration is allowed
unless ( $self->conf->{TOTP2fUserCanChangeKey} ) {
unless ( $self->conf->{totp2fUserCanChangeKey} ) {
return $self->p->sendError( $req, 'notAuthorized', 400 );
}
# Delete TOTP
#if ( $action eq 'unregister' ) {
#$self->p->updatePersistentSession( $req, { _totp2fSecret => '' } );
#$self->userLogger->notice('TOTP unregistration succeed');
#return [ 200, [ 'Content-Type' => 'application/json' ],
#['{"result":1}'] ];
#}
#elsif ( $action eq 'delete' ) {
if ( $action eq 'delete' ) {
my $epoch = $req->param('epoch');
my $_2fDevices = eval {

View File

@ -270,11 +270,13 @@ sub run {
sub loadUser {
my ( $self, $req ) = @_;
$self->logger->debug("Loading user U2F Devices ...");
my $uid = $req->userData->{ $self->conf->{whatToTrace} };
my $session = $self->p->getPersistentSession($uid);
my $kh = $session->data->{_u2fKeyHandle};
my $uk = $session->data->{_u2fUserKey};
unless ( $kh and $uk ) {
$self->logger->debug("UTOP -> No U2F key found !!!");
return 0;
}
$req->datas->{crypter} = $self->crypter(

View File

@ -2,6 +2,7 @@ package Lemonldap::NG::Portal::2F::TOTP;
use strict;
use Mouse;
use JSON qw(from_json to_json);
use Lemonldap::NG::Portal::Main::Constants qw(
PE_BADCREDENTIALS
PE_ERROR
@ -66,12 +67,49 @@ sub verify {
$self->userLogger->error('TOTP 2F: no code');
return PE_FORMEMPTY;
}
my $secret = '';
my $_2fDevices = eval {
$self->logger->debug("Loading 2F Devices ...");
# Read existing 2FDevices
from_json( $session->{_2fDevices}, { allow_nonref => 1 } );
};
unless ( $_2fDevices ) {
$self->logger->debug("No 2F Device found");
# Set default value
@$_2fDevices = [];
}
foreach (@$_2fDevices) {
$self->logger->debug("Reading TOTP secret if exists ...");
if ( $_->{type} eq 'TOTP' ) {
$secret = $_->{_secret};
last;
}
}
#my @totp2f = grep { $_->{type} =~ /TOTP/s } @$_2fDevices;
my $r = $self->verifyCode(
$self->conf->{totp2fInterval},
$self->conf->{totp2fRange},
$self->conf->{totp2fDigits},
$session->{_totp2fSecret}, $code
$secret, $code
);
if ( $r == -1 ) { return PE_ERROR; }
elsif ($r) {

View File

@ -7,6 +7,7 @@ package Lemonldap::NG::Portal::2F::U2F;
use 5.16.0;
use strict;
use Mouse;
use JSON qw(from_json to_json);
use Lemonldap::NG::Portal::Main::Constants qw(
PE_ERROR
PE_OK
@ -137,18 +138,55 @@ sub fail {
sub loadUser {
my ( $self, $req, $session ) = @_;
my ( $kh, $uk );
if ( ( $kh = $session->{_u2fKeyHandle} )
and ( $uk = $session->{_u2fUserKey} ) )
my $_2fDevices = eval {
$self->logger->debug("Loading 2F Devices ...");
# Read existing 2FDevices
from_json( $session->{_2fDevices}, { allow_nonref => 1 } );
};
unless ( $_2fDevices ) {
$self->logger->debug("No 2F Device found");
# Set default value
@$_2fDevices = [];
}
my @u2fs = ();
foreach ( @$_2fDevices ) {
$self->logger->debug("Reading U2F keys if exists ...");
if ( $_->{type} eq 'U2F' ) {
$self->logger->debug("_userKey = ".$_->{_userKey});
$_->{_userKey} = $self->decode_base64url($_->{_userKey});
push @u2fs, $_;
}
}
#if ( ( $kh = $session->{_u2fKeyHandle} )
# and ( $uk = $session->{_u2fUserKey} ) )
if ( ( $kh = $u2fs[0]{_keyHandle} )
and ( $uk = $u2fs[0]{_userKey} ) )
{
$req->datas->{crypter} = $self->crypter(
keyHandle => $self->decode_base64url($kh),
publicKey => $self->decode_base64url($uk)
#keyHandle => $self->decode_base64url($kh),
#publicKey => $self->decode_base64url($uk)
keyHandle => $kh,
publicKey => $uk
);
unless ( $req->datas->{crypter} ) {
$self->logger->error(
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
return -1;
}
};
return 1;
}
else {

View File

@ -2,6 +2,7 @@ package Lemonldap::NG::Portal::2F::UTOTP;
use strict;
use Mouse;
use JSON qw(from_json to_json);
use Lemonldap::NG::Portal::Main::Constants qw(
);

View File

@ -4,7 +4,7 @@ LemonLDAP::NG TOTP registration script
setMsg = (msg, level) ->
$('#msg').html window.translate msg
$('#color').removeClass 'message-positive message-warning alert-success alert-warning'
$('#color').removeClass 'message-positive message-warning message-danger alert-success alert-warning alert-danger'
$('#color').addClass "message-#{level}"
level = 'success' if level == 'positive'
$('#color').addClass "alert-#{level}"
@ -20,6 +20,7 @@ displayError = (j, status, err) ->
token=''
getKey = (reset) ->
setMsg 'yourTotpKey', 'warning'
$.ajax
type: "POST",
url: "#{portal}/2fregisters/totp/getkey"

View File

@ -9,7 +9,7 @@ LemonLDAP::NG TOTP registration script
setMsg = function(msg, level) {
$('#msg').html(window.translate(msg));
$('#color').removeClass('message-positive message-warning alert-success alert-warning');
$('#color').removeClass('message-positive message-warning message-danger alert-success alert-warning alert-danger');
$('#color').addClass("message-" + level);
if (level === 'positive') {
level = 'success';
@ -31,6 +31,7 @@ LemonLDAP::NG TOTP registration script
token = '';
getKey = function(reset) {
setMsg('yourTotpKey', 'warning');
return $.ajax({
type: "POST",
url: portal + "/2fregisters/totp/getkey",

View File

@ -1 +1 @@
(function(){var a,b,e,d,c,f;e=function(g,h){$("#msg").html(window.translate(g));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+h);if(h==="positive"){h="success"}return $("#color").addClass("alert-"+h)};a=function(h,g,k){var i;console.log("Error",k);i=JSON.parse(h.responseText);if(i&&i.error){i=i.error.replace(/.* /,"");console.log("Returned error",i);return e(i,"warning")}};d="";b=function(g){return $.ajax({type:"POST",url:portal+"/2fregisters/totp/getkey",dataType:"json",data:{newkey:g},error:a,success:function(j){var h,i;if(j.error){return e(j.error,"warning")}if(!(j.portal&&j.user&&j.secret)){return e("PE24","danger")}i="otpauth://totp/"+(escape(j.portal))+":"+(escape(j.user))+"?secret="+j.secret+"&issuer="+(escape(j.portal));if(j.digits!==6){i+="&digits="+j.digits}if(j.interval!==30){i+="&period="+j.interval}h=new QRious({element:document.getElementById("qr"),value:i,size:150});$("#serialized").text(i);if(j.newkey){e("yourNewTotpKey","warning")}else{e("yourTotpKey","success")}return d=j.token}})};f=function(){var g;g=$("#code").val();if(!g){return e("fillTheForm","warning")}else{return $.ajax({type:"POST",url:portal+"/2fregisters/totp/verify",dataType:"json",data:{token:d,code:g,TOTPName:$("#TOTPName").val()},error:a,success:function(h){if(h.error){if(h.error.match(/badCode/)){return e("badCode","warning")}else{return e(h.error,"danger")}}else{return e("yourKeyIsRegistered","success")}}})}};c=function(){return $.ajax({type:"POST",url:portal+"/totpregister/unregister",data:{},dataType:"json",error:a,success:function(g){}},e("yourKeyIsUnregistered","success"))};$(document).ready(function(){b(0);$("#changekey").on("click",function(){return b(1)});$("#verify").on("click",function(){return f()});return $("#unregister").on("click",function(){return c()})})}).call(this);
(function(){var a,b,e,d,c,f;e=function(g,h){$("#msg").html(window.translate(g));$("#color").removeClass("message-positive message-warning message-danger alert-success alert-warning alert-danger");$("#color").addClass("message-"+h);if(h==="positive"){h="success"}return $("#color").addClass("alert-"+h)};a=function(h,g,k){var i;console.log("Error",k);i=JSON.parse(h.responseText);if(i&&i.error){i=i.error.replace(/.* /,"");console.log("Returned error",i);return e(i,"warning")}};d="";b=function(g){e("yourTotpKey","warning");return $.ajax({type:"POST",url:portal+"/2fregisters/totp/getkey",dataType:"json",data:{newkey:g},error:a,success:function(j){var h,i;if(j.error){return e(j.error,"warning")}if(!(j.portal&&j.user&&j.secret)){return e("PE24","danger")}i="otpauth://totp/"+(escape(j.portal))+":"+(escape(j.user))+"?secret="+j.secret+"&issuer="+(escape(j.portal));if(j.digits!==6){i+="&digits="+j.digits}if(j.interval!==30){i+="&period="+j.interval}h=new QRious({element:document.getElementById("qr"),value:i,size:150});$("#serialized").text(i);if(j.newkey){e("yourNewTotpKey","warning")}else{e("yourTotpKey","success")}return d=j.token}})};f=function(){var g;g=$("#code").val();if(!g){return e("fillTheForm","warning")}else{return $.ajax({type:"POST",url:portal+"/2fregisters/totp/verify",dataType:"json",data:{token:d,code:g,TOTPName:$("#TOTPName").val()},error:a,success:function(h){if(h.error){if(h.error.match(/badCode/)){return e("badCode","warning")}else{return e(h.error,"danger")}}else{return e("yourKeyIsRegistered","success")}}})}};c=function(){return $.ajax({type:"POST",url:portal+"/totpregister/unregister",data:{},dataType:"json",error:a,success:function(g){}},e("yourKeyIsUnregistered","success"))};$(document).ready(function(){b(0);$("#changekey").on("click",function(){return b(1)});$("#verify").on("click",function(){return f()});return $("#unregister").on("click",function(){return c()})})}).call(this);