From: Michael M Slusarz Date: Sun, 16 Nov 2008 21:05:55 +0000 (-0700) Subject: Move Horde_Crypt from CVS. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=b038b6ac295e05fb5d8e9247b52379dd17b59b40;p=horde.git Move Horde_Crypt from CVS. Due to Horde_Mime changes. --- diff --git a/framework/Crypt/lib/Horde/Crypt.php b/framework/Crypt/lib/Horde/Crypt.php new file mode 100644 index 000000000..3428e735f --- /dev/null +++ b/framework/Crypt/lib/Horde/Crypt.php @@ -0,0 +1,176 @@ + + * @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 index 000000000..870b5c490 --- /dev/null +++ b/framework/Crypt/lib/Horde/Crypt/pgp.php @@ -0,0 +1,1705 @@ + + * @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. + *
+     * Return array:
+     * Key            Value
+     * --------------------------
+     * 'public'   =>  Public Key
+     * 'private'  =>  Private Key
+     * 
+ */ + 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. + *
+     * 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
+     *         )
+     *     )
+     * )
+     * 
+ * + * 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. + *
+     * 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
+     * 
+ */ + 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. + *
+     * 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.
+     * 
+ */ + 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. + *
+     * 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)
+     * 
+ * + * @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. + *
+     * 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)
+     * 
+ * + * @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. + *
+     * 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)
+     * 
+ * + * @return stdClass An object with the following properties, or PEAR_Error + * on error: + *
+     * 'message'     -  The decrypted message.
+     * 'sig_result'  -  The result of the signature test.
+     * 
+ */ + 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. + *
+     * Parameters:
+     * ===========
+     * 'type'       =>  'signature' or 'detached-signature' (REQUIRED)
+     * 'pubkey'     =>  PGP public key. (REQUIRED)
+     * 'signature'  =>  PGP signature block. (REQUIRED for detached signature)
+     * 
+ * + * @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 index 000000000..2a8294e62 --- /dev/null +++ b/framework/Crypt/lib/Horde/Crypt/smime.php @@ -0,0 +1,1377 @@ + + * @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. + *
+     * 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)
+     * 
+ * + * @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. + *
+     * 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)
+     * 
+ * + * @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. + *
+     * Parameters:
+     * ===========
+     * 'type'        =>  'message' (REQUIRED)
+     * 'pubkey'      =>  public key. (REQUIRED)
+     * 'privkey'     =>  private key. (REQUIRED)
+     * 'passphrase'  =>  Passphrase for Key. (REQUIRED)
+     * 
+ * + * @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 '
' . _("Unable to extract certificate details") . '
'; + } + $certificate = $cert_details['certificate']; + + $text = '
';
+
+        /* Subject (a/k/a Certificate Owner) */
+        if (isset($certificate['subject'])) {
+            $text .= "" . _("Certificate Owner") . ":\n";
+
+            foreach ($certificate['subject'] as $key => $value) {
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("  %s: %s\n", $fieldnames[$key], $value);
+                } else {
+                    $text .= sprintf("  *%s: %s\n", $key, $value);
+                }
+            }
+            $text .= "\n";
+        }
+
+        /* Issuer */
+        if (isset($certificate['issuer'])) {
+            $text .= "" . _("Issuer") . ":\n";
+
+            foreach ($certificate['issuer'] as $key => $value) {
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("  %s: %s\n", $fieldnames[$key], $value);
+                } else {
+                    $text .= sprintf("  *%s: %s\n", $key, $value);
+                }
+            }
+            $text .= "\n";
+        }
+
+        /* Dates  */
+        $text .= "" . _("Validity") . ":\n";
+        $text .= sprintf("  %s: %s\n", _("Not Before"), strftime("%x %X", $certificate['validity']['notbefore']));
+        $text .= sprintf("  %s: %s\n", _("Not After"), strftime("%x %X", $certificate['validity']['notafter']));
+        $text .= "\n";
+
+        /* Certificate Owner - Public Key Info */
+        $text .= "" . _("Public Key Info") . ":\n";
+        $text .= sprintf("  %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 .= "  " . 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      ";
+                    }
+                    $modulus_str .= substr($modulus_hex, $i, 2) . ':';
+                }
+
+                $text .= sprintf("    %s: %s\n", _("Modulus"), $modulus_str);
+            }
+
+            $text .= sprintf("    %s: %s\n", _("Exponent"), $certificate['subjectPublicKeyInfo']['subjectPublicKey']['publicExponent']);
+        }
+        $text .= "\n";
+
+        /* X509v3 extensions */
+        if (isset($certificate['extensions'])) {
+            $text .= "" . _("X509v3 extensions") . ":\n";
+
+            foreach ($certificate['extensions'] as $key => $value) {
+                if (is_array($value)) {
+                    $value = _("Unsupported Extension");
+                }
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("  %s:\n    %s\n", $fieldnames[$key], wordwrap($value, 40, "\n    "));
+                } else {
+                    $text .= sprintf("  %s:\n    %s\n", $key, wordwrap($value, 60, "\n    "));
+                }
+            }
+
+            $text .= "\n";
+        }
+
+        /* Certificate Details */
+        $text .= "" . _("Certificate Details") . ":\n";
+        $text .= sprintf("  %s: %d\n", _("Version"), $certificate['version']);
+        $text .= sprintf("  %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("  %s:\n      %s\n", $label, $fingerprint_str);
+        }
+        $text .= sprintf("  %s: %s\n", _("Signature Algorithm"), $cert_details['signatureAlgorithm']);
+        $text .= sprintf("  %s:", _("Signature"));
+
+        $sig_str = '';
+        for ($i = 0; $i < strlen($cert_details['signature']); $i++) {
+            if (($i % 16) == 0) {
+                $sig_str .= "\n      ";
+            }
+            $sig_str .= sprintf("%02x:", ord($cert_details['signature'][$i]));
+        }
+
+        return $text . $sig_str . "\n
"; + } + + /** + * 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. + *
+     * 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)
+     * 
+ * + * @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 index 000000000..4e96d1557 --- /dev/null +++ b/framework/Crypt/package.xml @@ -0,0 +1,139 @@ + + + Horde_Crypt + pear.horde.org + Horde Cryptography API + The Horde_Crypt:: class provides an API for various cryptographic systems + + + Michael Slusarz + slusarz + slusarz@horde.org + yes + + 2008-11-16 + + 0.1.0 + 0.1.0 + + + alpha + alpha + + LGPL + * Initial Horde 4 package. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.5.0 + + + Horde_Mime + pear.horde.org + + + Util + pear.horde.org + + + + + gettext + + + + + + + + + + + + + + 0.0.2 + 0.0.2 + + + alpha + alpha + + 2006-05-08 + LGPL + * 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) + + + + + 0.0.1 + 0.0.1 + + + alpha + alpha + + 2003-07-03 + LGPL + Initial release as a PEAR 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 index 000000000..e5caba078 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/bug_6601.phpt @@ -0,0 +1,34 @@ +--TEST-- +Bug #6601 +--SKIPIF-- + +--FILE-- +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 index 000000000..68e7ab2f8 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/bug_6601.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: GnuPG v1.4.9 (MingW32) + +mQGiBEf/ossRBACchuYgSW0Uz8DzoLTWAjx3leau0jdNq9ud4HzO9lzstkWh14k3 +Ld+KN9p4Z168NNj7fLtUKzwxLTTjDS9cMiwq5/gpP28ta0gyu8Ooq1i5U1BlwzKT +UFRduQ3jQKK2ZXa2jMeXdWcchYwMGj5qd/1YlM2MpGnKfOTYyZozs22OpwCg+6qR +mHAAcrY1QUN4zSlUlFIxAzUD+gPY0RQU0Ke1UE48pve6QcOlnRNFnj5XLjEzn2M8 +kkjbYBG2mEtXHALpYIQa7KrtfoD3nGLjf4wqzHrwYlWEu17jIdodCGI1F8dX7Mm+ +ARO8DbiX/fk1H53jGjrf4h4OEc6H6B3dMEKEO0S7ZNNp6QgudCjd+LHNJ0pjyzow +y84KA/9OXCkvZh/3xXBOPkO5LLmAw0ZZEEP7Sna75kw02G7S2elhRqWrLzVNzv2O +ppaj3oz73XYGxZAjsPOdmWYajCnycO7BqK7VFGw1EySldUmWB/Uadpeb8PwLpHFJ +d+mZy2INSHEAn484FHXHYSUYPOyNypGejwY2IOI0e/MjKqTHLbQfUmljaGFyZCBT +ZWxza3kgPHJzZWxza3lAYnUuZWR1PohmBBMRAgAmBQJH/6LLAhsjBQkJZgGABgsJ +CAcDAgQVAggDBBYCAwECHgECF4AACgkQBKZ7N/PAHUK25QCglppEGY1V0pK/hmuO +ocxXd23mJt0AnRIpLvWpVMjXfr/TYbrDXCaNv62FuQINBEf/ossQCADozRNDx7HC +KFHcX/ZZjW24Yc+GAHwxLq2Vtq/VyrlxCDFfw2tGFcKMa21f1WFCk2Z60GxiMAhq +2yE1ATkkAKHE0r9huz/NkuBqJXwbZzWV/9VkrDArGqTZDXdydpF0fADsqe64LPBU +b3kGI5UmJHkiyLD60EsldPM+sWe7VATRHjnikbu72J1JbLtvUxGoUB8LrwWcHIJr +Sql21XGUev8kabNhOOOUcj0uojvPlbmdj1aK9QuqcQ/fe2wK56s3LYM+u5mDb+uY +p+9pwAAfW2yqjx/gQrQZxXk0DZrmSHJzrBm6wvzWOwCXE8MM5S+Bp7xObjT0Hjiu +dZzuI5Q2MuB7AAMGCACvmsTPNwPKYDENZYujNSWWeES3byyLpOzSNt1ehed44nXT +ur5738BwCfHtdsqBVXZUS6k/2BabgxdDyBSzBn3utKCos7LFxNy4GmUNqU8cZgJj +gwA+poyTAJW4ndOlQbLGTOCOyaiXi1gKpv3RJQB1ADXNAazP29I+LWSWNOBeL1/p +EevcD1mCMM3tmoWOlBBm0HDg3UZtHg56D6cm5BVYjRODJNdOI8IDOwj4kONjIOS3 ++kfWgeJZmo8JatxhS2jBIGb5W033hI3a3EtUYrIRxsFAKT4QT/9YEDzZnIqxQIq8 +Q5AE5Ij6OjAKFO8bR1f7RiQsjgZK96GABBLgWsr6iE8EGBECAA8FAkf/ossCGwwF +CQlmAYAACgkQBKZ7N/PAHUKkFQCgnfx8fwXdCMrFJqV5ukOnzOr/q7YAniA3CIgF +dAM6dnvvMMNWckjddcwg +=ynmU +-----END PGP PUBLIC KEY BLOCK----- diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/clear.txt b/framework/Crypt/test/Horde/Crypt/fixtures/clear.txt new file mode 100644 index 000000000..9d32ba6ed --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/clear.txt @@ -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 index 000000000..985f41001 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted.txt @@ -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 index 000000000..5d170664d --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted_symmetric.txt @@ -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 index 000000000..a11a9795b --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_private.asc @@ -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 index 000000000..98b35854a --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_public.asc @@ -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 index 000000000..7e0f2d847 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signature.txt @@ -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 index 000000000..0cb19688b --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed.txt @@ -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 index 000000000..746d6766a --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed2.txt @@ -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 index 000000000..ba80372f0 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/fixtures/smime_subjectAltName.pem @@ -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 index 000000000..9e10eaf79 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp.inc @@ -0,0 +1,18 @@ + '/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 index 000000000..b6c94faed --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_decrypt.phpt @@ -0,0 +1,37 @@ +--TEST-- +Horde_Crypt_pgp::decrypt() message. +--SKIPIF-- + +--FILE-- +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 index 000000000..520483526 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_decrypt_symmetric.phpt @@ -0,0 +1,35 @@ +--TEST-- +Horde_Crypt_pgp::decrypt() message. +--SKIPIF-- + +--FILE-- +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 index 000000000..b7ad78745 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_encrypt.phpt @@ -0,0 +1,31 @@ +--TEST-- +Horde_Crypt_pgp::encrypt() message. +--SKIPIF-- + +--FILE-- +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 index 000000000..745e82b1b --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_encrypt_symmetric.phpt @@ -0,0 +1,26 @@ +--TEST-- +Horde_Crypt_pgp::encrypt() message symmetric. +--SKIPIF-- + +--FILE-- +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 index 000000000..0dbb939a9 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_encryptedSymmetrically.phpt @@ -0,0 +1,15 @@ +--TEST-- +Horde_Crypt_pgp::encryptedSymmetrically() +--SKIPIF-- + +--FILE-- +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 index 000000000..ba9439159 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_generateRevocation.phpt @@ -0,0 +1,20 @@ +--TEST-- +Horde_Crypt_pgp::generateRevocation() +--SKIPIF-- + +--FILE-- +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 index 000000000..98fa8e3d6 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_getSignersKeyID.phpt @@ -0,0 +1,13 @@ +--TEST-- +Horde_Crypt_pgp::getSignersKeyID() +--SKIPIF-- + +--FILE-- +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 index 000000000..48accc67b --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_parsePGPData.phpt @@ -0,0 +1,89 @@ +--TEST-- +Horde_Crypt_pgp::parsePGPData(). +--SKIPIF-- + +--FILE-- +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 index 000000000..7eb8ee6fb --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketInformation.phpt @@ -0,0 +1,133 @@ +--TEST-- +Horde_Crypt_pgp::pgpPacketInformation() +--SKIPIF-- + +--FILE-- +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 index 000000000..4c45345dc --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignature.phpt @@ -0,0 +1,82 @@ +--TEST-- +Horde_Crypt_pgp::pgpPacketSignature() +--SKIPIF-- + +--FILE-- +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 index 000000000..92597b5bb --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignatureByUidIndex.phpt @@ -0,0 +1,82 @@ +--TEST-- +Horde_Crypt_pgp::pgpPacketSignatureByUidIndex() +--SKIPIF-- + +--FILE-- +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 index 000000000..bbb9d5629 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_pgpPrettyKey.phpt @@ -0,0 +1,34 @@ +--TEST-- +Horde_Crypt_pgp::pgpPrettyKey() +--SKIPIF-- + +--FILE-- +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 index 000000000..886a6561b --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_publicKeyMIMEPart.phpt @@ -0,0 +1,53 @@ +--TEST-- +Horde_Crypt_pgp::publicKeyMIMEPart(). +--SKIPIF-- + +--FILE-- +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 index 000000000..5cc91c0c0 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_sign.phpt @@ -0,0 +1,55 @@ +--TEST-- +Horde_Crypt_pgp::encrypt() signature. +--SKIPIF-- + +--FILE-- +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 index 000000000..53e230f42 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_skipif.inc @@ -0,0 +1,8 @@ + +--FILE-- +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) " +gpg: Signature made Fri Aug 11 14:25:36 2006 GMT using DSA key ID BADEABD7 +gpg: Good signature from "My Name (My Comment) " +gpg: Signature made Fri Aug 11 14:28:48 2006 GMT using DSA key ID BADEABD7 +gpg: Good signature from "My Name (My Comment) " \ 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 index 000000000..33c5e948b --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/pgp_verifyPassphrase.phpt @@ -0,0 +1,16 @@ +--TEST-- +Horde_Crypt_pgp::verifyPassphrase(). +--SKIPIF-- + +--FILE-- +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 index 000000000..eb406af4e --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/smime.inc @@ -0,0 +1,11 @@ + 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 index 000000000..2dd7baf70 --- /dev/null +++ b/framework/Crypt/test/Horde/Crypt/smime_skipif.inc @@ -0,0 +1,8 @@ + +--FILE-- +getEmailFromKey($key); + +?> +--EXPECT-- +test1@example.com