[imp] PATCH: Horde_Crypt_pgp::pgpPacketInformation() and related

Chris Hastie lists at oak-wood.co.uk
Sun Jan 12 18:25:09 PST 2003


On Wed, 8 Jan 2003, Chris Hastie <lists at oak-wood.co.uk> wrote
>My starter for ten on this is just as broken in many ways,

I've looked a bit more at this and come up with the attached patches.
I've grepped my installation to try to find all the areas that may be
affected by the changes, and I think I've found the lot, but there may
be issues with modules I don't have installed (I have Imp, Nag,
Kronolith, Ingo, Turba).

Below is a summary of the changes. Unless otherwise indicated all
functions are in lib/Crypt/pgp.php

pgpPacketInformation()

    This is the main change. The structure of the returned array has
    changed. Email address is no longer used as the key for user ids as
    it can not be assumed to be unique. Instead, an ID index is created
    of the form "id{n}" where {n} is a positive integer.

    A call to gpg --with-colons is used to establish the key id
    (fingerprint) as gpg --list-packets does not include information to
    enable this to be reliably established. This call is only made if we
    have established that the block being tested is a secret or public
    key, rather than, for example, a signature.

    The structure of the returned array, other than the change to the
    key for User IDs, is kept as far as possible in order to minimise
    the number of potential problems from the change. For example,
    $result['fingerprint'] and
    $result['signature']['_SIGNATURE']['micalg']

pgpPacketSignature()

    This has been changed in order to provide the same functionality as
    previously despite the change to pgpPacketInformation(). This is to
    minimise disturbance elsewhere.

    Where a key has multiple identities with the same email address,
    only information on the first one found is returned. This means that
    pgpPacketInformation() is unsuitable for use in _printKeyInfo() in
    imp/pgp.php. Accordingly, a new function has been introduced for
    this purpose.

pgpPacketSignatureByUidIndex()

    This is basically the code that used to by pgpPacketSignature(). The
    parameters are now pgp data (ie a key) and the user ID index used by
    pgpPacketInformation(). As such, it is suitable for use in
    _printKeyInfo() in imp/pgp.php.

_encryptMessage()

    A minor change was needed here to cope with the change of output
    format from pgpPacketInformation(). Assuming $key_info is the result
    of pgpPacketInformation(), the first email address in a public key
    can now be found from

    $email = $key_info['signature']['id1']['email'];

signAndEncryptMIMEPart()

    This is, I think, an unrelated issue, but the line
    $part->setContents($part->toString());
    seems to cause some odd duplication of headers and the appending of
    the body again beneath the final mime boundary, so I took it out.

_printKeyInfo() (in imp/pgp.php)

    Changed to use pgpPacketSignatureByUidIndex() in place of
    pgpPacketSignature()


Here are the patches - I'm not overly familiar with generating patches
(nor with attaching Unix text files to emails composed on a Windows box)
so I hope there's nothing too wrong with these:

-------------- next part --------------
--- lib/Crypt/pgp.php v 1.40
+++ lib/Crypt/pgp.php	Sun Jan 12 15:56:10 2003
@@ -222,14 +222,17 @@
      * [fingerprint] => Fingerprint of the PGP data (if available)
      *                  16-bit hex value
      *
-     * * There will be an email_address entry for each signature
-     *   that appears in the packet. If there is no user ID for the
-     *   signature information (e.g. a signature packet), the data will
-     *   be stored under the special keyword '_SIGNATURE'.
+     * 
+     * Each User ID will be stored in the array 'signature' and
+     * have data associated with it, including 
+     * an array for information on each signature that has signed
+     * that UID. Signatures not associated with a UID (eg revocation
+     * signatures and sub keys) will be stored under the special 
+     * keyword '_SIGNATURE'.
      *
      * [signature] => Array
      *   (
-     *     [__email-address__] => Array
+     *     [id{n}]/[_SIGNATURE] => Array
      *       (
      *         [name]        => Full Name
      *         [comment]     => Comment
@@ -237,6 +240,12 @@
      *         [fingerprint] => 16-bit hex value
      *         [created]     => Signature creation - UNIX timestamp
      *         [micalg]      => The hash used to create the signature
+     *         [sig_{hex}]   => Array (details of a signature verifying the ID)
+     *           (
+     *             [fingerprint] => 16-bit hex value
+     *             [created]     => Signature creation - UNIX timestamp
+     *             [micalg]      => The hash used to create the signature
+     *           )
      *       )
      *   )
      * </pre>
@@ -247,6 +256,8 @@
         $header = null;
         $input = $this->_createTempFile('horde-pgp');
         $packetdata = '';
+        $uid_idx = 0;
+        $fingerprint = '';
 
         /* Store message in temporary file. */
         $fp1 = fopen($input, 'w+');
@@ -263,24 +276,39 @@
             /* Headers are prefaced with a ':' as the first character
                on the line. */
             if (strpos($line, ':') === 0) {
+                /* if we have a key (rather than a signature block), get the 
+                   key's fingerprint */
+                if (stristr($line, ':public key packet:') || stristr($line, ':secret key packet:')) {
+                    $fp3 = popen($this->_gnupg . '--with-colons ' . $input , 'r');
+                    while (!feof($fp3)) {
+                        $fpresult = fgets($fp3, 1024);
+                        if (preg_match("/(sec|pub):.*:.*:.*:([A-F0-9]{16}):/", $fpresult, $matches)) {
+                            $fingerprint = $matches[2];                            
+                            break;
+                        }
+                    }
+                    pclose($fp3);
+                }                
                 if (stristr($line, ':public key packet:')) {
                     $header = 'public_key';
                 } elseif (stristr($line, ':secret key packet:')) {
                     $header = 'secret_key';
                 } elseif (stristr($line, ':user ID packet:')) {
+                    $uid_idx ++;
                     if (preg_match("/\"([^\(\<]+)\s+(?:\(([^\)]*)\))*\s*\<([^\>]+)\>\"/", $line, $matches)) {
-                        $header = $matches[3];
+                        $header = 'id'.$uid_idx;
                         $data_array['signature'][$header]['name'] = $matches[1];
                         $data_array['signature'][$header]['comment'] = $matches[2];
                         $data_array['signature'][$header]['email'] = $matches[3];
+                        $data_array['signature'][$header]['fingerprint'] = $fingerprint;
                     }
                 } elseif (stristr($line, ':signature packet:')) {
-                    if (empty($header)) {
+                    if ((empty($header)) || !$uid_idx) {
                         $header = '_SIGNATURE';
                     }
                     if (preg_match("/keyid\s+([0-9A-F]+)/i", $line, $matches)) {
-                        $data_array['signature'][$header]['fingerprint'] = $matches[1];
-                        $data_array['fingerprint'] = $matches[1];
+                        $sig_id = $matches[1];
+                        $data_array['signature'][$header]["sig_".$sig_id]['fingerprint'] = $matches[1];                        
                     }
                 } else {
                     $header = null;
@@ -295,19 +323,32 @@
                     }
                 } elseif ($header) {
                     if (preg_match("/version\s+\d+,\s+created\s+(\d+)/i", $line, $matches)) {
-                        $data_array['signature'][$header]['created'] = $matches[1];
+                        $data_array['signature'][$header]["sig_".$sig_id]['created'] = $matches[1];
                     } elseif (preg_match("/digest algo\s+(\d{1})/", $line, $matches)) {
-                        $data_array['signature'][$header]['micalg'] = $this->_hashAlg[$matches[1]];
+                        $micalg = $this->_hashAlg[$matches[1]];
+                        $data_array['signature'][$header]["sig_".$sig_id]['micalg'] = $micalg;
+                        if ($header == '_SIGNATURE') {
+                            /* likely a signature block, not a key */
+                            $data_array['signature']['_SIGNATURE']['micalg'] = $micalg;
                     }
+                        if ($sig_id == $fingerprint){
+                            /* self signing signature - we can assume the micalg
+                            value from this signature is that for the key */
+                            $data_array['signature']['_SIGNATURE']['micalg'] = $micalg;
+                            $data_array['signature'][$header]['micalg'] = $micalg;
                 }
             }
         }
-
+            }
+        }
+        $fingerprint && $data_array['fingerprint'] = $fingerprint;
         return $data_array;
     }
 
     /**
      * Return information on a PGP signature embedded in PGP data.
+     * Returns only information on the first ID that matches the 
+     * email address input.
      *
      * @access public
      *
@@ -337,19 +378,110 @@
         $data = $this->pgpPacketInformation($pgpdata);
         $key_type = null;
         $return_array = array();
+        $email_found = 0;
 
-        /* Search for the e-mail address.  If not found, return the empty
+        /* Check that [signature] key exists.  If not found, return the empty
            array. */
-        if (!array_key_exists('signature', $data) ||
-            !array_key_exists($email, $data['signature'])) {
+        if (!array_key_exists('signature', $data)) {
             return $return_array;
         }
 
         /* Store the signature information now. */
+        if ($email == '_SIGNATURE' && array_key_exists('_SIGNATURE', $data['signature'])){
+            $email_found = 1;
         foreach ($data['signature'][$email] as $key => $value) {
             $return_array[$key] = $value;
         }
 
+        } else {
+            $uid_idx = 1;
+            while (array_key_exists ('id'.$uid_idx, $data['signature'])){
+                if ($data['signature']['id'.$uid_idx]['email'] == $email) {
+                    $email_found = 1;
+                    foreach ($data['signature']['id'.$uid_idx] as $key => $value) {
+                        $return_array[$key] = $value;
+                    } 
+                break;
+                }
+            $uid_idx ++;
+            }
+        }
+        
+        /* if we didn't find the email address, return an empty 
+           array */
+        if (!$email_found){
+            $return_array = array();
+            return $return_array;
+        }
+        
+        /* Store any public/private key information. */
+        if (array_key_exists('public_key', $data)) {
+            $key_type = 'public_key';
+        } elseif (array_key_exists('secret_key', $data)) {
+            $key_type = 'secret_key';
+        }
+        if (isset($key_type)) {
+            $return_array['key_type'] = $key_type;
+            if (array_key_exists('created', $data[$key_type])) {
+                $return_array['key_created'] = $data[$key_type]['created'];
+            }
+            if (array_key_exists('expires', $data[$key_type])) {
+                $return_array['key_expires'] = $data[$key_type]['expires'];
+            }
+            if (array_key_exists('size', $data[$key_type])) {
+                $return_array['key_size'] = $data[$key_type]['size'];
+            }
+        }
+
+        return $return_array;
+    }
+    /**
+     * Return information on a PGP signature embedded in PGP data.
+     * Similar to pgpPacketSignature(), but returns information by 
+     * unique User ID Index (format id{n} where n is an interger of
+     * 1 or more.
+     *
+     * @access public
+     *
+     * @param string $pgpdata  The PGP data block.
+     * @param string $email    An e-mail address.
+     *
+     * @return array  An array with information on the PGP data block.
+     *                If an element is not present in the data block,
+     *                it will likewise not be set in the array.
+     * <pre>
+     * Array Fields:
+     * -------------
+     * key_created  =>  Key creation - UNIX timestamp
+     * key_expires  =>  Key expiration - UNIX timestamp (0 = never expires)
+     * key_size     =>  Size of the key in bits
+     * key_type     =>  The key type (public_key or secret_key)
+     * name         =>  Full Name
+     * comment      =>  Comment
+     * email        =>  E-mail Address
+     * fingerprint  =>  16-bit hex value
+     * created      =>  Signature creation - UNIX timestamp
+     * micalg       =>  The hash used to create the signature
+     * </pre>
+     */
+    function pgpPacketSignatureByUidIndex($pgpdata, $uid_idx)
+    {
+        $data = $this->pgpPacketInformation($pgpdata);
+        $key_type = null;
+        $return_array = array();
+
+        /* Search for the uid index.  If not found, return the empty
+           array. */
+        if (!array_key_exists('signature', $data) ||
+            !array_key_exists($uid_idx, $data['signature'])) {
+            return $return_array;
+        }
+
+        /* Store the signature information now. */
+        foreach ($data['signature'][$uid_idx] as $key => $value) {
+            $return_array[$key] = $value;
+        }
+
         /* Store any public/private key information. */
         if (array_key_exists('public_key', $data)) {
             $key_type = 'public_key';
@@ -761,8 +892,7 @@
         if (empty($email)) {
             $key_info = $this->pgpPacketInformation($params['pubkey']);
             if (array_key_exists('signature', $key_info)) {
-                reset($key_info['signature']);
-                $email = key($key_info['signature']);
+                $email = $key_info['signature']['id1']['email'];
             } else {
                 return PEAR::raiseError(_("Could not determine the recipient's e-mail address."), 'horde.error');
             }
@@ -1113,7 +1243,6 @@
         if (is_a($part, 'PEAR_Error')) {
             return $part;
         }
-        $part->setContents($part->toString());
         $part = Horde_Crypt_pgp::encryptMIMEPart($part, $encrypt_params);
         $part->setContents('This message is in MIME format and has been PGP signed and encrypted.' . "\n");
         $part->setDescription(_("PGP Signed/Encrypted Data"));
-------------- next part --------------

-------------- next part --------------
--- imp/pgp.php v 2.50
+++ imp/pgp.php	Sun Jan 12 15:59:43 2003
@@ -18,9 +18,9 @@
     if (empty($packet_info)) {
         _textWindowOutput('PGP Key Information', _("Invalid key"));
     } else {
-        foreach (array_keys($packet_info['signature']) as $email) {
-            if ($email == '_SIGNATURE') continue;
-            $key_info = $imp_pgp->pgpPacketSignature($key, $email);
+        foreach (array_keys($packet_info['signature']) as $uid_idx) {
+            if ($uid_idx == '_SIGNATURE') continue;
+            $key_info = $imp_pgp->pgpPacketSignatureByUidIndex($key, $uid_idx);
             $msg .= _("Name") . ":             " . stripcslashes($key_info['name']) . "\n";
             $msg .= _("Key Type") . ":         " . (($key_info['key_type'] == 'public_key') ? _("Public Key") : _("Private Key")) . "\n";
             $msg .= _("Key Creation") . ":     " . strftime("%D", $key_info['key_created']) . "\n";
-------------- next part --------------

-- 
Chris Hastie


More information about the imp mailing list