OpenPGP PHP
 All Classes Namespaces Functions Variables
openpgp.php
1 <?php
2 // This is free and unencumbered software released into the public domain.
14 // OpenPGP utilities
16 
20 class OpenPGP {
26  static function enarmor($data, $marker = 'MESSAGE', array $headers = array()) {
27  $text = self::header($marker) . "\n";
28  foreach ($headers as $key => $value) {
29  $text .= $key . ': ' . (string)$value . "\n";
30  }
31  $text .= "\n" . base64_encode($data);
32  $text .= "\n".'=' . base64_encode(substr(pack('N', self::crc24($data)), 1)) . "\n";
33  $text .= self::footer($marker) . "\n";
34  return $text;
35  }
36 
41  static function unarmor($text, $header = 'PGP PUBLIC KEY BLOCK') {
42  $header = self::header($header);
43  $text = str_replace(array("\r\n", "\r"), array("\n", ''), $text);
44  if (($pos1 = strpos($text, $header)) !== FALSE &&
45  ($pos1 = strpos($text, "\n\n", $pos1 += strlen($header))) !== FALSE &&
46  ($pos2 = strpos($text, "\n=", $pos1 += 2)) !== FALSE) {
47  return base64_decode($text = substr($text, $pos1, $pos2 - $pos1));
48  }
49  }
50 
54  static function header($marker) {
55  return '-----BEGIN ' . strtoupper((string)$marker) . '-----';
56  }
57 
61  static function footer($marker) {
62  return '-----END ' . strtoupper((string)$marker) . '-----';
63  }
64 
69  static function crc24($data) {
70  $crc = 0x00b704ce;
71  for ($i = 0; $i < strlen($data); $i++) {
72  $crc ^= (ord($data[$i]) & 255) << 16;
73  for ($j = 0; $j < 8; $j++) {
74  $crc <<= 1;
75  if ($crc & 0x01000000) {
76  $crc ^= 0x01864cfb;
77  }
78  }
79  }
80  return $crc & 0x00ffffff;
81  }
82 
86  static function bitlength($data) {
87  return (strlen($data) - 1) * 8 + (int)floor(log(ord($data[0]), 2)) + 1;
88  }
89 
90  static function decode_s2k_count($c) {
91  return ((int)16 + ($c & 15)) << (($c >> 4) + 6);
92  }
93 
94  static function encode_s2k_count($iterations) {
95  if($iterations >= 65011712) return 255;
96 
97  $count = $iterations >> 6;
98  $c = 0;
99  while($count >= 32) {
100  $count = $count >> 1;
101  $c++;
102  }
103  $result = ($c << 4) | ($count - 16);
104 
105  if(OpenPGP::decode_s2k_count($result) < $iterations) {
106  return $result + 1;
107  }
108 
109  return $result;
110  }
111 }
112 
113 class OpenPGP_S2K {
115 
116  function __construct($salt='BADSALT', $hash_algorithm=10, $count=65536, $type=3) {
117  $this->type = $type;
118  $this->hash_algorithm = $hash_algorithm;
119  $this->salt = $salt;
120  $this->count = $count;
121  }
122 
123  static function parse(&$input) {
124  $s2k = new OpenPGP_S2k();
125  switch($s2k->type = ord($input{0})) {
126  case 0:
127  $s2k->hash_algorithm = ord($input{1});
128  $input = substr($input, 2);
129  break;
130  case 1:
131  $s2k->hash_algorithm = ord($input{1});
132  $s2k->salt = substr($input, 2, 8);
133  $input = substr($input, 10);
134  break;
135  case 3:
136  $s2k->hash_algorithm = ord($input{1});
137  $s2k->salt = substr($input, 2, 8);
138  $s2k->count = OpenPGP::decode_s2k_count(ord($input{10}));
139  $input = substr($input, 11);
140  break;
141  }
142 
143  return $s2k;
144  }
145 
146  function to_bytes() {
147  $bytes = chr($this->type);
148  switch($this->type) {
149  case 0:
150  $bytes .= chr($this->hash_algorithm);
151  break;
152  case 1:
153  $bytes .= chr($this->hash_algorithm);
154  $bytes .= $this->salt;
155  break;
156  case 3:
157  $bytes .= chr($this->hash_algorithm);
158  $bytes .= $this->salt;
159  $bytes .= chr(OpenPGP::encode_s2k_count($this->count));
160  break;
161  }
162  return $bytes;
163  }
164 
165  function raw_hash($s) {
166  return hash(strtolower(OpenPGP_SignaturePacket::$hash_algorithms[$this->hash_algorithm]), $s, true);
167  }
168 
169  function sized_hash($s, $size) {
170  $hash = $this->raw_hash($s);
171  while(strlen($hash) < $size) {
172  $s = "\0" . $s;
173  $hash .= $this->raw_hash($s);
174  }
175 
176  return substr($hash, 0, $size);
177  }
178 
179  function iterate($s) {
180  if(strlen($s) >= $this->count) return $s;
181  $s = str_repeat($s, ceil($this->count / strlen($s)));
182  return substr($s, 0, $this->count);
183  }
184 
185  function make_key($pass, $size) {
186  switch($this->type) {
187  case 0:
188  return $this->sized_hash($pass, $size);
189  case 1:
190  return $this->sized_hash($this->salt . $pass, $size);
191  case 3:
192  return $this->sized_hash($this->iterate($this->salt . $pass), $size);
193  }
194  }
195 }
196 
198 // OpenPGP messages
199 
205 class OpenPGP_Message implements IteratorAggregate, ArrayAccess {
206  public $uri = NULL;
207  public $packets = array();
208 
209  static function parse_file($path) {
210  if (($msg = self::parse(file_get_contents($path)))) {
211  $msg->uri = preg_match('!^[\w\d]+://!', $path) ? $path : 'file://' . realpath($path);
212  return $msg;
213  }
214  }
215 
220  static function parse($input) {
221  if (is_resource($input)) {
222  return self::parse_stream($input);
223  }
224  if (is_string($input)) {
225  return self::parse_string($input);
226  }
227  }
228 
229  static function parse_stream($input) {
230  return self::parse_string(stream_get_contents($input));
231  }
232 
233  static function parse_string($input) {
234  $msg = new self;
235  while (($length = strlen($input)) > 0) {
236  if (($packet = OpenPGP_Packet::parse($input))) {
237  $msg[] = $packet;
238  }
239  if ($length == strlen($input)) { // is parsing stuck?
240  break;
241  }
242  }
243  return $msg;
244  }
245 
246  function __construct(array $packets = array()) {
247  $this->packets = $packets;
248  }
249 
250  function to_bytes() {
251  $bytes = '';
252  foreach($this as $p) {
253  $bytes .= $p->to_bytes();
254  }
255  return $bytes;
256  }
257 
265  function signatures() {
266  $msg = $this;
267  while($msg[0] instanceof OpenPGP_CompressedDataPacket) $msg = $msg[0]->data;
268 
269  $key = NULL;
270  $userid = NULL;
271  $subkey = NULL;
272  $sigs = array();
273  $final_sigs = array();
274 
275  foreach($msg as $idx => $p) {
276  if($p instanceof OpenPGP_LiteralDataPacket) {
277  return array(array($p, array_values(array_filter($msg->packets, function($p) {
278  return $p instanceof OpenPGP_SignaturePacket;
279  }))));
280  } else if($p instanceof OpenPGP_PublicSubkeyPacket || $p instanceof OpenPGP_SecretSubkeyPacket) {
281  if($userid) {
282  array_push($final_sigs, array($key, $userid, $sigs));
283  $userid = NULL;
284  } else if($subkey) {
285  array_push($final_sigs, array($key, $subkey, $sigs));
286  $key = NULL;
287  }
288  $sigs = array();
289  $subkey = $p;
290  } else if($p instanceof OpenPGP_PublicKeyPacket) {
291  if($userid) {
292  array_push($final_sigs, array($key, $userid, $sigs));
293  $userid = NULL;
294  } else if($subkey) {
295  array_push($final_sigs, array($key, $subkey, $sigs));
296  $subkey = NULL;
297  } else if($key) {
298  array_push($final_sigs, array($key, $sigs));
299  $key = NULL;
300  }
301  $sigs = array();
302  $key = $p;
303  } else if($p instanceof OpenPGP_UserIDPacket) {
304  if($userid) {
305  array_push($final_sigs, array($key, $userid, $sigs));
306  $userid = NULL;
307  } else if($key) {
308  array_push($final_sigs, array($key, $sigs));
309  }
310  $sigs = array();
311  $userid = $p;
312  } else if($p instanceof OpenPGP_SignaturePacket) {
313  $sigs[] = $p;
314  }
315  }
316 
317  if($userid) {
318  array_push($final_sigs, array($key, $userid, $sigs));
319  } else if($subkey) {
320  array_push($final_sigs, array($key, $subkey, $sigs));
321  } else if($key) {
322  array_push($final_sigs, array($key, $sigs));
323  }
324 
325  return $final_sigs;
326  }
327 
332  function verified_signatures($verifiers) {
333  $signed = $this->signatures();
334  $vsigned = array();
335 
336  foreach($signed as $sign) {
337  $signatures = array_pop($sign);
338  $vsigs = array();
339 
340  foreach($signatures as $sig) {
341  $verifier = $verifiers[$sig->key_algorithm_name()][$sig->hash_algorithm_name()];
342  if($verifier && $this->verify_one($verifier, $sign, $sig)) {
343  $vsigs[] = $sig;
344  }
345  }
346  array_push($sign, $vsigs);
347  $vsigned[] = $sign;
348  }
349 
350  return $vsigned;
351  }
352 
353  function verify_one($verifier, $sign, $sig) {
354  if($sign[0] instanceof OpenPGP_LiteralDataPacket) {
355  $sign[0]->normalize();
356  $raw = $sign[0]->data;
357  } else if(isset($sign[1]) && $sign[1] instanceof OpenPGP_UserIDPacket) {
358  $raw = implode('', array_merge($sign[0]->fingerprint_material(), array(chr(0xB4),
359  pack('N', strlen($sign[1]->body())), $sign[1]->body())));
360  } else if(isset($sign[1]) && ($sign[1] instanceof OpenPGP_PublicSubkeyPacket || $sign[1] instanceof OpenPGP_SecretSubkeyPacket)) {
361  $raw = implode('', array_merge($sign[0]->fingerprint_material(), $sign[1]->fingerprint_material()));
362  } else if($sign[0] instanceof OpenPGP_PublicKeyPacket) {
363  $raw = implode('', $sign[0]->fingerprint_material());
364  } else {
365  return NULL;
366  }
367  return call_user_func($verifier, $raw.$sig->trailer, $sig);
368  }
369 
370  // IteratorAggregate interface
371 
372  function getIterator() {
373  return new ArrayIterator($this->packets);
374  }
375 
376  // ArrayAccess interface
377 
378  function offsetExists($offset) {
379  return isset($this->packets[$offset]);
380  }
381 
382  function offsetGet($offset) {
383  return $this->packets[$offset];
384  }
385 
386  function offsetSet($offset, $value) {
387  return is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value;
388  }
389 
390  function offsetUnset($offset) {
391  unset($this->packets[$offset]);
392  }
393 }
394 
396 // OpenPGP packets
397 
405  public $tag, $size, $data;
406 
407  static function class_for($tag) {
408  return isset(self::$tags[$tag]) && class_exists(
409  $class = 'OpenPGP_' . self::$tags[$tag] . 'Packet') ? $class : __CLASS__;
410  }
411 
417  static function parse(&$input) {
418  $packet = NULL;
419  if (strlen($input) > 0) {
420  $parser = ord($input[0]) & 64 ? 'parse_new_format' : 'parse_old_format';
421  list($tag, $head_length, $data_length) = self::$parser($input);
422  $input = substr($input, $head_length);
423  if ($tag && ($class = self::class_for($tag))) {
424  $packet = new $class();
425  $packet->tag = $tag;
426  $packet->input = substr($input, 0, $data_length);
427  $packet->length = $data_length;
428  $packet->read();
429  unset($packet->input);
430  unset($packet->length);
431  }
432  $input = substr($input, $data_length);
433  }
434  return $packet;
435  }
436 
442  static function parse_new_format($input) {
443  $tag = ord($input[0]) & 63;
444  $len = ord($input[1]);
445  if($len < 192) { // One octet length
446  return array($tag, 2, $len);
447  }
448  if($len > 191 && $len < 224) { // Two octet length
449  return array($tag, 3, (($len - 192) << 8) + ord($input[2]) + 192);
450  }
451  if($len == 255) { // Five octet length
452  $unpacked = unpack('N', substr($input, 2, 4));
453  return array($tag, 6, reset($unpacked));
454  }
455  // TODO: Partial body lengths. 1 << ($len & 0x1F)
456  }
457 
463  static function parse_old_format($input) {
464  $len = ($tag = ord($input[0])) & 3;
465  $tag = ($tag >> 2) & 15;
466  switch ($len) {
467  case 0: // The packet has a one-octet length. The header is 2 octets long.
468  $head_length = 2;
469  $data_length = ord($input[1]);
470  break;
471  case 1: // The packet has a two-octet length. The header is 3 octets long.
472  $head_length = 3;
473  $data_length = unpack('n', substr($input, 1, 2));
474  $data_length = $data_length[1];
475  break;
476  case 2: // The packet has a four-octet length. The header is 5 octets long.
477  $head_length = 5;
478  $data_length = unpack('N', substr($input, 1, 4));
479  $data_length = $data_length[1];
480  break;
481  case 3: // The packet is of indeterminate length. The header is 1 octet long.
482  $head_length = 1;
483  $data_length = strlen($input) - $head_length;
484  break;
485  }
486  return array($tag, $head_length, $data_length);
487  }
488 
489  function __construct($data=NULL) {
490  $this->tag = array_search(substr(substr(get_class($this), 8), 0, -6), self::$tags);
491  $this->data = $data;
492  }
493 
494  function read() {
495  }
496 
497  function body() {
498  return $this->data; // Will normally be overridden by subclasses
499  }
500 
501  function header_and_body() {
502  $body = $this->body(); // Get body first, we will need it's length
503  $tag = chr($this->tag | 0xC0); // First two bits are 1 for new packet format
504  $size = chr(255).pack('N', strlen($body)); // Use 5-octet lengths
505  return array('header' => $tag.$size, 'body' => $body);
506  }
507 
508  function to_bytes() {
509  $data = $this->header_and_body();
510  return $data['header'].$data['body'];
511  }
512 
516  function read_timestamp() {
517  return $this->read_unpacked(4, 'N');
518  }
519 
523  function read_mpi() {
524  $length = $this->read_unpacked(2, 'n'); // length in bits
525  $length = (int)floor(($length + 7) / 8); // length in bytes
526  return $this->read_bytes($length);
527  }
528 
532  function read_unpacked($count, $format) {
533  $unpacked = unpack($format, $this->read_bytes($count));
534  return reset($unpacked);
535  }
536 
537  function read_byte() {
538  return ($bytes = $this->read_bytes()) ? $bytes[0] : NULL;
539  }
540 
541  function read_bytes($count = 1) {
542  $bytes = substr($this->input, 0, $count);
543  $this->input = substr($this->input, $count);
544  return $bytes;
545  }
546 
547  static $tags = array(
548  1 => 'AsymmetricSessionKey', // Public-Key Encrypted Session Key
549  2 => 'Signature', // Signature Packet
550  3 => 'SymmetricSessionKey', // Symmetric-Key Encrypted Session Key Packet
551  4 => 'OnePassSignature', // One-Pass Signature Packet
552  5 => 'SecretKey', // Secret-Key Packet
553  6 => 'PublicKey', // Public-Key Packet
554  7 => 'SecretSubkey', // Secret-Subkey Packet
555  8 => 'CompressedData', // Compressed Data Packet
556  9 => 'EncryptedData', // Symmetrically Encrypted Data Packet
557  10 => 'Marker', // Marker Packet
558  11 => 'LiteralData', // Literal Data Packet
559  12 => 'Trust', // Trust Packet
560  13 => 'UserID', // User ID Packet
561  14 => 'PublicSubkey', // Public-Subkey Packet
562  17 => 'UserAttribute', // User Attribute Packet
563  18 => 'IntegrityProtectedData', // Sym. Encrypted and Integrity Protected Data Packet
564  19 => 'ModificationDetectionCode', // Modification Detection Code Packet
565  60 => 'Experimental', // Private or Experimental Values
566  61 => 'Experimental', // Private or Experimental Values
567  62 => 'Experimental', // Private or Experimental Values
568  63 => 'Experimental', // Private or Experimental Values
569  );
570 }
571 
579 
581  parent::__construct();
582  $this->version = $version;
583  $this->keyid = substr($keyid, -16);
584  $this->key_algorithm = $key_algorithm;
585  $this->encrypted_data = $encrypted_data;
586  }
587 
588  function read() {
589  switch($this->version = ord($this->read_byte())) {
590  case 3:
591  $rawkeyid = $this->read_bytes(8);
592  $this->keyid = '';
593  for($i = 0; $i < strlen($rawkeyid); $i++) { // Store KeyID in Hex
594  $this->keyid .= sprintf('%02X',ord($rawkeyid{$i}));
595  }
596 
597  $this->key_algorithm = ord($this->read_byte());
598 
599  $this->encrypted_data = $this->input;
600  break;
601  default:
602  throw new Exception("Unsupported AsymmetricSessionKeyPacket version: " . $this->version);
603  }
604  }
605 
606  function body() {
607  $bytes = chr($this->version);
608 
609  for($i = 0; $i < strlen($this->keyid); $i += 2) {
610  $bytes .= chr(hexdec($this->keyid{$i}.$this->keyid{$i+1}));
611  }
612 
613  $bytes .= chr($this->key_algorithm);
614  $bytes .= $this->encrypted_data;
615  return $bytes;
616  }
617 }
618 
627  public $trailer; // This is the literal bytes that get tacked on the end of the message when verifying the signature
628 
629  function __construct($data=NULL, $key_algorithm=NULL, $hash_algorithm=NULL) {
630  parent::__construct();
631  $this->version = 4; // Default to version 4 sigs
632  if(is_string($this->hash_algorithm = $hash_algorithm)) {
633  $this->hash_algorithm = array_search($this->hash_algorithm, self::$hash_algorithms);
634  }
635  if(is_string($this->key_algorithm = $key_algorithm)) {
636  $this->key_algorithm = array_search($this->key_algorithm, OpenPGP_PublicKeyPacket::$algorithms);
637  }
638  if($data) { // If we have any data, set up the creation time
639  $this->hashed_subpackets = array(new OpenPGP_SignaturePacket_SignatureCreationTimePacket(time()));
640  }
641  if($data instanceof OpenPGP_LiteralDataPacket) {
642  $this->signature_type = ($data->format == 'b') ? 0x00 : 0x01;
643  $data->normalize();
644  $data = $data->data;
645  } else if($data instanceof OpenPGP_Message && $data[0] instanceof OpenPGP_PublicKeyPacket) {
646  // $data is a message with PublicKey first, UserID second
647  $key = implode('', $data[0]->fingerprint_material());
648  $user_id = $data[1]->body();
649  $data = $key . chr(0xB4) . pack('N', strlen($user_id)) . $user_id;
650  }
651  $this->data = $data; // Store to-be-signed data in here until the signing happens
652  }
653 
658  function sign_data($signers) {
659  $this->trailer = $this->calculate_trailer();
660  $signer = $signers[$this->key_algorithm_name()][$this->hash_algorithm_name()];
661  $this->data = call_user_func($signer, $this->data.$this->trailer);
662  $unpacked = unpack('n', substr(implode('',$this->data), 0, 2));
663  $this->hash_head = reset($unpacked);
664  }
665 
666  function read() {
667  switch($this->version = ord($this->read_byte())) {
668  case 2:
669  case 3:
670  assert(ord($this->read_byte()) == 5);
671  $this->signature_type = ord($this->read_byte());
672  $creation_time = $this->read_timestamp();
673  $keyid = $this->read_bytes(8);
674  $keyidHex = '';
675  for($i = 0; $i < strlen($keyid); $i++) { // Store KeyID in Hex
676  $keyidHex .= sprintf('%02X',ord($keyid{$i}));
677  }
678 
679  $this->hashed_subpackets = array();
680  $this->unhashed_subpackets = array(
683  );
684 
685  $this->key_algorithm = ord($this->read_byte());
686  $this->hash_algorithm = ord($this->read_byte());
687  $this->hash_head = $this->read_unpacked(2, 'n');
688  $this->data = array();
689  while(strlen($this->input) > 0) {
690  $this->data[] = $this->read_mpi();
691  }
692  break;
693  case 4:
694  $this->signature_type = ord($this->read_byte());
695  $this->key_algorithm = ord($this->read_byte());
696  $this->hash_algorithm = ord($this->read_byte());
697  $this->trailer = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm);
698 
699  $hashed_size = $this->read_unpacked(2, 'n');
700  $hashed_subpackets = $this->read_bytes($hashed_size);
701  $this->trailer .= pack('n', $hashed_size).$hashed_subpackets;
702  $this->hashed_subpackets = self::get_subpackets($hashed_subpackets);
703 
704  $this->trailer .= chr(4).chr(0xff).pack('N', 6 + $hashed_size);
705 
706  $unhashed_size = $this->read_unpacked(2, 'n');
707  $this->unhashed_subpackets = self::get_subpackets($this->read_bytes($unhashed_size));
708 
709  $this->hash_head = $this->read_unpacked(2, 'n');
710 
711  $this->data = array();
712  while(strlen($this->input) > 0) {
713  $this->data[] = $this->read_mpi();
714  }
715  break;
716  }
717  }
718 
719  function calculate_trailer() {
720  // The trailer is just the top of the body plus some crap
721  $body = $this->body_start();
722  return $body.chr(4).chr(0xff).pack('N', strlen($body));
723  }
724 
725  function body_start() {
726  $body = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm);
727 
728  $hashed_subpackets = '';
729  foreach((array)$this->hashed_subpackets as $p) {
730  $hashed_subpackets .= $p->to_bytes();
731  }
732  $body .= pack('n', strlen($hashed_subpackets)).$hashed_subpackets;
733 
734  return $body;
735  }
736 
737  function body() {
738  switch($this->version) {
739  case 2:
740  case 3:
741  $body = chr($this->version) . chr(5) . chr($this->signature_type);
742 
743  foreach((array)$this->unhashed_subpackets as $p) {
745  $body .= pack('N', $p->data);
746  break;
747  }
748  }
749 
750  foreach((array)$this->unhashed_subpackets as $p) {
751  if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) {
752  for($i = 0; $i < strlen($p->data); $i += 2) {
753  $body .= chr(hexdec($p->data{$i}.$p->data{$i+1}));
754  }
755  break;
756  }
757  }
758 
759  $body .= chr($this->key_algorithm);
760  $body .= chr($this->hash_algorithm);
761  $body .= pack('n', $this->hash_head);
762 
763  foreach($this->data as $mpi) {
764  $body .= pack('n', OpenPGP::bitlength($mpi)).$mpi;
765  }
766 
767  return $body;
768  case 4:
769  if(!$this->trailer) $this->trailer = $this->calculate_trailer();
770  $body = substr($this->trailer, 0, -6);
771 
773  foreach((array)$this->unhashed_subpackets as $p) {
774  $unhashed_subpackets .= $p->to_bytes();
775  }
776  $body .= pack('n', strlen($unhashed_subpackets)).$unhashed_subpackets;
777 
778  $body .= pack('n', $this->hash_head);
779 
780  foreach((array)$this->data as $mpi) {
781  $body .= pack('n', OpenPGP::bitlength($mpi)).$mpi;
782  }
783 
784  return $body;
785  }
786  }
787 
788  function key_algorithm_name() {
789  return OpenPGP_PublicKeyPacket::$algorithms[$this->key_algorithm];
790  }
791 
792  function hash_algorithm_name() {
793  return self::$hash_algorithms[$this->hash_algorithm];
794  }
795 
796  function issuer() {
797  foreach($this->hashed_subpackets as $p) {
798  if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) return $p->data;
799  }
800  foreach($this->unhashed_subpackets as $p) {
801  if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) return $p->data;
802  }
803  return NULL;
804  }
805 
809  static function get_subpackets($input) {
810  $subpackets = array();
811  while(($length = strlen($input)) > 0) {
812  $subpackets[] = self::get_subpacket($input);
813  if($length == strlen($input)) { // Parsing stuck?
814  break;
815  }
816  }
817  return $subpackets;
818  }
819 
820  static function get_subpacket(&$input) {
821  $len = ord($input[0]);
822  $length_of_length = 1;
823  // if($len < 192) One octet length, no furthur processing
824  if($len > 190 && $len < 255) { // Two octet length
825  $length_of_length = 2;
826  $len = (($len - 192) << 8) + ord($input[1]) + 192;
827  }
828  if($len == 255) { // Five octet length
829  $length_of_length = 5;
830  $unpacked = unpack('N', substr($input, 1, 4));
831  $len = reset($unpacked);
832  }
833  $input = substr($input, $length_of_length); // Chop off length header
834  $tag = ord($input[0]);
835  $class = self::class_for($tag);
836  if($class) {
837  $packet = new $class();
838  $packet->tag = $tag;
839  $packet->input = substr($input, 1, $len-1);
840  $packet->length = $len-1;
841  $packet->read();
842  unset($packet->input);
843  unset($packet->length);
844  }
845  $input = substr($input, $len); // Chop off the data from this packet
846  return $packet;
847  }
848 
849  static $hash_algorithms = array(
850  1 => 'MD5',
851  2 => 'SHA1',
852  3 => 'RIPEMD160',
853  8 => 'SHA256',
854  9 => 'SHA384',
855  10 => 'SHA512',
856  11 => 'SHA224'
857  );
858 
859  static $subpacket_types = array(
860  //0 => 'Reserved',
861  //1 => 'Reserved',
862  2 => 'SignatureCreationTime',
863  3 => 'SignatureExpirationTime',
864  4 => 'ExportableCertification',
865  5 => 'TrustSignature',
866  6 => 'RegularExpression',
867  7 => 'Revocable',
868  //8 => 'Reserved',
869  9 => 'KeyExpirationTime',
870  //10 => 'Placeholder for backward compatibility',
871  11 => 'PreferredSymmetricAlgorithms',
872  12 => 'RevocationKey',
873  //13 => 'Reserved',
874  //14 => 'Reserved',
875  //15 => 'Reserved',
876  16 => 'Issuer',
877  //17 => 'Reserved',
878  //18 => 'Reserved',
879  //19 => 'Reserved',
880  20 => 'NotationData',
881  21 => 'PreferredHashAlgorithms',
882  22 => 'PreferredCompressionAlgorithms',
883  23 => 'KeyServerPreferences',
884  24 => 'PreferredKeyServer',
885  25 => 'PrimaryUserID',
886  26 => 'PolicyURI',
887  27 => 'KeyFlags',
888  28 => 'SignersUserID',
889  29 => 'ReasonforRevocation',
890  30 => 'Features',
891  31 => 'SignatureTarget',
892  32 => 'EmbeddedSignature',
893  );
894 
895  static function class_for($tag) {
896  if(!isset(self::$subpacket_types[$tag])) return 'OpenPGP_SignaturePacket_Subpacket';
897  return 'OpenPGP_SignaturePacket_'.self::$subpacket_types[$tag].'Packet';
898  }
899 
900 }
901 
903  function __construct($data=NULL) {
904  parent::__construct($data);
905  $this->tag = array_search(substr(substr(get_class($this), 8+16), 0, -6), OpenPGP_SignaturePacket::$subpacket_types);
906  }
907 
908  function header_and_body() {
909  $body = $this->body(); // Get body first, we will need it's length
910  $size = chr(255).pack('N', strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet
911  $tag = chr($this->tag);
912  return array('header' => $size.$tag, 'body' => $body);
913  }
914 
915  /* Defaults for unsupported packets */
916  function read() {
917  $this->data = $this->input;
918  }
919 
920  function body() {
921  return $this->data;
922  }
923 }
924 
929  function read() {
930  $this->data = $this->read_timestamp();
931  }
932 
933  function body() {
934  return pack('N', $this->data);
935  }
936 }
937 
939  function read() {
940  $this->data = $this->read_timestamp();
941  }
942 
943  function body() {
944  return pack('N', $this->data);
945  }
946 }
947 
949  function read() {
950  $this->data = (ord($this->input) != 0);
951  }
952 
953  function body() {
954  return chr($this->data ? 1 : 0);
955  }
956 }
957 
959  function read() {
960  $this->depth = ord($this->input{0});
961  $this->trust = ord($this->input{1});
962  }
963 
964  function body() {
965  return chr($this->depth) . chr($this->trust);
966  }
967 }
968 
970  function read() {
971  $this->data = substr($this->input, 0, -1);
972  }
973 
974  function body() {
975  return $this->data . chr(0);
976  }
977 }
978 
980  function read() {
981  $this->data = (ord($this->input) != 0);
982  }
983 
984  function body() {
985  return chr($this->data ? 1 : 0);
986  }
987 }
988 
990  function read() {
991  $this->data = $this->read_timestamp();
992  }
993 
994  function body() {
995  return pack('N', $this->data);
996  }
997 }
998 
1000  function read() {
1001  $this->data = array();
1002  while(strlen($this->input) > 0) {
1003  $this->data[] = ord($this->read_byte());
1004  }
1005  }
1006 
1007  function body() {
1008  $bytes = '';
1009  foreach($this->data as $algo) {
1010  $bytes .= chr($algo);
1011  }
1012  return $bytes;
1013  }
1014 }
1015 
1018 
1019  function read() {
1020  // bitfield must have bit 0x80 set, says the spec
1021  $bitfield = ord($this->read_byte());
1022  $this->sensitive = $bitfield & 0x40 == 0x40;
1023  $this->key_algorithm = ord($this->read_byte());
1024 
1025  $this->fingerprint = '';
1026  while(strlen($this->input) > 0) {
1027  $this->fingerprint .= sprintf('%02X',ord($this->read_byte()));
1028  }
1029  }
1030 
1031  function body() {
1032  $bytes = '';
1033  $bytes .= chr(0x80 | ($this->sensitive ? 0x40 : 0x00));
1034  $bytes .= chr($this->key_algorithm);
1035 
1036  for($i = 0; $i < strlen($this->fingerprint); $i += 2) {
1037  $bytes .= chr(hexdec($this->fingerprint{$i}.$this->fingerprint{$i+1}));
1038  }
1039 
1040  return $bytes;
1041  }
1042 }
1043 
1048  function read() {
1049  for($i = 0; $i < 8; $i++) { // Store KeyID in Hex
1050  $this->data .= sprintf('%02X',ord($this->read_byte()));
1051  }
1052  }
1053 
1054  function body() {
1055  $bytes = '';
1056  for($i = 0; $i < strlen($this->data); $i += 2) {
1057  $bytes .= chr(hexdec($this->data{$i}.$this->data{$i+1}));
1058  }
1059  return $bytes;
1060  }
1061 }
1062 
1065 
1066  function read() {
1067  $flags = $this->read_bytes(4);
1068  $namelen = $this->read_unpacked(2, 'n');
1069  $datalen = $this->read_unpacked(2, 'n');
1070  $this->human_readable = ord($flags[0]) & 0x80 == 0x80;
1071  $this->name = $this->read_bytes($namelen);
1072  $this->data = $this->read_bytes($datalen);
1073  }
1074 
1075  function body () {
1076  return chr($this->human_readable ? 0x80 : 0x00) . "\0\0\0" .
1077  pack('n', strlen($this->name)) . pack('n', strlen($this->data)) .
1078  $this->name . $this->data;
1079  }
1080 }
1081 
1083  function read() {
1084  $this->data = array();
1085  while(strlen($this->input) > 0) {
1086  $this->data[] = ord($this->read_byte());
1087  }
1088  }
1089 
1090  function body() {
1091  $bytes = '';
1092  foreach($this->data as $algo) {
1093  $bytes .= chr($algo);
1094  }
1095  return $bytes;
1096  }
1097 }
1098 
1100  function read() {
1101  $this->data = array();
1102  while(strlen($this->input) > 0) {
1103  $this->data[] = ord($this->read_byte());
1104  }
1105  }
1106 
1107  function body() {
1108  $bytes = '';
1109  foreach($this->data as $algo) {
1110  $bytes .= chr($algo);
1111  }
1112  return $bytes;
1113  }
1114 }
1115 
1117  public $no_modify;
1118 
1119  function read() {
1120  $flags = ord($this->input);
1121  $this->no_modify = $flags & 0x80 == 0x80;
1122  }
1123 
1124  function body() {
1125  return chr($this->no_modify ? 0x80 : 0x00);
1126  }
1127 }
1128 
1130  function read() {
1131  $this->data = $this->input;
1132  }
1133 
1134  function body() {
1135  return $this->data;
1136  }
1137 }
1138 
1140  function read() {
1141  $this->data = (ord($this->input) != 0);
1142  }
1143 
1144  function body() {
1145  return chr($this->data ? 1 : 0);
1146  }
1147 
1148 }
1149 
1151  function read() {
1152  $this->data = $this->input;
1153  }
1154 
1155  function body() {
1156  return $this->data;
1157  }
1158 }
1159 
1161  function __construct($flags=array()) {
1162  parent::__construct();
1163  $this->flags = $flags;
1164  }
1165 
1166  function read() {
1167  $this->flags = array();
1168  while($this->input) {
1169  $this->flags[] = ord($this->read_byte());
1170  }
1171  }
1172 
1173  function body() {
1174  $bytes = '';
1175  foreach($this->flags as $f) {
1176  $bytes .= chr($f);
1177  }
1178  return $bytes;
1179  }
1180 }
1181 
1183  function read() {
1184  $this->data = $this->input;
1185  }
1186 
1187  function body() {
1188  return $this->data;
1189  }
1190 }
1191 
1193  public $code;
1194 
1195  function read() {
1196  $this->code = ord($this->read_byte());
1197  $this->data = $this->input;
1198  }
1199 
1200  function body() {
1201  return chr($this->code) . $this->data;
1202  }
1203 }
1204 
1205 
1207  // Identical functionality to parent
1208 }
1209 
1212 
1213  function read() {
1214  $this->key_algorithm = ord($this->read_byte());
1215  $this->hash_algorithm = ord($this->read_byte());
1216  $this->data = $this->input;
1217  }
1218 
1219  function body() {
1220  return chr($this->key_algorithm) . chr($this->hash_algorithm) . $this->data;
1221  }
1222 
1223 }
1224 
1226  // TODO: This is duplicated from subpacket... improve?
1227  function __construct($data=NULL) {
1228  parent::__construct($data);
1229  $this->tag = array_search(substr(substr(get_class($this), 8+16), 0, -6), OpenPGP_SignaturePacket::$subpacket_types);
1230  }
1231 
1232  function header_and_body() {
1233  $body = $this->body(); // Get body first, we will need it's length
1234  $size = chr(255).pack('N', strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet
1235  $tag = chr($this->tag);
1236  return array('header' => $size.$tag, 'body' => $body);
1237  }
1238 }
1239 
1247 
1249  parent::__construct();
1250  $this->version = $version;
1251  $this->symmetric_algorithm = $symmetric_algorithm;
1252  $this->s2k = $s2k;
1253  $this->encrypted_data = $encrypted_data;
1254  }
1255 
1256  function read() {
1257  $this->version = ord($this->read_byte());
1258  $this->symmetric_algorithm = ord($this->read_byte());
1259  $this->s2k = OpenPGP_S2k::parse($this->input);
1260  $this->encrypted_data = $this->input;
1261  }
1262 
1263  function body() {
1264  return chr($this->version) . chr($this->symmetric_algorithm) .
1265  $this->s2k->to_bytes() . $this->encrypted_data;
1266  }
1267 }
1268 
1276  function read() {
1277  $this->version = ord($this->read_byte());
1278  $this->signature_type = ord($this->read_byte());
1279  $this->hash_algorithm = ord($this->read_byte());
1280  $this->key_algorithm = ord($this->read_byte());
1281  for($i = 0; $i < 8; $i++) { // Store KeyID in Hex
1282  $this->key_id .= sprintf('%02X',ord($this->read_byte()));
1283  }
1284  $this->nested = ord($this->read_byte());
1285  }
1286 
1287  function body() {
1288  $body = chr($this->version).chr($this->signature_type).chr($this->hash_algorithm).chr($this->key_algorithm);
1289  for($i = 0; $i < strlen($this->key_id); $i += 2) {
1290  $body .= chr(hexdec($this->key_id{$i}.$this->key_id{$i+1}));
1291  }
1292  $body .= chr((int)$this->nested);
1293  return $body;
1294  }
1295 }
1296 
1309 
1310  function __construct($key=array(), $algorithm='RSA', $timestamp=NULL, $version=4) {
1311  parent::__construct();
1312  $this->key = $key;
1313  if(is_string($this->algorithm = $algorithm)) {
1314  $this->algorithm = array_search($this->algorithm, self::$algorithms);
1315  }
1316  $this->timestamp = $timestamp ? $timestamp : time();
1317  $this->version = $version;
1318 
1319  if(count($this->key) > 0) {
1320  $this->key_id = substr($this->fingerprint(), -8);
1321  }
1322  }
1323 
1324  // Find self signatures in a message, these often contain metadata about the key
1325  function self_signatures($message) {
1326  $sigs = array();
1327  $keyid16 = strtoupper(substr($this->fingerprint, -16));
1328  foreach($message as $p) {
1329  if($p instanceof OpenPGP_SignaturePacket) {
1330  if(strtoupper($p->issuer()) == $keyid16) {
1331  $sigs[] = $p;
1332  } else {
1333  foreach(array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) {
1334  if($s instanceof OpenPGP_SignaturePacket_EmbeddedSignaturePacket && strtoupper($s->issuer()) == $keyid16) {
1335  $sigs[] = $p;
1336  break;
1337  }
1338  }
1339  }
1340  } else if(count($sigs)) break; // After we've seen a self sig, the next non-sig stop all self-sigs
1341  }
1342  return $sigs;
1343  }
1344 
1345  // Find expiry time of this key based on the self signatures in a message
1346  function expires($message) {
1347  foreach($this->self_signatures($message) as $p) {
1348  foreach(array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) {
1350  return $this->timestamp + $s->data;
1351  }
1352  }
1353  }
1354  return NULL; // Never expires
1355  }
1356 
1360  function read() {
1361  switch ($this->version = ord($this->read_byte())) {
1362  case 3:
1363  $this->timestamp = $this->read_timestamp();
1364  $this->v3_days_of_validity = $this->read_unpacked(2, 'n');
1365  $this->algorithm = ord($this->read_byte());
1366  $this->read_key_material();
1367  break;
1368  case 4:
1369  $this->timestamp = $this->read_timestamp();
1370  $this->algorithm = ord($this->read_byte());
1371  $this->read_key_material();
1372  }
1373  }
1374 
1378  function read_key_material() {
1379  foreach (self::$key_fields[$this->algorithm] as $field) {
1380  $this->key[$field] = $this->read_mpi();
1381  }
1382  $this->key_id = substr($this->fingerprint(), -8);
1383  }
1384 
1386  switch ($this->version) {
1387  case 3:
1388  $material = array();
1389  foreach (self::$key_fields[$this->algorithm] as $i) {
1390  $material[] = pack('n', OpenPGP::bitlength($this->key[$i]));
1391  $material[] = $this->key[$i];
1392  }
1393  return $material;
1394  case 4:
1395  $head = array(
1396  chr(0x99), NULL,
1397  chr($this->version), pack('N', $this->timestamp),
1398  chr($this->algorithm),
1399  );
1400  $material = array();
1401  foreach (self::$key_fields[$this->algorithm] as $i) {
1402  $material[] = pack('n', OpenPGP::bitlength($this->key[$i]));
1403  $material[] = $this->key[$i];
1404  }
1405  $material = implode('', $material);
1406  $head[1] = pack('n', 6 + strlen($material));
1407  $head[] = $material;
1408  return $head;
1409  }
1410  }
1411 
1416  function fingerprint() {
1417  switch ($this->version) {
1418  case 2:
1419  case 3:
1420  return $this->fingerprint = strtoupper(md5(implode('', $this->fingerprint_material())));
1421  case 4:
1422  return $this->fingerprint = strtoupper(sha1(implode('', $this->fingerprint_material())));
1423  }
1424  }
1425 
1426  function body() {
1427  switch ($this->version) {
1428  case 2:
1429  case 3:
1430  return implode('', array_merge(array(
1431  chr($this->version) . pack('N', $this->timestamp) .
1432  pack('n', $this->v3_days_of_validity) . chr($this->algorithm)
1433  ), $this->fingerprint_material())
1434  );
1435  case 4:
1436  return implode('', array_slice($this->fingerprint_material(), 2));
1437  }
1438  }
1439 
1440  static $key_fields = array(
1441  1 => array('n', 'e'), // RSA
1442  16 => array('p', 'g', 'y'), // ELG-E
1443  17 => array('p', 'q', 'g', 'y'), // DSA
1444  );
1445 
1446  static $algorithms = array(
1447  1 => 'RSA',
1448  2 => 'RSA',
1449  3 => 'RSA',
1450  16 => 'ELGAMAL',
1451  17 => 'DSA',
1452  18 => 'ECC',
1453  19 => 'ECDSA',
1454  21 => 'DH'
1455  );
1456 
1457 }
1458 
1468  // TODO
1469 }
1470 
1481  function read() {
1482  parent::read(); // All the fields from PublicKey
1483  $this->s2k_useage = ord($this->read_byte());
1484  if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
1485  $this->symmetric_algorithm = ord($this->read_byte());
1486  $this->s2k = OpenPGP_S2k::parse($this->input);
1487  } else if($this->s2k_useage > 0) {
1488  $this->symmetric_algorithm = $this->s2k_useage;
1489  }
1490  if($this->s2k_useage > 0) {
1491  $this->encrypted_data = $this->input; // Rest of input is MPIs and checksum (encrypted)
1492  } else {
1493  $this->key_from_input();
1494  $this->private_hash = $this->read_bytes(2); // TODO: Validate checksum?
1495  }
1496  }
1497 
1498  static $secret_key_fields = array(
1499  1 => array('d', 'p', 'q', 'u'), // RSA
1500  2 => array('d', 'p', 'q', 'u'), // RSA-E
1501  3 => array('d', 'p', 'q', 'u'), // RSA-S
1502  16 => array('x'), // ELG-E
1503  17 => array('x'), // DSA
1504  );
1505 
1506  function key_from_input() {
1507  foreach(self::$secret_key_fields[$this->algorithm] as $field) {
1508  $this->key[$field] = $this->read_mpi();
1509  }
1510  }
1511 
1512  function body() {
1513  $bytes = parent::body() . chr($this->s2k_useage);
1514  $secret_material = NULL;
1515  if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
1516  $bytes .= chr($this->symmetric_algorithm);
1517  $bytes .= $this->s2k->to_bytes();
1518  }
1519  if($this->s2k_useage > 0) {
1520  $bytes .= $this->encrypted_data;
1521  } else {
1522  $secret_material = '';
1523  foreach(self::$secret_key_fields[$this->algorithm] as $f) {
1524  $f = $this->key[$f];
1525  $secret_material .= pack('n', OpenPGP::bitlength($f));
1526  $secret_material .= $f;
1527  }
1528  $bytes .= $secret_material;
1529 
1530  // 2-octet checksum
1531  $chk = 0;
1532  for($i = 0; $i < strlen($secret_material); $i++) {
1533  $chk = ($chk + ord($secret_material[$i])) % 65536;
1534  }
1535  $bytes .= pack('n', $chk);
1536  }
1537  return $bytes;
1538  }
1539 }
1540 
1550  // TODO
1551 }
1552 
1558 class OpenPGP_CompressedDataPacket extends OpenPGP_Packet implements IteratorAggregate, ArrayAccess {
1559  public $algorithm;
1560  /* see http://tools.ietf.org/html/rfc4880#section-9.3 */
1561  static $algorithms = array(0 => 'Uncompressed', 1 => 'ZIP', 2 => 'ZLIB', 3 => 'BZip2');
1562  function read() {
1563  $this->algorithm = ord($this->read_byte());
1564  $this->data = $this->read_bytes($this->length);
1565  switch($this->algorithm) {
1566  case 0:
1567  $this->data = OpenPGP_Message::parse($this->data);
1568  break;
1569  case 1:
1570  $this->data = OpenPGP_Message::parse(gzinflate($this->data));
1571  break;
1572  case 2:
1573  $this->data = OpenPGP_Message::parse(gzuncompress($this->data));
1574  break;
1575  case 3:
1576  $this->data = OpenPGP_Message::parse(bzdecompress($this->data));
1577  break;
1578  default:
1579  /* TODO error? */
1580  }
1581  }
1582 
1583  function body() {
1584  $body = chr($this->algorithm);
1585  switch($this->algorithm) {
1586  case 0:
1587  $body .= $this->data->to_bytes();
1588  break;
1589  case 1:
1590  $body .= gzdeflate($this->data->to_bytes());
1591  break;
1592  case 2:
1593  $body .= gzcompress($this->data->to_bytes());
1594  break;
1595  case 3:
1596  $body .= bzcompress($this->data->to_bytes());
1597  break;
1598  default:
1599  /* TODO error? */
1600  }
1601  return $body;
1602  }
1603 
1604  // IteratorAggregate interface
1605 
1606  function getIterator() {
1607  return new ArrayIterator($this->data->packets);
1608  }
1609 
1610  // ArrayAccess interface
1611 
1612  function offsetExists($offset) {
1613  return isset($this->data[$offset]);
1614  }
1615 
1616  function offsetGet($offset) {
1617  return $this->data[$offset];
1618  }
1619 
1620  function offsetSet($offset, $value) {
1621  return is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value;
1622  }
1623 
1624  function offsetUnset($offset) {
1625  unset($this->data[$offset]);
1626  }
1627 
1628 }
1629 
1636  function read() {
1637  $this->data = $this->input;
1638  }
1639 
1640  function body() {
1641  return $this->data;
1642  }
1643 }
1644 
1651  // TODO
1652 }
1653 
1661 
1662  function __construct($data=NULL, $opt=array()) {
1663  parent::__construct();
1664  $this->data = $data;
1665  $this->format = isset($opt['format']) ? $opt['format'] : 'b';
1666  $this->filename = isset($opt['filename']) ? $opt['filename'] : 'data';
1667  $this->timestamp = isset($opt['timestamp']) ? $opt['timestamp'] : time();
1668  }
1669 
1670  function normalize() {
1671  if($this->format == 'u' || $this->format == 't') { // Normalize line endings
1672  $this->data = str_replace("\n", "\r\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $this->data)));
1673  }
1674  }
1675 
1676  function read() {
1677  $this->size = $this->length - 1 - 4;
1678  $this->format = $this->read_byte();
1679  $filename_length = ord($this->read_byte());
1680  $this->size -= $filename_length;
1681  $this->filename = $this->read_bytes($filename_length);
1682  $this->timestamp = $this->read_timestamp();
1683  $this->data = $this->read_bytes($this->size);
1684  }
1685 
1686  function body() {
1687  return $this->format.chr(strlen($this->filename)).$this->filename.pack('N', $this->timestamp).$this->data;
1688  }
1689 }
1690 
1697  function read() {
1698  $this->data = $this->input;
1699  }
1700 
1701  function body() {
1702  return $this->data;
1703  }
1704 }
1705 
1714 
1715  function __construct($name='', $comment='', $email='') {
1716  parent::__construct();
1717  if(!$comment && !$email) {
1718  $this->input = $name;
1719  $this->read();
1720  } else {
1721  $this->name = $name;
1722  $this->comment = $comment;
1723  $this->email = $email;
1724  }
1725  }
1726 
1727  function read() {
1728  $this->data = $this->input;
1729  // User IDs of the form: "name (comment) <email>"
1730  if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->data, $matches)) {
1731  $this->name = trim($matches[1]);
1732  $this->comment = trim($matches[2]);
1733  $this->email = trim($matches[3]);
1734  }
1735  // User IDs of the form: "name <email>"
1736  else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->data, $matches)) {
1737  $this->name = trim($matches[1]);
1738  $this->comment = NULL;
1739  $this->email = trim($matches[2]);
1740  }
1741  // User IDs of the form: "name"
1742  else if (preg_match('/^([^<]+)$/', $this->data, $matches)) {
1743  $this->name = trim($matches[1]);
1744  $this->comment = NULL;
1745  $this->email = NULL;
1746  }
1747  // User IDs of the form: "<email>"
1748  else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) {
1749  $this->name = NULL;
1750  $this->comment = NULL;
1751  $this->email = trim($matches[2]);
1752  }
1753  }
1754 
1755  function __toString() {
1756  $text = array();
1757  if ($this->name) { $text[] = $this->name; }
1758  if ($this->comment) { $text[] = "({$this->comment})"; }
1759  if ($this->email) { $text[] = "<{$this->email}>"; }
1760  return implode(' ', $text);
1761  }
1762 
1763  function body() {
1764  return ''.$this; // Convert to string is the body
1765  }
1766 }
1767 
1775  public $packets;
1776 
1777  // TODO
1778 }
1779 
1786  public $version;
1787 
1788  function __construct($data='', $version=1) {
1789  parent::__construct();
1790  $this->version = $version;
1791  $this->data = $data;
1792  }
1793 
1794  function read() {
1795  $this->version = ord($this->read_byte());
1796  $this->data = $this->input;
1797  }
1798 
1799  function body() {
1800  return chr($this->version) . $this->data;
1801  }
1802 }
1803 
1810  function __construct($sha1='') {
1811  parent::__construct();
1812  $this->data = $sha1;
1813  }
1814 
1815  function read() {
1816  $this->data = $this->input;
1817  if(strlen($this->input) != 20) throw new Exception("Bad ModificationDetectionCodePacket");
1818  }
1819 
1820  function header_and_body() {
1821  $body = $this->body(); // Get body first, we will need it's length
1822  if(strlen($body) != 20) throw new Exception("Bad ModificationDetectionCodePacket");
1823  return array('header' => "\xD3\x14", 'body' => $body);
1824  }
1825 
1826  function body() {
1827  return $this->data;
1828  }
1829 }
1830