Unverified Commit 66680ad4 authored by IKEDA Soji's avatar IKEDA Soji Committed by GitHub
Browse files

Merge pull request #557 from ikedas/deprecate_ciphersaber by ikedas

Deprecate CipherSaber RC4 encryption (#87)
parents 273d572f 18bdba2a
......@@ -65,6 +65,11 @@ Subject: [% FILTER qencode %][%|loc%]Upgrade procedures failed[%END%][%END%]
[%|loc%]Check log file for further details.[%END%]
[% ELSIF type == 'password_encrypted' -%]
Subject: [% FILTER qencode %][%|loc%]Password should be rehashed[%END%][%END%]
[%|loc%]Password in database seems encrypted. Run upgrade_sympa_password.pl to rehash passwords.[%END%]
[% ELSIF type == 'db_struct_updated' -%]
Subject: [% FILTER qencode %][%|loc%]Database structure updated[%END%][%END%]
X-Sympa-NoWrap: yes
......
......@@ -9,8 +9,8 @@
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
# Copyright 2017, 2018 The Sympa Community. See the AUTHORS.md file at the
# top-level directory of this distribution and at
# Copyright 2017, 2018, 2019 The Sympa Community. See the AUTHORS.md file at
# the top-level directory of this distribution and at
# <https://github.com/sympa-community/sympa.git>.
#
# This program is free software; you can redistribute it and/or modify
......@@ -29,14 +29,16 @@
use lib split(/:/, $ENV{SYMPALIB} || ''), '--modulesdir--';
use strict;
use warnings;
use Digest::MD5;
use Getopt::Long;
use MIME::Base64 qw();
use Time::HiRes qw(gettimeofday tv_interval);
BEGIN { eval 'use Crypt::CipherSaber'; }
use Conf;
use Sympa::DatabaseManager;
use Sympa::Tools::Password;
use Sympa::User;
use Digest::MD5;
use Getopt::Long;
use Time::HiRes qw(gettimeofday tv_interval);
my $usage =
"Usage: $0 [--dry_run|n] [--debug|d] [--verbose|v] [--config file] [--cache file] [--nosavecache] [--noupdateuser] [--limit|l]\n";
......@@ -77,8 +79,6 @@ if ($dry_run) {
$savecache = $updateuser = 0;
}
die "Crypt::CipherSaber not installed ; cannot crypt passwords"
unless $Crypt::CipherSaber::VERSION;
die 'Error in configuration'
unless Conf::load($config, 'no_db');
......@@ -104,8 +104,17 @@ $dry_run && print "dry_run: database will *not* be updated.\n";
my $sdm = Sympa::DatabaseManager->instance
or die 'Can\'t connect to database';
my $sth;
# Check if RC4 decryption required.
$sth = $sdm->do_prepared_query(
q{SELECT COUNT(*) FROM user_table WHERE password_user LIKE 'crypt.%'});
my ($encrypted) = $sth->fetchrow_array;
if ($encrypted and not $Crypt::CipherSaber::VERSION) {
die "Password seems encrypted while Crypt::CipherSaber is not installed!\n";
}
my $sth = $sdm->do_query(q{SELECT email_user, password_user from user_table});
$sth = $sdm->do_query(q{SELECT email_user, password_user from user_table});
unless ($sth) {
die 'Unable to prepare SQL statement';
}
......@@ -139,10 +148,11 @@ while (my $user = $sth->fetchrow_hashref('NAME_lc')) {
next;
}
if ($user->{'password_user'} =~ /^crypt.(.*)$/) {
$clear_password = Sympa::Tools::Password::decrypt_password(
$user->{'password_user'});
} else { ## Old style cleartext passwords
if ($user->{'password_user'} =~ /\Acrypt[.](.*)\z/) {
# Old style RC4 encrypted password.
$clear_password = _decrypt_rc4_password($user->{'password_user'});
} else {
# Old style cleartext password.
$clear_password = $user->{'password_user'};
}
......@@ -253,6 +263,20 @@ if ($total->{'prehashes'}) {
exit 0;
my $rc4;
# decrypt RC4 encrypted password.
# Old name: Sympa::Tools::Password::decrypt_password().
sub _decrypt_rc4_password {
my $inpasswd = shift;
return $inpasswd unless $inpasswd =~ /\Acrypt[.](.*)\z/;
$inpasswd = $1;
$rc4 = Crypt::CipherSaber->new($Conf::Conf{'cookie'}) unless $rc4;
return $rc4->decrypt(MIME::Base64::decode($inpasswd));
}
#
# Here we use MD5 as a quick way to make sure that a precalculated hash
# is still valid.
......
......@@ -64,7 +64,6 @@ use Sympa::Ticket;
use Sympa::Tools::Data;
use Sympa::Tools::Domains;
use Sympa::Tools::File;
use Sympa::Tools::Password;
use Sympa::Tools::SMIME;
use Sympa::Tools::Text;
use Sympa::User;
......@@ -3878,14 +3877,6 @@ sub add_list_member {
$new_user->{'custom_attribute'}
);
# Crypt password if it was not crypted.
unless (
Sympa::Tools::Data::smart_eq($new_user->{'password'}, qr/^crypt/))
{
$new_user->{'password'} = Sympa::Tools::Password::crypt_password(
$new_user->{'password'});
}
## Either is_included or is_subscribed must be set
## default is is_subscriber for backward compatibility reason
unless ($new_user->{'included'}) {
......
......@@ -8,6 +8,9 @@
# Copyright (c) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
# 2006, 2007, 2008, 2009, 2010, 2011 Comite Reseau des Universites
# Copyright (c) 2011, 2012, 2013, 2014, 2015, 2016, 2017 GIP RENATER
# Copyright 2019 The Sympa Community. See the AUTHORS.md file at
# the top-level directory of this distribution and at
# <https://github.com/sympa-community/sympa.git>.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
......@@ -27,15 +30,10 @@ package Sympa::Tools::Password;
use strict;
use warnings;
use Digest::MD5;
use MIME::Base64 qw();
BEGIN { eval 'use Crypt::CipherSaber'; }
BEGIN { eval 'use Data::Password'; }
use Conf;
use Sympa::Language;
use Sympa::Log;
my $log = Sympa::Log->instance;
sub tmp_passwd {
my $email = shift;
......@@ -47,46 +45,14 @@ sub tmp_passwd {
'init' . substr(Digest::MD5::md5_hex(join '/', $cookie, $email), -8));
}
# global var to store a CipherSaber object
my $cipher;
# create a cipher
sub ciphersaber_installed {
return $cipher if defined $cipher;
if ($Crypt::CipherSaber::VERSION) {
$cipher = Crypt::CipherSaber->new($Conf::Conf{'cookie'});
} else {
$cipher = '';
}
return $cipher;
}
# No longer used, Use _decrypt_rc4_password() in upgrade_sympa_password.pl.
#sub ciphersaber_installed;
## encrypt a password
sub crypt_password {
my $inpasswd = shift;
# No longer used.
#sub crypt_password;
ciphersaber_installed();
return $inpasswd unless $cipher;
return ("crypt." . MIME::Base64::encode($cipher->encrypt($inpasswd)));
}
## decrypt a password
sub decrypt_password {
my $inpasswd = shift;
$log->syslog('debug2', '(%s)', $inpasswd);
return $inpasswd unless ($inpasswd =~ /^crypt\.(.*)$/);
$inpasswd = $1;
ciphersaber_installed();
unless ($cipher) {
$log->syslog('info',
'Password seems crypted while CipherSaber is not installed !');
return $inpasswd;
}
return ($cipher->decrypt(MIME::Base64::decode($inpasswd)));
}
# Moved: Use _decrypt_rc4_password() in upgrade_sympa_password.pl.
#sub decrypt_password;
# Old name: Sympa::Session::get_random().
sub get_random {
......
......@@ -39,7 +39,6 @@ use Sympa::DatabaseManager;
use Sympa::Language;
use Sympa::Log;
use Sympa::Tools::Data;
use Sympa::Tools::Password;
use Sympa::Tools::Text;
my $log = Sympa::Log->instance;
......@@ -535,13 +534,7 @@ sub get_global_user {
$sth = pop @sth_stack;
if (defined $user) {
## decrypt password
if ($user->{'password'}) {
$user->{'password'} =
Sympa::Tools::Password::decrypt_password($user->{'password'});
}
## Canonicalize lang if possible
# Canonicalize lang if possible.
if ($user->{'lang'}) {
$user->{'lang'} = Sympa::Language::canonic_lang($user->{'lang'})
|| $user->{'lang'};
......
......@@ -152,6 +152,15 @@ sub authentication {
## the user passwords
## Other backends are Single Sign-On solutions
if ($auth_service->{'auth_type'} eq 'user_table') {
# Old style RC4 encrypted password.
if ($user->{'password'} and $user->{'password'} =~ /\Acrypt[.]/) {
$log->syslog('notice',
'Password in database seems encrypted. Run upgrade_sympa_password.pl to rehash passwords'
);
Sympa::send_notify_to_listmaster('*', 'password_encrypted');
return undef;
}
my $fingerprint =
Sympa::User::password_fingerprint($pwd, $user->{'password'});
......
......@@ -193,12 +193,8 @@ sub login {
}
## Create Sympa::WWW::Session object
my $session = Sympa::WWW::Session->new(
$robot,
{ 'cookie' =>
Sympa::WWW::Session::encrypt_session_id($ENV{'SESSION_ID'})
}
);
my $session =
Sympa::WWW::Session->new($robot, {cookie => $ENV{SESSION_ID}});
$ENV{'USER_EMAIL'} = $email;
$session->{'email'} = $email;
$session->store();
......@@ -208,7 +204,7 @@ sub login {
## Also return the cookie value
return SOAP::Data->name('result')->type('string')
->value(Sympa::WWW::Session::encrypt_session_id($ENV{'SESSION_ID'}));
->value($ENV{SESSION_ID});
}
sub casLogin {
......@@ -289,12 +285,8 @@ sub casLogin {
}
## Create Sympa::WWW::Session object
my $session = Sympa::WWW::Session->new(
$robot,
{ 'cookie' =>
Sympa::WWW::Session::encrypt_session_id($ENV{'SESSION_ID'})
}
);
my $session =
Sympa::WWW::Session->new($robot, {cookie => $ENV{SESSION_ID}});
$ENV{'USER_EMAIL'} = $email;
$session->{'email'} = $email;
$session->store();
......@@ -304,7 +296,7 @@ sub casLogin {
## Also return the cookie value
return SOAP::Data->name('result')->type('string')
->value(Sympa::WWW::Session::encrypt_session_id($ENV{'SESSION_ID'}));
->value($ENV{SESSION_ID});
}
## Used to call a service as an authenticated user without using HTTP cookies
......@@ -330,7 +322,7 @@ sub authenticateAndRun {
## Provided email is not trusted, we fetch the user email from the
## session_table instead
my $session =
Sympa::WWW::Session->new($ENV{'SYMPA_ROBOT'}, {'cookie' => $cookie});
Sympa::WWW::Session->new($ENV{'SYMPA_ROBOT'}, {cookie => $cookie});
if (defined $session) {
$email = $session->{'email'};
$session_id = $session->{'id_session'};
......
......@@ -116,125 +116,77 @@ sub new {
}
sub load {
$log->syslog('debug2', '(%s, %s)', @_);
my $self = shift;
my $cookie = shift;
$log->syslog('debug', '(%s)', $cookie);
unless ($cookie) {
$log->syslog('err', 'Internal error. Undefined id_session');
return undef;
}
my $sth;
my $statement;
my $id_session;
my $is_old_session = 0;
my $sdm = Sympa::DatabaseManager->instance;
my $sth;
## Load existing session.
if ($cookie and $cookie !~ /[^0-9]/ and length $cookie <= 16) {
## Compatibility: session by older releases of Sympa.
$id_session = $cookie;
$is_old_session = 1;
## Session by older releases of Sympa doesn't have refresh_date.
unless (
$sdm
and $sth = $sdm->do_prepared_query(
q{SELECT id_session AS id_session, id_session AS prev_id,
date_session AS "date",
remote_addr_session AS remote_addr,
email_session AS email,
data_session AS data, hit_session AS hit,
start_date_session AS start_date,
date_session AS refresh_date
FROM session_table
WHERE id_session = ? AND
refresh_date_session IS NULL},
$id_session
)
) {
$log->syslog('err', 'Unable to load session %s', $id_session);
return undef;
}
} else {
$id_session = decrypt_session_id($cookie);
unless ($id_session) {
$log->syslog('err', 'Internal error. Undefined id_session');
return undef;
}
## Cookie may contain current or previous session ID.
unless (
$sdm
and $sth = $sdm->do_prepared_query(
q{SELECT id_session AS id_session, prev_id_session AS prev_id,
date_session AS "date",
remote_addr_session AS remote_addr,
email_session AS email,
data_session AS data, hit_session AS hit,
start_date_session AS start_date,
refresh_date_session AS refresh_date
FROM session_table
WHERE id_session = ? AND prev_id_session IS NOT NULL OR
prev_id_session = ?},
$id_session, $id_session
)
) {
$log->syslog('err', 'Unable to load session %s', $id_session);
return undef;
}
my $session_id = _cookie2id($cookie);
unless ($session_id) {
$log->syslog('info', 'Undefined session ID in cookie "%s"', $cookie);
return undef;
}
my $session = undef;
my $new_session = undef;
my $counter = 0;
while ($new_session = $sth->fetchrow_hashref('NAME_lc')) {
if ($counter > 0) {
$log->syslog('err',
'The SQL statement did return more than one session');
$session->{'email'} = '';
last;
}
$session = $new_session;
$counter++;
## Cookie may contain current or previous session ID.
unless (
$sdm
and $sth = $sdm->do_prepared_query(
q{SELECT id_session AS id_session, prev_id_session AS prev_id,
date_session AS "date",
remote_addr_session AS remote_addr,
email_session AS email,
data_session AS data, hit_session AS hit,
start_date_session AS start_date,
refresh_date_session AS refresh_date
FROM session_table
WHERE id_session = ? AND prev_id_session IS NOT NULL OR
prev_id_session = ?},
$session_id,
$session_id
)
) {
$log->syslog('err', 'Unable to load session %s', $session_id);
return undef;
}
unless ($session) {
return 'not_found';
my $session = $sth->fetchrow_hashref('NAME_lc');
return 'not_found' unless $session;
if ($sth->fetchrow_hashref('NAME_lc')) {
$log->syslog('err',
'The SQL statement did return more than one session');
$session->{'email'} = ''; #FIXME
}
$sth->finish;
## Compatibility: Upgrade session by older releases of Sympa.
if ($is_old_session) {
$sdm->do_prepared_query(
q{UPDATE session_table
SET prev_id_session = id_session
WHERE id_session = ? AND prev_id_session IS NULL AND
refresh_date_session IS NULL},
$id_session
);
}
my @keys;
my %datas = Sympa::Tools::Data::string_2_hash($session->{'data'});
@keys = keys %datas;
@{$self}{@keys} = @datas{@keys};
# Canonicalize lang if possible.
$self->{lang} =
Sympa::Language::canonic_lang($self->{lang}) || $self->{lang}
if $self->{lang};
@keys = qw(id_session prev_id date refresh_date start_date hit
remote_addr email);
@{$self}{@keys} = @{$session}{@keys};
# Update hit count.
$self->{hit}++;
## canonicalize lang if possible.
$datas{'lang'} = Sympa::Language::canonic_lang($datas{'lang'})
|| $datas{'lang'}
if $datas{'lang'};
foreach my $key (keys %datas) { $self->{$key} = $datas{$key}; }
return $self;
}
$self->{'id_session'} = $session->{'id_session'};
$self->{'prev_id'} = $session->{'prev_id'};
$self->{'date'} = $session->{'date'};
$self->{'refresh_date'} = $session->{'refresh_date'};
$self->{'start_date'} = $session->{'start_date'};
$self->{'hit'} = $session->{'hit'} + 1;
$self->{'remote_addr'} = $session->{'remote_addr'};
$self->{'email'} = $session->{'email'};
# Get correct session ID from sympa_session cookie value.
sub _cookie2id {
my $cookie = shift;
return ($self);
return undef unless $cookie;
return $1 if $cookie =~ /\A5e55([0-9]{14,16})\z/; # Compat. < 6.2.42
return $cookie if $cookie =~ /\A[0-9]{14,16}\z/;
return undef;
}
## This method will both store the session information in the database
......@@ -560,15 +512,13 @@ sub set_cookie {
$expiration = '+' . $expires . 'm';
}
my $value = encrypt_session_id($self->{'id_session'});
my $cookie = CGI::Cookie->new(
-name => 'sympa_session',
-domain => (($dom eq 'localhost') ? '' : $dom),
-path => '/',
-secure => $use_ssl,
-httponly => 1,
-value => $value,
-value => $self->{id_session},
($expiration ? (-expires => $expiration) : ()),
);
......@@ -580,11 +530,6 @@ sub set_cookie {
sub soap_cookie2 {
my ($session_id, $http_domain, $expire) = @_;
my $cookie;
my $value;
# WARNING : to check the cookie the SOAP services does not gives
# all the cookie, only it's value so we need ':'
$value = encrypt_session_id($session_id);
## With set-cookie2 max-age of 0 means removing the cookie
## Maximum cookie lifetime is the session
......@@ -593,14 +538,14 @@ sub soap_cookie2 {
if ($http_domain eq 'localhost') {
$cookie = CGI::Cookie->new(
-name => 'sympa_session',
-value => $value,
-value => $session_id,
-path => '/',
);
$cookie->max_age(time + $expire); # needs CGI >= 3.51.
} else {
$cookie = CGI::Cookie->new(
-name => 'sympa_session',
-value => $value,
-value => $session_id,
-domain => $http_domain,
-path => '/',
);
......@@ -637,29 +582,12 @@ sub is_anonymous {
}
## Generate cookie from session ID.
sub encrypt_session_id {
my $id_session = shift;
my $cipher = Sympa::Tools::Password::ciphersaber_installed();
unless ($cipher) {
return "5e55$id_session";
}
return unpack 'H*', $cipher->encrypt(pack 'H*', $id_session);
}
# No longer used.
#sub encrypt_session_id;
## Get session ID from cookie.
sub decrypt_session_id {
my $cookie = shift;
return undef unless $cookie and $cookie =~ /\A(?:[0-9a-f]{2})+\z/;
my $cipher = Sympa::Tools::Password::ciphersaber_installed();
unless ($cipher) {
return undef unless $cookie =~ s/\A5e55//;
return $cookie;
}
return unpack 'H*', $cipher->decrypt(pack 'H*', $cookie);
}
# No longer used
#sub decrypt_session_id;
## Generic subroutine to set a cookie
# DEPRECATED: No longer used. Use CGI::Cookie::new().
......@@ -944,12 +872,12 @@ Deprecated.
=item decrypt_session_id ( )
I<Function>.
TBD.
Deprecated.
=item encrypt_session_id ( )
I<Function>.
TBD.
Deprecated.
=item list_sessions ( )
......
Supports Markdown
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