Commit 1e3ab69b authored by Xavier Guimard's avatar Xavier Guimard

Add minimal Auth::GPG

parent 2dfe4bdd
......@@ -277,6 +277,7 @@ Suggests: libcrypt-u2f-server-perl,
libglib-perl,
libgssapi-perl,
libimage-magick-perl,
libipc-run-perl,
liblasso-perl,
libnet-facebook-oauth2-perl (>= 0.10),
libnet-openid-consumer-perl,
......
......@@ -27,6 +27,7 @@ lib/Lemonldap/NG/Portal/Auth/Custom.pm
lib/Lemonldap/NG/Portal/Auth/DBI.pm
lib/Lemonldap/NG/Portal/Auth/Demo.pm
lib/Lemonldap/NG/Portal/Auth/Facebook.pm
lib/Lemonldap/NG/Portal/Auth/GPG.pm
lib/Lemonldap/NG/Portal/Auth/Kerberos.pm
lib/Lemonldap/NG/Portal/Auth/LDAP.pm
lib/Lemonldap/NG/Portal/Auth/LinkedIn.pm
......@@ -324,6 +325,7 @@ site/templates/bootstrap/customLoginHeader.tpl
site/templates/bootstrap/error.tpl
site/templates/bootstrap/ext2fcheck.tpl
site/templates/bootstrap/footer.tpl
site/templates/bootstrap/gpgform.tpl
site/templates/bootstrap/header.tpl
site/templates/bootstrap/idpchoice.tpl
site/templates/bootstrap/info.tpl
......@@ -402,6 +404,7 @@ t/26-AuthRemote.t
t/27-AuthProxy.t
t/28-AuthChoice-and-password.t
t/28-AuthChoice-with-rules.t
t/29-AuthGPG.t
t/29-AuthSSL.t
t/30-Auth-and-issuer-SAML-Artifact-with-SOAP-SLO-IdP-initiated.t
t/30-Auth-and-issuer-SAML-Artifact-with-SOAP-SLO.t
......@@ -499,6 +502,12 @@ t/76-2F-Ext-with-GrantSession.t
t/76-2F-Ext-with-HISTORY.t
t/90-Translations.t
t/99-pod.t
t/gpghome/key.asc
t/gpghome/openpgp-revocs.d/9482CEFB055809CBAFE6D71AAB2D5542891D1677.rev
t/gpghome/private-keys-v1.d/A076B0E7DB141A919271EE8B581CDFA8DA42F333.key
t/gpghome/private-keys-v1.d/B7219440BCCD85200121CFB89F94C8D98C0397B3.key
t/gpghome/pubring.kbx
t/gpghome/trustdb.gpg
t/lmConf-1.json
t/pdata.pm
t/README.md
......
......@@ -16,6 +16,7 @@ WriteMakefile(
'Glib' => 0,
'HTTP::Message' => 0,
'Image::Magick' => 0,
'IPC::Run' => 0,
'Lasso' => '2.3.0',
'LWP::UserAgent' => 0,
'LWP::Protocol::https' => 0,
......
package Lemonldap::NG::Portal::Auth::GPG;
use strict;
use File::Temp 'tempdir';
use IPC::Run;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_BADCREDENTIALS
PE_ERROR
PE_FIRSTACCESS
PE_FORMEMPTY
PE_NOTOKEN
PE_OK
);
our $VERSION = '2.0.1';
extends 'Lemonldap::NG::Portal::Auth::_WebForm';
has db => ( is => 'rw' );
has tmp => (
is => 'rw',
default => sub {
tempdir( CLEANUP => 1 );
},
);
sub init {
my ($self) = @_;
$self->db( $self->conf->{gpgDb} );
unless ( $_[0]->{conf}->{requireToken} ) {
$self->error("requireToken isn't set, unable to use GPG");
}
unless ( $self->db ) {
$self->error("gpgDb not set");
return 0;
}
unless ( -r $self->db ) {
$self->error( "Unable to read " . $self->db );
return 0;
}
return $self->SUPER::init();
}
sub extractFormInfo {
my ( $self, $req ) = @_;
# Keep token data for later use
my ( $token, $gpgToken );
if ( $token = $req->param('token') ) {
$gpgToken = $self->ott->getToken($token);
$req->data->{tokenVerified} = 1 if ($gpgToken);
}
my $res = $self->SUPER::extractFormInfo($req);
return $res if ($res);
my $signed = $req->data->{password};
unless ( $signed =~ /SIGNATURE/s ) {
$self->userLogger->error("Bad signature content");
$self->userLogger->debug($signed);
$self->setSecurity($req);
return PE_BADCREDENTIALS;
}
unless ( $signed =~ /\b\Q$token\E\b/ ) {
$self->userLogger->error("User replayed a bad token in GPG !");
$self->setSecurity($req);
return PE_BADCREDENTIALS;
}
my ( $out, $err );
$self->logger->debug(
"Launching:\ngpgv --homedir /dev/null --keyring $self->{db} <<EOF\n$signed\nEOF"
);
my $lang = $ENV{LANG};
$ENV{LANG} = 'C';
IPC::Run::run( [ 'gpgv', '--homedir', '/dev/null', '--keyring', $self->db ],
\$signed, \$out, \$err, IPC::Run::timeout(10) );
if ( $? >> 8 != 0 ) {
$self->userLogger->error("GPG verification fails:\n$out\n# # #\n$err");
$self->setSecurity($req);
return PE_BADCREDENTIALS;
}
$self->setSecurity($req);
$self->userLogger->notice("Good GPG signature");
$self->userLogger->debug("GPG out:\n$out\n$err");
unless ( $err =~ /using .*? key (.*)$/m ) {
$self->logger->error("Unable to parse gpgv result:\n$err");
return PE_ERROR;
}
my $key = $1;
chomp $key;
$self->logger->debug("GPG full sign key: $key");
my $in;
IPC::Run::run(
[
'gpg', '--homedir', $self->tmp, '--keyring',
$self->db, '--list-key', $key
],
\$in,
\$out,
\$err,
IPC::Run::timeout(10)
);
$ENV{LANG} = $lang;
if ( $? >> 8 != 0 ) {
$self->logger->error("gpg --list-key return an error:\n$err");
return PE_ERROR;
}
unless ( $out =~ /pub [^\n]*\r?\n +([^\n]+)\n/ ) {
$self->logger->error(
"Unable to parse gpg --list-key result:\n$out\n$err\n");
return PE_ERROR;
}
$key = $1;
chomp $key;
$self->logger->debug("GPG full master key: $key");
# Keep only gpgKeyLength characters
my $length = $self->conf->{gpgKeyLength} || 8;
$length = 8 if ( $length < 8 );
$key =~ s/.*?(.{8,$length})$/$1/;
$self->logger->info("User key: $key");
my @identities = ( $out =~ /uid\s+(.*)$/gm );
my $mail = $req->param('user');
foreach (@identities) {
if (/^(.*)\s+<\Q$mail\E>/) {
$req->data->{gpgFullName} = $1;
$req->data->{gpgMail} = $mail;
$req->user($mail);
$self->userLogger->notice("GPG user $mail authenticated");
return PE_OK;
}
}
$self->userLogger->warn("Given mail does not match with gpg key");
$self->setSecurity($req);
return PE_BADCREDENTIALS;
}
sub authenticate {
PE_OK;
}
sub setAuthSessionInfo {
my ( $self, $req ) = @_;
$req->sessionInfo->{gpgMail} = $req->data->{gpgMail};
return PE_OK;
}
sub authLogout {
PE_OK;
}
sub getDisplayType {
return "gpgform";
}
1;
......@@ -401,6 +401,7 @@ sub display {
? 1
: 0,
DISPLAY_SSL_FORM => $displayType =~ /sslform/ ? 1 : 0,
DISPLAY_GPG_FORM => $displayType =~ /gpgform/ ? 1 : 0,
DISPLAY_LOGO_FORM => $displayType eq "logo" ? 1 : 0,
module => $displayType eq "logo"
? $self->getModule( $req, 'auth' )
......
<span trspan="Please sign the following text with your GPG key"></span>
<pre>echo -n "<TMPL_VAR NAME="TOKEN">"| gpg --clear-sign</pre>
<div class="form">
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-user"></i> </span>
</div>
<input name="user" type="text" class="form-control" value="<TMPL_VAR NAME="LOGIN">" trplaceholder="mail" required aria-required="true"/>
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-lock"></i> </span>
</div>
<textarea name="password" class="form-control" trplaceholder="Signed text" required aria-required="true"></textarea>
</div>
<TMPL_IF NAME=CAPTCHA_SRC>
<div class="form-group">
<img src="<TMPL_VAR NAME=CAPTCHA_SRC>" class="img-thumbnail" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text"><i class="fa fa-eye"></i> </span>
</div>
<input type="text" name="captcha" size="<TMPL_VAR NAME=CAPTCHA_SIZE>" class="form-control" trplaceholder="captcha" required aria-required="true"/>
</div>
</TMPL_IF>
<input type="hidden" name="token" value="<TMPL_VAR NAME="TOKEN">" />
<TMPL_INCLUDE NAME="checklogins.tpl">
<button type="submit" class="btn btn-success" >
<span class="fa fa-sign-in"></span>
<span trspan="connect">Connect</span>
</button>
</div>
<div class="actions">
<TMPL_IF NAME="DISPLAY_RESETPASSWORD">
<a class="btn btn-secondary" href="<TMPL_VAR NAME="MAIL_URL">?skin=<TMPL_VAR NAME="SKIN"><TMPL_IF NAME="key">&<TMPL_VAR NAME="CHOICE_PARAM">=<TMPL_VAR NAME="key"></TMPL_IF><TMPL_IF NAME="AUTH_URL">&url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>">
<span class="fa fa-info-circle"></span>
<span trspan="resetPwd">Reset my password</span>
</a>
</TMPL_IF>
<TMPL_IF NAME="DISPLAY_REGISTER">
<a class="btn btn-secondary" href="<TMPL_VAR NAME="REGISTER_URL">?skin=<TMPL_VAR NAME="SKIN"><TMPL_IF NAME="key">&<TMPL_VAR NAME="CHOICE_PARAM">=<TMPL_VAR NAME="key"></TMPL_IF><TMPL_IF NAME="AUTH_URL">&url=<TMPL_VAR NAME="AUTH_URL"></TMPL_IF>">
<span class="fa fa-plus-circle"></span>
<span trspan="createAccount">Create an account</span>
</a>
</TMPL_IF>
</div>
......@@ -145,6 +145,23 @@
</div>
</TMPL_IF>
<TMPL_IF NAME="DISPLAY_GPG_FORM">
<div class="card">
<TMPL_IF NAME="module">
<form id="lform" action="#" method="post" class="login <TMPL_VAR NAME="module">" role="form">
<TMPL_ELSE>
<form id="lform" action="#" method="post" class="login" role="form">
</TMPL_IF>
<!-- Hidden fields -->
<TMPL_VAR NAME="HIDDEN_INPUTS">
<input type="hidden" name="url" value="<TMPL_VAR NAME="AUTH_URL">" />
<input type="hidden" name="timezone" />
<input type="hidden" name="skin" value="<TMPL_VAR NAME="SKIN">" />
<TMPL_INCLUDE NAME="gpgform.tpl">
</form>
</div>
</TMPL_IF>
<TMPL_IF NAME="DISPLAY_YUBIKEY_FORM">
<div class="card">
<TMPL_IF NAME="module">
......
use Test::More;
use IO::String;
use strict;
require 't/test-lib.pm';
my $mainTests = 5;
SKIP: {
eval "use IPC::Run 'run',";
skip "Missing dependency", $mainTests if ($@);
my $gpg = `which gpg`;
skip "Missing gpg", $mainTests if ($@);
chomp $gpg;
my $res;
use_ok('Lemonldap::NG::Common::FormEncode');
my $client = LLNG::Manager::Test->new(
{
ini => {
logLevel => 'error',
authentication => 'GPG',
userDB => 'Null',
gpgDb => 't/gpghome/key.asc',
requireToken => 1,
}
}
);
ok( $res = $client->_get( '/', accept => 'text/html' ), 'First access' );
my ( $host, $url, $query ) =
expectForm( $res, '#', undef, 'user', 'token' );
$query =~ s/user=/user=llng\@lemonldap-ng.org/;
$query =~ /token=([^&]+)/;
my $token = $1 or fail('No token');
ok( $res->[2]->[0] =~ /echo -n "$token"/m, "Found instructions" );
my ( $out, $err );
run( [ 'gpg', '--clear-sign', '--homedir', 't/gpghome' ],
\$token, \$out, \$err, IPC::Run::timeout(10) );
ok( $? == 0, "Succeed to sign" );
$query .= '&' . build_urlencoded( password => $out );
ok(
$res = $client->_post(
'/',
IO::String->new($query),
length => length($query),
),
'Post data'
);
expectOK($res);
expectCookie($res);
}
clean_sessions();
done_testing( count($mainTests) );
Ceci est un certificat de révocation pour la clef OpenPGP :
pub rsa3072 2018-12-04 [SC] [expire : 2020-12-03]
9482CEFB055809CBAFE6D71AAB2D5542891D1677
uid LL::NG <llng@lemonldap-ng.org>
A revocation certificate is a kind of "kill switch" to publicly
declare that a key shall not anymore be used. It is not possible
to retract such a revocation certificate once it has been published.
Use it to revoke this key in case of a compromise or loss of
the secret key. However, if the secret key is still accessible,
it is better to generate a new revocation certificate and give
a reason for the revocation. For details see the description of
of the gpg command "--generate-revocation" in the GnuPG manual.
To avoid an accidental use of this file, a colon has been inserted
before the 5 dashes below. Remove this colon with a text editor
before importing and publishing this revocation certificate.
:-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: This is a revocation certificate
iQG2BCABCgAgFiEElILO+wVYCcuv5tcaqy1VQokdFncFAlwG+U8CHQAACgkQqy1V
QokdFndF6Qv+Pk5xMIXm43dXghosAJZQTI1VvCDDmfhvSedoU+bweANKvnnugLnA
EGSaHFTY6/vn1928VZBtNJkumVnsT2QeL6nhZcf2QZdWs+1yc3LcRy5lvjgLfxXV
hLiDjUGUcENiMPkpmAf/p+4giC7nvYpUPnekQVsx6OYmf5ZfagMGBFa2gN/o2a42
lW4xNlM6213p9s6hlftR6acCQ1nu+aJMMpHeyVz2RC5FZM48CTgZDKoybpS6La7l
NzFOPvq/RQ3awtOehF51GGs0cxVZQ8GjUamZjdhZI3cV1YsG2huKVERdgUywQf4d
nic+nAqb4wQSDcuzH0Hqt7ieXG/y8wMRtzB3lYaqTwWTJ1QSs/ygehEWuWIiRJsd
RCQFkcIO5m0I4nxOJD8EA/71BnxeX6UwL/nqf4QvyBX+lAHZoWEo+rR1qwDLuUW+
iAx8lgHc0VbqX5XTl6xjvf/IDzK7WkjPpuI1018SjhYppj6Htkq/Ua3rwBXJ0bJx
3Sh+AbLnIK0w
=LO4h
-----END PGP PUBLIC KEY BLOCK-----
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