Skip to content

Commit

Permalink
[jan] Add Horde_Crypt_Pgp::pgpPacketInformationMultiple() and Horde_C…
Browse files Browse the repository at this point in the history
…rypt_Pgp_Backend_Binary::packetInfoMultiple() (Request #13190).
  • Loading branch information
yunosh committed Jan 26, 2016
1 parent cc271c8 commit 718c4a7
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 85 deletions.
31 changes: 31 additions & 0 deletions framework/Crypt/lib/Horde/Crypt/Pgp.php
Expand Up @@ -99,6 +99,12 @@ public function generateKey($realname, $email, $passphrase, $comment = '',
/**
* Returns information on a PGP data block.
*
* If the data block contains multiple keys, only the first is returned. To
* return all keys of this block, use pgpPacketInformationMultiple()
* instead.
*
* @see pgpPacketInformationMultiple()
*
* @param string $pgpdata The PGP data block.
*
* @return array An array with information on the PGP data block. If an
Expand Down Expand Up @@ -155,6 +161,31 @@ public function pgpPacketInformation($pgpdata)
return array();
}

/**
* Returns all information on a PGP data block.
*
* @since Horde_Crypt 2.7.0
* @see pgpPacketInformation()
*
* @param string $pgpdata The PGP data block.
*
* @return array An array with information on the PGP data block. The
* array contains one or more entries as returned from
* pgpPacketInformation().
*/
public function pgpPacketInformationMultiple($pgpdata)
{
$this->_initDrivers();

foreach ($this->_backends as $val) {
try {
return $val->packetInfoMultiple($pgpdata);
} catch (Horde_Crypt_Exception $e) {}
}

return array();
}

/**
* Returns human readable information on a PGP key.
*
Expand Down
15 changes: 15 additions & 0 deletions framework/Crypt/lib/Horde/Crypt/Pgp/Backend.php
Expand Up @@ -74,6 +74,21 @@ public function packetInfo($pgpdata)
throw new BadMethodCallException();
}

/**
* Returns all information on a PGP data block.
*
* @since Horde_Crypt 2.7.0
*
* @param string $pgpdata The PGP data block.
*
* @return array An array with information on the PGP data block.
* {@see Horde_Crypt_Pgp::pgpPacketInformationMultiple()}
*/
public function packetInfoMultiple($pgpdata)
{
throw new BadMethodCallException();
}

/**
* Returns the key ID of the key used to sign a block of PGP data.
*
Expand Down
189 changes: 110 additions & 79 deletions framework/Crypt/lib/Horde/Crypt/Pgp/Backend/Binary.php
Expand Up @@ -144,19 +144,22 @@ public function generateKey($opts)
/**
*/
public function packetInfo($pgpdata)
{
$info = $this->packetInfoMultiple($pgpdata);
return reset($info);
}

/**
*/
public function packetInfoMultiple($pgpdata)
{
$header = $keyid = null;
$input = $this->_createTempFile('horde-pgp');
$sig_id = $uid_idx = 0;
$key_idx = -1;
$out = array();

$this2 = $this;
$packetInfoKeyId = function ($input) use ($this2) {
$data = $this2->_callGpg(array('--with-colons', $input), 'r');
return preg_match('/(sec|pub):.*:.*:.*:([A-F0-9]{16}):/', $data->stdout, $matches)
? $matches[2]
: null;
};

$packetInfoHelper = function ($a) {
return chr(hexdec($a[1]));
Expand Down Expand Up @@ -193,93 +196,120 @@ public function packetInfo($pgpdata)

if (strpos($lowerLine, ':public key packet:') !== false) {
$header = 'public_key';
} elseif (strpos($lowerLine, ':secret key packet:') !== false) {
$key_idx++;
$uid_idx = 0;
continue;
}

if (strpos($lowerLine, ':secret key packet:') !== false) {
$header = 'secret_key';
} elseif (strpos($lowerLine, ':user id packet:') !== false) {
continue;
}

if (strpos($lowerLine, ':user id packet:') !== false) {
$uid_idx++;
$line = preg_replace_callback('/\\\\x([0-9a-f]{2})/', $packetInfoHelper, $line);
if (preg_match("/\"([^\<]+)\<([^\>]+)\>\"/", $line, $matches)) {
$header = 'id' . $uid_idx;
if (preg_match('/([^\(]+)\((.+)\)$/', trim($matches[1]), $comment_matches)) {
$out['signature'][$header]['name'] = trim($comment_matches[1]);
$out['signature'][$header]['comment'] = $comment_matches[2];
} else {
$out['signature'][$header]['name'] = trim($matches[1]);
$out['signature'][$header]['comment'] = '';
}
$out['signature'][$header]['email'] = $matches[2];
if (is_null($keyid)) {
$keyid = $packetInfoKeyId($input);
}
$out['signature'][$header]['keyid'] = $keyid;
if (!preg_match('/"([^\<]+)\<([^\>]+)\>"/', $line, $matches)) {
continue;
}
$header = 'id' . $uid_idx;
if (preg_match('/([^\(]+)\((.+)\)$/', trim($matches[1]), $comment_matches)) {
$out[$key_idx]['signature'][$header]['name'] = trim($comment_matches[1]);
$out[$key_idx]['signature'][$header]['comment'] = $comment_matches[2];
} else {
$out[$key_idx]['signature'][$header]['name'] = trim($matches[1]);
$out[$key_idx]['signature'][$header]['comment'] = '';
}
} elseif (strpos($lowerLine, ':signature packet:') !== false) {
$out[$key_idx]['signature'][$header]['email'] = $matches[2];
$out[$key_idx]['signature'][$header]['keyid'] = $keyid;
continue;
}

if (strpos($lowerLine, ':signature packet:') !== false) {
if (empty($header) || empty($uid_idx)) {
$header = '_SIGNATURE';
}
if (preg_match("/keyid\s+([0-9A-F]+)/i", $line, $matches)) {
$sig_id = $matches[1];
$out['signature'][$header]['sig_' . $sig_id]['keyid'] = $matches[1];
$out['keyid'] = $matches[1];
if (!preg_match('/keyid\s+([0-9A-F]+)/i', $line, $matches)) {
continue;
}
} elseif (strpos($lowerLine, ':literal data packet:') !== false) {
$sig_id = $matches[1];
$out[$key_idx]['signature'][$header]['sig_' . $sig_id]['keyid'] = $matches[1];
$out[$key_idx]['keyid'] = $matches[1];
continue;
}

if (strpos($lowerLine, ':literal data packet:') !== false) {
$header = 'literal';
} elseif (strpos($lowerLine, ':encrypted data packet:') !== false) {
continue;
}

if (strpos($lowerLine, ':encrypted data packet:') !== false) {
$header = 'encrypted';
} else {
$header = null;
continue;
}
} else {
if ($header == 'secret_key' || $header == 'public_key') {
if (preg_match("/created\s+(\d+),\s+expires\s+(\d+)/i", $line, $matches)) {
$out[$header]['created'] = $matches[1];
$out[$header]['expires'] = $matches[2];
} elseif (preg_match("/\s+[sp]key\[0\]:\s+\[(\d+)/i", $line, $matches)) {
$out[$header]['size'] = $matches[1];
} elseif (preg_match("/\s+keyid:\s+([0-9A-F]+)/i", $line, $matches)) {
$keyid = $matches[1];
}
} elseif ($header == 'literal' || $header == 'encrypted') {
$out[$header] = true;
} elseif ($header) {
if (preg_match("/version\s+\d+,\s+created\s+(\d+)/i", $line, $matches)) {
$out['signature'][$header]['sig_' . $sig_id]['created'] = $matches[1];
} elseif (isset($out['signature'][$header]['sig_' . $sig_id]['created']) &&
preg_match('/expires after (\d+y\d+d\d+h\d+m)\)$/', $line, $matches)) {
$expires = $matches[1];
preg_match('/^(\d+)y(\d+)d(\d+)h(\d+)m$/', $expires, $matches);
list(, $years, $days, $hours, $minutes) = $matches;
$out['signature'][$header]['sig_' . $sig_id]['expires'] =
strtotime('+ ' . $years . ' years + ' . $days . ' days + ' . $hours . ' hours + ' . $minutes . ' minutes', $out['signature'][$header]['sig_' . $sig_id]['created']);
} elseif (preg_match("/digest algo\s+(\d{1})/", $line, $matches)) {
$micalg = $hashAlg[$matches[1]];
$out['signature'][$header]['sig_' . $sig_id]['micalg'] = $micalg;
if ($header == '_SIGNATURE') {
/* Likely a signature block, not a key. */
$out['signature']['_SIGNATURE']['micalg'] = $micalg;
}

if (is_null($keyid)) {
$keyid = $packetInfoKeyId($input);
}
$header = null;
continue;
}

if ($sig_id == $keyid) {
/* Self signing signature - we can assume
* the micalg value from this signature is
* that for the key */
$out['signature']['_SIGNATURE']['micalg'] = $micalg;
$out['signature'][$header]['micalg'] = $micalg;
}
}
if ($header == 'secret_key' || $header == 'public_key') {
if (preg_match('/created\s+(\d+),\s+expires\s+(\d+)/i', $line, $matches)) {
$out[$key_idx][$header]['created'] = $matches[1];
$out[$key_idx][$header]['expires'] = $matches[2];
continue;
}
if (preg_match('/\s+[sp]key\[0\]:\s+\[(\d+)/i', $line, $matches)) {
$out[$key_idx][$header]['size'] = $matches[1];
continue;
}
if (preg_match('/\s+keyid:\s+([0-9A-F]+)/i', $line, $matches)) {
$keyid = $matches[1];
continue;
}
continue;
}
}

if (is_null($keyid)) {
$keyid = $packetInfoKeyId($input);
}
if ($header == 'literal' || $header == 'encrypted') {
$out[$key_idx][$header] = true;
continue;
}

$keyid && $out['keyid'] = $keyid;
if ($header) {
if (preg_match('/version\s+\d+,\s+created\s+(\d+)/i', $line, $matches)) {
$out[$key_idx]['signature'][$header]['sig_' . $sig_id]['created'] = $matches[1];
continue;
}

if (isset($out[$key_idx]['signature'][$header]['sig_' . $sig_id]['created']) &&
preg_match('/expires after (\d+y\d+d\d+h\d+m)\)$/', $line, $matches)) {
$expires = $matches[1];
preg_match('/^(\d+)y(\d+)d(\d+)h(\d+)m$/', $expires, $matches);
list(, $years, $days, $hours, $minutes) = $matches;
$out[$key_idx]['signature'][$header]['sig_' . $sig_id]['expires'] =
strtotime('+ ' . $years . ' years + ' . $days . ' days + ' . $hours . ' hours + ' . $minutes . ' minutes', $out[$key_idx]['signature'][$header]['sig_' . $sig_id]['created']);
continue;
}

if (preg_match('/digest algo\s+(\d{1})/', $line, $matches)) {
$micalg = $hashAlg[$matches[1]];
$out[$key_idx]['signature'][$header]['sig_' . $sig_id]['micalg'] = $micalg;
if ($header == '_SIGNATURE') {
/* Likely a signature block, not a key. */
$out[$key_idx]['signature']['_SIGNATURE']['micalg'] = $micalg;
}

if ($sig_id == $keyid) {
/* Self signing signature - we can assume
* the micalg value from this signature is
* that for the key */
$out[$key_idx]['signature']['_SIGNATURE']['micalg'] = $micalg;
$out[$key_idx]['signature'][$header]['micalg'] = $micalg;
}
}

continue;
}
}

return $out;
}
Expand Down Expand Up @@ -597,9 +627,10 @@ protected function _checkSignatureResult($result, $message = null)
* which does not give it access to non-puplic members, we must
* make this public until H6 when we can require at least PHP 5.4.
*/
public function _callGpg($options, $mode, $input = array(),
$output = false, $stderr = false,
$parseable = false, $verbose = false)
public function _callGpg(
$options, $mode, $input = array(), $output = false, $stderr = false,
$parseable = false, $verbose = false
)
{
$data = new stdClass;
$data->output = null;
Expand Down
14 changes: 8 additions & 6 deletions framework/Crypt/package.xml
Expand Up @@ -16,17 +16,18 @@
<email>jan@horde.org</email>
<active>yes</active>
</lead>
<date>2015-08-20</date>
<date>2016-01-26</date>
<version>
<release>2.6.2</release>
<api>2.6.0</api>
<release>2.7.0</release>
<api>2.7.0</api>
</version>
<stability>
<release>stable</release>
<api>stable</api>
</stability>
<license uri="http://www.horde.org/licenses/lgpl21">LGPL-2.1</license>
<notes>
* [jan] Add Horde_Crypt_Pgp::pgpPacketInformationMultiple() and Horde_Crypt_Pgp_Backend_Binary::packetInfoMultiple() (Request #13190).
* [jan] Fix retrieving PGP keys from the keyserver with certain HTTP client backends.
* [jan] Fix creating PGP keys with comments (Bug #14125).
* [jan] Mark PHP 7 as supported.
Expand Down Expand Up @@ -1111,14 +1112,15 @@ Initial release as a PEAR package
</release>
<release>
<version>
<release>2.6.2</release>
<api>2.6.0</api></version>
<release>2.7.0</release>
<api>2.7.0</api></version>
<stability>
<release>stable</release>
<api>stable</api></stability>
<date>2015-08-20</date>
<date>2016-01-26</date>
<license uri="http://www.horde.org/licenses/lgpl21">LGPL-2.1</license>
<notes>
* [jan] Add Horde_Crypt_Pgp::pgpPacketInformationMultiple() and Horde_Crypt_Pgp_Backend_Binary::packetInfoMultiple() (Request #13190).
* [jan] Fix retrieving PGP keys from the keyserver with certain HTTP client backends.
* [jan] Fix creating PGP keys with comments (Bug #14125).
* [jan] Mark PHP 7 as supported.
Expand Down

0 comments on commit 718c4a7

Please sign in to comment.