Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #1568 from eBaySF/keychain

Keychain package
  • Loading branch information...
commit 4f7d02212647cc95bf07b82f3c4b857f0cd407ea 2 parents 3d21b02 + eaf715a
@LouisLandry LouisLandry authored
View
381 bin/keychain.php
@@ -0,0 +1,381 @@
+#!/usr/bin/env php
+<?php
+/**
+ * @package Joomla.Platform
+ *
+ * @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
+ * @license GNU General Public License version 2 or later; see LICENSE
+ */
+
+define('_JEXEC', 1);
+define('JPATH_BASE', dirname(__FILE__));
+
+// Load the Joomla! Platform
+require_once realpath('../libraries/import.php');
+
+/**
+ * Keychain Manager
+ *
+ * @package Joomla.Platform
+ * @since 12.3
+ */
+class KeychainManager extends JApplicationCli
+{
+ /**
+ * @var boolean A flag if the keychain has been updated to trigger saving the keychain
+ * @since 12.3
+ */
+ protected $updated = false;
+
+ /**
+ * @var JKeychain The keychain object being manipulated.
+ * @since 12.3
+ */
+ protected $keychain = null;
+
+ /**
+ * Execute the application
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public function execute( )
+ {
+ if (!count($this->input->args))
+ {
+ // Check if they passed --help in otherwise display short usage summary
+ if ($this->input->get('help', false) === false)
+ {
+ $this->out("usage: {$this->input->executable} [options] [command] [<args>]");
+ exit(1);
+ }
+ else
+ {
+ $this->displayHelp();
+ exit(0);
+ }
+ }
+
+ // For all tasks but help and init we use the keychain
+ if (!in_array($this->input->args[0], array('help', 'init')))
+ {
+ $this->loadKeychain();
+ }
+
+ switch ($this->input->args[0])
+ {
+ case 'init':
+ $this->initPassphraseFile();
+ break;
+ case 'list':
+ $this->listEntries();
+ break;
+ case 'create':
+ $this->create();
+ break;
+ case 'change':
+ $this->change();
+ case 'delete':
+ $this->delete();
+ break;
+ case 'read':
+ $this->read();
+ break;
+ case 'help':
+ $this->displayHelp();
+ break;
+ default:
+ $this->out('Invalid command.');
+ break;
+ }
+
+ if ($this->updated)
+ {
+ $this->saveKeychain();
+ }
+ exit(0);
+ }
+
+ /**
+ * Load the keychain from a file.
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function loadKeychain()
+ {
+ $keychain = $this->input->get('keychain', '', 'raw');
+ $publicKeyFile = $this->input->get('public-key', '', 'raw');
+ $passphraseFile = $this->input->get('passphrase', '', 'raw');
+
+ $this->keychain = new JKeychain;
+
+ if (file_exists($keychain))
+ {
+ if (file_exists($publicKeyFile))
+ {
+ $this->keychain->loadKeychain($keychain, $passphraseFile, $publicKeyFile);
+ }
+ else
+ {
+ $this->out('Public key not specified or missing!');
+ exit(1);
+ }
+ }
+ }
+
+ /**
+ * Save this keychain to a file.
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function saveKeychain()
+ {
+ $keychain = $this->input->get('keychain', '', 'raw');
+ $publicKeyFile = $this->input->get('public-key', '', 'raw');
+ $passphraseFile = $this->input->get('passphrase', '', 'raw');
+
+ if (!file_exists($publicKeyFile))
+ {
+ $this->out("Public key file specified doesn't exist: $publicKeyFile");
+ exit(1);
+ }
+
+ $this->keychain->saveKeychain($keychain, $passphraseFile, $publicKeyFile);
+ }
+
+ /**
+ * Initialise a new passphrase file.
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function initPassphraseFile()
+ {
+ $keychain = new JKeychain;
+
+ $passphraseFile = $this->input->get('passphrase', '', 'raw');
+ $privateKeyFile = $this->input->get('private-key', '', 'raw');
+
+ if (!strlen($passphraseFile))
+ {
+ $this->out('A passphrase file must be specified with --passphrase');
+ exit(1);
+ }
+
+ if (!file_exists($privateKeyFile))
+ {
+ $this->out("protected key file specified doesn't exist: $privateKeyFile");
+ exit(1);
+ }
+
+ $this->out('Please enter the new passphrase:');
+ $passphrase = $this->in();
+
+ $this->out('Please enter the passphrase for the protected key:');
+ $privateKeyPassphrase = $this->in();
+
+ $keychain->createPassphraseFile($passphrase, $passphraseFile, $privateKeyFile, $privateKeyPassphrase);
+ }
+
+ /**
+ * Create a new entry
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function create()
+ {
+ if (count($this->input->args) != 3)
+ {
+ $this->out("usage: {$this->input->executable} [options] create entry_name entry_value");
+ exit(1);
+ }
+
+ if ($this->keychain->exists($this->input->args[1]))
+ {
+ $this->out('error: entry already exists. To change this entry, use "change"');
+ exit(1);
+ }
+ $this->change();
+ }
+
+ /**
+ * Change an existing entry to a new value or create an entry if missing.
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function change()
+ {
+ if (count($this->input->args) != 3)
+ {
+ $this->out("usage: {$this->input->executable} [options] change entry_name entry_value");
+ exit(1);
+ }
+ $this->updated = true;
+ $this->keychain->setValue($this->input->args[1], $this->input->args[2]);
+ }
+
+ /**
+ * Read an entry from the keychain
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function read()
+ {
+ if (count($this->input->args) != 2)
+ {
+ $this->out("usage: {$this->input->executable} [options] read entry_name");
+ exit(1);
+ }
+
+ $key = $this->input->args[1];
+ $this->out($key . ': ' . $this->dumpVar($this->keychain->get($key)));
+ }
+
+ /**
+ * Get the string from var_dump
+ *
+ * @param mixed $var The variable you want to have dumped.
+ *
+ * @return string The result of var_dump
+ *
+ * @since 12.3
+ */
+ private function dumpVar($var)
+ {
+ ob_start();
+ var_dump($var);
+ $result = trim(ob_get_contents());
+ ob_end_clean();
+ return $result;
+ }
+
+ /**
+ * Delete an entry from the keychain
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function delete()
+ {
+ if (count($this->input->args) != 2)
+ {
+ $this->out("usage: {$this->input->executable} [options] delete entry_name");
+ exit(1);
+ }
+
+ $this->updated = true;
+ $this->keychain->deleteValue($this->input->args[1], null);
+ }
+
+ /**
+ * List entries in the keychain
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function listEntries()
+ {
+ foreach ($this->keychain->toArray() as $key => $value)
+ {
+ $line = $key;
+ if ($this->input->get('print-values'))
+ {
+ $line .= ': ' . $this->dumpVar($value);
+ }
+ $this->out($line);
+ }
+ }
+
+ /**
+ * Display the help information
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ protected function displayHelp()
+ {
+/*
+COMMANDS
+
+ - list
+ - create entry_name entry_value
+ - change entry_name entry_value
+ - delete entry_name
+ - read entry_name
+*/
+
+ $help = <<<HELP
+Keychain Management Utility
+
+usage: {$this->input->executable} [--keychain=/path/to/keychain]
+ [--passphrase=/path/to/passphrase.dat] [--public-key=/path/to/public.pem]
+ [command] [<args>]
+
+OPTIONS
+
+ --keychain=/path/to/keychain
+ Path to a keychain file to manipulate.
+
+ --passphrase=/path/to/passphrase.dat
+ Path to a passphrase file containing the encryption/decryption key.
+
+ --public-key=/path/to/public.pem
+ Path to a public key file to decrypt the passphrase file.
+
+
+COMMANDS
+
+ list:
+ Usage: list [--print-values]
+ Lists all entries in the keychain. Optionally pass --print-values to print the values as well.
+
+ create:
+ Usage: create entry_name entry_value
+ Creates a new entry in the keychain called "entry_name" with the plaintext value "entry_value".
+ NOTE: This is an alias for change.
+
+ change:
+ Usage: change entry_name entry_value
+ Updates the keychain entry called "entry_name" with the value "entry_value".
+
+ delete:
+ Usage: delete entry_name
+ Removes an entry called "entry_name" from the keychain.
+
+ read:
+ Usage: read entry_name
+ Outputs the plaintext value of "entry_name" from the keychain.
+
+ init:
+ Usage: init
+ Creates a new passphrase file and prompts for a new passphrase.
+
+HELP;
+ $this->out($help);
+ }
+}
+
+try
+{
+ JApplicationCli::getInstance('KeychainManager')->execute();
+}
+catch (Exception $e)
+{
+ echo $e->getMessage() . "\n";
+ exit(1);
+}
View
81 docs/manual/en-US/chapters/classes/jkeychain.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+<!ENTITY % BOOK_ENTITIES SYSTEM "../../Developer_Manual.ent">
+%BOOK_ENTITIES;
+]>
+<section>
+ <title>JKeychain</title>
+
+ <section>
+ <title>Construction</title>
+
+ <para>The <classname>JKeychain</classname> class extends <classname>JRegistry</classname>. There are no changes to the
+ constructor's argument list so optional initialisation with data can be done in the normal way.</para>
+
+ <example>
+ <title>JKeychain construction</title>
+
+ <programlisting>// Create a keychain.
+$keychain1 = new JKeychain;
+
+$keychain2 = new JKeychain(array('username' =&gt; 'foo', 'password' =&gt; 'bar'));</programlisting>
+ </example>
+ </section>
+
+ <section>
+ <title>Usage</title>
+
+ <para>A <classname>JKeychain</classname> object operates in the same way as a <classname>JRegistry</classname> object. What
+ <classname>JKeychain</classname> provides is a way to load data from, and store data to an encrypted data source.</para>
+
+ <para>When using this class, the private and public keys must already exist on the server. The third required element is the
+ passphrase file and the following example shows how to create it.</para>
+
+ <example>
+ <title>Using JKeychain to create a passphrase file</title>
+
+ <programlisting>// Create a keychain object.
+$keychain = new JKeychain;
+
+// The passphrase/passowrd should not be stored in any code repository.
+$passPhrase = 'the Pass Phrase';
+$privateKeyPwd = 'the Private Key Password';
+
+// The paths to keychain files could come from the application configuration.
+$passPhrasePath = '/etc/project/config/keychain.passphrase';
+$privateKeyPath = '/etc/project/config/keychain.key';
+
+$keychain-&gt;createPassphraseFile($passPhrase, $passPhrasePath, $privateKeyPath, $privateKeyPwd);</programlisting>
+ </example>
+
+ <para>The passphrase file will generally be created using the Keychain Management Utility (see next section) on the command
+ line so that neither the passphrase, nor the private key password are stored in clear text in any application code.</para>
+
+ <para>Likewise, initial data is probably already created in a keychain data file (again, using the Keychain Management Utility
+ and the <command>create</command> command). The following example shows how to load the keychain data:</para>
+
+ <example>
+ <title>Using JKeychain to access existing data</title>
+
+ <programlisting>// Create a keychain object.
+$keychain = new JKeychain;
+
+$keychainFile = '/etc/project/config/keychain.dat';
+$passPhrasePath = '/etc/project/config/keychain.passphrase';
+$publicKeyPath = '/etc/project/config/keychain.pem';
+
+$keychain-&gt;loadKeychain($keychainFile, $passPhrasePath, $publicKeyPath);
+
+$secureUsername = $keychain-&gt;get('secure.username');
+$securePassword = $keychain-&gt;get('secure.password');
+
+$conn = connect_to_server($secureUsername, $securePassword);</programlisting>
+ </example>
+
+ <para>The keychain object can manipulate data as if it was a normal <classname>JRegistry</classname> object. However, an
+ additional <methodname>deleteValue</methodname> method is provided to strip out registry data if required.</para>
+
+ <para>Finally, the <methodname>saveKeychain</methodname> method can be used to save data back to the keychain file.</para>
+ </section>
+</section>
View
2  docs/manual/en-US/chapters/packages.xml
@@ -21,6 +21,8 @@
<xi:include href="packages/input.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
+ <xi:include href="packages/keychain.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
+
<xi:include href="packages/log.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
<xi:include href="packages/mvc.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
View
202 docs/manual/en-US/chapters/packages/keychain.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"
+"http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd" [
+<!ENTITY % BOOK_ENTITIES SYSTEM "../../Developer_Manual.ent">
+%BOOK_ENTITIES;
+]>
+<section id="chap-Joomla_Platform_Manual-Keychain">
+ <title>The Keychain Package</title>
+
+ <para>The keychain provides a way to securely store sensitive information such as access credentials or any other data.</para>
+
+ <para>The system relies on three files:</para>
+
+ <simplelist>
+ <member>a public key;</member>
+
+ <member>a private key;</member>
+
+ <member>a passphrase file; and</member>
+
+ <member>a keychain file.</member>
+ </simplelist>
+
+ <para>The <emphasis>passphrase file</emphasis> is generated by using the <emphasis>private key</emphasis> to encrypt the
+ passphrase. This is so that the passphrase file can be decrypted by the <emphasis>public key</emphasis> without requiring the
+ knowledge of the passphrase for the private key. This means it can be deployed onto a server without requiring manual
+ intervention or a passphrased stored plain text on disk. Because of this the public key should not be stored in a repository and
+ should be stored on servers in a protected location.</para>
+
+ <para>The <emphasis>keychain file</emphasis> is the actual valuable contents. It is encrypted using the passphrase stored in the
+ passphrase file (which itself is decrypted using the public key).</para>
+
+ <para>This provides a balance between not storing credentials plain text but also making the system reasonably
+ independent.</para>
+
+ <para>A good example of where the keychain is useful is where some code needs to establish a connection with another server or
+ service using some access credentials (usually a username and password, but any number of authentication credentials could be
+ used); using clear text credentials in the code, which is probably stored on a relatively public code repository, can be avoided
+ by storing the credentials in an encrypted data file that the keychain can read.</para>
+
+ <section>
+ <title>Key Storage</title>
+
+ <para>You can store the private key in a code repository but you <emphasis role="bold">MUST NOT</emphasis> commit the
+ <emphasis role="bold">public key</emphasis>. Doing so will compromise the security of the keychain (you will need to
+ regenerate the private key if you accidentally do commit it).</para>
+ </section>
+
+ <xi:include href="../classes/jkeychain.xml" xmlns:xi="http://www.w3.org/2001/XInclude" />
+
+ <section>
+ <title>Keychain Management Utility</title>
+
+ <para>The keychain management utility (<filename>/bin/keychain.php</filename>) allows you to manage keychain resources and
+ data from the command line.</para>
+
+ <section>
+ <title>Usage</title>
+
+ <programlisting>$ keychain.php [--keychain=/path/to/keychain]
+ [--passphrase=/path/to/passphrase.dat] [--public-key=/path/to/public.pem]
+ [command] [&lt;args&gt;]</programlisting>
+
+ <para>Options</para>
+
+ <simplelist>
+ <member>--keychain=/path/to/keychain - Path to a keychain file to manipulate.</member>
+
+ <member>--passphrase=/path/to/passphrase.dat - Path to a passphrase file containing the encryption/decryption
+ key.</member>
+
+ <member>--public-key=/path/to/public.pem - Path to a public key file to decrypt the passphrase file.</member>
+ </simplelist>
+
+ <para>Commands</para>
+
+ <itemizedlist>
+ <listitem>
+ <para><command>list</command> [--print-values]</para>
+
+ <para>Lists all entries in the keychain. Optionally pass --print-values to print the values as well.</para>
+ </listitem>
+
+ <listitem>
+ <para><command>create</command> entry_name entry_value</para>
+
+ <para>Creates a new entry in the keychain called "entry_name" with the plaintext value "entry_value". NOTE: This is an
+ alias for change.</para>
+ </listitem>
+
+ <listitem>
+ <para><command>change</command> entry_name entry_value</para>
+
+ <para>Updates the keychain entry called "entry_name" with the value "entry_value".</para>
+ </listitem>
+
+ <listitem>
+ <para><command>delete</command> entry_name</para>
+
+ <para>Removes an entry called "entry_name" from the keychain.</para>
+ </listitem>
+
+ <listitem>
+ <para><command>read</command> entry_name</para>
+
+ <para>Outputs the plaintext value of "entry_name" from the keychain.</para>
+ </listitem>
+
+ <listitem>
+ <para><command>init</command></para>
+
+ <para>Creates a new passphrase file and prompts for a new passphrase.</para>
+ </listitem>
+ </itemizedlist>
+
+ <para></para>
+ </section>
+
+ <section>
+ <title>Generating Keys</title>
+
+ <para>On a command line with openssl installed (any Mac OS X or Linux box is suitable):</para>
+
+ <programlisting>$ openssl genrsa -des3 -out private.key 1024</programlisting>
+
+ <para>This command will generate a new private key in the file "private.key". This can be then used to create a new public
+ key file:</para>
+
+ <programlisting>$ openssl rsa -in private.key -pubout -out publickey.pem</programlisting>
+
+ <para>This will use the private key we just created in private.key to output a new public key into the file
+ publickey.pem.</para>
+ </section>
+
+ <section>
+ <title>Generating Keys with Certificates</title>
+
+ <para>If you need to generate keys with certificates (exact details will vary from system to system), on a command line with
+ openssl installed:</para>
+
+ <programlisting>openssl req -x509 -days 3650 -newkey rsa:1024 -keyout private.key -out publickey.pem</programlisting>
+
+ <para>This will create a new private key in the file private.key and a new public key in the file publickey.pem. You will be
+ asked for a passphrase to secure the private key. and then prompted for information to be incorporated into the certificate
+ request:</para>
+
+ <programlisting>Country Name (2 letter code) [AU]: US
+State or Province Name (full name) [Some-State]: New York
+Locality Name (eg, city) []: New York
+Organization Name (eg, company) [Internet Widgits Pty Ltd]: Open Source Matters, Inc.
+Organizational Unit Name (eg, section) []: Joomla! Platform
+Common Name (eg, YOUR name) []: Joomla Credentials
+Email Address []: platform@joomla.org</programlisting>
+
+ <para>Once this is done there will be a private.key and publickey.pem file that you can use for managing the passphrase
+ file.</para>
+ </section>
+
+ <section>
+ <title>Initialise a new passphrase file</title>
+
+ <para>This step requires that you have already generated a private key (and assumes the <filename>keychain.php</filename>
+ file is executable and in your lookup path). The following command will initialise a new passphrase file:</para>
+
+ <programlisting>$ keychain.php init --passphrase=/path/to/passphrase.file --private-key=/path/to/private.key</programlisting>
+
+ <para>This will prompt for two things:</para>
+
+ <simplelist>
+ <member>the passphrase to store in <filename>passphrase.file</filename>; and</member>
+
+ <member>the passphrase for the private key.</member>
+ </simplelist>
+
+ <para>It will create a new file at <filename>/path/to/passphrase.file</filename> replacing any file that might be there
+ already.</para>
+ </section>
+
+ <section>
+ <title>Create a new entry in the keychain</title>
+
+ <para>This step requires that you have already generated the private key and the passphrase file. The following command will
+ create or update an entry in the keychain:</para>
+
+ <programlisting>$ keychain.php create --passphrase=/path/to/passphrase.file --public-key=/path/to/publickey.pem --keychain=/path/to/keychain.dat name value</programlisting>
+
+ <para>An existing keychain file will attempt to be loaded and then key name will be set to value.</para>
+ </section>
+
+ <section>
+ <title>Create a new public key from private key</title>
+
+ <para>If you know the passphrase for the private key but have lost the public key you can regenerate the public key:</para>
+
+ <programlisting>openssl rsa -in private.key -pubout -out publickey.pem</programlisting>
+
+ <para>This will use the private key in the file <filename>private.key</filename> and output a new public key to
+ <filename>publickey.pem</filename>. If the private key has a passphrase on it, you will be prompted to enter the
+ passphrase.</para>
+ </section>
+ </section>
+</section>
View
192 libraries/joomla/keychain/keychain.php
@@ -0,0 +1,192 @@
+<?php
+/**
+ * @package Joomla.Platform
+ * @subpackage Keychain
+ *
+ * @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
+ * @license GNU General Public License version 2 or later; see LICENSE
+ */
+
+defined('JPATH_PLATFORM') or die;
+
+/**
+ * Keychain Class
+ *
+ * @package Joomla.Platform
+ * @subpackage Keychain
+ * @since 12.3
+ */
+class JKeychain extends JRegistry
+{
+ /**
+ * @var string Method to use for encryption.
+ * @since 12.3
+ */
+ public $method = 'aes-128-cbc';
+
+ /**
+ * @var string Initialisation vector for encryption method.
+ * @since 12.3
+ */
+ public $iv = "1234567890123456";
+
+ /**
+ * Create a passphrase file
+ *
+ * @param string $passphrase The passphrase to store in the passphrase file.
+ * @param string $passphraseFile Path to the passphrase file to create.
+ * @param string $privateKeyFile Path to the private key file to encrypt the passphrase file.
+ * @param string $privateKeyPassphrase The passphrase for the private key.
+ *
+ * @return boolean Result of writing the passphrase file to disk.
+ *
+ * @since 12.3
+ * @throws RuntimeException
+ */
+ public function createPassphraseFile($passphrase, $passphraseFile, $privateKeyFile, $privateKeyPassphrase)
+ {
+ $privateKey = openssl_get_privatekey(file_get_contents($privateKeyFile), $privateKeyPassphrase);
+ if (!$privateKey)
+ {
+ throw new RuntimeException("Failed to load private key.");
+ }
+
+ $crypted = '';
+ if (!openssl_private_encrypt($passphrase, $crypted, $privateKey))
+ {
+ throw new RuntimeException("Failed to encrypt data using private key.");
+ }
+
+ return file_put_contents($passphraseFile, $crypted);
+ }
+
+ /**
+ * Delete a registry value (very simple method)
+ *
+ * @param string $path Registry Path (e.g. joomla.content.showauthor)
+ *
+ * @return mixed Value of old value or boolean false if operation failed
+ *
+ * @since 12.3
+ */
+ public function deleteValue($path)
+ {
+ $result = null;
+
+ // Explode the registry path into an array
+ $nodes = explode('.', $path);
+ if ($nodes)
+ {
+ // Initialize the current node to be the registry root.
+ $node = $this->data;
+
+ // Traverse the registry to find the correct node for the result.
+ for ($i = 0, $n = count($nodes) - 1; $i < $n; $i++)
+ {
+ if (!isset($node->$nodes[$i]) && ($i != $n))
+ {
+ $node->$nodes[$i] = new stdClass;
+ }
+ $node = $node->$nodes[$i];
+ }
+
+ // Get the old value if exists so we can return it
+ $result = $node->$nodes[$i];
+ unset($node->$nodes[$i]);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Load a keychain file into this object.
+ *
+ * @param string $keychainFile Path to the keychain file.
+ * @param string $passphraseFile The path to the passphrase file to decript the keychain.
+ * @param string $publicKeyFile The file containing the public key to decrypt the passphrase file.
+ *
+ * @return boolean Result of loading the object.
+ *
+ * @since 12.3
+ * @throws RuntimeException
+ */
+ public function loadKeychain($keychainFile, $passphraseFile, $publicKeyFile)
+ {
+ if (!file_exists($keychainFile))
+ {
+ throw new RuntimeException('Attempting to load non-existent keychain file');
+ }
+ $passphrase = $this->getPassphraseFromFile($passphraseFile, $publicKeyFile);
+
+ $cleartext = openssl_decrypt(file_get_contents($keychainFile), $this->method, $passphrase, true, $this->iv);
+
+ if ($cleartext === false)
+ {
+ throw new RuntimeException("Failed to decrypt keychain file");
+ }
+
+ return $this->loadObject(json_decode($cleartext));
+ }
+
+ /**
+ * Save this keychain to a file.
+ *
+ * @param string $keychainFile The path to the keychain file.
+ * @param string $passphraseFile The path to the passphrase file to encrypt the keychain.
+ * @param string $publicKeyFile The file containing the public key to decrypt the passphrase file.
+ *
+ * @return boolean Result of storing the file.
+ *
+ * @since 12.3
+ * @throws RuntimeException
+ */
+ public function saveKeychain($keychainFile, $passphraseFile, $publicKeyFile)
+ {
+ $passphrase = $this->getPassphraseFromFile($passphraseFile, $publicKeyFile);
+ $data = $this->toString('JSON');
+
+ $encrypted = @openssl_encrypt($data, $this->method, $passphrase, true, $this->iv);
+
+ if ($encrypted === false)
+ {
+ throw new RuntimeException('Unable to encrypt keychain');
+ }
+
+ return file_put_contents($keychainFile, $encrypted);
+ }
+
+ /**
+ * Get the passphrase for this keychain
+ *
+ * @param string $passphraseFile The file containing the passphrase to encrypt and decrypt.
+ * @param string $publicKeyFile The file containing the public key to decrypt the passphrase file.
+ *
+ * @return string The passphrase in from passphraseFile
+ *
+ * @since 12.3
+ * @throws RuntimeException
+ */
+ protected function getPassphraseFromFile($passphraseFile, $publicKeyFile)
+ {
+ if (!file_exists($publicKeyFile))
+ {
+ throw new RuntimeException('Missing public key file');
+ }
+ $publicKey = openssl_get_publickey(file_get_contents($publicKeyFile));
+ if (!$publicKey)
+ {
+ throw new RuntimeException("Failed to load public key.");
+ }
+
+ if (!file_exists($passphraseFile))
+ {
+ throw new RuntimeException('Missing passphrase file');
+ }
+ $passphrase = '';
+ if (!openssl_public_decrypt(file_get_contents($passphraseFile), $passphrase, $publicKey))
+ {
+ throw new RuntimeException('Failed to decrypt passphrase file');
+ }
+ return $passphrase;
+ }
+}
View
5 tests/suites/unit/joomla/keychain/data/README.md
@@ -0,0 +1,5 @@
+# Test public and private key.
+
+Passphrase for private key is "password".
+
+**DO NOT USE THESE KEYS IN PRODUCTION! TEST USE ONLY!**
View
1  tests/suites/unit/joomla/keychain/data/cli-keychain.dat
@@ -0,0 +1 @@
+O�V:� Ck0�Q��sT�Du�g��y����2���j�~�w��
View
BIN  tests/suites/unit/joomla/keychain/data/cli-passphrase.dat
Binary file not shown
View
18 tests/suites/unit/joomla/keychain/data/private.key
@@ -0,0 +1,18 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,8F48E3F7B96F5E8F
+
+TFdn7LwXLRHSRrPAgHSM3JSftjYzPweGPzxurr8yEOYhSmEnD/GMrR8kKQiRhCdW
++BlJWcByBPaZUd3tJY4jUflRuns3VvGjPGrgflWPgVBH/orYnwQG/Z6o5EWS2JXt
+Xm/rv2HuiG6uamQDU/a8zCHQh583EqRPFd0GxeGmnUKY3B3D+D6G3SuMuYobNPy4
+db3j18JIp9PionN7w+zxZeMUR1f3G4K5wldPzQFvZ349Sa9/O9ROzvpLXrHAysGG
+++puWGbLkAIimwasM1JhlTTYa8oT9cLgoBuq0QGVXDQyQubzlAWYeCib3G/TUbXU
+Gj9Nz35kqET8ufFASMlYwbvB2rYT93fJlk4YOUw1L7agIkwEc+ix/3Zryw4VLHQ3
+l+g0YPs1JhH4UYzquAFqTKi0o/DpoayyjIeEjaN34TN5DqJtAV0i2kUUddKiHYSO
++uuOrdjKmDXjZL2pUYyzyzM22/Bt0+kE4TOwC8Ko3j7FNg7bOUGDbEXP1srSrqG/
+BaUwnti5DV2BD5ABbOtdmO/GMNjPtxsZ5vmu3XwvFXG1kr0fXGkqzW3zYvEKBsAE
+ifCcFRS2N9hH/veZp16TODGxFtMKJQadFoFYJOBB40LtXb9dNNdOprIrlWlSSi6p
+Lvay3u49wk2WO0pabzsYc4rF5MTIU7lRYYGb/j1mUuBru3i+fAN513P6gtxl8f5Z
+9aCdiEpY4jqX+ZG8LkEVmcoFMFWJX8YJpw2eyotq/wK6QvHr4+jwtqIQ3+xI45Uo
+Ivgb4MZI1VHk5ZSGSOLNhBIXWbpVutQJVux2ZYJUk8s=
+-----END RSA PRIVATE KEY-----
View
6 tests/suites/unit/joomla/keychain/data/publickey.pem
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC/RLI8OsHHd5snYPkd0czsxz8+
+4AwExCjooRXEiQsqQIOyZSYRqxLUnnQc+1h+nqnaPs89ccwUiS0VNvCZ+8Rbe9Qx
+ZwNMj1xXrHB20J7lr2fq/jlD5LHHLAQuk0iL5Ddar1IVLNoBFR9qU6oj6mEnzISr
+UIMUTTQOmFTQt3iAtQIDAQAB
+-----END PUBLIC KEY-----
View
209 tests/suites/unit/joomla/keychain/keychainTest.php
@@ -0,0 +1,209 @@
+<?php
+/**
+ * @package Joomla.Platform
+ * @subpackage Keychain
+ *
+ * @copyright Copyright (C) 2005 - 2012 Open Source Matters, Inc. All rights reserved.
+ * @license GNU General Public License version 2 or later; see LICENSE
+ */
+
+/**
+ * Tests for the Joomla Platform Keychain Class
+ *
+ * @package Joomla.UnitTest
+ * @subpackage Keychain
+ * @since 12.3
+ */
+class JKeychainTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * Set up the system by ensuring some files aren't there.
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public static function setUpBeforeClass()
+ {
+ // clean up files
+ @unlink(__DIR__ . '/data/web-keychain.dat');
+ @unlink(__DIR__ . '/data/web-passphrase.dat');
+
+ parent::setUpBeforeClass();
+ }
+
+ /**
+ * Clean up afterwards.
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public static function tearDownAfterClass()
+ {
+ // clean up files
+ @unlink(__DIR__ . '/data/web-keychain.dat');
+ @unlink(__DIR__ . '/data/web-passphrase.dat');
+ parent::tearDownAfterClass();
+ }
+
+ /**
+ * Test loading a file created in the CLI client (Joomla! Platform)
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public function testLoadCLIKeychain()
+ {
+ $keychain = new JKeychain;
+
+ $keychainFile = __DIR__ . '/data/cli-keychain.dat';
+ $passphraseFile = __DIR__ . '/data/cli-passphrase.dat';
+ $publicKeyFile = __DIR__ . '/data/publickey.pem';
+
+ $keychain->loadKeychain($keychainFile, $passphraseFile, $publicKeyFile);
+
+ $this->assertEquals('value', $keychain->get('test'));
+ }
+
+ /**
+ * Test trying to create a new passphrase file
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public function testCreatePassphraseFile()
+ {
+ $keychainFile = __DIR__ . '/data/web-keychain.dat';
+ $privateKeyFile = __DIR__ . '/data/private.key';
+ $publicKeyFile = __DIR__ . '/data/publickey.pem';
+ $passphraseFile = __DIR__ . '/data/web-passphrase.dat';
+
+ $keychain = new JKeychain;
+ $keychain->createPassphraseFile('testpassphrase', $passphraseFile, $privateKeyFile, 'password');
+
+ $this->assertTrue(file_exists($passphraseFile), 'Test passphrase file exists');
+ }
+
+ /**
+ * Try to load a keychain that liaosn't exist (this shouldn't cause an error)
+ *
+ * @expectedException RuntimeException
+ * @expectedExceptionMessage Attempting to load non-existent keychain file
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public function testLoadKeychainNonexistant()
+ {
+ $keychainFile = __DIR__ . '/data/fake-web-keychain.dat';
+ $publicKeyFile = __DIR__ . '/data/publickey.pem';
+ $passphraseFile = __DIR__ . '/data/web-passphrase.dat';
+
+ $keychain = new JKeychain;
+
+ $keychain->loadKeychain($keychainFile, $passphraseFile, $publicKeyFile);
+ }
+
+ /**
+ * Try to load a keychain that isn't a keychain
+ *
+ * @depends testCreatePassphraseFile
+ * @expectedException RuntimeException
+ * @expectedExceptionMessage Failed to decrypt keychain file
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public function testLoadKeychainInvalid()
+ {
+ $publicKeyFile = __DIR__ . '/data/publickey.pem';
+ $passphraseFile = __DIR__ . '/data/web-passphrase.dat';
+
+ $keychain = new JKeychain;
+
+ $keychain->loadKeychain($passphraseFile, $passphraseFile, $publicKeyFile);
+ }
+
+ /**
+ * Create a new keychain and persist it to a new file.
+ *
+ * @depends testCreatePassphraseFile
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public function testSaveKeychain()
+ {
+ $keychainFile = __DIR__ . '/data/web-keychain.dat';
+ $publicKeyFile = __DIR__ . '/data/publickey.pem';
+ $passphraseFile = __DIR__ . '/data/web-passphrase.dat';
+
+ $keychain = new JKeychain;
+ $keychain->set('dennis', 'liao');
+ $this->assertTrue((bool) $keychain->saveKeychain($keychainFile, $passphraseFile, $publicKeyFile), 'Assert that saveKeychain returns true.');
+
+ $this->assertTrue(file_exists($keychainFile), 'Check that keychain file was created properly.');
+ }
+
+ /**
+ * Load a keychain file we just created
+ *
+ * @depends testSaveKeychain
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public function testLoadKeychain()
+ {
+ $keychainFile = __DIR__ . '/data/web-keychain.dat';
+ $publicKeyFile = __DIR__ . '/data/publickey.pem';
+ $passphraseFile = __DIR__ . '/data/web-passphrase.dat';
+
+ $keychain = new JKeychain;
+
+ $keychain->loadKeychain($keychainFile, $passphraseFile, $publicKeyFile);
+
+ $this->assertEquals('liao', $keychain->get('dennis'));
+ }
+
+ /**
+ * Delete a value from the keychain
+ *
+ * @depends testSaveKeychain
+ *
+ * @return void
+ *
+ * @since 12.3
+ */
+ public function testDeleteValue()
+ {
+ $keychainFile = __DIR__ . '/data/web-keychain.dat';
+ $publicKeyFile = __DIR__ . '/data/publickey.pem';
+ $passphraseFile = __DIR__ . '/data/web-passphrase.dat';
+
+ $keychain = new JKeychain;
+
+ $keychain->loadKeychain($keychainFile, $passphraseFile, $publicKeyFile);
+
+ $this->assertEquals('liao', $keychain->get('dennis'));
+
+ $keychain->deleteValue('dennis');
+
+ $this->assertFalse($keychain->exists('dennis'));
+
+ $keychain->saveKeychain($keychainFile, $passphraseFile, $publicKeyFile);
+
+ $keychain = new JKeychain;
+
+ $keychain->loadKeychain($keychainFile, $passphraseFile, $publicKeyFile);
+
+ $this->assertFalse($keychain->exists('dennis'));
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.