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:
parent
f9ea5ee0e5
commit
4263d03188
140
lib/openpgp.php
140
lib/openpgp.php
@ -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]);
|
||||||
|
@ -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
|
||||||
|
@ -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
BIN
tests/data/helloKey.gpg
Normal file
Binary file not shown.
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user