Merge branch 'portal-multi-U2F-registration' into 'master'
Portal multi u2f registration See merge request lemonldap-ng/lemonldap-ng!24
This commit is contained in:
commit
22e5cf5bf0
|
@ -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'
|
||||
}
|
||||
],
|
||||
|
|
|
@ -233,7 +233,6 @@ sub run {
|
|||
return $self->p->sendError( $req, "Corrupted session", 500 );
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
$self->logger->debug("No 2F Device found");
|
||||
$_2fDevices = [];
|
||||
|
|
|
@ -51,23 +51,47 @@ sub init {
|
|||
sub run {
|
||||
my ( $self, $req, $token ) = @_;
|
||||
|
||||
my ( $kh, $uk );
|
||||
|
||||
# Check if user is registered
|
||||
if ( my $res = $self->loadUser( $req, $req->sessionInfo ) ) {
|
||||
return PE_ERROR if ( $res == -1 );
|
||||
return PE_U2FFAILED if ( $res == 0 );
|
||||
|
||||
my $challenge = $req->datas->{crypter}->authenticationChallenge;
|
||||
$self->ott->updateToken( $token,
|
||||
__ch => JSON::from_json($challenge)->{challenge} );
|
||||
# Get a challenge (from first key)
|
||||
my $data = eval {
|
||||
from_json( $req->datas->{crypter}->[0]->authenticationChallenge );
|
||||
};
|
||||
|
||||
if ($@) {
|
||||
$self->logger->error( Crypt::U2F::Server::u2fclib_getError() );
|
||||
return PE_ERROR;
|
||||
}
|
||||
|
||||
# Get registered keys
|
||||
my @rk;
|
||||
foreach ( @{ $req->datas->{crypter} } ) {
|
||||
my $k = push @rk,
|
||||
{ keyHandle => $_->{keyHandle}, version => $data->{version} };
|
||||
|
||||
}
|
||||
|
||||
$self->ott->updateToken( $token, __ch => $data->{challenge} );
|
||||
|
||||
# Serialize datas
|
||||
$data = to_json(
|
||||
{
|
||||
challenge => $data->{challenge},
|
||||
appId => $data->{appId},
|
||||
registeredKeys => \@rk
|
||||
}
|
||||
);
|
||||
|
||||
my $tmp = $self->p->sendHtml(
|
||||
$req,
|
||||
'u2fcheck',
|
||||
params => {
|
||||
SKIN => $self->conf->{portalSkin},
|
||||
CHALLENGE => $challenge,
|
||||
TOKEN => $token
|
||||
SKIN => $self->conf->{portalSkin},
|
||||
DATA => $data,
|
||||
TOKEN => $token
|
||||
}
|
||||
);
|
||||
$self->logger->debug("Prepare U2F verification");
|
||||
|
@ -91,19 +115,40 @@ sub verify {
|
|||
}
|
||||
|
||||
$self->logger->debug("Get challenge: $challenge");
|
||||
|
||||
unless ( $session->{__ch} and $session->{__ch} eq $challenge ) {
|
||||
$self->userLogger->error("U2F challenge changes by user !!! $session->{__ch}/$challenge");
|
||||
$self->userLogger->error(
|
||||
"U2F challenge changes by user !!! $session->{__ch} / $challenge"
|
||||
);
|
||||
$req->error(PE_BADCREDENTIALS);
|
||||
return $self->fail($req);
|
||||
}
|
||||
delete $session->{__ch};
|
||||
if ( not $req->datas->{crypter}->setChallenge($challenge) ) {
|
||||
|
||||
$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);
|
||||
}
|
||||
my $crypter;
|
||||
foreach ( @{ $req->datas->{crypter} } ) {
|
||||
$crypter = $_ if ( $_->{keyHandle} eq $data->{keyHandle} );
|
||||
}
|
||||
unless ($crypter) {
|
||||
$self->userLogger->error("Unregistered U2F key");
|
||||
$req->error(PE_BADCREDENTIALS);
|
||||
return $self->fail($req);
|
||||
}
|
||||
|
||||
if ( not $crypter->setChallenge($challenge) ) {
|
||||
$self->logger->error(
|
||||
$@ ? $@ : Crypt::U2F::Server::Simple::lastError() );
|
||||
$req->error(PE_ERROR);
|
||||
return $self->fail($req);
|
||||
}
|
||||
if ( $req->datas->{crypter}->authenticationVerify($resp) ) {
|
||||
if ( $crypter->authenticationVerify($resp) ) {
|
||||
$self->userLogger->info('U2F signature verified');
|
||||
return PE_OK;
|
||||
}
|
||||
|
@ -113,7 +158,6 @@ sub verify {
|
|||
. Crypt::U2F::Server::u2fclib_getError()
|
||||
. ')' );
|
||||
$req->error(PE_U2FFAILED);
|
||||
$req->authResult(PE_U2FFAILED);
|
||||
return $self->fail($req);
|
||||
}
|
||||
}
|
||||
|
@ -160,34 +204,49 @@ sub loadUser {
|
|||
$self->logger->debug("2F Device(s) found");
|
||||
|
||||
foreach (@$_2fDevices) {
|
||||
$self->logger->debug("Reading U2F keys if exists ...");
|
||||
$self->logger->debug("Looking for registered U2F key(s) ...");
|
||||
if ( $_->{type} eq 'U2F' ) {
|
||||
$self->logger->debug( "_userKey = " . $_->{_userKey} );
|
||||
$self->logger->debug( "_keyHandle = " . $_->{_keyHandle} );
|
||||
unless ( $_->{_userKey} and $_->{_userKey} ) {
|
||||
$self->logger->error(
|
||||
"Missing required U2F attributes in storage ($session->{_2fDevices})"
|
||||
);
|
||||
next;
|
||||
}
|
||||
$self->logger->debug( "Found U2F key -> _userKey = "
|
||||
. $_->{_userKey}
|
||||
. "/ _keyHandle = "
|
||||
. $_->{_keyHandle} );
|
||||
$_->{_userKey} = $self->decode_base64url( $_->{_userKey} );
|
||||
push @u2fs, $_;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#### TOTO : MANAGE MULTI U2F KEYS
|
||||
if ( ( $kh = $u2fs[0]{_keyHandle} )
|
||||
and ( $uk = $u2fs[0]{_userKey} ) )
|
||||
{
|
||||
# Manage multi u2f keys
|
||||
my @crypters;
|
||||
if (@u2fs) {
|
||||
$self->logger->debug("kh & uk -> OK");
|
||||
$req->datas->{crypter} = $self->crypter(
|
||||
keyHandle => $kh,
|
||||
publicKey => $uk
|
||||
);
|
||||
unless ( $req->datas->{crypter} ) {
|
||||
$self->logger->error(
|
||||
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
|
||||
|
||||
foreach (@u2fs) {
|
||||
$kh = $_->{_keyHandle};
|
||||
$uk = $_->{_userKey};
|
||||
my $c = $self->crypter( keyHandle => $kh, publicKey => $uk );
|
||||
if ($c) {
|
||||
push @crypters, $c;
|
||||
}
|
||||
else {
|
||||
$self->logger->error(
|
||||
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
|
||||
}
|
||||
}
|
||||
unless (@crypters) {
|
||||
return -1;
|
||||
}
|
||||
$req->datas->{crypter} = \@crypters;
|
||||
return 1;
|
||||
}
|
||||
else {
|
||||
$self->userLogger->info("U2F: user not registered");
|
||||
$self->userLogger->info("U2F : user not registered");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,13 +3,8 @@ LemonLDAP::NG U2F verify script
|
|||
###
|
||||
|
||||
check = ->
|
||||
registeredKey = [
|
||||
keyHandle: window.datas.keyHandle
|
||||
version: window.datas.version
|
||||
]
|
||||
console.log 'Key: ', registeredKey
|
||||
u2f.sign window.datas.appId, window.datas.challenge, registeredKey, (data) ->
|
||||
$('#verify-data').val JSON.stringify(data)
|
||||
u2f.sign window.datas.appId, window.datas.challenge, window.datas.registeredKeys, (data) ->
|
||||
$('#verify-data').val JSON.stringify data
|
||||
$('#verify-challenge').val window.datas.challenge
|
||||
$('#verify-form').submit()
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ verify = ->
|
|||
u2f.sign ch.appId, ch.challenge, request, (data) ->
|
||||
# Handle errors
|
||||
if data.errorCode
|
||||
setMsg 'unableToGetU2FKey', 'warning'
|
||||
setMsg 'unableToGetKey', 'warning'
|
||||
else
|
||||
# 3 send response
|
||||
$.ajax
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Generated by CoffeeScript 1.12.7
|
||||
// Generated by CoffeeScript 1.9.3
|
||||
|
||||
/*
|
||||
LemonLDAP::NG U2F verify script
|
||||
|
@ -8,15 +8,7 @@ LemonLDAP::NG U2F verify script
|
|||
var check;
|
||||
|
||||
check = function() {
|
||||
var registeredKey;
|
||||
registeredKey = [
|
||||
{
|
||||
keyHandle: window.datas.keyHandle,
|
||||
version: window.datas.version
|
||||
}
|
||||
];
|
||||
console.log('Key: ', registeredKey);
|
||||
return u2f.sign(window.datas.appId, window.datas.challenge, registeredKey, function(data) {
|
||||
return u2f.sign(window.datas.appId, window.datas.challenge, window.datas.registeredKeys, function(data) {
|
||||
$('#verify-data').val(JSON.stringify(data));
|
||||
$('#verify-challenge').val(window.datas.challenge);
|
||||
return $('#verify-form').submit();
|
||||
|
|
|
@ -1 +1 @@
|
|||
(function(){var a;a=function(){var b;b=[{keyHandle:window.datas.keyHandle,version:window.datas.version}];console.log("Key: ",b);return u2f.sign(window.datas.appId,window.datas.challenge,b,function(c){$("#verify-data").val(JSON.stringify(c));$("#verify-challenge").val(window.datas.challenge);return $("#verify-form").submit()})};$(document).ready(function(){return setTimeout(a,1000)})}).call(this);
|
||||
(function(){var a;a=function(){return u2f.sign(window.datas.appId,window.datas.challenge,window.datas.registeredKeys,function(b){$("#verify-data").val(JSON.stringify(b));$("#verify-challenge").val(window.datas.challenge);return $("#verify-form").submit()})};$(document).ready(function(){return setTimeout(a,1000)})}).call(this);
|
|
@ -92,7 +92,7 @@ LemonLDAP::NG U2F registration script
|
|||
setMsg('touchU2fDevice', 'positive');
|
||||
return u2f.sign(ch.appId, ch.challenge, request, function(data) {
|
||||
if (data.errorCode) {
|
||||
return setMsg('unableToGetU2FKey', 'warning');
|
||||
return setMsg('unableToGetKey', 'warning');
|
||||
} else {
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
|
|
|
@ -1 +1 @@
|
|||
(function(){var a,b,c,d;c=function(e,f){$("#msg").html(window.translate(e));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+f);if(f==="positive"){f="success"}return $("#color").addClass("alert-"+f)};a=function(f,e,h){var g;console.log("Error",h);g=JSON.parse(f.responseText);if(g&&g.error){g=g.error.replace(/.* /,"");console.log("Returned error",g);return c(g,"warning")}};b=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/register",data:{},dataType:"json",error:a,success:function(e){var f;f=[{challenge:e.challenge,version:e.version}];c("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(e.appId,f,[],function(g){$("#u2fPermission").hide();if(g.errorCode){return c(g.error,"warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/registration",data:{registration:JSON.stringify(g),challenge:JSON.stringify(e),keyName:$("#keyName").val()},dataType:"json",success:function(h){if(h.error){return c("u2fFailed","warning")}else{if(h.result){return c("yourKeyIsRegistered","positive")}}},error:a})}})}})};d=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/verify",data:{},dataType:"json",error:a,success:function(e){var f;f=[{keyHandle:e.keyHandle,version:e.version}];c("touchU2fDevice","positive");return u2f.sign(e.appId,e.challenge,f,function(g){if(g.errorCode){return c("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/signature",data:{signature:JSON.stringify(g),challenge:e.challenge},dataType:"json",success:function(h){if(h.error){return c("u2fFailed","warning")}else{if(h.result){return c("yourKeyIsVerified","positive")}}},error:function(i,h,k){return console.log("error",k)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",b);$("#verify").on("click",d);return $("#goback").attr("href",portal)})}).call(this);
|
||||
(function(){var a,b,c,d;c=function(e,f){$("#msg").html(window.translate(e));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+f);if(f==="positive"){f="success"}return $("#color").addClass("alert-"+f)};a=function(f,e,h){var g;console.log("Error",h);g=JSON.parse(f.responseText);if(g&&g.error){g=g.error.replace(/.* /,"");console.log("Returned error",g);return c(g,"warning")}};b=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/register",data:{},dataType:"json",error:a,success:function(e){var f;f=[{challenge:e.challenge,version:e.version}];c("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(e.appId,f,[],function(g){$("#u2fPermission").hide();if(g.errorCode){return c(g.error,"warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/registration",data:{registration:JSON.stringify(g),challenge:JSON.stringify(e),keyName:$("#keyName").val()},dataType:"json",success:function(h){if(h.error){return c("u2fFailed","warning")}else{if(h.result){return c("yourKeyIsRegistered","positive")}}},error:a})}})}})};d=function(){return $.ajax({type:"POST",url:portal+"2fregisters/u/verify",data:{},dataType:"json",error:a,success:function(e){var f;f=[{keyHandle:e.keyHandle,version:e.version}];c("touchU2fDevice","positive");return u2f.sign(e.appId,e.challenge,f,function(g){if(g.errorCode){return c("unableToGetKey","warning")}else{return $.ajax({type:"POST",url:portal+"2fregisters/u/signature",data:{signature:JSON.stringify(g),challenge:e.challenge},dataType:"json",success:function(h){if(h.error){return c("u2fFailed","warning")}else{if(h.result){return c("yourKeyIsVerified","positive")}}},error:function(i,h,k){return console.log("error",k)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",b);$("#verify").on("click",d);return $("#goback").attr("href",portal)})}).call(this);
|
|
@ -82,7 +82,7 @@
|
|||
"PE80":"This address is already used",
|
||||
"PE81":"Invalid authentication attempt",
|
||||
"PE82":"Exceeded authentication timeout",
|
||||
"PE83":"U2F verification failed",
|
||||
"PE83":"U2F verification failed. Retry or contact your administrator",
|
||||
"PE84":"You're not authorized to access to this host",
|
||||
"PE85":"The remote site ask for a newer session (and UpgradeSession plugin isn't loaded). Logout and retry",
|
||||
"2FManagment":"2ndF Managment",
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
"PE80":"Esta dirección ya está utilizada",
|
||||
"PE81":"Invalid authentication attempt",
|
||||
"PE82":"Exceeded authentication timeout",
|
||||
"PE83":"U2F verification failed",
|
||||
"PE83":"U2F verification failed. Retry or contact your administrator",
|
||||
"PE84":"You're not authorized to access to this host",
|
||||
"PE85":"The remote site ask for a newer session (and UpgradeSession plugin isn't loaded). Logout and retry",
|
||||
"2FManagment":"2ndF Managment",
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
"PE80":"Cette adresse est déjà utilisée",
|
||||
"PE81":"Tentative d'authentification invalide",
|
||||
"PE82":"Délai d'authentification dépassé",
|
||||
"PE83":"La vérification U2F a échoué",
|
||||
"PE83":"La vérification U2F a échoué. Réessayez ou contactez votre administrateur",
|
||||
"PE84":"Vous n'êtes pas autorisé à accéder à ce site",
|
||||
"PE85":"Le site souhaite une authentification plus récente (et le plugin UpgradeSession n'est pas chargé). Déconnectez-vous et réessayez",
|
||||
"2FManagment":"Gestionnaire 2ndF",
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
"PE80":"Dit adres is al in gebruik",
|
||||
"PE81":"Invalid authentication attempt",
|
||||
"PE82":"Exceeded authentication timeout",
|
||||
"PE83":"U2F verification failed",
|
||||
"PE83":"U2F verification failed. Retry or contact your administrator",
|
||||
"PE84":"You're not authorized to access to this host",
|
||||
"PE85":"The remote site ask for a newer session (and UpgradeSession plugin isn't loaded). Logout and retry",
|
||||
"2FManagment":"Gestionnaire 2ndF",
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
"PE80":"Este endereço já é utilizado",
|
||||
"PE81":"Invalid authentication attempt",
|
||||
"PE82":"Exceeded authentication timeout",
|
||||
"PE83":"U2F verification failed",
|
||||
"PE83":"U2F verification failed. Retry or contact your administrator",
|
||||
"PE84":"You're not authorized to access to this host",
|
||||
"PE85":"The remote site ask for a newer session (and UpgradeSession plugin isn't loaded). Logout and retry",
|
||||
"2FManagment":"Gestionnaire 2ndF",
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
"PE80":"This address is already used",
|
||||
"PE81":"Invalid authentication attempt",
|
||||
"PE82":"Exceeded authentication timeout",
|
||||
"PE83":"U2F verification failed",
|
||||
"PE83":"U2F verification failed. Retry or contact your administrator",
|
||||
"PE84":"You're not authorized to access to this host",
|
||||
"PE85":"The remote site ask for a newer session (and UpgradeSession plugin isn't loaded). Logout and retry",
|
||||
"2FManagment":"Gestionnaire 2ndF",
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
|
||||
<span id="verify" class="btn btn-success" role="button">
|
||||
<span class="glyphicon glyphicon-check"></span>
|
||||
<span trspan="verify">Verify</span>
|
||||
<span trspan="register">Register</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,12 +5,7 @@
|
|||
<TMPL_IF NAME="AUTH_ERROR">
|
||||
<div class="message message-<TMPL_VAR NAME="AUTH_ERROR_TYPE"> alert"><span trmsg="<TMPL_VAR NAME="AUTH_ERROR">"></span></div>
|
||||
</TMPL_IF>
|
||||
|
||||
<TMPL_IF NAME="FAILED">
|
||||
<p trspan="u2fFailed"></p>
|
||||
</TMPL_IF>
|
||||
|
||||
<TMPL_IF NAME="CHALLENGE">
|
||||
<TMPL_IF NAME="DATA">
|
||||
<div class="message message-positive alert"><span trspan="touchU2fDevice"></span></div>
|
||||
<form id="verify-form" action="/u2fcheck" method="post">
|
||||
<input type="hidden" id="verify-data" name="signature" value="">
|
||||
|
@ -18,7 +13,7 @@
|
|||
<input type="hidden" id="token" name="token" value="<TMPL_VAR NAME="TOKEN">">
|
||||
</form>
|
||||
<script type="application/init">
|
||||
<TMPL_VAR NAME="CHALLENGE">
|
||||
<TMPL_VAR NAME="DATA">
|
||||
</script>
|
||||
<!-- //if:jsminified
|
||||
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">/common/js/u2f-api.min.js"></script>
|
||||
|
|
|
@ -147,22 +147,22 @@ JjTJecOOS+88fK8qL1TrYv5rapIdqUI7aQ==
|
|||
( $host, $url, $query ) = expectForm( $res, undef, '/u2fcheck', 'token' );
|
||||
|
||||
# Get challenge
|
||||
ok( $res->[2]->[0] =~ /^.*"keyHandle".*$/m, ' get keyHandle' );
|
||||
$data = $&;
|
||||
ok( $res->[2]->[0] =~ /^.*"keyHandle":.*$/m, ' get keyHandle' );
|
||||
$data = "$&";
|
||||
eval { $data = JSON::from_json($data) };
|
||||
ok( not($@), ' Content is JSON' )
|
||||
or explain( [ $@, $data ], 'JSON content' );
|
||||
|
||||
# Build U2F signature
|
||||
$r =
|
||||
$tester->sign( $data->{appId}, $data->{challenge}, $data->{keyHandle} );
|
||||
$tester->sign( $data->{appId}, $data->{challenge}, $data->{registeredKeys}->[0]->{keyHandle} );
|
||||
ok( $r->is_success, ' Good challenge value' ) or diag( $r->error_message );
|
||||
my $sign = JSON::to_json(
|
||||
{
|
||||
errorCode => 0,
|
||||
signatureData => $r->signature_data,
|
||||
clientData => $r->client_data,
|
||||
keyHandle => $data->{keyHandle},
|
||||
keyHandle => $data->{registeredKeys}->[0]->{keyHandle},
|
||||
}
|
||||
);
|
||||
$sign =
|
||||
|
|
Loading…
Reference in New Issue
Block a user