Commit c1e734e1 authored by Christophe Maudoux's avatar Christophe Maudoux 🐛

Merge remote-tracking branch 'upstream/master' into manager-u2f-module_add-u2f-key

parents d306f0e5 2c8c7047
......@@ -30,25 +30,37 @@ sub run {
return [ 200, [ 'Content-Type' => 'application/json' ], [$challenge] ];
}
if ( $action eq 'registration' ) {
my $resp;
unless ( $resp = $req->param('registration') ) {
my ( $resp, $challenge );
unless ($resp = $req->param('registration')
and $challenge = $req->param('challenge') )
{
return $self->p->sendError( $req, 'Missing registration parameter',
400 );
}
$self->logger->debug("Get registration data $resp");
my ( $keyHandle, $userKey ) = $self->crypter->registrationVerify($resp);
if ( $keyHandle and $userKey ) {
$self->p->updatePersistentSession(
$req,
{
_u2fKeyHandle => $self->encode_base64url( $keyHandle, '' ),
_u2fUserKey => $self->encode_base64url( $userKey, '' )
}
);
return [
200, [ 'Content-Type' => 'application/json' ],
['{"result":1}']
];
$self->logger->debug("Get challenge $challenge");
eval { $challenge = JSON::from_json($challenge)->{challenge} };
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 ) {
$self->p->updatePersistentSession(
$req,
{
_u2fKeyHandle =>
$self->encode_base64url( $keyHandle, '' ),
_u2fUserKey => $self->encode_base64url( $userKey, '' )
}
);
return [
200, [ 'Content-Type' => 'application/json' ],
['{"result":1}']
];
}
}
my $err = Crypt::U2F::Server::Simple::lastError();
$self->userLogger->warn("U2F Registration failed: $err");
......@@ -60,25 +72,36 @@ sub run {
return [ 200, [ 'Content-Type' => 'application/json' ], [$challenge] ];
}
if ( $action eq 'unregistration' ) {
my $resp;
unless ( $resp = $req->param('registration') ) {
return $self->p->sendError( $req, 'Missing unregistration parameter',
my ( $resp, $challenge );
unless ($resp = $req->param('registration')
and $challenge = $req->param('challenge') )
{
return $self->p->sendError( $req, 'Missing registration parameter',
400 );
}
$self->logger->debug("Get unregistration data $resp");
my ( $keyHandle, $userKey ) = $self->crypter->registrationVerify($resp);
if ( $keyHandle and $userKey ) {
$self->p->updatePersistentSession(
$req,
{
_u2fKeyHandle => '',
_u2fUserKey => ''
}
);
return [
200, [ 'Content-Type' => 'application/json' ],
['{"result":1}']
];
$self->logger->debug("Get challenge $challenge");
eval { $challenge = JSON::from_json($challenge)->{challenge} };
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 ) {
$self->p->updatePersistentSession(
$req,
{
_u2fKeyHandle => '',
_u2fUserKey => ''
}
);
return [
200, [ 'Content-Type' => 'application/json' ],
['{"result":1}']
];
}
}
my $err = Crypt::U2F::Server::Simple::lastError();
$self->userLogger->warn("U2F Unregistration failed: $err");
......@@ -93,7 +116,7 @@ sub run {
elsif ( $err == 0 ) {
return $self->p->sendError( $req, "noU2FKeyFound" );
}
my $challenge = $self->crypter->authenticationChallenge;
my $challenge = $req->data->{crypter}->authenticationChallenge;
return [ 200, [ 'Content-Type' => 'application/json' ], [$challenge] ];
}
if ( $action eq 'signature' ) {
......@@ -109,7 +132,8 @@ sub run {
elsif ( $err == 0 ) {
return $self->p->sendError( $req, "noU2FKeyFound" );
}
my $res = ( $self->crypter->authenticationVerify($resp) ? 1 : 0 );
my $res =
( $req->data->{crypter}->authenticationVerify($resp) ? 1 : 0 );
return [
200, [ 'Content-Type' => 'application/json' ],
[qq'{"result":$res}']
......@@ -126,9 +150,11 @@ sub loadUser {
unless ( $kh and $uk ) {
return 0;
}
$self->crypter->{keyHandle} = $self->decode_base64url($kh);
$self->crypter->{publicKey} = $self->decode_base64url($uk);
unless ( $self->crypter->setKeyHandle and $self->crypter->setPublicKey ) {
$req->data->{crypter} = $self->crypter(
keyHandle => $self->decode_base64url($kh),
publicKey => $self->decode_base64url($uk)
);
unless ( $req->data->{crypter} ) {
my $error = Crypt::U2F::Server::Simple::lastError();
return ( -1, $error );
}
......
......@@ -50,10 +50,10 @@ sub run {
my ( $kh, $uk );
# Check if user is registered
if ( my $res = $self->loadUser( $req->sessionInfo ) ) {
if ( my $res = $self->loadUser( $req, $req->sessionInfo ) ) {
return PE_ERROR if ( $res == -1 );
my $challenge = $self->crypter->authenticationChallenge;
my $challenge = $req->datas->{crypter}->authenticationChallenge;
my $tmp = $self->p->sendHtml(
$req,
'u2fcheck',
......@@ -75,12 +75,22 @@ sub verify {
my ( $self, $req, $session ) = @_;
# Check U2F signature
if ( my $resp = $req->param('signature') ) {
unless ( $self->loadUser($session) == 1 ) {
if ( my $resp = $req->param('signature')
and my $challenge = $req->param('challenge') )
{
unless ( $self->loadUser( $req, $session ) == 1 ) {
$req->error(PE_ERROR);
return $self->fail($req);
}
if ( $self->crypter->authenticationVerify($resp) ) {
$self->logger->debug("Get challenge: $challenge");
#eval { $challenge = JSON::from_json($challenge)->{challenge} };
if ( not $req->datas->{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) ) {
$self->userLogger->info('U2F signature verified');
return PE_OK;
}
......@@ -95,7 +105,7 @@ sub verify {
}
}
else {
$self->userLogger->notice( 'No U2F response for user'
$self->userLogger->notice( 'No valid U2F response for user'
. $session->{ $self->conf->{whatToTrace} } );
$req->authResult(PE_U2FFAILED);
return $self->fail($req);
......@@ -120,16 +130,16 @@ sub fail {
}
sub loadUser {
my ( $self, $session ) = @_;
my ( $self, $req, $session ) = @_;
my ( $kh, $uk );
if ( ( $kh = $session->{_u2fKeyHandle} )
and ( $uk = $session->{_u2fUserKey} ) )
{
$self->crypter->{keyHandle} = $self->decode_base64url($kh);
$self->crypter->{publicKey} = $self->decode_base64url($uk);
unless ($self->crypter->setKeyHandle
and $self->crypter->setPublicKey )
{
$req->datas->{crypter} = $self->crypter(
keyHandle => $self->decode_base64url($kh),
publicKey => $self->decode_base64url($uk)
);
unless ( $req->datas->{crypter} ) {
$self->logger->error(
'U2F error: ' . Crypt::U2F::Server::u2fclib_getError() );
return -1;
......
......@@ -8,8 +8,6 @@ our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Plugin';
has crypter => ( is => 'rw' );
has origin => ( is => 'rw', );
sub init {
......@@ -22,12 +20,12 @@ sub init {
my $p = $_[0]->{conf}->{portal};
$p =~ s#^(https?://[^/]+).*$#$1#;
$self->origin($p);
# Test if a new object can be created
unless (
$self->crypter(
Crypt::U2F::Server::Simple->new(
appId => $self->origin,
origin => $self->origin,
)
Crypt::U2F::Server::Simple->new(
appId => $self->origin,
origin => $self->origin,
)
)
{
......@@ -37,6 +35,15 @@ sub init {
return 1;
}
sub crypter {
my ( $self, %args ) = @_;
return Crypt::U2F::Server::Simple->new(
appId => $self->origin,
origin => $self->origin,
%args,
);
}
sub encode_base64url {
shift;
my $e = encode_base64( shift, '' );
......
......@@ -151,8 +151,8 @@ L<Lemonldap::NG::Portal> second factor plugins.
# $token must be inserted in a hidden input in your form with the name
# "token"
...
# A LLNG constant must be returned. Example
$req->response($my_html_form_page)
# A LLNG constant must be returned. Example:
$req->response($my_psgi_response)
return PE_SENDRESPONSE;
}
sub verify {
......
......@@ -10,6 +10,7 @@ check = ->
console.log 'Key: ', registeredKey
u2f.sign window.datas.appId, window.datas.challenge, registeredKey, (data) ->
$('#verify-data').val JSON.stringify(data)
$('#verify-challenge').val window.datas.challenge
$('#verify-form').submit()
$(document).ready ->
......
......@@ -46,6 +46,7 @@ register = ->
url: "#{portal}u2fregister/registration"
data:
registration: JSON.stringify data
challenge: JSON.stringify ch
dataType: 'json'
success: (resp) ->
if resp.error
......@@ -83,6 +84,7 @@ unregister = ->
url: "#{portal}u2fregister/unregistration"
data:
registration: JSON.stringify data
challenge: JSON.stringify ch
dataType: 'json'
success: (resp) ->
if resp.error
......
// Generated by CoffeeScript 1.10.0
// Generated by CoffeeScript 1.12.7
/*
LemonLDAP::NG U2F verify script
......@@ -18,6 +18,7 @@ LemonLDAP::NG U2F verify script
console.log('Key: ', registeredKey);
return u2f.sign(window.datas.appId, window.datas.challenge, registeredKey, function(data) {
$('#verify-data').val(JSON.stringify(data));
$('#verify-challenge').val(window.datas.challenge);
return $('#verify-form').submit();
});
};
......
(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));return $("#verify-form").submit()})};$(document).ready(function(){return setTimeout(a,1000)})}).call(this);
\ No newline at end of file
(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);
\ No newline at end of file
// Generated by CoffeeScript 1.10.0
// Generated by CoffeeScript 1.12.7
/*
LemonLDAP::NG U2F registration script
*/
(function() {
var displayError, register, setMsg, verify, unregister;
var displayError, register, setMsg, unregister, verify;
setMsg = function(msg, level) {
$('#msg').html(window.translate(msg));
......@@ -54,7 +54,8 @@ LemonLDAP::NG U2F registration script
type: "POST",
url: portal + "u2fregister/registration",
data: {
registration: JSON.stringify(data)
registration: JSON.stringify(data),
challenge: JSON.stringify(ch)
},
dataType: 'json',
success: function(resp) {
......@@ -98,7 +99,8 @@ LemonLDAP::NG U2F registration script
type: "POST",
url: portal + "u2fregister/unregistration",
data: {
registration: JSON.stringify(data)
registration: JSON.stringify(data),
challenge: JSON.stringify(ch)
},
dataType: 'json',
success: function(resp) {
......@@ -163,8 +165,8 @@ LemonLDAP::NG U2F registration script
$(document).ready(function() {
$('#u2fPermission').hide();
$('#register').on('click', register);
$('#verify').on('click', verify);
$('#unregister').on('click', unregister);
$('#verify').on('click', verify);
return $('#goback').attr('href', portal);
});
......
(function(){var a,c,d,e,b;d=function(f,g){$("#msg").html(window.translate(f));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+g);if(g==="positive"){g="success"}return $("#color").addClass("alert-"+g)};a=function(g,f,i){var h;console.log("Error",i);h=JSON.parse(g.responseText);if(h&&h.error){h=h.error.replace(/.* /,"");console.log("Returned error",h);return d(h,"warning")}};c=function(){return $.ajax({type:"POST",url:portal+"u2fregister/register",data:{},dataType:"json",error:a,success:function(f){var g;g=[{challenge:f.challenge,version:f.version}];d("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(f.appId,g,[],function(h){$("#u2fPermission").hide();if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/registration",data:{registration:JSON.stringify(h)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fRegistered","positive")}}},error:a})}})}})};b=function(){return $.ajax({type:"POST",url:portal+"u2fregister/unregister",data:{},dataType:"json",error:a,success:function(f){var g;g=[{challenge:f.challenge,version:f.version}];d("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(f.appId,g,[],function(h){$("#u2fPermission").hide();if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/unregistration",data:{registration:JSON.stringify(h)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fUnregistered","positive")}}},error:a})}})}})};e=function(){return $.ajax({type:"POST",url:portal+"u2fregister/verify",data:{},dataType:"json",error:a,success:function(f){var g;g=[{keyHandle:f.keyHandle,version:f.version}];d("touchU2fDevice","positive");return u2f.sign(f.appId,f.challenge,g,function(h){if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/signature",data:{signature:JSON.stringify(h)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fSuccess","positive")}}},error:function(k,i,l){return console.log("error",l)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",c);$("#verify").on("click",e);$("#unregister").on("click",b);return $("#goback").attr("href",portal)})}).call(this);
\ No newline at end of file
(function(){var a,c,d,b,e;d=function(f,g){$("#msg").html(window.translate(f));$("#color").removeClass("message-positive message-warning alert-success alert-warning");$("#color").addClass("message-"+g);if(g==="positive"){g="success"}return $("#color").addClass("alert-"+g)};a=function(g,f,i){var h;console.log("Error",i);h=JSON.parse(g.responseText);if(h&&h.error){h=h.error.replace(/.* /,"");console.log("Returned error",h);return d(h,"warning")}};c=function(){return $.ajax({type:"POST",url:portal+"u2fregister/register",data:{},dataType:"json",error:a,success:function(f){var g;g=[{challenge:f.challenge,version:f.version}];d("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(f.appId,g,[],function(h){$("#u2fPermission").hide();if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/registration",data:{registration:JSON.stringify(h),challenge:JSON.stringify(f)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fRegistered","positive")}}},error:a})}})}})};b=function(){return $.ajax({type:"POST",url:portal+"u2fregister/unregister",data:{},dataType:"json",error:a,success:function(f){var g;g=[{challenge:f.challenge,version:f.version}];d("touchU2fDevice","positive");$("#u2fPermission").show();return u2f.register(f.appId,g,[],function(h){$("#u2fPermission").hide();if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/unregistration",data:{registration:JSON.stringify(h),challenge:JSON.stringify(f)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fUnregistered","positive")}}},error:a})}})}})};e=function(){return $.ajax({type:"POST",url:portal+"u2fregister/verify",data:{},dataType:"json",error:a,success:function(f){var g;g=[{keyHandle:f.keyHandle,version:f.version}];d("touchU2fDevice","positive");return u2f.sign(f.appId,f.challenge,g,function(h){if(h.errorCode){return d("unableToGetU2FKey","warning")}else{return $.ajax({type:"POST",url:portal+"u2fregister/signature",data:{signature:JSON.stringify(h)},dataType:"json",success:function(i){if(i.error){return d("u2fFailed","warning")}else{if(i.result){return d("u2fSuccess","positive")}}},error:function(k,i,l){return console.log("error",l)}})}})}})};$(document).ready(function(){$("#u2fPermission").hide();$("#register").on("click",c);$("#unregister").on("click",b);$("#verify").on("click",e);return $("#goback").attr("href",portal)})}).call(this);
\ No newline at end of file
......@@ -14,6 +14,7 @@
<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="">
<input type="hidden" id="verify-challenge" name="challenge" value="">
<input type="hidden" id="token" name="token" value="<TMPL_VAR NAME="TOKEN">">
</form>
<script type="application/init">
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment