diff options
author | Aleksander Machniak <alec@alec.pl> | 2013-12-27 13:14:40 +0100 |
---|---|---|
committer | Aleksander Machniak <alec@alec.pl> | 2013-12-27 13:14:40 +0100 |
commit | 3e98f8be718578644bb15ee6a992a875f6468e8f (patch) | |
tree | a0721e608a9ba04ca23d5535f90e579942581e6e /plugins | |
parent | c97625e02a95ebd995af8a06c27229581a071ddd (diff) |
Add some code for S/MIME signatures verification, update Crypt_GPG package
Diffstat (limited to 'plugins')
-rw-r--r-- | plugins/enigma/README | 1 | ||||
-rw-r--r-- | plugins/enigma/enigma.php | 21 | ||||
-rw-r--r-- | plugins/enigma/lib/enigma_driver_gnupg.php | 54 | ||||
-rw-r--r-- | plugins/enigma/lib/enigma_driver_phpssl.php | 238 | ||||
-rw-r--r-- | plugins/enigma/lib/enigma_engine.php | 51 | ||||
-rw-r--r-- | plugins/enigma/lib/enigma_error.php | 4 | ||||
-rw-r--r-- | plugins/enigma/localization/en_US.inc | 7 |
7 files changed, 323 insertions, 53 deletions
diff --git a/plugins/enigma/README b/plugins/enigma/README index 22d6e513a..c4e474be2 100644 --- a/plugins/enigma/README +++ b/plugins/enigma/README @@ -12,6 +12,7 @@ Enigma Plugin Status: - Handling of PGP keys files attached to incoming messages - PGP encrypted messages decryption (started) - PGP keys management UI (started) +- S/MIME signatures verification (started) * TODO (must have): diff --git a/plugins/enigma/enigma.php b/plugins/enigma/enigma.php index 25520a27d..870b923b6 100644 --- a/plugins/enigma/enigma.php +++ b/plugins/enigma/enigma.php @@ -179,10 +179,11 @@ class enigma extends rcube_plugin { // add labels $this->add_texts('localization/'); - +/* $p['list']['enigmasettings'] = array( 'id' => 'enigmasettings', 'section' => $this->gettext('enigmasettings'), ); +*/ $p['list']['enigmacerts'] = array( 'id' => 'enigmacerts', 'section' => $this->gettext('enigmacerts'), ); @@ -203,11 +204,13 @@ class enigma extends rcube_plugin */ function preferences_list($p) { +/* if ($p['section'] == 'enigmasettings') { // This makes that section is not removed from the list $p['blocks']['dummy']['options']['dummy'] = array(); } - else if ($p['section'] == 'enigmacerts') { + else */ + if ($p['section'] == 'enigmacerts') { // This makes that section is not removed from the list $p['blocks']['dummy']['options']['dummy'] = array(); } @@ -313,18 +316,24 @@ class enigma extends rcube_plugin $attrib['id'] = 'enigma-message'; if ($sig instanceof enigma_signature) { - if ($sig->valid) { + $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>'; + + if ($sig->valid === enigma_error::E_UNVERIFIED) { + $attrib['class'] = 'enigmawarning'; + $msg = str_replace('$sender', $sender, $this->gettext('sigunverified')); + $msg = str_replace('$keyid', $sig->id, $msg); + $msg = rcube::Q($msg); + } + else if ($sig->valid) { $attrib['class'] = 'enigmanotice'; - $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>'; $msg = rcube::Q(str_replace('$sender', $sender, $this->gettext('sigvalid'))); } else { $attrib['class'] = 'enigmawarning'; - $sender = ($sig->name ? $sig->name . ' ' : '') . '<' . $sig->email . '>'; $msg = rcube::Q(str_replace('$sender', $sender, $this->gettext('siginvalid'))); } } - else if ($sig->getCode() == enigma_error::E_KEYNOTFOUND) { + else if ($sig && $sig->getCode() == enigma_error::E_KEYNOTFOUND) { $attrib['class'] = 'enigmawarning'; $msg = rcube::Q(str_replace('$keyid', enigma_key::format_id($sig->getData('id')), $this->gettext('signokey'))); diff --git a/plugins/enigma/lib/enigma_driver_gnupg.php b/plugins/enigma/lib/enigma_driver_gnupg.php index 5aa32217e..c4280a089 100644 --- a/plugins/enigma/lib/enigma_driver_gnupg.php +++ b/plugins/enigma/lib/enigma_driver_gnupg.php @@ -76,7 +76,7 @@ class enigma_driver_gnupg extends enigma_driver // Create Crypt_GPG object try { - $this->gpg = new Crypt_GPG(array( + $this->gpg = new Crypt_GPG(array( 'homedir' => $this->homedir, // 'debug' => true, )); @@ -89,20 +89,20 @@ class enigma_driver_gnupg extends enigma_driver function encrypt($text, $keys) { /* - foreach ($keys as $key) { - $this->gpg->addEncryptKey($key); - } - $enc = $this->gpg->encrypt($text); - return $enc; + foreach ($keys as $key) { + $this->gpg->addEncryptKey($key); + } + $enc = $this->gpg->encrypt($text); + return $enc; */ } function decrypt($text, $key, $passwd) { -// $this->gpg->addDecryptKey($key, $passwd); +// $this->gpg->addDecryptKey($key, $passwd); try { - $dec = $this->gpg->decrypt($text); - return $dec; + $dec = $this->gpg->decrypt($text); + return $dec; } catch (Exception $e) { return $this->get_error_from_exception($e); @@ -112,17 +112,17 @@ class enigma_driver_gnupg extends enigma_driver function sign($text, $key, $passwd) { /* - $this->gpg->addSignKey($key, $passwd); - $signed = $this->gpg->sign($text, Crypt_GPG::SIGN_MODE_DETACHED); - return $signed; + $this->gpg->addSignKey($key, $passwd); + $signed = $this->gpg->sign($text, Crypt_GPG::SIGN_MODE_DETACHED); + return $signed; */ } function verify($text, $signature) { try { - $verified = $this->gpg->verify($text, $signature); - return $this->parse_signature($verified[0]); + $verified = $this->gpg->verify($text, $signature); + return $this->parse_signature($verified[0]); } catch (Exception $e) { return $this->get_error_from_exception($e); @@ -141,11 +141,11 @@ class enigma_driver_gnupg extends enigma_driver return $this->get_error_from_exception($e); } } - + public function list_keys($pattern='') { try { - $keys = $this->gpg->getKeys($pattern); + $keys = $this->gpg->getKeys($pattern); $result = array(); //print_r($keys); foreach ($keys as $idx => $key) { @@ -153,13 +153,13 @@ class enigma_driver_gnupg extends enigma_driver unset($keys[$idx]); } //print_r($result); - return $result; + return $result; } catch (Exception $e) { return $this->get_error_from_exception($e); } } - + public function get_key($keyid) { $list = $this->list_keys($keyid); @@ -167,7 +167,7 @@ class enigma_driver_gnupg extends enigma_driver if (is_array($list)) return array_shift($list); - // error + // error return $list; } @@ -178,14 +178,12 @@ class enigma_driver_gnupg extends enigma_driver public function del_key($keyid) { // $this->get_key($keyid); - - } - + public function del_privkey($keyid) { try { - $this->gpg->deletePrivateKey($keyid); + $this->gpg->deletePrivateKey($keyid); return true; } catch (Exception $e) { @@ -196,14 +194,14 @@ class enigma_driver_gnupg extends enigma_driver public function del_pubkey($keyid) { try { - $this->gpg->deletePublicKey($keyid); + $this->gpg->deletePublicKey($keyid); return true; } catch (Exception $e) { return $this->get_error_from_exception($e); } } - + /** * Converts Crypt_GPG exception into Enigma's error object * @@ -281,7 +279,7 @@ class enigma_driver_gnupg extends enigma_driver $ekey->users[$idx] = $id; } - + $ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>'); foreach ($key->getSubKeys() as $idx => $subkey) { @@ -297,9 +295,9 @@ class enigma_driver_gnupg extends enigma_driver $ekey->subkeys[$idx] = $skey; }; - + $ekey->id = $ekey->subkeys[0]->id; - + return $ekey; } } diff --git a/plugins/enigma/lib/enigma_driver_phpssl.php b/plugins/enigma/lib/enigma_driver_phpssl.php new file mode 100644 index 000000000..50af44762 --- /dev/null +++ b/plugins/enigma/lib/enigma_driver_phpssl.php @@ -0,0 +1,238 @@ +<?php +/* + +-------------------------------------------------------------------------+ + | S/MIME driver for the Enigma Plugin | + | | + | This program is free software; you can redistribute it and/or modify | + | it under the terms of the GNU General Public License version 2 | + | as published by the Free Software Foundation. | + | | + | This program is distributed in the hope that it will be useful, | + | but WITHOUT ANY WARRANTY; without even the implied warranty of | + | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | + | GNU General Public License for more details. | + | | + | You should have received a copy of the GNU General Public License along | + | with this program; if not, write to the Free Software Foundation, Inc., | + | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | + | | + +-------------------------------------------------------------------------+ + | Author: Aleksander Machniak <alec@alec.pl> | + +-------------------------------------------------------------------------+ +*/ + +class enigma_driver_phpssl extends enigma_driver +{ + private $rc; + //private $gpg; + private $homedir; + private $user; + + function __construct($user) + { + $rcmail = rcmail::get_instance(); + $this->rc = $rcmail; + $this->user = $user; + } + + /** + * Driver initialization and environment checking. + * Should only return critical errors. + * + * @return mixed NULL on success, enigma_error on failure + */ + function init() + { + $homedir = $this->rc->config->get('enigma_smime_homedir', INSTALL_PATH . '/plugins/enigma/home'); + + if (!$homedir) + return new enigma_error(enigma_error::E_INTERNAL, + "Option 'enigma_smime_homedir' not specified"); + + // check if homedir exists (create it if not) and is readable + if (!file_exists($homedir)) + return new enigma_error(enigma_error::E_INTERNAL, + "Keys directory doesn't exists: $homedir"); + if (!is_writable($homedir)) + return new enigma_error(enigma_error::E_INTERNAL, + "Keys directory isn't writeable: $homedir"); + + $homedir = $homedir . '/' . $this->user; + + // check if user's homedir exists (create it if not) and is readable + if (!file_exists($homedir)) + mkdir($homedir, 0700); + + if (!file_exists($homedir)) + return new enigma_error(enigma_error::E_INTERNAL, + "Unable to create keys directory: $homedir"); + if (!is_writable($homedir)) + return new enigma_error(enigma_error::E_INTERNAL, + "Unable to write to keys directory: $homedir"); + + $this->homedir = $homedir; + + } + + function encrypt($text, $keys) + { + } + + function decrypt($text, $key, $passwd) + { + } + + function sign($text, $key, $passwd) + { + } + + function verify($struct, $message) + { + // use common temp dir + $temp_dir = $this->rc->config->get('temp_dir'); + $msg_file = tempnam($temp_dir, 'rcmMsg'); + $cert_file = tempnam($temp_dir, 'rcmCert'); + + $fh = fopen($msg_file, "w"); + if ($struct->mime_id) { + $message->get_part_content($struct->mime_id, $fh, true, 0, false); + } + else { + $this->rc->storage->get_raw_body($message->uid, $fh); + } + fclose($fh); + + // @TODO: use stored certificates + + // try with certificate verification + $sig = openssl_pkcs7_verify($msg_file, 0, $cert_file); + $validity = true; + + if ($sig !== true) { + // try without certificate verification + $sig = openssl_pkcs7_verify($msg_file, PKCS7_NOVERIFY, $cert_file); + $validity = enigma_error::E_UNVERIFIED; + } + + if ($sig === true) { + $sig = $this->parse_sig_cert($cert_file, $validity); + } + else { + $errorstr = $this->get_openssl_error(); + $sig = new enigma_error(enigma_error::E_INTERNAL, $errorstr); + } + + // remove temp files + @unlink($msg_file); + @unlink($cert_file); + + return $sig; + } + + public function import($content, $isfile=false) + { + } + + public function list_keys($pattern='') + { + } + + public function get_key($keyid) + { + } + + public function gen_key($data) + { + } + + public function del_key($keyid) + { + } + + public function del_privkey($keyid) + { + } + + public function del_pubkey($keyid) + { + } + + /** + * Converts Crypt_GPG_Key object into Enigma's key object + * + * @param Crypt_GPG_Key Key object + * + * @return enigma_key Key object + */ + private function parse_key($key) + { +/* + $ekey = new enigma_key(); + + foreach ($key->getUserIds() as $idx => $user) { + $id = new enigma_userid(); + $id->name = $user->getName(); + $id->comment = $user->getComment(); + $id->email = $user->getEmail(); + $id->valid = $user->isValid(); + $id->revoked = $user->isRevoked(); + + $ekey->users[$idx] = $id; + } + + $ekey->name = trim($ekey->users[0]->name . ' <' . $ekey->users[0]->email . '>'); + + foreach ($key->getSubKeys() as $idx => $subkey) { + $skey = new enigma_subkey(); + $skey->id = $subkey->getId(); + $skey->revoked = $subkey->isRevoked(); + $skey->created = $subkey->getCreationDate(); + $skey->expires = $subkey->getExpirationDate(); + $skey->fingerprint = $subkey->getFingerprint(); + $skey->has_private = $subkey->hasPrivate(); + $skey->can_sign = $subkey->canSign(); + $skey->can_encrypt = $subkey->canEncrypt(); + + $ekey->subkeys[$idx] = $skey; + }; + + $ekey->id = $ekey->subkeys[0]->id; + + return $ekey; +*/ + } + + private function get_openssl_error() + { + $tmp = array(); + while ($errorstr = openssl_error_string()) { + $tmp[] = $errorstr; + } + + return join("\n", array_values($tmp)); + } + + private function parse_sig_cert($file, $validity) + { + $cert = openssl_x509_parse(file_get_contents($file)); + + if (empty($cert) || empty($cert['subject'])) { + $errorstr = $this->get_openssl_error(); + return new enigma_error(enigm_error::E_INTERNAL, $errorstr); + } + + $data = new enigma_signature(); + + $data->id = $cert['hash']; //? + $data->valid = $validity; + $data->fingerprint = $cert['serialNumber']; + $data->created = $cert['validFrom_time_t']; + $data->expires = $cert['validTo_time_t']; + $data->name = $cert['subject']['CN']; +// $data->comment = ''; + $data->email = $cert['subject']['emailAddress']; + + return $data; + } + +} diff --git a/plugins/enigma/lib/enigma_engine.php b/plugins/enigma/lib/enigma_engine.php index 8a64c07ff..e4972c6a9 100644 --- a/plugins/enigma/lib/enigma_engine.php +++ b/plugins/enigma/lib/enigma_engine.php @@ -92,9 +92,6 @@ class enigma_engine if ($this->smime_driver) return; - // NOT IMPLEMENTED! - return; - $driver = 'enigma_driver_' . $this->rc->config->get('enigma_smime_driver', 'phpssl'); $username = $this->rc->user->get_username(); @@ -246,7 +243,7 @@ class enigma_engine fclose($fh); } - + /** * Handler for PGP/MIME signed message. * Verifies signature. @@ -255,14 +252,14 @@ class enigma_engine */ private function parse_pgp_signed(&$p) { - $this->load_pgp_driver(); - $struct = $p['structure']; - // Verify signature if ($this->rc->action == 'show' || $this->rc->action == 'preview') { + $this->load_pgp_driver(); + $struct = $p['structure']; + $msg_part = $struct->parts[0]; $sig_part = $struct->parts[1]; - + // Get bodies $this->set_part_body($msg_part, $p['object']->uid); $this->set_part_body($sig_part, $p['object']->uid); @@ -294,7 +291,31 @@ class enigma_engine */ private function parse_smime_signed(&$p) { - $this->load_smime_driver(); + // Verify signature + if ($this->rc->action == 'show' || $this->rc->action == 'preview') { + $this->load_smime_driver(); + + $struct = $p['structure']; + $msg_part = $struct->parts[0]; + + // Verify + $sig = $this->smime_driver->verify($struct, $p['object']); + + // Store signature data for display + $this->signatures[$struct->mime_id] = $sig; + + // Message can be multipart (assign signature to each subpart) + if (!empty($msg_part->parts)) { + foreach ($msg_part->parts as $part) + $this->signed_parts[$part->mime_id] = $struct->mime_id; + } + else { + $this->signed_parts[$msg_part->mime_id] = $struct->mime_id; + } + + // Remove signature file from attachments list + unset($struct->parts[1]); + } } /** @@ -306,22 +327,22 @@ class enigma_engine { $this->load_pgp_driver(); $part = $p['structure']; - + // Get body $this->set_part_body($part, $p['object']->uid); - // Decrypt + // Decrypt $result = $this->pgp_decrypt($part->body); - + // Store decryption status $this->decryptions[$part->mime_id] = $result; - + // Parse decrypted message if ($result === true) { // @TODO } } - + /** * Handler for PGP/MIME encrypted message. * @@ -359,7 +380,7 @@ class enigma_engine */ private function parse_smime_encrypted(&$p) { - $this->load_smime_driver(); +// $this->load_smime_driver(); } /** diff --git a/plugins/enigma/lib/enigma_error.php b/plugins/enigma/lib/enigma_error.php index 9f424dc2b..ab8d01557 100644 --- a/plugins/enigma/lib/enigma_error.php +++ b/plugins/enigma/lib/enigma_error.php @@ -34,7 +34,9 @@ class enigma_error const E_KEYNOTFOUND = 3; const E_DELKEY = 4; const E_BADPASS = 5; - + const E_EXPIRED = 6; + const E_UNVERIFIED = 7; + function __construct($code = null, $message = '', $data = array()) { $this->code = $code; diff --git a/plugins/enigma/localization/en_US.inc b/plugins/enigma/localization/en_US.inc index e0f03d9a0..f1ff8d1fb 100644 --- a/plugins/enigma/localization/en_US.inc +++ b/plugins/enigma/localization/en_US.inc @@ -1,9 +1,9 @@ <?php $labels = array(); -$labels['enigmasettings'] = 'Enigma: Settings'; -$labels['enigmacerts'] = 'Enigma: Certificates (S/MIME)'; -$labels['enigmakeys'] = 'Enigma: Keys (PGP)'; +$labels['enigmasettings'] = 'Enigma Settings'; +$labels['enigmacerts'] = 'S/MIME Certificates'; +$labels['enigmakeys'] = 'PGP Keys'; $labels['keysfromto'] = 'Keys $from to $to of $count'; $labels['keyname'] = 'Name'; $labels['keyid'] = 'Key ID'; @@ -36,6 +36,7 @@ $labels['signmsg'] = 'Digitally sign this message'; $messages = array(); $messages['sigvalid'] = 'Verified signature from $sender.'; $messages['siginvalid'] = 'Invalid signature from $sender.'; +$messages['sigunverified'] = 'Unverified signature. Certificate not verified. Certificate ID: $keyid.'; $messages['signokey'] = 'Unverified signature. Public key not found. Key ID: $keyid.'; $messages['sigerror'] = 'Unverified signature. Internal error.'; $messages['decryptok'] = 'Message decrypted.'; |