Move Horde_Crypt from CVS.
authorMichael M Slusarz <slusarz@curecanti.org>
Sun, 16 Nov 2008 21:05:55 +0000 (14:05 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Sun, 16 Nov 2008 21:05:55 +0000 (14:05 -0700)
Due to Horde_Mime changes.

36 files changed:
framework/Crypt/lib/Horde/Crypt.php [new file with mode: 0644]
framework/Crypt/lib/Horde/Crypt/pgp.php [new file with mode: 0644]
framework/Crypt/lib/Horde/Crypt/smime.php [new file with mode: 0644]
framework/Crypt/package.xml [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/bug_6601.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/bug_6601.asc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/clear.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted_symmetric.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_private.asc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_public.asc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_signature.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed2.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/smime_subjectAltName.pem [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp.inc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_decrypt.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_decrypt_symmetric.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_encrypt.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_encrypt_symmetric.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_encryptedSymmetrically.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_generateRevocation.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_getSignersKeyID.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_parsePGPData.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_pgpPacketInformation.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignature.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignatureByUidIndex.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_pgpPrettyKey.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_publicKeyMIMEPart.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_sign.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_skipif.inc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_verify.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_verifyPassphrase.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/smime.inc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/smime_skipif.inc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/smime_subjectAltName.phpt [new file with mode: 0644]

diff --git a/framework/Crypt/lib/Horde/Crypt.php b/framework/Crypt/lib/Horde/Crypt.php
new file mode 100644 (file)
index 0000000..3428e73
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+/**
+ * The Horde_Crypt:: class provides an API for various cryptographic
+ * systems used by Horde applications.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Crypt
+ */
+class Horde_Crypt
+{
+    /**
+     * The temporary directory to use.
+     *
+     * @var string
+     */
+    protected var $_tempdir;
+
+    /**
+     * Attempts to return a concrete Horde_Crypt instance based on $driver.
+     *
+     * @param mixed $driver  The type of concrete Horde_Crypt subclass to
+     *                       return. If $driver is an array, then we will look
+     *                       in $driver[0]/lib/Crypt/ for the subclass
+     *                       implementation named $driver[1].php.
+     * @param array $params  A hash containing any additional configuration or
+     *                       parameters a subclass might need.
+     *
+     * @return Horde_Crypt  The newly created concrete Horde_Crypt instance, or
+     *                      false on an error.
+     */
+    static public function factory($driver, $params = array())
+    {
+        if (is_array($driver)) {
+            list($app, $driver) = $driver;
+        }
+
+        /* Return a base Horde_Crypt object if no driver is specified. */
+        $driver = basename($driver);
+        if (empty($driver) || (strcmp($driver, 'none') == 0)) {
+            return new Horde_Crypt();
+        }
+
+        $class = 'Horde_Crypt_' . $driver;
+        if (!class_exists($class)) {
+            if (!empty($app)) {
+                include_once $GLOBALS['registry']->get('fileroot', $app) . '/lib/Crypt/' . $driver . '.php';
+            } else {
+                include_once dirname(__FILE__) . '/Crypt/' . $driver . '.php';
+            }
+        }
+
+        return class_exists($class)
+            ? new $class($params);
+            PEAR::raiseError('Class definition of ' . $class . ' not found.');
+    }
+
+    /**
+     * Attempts to return a reference to a concrete Horde_Crypt instance
+     * based on $driver. It will only create a new instance if no
+     * Horde_Crypt instance with the same parameters currently exists.
+     *
+     * This should be used if multiple crypto backends (and, thus,
+     * multiple Horde_Crypt instances) are required.
+     *
+     * This method must be invoked as: $var = &Horde_Crypt::singleton()
+     *
+     * @param mixed $driver  The type of concrete Horde_Crypt subclass to
+     *                       return. If $driver is an array, then we will look
+     *                       in $driver[0]/lib/Crypt/ for the subclass
+     *                       implementation named $driver[1].php.
+     * @param array $params  A hash containing any additional configuration or
+     *                       connection parameters a subclass might need.
+     *
+     * @return Horde_Crypt  The concrete Horde_Crypt reference, or false on an
+     *                      error.
+     */
+    static public function &singleton($driver, $params = array())
+    {
+        static $instances = array();
+
+        $signature = serialize(array($driver, $params));
+        if (!isset($instances[$signature])) {
+            $instances[$signature] = Horde_Crypt::factory($driver, $params);
+        }
+
+        return $instances[$signature];
+    }
+
+    /**
+     * Outputs error message if we are not using a secure connection.
+     *
+     * @return PEAR_Error  Returns a PEAR_Error object if there is no secure
+     *                     connection.
+     */
+    public function requireSecureConnection()
+    {
+        if ($GLOBALS['browser']->usingSSLConnection()) {
+            return;
+        }
+
+        if (!empty($GLOBALS['conf']['safe_ips'])) {
+            if (reset($GLOBALS['conf']['safe_ips']) == '*') {
+                return;
+            }
+
+            /* $_SERVER['HTTP_X_FORWARDED_FOR'] is user data and not
+             * reliable. We dont' consult it for safe IPs. We also
+             * have to assume that if it is present, the user is
+             * coming through a proxy server. If so, we don't count
+             * any non-SSL connection as safe, no matter the source
+             * IP. */
+            if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+                $remote_addr = $_SERVER['REMOTE_ADDR'];
+                foreach ($GLOBALS['conf']['safe_ips'] as $safe_ip) {
+                    $safe_ip = preg_replace('/(\.0)*$/', '', $safe_ip);
+                    if (strpos($remote_addr, $safe_ip) === 0) {
+                        return;
+                    }
+                }
+            }
+        }
+
+        return PEAR::raiseError(_("The encryption features require a secure web connection."));
+    }
+
+    /**
+     * Encrypt the requested data.
+     * This method should be provided by all classes that extend Horde_Crypt.
+     *
+     * @param string $data   The data to encrypt.
+     * @param array $params  An array of arguments needed to encrypt the data.
+     *
+     * @return array  The encrypted data.
+     */
+    public function encrypt($data, $params = array())
+    {
+        return $data;
+    }
+
+    /**
+     * Decrypt the requested data.
+     * This method should be provided by all classes that extend Horde_Crypt.
+     *
+     * @param string $data   The data to decrypt.
+     * @param array $params  An array of arguments needed to decrypt the data.
+     *
+     * @return array  The decrypted data.
+     */
+    public function decrypt($data, $params = array())
+    {
+        return $data;
+    }
+
+    /**
+     * Create a temporary file that will be deleted at the end of this
+     * process.
+     *
+     * @access private
+     *
+     * @param string  $descrip  Description string to use in filename.
+     * @param boolean $delete   Delete the file automatically?
+     *
+     * @return string  Filename of a temporary file.
+     */
+    protected function _createTempFile($descrip = 'horde-crypt',
+                                       $delete = true)
+    {
+        return Util::getTempFile($descrip, $delete, $this->_tempdir, true);
+    }
+
+}
diff --git a/framework/Crypt/lib/Horde/Crypt/pgp.php b/framework/Crypt/lib/Horde/Crypt/pgp.php
new file mode 100644 (file)
index 0000000..870b5c4
--- /dev/null
@@ -0,0 +1,1705 @@
+<?php
+
+/**
+ * Horde_Crypt_pgp:: provides a framework for Horde applications to interact
+ * with the GNU Privacy Guard program ("GnuPG").  GnuPG implements the OpenPGP
+ * standard (RFC 2440).
+ *
+ * GnuPG Website: http://www.gnupg.org/
+ *
+ * This class has been developed with, and is only guaranteed to work with,
+ * Version 1.21 or above of GnuPG.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Crypt
+ */
+class Horde_Crypt_pgp extends Horde_Crypt
+{
+    /**
+     * Armor Header Lines - From RFC 2440:
+     *
+     * An Armor Header Line consists of the appropriate header line text
+     * surrounded by five (5) dashes ('-', 0x2D) on either side of the header
+     * line text. The header line text is chosen based upon the type of data
+     * that is being encoded in Armor, and how it is being encoded.
+     *
+     *  All Armor Header Lines are prefixed with 'PGP'.
+     *
+     *  The Armor Tail Line is composed in the same manner as the Armor Header
+     *  Line, except the string "BEGIN" is replaced by the string "END."
+     */
+
+    /* Used for signed, encrypted, or compressed files. */
+    const ARMOR_MESSAGE = 1;
+
+    /* Used for signed files. */
+    const ARMOR_SIGNED_MESSAGE = 2;
+
+    /* Used for armoring public keys. */
+    const ARMOR_PUBLIC_KEY = 3;
+
+    /* Used for armoring private keys. */
+    const ARMOR_PRIVATE_KEY = 4;
+
+    /* Used for detached signatures, PGP/MIME signatures, and natures
+     * following clearsigned messages. */
+    const ARMOR_SIGNATURE = 5;
+
+    /* Regular text contained in an PGP message. */
+    const ARMOR_TEXT = 6;
+
+    /**
+     * Strings in armor header lines used to distinguish between the different
+     * types of PGP decryption/encryption.
+     *
+     * @var array
+     */
+    protected $_armor = array(
+        'MESSAGE'            =>  self::ARMOR_MESSAGE,
+        'SIGNED MESSAGE'     =>  self::ARMOR_SIGNED_MESSAGE,
+        'PUBLIC KEY BLOCK'   =>  self::ARMOR_PUBLIC_KEY,
+        'PRIVATE KEY BLOCK'  =>  self::ARMOR_PRIVATE_KEY,
+        'SIGNATURE'          =>  self::ARMOR_SIGNATURE
+    );
+
+    /* The default public PGP keyserver to use. */
+    const KEYSERVER_PUBLIC = 'pgp.mit.edu';
+
+    /* The number of times the keyserver refuses connection before an error is
+     * returned. */
+    const KEYSERVER_REFUSE = 3;
+
+    /* The number of seconds that PHP will attempt to connect to the keyserver
+     * before it will stop processing the request. */
+    const KEYSERVER_TIMEOUT = 10;
+
+    /**
+     * The list of PGP hash algorithms (from RFC 3156).
+     *
+     * @var array
+     */
+    protected $_hashAlg = array(
+        1  =>  'pgp-md5',
+        2  =>  'pgp-sha1',
+        3  =>  'pgp-ripemd160',
+        5  =>  'pgp-md2',
+        6  =>  'pgp-tiger192',
+        7  =>  'pgp-haval-5-160',
+        8  =>  'pgp-sha256',
+        9  =>  'pgp-sha384',
+        10 =>  'pgp-sha512',
+        11 =>  'pgp-sha224',
+    );
+
+    /**
+     * GnuPG program location/common options.
+     *
+     * @var array
+     */
+    protected $_gnupg;
+
+    /**
+     * Filename of the temporary public keyring.
+     *
+     * @var string
+     */
+    protected $_publicKeyring;
+
+    /**
+     * Filename of the temporary private keyring.
+     *
+     * @var string
+     */
+    protected $_privateKeyring;
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Parameter array containing the path to the GnuPG
+     *                       binary (key = 'program') and to a temporary
+     *                       directory.
+     */
+    function __construct($params = array())
+    {
+        $this->_tempdir = Util::createTempDir(true, $params['temp']);
+
+        if (empty($params['program'])) {
+            Horde::fatal(PEAR::raiseError('The location of the GnuPG binary must be given to the Horde_Crypt_pgp:: class.'), __FILE__, __LINE__);
+        }
+
+        /* Store the location of GnuPG and set common options. */
+        $this->_gnupg = array(
+            $params['program'],
+            '--no-tty',
+            '--no-secmem-warning',
+            '--no-options',
+            '--no-default-keyring',
+            '--yes',
+            '--homedir ' . $this->_tempdir
+        );
+
+        if (strncasecmp(PHP_OS, 'WIN', 3)) {
+            array_unshift($this->_gnupg, 'LANG= ;');
+        }
+    }
+
+    /**
+     * Generates a personal Public/Private keypair combination.
+     *
+     * @param string $realname    The name to use for the key.
+     * @param string $email       The email to use for the key.
+     * @param string $passphrase  The passphrase to use for the key.
+     * @param string $comment     The comment to use for the key.
+     * @param integer $keylength  The keylength to use for the key.
+     *
+     * @return array  An array consisting of the public key and the private
+     *                key, or PEAR_Error on error.
+     * <pre>
+     * Return array:
+     * Key            Value
+     * --------------------------
+     * 'public'   =>  Public Key
+     * 'private'  =>  Private Key
+     * </pre>
+     */
+    public function generateKey($realname, $email, $passphrase, $comment = '',
+                                $keylength = 1024)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Create temp files to hold the generated keys. */
+        $pub_file = $this->_createTempFile('horde-pgp');
+        $secret_file = $this->_createTempFile('horde-pgp');
+
+        /* Create the config file necessary for GnuPG to run in batch mode. */
+        /* TODO: Sanitize input, More user customizable? */
+        $input = array();
+        $input[] = '%pubring ' . $pub_file;
+        $input[] = '%secring ' . $secret_file;
+        $input[] = 'Key-Type: DSA';
+        $input[] = 'Key-Length: 1024';
+        $input[] = 'Subkey-Type: ELG-E';
+        $input[] = 'Subkey-Length: ' . $keylength;
+        $input[] = 'Name-Real: ' . $realname;
+        if (!empty($comment)) {
+            $input[] = 'Name-Comment: ' . $comment;
+        }
+        $input[] = 'Name-Email: ' . $email;
+        $input[] = 'Expire-Date: 0';
+        $input[] = 'Passphrase: ' . $passphrase;
+        $input[] = '%commit';
+
+        /* Run through gpg binary. */
+        $cmdline = array(
+            '--gen-key',
+            '--batch',
+            '--armor'
+        );
+        $result = $this->_callGpg($cmdline, 'w', $input, true, true);
+
+        /* Get the keys from the temp files. */
+        $public_key = file($pub_file);
+        $secret_key = file($secret_file);
+
+        /* If either key is empty, something went wrong. */
+        if (empty($public_key) || empty($secret_key)) {
+            $msg = _("Public/Private keypair not generated successfully.");
+            if (!empty($result->stderr)) {
+                $msg .= ' ' . _("Returned error message:") . ' ' . $result->stderr;
+            }
+            return PEAR::raiseError($msg, 'horde.error');
+        }
+
+        return array('public' => $public_key, 'private' => $secret_key);
+    }
+
+    /**
+     * Returns information on a PGP data block.
+     *
+     * @param string $pgpdata  The PGP data block.
+     *
+     * @return array  An array with information on the PGP data block. If an
+     *                element is not present in the data block, it will
+     *                likewise not be set in the array.
+     * <pre>
+     * Array Format:
+     * -------------
+     * [public_key]/[secret_key] => Array
+     *   (
+     *     [created] => Key creation - UNIX timestamp
+     *     [expires] => Key expiration - UNIX timestamp (0 = never expires)
+     *     [size]    => Size of the key in bits
+     *   )
+     *
+     * [fingerprint] => Fingerprint of the PGP data (if available)
+     *                  16-bit hex value (DEPRECATED)
+     * [keyid] => Key ID of the PGP data (if available)
+     *            16-bit hex value (as of Horde 3.2)
+     *
+     * [signature] => Array (
+     *     [id{n}/'_SIGNATURE'] => Array (
+     *         [name]        => Full Name
+     *         [comment]     => Comment
+     *         [email]       => E-mail Address
+     *         [fingerprint] => 16-bit hex value (DEPRECATED)
+     *         [keyid]       => 16-bit hex value (as of Horde 3.2)
+     *         [created]     => Signature creation - UNIX timestamp
+     *         [expires]     => Signature expiration - UNIX timestamp
+     *         [micalg]      => The hash used to create the signature
+     *         [sig_{hex}]   => Array [details of a sig verifying the ID] (
+     *             [created]     => Signature creation - UNIX timestamp
+     *             [expires]     => Signature expiration - UNIX timestamp
+     *             [fingerprint] => 16-bit hex value (DEPRECATED)
+     *             [keyid]       => 16-bit hex value (as of Horde 3.2)
+     *             [micalg]      => The hash used to create the signature
+     *         )
+     *     )
+     * )
+     * </pre>
+     *
+     * Each user ID will be stored in the array 'signature' and have data
+     * associated with it, including an array for information on each
+     * signature that has signed that UID. Signatures not associated with a
+     * UID (e.g. revocation signatures and sub keys) will be stored under the
+     * special keyword '_SIGNATURE'.
+     */
+    public function pgpPacketInformation($pgpdata)
+    {
+        $data_array = array();
+        $keyid = '';
+        $header = null;
+        $input = $this->_createTempFile('horde-pgp');
+        $sig_id = $uid_idx = 0;
+
+        /* Store message in temporary file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $pgpdata);
+        fclose($fp);
+
+        $cmdline = array(
+            '--list-packets',
+            $input
+        );
+        $result = $this->_callGpg($cmdline, 'r');
+
+        foreach (explode("\n", $result->stdout) as $line) {
+            /* Headers are prefaced with a ':' as the first character on the
+               line. */
+            if (strpos($line, ':') === 0) {
+                $lowerLine = String::lower($line);
+
+                /* If we have a key (rather than a signature block), get the
+                   key's ID */
+                if (strpos($lowerLine, ':public key packet:') !== false ||
+                    strpos($lowerLine, ':secret key packet:') !== false) {
+                    $cmdline = array(
+                        '--with-colons',
+                        $input
+                    );
+                    $data = $this->_callGpg($cmdline, 'r');
+                    if (preg_match("/(sec|pub):.*:.*:.*:([A-F0-9]{16}):/", $data->stdout, $matches)) {
+                        $keyid = $matches[2];
+                    }
+                }
+
+                if (strpos($lowerLine, ':public key packet:') !== false) {
+                    $header = 'public_key';
+                } elseif (strpos($lowerLine, ':secret key packet:') !== false) {
+                    $header = 'secret_key';
+                } elseif (strpos($lowerLine, ':user id packet:') !== false) {
+                    $uid_idx++;
+                    $line = preg_replace_callback('/\\\\x([0-9a-f]{2})/', array($this, '_pgpPacketInformationHelper'), $line);
+                    if (preg_match("/\"([^\<]+)\<([^\>]+)\>\"/", $line, $matches)) {
+                        $header = 'id' . $uid_idx;
+                        if (preg_match('/([^\(]+)\((.+)\)$/', trim($matches[1]), $comment_matches)) {
+                            $data_array['signature'][$header]['name'] = trim($comment_matches[1]);
+                            $data_array['signature'][$header]['comment'] = $comment_matches[2];
+                        } else {
+                            $data_array['signature'][$header]['name'] = trim($matches[1]);
+                            $data_array['signature'][$header]['comment'] = '';
+                        }
+                        $data_array['signature'][$header]['email'] = $matches[2];
+                        $data_array['signature'][$header]['keyid'] = $keyid;
+                        // TODO: Remove in Horde 4
+                        $data_array['signature'][$header]['fingerprint'] = $keyid;
+                    }
+                } elseif (strpos($lowerLine, ':signature packet:') !== false) {
+                    if (empty($header) || empty($uid_idx)) {
+                        $header = '_SIGNATURE';
+                    }
+                    if (preg_match("/keyid\s+([0-9A-F]+)/i", $line, $matches)) {
+                        $sig_id = $matches[1];
+                        $data_array['signature'][$header]['sig_' . $sig_id]['keyid'] = $matches[1];
+                        $data_array['keyid'] = $matches[1];
+                        // TODO: Remove these 2 entries in Horde 4
+                        $data_array['signature'][$header]['sig_' . $sig_id]['fingerprint'] = $matches[1];
+                        $data_array['fingerprint'] = $matches[1];
+                    }
+                } elseif (strpos($lowerLine, ':literal data packet:') !== false) {
+                    $header = 'literal';
+                } elseif (strpos($lowerLine, ':encrypted data packet:') !== false) {
+                    $header = 'encrypted';
+                } else {
+                    $header = null;
+                }
+            } else {
+                if ($header == 'secret_key' || $header == 'public_key') {
+                    if (preg_match("/created\s+(\d+),\s+expires\s+(\d+)/i", $line, $matches)) {
+                        $data_array[$header]['created'] = $matches[1];
+                        $data_array[$header]['expires'] = $matches[2];
+                    } elseif (preg_match("/\s+[sp]key\[0\]:\s+\[(\d+)/i", $line, $matches)) {
+                        $data_array[$header]['size'] = $matches[1];
+                    }
+                } elseif ($header == 'literal' || $header == 'encrypted') {
+                    $data_array[$header] = true;
+                } elseif ($header) {
+                    if (preg_match("/version\s+\d+,\s+created\s+(\d+)/i", $line, $matches)) {
+                        $data_array['signature'][$header]['sig_' . $sig_id]['created'] = $matches[1];
+                    } elseif (isset($data_array['signature'][$header]['sig_' . $sig_id]['created']) &&
+                              preg_match('/expires after (\d+y\d+d\d+h\d+m)\)$/', $line, $matches)) {
+                        $expires = $matches[1];
+                        preg_match('/^(\d+)y(\d+)d(\d+)h(\d+)m$/', $expires, $matches);
+                        list(, $years, $days, $hours, $minutes) = $matches;
+                        $data_array['signature'][$header]['sig_' . $sig_id]['expires'] =
+                            strtotime('+ ' . $years . ' years + ' . $days . ' days + ' . $hours . ' hours + ' . $minutes . ' minutes', $data_array['signature'][$header]['sig_' . $sig_id]['created']);
+                    } elseif (preg_match("/digest algo\s+(\d{1})/", $line, $matches)) {
+                        $micalg = $this->_hashAlg[$matches[1]];
+                        $data_array['signature'][$header]['sig_' . $sig_id]['micalg'] = $micalg;
+                        if ($header == '_SIGNATURE') {
+                            /* Likely a signature block, not a key. */
+                            $data_array['signature']['_SIGNATURE']['micalg'] = $micalg;
+                        }
+                        if ($sig_id == $keyid) {
+                            /* Self signing signature - we can assume
+                             * the micalg value from this signature is
+                             * that for the key */
+                            $data_array['signature']['_SIGNATURE']['micalg'] = $micalg;
+                            $data_array['signature'][$header]['micalg'] = $micalg;
+                        }
+                    }
+                }
+            }
+        }
+
+        $keyid && $data_array['keyid'] = $keyid;
+        // TODO: Remove for Horde 4
+        $keyid && $data_array['fingerprint'] = $keyid;
+
+        return $data_array;
+    }
+
+    protected function _pgpPacketInformationHelper($a)
+    {
+        return chr(hexdec($a[1]));
+    }
+
+    /**
+     * Returns human readable information on a PGP key.
+     *
+     * @param string $pgpdata  The PGP data block.
+     *
+     * @return string  Tabular information on the PGP key.
+     */
+    public function pgpPrettyKey($pgpdata)
+    {
+        $msg = '';
+        $packet_info = $this->pgpPacketInformation($pgpdata);
+        $fingerprints = $this->getFingerprintsFromKey($pgpdata);
+
+        if (!empty($packet_info['signature'])) {
+            /* Making the property names the same width for all
+             * localizations .*/
+            $leftrow = array(_("Name"), _("Key Type"), _("Key Creation"),
+                             _("Expiration Date"), _("Key Length"),
+                             _("Comment"), _("E-Mail"), _("Hash-Algorithm"),
+                             _("Key ID"), _("Key Fingerprint"));
+            $leftwidth = array_map('strlen', $leftrow);
+            $maxwidth  = max($leftwidth) + 2;
+            array_walk($leftrow, array($this, '_pgpPrettyKeyFormatter'), $maxwidth);
+
+            foreach (array_keys($packet_info['signature']) as $uid_idx) {
+                if ($uid_idx == '_SIGNATURE') {
+                    continue;
+                }
+                $key_info = $this->pgpPacketSignatureByUidIndex($pgpdata, $uid_idx);
+
+                if (!empty($key_info['keyid'])) {
+                    $key_info['keyid'] = $this->_getKeyIDString($key_info['keyid']);
+                } else {
+                    $key_info['keyid'] = null;
+                }
+
+                $fingerprint = isset($fingerprints[$key_info['keyid']]) ? $fingerprints[$key_info['keyid']] : null;
+
+                $msg .= $leftrow[0] . (isset($key_info['name']) ? stripcslashes($key_info['name']) : '') . "\n"
+                    . $leftrow[1] . (($key_info['key_type'] == 'public_key') ? _("Public Key") : _("Private Key")) . "\n"
+                    . $leftrow[2] . strftime("%D", $key_info['key_created']) . "\n"
+                    . $leftrow[3] . (empty($key_info['key_expires']) ? '[' . _("Never") . ']' : strftime("%D", $key_info['key_expires'])) . "\n"
+                    . $leftrow[4] . $key_info['key_size'] . " Bytes\n"
+                    . $leftrow[5] . (empty($key_info['comment']) ? '[' . _("None") . ']' : $key_info['comment']) . "\n"
+                    . $leftrow[6] . (empty($key_info['email']) ? '[' . _("None") . ']' : $key_info['email']) . "\n"
+                    . $leftrow[7] . (empty($key_info['micalg']) ? '[' . _("Unknown") . ']' : $key_info['micalg']) . "\n"
+                    . $leftrow[8] . (empty($key_info['keyid']) ? '[' . _("Unknown") . ']' : $key_info['keyid']) . "\n"
+                    . $leftrow[9] . (empty($fingerprint) ? '[' . _("Unknown") . ']' : $fingerprint) . "\n\n";
+            }
+        }
+
+        return $msg;
+    }
+
+    protected function _pgpPrettyKeyFormatter(&$s, $k, $m)
+    {
+        $s .= ':' . str_repeat(' ', $m - String::length($s));
+    }
+
+    protected function _getKeyIDString($keyid)
+    {
+        /* Get the 8 character key ID string. */
+        if (strpos($keyid, '0x') === 0) {
+            $keyid = substr($keyid, 2);
+        }
+        if (strlen($keyid) > 8) {
+            $keyid = substr($keyid, -8);
+        }
+        return '0x' . $keyid;
+    }
+
+    /**
+     * Returns only information on the first ID that matches the email address
+     * input.
+     *
+     * @param string $pgpdata  The PGP data block.
+     * @param string $email    An e-mail address.
+     *
+     * @return array  An array with information on the PGP data block. If an
+     *                element is not present in the data block, it will
+     *                likewise not be set in the array.
+     * <pre>
+     * Array Fields:
+     * -------------
+     * key_created  =>  Key creation - UNIX timestamp
+     * key_expires  =>  Key expiration - UNIX timestamp (0 = never expires)
+     * key_size     =>  Size of the key in bits
+     * key_type     =>  The key type (public_key or secret_key)
+     * name         =>  Full Name
+     * comment      =>  Comment
+     * email        =>  E-mail Address
+     * fingerprint  =>  16-bit hex value (DEPRECATED)
+     * keyid        =>  16-bit hex value
+     * created      =>  Signature creation - UNIX timestamp
+     * micalg       =>  The hash used to create the signature
+     * </pre>
+     */
+    public function pgpPacketSignature($pgpdata, $email)
+    {
+        $data = $this->pgpPacketInformation($pgpdata);
+        $key_type = null;
+        $return_array = array();
+
+        /* Check that [signature] key exists. */
+        if (!isset($data['signature'])) {
+            return $return_array;
+        }
+
+        /* Store the signature information now. */
+        if (($email == '_SIGNATURE') &&
+            isset($data['signature']['_SIGNATURE'])) {
+            foreach ($data['signature'][$email] as $key => $value) {
+                $return_array[$key] = $value;
+            }
+        } else {
+            $uid_idx = 1;
+
+            while (isset($data['signature']['id' . $uid_idx])) {
+                if ($data['signature']['id' . $uid_idx]['email'] == $email) {
+                    foreach ($data['signature']['id' . $uid_idx] as $key => $val) {
+                        $return_array[$key] = $val;
+                    }
+                    break;
+                }
+                $uid_idx++;
+            }
+        }
+
+        return $this->_pgpPacketSignature($data, $return_array);
+    }
+
+    /**
+     * Returns information on a PGP signature embedded in PGP data.  Similar
+     * to pgpPacketSignature(), but returns information by unique User ID
+     * Index (format id{n} where n is an integer of 1 or greater).
+     *
+     * @param string $pgpdata  See pgpPacketSignature().
+     * @param string $uid_idx  The UID index.
+     *
+     * @return array  See pgpPacketSignature().
+     */
+    public function pgpPacketSignatureByUidIndex($pgpdata, $uid_idx)
+    {
+        $data = $this->pgpPacketInformation($pgpdata);
+        $key_type = null;
+        $return_array = array();
+
+        /* Search for the UID index. */
+        if (!isset($data['signature']) ||
+            !isset($data['signature'][$uid_idx])) {
+            return $return_array;
+        }
+
+        /* Store the signature information now. */
+        foreach ($data['signature'][$uid_idx] as $key => $value) {
+            $return_array[$key] = $value;
+        }
+
+        return $this->_pgpPacketSignature($data, $return_array);
+    }
+
+    /**
+     * Adds some data to the pgpPacketSignature*() function array.
+     *
+     * @param array $data      See pgpPacketSignature().
+     * @param array $retarray  The return array.
+     *
+     * @return array  The return array.
+     */
+    protected function _pgpPacketSignature($data, $retarray)
+    {
+        /* If empty, return now. */
+        if (empty($retarray)) {
+            return $retarray;
+        }
+
+        $key_type = null;
+
+        /* Store any public/private key information. */
+        if (isset($data['public_key'])) {
+            $key_type = 'public_key';
+        } elseif (isset($data['secret_key'])) {
+            $key_type = 'secret_key';
+        }
+
+        if ($key_type) {
+            $retarray['key_type'] = $key_type;
+            if (isset($data[$key_type]['created'])) {
+                $retarray['key_created'] = $data[$key_type]['created'];
+            }
+            if (isset($data[$key_type]['expires'])) {
+                $retarray['key_expires'] = $data[$key_type]['expires'];
+            }
+            if (isset($data[$key_type]['size'])) {
+                $retarray['key_size'] = $data[$key_type]['size'];
+            }
+        }
+
+        return $retarray;
+    }
+
+    /**
+     * Returns the key ID of the key used to sign a block of PGP data.
+     *
+     * @param string $text  The PGP signed text block.
+     *
+     * @return string  The key ID of the key used to sign $text.
+     */
+    public function getSignersKeyID($text)
+    {
+        $keyid = null;
+
+        $input = $this->_createTempFile('horde-pgp');
+
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        $cmdline = array(
+            '--verify',
+            $input
+        );
+        $result = $this->_callGpg($cmdline, 'r', null, true, true);
+        if (preg_match('/gpg:\sSignature\smade.*ID\s+([A-F0-9]{8})\s+/', $result->stderr, $matches)) {
+            $keyid = $matches[1];
+        }
+
+        return $keyid;
+    }
+
+    /**
+     * Verify a passphrase for a given public/private keypair.
+     *
+     * @param string $public_key   The user's PGP public key.
+     * @param string $private_key  The user's PGP private key.
+     * @param string $passphrase   The user's passphrase.
+     *
+     * @return boolean  Returns true on valid passphrase, false on invalid
+     *                  passphrase, and PEAR_Error on error.
+     */
+    public function verifyPassphrase($public_key, $private_key, $passphrase)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Encrypt a test message. */
+        $result = $this->encrypt('Test', array('type' => 'message', 'pubkey' => $public_key));
+        if (is_a($result, 'PEAR_Error')) {
+            return false;
+        }
+
+        /* Try to decrypt the message. */
+        $result = $this->decrypt($result, array('type' => 'message', 'pubkey' => $public_key, 'privkey' => $private_key, 'passphrase' => $passphrase));
+        if (is_a($result, 'PEAR_Error')) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Parses a message into text and PGP components.
+     *
+     * @param string $text  The text to parse.
+     *
+     * @return array  An array with the parsed text, returned in blocks of
+     *                text corresponding to their actual order.
+     * <pre>
+     * Return array:
+     * Key         Value
+     * -------------------------------------------------
+     * 'type'  =>  The type of data contained in block.
+     *             Valid types are defined at the top of this class
+     *             (the ARMOR_* constants).
+     * 'data'  =>  The actual data for each section.
+     * </pre>
+     */
+    public function parsePGPData($text)
+    {
+        $data = array();
+
+        $buffer = explode("\n", $text);
+
+        /* Set $temp_array to be of type ARMOR_TEXT. */
+        $temp_array = array();
+        $temp_array['type'] = self::ARMOR_TEXT;
+
+        foreach ($buffer as $value) {
+            if (preg_match('/^-----(BEGIN|END) PGP ([^-]+)-----\s*$/', $value, $matches)) {
+                if (isset($temp_array['data'])) {
+                    $data[] = $temp_array;
+                }
+                unset($temp_array);
+                $temp_array = array();
+
+                if ($matches[1] === 'BEGIN') {
+                    $temp_array['type'] = $this->_armor[$matches[2]];
+                    $temp_array['data'][] = $value;
+                } elseif ($matches[1] === 'END') {
+                    $temp_array['type'] = self::ARMOR_TEXT;
+                    $data[count($data) - 1]['data'][] = $value;
+                }
+            } else {
+                $temp_array['data'][] = $value;
+            }
+        }
+
+        if (isset($temp_array['data'])) {
+            $data[] = $temp_array;
+        }
+
+        return $data;
+    }
+
+    /**
+     * Returns a PGP public key from a public keyserver.
+     *
+     * @param string $keyid    The key ID of the PGP key.
+     * @param string $server   The keyserver to use.
+     * @param float $timeout   The keyserver timeout.
+     * @param string $address  The email address of the PGP key.
+     *
+     * @return string  The PGP public key, or PEAR_Error on error.
+     */
+    public function getPublicKeyserver($keyid,
+                                       $server = self::KEYSERVER_PUBLIC,
+                                       $timeout = self::KEYSERVER_TIMEOUT,
+                                       $address = null)
+    {
+        if (empty($keyid) && !empty($address)) {
+            $keyid = $this->getKeyID($address, $server, $timeout);
+            if (is_a($keyid, 'PEAR_Error')) {
+                return $keyid;
+            }
+        }
+
+        /* Connect to the public keyserver. */
+        $uri = '/pks/lookup?op=get&search=' . $this->_getKeyIDString($keyid);
+        $output = $this->_connectKeyserver('GET', $server, $uri, '', $timeout);
+        if (is_a($output, 'PEAR_Error')) {
+            return $output;
+        }
+
+        /* Strip HTML Tags from output. */
+        if (($start = strstr($output, '-----BEGIN'))) {
+            $length = strpos($start, '-----END') + 34;
+            return substr($start, 0, $length);
+        } else {
+            return PEAR::raiseError(_("Could not obtain public key from the keyserver."), 'horde.error');
+        }
+    }
+
+    /**
+     * Sends a PGP public key to a public keyserver.
+     *
+     * @param string $pubkey  The PGP public key
+     * @param string $server  The keyserver to use.
+     * @param float $timeout  The keyserver timeout.
+     *
+     * @return PEAR_Error  PEAR_Error on error/failure.
+     */
+    public function putPublicKeyserver($pubkey,
+                                       $server = self::KEYSERVER_PUBLIC,
+                                       $timeout = self::KEYSERVER_TIMEOUT)
+    {
+        /* Get the key ID of the public key. */
+        $info = $this->pgpPacketInformation($pubkey);
+
+        /* See if the public key already exists on the keyserver. */
+        if (!is_a($this->getPublicKeyserver($info['keyid'], $server, $timeout), 'PEAR_Error')) {
+            return PEAR::raiseError(_("Key already exists on the public keyserver."), 'horde.warning');
+        }
+
+        /* Connect to the public keyserver. _connectKeyserver()
+         * returns a PEAR_Error object on error and the output text on
+         * success. */
+        $pubkey = 'keytext=' . urlencode(rtrim($pubkey));
+        $cmd = array(
+            'Host: ' . $server . ':11371',
+            'User-Agent: Horde Application Framework 3.2',
+            'Content-Type: application/x-www-form-urlencoded',
+            'Content-Length: ' . strlen($pubkey),
+            'Connection: close',
+            '',
+            $pubkey
+        );
+
+        $result = $this->_connectKeyserver('POST', $server, '/pks/add', implode("\r\n", $cmd), $timeout);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+    }
+
+    /**
+     * Returns the first matching key ID for an email address from a
+     * public keyserver.
+     *
+     * @param string $address  The email address of the PGP key.
+     * @param string $server   The keyserver to use.
+     * @param float $timeout   The keyserver timeout.
+     *
+     * @return string  The PGP key ID, or PEAR_Error on error.
+     */
+    public function getKeyID($address, $server = self::KEYSERVER_PUBLIC,
+                      $timeout = self::KEYSERVER_TIMEOUT)
+    {
+        /* Connect to the public keyserver. */
+        $uri = '/pks/lookup?op=index&options=mr&search=' . urlencode($address);
+        $output = $this->_connectKeyserver('GET', $server, $uri, '', $timeout);
+        if (is_a($output, 'PEAR_Error')) {
+            return $output;
+        }
+
+        if (($start = strstr($output, '-----BEGIN PGP PUBLIC KEY BLOCK'))) {
+            /* The server returned the matching key immediately. */
+            $length = strpos($start, '-----END PGP PUBLIC KEY BLOCK') + 34;
+            $info = $this->pgpPacketInformation(substr($start, 0, $length));
+            if (!empty($info['keyid']) &&
+                (empty($info['public_key']['expires']) ||
+                 $info['public_key']['expires'] > time())) {
+                return $info['keyid'];
+            }
+        } elseif (strpos($output, 'pub:') !== false) {
+            $output = explode("\n", $output);
+            $keyids = array();
+            foreach ($output as $line) {
+                if (substr($line, 0, 4) == 'pub:') {
+                    $line = explode(':', $line);
+                    /* Ignore invalid lines and expired keys. */
+                    if (count($line) != 7 ||
+                        (!empty($line[5]) && $line[5] <= time())) {
+                        continue;
+                    }
+                    $keyids[$line[4]] = $line[1];
+                }
+            }
+            /* Sort by timestamp to use the newest key. */
+            if (count($keyids)) {
+                ksort($keyids);
+                return array_pop($keyids);
+            }
+        }
+
+        return PEAR::raiseError(_("Could not obtain public key from the keyserver."));
+    }
+
+    /**
+     * Get the fingerprints from a key block.
+     *
+     * @param string $pgpdata  The PGP data block.
+     *
+     * @return array The fingerprints in $pgpdata indexed by key id.
+     */
+    public function getFingerprintsFromKey($pgpdata)
+    {
+        $fingerprints = array();
+
+        /* Store the key in a temporary keyring. */
+        $keyring = $this->_putInKeyring($pgpdata);
+
+        /* Options for the GPG binary. */
+        $cmdline = array(
+            '--fingerprint',
+            $keyring,
+        );
+
+        $result = $this->_callGpg($cmdline, 'r');
+        if (!$result || !$result->stdout) {
+            return $fingerprints;
+        }
+
+        /* Parse fingerprints and key ids from output. */
+        $lines = explode("\n", $result->stdout);
+        $keyid = null;
+        foreach ($lines as $line) {
+            if (preg_match('/pub\s+\w+\/(\w{8})/', $line, $matches)) {
+                $keyid = '0x' . $matches[1];
+            } elseif ($keyid && preg_match('/^\s+[\s\w]+=\s*([\w\s]+)$/m', $line, $matches)) {
+                $fingerprints[$keyid] = trim($matches[1]);
+                $keyid = null;
+            }
+        }
+
+        return $fingerprints;
+    }
+
+    /**
+     * Connects to a public key server via HKP (Horrowitz Keyserver Protocol).
+     *
+     * @param string $method   POST, GET, etc.
+     * @param string $server   The keyserver to use.
+     * @param string $uri      The URI to access (relative to the server).
+     * @param string $command  The PGP command to run.
+     * @param float $timeout   The timeout value.
+     *
+     * @return string  The text from standard output on success, or PEAR_Error
+     *                 on error/failure.
+     */
+    protected function _connectKeyserver($method, $server, $resource,
+                                         $command, $timeout)
+    {
+        $connRefuse = 0;
+        $output = '';
+
+        $port = '11371';
+        if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) {
+            $resource = 'http://' . $server . ':' . $port . $resource;
+
+            $server = $GLOBALS['conf']['http']['proxy']['proxy_host'];
+            if (!empty($GLOBALS['conf']['http']['proxy']['proxy_port'])) {
+                $port = $GLOBALS['conf']['http']['proxy']['proxy_port'];
+            } else {
+                $port = 80;
+            }
+        }
+
+        $command = $method . ' ' . $resource . ' HTTP/1.0' . ($command ? "\r\n" . $command : '');
+
+        /* Attempt to get the key from the keyserver. */
+        do {
+            $connError = false;
+            $errno = $errstr = null;
+
+            /* The HKP server is located on port 11371. */
+            $fp = @fsockopen($server, $port, $errno, $errstr, $timeout);
+            if (!$fp) {
+                $connError = true;
+            } else {
+                fputs($fp, $command . "\n\n");
+                while (!feof($fp)) {
+                    $output .= fgets($fp, 1024);
+                }
+                fclose($fp);
+            }
+
+            if ($connError) {
+                if (++$connRefuse === self::KEYSERVER_REFUSE) {
+                    if ($errno == 0) {
+                        $output = PEAR::raiseError(_("Connection refused to the public keyserver."), 'horde.error');
+                    } else {
+                        $output = PEAR::raiseError(sprintf(_("Connection refused to the public keyserver. Reason: %s (%s)"), String::convertCharset($errstr, NLS::getExternalCharset()), $errno), 'horde.error');
+                    }
+                    break;
+                }
+            }
+        } while ($connError);
+
+        return $output;
+    }
+
+    /**
+     * Encrypts text using PGP.
+     *
+     * @param string $text   The text to be PGP encrypted.
+     * @param array $params  The parameters needed for encryption.
+     *                       See the individual _encrypt*() functions for the
+     *                       parameter requirements.
+     *
+     * @return string  The encrypted message, or PEAR_Error on error.
+     */
+    public function encrypt($text, $params = array())
+    {
+        if (isset($params['type'])) {
+            if ($params['type'] === 'message') {
+                return $this->_encryptMessage($text, $params);
+            } elseif ($params['type'] === 'signature') {
+                return $this->_encryptSignature($text, $params);
+            }
+        }
+    }
+
+    /**
+     * Decrypts text using PGP.
+     *
+     * @param string $text   The text to be PGP decrypted.
+     * @param array $params  The parameters needed for decryption.
+     *                       See the individual _decrypt*() functions for the
+     *                       parameter requirements.
+     *
+     * @return string  The decrypted message, or PEAR_Error on error.
+     */
+    public function decrypt($text, $params = array())
+    {
+        if (isset($params['type'])) {
+            if ($params['type'] === 'message') {
+                return $this->_decryptMessage($text, $params);
+            } elseif (($params['type'] === 'signature') ||
+                      ($params['type'] === 'detached-signature')) {
+                return $this->_decryptSignature($text, $params);
+            }
+        }
+    }
+
+    /**
+     * Returns whether a text has been encrypted symmetrically.
+     *
+     * @param string $text  The PGP encrypted text.
+     *
+     * @return boolean  True if the text is symmetricallly encrypted.
+     */
+    public function encryptedSymmetrically($text)
+    {
+        $cmdline = array(
+            '--decrypt',
+            '--batch'
+        );
+        $result = $this->_callGpg($cmdline, 'w', $text, true, true, true);
+        return strpos($result->stderr, 'gpg: encrypted with 1 passphrase') !== false;
+    }
+
+    /**
+     * Creates a temporary gpg keyring.
+     *
+     * @param string $type  The type of key to analyze. Either 'public'
+     *                      (Default) or 'private'
+     *
+     * @return string  Command line keystring option to use with gpg program.
+     */
+    protected function _createKeyring($type = 'public')
+    {
+        $type = String::lower($type);
+
+        if ($type === 'public') {
+            if (empty($this->_publicKeyring)) {
+                $this->_publicKeyring = $this->_createTempFile('horde-pgp');
+            }
+            return '--keyring ' . $this->_publicKeyring;
+        } elseif ($type === 'private') {
+            if (empty($this->_privateKeyring)) {
+                $this->_privateKeyring = $this->_createTempFile('horde-pgp');
+            }
+            return '--secret-keyring ' . $this->_privateKeyring;
+        }
+    }
+
+    /**
+     * Adds PGP keys to the keyring.
+     *
+     * @param mixed $keys   A single key or an array of key(s) to add to the
+     *                      keyring.
+     * @param string $type  The type of key(s) to add. Either 'public'
+     *                      (Default) or 'private'
+     *
+     * @return string  Command line keystring option to use with gpg program.
+     */
+    protected function _putInKeyring($keys = array(), $type = 'public')
+    {
+        $type = String::lower($type);
+
+        if (!is_array($keys)) {
+            $keys = array($keys);
+        }
+
+        /* Create the keyrings if they don't already exist. */
+        $keyring = $this->_createKeyring($type);
+
+        /* Store the key(s) in the keyring. */
+        $cmdline = array(
+            '--allow-secret-key-import',
+            '--fast-import',
+            $keyring
+        );
+        $this->_callGpg($cmdline, 'w', array_values($keys));
+
+        return $keyring;
+    }
+
+    /**
+     * Encrypts a message in PGP format using a public key.
+     *
+     * @param string $text   The text to be encrypted.
+     * @param array $params  The parameters needed for encryption.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'       => 'message' (REQUIRED)
+     * 'symmetric'  => Whether to use symmetric instead of asymmetric
+     *                 encryption (defaults to false)
+     * 'recips'     => An array with the e-mail address of the recipient as
+     *                 the key and that person's public key as the value.
+     *                 (REQUIRED if 'symmetric' is false)
+     * 'passphrase' => The passphrase for the symmetric encryption (REQUIRED if
+     *                 'symmetric' is true)
+     * 'pubkey'     => PGP public key. (Optional) (DEPRECATED)
+     * 'email'      => E-mail address of recipient. If not present, or not
+     *                 found in the public key, the first e-mail address found
+     *                 in the key will be used instead. (Optional) (DEPRECATED)
+     * </pre>
+     *
+     * @return string  The encrypted message, or PEAR_Error on error.
+     */
+    protected function _encryptMessage($text, $params)
+    {
+        $email = null;
+
+        if (empty($params['symmetric']) && !isset($params['recips'])) {
+            /* Check for required parameters. */
+            if (!isset($params['pubkey'])) {
+                return PEAR::raiseError(_("A public PGP key is required to encrypt a message."), 'horde.error');
+            }
+
+            /* Get information on the key. */
+            if (isset($params['email'])) {
+                $key_info = $this->pgpPacketSignature($params['pubkey'], $params['email']);
+                if (!empty($key_info)) {
+                    $email = $key_info['email'];
+                }
+            }
+
+            /* If we have no email address at this point, use the first email
+               address found in the public key. */
+            if (empty($email)) {
+                $key_info = $this->pgpPacketInformation($params['pubkey']);
+                if (isset($key_info['signature']['id1']['email'])) {
+                    $email = $key_info['signature']['id1']['email'];
+                } else {
+                    return PEAR::raiseError(_("Could not determine the recipient's e-mail address."), 'horde.error');
+                }
+            }
+
+            $params['recips'] = array($email => $params['pubkey']);
+        }
+
+        /* Create temp files for input. */
+        $input = $this->_createTempFile('horde-pgp');
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Build command line. */
+        $cmdline = array(
+            '--armor',
+            '--batch',
+            '--always-trust'
+        );
+        if (empty($params['symmetric'])) {
+            /* Store public key in temporary keyring. */
+            $keyring = $this->_putInKeyring(array_values($params['recips']));
+
+            $cmdline[] = $keyring;
+            $cmdline[] = '--encrypt';
+            foreach (array_keys($params['recips']) as $val) {
+                $cmdline[] = '--recipient ' . $val;
+            }
+        } else {
+            $cmdline[] = '--symmetric';
+            $cmdline[] = '--passphrase-fd 0';
+        }
+        $cmdline[] = $input;
+
+        /* Encrypt the document. */
+        $result = $this->_callGpg($cmdline, 'w', empty($params['symmetric']) ? null : $params['passphrase'], true, true);
+        if (empty($result->output)) {
+            $error = preg_replace('/\n.*/', '', $result->stderr);
+            return PEAR::raiseError(_("Could not PGP encrypt message: ") . $error, 'horde.error');
+        }
+
+        return $result->output;
+    }
+
+    /**
+     * Signs a message in PGP format using a private key.
+     *
+     * @param string $text   The text to be signed.
+     * @param array $params  The parameters needed for signing.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'        =>  'signature' (REQUIRED)
+     * 'pubkey'      =>  PGP public key. (REQUIRED)
+     * 'privkey'     =>  PGP private key. (REQUIRED)
+     * 'passphrase'  =>  Passphrase for PGP Key. (REQUIRED)
+     * 'sigtype'     =>  Determine the signature type to use. (Optional)
+     *                   'cleartext'  --  Make a clear text signature
+     *                   'detach'     --  Make a detached signature (DEFAULT)
+     * </pre>
+     *
+     * @return string  The signed message, or PEAR_Error on error.
+     */
+    protected function _encryptSignature($text, $params)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Check for required parameters. */
+        if (!isset($params['pubkey']) ||
+            !isset($params['privkey']) ||
+            !isset($params['passphrase'])) {
+            return PEAR::raiseError(_("A public PGP key, private PGP key, and passphrase are required to sign a message."), 'horde.error');
+        }
+
+        /* Create temp files for input. */
+        $input = $this->_createTempFile('horde-pgp');
+
+        /* Encryption requires both keyrings. */
+        $pub_keyring = $this->_putInKeyring(array($params['pubkey']));
+        $sec_keyring = $this->_putInKeyring(array($params['privkey']), 'private');
+
+        /* Store message in temporary file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Determine the signature type to use. */
+        $cmdline = array();
+        if (isset($params['sigtype']) &&
+            $params['sigtype'] == 'cleartext') {
+            $sign_type = '--clearsign';
+        } else {
+            $sign_type = '--detach-sign';
+        }
+
+        /* Additional GPG options. */
+        $cmdline += array(
+            '--armor',
+            '--batch',
+            '--passphrase-fd 0',
+            $sec_keyring,
+            $pub_keyring,
+            $sign_type,
+            $input
+        );
+
+        /* Sign the document. */
+        $result = $this->_callGpg($cmdline, 'w', $params['passphrase'], true, true);
+        if (empty($result->output)) {
+            $error = preg_replace('/\n.*/', '', $result->stderr);
+            return PEAR::raiseError(_("Could not PGP sign message: ") . $error, 'horde.error');
+        } else {
+            return $result->output;
+        }
+    }
+
+    /**
+     * Decrypts an PGP encrypted message using a private/public keypair and a
+     * passhprase.
+     *
+     * @param string $text   The text to be decrypted.
+     * @param array $params  The parameters needed for decryption.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'        =>  'message' (REQUIRED)
+     * 'pubkey'      =>  PGP public key. (REQUIRED for asymmetric encryption)
+     * 'privkey'     =>  PGP private key. (REQUIRED for asymmetric encryption)
+     * 'passphrase'  =>  Passphrase for PGP Key. (REQUIRED)
+     * </pre>
+     *
+     * @return stdClass  An object with the following properties, or PEAR_Error
+     *                   on error:
+     * <pre>
+     * 'message'     -  The decrypted message.
+     * 'sig_result'  -  The result of the signature test.
+     * </pre>
+     */
+    protected function _decryptMessage($text, $params)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        $good_sig_flag = false;
+
+        /* Check for required parameters. */
+        if (!isset($params['passphrase']) && empty($params['no_passphrase'])) {
+            return PEAR::raiseError(_("A passphrase is required to decrypt a message."), 'horde.error');
+        }
+
+        /* Create temp files. */
+        $input = $this->_createTempFile('horde-pgp');
+
+        /* Store message in file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Build command line. */
+        $cmdline = array(
+            '--always-trust',
+            '--armor',
+            '--batch'
+        );
+        if (empty($param['no_passphrase'])) {
+            $cmdline[] = '--passphrase-fd 0';
+        }
+        if (!empty($params['pubkey']) && !empty($params['privkey'])) {
+            /* Decryption requires both keyrings. */
+            $pub_keyring = $this->_putInKeyring(array($params['pubkey']));
+            $sec_keyring = $this->_putInKeyring(array($params['privkey']), 'private');
+            $cmdline[] = $sec_keyring;
+            $cmdline[] = $pub_keyring;
+        }
+        $cmdline[] = '--decrypt';
+        $cmdline[] = $input;
+
+        /* Decrypt the document now. */
+        if (empty($params['no_passphrase'])) {
+            $result = $this->_callGpg($cmdline, 'w', $params['passphrase'], true, true);
+        } else {
+            $result = $this->_callGpg($cmdline, 'r', null, true, true);
+        }
+        if (empty($result->output)) {
+            $error = preg_replace('/\n.*/', '', $result->stderr);
+            return PEAR::raiseError(_("Could not decrypt PGP data: ") . $error, 'horde.error');
+        }
+
+        /* Create the return object. */
+        $ob = new stdClass;
+        $ob->message = $result->output;
+
+        /* Check the PGP signature. */
+        $sig_check = $this->_checkSignatureResult($result->stderr);
+        if (is_a($sig_check, 'PEAR_Error')) {
+            $ob->sig_result = $sig_check;
+        } else {
+            $ob->sig_result = ($sig_check) ? $result->stderr : '';
+        }
+
+        return $ob;
+    }
+
+    /**
+     * Decrypts an PGP signed message using a public key.
+     *
+     * @param string $text   The text to be verified.
+     * @param array $params  The parameters needed for verification.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'       =>  'signature' or 'detached-signature' (REQUIRED)
+     * 'pubkey'     =>  PGP public key. (REQUIRED)
+     * 'signature'  =>  PGP signature block. (REQUIRED for detached signature)
+     * </pre>
+     *
+     * @return string  The verification message from gpg. If no signature,
+     *                 returns empty string, and PEAR_Error on error.
+     */
+    protected function _decryptSignature($text, $params)
+    {
+        /* Check for required parameters. */
+        if (!isset($params['pubkey'])) {
+            return PEAR::raiseError(_("A public PGP key is required to verify a signed message."), 'horde.error');
+        }
+        if (($params['type'] === 'detached-signature') &&
+            !isset($params['signature'])) {
+            return PEAR::raiseError(_("The detached PGP signature block is required to verify the signed message."), 'horde.error');
+        }
+
+        $good_sig_flag = 0;
+
+        /* Create temp files for input. */
+        $input = $this->_createTempFile('horde-pgp');
+
+        /* Store public key in temporary keyring. */
+        $keyring = $this->_putInKeyring($params['pubkey']);
+
+        /* Store the message in a temporary file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Options for the GPG binary. */
+        $cmdline = array(
+            '--armor',
+            '--always-trust',
+            '--batch',
+            '--charset ' . NLS::getCharset(),
+            $keyring,
+            '--verify'
+        );
+
+        /* Extra stuff to do if we are using a detached signature. */
+        if ($params['type'] === 'detached-signature') {
+            $sigfile = $this->_createTempFile('horde-pgp');
+            $cmdline[] = $sigfile . ' ' . $input;
+
+            $fp = fopen($sigfile, 'w+');
+            fputs($fp, $params['signature']);
+            fclose($fp);
+        } else {
+            $cmdline[] = $input;
+        }
+
+        /* Verify the signature.  We need to catch standard error output,
+         * since this is where the signature information is sent. */
+        $result = $this->_callGpg($cmdline, 'r', null, true, true);
+        $sig_result = $this->_checkSignatureResult($result->stderr);
+        if (is_a($sig_result, 'PEAR_Error')) {
+            return $sig_result;
+        } else {
+            return ($sig_result) ? $result->stderr : '';
+        }
+    }
+
+    /**
+     * Checks signature result from the GnuPG binary.
+     *
+     * @param string $result  The signature result.
+     *
+     * @return boolean  True if signature is good.
+     */
+    protected function _checkSignatureResult($result)
+    {
+        /* Good signature:
+         *   gpg: Good signature from "blah blah blah (Comment)"
+         * Bad signature:
+         *   gpg: BAD signature from "blah blah blah (Comment)" */
+        if (strpos($result, 'gpg: BAD signature') !== false) {
+            return PEAR::raiseError($result, 'horde.error');
+        } elseif (strpos($result, 'gpg: Good signature') !== false) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Signs a MIME part using PGP.
+     *
+     * @param Horde_Mime_Part $mime_part  The object to sign.
+     * @param array $params               The parameters required for signing.
+     *                                    @see _encryptSignature().
+     *
+     * @return mixed  A Horde_Mime_Part object that is signed according to RFC
+     *                3156, or PEAR_Error on error.
+     */
+    public function signMIMEPart($mime_part, $params = array())
+    {
+        $params = array_merge($params, array('type' => 'signature', 'sigtype' => 'detach'));
+
+        /* RFC 3156 Requirements for a PGP signed message:
+         * + Content-Type params 'micalg' & 'protocol' are REQUIRED.
+         * + The digitally signed message MUST be constrained to 7 bits.
+         * + The MIME headers MUST be a part of the signed data. */
+
+        $mime_part->strict7bit(true);
+        $msg_sign = $this->encrypt($mime_part->toCanonicalString(), $params);
+        if (is_a($msg_sign, 'PEAR_Error')) {
+            return $msg_sign;
+        }
+
+        /* Add the PGP signature. */
+        $charset = NLS::getEmailCharset();
+        $pgp_sign = new Horde_Mime_Part()
+        $pgp_sign->setType('application/pgp-signature');
+        $pgp_sign->setCharset($charset);
+        $pgp_sign->setDisposition('inline')
+        $pgp_sign->setDescription(String::convertCharset(_("PGP Digital Signature"), NLS::getCharset(), $charset));
+        $pgp_sign->setContents($msg_sign);
+
+        /* Get the algorithim information from the signature. Since we are
+         * analyzing a signature packet, we need to use the special keyword
+         * '_SIGNATURE' - see Horde_Crypt_pgp. */
+        $sig_info = $this->pgpPacketSignature($msg_sign, '_SIGNATURE');
+
+        /* Setup the multipart MIME Part. */
+        $part = new Horde_Mime_Part();
+        $part->setType('multipart/signed');
+        $part->setContents('This message is in MIME format and has been PGP signed.' . "\n");
+        $part->addPart($mime_part);
+        $part->addPart($pgp_sign);
+        $part->setContentTypeParameter('protocol', 'application/pgp-signature');
+        $part->setContentTypeParameter('micalg', $sig_info['micalg']);
+
+        return $part;
+    }
+
+    /**
+     * Encrypts a MIME part using PGP.
+     *
+     * @param Horde_Mime_Part $mime_part  The object to encrypt.
+     * @param array $params               The parameters required for
+     *                                    encryption.
+     *                                    @see _encryptMessage().
+     *
+     * @return mixed  A Horde_Mime_Part object that is encrypted according to
+     *                RFC 3156, or PEAR_Error on error.
+     */
+    public function encryptMIMEPart($mime_part, $params = array())
+    {
+        $params = array_merge($params, array('type' => 'message'));
+
+        $signenc_body = $mime_part->toCanonicalString();
+        $message_encrypt = $this->encrypt($signenc_body, $params);
+        if (is_a($message_encrypt, 'PEAR_Error')) {
+            return $message_encrypt;
+        }
+
+        /* Set up MIME Structure according to RFC 3156. */
+        $charset = NLS::getEmailCharset();
+        $part = new Horde_Mime_Part();
+        $part->setType('multipart/encrypted');
+        $part->setCharset($charset);
+        $part->setContentTypeParameter('protocol', 'application/pgp-encrypted');
+        $part->setDescription(String::convertCharset(_("PGP Encrypted Data"), NLS::getCharset(), $charset));
+        $part->setContents('This message is in MIME format and has been PGP encrypted.' . "\n");
+
+        $part1 = new Horde_Mime_Part();
+        $part1->setType('application/pgp-encrypted');
+        $part1->setCharset(null);
+        $part1->setContents("Version: 1\n");
+        $part->addPart($part1);
+
+        $part2 = new Horde_Mime_Part();
+        $part2->setType('application/octet-stream');
+        $part2->setCharset(null);
+        $part2->setContents($message_encrypt);
+        $part2->setDisposition('inline');
+        $part->addPart($part2);
+
+        return $part;
+    }
+
+    /**
+     * Signs and encrypts a MIME part using PGP.
+     *
+     * @param Horde_Mime_Part $mime_part   The object to sign and encrypt.
+     * @param array $sign_params           The parameters required for
+     *                                     signing. @see _encryptSignature().
+     * @param array $encrypt_params        The parameters required for
+     *                                     encryption. @see _encryptMessage().
+     *
+     * @return mixed  A Horde_Mime_Part object that is signed and encrypted
+     *                according to RFC 3156, or PEAR_Error on error.
+     */
+    public function signAndEncryptMIMEPart($mime_part, $sign_params = array(),
+                                           $encrypt_params = array())
+    {
+        /* RFC 3156 requires that the entire signed message be encrypted.  We
+         * need to explicitly call using Horde_Crypt_pgp:: because we don't
+         * know whether a subclass has extended these methods. */
+        $part = $this->signMIMEPart($mime_part, $sign_params);
+        if (is_a($part, 'PEAR_Error')) {
+            return $part;
+        }
+        $part = $this->encryptMIMEPart($part, $encrypt_params);
+        if (is_a($part, 'PEAR_Error')) {
+            return $part;
+        }
+        $part->setContents('This message is in MIME format and has been PGP signed and encrypted.' . "\n");
+
+        $charset = NLS::getEmailCharset();
+        $part->setCharset($charset);
+        $part->setDescription(String::convertCharset(_("PGP Signed/Encrypted Data"), NLS::getCharset(), $charset));
+
+        return $part;
+    }
+
+    /**
+     * Generates a Horde_Mime_Part object, in accordance with RFC 3156, that
+     * contains a public key.
+     *
+     * @param string $key  The public key.
+     *
+     * @return Horde_Mime_Part  An object that contains the public key.
+     */
+    public function publicKeyMIMEPart($key)
+    {
+        include_once 'Horde/Mime/Part.php';
+
+        $charset = NLS::getEmailCharset();
+        $part = new Horde_Mime_Part();
+        $part->setType('application/pgp-keys');
+        $part->setCharset($charset);
+        $part->setDescription(String::convertCharset(_("PGP Public Key"), NLS::getCharset(), $charset));
+        $part->setContents($key);
+
+        return $part;
+    }
+
+    /**
+     * Function that handles interfacing with the GnuPG binary.
+     *
+     * @param array $options   Options and commands to pass to GnuPG.
+     * @param string $mode     'r' to read from stdout, 'w' to write to stdin.
+     * @param array $input     Input to write to stdin.
+     * @param boolean $output  If true, collect and store output in object returned.
+     * @param boolean $stderr  If true, collect and store stderr in object returned.
+     * @param boolean $verbose If true, run GnuPG with quiet flag.
+     *
+     * @return stdClass  Class with members output, stderr, and stdout.
+     */
+    protected function _callGpg($options, $mode, $input = array(),
+                                $output = false, $stderr = false,
+                                $verbose = false)
+    {
+        $data = new stdClass;
+        $data->output = null;
+        $data->stderr = null;
+        $data->stdout = null;
+
+        /* Verbose output? */
+        if (!$verbose) {
+            array_unshift($options, '--quiet');
+        }
+
+        /* Create temp files for output. */
+        if ($output) {
+            $output_file = $this->_createTempFile('horde-pgp', false);
+            array_unshift($options, '--output ' . $output_file);
+
+            /* Do we need standard error output? */
+            if ($stderr) {
+                $stderr_file = $this->_createTempFile('horde-pgp', false);
+                $options[] = '2> ' . $stderr_file;
+            }
+        }
+
+        /* Silence errors if not requested. */
+        if (!$output || !$stderr) {
+            $options[] = '2> /dev/null';
+        }
+
+        /* Build the command line string now. */
+        $cmdline = implode(' ', array_merge($this->_gnupg, $options));
+
+        if ($mode == 'w') {
+            $fp = popen($cmdline, 'w');
+            $win32 = !strncasecmp(PHP_OS, 'WIN', 3);
+
+            if (!is_array($input)) {
+                $input = array($input);
+            }
+            foreach ($input as $line) {
+                if ($win32 && (strpos($line, "\x0d\x0a") !== false)) {
+                    $chunks = explode("\x0d\x0a", $line);
+                    foreach ($chunks as $chunk) {
+                        fputs($fp, $chunk . "\n");
+                    }
+                } else {
+                    fputs($fp, $line . "\n");
+                }
+            }
+        } elseif ($mode == 'r') {
+            $fp = popen($cmdline, 'r');
+            while (!feof($fp)) {
+                $data->stdout .= fgets($fp, 1024);
+            }
+        }
+        pclose($fp);
+
+        if ($output) {
+            $data->output = file_get_contents($output_file);
+            unlink($output_file);
+            if ($stderr) {
+                $data->stderr = file_get_contents($stderr_file);
+                unlink($stderr_file);
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * Generates a revocation certificate.
+     *
+     * @param string $key         The private key.
+     * @param string $email       The email to use for the key.
+     * @param string $passphrase  The passphrase to use for the key.
+     *
+     * @return string  The revocation certificate, or PEAR_Error on error.
+     */
+    public function generateRevocation($key, $email, $passphrase)
+    {
+        $keyring = $this->_putInKeyring($key, 'private');
+
+        /* Prepare the canned answers. */
+        $input = array();
+        $input[] = 'y'; // Really generate a revocation certificate
+        $input[] = '0'; // Refuse to specify a reason
+        $input[] = '';  // Empty comment
+        $input[] = 'y'; // Confirm empty comment
+        if (!empty($passphrase)) {
+            $input[] = $passphrase;
+        }
+
+        /* Run through gpg binary. */
+        $cmdline = array(
+            $keyring,
+            '--command-fd 0',
+            '--gen-revoke' . ' ' . $email,
+        );
+        $results = $this->_callGpg($cmdline, 'w', $input, true);
+
+        /* If the key is empty, something went wrong. */
+        if (empty($results->output)) {
+            return PEAR::raiseError(_("Revocation key not generated successfully."), 'horde.error');
+        }
+
+        return $results->output;
+    }
+
+}
diff --git a/framework/Crypt/lib/Horde/Crypt/smime.php b/framework/Crypt/lib/Horde/Crypt/smime.php
new file mode 100644 (file)
index 0000000..2a8294e
--- /dev/null
@@ -0,0 +1,1377 @@
+<?php
+/**
+ * Horde_Crypt_smime:: provides a framework for Horde applications to
+ * interact with the OpenSSL library and implement S/MIME.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Mike Cochrane <mike@graftonhall.co.nz>
+ * @package Horde_Crypt
+ */
+class Horde_Crypt_smime extends Horde_Crypt
+{
+    /**
+     * Object Identifers to name array.
+     *
+     * @var array
+     */
+    protected $_oids = array(
+        '2.5.4.3' => 'CommonName',
+        '2.5.4.4' => 'Surname',
+        '2.5.4.6' => 'Country',
+        '2.5.4.7' => 'Location',
+        '2.5.4.8' => 'StateOrProvince',
+        '2.5.4.9' => 'StreetAddress',
+        '2.5.4.10' => 'Organisation',
+        '2.5.4.11' => 'OrganisationalUnit',
+        '2.5.4.12' => 'Title',
+        '2.5.4.20' => 'TelephoneNumber',
+        '2.5.4.42' => 'GivenName',
+
+        '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
+
+        '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
+        '2.5.29.15' => 'id-ce-keyUsage',
+        '2.5.29.17' => 'id-ce-subjectAltName',
+        '2.5.29.19' => 'id-ce-basicConstraints',
+        '2.5.29.31' => 'id-ce-CRLDistributionPoints',
+        '2.5.29.32' => 'id-ce-certificatePolicies',
+        '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
+        '2.5.29.37' => 'id-ce-extKeyUsage',
+
+        '1.2.840.113549.1.9.1' => 'Email',
+        '1.2.840.113549.1.1.1' => 'RSAEncryption',
+        '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
+        '1.2.840.113549.1.1.4' => 'md5withRSAEncryption',
+        '1.2.840.113549.1.1.5' => 'SHA-1WithRSAEncryption',
+        '1.2.840.10040.4.3' => 'id-dsa-with-sha-1',
+
+        '1.3.6.1.5.5.7.3.2' => 'id_kp_clientAuth',
+
+        '2.16.840.1.113730.1.1' => 'netscape-cert-type',
+        '2.16.840.1.113730.1.2' => 'netscape-base-url',
+        '2.16.840.1.113730.1.3' => 'netscape-revocation-url',
+        '2.16.840.1.113730.1.4' => 'netscape-ca-revocation-url',
+        '2.16.840.1.113730.1.7' => 'netscape-cert-renewal-url',
+        '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
+        '2.16.840.1.113730.1.12' => 'netscape-ssl-server-name',
+        '2.16.840.1.113730.1.13' => 'netscape-comment',
+    );
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Parameter array.
+     *                       'temp' => Location of temporary directory.
+     */
+    function __construct($params)
+    {
+        $this->_tempdir = $params['temp'];
+    }
+
+    /**
+     * Verify a passphrase for a given private key.
+     *
+     * @param string $private_key  The user's private key.
+     * @param string $passphrase   The user's passphrase.
+     *
+     * @return boolean  Returns true on valid passphrase, false on invalid
+     *                  passphrase.
+     *                  Returns PEAR_Error on error.
+     */
+    public function verifyPassphrase($private_key, $passphrase)
+    {
+        if (is_null($passphrase)) {
+            $res = openssl_pkey_get_private($private_key);
+        } else {
+            $res = openssl_pkey_get_private($private_key, $passphrase);
+        }
+
+        return is_resource($res);
+    }
+
+    /**
+     * Encrypt text using S/MIME.
+     *
+     * @param string $text   The text to be encrypted.
+     * @param array $params  The parameters needed for encryption.
+     *                       See the individual _encrypt*() functions for
+     *                       the parameter requirements.
+     *
+     * @return string  The encrypted message.
+     *                 Returns PEAR_Error object on error.
+     */
+    public function encrypt($text, $params = array())
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        if (isset($params['type'])) {
+            if ($params['type'] === 'message') {
+                return $this->_encryptMessage($text, $params);
+            } elseif ($params['type'] === 'signature') {
+                return $this->_encryptSignature($text, $params);
+            }
+        }
+    }
+
+    /**
+     * Decrypt text via S/MIME.
+     *
+     * @param string $text   The text to be smime decrypted.
+     * @param array $params  The parameters needed for decryption.
+     *                       See the individual _decrypt*() functions for
+     *                       the parameter requirements.
+     *
+     * @return string  The decrypted message.
+     *                 Returns PEAR_Error object on error.
+     */
+    public function decrypt($text, $params = array())
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        if (isset($params['type'])) {
+            if ($params['type'] === 'message') {
+                return $this->_decryptMessage($text, $params);
+            } elseif (($params['type'] === 'signature') ||
+                      ($params['type'] === 'detached-signature')) {
+                return $this->_decryptSignature($text, $params);
+            }
+        }
+    }
+
+    /**
+     * Verify a signature using via S/MIME.
+     *
+     * @param string $text  The multipart/signed data to be verified.
+     * @param mixed $certs  Either a single or array of root certificates.
+     *
+     * @return stdClass  Object with the following elements:
+     *                   'result' -> Returns true on success;
+     *                               PEAR_Error object on error.
+     *                   'cert' -> The certificate of the signer stored
+     *                             in the message (in PEM format).
+     *                   'email' -> The email of the signing person.
+     */
+    public function verify($text, $certs)
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        /* Write text to file */
+        $fp = fopen($input, 'w+');
+        fwrite($fp, $text);
+        fclose($fp);
+
+        $root_certs = array();
+        if (!is_array($certs)) {
+            $certs = array($certs);
+        }
+        foreach ($certs as $file) {
+            if (file_exists($file)) {
+                $root_certs[] = $file;
+            }
+        }
+
+        $ob = new stdClass;
+
+        if (!empty($root_certs)) {
+            $result = openssl_pkcs7_verify($input, 0, $output, $root_certs);
+            /* Message verified */
+            if ($result === true) {
+                $ob->result = true;
+                $ob->cert = file_get_contents($output);
+                $ob->email = $this->getEmailFromKey($ob->cert);
+                return $ob;
+            }
+        }
+
+        /* Try again without verfying the signer's cert */
+        $result = openssl_pkcs7_verify($input, PKCS7_NOVERIFY, $output);
+
+        if ($result === true) {
+            $ob->result = PEAR::raiseError(_("Message Verified Successfully but the signer's certificate could not be verified."), 'horde.warning');
+        } elseif ($result == -1) {
+            $ob->result = PEAR::raiseError(_("Verification failed - an unknown error has occurred."), 'horde.error');
+        } else {
+            $ob->result = PEAR::raiseError(_("Verification failed - this message may have been tampered with."), 'horde.error');
+        }
+
+        $ob->cert = file_get_contents($output);
+        $ob->email = $this->getEmailFromKey($ob->cert);
+
+        return $ob;
+    }
+
+    /**
+     * Extract the contents from signed S/MIME data.
+     *
+     * @param string $data     The signed S/MIME data.
+     * @param string $sslpath  The path to the OpenSSL binary.
+     *
+     * @return string  The contents embedded in the signed data.
+     *                 Returns PEAR_Error on error.
+     */
+    public function extractSignedContents($data, $sslpath)
+    {
+        $pipes_desc = array(
+            0 => array('pipe', 'r'),
+            1 => array('pipe', 'w')
+        );
+
+        $fp = proc_open($sslpath . ' smime -verify -noverify -nochain', $pipes_desc, $pipes);
+        if (!is_resource($fp)) {
+            return PEAR::raiseError(_("OpenSSL error: Could not extract data from signed S/MIME part."), 'horde.error');
+        }
+
+        $output = '';
+
+        /* $pipes[0] => writeable handle connected to child stdin
+           $pipes[1] => readable handle connected to child stdout */
+        fwrite($pipes[0], $data);
+        fclose($pipes[0]);
+
+        while (!feof($pipes[1])) {
+            $output .= fgets($pipes[1], 1024);
+        }
+        fclose($pipes[1]);
+        proc_close($fp);
+
+        return $output;
+    }
+
+    /**
+     * Sign a MIME part using S/MIME.
+     *
+     * @param Horde_Mime_Part $mime_part  The object to sign.
+     * @param array $params               The parameters required for signing.
+     *
+     * @return mixed  A Horde_Mime_Part object that is signed, or a
+     *                PEAR_Error object on error.
+     */
+    public function signMIMEPart($mime_part, $params)
+    {
+        require_once 'Horde/Mime/Message.php';
+
+        /* Sign the part as a message */
+        $message = $this->encrypt($mime_part->toCanonicalString(), $params);
+
+        /* Break the result into its components */
+        $mime_message = MIME_message::parseMessage($message);
+
+        $smime_sign = $mime_message->getPart('2');
+        $smime_sign->setDescription(_("S/MIME Cryptographic Signature"));
+        $smime_sign->transferDecodeContents();
+        $smime_sign->setTransferEncoding('base64');
+
+        $smime_part = new Horde_Mime_Part();
+        $smime_part->setType('multipart/signed');
+        $smime_part->setContents('This is a cryptographically signed message in MIME format.' . "\n");
+        $smime_part->addPart($mime_part);
+        $smime_part->addPart($smime_sign);
+        $smime_part->setContentTypeParameter('protocol', 'application/pkcs7-signature');
+        $smime_part->setContentTypeParameter('micalg', 'sha1');
+
+        return $smime_part;
+    }
+
+    /**
+     * Encrypt a MIME part using S/MIME.
+     *
+     * @param Horde_Mime_Part $mime_part  The object to encrypt.
+     * @param array $params               The parameters required for
+     *                                    encryption.
+     *
+     * @return mixed  A Horde_Mme_Part object that is encrypted or a
+     *                PEAR_Error on error.
+     */
+    public function encryptMIMEPart($mime_part, $params = array())
+    {
+        require_once 'Horde/Mime/Message.php';
+
+        /* Sign the part as a message */
+        $message = $this->encrypt($mime_part->toCanonicalString(), $params);
+        if (is_a($message, 'PEAR_Error')) {
+            return $message;
+        }
+
+        /* Get charset for mime part description. */
+        $charset = NLS::getEmailCharset();
+
+        /* Break the result into its components */
+        $mime_message = MIME_Message::parseMessage($message);
+
+        $mime_message->setCharset($charset);
+        $mime_message->setDescription(String::convertCharset(_("S/MIME Encrypted Message"), NLS::getCharset(), $charset));
+        $mime_message->transferDecodeContents();
+        $mime_message->setTransferEncoding('base64');
+        $mime_message->setDisposition('inline');
+
+        /* By default, encrypt() produces a message with type
+         * 'application/x-pkcs7-mime' and no 'smime-type' parameter. Per
+         * RFC 2311, the more correct MIME type is 'application/pkcs7-mime'
+         * and the smime-type should be 'enveloped-data'. */
+        $mime_message->setType('application/pkcs7-mime');
+        $mime_message->setContentTypeParameter('smime-type', 'enveloped-data');
+
+        return $mime_message;
+    }
+
+    /**
+     * Encrypt a message in S/MIME format using a public key.
+     *
+     * @param string $text   The text to be encrypted.
+     * @param array $params  The parameters needed for encryption.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'    =>  'message' (REQUIRED)
+     * 'pubkey'  =>  public key. (REQUIRED)
+     * 'email'   =>  E-mail address of recipient. If not present, or not found
+     *               in the public key, the first e-mail address found in the
+     *               key will be used instead. (Optional)
+     * </pre>
+     *
+     * @return string  The encrypted message.
+     *                 Return PEAR_Error object on error.
+     */
+    protected function _encryptMessage($text, $params)
+    {
+        $email = null;
+
+        /* Check for required parameters. */
+        if (!isset($params['pubkey'])) {
+            return PEAR::raiseError(_("A public S/MIME key is required to encrypt a message."), 'horde.error');
+        }
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        /* Store message in file. */
+        $fp1 = fopen($input, 'w+');
+        fputs($fp1, $text);
+        fclose($fp1);
+
+        if (isset($params['email'])) {
+            $email = $params['email'];
+        } else {
+            $email = $this->getEmailFromKey($params['pubkey']);
+            if (is_null($email)) {
+                return PEAR::raiseError(_("Could not determine the recipient's e-mail address."), 'horde.error');
+            }
+        }
+
+        /* Encrypt the document. */
+        if (openssl_pkcs7_encrypt($input, $output, $params['pubkey'], array('To' => $email))) {
+            $result = file_get_contents($output);
+            if (!empty($result)) {
+                return $this->_fixContentType($result, 'encrypt');
+            }
+        }
+
+        return PEAR::raiseError(_("Could not S/MIME encrypt message."), 'horde.error');
+    }
+
+    /**
+     * Sign a message in S/MIME format using a private key.
+     *
+     * @param string $text   The text to be signed.
+     * @param array $params  The parameters needed for signing.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'certs'       =>  Additional signing certs (Optional)
+     * 'passphrase'  =>  Passphrase for key (REQUIRED)
+     * 'privkey'     =>  Private key (REQUIRED)
+     * 'pubkey'      =>  Public key (REQUIRED)
+     * 'sigtype'     =>  Determine the signature type to use. (Optional)
+     *                   'cleartext'  --  Make a clear text signature
+     *                   'detach'     --  Make a detached signature (DEFAULT)
+     * 'type'        =>  'signature' (REQUIRED)
+     * </pre>
+     *
+     * @return string  The signed message.
+     *                 Return PEAR_Error object on error.
+     */
+    protected function _encryptSignature($text, $params)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Check for required parameters. */
+        if (!isset($params['pubkey']) ||
+            !isset($params['privkey']) ||
+            !array_key_exists('passphrase', $params)) {
+            return PEAR::raiseError(_("A public S/MIME key, private S/MIME key, and passphrase are required to sign a message."), 'horde.error');
+        }
+
+        /* Create temp files for input/output/certificates. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+        $certs = $this->_createTempFile('horde-smime');
+
+        /* Store message in temporary file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Store additional certs in temporary file. */
+        if (!empty($params['certs'])) {
+            $fp = fopen($certs, 'w+');
+            fputs($fp, $params['certs']);
+            fclose($fp);
+        }
+
+        /* Determine the signature type to use. */
+        if (isset($params['sigtype']) && ($params['sigtype'] == 'cleartext')) {
+            $flags = PKCS7_TEXT;
+        } else {
+            $flags = PKCS7_DETACHED;
+        }
+
+        $privkey = (is_null($params['passphrase'])) ? $params['privkey'] : array($params['privkey'], $params['passphrase']);
+
+        if (empty($params['certs'])) {
+            $res = openssl_pkcs7_sign($input, $output, $params['pubkey'], $privkey, array(), $flags);
+        } else {
+            $res = openssl_pkcs7_sign($input, $output, $params['pubkey'], $privkey, array(), $flags, $certs);
+        }
+
+        if (!$res) {
+            return PEAR::raiseError(_("Could not S/MIME sign message."), 'horde.error');
+        }
+
+        $data = file_get_contents($output);
+        return $this->_fixContentType($data, 'signature');
+    }
+
+    /**
+     * Decrypt an S/MIME encrypted message using a private/public keypair
+     * and a passhprase.
+     *
+     * @param string $text   The text to be decrypted.
+     * @param array $params  The parameters needed for decryption.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'        =>  'message' (REQUIRED)
+     * 'pubkey'      =>  public key. (REQUIRED)
+     * 'privkey'     =>  private key. (REQUIRED)
+     * 'passphrase'  =>  Passphrase for Key. (REQUIRED)
+     * </pre>
+     *
+     * @return string  The decrypted message.
+     *                 Returns PEAR_Error object on error.
+     */
+    protected function _decryptMessage($text, $params)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Check for required parameters. */
+        if (!isset($params['pubkey']) ||
+            !isset($params['privkey']) ||
+            !array_key_exists('passphrase', $params)) {
+            return PEAR::raiseError(_("A public S/MIME key, private S/MIME key, and passphrase are required to decrypt a message."), 'horde.error');
+        }
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        /* Store message in file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, trim($text));
+        fclose($fp);
+
+        $privkey = (is_null($params['passphrase'])) ? $params['privkey'] : array($params['privkey'], $params['passphrase']);
+        if (openssl_pkcs7_decrypt($input, $output, $params['pubkey'], $privkey)) {
+            return file_get_contents($output);
+        }
+
+        return PEAR::raiseError(_("Could not decrypt S/MIME data."), 'horde.error');
+    }
+
+    /**
+     * Sign and Encrypt a MIME part using S/MIME.
+     *
+     * @param Horde_Mime_Part $mime_part   The object to sign and encrypt.
+     * @param array $sign_params           The parameters required for
+     *                                     signing. @see _encryptSignature().
+     * @param array $encrypt_params        The parameters required for
+     *                                     encryption.
+     *                                     @see _encryptMessage().
+     *
+     * @return mixed  A Horde_Mime_Part object that is signed and encrypted.
+     *                Returns PEAR_Error on error.
+     */
+    public function signAndEncryptMIMEPart($mime_part, $sign_params = array(),
+                                           $encrypt_params = array())
+    {
+        $part = $this->signMIMEPart($mime_part, $sign_params);
+        if (is_a($part, 'PEAR_Error')) {
+            return $part;
+        }
+
+        return $this->encryptMIMEPart($part, $encrypt_params);
+    }
+
+    /**
+     * Convert a PEM format certificate to readable HTML version
+     *
+     * @param string $cert   PEM format certificate
+     *
+     * @return string  HTML detailing the certificate.
+     */
+    public function certToHTML($cert)
+    {
+        /* Common Fields */
+        $fieldnames = array(
+            'Email' => _("Email Address"),
+            'CommonName' => _("Common Name"),
+            'Organisation' => _("Organisation"),
+            'OrganisationalUnit' => _("Organisational Unit"),
+            'Country' => _("Country"),
+            'StateOrProvince' => _("State or Province"),
+            'Location' => _("Location"),
+            'StreetAddress' => _("Street Address"),
+            'TelephoneNumber' => _("Telephone Number"),
+            'Surname' => _("Surname"),
+            'GivenName' => _("Given Name")
+        );
+
+        /* Netscape Extensions */
+        $fieldnames += array(
+            'netscape-cert-type' => _("Netscape certificate type"),
+            'netscape-base-url' => _("Netscape Base URL"),
+            'netscape-revocation-url' => _("Netscape Revocation URL"),
+            'netscape-ca-revocation-url' => _("Netscape CA Revocation URL"),
+            'netscape-cert-renewal-url' => _("Netscape Renewal URL"),
+            'netscape-ca-policy-url' => _("Netscape CA policy URL"),
+            'netscape-ssl-server-name' => _("Netscape SSL server name"),
+            'netscape-comment' => _("Netscape certificate comment")
+        );
+
+        /* X590v3 Extensions */
+        $fieldnames += array(
+            'id-ce-extKeyUsage' => _("X509v3 Extended Key Usage"),
+            'id-ce-basicConstraints' => _("X509v3 Basic Constraints"),
+            'id-ce-subjectAltName' => _("X509v3 Subject Alternative Name"),
+            'id-ce-subjectKeyIdentifier' => _("X509v3 Subject Key Identifier"),
+            'id-ce-certificatePolicies' => _("Certificate Policies"),
+            'id-ce-CRLDistributionPoints' => _("CRL Distribution Points"),
+            'id-ce-keyUsage' => _("Key Usage")
+        );
+
+        $cert_details = $this->parseCert($cert);
+        if (!is_array($cert_details)) {
+            return '<pre class="fixed">' . _("Unable to extract certificate details") . '</pre>';
+        }
+        $certificate = $cert_details['certificate'];
+
+        $text = '<pre class="fixed">';
+
+        /* Subject (a/k/a Certificate Owner) */
+        if (isset($certificate['subject'])) {
+            $text .= "<strong>" . _("Certificate Owner") . ":</strong>\n";
+
+            foreach ($certificate['subject'] as $key => $value) {
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("&nbsp;&nbsp;%s: %s\n", $fieldnames[$key], $value);
+                } else {
+                    $text .= sprintf("&nbsp;&nbsp;*%s: %s\n", $key, $value);
+                }
+            }
+            $text .= "\n";
+        }
+
+        /* Issuer */
+        if (isset($certificate['issuer'])) {
+            $text .= "<strong>" . _("Issuer") . ":</strong>\n";
+
+            foreach ($certificate['issuer'] as $key => $value) {
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("&nbsp;&nbsp;%s: %s\n", $fieldnames[$key], $value);
+                } else {
+                    $text .= sprintf("&nbsp;&nbsp;*%s: %s\n", $key, $value);
+                }
+            }
+            $text .= "\n";
+        }
+
+        /* Dates  */
+        $text .= "<strong>" . _("Validity") . ":</strong>\n";
+        $text .= sprintf("&nbsp;&nbsp;%s: %s\n", _("Not Before"), strftime("%x %X", $certificate['validity']['notbefore']));
+        $text .= sprintf("&nbsp;&nbsp;%s: %s\n", _("Not After"), strftime("%x %X", $certificate['validity']['notafter']));
+        $text .= "\n";
+
+        /* Certificate Owner - Public Key Info */
+        $text .= "<strong>" . _("Public Key Info") . ":</strong>\n";
+        $text .= sprintf("&nbsp;&nbsp;%s: %s\n", _("Public Key Algorithm"), $certificate['subjectPublicKeyInfo']['algorithm']);
+        if ($certificate['subjectPublicKeyInfo']['algorithm'] == 'rsaEncryption') {
+            if (Util::extensionExists('bcmath')) {
+                $modulus = $certificate['subjectPublicKeyInfo']['subjectPublicKey']['modulus'];
+                $modulus_hex = '';
+                while ($modulus != '0') {
+                    $modulus_hex = dechex(bcmod($modulus, '16')) . $modulus_hex;
+                    $modulus = bcdiv($modulus, '16', 0);
+                }
+
+                if ((strlen($modulus_hex) > 64) &&
+                    (strlen($modulus_hex) < 128)) {
+                    str_pad($modulus_hex, 128, '0', STR_PAD_RIGHT);
+                } elseif ((strlen($modulus_hex) > 128) &&
+                          (strlen($modulus_hex) < 256)) {
+                    str_pad($modulus_hex, 256, '0', STR_PAD_RIGHT);
+                }
+
+                $text .= "&nbsp;&nbsp;" . sprintf(_("RSA Public Key (%d bit)"), strlen($modulus_hex) * 4) . ":\n";
+
+                $modulus_str = '';
+                for ($i = 0; $i < strlen($modulus_hex); $i += 2) {
+                    if (($i % 32) == 0) {
+                        $modulus_str .= "\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
+                    }
+                    $modulus_str .= substr($modulus_hex, $i, 2) . ':';
+                }
+
+                $text .= sprintf("&nbsp;&nbsp;&nbsp;&nbsp;%s: %s\n", _("Modulus"), $modulus_str);
+            }
+
+            $text .= sprintf("&nbsp;&nbsp;&nbsp;&nbsp;%s: %s\n", _("Exponent"), $certificate['subjectPublicKeyInfo']['subjectPublicKey']['publicExponent']);
+        }
+        $text .= "\n";
+
+        /* X509v3 extensions */
+        if (isset($certificate['extensions'])) {
+            $text .= "<strong>" . _("X509v3 extensions") . ":</strong>\n";
+
+            foreach ($certificate['extensions'] as $key => $value) {
+                if (is_array($value)) {
+                    $value = _("Unsupported Extension");
+                }
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("&nbsp;&nbsp;%s:\n&nbsp;&nbsp;&nbsp;&nbsp;%s\n", $fieldnames[$key], wordwrap($value, 40, "\n&nbsp;&nbsp;&nbsp;&nbsp;"));
+                } else {
+                    $text .= sprintf("&nbsp;&nbsp;%s:\n&nbsp;&nbsp;&nbsp;&nbsp;%s\n", $key, wordwrap($value, 60, "\n&nbsp;&nbsp;&nbsp;&nbsp;"));
+                }
+            }
+
+            $text .= "\n";
+        }
+
+        /* Certificate Details */
+        $text .= "<strong>" . _("Certificate Details") . ":</strong>\n";
+        $text .= sprintf("&nbsp;&nbsp;%s: %d\n", _("Version"), $certificate['version']);
+        $text .= sprintf("&nbsp;&nbsp;%s: %d\n", _("Serial Number"), $certificate['serialNumber']);
+
+        foreach ($cert_details['fingerprints'] as $hash => $fingerprint) {
+            $label = sprintf(_("%s Fingerprint"), String::upper($hash));
+            $fingerprint_str = '';
+            for ($i = 0; $i < strlen($fingerprint); $i += 2) {
+                $fingerprint_str .= substr($fingerprint, $i, 2) . ':';
+            }
+            $text .= sprintf("&nbsp;&nbsp;%s:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%s\n", $label, $fingerprint_str);
+        }
+        $text .= sprintf("&nbsp;&nbsp;%s: %s\n", _("Signature Algorithm"), $cert_details['signatureAlgorithm']);
+        $text .= sprintf("&nbsp;&nbsp;%s:", _("Signature"));
+
+        $sig_str = '';
+        for ($i = 0; $i < strlen($cert_details['signature']); $i++) {
+            if (($i % 16) == 0) {
+                $sig_str .= "\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
+            }
+            $sig_str .= sprintf("%02x:", ord($cert_details['signature'][$i]));
+        }
+
+        return $text . $sig_str . "\n</pre>";
+    }
+
+    /**
+     * Extract the contents of a PEM format certificate to an array.
+     *
+     * @param string $cert  PEM format certificate
+     *
+     * @return array  Array containing all extractable information about
+     *                the certificate.
+     */
+    public function parseCert($cert)
+    {
+        $cert_split = preg_split('/(-----((BEGIN)|(END)) CERTIFICATE-----)/', $cert);
+        if (!isset($cert_split[1])) {
+            $raw_cert = base64_decode($cert);
+        } else {
+            $raw_cert = base64_decode($cert_split[1]);
+        }
+
+        $cert_data = $this->_parseASN($raw_cert);
+        if (!is_array($cert_data) || ($cert_data[0] == 'UNKNOWN')) {
+            return false;
+        }
+
+        $cert_details = array();
+        $cert_details['fingerprints']['md5'] = md5($raw_cert);
+        $cert_details['fingerprints']['sha1'] = sha1($raw_cert);
+
+        $cert_details['certificate']['extensions'] = array();
+        $cert_details['certificate']['version'] = $cert_data[1][0][1][0][1] + 1;
+        $cert_details['certificate']['serialNumber'] = $cert_data[1][0][1][1][1];
+        $cert_details['certificate']['signature'] = $cert_data[1][0][1][2][1][0][1];
+        $cert_details['certificate']['issuer'] = $cert_data[1][0][1][3][1];
+        $cert_details['certificate']['validity'] = $cert_data[1][0][1][4][1];
+        $cert_details['certificate']['subject'] = @$cert_data[1][0][1][5][1];
+        $cert_details['certificate']['subjectPublicKeyInfo'] = $cert_data[1][0][1][6][1];
+
+        $cert_details['signatureAlgorithm'] = $cert_data[1][1][1][0][1];
+        $cert_details['signature'] = $cert_data[1][2][1];
+
+        // issuer
+        $issuer = array();
+        foreach ($cert_details['certificate']['issuer'] as $value) {
+            $issuer[$value[1][1][0][1]] = $value[1][1][1][1];
+        }
+        $cert_details['certificate']['issuer'] = $issuer;
+
+        // subject
+        $subject = array();
+        foreach ($cert_details['certificate']['subject'] as $value) {
+            $subject[$value[1][1][0][1]] = $value[1][1][1][1];
+        }
+        $cert_details['certificate']['subject'] = $subject;
+
+        // validity
+        $vals = $cert_details['certificate']['validity'];
+        $cert_details['certificate']['validity'] = array();
+        $cert_details['certificate']['validity']['notbefore'] = $vals[0][1];
+        $cert_details['certificate']['validity']['notafter'] = $vals[1][1];
+        foreach ($cert_details['certificate']['validity'] as $key => $val) {
+            $year = substr($val, 0, 2);
+            $month = substr($val, 2, 2);
+            $day = substr($val, 4, 2);
+            $hour = substr($val, 6, 2);
+            $minute = substr($val, 8, 2);
+            if (($val[11] == '-') || ($val[9] == '+')) {
+                // handle time zone offset here
+                $seconds = 0;
+            } elseif (String::upper($val[11]) == 'Z') {
+                $seconds = 0;
+            } else {
+                $seconds = substr($val, 10, 2);
+                if (($val[11] == '-') || ($val[9] == '+')) {
+                    // handle time zone offset here
+                }
+            }
+            $cert_details['certificate']['validity'][$key] = mktime ($hour, $minute, $seconds, $month, $day, $year);
+        }
+
+        // Split the Public Key into components.
+        $subjectPublicKeyInfo = array();
+        $subjectPublicKeyInfo['algorithm'] = $cert_details['certificate']['subjectPublicKeyInfo'][0][1][0][1];
+        if ($subjectPublicKeyInfo['algorithm'] == 'rsaEncryption') {
+            $subjectPublicKey = $this->_parseASN($cert_details['certificate']['subjectPublicKeyInfo'][1][1]);
+            $subjectPublicKeyInfo['subjectPublicKey']['modulus'] = $subjectPublicKey[1][0][1];
+            $subjectPublicKeyInfo['subjectPublicKey']['publicExponent'] = $subjectPublicKey[1][1][1];
+        }
+        $cert_details['certificate']['subjectPublicKeyInfo'] = $subjectPublicKeyInfo;
+
+        if (isset($cert_data[1][0][1][7]) &&
+            is_array($cert_data[1][0][1][7][1])) {
+            foreach ($cert_data[1][0][1][7][1] as $ext) {
+                $oid = $ext[1][0][1];
+                $cert_details['certificate']['extensions'][$oid] = $ext[1][1];
+            }
+        }
+
+        $i = 9;
+
+        while (isset($cert_data[1][0][1][$i]) &&
+               is_array($cert_data[1][0][1][$i][1])) {
+            $oid = $cert_data[1][0][1][$i][1][0][1];
+            $cert_details['certificate']['extensions'][$oid] = $cert_data[1][0][1][$i][1][1];
+            ++$i;
+        }
+
+        foreach ($cert_details['certificate']['extensions'] as $oid => $val) {
+            switch ($oid) {
+            case 'netscape-base-url':
+            case 'netscape-revocation-url':
+            case 'netscape-ca-revocation-url':
+            case 'netscape-cert-renewal-url':
+            case 'netscape-ca-policy-url':
+            case 'netscape-ssl-server-name':
+            case 'netscape-comment':
+                $val = $this->_parseASN($val[1]);
+                $cert_details['certificate']['extensions'][$oid] = $val[1];
+                break;
+
+            case 'id-ce-subjectAltName':
+                $val = $this->_parseASN($val[1]);
+                $cert_details['certificate']['extensions'][$oid] = '';
+                foreach ($val[1] as $name) {
+                    if (!empty($cert_details['certificate']['extensions'][$oid])) {
+                        $cert_details['certificate']['extensions'][$oid] .= ', ';
+                    }
+                    $cert_details['certificate']['extensions'][$oid] .= $name[1];
+                }
+                break;
+
+            case 'netscape-cert-type':
+                $val = $this->_parseASN($val[1]);
+                $val = ord($val[1]);
+                $newVal = '';
+
+                if ($val & 0x80) {
+                    $newVal .= empty($newVal) ? 'SSL client' : ', SSL client';
+                }
+                if ($val & 0x40) {
+                    $newVal .= empty($newVal) ? 'SSL server' : ', SSL server';
+                }
+                if ($val & 0x20) {
+                    $newVal .= empty($newVal) ? 'S/MIME' : ', S/MIME';
+                }
+                if ($val & 0x10) {
+                    $newVal .= empty($newVal) ? 'Object Signing' : ', Object Signing';
+                }
+                if ($val & 0x04) {
+                    $newVal .= empty($newVal) ? 'SSL CA' : ', SSL CA';
+                }
+                if ($val & 0x02) {
+                    $newVal .= empty($newVal) ? 'S/MIME CA' : ', S/MIME CA';
+                }
+                if ($val & 0x01) {
+                    $newVal .= empty($newVal) ? 'Object Signing CA' : ', Object Signing CA';
+                }
+
+                $cert_details['certificate']['extensions'][$oid] = $newVal;
+                break;
+
+            case 'id-ce-extKeyUsage':
+                $val = $this->_parseASN($val[1]);
+                $val = $val[1];
+
+                $newVal = '';
+                if ($val[0][1] != 'sequence') {
+                    $val = array($val);
+                } else {
+                    $val = $val[1][1];
+                }
+                foreach ($val as $usage) {
+                    if ($usage[1] == 'id_kp_clientAuth') {
+                        $newVal .= empty($newVal) ? 'TLS Web Client Authentication' : ', TLS Web Client Authentication';
+                    } else {
+                        $newVal .= empty($newVal) ? $usage[1] : ', ' . $usage[1];
+                    }
+                }
+                $cert_details['certificate']['extensions'][$oid] = $newVal;
+                break;
+
+            case 'id-ce-subjectKeyIdentifier':
+                $val = $this->_parseASN($val[1]);
+                $val = $val[1];
+
+                $newVal = '';
+
+                for ($i = 0; $i < strlen($val); $i++) {
+                    $newVal .= sprintf("%02x:", ord($val[$i]));
+                }
+                $cert_details['certificate']['extensions'][$oid] = $newVal;
+                break;
+
+            case 'id-ce-authorityKeyIdentifier':
+                $val = $this->_parseASN($val[1]);
+                if ($val[0] == 'string') {
+                    $val = $val[1];
+
+                    $newVal = '';
+                    for ($i = 0; $i < strlen($val); $i++) {
+                        $newVal .= sprintf("%02x:", ord($val[$i]));
+                    }
+                    $cert_details['certificate']['extensions'][$oid] = $newVal;
+                } else {
+                    $cert_details['certificate']['extensions'][$oid] = _("Unsupported Extension");
+                }
+                break;
+
+            case 'id-ce-basicConstraints':
+            case 'default':
+                $cert_details['certificate']['extensions'][$oid] = _("Unsupported Extension");
+                break;
+            }
+        }
+
+        return $cert_details;
+    }
+
+    /**
+     * Attempt to parse ASN.1 formated data.
+     *
+     * @param string $data  ASN.1 formated data
+     *
+     * @return array  Array contained the extracted values.
+     */
+    protected function _parseASN($data)
+    {
+        $result = array();
+
+        while (strlen($data) > 1) {
+            $class = ord($data[0]);
+            switch ($class) {
+            case 0x30:
+                // Sequence
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $sequence_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+
+                $values = $this->_parseASN($sequence_data);
+                if (!is_array($values) || is_string($values[0])) {
+                    $values = array($values);
+                }
+                $sequence_values = array();
+                $i = 0;
+                foreach ($values as $val) {
+                    if ($val[0] == 'extension') {
+                        $sequence_values['extensions'][] = $val;
+                    } else {
+                        $sequence_values[$i++] = $val;
+                    }
+                }
+                $result[] = array('sequence', $sequence_values);
+                break;
+
+            case 0x31:
+                // Set of
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $sequence_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('set', $this->_parseASN($sequence_data));
+                break;
+
+            case 0x01:
+                // Boolean type
+                $boolean_value = (ord($data[2]) == 0xff);
+                $data = substr($data, 3);
+                $result[] = array('boolean', $boolean_value);
+                break;
+
+            case 0x02:
+                // Integer type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+
+                $integer_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+
+                $value = 0;
+                if ($len <= 4) {
+                    /* Method works fine for small integers */
+                    for ($i = 0; $i < strlen($integer_data); $i++) {
+                        $value = ($value << 8) | ord($integer_data[$i]);
+                    }
+                } else {
+                    /* Method works for arbitrary length integers */
+                    if (Util::extensionExists('bcmath')) {
+                        for ($i = 0; $i < strlen($integer_data); $i++) {
+                            $value = bcadd(bcmul($value, 256), ord($integer_data[$i]));
+                        }
+                    } else {
+                        $value = -1;
+                    }
+                }
+                $result[] = array('integer(' . $len . ')', $value);
+                break;
+
+            case 0x03:
+                // Bitstring type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $bitstring_data = substr($data, 3 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('bit string', $bitstring_data);
+                break;
+
+            case 0x04:
+                // Octetstring type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $octectstring_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('octet string', $octectstring_data);
+                break;
+
+            case 0x05:
+                // Null type
+                $data = substr($data, 2);
+                $result[] = array('null', null);
+                break;
+
+            case 0x06:
+                // Object identifier type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $oid_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+
+                // Unpack the OID
+                $plain  = floor(ord($oid_data[0]) / 40);
+                $plain .= '.' . ord($oid_data[0]) % 40;
+
+                $value = 0;
+                $i = 1;
+                while ($i < strlen($oid_data)) {
+                    $value = $value << 7;
+                    $value = $value | (ord($oid_data[$i]) & 0x7f);
+
+                    if (!(ord($oid_data[$i]) & 0x80)) {
+                        $plain .= '.' . $value;
+                        $value = 0;
+                    }
+                    $i++;
+                }
+
+                if (isset($this->_oids[$plain])) {
+                    $result[] = array('oid', $this->_oids[$plain]);
+                } else {
+                    $result[] = array('oid', $plain);
+                }
+
+                break;
+
+            case 0x12:
+            case 0x13:
+            case 0x14:
+            case 0x15:
+            case 0x16:
+            case 0x81:
+            case 0x80:
+                // Character string type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $string_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('string', $string_data);
+                break;
+
+            case 0x17:
+                // Time types
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $time_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('utctime', $time_data);
+                break;
+
+            case 0x82:
+                // X509v3 extensions?
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $sequence_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('extension', 'X509v3 extensions');
+                $result[] = $this->_parseASN($sequence_data);
+                break;
+
+            case 0xa0:
+            case 0xa3:
+                // Extensions
+                $extension_data = substr($data, 0, 2);
+                $data = substr($data, 2);
+                $result[] = array('extension', dechex($extension_data));
+                break;
+
+            case 0xe6:
+                $extension_data = substr($data, 0, 1);
+                $data = substr($data, 1);
+                $result[] = array('extension', dechex($extension_data));
+                break;
+
+            case 0xa1:
+                $extension_data = substr($data, 0, 1);
+                $data = substr($data, 6);
+                $result[] = array('extension', dechex($extension_data));
+                break;
+
+            default:
+                // Unknown
+                $result[] = array('UNKNOWN', dechex($data));
+                $data = '';
+                break;
+            }
+        }
+
+        return (count($result) > 1) ? $result : array_pop($result);
+    }
+
+    /**
+     * Decrypt an S/MIME signed message using a public key.
+     *
+     * @param string $text   The text to be verified.
+     * @param array $params  The parameters needed for verification.
+     *
+     * @return string  The verification message.
+     *                 Returns PEAR_Error object on error.
+     */
+    protected function _decryptSignature($text, $params)
+    {
+        return PEAR::raiseError('_decryptSignature() ' . _("not yet implemented"));
+    }
+
+    /**
+     * Check for the presence of the OpenSSL extension to PHP.
+     *
+     * @return boolean  Returns true if the openssl extension is available.
+     *                  Returns a PEAR_Error if not.
+     */
+    public function checkForOpenSSL()
+    {
+        if (!Util::extensionExists('openssl')) {
+            return PEAR::raiseError(_("The openssl module is required for the Horde_Crypt_smime:: class."));
+        }
+    }
+
+    /**
+     * Extract the email address from a public key.
+     *
+     * @param string $key  The public key.
+     *
+     * @return mixed Returns the first email address found, or null if
+     * there are none.
+     */
+    public function getEmailFromKey($key)
+    {
+        $key_info = openssl_x509_parse($key);
+        if (!is_array($key_info)) {
+            return null;
+        }
+
+        if (isset($key_info['subject'])) {
+            if (isset($key_info['subject']['Email'])) {
+                return $key_info['subject']['Email'];
+            } elseif (isset($key_info['subject']['emailAddress'])) {
+                return $key_info['subject']['emailAddress'];
+            }
+        }
+
+        // Check subjectAltName per http://www.ietf.org/rfc/rfc3850.txt
+        if (isset($key_info['extensions']['subjectAltName'])) {
+            $names = preg_split('/\s*,\s*/', $key_info['extensions']['subjectAltName'], -1, PREG_SPLIT_NO_EMPTY);
+            foreach ($names as $name) {
+                if (strpos($name, ':') === false) {
+                    continue;
+                }
+                list($kind, $value) = explode(':', $name, 2);
+                if (String::lower($kind) == 'email') {
+                    return $value;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Convert a PKCS 12 encrypted certificate package into a private key,
+     * public key, and any additional keys.
+     *
+     * @param string $text   The PKCS 12 data.
+     * @param array $params  The parameters needed for parsing.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'sslpath' => The path to the OpenSSL binary. (REQUIRED)
+     * 'password' => The password to use to decrypt the data. (Optional)
+     * 'newpassword' => The password to use to encrypt the private key.
+     *                  (Optional)
+     * </pre>
+     *
+     * @return stdClass  An object.
+     *                   'private' -  The private key in PEM format.
+     *                   'public'  -  The public key in PEM format.
+     *                   'certs'   -  An array of additional certs.
+     *                   Returns PEAR_Error on error.
+     */
+    public function parsePKCS12Data($pkcs12, $params)
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        if (!isset($params['sslpath'])) {
+            return PEAR::raiseError(_("No path to the OpenSSL binary provided. The OpenSSL binary is necessary to work with PKCS 12 data."), 'horde.error');
+        }
+        $sslpath = escapeshellcmd($params['sslpath']);
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        $ob = new stdClass;
+
+        /* Write text to file */
+        $fp = fopen($input, 'w+');
+        fwrite($fp, $pkcs12);
+        fclose($fp);
+
+        /* Extract the private key from the file first. */
+        $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nocerts';
+        if (isset($params['password'])) {
+            $cmdline .= ' -passin stdin';
+            if (!empty($params['newpassword'])) {
+                $cmdline .= ' -passout stdin';
+            } else {
+                $cmdline .= ' -nodes';
+            }
+            $fd = popen($cmdline, 'w');
+            fwrite($fd, $params['password'] . "\n");
+            if (!empty($params['newpassword'])) {
+                fwrite($fd, $params['newpassword'] . "\n");
+            }
+            pclose($fd);
+        } else {
+            $cmdline .= ' -nodes';
+            exec($cmdline);
+        }
+        $ob->private = trim(file_get_contents($output));
+        if (empty($ob->private)) {
+            return PEAR::raiseError(_("Password incorrect"), 'horde.error');
+        }
+
+        /* Extract the client public key next. */
+        $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nokeys -clcerts';
+        if (isset($params['password'])) {
+            $cmdline .= ' -passin stdin';
+            $fd = popen($cmdline, 'w');
+            fwrite($fd, $params['password'] . "\n");
+            pclose($fd);
+        } else {
+            exec($cmdline);
+        }
+        $ob->public = trim(file_get_contents($output));
+
+        /* Extract the CA public key next. */
+        $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nokeys -cacerts';
+        if (isset($params['password'])) {
+            $cmdline .= ' -passin stdin';
+            $fd = popen($cmdline, 'w');
+            fwrite($fd, $params['password'] . "\n");
+            pclose($fd);
+        } else {
+            exec($cmdline);
+        }
+        $ob->certs = trim(file_get_contents($output));
+
+        return $ob;
+    }
+
+    /**
+     * The Content-Type parameters PHP's openssl_pkcs7_* functions return are
+     * deprecated.  Fix these headers to the correct ones (see RFC 2311).
+     *
+     * @param string $text  The PKCS7 data.
+     * @param string $type  Is this 'message' or 'signature' data?
+     *
+     * @return string  The PKCS7 data with the correct Content-Type parameter.
+     */
+    protected function _fixContentType($text, $type)
+    {
+        if ($type == 'message') {
+            $from = 'application/x-pkcs7-mime';
+            $to = 'application/pkcs7-mime';
+        } else {
+            $from = 'application/x-pkcs7-signature';
+            $to = 'application/pkcs7-signature';
+        }
+        return str_replace('Content-Type: ' . $from, 'Content-Type: ' . $to, $text);
+    }
+
+}
diff --git a/framework/Crypt/package.xml b/framework/Crypt/package.xml
new file mode 100644 (file)
index 0000000..4e96d15
--- /dev/null
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Horde_Crypt</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Cryptography API</summary>
+ <description>The Horde_Crypt:: class provides an API for various cryptographic systems
+ </description>
+ <lead>
+  <name>Michael Slusarz</name>
+  <user>slusarz</user>
+  <email>slusarz@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2008-11-16</date>
+ <version>
+  <release>0.1.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>alpha</release>
+  <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial Horde 4 package.</notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="Crypt">
+      <file name="pgp.php" role="php" />
+      <file name="smime.php" role="php" />
+     </dir> <!-- /lib/Horde/Crypt -->
+     <file name="Crypt.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+   <dir name="test">
+    <dir name="Horde">
+     <dir name="Crypt">
+      <dir name="fixtures">
+       <file name="clear.txt" role="test" />
+       <file name="pgp_encrypted.txt" role="test" />
+       <file name="pgp_encrypted_symmetric.txt" role="test" />
+       <file name="pgp_private.asc" role="test" />
+       <file name="pgp_public.asc" role="test" />
+       <file name="pgp_signature.txt" role="test" />
+       <file name="pgp_signed.txt" role="test" />
+       <file name="pgp_signed2.txt" role="test" />
+       <file name="smime_subjectAltName.pem" role="test" />
+      </dir> <!-- /test/Horde/Crypt/fixtures -->
+      <file name="pgp.inc" role="test" />
+      <file name="pgp_decrypt.phpt" role="test" />
+      <file name="pgp_decrypt_symmetric.phpt" role="test" />
+      <file name="pgp_encrypt.phpt" role="test" />
+      <file name="pgp_encrypt_symmetric.phpt" role="test" />
+      <file name="pgp_getSignersKeyID.phpt" role="test" />
+      <file name="pgp_parsePGPData.phpt" role="test" />
+      <file name="pgp_pgpPacketInformation.phpt" role="test" />
+      <file name="pgp_pgpPacketSignature.phpt" role="test" />
+      <file name="pgp_pgpPacketSignatureByUidIndex.phpt" role="test" />
+      <file name="pgp_pgpPrettyKey.phpt" role="test" />
+      <file name="pgp_publicKeyMIMEPart.phpt" role="test" />
+      <file name="pgp_sign.phpt" role="test" />
+      <file name="pgp_skipif.inc" role="test" />
+      <file name="pgp_verify.phpt" role="test" />
+      <file name="pgp_verifyPassphrase.phpt" role="test" />
+      <file name="smime.inc" role="test" />
+      <file name="smime_skipif.inc" role="test" />
+      <file name="smime_subjectAltName.phpt" role="test" />
+     </dir> <!-- /test/Horde/Crypt -->
+    </dir> <!-- /test/Horde -->
+   </dir> <!-- /test -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.5.0</min>
+   </pearinstaller>
+   <package>
+    <name>Horde_Mime</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Util</name>
+    <channel>pear.horde.org</channel>
+   </package>
+  </required>
+  <optional>
+   <extension>
+    <name>gettext</name>
+   </extension>
+  </optional>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Crypt/pgp.php" as="Horde/Crypt/pgp.php" />
+   <install name="lib/Horde/Crypt/smime.php" as="Horde/Crypt/smime.php" />
+   <install name="lib/Horde/Crypt.php" as="Horde/Crypt.php" />
+  </filelist>
+ </phprelease>
+ <changelog>
+  <release>
+   <version>
+    <release>0.0.2</release>
+    <api>0.0.2</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2006-05-08</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>* Converted to package.xml 2.0 for pear.horde.org
+* Added support for email addresses in subjectAltName of S/MIME certs (Bug #5986)
+* Improved support for PGP fingerprints (Bug #6363)
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.0.1</release>
+    <api>0.0.1</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2003-07-03</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>Initial release as a PEAR package
+   </notes>
+  </release>
+ </changelog>
+</package>
diff --git a/framework/Crypt/test/Horde/Crypt/bug_6601.phpt b/framework/Crypt/test/Horde/Crypt/bug_6601.phpt
new file mode 100644 (file)
index 0000000..e5caba0
--- /dev/null
@@ -0,0 +1,34 @@
+--TEST--
+Bug #6601
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+// Fake preferences class to set the timezone to a consistent value.
+class mock_prefs {
+    function getValue($key)
+    {
+        return 'GMT';
+    }
+}
+$GLOBALS['prefs'] = new mock_prefs();
+
+require_once 'Horde/NLS.php';
+NLS::setTimezone();
+require 'pgp.inc';
+
+echo $pgp->pgpPrettyKey(file_get_contents(dirname(__FILE__) . '/fixtures/bug_6601.asc'));
+?>
+--EXPECT--
+Name:             Richard Selsky
+Key Type:         Public Key
+Key Creation:     04/11/08
+Expiration Date:  [Never]
+Key Length:       1024 Bytes
+Comment:          [None]
+E-Mail:           rselsky@bu.edu
+Hash-Algorithm:   pgp-sha1
+Key ID:           0xF3C01D42
+Key Fingerprint:  5912 D91D 4C79 C670 1FFF  1486 04A6 7B37 F3C0 1D42
+
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/bug_6601.asc b/framework/Crypt/test/Horde/Crypt/fixtures/bug_6601.asc
new file mode 100644 (file)
index 0000000..68e7ab2
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----\r
+Version: GnuPG v1.4.9 (MingW32)\r
+\r
+mQGiBEf/ossRBACchuYgSW0Uz8DzoLTWAjx3leau0jdNq9ud4HzO9lzstkWh14k3\r
+Ld+KN9p4Z168NNj7fLtUKzwxLTTjDS9cMiwq5/gpP28ta0gyu8Ooq1i5U1BlwzKT\r
+UFRduQ3jQKK2ZXa2jMeXdWcchYwMGj5qd/1YlM2MpGnKfOTYyZozs22OpwCg+6qR\r
+mHAAcrY1QUN4zSlUlFIxAzUD+gPY0RQU0Ke1UE48pve6QcOlnRNFnj5XLjEzn2M8\r
+kkjbYBG2mEtXHALpYIQa7KrtfoD3nGLjf4wqzHrwYlWEu17jIdodCGI1F8dX7Mm+\r
+ARO8DbiX/fk1H53jGjrf4h4OEc6H6B3dMEKEO0S7ZNNp6QgudCjd+LHNJ0pjyzow\r
+y84KA/9OXCkvZh/3xXBOPkO5LLmAw0ZZEEP7Sna75kw02G7S2elhRqWrLzVNzv2O\r
+ppaj3oz73XYGxZAjsPOdmWYajCnycO7BqK7VFGw1EySldUmWB/Uadpeb8PwLpHFJ\r
+d+mZy2INSHEAn484FHXHYSUYPOyNypGejwY2IOI0e/MjKqTHLbQfUmljaGFyZCBT\r
+ZWxza3kgPHJzZWxza3lAYnUuZWR1PohmBBMRAgAmBQJH/6LLAhsjBQkJZgGABgsJ\r
+CAcDAgQVAggDBBYCAwECHgECF4AACgkQBKZ7N/PAHUK25QCglppEGY1V0pK/hmuO\r
+ocxXd23mJt0AnRIpLvWpVMjXfr/TYbrDXCaNv62FuQINBEf/ossQCADozRNDx7HC\r
+KFHcX/ZZjW24Yc+GAHwxLq2Vtq/VyrlxCDFfw2tGFcKMa21f1WFCk2Z60GxiMAhq\r
+2yE1ATkkAKHE0r9huz/NkuBqJXwbZzWV/9VkrDArGqTZDXdydpF0fADsqe64LPBU\r
+b3kGI5UmJHkiyLD60EsldPM+sWe7VATRHjnikbu72J1JbLtvUxGoUB8LrwWcHIJr\r
+Sql21XGUev8kabNhOOOUcj0uojvPlbmdj1aK9QuqcQ/fe2wK56s3LYM+u5mDb+uY\r
+p+9pwAAfW2yqjx/gQrQZxXk0DZrmSHJzrBm6wvzWOwCXE8MM5S+Bp7xObjT0Hjiu\r
+dZzuI5Q2MuB7AAMGCACvmsTPNwPKYDENZYujNSWWeES3byyLpOzSNt1ehed44nXT\r
+ur5738BwCfHtdsqBVXZUS6k/2BabgxdDyBSzBn3utKCos7LFxNy4GmUNqU8cZgJj\r
+gwA+poyTAJW4ndOlQbLGTOCOyaiXi1gKpv3RJQB1ADXNAazP29I+LWSWNOBeL1/p\r
+EevcD1mCMM3tmoWOlBBm0HDg3UZtHg56D6cm5BVYjRODJNdOI8IDOwj4kONjIOS3\r
++kfWgeJZmo8JatxhS2jBIGb5W033hI3a3EtUYrIRxsFAKT4QT/9YEDzZnIqxQIq8\r
+Q5AE5Ij6OjAKFO8bR1f7RiQsjgZK96GABBLgWsr6iE8EGBECAA8FAkf/ossCGwwF\r
+CQlmAYAACgkQBKZ7N/PAHUKkFQCgnfx8fwXdCMrFJqV5ukOnzOr/q7YAniA3CIgF\r
+dAM6dnvvMMNWckjddcwg\r
+=ynmU\r
+-----END PGP PUBLIC KEY BLOCK-----\r
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/clear.txt b/framework/Crypt/test/Horde/Crypt/fixtures/clear.txt
new file mode 100644 (file)
index 0000000..9d32ba6
--- /dev/null
@@ -0,0 +1,14 @@
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted.txt
new file mode 100644 (file)
index 0000000..985f410
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+hQEOAyTtKXee8HSpEAP6Aj5+9VlYxyMQepJxeejXHePK58xs6jSv1oUn/I4zjdW3
+XKvXAGy/2dQdYLOFHCfMqzLx2cKPh/UL5+M89p/oSIXpeWz2anxrt/1JxnQHSIaD
+n88jQ/H6Es07wGbJ41RvM8n4aobdVXVk9ujLi1nk4/2Pi2Ped22I6aAi3eO+dRED
+/An4L6SCmFFdqTphEjIHRSeqf6QVp/tjdnYXMkMrqVJGtFSsvfrbhF7RGyWVQra3
+rEZdnJaPPF6CwtG99akP38I6iQ0c9GqWX8/VfAYaQyeqKkDJpdzfN8Pe9Z8Q9XVs
+/qQXaIM2QxSen7K8qEC5WBah/ZOWpSrWSlk+fM65AXaS0qUBwvQoHi2/tjijfGZD
+uKt4cYNal3aZLeDNWry+nGO2mckjCYsOiyerPOYPU84OvygndcOR8Y8r8+/No8IA
+vCzvSVygJ0MCvq0QtIXRF8XDVQJt5eSAo18Tb4yfc6cU/NKWApY0/92OPAiRu3sA
+NdxJM6Whb1cDsfAIOPT9sX4yM2K7JmnoxP8owOtu/ViwvIkWiJ55+dPNUVFG3cAf
+KUjSRnwgJrg=
+=RtGk
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted_symmetric.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted_symmetric.txt
new file mode 100644 (file)
index 0000000..5d17066
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+jA0EAwMC8w6TJuJ4zhBgyYdWhfV8CJIOWg2IHcVVngMH5xHp7eiESg753YuwYaCJ
+bYR4pItctfQbRDVhlQXqveO6SZCv9WJVrtiUBIo0N4r3p2Hsx0gL34MgLW02xpTc
+4CfPwB+3VNeSTym5unWvVsB4VAuW78Xvc8fw6tjZnTPNUqA2IfH30gkpfnzBwcno
+lNMhxacLMXE=
+=N1Bk
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_private.asc b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_private.asc
new file mode 100644 (file)
index 0000000..a11a979
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+lQHhBETcWvARBADNitbvsWy5/hhV+WcU2ttmtXkAj2DqJVgJdGS2RH8msO0roG5j
+CQK/e0iMJki5lfdgxvxxWgStYMnfF5ftgWA7JV+BZUzJt12Lobm0zdENv2TqL2vc
+xlPTmEGsvfPDTbY+Gr3jvuODboXat7bUn2E723WXPdh2A7KNNnLou7JF2wCgiKs/
+RqNKM/Zm01PxLbQ+rs9ghd0D/jLUfJeYWySoDsvfO8e4UyDxDVTBLkkdw3XzLx1F
+4SS/Cc2Z9yJuXiepzSH/G/vhdN5ROv12kJwA4FbwsFv5C1uCQleWiPngFixca9Nw
+lAd2X2Cp0/4D2XRq1M9dEbcYdrgAuyzt2ZToj3aFaYNGwjfHoLqSngOu6/d3KD1d
+i0b2A/9wnXo41kPwS73gU1Un2KKMkKqnczCQHdpopO6NjKaLhNcouRauLrgbrS5y
+A1CW+nxjkKVvWrP/VFBmapUpjE1C51J9P0/ub8tRr7H0xHdTQyufv01lmfkjUpVF
+n3GVf95l4seBFzD7r580aTx+dJztoHEGWrsWZTNJwo6IIlFOIf4DAwKhFBeOUwqs
+hWA4x/9ffd4UC9JengF978ktHEm4RtB+5QS2angOVMG4mHqkeQobo2qEta2lciQQ
+aqeJ5rQlTXkgTmFtZSAoTXkgQ29tbWVudCkgPG1lQGV4YW1wbGUuY29tPohgBBMR
+AgAgBQJE3FrwAhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQfKdEJrreq9fi
+vACeLBcWErSQU4ZGQsjedhwfdst9cLQAnRETpwmqt/XvcGFVsOE28MUrUzOGnQFX
+BETcWvAQBADNgIJ4nDlfgBOI/iqyNy08C9+MjxrqMylrj4TPn3rRk8wySr2myX+j
+9CML5EHOtsxANYeI9u7hOF11n5Z8fDht/WGJrNR7EtRhNN6mkKsPaEuO3fhz6Egw
+JYReUVzDJbvnV2fRCvQoEGaSntZGQaQzIzIL+/gMEFpEVRK1P2I3VwADBQP+K2Rm
+mkm3DonXFlUUDWWdhEF4b7fy5/IPj41PSSOdo0IP4dprFoe15Vs9gWOYvzcnjy+B
+bOwhVwsjE3F36hf04od3uTSM0dLS/xmpSvgbBh181T5c3W5aKT/daVhyxXJ4csxE
++JCVKbaBubne0DPEuyZjrYlL5Lm0z3VhNCcR0Lz+AwMCoRQXjlMKrIVgzwaI65oM
+T4XCFhFGk67RoJ4D1I4R/7pXZffFhdCHsPzZBHqsadyC0chmu2ZoYtIcOxxSQmzc
+4swO7XC5lohJBBgRAgAJBQJE3FrwAhsMAAoJEHynRCa63qvXpjcAoIeCpIKzYgIj
+AfGSLg/OfLBCU3xMAJ9V3CCBw+tP458WDRprIE+SbhUY8Q==
+=41gN
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_public.asc b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_public.asc
new file mode 100644 (file)
index 0000000..98b3585
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+mQGiBETcWvARBADNitbvsWy5/hhV+WcU2ttmtXkAj2DqJVgJdGS2RH8msO0roG5j
+CQK/e0iMJki5lfdgxvxxWgStYMnfF5ftgWA7JV+BZUzJt12Lobm0zdENv2TqL2vc
+xlPTmEGsvfPDTbY+Gr3jvuODboXat7bUn2E723WXPdh2A7KNNnLou7JF2wCgiKs/
+RqNKM/Zm01PxLbQ+rs9ghd0D/jLUfJeYWySoDsvfO8e4UyDxDVTBLkkdw3XzLx1F
+4SS/Cc2Z9yJuXiepzSH/G/vhdN5ROv12kJwA4FbwsFv5C1uCQleWiPngFixca9Nw
+lAd2X2Cp0/4D2XRq1M9dEbcYdrgAuyzt2ZToj3aFaYNGwjfHoLqSngOu6/d3KD1d
+i0b2A/9wnXo41kPwS73gU1Un2KKMkKqnczCQHdpopO6NjKaLhNcouRauLrgbrS5y
+A1CW+nxjkKVvWrP/VFBmapUpjE1C51J9P0/ub8tRr7H0xHdTQyufv01lmfkjUpVF
+n3GVf95l4seBFzD7r580aTx+dJztoHEGWrsWZTNJwo6IIlFOIbQlTXkgTmFtZSAo
+TXkgQ29tbWVudCkgPG1lQGV4YW1wbGUuY29tPohgBBMRAgAgBQJE3FrwAhsjBgsJ
+CAcDAgQVAggDBBYCAwECHgECF4AACgkQfKdEJrreq9fivACeLBcWErSQU4ZGQsje
+dhwfdst9cLQAnRETpwmqt/XvcGFVsOE28MUrUzOGuQENBETcWvAQBADNgIJ4nDlf
+gBOI/iqyNy08C9+MjxrqMylrj4TPn3rRk8wySr2myX+j9CML5EHOtsxANYeI9u7h
+OF11n5Z8fDht/WGJrNR7EtRhNN6mkKsPaEuO3fhz6EgwJYReUVzDJbvnV2fRCvQo
+EGaSntZGQaQzIzIL+/gMEFpEVRK1P2I3VwADBQP+K2Rmmkm3DonXFlUUDWWdhEF4
+b7fy5/IPj41PSSOdo0IP4dprFoe15Vs9gWOYvzcnjy+BbOwhVwsjE3F36hf04od3
+uTSM0dLS/xmpSvgbBh181T5c3W5aKT/daVhyxXJ4csxE+JCVKbaBubne0DPEuyZj
+rYlL5Lm0z3VhNCcR0LyISQQYEQIACQUCRNxa8AIbDAAKCRB8p0Qmut6r16Y3AJ9h
+umO5uT5yDcir3zwqUAxzBAkE4ACcCtGfb6usaTKnNXo+ZuLoHiOwIE4=
+=GCjU
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signature.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signature.txt
new file mode 100644 (file)
index 0000000..7e0f2d8
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+iD8DBQBE3JdufKdEJrreq9cRAhQ2AJ44XNI1uKRJB2KR3EvhUCcIKQwJ0wCfRsjv
+5pAbY1qypr+8m0ZHJJcpubE=
+=F0tK
+-----END PGP SIGNATURE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed.txt
new file mode 100644 (file)
index 0000000..0cb1968
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+iD8DBQFE3JNgfKdEJrreq9cRAm4lAJ48IbiwbO4ToXa2BrJaAZAFt43AiACZATs+
+gnfrwrK41BzMWmVRhtjB1Po=
+=5HXb
+-----END PGP SIGNATURE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed2.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed2.txt
new file mode 100644 (file)
index 0000000..746d676
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+owGbwMvMwCRYs9xFbde91dcZT79MYnC5M0XBwNDI2MTUzNzCEj+LKyQjVaGwNDM5
+WyGpKL88TyEtv0IhqzS3oFghvyy1SKEEKJ2TWFWpkJKfrjeqmD6KFSgxeRoVHU10
+KlJUUlmuqqahaWsft2VDgrayrp6Olnq8lbWNXU1dTHV0bC1Xhx0zKzhlwpKqIFPg
+ZIYZfd+39c/3v2Hbbi9tZT5P+o/7eyOGuVJ6rUZsLHVikwvyfuc/2lPb3Cv/CwA=
+=Cw7w
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/smime_subjectAltName.pem b/framework/Crypt/test/Horde/Crypt/fixtures/smime_subjectAltName.pem
new file mode 100644 (file)
index 0000000..ba80372
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAoSgAwIBAgIJAO0Hyim1pgtmMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
+BAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMQ8wDQYDVQQHEwZCb3N0b24x
+GjAYBgNVBAoTEVRoZSBIb3JkZSBQcm9qZWN0MB4XDTA3MTIyODIyMDQwN1oXDTA4
+MDEyNzIyMDQwN1owUjELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0
+dHMxDzANBgNVBAcTBkJvc3RvbjEaMBgGA1UEChMRVGhlIEhvcmRlIFByb2plY3Qw
+gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOKFM64qxeVqHmfxxhBzE/zCgqzA
+8c5JxJc2YYfKPCkQWGozRfUq45YDV4CsfJNgGMbjYdOe2cQOMpofdWgobD1xnKid
+mmwRvxTuqjOhjx020VFOMuq0t8tl7dYPrsQcNl+9g7Pg2mG2oC768NQHgP6GnZgH
+LPxkfEaDDVrqjSyFAgMBAAGjgfgwgfUwQQYDVR0RBDowOIERdGVzdDFAZXhhbXBs
+ZS5jb22BEXRlc3QyQGV4YW1wbGUuY29tgRB0ZXN0QGV4YW1wbGUuY29tMB0GA1Ud
+DgQWBBReeRq4wDuMdSnGMChBJCSzXmGxTTCBggYDVR0jBHsweYAUXnkauMA7jHUp
+xjAoQSQks15hsU2hVqRUMFIxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNo
+dXNldHRzMQ8wDQYDVQQHEwZCb3N0b24xGjAYBgNVBAoTEVRoZSBIb3JkZSBQcm9q
+ZWN0ggkA7QfKKbWmC2YwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQDN
+ZR6muF28V3EdsUAwIw7JuZSGMnmZJdKz+b/aNGbK40iZY29OGmEnhy3qBFQHTQY3
+jSpLszDH3l9ZSLNjhoOmnm1TydCMAW4/7dQny7ONx/CFS3r9vHxBNreNk+ZmJgDJ
+ASZwyX6IszJ1a3k4t+N3KY9+ZzSISIHJTPZZPzJiRA==
+-----END CERTIFICATE-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp.inc b/framework/Crypt/test/Horde/Crypt/pgp.inc
new file mode 100644 (file)
index 0000000..9e10eaf
--- /dev/null
@@ -0,0 +1,18 @@
+<?php
+/**
+ * @package Horde_Crypt
+ */
+
+require 'PEAR.php';
+require 'Horde/Util.php';
+require 'Horde/Browser.php';
+require dirname(__FILE__) . '/../Crypt/pgp.php';
+
+$_SERVER['HTTPS'] = 'on';
+$browser = &Browser::singleton();
+
+$pgp = new Horde_Crypt_pgp(array('program' => '/usr/bin/gpg',
+                                 'temp' => Util::getTempDir()));
+
+$pubkey = file_get_contents(dirname(__FILE__) . '/fixtures/pgp_public.asc');
+$privkey = file_get_contents(dirname(__FILE__) . '/fixtures/pgp_private.asc');
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_decrypt.phpt b/framework/Crypt/test/Horde/Crypt/pgp_decrypt.phpt
new file mode 100644 (file)
index 0000000..b6c94fa
--- /dev/null
@@ -0,0 +1,37 @@
+--TEST--
+Horde_Crypt_pgp::decrypt() message.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$crypt = file_get_contents(dirname(__FILE__) . '/fixtures/pgp_encrypted.txt');
+
+$decrypt = $pgp->decrypt($crypt,
+                         array('type' => 'message',
+                               'pubkey' => $pubkey,
+                               'privkey' => $privkey,
+                               'passphrase' => 'Secret'));
+if (is_a($decrypt, 'PEAR_Error')) {
+    var_dump($decrypt);
+} else {
+    echo $decrypt->message;
+}
+
+?>
+--EXPECT--
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_decrypt_symmetric.phpt b/framework/Crypt/test/Horde/Crypt/pgp_decrypt_symmetric.phpt
new file mode 100644 (file)
index 0000000..5204835
--- /dev/null
@@ -0,0 +1,35 @@
+--TEST--
+Horde_Crypt_pgp::decrypt() message.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$crypt = file_get_contents(dirname(__FILE__) . '/fixtures/pgp_encrypted_symmetric.txt');
+
+$decrypt = $pgp->decrypt($crypt,
+                         array('type' => 'message',
+                               'passphrase' => 'Secret'));
+if (is_a($decrypt, 'PEAR_Error')) {
+    var_dump($decrypt);
+} else {
+    echo $decrypt->message;
+}
+
+?>
+--EXPECT--
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_encrypt.phpt b/framework/Crypt/test/Horde/Crypt/pgp_encrypt.phpt
new file mode 100644 (file)
index 0000000..b7ad787
--- /dev/null
@@ -0,0 +1,31 @@
+--TEST--
+Horde_Crypt_pgp::encrypt() message.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$clear = file_get_contents(dirname(__FILE__) . '/fixtures/clear.txt');
+
+echo $pgp->encrypt($clear,
+                   array('type' => 'message',
+                         'recips' => array('me@example.com' => $pubkey)));
+
+?>
+--EXPECTF--
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v%d.%d.%d (%s)
+
+%s
+%s
+%s
+%s
+%s
+%s
+%s
+%s
+%s
+%s
+=%s
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_encrypt_symmetric.phpt b/framework/Crypt/test/Horde/Crypt/pgp_encrypt_symmetric.phpt
new file mode 100644 (file)
index 0000000..745e82b
--- /dev/null
@@ -0,0 +1,26 @@
+--TEST--
+Horde_Crypt_pgp::encrypt() message symmetric.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$clear = file_get_contents(dirname(__FILE__) . '/fixtures/clear.txt');
+
+echo $pgp->encrypt($clear,
+                   array('type' => 'message',
+                         'symmetric' => true,
+                         'passphrase' => 'Secret'));
+
+?>
+--EXPECTF--
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v%d.%d.%d (%s)
+
+%s
+%s
+%s
+%s
+=%s
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_encryptedSymmetrically.phpt b/framework/Crypt/test/Horde/Crypt/pgp_encryptedSymmetrically.phpt
new file mode 100644 (file)
index 0000000..0dbb939
--- /dev/null
@@ -0,0 +1,15 @@
+--TEST--
+Horde_Crypt_pgp::encryptedSymmetrically()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+var_dump($pgp->encryptedSymmetrically(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_encrypted.txt')));
+var_dump($pgp->encryptedSymmetrically(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_encrypted_symmetric.txt')));
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_generateRevocation.phpt b/framework/Crypt/test/Horde/Crypt/pgp_generateRevocation.phpt
new file mode 100644 (file)
index 0000000..ba94391
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+Horde_Crypt_pgp::generateRevocation()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+echo $pgp->generateRevocation($privkey, 'me@example.com', 'Secret');
+
+?>
+--EXPECTF--
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v%d.%d.%d (%s)
+Comment: A revocation certificate should follow
+
+%s
+%s
+=%s
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_getSignersKeyID.phpt b/framework/Crypt/test/Horde/Crypt/pgp_getSignersKeyID.phpt
new file mode 100644 (file)
index 0000000..98fa8e3
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+Horde_Crypt_pgp::getSignersKeyID()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+echo $pgp->getSignersKeyID(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signed.txt'));
+
+?>
+--EXPECT--
+BADEABD7
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_parsePGPData.phpt b/framework/Crypt/test/Horde/Crypt/pgp_parsePGPData.phpt
new file mode 100644 (file)
index 0000000..48accc6
--- /dev/null
@@ -0,0 +1,89 @@
+--TEST--
+Horde_Crypt_pgp::parsePGPData().
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+
+var_dump($pgp->parsePGPData(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signed.txt')));
+
+?>
+--EXPECT--
+array(3) {
+  [0]=>
+  array(2) {
+    ["type"]=>
+    int(2)
+    ["data"]=>
+    array(17) {
+      [0]=>
+      string(34) "-----BEGIN PGP SIGNED MESSAGE-----"
+      [1]=>
+      string(10) "Hash: SHA1"
+      [2]=>
+      string(0) ""
+      [3]=>
+      string(40) "0123456789012345678901234567890123456789"
+      [4]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [5]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [6]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [7]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [8]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [9]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [10]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [11]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [12]=>
+      string(89) "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
+      [13]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [14]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [15]=>
+      string(40) "0123456789012345678901234567890123456789"
+      [16]=>
+      string(33) "!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}"
+    }
+  }
+  [1]=>
+  array(2) {
+    ["type"]=>
+    int(5)
+    ["data"]=>
+    array(7) {
+      [0]=>
+      string(29) "-----BEGIN PGP SIGNATURE-----"
+      [1]=>
+      string(33) "Version: GnuPG v1.4.5 (GNU/Linux)"
+      [2]=>
+      string(0) ""
+      [3]=>
+      string(64) "iD8DBQFE3JNgfKdEJrreq9cRAm4lAJ48IbiwbO4ToXa2BrJaAZAFt43AiACZATs+"
+      [4]=>
+      string(24) "gnfrwrK41BzMWmVRhtjB1Po="
+      [5]=>
+      string(5) "=5HXb"
+      [6]=>
+      string(27) "-----END PGP SIGNATURE-----"
+    }
+  }
+  [2]=>
+  array(2) {
+    ["type"]=>
+    int(6)
+    ["data"]=>
+    array(1) {
+      [0]=>
+      string(0) ""
+    }
+  }
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketInformation.phpt b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketInformation.phpt
new file mode 100644 (file)
index 0000000..7eb8ee6
--- /dev/null
@@ -0,0 +1,133 @@
+--TEST--
+Horde_Crypt_pgp::pgpPacketInformation()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+var_dump($pgp->pgpPacketInformation($pubkey));
+var_dump($pgp->pgpPacketInformation($privkey));
+
+?>
+--EXPECT--
+array(4) {
+  ["public_key"]=>
+  array(3) {
+    ["created"]=>
+    string(10) "1155291888"
+    ["expires"]=>
+    string(1) "0"
+    ["size"]=>
+    string(4) "1024"
+  }
+  ["signature"]=>
+  array(2) {
+    ["id1"]=>
+    array(7) {
+      ["name"]=>
+      string(7) "My Name"
+      ["comment"]=>
+      string(10) "My Comment"
+      ["email"]=>
+      string(14) "me@example.com"
+      ["keyid"]=>
+      string(16) "7CA74426BADEABD7"
+      ["fingerprint"]=>
+      string(16) "7CA74426BADEABD7"
+      ["sig_7CA74426BADEABD7"]=>
+      array(4) {
+        ["keyid"]=>
+        string(16) "7CA74426BADEABD7"
+        ["fingerprint"]=>
+        string(16) "7CA74426BADEABD7"
+        ["created"]=>
+        string(10) "1155291888"
+        ["micalg"]=>
+        string(8) "pgp-sha1"
+      }
+      ["micalg"]=>
+      string(8) "pgp-sha1"
+    }
+    ["_SIGNATURE"]=>
+    array(2) {
+      ["micalg"]=>
+      string(8) "pgp-sha1"
+      ["sig_7CA74426BADEABD7"]=>
+      array(4) {
+        ["keyid"]=>
+        string(16) "7CA74426BADEABD7"
+        ["fingerprint"]=>
+        string(16) "7CA74426BADEABD7"
+        ["created"]=>
+        string(10) "1155291888"
+        ["micalg"]=>
+        string(8) "pgp-sha1"
+      }
+    }
+  }
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+}
+array(4) {
+  ["secret_key"]=>
+  array(3) {
+    ["created"]=>
+    string(10) "1155291888"
+    ["expires"]=>
+    string(1) "0"
+    ["size"]=>
+    string(4) "1024"
+  }
+  ["signature"]=>
+  array(2) {
+    ["id1"]=>
+    array(7) {
+      ["name"]=>
+      string(7) "My Name"
+      ["comment"]=>
+      string(10) "My Comment"
+      ["email"]=>
+      string(14) "me@example.com"
+      ["keyid"]=>
+      string(16) "7CA74426BADEABD7"
+      ["fingerprint"]=>
+      string(16) "7CA74426BADEABD7"
+      ["sig_7CA74426BADEABD7"]=>
+      array(4) {
+        ["keyid"]=>
+        string(16) "7CA74426BADEABD7"
+        ["fingerprint"]=>
+        string(16) "7CA74426BADEABD7"
+        ["created"]=>
+        string(10) "1155291888"
+        ["micalg"]=>
+        string(8) "pgp-sha1"
+      }
+      ["micalg"]=>
+      string(8) "pgp-sha1"
+    }
+    ["_SIGNATURE"]=>
+    array(2) {
+      ["micalg"]=>
+      string(8) "pgp-sha1"
+      ["sig_7CA74426BADEABD7"]=>
+      array(4) {
+        ["keyid"]=>
+        string(16) "7CA74426BADEABD7"
+        ["fingerprint"]=>
+        string(16) "7CA74426BADEABD7"
+        ["created"]=>
+        string(10) "1155291888"
+        ["micalg"]=>
+        string(8) "pgp-sha1"
+      }
+    }
+  }
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignature.phpt b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignature.phpt
new file mode 100644 (file)
index 0000000..4c45345
--- /dev/null
@@ -0,0 +1,82 @@
+--TEST--
+Horde_Crypt_pgp::pgpPacketSignature()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+var_dump($pgp->pgpPacketSignature($pubkey, 'me@example.com'));
+var_dump($pgp->pgpPacketSignature($privkey, 'me@example.com'));
+var_dump($pgp->pgpPacketSignature($privkey, 'foo@example.com'));
+
+?>
+--EXPECT--
+array(11) {
+  ["name"]=>
+  string(7) "My Name"
+  ["comment"]=>
+  string(10) "My Comment"
+  ["email"]=>
+  string(14) "me@example.com"
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+  ["sig_7CA74426BADEABD7"]=>
+  array(4) {
+    ["keyid"]=>
+    string(16) "7CA74426BADEABD7"
+    ["fingerprint"]=>
+    string(16) "7CA74426BADEABD7"
+    ["created"]=>
+    string(10) "1155291888"
+    ["micalg"]=>
+    string(8) "pgp-sha1"
+  }
+  ["micalg"]=>
+  string(8) "pgp-sha1"
+  ["key_type"]=>
+  string(10) "public_key"
+  ["key_created"]=>
+  string(10) "1155291888"
+  ["key_expires"]=>
+  string(1) "0"
+  ["key_size"]=>
+  string(4) "1024"
+}
+array(11) {
+  ["name"]=>
+  string(7) "My Name"
+  ["comment"]=>
+  string(10) "My Comment"
+  ["email"]=>
+  string(14) "me@example.com"
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+  ["sig_7CA74426BADEABD7"]=>
+  array(4) {
+    ["keyid"]=>
+    string(16) "7CA74426BADEABD7"
+    ["fingerprint"]=>
+    string(16) "7CA74426BADEABD7"
+    ["created"]=>
+    string(10) "1155291888"
+    ["micalg"]=>
+    string(8) "pgp-sha1"
+  }
+  ["micalg"]=>
+  string(8) "pgp-sha1"
+  ["key_type"]=>
+  string(10) "secret_key"
+  ["key_created"]=>
+  string(10) "1155291888"
+  ["key_expires"]=>
+  string(1) "0"
+  ["key_size"]=>
+  string(4) "1024"
+}
+array(0) {
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignatureByUidIndex.phpt b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignatureByUidIndex.phpt
new file mode 100644 (file)
index 0000000..92597b5
--- /dev/null
@@ -0,0 +1,82 @@
+--TEST--
+Horde_Crypt_pgp::pgpPacketSignatureByUidIndex()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+var_dump($pgp->pgpPacketSignatureByUidIndex($pubkey, 'id1'));
+var_dump($pgp->pgpPacketSignatureByUidIndex($privkey, 'id1'));
+var_dump($pgp->pgpPacketSignatureByUidIndex($privkey, 'id2'));
+
+?>
+--EXPECT--
+array(11) {
+  ["name"]=>
+  string(7) "My Name"
+  ["comment"]=>
+  string(10) "My Comment"
+  ["email"]=>
+  string(14) "me@example.com"
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+  ["sig_7CA74426BADEABD7"]=>
+  array(4) {
+    ["keyid"]=>
+    string(16) "7CA74426BADEABD7"
+    ["fingerprint"]=>
+    string(16) "7CA74426BADEABD7"
+    ["created"]=>
+    string(10) "1155291888"
+    ["micalg"]=>
+    string(8) "pgp-sha1"
+  }
+  ["micalg"]=>
+  string(8) "pgp-sha1"
+  ["key_type"]=>
+  string(10) "public_key"
+  ["key_created"]=>
+  string(10) "1155291888"
+  ["key_expires"]=>
+  string(1) "0"
+  ["key_size"]=>
+  string(4) "1024"
+}
+array(11) {
+  ["name"]=>
+  string(7) "My Name"
+  ["comment"]=>
+  string(10) "My Comment"
+  ["email"]=>
+  string(14) "me@example.com"
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+  ["sig_7CA74426BADEABD7"]=>
+  array(4) {
+    ["keyid"]=>
+    string(16) "7CA74426BADEABD7"
+    ["fingerprint"]=>
+    string(16) "7CA74426BADEABD7"
+    ["created"]=>
+    string(10) "1155291888"
+    ["micalg"]=>
+    string(8) "pgp-sha1"
+  }
+  ["micalg"]=>
+  string(8) "pgp-sha1"
+  ["key_type"]=>
+  string(10) "secret_key"
+  ["key_created"]=>
+  string(10) "1155291888"
+  ["key_expires"]=>
+  string(1) "0"
+  ["key_size"]=>
+  string(4) "1024"
+}
+array(0) {
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_pgpPrettyKey.phpt b/framework/Crypt/test/Horde/Crypt/pgp_pgpPrettyKey.phpt
new file mode 100644 (file)
index 0000000..bbb9d56
--- /dev/null
@@ -0,0 +1,34 @@
+--TEST--
+Horde_Crypt_pgp::pgpPrettyKey()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+echo $pgp->pgpPrettyKey($pubkey);
+echo $pgp->pgpPrettyKey($privkey);
+
+?>
+--EXPECT--
+Name:             My Name
+Key Type:         Public Key
+Key Creation:     08/11/06
+Expiration Date:  [Never]
+Key Length:       1024 Bytes
+Comment:          My Comment
+E-Mail:           me@example.com
+Hash-Algorithm:   pgp-sha1
+Key ID:           0xBADEABD7
+Key Fingerprint:  966F 4BA9 569D E6F6 5E82  5397 7CA7 4426 BADE ABD7
+
+Name:             My Name
+Key Type:         Private Key
+Key Creation:     08/11/06
+Expiration Date:  [Never]
+Key Length:       1024 Bytes
+Comment:          My Comment
+E-Mail:           me@example.com
+Hash-Algorithm:   pgp-sha1
+Key ID:           0xBADEABD7
+Key Fingerprint:  966F 4BA9 569D E6F6 5E82  5397 7CA7 4426 BADE ABD7
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_publicKeyMIMEPart.phpt b/framework/Crypt/test/Horde/Crypt/pgp_publicKeyMIMEPart.phpt
new file mode 100644 (file)
index 0000000..886a656
--- /dev/null
@@ -0,0 +1,53 @@
+--TEST--
+Horde_Crypt_pgp::publicKeyMIMEPart().
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+class PrefsStub {
+    function getValue($pref)
+    {
+        if ($pref == 'sending_charset') {
+            return 'iso-8859-1';
+        }
+        die('unknown preference');
+    }
+}
+
+require_once 'Horde/NLS.php';
+require 'pgp.inc';
+
+$prefs = new PrefsStub;
+$mime_part = $pgp->publicKeyMIMEPart($pubkey);
+echo $mime_part->getType() . "\n\n";
+echo $mime_part->getContents();
+
+?>
+--EXPECTF--
+application/pgp-keys
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v%d.%d.%d (GNU/Linux)
+
+mQGiBETcWvARBADNitbvsWy5/hhV+WcU2ttmtXkAj2DqJVgJdGS2RH8msO0roG5j
+CQK/e0iMJki5lfdgxvxxWgStYMnfF5ftgWA7JV+BZUzJt12Lobm0zdENv2TqL2vc
+xlPTmEGsvfPDTbY+Gr3jvuODboXat7bUn2E723WXPdh2A7KNNnLou7JF2wCgiKs/
+RqNKM/Zm01PxLbQ+rs9ghd0D/jLUfJeYWySoDsvfO8e4UyDxDVTBLkkdw3XzLx1F
+4SS/Cc2Z9yJuXiepzSH/G/vhdN5ROv12kJwA4FbwsFv5C1uCQleWiPngFixca9Nw
+lAd2X2Cp0/4D2XRq1M9dEbcYdrgAuyzt2ZToj3aFaYNGwjfHoLqSngOu6/d3KD1d
+i0b2A/9wnXo41kPwS73gU1Un2KKMkKqnczCQHdpopO6NjKaLhNcouRauLrgbrS5y
+A1CW+nxjkKVvWrP/VFBmapUpjE1C51J9P0/ub8tRr7H0xHdTQyufv01lmfkjUpVF
+n3GVf95l4seBFzD7r580aTx+dJztoHEGWrsWZTNJwo6IIlFOIbQlTXkgTmFtZSAo
+TXkgQ29tbWVudCkgPG1lQGV4YW1wbGUuY29tPohgBBMRAgAgBQJE3FrwAhsjBgsJ
+CAcDAgQVAggDBBYCAwECHgECF4AACgkQfKdEJrreq9fivACeLBcWErSQU4ZGQsje
+dhwfdst9cLQAnRETpwmqt/XvcGFVsOE28MUrUzOGuQENBETcWvAQBADNgIJ4nDlf
+gBOI/iqyNy08C9+MjxrqMylrj4TPn3rRk8wySr2myX+j9CML5EHOtsxANYeI9u7h
+OF11n5Z8fDht/WGJrNR7EtRhNN6mkKsPaEuO3fhz6EgwJYReUVzDJbvnV2fRCvQo
+EGaSntZGQaQzIzIL+/gMEFpEVRK1P2I3VwADBQP+K2Rmmkm3DonXFlUUDWWdhEF4
+b7fy5/IPj41PSSOdo0IP4dprFoe15Vs9gWOYvzcnjy+BbOwhVwsjE3F36hf04od3
+uTSM0dLS/xmpSvgbBh181T5c3W5aKT/daVhyxXJ4csxE+JCVKbaBubne0DPEuyZj
+rYlL5Lm0z3VhNCcR0LyISQQYEQIACQUCRNxa8AIbDAAKCRB8p0Qmut6r16Y3AJ9h
+umO5uT5yDcir3zwqUAxzBAkE4ACcCtGfb6usaTKnNXo+ZuLoHiOwIE4=
+=GCjU
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_sign.phpt b/framework/Crypt/test/Horde/Crypt/pgp_sign.phpt
new file mode 100644 (file)
index 0000000..5cc91c0
--- /dev/null
@@ -0,0 +1,55 @@
+--TEST--
+Horde_Crypt_pgp::encrypt() signature.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$clear = file_get_contents(dirname(__FILE__) . '/fixtures/clear.txt');
+
+echo $pgp->encrypt($clear,
+                   array('type' => 'signature',
+                         'pubkey' => $pubkey,
+                         'privkey' => $privkey,
+                         'passphrase' => 'Secret'));
+echo $pgp->encrypt($clear,
+                   array('type' => 'signature',
+                         'pubkey' => $pubkey,
+                         'privkey' => $privkey,
+                         'passphrase' => 'Secret',
+                         'sigtype' => 'cleartext'));
+
+?>
+--EXPECTF--
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v%d.%d.%d (%s)
+
+%s
+%s
+=%s
+-----END PGP SIGNATURE-----
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v%d.%d.%d (%s)
+
+%s
+%s
+=%s
+-----END PGP SIGNATURE-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_skipif.inc b/framework/Crypt/test/Horde/Crypt/pgp_skipif.inc
new file mode 100644 (file)
index 0000000..53e230f
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+/**
+ * @package Horde_Crypt
+ */
+
+if (!is_executable('/usr/bin/gpg')) {
+    echo 'skip No GPG binary at /usr/bin/gpg';
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_verify.phpt b/framework/Crypt/test/Horde/Crypt/pgp_verify.phpt
new file mode 100644 (file)
index 0000000..0d4f960
--- /dev/null
@@ -0,0 +1,41 @@
+--TEST--
+Horde_Crypt_pgp::decrypt() signature.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+// Fake preferences class to set the timezone to a consistent value.
+class mock_prefs {
+    function getValue($key)
+    {
+        return 'GMT';
+    }
+}
+$GLOBALS['prefs'] = new mock_prefs();
+
+require_once 'Horde/NLS.php';
+NLS::setTimezone();
+require 'pgp.inc';
+
+echo $pgp->decrypt(file_get_contents(dirname(__FILE__) . '/fixtures/clear.txt'),
+                   array('type' => 'detached-signature',
+                         'pubkey' => $pubkey,
+                         'signature' => file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signature.txt')));
+
+echo $pgp->decrypt(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signed.txt'),
+                   array('type' => 'signature',
+                         'pubkey' => $pubkey));
+
+echo $pgp->decrypt(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signed2.txt'),
+                   array('type' => 'signature',
+                         'pubkey' => $pubkey));
+
+?>
+--EXPECT--
+gpg: Signature made Fri Aug 11 14:42:54 2006 GMT using DSA key ID BADEABD7
+gpg: Good signature from "My Name (My Comment) <me@example.com>"
+gpg: Signature made Fri Aug 11 14:25:36 2006 GMT using DSA key ID BADEABD7
+gpg: Good signature from "My Name (My Comment) <me@example.com>"
+gpg: Signature made Fri Aug 11 14:28:48 2006 GMT using DSA key ID BADEABD7
+gpg: Good signature from "My Name (My Comment) <me@example.com>"
\ No newline at end of file
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_verifyPassphrase.phpt b/framework/Crypt/test/Horde/Crypt/pgp_verifyPassphrase.phpt
new file mode 100644 (file)
index 0000000..33c5e94
--- /dev/null
@@ -0,0 +1,16 @@
+--TEST--
+Horde_Crypt_pgp::verifyPassphrase().
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+
+var_dump($pgp->verifyPassphrase($pubkey, $privkey, 'Secret'));
+var_dump($pgp->verifyPassphrase($pubkey, $privkey, 'Wrong'));
+
+?>
+--EXPECT--
+bool(true)
+bool(false)
diff --git a/framework/Crypt/test/Horde/Crypt/smime.inc b/framework/Crypt/test/Horde/Crypt/smime.inc
new file mode 100644 (file)
index 0000000..eb406af
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+/**
+ * @package Horde_Crypt
+ */
+
+require 'PEAR.php';
+require 'Horde/String.php';
+require 'Horde/Util.php';
+require dirname(__FILE__) . '/../Crypt/smime.php';
+
+$smime = new Horde_Crypt_smime(array('temp' => Util::getTempDir()));
diff --git a/framework/Crypt/test/Horde/Crypt/smime_skipif.inc b/framework/Crypt/test/Horde/Crypt/smime_skipif.inc
new file mode 100644 (file)
index 0000000..2dd7baf
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+/**
+ * @package Horde_Crypt
+ */
+
+if (!extension_loaded('openssl')) {
+    echo 'skip No openssl support in PHP';
+}
diff --git a/framework/Crypt/test/Horde/Crypt/smime_subjectAltName.phpt b/framework/Crypt/test/Horde/Crypt/smime_subjectAltName.phpt
new file mode 100644 (file)
index 0000000..adc4d1e
--- /dev/null
@@ -0,0 +1,17 @@
+--TEST--
+Horde_Crypt_smime::getEmailFromKey() with subjectAltName
+--SKIPIF--
+<?php
+echo "skip openssl_x509_parse() doesn't return subjectAltName of the example cert.";
+require 'smime_skipif.inc';
+?>
+--FILE--
+<?php
+
+require 'smime.inc';
+$key = file_get_contents(dirname(__FILE__) . '/fixtures/smime_subjectAltName.pem');
+echo $smime->getEmailFromKey($key);
+
+?>
+--EXPECT--
+test1@example.com