#!/usr/bin/perl # use warnings; use strict; use POSIX; use Lemonldap::NG::Common::Conf; use Lemonldap::NG::Common::Apache::Session; use Lemonldap::NG::Common::Session; use Lemonldap::NG::Common::TOTP; use JSON qw(from_json to_json); use Getopt::Long qw(:config auto_help); use Pod::Usage; my ( $dryrun, $verbose, $force, $update, $oldkey, $newkey ); GetOptions( "n|dry-run" => \$dryrun, "v|verbose" => \$verbose, "f|force" => \$force, "u|update" => \$update, "o|old-key=s" => \$oldkey, "k|new-key=s" => \$newkey, ); eval { POSIX::setgid( scalar( getgrnam('www-data') ) ); POSIX::setuid( scalar( getpwnam('www-data') ) ); }; sub verbose { print STDERR @_, "\n" if $verbose; } sub info { print STDERR @_, "\n"; } # Get config my $res = Lemonldap::NG::Common::Conf->new(); die $Lemonldap::NG::Common::Conf::msg unless ($res); my $conf = $res->getConf(); my $localconf = $res->getLocalConf() or die "Unable to get local configuration ($!)"; if ($localconf) { $conf->{$_} = $localconf->{$_} foreach ( keys %$localconf ); } if ( !$conf->{totp2fEncryptSecret} ) { if ( !$force ) { die "Encryption of TOTP secrets is not enabled in configuration." . " Use --force to ignore this error"; } } # Create TOTP object my $decrypt_totp = Lemonldap::NG::Common::TOTP->new( key => ( $oldkey || $conf->{totp2fKey} || $conf->{key} ), encryptSecret => 0, ); my $encrypt_totp = Lemonldap::NG::Common::TOTP->new( key => ( $newkey || $conf->{totp2fKey} || $conf->{key} ), encryptSecret => ( ( $newkey || "" ) eq "DECRYPT" ? 0 : 1 ), ); # Search psessions my $args; if ( $conf->{"persistentStorage"} ) { $args = $conf->{"persistentStorageOptions"}; $args->{backend} = $conf->{"persistentStorage"}; } else { $args = $conf->{"globalStorageOptions"}; $args->{backend} = $conf->{"globalStorage"}; } verbose "Searching for persistent sessions"; $res = Lemonldap::NG::Common::Apache::Session->searchOn( $args, '_session_kind', 'Persistent', '_2fDevices', '_session_uid' ); if ( ref($res) eq "HASH" ) { verbose "Found " . scalar( keys %{$res} ) . " persistent sessions"; # For each found psession for my $k ( keys %{$res} ) { my $_2fDevices = $res->{$k}->{_2fDevices}; my $uid = $res->{$k}->{_session_uid}; verbose "Processing psession $k for user $uid"; encrypt_session( $k, $uid, $_2fDevices ); } } else { die "Could not find any persistent sessions"; } sub encrypt_session { my ( $k, $uid, $_2fDevices ) = @_; eval { # parse _2fDevices if found if ($_2fDevices) { $_2fDevices = from_json($_2fDevices); # If the session has 2f devices if ( ref($_2fDevices) eq "ARRAY" and @{$_2fDevices} > 0 ) { my $changed = convert_keys_for_user( $uid, $_2fDevices ); if ( $changed and !$dryrun ) { eval { update2fArray( $k, $_2fDevices ); }; if ($@) { info "Error updating session for $uid: $@"; } } } else { verbose "User $uid does not have a TOTP"; } } else { verbose "User $uid does not have a TOTP"; } }; if ($@) { verbose "Error on psession $k: $@"; } } sub update2fArray { my ( $id, $_2fDevices ) = @_; my $session = Lemonldap::NG::Common::Session->new( storageModule => $args->{backend}, storageModuleOptions => $args, id => $id, ); unless ( $session->data ) { die "Error while opening session $id"; } unless ( $session->update( { _2fDevices => to_json($_2fDevices) } ) ) { die "Error while updating session $id"; } } sub convert_device_for_user { my ( $uid, $device ) = @_; my $changed = 0; # In update mode, decrypt then encrypt if ($update) { my $cleartext_secret = $decrypt_totp->get_cleartext_secret( $device->{_secret} ); if ($cleartext_secret) { my $newsecret = $encrypt_totp->get_storable_secret($cleartext_secret); $device->{_secret} = $newsecret; $changed = 1; verbose 'Updated secret for ' . $uid; } else { info 'Unable to decrypt TOTP secret for ' . $uid; } # In normal mode, only encrypt non-encrypted secrets } else { if ( !$encrypt_totp->is_encrypted( $device->{_secret} ) ) { $device->{_secret} = $encrypt_totp->get_storable_secret( $device->{_secret} ); $changed = 1; info 'Encrypted TOTP secret for ' . $uid; } else { verbose 'Secret is already encrypted'; } } return $changed; } sub convert_keys_for_user { my ( $uid, $devices ) = @_; my $has_totp = 0; my $changed = 0; for my $device ( @{$devices} ) { if ( $device->{type} eq "TOTP" ) { $has_totp = 1; my $epoch = $device->{epoch}; verbose "Processing device with epoch $epoch for user $uid"; my $changed_current = convert_device_for_user( $uid, $device ); $changed = 1 if $changed_current; } } if ( !$has_totp ) { verbose "User $uid does not have a TOTP"; } return ( $has_totp, $changed ); } __END__ =encoding utf8 =head1 NAME encryptTotpSecret - A tool to encrypt existing TOTP secrets =head1 SYNOPSIS encryptTotpSecret [options] =head1 DESCRIPTION This script is a migration tool that you can use after enabling TOTP secret encryption in the Manager. It will make sure that existing secrets are encrypted, and not just newly registered secrets. =head1 OPTIONS =over 8 =item B<--help>, B<-h> Print a brief help message and exit. =item B<--dry-run>, B<-n> Prevent the script from saving modifications to the session database =item B<--update>, B<-u> By default, secrets that are already in encrypted form are skipped by the script. Use this option to force already encrypted secrets to be decrypted, then re-encrypted using a different key (or decrypted) =item B<--old-key>, B<-o> The key used to decrypt secrets in B<--update> mode. By default, the B or B LemonLDAP::NG configuration parameters are used. =item B<--new-key>, B<-k> The key used to encrypt secrets. Use B<-u -k DECRYPT> to decrypt secrets instead. By default, the B or B LemonLDAP::NG configuration parameters are used. =item B<--force>, B<-f> Encrypt existing TOTP secrets even if encryption is disabled in the configuration =item B<--verbose>, B<-v> Increase the level of details provided by the script =back =head1 SEE ALSO L =head1 AUTHORS =over =item Maxime Besson, Emaxime.besson@worteks.comE =back =head1 BUG REPORT Use OW2 system to report bug or ask for features: L =head1 DOWNLOAD Lemonldap::NG is available at L =head1 COPYRIGHT AND LICENSE =over =item Copyright (C) 2008-2016 by Xavier Guimard, Ex.guimard@free.frE =item Copyright (C) 2008-2016 by Clément Oudot, Eclem.oudot@gmail.comE =back This library is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see L. =cut