472 lines
15 KiB
PHP
472 lines
15 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2002-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (GPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/gpl.
|
|
*
|
|
* @category Horde
|
|
* @copyright 2002-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/gpl GPL
|
|
* @package IMP
|
|
*/
|
|
|
|
/**
|
|
* The IMP_Crypt_Smime:: class contains all functions related to handling
|
|
* S/MIME messages within IMP.
|
|
*
|
|
* @author Mike Cochrane <mike@graftonhall.co.nz>
|
|
* @category Horde
|
|
* @copyright 2002-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/gpl GPL
|
|
* @package IMP
|
|
*/
|
|
class IMP_Crypt_Smime extends Horde_Crypt_Smime
|
|
{
|
|
/* Name of the S/MIME public key field in addressbook. */
|
|
const PUBKEY_FIELD = 'smimePublicKey';
|
|
|
|
/* Encryption type constants. */
|
|
const ENCRYPT = 'smime_encrypt';
|
|
const SIGN = 'smime_sign';
|
|
const SIGNENC = 'smime_signenc';
|
|
|
|
/**
|
|
* Return the list of available encryption options for composing.
|
|
*
|
|
* @return array Keys are encryption type constants, values are gettext
|
|
* strings describing the encryption type.
|
|
*/
|
|
public function encryptList()
|
|
{
|
|
$ret = array();
|
|
|
|
if ($GLOBALS['registry']->hasMethod('contacts/getField') ||
|
|
$GLOBALS['injector']->getInstance('Horde_Core_Hooks')->hookExists('smime_key', 'imp')) {
|
|
$ret += array(
|
|
self::ENCRYPT => _("S/MIME Encrypt Message")
|
|
);
|
|
}
|
|
|
|
if ($this->getPersonalPrivateKey()) {
|
|
$ret += array(
|
|
self::SIGN => _("S/MIME Sign Message"),
|
|
self::SIGNENC => _("S/MIME Sign/Encrypt Message")
|
|
);
|
|
}
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Add the personal public key to the prefs.
|
|
*
|
|
* @param mixed $key The public key to add (either string or array).
|
|
*/
|
|
public 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).
|
|
*/
|
|
public 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).
|
|
*/
|
|
public 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.
|
|
*/
|
|
public 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.
|
|
*/
|
|
public function getPersonalPrivateKey()
|
|
{
|
|
return $GLOBALS['prefs']->getValue('smime_private_key');
|
|
}
|
|
|
|
/**
|
|
* Get any additional certificates from the prefs.
|
|
*
|
|
* @return string Additional signing certs for inclusion.
|
|
*/
|
|
public function getAdditionalCert()
|
|
{
|
|
return $GLOBALS['prefs']->getValue('smime_additional_cert');
|
|
}
|
|
|
|
/**
|
|
* Deletes the specified personal keys from the prefs.
|
|
*/
|
|
public 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.
|
|
*
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function addPublicKey($cert)
|
|
{
|
|
list($name, $email) = $this->publicKeyInfo($cert);
|
|
|
|
$GLOBALS['registry']->call('contacts/addField', array($email, $name, self::PUBKEY_FIELD, $cert, $GLOBALS['prefs']->getValue('add_source')));
|
|
}
|
|
|
|
/**
|
|
* Get information about a public certificate.
|
|
*
|
|
* @param string $cert The public certificate.
|
|
*
|
|
* @return array Two element array: the name and e-mail for the cert.
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function publicKeyInfo($cert)
|
|
{
|
|
/* Make sure the certificate is valid. */
|
|
$key_info = openssl_x509_parse($cert);
|
|
if (!is_array($key_info) || !isset($key_info['subject'])) {
|
|
throw new Horde_Crypt_Exception(_("Not a valid public key."));
|
|
}
|
|
|
|
/* Add key to the user's address book. */
|
|
$email = $this->getEmailFromKey($cert);
|
|
if (is_null($email)) {
|
|
throw new Horde_Crypt_Exception(_("No email information located in the public key."));
|
|
}
|
|
|
|
/* 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 {
|
|
$name = $email;
|
|
}
|
|
|
|
return array($name, $email);
|
|
}
|
|
|
|
/**
|
|
* Returns the params needed to encrypt a message being sent to the
|
|
* specified email address.
|
|
*
|
|
* @param Horde_Mail_Rfc822_Address $address The e-mail address of the
|
|
* recipient.
|
|
*
|
|
* @return array The list of parameters needed by encrypt().
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
protected function _encryptParameters(Horde_Mail_Rfc822_Address $address)
|
|
{
|
|
/* We can only encrypt if we are sending to a single person. */
|
|
return array(
|
|
'pubkey' => $this->getPublicKey($address->bare_address),
|
|
'type' => 'message'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public function getPublicKey($address)
|
|
{
|
|
global $injector, $registry;
|
|
|
|
try {
|
|
$key = $injector->getInstance('Horde_Core_Hooks')->callHook(
|
|
'smime_key',
|
|
'imp',
|
|
array($address)
|
|
);
|
|
if ($key) {
|
|
return $key;
|
|
}
|
|
} catch (Horde_Exception_HookNotSet $e) {}
|
|
|
|
$params = $injector->getInstance('IMP_Contacts')->getAddressbookSearchParams();
|
|
|
|
try {
|
|
$key = $registry->call('contacts/getField', array($address, self::PUBKEY_FIELD, $params['sources'], true, true));
|
|
} catch (Horde_Exception $e) {
|
|
/* See if the address points to the user's public key. */
|
|
$personal_pubkey = $this->getPersonalPublicKey();
|
|
if (!empty($personal_pubkey) &&
|
|
$injector->getInstance('IMP_Identity')->hasAddress($address)) {
|
|
return $personal_pubkey;
|
|
}
|
|
|
|
throw $e;
|
|
}
|
|
|
|
/* 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. */
|
|
return is_array($key) ? reset($key) : $key;
|
|
}
|
|
|
|
/**
|
|
* Retrieves all public keys from a user's address book(s).
|
|
*
|
|
* @return array All S/MIME public keys available.
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function listPublicKeys()
|
|
{
|
|
$params = $GLOBALS['injector']->getInstance('IMP_Contacts')->getAddressbookSearchParams();
|
|
|
|
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.
|
|
*
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function deletePublicKey($email)
|
|
{
|
|
$params = $GLOBALS['injector']->getInstance('IMP_Contacts')->getAddressbookSearchParams();
|
|
$GLOBALS['registry']->call('contacts/deleteField', array($email, self::PUBKEY_FIELD, $params['sources']));
|
|
}
|
|
|
|
/**
|
|
* Returns the parameters needed for signing a message.
|
|
*
|
|
* @return array The list of parameters needed by encrypt().
|
|
*/
|
|
protected 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().
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function verifySignature($text)
|
|
{
|
|
return $this->verify($text, empty($GLOBALS['conf']['openssl']['cafile']) ? array() : $GLOBALS['conf']['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().
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function decryptMessage($text)
|
|
{
|
|
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.
|
|
*/
|
|
public function getPassphrase()
|
|
{
|
|
global $session;
|
|
|
|
$private_key = $GLOBALS['prefs']->getValue('smime_private_key');
|
|
if (empty($private_key)) {
|
|
return false;
|
|
}
|
|
|
|
if ($session->exists('imp', 'smime_passphrase')) {
|
|
return $session->get('imp', 'smime_passphrase');
|
|
} elseif (!$session->exists('imp', 'smime_null_passphrase')) {
|
|
$session->set(
|
|
'imp',
|
|
'smime_null_passphrase',
|
|
$this->verifyPassphrase($private_key, null)
|
|
? null
|
|
: false
|
|
);
|
|
}
|
|
|
|
return $session->get('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.
|
|
*/
|
|
public function storePassphrase($passphrase)
|
|
{
|
|
global $session;
|
|
|
|
if ($this->verifyPassphrase($this->getPersonalPrivateKey(), $passphrase) !== false) {
|
|
$session->set('imp', 'smime_passphrase', $passphrase, $session::ENCRYPT);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Clear the passphrase from the session cache.
|
|
*/
|
|
public function unsetPassphrase()
|
|
{
|
|
global $session;
|
|
|
|
$session->remove('imp', 'smime_null_passphrase');
|
|
$session->remove('imp', 'smime_passphrase');
|
|
}
|
|
|
|
/**
|
|
* Encrypt a MIME_Part using S/MIME using IMP defaults.
|
|
*
|
|
* @param Horde_Mime_Part $mime_part The object to encrypt.
|
|
* @param Horde_Mail_Rfc822_Address $to_address The e-mail address of the
|
|
* key to use for encryption.
|
|
*
|
|
* @return MIME_Part See Horde_Crypt_Smime::encryptMIMEPart().
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function IMPencryptMIMEPart($mime_part,
|
|
Horde_Mail_Rfc822_Address $to_address)
|
|
{
|
|
return $this->encryptMIMEPart($mime_part, $this->_encryptParameters($to_address));
|
|
}
|
|
|
|
/**
|
|
* 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().
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function IMPsignMIMEPart($mime_part)
|
|
{
|
|
return $this->signMIMEPart($mime_part, $this->_signParameters());
|
|
}
|
|
|
|
/**
|
|
* Sign and encrypt a MIME_Part using S/MIME using IMP defaults.
|
|
*
|
|
* @param Horde_Mime_Part $mime_part The object to sign and
|
|
* encrypt.
|
|
* @param Horde_Mail_Rfc822_Address $to_address The e-mail address of the
|
|
* key to use for encryption.
|
|
*
|
|
* @return MIME_Part See Horde_Crypt_Smime::signAndencryptMIMEPart().
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function IMPsignAndEncryptMIMEPart($mime_part,
|
|
Horde_Mail_Rfc822_Address $to_address)
|
|
{
|
|
return $this->signAndEncryptMIMEPart($mime_part, $this->_signParameters(), $this->_encryptParameters($to_address));
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function addFromPKCS12($pkcs12, $password, $pkpass = null)
|
|
{
|
|
$sslpath = empty($GLOBALS['conf']['openssl']['path'])
|
|
? null
|
|
: $GLOBALS['conf']['openssl']['path'];
|
|
|
|
$params = array('sslpath' => $sslpath, 'password' => $password);
|
|
if (!empty($pkpass)) {
|
|
$params['newpassword'] = $pkpass;
|
|
}
|
|
|
|
$result = $this->parsePKCS12Data($pkcs12, $params);
|
|
$this->addPersonalPrivateKey($result->private);
|
|
$this->addPersonalPublicKey($result->public);
|
|
$this->addAdditionalCert($result->certs);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* @throws Horde_Crypt_Exception
|
|
*/
|
|
public function extractSignedContents($data, $sslpath = null)
|
|
{
|
|
$sslpath = empty($GLOBALS['conf']['openssl']['path'])
|
|
? null
|
|
: $GLOBALS['conf']['openssl']['path'];
|
|
|
|
return parent::extractSignedContents($data, $sslpath);
|
|
}
|
|
|
|
}
|