From: Michael M Slusarz Date: Sun, 16 Nov 2008 22:58:38 +0000 (-0700) Subject: Rename to match new Horde_Crypt API X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=866c4c6f8e36a707d651fb02e35081629168c300;p=horde.git Rename to match new Horde_Crypt API --- diff --git a/imp/lib/Crypt/PGP.php b/imp/lib/Crypt/PGP.php deleted file mode 100644 index 8020b7c2a..000000000 --- a/imp/lib/Crypt/PGP.php +++ /dev/null @@ -1,761 +0,0 @@ - - * @package IMP - */ -class IMP_PGP extends Horde_Crypt_pgp { - - /** - * Constructor - */ - function IMP_PGP() - { - parent::Horde_Crypt_pgp(array('program' => $GLOBALS['conf']['utils']['gnupg'], - 'temp' => Horde::getTempDir())); - } - - /** - * Generate the personal Public/Private keypair and store in prefs. - * - * @param string $realname See Horde_Crypt_pgp:: - * @param string $email See Horde_Crypt_pgp:: - * @param string $passphrase See Horde_Crypt_pgp:: - * @param string $comment See Horde_Crypt_pgp:: - * @param string $keylength See Horde_Crypt_pgp:: - * - * @return PEAR_Error Returns PEAR_Error object on error. - */ - function generatePersonalKeys($name, $email, $passphrase, $comment = '', - $keylength = 1024) - { - $keys = $this->generateKey($name, $email, $passphrase, $comment, $keylength); - if (is_a($keys, 'PEAR_Error')) { - return $keys; - } - - /* Store the keys in the user's preferences. */ - $this->addPersonalPublicKey($keys['public']); - $this->addPersonalPrivateKey($keys['private']); - } - - /** - * Add the personal public key to the prefs. - * - * @param mixed $public_key The public key to add (either string or - * array). - */ - function addPersonalPublicKey($public_key) - { - $GLOBALS['prefs']->setValue('pgp_public_key', (is_array($public_key)) ? implode('', $public_key) : $public_key); - } - - /** - * Add the personal private key to the prefs. - * - * @param mixed $private_key The private key to add (either string or - * array). - */ - function addPersonalPrivateKey($private_key) - { - $GLOBALS['prefs']->setValue('pgp_private_key', (is_array($private_key)) ? implode('', $private_key) : $private_key); - } - - /** - * Get the personal public key from the prefs. - * - * @return string The personal PGP public key. - */ - function getPersonalPublicKey() - { - return $GLOBALS['prefs']->getValue('pgp_public_key'); - } - - /** - * Get the personal private key from the prefs. - * - * @return string The personal PGP private key. - */ - function getPersonalPrivateKey() - { - return $GLOBALS['prefs']->getValue('pgp_private_key'); - } - - /** - * Deletes the specified personal keys from the prefs. - */ - function deletePersonalKeys() - { - $GLOBALS['prefs']->setValue('pgp_public_key', ''); - $GLOBALS['prefs']->setValue('pgp_private_key', ''); - - $this->unsetPassphrase(); - } - - /** - * Add a public key to an address book. - * - * @param string $public_key An PGP public key. - * - * @return array See Horde_Crypt_pgp::pgpPacketInformation() - * Returns PEAR_Error or error. - */ - function addPublicKey($public_key) - { - /* Make sure the key is valid. */ - $key_info = $this->pgpPacketInformation($public_key); - if (!isset($key_info['signature'])) { - return PEAR::raiseError(_("Not a valid public key."), 'horde.error'); - } - - /* Remove the '_SIGNATURE' entry. */ - unset($key_info['signature']['_SIGNATURE']); - - /* Store all signatures that appear in the key. */ - foreach ($key_info['signature'] as $id => $sig) { - /* Check to make sure the key does not already exist in ANY - * address book and remove the id from the key_info for a correct - * output. */ - $result = $this->getPublicKey($sig['email'], null, false); - if (!is_a($result, 'PEAR_Error') && !empty($result)) { - unset($key_info['signature'][$id]); - continue; - } - - /* Add key to the user's address book. */ - $result = $GLOBALS['registry']->call('contacts/addField', array($sig['email'], $sig['name'], IMP_PGP_PUBKEY_FIELD, $public_key, $GLOBALS['prefs']->getValue('add_source'))); - if (is_a($result, 'PEAR_Error')) { - return $result; - } - } - - return $key_info; - } - - /** - * Retrieves a public key by e-mail. - * - * First, the key will be attempted to be retrieved from a user's address - * book(s). - * Second, if unsuccessful, the key is attempted to be retrieved via a - * public PGP keyserver. - * - * @param string $address The e-mail address to search by. - * @param string $fingerprint The fingerprint of the user's key. - * @param boolean $server Whether to check the publick key servers for - * the key. - * - * @return string The PGP public key requested. Returns PEAR_Error object - * on error. - */ - function getPublicKey($address, $fingerprint = null, $server = true) - { - $result = false; - - /* If there is a cache driver configured, try to get the public key - * from the cache. */ - if (!empty($GLOBALS['conf']['cache']['driver'])) { - $cache = &Horde_Cache::singleton($GLOBALS['conf']['cache']['driver'], Horde::getDriverConfig('cache', $GLOBALS['conf']['cache']['driver'])); - if (is_a($cache, 'PEAR_Error')) { - Horde::fatal($cache, __FILE__, __LINE__); - } else { - $result = $cache->get("PGPpublicKey_" . $address . $fingerprint, 3600); - } - } - - if ($result) { - Horde::logMessage('PGPpublicKey: ' . serialize($result), __FILE__, __LINE__, PEAR_LOG_DEBUG); - return $result; - } - - /* Try retrieving by e-mail only first. */ - $params = IMP_Compose::getAddressSearchParams(); - $result = $GLOBALS['registry']->call('contacts/getField', array($address, IMP_PGP_PUBKEY_FIELD, $params['sources'], false, true)); - - /* TODO: Retrieve by ID. */ - - /* See if the address points to the user's public key. */ - if (is_a($result, 'PEAR_Error')) { - require_once 'Horde/Identity.php'; - $identity = &Identity::singleton(array('imp', 'imp')); - $personal_pubkey = $this->getPersonalPublicKey(); - if (!empty($personal_pubkey) && $identity->hasAddress($address)) { - $result = $personal_pubkey; - } - } - - /* Try retrieving via a PGP public keyserver. */ - if ($server && is_a($result, 'PEAR_Error')) { - $result = $this->getFromPublicKeyserver($fingerprint, $address); - } - - /* Return now, if no public key found at all. */ - if (is_a($result, 'PEAR_Error')) { - return $result; - } - - /* If more than one public key is returned, just return the first in - * the array. There is no way of knowing which is the "preferred" key, - * if the keys are different. */ - if (is_array($result)) { - reset($result); - } - - /* If there is a cache driver configured and a cache object exists, - * store the public key in the cache. */ - if (is_object($cache)) { - $cache->set("PGPpublicKey_" . $address . $fingerprint, $result, 3600); - } - - return $result; - } - - /** - * Retrieves all public keys from a user's address book(s). - * - * @return array All PGP public keys available. Returns PEAR_Error object - * on error. - */ - function listPublicKeys() - { - $params = IMP_Compose::getAddressSearchParams(); - return (empty($params['sources'])) ? array() : $GLOBALS['registry']->call('contacts/getAllAttributeValues', array(IMP_PGP_PUBKEY_FIELD, $params['sources'])); - } - - /** - * Deletes a public key from a user's address book(s) by e-mail. - * - * @param string $email The e-mail address to delete. - * - * @return PEAR_Error Returns PEAR_Error object on error. - */ - function deletePublicKey($email) - { - $params = IMP_Compose::getAddressSearchParams(); - return $GLOBALS['registry']->call('contacts/deleteField', array($email, IMP_PGP_PUBKEY_FIELD, $params['sources'])); - } - - /** - * Parse a message into its PGP components. - * - * @param string $text See Horde_Crypt_pgp::parsePGPData() - * - * @return array Returns an array of Horde_Mime_Part objects. - * If there was no PGP data, returns false. - */ - function &parseMessage($text) - { - $result = $this->parsePGPData($text); - if (empty($result) || - ((count($result) == 1) && ($result[0]['type'] == PGP_ARMOR_TEXT))) { - $result = false; - return $result; - } - - $return_array = array(); - - reset($result); - do { - $block = current($result); - $temp_part = new Horde_Mime_Part(); - $temp_part->setContents(implode("\n", $block['data'])); - - /* Since private keys should NEVER be sent across email (in fact, - there is no MIME type to handle them) we will render them, if - someone is foolish enough to send one, in simple text. */ - if (($block['type'] == PGP_ARMOR_TEXT) || - ($block['type'] == PGP_ARMOR_PRIVATE_KEY)) { - $temp_part->setType('text/plain'); - } elseif ($block['type'] == PGP_ARMOR_PUBLIC_KEY) { - $temp_part->setType('application/pgp-keys'); - } elseif ($block['type'] == PGP_ARMOR_MESSAGE) { - $temp_part->setType('application/pgp-encrypted'); - } elseif ($block['type'] == PGP_ARMOR_SIGNED_MESSAGE) { - $temp_part->setType('application/pgp-signature'); - if (($block = next($result))) { - if (!empty($block) && ($block['type'] == PGP_ARMOR_SIGNATURE)) { - $temp_part->appendContents("\n" . implode("\n", $block['data'])); - } - } - } elseif ($block['type'] == PGP_ARMOR_SIGNATURE) { - continue; - } - - $return_array[] = $temp_part; - } while (next($result)); - - return $return_array; - } - - /** - * Renders a text message with PGP components. - * - * @param Horde_Mime_Part &$part The part containing the data to - * render. - * @param IMP_Contents &$contents The IMP_Contents:: module to use to - * output the text. - * - * @return string Returns the rendered text. - * If there was no PGP data, returns false. - */ - function parseMessageOutput(&$part, &$contents) - { - if (!($parts = &$this->parseMessage($part->getContents()))) { - return false; - } - - $text = ''; - - $base_ob = &$contents->getBaseObjectPtr(); - $addr = $base_ob->getFromAddress(); - - $message = new Horde_Mime_Message(); - foreach ($parts as $val) { - $message->addPart($val); - } - - $mc = new IMP_Contents($message, array('download' => 'download_attach', 'view' => 'view_attach'), array(&$contents)); - $message->buildMessage(); - - foreach ($message->getParts() as $val) { - /* If the part appears to be nothing but empty space, don't - display it. */ - if (($val->getBytes() < 5) && - !(rtrim($val->getContents()))) { - continue; - } - $v = &$mc->getMIMEViewer($val); - if (!is_a($v, 'IMP_Horde_Mime_Viewer_pgp')) { - $text .= $mc->formatStatusMsg(_("The message below has not been digitally signed or encrypted with PGP."), Horde::img('alerts/warning.png', _("Warning"), '', $GLOBALS['registry']->getImageDir('horde'))); - } - $text .= $mc->renderMIMEPart($val); - } - - return $text; - } - - /** - * Returns the signed data only for a plaintext signed Horde_Mime_Part. - * - * @param Horde_Mime_Part $mime_part The object with a plaintext PGP - * signed message in the contents. - * - * @return string The contents of the signed message. - */ - function getSignedMessage(&$mime_part) - { - $msg = ''; - - /* Just output signed data - remove all PGP headers. */ - $result = $this->parsePGPData($mime_part->getContents()); - foreach ($result as $block) { - if ($block['type'] == PGP_ARMOR_SIGNED_MESSAGE) { - $headerSeen = false; - $headerDone = false; - foreach ($block['data'] as $line) { - if ($headerDone) { - $msg .= $line . "\n"; - } elseif (strpos($line, "-----") === 0) { - $headerSeen = true; - continue; - } elseif ($headerSeen) { - /* There are some versions of GnuPG (like Version: - GnuPG v1.2.1 (MingW32)) which separate headers from - content with a line containing a blank, but this - isn't RFC conforming, so this isn't handled. - It results in a good signature with an empty - message. - The wrong code would be: - elseif (empty($line) || (strcmp($line, ' ') == 0)) - */ - $line = trim($line); - if (empty($line)) { - $headerDone = true; - } - } - } - } - } - - return rtrim($msg); - } - - /** - * Get a public key via a public PGP keyserver. - * - * @param string $fingerprint The fingerprint of the requested key. - * @param string $address The email address of the requested key. - * - * @return string See Horde_Crypt_pgp::getPublicKeyserver() - */ - function getFromPublicKeyserver($fingerprint, $address = null) - { - return $this->_keyserverConnect($fingerprint, 'get', $address); - } - - /** - * Send a public key to a public PGP keyserver. - * - * @param string $pubkey The PGP public key. - * - * @return string See Horde_Crypt_pgp::putPublicKeyserver() - */ - function sendToPublicKeyserver($pubkey) - { - return $this->_keyserverConnect($pubkey, 'put'); - } - - /** - * Connect to the keyservers - * - * @access private - * - * @param string $data The data to send to the keyserver. - * @param string $method The method to use - either 'get' or 'put'. - * @param string $additional Any additional data. - * - * @return string See Horde_Crypt_pgp::getPublicKeyserver() -or- - * Horde_Crypt_pgp::putPublicKeyserver(). - */ - function _keyserverConnect($data, $method, $additional = null) - { - global $conf; - - if (!empty($conf['utils']['gnupg_keyserver'])) { - $timeout = (empty($conf['utils']['gnupg_timeout'])) ? PGP_KEYSERVER_TIMEOUT : $conf['utils']['gnupg_timeout']; - if ($method == 'get') { - foreach ($conf['utils']['gnupg_keyserver'] as $server) { - $result = $this->getPublicKeyserver($data, $server, $timeout, $additional); - if (!is_a($result, 'PEAR_Error')) { - return $result; - } - } - return $result; - } else { - return $this->putPublicKeyserver($data, $conf['utils']['gnupg_keyserver'][0], $timeout); - } - } else { - return PEAR::raiseError(_("Public PGP keyserver support has been disabled."), 'horde.warning'); - } - } - - /** - * Verifies a signed message with a given public key. - * - * @param string $text The text to verify. - * @param string $address E-mail address of public key. - * @param string $signature A PGP signature block. - * - * @return string See Horde_Crypt_pgp::decryptSignature() -or- - * Horde_Crypt_pgp::decryptDetachedSignature(). - */ - function verifySignature($text, $address, $signature = '') - { - $fingerprint = null; - - /* Get fingerprint of key. */ - if (!empty($signature)) { - $packet_info = $this->pgpPacketInformation($signature); - if (isset($packet_info['fingerprint'])) { - $fingerprint = $packet_info['fingerprint']; - } - } else { - $fingerprint = $this->getSignersKeyID($text); - } - - $public_key = $this->getPublicKey($address, $fingerprint); - if (is_a($public_key, 'PEAR_Error')) { - return $public_key; - } - - if (!empty($signature)) { - $options = array('type' => 'detached-signature', 'signature' => $signature); - } else { - $options = array('type' => 'signature'); - } - $options['pubkey'] = $public_key; - - /* decrypt() returns a PEAR_Error object on error. */ - return $this->decrypt($text, $options); - } - - /** - * Decrypt a message with user's public/private keypair or a passphrase. - * - * @param string $text The text to decrypt. - * @param boolean $symmetric_hint Whether the text has been encrypted - * symmetrically. If null, we try to find - * out. - * @param boolean $passphrase Whether a passphrase has to be used. - * - * @return string The decrypted message. Returns PEAR_Error object on - * error. - */ - function decryptMessage($text, $symmetric_hint = null, $passphrase = true) - { - if (is_null($symmetric_hint)) { - $symmetric_hint = $this->encryptedSymmetrically($text); - } - - /* decrypt() returns a PEAR_Error object on error. */ - if (!$passphrase) { - return $this->decrypt($text, array('type' => 'message', 'no_passphrase' => true)); - } elseif ($symmetric_hint) { - return $this->decrypt($text, array('type' => 'message', 'passphrase' => $this->getSymmetricPassphrase())); - } else { - return $this->decrypt($text, array('type' => 'message', 'pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $this->getPassphrase())); - } - } - - /** - * Gets the user's passphrase from the session cache. - * - * @return string The passphrase, if set. - */ - function getPassphrase() - { - if (isset($_SESSION['imp']['pgp_passphrase'])) { - return Secret::read(IMP::getAuthKey(), $_SESSION['imp']['pgp_passphrase']); - } - } - - /** - * Gets the user's passphrase for symmetric encryption from the session - * cache. - * - * @return string The passphrase, if set. - */ - function getSymmetricPassphrase() - { - if (isset($_SESSION['imp']['pgp_sym_passphrase'])) { - return Secret::read(IMP::getAuthKey(), $_SESSION['imp']['pgp_sym_passphrase']); - } - } - - /** - * Store's the user's passphrase in the session cache. - * - * @param string $passphrase The user's passphrase. - * - * @return boolean Returns true if correct passphrase, false if incorrect. - */ - function storePassphrase($passphrase) - { - if ($this->verifyPassphrase($this->getPersonalPublicKey(), $this->getPersonalPrivateKey(), $passphrase) === false) { - return false; - } - - $_SESSION['imp']['pgp_passphrase'] = Secret::write(IMP::getAuthKey(), $passphrase); - return true; - } - - /** - * Store's the user's passphrase for symmetric encryption in the session - * cache. - * - * @param string $passphrase The user's passphrase. - * - * @return boolean True - */ - function storeSymmetricPassphrase($passphrase) - { - $_SESSION['imp']['pgp_sym_passphrase'] = Secret::write(IMP::getAuthKey(), $passphrase); - return true; - } - - /** - * Clear the passphrase from the session cache. - */ - function unsetPassphrase() - { - unset($_SESSION['imp']['pgp_passphrase']); - } - - /** - * Clear the passphrase for symmetric encryption from the session cache. - */ - function unsetSymmetricPassphrase() - { - unset($_SESSION['imp']['pgp_sym_passphrase']); - } - - /** - * Generates the javascript code for saving public keys. - * - * @param Horde_Mime_Part &$mime_part The part containing the public key. - * @param string $cache The part identifier. - * - * @return string The URL for saving public keys. - */ - function savePublicKeyURL(&$mime_part, $cache = null) - { - if (empty($cache)) { - require_once 'Horde/SessionObjects.php'; - $cacheSess = &Horde_SessionObjects::singleton(); - $oid = $cacheSess->storeOid($mime_part); - } - - return $this->getJSOpenWinCode('save_attachment_public_key', false, array('mimecache' => $oid)); - } - - /** - * Print out the link for the javascript PGP popup. - * - * @param string $actionid The ActionID to perform. - * @param mixed $reload If true, reload base window on close. If text, - * run this JS on close. If false, don't do - * anything on close. - * @param array $params Additional parameters needed for the reload - * page. - * - * @return string The javascript link. - */ - function getJSOpenWinCode($actionid, $reload = true, $params = array()) - { - $params['actionID'] = $actionid; - if (!empty($reload)) { - if (is_bool($reload)) { - $params['reload'] = html_entity_decode(Util::removeParameter(Horde::selfUrl(true), array('actionID'))); - } else { - require_once 'Horde/SessionObjects.php'; - $cacheSess = &Horde_SessionObjects::singleton(); - $params['passphrase_action'] = $cacheSess->storeOid($reload, false); - } - } - - return IMP::popupIMPString('pgp.php', $params, 450, 200); - } - - /** - * Provide the list of parameters needed for signing a message. - * - * @access private - * - * @return array The list of parameters needed by encrypt(). - */ - function _signParameters() - { - return array('pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $this->getPassphrase()); - } - - /** - * Provide the list of parameters needed for encrypting a message. - * - * @access private - * - * @param array $addresses The e-mail address of the keys to use for - * encryption. - * @param boolean $symmetric Whether to encrypt symmetrically. - * - * @return array The list of parameters needed by encrypt(). - * Returns PEAR_Error on error. - */ - function _encryptParameters($addresses, $symmetric = false) - { - if ($symmetric) { - return array('symmetric' => true, - 'passphrase' => $this->getSymmetricPassphrase()); - } - - $addr_list = array(); - - foreach ($addresses as $val) { - $addrOb = Horde_Mime_Address::bareAddress($val, $_SESSION['imp']['maildomain'], true); - $key_addr = array_pop($addrOb); - - /* Get the public key for the address. */ - $public_key = $this->getPublicKey($key_addr); - if (is_a($public_key, 'PEAR_Error')) { - return $public_key; - } - $addr_list[$key_addr] = $public_key; - } - - return array('recips' => $addr_list); - } - - /** - * Sign a Horde_Mime_Part using PGP using IMP default parameters. - * - * @param Horde_Mime_Part $mime_part The object to sign. - * - * @return Horde_Mime_Part See Horde_Crypt_pgp::signMIMEPart(). Returns - * PEAR_Error object on error. - */ - function IMPsignMIMEPart($mime_part) - { - return $this->signMIMEPart($mime_part, $this->_signParameters()); - } - - /** - * Encrypt a Horde_Mime_Part using PGP using IMP default parameters. - * - * @param Horde_Mime_Part $mime_part The object to encrypt. - * @param array $addresses The e-mail address of the keys to use for - * encryption. - * @param boolean $symmetric Whether to encrypt symmetrically. - * - * @return Horde_Mime_Part See Horde_Crypt_pgp::encryptMIMEPart(). Returns - * PEAR_Error object on error. - */ - function IMPencryptMIMEPart($mime_part, $addresses, $symmetric = false) - { - $params = $this->_encryptParameters($addresses, $symmetric); - if (is_a($params, 'PEAR_Error')) { - return $params; - } - return $this->encryptMIMEPart($mime_part, $params); - } - - /** - * Sign and Encrypt a Horde_Mime_Part using PGP using IMP default parameters. - * - * @param Horde_Mime_Part $mime_part The object to sign and encrypt. - * @param array $addresses The e-mail address of the keys to use for - * encryption. - * @param boolean $symmetric Whether to encrypt symmetrically. - * - * @return Horde_Mime_Part See Horde_Crypt_pgp::signAndencryptMIMEPart(). - * Returns PEAR_Error object on error. - */ - function IMPsignAndEncryptMIMEPart($mime_part, $addresses, - $symmetric = false) - { - $encrypt_params = $this->_encryptParameters($addresses, $symmetric); - if (is_a($encrypt_params, 'PEAR_Error')) { - return $encrypt_params; - } - return $this->signAndEncryptMIMEPart($mime_part, $this->_signParameters(), $encrypt_params); - } - - /** - * Generate a Horde_Mime_Part object, in accordance with RFC 2015/3156, that - * contains the user's public key. - * - * @return Horde_Mime_Part See Horde_Crypt_pgp::publicKeyMIMEPart(). - */ - function publicKeyMIMEPart() - { - return parent::publicKeyMIMEPart($this->getPersonalPublicKey()); - } - -} diff --git a/imp/lib/Crypt/SMIME.php b/imp/lib/Crypt/SMIME.php deleted file mode 100644 index 7c2494b8c..000000000 --- a/imp/lib/Crypt/SMIME.php +++ /dev/null @@ -1,473 +0,0 @@ - - * @package IMP - */ -class IMP_SMIME extends Horde_Crypt_smime { - - /** - * Constructor. - */ - function IMP_SMIME() - { - parent::Horde_Crypt_smime(array('temp' => Horde::getTempDir())); - } - - /** - * Add the personal public key to the prefs. - * - * @param mixed $key The public key to add (either string or array). - */ - function addPersonalPublicKey($key) - { - $GLOBALS['prefs']->setValue('smime_public_key', (is_array($key)) ? implode('', $key) : $key); - } - - /** - * Add the personal private key to the prefs. - * - * @param mixed $key The private key to add (either string or array). - */ - function addPersonalPrivateKey($key) - { - $GLOBALS['prefs']->setValue('smime_private_key', (is_array($key)) ? implode('', $key) : $key); - } - - /** - * Add the list of additional certs to the prefs. - * - * @param mixed $key The private key to add (either string or array). - */ - function addAdditionalCert($key) - { - $GLOBALS['prefs']->setValue('smime_additional_cert', (is_array($key)) ? implode('', $key) : $key); - } - - /** - * Get the personal public key from the prefs. - * - * @return string The personal S/MIME public key. - */ - function getPersonalPublicKey() - { - return $GLOBALS['prefs']->getValue('smime_public_key'); - } - - /** - * Get the personal private key from the prefs. - * - * @return string The personal S/MIME private key. - */ - function getPersonalPrivateKey() - { - return $GLOBALS['prefs']->getValue('smime_private_key'); - } - - /** - * Get any additional certificates from the prefs. - * - * @return string Additional signing certs for inclusion. - */ - function getAdditionalCert() - { - return $GLOBALS['prefs']->getValue('smime_additional_cert'); - } - - /** - * Deletes the specified personal keys from the prefs. - */ - function deletePersonalKeys() - { - $GLOBALS['prefs']->setValue('smime_public_key', ''); - $GLOBALS['prefs']->setValue('smime_private_key', ''); - $GLOBALS['prefs']->setValue('smime_additional_cert', ''); - $this->unsetPassphrase(); - } - - /** - * Add a public key to an address book. - * - * @param string $cert A public certificate to add. - * - * @return boolean True on successful add. - * Returns PEAR_Error or error. - */ - function addPublicKey($cert) - { - /* Make sure the certificate is valid. */ - $key_info = openssl_x509_parse($cert); - if (!is_array($key_info) || !isset($key_info['subject'])) { - return PEAR::raiseError(_("Not a valid public key."), 'horde.error'); - } - - /* Add key to the user's address book. */ - $email = $this->getEmailFromKey($cert); - if ($email === null) { - return PEAR::raiseError(_("No email information located in the public key."), 'horde.error'); - } - - /* Get the name corresponding to this key. */ - if (isset($key_info['subject']['CN'])) { - $name = $key_info['subject']['CN']; - } elseif (isset($key_info['subject']['OU'])) { - $name = $key_info['subject']['OU']; - } else { - return PEAR::raiseError(_("Not a valid public key."), 'horde.error'); - } - - $res = $GLOBALS['registry']->call('contacts/addField', array($email, $name, IMP_SMIME_PUBKEY_FIELD, $cert, $GLOBALS['prefs']->getValue('add_source'))); - if (is_a($res, 'PEAR_Error')) { - return $res; - } - - return $key_info; - } - - /** - * Returns the params needed to encrypt a message being sent to the - * specified email address. - * - * @param string $address The e-mail address of the recipient. - * - * @return array The list of parameters needed by encrypt(). - * Returns PEAR_Error object on error. - */ - function _encryptParameters($address) - { - /* We can only encrypt if we are sending to a single person. */ - $addrOb = Horde_Mime_Address::bareAddress($address, $_SESSION['imp']['maildomain'], true); - $key_addr = array_pop($addrOb); - - $public_key = $this->getPublicKey($key_addr); - if (is_a($public_key, 'PEAR_Error')) { - return $public_key; - } - - return array('type' => 'message', 'pubkey' => $public_key, 'email' => $address); - } - - /** - * Retrieves a public key by e-mail. - * The key will be retrieved from a user's address book(s). - * - * @param string $address The e-mail address to search for. - * - * @return string The S/MIME public key requested. - * Returns PEAR_Error object on error. - */ - function getPublicKey($address) - { - $params = IMP_Compose::getAddressSearchParams(); - $key = $GLOBALS['registry']->call('contacts/getField', array($address, IMP_SMIME_PUBKEY_FIELD, $params['sources'], false, true)); - - /* See if the address points to the user's public key. */ - if (is_a($key, 'PEAR_Error')) { - require_once 'Horde/Identity.php'; - $identity = &Identity::singleton(array('imp', 'imp')); - $personal_pubkey = $this->getPersonalPublicKey(); - if (!empty($personal_pubkey) && $identity->hasAddress($address)) { - return $personal_pubkey; - } - } - - /* If more than one public key is returned, just return the first in - * the array. There is no way of knowing which is the "preferred" key, - * if the keys are different. */ - if (is_array($key)) { - return reset($key); - } - - return $key; - } - - /** - * Retrieves all public keys from a user's address book(s). - * - * @return array All PGP public keys available. - * Returns PEAR_Error object on error. - */ - function listPublicKeys() - { - $params = IMP_Compose::getAddressSearchParams(); - return (empty($params['sources'])) ? array() : $GLOBALS['registry']->call('contacts/getAllAttributeValues', array(IMP_SMIME_PUBKEY_FIELD, $params['sources'])); - } - - /** - * Deletes a public key from a user's address book(s) by e-mail. - * - * @param string $email The e-mail address to delete. - * - * @return PEAR_Error Returns PEAR_Error object on error. - */ - function deletePublicKey($email) - { - $params = IMP_Compose::getAddressSearchParams(); - return $GLOBALS['registry']->call('contacts/deleteField', array($email, IMP_SMIME_PUBKEY_FIELD, $params['sources'])); - } - - /** - * Returns the parameters needed for signing a message. - * - * @access private - * - * @return array The list of parameters needed by encrypt(). - */ - function _signParameters() - { - return array( - 'type' => 'signature', - 'pubkey' => $this->getPersonalPublicKey(), - 'privkey' => $this->getPersonalPrivateKey(), - 'passphrase' => $this->getPassphrase(), - 'sigtype' => 'detach', - 'certs' => $this->getAdditionalCert() - ); - } - - /** - * Verifies a signed message with a given public key. - * - * @param string $text The text to verify. - * - * @return stdClass See Horde_Crypt_smime::verify(). - */ - function verifySignature($text) - { - return $this->verify($text, empty($GLOBALS['conf']['utils']['openssl_cafile']) ? array() : $GLOBALS['conf']['utils']['openssl_cafile']); - } - - - /** - * Decrypt a message with user's public/private keypair. - * - * @param string $text The text to decrypt. - * - * @return string See Horde_Crypt_smime::decrypt(). - * Returns PEAR_Error object on error. - */ - function decryptMessage($text) - { - /* decrypt() returns a PEAR_Error object on error. */ - return $this->decrypt($text, array('type' => 'message', 'pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $this->getPassphrase())); - } - - /** - * Gets the user's passphrase from the session cache. - * - * @return mixed The passphrase, if set. Returns false if the passphrase - * has not been loaded yet. Returns null if no passphrase - * is needed. - */ - function getPassphrase() - { - $private_key = $GLOBALS['prefs']->getValue('smime_private_key'); - if (empty($private_key)) { - return false; - } - - if (isset($_SESSION['imp']['smime']['passphrase'])) { - return Secret::read(IMP::getAuthKey(), $_SESSION['imp']['smime']['passphrase']); - } elseif (isset($_SESSION['imp']['smime']['null_passphrase'])) { - return ($_SESSION['imp']['smime']['null_passphrase']) ? null : false; - } else { - $res = $this->verifyPassphrase($private_key, null); - if (!isset($_SESSION['imp']['smime'])) { - $_SESSION['imp']['smime'] = array(); - } - $_SESSION['imp']['smime']['null_passphrase'] = ($res) ? null : false; - return $_SESSION['imp']['smime']['null_passphrase']; - } - } - - /** - * Store's the user's passphrase in the session cache. - * - * @param string $passphrase The user's passphrase. - * - * @return boolean Returns true if correct passphrase, false if incorrect. - */ - function storePassphrase($passphrase) - { - if ($this->verifyPassphrase($this->getPersonalPrivateKey(), $passphrase) === false) { - return false; - } - - if (!isset($_SESSION['imp']['smime'])) { - $_SESSION['imp']['smime'] = array(); - } - $_SESSION['imp']['smime']['passphrase'] = Secret::write(IMP::getAuthKey(), $passphrase); - - return true; - } - - /** - * Clear the passphrase from the session cache. - */ - function unsetPassphrase() - { - unset($_SESSION['imp']['smime']['null_passphrase']); - unset($_SESSION['imp']['smime']['passphrase']); - } - - /** - * Generates the javascript code for saving public keys. - * - * @param MIME_Part $mime_part The MIME_Part containing the public key. - * - * @return string The URL for saving public keys. - */ - function savePublicKeyURL($mime_part) - { - if (empty($cache)) { - require_once 'Horde/SessionObjects.php'; - $cacheSess = &Horde_SessionObjects::singleton(); - $oid = $cacheSess->storeOid($mime_part); - } - - return $this->getJSOpenWinCode('save_attachment_public_key', false, array('cert' => $oid)); - } - - /** - * Print out the link for the javascript S/MIME popup. - * - * @param integer $actionid The actionID to perform. - * @param mixed $reload If true, reload base window on close. If text, - * run this JS on close. If false, don't do - * anything on close. - * @param array $params Additional parameters needed for the reload - * page. - * - * @return string The javascript link. - */ - function getJSOpenWinCode($actionid, $reload = true, $params = array()) - { - $params['actionID'] = $actionid; - if (!empty($reload)) { - if (is_bool($reload)) { - $params['reload'] = html_entity_decode(Util::removeParameter(Horde::selfUrl(true), array('actionID'))); - } else { - require_once 'Horde/SessionObjects.php'; - $cacheSess = &Horde_SessionObjects::singleton(); - $params['passphrase_action'] = $cacheSess->storeOid($reload, false); - } - } - - return IMP::popupIMPString('smime.php', $params, 450, 200); - } - - /** - * Encrypt a MIME_Part using S/MIME using IMP defaults. - * - * @param MIME_Part $mime_part The MIME_Part object to encrypt. - * @param mixed $to_address The e-mail address of the key to use for - * encryption. - * - * @return MIME_Part See Horde_Crypt_smime::encryptMIMEPart(). Returns - * PEAR_Error on error. - */ - function IMPencryptMIMEPart($mime_part, $to_address) - { - $params = $this->_encryptParameters($to_address); - if (is_a($params, 'PEAR_Error')) { - return $params; - } - return $this->encryptMIMEPart($mime_part, $params); - } - - /** - * Sign a MIME_Part using S/MIME using IMP defaults. - * - * @param MIME_Part $mime_part The MIME_Part object to sign. - * - * @return MIME_Part See Horde_Crypt_smime::signMIMEPart(). Returns - * PEAR_Error on error. - */ - function IMPsignMIMEPart($mime_part) - { - return $this->signMIMEPart($mime_part, $this->_signParameters()); - } - - /** - * Sign and encrypt a MIME_Part using S/MIME using IMP defaults. - * - * @param MIME_Part $mime_part The MIME_Part object to sign and encrypt. - * @param string $to_address The e-mail address of the key to use for - * encryption. - * - * @return MIME_Part See Horde_Crypt_smime::signAndencryptMIMEPart(). - * Returns PEAR_Error on error. - */ - function IMPsignAndEncryptMIMEPart($mime_part, $to_address) - { - $encrypt_params = $this->_encryptParameters($to_address); - if (is_a($encrypt_params, 'PEAR_Error')) { - return $encrypt_params; - } - return $this->signAndEncryptMIMEPart($mime_part, $this->_signParameters(), $encrypt_params); - } - - /** - * Store the public/private/additional certificates in the preferences - * from a given PKCS 12 file. - * - * @param string $pkcs12 The PKCS 12 data. - * @param string $password The password of the PKCS 12 file. - * @param string $pkpass The password to use to encrypt the private key. - * - * @return boolean True on success, PEAR_Error on error. - */ - function addFromPKCS12($pkcs12, $password, $pkpass = null) - { - $openssl = IMP_SMIME::checkForOpenSSL(); - if (is_a($openssl, 'PEAR_Error')) { - return $openssl; - } - - $sslpath = (empty($GLOBALS['conf']['utils']['openssl_binary'])) ? null : $GLOBALS['conf']['utils']['openssl_binary']; - $params = array('sslpath' => $sslpath, 'password' => $password); - if (!empty($pkpass)) { - $params['newpassword'] = $pkpass; - } - $res = $this->parsePKCS12Data($pkcs12, $params); - if (is_a($res, 'PEAR_Error')) { - return $res; - } - - $this->addPersonalPrivateKey($res->private); - $this->addPersonalPublicKey($res->public); - $this->addAdditionalCert($res->certs); - - return true; - } - - /** - * Extract the contents from signed S/MIME data. - * - * @param string $data The signed S/MIME data. - * - * @return string The contents embedded in the signed data. - * Returns PEAR_Error on error. - */ - function extractSignedContents($data) - { - $sslpath = (empty($GLOBALS['conf']['utils']['openssl_binary'])) ? null : $GLOBALS['conf']['utils']['openssl_binary']; - return parent::extractSignedContents($data, $sslpath); - } - -} diff --git a/imp/lib/Crypt/pgp.php b/imp/lib/Crypt/pgp.php new file mode 100644 index 000000000..2b4482988 --- /dev/null +++ b/imp/lib/Crypt/pgp.php @@ -0,0 +1,752 @@ + + * @package IMP + */ +class IMP_Horde_Crypt_pgp extends Horde_Crypt_pgp +{ + /* Name of PGP public key field in addressbook. */ + const PUBKEY_FIELD = 'pgpPublicKey'; + + /** + * Constructor + */ + function __construct() + { + parent::__construct(array( + 'program' => $GLOBALS['conf']['utils']['gnupg'], + 'temp' => Horde::getTempDir() + )); + } + + /** + * Generate the personal Public/Private keypair and store in prefs. + * + * @param string $realname See Horde_Crypt_pgp:: + * @param string $email See Horde_Crypt_pgp:: + * @param string $passphrase See Horde_Crypt_pgp:: + * @param string $comment See Horde_Crypt_pgp:: + * @param string $keylength See Horde_Crypt_pgp:: + * + * @return PEAR_Error Returns PEAR_Error object on error. + */ + public function generatePersonalKeys($name, $email, $passphrase, + $comment = '', $keylength = 1024) + { + $keys = $this->generateKey($name, $email, $passphrase, $comment, $keylength); + if (is_a($keys, 'PEAR_Error')) { + return $keys; + } + + /* Store the keys in the user's preferences. */ + $this->addPersonalPublicKey($keys['public']); + $this->addPersonalPrivateKey($keys['private']); + } + + /** + * Add the personal public key to the prefs. + * + * @param mixed $public_key The public key to add (either string or + * array). + */ + public function addPersonalPublicKey($public_key) + { + $GLOBALS['prefs']->setValue('pgp_public_key', (is_array($public_key)) ? implode('', $public_key) : $public_key); + } + + /** + * Add the personal private key to the prefs. + * + * @param mixed $private_key The private key to add (either string or + * array). + */ + public function addPersonalPrivateKey($private_key) + { + $GLOBALS['prefs']->setValue('pgp_private_key', (is_array($private_key)) ? implode('', $private_key) : $private_key); + } + + /** + * Get the personal public key from the prefs. + * + * @return string The personal PGP public key. + */ + public function getPersonalPublicKey() + { + return $GLOBALS['prefs']->getValue('pgp_public_key'); + } + + /** + * Get the personal private key from the prefs. + * + * @return string The personal PGP private key. + */ + public function getPersonalPrivateKey() + { + return $GLOBALS['prefs']->getValue('pgp_private_key'); + } + + /** + * Deletes the specified personal keys from the prefs. + */ + public function deletePersonalKeys() + { + $GLOBALS['prefs']->setValue('pgp_public_key', ''); + $GLOBALS['prefs']->setValue('pgp_private_key', ''); + + $this->unsetPassphrase(); + } + + /** + * Add a public key to an address book. + * + * @param string $public_key An PGP public key. + * + * @return array See Horde_Crypt_pgp::pgpPacketInformation() + * Returns PEAR_Error or error. + */ + public function addPublicKey($public_key) + { + /* Make sure the key is valid. */ + $key_info = $this->pgpPacketInformation($public_key); + if (!isset($key_info['signature'])) { + return PEAR::raiseError(_("Not a valid public key."), 'horde.error'); + } + + /* Remove the '_SIGNATURE' entry. */ + unset($key_info['signature']['_SIGNATURE']); + + /* Store all signatures that appear in the key. */ + foreach ($key_info['signature'] as $id => $sig) { + /* Check to make sure the key does not already exist in ANY + * address book and remove the id from the key_info for a correct + * output. */ + $result = $this->getPublicKey($sig['email'], null, false); + if (!is_a($result, 'PEAR_Error') && !empty($result)) { + unset($key_info['signature'][$id]); + continue; + } + + /* Add key to the user's address book. */ + $result = $GLOBALS['registry']->call('contacts/addField', array($sig['email'], $sig['name'], self::PUBKEY_FIELD, $public_key, $GLOBALS['prefs']->getValue('add_source'))); + if (is_a($result, 'PEAR_Error')) { + return $result; + } + } + + return $key_info; + } + + /** + * Retrieves a public key by e-mail. + * + * First, the key will be attempted to be retrieved from a user's address + * book(s). + * Second, if unsuccessful, the key is attempted to be retrieved via a + * public PGP keyserver. + * + * @param string $address The e-mail address to search by. + * @param string $fingerprint The fingerprint of the user's key. + * @param boolean $server Whether to check the publick key servers for + * the key. + * + * @return string The PGP public key requested. Returns PEAR_Error object + * on error. + */ + public function getPublicKey($address, $fingerprint = null, $server = true) + { + $result = false; + + /* If there is a cache driver configured, try to get the public key + * from the cache. */ + if (!empty($GLOBALS['conf']['cache']['driver'])) { + $cache = &Horde_Cache::singleton($GLOBALS['conf']['cache']['driver'], Horde::getDriverConfig('cache', $GLOBALS['conf']['cache']['driver'])); + if (is_a($cache, 'PEAR_Error')) { + Horde::fatal($cache, __FILE__, __LINE__); + } else { + $result = $cache->get("PGPpublicKey_" . $address . $fingerprint, 3600); + } + } + + if ($result) { + Horde::logMessage('PGPpublicKey: ' . serialize($result), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return $result; + } + + /* Try retrieving by e-mail only first. */ + $params = IMP_Compose::getAddressSearchParams(); + $result = $GLOBALS['registry']->call('contacts/getField', array($address, self::PUBKEY_FIELD, $params['sources'], false, true)); + + /* TODO: Retrieve by ID. */ + + /* See if the address points to the user's public key. */ + if (is_a($result, 'PEAR_Error')) { + require_once 'Horde/Identity.php'; + $identity = &Identity::singleton(array('imp', 'imp')); + $personal_pubkey = $this->getPersonalPublicKey(); + if (!empty($personal_pubkey) && $identity->hasAddress($address)) { + $result = $personal_pubkey; + } + } + + /* Try retrieving via a PGP public keyserver. */ + if ($server && is_a($result, 'PEAR_Error')) { + $result = $this->getFromPublicKeyserver($fingerprint, $address); + } + + /* Return now, if no public key found at all. */ + if (is_a($result, 'PEAR_Error')) { + return $result; + } + + /* If more than one public key is returned, just return the first in + * the array. There is no way of knowing which is the "preferred" key, + * if the keys are different. */ + if (is_array($result)) { + reset($result); + } + + /* If there is a cache driver configured and a cache object exists, + * store the public key in the cache. */ + if (is_object($cache)) { + $cache->set("PGPpublicKey_" . $address . $fingerprint, $result, 3600); + } + + return $result; + } + + /** + * Retrieves all public keys from a user's address book(s). + * + * @return array All PGP public keys available. Returns PEAR_Error object + * on error. + */ + public function listPublicKeys() + { + $params = IMP_Compose::getAddressSearchParams(); + return (empty($params['sources'])) ? array() : $GLOBALS['registry']->call('contacts/getAllAttributeValues', array(self::PUBKEY_FIELD, $params['sources'])); + } + + /** + * Deletes a public key from a user's address book(s) by e-mail. + * + * @param string $email The e-mail address to delete. + * + * @return PEAR_Error Returns PEAR_Error object on error. + */ + public function deletePublicKey($email) + { + $params = IMP_Compose::getAddressSearchParams(); + return $GLOBALS['registry']->call('contacts/deleteField', array($email, self::PUBKEY_FIELD, $params['sources'])); + } + + /** + * Parse a message into its PGP components. + * + * @param string $text See Horde_Crypt_pgp::parsePGPData() + * + * @return array Returns an array of Horde_Mime_Part objects. + * If there was no PGP data, returns false. + */ + public function &parseMessage($text) + { + $result = $this->parsePGPData($text); + if (empty($result) || + ((count($result) == 1) && ($result[0]['type'] == self::ARMOR_TEXT))) { + $result = false; + return $result; + } + + $return_array = array(); + + reset($result); + do { + $block = current($result); + $temp_part = new Horde_Mime_Part(); + $temp_part->setContents(implode("\n", $block['data'])); + + /* Since private keys should NEVER be sent across email (in fact, + there is no MIME type to handle them) we will render them, if + someone is foolish enough to send one, in simple text. */ + if (($block['type'] == self::ARMOR_TEXT) || + ($block['type'] == self::ARMOR_PRIVATE_KEY)) { + $temp_part->setType('text/plain'); + } elseif ($block['type'] == self::ARMOR_PUBLIC_KEY) { + $temp_part->setType('application/pgp-keys'); + } elseif ($block['type'] == self::ARMOR_MESSAGE) { + $temp_part->setType('application/pgp-encrypted'); + } elseif ($block['type'] == self::ARMOR_SIGNED_MESSAGE) { + $temp_part->setType('application/pgp-signature'); + if (($block = next($result))) { + if (!empty($block) && ($block['type'] == self::ARMOR_SIGNATURE)) { + $temp_part->appendContents("\n" . implode("\n", $block['data'])); + } + } + } elseif ($block['type'] == self::ARMOR_SIGNATURE) { + continue; + } + + $return_array[] = $temp_part; + } while (next($result)); + + return $return_array; + } + + /** + * Renders a text message with PGP components. + * + * @param Horde_Mime_Part &$part The part containing the data to + * render. + * @param IMP_Contents &$contents The IMP_Contents:: module to use to + * output the text. + * + * @return string Returns the rendered text. + * If there was no PGP data, returns false. + */ + function parseMessageOutput(&$part, &$contents) + { + if (!($parts = &$this->parseMessage($part->getContents()))) { + return false; + } + + $text = ''; + + // TODO + $base_ob = &$contents->getBaseObjectPtr(); + $addr = $base_ob->getFromAddress(); + + $message = new Horde_Mime_Message(); + foreach ($parts as $val) { + $message->addPart($val); + } + + // TODO + $mc = new IMP_Contents($message, array('download' => 'download_attach', 'view' => 'view_attach'), array(&$contents)); + $message->buildMessage(); + + foreach ($message->getParts() as $val) { + /* If the part appears to be nothing but empty space, don't + display it. */ + if (($val->getBytes() < 5) && + !(rtrim($val->getContents()))) { + continue; + } + $v = &$mc->getMIMEViewer($val); + if (!is_a($v, 'IMP_Horde_Mime_Viewer_pgp')) { + $text .= $mc->formatStatusMsg(_("The message below has not been digitally signed or encrypted with PGP."), Horde::img('alerts/warning.png', _("Warning"), '', $GLOBALS['registry']->getImageDir('horde'))); + } + $text .= $mc->renderMIMEPart($val); + } + + return $text; + } + + /** + * Returns the signed data only for a plaintext signed Horde_Mime_Part. + * + * @param Horde_Mime_Part $mime_part The object with a plaintext PGP + * signed message in the contents. + * + * @return string The contents of the signed message. + */ + public function getSignedMessage(&$mime_part) + { + $msg = ''; + + /* Just output signed data - remove all PGP headers. */ + $result = $this->parsePGPData($mime_part->getContents()); + foreach ($result as $block) { + if ($block['type'] == self::ARMOR_SIGNED_MESSAGE) { + $headerSeen = false; + $headerDone = false; + foreach ($block['data'] as $line) { + if ($headerDone) { + $msg .= $line . "\n"; + } elseif (strpos($line, "-----") === 0) { + $headerSeen = true; + continue; + } elseif ($headerSeen) { + /* There are some versions of GnuPG (like Version: + GnuPG v1.2.1 (MingW32)) which separate headers from + content with a line containing a blank, but this + isn't RFC conforming, so this isn't handled. + It results in a good signature with an empty + message. + The wrong code would be: + elseif (empty($line) || (strcmp($line, ' ') == 0)) + */ + $line = trim($line); + if (empty($line)) { + $headerDone = true; + } + } + } + } + } + + return rtrim($msg); + } + + /** + * Get a public key via a public PGP keyserver. + * + * @param string $fingerprint The fingerprint of the requested key. + * @param string $address The email address of the requested key. + * + * @return string See Horde_Crypt_pgp::getPublicKeyserver() + */ + public function getFromPublicKeyserver($fingerprint, $address = null) + { + return $this->_keyserverConnect($fingerprint, 'get', $address); + } + + /** + * Send a public key to a public PGP keyserver. + * + * @param string $pubkey The PGP public key. + * + * @return string See Horde_Crypt_pgp::putPublicKeyserver() + */ + public function sendToPublicKeyserver($pubkey) + { + return $this->_keyserverConnect($pubkey, 'put'); + } + + /** + * Connect to the keyservers + * + * @param string $data The data to send to the keyserver. + * @param string $method The method to use - either 'get' or 'put'. + * @param string $additional Any additional data. + * + * @return string See Horde_Crypt_pgp::getPublicKeyserver() -or- + * Horde_Crypt_pgp::putPublicKeyserver(). + */ + protected function _keyserverConnect($data, $method, $additional = null) + { + global $conf; + + if (!empty($conf['utils']['gnupg_keyserver'])) { + $timeout = (empty($conf['utils']['gnupg_timeout'])) ? PGP_KEYSERVER_TIMEOUT : $conf['utils']['gnupg_timeout']; + if ($method == 'get') { + foreach ($conf['utils']['gnupg_keyserver'] as $server) { + $result = $this->getPublicKeyserver($data, $server, $timeout, $additional); + if (!is_a($result, 'PEAR_Error')) { + return $result; + } + } + return $result; + } else { + return $this->putPublicKeyserver($data, $conf['utils']['gnupg_keyserver'][0], $timeout); + } + } else { + return PEAR::raiseError(_("Public PGP keyserver support has been disabled."), 'horde.warning'); + } + } + + /** + * Verifies a signed message with a given public key. + * + * @param string $text The text to verify. + * @param string $address E-mail address of public key. + * @param string $signature A PGP signature block. + * + * @return string See Horde_Crypt_pgp::decryptSignature() -or- + * Horde_Crypt_pgp::decryptDetachedSignature(). + */ + public function verifySignature($text, $address, $signature = '') + { + $fingerprint = null; + + /* Get fingerprint of key. */ + if (!empty($signature)) { + $packet_info = $this->pgpPacketInformation($signature); + if (isset($packet_info['fingerprint'])) { + $fingerprint = $packet_info['fingerprint']; + } + } else { + $fingerprint = $this->getSignersKeyID($text); + } + + $public_key = $this->getPublicKey($address, $fingerprint); + if (is_a($public_key, 'PEAR_Error')) { + return $public_key; + } + + if (!empty($signature)) { + $options = array('type' => 'detached-signature', 'signature' => $signature); + } else { + $options = array('type' => 'signature'); + } + $options['pubkey'] = $public_key; + + /* decrypt() returns a PEAR_Error object on error. */ + return $this->decrypt($text, $options); + } + + /** + * Decrypt a message with user's public/private keypair or a passphrase. + * + * @param string $text The text to decrypt. + * @param boolean $symmetric_hint Whether the text has been encrypted + * symmetrically. If null, we try to find + * out. + * @param boolean $passphrase Whether a passphrase has to be used. + * + * @return string The decrypted message. Returns PEAR_Error object on + * error. + */ + public function decryptMessage($text, $symmetric_hint = null, + $passphrase = true) + { + if (is_null($symmetric_hint)) { + $symmetric_hint = $this->encryptedSymmetrically($text); + } + + /* decrypt() returns a PEAR_Error object on error. */ + if (!$passphrase) { + return $this->decrypt($text, array('type' => 'message', 'no_passphrase' => true)); + } elseif ($symmetric_hint) { + return $this->decrypt($text, array('type' => 'message', 'passphrase' => $this->getSymmetricPassphrase())); + } else { + return $this->decrypt($text, array('type' => 'message', 'pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $this->getPassphrase())); + } + } + + /** + * Gets the user's passphrase from the session cache. + * + * @return string The passphrase, if set. + */ + public function getPassphrase() + { + if (isset($_SESSION['imp']['pgp_passphrase'])) { + return Secret::read(IMP::getAuthKey(), $_SESSION['imp']['pgp_passphrase']); + } + } + + /** + * Gets the user's passphrase for symmetric encryption from the session + * cache. + * + * @return string The passphrase, if set. + */ + public function getSymmetricPassphrase() + { + if (isset($_SESSION['imp']['pgp_sym_passphrase'])) { + return Secret::read(IMP::getAuthKey(), $_SESSION['imp']['pgp_sym_passphrase']); + } + } + + /** + * Store's the user's passphrase in the session cache. + * + * @param string $passphrase The user's passphrase. + * + * @return boolean Returns true if correct passphrase, false if incorrect. + */ + public function storePassphrase($passphrase) + { + if ($this->verifyPassphrase($this->getPersonalPublicKey(), $this->getPersonalPrivateKey(), $passphrase) === false) { + return false; + } + + $_SESSION['imp']['pgp_passphrase'] = Secret::write(IMP::getAuthKey(), $passphrase); + return true; + } + + /** + * Store's the user's passphrase for symmetric encryption in the session + * cache. + * + * @param string $passphrase The user's passphrase. + * + * @return boolean True + */ + public function storeSymmetricPassphrase($passphrase) + { + $_SESSION['imp']['pgp_sym_passphrase'] = Secret::write(IMP::getAuthKey(), $passphrase); + return true; + } + + /** + * Clear the passphrase from the session cache. + */ + public function unsetPassphrase() + { + unset($_SESSION['imp']['pgp_passphrase']); + } + + /** + * Clear the passphrase for symmetric encryption from the session cache. + */ + public function unsetSymmetricPassphrase() + { + unset($_SESSION['imp']['pgp_sym_passphrase']); + } + + /** + * Generates the javascript code for saving public keys. + * + * @param Horde_Mime_Part &$mime_part The part containing the public key. + * @param string $cache The part identifier. + * + * @return string The URL for saving public keys. + */ + public function savePublicKeyURL(&$mime_part, $cache = null) + { + if (empty($cache)) { + require_once 'Horde/SessionObjects.php'; + $cacheSess = &Horde_SessionObjects::singleton(); + $oid = $cacheSess->storeOid($mime_part); + } + + return $this->getJSOpenWinCode('save_attachment_public_key', false, array('mimecache' => $oid)); + } + + /** + * Print out the link for the javascript PGP popup. + * + * @param string $actionid The ActionID to perform. + * @param mixed $reload If true, reload base window on close. If text, + * run this JS on close. If false, don't do + * anything on close. + * @param array $params Additional parameters needed for the reload + * page. + * + * @return string The javascript link. + */ + public function getJSOpenWinCode($actionid, $reload = true, $params = array()) + { + $params['actionID'] = $actionid; + if (!empty($reload)) { + if (is_bool($reload)) { + $params['reload'] = html_entity_decode(Util::removeParameter(Horde::selfUrl(true), array('actionID'))); + } else { + require_once 'Horde/SessionObjects.php'; + $cacheSess = &Horde_SessionObjects::singleton(); + $params['passphrase_action'] = $cacheSess->storeOid($reload, false); + } + } + + return IMP::popupIMPString('pgp.php', $params, 450, 200); + } + + /** + * Provide the list of parameters needed for signing a message. + * + * @return array The list of parameters needed by encrypt(). + */ + protected function _signParameters() + { + return array('pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $this->getPassphrase()); + } + + /** + * Provide the list of parameters needed for encrypting a message. + * + * @param array $addresses The e-mail address of the keys to use for + * encryption. + * @param boolean $symmetric Whether to encrypt symmetrically. + * + * @return array The list of parameters needed by encrypt(). + * Returns PEAR_Error on error. + */ + protected function _encryptParameters($addresses, $symmetric = false) + { + if ($symmetric) { + return array('symmetric' => true, + 'passphrase' => $this->getSymmetricPassphrase()); + } + + $addr_list = array(); + + foreach ($addresses as $val) { + $addrOb = Horde_Mime_Address::bareAddress($val, $_SESSION['imp']['maildomain'], true); + $key_addr = array_pop($addrOb); + + /* Get the public key for the address. */ + $public_key = $this->getPublicKey($key_addr); + if (is_a($public_key, 'PEAR_Error')) { + return $public_key; + } + $addr_list[$key_addr] = $public_key; + } + + return array('recips' => $addr_list); + } + + /** + * Sign a Horde_Mime_Part using PGP using IMP default parameters. + * + * @param Horde_Mime_Part $mime_part The object to sign. + * + * @return Horde_Mime_Part See Horde_Crypt_pgp::signMIMEPart(). Returns + * PEAR_Error object on error. + */ + public function IMPsignMIMEPart($mime_part) + { + return $this->signMIMEPart($mime_part, $this->_signParameters()); + } + + /** + * Encrypt a Horde_Mime_Part using PGP using IMP default parameters. + * + * @param Horde_Mime_Part $mime_part The object to encrypt. + * @param array $addresses The e-mail address of the keys to use for + * encryption. + * @param boolean $symmetric Whether to encrypt symmetrically. + * + * @return Horde_Mime_Part See Horde_Crypt_pgp::encryptMIMEPart(). Returns + * PEAR_Error object on error. + */ + public function IMPencryptMIMEPart($mime_part, $addresses, + $symmetric = false) + { + $params = $this->_encryptParameters($addresses, $symmetric); + if (is_a($params, 'PEAR_Error')) { + return $params; + } + return $this->encryptMIMEPart($mime_part, $params); + } + + /** + * Sign and Encrypt a Horde_Mime_Part using PGP using IMP default parameters. + * + * @param Horde_Mime_Part $mime_part The object to sign and encrypt. + * @param array $addresses The e-mail address of the keys to use for + * encryption. + * @param boolean $symmetric Whether to encrypt symmetrically. + * + * @return Horde_Mime_Part See Horde_Crypt_pgp::signAndencryptMIMEPart(). + * Returns PEAR_Error object on error. + */ + public function IMPsignAndEncryptMIMEPart($mime_part, $addresses, + $symmetric = false) + { + $encrypt_params = $this->_encryptParameters($addresses, $symmetric); + if (is_a($encrypt_params, 'PEAR_Error')) { + return $encrypt_params; + } + return $this->signAndEncryptMIMEPart($mime_part, $this->_signParameters(), $encrypt_params); + } + + /** + * Generate a Horde_Mime_Part object, in accordance with RFC 2015/3156, that + * contains the user's public key. + * + * @return Horde_Mime_Part See Horde_Crypt_pgp::publicKeyMIMEPart(). + */ + public function publicKeyMIMEPart() + { + return parent::publicKeyMIMEPart($this->getPersonalPublicKey()); + } + +} diff --git a/imp/lib/Crypt/smime.php b/imp/lib/Crypt/smime.php new file mode 100644 index 000000000..7c2494b8c --- /dev/null +++ b/imp/lib/Crypt/smime.php @@ -0,0 +1,473 @@ + + * @package IMP + */ +class IMP_SMIME extends Horde_Crypt_smime { + + /** + * Constructor. + */ + function IMP_SMIME() + { + parent::Horde_Crypt_smime(array('temp' => Horde::getTempDir())); + } + + /** + * Add the personal public key to the prefs. + * + * @param mixed $key The public key to add (either string or array). + */ + function addPersonalPublicKey($key) + { + $GLOBALS['prefs']->setValue('smime_public_key', (is_array($key)) ? implode('', $key) : $key); + } + + /** + * Add the personal private key to the prefs. + * + * @param mixed $key The private key to add (either string or array). + */ + function addPersonalPrivateKey($key) + { + $GLOBALS['prefs']->setValue('smime_private_key', (is_array($key)) ? implode('', $key) : $key); + } + + /** + * Add the list of additional certs to the prefs. + * + * @param mixed $key The private key to add (either string or array). + */ + function addAdditionalCert($key) + { + $GLOBALS['prefs']->setValue('smime_additional_cert', (is_array($key)) ? implode('', $key) : $key); + } + + /** + * Get the personal public key from the prefs. + * + * @return string The personal S/MIME public key. + */ + function getPersonalPublicKey() + { + return $GLOBALS['prefs']->getValue('smime_public_key'); + } + + /** + * Get the personal private key from the prefs. + * + * @return string The personal S/MIME private key. + */ + function getPersonalPrivateKey() + { + return $GLOBALS['prefs']->getValue('smime_private_key'); + } + + /** + * Get any additional certificates from the prefs. + * + * @return string Additional signing certs for inclusion. + */ + function getAdditionalCert() + { + return $GLOBALS['prefs']->getValue('smime_additional_cert'); + } + + /** + * Deletes the specified personal keys from the prefs. + */ + function deletePersonalKeys() + { + $GLOBALS['prefs']->setValue('smime_public_key', ''); + $GLOBALS['prefs']->setValue('smime_private_key', ''); + $GLOBALS['prefs']->setValue('smime_additional_cert', ''); + $this->unsetPassphrase(); + } + + /** + * Add a public key to an address book. + * + * @param string $cert A public certificate to add. + * + * @return boolean True on successful add. + * Returns PEAR_Error or error. + */ + function addPublicKey($cert) + { + /* Make sure the certificate is valid. */ + $key_info = openssl_x509_parse($cert); + if (!is_array($key_info) || !isset($key_info['subject'])) { + return PEAR::raiseError(_("Not a valid public key."), 'horde.error'); + } + + /* Add key to the user's address book. */ + $email = $this->getEmailFromKey($cert); + if ($email === null) { + return PEAR::raiseError(_("No email information located in the public key."), 'horde.error'); + } + + /* Get the name corresponding to this key. */ + if (isset($key_info['subject']['CN'])) { + $name = $key_info['subject']['CN']; + } elseif (isset($key_info['subject']['OU'])) { + $name = $key_info['subject']['OU']; + } else { + return PEAR::raiseError(_("Not a valid public key."), 'horde.error'); + } + + $res = $GLOBALS['registry']->call('contacts/addField', array($email, $name, IMP_SMIME_PUBKEY_FIELD, $cert, $GLOBALS['prefs']->getValue('add_source'))); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + + return $key_info; + } + + /** + * Returns the params needed to encrypt a message being sent to the + * specified email address. + * + * @param string $address The e-mail address of the recipient. + * + * @return array The list of parameters needed by encrypt(). + * Returns PEAR_Error object on error. + */ + function _encryptParameters($address) + { + /* We can only encrypt if we are sending to a single person. */ + $addrOb = Horde_Mime_Address::bareAddress($address, $_SESSION['imp']['maildomain'], true); + $key_addr = array_pop($addrOb); + + $public_key = $this->getPublicKey($key_addr); + if (is_a($public_key, 'PEAR_Error')) { + return $public_key; + } + + return array('type' => 'message', 'pubkey' => $public_key, 'email' => $address); + } + + /** + * Retrieves a public key by e-mail. + * The key will be retrieved from a user's address book(s). + * + * @param string $address The e-mail address to search for. + * + * @return string The S/MIME public key requested. + * Returns PEAR_Error object on error. + */ + function getPublicKey($address) + { + $params = IMP_Compose::getAddressSearchParams(); + $key = $GLOBALS['registry']->call('contacts/getField', array($address, IMP_SMIME_PUBKEY_FIELD, $params['sources'], false, true)); + + /* See if the address points to the user's public key. */ + if (is_a($key, 'PEAR_Error')) { + require_once 'Horde/Identity.php'; + $identity = &Identity::singleton(array('imp', 'imp')); + $personal_pubkey = $this->getPersonalPublicKey(); + if (!empty($personal_pubkey) && $identity->hasAddress($address)) { + return $personal_pubkey; + } + } + + /* If more than one public key is returned, just return the first in + * the array. There is no way of knowing which is the "preferred" key, + * if the keys are different. */ + if (is_array($key)) { + return reset($key); + } + + return $key; + } + + /** + * Retrieves all public keys from a user's address book(s). + * + * @return array All PGP public keys available. + * Returns PEAR_Error object on error. + */ + function listPublicKeys() + { + $params = IMP_Compose::getAddressSearchParams(); + return (empty($params['sources'])) ? array() : $GLOBALS['registry']->call('contacts/getAllAttributeValues', array(IMP_SMIME_PUBKEY_FIELD, $params['sources'])); + } + + /** + * Deletes a public key from a user's address book(s) by e-mail. + * + * @param string $email The e-mail address to delete. + * + * @return PEAR_Error Returns PEAR_Error object on error. + */ + function deletePublicKey($email) + { + $params = IMP_Compose::getAddressSearchParams(); + return $GLOBALS['registry']->call('contacts/deleteField', array($email, IMP_SMIME_PUBKEY_FIELD, $params['sources'])); + } + + /** + * Returns the parameters needed for signing a message. + * + * @access private + * + * @return array The list of parameters needed by encrypt(). + */ + function _signParameters() + { + return array( + 'type' => 'signature', + 'pubkey' => $this->getPersonalPublicKey(), + 'privkey' => $this->getPersonalPrivateKey(), + 'passphrase' => $this->getPassphrase(), + 'sigtype' => 'detach', + 'certs' => $this->getAdditionalCert() + ); + } + + /** + * Verifies a signed message with a given public key. + * + * @param string $text The text to verify. + * + * @return stdClass See Horde_Crypt_smime::verify(). + */ + function verifySignature($text) + { + return $this->verify($text, empty($GLOBALS['conf']['utils']['openssl_cafile']) ? array() : $GLOBALS['conf']['utils']['openssl_cafile']); + } + + + /** + * Decrypt a message with user's public/private keypair. + * + * @param string $text The text to decrypt. + * + * @return string See Horde_Crypt_smime::decrypt(). + * Returns PEAR_Error object on error. + */ + function decryptMessage($text) + { + /* decrypt() returns a PEAR_Error object on error. */ + return $this->decrypt($text, array('type' => 'message', 'pubkey' => $this->getPersonalPublicKey(), 'privkey' => $this->getPersonalPrivateKey(), 'passphrase' => $this->getPassphrase())); + } + + /** + * Gets the user's passphrase from the session cache. + * + * @return mixed The passphrase, if set. Returns false if the passphrase + * has not been loaded yet. Returns null if no passphrase + * is needed. + */ + function getPassphrase() + { + $private_key = $GLOBALS['prefs']->getValue('smime_private_key'); + if (empty($private_key)) { + return false; + } + + if (isset($_SESSION['imp']['smime']['passphrase'])) { + return Secret::read(IMP::getAuthKey(), $_SESSION['imp']['smime']['passphrase']); + } elseif (isset($_SESSION['imp']['smime']['null_passphrase'])) { + return ($_SESSION['imp']['smime']['null_passphrase']) ? null : false; + } else { + $res = $this->verifyPassphrase($private_key, null); + if (!isset($_SESSION['imp']['smime'])) { + $_SESSION['imp']['smime'] = array(); + } + $_SESSION['imp']['smime']['null_passphrase'] = ($res) ? null : false; + return $_SESSION['imp']['smime']['null_passphrase']; + } + } + + /** + * Store's the user's passphrase in the session cache. + * + * @param string $passphrase The user's passphrase. + * + * @return boolean Returns true if correct passphrase, false if incorrect. + */ + function storePassphrase($passphrase) + { + if ($this->verifyPassphrase($this->getPersonalPrivateKey(), $passphrase) === false) { + return false; + } + + if (!isset($_SESSION['imp']['smime'])) { + $_SESSION['imp']['smime'] = array(); + } + $_SESSION['imp']['smime']['passphrase'] = Secret::write(IMP::getAuthKey(), $passphrase); + + return true; + } + + /** + * Clear the passphrase from the session cache. + */ + function unsetPassphrase() + { + unset($_SESSION['imp']['smime']['null_passphrase']); + unset($_SESSION['imp']['smime']['passphrase']); + } + + /** + * Generates the javascript code for saving public keys. + * + * @param MIME_Part $mime_part The MIME_Part containing the public key. + * + * @return string The URL for saving public keys. + */ + function savePublicKeyURL($mime_part) + { + if (empty($cache)) { + require_once 'Horde/SessionObjects.php'; + $cacheSess = &Horde_SessionObjects::singleton(); + $oid = $cacheSess->storeOid($mime_part); + } + + return $this->getJSOpenWinCode('save_attachment_public_key', false, array('cert' => $oid)); + } + + /** + * Print out the link for the javascript S/MIME popup. + * + * @param integer $actionid The actionID to perform. + * @param mixed $reload If true, reload base window on close. If text, + * run this JS on close. If false, don't do + * anything on close. + * @param array $params Additional parameters needed for the reload + * page. + * + * @return string The javascript link. + */ + function getJSOpenWinCode($actionid, $reload = true, $params = array()) + { + $params['actionID'] = $actionid; + if (!empty($reload)) { + if (is_bool($reload)) { + $params['reload'] = html_entity_decode(Util::removeParameter(Horde::selfUrl(true), array('actionID'))); + } else { + require_once 'Horde/SessionObjects.php'; + $cacheSess = &Horde_SessionObjects::singleton(); + $params['passphrase_action'] = $cacheSess->storeOid($reload, false); + } + } + + return IMP::popupIMPString('smime.php', $params, 450, 200); + } + + /** + * Encrypt a MIME_Part using S/MIME using IMP defaults. + * + * @param MIME_Part $mime_part The MIME_Part object to encrypt. + * @param mixed $to_address The e-mail address of the key to use for + * encryption. + * + * @return MIME_Part See Horde_Crypt_smime::encryptMIMEPart(). Returns + * PEAR_Error on error. + */ + function IMPencryptMIMEPart($mime_part, $to_address) + { + $params = $this->_encryptParameters($to_address); + if (is_a($params, 'PEAR_Error')) { + return $params; + } + return $this->encryptMIMEPart($mime_part, $params); + } + + /** + * Sign a MIME_Part using S/MIME using IMP defaults. + * + * @param MIME_Part $mime_part The MIME_Part object to sign. + * + * @return MIME_Part See Horde_Crypt_smime::signMIMEPart(). Returns + * PEAR_Error on error. + */ + function IMPsignMIMEPart($mime_part) + { + return $this->signMIMEPart($mime_part, $this->_signParameters()); + } + + /** + * Sign and encrypt a MIME_Part using S/MIME using IMP defaults. + * + * @param MIME_Part $mime_part The MIME_Part object to sign and encrypt. + * @param string $to_address The e-mail address of the key to use for + * encryption. + * + * @return MIME_Part See Horde_Crypt_smime::signAndencryptMIMEPart(). + * Returns PEAR_Error on error. + */ + function IMPsignAndEncryptMIMEPart($mime_part, $to_address) + { + $encrypt_params = $this->_encryptParameters($to_address); + if (is_a($encrypt_params, 'PEAR_Error')) { + return $encrypt_params; + } + return $this->signAndEncryptMIMEPart($mime_part, $this->_signParameters(), $encrypt_params); + } + + /** + * Store the public/private/additional certificates in the preferences + * from a given PKCS 12 file. + * + * @param string $pkcs12 The PKCS 12 data. + * @param string $password The password of the PKCS 12 file. + * @param string $pkpass The password to use to encrypt the private key. + * + * @return boolean True on success, PEAR_Error on error. + */ + function addFromPKCS12($pkcs12, $password, $pkpass = null) + { + $openssl = IMP_SMIME::checkForOpenSSL(); + if (is_a($openssl, 'PEAR_Error')) { + return $openssl; + } + + $sslpath = (empty($GLOBALS['conf']['utils']['openssl_binary'])) ? null : $GLOBALS['conf']['utils']['openssl_binary']; + $params = array('sslpath' => $sslpath, 'password' => $password); + if (!empty($pkpass)) { + $params['newpassword'] = $pkpass; + } + $res = $this->parsePKCS12Data($pkcs12, $params); + if (is_a($res, 'PEAR_Error')) { + return $res; + } + + $this->addPersonalPrivateKey($res->private); + $this->addPersonalPublicKey($res->public); + $this->addAdditionalCert($res->certs); + + return true; + } + + /** + * Extract the contents from signed S/MIME data. + * + * @param string $data The signed S/MIME data. + * + * @return string The contents embedded in the signed data. + * Returns PEAR_Error on error. + */ + function extractSignedContents($data) + { + $sslpath = (empty($GLOBALS['conf']['utils']['openssl_binary'])) ? null : $GLOBALS['conf']['utils']['openssl_binary']; + return parent::extractSignedContents($data, $sslpath); + } + +}