Move to more PSR-4 standards.
This commit is contained in:
parent
d458fecff6
commit
7d259b251f
2
AUTHORS
2
AUTHORS
@ -1,2 +0,0 @@
|
||||
* Arto Bendiken <arto.bendiken@gmail.com>
|
||||
* Stephen Paul Weber <singpolyma@singpolyma.net>
|
133
lib/OpenPGP.php
Normal file
133
lib/OpenPGP.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
/**
|
||||
* OpenPGP.php is a pure-PHP implementation of the OpenPGP Message Format
|
||||
* (RFC 4880).
|
||||
*
|
||||
* @package OpenPGP
|
||||
* @author Arto Bendiken <arto.bendiken@gmail.com>
|
||||
* @author Stephen Paul Weber <singpolyma@singpolyma.net>
|
||||
* @author Deon George <deon@leenooks.net>
|
||||
* @see http://github.com/bendiken/openpgp-php
|
||||
*/
|
||||
|
||||
namespace Leenooks;
|
||||
|
||||
use Leenooks\OpenPGP\Exceptions\PacketTagException;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880
|
||||
*/
|
||||
class OpenPGP
|
||||
{
|
||||
const VERSION = [0,5,0];
|
||||
|
||||
/**
|
||||
* @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/rfc2045
|
||||
*/
|
||||
static function enarmor($data,$marker='MESSAGE',array $headers=[])
|
||||
{
|
||||
$text = self::header($marker)."\n";
|
||||
|
||||
foreach ($headers as $key => $value) {
|
||||
$text .= $key.': '.(string)$value."\n";
|
||||
}
|
||||
|
||||
$text .= "\n".wordwrap(base64_encode($data),76,"\n",true);
|
||||
$text .= "\n".'='.base64_encode(substr(pack('N',self::crc24($data)),1))."\n";
|
||||
$text .= self::footer($marker)."\n";
|
||||
|
||||
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;
|
||||
|
||||
$count = $iterations >> 6;
|
||||
$c = 0;
|
||||
|
||||
while($count >= 32) {
|
||||
$count = $count >> 1;
|
||||
$c++;
|
||||
}
|
||||
$result = ($c << 4) | ($count - 16);
|
||||
|
||||
if (OpenPGP::decode_s2k_count($result) < $iterations) {
|
||||
return $result + 1;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
60
lib/OpenPgP/AsymmetricSessionKeyPacket.php
Normal file
60
lib/OpenPgP/AsymmetricSessionKeyPacket.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Public-Key Encrypted Session Key packet (tag 1).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.1
|
||||
*/
|
||||
class AsymmetricSessionKeyPacket extends Packet
|
||||
{
|
||||
protected $tag = 1;
|
||||
public $version,$keyid,$key_algorithm,$encrypted_data;
|
||||
|
||||
function __construct($key_algorithm='',$keyid='',$encrypted_data='',$version=3)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->version = $version;
|
||||
$this->keyid = substr($keyid,-16);
|
||||
$this->key_algorithm = $key_algorithm;
|
||||
$this->encrypted_data = $encrypted_data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
switch ($this->version = ord($this->read_byte())) {
|
||||
case 3:
|
||||
$rawkeyid = $this->read_bytes(8);
|
||||
$this->keyid = '';
|
||||
|
||||
// Store KeyID in Hex
|
||||
for ($i=0;$i<strlen($rawkeyid);$i++) {
|
||||
$this->keyid .= sprintf('%02X',ord($rawkeyid{$i}));
|
||||
}
|
||||
|
||||
$this->key_algorithm = ord($this->read_byte());
|
||||
|
||||
$this->encrypted_data = $this->input;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Exception("Unsupported AsymmetricSessionKeyPacket version: ".$this->version);
|
||||
}
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
$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($this->key_algorithm);
|
||||
$bytes .= $this->encrypted_data;
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
}
|
102
lib/OpenPgP/CompressedDataPacket.php
Normal file
102
lib/OpenPgP/CompressedDataPacket.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Compressed Data packet (tag 8).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.6
|
||||
*/
|
||||
class CompressedDataPacket extends Packet implements \IteratorAggregate, \ArrayAccess
|
||||
{
|
||||
protected $tag = 8;
|
||||
public $algorithm;
|
||||
|
||||
/* see http://tools.ietf.org/html/rfc4880#section-9.3 */
|
||||
static $algorithms = [
|
||||
0 => 'Uncompressed',
|
||||
1 => 'ZIP',
|
||||
2 => 'ZLIB',
|
||||
3 => 'BZip2',
|
||||
];
|
||||
|
||||
// IteratorAggregate interface
|
||||
|
||||
function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->data->packets);
|
||||
}
|
||||
|
||||
// ArrayAccess interface
|
||||
|
||||
function offsetExists($offset) {
|
||||
return isset($this->data[$offset]);
|
||||
}
|
||||
|
||||
function offsetGet($offset) {
|
||||
return $this->data[$offset];
|
||||
}
|
||||
|
||||
function offsetSet($offset, $value) {
|
||||
return is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value;
|
||||
}
|
||||
|
||||
function offsetUnset($offset) {
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
$body = chr($this->algorithm);
|
||||
|
||||
switch($this->algorithm) {
|
||||
case 0:
|
||||
$body .= $this->data->to_bytes();
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$body .= gzdeflate($this->data->to_bytes());
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$body .= gzcompress($this->data->to_bytes());
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$body .= bzcompress($this->data->to_bytes());
|
||||
break;
|
||||
|
||||
default:
|
||||
/* @todo error? */
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->algorithm = ord($this->read_byte());
|
||||
$this->data = $this->read_bytes($this->length);
|
||||
|
||||
switch($this->algorithm) {
|
||||
case 0:
|
||||
$this->data = OpenPGP\Message::parse($this->data);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$this->data = OpenPGP\Message::parse(gzinflate($this->data));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$this->data = OpenPGP\Message::parse(gzuncompress($this->data));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$this->data = OpenPGP\Message::parse(bzdecompress($this->data));
|
||||
break;
|
||||
|
||||
default:
|
||||
/* TODO error? */
|
||||
}
|
||||
}
|
||||
}
|
356
lib/OpenPgP/Crypt/RSA.php
Normal file
356
lib/OpenPgP/Crypt/RSA.php
Normal file
@ -0,0 +1,356 @@
|
||||
<?php
|
||||
/**
|
||||
* OpenPGP_Crypt_RSA.php is a wrapper for using the classes from OpenPGP.php with Crypt_RSA
|
||||
*
|
||||
* @package OpenPGP
|
||||
*/
|
||||
|
||||
namespace Leenooks\OpenPGP\Crypt;
|
||||
|
||||
use Leenooks\OpenPGP;
|
||||
|
||||
// From http://phpseclib.sourceforge.net/
|
||||
use phpseclib\Crypt\RSA as Crypt_RSA;
|
||||
use phpseclib\Math\BigInteger as Math_BigInteger;
|
||||
|
||||
define('CRYPT_RSA_ENCRYPTION_PKCS1', Crypt_RSA::ENCRYPTION_PKCS1);
|
||||
define('CRYPT_RSA_SIGNATURE_PKCS1', Crypt_RSA::SIGNATURE_PKCS1);
|
||||
|
||||
class RSA
|
||||
{
|
||||
protected static $DEBUG = FALSE;
|
||||
protected $key,$message;
|
||||
|
||||
// Construct a wrapper object from a key or a message packet
|
||||
function __construct($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;
|
||||
}
|
||||
}
|
||||
|
||||
function key($keyid=NULL)
|
||||
{
|
||||
// No key
|
||||
if (! $this->key)
|
||||
return NULL;
|
||||
|
||||
if ($this->key instanceof OpenPGP\Message) {
|
||||
foreach ($this->key as $p) {
|
||||
if ($p instanceof OpenPGP\PublicKeyPacket) {
|
||||
if (!$keyid || strtoupper(substr($p->fingerprint,strlen($keyid)*-1)) == strtoupper($keyid))
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
// Get Crypt_RSA for the public key
|
||||
function public_key($keyid=NULL)
|
||||
{
|
||||
return self::convert_public_key($this->key($keyid));
|
||||
}
|
||||
|
||||
// Get Crypt_RSA for the private key
|
||||
function private_key($keyid=NULL)
|
||||
{
|
||||
return self::convert_private_key($this->key($keyid));
|
||||
}
|
||||
|
||||
// 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)
|
||||
function verify($packet)
|
||||
{
|
||||
if (static::$DEBUG)
|
||||
dump(['In METHOD: '=>__METHOD__,'packet'=>$packet]);
|
||||
|
||||
$self = $this; // For old PHP
|
||||
if (! is_object($packet))
|
||||
$packet = OpenPGP\Message::parse($packet);
|
||||
|
||||
if (! $this->message) {
|
||||
$m = $packet;
|
||||
$verifier = function($m, $s) use($self) {
|
||||
$key = $self->public_key($s->issuer());
|
||||
if(!$key) return false;
|
||||
$key->setHash(strtolower($s->hash_algorithm_name()));
|
||||
return $key->verify($m, reset($s->data));
|
||||
};
|
||||
} else {
|
||||
if(!($packet instanceof Crypt_RSA)) {
|
||||
$packet = new self($packet);
|
||||
}
|
||||
|
||||
$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
|
||||
// Second parameter is hash algorithm to use (default SHA256)
|
||||
// Third parameter is the 16-digit key ID to use... defaults to the key id in the key packet
|
||||
function sign($packet,$hash='SHA256',$keyid=NULL)
|
||||
{
|
||||
if (! is_object($packet)) {
|
||||
if($this->key) {
|
||||
$packet = new OpenPGP\LiteralDataPacket($packet);
|
||||
} else {
|
||||
$packet = OpenPGP\Message::parse($packet);
|
||||
}
|
||||
}
|
||||
|
||||
if ($packet instanceof OpenPGP\SecretKeyPacket
|
||||
|| $packet instanceof Crypt_RSA
|
||||
|| ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP\SecretKeyPacket))
|
||||
{
|
||||
$key = $packet;
|
||||
$message = $this->message;
|
||||
|
||||
} else {
|
||||
$key = $this->key;
|
||||
$message = $packet;
|
||||
}
|
||||
|
||||
// Missing some data
|
||||
if (! $key || !$message)
|
||||
return NULL;
|
||||
|
||||
if ($message instanceof OpenPGP\Message) {
|
||||
$sign = $message->signatures();
|
||||
$message = $sign[0][0];
|
||||
}
|
||||
|
||||
if (!($key instanceof Crypt_RSA)) {
|
||||
$key = new self($key);
|
||||
|
||||
if (! $keyid)
|
||||
$keyid = substr($key->key()->fingerprint,-16,16);
|
||||
|
||||
$key = $key->private_key($keyid);
|
||||
}
|
||||
|
||||
$key->setHash(strtolower($hash));
|
||||
|
||||
$sig = new 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)];}]]);
|
||||
|
||||
return new OpenPGP\Message(array($sig, $message));
|
||||
}
|
||||
|
||||
/** Pass a message with a key and userid packet to sign */
|
||||
// TODO: merge this with the normal sign function
|
||||
function sign_key_userid($packet,$hash='SHA256',$keyid=NULL)
|
||||
{
|
||||
if (is_array($packet)) {
|
||||
$packet = new OpenPGP\Message($packet);
|
||||
} else if(!is_object($packet)) {
|
||||
$packet = OpenPGP\Message::parse($packet);
|
||||
}
|
||||
|
||||
$key = $this->private_key($keyid);
|
||||
|
||||
// Missing some data
|
||||
if (! $key || ! $packet)
|
||||
return NULL;
|
||||
|
||||
if (! $keyid)
|
||||
$keyid = substr($this->key->fingerprint,-16);
|
||||
$key->setHash(strtolower($hash));
|
||||
|
||||
$sig = NULL;
|
||||
foreach($packet as $p) {
|
||||
if ($p instanceof OpenPGP\SignaturePacket)
|
||||
$sig = $p;
|
||||
}
|
||||
|
||||
if (! $sig) {
|
||||
$sig = new OpenPGP\SignaturePacket($packet,'RSA',strtoupper($hash));
|
||||
$sig->signature_type = 0x13;
|
||||
$sig->hashed_subpackets[] = new OpenPGP\SignaturePacket\KeyFlagsPacket(array(0x01|0x02));
|
||||
$sig->hashed_subpackets[] = new OpenPGP\SignaturePacket\IssuerPacket($keyid);
|
||||
$packet[] = $sig;
|
||||
}
|
||||
|
||||
$sig->sign_data(['RSA'=>[$hash => function($data) use($key) {return [$key->sign($data)];}]]);
|
||||
|
||||
return $packet;
|
||||
}
|
||||
|
||||
function decrypt($packet)
|
||||
{
|
||||
if (! is_object($packet))
|
||||
$packet = OpenPGP\Message::parse($packet);
|
||||
|
||||
if ($packet instanceof OpenPGP\SecretKeyPacket
|
||||
|| $packet instanceof Crypt_RSA
|
||||
|| ($packet instanceof \ArrayAccess && $packet[0] instanceof OpenPGP\SecretKeyPacket))
|
||||
{
|
||||
$keys = $packet;
|
||||
$message = $this->message;
|
||||
|
||||
} else {
|
||||
$keys = $this->key;
|
||||
$message = $packet;
|
||||
}
|
||||
|
||||
// Missing some data
|
||||
if (! $keys || ! $message)
|
||||
return NULL;
|
||||
|
||||
if (! ($keys instanceof Crypt_RSA)) {
|
||||
$keys = new self($keys);
|
||||
}
|
||||
|
||||
if (static::$DEBUG)
|
||||
dump([__METHOD__=>['keys'=>$keys,'message'=>$message]]);
|
||||
|
||||
foreach ($message as $p) {
|
||||
if (static::$DEBUG)
|
||||
dump(['p'=>$p,'test'=> ($p instanceof OpenPGP\AsymmetricSessionKeyPacket) ]);
|
||||
|
||||
if ($p instanceof OpenPGP\AsymmetricSessionKeyPacket) {
|
||||
if (static::$DEBUG)
|
||||
dump(['keys'=>$keys,'test'=>($keys instanceof Crypt_RSA)]);
|
||||
|
||||
if($keys instanceof Crypt_RSA) {
|
||||
$sk = self::try_decrypt_session($keys,substr($p->encrypted_data,2));
|
||||
|
||||
} elseif (strlen(str_replace('0','',$p->keyid)) < 1) {
|
||||
foreach($keys->key as $k) {
|
||||
$sk = self::try_decrypt_session(self::convert_private_key($k), substr($p->encrypted_data, 2));
|
||||
|
||||
if ($sk)
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
$key = $keys->private_key($p->keyid);
|
||||
$sk = self::try_decrypt_session($key,substr($p->encrypted_data,2));
|
||||
|
||||
if (static::$DEBUG)
|
||||
dump(['c'=>$p->keyid,'key'=>$key,'sk'=>$sk]);
|
||||
}
|
||||
|
||||
if (! $sk)
|
||||
continue;
|
||||
|
||||
$r = Symmetric::decryptPacket(Symmetric::getEncryptedData($message),$sk[0],$sk[1]);
|
||||
|
||||
if ($r)
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; /* Failed */
|
||||
}
|
||||
|
||||
static function try_decrypt_session($key,$edata)
|
||||
{
|
||||
$key->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
|
||||
$data = @$key->decrypt($edata);
|
||||
if (! $data)
|
||||
return NULL;
|
||||
|
||||
$sk = substr($data,1,strlen($data)-3);
|
||||
$chk = unpack('n',substr($data,-2));
|
||||
$chk = reset($chk);
|
||||
|
||||
$sk_chk = 0;
|
||||
for ($i=0;$i<strlen($sk);$i++) {
|
||||
$sk_chk = ($sk_chk+ord($sk{$i}))%65536;
|
||||
}
|
||||
|
||||
if ($sk_chk != $chk)
|
||||
return NULL;
|
||||
|
||||
return array(ord($data{0}),$sk);
|
||||
}
|
||||
|
||||
static function crypt_rsa_key($mod,$exp,$hash='SHA256')
|
||||
{
|
||||
$rsa = new Crypt_RSA();
|
||||
|
||||
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
|
||||
$rsa->setHash(strtolower($hash));
|
||||
$rsa->modulus = new Math_BigInteger($mod,256);
|
||||
$rsa->k = strlen($rsa->modulus->toBytes());
|
||||
$rsa->exponent = new Math_BigInteger($exp,256);
|
||||
$rsa->setPublicKey();
|
||||
|
||||
return $rsa;
|
||||
}
|
||||
|
||||
static function convert_key($packet,$private=false)
|
||||
{
|
||||
if (! is_object($packet))
|
||||
$packet = OpenPGP\Message::parse($packet);
|
||||
|
||||
if ($packet instanceof OpenPGP\Message)
|
||||
$packet = $packet[0];
|
||||
|
||||
$mod = $packet->key['n'];
|
||||
$exp = $packet->key['e'];
|
||||
|
||||
if ($private)
|
||||
$exp = $packet->key['d'];
|
||||
|
||||
// Packet doesn't have needed data
|
||||
if (! $exp)
|
||||
return NULL;
|
||||
|
||||
$rsa = self::crypt_rsa_key($mod,$exp);
|
||||
|
||||
if ($private) {
|
||||
/**
|
||||
* @see https://github.com/phpseclib/phpseclib/issues/1113
|
||||
* Primes and coefficients now use BigIntegers.
|
||||
**/
|
||||
//set the primes
|
||||
if ($packet->key['p'] && $packet->key['q'])
|
||||
$rsa->primes = [
|
||||
1 => new Math_BigInteger($packet->key['p'], 256),
|
||||
2 => new Math_BigInteger($packet->key['q'], 256)
|
||||
];
|
||||
// set the coefficients
|
||||
if ($packet->key['u'])
|
||||
$rsa->coefficients = [2=>new Math_BigInteger($packet->key['u'],256)];
|
||||
}
|
||||
|
||||
return $rsa;
|
||||
}
|
||||
|
||||
static function convert_public_key($packet)
|
||||
{
|
||||
return self::convert_key($packet, false);
|
||||
}
|
||||
|
||||
static function convert_private_key($packet)
|
||||
{
|
||||
return self::convert_key($packet, true);
|
||||
}
|
||||
}
|
328
lib/OpenPgP/Crypt/Symmetric.php
Normal file
328
lib/OpenPgP/Crypt/Symmetric.php
Normal file
@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\Crypt;
|
||||
|
||||
use phpseclib\Crypt\AES as Crypt_AES;
|
||||
use phpseclib\Crypt\Blowfish as Crypt_Blowfish;
|
||||
use phpseclib\Crypt\TripleDES as Crypt_TripleDES;
|
||||
use phpseclib\Crypt\Twofish as Crypt_Twofish;
|
||||
use phpseclib\Crypt\Random;
|
||||
|
||||
use Leenooks\OpenPGP;
|
||||
|
||||
class Symmetric
|
||||
{
|
||||
protected static $DEBUG = FALSE;
|
||||
|
||||
public static function encrypt($passphrases_and_keys,$message,$symmetric_algorithm=9): OpenPGP\Message
|
||||
{
|
||||
if (static::$DEBUG)
|
||||
dump(['In METHOD: '=>__METHOD__,'passphrases_and_keys'=>$passphrases_and_keys,'symmetric_algorithm'=>$symmetric_algorithm]);
|
||||
|
||||
list($cipher,$key_bytes,$key_block_bytes) = self::getCipher($symmetric_algorithm);
|
||||
if (static::$DEBUG)
|
||||
dump(['cipher'=>$cipher,'key_bytes'=>$key_bytes,'key_block_bytes'=>$key_block_bytes]);
|
||||
|
||||
if (! $cipher)
|
||||
throw new Exception("Unsupported cipher");
|
||||
|
||||
$prefix = Random::string($key_block_bytes);
|
||||
$prefix .= substr($prefix, -2);
|
||||
|
||||
$key = Random::string($key_bytes);
|
||||
$cipher->setKey($key);
|
||||
|
||||
$to_encrypt = $prefix.$message->to_bytes();
|
||||
|
||||
$mdc = new OpenPGP\ModificationDetectionCodePacket(hash('sha1',$to_encrypt."\xD3\x14",true));
|
||||
$to_encrypt .= $mdc->to_bytes();
|
||||
|
||||
if (static::$DEBUG)
|
||||
dump(['to_encrypt'=>$to_encrypt]);
|
||||
|
||||
$encrypted = [new OpenPGP\IntegrityProtectedDataPacket($cipher->encrypt($to_encrypt))];
|
||||
|
||||
if (static::$DEBUG)
|
||||
dump(['encrypted'=>$encrypted]);
|
||||
|
||||
if (! is_array($passphrases_and_keys) && ! ($passphrases_and_keys instanceof \IteratorAggregate)) {
|
||||
$passphrases_and_keys = (array)$passphrases_and_keys;
|
||||
}
|
||||
|
||||
if (static::$DEBUG)
|
||||
dump(['pk'=>$passphrases_and_keys]);
|
||||
|
||||
foreach ($passphrases_and_keys as $pass) {
|
||||
if ($pass instanceof OpenPGP\PublicKeyPacket) {
|
||||
if (static::$DEBUG)
|
||||
dump(['pass'=>$pass,'instanceof'=>'Leenooks\OpenPGP\PublicKeyPacket']);
|
||||
|
||||
if (! in_array($pass->algorithm,[1,2,3]))
|
||||
throw new Exception("Only RSA keys are supported.");
|
||||
|
||||
$crypt_rsa = new RSA($pass);
|
||||
$rsa = $crypt_rsa->public_key();
|
||||
|
||||
if (static::$DEBUG)
|
||||
dump(['public_key'=>$rsa]);
|
||||
|
||||
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
|
||||
$esk = $rsa->encrypt(chr($symmetric_algorithm).$key.pack('n', self::checksum($key)));
|
||||
$esk = pack('n',OpenPGP::bitlength($esk)).$esk;
|
||||
|
||||
array_unshift($encrypted, new OpenPGP\AsymmetricSessionKeyPacket($pass->algorithm,$pass->fingerprint(),$esk));
|
||||
|
||||
} elseif (is_string($pass)) {
|
||||
$s2k = new OpenPGP\S2K(Random::string(8));
|
||||
|
||||
$cipher->setKey($s2k->make_key($pass, $key_bytes));
|
||||
$esk = $cipher->encrypt(chr($symmetric_algorithm) . $key);
|
||||
|
||||
array_unshift($encrypted, new OpenPGP\SymmetricSessionKeyPacket($s2k, $esk, $symmetric_algorithm));
|
||||
}
|
||||
}
|
||||
|
||||
if (static::$DEBUG)
|
||||
dump(['Out METHOD: '=>__METHOD__,'encrypted'=>$encrypted,'message'=>(new OpenPGP\Message($encrypted))]);
|
||||
|
||||
return new OpenPGP\Message($encrypted);
|
||||
}
|
||||
|
||||
public static function decryptSymmetric($pass,$m)
|
||||
{
|
||||
$epacket = self::getEncryptedData($m);
|
||||
|
||||
foreach ($m as $p) {
|
||||
if ($p instanceof OpenPGP\SymmetricSessionKeyPacket) {
|
||||
if (strlen($p->encrypted_data) > 0) {
|
||||
list ($cipher,$key_bytes,$key_block_bytes) = self::getCipher($p->symmetric_algorithm);
|
||||
|
||||
if (! $cipher)
|
||||
continue;
|
||||
|
||||
$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));
|
||||
|
||||
} else {
|
||||
list($cipher,$key_bytes,$key_block_bytes) = self::getCipher($p->symmetric_algorithm);
|
||||
|
||||
$decrypted = self::decryptPacket($epacket,$p->symmetric_algorithm,$p->s2k->make_key($pass,$key_bytes));
|
||||
}
|
||||
|
||||
if ($decrypted)
|
||||
return $decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; /* If we get here, we failed */
|
||||
}
|
||||
|
||||
public static function encryptSecretKey($pass,$packet,$symmetric_algorithm=9)
|
||||
{
|
||||
$packet = clone $packet; // Do not mutate original
|
||||
$packet->s2k_useage = 254;
|
||||
$packet->symmetric_algorithm = $symmetric_algorithm;
|
||||
|
||||
list($cipher,$key_bytes,$key_block_bytes) = self::getCipher($packet->symmetric_algorithm);
|
||||
if (! $cipher)
|
||||
throw new Exception("Unsupported cipher");
|
||||
|
||||
$material = '';
|
||||
foreach (OpenPGP\SecretKeyPacket::$secret_key_fields[$packet->algorithm] as $field) {
|
||||
$f = $packet->key[$field];
|
||||
$material .= pack('n',OpenPGP::bitlength($f)).$f;
|
||||
unset($packet->key[$field]);
|
||||
}
|
||||
$material .= hash('sha1',$material,true);
|
||||
|
||||
$iv = Random::string($key_block_bytes);
|
||||
if (! $packet->s2k)
|
||||
$packet->s2k = new OpenPGP\S2K(Random::string(8));
|
||||
|
||||
$cipher->setKey($packet->s2k->make_key($pass, $key_bytes));
|
||||
$cipher->setIV($iv);
|
||||
$packet->encrypted_data = $iv.$cipher->encrypt($material);
|
||||
|
||||
return $packet;
|
||||
}
|
||||
|
||||
public static function decryptSecretKey($pass,$packet)
|
||||
{
|
||||
$packet = clone $packet; // Do not mutate orinigal
|
||||
|
||||
list($cipher,$key_bytes,$key_block_bytes) = self::getCipher($packet->symmetric_algorithm);
|
||||
if (! $cipher)
|
||||
throw new Exception("Unsupported cipher");
|
||||
|
||||
$cipher->setKey($packet->s2k->make_key($pass, $key_bytes));
|
||||
$cipher->setIV(substr($packet->encrypted_data, 0, $key_block_bytes));
|
||||
$material = $cipher->decrypt(substr($packet->encrypted_data, $key_block_bytes));
|
||||
|
||||
if ($packet->s2k_useage == 254) {
|
||||
$chk = substr($material, -20);
|
||||
$material = substr($material, 0, -20);
|
||||
if ($chk != hash('sha1', $material, true))
|
||||
return NULL;
|
||||
|
||||
} else {
|
||||
$chk = unpack('n', substr($material, -2));
|
||||
$chk = reset($chk);
|
||||
$material = substr($material, 0, -2);
|
||||
|
||||
$mkChk = self::checksum($material);
|
||||
if ($chk != $mkChk)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
$packet->s2k = NULL;
|
||||
$packet->s2k_useage = 0;
|
||||
$packet->symmetric_algorithm = 0;
|
||||
$packet->encrypted_data = NULL;
|
||||
$packet->input = $material;
|
||||
$packet->key_from_input();
|
||||
unset($packet->input);
|
||||
|
||||
return $packet;
|
||||
}
|
||||
|
||||
public static function decryptPacket($epacket, $symmetric_algorithm, $key)
|
||||
{
|
||||
list($cipher,$key_bytes,$key_block_bytes) = self::getCipher($symmetric_algorithm);
|
||||
if (! $cipher)
|
||||
return NULL;
|
||||
|
||||
$cipher->setKey($key);
|
||||
|
||||
if ($epacket instanceof OpenPGP\IntegrityProtectedDataPacket) {
|
||||
$padAmount = $key_block_bytes - (strlen($epacket->data) % $key_block_bytes);
|
||||
$data = substr($cipher->decrypt($epacket->data . str_repeat("\0", $padAmount)), 0, strlen($epacket->data));
|
||||
$prefix = substr($data, 0, $key_block_bytes + 2);
|
||||
$mdc = substr(substr($data, -22, 22), 2);
|
||||
$data = substr($data, $key_block_bytes + 2, -22);
|
||||
|
||||
$mkMDC = hash("sha1", $prefix . $data . "\xD3\x14", true);
|
||||
if ($mkMDC !== $mdc)
|
||||
return false;
|
||||
|
||||
try {
|
||||
$msg = OpenPGP\Message::parse($data);
|
||||
dump(['data'=>$data,'msg'=>$msg]);
|
||||
|
||||
} catch (Exception $ex) {
|
||||
$msg = NULL;
|
||||
}
|
||||
|
||||
if ($msg)
|
||||
return $msg; /* Otherwise keep trying */
|
||||
|
||||
} else {
|
||||
// No MDC mean decrypt with resync
|
||||
$iv = substr($epacket->data, 2, $key_block_bytes);
|
||||
$edata = substr($epacket->data, $key_block_bytes + 2);
|
||||
$padAmount = $key_block_bytes - (strlen($edata) % $key_block_bytes);
|
||||
|
||||
$cipher->setIV($iv);
|
||||
$data = substr($cipher->decrypt($edata . str_repeat("\0", $padAmount)), 0, strlen($edata));
|
||||
|
||||
try {
|
||||
$msg = OpenPGP\Message::parse($data);
|
||||
|
||||
} catch (Exception $ex) {
|
||||
$msg = NULL;
|
||||
}
|
||||
|
||||
if ($msg)
|
||||
return $msg; /* Otherwise keep trying */
|
||||
}
|
||||
|
||||
return NULL; /* Failed */
|
||||
}
|
||||
|
||||
public static function getCipher($algo) {
|
||||
$cipher = NULL;
|
||||
|
||||
switch($algo) {
|
||||
case NULL:
|
||||
case 0:
|
||||
throw new Exception("Data is already unencrypted");
|
||||
|
||||
case 2:
|
||||
$cipher = new Crypt_TripleDES(Crypt_TripleDES::MODE_CFB);
|
||||
$key_bytes = 24;
|
||||
$key_block_bytes = 8;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if (class_exists('OpenSSLWrapper')) {
|
||||
$cipher = new OpenSSLWrapper("CAST5-CFB");
|
||||
} else if(defined('MCRYPT_CAST_128')) {
|
||||
$cipher = new MCryptWrapper(MCRYPT_CAST_128);
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$cipher = new Crypt_Blowfish(Crypt_Blowfish::MODE_CFB);
|
||||
$key_bytes = 16;
|
||||
$key_block_bytes = 8;
|
||||
break;
|
||||
|
||||
case 7:
|
||||
$cipher = new Crypt_AES(Crypt_AES::MODE_CFB);
|
||||
$cipher->setKeyLength(128);
|
||||
break;
|
||||
|
||||
case 8:
|
||||
$cipher = new Crypt_AES(Crypt_AES::MODE_CFB);
|
||||
$cipher->setKeyLength(192);
|
||||
break;
|
||||
|
||||
case 9:
|
||||
$cipher = new Crypt_AES(Crypt_AES::MODE_CFB);
|
||||
$cipher->setKeyLength(256);
|
||||
break;
|
||||
|
||||
case 10:
|
||||
$cipher = new Crypt_Twofish(Crypt_Twofish::MODE_CFB);
|
||||
if (method_exists($cipher, 'setKeyLength')) {
|
||||
$cipher->setKeyLength(256);
|
||||
} else {
|
||||
$cipher = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Unsupported cipher
|
||||
if (! $cipher)
|
||||
return [NULL,NULL,NULL];
|
||||
|
||||
if (! isset($key_bytes))
|
||||
$key_bytes = isset($cipher->key_size)?$cipher->key_size:$cipher->key_length;
|
||||
|
||||
if (! isset($key_block_bytes))
|
||||
$key_block_bytes = $cipher->block_size;
|
||||
|
||||
return [$cipher,$key_bytes,$key_block_bytes];
|
||||
}
|
||||
|
||||
public static function getEncryptedData($m)
|
||||
{
|
||||
foreach ($m as $p) {
|
||||
if ($p instanceof OpenPGP\EncryptedDataPacket)
|
||||
return $p;
|
||||
}
|
||||
|
||||
throw new Exception("Can only decrypt EncryptedDataPacket");
|
||||
}
|
||||
|
||||
public static function checksum($s) {
|
||||
$mkChk = 0;
|
||||
|
||||
for($i = 0; $i < strlen($s); $i++) {
|
||||
$mkChk = ($mkChk + ord($s{$i})) % 65536;
|
||||
}
|
||||
|
||||
return $mkChk;
|
||||
}
|
||||
}
|
18
lib/OpenPgP/EncryptedDataPacket.php
Normal file
18
lib/OpenPgP/EncryptedDataPacket.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Symmetrically Encrypted Data packet (tag 9).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.7
|
||||
*/
|
||||
class EncryptedDataPacket extends Packet
|
||||
{
|
||||
protected $tag = 9;
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
14
lib/OpenPgP/Exceptions/PacketTagException.php
Normal file
14
lib/OpenPgP/Exceptions/PacketTagException.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\Exceptions;
|
||||
|
||||
class PacketTagException extends \Exception
|
||||
{
|
||||
protected $message = 'Packet class missing tag value';
|
||||
|
||||
public function __construct($message, $code = 0, Exception $previous = null)
|
||||
{
|
||||
if ($message)
|
||||
$this->message = $message;
|
||||
}
|
||||
}
|
11
lib/OpenPgP/ExperimentalPacket.php
Normal file
11
lib/OpenPgP/ExperimentalPacket.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* OpenPGP Private or Experimental packet (tags 60..63).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.3
|
||||
*/
|
||||
class ExperimentalPacket extends Packet
|
||||
{
|
||||
protected $tag = 60;
|
||||
}
|
31
lib/OpenPgP/IntegrityProtectedDataPacket.php
Normal file
31
lib/OpenPgP/IntegrityProtectedDataPacket.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Sym. Encrypted Integrity Protected Data packet (tag 18).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.13
|
||||
*/
|
||||
class IntegrityProtectedDataPacket extends EncryptedDataPacket
|
||||
{
|
||||
protected $tag = 18;
|
||||
public $version;
|
||||
|
||||
function __construct($data='',$version=1) {
|
||||
parent::__construct($data);
|
||||
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->version).$this->data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->version = ord($this->read_byte());
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
55
lib/OpenPgP/LiteralDataPacket.php
Normal file
55
lib/OpenPgP/LiteralDataPacket.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Literal Data packet (tag 11).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.9
|
||||
*/
|
||||
class LiteralDataPacket extends Packet
|
||||
{
|
||||
protected $tag = 11;
|
||||
public $format,$filename,$timestamp;
|
||||
|
||||
function __construct($data=NULL, $opt=array())
|
||||
{
|
||||
parent::__construct($data);
|
||||
|
||||
$this->format = isset($opt['format']) ? $opt['format'] : 'b';
|
||||
$this->filename = isset($opt['filename']) ? $opt['filename'] : 'data';
|
||||
$this->timestamp = isset($opt['timestamp']) ? $opt['timestamp'] : time();
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
return $this->format.chr(strlen($this->filename)).$this->filename.pack('N', $this->timestamp).$this->data;
|
||||
}
|
||||
|
||||
function normalize($clearsign=false)
|
||||
{
|
||||
if ($clearsign && ($this->format != 'u' && $this->format != 't')) {
|
||||
$this->format = 'u'; // Clearsign must be text
|
||||
}
|
||||
|
||||
if ($this->format == 'u' || $this->format == 't') { // Normalize line endings
|
||||
$this->data = str_replace("\n", "\r\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $this->data)));
|
||||
}
|
||||
|
||||
if ($clearsign) {
|
||||
// When clearsigning, do not sign over trailing whitespace
|
||||
$this->data = preg_replace('/\s+\r/', "\r", $this->data);
|
||||
}
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->size = $this->length - 1 - 4;
|
||||
$this->format = $this->read_byte();
|
||||
$filename_length = ord($this->read_byte());
|
||||
$this->size -= $filename_length;
|
||||
$this->filename = $this->read_bytes($filename_length);
|
||||
$this->timestamp = $this->read_timestamp();
|
||||
$this->data = $this->read_bytes($this->size);
|
||||
}
|
||||
}
|
14
lib/OpenPgP/MarkerPacket.php
Normal file
14
lib/OpenPgP/MarkerPacket.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Marker packet (tag 10).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.8
|
||||
*/
|
||||
class MarkerPacket extends Packet
|
||||
{
|
||||
// TODO
|
||||
protected $tag = 10;
|
||||
}
|
256
lib/OpenPgP/Message.php
Normal file
256
lib/OpenPgP/Message.php
Normal file
@ -0,0 +1,256 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.1
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-11
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-11.3
|
||||
*/
|
||||
class Message implements \IteratorAggregate,\ArrayAccess
|
||||
{
|
||||
protected $packets;
|
||||
protected $uri = NULL;
|
||||
|
||||
// IteratorAggregate interface
|
||||
|
||||
function getIterator()
|
||||
{
|
||||
return new \ArrayIterator($this->packets);
|
||||
}
|
||||
|
||||
// ArrayAccess interface
|
||||
|
||||
function offsetExists($offset)
|
||||
{
|
||||
return isset($this->packets[$offset]);
|
||||
}
|
||||
|
||||
function offsetGet($offset)
|
||||
{
|
||||
return $this->packets[$offset];
|
||||
}
|
||||
|
||||
function offsetSet($offset,$value)
|
||||
{
|
||||
return is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value;
|
||||
}
|
||||
|
||||
function offsetUnset($offset)
|
||||
{
|
||||
unset($this->packets[$offset]);
|
||||
}
|
||||
|
||||
// Class
|
||||
|
||||
function __construct(array $packets=[])
|
||||
{
|
||||
$this->packets = $packets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.1
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.2
|
||||
*/
|
||||
static function parse($input): self
|
||||
{
|
||||
if (is_resource($input)) {
|
||||
return self::parse_stream($input);
|
||||
}
|
||||
|
||||
if (is_string($input)) {
|
||||
return self::parse_string($input);
|
||||
}
|
||||
}
|
||||
|
||||
static function parse_file($path): self
|
||||
{
|
||||
if (($msg=self::parse(file_get_contents($path)))) {
|
||||
$msg->uri = preg_match('!^[\w\d]+://!',$path) ? $path : 'file://'.realpath($path);
|
||||
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
|
||||
static function parse_stream($input): self
|
||||
{
|
||||
return self::parse_string(stream_get_contents($input));
|
||||
}
|
||||
|
||||
static function parse_string($input): self
|
||||
{
|
||||
$msg = new self;
|
||||
|
||||
while (($length=strlen($input)) > 0) {
|
||||
if (($packet=Packet::parse($input))) {
|
||||
$msg[] = $packet;
|
||||
}
|
||||
|
||||
// is parsing stuck?
|
||||
if ($length == strlen($input)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract signed objects from a well-formatted message
|
||||
*
|
||||
* Recurses into CompressedDataPacket
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-11
|
||||
*/
|
||||
public function signatures(): array
|
||||
{
|
||||
$msg = $this;
|
||||
|
||||
$key = NULL;
|
||||
$userid = NULL;
|
||||
$subkey = NULL;
|
||||
$sigs = [];
|
||||
$final_sigs = [];
|
||||
|
||||
while ($msg[0] instanceof CompressedDataPacket)
|
||||
$msg = $msg[0]->data;
|
||||
|
||||
foreach ($msg as $idx => $p) {
|
||||
if ($p instanceof LiteralDataPacket) {
|
||||
return [
|
||||
[
|
||||
$p,
|
||||
array_values(array_filter($msg->packets,function($p)
|
||||
{
|
||||
return $p instanceof SignaturePacket;
|
||||
}))
|
||||
]
|
||||
];
|
||||
|
||||
} elseif ($p instanceof PublicSubkeyPacket || $p instanceof SecretSubkeyPacket) {
|
||||
if ($userid) {
|
||||
array_push($final_sigs,[$key,$userid,$sigs]);
|
||||
$userid = NULL;
|
||||
|
||||
} elseif ($subkey) {
|
||||
array_push($final_sigs,[$key,$subkey,$sigs]);
|
||||
$key = NULL;
|
||||
}
|
||||
|
||||
$sigs = [];
|
||||
$subkey = $p;
|
||||
|
||||
} elseif ($p instanceof PublicKeyPacket) {
|
||||
if ($userid) {
|
||||
array_push($final_sigs,[$key,$userid,$sigs]);
|
||||
$userid = NULL;
|
||||
|
||||
} elseif ($subkey) {
|
||||
array_push($final_sigs,[$key,$subkey,$sigs]);
|
||||
$subkey = NULL;
|
||||
|
||||
} elseif ($key) {
|
||||
array_push($final_sigs,[$key,$sigs]);
|
||||
$key = NULL;
|
||||
}
|
||||
|
||||
$sigs = [];
|
||||
$key = $p;
|
||||
|
||||
} elseif ($p instanceof UserIDPacket) {
|
||||
if ($userid) {
|
||||
array_push($final_sigs,[$key,$userid,$sigs]);
|
||||
$userid = NULL;
|
||||
|
||||
} elseif ($key) {
|
||||
array_push($final_sigs,[$key,$sigs]);
|
||||
}
|
||||
|
||||
$sigs = [];
|
||||
$userid = $p;
|
||||
|
||||
} elseif ($p instanceof SignaturePacket) {
|
||||
$sigs[] = $p;
|
||||
}
|
||||
}
|
||||
|
||||
if ($userid) {
|
||||
array_push($final_sigs,[$key,$userid,$sigs]);
|
||||
|
||||
} elseif ($subkey) {
|
||||
array_push($final_sigs,[$key,$subkey,$sigs]);
|
||||
|
||||
} elseif ($key) {
|
||||
array_push($final_sigs,[$key,$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
|
||||
*
|
||||
* $verifiers is an array of callbacks formatted like array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: raw message and signature packet
|
||||
*/
|
||||
function verified_signatures($verifiers): array
|
||||
{
|
||||
$signed = $this->signatures();
|
||||
$vsigned = [];
|
||||
|
||||
foreach ($signed as $sign) {
|
||||
$signatures = array_pop($sign);
|
||||
$vsigs = [];
|
||||
|
||||
foreach ($signatures as $sig) {
|
||||
$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 LiteralDataPacket) {
|
||||
$sign[0]->normalize();
|
||||
$raw = $sign[0]->data;
|
||||
|
||||
} elseif (isset($sign[1]) && $sign[1] instanceof UserIDPacket) {
|
||||
$raw = implode(
|
||||
'',
|
||||
array_merge(
|
||||
$sign[0]->fingerprint_material(),
|
||||
array(chr(0xB4),pack('N',strlen($sign[1]->body())),$sign[1]->body())
|
||||
));
|
||||
|
||||
} elseif (isset($sign[1]) && ($sign[1] instanceof PublicSubkeyPacket || $sign[1] instanceof SecretSubkeyPacket)) {
|
||||
$raw = implode('',array_merge($sign[0]->fingerprint_material(),$sign[1]->fingerprint_material()));
|
||||
|
||||
} elseif ($sign[0] instanceof PublicKeyPacket) {
|
||||
$raw = implode('',$sign[0]->fingerprint_material());
|
||||
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return call_user_func($verifier,$raw.$sig->trailer,$sig);
|
||||
}
|
||||
}
|
32
lib/OpenPgP/ModificationDetectionCodePacket.php
Normal file
32
lib/OpenPgP/ModificationDetectionCodePacket.php
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Modification Detection Code packet (tag 19).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.14
|
||||
*/
|
||||
class ModificationDetectionCodePacket extends Packet
|
||||
{
|
||||
protected $tag = 19;
|
||||
|
||||
function header_and_body(): array
|
||||
{
|
||||
// Get body first, we will need it's length
|
||||
$body = $this->body();
|
||||
|
||||
if (strlen($body) != 20)
|
||||
throw new Exception("Bad ModificationDetectionCodePacket");
|
||||
|
||||
return ['header'=>"\xD3\x14",'body'=>$body];
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->input;
|
||||
|
||||
if (strlen($this->input) != 20)
|
||||
throw new Exception("Bad ModificationDetectionCodePacket");
|
||||
}
|
||||
}
|
39
lib/OpenPgP/OnePassSignaturePacket.php
Normal file
39
lib/OpenPgP/OnePassSignaturePacket.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP One-Pass Signature packet (tag 4).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.4
|
||||
*/
|
||||
class OnePassSignaturePacket extends Packet
|
||||
{
|
||||
protected $tag = 4;
|
||||
public $version, $signature_type, $hash_algorithm, $key_algorithm, $key_id, $nested;
|
||||
|
||||
function body()
|
||||
{
|
||||
$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((int)$this->nested);
|
||||
return $body;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->version = ord($this->read_byte());
|
||||
$this->signature_type = ord($this->read_byte());
|
||||
$this->hash_algorithm = ord($this->read_byte());
|
||||
$this->key_algorithm = ord($this->read_byte());
|
||||
|
||||
// Store KeyID in Hex
|
||||
for ($i=0;$i<8;$i++) {
|
||||
$this->key_id .= sprintf('%02X',ord($this->read_byte()));
|
||||
}
|
||||
|
||||
$this->nested = ord($this->read_byte());
|
||||
}
|
||||
}
|
271
lib/OpenPgP/Packet.php
Normal file
271
lib/OpenPgP/Packet.php
Normal file
@ -0,0 +1,271 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
210
lib/OpenPgP/PublicKeyPacket.php
Normal file
210
lib/OpenPgP/PublicKeyPacket.php
Normal file
@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
use Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Public-Key packet (tag 6).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.1.1
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-11.1
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-12
|
||||
*/
|
||||
class PublicKeyPacket extends Packet
|
||||
{
|
||||
protected $tag = 6;
|
||||
public $version, $timestamp, $algorithm;
|
||||
public $key, $key_id, $fingerprint;
|
||||
public $v3_days_of_validity;
|
||||
|
||||
static $key_fields = array(
|
||||
1 => array('n', 'e'), // RSA
|
||||
16 => array('p', 'g', 'y'), // ELG-E
|
||||
17 => array('p', 'q', 'g', 'y'), // DSA
|
||||
);
|
||||
|
||||
static $algorithms = [
|
||||
1 => 'RSA',
|
||||
2 => 'RSA',
|
||||
3 => 'RSA',
|
||||
16 => 'ELGAMAL',
|
||||
17 => 'DSA',
|
||||
18 => 'ECC',
|
||||
19 => 'ECDSA',
|
||||
21 => 'DH'
|
||||
];
|
||||
|
||||
function __construct($key=[],$algorithm='RSA',$timestamp=NULL,$version=4)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if ($key instanceof PublicKeyPacket) {
|
||||
$this->algorithm = $key->algorithm;
|
||||
$this->key = array();
|
||||
|
||||
// Restrict to only the fields we need
|
||||
foreach (self::$key_fields[$this->algorithm] as $field) {
|
||||
$this->key[$field] = $key->key[$field];
|
||||
}
|
||||
|
||||
$this->key_id = $key->key_id;
|
||||
$this->fingerprint = $key->fingerprint;
|
||||
$this->timestamp = $key->timestamp;
|
||||
$this->version = $key->version;
|
||||
$this->v3_days_of_validity = $key->v3_days_of_validity;
|
||||
|
||||
} else {
|
||||
$this->key = $key;
|
||||
if (is_string($this->algorithm = $algorithm)) {
|
||||
$this->algorithm = array_search($this->algorithm,self::$algorithms);
|
||||
}
|
||||
|
||||
$this->timestamp = $timestamp ? $timestamp : time();
|
||||
$this->version = $version;
|
||||
|
||||
if (count($this->key) > 0) {
|
||||
$this->key_id = substr($this->fingerprint(),-8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
switch ($this->version) {
|
||||
case 2:
|
||||
case 3:
|
||||
return implode('', array_merge(array(
|
||||
chr($this->version) . pack('N', $this->timestamp) .
|
||||
pack('n', $this->v3_days_of_validity) . chr($this->algorithm)
|
||||
), $this->fingerprint_material())
|
||||
);
|
||||
|
||||
case 4:
|
||||
return implode('', array_slice($this->fingerprint_material(), 2));
|
||||
}
|
||||
}
|
||||
|
||||
// Find expiry time of this key based on the self signatures in a message
|
||||
function expires($message)
|
||||
{
|
||||
foreach ($this->self_signatures($message) as $p) {
|
||||
foreach (array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) {
|
||||
if ($s instanceof SignaturePacket\KeyExpirationTimePacket) {
|
||||
return $this->timestamp + $s->data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Never expires
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-12.2
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-3.3
|
||||
*/
|
||||
function fingerprint()
|
||||
{
|
||||
switch ($this->version) {
|
||||
case 2:
|
||||
case 3:
|
||||
return $this->fingerprint = strtoupper(md5(implode('', $this->fingerprint_material())));
|
||||
|
||||
case 4:
|
||||
return $this->fingerprint = strtoupper(sha1(implode('', $this->fingerprint_material())));
|
||||
}
|
||||
}
|
||||
|
||||
function fingerprint_material()
|
||||
{
|
||||
switch ($this->version) {
|
||||
case 3:
|
||||
$material = array();
|
||||
foreach (self::$key_fields[$this->algorithm] as $i) {
|
||||
$material[] = pack('n', OpenPGP::bitlength($this->key[$i]));
|
||||
$material[] = $this->key[$i];
|
||||
}
|
||||
return $material;
|
||||
|
||||
case 4:
|
||||
$head = array(
|
||||
chr(0x99), NULL,
|
||||
chr($this->version), pack('N', $this->timestamp),
|
||||
chr($this->algorithm),
|
||||
);
|
||||
$material = [];
|
||||
foreach (self::$key_fields[$this->algorithm] as $i) {
|
||||
$material[] = pack('n', OpenPGP::bitlength($this->key[$i]));
|
||||
$material[] = $this->key[$i];
|
||||
}
|
||||
|
||||
$material = implode('', $material);
|
||||
$head[1] = pack('n', 6 + strlen($material));
|
||||
$head[] = $material;
|
||||
|
||||
return $head;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
|
||||
*/
|
||||
function read()
|
||||
{
|
||||
switch ($this->version = ord($this->read_byte())) {
|
||||
case 3:
|
||||
$this->timestamp = $this->read_timestamp();
|
||||
$this->v3_days_of_validity = $this->read_unpacked(2, 'n');
|
||||
$this->algorithm = ord($this->read_byte());
|
||||
$this->read_key_material();
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$this->timestamp = $this->read_timestamp();
|
||||
$this->algorithm = ord($this->read_byte());
|
||||
$this->read_key_material();
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
|
||||
*/
|
||||
function read_key_material()
|
||||
{
|
||||
foreach (self::$key_fields[$this->algorithm] as $field) {
|
||||
$this->key[$field] = $this->read_mpi();
|
||||
}
|
||||
|
||||
$this->key_id = substr($this->fingerprint(), -8);
|
||||
}
|
||||
|
||||
// Find self signatures in a message, these often contain metadata about the key
|
||||
function self_signatures($message)
|
||||
{
|
||||
$sigs = [];
|
||||
$keyid16 = strtoupper(substr($this->fingerprint,-16));
|
||||
|
||||
foreach ($message as $p) {
|
||||
if ($p instanceof OpenPGP\SignaturePacket) {
|
||||
if (strtoupper($p->issuer()) == $keyid16) {
|
||||
$sigs[] = $p;
|
||||
|
||||
} else {
|
||||
foreach (array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) {
|
||||
if ($s instanceof SignaturePacket\EmbeddedSignaturePacket && strtoupper($s->issuer()) == $keyid16) {
|
||||
$sigs[] = $p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After we've seen a self sig, the next non-sig stop all self-sigs
|
||||
} elseif (count($sigs))
|
||||
break;
|
||||
}
|
||||
|
||||
return $sigs;
|
||||
}
|
||||
}
|
17
lib/OpenPgP/PublicSubkeyPacket.php
Normal file
17
lib/OpenPgP/PublicSubkeyPacket.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Public-Subkey packet (tag 14).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.1.2
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-11.1
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-12
|
||||
*/
|
||||
class PublicSubkeyPacket extends PublicKeyPacket
|
||||
{
|
||||
// TODO
|
||||
protected $tag = 14;
|
||||
}
|
117
lib/OpenPgP/S2K.php
Normal file
117
lib/OpenPgP/S2K.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
use Leenooks\OpenPGP;
|
||||
use Leenooks\OpenPGP_SignaturePacket;
|
||||
|
||||
class S2K
|
||||
{
|
||||
public $type, $hash_algorithm, $salt, $count;
|
||||
|
||||
function __construct($salt='BADSALT',$hash_algorithm=10,$count=65536,$type=3)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->hash_algorithm = $hash_algorithm;
|
||||
$this->salt = $salt;
|
||||
$this->count = $count;
|
||||
}
|
||||
|
||||
static function parse(&$input)
|
||||
{
|
||||
$s2k = new self;
|
||||
|
||||
switch($s2k->type = ord($input{0})) {
|
||||
case 0:
|
||||
$s2k->hash_algorithm = ord($input{1});
|
||||
$input = substr($input,2);
|
||||
break;
|
||||
|
||||
case 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->salt = substr($input,2,8);
|
||||
$s2k->count = OpenPGP::decode_s2k_count(ord($input{10}));
|
||||
$input = substr($input,11);
|
||||
break;
|
||||
}
|
||||
|
||||
return $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);
|
||||
}
|
||||
|
||||
function sized_hash($s,$size)
|
||||
{
|
||||
$hash = $this->raw_hash($s);
|
||||
|
||||
while(strlen($hash) < $size) {
|
||||
$s = "\0".$s;
|
||||
$hash .= $this->raw_hash($s);
|
||||
}
|
||||
|
||||
return substr($hash,0,$size);
|
||||
}
|
||||
|
||||
function iterate($s)
|
||||
{
|
||||
if (strlen($s) >= $this->count)
|
||||
return $s;
|
||||
|
||||
$s = str_repeat($s,ceil($this->count/strlen($s)));
|
||||
|
||||
return substr($s,0,$this->count);
|
||||
}
|
||||
|
||||
function make_key($pass,$size)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
80
lib/OpenPgP/SecretKeyPacket.php
Normal file
80
lib/OpenPgP/SecretKeyPacket.php
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Secret-Key packet (tag 5).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.1.3
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.3
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-11.2
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-12
|
||||
*/
|
||||
class SecretKeyPacket extends PublicKeyPacket
|
||||
{
|
||||
protected $tag = 5;
|
||||
public $s2k_useage, $s2k, $symmetric_algorithm, $private_hash, $encrypted_data;
|
||||
|
||||
static $secret_key_fields = array(
|
||||
1 => array('d', 'p', 'q', 'u'), // RSA
|
||||
2 => array('d', 'p', 'q', 'u'), // RSA-E
|
||||
3 => array('d', 'p', 'q', 'u'), // RSA-S
|
||||
16 => array('x'), // ELG-E
|
||||
17 => array('x'), // DSA
|
||||
);
|
||||
|
||||
function body()
|
||||
{
|
||||
$bytes = parent::body() . chr($this->s2k_useage);
|
||||
$secret_material = NULL;
|
||||
if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
|
||||
$bytes .= chr($this->symmetric_algorithm);
|
||||
$bytes .= $this->s2k->to_bytes();
|
||||
}
|
||||
if($this->s2k_useage > 0) {
|
||||
$bytes .= $this->encrypted_data;
|
||||
} else {
|
||||
$secret_material = '';
|
||||
foreach(self::$secret_key_fields[$this->algorithm] as $f) {
|
||||
$f = $this->key[$f];
|
||||
$secret_material .= pack('n', \Leenooks\OpenPGP::bitlength($f));
|
||||
$secret_material .= $f;
|
||||
}
|
||||
$bytes .= $secret_material;
|
||||
|
||||
// 2-octet checksum
|
||||
$chk = 0;
|
||||
for($i = 0; $i < strlen($secret_material); $i++) {
|
||||
$chk = ($chk + ord($secret_material[$i])) % 65536;
|
||||
}
|
||||
$bytes .= pack('n', $chk);
|
||||
}
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
function key_from_input()
|
||||
{
|
||||
foreach(self::$secret_key_fields[$this->algorithm] as $field) {
|
||||
$this->key[$field] = $this->read_mpi();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
} else if($this->s2k_useage > 0) {
|
||||
$this->symmetric_algorithm = $this->s2k_useage;
|
||||
}
|
||||
if($this->s2k_useage > 0) {
|
||||
$this->encrypted_data = $this->input; // Rest of input is MPIs and checksum (encrypted)
|
||||
} else {
|
||||
$this->key_from_input();
|
||||
$this->private_hash = $this->read_bytes(2); // TODO: Validate checksum?
|
||||
}
|
||||
}
|
||||
}
|
17
lib/OpenPgP/SecretSubkeyPacket.php
Normal file
17
lib/OpenPgP/SecretSubkeyPacket.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Secret-Subkey packet (tag 7).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.1.4
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.3
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-11.2
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-12
|
||||
*/
|
||||
class SecretSubkeyPacket extends SecretKeyPacket
|
||||
{
|
||||
// TODO
|
||||
protected $tag = 7;
|
||||
}
|
351
lib/OpenPgP/SignaturePacket.php
Normal file
351
lib/OpenPgP/SignaturePacket.php
Normal file
@ -0,0 +1,351 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
use Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Signature packet (tag 2).
|
||||
* Be sure to NULL the trailer if you update a signature packet!
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2
|
||||
*/
|
||||
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;
|
||||
|
||||
// This is the literal bytes that get tacked on the end of the message when verifying the signature
|
||||
public $trailer;
|
||||
|
||||
static $hash_algorithms = [
|
||||
1 => 'MD5',
|
||||
2 => 'SHA1',
|
||||
3 => 'RIPEMD160',
|
||||
8 => 'SHA256',
|
||||
9 => 'SHA384',
|
||||
10 => 'SHA512',
|
||||
11 => 'SHA224'
|
||||
];
|
||||
|
||||
static $subpacket_types = [
|
||||
//0 => 'Reserved',
|
||||
//1 => 'Reserved',
|
||||
2 => 'SignatureCreationTime',
|
||||
3 => 'SignatureExpirationTime',
|
||||
4 => 'ExportableCertification',
|
||||
5 => 'TrustSignature',
|
||||
6 => 'RegularExpression',
|
||||
7 => 'Revocable',
|
||||
//8 => 'Reserved',
|
||||
9 => 'KeyExpirationTime',
|
||||
//10 => 'Placeholder for backward compatibility',
|
||||
11 => 'PreferredSymmetricAlgorithms',
|
||||
12 => 'RevocationKey',
|
||||
//13 => 'Reserved',
|
||||
//14 => 'Reserved',
|
||||
//15 => 'Reserved',
|
||||
16 => 'Issuer',
|
||||
//17 => 'Reserved',
|
||||
//18 => 'Reserved',
|
||||
//19 => 'Reserved',
|
||||
20 => 'NotationData',
|
||||
21 => 'PreferredHashAlgorithms',
|
||||
22 => 'PreferredCompressionAlgorithms',
|
||||
23 => 'KeyServerPreferences',
|
||||
24 => 'PreferredKeyServer',
|
||||
25 => 'PrimaryUserID',
|
||||
26 => 'PolicyURI',
|
||||
27 => 'KeyFlags',
|
||||
28 => 'SignersUserID',
|
||||
29 => 'ReasonforRevocation',
|
||||
30 => 'Features',
|
||||
31 => 'SignatureTarget',
|
||||
32 => 'EmbeddedSignature',
|
||||
];
|
||||
|
||||
function __construct($data=NULL,$key_algorithm=NULL,$hash_algorithm=NULL)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
// Default to version 4 sigs
|
||||
$this->version = 4;
|
||||
|
||||
if (is_string($this->hash_algorithm = $hash_algorithm)) {
|
||||
$this->hash_algorithm = array_search($this->hash_algorithm, self::$hash_algorithms);
|
||||
}
|
||||
|
||||
if (is_string($this->key_algorithm = $key_algorithm)) {
|
||||
$this->key_algorithm = array_search($this->key_algorithm,PublicKeyPacket::$algorithms);
|
||||
}
|
||||
|
||||
// If we have any data, set up the creation time
|
||||
if ($data) {
|
||||
$this->hashed_subpackets = [new SignaturePacket\SignatureCreationTimePacket(time())];
|
||||
}
|
||||
|
||||
if ($data instanceof LiteralDataPacket) {
|
||||
$this->signature_type = ($data->format == 'b') ? 0x00 : 0x01;
|
||||
$data->normalize();
|
||||
$data = $data->data;
|
||||
|
||||
} elseif ($data instanceof Message && $data[0] instanceof PublicKeyPacket) {
|
||||
// $data is a message with PublicKey first, UserID second
|
||||
$key = implode('',$data[0]->fingerprint_material());
|
||||
$user_id = $data[1]->body();
|
||||
$data = $key.chr(0xB4).pack('N',strlen($user_id)).$user_id;
|
||||
}
|
||||
|
||||
// Store to-be-signed data in here until the signing happens
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
switch($this->version) {
|
||||
case 2:
|
||||
case 3:
|
||||
$body = chr($this->version).chr(5).chr($this->signature_type);
|
||||
|
||||
foreach ((array)$this->unhashed_subpackets as $p) {
|
||||
if ($p instanceof SignaturePacket\SignatureCreationTimePacket) {
|
||||
$body .= pack('N',$p->data);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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}));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$body .= chr($this->key_algorithm);
|
||||
$body .= chr($this->hash_algorithm);
|
||||
$body .= pack('n',$this->hash_head);
|
||||
|
||||
foreach ($this->data as $mpi) {
|
||||
$body .= pack('n',OpenPGP::bitlength($mpi)).$mpi;
|
||||
}
|
||||
|
||||
return $body;
|
||||
|
||||
case 4:
|
||||
if (!$this->trailer)
|
||||
$this->trailer = $this->calculate_trailer();
|
||||
|
||||
$body = substr($this->trailer,0,-6);
|
||||
|
||||
$unhashed_subpackets = '';
|
||||
foreach((array)$this->unhashed_subpackets as $p) {
|
||||
$unhashed_subpackets .= $p->to_bytes();
|
||||
}
|
||||
|
||||
$body .= pack('n',strlen($unhashed_subpackets)).$unhashed_subpackets;
|
||||
|
||||
$body .= pack('n',$this->hash_head);
|
||||
|
||||
foreach ((array)$this->data as $mpi) {
|
||||
$body .= pack('n',OpenPGP::bitlength($mpi)).$mpi;
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
|
||||
function body_start()
|
||||
{
|
||||
$body = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm);
|
||||
|
||||
$hashed_subpackets = '';
|
||||
foreach((array)$this->hashed_subpackets as $p) {
|
||||
$hashed_subpackets .= $p->to_bytes();
|
||||
}
|
||||
|
||||
$body .= pack('n',strlen($hashed_subpackets)).$hashed_subpackets;
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
function calculate_trailer() {
|
||||
// The trailer is just the top of the body plus some crap
|
||||
$body = $this->body_start();
|
||||
|
||||
return $body.chr(4).chr(0xff).pack('N',strlen($body));
|
||||
}
|
||||
|
||||
static function class_for($tag)
|
||||
{
|
||||
return (isset(self::$subpacket_types[$tag]) AND class_exists($class='Leenooks\OpenPGP\SignaturePacket\\'.self::$subpacket_types[$tag].'Packet'))
|
||||
? $class
|
||||
: 'Leenooks\OpenPGP\SignaturePacket\Subpacket';
|
||||
}
|
||||
|
||||
static function get_subpacket(&$input)
|
||||
{
|
||||
$len = ord($input[0]);
|
||||
$length_of_length = 1;
|
||||
|
||||
// if($len < 192) One octet length, no furthur processing
|
||||
if ($len > 190 && $len < 255) { // Two octet length
|
||||
$length_of_length = 2;
|
||||
$len = (($len - 192) << 8) + ord($input[1]) + 192;
|
||||
}
|
||||
|
||||
// Five octet length
|
||||
if ($len == 255) {
|
||||
$length_of_length = 5;
|
||||
$unpacked = unpack('N', substr($input, 1, 4));
|
||||
$len = reset($unpacked);
|
||||
}
|
||||
|
||||
$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;
|
||||
//$packet->tag = $tag; // @todo Tag should already be set.
|
||||
$packet->input = substr($input, 1, $len-1);
|
||||
$packet->length = $len-1;
|
||||
$packet->read();
|
||||
unset($packet->input);
|
||||
unset($packet->length);
|
||||
}
|
||||
|
||||
// Chop off the data from this packet
|
||||
$input = substr($input,$len);
|
||||
|
||||
return $packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.1
|
||||
*/
|
||||
static function get_subpackets($input)
|
||||
{
|
||||
$subpackets = array();
|
||||
|
||||
while(($length = strlen($input)) > 0) {
|
||||
$subpackets[] = self::get_subpacket($input);
|
||||
|
||||
// Parsing stuck?
|
||||
if ($length == strlen($input)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $subpackets;
|
||||
}
|
||||
|
||||
function hash_algorithm_name()
|
||||
{
|
||||
return self::$hash_algorithms[$this->hash_algorithm];
|
||||
}
|
||||
|
||||
function issuer()
|
||||
{
|
||||
foreach ($this->hashed_subpackets as $p) {
|
||||
if ($p instanceof SignaturePacket\IssuerPacket)
|
||||
return $p->data;
|
||||
}
|
||||
|
||||
foreach($this->unhashed_subpackets as $p) {
|
||||
if ($p instanceof SignaturePacket\IssuerPacket)
|
||||
return $p->data;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
function key_algorithm_name()
|
||||
{
|
||||
return PublicKeyPacket::$algorithms[$this->key_algorithm];
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
switch($this->version = ord($this->read_byte())) {
|
||||
case 2:
|
||||
case 3:
|
||||
if (ord($this->read_byte()) != 5) {
|
||||
throw new Exception("Invalid version 2 or 3 SignaturePacket");
|
||||
}
|
||||
|
||||
$this->signature_type = ord($this->read_byte());
|
||||
$creation_time = $this->read_timestamp();
|
||||
$keyid = $this->read_bytes(8);
|
||||
$keyidHex = '';
|
||||
|
||||
// Store KeyID in Hex
|
||||
for ($i=0;$i<strlen($keyid);$i++) {
|
||||
$keyidHex .= sprintf('%02X',ord($keyid{$i}));
|
||||
}
|
||||
|
||||
$this->hashed_subpackets = [];
|
||||
$this->unhashed_subpackets = [
|
||||
new SignaturePacket\SignatureCreationTimePacket($creation_time),
|
||||
new SignaturePacket\IssuerPacket($keyidHex)
|
||||
];
|
||||
|
||||
$this->key_algorithm = ord($this->read_byte());
|
||||
$this->hash_algorithm = ord($this->read_byte());
|
||||
$this->hash_head = $this->read_unpacked(2, 'n');
|
||||
$this->data = array();
|
||||
|
||||
while (strlen($this->input)>0) {
|
||||
$this->data[] = $this->read_mpi();
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 4:
|
||||
$this->signature_type = ord($this->read_byte());
|
||||
$this->key_algorithm = ord($this->read_byte());
|
||||
$this->hash_algorithm = ord($this->read_byte());
|
||||
$this->trailer = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm);
|
||||
|
||||
$hashed_size = $this->read_unpacked(2, 'n');
|
||||
$hashed_subpackets = $this->read_bytes($hashed_size);
|
||||
$this->trailer .= pack('n', $hashed_size).$hashed_subpackets;
|
||||
$this->hashed_subpackets = self::get_subpackets($hashed_subpackets);
|
||||
|
||||
$this->trailer .= chr(4).chr(0xff).pack('N', 6 + $hashed_size);
|
||||
|
||||
$unhashed_size = $this->read_unpacked(2, 'n');
|
||||
$this->unhashed_subpackets = self::get_subpackets($this->read_bytes($unhashed_size));
|
||||
|
||||
$this->hash_head = $this->read_unpacked(2, 'n');
|
||||
|
||||
$this->data = array();
|
||||
|
||||
while(strlen($this->input) > 0) {
|
||||
$this->data[] = $this->read_mpi();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* $this->data must be set to the data to sign (done by constructor)
|
||||
* $signers in the same format as $verifiers for Message.
|
||||
*/
|
||||
public function sign_data($signers)
|
||||
{
|
||||
$this->trailer = $this->calculate_trailer();
|
||||
$signer = $signers[$this->key_algorithm_name()][$this->hash_algorithm_name()];
|
||||
$this->data = call_user_func($signer,$this->data.$this->trailer);
|
||||
$unpacked = unpack('n', substr(implode('',$this->data),0,2));
|
||||
$this->hash_head = reset($unpacked);
|
||||
}
|
||||
}
|
22
lib/OpenPgP/SignaturePacket/EmbeddedSignaturePacket.php
Normal file
22
lib/OpenPgP/SignaturePacket/EmbeddedSignaturePacket.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
use Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.26
|
||||
*/
|
||||
class EmbeddedSignaturePacket extends SignaturePacket
|
||||
{
|
||||
protected $tag = 32;
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.11
|
||||
*/
|
||||
class ExportableCertificationPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 4;
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->data ? 1 : 0);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = (ord($this->input) != 0);
|
||||
}
|
||||
}
|
12
lib/OpenPgP/SignaturePacket/FeaturesPacket.php
Normal file
12
lib/OpenPgP/SignaturePacket/FeaturesPacket.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.24
|
||||
* @note Identical functionality to parent
|
||||
*/
|
||||
class FeaturesPacket extends KeyFlagsPacket
|
||||
{
|
||||
protected $tag = 30;
|
||||
}
|
27
lib/OpenPgP/SignaturePacket/IssuerPacket.php
Normal file
27
lib/OpenPgP/SignaturePacket/IssuerPacket.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.5
|
||||
*/
|
||||
class IssuerPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 16;
|
||||
|
||||
function body()
|
||||
{
|
||||
$bytes = '';
|
||||
for($i = 0; $i < strlen($this->data); $i += 2) {
|
||||
$bytes .= chr(hexdec($this->data{$i}.$this->data{$i+1}));
|
||||
}
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
for($i = 0; $i < 8; $i++) { // Store KeyID in Hex
|
||||
$this->data .= sprintf('%02X',ord($this->read_byte()));
|
||||
}
|
||||
}
|
||||
}
|
21
lib/OpenPgP/SignaturePacket/KeyExpirationTimePacket.php
Normal file
21
lib/OpenPgP/SignaturePacket/KeyExpirationTimePacket.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.6
|
||||
*/
|
||||
class KeyExpirationTimePacket extends Subpacket
|
||||
{
|
||||
protected $tag = 9;
|
||||
|
||||
function body()
|
||||
{
|
||||
return pack('N', $this->data);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->read_timestamp();
|
||||
}
|
||||
}
|
37
lib/OpenPgP/SignaturePacket/KeyFlagsPacket.php
Normal file
37
lib/OpenPgP/SignaturePacket/KeyFlagsPacket.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.21
|
||||
*/
|
||||
class KeyFlagsPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 27;
|
||||
|
||||
function __construct($flags=[])
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->flags = $flags;
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
$bytes = '';
|
||||
|
||||
foreach($this->flags as $f) {
|
||||
$bytes .= chr($f);
|
||||
}
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->flags = array();
|
||||
|
||||
while($this->input) {
|
||||
$this->flags[] = ord($this->read_byte());
|
||||
}
|
||||
}
|
||||
}
|
23
lib/OpenPgP/SignaturePacket/KeyServerPreferencesPacket.php
Normal file
23
lib/OpenPgP/SignaturePacket/KeyServerPreferencesPacket.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.17
|
||||
*/
|
||||
class KeyServerPreferencesPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 23;
|
||||
public $no_modify;
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->no_modify ? 0x80 : 0x00);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$flags = ord($this->input);
|
||||
$this->no_modify = $flags & 0x80 == 0x80;
|
||||
}
|
||||
}
|
29
lib/OpenPgP/SignaturePacket/NotationDataPacket.php
Normal file
29
lib/OpenPgP/SignaturePacket/NotationDataPacket.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.16
|
||||
*/
|
||||
class NotationDataPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 20;
|
||||
public $human_readable, $name;
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->human_readable ? 0x80 : 0x00) . "\0\0\0" .
|
||||
pack('n', strlen($this->name)) . pack('n', strlen($this->data)) .
|
||||
$this->name . $this->data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$flags = $this->read_bytes(4);
|
||||
$namelen = $this->read_unpacked(2, 'n');
|
||||
$datalen = $this->read_unpacked(2, 'n');
|
||||
$this->human_readable = ord($flags[0]) & 0x80 == 0x80;
|
||||
$this->name = $this->read_bytes($namelen);
|
||||
$this->data = $this->read_bytes($datalen);
|
||||
}
|
||||
}
|
21
lib/OpenPgP/SignaturePacket/PolicyURIPacket.php
Normal file
21
lib/OpenPgP/SignaturePacket/PolicyURIPacket.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.20
|
||||
*/
|
||||
class PolicyURIPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 26;
|
||||
|
||||
function body()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.9
|
||||
*/
|
||||
class PreferredCompressionAlgorithmsPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 22;
|
||||
|
||||
function body()
|
||||
{
|
||||
$bytes = '';
|
||||
|
||||
foreach($this->data as $algo) {
|
||||
$bytes .= chr($algo);
|
||||
}
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = array();
|
||||
|
||||
while(strlen($this->input) > 0) {
|
||||
$this->data[] = ord($this->read_byte());
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.8
|
||||
*/
|
||||
class PreferredHashAlgorithmsPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 21;
|
||||
|
||||
function body()
|
||||
{
|
||||
$bytes = '';
|
||||
foreach($this->data as $algo) {
|
||||
$bytes .= chr($algo);
|
||||
}
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = array();
|
||||
|
||||
while(strlen($this->input) > 0) {
|
||||
$this->data[] = ord($this->read_byte());
|
||||
}
|
||||
}
|
||||
}
|
21
lib/OpenPgP/SignaturePacket/PreferredKeyServerPacket.php
Normal file
21
lib/OpenPgP/SignaturePacket/PreferredKeyServerPacket.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.18
|
||||
*/
|
||||
class PreferredKeyServerPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 24;
|
||||
|
||||
function body()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.7
|
||||
*/
|
||||
class PreferredSymmetricAlgorithmsPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 11;
|
||||
|
||||
function body()
|
||||
{
|
||||
$bytes = '';
|
||||
|
||||
foreach($this->data as $algo) {
|
||||
$bytes .= chr($algo);
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = array();
|
||||
|
||||
while(strlen($this->input) > 0) {
|
||||
$this->data[] = ord($this->read_byte());
|
||||
}
|
||||
}
|
||||
}
|
21
lib/OpenPgP/SignaturePacket/PrimaryUserIDPacket.php
Normal file
21
lib/OpenPgP/SignaturePacket/PrimaryUserIDPacket.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.19
|
||||
*/
|
||||
class PrimaryUserIDPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 25;
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->data ? 1 : 0);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = (ord($this->input) != 0);
|
||||
}
|
||||
}
|
23
lib/OpenPgP/SignaturePacket/ReasonforRevocationPacket.php
Normal file
23
lib/OpenPgP/SignaturePacket/ReasonforRevocationPacket.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.23
|
||||
*/
|
||||
class ReasonforRevocationPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 29;
|
||||
public $code;
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->code) . $this->data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->code = ord($this->read_byte());
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
21
lib/OpenPgP/SignaturePacket/RegularExpressionPacket.php
Normal file
21
lib/OpenPgP/SignaturePacket/RegularExpressionPacket.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.14
|
||||
*/
|
||||
class RegularExpressionPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 6;
|
||||
|
||||
function body()
|
||||
{
|
||||
return $this->data . chr(0);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = substr($this->input, 0, -1);
|
||||
}
|
||||
}
|
21
lib/OpenPgP/SignaturePacket/RevocablePacket.php
Normal file
21
lib/OpenPgP/SignaturePacket/RevocablePacket.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.15
|
||||
*/
|
||||
class RevocablePacket extends Subpacket
|
||||
{
|
||||
protected $tag = 7;
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->data ? 1 : 0);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = (ord($this->input) != 0);
|
||||
}
|
||||
}
|
38
lib/OpenPgP/SignaturePacket/RevocationKeyPacket.php
Normal file
38
lib/OpenPgP/SignaturePacket/RevocationKeyPacket.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.15
|
||||
*/
|
||||
class RevocationKeyPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 12;
|
||||
public $key_algorithm, $fingerprint, $sensitive;
|
||||
|
||||
function body()
|
||||
{
|
||||
$bytes = '';
|
||||
$bytes .= chr(0x80 | ($this->sensitive ? 0x40 : 0x00));
|
||||
$bytes .= chr($this->key_algorithm);
|
||||
|
||||
for($i = 0; $i < strlen($this->fingerprint); $i += 2) {
|
||||
$bytes .= chr(hexdec($this->fingerprint{$i}.$this->fingerprint{$i+1}));
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
// bitfield must have bit 0x80 set, says the spec
|
||||
$bitfield = ord($this->read_byte());
|
||||
$this->sensitive = $bitfield & 0x40 == 0x40;
|
||||
$this->key_algorithm = ord($this->read_byte());
|
||||
|
||||
$this->fingerprint = '';
|
||||
while(strlen($this->input) > 0) {
|
||||
$this->fingerprint .= sprintf('%02X',ord($this->read_byte()));
|
||||
}
|
||||
}
|
||||
}
|
21
lib/OpenPgP/SignaturePacket/SignatureCreationTimePacket.php
Normal file
21
lib/OpenPgP/SignaturePacket/SignatureCreationTimePacket.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.4
|
||||
*/
|
||||
class SignatureCreationTimePacket extends Subpacket
|
||||
{
|
||||
protected $tag = 2;
|
||||
|
||||
function body()
|
||||
{
|
||||
return pack('N',$this->data);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->read_timestamp();
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.6
|
||||
*/
|
||||
class SignatureExpirationTimePacket extends Subpacket
|
||||
{
|
||||
protected $tag = 3;
|
||||
|
||||
function body()
|
||||
{
|
||||
return pack('N', $this->data);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->read_timestamp();
|
||||
}
|
||||
}
|
24
lib/OpenPgP/SignaturePacket/SignatureTargetPacket.php
Normal file
24
lib/OpenPgP/SignaturePacket/SignatureTargetPacket.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.25
|
||||
*/
|
||||
class SignatureTargetPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 31;
|
||||
public $key_algorithm, $hash_algorithm;
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->key_algorithm) . chr($this->hash_algorithm) . $this->data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->key_algorithm = ord($this->read_byte());
|
||||
$this->hash_algorithm = ord($this->read_byte());
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
21
lib/OpenPgP/SignaturePacket/SignersUserIDPacket.php
Normal file
21
lib/OpenPgP/SignaturePacket/SignersUserIDPacket.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.22
|
||||
*/
|
||||
class SignersUserIDPacket extends Subpacket
|
||||
{
|
||||
protected $tag = 28;
|
||||
|
||||
function body()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
30
lib/OpenPgP/SignaturePacket/Subpacket.php
Normal file
30
lib/OpenPgP/SignaturePacket/Subpacket.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
use Leenooks\OpenPGP\Packet;
|
||||
|
||||
class Subpacket extends Packet
|
||||
{
|
||||
protected $tag = NULL;
|
||||
|
||||
function body()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
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 */
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
22
lib/OpenPgP/SignaturePacket/TrustSignaturePacket.php
Normal file
22
lib/OpenPgP/SignaturePacket/TrustSignaturePacket.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.13
|
||||
*/
|
||||
class TrustSignaturePacket extends Subpacket
|
||||
{
|
||||
protected $tag = 5;
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->depth) . chr($this->trust);
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->depth = ord($this->input{0});
|
||||
$this->trust = ord($this->input{1});
|
||||
}
|
||||
}
|
37
lib/OpenPgP/SymmetricSessionKeyPacket.php
Normal file
37
lib/OpenPgP/SymmetricSessionKeyPacket.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Symmetric-Key Encrypted Session Key packet (tag 3).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.3
|
||||
*/
|
||||
class SymmetricSessionKeyPacket extends Packet
|
||||
{
|
||||
protected $tag = 3;
|
||||
public $version, $symmetric_algorithm, $s2k, $encrypted_data;
|
||||
|
||||
function __construct($s2k=NULL,$encrypted_data='',$symmetric_algorithm=9,$version=3)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->version = $version;
|
||||
$this->symmetric_algorithm = $symmetric_algorithm;
|
||||
$this->s2k = $s2k;
|
||||
$this->encrypted_data = $encrypted_data;
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
return chr($this->version) . chr($this->symmetric_algorithm) .
|
||||
$this->s2k->to_bytes() . $this->encrypted_data;
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->version = ord($this->read_byte());
|
||||
$this->symmetric_algorithm = ord($this->read_byte());
|
||||
$this->s2k = OpenPGP\S2k::parse($this->input);
|
||||
$this->encrypted_data = $this->input;
|
||||
}
|
||||
}
|
17
lib/OpenPgP/TrustPacket.php
Normal file
17
lib/OpenPgP/TrustPacket.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP Trust packet (tag 12).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.10
|
||||
*/
|
||||
class TrustPacket extends Packet
|
||||
{
|
||||
protected $tag = 12;
|
||||
|
||||
function read() {
|
||||
$this->data = $this->input;
|
||||
}
|
||||
}
|
16
lib/OpenPgP/UserAttributePacket.php
Normal file
16
lib/OpenPgP/UserAttributePacket.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP User Attribute packet (tag 17).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.12
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-11.1
|
||||
*/
|
||||
class UserAttributePacket extends Packet
|
||||
{
|
||||
// TODO
|
||||
protected $tag = 17;
|
||||
public $packets;
|
||||
}
|
78
lib/OpenPgP/UserIDPacket.php
Normal file
78
lib/OpenPgP/UserIDPacket.php
Normal file
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
/**
|
||||
* OpenPGP User ID packet (tag 13).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.11
|
||||
* @see http://tools.ietf.org/html/rfc2822
|
||||
*/
|
||||
class UserIDPacket extends Packet
|
||||
{
|
||||
protected $tag = 13;
|
||||
public $name, $comment, $email;
|
||||
|
||||
function __construct($name='',$comment='',$email='')
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
if (! $comment && ! $email) {
|
||||
$this->input = $name;
|
||||
$this->read();
|
||||
|
||||
} else {
|
||||
$this->name = $name;
|
||||
$this->comment = $comment;
|
||||
$this->email = $email;
|
||||
}
|
||||
}
|
||||
|
||||
function __toString()
|
||||
{
|
||||
$text = [];
|
||||
|
||||
if ($this->name) { $text[] = $this->name; }
|
||||
if ($this->comment) { $text[] = "({$this->comment})"; }
|
||||
if ($this->email) { $text[] = "<{$this->email}>"; }
|
||||
|
||||
return implode(' ', $text);
|
||||
}
|
||||
|
||||
function body()
|
||||
{
|
||||
return ''.$this; // Convert to string is the body
|
||||
}
|
||||
|
||||
function read()
|
||||
{
|
||||
$this->data = $this->input;
|
||||
// User IDs of the form: "name (comment) <email>"
|
||||
if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->data, $matches)) {
|
||||
$this->name = trim($matches[1]);
|
||||
$this->comment = trim($matches[2]);
|
||||
$this->email = trim($matches[3]);
|
||||
}
|
||||
|
||||
// User IDs of the form: "name <email>"
|
||||
else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->data, $matches)) {
|
||||
$this->name = trim($matches[1]);
|
||||
$this->comment = NULL;
|
||||
$this->email = trim($matches[2]);
|
||||
}
|
||||
|
||||
// User IDs of the form: "name"
|
||||
else if (preg_match('/^([^<]+)$/', $this->data, $matches)) {
|
||||
$this->name = trim($matches[1]);
|
||||
$this->comment = NULL;
|
||||
$this->email = NULL;
|
||||
}
|
||||
|
||||
// User IDs of the form: "<email>"
|
||||
else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) {
|
||||
$this->name = NULL;
|
||||
$this->comment = NULL;
|
||||
$this->email = trim($matches[2]);
|
||||
}
|
||||
}
|
||||
}
|
1886
lib/openpgp.php
1886
lib/openpgp.php
File diff suppressed because it is too large
Load Diff
@ -1,276 +0,0 @@
|
||||
<?php
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
/**
|
||||
* OpenPGP_Crypt_RSA.php is a wrapper for using the classes from OpenPGP.php with Crypt_RSA
|
||||
*
|
||||
* @package OpenPGP
|
||||
*/
|
||||
|
||||
// From http://phpseclib.sourceforge.net/
|
||||
use phpseclib\Crypt\RSA as Crypt_RSA;
|
||||
use phpseclib\Math\BigInteger as Math_BigInteger;
|
||||
|
||||
define('CRYPT_RSA_ENCRYPTION_PKCS1', Crypt_RSA::ENCRYPTION_PKCS1);
|
||||
define('CRYPT_RSA_SIGNATURE_PKCS1', Crypt_RSA::SIGNATURE_PKCS1);
|
||||
|
||||
require_once dirname(__FILE__).'/openpgp.php';
|
||||
@include_once dirname(__FILE__).'/openpgp_crypt_symmetric.php'; /* For encrypt/decrypt */
|
||||
|
||||
class OpenPGP_Crypt_RSA {
|
||||
protected $key, $message;
|
||||
|
||||
// Construct a wrapper object from a key or a message packet
|
||||
function __construct($packet) {
|
||||
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
|
||||
if($packet instanceof OpenPGP_PublicKeyPacket || $packet[0] instanceof OpenPGP_PublicKeyPacket) { // If it's a key (other keys are subclasses of this one)
|
||||
$this->key = $packet;
|
||||
} else {
|
||||
$this->message = $packet;
|
||||
}
|
||||
}
|
||||
|
||||
function key($keyid=NULL) {
|
||||
if(!$this->key) return NULL; // No key
|
||||
if($this->key instanceof OpenPGP_Message) {
|
||||
foreach($this->key as $p) {
|
||||
if($p instanceof OpenPGP_PublicKeyPacket) {
|
||||
if(!$keyid || strtoupper(substr($p->fingerprint, strlen($keyid)*-1)) == strtoupper($keyid)) return $p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->key;
|
||||
}
|
||||
|
||||
// Get Crypt_RSA for the public key
|
||||
function public_key($keyid=NULL) {
|
||||
return self::convert_public_key($this->key($keyid));
|
||||
}
|
||||
|
||||
// Get Crypt_RSA for the private key
|
||||
function private_key($keyid=NULL) {
|
||||
return self::convert_private_key($this->key($keyid));
|
||||
}
|
||||
|
||||
// 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)
|
||||
function verify($packet) {
|
||||
$self = $this; // For old PHP
|
||||
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
|
||||
if(!$this->message) {
|
||||
$m = $packet;
|
||||
$verifier = function($m, $s) use($self) {
|
||||
$key = $self->public_key($s->issuer());
|
||||
if(!$key) return false;
|
||||
$key->setHash(strtolower($s->hash_algorithm_name()));
|
||||
return $key->verify($m, reset($s->data));
|
||||
};
|
||||
} else {
|
||||
if(!($packet instanceof Crypt_RSA)) {
|
||||
$packet = new self($packet);
|
||||
}
|
||||
|
||||
$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
|
||||
// Second parameter is hash algorithm to use (default SHA256)
|
||||
// Third parameter is the 16-digit key ID to use... defaults to the key id in the key packet
|
||||
function sign($packet, $hash='SHA256', $keyid=NULL) {
|
||||
if(!is_object($packet)) {
|
||||
if($this->key) {
|
||||
$packet = new OpenPGP_LiteralDataPacket($packet);
|
||||
} else {
|
||||
$packet = OpenPGP_Message::parse($packet);
|
||||
}
|
||||
}
|
||||
|
||||
if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA
|
||||
|| ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) {
|
||||
$key = $packet;
|
||||
$message = $this->message;
|
||||
} else {
|
||||
$key = $this->key;
|
||||
$message = $packet;
|
||||
}
|
||||
|
||||
if(!$key || !$message) return NULL; // Missing some data
|
||||
|
||||
if($message instanceof OpenPGP_Message) {
|
||||
$sign = $message->signatures();
|
||||
$message = $sign[0][0];
|
||||
}
|
||||
|
||||
if(!($key instanceof Crypt_RSA)) {
|
||||
$key = new self($key);
|
||||
if(!$keyid) $keyid = substr($key->key()->fingerprint, -16, 16);
|
||||
$key = $key->private_key($keyid);
|
||||
}
|
||||
$key->setHash(strtolower($hash));
|
||||
|
||||
$sig = new OpenPGP_SignaturePacket($message, 'RSA', strtoupper($hash));
|
||||
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
|
||||
$sig->sign_data(array('RSA' => array($hash => function($data) use($key) {return array($key->sign($data));})));
|
||||
|
||||
return new OpenPGP_Message(array($sig, $message));
|
||||
}
|
||||
|
||||
/** Pass a message with a key and userid packet to sign */
|
||||
// TODO: merge this with the normal sign function
|
||||
function sign_key_userid($packet, $hash='SHA256', $keyid=NULL) {
|
||||
if(is_array($packet)) {
|
||||
$packet = new OpenPGP_Message($packet);
|
||||
} else if(!is_object($packet)) {
|
||||
$packet = OpenPGP_Message::parse($packet);
|
||||
}
|
||||
|
||||
$key = $this->private_key($keyid);
|
||||
if(!$key || !$packet) return NULL; // Missing some data
|
||||
|
||||
if(!$keyid) $keyid = substr($this->key->fingerprint, -16);
|
||||
$key->setHash(strtolower($hash));
|
||||
|
||||
$sig = NULL;
|
||||
foreach($packet as $p) {
|
||||
if($p instanceof OpenPGP_SignaturePacket) $sig = $p;
|
||||
}
|
||||
if(!$sig) {
|
||||
$sig = new OpenPGP_SignaturePacket($packet, 'RSA', strtoupper($hash));
|
||||
$sig->signature_type = 0x13;
|
||||
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02));
|
||||
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
|
||||
$packet[] = $sig;
|
||||
}
|
||||
|
||||
$sig->sign_data(array('RSA' => array($hash => function($data) use($key) {return array($key->sign($data));})));
|
||||
|
||||
return $packet;
|
||||
}
|
||||
|
||||
function decrypt($packet) {
|
||||
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
|
||||
|
||||
if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA
|
||||
|| ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) {
|
||||
$keys = $packet;
|
||||
$message = $this->message;
|
||||
} else {
|
||||
$keys = $this->key;
|
||||
$message = $packet;
|
||||
}
|
||||
|
||||
if(!$keys || !$message) return NULL; // Missing some data
|
||||
|
||||
if(!($keys instanceof Crypt_RSA)) {
|
||||
$keys = new self($keys);
|
||||
}
|
||||
|
||||
foreach($message as $p) {
|
||||
if($p instanceof OpenPGP_AsymmetricSessionKeyPacket) {
|
||||
if($keys instanceof Crypt_RSA) {
|
||||
$sk = self::try_decrypt_session($keys, substr($p->encrypted_data, 2));
|
||||
} else if(strlen(str_replace('0', '', $p->keyid)) < 1) {
|
||||
foreach($keys->key as $k) {
|
||||
$sk = self::try_decrypt_session(self::convert_private_key($k), substr($p->encrypted_data, 2));
|
||||
if($sk) break;
|
||||
}
|
||||
} else {
|
||||
$key = $keys->private_key($p->keyid);
|
||||
$sk = self::try_decrypt_session($key, substr($p->encrypted_data, 2));
|
||||
}
|
||||
|
||||
if(!$sk) continue;
|
||||
|
||||
$r = OpenPGP_Crypt_Symmetric::decryptPacket(OpenPGP_Crypt_Symmetric::getEncryptedData($message), $sk[0], $sk[1]);
|
||||
if($r) return $r;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; /* Failed */
|
||||
}
|
||||
|
||||
static function try_decrypt_session($key, $edata) {
|
||||
$key->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
|
||||
$data = @$key->decrypt($edata);
|
||||
if(!$data) return NULL;
|
||||
$sk = substr($data, 1, strlen($data)-3);
|
||||
$chk = unpack('n', substr($data, -2));
|
||||
$chk = reset($chk);
|
||||
|
||||
$sk_chk = 0;
|
||||
for($i = 0; $i < strlen($sk); $i++) {
|
||||
$sk_chk = ($sk_chk + ord($sk{$i})) % 65536;
|
||||
}
|
||||
|
||||
if($sk_chk != $chk) return NULL;
|
||||
return array(ord($data{0}), $sk);
|
||||
}
|
||||
|
||||
static function crypt_rsa_key($mod, $exp, $hash='SHA256') {
|
||||
$rsa = new Crypt_RSA();
|
||||
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
|
||||
$rsa->setHash(strtolower($hash));
|
||||
$rsa->modulus = new Math_BigInteger($mod, 256);
|
||||
$rsa->k = strlen($rsa->modulus->toBytes());
|
||||
$rsa->exponent = new Math_BigInteger($exp, 256);
|
||||
$rsa->setPublicKey();
|
||||
return $rsa;
|
||||
}
|
||||
|
||||
static function convert_key($packet, $private=false) {
|
||||
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
|
||||
if($packet instanceof OpenPGP_Message) $packet = $packet[0];
|
||||
|
||||
$mod = $packet->key['n'];
|
||||
$exp = $packet->key['e'];
|
||||
if($private) $exp = $packet->key['d'];
|
||||
if(!$exp) return NULL; // Packet doesn't have needed data
|
||||
|
||||
$rsa = self::crypt_rsa_key($mod, $exp);
|
||||
|
||||
if($private) {
|
||||
/**
|
||||
* @see https://github.com/phpseclib/phpseclib/issues/1113
|
||||
* Primes and coefficients now use BigIntegers.
|
||||
**/
|
||||
//set the primes
|
||||
if($packet->key['p'] && $packet->key['q'])
|
||||
$rsa->primes = array(
|
||||
1 => new Math_BigInteger($packet->key['p'], 256),
|
||||
2 => new Math_BigInteger($packet->key['q'], 256)
|
||||
);
|
||||
// set the coefficients
|
||||
if($packet->key['u']) $rsa->coefficients = array(2 => new Math_BigInteger($packet->key['u'], 256));
|
||||
}
|
||||
|
||||
return $rsa;
|
||||
}
|
||||
|
||||
static function convert_public_key($packet) {
|
||||
return self::convert_key($packet, false);
|
||||
}
|
||||
|
||||
static function convert_private_key($packet) {
|
||||
return self::convert_key($packet, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
?>
|
@ -1,236 +0,0 @@
|
||||
<?php
|
||||
|
||||
use phpseclib\Crypt\AES as Crypt_AES;
|
||||
use phpseclib\Crypt\Blowfish as Crypt_Blowfish;
|
||||
use phpseclib\Crypt\TripleDES as Crypt_TripleDES;
|
||||
use phpseclib\Crypt\Twofish as Crypt_Twofish;
|
||||
use phpseclib\Crypt\Random;
|
||||
|
||||
require_once dirname(__FILE__).'/openpgp.php';
|
||||
@include_once dirname(__FILE__).'/openpgp_crypt_rsa.php';
|
||||
@include_once dirname(__FILE__).'/openpgp_mcrypt_wrapper.php';
|
||||
@include_once dirname(__FILE__).'/openpgp_openssl_wrapper.php';
|
||||
|
||||
class OpenPGP_Crypt_Symmetric {
|
||||
public static function encrypt($passphrases_and_keys, $message, $symmetric_algorithm=9) {
|
||||
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm);
|
||||
if(!$cipher) throw new Exception("Unsupported cipher");
|
||||
$prefix = Random::string($key_block_bytes);
|
||||
$prefix .= substr($prefix, -2);
|
||||
|
||||
$key = Random::string($key_bytes);
|
||||
$cipher->setKey($key);
|
||||
|
||||
$to_encrypt = $prefix . $message->to_bytes();
|
||||
$mdc = new OpenPGP_ModificationDetectionCodePacket(hash('sha1', $to_encrypt . "\xD3\x14", true));
|
||||
$to_encrypt .= $mdc->to_bytes();
|
||||
$encrypted = array(new OpenPGP_IntegrityProtectedDataPacket($cipher->encrypt($to_encrypt)));
|
||||
|
||||
if(!is_array($passphrases_and_keys) && !($passphrases_and_keys instanceof IteratorAggregate)) {
|
||||
$passphrases_and_keys = (array)$passphrases_and_keys;
|
||||
}
|
||||
|
||||
foreach($passphrases_and_keys as $pass) {
|
||||
if($pass instanceof OpenPGP_PublicKeyPacket) {
|
||||
if(!in_array($pass->algorithm, array(1,2,3))) throw new Exception("Only RSA keys are supported.");
|
||||
$crypt_rsa = new OpenPGP_Crypt_RSA($pass);
|
||||
$rsa = $crypt_rsa->public_key();
|
||||
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
|
||||
$esk = $rsa->encrypt(chr($symmetric_algorithm) . $key . pack('n', self::checksum($key)));
|
||||
$esk = pack('n', OpenPGP::bitlength($esk)) . $esk;
|
||||
array_unshift($encrypted, new OpenPGP_AsymmetricSessionKeyPacket($pass->algorithm, $pass->fingerprint(), $esk));
|
||||
} else if(is_string($pass)) {
|
||||
$s2k = new OpenPGP_S2K(Random::string(8));
|
||||
$cipher->setKey($s2k->make_key($pass, $key_bytes));
|
||||
$esk = $cipher->encrypt(chr($symmetric_algorithm) . $key);
|
||||
array_unshift($encrypted, new OpenPGP_SymmetricSessionKeyPacket($s2k, $esk, $symmetric_algorithm));
|
||||
}
|
||||
}
|
||||
|
||||
return new OpenPGP_Message($encrypted);
|
||||
}
|
||||
|
||||
public static function decryptSymmetric($pass, $m) {
|
||||
$epacket = self::getEncryptedData($m);
|
||||
|
||||
foreach($m as $p) {
|
||||
if($p instanceof OpenPGP_SymmetricSessionKeyPacket) {
|
||||
if(strlen($p->encrypted_data) > 0) {
|
||||
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
|
||||
if(!$cipher) continue;
|
||||
$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));
|
||||
} else {
|
||||
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
|
||||
$decrypted = self::decryptPacket($epacket, $p->symmetric_algorithm, $p->s2k->make_key($pass, $key_bytes));
|
||||
}
|
||||
|
||||
if($decrypted) return $decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; /* If we get here, we failed */
|
||||
}
|
||||
|
||||
public static function encryptSecretKey($pass, $packet, $symmetric_algorithm=9) {
|
||||
$packet = clone $packet; // Do not mutate original
|
||||
$packet->s2k_useage = 254;
|
||||
$packet->symmetric_algorithm = $symmetric_algorithm;
|
||||
|
||||
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($packet->symmetric_algorithm);
|
||||
if(!$cipher) throw new Exception("Unsupported cipher");
|
||||
|
||||
$material = '';
|
||||
foreach(OpenPGP_SecretKeyPacket::$secret_key_fields[$packet->algorithm] as $field) {
|
||||
$f = $packet->key[$field];
|
||||
$material .= pack('n', OpenPGP::bitlength($f)) . $f;
|
||||
unset($packet->key[$field]);
|
||||
}
|
||||
$material .= hash('sha1', $material, true);
|
||||
|
||||
$iv = Random::string($key_block_bytes);
|
||||
if(!$packet->s2k) $packet->s2k = new OpenPGP_S2K(Random::string(8));
|
||||
$cipher->setKey($packet->s2k->make_key($pass, $key_bytes));
|
||||
$cipher->setIV($iv);
|
||||
$packet->encrypted_data = $iv . $cipher->encrypt($material);
|
||||
|
||||
return $packet;
|
||||
}
|
||||
|
||||
public static function decryptSecretKey($pass, $packet) {
|
||||
$packet = clone $packet; // Do not mutate orinigal
|
||||
|
||||
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($packet->symmetric_algorithm);
|
||||
if(!$cipher) throw new Exception("Unsupported cipher");
|
||||
$cipher->setKey($packet->s2k->make_key($pass, $key_bytes));
|
||||
$cipher->setIV(substr($packet->encrypted_data, 0, $key_block_bytes));
|
||||
$material = $cipher->decrypt(substr($packet->encrypted_data, $key_block_bytes));
|
||||
|
||||
if($packet->s2k_useage == 254) {
|
||||
$chk = substr($material, -20);
|
||||
$material = substr($material, 0, -20);
|
||||
if($chk != hash('sha1', $material, true)) return NULL;
|
||||
} else {
|
||||
$chk = unpack('n', substr($material, -2));
|
||||
$chk = reset($chk);
|
||||
$material = substr($material, 0, -2);
|
||||
|
||||
$mkChk = self::checksum($material);
|
||||
if($chk != $mkChk) return NULL;
|
||||
}
|
||||
|
||||
$packet->s2k = NULL;
|
||||
$packet->s2k_useage = 0;
|
||||
$packet->symmetric_algorithm = 0;
|
||||
$packet->encrypted_data = NULL;
|
||||
$packet->input = $material;
|
||||
$packet->key_from_input();
|
||||
unset($packet->input);
|
||||
return $packet;
|
||||
}
|
||||
|
||||
public static function decryptPacket($epacket, $symmetric_algorithm, $key) {
|
||||
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm);
|
||||
if(!$cipher) return NULL;
|
||||
$cipher->setKey($key);
|
||||
|
||||
if($epacket instanceof OpenPGP_IntegrityProtectedDataPacket) {
|
||||
$padAmount = $key_block_bytes - (strlen($epacket->data) % $key_block_bytes);
|
||||
$data = substr($cipher->decrypt($epacket->data . str_repeat("\0", $padAmount)), 0, strlen($epacket->data));
|
||||
$prefix = substr($data, 0, $key_block_bytes + 2);
|
||||
$mdc = substr(substr($data, -22, 22), 2);
|
||||
$data = substr($data, $key_block_bytes + 2, -22);
|
||||
|
||||
$mkMDC = hash("sha1", $prefix . $data . "\xD3\x14", true);
|
||||
if($mkMDC !== $mdc) return false;
|
||||
|
||||
try {
|
||||
$msg = OpenPGP_Message::parse($data);
|
||||
} catch (Exception $ex) { $msg = NULL; }
|
||||
if($msg) return $msg; /* Otherwise keep trying */
|
||||
} else {
|
||||
// No MDC mean decrypt with resync
|
||||
$iv = substr($epacket->data, 2, $key_block_bytes);
|
||||
$edata = substr($epacket->data, $key_block_bytes + 2);
|
||||
$padAmount = $key_block_bytes - (strlen($edata) % $key_block_bytes);
|
||||
|
||||
$cipher->setIV($iv);
|
||||
$data = substr($cipher->decrypt($edata . str_repeat("\0", $padAmount)), 0, strlen($edata));
|
||||
|
||||
try {
|
||||
$msg = OpenPGP_Message::parse($data);
|
||||
} catch (Exception $ex) { $msg = NULL; }
|
||||
if($msg) return $msg; /* Otherwise keep trying */
|
||||
}
|
||||
|
||||
return NULL; /* Failed */
|
||||
}
|
||||
|
||||
public static function getCipher($algo) {
|
||||
$cipher = NULL;
|
||||
switch($algo) {
|
||||
case NULL:
|
||||
case 0:
|
||||
throw new Exception("Data is already unencrypted");
|
||||
case 2:
|
||||
$cipher = new Crypt_TripleDES(Crypt_TripleDES::MODE_CFB);
|
||||
$key_bytes = 24;
|
||||
$key_block_bytes = 8;
|
||||
break;
|
||||
case 3:
|
||||
if(class_exists('OpenSSLWrapper')) {
|
||||
$cipher = new OpenSSLWrapper("CAST5-CFB");
|
||||
} else if(defined('MCRYPT_CAST_128')) {
|
||||
$cipher = new MCryptWrapper(MCRYPT_CAST_128);
|
||||
}
|
||||
break;
|
||||
case 4:
|
||||
$cipher = new Crypt_Blowfish(Crypt_Blowfish::MODE_CFB);
|
||||
$key_bytes = 16;
|
||||
$key_block_bytes = 8;
|
||||
break;
|
||||
case 7:
|
||||
$cipher = new Crypt_AES(Crypt_AES::MODE_CFB);
|
||||
$cipher->setKeyLength(128);
|
||||
break;
|
||||
case 8:
|
||||
$cipher = new Crypt_AES(Crypt_AES::MODE_CFB);
|
||||
$cipher->setKeyLength(192);
|
||||
break;
|
||||
case 9:
|
||||
$cipher = new Crypt_AES(Crypt_AES::MODE_CFB);
|
||||
$cipher->setKeyLength(256);
|
||||
break;
|
||||
case 10:
|
||||
$cipher = new Crypt_Twofish(Crypt_Twofish::MODE_CFB);
|
||||
if(method_exists($cipher, 'setKeyLength')) {
|
||||
$cipher->setKeyLength(256);
|
||||
} else {
|
||||
$cipher = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if(!$cipher) return array(NULL, NULL, NULL); // Unsupported cipher
|
||||
if(!isset($key_bytes)) $key_bytes = isset($cipher->key_size)?$cipher->key_size:$cipher->key_length;
|
||||
if(!isset($key_block_bytes)) $key_block_bytes = $cipher->block_size;
|
||||
return array($cipher, $key_bytes, $key_block_bytes);
|
||||
}
|
||||
|
||||
public static function getEncryptedData($m) {
|
||||
foreach($m as $p) {
|
||||
if($p instanceof OpenPGP_EncryptedDataPacket) return $p;
|
||||
}
|
||||
throw new Exception("Can only decrypt EncryptedDataPacket");
|
||||
}
|
||||
|
||||
public static function checksum($s) {
|
||||
$mkChk = 0;
|
||||
for($i = 0; $i < strlen($s); $i++) {
|
||||
$mkChk = ($mkChk + ord($s{$i})) % 65536;
|
||||
}
|
||||
return $mkChk;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user