Updates to pass unit testing

This commit is contained in:
Deon George
2020-06-18 22:03:56 +10:00
parent 7d259b251f
commit 358f28273c
18 changed files with 248 additions and 176 deletions

View File

@@ -13,6 +13,9 @@
namespace Leenooks;
use Illuminate\Support\Arr;
use phpseclib\Crypt\RSA as Crypt_RSA;
use Leenooks\OpenPGP\Exceptions\PacketTagException;
/**
@@ -21,6 +24,78 @@ use Leenooks\OpenPGP\Exceptions\PacketTagException;
class OpenPGP
{
const VERSION = [0,5,0];
private $key = NULL;
/**
* @see http://tools.ietf.org/html/rfc4880#section-12.2
*/
static function bitlength($data)
{
return (strlen($data) - 1) * 8 + (int)floor(log(ord($data[0]), 2)) + 1;
}
/**
* Create a PGP Key
*
* @todo Incomplete and untested.
*
* @param string $name
* @param string $email
* @param string $comment
* @param int $keysize
* @return OpenPGP
*/
static function create(string $name,string $email,string $comment,int $keysize=512): self
{
$result = new self;
$rsa = new Crypt_RSA;
$rsa->loadKey(Arr::get($rsa->createKey($keysize),'privatekey'));
$nkey = new OpenPGP\SecretKeyPacket(array(
'n' => $rsa->modulus->toBytes(),
'e' => $rsa->publicExponent->toBytes(),
'd' => $rsa->exponent->toBytes(),
'p' => $rsa->primes[2]->toBytes(),
'q' => $rsa->primes[1]->toBytes(),
'u' => $rsa->coefficients[2]->toBytes()
));
$wkey = new OpenPGP\Crypt\RSA($nkey);
$uid = new OpenPGP\UserIDPacket($name,$comment,$email);
$result->key = $wkey->sign_key_userid([$nkey,$uid]);
return $result;
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6
* @see http://tools.ietf.org/html/rfc4880#section-6.1
*/
static function crc24($data): int
{
$crc = 0x00b704ce;
for ($i = 0; $i < strlen($data); $i++) {
$crc ^= (ord($data[$i]) & 255) << 16;
for ($j = 0; $j < 8; $j++) {
$crc <<= 1;
if ($crc & 0x01000000) {
$crc ^= 0x01864cfb;
}
}
}
return $crc & 0x00ffffff;
}
static function decode_s2k_count($c)
{
return ((int)16 + ($c & 15)) << (($c >> 4) + 6);
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6
@@ -42,75 +117,6 @@ class OpenPGP
return $text;
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6
* @see http://tools.ietf.org/html/rfc2045
*/
static function unarmor($text,$header='PGP PUBLIC KEY BLOCK')
{
$header = self::header($header);
$text = str_replace(["\r\n","\r"],["\n",''],$text);
if (($pos1=strpos($text,$header)) !== FALSE
&& ($pos1=strpos($text,"\n\n",$pos1+=strlen($header))) !== FALSE
&& ($pos2=strpos($text,"\n=",$pos1+=2)) !== FALSE)
{
return base64_decode($text=substr($text,$pos1,$pos2-$pos1));
}
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6.2
*/
static protected function header($marker): string
{
return '-----BEGIN '.strtoupper((string)$marker).'-----';
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6.2
*/
static protected function footer($marker): string
{
return'-----END '.strtoupper((string)$marker).'-----';
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6
* @see http://tools.ietf.org/html/rfc4880#section-6.1
*/
static function crc24($data): int
{
$crc = 0x00b704ce;
for ($i = 0; $i < strlen($data); $i++) {
$crc ^= (ord($data[$i]) & 255) << 16;
for ($j = 0; $j < 8; $j++) {
$crc <<= 1;
if ($crc & 0x01000000) {
$crc ^= 0x01864cfb;
}
}
}
return $crc & 0x00ffffff;
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-12.2
*/
static function bitlength($data)
{
return (strlen($data) - 1) * 8 + (int)floor(log(ord($data[0]), 2)) + 1;
}
static function decode_s2k_count($c)
{
return ((int)16 + ($c & 15)) << (($c >> 4) + 6);
}
static function encode_s2k_count($iterations)
{
if($iterations >= 65011712) return 255;
@@ -130,4 +136,38 @@ class OpenPGP
return $result;
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6.2
*/
static protected function footer($marker): string
{
return'-----END '.strtoupper((string)$marker).'-----';
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6.2
*/
static protected function header($marker): string
{
return '-----BEGIN '.strtoupper((string)$marker).'-----';
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-6
* @see http://tools.ietf.org/html/rfc2045
*/
static function unarmor($text,$header='PGP PUBLIC KEY BLOCK')
{
$header = self::header($header);
$text = str_replace(["\r\n","\r"],["\n",''],$text);
if (($pos1=strpos($text,$header)) !== FALSE
&& ($pos1=strpos($text,"\n\n",$pos1+=strlen($header))) !== FALSE
&& ($pos2=strpos($text,"\n=",$pos1+=2)) !== FALSE)
{
return base64_decode($text=substr($text,$pos1,$pos2-$pos1));
}
}
}

View File

@@ -31,7 +31,7 @@ class AsymmetricSessionKeyPacket extends Packet
// Store KeyID in Hex
for ($i=0;$i<strlen($rawkeyid);$i++) {
$this->keyid .= sprintf('%02X',ord($rawkeyid{$i}));
$this->keyid .= sprintf('%02X',ord($rawkeyid[$i]));
}
$this->key_algorithm = ord($this->read_byte());
@@ -49,7 +49,7 @@ class AsymmetricSessionKeyPacket extends Packet
$bytes = chr($this->version);
for ($i=0;$i<strlen($this->keyid);$i+= 2) {
$bytes .= chr(hexdec($this->keyid{$i}.$this->keyid{$i+1}));
$bytes .= chr(hexdec($this->keyid[$i].$this->keyid[$i+1]));
}
$bytes .= chr($this->key_algorithm);

View File

@@ -80,19 +80,19 @@ class CompressedDataPacket extends Packet implements \IteratorAggregate, \ArrayA
switch($this->algorithm) {
case 0:
$this->data = OpenPGP\Message::parse($this->data);
$this->data = Message::parse($this->data);
break;
case 1:
$this->data = OpenPGP\Message::parse(gzinflate($this->data));
$this->data = Message::parse(gzinflate($this->data));
break;
case 2:
$this->data = OpenPGP\Message::parse(gzuncompress($this->data));
$this->data = Message::parse(gzuncompress($this->data));
break;
case 3:
$this->data = OpenPGP\Message::parse(bzdecompress($this->data));
$this->data = Message::parse(bzdecompress($this->data));
break;
default:

View File

@@ -24,12 +24,13 @@ class RSA
// Construct a wrapper object from a key or a message packet
function __construct($packet)
{
if (!is_object($packet))
if (! is_object($packet))
$packet = OpenPGP\Message::parse($packet);
// If it's a key (other keys are subclasses of this one)
if ($packet instanceof OpenPGP\PublicKeyPacket || $packet[0] instanceof OpenPGP\PublicKeyPacket) {
$this->key = $packet;
} else {
$this->message = $packet;
}
@@ -155,7 +156,7 @@ class RSA
$key->setHash(strtolower($hash));
$sig = new SignaturePacket($message,'RSA',strtoupper($hash));
$sig = new OpenPGP\SignaturePacket($message,'RSA',strtoupper($hash));
$sig->hashed_subpackets[] = new OpenPGP\SignaturePacket\IssuerPacket($keyid);
$sig->sign_data(['RSA'=>[$hash => function($data) use($key) {return [$key->sign($data)];}]]);
@@ -180,6 +181,7 @@ class RSA
if (! $keyid)
$keyid = substr($this->key->fingerprint,-16);
$key->setHash(strtolower($hash));
$sig = NULL;
@@ -282,13 +284,13 @@ class RSA
$sk_chk = 0;
for ($i=0;$i<strlen($sk);$i++) {
$sk_chk = ($sk_chk+ord($sk{$i}))%65536;
$sk_chk = ($sk_chk+ord($sk[$i]))%65536;
}
if ($sk_chk != $chk)
return NULL;
return array(ord($data{0}),$sk);
return array(ord($data[0]),$sk);
}
static function crypt_rsa_key($mod,$exp,$hash='SHA256')

View File

@@ -103,7 +103,7 @@ class Symmetric
$cipher->setKey($p->s2k->make_key($pass, $key_bytes));
$padAmount = $key_block_bytes - (strlen($p->encrypted_data) % $key_block_bytes);
$data = substr($cipher->decrypt($p->encrypted_data . str_repeat("\0", $padAmount)), 0, strlen($p->encrypted_data));
$decrypted = self::decryptPacket($epacket, ord($data{0}), substr($data, 1));
$decrypted = self::decryptPacket($epacket, ord($data[0]), substr($data, 1));
} else {
list($cipher,$key_bytes,$key_block_bytes) = self::getCipher($p->symmetric_algorithm);
@@ -208,7 +208,6 @@ class Symmetric
try {
$msg = OpenPGP\Message::parse($data);
dump(['data'=>$data,'msg'=>$msg]);
} catch (Exception $ex) {
$msg = NULL;
@@ -246,7 +245,7 @@ class Symmetric
switch($algo) {
case NULL:
case 0:
throw new Exception("Data is already unencrypted");
throw new \Exception("Data is already unencrypted");
case 2:
$cipher = new Crypt_TripleDES(Crypt_TripleDES::MODE_CFB);
@@ -256,9 +255,9 @@ class Symmetric
case 3:
if (class_exists('OpenSSLWrapper')) {
$cipher = new OpenSSLWrapper("CAST5-CFB");
$cipher = new \OpenSSLWrapper("CAST5-CFB");
} else if(defined('MCRYPT_CAST_128')) {
$cipher = new MCryptWrapper(MCRYPT_CAST_128);
$cipher = new \MCryptWrapper(MCRYPT_CAST_128);
}
break;
@@ -320,7 +319,7 @@ class Symmetric
$mkChk = 0;
for($i = 0; $i < strlen($s); $i++) {
$mkChk = ($mkChk + ord($s{$i})) % 65536;
$mkChk = ($mkChk + ord($s[$i])) % 65536;
}
return $mkChk;

View File

@@ -16,7 +16,7 @@ class OnePassSignaturePacket extends Packet
{
$body = chr($this->version).chr($this->signature_type).chr($this->hash_algorithm).chr($this->key_algorithm);
for($i = 0; $i < strlen($this->key_id); $i += 2) {
$body .= chr(hexdec($this->key_id{$i}.$this->key_id{$i+1}));
$body .= chr(hexdec($this->key_id[$i].$this->key_id[$i+1]));
}
$body .= chr((int)$this->nested);
return $body;

View File

@@ -182,9 +182,12 @@ abstract class Packet
{
// Make sure our tag is set in our packet class.
try {
if (is_null($this->tag))
if (is_null($this->tag) AND get_class($this) != 'Leenooks\OpenPGP\SignaturePacket\Subpacket')
throw new PacketTagException('Missing tag in '.get_class($this));
} catch (\Exception $e) {
debug_print_backtrace(0,5);
dump($this);
dd($e->getMessage());
}
@@ -197,7 +200,7 @@ abstract class Packet
'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->tag = array_search(substr(substr(get_class($this),strlen("Leenooks\OpenPGP")+1),0,-6),self::$tags);
$this->data = $data;
}
@@ -220,6 +223,11 @@ abstract class Packet
return ['header'=>$tag.$size,'body'=>$body];
}
public function tag(): int
{
return $this->tag;
}
function to_bytes()
{
$data = $this->header_and_body();

View File

@@ -3,7 +3,6 @@
namespace Leenooks\OpenPGP;
use Leenooks\OpenPGP;
use Leenooks\OpenPGP_SignaturePacket;
class S2K
{
@@ -21,22 +20,22 @@ class S2K
{
$s2k = new self;
switch($s2k->type = ord($input{0})) {
switch($s2k->type = ord($input[0])) {
case 0:
$s2k->hash_algorithm = ord($input{1});
$s2k->hash_algorithm = ord($input[1]);
$input = substr($input,2);
break;
case 1:
$s2k->hash_algorithm = ord($input{1});
$s2k->hash_algorithm = ord($input[1]);
$s2k->salt = substr($input,2,8);
$input = substr($input,10);
break;
case 3:
$s2k->hash_algorithm = ord($input{1});
$s2k->hash_algorithm = ord($input[1]);
$s2k->salt = substr($input,2,8);
$s2k->count = OpenPGP::decode_s2k_count(ord($input{10}));
$s2k->count = OpenPGP::decode_s2k_count(ord($input[10]));
$input = substr($input,11);
break;
}
@@ -47,42 +46,42 @@ class S2K
function to_bytes()
{
$bytes = chr($this->type);
switch($this->type) {
case 0:
$bytes .= chr($this->hash_algorithm);
break;
case 1:
if (strlen($this->salt) != 8)
throw new Exception('Invalid salt length');
$bytes .= chr($this->hash_algorithm);
$bytes .= $this->salt;
break;
case 3:
if (strlen($this->salt) != 8)
throw new Exception('Invalid salt length');
$bytes .= chr($this->hash_algorithm);
$bytes .= $this->salt;
$bytes .= chr(OpenPGP::encode_s2k_count($this->count));
break;
}
return $bytes;
}
function raw_hash($s)
{
return hash(strtolower(OpenPGP_SignaturePacket::$hash_algorithms[$this->hash_algorithm]),$s,true);
return hash(strtolower(SignaturePacket::$hash_algorithms[$this->hash_algorithm]),$s,true);
}
function sized_hash($s,$size)
{
$hash = $this->raw_hash($s);
while(strlen($hash) < $size) {
$s = "\0".$s;
$hash .= $this->raw_hash($s);
@@ -95,9 +94,9 @@ class S2K
{
if (strlen($s) >= $this->count)
return $s;
$s = str_repeat($s,ceil($this->count/strlen($s)));
return substr($s,0,$this->count);
}
@@ -106,10 +105,10 @@ class S2K
switch($this->type) {
case 0:
return $this->sized_hash($pass, $size);
case 1:
return $this->sized_hash($this->salt . $pass, $size);
case 3:
return $this->sized_hash($this->iterate($this->salt . $pass), $size);
}

View File

@@ -62,11 +62,11 @@ class SecretKeyPacket extends PublicKeyPacket
function read()
{
parent::read(); // All the fields from PublicKey
$this->s2k_useage = ord($this->read_byte());
if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
$this->symmetric_algorithm = ord($this->read_byte());
$this->s2k = OpenPGP\S2K::parse($this->input);
$this->s2k = S2K::parse($this->input);
} else if($this->s2k_useage > 0) {
$this->symmetric_algorithm = $this->s2k_useage;
}

View File

@@ -12,7 +12,6 @@ use Leenooks\OpenPGP;
*/
class SignaturePacket extends Packet
{
protected static $DEBUG = FALSE;
protected $tag = 2;
public $version, $signature_type, $hash_algorithm, $key_algorithm, $hashed_subpackets, $unhashed_subpackets, $hash_head;
@@ -119,7 +118,7 @@ class SignaturePacket extends Packet
foreach ((array)$this->unhashed_subpackets as $p) {
if ($p instanceof SignaturePacket\IssuerPacket) {
for($i = 0; $i < strlen($p->data); $i += 2) {
$body .= chr(hexdec($p->data{$i}.$p->data{$i+1}));
$body .= chr(hexdec($p->data[$i].$p->data[$i+1]));
}
break;
@@ -189,9 +188,15 @@ class SignaturePacket extends Packet
static function get_subpacket(&$input)
{
if (self::$DEBUG)
dump(['method'=>__METHOD__,'input'=>$input]);
$len = ord($input[0]);
$length_of_length = 1;
if (self::$DEBUG)
dump(['len'=>$len]);
// if($len < 192) One octet length, no furthur processing
if ($len > 190 && $len < 255) { // Two octet length
$length_of_length = 2;
@@ -203,17 +208,27 @@ class SignaturePacket extends Packet
$length_of_length = 5;
$unpacked = unpack('N', substr($input, 1, 4));
$len = reset($unpacked);
if (self::$DEBUG)
dump(['len'=>$len,'unpacked'=>$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]);
$class = self::class_for($tag);
if (self::$DEBUG)
dump(['class'=>$class,'tag'=>$tag]);
if ($class) {
$packet = new $class;
// In case we got the catch all class.
if ($class == 'Leenooks\OpenPGP\SignaturePacket\Subpacket')
$packet->setTag($tag);
if ($packet->tag() !== $tag)
throw new OpenPGP\Exceptions\PacketTagException(sprintf('Packet tag [%s] doesnt match tag [%s]?',$packet->tag(),$tag));
//$packet->tag = $tag; // @todo Tag should already be set.
$packet->input = substr($input, 1, $len-1);
$packet->length = $len-1;
@@ -288,7 +303,7 @@ class SignaturePacket extends Packet
// Store KeyID in Hex
for ($i=0;$i<strlen($keyid);$i++) {
$keyidHex .= sprintf('%02X',ord($keyid{$i}));
$keyidHex .= sprintf('%02X',ord($keyid[$i]));
}
$this->hashed_subpackets = [];

View File

@@ -13,7 +13,7 @@ class IssuerPacket extends Subpacket
{
$bytes = '';
for($i = 0; $i < strlen($this->data); $i += 2) {
$bytes .= chr(hexdec($this->data{$i}.$this->data{$i+1}));
$bytes .= chr(hexdec($this->data[$i].$this->data[$i+1]));
}
return $bytes;
}

View File

@@ -17,7 +17,7 @@ class RevocationKeyPacket extends Subpacket
$bytes .= chr($this->key_algorithm);
for($i = 0; $i < strlen($this->fingerprint); $i += 2) {
$bytes .= chr(hexdec($this->fingerprint{$i}.$this->fingerprint{$i+1}));
$bytes .= chr(hexdec($this->fingerprint[$i].$this->fingerprint[$i+1]));
}
return $bytes;

View File

@@ -2,6 +2,7 @@
namespace Leenooks\OpenPGP\SignaturePacket;
use Leenooks\OpenPGP\Exceptions\PacketTagException;
use Leenooks\OpenPGP\Packet;
class Subpacket extends Packet
@@ -21,10 +22,18 @@ class Subpacket extends Packet
return ['header'=>$size.$tag,'body'=>$body];
}
/* Defaults for unsupported packets */
function read()
{
$this->data = $this->input;
}
public function setTag(int $tag): void
{
if (get_class($this) !== Subpacket::class)
throw new PacketTagException('Attempting to set a tag for invalid class: ',get_class($this));
$this->tag = $tag;
}
}

View File

@@ -31,7 +31,7 @@ class SymmetricSessionKeyPacket extends Packet
{
$this->version = ord($this->read_byte());
$this->symmetric_algorithm = ord($this->read_byte());
$this->s2k = OpenPGP\S2k::parse($this->input);
$this->s2k = S2k::parse($this->input);
$this->encrypted_data = $this->input;
}
}