2018-12-04 18:14:19 +01:00
|
|
|
package Lemonldap::NG::Portal::Auth::GPG;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use File::Temp 'tempdir';
|
|
|
|
use IPC::Run;
|
|
|
|
use Mouse;
|
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
2019-02-05 23:12:17 +01:00
|
|
|
PE_BADCREDENTIALS
|
|
|
|
PE_ERROR
|
|
|
|
PE_FORMEMPTY
|
|
|
|
PE_NOTOKEN
|
|
|
|
PE_OK
|
2018-12-04 18:14:19 +01:00
|
|
|
);
|
|
|
|
|
2019-02-12 18:21:38 +01:00
|
|
|
our $VERSION = '2.1.0';
|
2018-12-04 18:14:19 +01:00
|
|
|
|
|
|
|
extends 'Lemonldap::NG::Portal::Auth::_WebForm';
|
|
|
|
|
|
|
|
has db => ( is => 'rw' );
|
|
|
|
has tmp => (
|
|
|
|
is => 'rw',
|
|
|
|
default => sub {
|
|
|
|
tempdir( CLEANUP => 1 );
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
sub init {
|
2019-04-05 17:24:16 +02:00
|
|
|
my $self = shift;
|
|
|
|
|
2018-12-04 18:14:19 +01:00
|
|
|
$self->db( $self->conf->{gpgDb} );
|
|
|
|
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 ) = @_;
|
|
|
|
|
2019-04-05 17:24:16 +02:00
|
|
|
unless ( $self->ottRule->( $req, {} ) ) {
|
|
|
|
$self->error("OTT isn't set, unable to use GPG");
|
|
|
|
}
|
|
|
|
|
2018-12-04 18:14:19 +01:00
|
|
|
# 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(
|
2019-02-05 23:12:17 +01:00
|
|
|
"Launching:\ngpgv --homedir /dev/null --keyring $self->{db} <<EOF\n$signed\nEOF"
|
2018-12-04 18:14:19 +01:00
|
|
|
);
|
2019-01-06 22:28:25 +01:00
|
|
|
my ( $lang, $language ) = ( $ENV{LANG}, $ENV{LANGUAGE} );
|
|
|
|
$ENV{LANG} = $ENV{LANGUAGE} = 'C';
|
2019-02-05 23:12:17 +01:00
|
|
|
IPC::Run::run( [ 'gpgv', '--homedir', '/dev/null', '--keyring', $self->db ],
|
2018-12-04 18:14:19 +01:00
|
|
|
\$signed, \$out, \$err, IPC::Run::timeout(10) );
|
|
|
|
if ( $? >> 8 != 0 ) {
|
2019-02-05 23:12:17 +01:00
|
|
|
$self->userLogger->error("GPG verification fails:\n$out\n# # #\n$err");
|
2018-12-04 18:14:19 +01:00
|
|
|
$self->setSecurity($req);
|
|
|
|
return PE_BADCREDENTIALS;
|
|
|
|
}
|
|
|
|
$self->setSecurity($req);
|
|
|
|
$self->userLogger->notice("Good GPG signature");
|
2018-12-26 23:22:25 +01:00
|
|
|
$self->userLogger->debug("GPG out:\n$out\n$err");
|
|
|
|
unless ( $err =~ /using .*? key (.*)$/m ) {
|
2018-12-04 18:14:19 +01:00
|
|
|
$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;
|
2019-02-07 09:27:56 +01:00
|
|
|
IPC::Run::run( [
|
2019-02-05 23:12:17 +01:00
|
|
|
'gpg', '--homedir', $self->tmp, '--keyring',
|
2018-12-04 18:14:19 +01:00
|
|
|
$self->db, '--list-key', $key
|
|
|
|
],
|
|
|
|
\$in,
|
|
|
|
\$out,
|
|
|
|
\$err,
|
|
|
|
IPC::Run::timeout(10)
|
|
|
|
);
|
2019-01-06 22:28:25 +01:00
|
|
|
( $ENV{LANG}, $ENV{LANGUAGE} ) = ( $lang, $language );
|
2018-12-04 18:14:19 +01:00
|
|
|
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 ) = @_;
|
2019-05-11 20:18:43 +02:00
|
|
|
$req->sessionInfo->{gpgMail} = $req->data->{gpgMail};
|
2019-04-05 22:58:48 +02:00
|
|
|
$req->sessionInfo->{authenticationLevel} = $self->conf->{gpgAuthnLevel};
|
|
|
|
PE_OK;
|
2018-12-04 18:14:19 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
sub authLogout {
|
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
2019-03-19 05:56:36 +01:00
|
|
|
sub getForm {
|
2018-12-04 18:14:19 +01:00
|
|
|
return "gpgform";
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|