Compare commits

...

2 Commits
master ... wip

Author SHA1 Message Date
Deon George
fc914b727d Remove to_bytes() methods 2020-06-24 22:46:20 +10:00
Deon George
9936e839a4 Update objects to use __toString() 2020-06-24 22:37:14 +10:00
19 changed files with 152 additions and 97 deletions

View File

@ -14,8 +14,8 @@
namespace Leenooks; namespace Leenooks;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use phpseclib\Crypt\RSA as Crypt_RSA;
use phpseclib\Crypt\RSA as Crypt_RSA;
use Leenooks\OpenPGP\Exceptions\PacketTagException; use Leenooks\OpenPGP\Exceptions\PacketTagException;
/** /**
@ -26,6 +26,8 @@ class OpenPGP
const VERSION = [0,5,0]; const VERSION = [0,5,0];
private $key = NULL; private $key = NULL;
// Functions
/** /**
* @see http://tools.ietf.org/html/rfc4880#section-12.2 * @see http://tools.ietf.org/html/rfc4880#section-12.2
*/ */
@ -97,12 +99,18 @@ class OpenPGP
return ((int)16 + ($c & 15)) << (($c >> 4) + 6); return ((int)16 + ($c & 15)) << (($c >> 4) + 6);
} }
public function decrypt($data)
{
$decryptor = new OpenPGP\Crypt\RSA($this->key);
return $decryptor->decrypt($data);
}
/** /**
* @see http://tools.ietf.org/html/rfc4880#section-6 * @see http://tools.ietf.org/html/rfc4880#section-6
* @see http://tools.ietf.org/html/rfc4880#section-6.2 * @see http://tools.ietf.org/html/rfc4880#section-6.2
* @see http://tools.ietf.org/html/rfc2045 * @see http://tools.ietf.org/html/rfc2045
*/ */
static function enarmor($data,$marker='MESSAGE',array $headers=[]) static function enarmor1($data,$marker='MESSAGE',array $headers=[])
{ {
$text = self::header($marker)."\n"; $text = self::header($marker)."\n";
@ -117,6 +125,16 @@ class OpenPGP
return $text; return $text;
} }
protected function enarmor(string $data,$marker='MESSAGE',array $headers=[]): string
{
return static::enarmor1($data,$marker,$headers);
}
public function encrypt(OpenPGP\LiteralDataPacket $data)
{
return OpenPGP\Crypt\Symmetric::encrypt($this->key,new OpenPGP\Message([$data]));
}
static function encode_s2k_count($iterations) static function encode_s2k_count($iterations)
{ {
if($iterations >= 65011712) return 255; if($iterations >= 65011712) return 255;
@ -153,6 +171,42 @@ class OpenPGP
return '-----BEGIN '.strtoupper((string)$marker).'-----'; return '-----BEGIN '.strtoupper((string)$marker).'-----';
} }
public function key()
{
return $this->key;
}
static function load(string $data,$marker='MESSAGE',bool $binary=TRUE): self
{
$result = new self;
$result->key = OpenPGP\Message::parse(
$binary ? $data : OpenPGP::unarmor($data,$marker)
);
return $result;
}
public function private(): string
{
return $this->enarmor((string)$this->key,'PRIVATE KEY');
}
protected function publicKey(): OpenPGP\PublicKeyPacket
{
return $this->key[0];
}
public function public(): string
{
return $this->enarmor((string)$this->publicKey(),'PUBLIC KEY');
}
public function signatures()
{
return $this->key->signatures();
}
/** /**
* @see http://tools.ietf.org/html/rfc4880#section-6 * @see http://tools.ietf.org/html/rfc4880#section-6
* @see http://tools.ietf.org/html/rfc2045 * @see http://tools.ietf.org/html/rfc2045
@ -170,4 +224,10 @@ class OpenPGP
return base64_decode($text=substr($text,$pos1,$pos2-$pos1)); return base64_decode($text=substr($text,$pos1,$pos2-$pos1));
} }
} }
public function verify(OpenPGP\Message $data)
{
$verify = new OpenPGP\Crypt\RSA($this->key);
return $verify->verify($data);
}
} }

View File

@ -51,19 +51,19 @@ class CompressedDataPacket extends Packet implements \IteratorAggregate, \ArrayA
switch($this->algorithm) { switch($this->algorithm) {
case 0: case 0:
$body .= $this->data->to_bytes(); $body .= (string)$this->data;
break; break;
case 1: case 1:
$body .= gzdeflate($this->data->to_bytes()); $body .= gzdeflate((string)$this->data);
break; break;
case 2: case 2:
$body .= gzcompress($this->data->to_bytes()); $body .= gzcompress((string)$this->data);
break; break;
case 3: case 3:
$body .= bzcompress($this->data->to_bytes()); $body .= bzcompress((string)$this->data);
break; break;
default: default:

View File

@ -32,10 +32,10 @@ class Symmetric
$key = Random::string($key_bytes); $key = Random::string($key_bytes);
$cipher->setKey($key); $cipher->setKey($key);
$to_encrypt = $prefix.$message->to_bytes(); $to_encrypt = $prefix.$message;
$mdc = new OpenPGP\ModificationDetectionCodePacket(hash('sha1',$to_encrypt."\xD3\x14",true)); $mdc = new OpenPGP\ModificationDetectionCodePacket(hash('sha1',$to_encrypt."\xD3\x14",true));
$to_encrypt .= $mdc->to_bytes(); $to_encrypt .= (string)$mdc;
if (static::$DEBUG) if (static::$DEBUG)
dump(['to_encrypt'=>$to_encrypt]); dump(['to_encrypt'=>$to_encrypt]);

View File

@ -48,6 +48,17 @@ class Message implements \IteratorAggregate,\ArrayAccess
$this->packets = $packets; $this->packets = $packets;
} }
public function __toString()
{
$result = '';
foreach ($this as $p) {
$result .= (string)$p;
}
return $result;
}
/** /**
* @see http://tools.ietf.org/html/rfc4880#section-4.1 * @see http://tools.ietf.org/html/rfc4880#section-4.1
* @see http://tools.ietf.org/html/rfc4880#section-4.2 * @see http://tools.ietf.org/html/rfc4880#section-4.2
@ -187,17 +198,6 @@ class Message implements \IteratorAggregate,\ArrayAccess
return $final_sigs; return $final_sigs;
} }
public function to_bytes(): string
{
$bytes = '';
foreach ($this as $p) {
$bytes .= $p->to_bytes();
}
return $bytes;
}
/** /**
* Function to extract verified signatures * Function to extract verified signatures
* *

View File

@ -11,7 +11,7 @@ class ModificationDetectionCodePacket extends Packet
{ {
protected $tag = 19; protected $tag = 19;
function header_and_body(): array protected function header_and_body(): array
{ {
// Get body first, we will need it's length // Get body first, we will need it's length
$body = $this->body(); $body = $this->body();

View File

@ -40,6 +40,13 @@ abstract class Packet
63 => 'Experimental', // Private or Experimental Values 63 => 'Experimental', // Private or Experimental Values
]; ];
public function __toString()
{
$data = $this->header_and_body();
return $data['header'].$data['body'];
}
static function class_for($tag) static function class_for($tag)
{ {
return (isset(self::$tags[$tag]) AND class_exists($class='Leenooks\OpenPGP\\'.self::$tags[$tag].'Packet')) return (isset(self::$tags[$tag]) AND class_exists($class='Leenooks\OpenPGP\\'.self::$tags[$tag].'Packet'))
@ -214,13 +221,17 @@ abstract class Packet
{ {
} }
function header_and_body(): array protected 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 $tag = chr($this->tag|0xC0); // First two bits are 1 for new packet format
return ['header'=>$tag.$size,'body'=>$body]; return ['header'=>$tag.$this->size(),'body'=>$this->body()];
}
protected function size(): string
{
// Use 5-octet lengths
return chr(255).pack('N',strlen($this->body()));
} }
public function tag(): int public function tag(): int
@ -228,13 +239,6 @@ abstract class Packet
return $this->tag; return $this->tag;
} }
function to_bytes()
{
$data = $this->header_and_body();
return $data['header'].$data['body'];
}
/** /**
* @see http://tools.ietf.org/html/rfc4880#section-3.5 * @see http://tools.ietf.org/html/rfc4880#section-3.5
*/ */

View File

@ -38,9 +38,15 @@ class PublicKeyPacket extends Packet
function __construct($key=[],$algorithm='RSA',$timestamp=NULL,$version=4) function __construct($key=[],$algorithm='RSA',$timestamp=NULL,$version=4)
{ {
if (self::$DEBUG)
dump(['CREATE'=>__METHOD__,'key'=>$key,'alg'=>$algorithm,'ts'=>$timestamp,'version'=>$version]);
parent::__construct(); parent::__construct();
if ($key instanceof PublicKeyPacket) { if ($key instanceof PublicKeyPacket) {
if (self::$DEBUG)
dump('key is PublicKeyPacket');
$this->algorithm = $key->algorithm; $this->algorithm = $key->algorithm;
$this->key = array(); $this->key = array();
@ -56,6 +62,9 @@ class PublicKeyPacket extends Packet
$this->v3_days_of_validity = $key->v3_days_of_validity; $this->v3_days_of_validity = $key->v3_days_of_validity;
} else { } else {
if (self::$DEBUG)
dump(['key'=>$key]);
$this->key = $key; $this->key = $key;
if (is_string($this->algorithm = $algorithm)) { if (is_string($this->algorithm = $algorithm)) {
$this->algorithm = array_search($this->algorithm,self::$algorithms); $this->algorithm = array_search($this->algorithm,self::$algorithms);

View File

@ -43,7 +43,7 @@ class S2K
return $s2k; return $s2k;
} }
function to_bytes() function __toString()
{ {
$bytes = chr($this->type); $bytes = chr($this->type);

View File

@ -29,7 +29,7 @@ class SecretKeyPacket extends PublicKeyPacket
$secret_material = NULL; $secret_material = NULL;
if($this->s2k_useage == 255 || $this->s2k_useage == 254) { if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
$bytes .= chr($this->symmetric_algorithm); $bytes .= chr($this->symmetric_algorithm);
$bytes .= $this->s2k->to_bytes(); $bytes .= (string)$this->s2k;
} }
if($this->s2k_useage > 0) { if($this->s2k_useage > 0) {
$bytes .= $this->encrypted_data; $bytes .= $this->encrypted_data;

View File

@ -143,7 +143,7 @@ class SignaturePacket extends Packet
$unhashed_subpackets = ''; $unhashed_subpackets = '';
foreach((array)$this->unhashed_subpackets as $p) { foreach((array)$this->unhashed_subpackets as $p) {
$unhashed_subpackets .= $p->to_bytes(); $unhashed_subpackets .= (string)$p;
} }
$body .= pack('n',strlen($unhashed_subpackets)).$unhashed_subpackets; $body .= pack('n',strlen($unhashed_subpackets)).$unhashed_subpackets;
@ -164,7 +164,7 @@ class SignaturePacket extends Packet
$hashed_subpackets = ''; $hashed_subpackets = '';
foreach((array)$this->hashed_subpackets as $p) { foreach((array)$this->hashed_subpackets as $p) {
$hashed_subpackets .= $p->to_bytes(); $hashed_subpackets .= (string)$p;
} }
$body .= pack('n',strlen($hashed_subpackets)).$hashed_subpackets; $body .= pack('n',strlen($hashed_subpackets)).$hashed_subpackets;

View File

@ -11,12 +11,14 @@ class EmbeddedSignaturePacket extends SignaturePacket
{ {
protected $tag = 32; protected $tag = 32;
function header_and_body(): array protected function header_and_body(): array
{ {
$body = $this->body(); // Get body first, we will need it's length return ['header'=>$this->size().chr($this->tag),'body'=>$this->body()];
$size = chr(255).pack('N',strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet }
$tag = chr($this->tag);
return ['header'=>$size.$tag,'body'=>$body]; protected function size(): string
{
// Use 5-octet lengths + 1 for tag as first packet body octet
return chr(255).pack('N',strlen($this->body())+1);
} }
} }

View File

@ -9,11 +9,6 @@ class PolicyURIPacket extends Subpacket
{ {
protected $tag = 26; protected $tag = 26;
function body()
{
return $this->data;
}
function read() function read()
{ {
$this->data = $this->input; $this->data = $this->input;

View File

@ -9,11 +9,6 @@ class PreferredKeyServerPacket extends Subpacket
{ {
protected $tag = 24; protected $tag = 24;
function body()
{
return $this->data;
}
function read() function read()
{ {
$this->data = $this->input; $this->data = $this->input;

View File

@ -9,11 +9,6 @@ class SignersUserIDPacket extends Subpacket
{ {
protected $tag = 28; protected $tag = 28;
function body()
{
return $this->data;
}
function read() function read()
{ {
$this->data = $this->input; $this->data = $this->input;

View File

@ -9,18 +9,9 @@ class Subpacket extends Packet
{ {
protected $tag = NULL; protected $tag = NULL;
function body() protected function header_and_body(): array
{ {
return $this->data; return ['header'=>$this->size().chr($this->tag),'body'=>$this->body()];
}
function header_and_body(): array
{
$body = $this->body(); // Get body first, we will need it's length
$size = chr(255).pack('N',strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet
$tag = chr($this->tag);
return ['header'=>$size.$tag,'body'=>$body];
} }
/* Defaults for unsupported packets */ /* Defaults for unsupported packets */
@ -36,4 +27,10 @@ class Subpacket extends Packet
$this->tag = $tag; $this->tag = $tag;
} }
protected function size(): string
{
// Use 5-octet lengths + 1 for tag as first packet body octet
return chr(255).pack('N',strlen($this->body())+1);
}
} }

View File

@ -23,8 +23,7 @@ class SymmetricSessionKeyPacket extends Packet
function body() function body()
{ {
return chr($this->version) . chr($this->symmetric_algorithm) . return chr($this->version).chr($this->symmetric_algorithm).$this->s2k.$this->encrypted_data;
$this->s2k->to_bytes() . $this->encrypted_data;
} }
function read() function read()

View File

@ -16,11 +16,11 @@ class UserIDPacket extends Packet
function __construct($name='',$comment='',$email='') function __construct($name='',$comment='',$email='')
{ {
parent::__construct(); parent::__construct();
if (! $comment && ! $email) { if (! $comment && ! $email) {
$this->input = $name; $this->input = $name;
$this->read(); $this->read();
} else { } else {
$this->name = $name; $this->name = $name;
$this->comment = $comment; $this->comment = $comment;
@ -28,7 +28,7 @@ class UserIDPacket extends Packet
} }
} }
function __toString() public function display()
{ {
$text = []; $text = [];
@ -41,7 +41,7 @@ class UserIDPacket extends Packet
function body() function body()
{ {
return ''.$this; // Convert to string is the body return ''.$this->display(); // Convert to string is the body
} }
function read() function read()
@ -53,21 +53,21 @@ class UserIDPacket extends Packet
$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->data, $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->data, $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->data, $matches)) { else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) {
$this->name = NULL; $this->name = NULL;

View File

@ -3,12 +3,11 @@
use Leenooks\OpenPGP; use Leenooks\OpenPGP;
class MessageVerification extends PHPUnit\Framework\TestCase { class MessageVerification extends PHPUnit\Framework\TestCase {
public function oneMessageRSA($pkey, $path) { public function oneMessageRSA($pkey, $path) {
$pkeyM = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $pkey)); $pkeyM = OpenPGP::load(file_get_contents(dirname(__FILE__).'/data/'.$pkey));
$m = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); $m = OpenPGP::load(file_get_contents(dirname(__FILE__).'/data/'.$path));
$verify = new OpenPGP\Crypt\RSA($pkeyM); $this->assertSame($pkeyM->verify($m->key()), $m->signatures());
$this->assertSame($verify->verify($m), $m->signatures()); }
}
public function testUncompressedOpsRSA() { public function testUncompressedOpsRSA() {
$this->oneMessageRSA('pubring.gpg', 'uncompressed-ops-rsa.gpg'); $this->oneMessageRSA('pubring.gpg', 'uncompressed-ops-rsa.gpg');
@ -26,14 +25,14 @@ class MessageVerification extends PHPUnit\Framework\TestCase {
$this->oneMessageRSA('pubring.gpg', 'compressedsig-bzip2.gpg'); $this->oneMessageRSA('pubring.gpg', 'compressedsig-bzip2.gpg');
} }
public function testSigningMessages() { public function testSigningMessages() {
$wkey = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); $wkey = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); $data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$sign = new OpenPGP\Crypt\RSA($wkey); $sign = new OpenPGP\Crypt\RSA($wkey);
$m = $sign->sign($data)->to_bytes(); $m = (string)$sign->sign($data);
$reparsedM = OpenPGP\Message::parse($m); $reparsedM = OpenPGP\Message::parse($m);
$this->assertSame($sign->verify($reparsedM), $reparsedM->signatures()); $this->assertSame($sign->verify($reparsedM), $reparsedM->signatures());
} }
/* /*
public function testUncompressedOpsDSA() { public function testUncompressedOpsDSA() {
@ -158,7 +157,7 @@ class Encryption extends PHPUnit\Framework\TestCase {
public function oneSymmetric($algorithm) { public function oneSymmetric($algorithm) {
$data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); $data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$encrypted = OpenPGP\Crypt\Symmetric::encrypt('secret', new OpenPGP\Message(array($data)), $algorithm); $encrypted = OpenPGP\Crypt\Symmetric::encrypt('secret', new OpenPGP\Message(array($data)), $algorithm);
$encrypted = OpenPGP\Message::parse($encrypted->to_bytes()); $encrypted = OpenPGP\Message::parse((string)$encrypted);
$decrypted = OpenPGP\Crypt\Symmetric::decryptSymmetric('secret', $encrypted); $decrypted = OpenPGP\Crypt\Symmetric::decryptSymmetric('secret', $encrypted);
$this->assertEquals($decrypted[0]->data, 'This is text.'); $this->assertEquals($decrypted[0]->data, 'This is text.');
} }
@ -197,7 +196,7 @@ class Encryption extends PHPUnit\Framework\TestCase {
$key = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); $key = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); $data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$encrypted = OpenPGP\Crypt\Symmetric::encrypt($key, new OpenPGP\Message(array($data))); $encrypted = OpenPGP\Crypt\Symmetric::encrypt($key, new OpenPGP\Message(array($data)));
$encrypted = OpenPGP\Message::parse($encrypted->to_bytes()); $encrypted = OpenPGP\Message::parse((string)$encrypted);
$decryptor = new OpenPGP\Crypt\RSA($key); $decryptor = new OpenPGP\Crypt\RSA($key);
$decrypted = $decryptor->decrypt($encrypted); $decrypted = $decryptor->decrypt($encrypted);
$this->assertEquals($decrypted[0]->data, 'This is text.'); $this->assertEquals($decrypted[0]->data, 'This is text.');

View File

@ -3,12 +3,12 @@
use Leenooks\OpenPGP; use Leenooks\OpenPGP;
class Serialization extends PHPUnit\Framework\TestCase { class Serialization extends PHPUnit\Framework\TestCase {
public function oneSerialization($path) { public function oneSerialization($path) {
$in = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); $in = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$mid = $in->to_bytes(); $mid = (string)$in;
$out = OpenPGP\Message::parse($mid); $out = OpenPGP\Message::parse($mid);
$this->assertEquals($in, $out); $this->assertEquals($in, $out);
} }
public function test000001006public_key() { public function test000001006public_key() {
$this->oneSerialization("000001-006.public_key"); $this->oneSerialization("000001-006.public_key");