Restructure signing code

All sorts of signatures can be verified now, and it is easier to extract
information from the verified signature packets.
This commit is contained in:
Stephen Paul Weber 2013-01-20 21:44:33 -05:00
parent f9ea5ee0e5
commit 4263d03188
6 changed files with 163 additions and 53 deletions

View File

@ -171,36 +171,116 @@ class OpenPGP_Message implements IteratorAggregate, ArrayAccess {
return $bytes; return $bytes;
} }
function signature_and_data($index=0) { /**
* Extract signed objects from a well-formatted message
*
* Recurses into CompressedDataPacket
*
* <http://tools.ietf.org/html/rfc4880#section-11>
*/
function signatures() {
$msg = $this; $msg = $this;
while($msg[0] instanceof OpenPGP_CompressedDataPacket) $msg = $msg[0]; while($msg[0] instanceof OpenPGP_CompressedDataPacket) $msg = $msg[0]->data;
$i = 0; $key = NULL;
foreach($msg as $p) { $userid = NULL;
if($p instanceof OpenPGP_SignaturePacket) { $subkey = NULL;
if($i == $index) $signature_packet = $p; $sigs = array();
$i++; $final_sigs = array();
foreach($msg as $idx => $p) {
if($p instanceof OpenPGP_LiteralDataPacket) {
return array(array($p, array_values(array_filter($msg->packets, function($p) {
return $p instanceof OpenPGP_SignaturePacket;
}))));
} else if($p instanceof OpenPGP_PublicSubkeyPacket || $p instanceof OpenPGP_SecretSubkeyPacket) {
if($userid) {
array_push($final_sigs, array($key, $userid, $sigs));
$userid = NULL;
} else if($subkey) {
array_push($final_sigs, array($key, $subkey, $sigs));
$key = NULL;
}
$sigs = array();
$subkey = $p;
} else if($p instanceof OpenPGP_PublicKeyPacket) {
if($userid) {
array_push($final_sigs, array($key, $userid, $sigs));
$userid = NULL;
} else if($subkey) {
array_push($final_sigs, array($key, $subkey, $sigs));
$subkey = NULL;
} else if($key) {
array_push($final_sigs, array($key, $sigs));
$key = NULL;
}
$sigs = array();
$key = $p;
} else if($p instanceof OpenPGP_UserIDPacket) {
if($userid) {
array_push($final_sigs, array($key, $userid, $sigs));
$userid = NULL;
} else if($key) {
array_push($final_sigs, array($key, $sigs));
}
$sigs = array();
$userid = $p;
} else if($p instanceof OpenPGP_SignaturePacket) {
$sigs[] = $p;
} }
if($p instanceof OpenPGP_LiteralDataPacket) $data_packet = $p;
if(isset($signature_packet) && isset($data_packet)) break;
} }
return array($signature_packet, $data_packet); if($userid) {
array_push($final_sigs, array($key, $userid, $sigs));
} else if($subkey) {
array_push($final_sigs, array($key, $subkey, $sigs));
} else if($key) {
array_push($final_sigs, array($key, $sigs));
}
return $final_sigs;
} }
/** /**
* Function to verify signature number $index * Function to extract verified signatures
* $verifiers is an array of callbacks formatted like array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: message and signature * $verifiers is an array of callbacks formatted like array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: raw message and signature packet
*/ */
function verify($verifiers, $index=0) { function verified_signatures($verifiers) {
list($signature_packet, $data_packet) = $this->signature_and_data($index); $signed = $this->signatures();
if(!$signature_packet || !$data_packet) return NULL; // No signature or no data $vsigned = array();
$verifier = $verifiers[$signature_packet->key_algorithm_name()][$signature_packet->hash_algorithm_name()]; foreach($signed as $sign) {
if(!$verifier) return NULL; // No verifier $signatures = array_pop($sign);
$vsigs = array();
$data_packet->normalize(); foreach($signatures as $sig) {
return call_user_func($verifier, $data_packet->data.$signature_packet->trailer, $signature_packet->data); $verifier = $verifiers[$sig->key_algorithm_name()][$sig->hash_algorithm_name()];
if($verifier && $this->verify_one($verifier, $sign, $sig)) {
$vsigs[] = $sig;
}
}
array_push($sign, $vsigs);
$vsigned[] = $sign;
}
return $vsigned;
}
function verify_one($verifier, $sign, $sig) {
if($sign[0] instanceof OpenPGP_LiteralDataPacket) {
$sign[0]->normalize();
$raw = $sign[0]->data;
} else if(isset($sign[1]) && $sign[1] instanceof OpenPGP_UserIDPacket) {
$raw = implode('', array_merge($sign[0]->fingerprint_material(), array(chr(0xB4),
pack('N', strlen($sign[1]->body())), $sign[1]->body())));
} else if(isset($sign[1]) && ($sign[1] instanceof OpenPGP_PublicSubkeyPacket || $sign[1] instanceof OpenPGP_SecretSubkeyPacket)) {
$raw = implode('', array_merge($sign[0]->fingerprint_material(), $sign[1]->fingerprint_material()));
} else if($sign[0] instanceof OpenPGP_PublicKeyPacket) {
$raw = implode('', $sign[0]->fingerprint_material());
} else {
return NULL;
}
return call_user_func($verifier, $raw.$sig->trailer, $sig);
} }
// IteratorAggregate interface // IteratorAggregate interface
@ -285,8 +365,7 @@ class OpenPGP_Packet {
return array($tag, 3, (($len - 192) << 8) + ord($input[2]) + 192); return array($tag, 3, (($len - 192) << 8) + ord($input[2]) + 192);
} }
if($len == 255) { // Five octet length if($len == 255) { // Five octet length
$unpacked = unpack('N', substr($input, 2, 4)); return array($tag, 6, reset(unpack('N', substr($input, 2, 4))));
return array($tag, 6, array_pop($unpacked));
} }
// TODO: Partial body lengths. 1 << ($len & 0x1F) // TODO: Partial body lengths. 1 << ($len & 0x1F)
} }
@ -366,8 +445,7 @@ class OpenPGP_Packet {
* @see http://php.net/manual/en/function.unpack.php * @see http://php.net/manual/en/function.unpack.php
*/ */
function read_unpacked($count, $format) { function read_unpacked($count, $format) {
$unpacked = unpack($format, $this->read_bytes($count)); return reset(unpack($format, $this->read_bytes($count)));
return array_pop($unpacked);
} }
function read_byte() { function read_byte() {
@ -457,8 +535,7 @@ class OpenPGP_SignaturePacket extends OpenPGP_Packet {
$this->trailer = $this->body(true); $this->trailer = $this->body(true);
$signer = $signers[$this->key_algorithm_name()][$this->hash_algorithm_name()]; $signer = $signers[$this->key_algorithm_name()][$this->hash_algorithm_name()];
$this->data = call_user_func($signer, $this->data.$this->trailer); $this->data = call_user_func($signer, $this->data.$this->trailer);
$unpacked = unpack('n', substr($this->data, 0, 2)); $this->hash_head = reset(unpack('n', substr($this->data, 0, 2)));
$this->hash_head = array_pop($unpacked);
} }
function read() { function read() {
@ -623,8 +700,7 @@ class OpenPGP_SignaturePacket extends OpenPGP_Packet {
} }
if($len == 255) { // Five octet length if($len == 255) { // Five octet length
$length_of_length = 5; $length_of_length = 5;
$unpacked = unpack('N', substr($input, 1, 4)); $len = reset(unpack('N', substr($input, 1, 4)));
$len = array_pop($unpacked);
} }
$input = substr($input, $length_of_length); // Chop off length header $input = substr($input, $length_of_length); // Chop off length header
$tag = ord($input[0]); $tag = ord($input[0]);
@ -1520,27 +1596,27 @@ class OpenPGP_UserIDPacket extends OpenPGP_Packet {
} }
function read() { function read() {
$this->text = $this->input; $this->data = $this->input;
// User IDs of the form: "name (comment) <email>" // User IDs of the form: "name (comment) <email>"
if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->text, $matches)) { if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->data, $matches)) {
$this->name = trim($matches[1]); $this->name = trim($matches[1]);
$this->comment = trim($matches[2]); $this->comment = trim($matches[2]);
$this->email = trim($matches[3]); $this->email = trim($matches[3]);
} }
// User IDs of the form: "name <email>" // User IDs of the form: "name <email>"
else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->text, $matches)) { else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->data, $matches)) {
$this->name = trim($matches[1]); $this->name = trim($matches[1]);
$this->comment = NULL; $this->comment = NULL;
$this->email = trim($matches[2]); $this->email = trim($matches[2]);
} }
// User IDs of the form: "name" // User IDs of the form: "name"
else if (preg_match('/^([^<]+)$/', $this->text, $matches)) { else if (preg_match('/^([^<]+)$/', $this->data, $matches)) {
$this->name = trim($matches[1]); $this->name = trim($matches[1]);
$this->comment = NULL; $this->comment = NULL;
$this->email = NULL; $this->email = NULL;
} }
// User IDs of the form: "<email>" // User IDs of the form: "<email>"
else if (preg_match('/^<([^>]+)>$/', $this->text, $matches)) { else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) {
$this->name = NULL; $this->name = NULL;
$this->comment = NULL; $this->comment = NULL;
$this->email = trim($matches[2]); $this->email = trim($matches[2]);

View File

@ -50,23 +50,40 @@ class OpenPGP_Crypt_RSA {
// Pass a message to verify with this key, or a key (OpenPGP or Crypt_RSA) to check this message with // Pass a message to verify with this key, or a key (OpenPGP or Crypt_RSA) to check this message with
// Second optional parameter to specify which signature to verify (if there is more than one) // Second optional parameter to specify which signature to verify (if there is more than one)
function verify($packet, $index=0) { function verify($packet, $index=0) {
$self = $this; // For old PHP
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if($packet instanceof OpenPGP_Message && !($packet[0] instanceof OpenPGP_PublicKeyPacket)) { if(!$this->message) {
list($signature_packet, $data_packet) = $packet->signature_and_data($index); $m = $packet;
$key = $this->public_key($signature_packet->issuer()); $verifier = function($m, $s) use($self) {
if(!$key || $signature_packet->key_algorithm_name() != 'RSA') return NULL; $key = $self->public_key($s->issuer());
$key->setHash(strtolower($signature_packet->hash_algorithm_name())); if(!$key) return false;
return $packet->verify(array('RSA' => array($signature_packet->hash_algorithm_name() => function($m, $s) use($key) {return $key->verify($m, $s[0]);}))); $key->setHash(strtolower($s->hash_algorithm_name()));
return $key->verify($m, reset($s->data));
};
} else { } else {
list($signature_packet, $data_packet) = $this->message->signature_and_data($index);
if(!$this->message || $signature_packet->key_algorithm_name() != 'RSA') return NULL;
if(!($packet instanceof Crypt_RSA)) { if(!($packet instanceof Crypt_RSA)) {
$packet = new self($packet); $packet = new self($packet);
$packet = $packet->public_key($signature_packet->issuer());
} }
$packet->setHash(strtolower($signature_packet->hash_algorithm_name()));
return $this->message->verify(array('RSA' => array($signature_packet->hash_algorithm_name() => function($m, $s) use($packet) {return $packet->verify($m, $s[0]);}))); $m = $this->message;
$verifier = function($m, $s) use($self, $packet) {
if(!($packet instanceof Crypt_RSA)) {
$key = $packet->public_key($s->issuer());
}
if(!$key) return false;
$key->setHash(strtolower($s->hash_algorithm_name()));
return $key->verify($m, reset($s->data));
};
} }
return $m->verified_signatures(array('RSA' => array(
'MD5' => $verifier,
'SHA1' => $verifier,
'SHA224' => $verifier,
'SHA256' => $verifier,
'SHA384' => $verifier,
'SHA512' => $verifier
)));
} }
// Pass a message to sign with this key, or a secret key to sign this message with // Pass a message to sign with this key, or a secret key to sign this message with

View File

@ -11,5 +11,9 @@
<testsuite name="MessageVerification"> <testsuite name="MessageVerification">
<file>tests/phpseclib_suite.php</file> <file>tests/phpseclib_suite.php</file>
</testsuite> </testsuite>
<testsuite name="KeyVerification">
<file>tests/phpseclib_suite.php</file>
</testsuite>
</testsuites> </testsuites>
</phpunit> </phpunit>

BIN
tests/data/helloKey.gpg Normal file

Binary file not shown.

View File

@ -10,7 +10,7 @@ class MessageVerification extends PHPUnit_Framework_TestCase {
$pkeyM = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $pkey)); $pkeyM = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $pkey));
$m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$verify = new OpenPGP_Crypt_RSA($pkeyM); $verify = new OpenPGP_Crypt_RSA($pkeyM);
$this->assertSame($verify->verify($m), TRUE); $this->assertSame($verify->verify($m), $m->signatures());
} }
public function testUncompressedOpsRSA() { public function testUncompressedOpsRSA() {
@ -39,3 +39,16 @@ class MessageVerification extends PHPUnit_Framework_TestCase {
} }
*/ */
} }
class KeyVerification extends PHPUnit_Framework_TestCase {
public function oneKeyRSA($path) {
$m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$verify = new OpenPGP_Crypt_RSA($m);
$this->assertSame($verify->verify($m), $m->signatures());
}
public function testHelloKey() {
$this->oneKeyRSA("helloKey.gpg");
}
}

View File

@ -378,15 +378,15 @@ class Fingerprint extends PHPUnit_Framework_TestCase {
$this->oneFingerprint("000001-006.public_key", "421F28FEAAD222F856C8FFD5D4D54EA16F87040E"); $this->oneFingerprint("000001-006.public_key", "421F28FEAAD222F856C8FFD5D4D54EA16F87040E");
} }
public function test000016006public_key() { public function test000016006public_key() {
$this->oneFingerprint("000016-006.public_key", "AF95E4D7BAC521EE9740BED75E9F1523413262DC"); $this->oneFingerprint("000016-006.public_key", "AF95E4D7BAC521EE9740BED75E9F1523413262DC");
} }
public function test000027006public_key() { public function test000027006public_key() {
$this->oneFingerprint("000027-006.public_key", "1EB20B2F5A5CC3BEAFD6E5CB7732CF988A63EA86"); $this->oneFingerprint("000027-006.public_key", "1EB20B2F5A5CC3BEAFD6E5CB7732CF988A63EA86");
} }
public function test000035006public_key() { public function test000035006public_key() {
$this->oneFingerprint("000035-006.public_key", "CB7933459F59C70DF1C3FBEEDEDC3ECF689AF56D"); $this->oneFingerprint("000035-006.public_key", "CB7933459F59C70DF1C3FBEEDEDC3ECF689AF56D");
} }
} }