Implement 'Double cookie for single session' feature (Lemonldap-421)

+ Fix bug with http vhosts and double cookie (Lemonldap-420)
environments/ppa-mbqj77/deployments/1
François-Xavier Deltombe 14 years ago
parent b63e8ee908
commit bdfc006b8f
  1. 3
      modules/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf.pm
  2. 125
      modules/lemonldap-ng-common/lib/Lemonldap/NG/Common/Crypto.pm
  3. 13
      modules/lemonldap-ng-common/t/35-Common-Crypto.t
  4. 34
      modules/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Simple.pm
  5. 19
      modules/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Vhost.pm
  6. 3
      modules/lemonldap-ng-manager/example/skins/default/js/manager.js
  7. 3
      modules/lemonldap-ng-manager/example/skins/default/manager.tpl
  8. 5
      modules/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/_Struct.pm
  9. 10
      modules/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Simple.pm

@ -171,8 +171,7 @@ sub getConf {
unless ( $r->{key} );
eval {
$r->{cipher} = Lemonldap::NG::Common::Crypto->new(
$r->{key} || 'lemonldap-ng-key',
Crypt::Rijndael::MODE_CBC()
$r->{key} || 'lemonldap-ng-key'
);
};
if ($@) {

@ -1,8 +1,10 @@
##@file
# Extend Crypt::Rijndael to add base64 encoding to cypher functions
# Extend Crypt::Rijndael to get several keys from a single secret key,
# add base64 encoding of binary data, and cipher hexadecimal data.
##@class
# Extend Crypt::Rijndael to add base64 encoding to cypher functions.
# Extend Crypt::Rijndael to get several keys from a single secret key,
# add base64 encoding of binary data, and cipher hexadecimal data.
# $Lemonldap::NG::Common::Crypto::msg contains Crypt::Rijndael errors.
package Lemonldap::NG::Common::Crypto;
@ -10,36 +12,54 @@ use strict;
use Crypt::Rijndael;
use MIME::Base64;
use bytes;
use base qw(Crypt::Rijndael);
our $VERSION = '1.0.0';
our $msg;
## @cmethod Lemonldap::NG::Common::Crypto new(array param)
## @cmethod Lemonldap::NG::Common::Crypto new(string key, string mode)
# Constructor
# @param @param Crypt::Rijndael::new() parameters
# @param key key defined in LL::NG conf
# @param mode Crypt::Rijndael constant
# @return Lemonldap::NG::Common::Crypto object
sub new {
my $class = shift;
my $self = Crypt::Rijndael->new(@_);
my ($class, $key, $mode) = @_;
$mode ||= Crypt::Rijndael::MODE_CBC();
my $self = {
key => $key,
mode => $mode,
ciphers => {}
};
return bless $self, $class;
}
## @method private Crypt::Rijndael _getCipher(string key)
# Returns a Crypt::Rijndael object whose key is mainKey ^ secondKey,
# where mainKey is defined in LL::NG conf,
# and secondKey is set in code so as to get different keys
# @param key that secondary key
# @return Crypt::Rijndael object
sub _getCipher {
my ($self, $key) = @_;
$key ||= "";
$self->{ciphers}->{$key} ||=
Crypt::Rijndael->new(($self->{key})^$key, $self->{mode});
return $self->{ciphers}->{$key};
}
## @method string encrypt(string data)
# Encrypt $data and return it in Base64 format
# @param data datas to encrypt
# @return encrypted datas in Base64 format
sub encrypt {
my ( $self, $str ) = @_;
my $tmp;
my ( $self, $data ) = @_;
# pad $data so that its length be multiple of 16 bytes
my $l = bytes::length($data) % 16;
$data .= "\0" x ( 16 - $l ) unless ($l == 0);
eval {
$tmp = encode_base64(
$self->SUPER::encrypt(
$str . "\0" x ( 16 - bytes::length($str) % 16 )
),
''
);
$data = encode_base64( $self->_getCipher->encrypt( $data ) );
};
if ($@) {
$msg = "Crypt::Rijndael error : $@";
@ -47,21 +67,21 @@ sub encrypt {
}
else {
$msg = '';
return $tmp;
chomp $data;
return $data;
}
}
## @method string decrypt(string data)
# Decrypt $data and return it in
# Decrypt $data and return it
# @param data datas to decrypt in Base64 format
# @return decrypted datas
sub decrypt {
my $self = shift;
my $tmp = shift;
$tmp =~ s/%2B/\+/ig;
$tmp =~ s/%2F/\//ig;
$tmp =~ s/%3D/=/ig;
eval { $tmp = $self->SUPER::decrypt( decode_base64($tmp) ); };
my ($self, $data) = @_;
$data =~ s/%2B/\+/ig;
$data =~ s/%2F/\//ig;
$data =~ s/%3D/=/ig;
eval { $data = $self->_getCipher->decrypt( decode_base64($data) ); };
if ($@) {
$msg = "Crypt::Rijndael error : $@";
return undef;
@ -70,10 +90,63 @@ sub decrypt {
$msg = '';
# Obscure Perl re bug...
$tmp .= "\0";
$tmp =~ s/\0*$//;
return $tmp;
$data .= "\0";
$data =~ s/\0*$//;
return $data;
}
}
## @method string encryptHex(string data, string key)
# Encrypt $data and return it in hexadecimal format
# Data must be hexadecimal and its length must be a multiple of 32
# the encrypted data have same length as the original data
# @param data datas to encrypt
# @param key optional secondary key
# @return encrypted datas in hexadecimal data
sub encryptHex {
my ($self, $data, $key) = @_;
return _cryptHex($self, $data, $key, "encrypt")
}
## @method string decryptHex(string data, string key)
# Decrypt $data and return it in hexadecimal format
# Data must be hexadecimal and its length must be a multiple of 32
# the decrypted data have same length as the encrypted data
# @param data datas to decrypt
# @param key optional secondary key
# @return decrypted datas in hexadecimal data
sub decryptHex {
my ($self, $data, $key) = @_;
return _cryptHex($self, $data, $key, "decrypt")
}
## @method private string _cryptHex (string data, string key, string sub)
# Auxiliary method to share code between encrypt and decrypt
# @param data datas to decrypt
# @param key secondary key
# @param sub may be "encrypt" or "decrypt"
# @return decrypted datas in hexadecimal data
sub _cryptHex {
my ($self, $data, $key, $sub) = @_;
unless ($data =~ /^([0-9a-fA-F]{2})*$/) {
$msg = "Lemonldap::NG::Common::Crypto::${sub}Hex error : data is not hexadecimal";
return undef;
}
# $data's length must be multiple of 32,
# since Rijndael requires data length multiple of 16
unless (bytes::length($data) % 32 == 0) {
$msg = "Lemonldap::NG::Common::Crypto::${sub}Hex error : data length must be multiple of 32";
return undef;
}
$data = pack "H*", $data;
eval { $data = $self->_getCipher($key)->$sub($data); };
if ($@) {
$msg = "Crypt::Rijndael error : $@";
return undef;
}
$msg = "";
$data = unpack "H*", $data;
return $data;
}
1;

@ -5,7 +5,8 @@
# change 'tests => 1' to 'tests => last_test_to_print';
use Test::More tests => 19;
use Test::More tests => 20;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use strict;
BEGIN {
@ -29,6 +30,14 @@ foreach my $i ( 1 .. 17 ) {
my $s = '';
$s = join( '', map { chr( int( rand(94) ) + 33 ) } ( 1 .. $i ) );
ok( $c->decrypt( $c->encrypt($s) ) eq $s,
"Test with $i characters string" );
"Test of base64 encrypting with $i characters string" );
}
my $data = md5_hex(rand);
my $secondKey = md5(rand);
ok(
$c->decryptHex(
$c->encryptHex( $data, $secondKey ),
$secondKey ) eq $data,
"Test of hexadecimal encrypting"
);

@ -20,6 +20,7 @@ use Exporter 'import';
use AutoLoader 'AUTOLOAD';
use Safe;
use Lemonldap::NG::Common::Safelib; #link protected safe Safe object
use Lemonldap::NG::Common::Crypto;
require POSIX;
use CGI::Util 'expires';
use constant SAFEWRAP => ( Safe->can("wrap_code_ref") ? 1 : 0 );
@ -48,7 +49,8 @@ our (
$customFunctions, $transform, $cda,
$childInitDone, $httpOnly, $cookieExpiration,
$timeoutActivity, $datasUpdate, $useRedirectOnForbidden,
$useRedirectOnError, $useSafeJail,
$useRedirectOnError, $useSafeJail, $securedCookie,
$key, $cipher,
);
##########################################
@ -73,6 +75,7 @@ BEGIN {
qw(
$forgeHeaders lmHeaderIn lmSetHeaderIn lmHeaderOut
lmSetHeaderOut lmSetErrHeaderOut $cookieName $https $port
$securedCookie $key $cipher
)
],
traces => [qw( $whatToTrace $statusPipe $statusOut)],
@ -82,7 +85,12 @@ BEGIN {
],
post => [qw($transform postFilter)],
cda => ['$cda'],
cookie => [qw($cookieName $https $httpOnly $cookieExpiration)],
cookie => [
qw(
$cookieName $https $httpOnly $cookieExpiration
$securedCookie $key $cipher
)
],
session => ['$timeoutActivity'],
);
push( @EXPORT_OK, @{ $EXPORT_TAGS{$_} } ) foreach ( keys %EXPORT_TAGS );
@ -149,7 +157,10 @@ BEGIN {
threads::shared::share($useRedirectOnError);
threads::shared::share($useSafeJail);
threads::shared::share($customFunctions);
threads::shared::share($securedCookie);
threads::shared::share($key);
};
print "eval error: $@" if($@);
}
elsif ( MP() == 1 ) {
require Apache;
@ -623,11 +634,12 @@ sub defaultValuesInit {
# and lemonldap-ng.ini
# These values should be erased by global configuration!
$cookieName = $args->{cookieName} || $cookieName || 'lemonldap';
$securedCookie =
defined( $args->{securedCookie} ) ? $args->{securedCookie} :
defined($securedCookie) ? $securedCookie : 1;
$whatToTrace = $args->{whatToTrace} || $whatToTrace || 'uid';
$whatToTrace =~ s/\$//g;
$https = defined($https) ? $https : $args->{https};
$args->{securedCookie} = 1 unless defined( $args->{securedCookie} );
$cookieName .= 'http' if ( $args->{securedCookie} == 2 and $https == 0 );
$port ||= $args->{port};
$customFunctions = $args->{customFunctions};
$cda = defined($cda) ? $cda : $args->{cda};
@ -646,6 +658,12 @@ sub defaultValuesInit {
defined($useSafeJail)
? $useSafeJail
: $args->{useSafeJail};
$key ||= 'lemonldap-ng-key';
$cipher ||= Lemonldap::NG::Common::Crypto->new($key);
if ($args->{key} && ($args->{key} ne $key) ) {
$key = $args->{key};
$cipher = Lemonldap::NG::Common::Crypto->new($key);
}
1;
}
@ -812,14 +830,6 @@ sub goToPortal {
return REDIRECT;
}
## @rmethod protected $ fetchId()
# Get user cookies and search for Lemonldap::NG cookie.
# @return Value of the cookie if found, 0 else
sub fetchId {
my $t = lmHeaderIn( $apacheRequest, 'Cookie' );
return ( $t =~ /$cookieName=([^,; ]+)/o ) ? $1 : 0;
}
# MAIN SUBROUTINE called by Apache (using PerlHeaderParserHandler option)
## @rmethod int run(Apache2::RequestRec apacheRequest)

@ -162,6 +162,25 @@ sub grant {
return &{ $defaultCondition->{$vhost} }($datas);
}
## @rmethod protected $ fetchId()
# Get user cookies and search for Lemonldap::NG cookie.
# @return Value of the cookie if found, 0 else
sub fetchId {
my $t = lmHeaderIn( $apacheRequest, 'Cookie' );
my $vhost = $apacheRequest->hostname;
my $lookForHttpCookie =
$securedCookie =~ /^(2|3)$/
&& ( $https->{$vhost} == 0
|| ( !defined($https->{$vhost}) && $https->{_} == 0 ) ) ;
my $value = $lookForHttpCookie ?
( $t =~ /${cookieName}http=([^,; ]+)/o ? $1 : 0 ) :
( $t =~ /$cookieName=([^,; ]+)/o ? $1 : 0 ) ;
$value = $cipher->decryptHex($value, "http")
if ( $lookForHttpCookie && $securedCookie == 3 );
return $value;
}
## @cmethod private string _buildUrl(string s)
# Transform /<s> into http(s?)://<host>:<port>/s
# @param $s path

@ -941,7 +941,8 @@ function securedCookieValues(id){
formateSelect('select',[
'0='+text4securedCookie0,
'1='+text4securedCookie1,
'2='+text4securedCookie2
'2='+text4securedCookie2,
'3='+text4securedCookie3
],lmdata(id));
display('select',lmtext(id));
}

@ -36,7 +36,8 @@
var text4newFilename='<lang en="Filename" fr="Nom du fichier" />';
var text4securedCookie0='<lang en="Non secured cookie" fr="Cookie non sécurisé"/>';
var text4securedCookie1='<lang en="Secured cookie (HTTPS)" fr="Cookie sécurisé (HTTPS)"/>';
var text4securedCookie2='Double cookie (HTTP and HTTPS)';
var text4securedCookie2='<lang en="Double cookie (HTTP and HTTPS)" fr="Double cookie (HTTP et HTTPS)"/>';
var text4securedCookie3='<lang en="Double cookie for single session" fr="Double cookie pour une seule session"/>';
var text4newGeneratedFile='<lang en="Password (optional)" fr="Mot de passe (optionnel)" />';
var text4edit='<lang en="Edit" fr="Éditer" />';
var text4protect='<lang en="Protect" fr="Protéger" />';

@ -761,7 +761,6 @@ sub struct {
grantSessionRules => {
_nodes => ['hash:/grantSessionRules:grantSessionRules:grantSessionRules'],
_js => 'grantSessionRulesRoot',
_help => 'grantSessionRules',
},
sessionStorage => {
@ -1550,8 +1549,8 @@ sub testStruct {
msgFail => 'Bad url'
},
securedCookie => {
test => qr/^(?:0|1|2)$/,
msgFail => 'securedCookie must be 0, 1 or 2',
test => qr/^(?:0|1|2|3)$/,
msgFail => 'securedCookie must be 0, 1, 2 or 3',
},
sessionDataToDisplay => {
keyTest => qr/^[\w-]+$/,

@ -2242,7 +2242,7 @@ sub grantSession {
return !$A ? 1 : !$B ? -1 : $A cmp $B;
}
foreach ( sort sortByComment keys %{ $self->{grantSessionRules} } ) {
$self->lmLog( "Grant session condition \"$_\" checked", "warn" );
$self->lmLog( "Grant session condition \"$_\" checked", "debug" );
unless ( $self->safe->reval($_) ) {
$self->lmLog(
"User " . $self->{user} . " was not granted to open session",
@ -2330,11 +2330,15 @@ sub buildCookie {
-expires => $self->{cookieExpiration},
@_,
);
if ( $self->{securedCookie} == 2 ) {
if ( $self->{securedCookie} =~ /^(2|3)$/ ) {
push @{ $self->{cookie} },
$self->cookie(
-name => $self->{cookieName} . "http",
-value => $self->{sessionInfo}->{_httpSession},
-value => (
$self->{securedCookie} == 2
? $self->{sessionInfo}->{_httpSession}
: $self->{cipher}->encryptHex( $self->{id}, "http" )
),
-domain => $self->{domain},
-path => "/",
-secure => 0,

Loading…
Cancel
Save