diff --git a/debian/NEWS b/debian/NEWS index 63f3d7a50..a2032aa91 100644 --- a/debian/NEWS +++ b/debian/NEWS @@ -1,3 +1,10 @@ +lemonldap-ng (2.0.5-1) unstable; urgency=medium + + This version adds some improvements in cryptographic functions. To take + advantage of them, you must change the master key of LemonLDAP::NG. + + -- Xavier Guimard Thu, 27 Jun 2019 23:19:09 +0200 + lemonldap-ng (2.0.0-1) unstable; urgency=medium 2.0 is a major release, many things have been changed. You must read diff --git a/debian/control b/debian/control index 47899d48f..08536b755 100644 --- a/debian/control +++ b/debian/control @@ -211,7 +211,8 @@ Recommends: libapache-session-browseable-perl, libdbi-perl, libhttp-parser-xs-perl, libjson-xs-perl, - liblwp-protocol-https-perl + liblwp-protocol-https-perl, + libstring-random-perl Suggests: libconvert-base32-perl, libnet-ldap-perl, libsoap-lite-perl, @@ -277,7 +278,6 @@ Recommends: libcrypt-openssl-bignum-perl, libgd-securityimage-perl, libmime-tools-perl, libnet-ldap-perl, - libstring-random-perl, libunicode-string-perl Suggests: libcrypt-u2f-server-perl, libdbi-perl, diff --git a/lemonldap-ng-common/META.json b/lemonldap-ng-common/META.json index 193030fa6..d26501ef6 100644 --- a/lemonldap-ng-common/META.json +++ b/lemonldap-ng-common/META.json @@ -4,13 +4,13 @@ "Xavier Guimard , Clément Oudot " ], "dynamic_config" : 1, - "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "open_source" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", - "version" : "2" + "version" : 2 }, "name" : "Lemonldap-NG-Common", "no_index" : { @@ -41,7 +41,8 @@ "DBI" : "0", "LWP::Protocol::https" : "0", "Net::LDAP" : "0", - "SOAP::Lite" : "0" + "SOAP::Lite" : "0", + "String::Random" : "0" }, "requires" : { "Apache::Session" : "0", @@ -72,5 +73,5 @@ "x_MailingList" : "mailto:lemonldap-ng-dev@ow2.org" }, "version" : "v2.0.4", - "x_serialization_backend" : "JSON::PP version 2.27400_02" + "x_serialization_backend" : "JSON::PP version 2.97001" } diff --git a/lemonldap-ng-common/META.yml b/lemonldap-ng-common/META.yml index f58d6eccd..3100dabaa 100644 --- a/lemonldap-ng-common/META.yml +++ b/lemonldap-ng-common/META.yml @@ -9,7 +9,7 @@ build_requires: configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 -generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -28,6 +28,7 @@ recommends: LWP::Protocol::https: '0' Net::LDAP: '0' SOAP::Lite: '0' + String::Random: '0' requires: Apache::Session: '0' Cache::Cache: '0' diff --git a/lemonldap-ng-common/Makefile.PL b/lemonldap-ng-common/Makefile.PL index 0c0af0207..86745216a 100644 --- a/lemonldap-ng-common/Makefile.PL +++ b/lemonldap-ng-common/Makefile.PL @@ -34,6 +34,7 @@ WriteMakefile( 'Convert::Base32' => 0, 'Cookie::Baker::XS' => 0, 'Crypt::URandom' => 0, + 'String::Random' => 0, 'DBI' => 0, 'Net::LDAP' => 0, 'SOAP::Lite' => 0, diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Crypto.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Crypto.pm index 9d6c4b443..b0df054c0 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Crypto.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Crypto.pm @@ -11,10 +11,33 @@ package Lemonldap::NG::Common::Crypto; use strict; use Crypt::Rijndael; use MIME::Base64; -use Digest::MD5 qw(md5); +use Digest::SHA; use bytes; our $VERSION = '2.0.0'; +my ( $newIv, $randG, $hash ); +$hash = \&Digest::SHA::sha256; + +use constant HMAC_LENGTH => 32; +use constant IV_LENGTH => 16; + +BEGIN { + eval { require Crypt::URandom; Crypt::URandom::urandom(IV_LENGTH) }; + if ($@) { + $newIv = sub { + return bytes::substr( Digest::SHA::sha1( rand() . time . {} ), + 0, IV_LENGTH ); + }; + $randG = sub { return int( rand( $_[0] ) ) }; + } + else { + $newIv = sub { return Crypt::URandom::urandom(IV_LENGTH) }; + $randG = sub { + return + int( unpack( "C", Crypt::URandom::urandom(1) ) * $_[0] / 256 ); + }; + } +} our $msg; @@ -44,7 +67,7 @@ sub _getCipher { my ( $self, $key ) = @_; $key ||= ""; $self->{ciphers}->{$key} ||= - Crypt::Rijndael->new( md5( $self->{key}, $key ), $self->{mode} ); + Crypt::Rijndael->new( $hash->( $self->{key}, $key ), $self->{mode} ); return $self->{ciphers}->{$key}; } @@ -53,13 +76,23 @@ sub _getCipher { # @param data data to encrypt # @return encrypted data in Base64 format sub encrypt { - my ( $self, $data ) = @_; + my ( $self, $data, $low ) = @_; # 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 { $data = encode_base64( $self->_getCipher->encrypt($data), '' ); }; + my $iv = + $low + ? bytes::substr( Digest::SHA::sha1( rand() . time . {} ), 0, IV_LENGTH ) + : $newIv->(); + my $hmac = $hash->($data); + eval { + $data = + encode_base64( + $iv . $self->_getCipher->set_iv($iv)->encrypt( $hmac . $data ), + '' ); + }; if ($@) { $msg = "Crypt::Rijndael error : $@"; return undef; @@ -81,11 +114,22 @@ sub decrypt { $data =~ s/%2F/\//ig; $data =~ s/%3D/=/ig; $data =~ s/%0A/\n/ig; - eval { $data = $self->_getCipher->decrypt( decode_base64($data) ); }; + $data = decode_base64($data); + my $iv; + $iv = bytes::substr( $data, 0, IV_LENGTH ); + $data = bytes::substr( $data, IV_LENGTH ); + eval { $data = $self->_getCipher->set_iv($iv)->decrypt($data); }; + if ($@) { $msg = "Crypt::Rijndael error : $@"; return undef; } + my $hmac = bytes::substr( $data, 0, HMAC_LENGTH ); + $data = bytes::substr( $data, HMAC_LENGTH ); + if ( $hash->($data) ne $hmac ) { + $msg = "Bad MAC"; + return undef; + } else { $msg = ''; @@ -141,15 +185,34 @@ sub _cryptHex { "Lemonldap::NG::Common::Crypto::${sub}Hex error : data length must be multiple of 32"; return undef; } + my $iv; + if ( $sub eq 'encrypt' ) { + $iv = $newIv->(); + } $data = pack "H*", $data; - eval { $data = $self->_getCipher($key)->$sub($data); }; + if ( $sub eq 'decrypt' ) { + $iv = bytes::substr( $data, 0, IV_LENGTH ); + $data = bytes::substr( $data, IV_LENGTH ); + } + eval { $data = $self->_getCipher($key)->set_iv($iv)->$sub($data); }; if ($@) { $msg = "Crypt::Rijndael error : $@"; return undef; } + if ( $sub eq 'encrypt' ) { + $data = $iv . $data; + } $msg = ""; $data = unpack "H*", $data; return $data; } +sub srandom { + eval { require String::Random }; + if ($@) { + die 'Missing recommended dependency to String::Random'; + } + return String::Random->new( rand_gen => $randG ); +} + 1; diff --git a/lemonldap-ng-common/scripts/rotateOidcKeys b/lemonldap-ng-common/scripts/rotateOidcKeys index 25d36c38b..8f69ea6a8 100755 --- a/lemonldap-ng-common/scripts/rotateOidcKeys +++ b/lemonldap-ng-common/scripts/rotateOidcKeys @@ -12,7 +12,7 @@ use strict; use Convert::PEM; use Crypt::OpenSSL::RSA; use Lemonldap::NG::Common::Conf; -use String::Random qw(random_string); +use Lemonldap::NG::Common::Crypto; my $debug = 0; @@ -28,9 +28,10 @@ print "Configuration loaded\n" if $debug; #============================================================================= # Generate new key #============================================================================= -my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); -my $key_id = random_string("ssssssssss"); -my $keys = { +my $rsa = Crypt::OpenSSL::RSA->generate_key(2048); +my $key_id = + Lemonldap::NG::Common::Crypto::srandom()->randpattern("ssssssssss"); +my $keys = { 'private' => $rsa->get_private_key_string(), 'public' => $rsa->get_public_key_x509_string(), 'id' => $key_id, diff --git a/lemonldap-ng-common/t/35-Common-Crypto.t b/lemonldap-ng-common/t/35-Common-Crypto.t index f926a55b7..3d682d7f6 100644 --- a/lemonldap-ng-common/t/35-Common-Crypto.t +++ b/lemonldap-ng-common/t/35-Common-Crypto.t @@ -5,7 +5,7 @@ # change 'tests => 1' to 'tests => last_test_to_print'; -use Test::More tests => 21; +use Test::More tests => 22; use Digest::MD5 qw(md5 md5_hex md5_base64); use strict; @@ -30,7 +30,11 @@ 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 of base64 encrypting with $i characters string" ); + "Test of base64 encrypting with $i characters string" ) + or diag "Source: $s\nCypher: " + . $c->encrypt($s) + . "\nUncipher:" + . $c->decrypt( $c->encrypt($s) ); } my $data = md5_hex(rand); @@ -42,6 +46,9 @@ ok( # Test a long value, and replace carriage return by %0A my $long = "f5a1f72e7ab2f7712855a068af0066f36bfcf2c87e6feb9cf4200da1868e1dfe"; -my $cryptedlong = -"Da6sYxp9NCXv8+8TirqHmPWwTQHyEGmkCBGCLCX/81dPSMwIQVQNV7X9KG3RrKZfyRmzJR6DZYdU%0Ab75+VH3+CA=="; -ok( $c->decrypt($cryptedlong) eq $long, "Test of long value encrypting" ); +ok( $c->decrypt( $c->encrypt($long) ) eq $long, + "Test of long value encrypting" ); +ok( + $c->decryptHex( $c->encryptHex($long) ) eq $long, + "Test of long value encrypting (hex)" +); diff --git a/lemonldap-ng-handler/META.json b/lemonldap-ng-handler/META.json index 808e97c07..8f47b4f9a 100644 --- a/lemonldap-ng-handler/META.json +++ b/lemonldap-ng-handler/META.json @@ -4,13 +4,13 @@ "Xavier Guimard , Clément Oudot " ], "dynamic_config" : 1, - "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "open_source" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", - "version" : "2" + "version" : 2 }, "name" : "Lemonldap-NG-Handler", "no_index" : { @@ -59,5 +59,5 @@ "x_MailingList" : "mailto:lemonldap-ng-dev@ow2.org" }, "version" : "v2.0.4", - "x_serialization_backend" : "JSON::PP version 2.27400_02" + "x_serialization_backend" : "JSON::PP version 2.97001" } diff --git a/lemonldap-ng-handler/META.yml b/lemonldap-ng-handler/META.yml index d36ae2e91..55ae287ab 100644 --- a/lemonldap-ng-handler/META.yml +++ b/lemonldap-ng-handler/META.yml @@ -11,7 +11,7 @@ build_requires: configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 -generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm index 02b1dcb1a..d48bf74bf 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Jail.pm @@ -103,7 +103,7 @@ sub build_jail { # Import crypto methods for jail sub encrypt { - return &Lemonldap::NG::Handler::Main::tsv->{cipher}->encrypt(@_); + return &Lemonldap::NG::Handler::Main::tsv->{cipher}->encrypt( $_[0], 1 ); } sub token { diff --git a/lemonldap-ng-manager/META.json b/lemonldap-ng-manager/META.json index a962fa4d4..34c00e7be 100644 --- a/lemonldap-ng-manager/META.json +++ b/lemonldap-ng-manager/META.json @@ -4,13 +4,13 @@ "Xavier Guimard , Clément Oudot " ], "dynamic_config" : 1, - "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "open_source" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", - "version" : "2" + "version" : 2 }, "name" : "Lemonldap-NG-Manager", "no_index" : { @@ -55,5 +55,5 @@ "x_MailingList" : "mailto:lemonldap-ng-dev@ow2.org" }, "version" : "v2.0.4", - "x_serialization_backend" : "JSON::PP version 2.27400_02" + "x_serialization_backend" : "JSON::PP version 2.97001" } diff --git a/lemonldap-ng-manager/META.yml b/lemonldap-ng-manager/META.yml index 9ff57b806..e98e13f1f 100644 --- a/lemonldap-ng-manager/META.yml +++ b/lemonldap-ng-manager/META.yml @@ -9,7 +9,7 @@ build_requires: configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 -generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html diff --git a/lemonldap-ng-portal/META.json b/lemonldap-ng-portal/META.json index c85aa5d50..51e50ee7f 100644 --- a/lemonldap-ng-portal/META.json +++ b/lemonldap-ng-portal/META.json @@ -4,13 +4,13 @@ "Xavier Guimard , Clément Oudot " ], "dynamic_config" : 1, - "generated_by" : "ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010", + "generated_by" : "ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010", "license" : [ "open_source" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", - "version" : "2" + "version" : 2 }, "name" : "Lemonldap-NG-Portal", "no_index" : { @@ -57,7 +57,6 @@ "Net::OpenID::Consumer" : "0", "Net::OpenID::Server" : "0", "SOAP::Lite" : "0", - "String::Random" : "0", "Unicode::String" : "0", "Web::ID" : "0" }, @@ -78,5 +77,5 @@ "x_MailingList" : "mailto:lemonldap-ng-dev@ow2.org" }, "version" : "v2.0.4", - "x_serialization_backend" : "JSON::PP version 2.27400_02" + "x_serialization_backend" : "JSON::PP version 2.97001" } diff --git a/lemonldap-ng-portal/META.yml b/lemonldap-ng-portal/META.yml index 854ab4517..097b63005 100644 --- a/lemonldap-ng-portal/META.yml +++ b/lemonldap-ng-portal/META.yml @@ -14,7 +14,7 @@ build_requires: configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 -generated_by: 'ExtUtils::MakeMaker version 7.24, CPAN::Meta::Converter version 2.150010' +generated_by: 'ExtUtils::MakeMaker version 7.34, CPAN::Meta::Converter version 2.150010' license: open_source meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html @@ -43,7 +43,6 @@ recommends: Net::OpenID::Consumer: '0' Net::OpenID::Server: '0' SOAP::Lite: '0' - String::Random: '0' Unicode::String: '0' Web::ID: '0' requires: diff --git a/lemonldap-ng-portal/Makefile.PL b/lemonldap-ng-portal/Makefile.PL index f087cda7b..b651bb744 100644 --- a/lemonldap-ng-portal/Makefile.PL +++ b/lemonldap-ng-portal/Makefile.PL @@ -27,7 +27,6 @@ WriteMakefile( 'Net::OpenID::Consumer' => 0, 'Net::OpenID::Server' => 0, 'SOAP::Lite' => 0, - 'String::Random' => 0, 'Unicode::String' => 0, 'Web::ID' => 0, }, diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Ext2F.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Ext2F.pm index a8a7f9a1c..20b4cb775 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Ext2F.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Ext2F.pm @@ -2,7 +2,6 @@ package Lemonldap::NG::Portal::2F::Ext2F; use strict; use Mouse; -use String::Random; use Lemonldap::NG::Portal::Main::Constants qw( PE_BADCREDENTIALS PE_ERROR @@ -38,7 +37,7 @@ sub init { $self->error("Missing 'ext2FSendCommand' parameter, aborting"); return 0; } - $self->random( String::Random->new ); + $self->random( Lemonldap::NG::Common::Crypto::srandom() ); $self->logo( $self->conf->{ext2fLogo} ) if ( $self->conf->{ext2fLogo} ); return $self->SUPER::init(); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Mail2F.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Mail2F.pm index c34c50c9d..2c4c6da80 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Mail2F.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/2F/Mail2F.pm @@ -2,7 +2,6 @@ package Lemonldap::NG::Portal::2F::Mail2F; use strict; use Mouse; -use String::Random; use Lemonldap::NG::Portal::Main::Constants qw( PE_BADCREDENTIALS PE_ERROR @@ -23,7 +22,7 @@ has prefix => ( is => 'ro', default => 'mail' ); has random => ( is => 'rw', default => sub { - return String::Random->new; + return Lemonldap::NG::Common::Crypto::srandom(); } ); diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm index 849350b74..d4f89ef90 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SAML.pm @@ -8,7 +8,6 @@ use Lemonldap::NG::Common::UserAgent; use Lemonldap::NG::Common::FormEncode; use XML::Simple; use MIME::Base64; -use String::Random; use HTTP::Request; # SOAP call use POSIX qw(strftime); # Convert SAML2 date into timestamp use Time::Local; # Convert SAML2 date into timestamp diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SMTP.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SMTP.pm index 8cafb192d..3b82cb098 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SMTP.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/SMTP.pm @@ -8,7 +8,6 @@ package Lemonldap::NG::Portal::Lib::SMTP; use strict; use Mouse; use JSON qw(from_json); -use String::Random; use MIME::Entity; use Email::Sender::Simple qw(sendmail); use Email::Sender::Transport::SMTP qw(); @@ -24,7 +23,7 @@ our $transport; has random => ( is => 'rw', default => sub { - return String::Random->new; + return Lemonldap::NG::Common::Crypto::srandom(); } ); has charset => ( diff --git a/lemonldap-ng-portal/t/59-Double-cookies-for-a-Single-session.t b/lemonldap-ng-portal/t/59-Double-cookies-for-a-Single-session.t index 25de134f1..8d6d81def 100644 --- a/lemonldap-ng-portal/t/59-Double-cookies-for-a-Single-session.t +++ b/lemonldap-ng-portal/t/59-Double-cookies-for-a-Single-session.t @@ -44,8 +44,8 @@ my $id1 = expectCookie($res); my $id2 = expectCookie( $res, 'lemonldaphttp' ); # Check lemonldap Cookie -ok( $id1 =~ /^\w{64}$/, " -> Get cookie : lemonldap=something" ) - or explain( $res->[1], "Set-Cookie: lemonldap=$id1" ); +ok( $id1 =~ /^\w{64}$/, " -> https cookie is 64 char long" ) + or explain( $id1, '64-char string' ); ok( ${ $res->[1] }[3] =~ /HttpOnly=1/, " -> Cookie 'lemonldap' is HttpOnly" ) or explain( $res->[1] ); ok( ${ $res->[1] }[3] =~ /secure/, " -> Cookie 'lemonldap' is secure" ) @@ -53,8 +53,8 @@ ok( ${ $res->[1] }[3] =~ /secure/, " -> Cookie 'lemonldap' is secure" ) count(3); # Check lemonldaphttp Cookie -ok( $id2 =~ /^\w{64}$/, " -> Get cookie lemonldaphttp=something" ) - or explain( $res->[1], "Set-Cookie: lemonldaphttp=$id2" ); +ok( length($id2) % 32 == 0, " -> http cookie is 96 byte long" ) + or explain( $id2, '\w x 32 string' ); ok( ${ $res->[1] }[5] =~ /HttpOnly=1/, " -> Cookie 'lemonldaphttp' is HttpOnly"