From 7d87916f0afb78d8f236ede45cd1cbaac8a06084 Mon Sep 17 00:00:00 2001
From: Chris Hallberg <crhallberg@gmail.com>
Date: Mon, 4 Apr 2016 15:45:10 -0400
Subject: [PATCH] Configure encryption algorithms

Configure ILS encryption hash algorithm.
* Includes hash conversion util script.
---
 config/vufind/config.ini                      |   8 ++
 module/VuFind/src/VuFind/Db/Row/User.php      |   5 +-
 module/VuFindConsole/Module.php               |   3 +
 .../VuFindConsole/Controller/AbstractBase.php |  12 ++
 .../Controller/UtilController.php             | 106 ++++++++++++++++++
 5 files changed, 133 insertions(+), 1 deletion(-)

diff --git a/config/vufind/config.ini b/config/vufind/config.ini
index 837fa1161d6..63edf67623e 100644
--- a/config/vufind/config.ini
+++ b/config/vufind/config.ini
@@ -297,6 +297,14 @@ encrypt_ils_password = false
 ; filled in with a random string value when encrypt_ils_passwords is set to true.
 ils_encryption_key = false
 
+; This is the algorithm used to encrypt and decrypt catalog passwords.
+; A symmetrical encryption algorithm must be used.
+; You can use mcrypt_list_algorithms() to see available options on your system.
+; Common choices: blowfish (default), aes
+; If you want to convert from one algorithm to another, run this from $VUFIND_HOME:
+;   php public/index.php util switch_db_hash oldhash:oldkey (or none) newhash:newkey
+;ils_encryption_algo = "blowfish"
+
 ; This setting may optionally be uncommented to restrict the email domain(s) from
 ; which users are allowed to register when using the Database method.
 ;domain_whitelist[] = "myuniversity.edu"
diff --git a/module/VuFind/src/VuFind/Db/Row/User.php b/module/VuFind/src/VuFind/Db/Row/User.php
index 9acbcadcb23..5519daff554 100644
--- a/module/VuFind/src/VuFind/Db/Row/User.php
+++ b/module/VuFind/src/VuFind/Db/Row/User.php
@@ -191,7 +191,10 @@ class User extends RowGateway implements \VuFind\Db\Table\DbTableAwareInterface,
         }
 
         // Perform encryption:
-        $cipher = new BlockCipher(new Mcrypt(['algorithm' => 'blowfish']));
+        $algo = isset($this->config->Authentication->ils_encryption_algo)
+            ? $this->config->Authentication->ils_encryption_algo
+            : 'blowfish';
+        $cipher = new BlockCipher(new Mcrypt(['algorithm' => $algo]));
         $cipher->setKey($this->encryptionKey);
         return $encrypt ? $cipher->encrypt($text) : $cipher->decrypt($text);
     }
diff --git a/module/VuFindConsole/Module.php b/module/VuFindConsole/Module.php
index 1043410a50a..8b21d20f506 100644
--- a/module/VuFindConsole/Module.php
+++ b/module/VuFindConsole/Module.php
@@ -101,6 +101,9 @@ class Module implements \Zend\ModuleManager\Feature\ConsoleUsageProviderInterfac
             'util index_reserves' => 'Solr reserves indexer',
             'util optimize' => 'Solr optimize tool',
             'util sitemap' => 'XML sitemap generator',
+            'util switch_db_hash' => 'Switch the hashing algorithm in the database '
+                . 'and config. Expects new algorithm and (optional) new key as'
+                . ' parameters.',
             'util suppressed' => 'Remove ILS-suppressed records from Solr',
         ];
     }
diff --git a/module/VuFindConsole/src/VuFindConsole/Controller/AbstractBase.php b/module/VuFindConsole/src/VuFindConsole/Controller/AbstractBase.php
index 38256c4865f..022e32a2d7b 100644
--- a/module/VuFindConsole/src/VuFindConsole/Controller/AbstractBase.php
+++ b/module/VuFindConsole/src/VuFindConsole/Controller/AbstractBase.php
@@ -112,6 +112,18 @@ class AbstractBase extends AbstractActionController
         return $this->getResponse()->setErrorLevel(0);
     }
 
+    /**
+     * Get a VuFind configuration.
+     *
+     * @param string $id Configuration identifier (default = main VuFind config)
+     *
+     * @return \Zend\Config\Config
+     */
+    public function getConfig($id = 'config')
+    {
+        return $this->getServiceLocator()->get('VuFind\Config')->get($id);
+    }
+
     /**
      * Get the ILS connection.
      *
diff --git a/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php b/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php
index 2f6b954ee57..29d013db4dd 100644
--- a/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php
+++ b/module/VuFindConsole/src/VuFindConsole/Controller/UtilController.php
@@ -27,9 +27,13 @@
  */
 namespace VuFindConsole\Controller;
 use File_MARC, File_MARCXML, VuFind\Sitemap\Generator as Sitemap;
+use VuFind\Config\Locator as ConfigLocator;
+use VuFind\Config\Writer as ConfigWriter;
 use VuFindSearch\Backend\Solr\Document\UpdateDocument;
 use VuFindSearch\Backend\Solr\Record\SerializableRecord;
 use Zend\Console\Console;
+use Zend\Crypt\Symmetric\Mcrypt,
+    Zend\Crypt\BlockCipher as BlockCipher;
 
 /**
  * This controller handles various command-line tools
@@ -690,4 +694,106 @@ class UtilController extends AbstractBase
         Console::writeLine(str_replace('%%count%%', $count, $successString));
         return $this->getSuccessResponse();
     }
+
+    /**
+     * Convert hash algorithms
+     * Expected parameters: oldmethod:oldkey (or none) newmethod:newkey
+     *
+     * @return \Zend\Console\Response
+     */
+    public function switchdbhashAction()
+    {
+        // Validate command line arguments:
+        $argv = $this->consoleOpts->getRemainingArgs();
+        if (count($argv) < 1) {
+            Console::writeLine(
+                'Expected parameters: newmethod [newkey]'
+            );
+            return $this->getFailureResponse();
+        }
+
+        // Pull existing encryption settings from the configuration:
+        $config = $this->getConfig();
+        if (!isset($config->Authentication->encrypt_ils_password)
+            || !isset($config->Authentication->ils_encryption_key)
+            || !$config->Authentication->encrypt_ils_password
+        ) {
+            $oldhash = 'none';
+            $oldkey = null;
+        } else {
+            $oldhash = isset($config->Authentication->ils_encryption_algo)
+                ? $config->Authentication->ils_encryption_algo : 'blowfish';
+            $oldkey = $config->Authentication->ils_encryption_key;
+        }
+
+        // Pull new encryption settings from arguments:
+        $newhash = $argv[0];
+        $newkey = isset($argv[1]) ? $argv[1] : $oldkey;
+
+        // No key specified AND no key on file = fatal error:
+        if ($newkey === null) {
+            Console::writeLine('Please specify a key as the second parameter.');
+            return $this->getFailureResponse();
+        }
+
+        // If no changes were requested, abort early:
+        if ($oldkey == $newkey && $oldhash == $newhash) {
+            Console::writeLine('No changes requested -- no action needed.');
+            return $this->getSuccessResponse();
+        }
+
+        // Initialize Mcrypt first, so we can catch any illegal algorithms before
+        // making any changes:
+        try {
+            if ($oldhash != 'none') {
+                $oldCrypt = new Mcrypt(['algorithm' => $oldhash]);
+            }
+            $newCrypt = new Mcrypt(['algorithm' => $newhash]);
+        } catch (\Exception $e) {
+            Console::writeLine($e->getMessage());
+            return $this->getFailureResponse();
+        }
+
+        // Next update the config file, so if we are unable to write the file,
+        // we don't go ahead and make unwanted changes to the database:
+        $configPath = ConfigLocator::getLocalConfigPath('config.ini', null, true);
+        Console::writeLine("\tUpdating $configPath...");
+        $writer = new ConfigWriter($configPath);
+        $writer->set('Authentication', 'encrypt_ils_password', true);
+        $writer->set('Authentication', 'ils_encryption_algo', $newhash);
+        $writer->set('Authentication', 'ils_encryption_key', $newkey);
+        if (!$writer->save()) {
+            Console::writeLine("\tWrite failed!");
+            return $this->getFailureResponse();
+        }
+
+        // Now do the database rewrite:
+        $userTable = $this->getServiceLocator()->get('VuFind\DbTablePluginManager')
+            ->get('User');
+        $users = $userTable->select(
+            function ($select) {
+                $select->where->isNotNull('cat_username');
+            }
+        );
+        Console::writeLine("\tConverting hashes for " . count($users) . ' user(s).');
+        foreach ($users as $row) {
+            $pass = null;
+            if ($oldhash != 'none' && isset($row['cat_pass_enc'])) {
+                $oldcipher = new BlockCipher($oldCrypt);
+                $oldcipher->setKey($oldkey);
+                $pass = $oldcipher->decrypt($row['cat_pass_enc']);
+            } else {
+                $pass = $row['cat_password'];
+            }
+            $newcipher = new BlockCipher($newCrypt);
+            $newcipher->setKey($newkey);
+            $row['cat_password'] = null;
+            $row['cat_pass_enc'] = $newcipher->encrypt($pass);
+            $row->save();
+        }
+
+        // If we got this far, all went well!
+        Console::writeLine("\tFinished.");
+        return $this->getSuccessResponse();
+    }
 }
-- 
GitLab