openpgp-php/lib/OpenPgP/Packet.php
2020-06-17 22:44:51 +10:00

271 lines
6.8 KiB
PHP

<?php
namespace Leenooks\OpenPGP;
use Leenooks\OpenPGP\Exceptions\PacketTagException;
/**
* OpenPGP packet.
*
* @see http://tools.ietf.org/html/rfc4880#section-4.1
* @see http://tools.ietf.org/html/rfc4880#section-4.3
*/
abstract class Packet
{
protected static $DEBUG = FALSE;
protected $tag = NULL;
public $size,$data;
static protected $tags = [
1 => 'AsymmetricSessionKey', // Public-Key Encrypted Session Key
2 => 'Signature', // Signature Packet
3 => 'SymmetricSessionKey', // Symmetric-Key Encrypted Session Key Packet
4 => 'OnePassSignature', // One-Pass Signature Packet
5 => 'SecretKey', // Secret-Key Packet
6 => 'PublicKey', // Public-Key Packet
7 => 'SecretSubkey', // Secret-Subkey Packet
8 => 'CompressedData', // Compressed Data Packet
9 => 'EncryptedData', // Symmetrically Encrypted Data Packet
10 => 'Marker', // Marker Packet
11 => 'LiteralData', // Literal Data Packet
12 => 'Trust', // Trust Packet
13 => 'UserID', // User ID Packet
14 => 'PublicSubkey', // Public-Subkey Packet
17 => 'UserAttribute', // User Attribute Packet
18 => 'IntegrityProtectedData', // Sym. Encrypted and Integrity Protected Data Packet
19 => 'ModificationDetectionCode', // Modification Detection Code Packet
60 => 'Experimental', // Private or Experimental Values
61 => 'Experimental', // Private or Experimental Values
62 => 'Experimental', // Private or Experimental Values
63 => 'Experimental', // Private or Experimental Values
];
static function class_for($tag)
{
return (isset(self::$tags[$tag]) AND class_exists($class='Leenooks\OpenPGP\\'.self::$tags[$tag].'Packet'))
? $class
: __CLASS__;
}
/**
* Parses an OpenPGP packet.
*
* Partial body lengths based on https://github.com/toofishes/python-pgpdump/blob/master/pgpdump/packet.py
*
* @see http://tools.ietf.org/html/rfc4880#section-4.2
*/
static function parse(&$input)
{
if (static::$DEBUG)
dump(['In METHOD: '=>__METHOD__,'input'=>$input]);
$packet = NULL;
if (strlen($input) > 0) {
$parser = (ord($input[0]) & 64) ? 'parse_new_format' : 'parse_old_format';
$header_start0 = 0;
$consumed = 0;
$packet_data = '';
do {
list($tag,$data_offset,$data_length,$partial) = self::$parser($input,$header_start0);
$data_start0 = $header_start0+$data_offset;
$header_start0 = $data_start0+$data_length-1;
$packet_data .= substr($input,$data_start0,$data_length);
$consumed += $data_offset+$data_length;
if ($partial) {
$consumed -= 1;
}
} while ($partial === TRUE && $parser === 'parse_new_format');
if (static::$DEBUG)
dump(['parser'=>$parser,'tag'=>$tag,'class'=>($class=self::class_for($tag)),'c'=>$class]);
if ($tag && ($class=self::class_for($tag))) {
$packet = new $class;
$packet->tag = $tag;
$packet->input = $packet_data;
$packet->length = strlen($packet_data);
$packet->read();
unset($packet->input);
unset($packet->length);
}
$input = substr($input,$consumed);
}
if (static::$DEBUG)
dump(['Out METHOD: '=>__METHOD__,'packet'=>$packet]);
return $packet;
}
/**
* Parses a new-format (RFC 4880) OpenPGP packet.
*
* @see http://tools.ietf.org/html/rfc4880#section-4.2.2
*/
static function parse_new_format($input,$header_start=0): array
{
$tag = ord($input[0]) & 63;
$len = ord($input[$header_start+1]);
// One octet length
if ($len < 192) {
return [$tag,2,$len,FALSE];
}
// Two octet length
if ($len > 191 && $len < 224) {
return [$tag,3,(($len-192)<<8)+ord($input[$header_start+2])+192,FALSE];
}
// Five octet length
if ($len == 255) {
$unpacked = unpack('N',substr($input,$header_start+2,4));
return [$tag,6,reset($unpacked),FALSE];
}
// Partial body lengths
return [$tag,2,1<<($len & 0x1f),TRUE];
}
/**
* Parses an old-format (PGP 2.6.x) OpenPGP packet.
*
* @see http://tools.ietf.org/html/rfc4880#section-4.2.1
*/
static function parse_old_format($input): array
{
$len = ($tag=ord($input[0]))&3;
$tag = ($tag>>2)&15;
switch ($len) {
// The packet has a one-octet length. The header is 2 octets long.
case 0:
$head_length = 2;
$data_length = ord($input[1]);
break;
// The packet has a two-octet length. The header is 3 octets long.
case 1:
$head_length = 3;
$data_length = unpack('n', substr($input, 1, 2));
$data_length = $data_length[1];
break;
// The packet has a four-octet length. The header is 5 octets long.
case 2:
$head_length = 5;
$data_length = unpack('N', substr($input, 1, 4));
$data_length = $data_length[1];
break;
// The packet is of indeterminate length. The header is 1 octet long.
case 3:
$head_length = 1;
$data_length = strlen($input) - $head_length;
break;
}
return [$tag, $head_length, $data_length, FALSE];
}
public function __construct($data=NULL)
{
// Make sure our tag is set in our packet class.
try {
if (is_null($this->tag))
throw new PacketTagException('Missing tag in '.get_class($this));
} catch (\Exception $e) {
dd($e->getMessage());
}
if (static::$DEBUG)
dump(['CREATE: '=>get_class($this),'data'=>$data]);
if (static::$DEBUG)
dump([
'substr1'=>substr(get_class($this),strlen("Leenooks\OpenPGP")+1),
'substr2'=>substr(substr(get_class($this),strlen("Leenooks\OpenPGP")+1),0,-6),
'tags: '=>serialize(self::$tags)]);
$this->tag = array_search(substr(substr(get_class($this),strlen("Leenooks\OpenPGP")+1),0,-6),self::$tags);
$this->data = $data;
}
// Will normally be overridden by subclasses
public function body()
{
return $this->data;
}
public function read()
{
}
function header_and_body(): array
{
$body = $this->body(); // Get body first, we will need it's length
$size = chr(255).pack('N',strlen($body)); // Use 5-octet lengths
$tag = chr($this->tag|0xC0); // First two bits are 1 for new packet format
return ['header'=>$tag.$size,'body'=>$body];
}
function to_bytes()
{
$data = $this->header_and_body();
return $data['header'].$data['body'];
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-3.5
*/
function read_timestamp()
{
return $this->read_unpacked(4,'N');
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-3.2
*/
function read_mpi()
{
$length = $this->read_unpacked(2,'n'); // length in bits
$length = (int)floor(($length+7)/8); // length in bytes
return $this->read_bytes($length);
}
/**
* @see http://php.net/manual/en/function.unpack.php
*/
protected function read_unpacked($count,$format)
{
$unpacked = unpack($format,$this->read_bytes($count));
return reset($unpacked);
}
protected function read_byte()
{
return ($bytes=$this->read_bytes()) ? $bytes[0] : NULL;
}
protected function read_bytes($count=1)
{
$bytes = substr($this->input,0,$count);
$this->input = substr($this->input,$count);
return $bytes;
}
}