Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enigma smime signed messages verification #8513

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions plugins/enigma/config.inc.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ $config['enigma_password_time'] = 5;
// Enable support for private keys without passwords.
$config['enigma_passwordless'] = false;

// Trusted Root CAs
// When this array is filled with root(!) CA files,
// enigma will first try to use these CAs to verify a signature.
// If this fails, verification with default system CAs will still be attempted,
// but lower trust will be signalled to user.
$config['enigma_smime_ca'] = array();

// With this option you can lock composing options
// of the plugin forcing the user to use configured settings.
// The array accepts: 'sign', 'encrypt', 'pubkey'.
Expand Down
59 changes: 52 additions & 7 deletions plugins/enigma/lib/enigma_driver_phpssl.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class enigma_driver_phpssl extends enigma_driver
private $rc;
private $homedir;
private $user;
private $cainfo;

function __construct($user)
{
Expand All @@ -36,6 +37,7 @@ function __construct($user)
function init()
{
$homedir = $this->rc->config->get('enigma_smime_homedir', INSTALL_PATH . '/plugins/enigma/home');
$this->cainfo = $this->rc->config->get('enigma_smime_ca', []);

if (!$homedir)
return new enigma_error(enigma_error::INTERNAL,
Expand Down Expand Up @@ -64,6 +66,14 @@ function init()

$this->homedir = $homedir;

// XXX: Workaround for https://bugs.php.net/bug.php?id=75494
if (!is_array($this->cainfo) || count($this->cainfo) > 0) {
$dummy_cert_dir = $this->homedir . '/' . 'cert_dummy';
if (!file_exists($dummy_cert_dir)) {
mkdir($dummy_cert_dir, 0700);
}
$this->cainfo[] = $dummy_cert_dir;
}
}

function encrypt($text, $keys, $sign_key = null)
Expand All @@ -78,35 +88,59 @@ function sign($text, $key, $mode = null)
{
}

/**
* Verifying S/MIME signed message.
*
* @param array $struct to verify
* @param string $message message body
*
* @return mixed enigma_signature or enigma_signature
*/
function verify($struct, $message)
{
// use common temp dir
$msg_file = rcube_utils::temp_filename('enigmsg');
$cert_file = rcube_utils::temp_filename('enigcrt');
$content_file = rcube_utils::temp_filename('enigcnt');

$fh = fopen($msg_file, "w");
if ($struct->mime_id) {
$message->get_part_body($struct->mime_id, false, 0, $fh);
if ($struct->ctype_parameters['smime-type'] == 'signed-data' ) {
// The following statement gives the whole body.
// openssl can verify this (openssl pkcs7 -verify -noverify)
$this->rc->storage->get_raw_body($message->uid, $fh);
}
else {
// this will only give the *one part*.
$message->get_part_body($struct->mime_id, false, 0, $fh);
}
}
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);
// try with global config'd certificates
$sig = openssl_pkcs7_verify($msg_file, 0, $cert_file, $this->cainfo, null, $content_file);
$validity = true;

if ($sig !== true) {
// try with server trusted certificate verification
$sig = openssl_pkcs7_verify($msg_file, 0, $cert_file, [], null, $content_file);
$validity = enigma_error::SERVER_VERIFIED;
}

if ($sig !== true) {
// try without certificate verification
$sig = openssl_pkcs7_verify($msg_file, PKCS7_NOVERIFY, $cert_file);
$sig = openssl_pkcs7_verify($msg_file, PKCS7_NOVERIFY, $cert_file, [], null, $content_file);
$validity = enigma_error::UNVERIFIED;
}

if ($sig === true) {
$sig = $this->parse_sig_cert($cert_file, $validity);
$content = file_get_contents($content_file);
$sig->comment = $content;
}
else {
$errorstr = $this->get_openssl_error();
Expand All @@ -116,6 +150,7 @@ function verify($struct, $message)
// remove temp files
@unlink($msg_file);
@unlink($cert_file);
@unlink($content_file);

return $sig;
}
Expand Down Expand Up @@ -185,15 +220,25 @@ private function parse_sig_cert($file, $validity)
}

$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'];
if (array_key_exists('emailAddress', $cert['subject'])) {
$data->email = $cert['subject']['emailAddress'];
}
else {
if (array_key_exists('extensions', $cert)) {
if (array_key_exists('subjectAltName', $cert['extensions'])) {
if (substr($cert['extensions']['subjectAltName'], 0, 6) == "email:") {
$data->email = substr($cert['extensions']['subjectAltName'], 6);
}
}
}
}

return $data;
}
Expand Down
55 changes: 51 additions & 4 deletions plugins/enigma/lib/enigma_engine.php
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,11 @@ function part_structure($p, $body = null)
$got_content = true;
}
else if ($p['mimetype'] == 'application/pkcs7-mime') {
$this->parse_encrypted($p);
if ($p['structure']->ctype_parameters['smime-type'] == 'signed-data') {
$this->parse_signed($p, $body);
} else {
$this->parse_encrypted($p);
}
$got_content = true;
}
else {
Expand All @@ -418,7 +422,6 @@ function part_body($p)
// encrypted attachment, see parse_plain_encrypted()
if (!empty($p['part']->need_decryption) && $p['part']->body === null) {
$this->load_pgp_driver();

$storage = $this->rc->get_storage();
$body = $storage->get_message_part($p['object']->uid, $p['part']->mime_id, $p['part'], null, null, true, 0, false);
$result = $this->pgp_decrypt($body);
Expand Down Expand Up @@ -534,6 +537,14 @@ function parse_signed(&$p, $body = null)
if (!empty($struct->parts[1]) && $struct->parts[1]->mimetype == 'application/pkcs7-signature') {
$this->parse_smime_signed($p, $body);
}

// S/MIME smime.p7m signed
if ($p['mimetype'] == 'application/pkcs7-mime') {
if ($p['structure']->ctype_parameters['smime-type'] == 'signed-data') {
$this->parse_smime_signed($p, $body);
}
}

// PGP/MIME: RFC3156
// The multipart/signed body MUST consist of exactly two parts.
// The first part contains the signed data in MIME canonical format,
Expand Down Expand Up @@ -706,7 +717,44 @@ private function parse_smime_signed(&$p, $body = null)
return;
}

// @TODO
if ($this->rc->action != 'show' && $this->rc->action != 'preview' && $this->rc->action != 'print') {
return;
}

$this->load_smime_driver();
$struct = $p['structure'];

// Get body
if ($body === null) {
if (empty($struct->body_modified)) {
$body = $this->get_part_body($p['object'], $struct);
}
}

// XXX The parts array may be zero-length.
$msg_part = $struct->parts[0];

$sig = $this->smime_driver->verify($struct, $p['object']);

if (($sig instanceof enigma_error) && $sig->getCode() != enigma_error::KEYNOTFOUND) {
self::raise_error($sig, __LINE__);
} else {
// Store signature data for display
$this->signatures[$struct->mime_id] = $sig;
if ($struct->ctype_parameters['smime-type'] == 'signed-data') {
// for now, we get the "decrypted" message in $sig->comment,
// because verify() only returns $sig
$body = $sig->comment;

$struct = $this->parse_body($body);
// Modify original message structure
$this->modify_structure($p, $struct, strlen($body));
}
// XXX $msg_part can be null!
if (!is_null($msg_part)) {
$this->signatures[$msg_part->mime_id] = $sig;
}
}
}

/**
Expand Down Expand Up @@ -865,7 +913,6 @@ private function parse_smime_encrypted(&$p)
if (!$this->rc->config->get('enigma_decryption', true)) {
return;
}

// @TODO
}

Expand Down
1 change: 1 addition & 0 deletions plugins/enigma/lib/enigma_error.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class enigma_error
const EXPIRED = 6;
const UNVERIFIED = 7;
const NOMDC = 8;
const SERVER_VERIFIED = 9;


function __construct($code = null, $message = '', $data = [])
Expand Down
2 changes: 2 additions & 0 deletions plugins/enigma/lib/enigma_mime_message.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class enigma_mime_message extends Mail_mime
{
const PGP_SIGNED = 1;
const PGP_ENCRYPTED = 2;
const SMIME_SIGNED = 3;
const SMIME_ENCRYPTED = 4;

protected $type;
protected $message;
Expand Down
6 changes: 6 additions & 0 deletions plugins/enigma/lib/enigma_ui.php
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,12 @@ function status_message($p)
$msg = str_replace('$keyid', $sig->id, $msg);
$msg = rcube::Q($msg);
}
else if ($sig->valid === enigma_error::SERVER_VERIFIED) {
$attrib['class'] = 'enigmawarning';
$msg = str_replace('$sender', $sender, $this->enigma->gettext('sigserververified'));
$msg = str_replace('$keyid', $sig->id, $msg);
$msg = rcube::Q($msg);
}
else if ($sig->valid) {
$attrib['class'] = ($sig->partial ? 'boxwarning enigmawarning' : 'boxconfirmation enigmanotice') . ' signed';
$label = 'sigvalid' . ($sig->partial ? 'partial' : '');
Expand Down
1 change: 1 addition & 0 deletions plugins/enigma/localization/en_US.inc
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ $messages = [];
$messages['sigvalid'] = 'Verified signature from $sender.';
$messages['sigvalidpartial'] = 'Verified signature from $sender, but part of the body was not signed.';
$messages['siginvalid'] = 'Invalid signature from $sender.';
$messages['sigserververified'] = 'Server-verified signature. Certificate ID: $keyid.';
$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.';
Expand Down
6 changes: 4 additions & 2 deletions program/actions/mail/show.php
Original file line number Diff line number Diff line change
Expand Up @@ -686,7 +686,7 @@ public static function message_body($attrib)
else if ($part->type == 'content') {
// unsupported (e.g. encrypted)
if (!empty($part->realtype)) {
if ($part->realtype == 'multipart/encrypted' || $part->realtype == 'application/pkcs7-mime') {
if ($part->realtype == 'multipart/encrypted') {
if (
!empty($_SESSION['browser_caps']['pgpmime'])
&& ($pgp_mime_part = self::$MESSAGE->get_multipart_encrypted_part())
Expand All @@ -701,7 +701,9 @@ public static function message_body($attrib)
$out .= html::span('part-notice', $rcmail->gettext('encryptedmessage'));
}
}
continue;
if ($part->realtype !== 'application/pkcs7-mime') {
continue;
}
}
else if (!$part->size) {
continue;
Expand Down
4 changes: 3 additions & 1 deletion program/lib/Roundcube/rcube_message.php
Original file line number Diff line number Diff line change
Expand Up @@ -807,7 +807,9 @@ private function parse_structure($structure, $recursive = false)

$this->add_part($p);

if (!empty($structure->filename)) {
// don't display smime.p7m attachment, but display other attachments,
// since we can not display them (yet).
if (!empty($structure->filename) && $structure->filename != 'smime.p7m') {
$this->add_part($structure, 'attachment');
}
}
Expand Down