Compare commits
No commits in common. "master" and "0.0.1" have entirely different histories.
51
.travis.yml
51
.travis.yml
@ -1,51 +0,0 @@
|
||||
---
|
||||
language: php
|
||||
php:
|
||||
- 7.0
|
||||
- 7.1
|
||||
- 7.2
|
||||
- 5.6
|
||||
|
||||
dist: trusty
|
||||
|
||||
env:
|
||||
- PHPSECLIB='^2.0 !=2.0.8'
|
||||
- PHPSECLIB="2.0.0"
|
||||
- PHPSECLIB="2.0.1"
|
||||
- PHPSECLIB="2.0.2"
|
||||
- PHPSECLIB="2.0.3"
|
||||
- PHPSECLIB="2.0.4"
|
||||
- PHPSECLIB="2.0.5"
|
||||
- PHPSECLIB="2.0.6"
|
||||
- PHPSECLIB="2.0.7"
|
||||
- PHPSECLIB="2.0.9"
|
||||
- PHPSECLIB="2.0.10"
|
||||
- PHPSECLIB="2.0.11"
|
||||
|
||||
matrix:
|
||||
exclude:
|
||||
- php: 7.1
|
||||
- env: PHPSECLIB="2.0.0"
|
||||
- php: 7.2
|
||||
- env: PHPSECLIB="2.0.0"
|
||||
- php: 7.1
|
||||
- env: PHPSECLIB="2.0.1"
|
||||
- php: 7.2
|
||||
- env: PHPSECLIB="2.0.1"
|
||||
- php: 7.1
|
||||
- env: PHPSECLIB="2.0.2"
|
||||
- php: 7.2
|
||||
- env: PHPSECLIB="2.0.2"
|
||||
- php: 7.1
|
||||
- env: PHPSECLIB="2.0.3"
|
||||
- php: 7.2
|
||||
- env: PHPSECLIB="2.0.3"
|
||||
- php: 7.2
|
||||
- env: PHPSECLIB="2.0.4"
|
||||
- php: 7.2
|
||||
- env: PHPSECLIB="2.0.5"
|
||||
- php: 7.2
|
||||
- env: PHPSECLIB="2.0.6"
|
||||
fast_finish: true
|
||||
|
||||
before_script: 'sed -i "s/\"phpseclib\/phpseclib\": \"[^\"]*/\"phpseclib\/phpseclib\": \"$PHPSECLIB/" composer.json && composer install --prefer-source'
|
43
README.md
43
README.md
@ -1,16 +1,11 @@
|
||||
[](https://travis-ci.org/singpolyma/openpgp-php)
|
||||
|
||||
OpenPGP.php: OpenPGP for PHP
|
||||
============================
|
||||
|
||||
This is a pure-PHP implementation of the OpenPGP Message Format (RFC 4880).
|
||||
|
||||
This repository is forked from singpolyma/openpgp-php, and was only created to learn and understand the code.
|
||||
* <http://github.com/bendiken/openpgp-php>
|
||||
|
||||
* <https://github.com/singpolyma/openpgp-php>
|
||||
|
||||
About OpenPGP
|
||||
-------------
|
||||
### About OpenPGP
|
||||
|
||||
OpenPGP is the most widely-used e-mail encryption standard in the world. It
|
||||
is defined by the OpenPGP Working Group of the Internet Engineering Task
|
||||
@ -18,8 +13,8 @@ Force (IETF) Proposed Standard RFC 4880. The OpenPGP standard was originally
|
||||
derived from PGP (Pretty Good Privacy), first created by Phil Zimmermann in
|
||||
1991.
|
||||
|
||||
* <https://tools.ietf.org/html/rfc4880>
|
||||
* <https://www.openpgp.org/>
|
||||
* <http://tools.ietf.org/html/rfc4880>
|
||||
* <http://www.openpgp.org/>
|
||||
|
||||
Features
|
||||
--------
|
||||
@ -27,48 +22,26 @@ Features
|
||||
* Encodes and decodes ASCII-armored OpenPGP messages.
|
||||
* Parses OpenPGP messages into their constituent packets.
|
||||
* Supports both old-format (PGP 2.6.x) and new-format (RFC 4880) packets.
|
||||
* Helper class for verifying, signing, encrypting, and decrypting messages <http://phpseclib.sourceforge.net>
|
||||
* Helper class for encrypting and decrypting messages and keys using <http://phpseclib.sourceforge.net>
|
||||
* openssl or mcrypt required for CAST5 encryption and decryption
|
||||
|
||||
Bugs, Feature Requests, Patches
|
||||
-------------------------------
|
||||
|
||||
This project is primarily maintained by a single volunteer with many other
|
||||
things vying for their attention, please be patient.
|
||||
|
||||
Bugs, feature request, pull requests, patches, and general discussion may
|
||||
be submitted publicly via email to: dev@singpolyma.net
|
||||
|
||||
Github users may alternately submit on the web there.
|
||||
|
||||
Users
|
||||
-----
|
||||
|
||||
OpenPGP.php is currently being used in the following projects:
|
||||
|
||||
* <https://wordpress.org/plugins/wp-pgp-encrypted-emails/>
|
||||
|
||||
Download
|
||||
--------
|
||||
|
||||
To get a local working copy of the development repository, do:
|
||||
|
||||
git clone https://github.com/singpolyma/openpgp-php.git
|
||||
% git clone git://github.com/bendiken/openpgp-php.git
|
||||
|
||||
Alternatively, you can download the latest development version as a tarball
|
||||
as follows:
|
||||
|
||||
wget https://github.com/singpolyma/openpgp-php/tarball/master
|
||||
% wget http://github.com/bendiken/openpgp-php/tarball/master
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) (Original author) - <http://ar.to/>
|
||||
* [Stephen Paul Weber](mailto:singpolyma@singpolyma.net) (Maintainer) - <https://singpolyma.net/>
|
||||
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
OpenPGP.php is free and unencumbered public domain software. For more
|
||||
information, see <https://unlicense.org/> or the accompanying UNLICENSE file.
|
||||
information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
|
||||
|
@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "leenooks/openpgp-php",
|
||||
"description": "Pure-PHP implementation of the OpenPGP Message Format (RFC 4880)",
|
||||
"license": "Unlicense",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Arto Bendiken",
|
||||
"email": "arto.bendiken@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Stephen Paul Weber",
|
||||
"email": "singpolyma@singpolyma.net"
|
||||
},
|
||||
{
|
||||
"name": "Deon George",
|
||||
"email": "deon@leenooks.net"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^5.6 || ^7.0",
|
||||
"phpseclib/phpseclib": "^2.0 !=2.0.8"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/var-dumper": "^5.0",
|
||||
"phpunit/phpunit": "^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mcrypt": "required if you use encryption cast5"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["lib/"]
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
OpenPGP.php Examples
|
||||
====================
|
||||
|
||||
The scripts in this folder show how to use this library to perform various tasks
|
||||
such as [generating a new key](keygen.php), [signing a message](sign.php), and
|
||||
[verifying a message](verify.php) that has been signed.
|
||||
|
||||
To use these examples, make sure [`phpseclib`](http://phpseclib.sourceforge.net/) is available. You can install it
|
||||
using [Composer](https://getcomposer.org/):
|
||||
|
||||
```sh
|
||||
git clone https://github.com/singpolyma/openpgp-php.git # Clone the repository.
|
||||
cd openpgp-php
|
||||
composer install # Use Composer to install the requirements.
|
||||
```
|
||||
|
||||
Once Composer has installed the requirements, run the examples using PHP:
|
||||
|
||||
```sh
|
||||
# Generate a new OpenPGP key; see the `keygen.php` file for parameters.
|
||||
php ./examples/keygen.php > mykey.gpg
|
||||
```
|
@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
@include_once dirname(__FILE__).'/../vendor/autoload.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
|
||||
|
||||
/* Parse secret key from STDIN, the key must not be password protected */
|
||||
$wkey = OpenPGP_Message::parse(file_get_contents('php://stdin'));
|
||||
$wkey = $wkey[0];
|
||||
|
||||
$string = "This\nis\na\ntest.";
|
||||
|
||||
/* Create a new literal data packet */
|
||||
$data = new OpenPGP_LiteralDataPacket($string, array('format' => 'u', 'filename' => 'stuff.txt'));
|
||||
$data->normalize(true); // Clearsign-style normalization of the LiteralDataPacket
|
||||
|
||||
/* Create a signer from the key */
|
||||
$sign = new OpenPGP_Crypt_RSA($wkey);
|
||||
|
||||
/* The message is the signed data packet */
|
||||
$m = $sign->sign($data);
|
||||
|
||||
/* Generate clearsigned data */
|
||||
$packets = $m->signatures()[0];
|
||||
echo "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n";
|
||||
// Output normalised data. You could convert line endings here
|
||||
// without breaking the signature, but do not add any
|
||||
// trailing whitespace to lines.
|
||||
echo preg_replace("/^-/", "- -", $packets[0]->data)."\n";
|
||||
echo OpenPGP::enarmor($packets[1][0]->to_bytes(), "PGP SIGNATURE");
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
// USAGE: php examples/deASCIIdeCrypt.php secretkey.asc password message.asc
|
||||
// This will fail if the algo on key or message is not 3DES or AES
|
||||
|
||||
@include_once dirname(__FILE__).'/../vendor/autoload.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_symmetric.php';
|
||||
|
||||
$keyASCII = file_get_contents($argv[1]);
|
||||
$msgASCII = file_get_contents($argv[3]);
|
||||
|
||||
$keyEncrypted = OpenPGP_Message::parse(OpenPGP::unarmor($keyASCII, 'PGP PRIVATE KEY BLOCK'));
|
||||
|
||||
// Try each secret key packet
|
||||
foreach($keyEncrypted as $p) {
|
||||
if(!($p instanceof OpenPGP_SecretKeyPacket)) continue;
|
||||
|
||||
$key = OpenPGP_Crypt_Symmetric::decryptSecretKey($argv[2], $p);
|
||||
|
||||
$msg = OpenPGP_Message::parse(OpenPGP::unarmor($msgASCII, 'PGP MESSAGE'));
|
||||
|
||||
$decryptor = new OpenPGP_Crypt_RSA($key);
|
||||
$decrypted = $decryptor->decrypt($msg);
|
||||
|
||||
var_dump($decrypted);
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
@include_once dirname(__FILE__).'/../vendor/autoload.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_symmetric.php';
|
||||
|
||||
$key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/../tests/data/helloKey.gpg'));
|
||||
$data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
|
||||
$encrypted = OpenPGP_Crypt_Symmetric::encrypt($key, new OpenPGP_Message(array($data)));
|
||||
|
||||
// Now decrypt it with the same key
|
||||
$decryptor = new OpenPGP_Crypt_RSA($key);
|
||||
$decrypted = $decryptor->decrypt($encrypted);
|
||||
|
||||
var_dump($decrypted);
|
@ -1,32 +0,0 @@
|
||||
<?php
|
||||
|
||||
@include_once dirname(__FILE__).'/../vendor/autoload.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
|
||||
|
||||
$rsa = new \phpseclib\Crypt\RSA();
|
||||
$k = $rsa->createKey(512);
|
||||
$rsa->loadKey($k['privatekey']);
|
||||
|
||||
$nkey = new OpenPGP_SecretKeyPacket(array(
|
||||
'n' => $rsa->modulus->toBytes(),
|
||||
'e' => $rsa->publicExponent->toBytes(),
|
||||
'd' => $rsa->exponent->toBytes(),
|
||||
'p' => $rsa->primes[2]->toBytes(),
|
||||
'q' => $rsa->primes[1]->toBytes(),
|
||||
'u' => $rsa->coefficients[2]->toBytes()
|
||||
));
|
||||
|
||||
$uid = new OpenPGP_UserIDPacket('Test <test@example.com>');
|
||||
|
||||
$wkey = new OpenPGP_Crypt_RSA($nkey);
|
||||
$m = $wkey->sign_key_userid(array($nkey, $uid));
|
||||
|
||||
// Serialize private key
|
||||
print $m->to_bytes();
|
||||
|
||||
// Serialize public key message
|
||||
$pubm = clone($m);
|
||||
$pubm[0] = new OpenPGP_PublicKeyPacket($pubm[0]);
|
||||
|
||||
$public_bytes = $pubm->to_bytes();
|
@ -1,28 +0,0 @@
|
||||
<?php
|
||||
|
||||
@include_once dirname(__FILE__).'/../vendor/autoload.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_symmetric.php';
|
||||
|
||||
$rsa = new \phpseclib\Crypt\RSA();
|
||||
$k = $rsa->createKey(512);
|
||||
$rsa->loadKey($k['privatekey']);
|
||||
|
||||
$nkey = new OpenPGP_SecretKeyPacket(array(
|
||||
'n' => $rsa->modulus->toBytes(),
|
||||
'e' => $rsa->publicExponent->toBytes(),
|
||||
'd' => $rsa->exponent->toBytes(),
|
||||
'p' => $rsa->primes[2]->toBytes(),
|
||||
'q' => $rsa->primes[1]->toBytes(),
|
||||
'u' => $rsa->coefficients[2]->toBytes()
|
||||
));
|
||||
|
||||
$uid = new OpenPGP_UserIDPacket('Test <test@example.com>');
|
||||
|
||||
$wkey = new OpenPGP_Crypt_RSA($nkey);
|
||||
$m = $wkey->sign_key_userid(array($nkey, $uid));
|
||||
$m[0] = OpenPGP_Crypt_Symmetric::encryptSecretKey("password", $nkey);
|
||||
|
||||
// Serialize encrypted private key
|
||||
print $m->to_bytes();
|
@ -1,21 +0,0 @@
|
||||
<?php
|
||||
|
||||
@include_once dirname(__FILE__).'/../vendor/autoload.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
|
||||
|
||||
/* Parse secret key from STDIN, the key must not be password protected */
|
||||
$wkey = OpenPGP_Message::parse(file_get_contents('php://stdin'));
|
||||
$wkey = $wkey[0];
|
||||
|
||||
/* Create a new literal data packet */
|
||||
$data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
|
||||
|
||||
/* Create a signer from the key */
|
||||
$sign = new OpenPGP_Crypt_RSA($wkey);
|
||||
|
||||
/* The message is the signed data packet */
|
||||
$m = $sign->sign($data);
|
||||
|
||||
/* Output the raw message bytes to STDOUT */
|
||||
echo $m->to_bytes();
|
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
@include_once dirname(__FILE__).'/../vendor/autoload.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp.php';
|
||||
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
|
||||
|
||||
/* Parse public key from STDIN */
|
||||
$wkey = OpenPGP_Message::parse(file_get_contents('php://stdin'));
|
||||
|
||||
/* Parse signed message from file named "t" */
|
||||
$m = OpenPGP_Message::parse(file_get_contents('t'));
|
||||
|
||||
/* Create a verifier for the key */
|
||||
$verify = new OpenPGP_Crypt_RSA($wkey);
|
||||
|
||||
/* Dump verification information to STDOUT */
|
||||
var_dump($verify->verify($m));
|
173
lib/OpenPGP.php
173
lib/OpenPGP.php
@ -1,173 +0,0 @@
|
||||
<?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 Illuminate\Support\Arr;
|
||||
use phpseclib\Crypt\RSA as Crypt_RSA;
|
||||
|
||||
use Leenooks\OpenPGP\Exceptions\PacketTagException;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880
|
||||
*/
|
||||
class OpenPGP
|
||||
{
|
||||
const VERSION = [0,5,0];
|
||||
private $key = NULL;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-12.2
|
||||
*/
|
||||
static function bitlength($data)
|
||||
{
|
||||
return (strlen($data) - 1) * 8 + (int)floor(log(ord($data[0]), 2)) + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a PGP Key
|
||||
*
|
||||
* @todo Incomplete and untested.
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $email
|
||||
* @param string $comment
|
||||
* @param int $keysize
|
||||
* @return OpenPGP
|
||||
*/
|
||||
static function create(string $name,string $email,string $comment,int $keysize=512): self
|
||||
{
|
||||
$result = new self;
|
||||
|
||||
$rsa = new Crypt_RSA;
|
||||
$rsa->loadKey(Arr::get($rsa->createKey($keysize),'privatekey'));
|
||||
|
||||
$nkey = new OpenPGP\SecretKeyPacket(array(
|
||||
'n' => $rsa->modulus->toBytes(),
|
||||
'e' => $rsa->publicExponent->toBytes(),
|
||||
'd' => $rsa->exponent->toBytes(),
|
||||
'p' => $rsa->primes[2]->toBytes(),
|
||||
'q' => $rsa->primes[1]->toBytes(),
|
||||
'u' => $rsa->coefficients[2]->toBytes()
|
||||
));
|
||||
|
||||
$wkey = new OpenPGP\Crypt\RSA($nkey);
|
||||
|
||||
$uid = new OpenPGP\UserIDPacket($name,$comment,$email);
|
||||
|
||||
$result->key = $wkey->sign_key_userid([$nkey,$uid]);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-6
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-6.1
|
||||
*/
|
||||
static function crc24($data): int
|
||||
{
|
||||
$crc = 0x00b704ce;
|
||||
|
||||
for ($i = 0; $i < strlen($data); $i++) {
|
||||
$crc ^= (ord($data[$i]) & 255) << 16;
|
||||
|
||||
for ($j = 0; $j < 8; $j++) {
|
||||
$crc <<= 1;
|
||||
if ($crc & 0x01000000) {
|
||||
$crc ^= 0x01864cfb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $crc & 0x00ffffff;
|
||||
}
|
||||
|
||||
static function decode_s2k_count($c)
|
||||
{
|
||||
return ((int)16 + ($c & 15)) << (($c >> 4) + 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-6
|
||||
* @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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-6.2
|
||||
*/
|
||||
static protected function footer($marker): string
|
||||
{
|
||||
return'-----END '.strtoupper((string)$marker).'-----';
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-6.2
|
||||
*/
|
||||
static protected function header($marker): string
|
||||
{
|
||||
return '-----BEGIN '.strtoupper((string)$marker).'-----';
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-6
|
||||
* @see http://tools.ietf.org/html/rfc2045
|
||||
*/
|
||||
static function unarmor($text,$header='PGP PUBLIC KEY BLOCK')
|
||||
{
|
||||
$header = self::header($header);
|
||||
|
||||
$text = str_replace(["\r\n","\r"],["\n",''],$text);
|
||||
|
||||
if (($pos1=strpos($text,$header)) !== FALSE
|
||||
&& ($pos1=strpos($text,"\n\n",$pos1+=strlen($header))) !== FALSE
|
||||
&& ($pos2=strpos($text,"\n=",$pos1+=2)) !== FALSE)
|
||||
{
|
||||
return base64_decode($text=substr($text,$pos1,$pos2-$pos1));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
<?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 = Message::parse($this->data);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$this->data = Message::parse(gzinflate($this->data));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$this->data = Message::parse(gzuncompress($this->data));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$this->data = Message::parse(bzdecompress($this->data));
|
||||
break;
|
||||
|
||||
default:
|
||||
/* TODO error? */
|
||||
}
|
||||
}
|
||||
}
|
@ -1,358 +0,0 @@
|
||||
<?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 OpenPGP\SignaturePacket($message,'RSA',strtoupper($hash));
|
||||
$sig->hashed_subpackets[] = new OpenPGP\SignaturePacket\IssuerPacket($keyid);
|
||||
$sig->sign_data(['RSA'=>[$hash => function($data) use($key) {return [$key->sign($data)];}]]);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -1,327 +0,0 @@
|
||||
<?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);
|
||||
|
||||
} 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;
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<?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;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?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;
|
||||
}
|
@ -1,256 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?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");
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
@ -1,279 +0,0 @@
|
||||
<?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) AND get_class($this) != 'Leenooks\OpenPGP\SignaturePacket\Subpacket')
|
||||
throw new PacketTagException('Missing tag in '.get_class($this));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
debug_print_backtrace(0,5);
|
||||
dump($this);
|
||||
dd($e->getMessage());
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
public function tag(): int
|
||||
{
|
||||
return $this->tag;
|
||||
}
|
||||
|
||||
function to_bytes()
|
||||
{
|
||||
$data = $this->header_and_body();
|
||||
|
||||
return $data['header'].$data['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-3.5
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,210 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?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;
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP;
|
||||
|
||||
use Leenooks\OpenPGP;
|
||||
|
||||
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(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);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
<?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 = 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?
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?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;
|
||||
}
|
@ -1,366 +0,0 @@
|
||||
<?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 $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)
|
||||
{
|
||||
if (self::$DEBUG)
|
||||
dump(['method'=>__METHOD__,'input'=>$input]);
|
||||
|
||||
$len = ord($input[0]);
|
||||
$length_of_length = 1;
|
||||
|
||||
if (self::$DEBUG)
|
||||
dump(['len'=>$len]);
|
||||
|
||||
// if($len < 192) One octet length, no furthur processing
|
||||
if ($len > 190 && $len < 255) { // Two octet length
|
||||
$length_of_length = 2;
|
||||
$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);
|
||||
if (self::$DEBUG)
|
||||
dump(['len'=>$len,'unpacked'=>$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;
|
||||
|
||||
// In case we got the catch all class.
|
||||
if ($class == 'Leenooks\OpenPGP\SignaturePacket\Subpacket')
|
||||
$packet->setTag($tag);
|
||||
|
||||
if ($packet->tag() !== $tag)
|
||||
throw new OpenPGP\Exceptions\PacketTagException(sprintf('Packet tag [%s] doesnt match tag [%s]?',$packet->tag(),$tag));
|
||||
//$packet->tag = $tag; // @todo Tag should already be set.
|
||||
$packet->input = substr($input, 1, $len-1);
|
||||
$packet->length = $len-1;
|
||||
$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);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?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];
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
<?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;
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<?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()));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<?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()));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Leenooks\OpenPGP\SignaturePacket;
|
||||
|
||||
use Leenooks\OpenPGP\Exceptions\PacketTagException;
|
||||
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;
|
||||
}
|
||||
|
||||
public function setTag(int $tag): void
|
||||
{
|
||||
if (get_class($this) !== Subpacket::class)
|
||||
throw new PacketTagException('Attempting to set a tag for invalid class: ',get_class($this));
|
||||
|
||||
$this->tag = $tag;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?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});
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
<?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 = S2k::parse($this->input);
|
||||
$this->encrypted_data = $this->input;
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
<?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;
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
<?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]);
|
||||
}
|
||||
}
|
||||
}
|
579
lib/openpgp.php
Normal file
579
lib/openpgp.php
Normal file
@ -0,0 +1,579 @@
|
||||
<?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
|
||||
* @version 0.0.1
|
||||
* @author Arto Bendiken <arto.bendiken@gmail.com>
|
||||
* @link http://github.com/bendiken/openpgp-php
|
||||
*/
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// OpenPGP utilities
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880
|
||||
*/
|
||||
class OpenPGP {
|
||||
/**
|
||||
* @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 = array()) {
|
||||
$text = self::header($marker) . "\n";
|
||||
foreach ($headers as $key => $value) {
|
||||
$text .= $key . ': ' . (string)$value . "\n";
|
||||
}
|
||||
$text .= "\n" . base64_encode($data);
|
||||
$text .= '=' . 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(array("\r\n", "\r"), array("\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 function header($marker) {
|
||||
return '-----BEGIN ' . strtoupper((string)$marker) . '-----';
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-6.2
|
||||
*/
|
||||
static function footer($marker) {
|
||||
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) {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// OpenPGP messages
|
||||
|
||||
/**
|
||||
* @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 OpenPGP_Message implements IteratorAggregate, ArrayAccess {
|
||||
public $uri = NULL;
|
||||
public $packets = array();
|
||||
|
||||
static function parse_file($path) {
|
||||
if (($msg = self::parse(file_get_contents($path)))) {
|
||||
$msg->uri = preg_match('!^[\w\d]+://!', $path) ? $path : 'file://' . realpath($path);
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.1
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.2
|
||||
*/
|
||||
static function parse($input) {
|
||||
if (is_resource($input)) {
|
||||
return self::parse_stream($input);
|
||||
}
|
||||
if (is_string($input)) {
|
||||
return self::parse_string($input);
|
||||
}
|
||||
}
|
||||
|
||||
static function parse_stream($input) {
|
||||
return self::parse_string(stream_get_contents($input));
|
||||
}
|
||||
|
||||
static function parse_string($input) {
|
||||
$msg = new self;
|
||||
while (($length = strlen($input)) > 0) {
|
||||
if (($packet = OpenPGP_Packet::parse($input))) {
|
||||
$msg[] = $packet;
|
||||
}
|
||||
if ($length == strlen($input)) { // is parsing stuck?
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $msg;
|
||||
}
|
||||
|
||||
function __construct(array $packets = array()) {
|
||||
$this->packets = $packets;
|
||||
}
|
||||
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
// OpenPGP packets
|
||||
|
||||
/**
|
||||
* OpenPGP packet.
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.1
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.3
|
||||
*/
|
||||
class OpenPGP_Packet {
|
||||
public $tag, $size, $data;
|
||||
|
||||
static function class_for($tag) {
|
||||
return isset(self::$tags[$tag]) && class_exists(
|
||||
$class = 'OpenPGP_' . self::$tags[$tag] . 'Packet') ? $class : __CLASS__;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an OpenPGP packet.
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.2
|
||||
*/
|
||||
static function parse(&$input) {
|
||||
$packet = NULL;
|
||||
if (strlen($input) > 0) {
|
||||
$parser = ord($input[0]) & 64 ? 'parse_new_format' : 'parse_old_format';
|
||||
list($tag, $head_length, $data_length) = self::$parser($input);
|
||||
$input = substr($input, $head_length);
|
||||
if ($tag && ($class = self::class_for($tag))) {
|
||||
$packet = new $class();
|
||||
$packet->tag = $tag;
|
||||
$packet->input = substr($input, 0, $data_length);
|
||||
$packet->length = $data_length;
|
||||
$packet->read();
|
||||
unset($packet->input);
|
||||
}
|
||||
$input = substr($input, $data_length);
|
||||
}
|
||||
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) {
|
||||
$tag = ord($input[0]) & 63;
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$len = ($tag = ord($input[0])) & 3;
|
||||
$tag = ($tag >> 2) & 15;
|
||||
switch ($len) {
|
||||
case 0: // The packet has a one-octet length. The header is 2 octets long.
|
||||
$head_length = 2;
|
||||
$data_length = ord($input[1]);
|
||||
break;
|
||||
case 1: // The packet has a two-octet length. The header is 3 octets long.
|
||||
$head_length = 3;
|
||||
$data_length = unpack('n', substr($input, 1, 2));
|
||||
$data_length = $data_length[1];
|
||||
break;
|
||||
case 2: // The packet has a four-octet length. The header is 5 octets long.
|
||||
$head_length = 5;
|
||||
$data_length = unpack('N', substr($input, 1, 4));
|
||||
$data_length = $data_length[1];
|
||||
break;
|
||||
case 3: // The packet is of indeterminate length. The header is 1 octet long.
|
||||
$head_length = 1;
|
||||
$data_length = strlen($input) - $head_length;
|
||||
break;
|
||||
}
|
||||
return array($tag, $head_length, $data_length);
|
||||
}
|
||||
|
||||
function __construct() {}
|
||||
|
||||
function read() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
function read_unpacked($count, $format) {
|
||||
$unpacked = unpack($format, $this->read_bytes($count));
|
||||
return $unpacked[1];
|
||||
}
|
||||
|
||||
function read_byte() {
|
||||
return ($bytes = $this->read_bytes()) ? $bytes[0] : NULL;
|
||||
}
|
||||
|
||||
function read_bytes($count = 1) {
|
||||
$bytes = substr($this->input, 0, $count);
|
||||
$this->input = substr($this->input, $count);
|
||||
return $bytes;
|
||||
}
|
||||
|
||||
static $tags = array(
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Public-Key Encrypted Session Key packet (tag 1).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.1
|
||||
*/
|
||||
class OpenPGP_AsymmetricSessionKeyPacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Signature packet (tag 2).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.2
|
||||
*/
|
||||
class OpenPGP_SignaturePacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Symmetric-Key Encrypted Session Key packet (tag 3).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.3
|
||||
*/
|
||||
class OpenPGP_SymmetricSessionKeyPacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP One-Pass Signature packet (tag 4).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.4
|
||||
*/
|
||||
class OpenPGP_OnePassSignaturePacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 OpenPGP_PublicKeyPacket extends OpenPGP_Packet {
|
||||
public $version, $timestamp, $algorithm;
|
||||
public $key, $key_id, $fingerprint;
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
|
||||
*/
|
||||
function read() {
|
||||
switch ($this->version = ord($this->read_byte())) {
|
||||
case 2:
|
||||
case 3:
|
||||
return FALSE; // TODO
|
||||
case 4:
|
||||
$this->timestamp = $this->read_timestamp();
|
||||
$this->algorithm = ord($this->read_byte());
|
||||
$this->read_key_material();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
|
||||
*/
|
||||
function read_key_material() {
|
||||
static $key_fields = array(
|
||||
1 => array('n', 'e'), // RSA
|
||||
16 => array('p', 'g', 'y'), // ELG-E
|
||||
17 => array('p', 'q', 'g', 'y'), // DSA
|
||||
);
|
||||
foreach ($key_fields[$this->algorithm] as $field) {
|
||||
$this->key[$field] = $this->read_mpi();
|
||||
}
|
||||
$this->key_id = substr($this->fingerprint(), -8);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 = md5($this->key['n'] . $this->key['e']);
|
||||
case 4:
|
||||
$material = array(
|
||||
chr(0x99), pack('n', $this->length),
|
||||
chr($this->version), pack('N', $this->timestamp),
|
||||
chr($this->algorithm),
|
||||
);
|
||||
foreach ($this->key as $data) {
|
||||
$material[] = pack('n', OpenPGP::bitlength($data));
|
||||
$material[] = $data;
|
||||
}
|
||||
return $this->fingerprint = sha1(implode('', $material));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 OpenPGP_PublicSubkeyPacket extends OpenPGP_PublicKeyPacket {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 OpenPGP_SecretKeyPacket extends OpenPGP_PublicKeyPacket {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 OpenPGP_SecretSubkeyPacket extends OpenPGP_SecretKeyPacket {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Compressed Data packet (tag 8).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.6
|
||||
*/
|
||||
class OpenPGP_CompressedDataPacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Symmetrically Encrypted Data packet (tag 9).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.7
|
||||
*/
|
||||
class OpenPGP_EncryptedDataPacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Marker packet (tag 10).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.8
|
||||
*/
|
||||
class OpenPGP_MarkerPacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Literal Data packet (tag 11).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.9
|
||||
*/
|
||||
class OpenPGP_LiteralDataPacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Trust packet (tag 12).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.10
|
||||
*/
|
||||
class OpenPGP_TrustPacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP User ID packet (tag 13).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.11
|
||||
* @see http://tools.ietf.org/html/rfc2822
|
||||
*/
|
||||
class OpenPGP_UserIDPacket extends OpenPGP_Packet {
|
||||
public $name, $comment, $email;
|
||||
|
||||
function read() {
|
||||
$this->text = $this->input;
|
||||
// User IDs of the form: "name (comment) <email>"
|
||||
if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->text, $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->text, $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->text, $matches)) {
|
||||
$this->name = trim($matches[1]);
|
||||
$this->comment = NULL;
|
||||
$this->email = NULL;
|
||||
}
|
||||
// User IDs of the form: "<email>"
|
||||
else if (preg_match('/^<([^>]+)>$/', $this->text, $matches)) {
|
||||
$this->name = NULL;
|
||||
$this->comment = NULL;
|
||||
$this->email = trim($matches[2]);
|
||||
}
|
||||
}
|
||||
|
||||
function __toString() {
|
||||
$text = array();
|
||||
if ($this->name) { $text[] = $this->name; }
|
||||
if ($this->comment) { $text[] = "({$this->comment})"; }
|
||||
if ($this->email) { $text[] = "<{$this->email}>"; }
|
||||
return implode(' ', $text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 OpenPGP_UserAttributePacket extends OpenPGP_Packet {
|
||||
public $packets;
|
||||
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Sym. Encrypted Integrity Protected Data packet (tag 18).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.13
|
||||
*/
|
||||
class OpenPGP_IntegrityProtectedDataPacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Modification Detection Code packet (tag 19).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-5.14
|
||||
*/
|
||||
class OpenPGP_ModificationDetectionCodePacket extends OpenPGP_Packet {
|
||||
// TODO
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenPGP Private or Experimental packet (tags 60..63).
|
||||
*
|
||||
* @see http://tools.ietf.org/html/rfc4880#section-4.3
|
||||
*/
|
||||
class OpenPGP_ExperimentalPacket extends OpenPGP_Packet {}
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
if(function_exists('mcrypt_encrypt') && defined('MCRYPT_MODE_CFB')) {
|
||||
class MCryptWrapper {
|
||||
public $cipher, $key, $iv, $key_size, $block_size;
|
||||
|
||||
|
||||
function __construct($cipher) {
|
||||
$this->cipher = $cipher;
|
||||
$this->key_size = mcrypt_module_get_algo_key_size($cipher);
|
||||
$this->block_size = mcrypt_module_get_algo_block_size($cipher);
|
||||
$this->iv = str_repeat("\0", mcrypt_get_iv_size($cipher, 'ncfb'));
|
||||
}
|
||||
|
||||
function setKey($key) {
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
function setIV($iv) {
|
||||
$this->iv = $iv;
|
||||
}
|
||||
|
||||
function encrypt($data) {
|
||||
return mcrypt_encrypt($this->cipher, $this->key, $data, 'ncfb', $this->iv);
|
||||
}
|
||||
|
||||
function decrypt($data) {
|
||||
return mcrypt_decrypt($this->cipher, $this->key, $data, 'ncfb', $this->iv);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
if(function_exists('openssl_encrypt')) {
|
||||
class OpenSSLWrapper {
|
||||
public $cipher, $key, $iv, $key_size, $block_size;
|
||||
|
||||
|
||||
function __construct($cipher) {
|
||||
if($cipher != "CAST5-CFB") throw Exception("OpenSSLWrapper is only used for CAST5 right now");
|
||||
|
||||
$this->cipher = $cipher;
|
||||
$this->key_size = 16;
|
||||
$this->block_size = 8;
|
||||
$this->iv = str_repeat("\0", 8);
|
||||
}
|
||||
|
||||
function setKey($key) {
|
||||
$this->key = $key;
|
||||
}
|
||||
|
||||
function setIV($iv) {
|
||||
$this->iv = $iv;
|
||||
}
|
||||
|
||||
function encrypt($data) {
|
||||
return openssl_encrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $this->iv);
|
||||
}
|
||||
|
||||
function decrypt($data) {
|
||||
return openssl_decrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $this->iv);
|
||||
}
|
||||
}
|
||||
}
|
27
phpunit.xml
27
phpunit.xml
@ -1,27 +0,0 @@
|
||||
<phpunit bootstrap="tests/bootstrap.php">
|
||||
<testsuites>
|
||||
<testsuite name="Serialization">
|
||||
<file>tests/suite.php</file>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="Fingerprint">
|
||||
<file>tests/suite.php</file>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="MessageVerification">
|
||||
<file>tests/phpseclib_suite.php</file>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="KeyVerification">
|
||||
<file>tests/phpseclib_suite.php</file>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="Decryption">
|
||||
<file>tests/phpseclib_suite.php</file>
|
||||
</testsuite>
|
||||
|
||||
<testsuite name="Encryption">
|
||||
<file>tests/phpseclib_suite.php</file>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
@ -1,2 +0,0 @@
|
||||
<?php
|
||||
@include_once dirname(__FILE__) . '/../vendor/autoload.php';
|
Binary file not shown.
@ -1 +0,0 @@
|
||||
´$Test Key (RSA) <testkey@example.org>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
´$Test Key (DSA) <testkey@example.com>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@
|
||||
´+Test Key (DSA sign-only) <test@example.net>
|
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user