Compare commits

...

37 Commits

Author SHA1 Message Date
Deon George
358f28273c Updates to pass unit testing 2020-06-18 22:03:56 +10:00
Deon George
7d259b251f Move to more PSR-4 standards. 2020-06-17 22:44:51 +10:00
Deon George
d458fecff6 Renamed to leenooks/openpgp-php for composer 2020-06-04 21:28:48 +10:00
Stephen Paul Weber
8ab0a21c68
Merge branch 'encryptSecretKey'
* encryptSecretKey:
  Implement encryptSecretKey
2019-09-14 13:15:08 -05:00
Stephen Paul Weber
d27d30a352
Implement encryptSecretKey
Allow encrypting a decrypted secret key, which is especially useful for
generating a new encrypted secret key.  Defaults to AES256, S2K
iter+salt SHA512, always uses s2k_useage 254 with sha1 integrity
protection of the encrypted key material.

Also add an example to parallel keygen.php that generates a key and then
encrypts it with a passphrase.
2019-09-10 21:31:06 -05:00
Stephen Paul Weber
67aba78699
Bump to 0.4.0 2019-08-01 13:11:09 -05:00
Stephen Paul Weber
c0e6aeb163
Remove support for hhvm
Closes #79
2019-04-30 19:05:28 -05:00
Stephen Paul Weber
5e6a097677
Merge pull request #76 from meitar/docs
Provide more guidance for understanding the examples.
2019-03-28 21:19:25 -05:00
Meitar M
f8e0e997fc
Provide more guidance for understanding examples, update .travis.yml.
This commit adds an `example/README.md` file with a little bit of
guidance for running the examples themselves. This is helpful because
the examples all rely on the presence of a `phpseclib` installation
available to the PHP interpreter, and while there is a `composer.json`
file to this effect, none of the examples included the Composer
`autoload.php` file.

This commit makes no modifications to the example code itself, but does
`include_once()` the Composer autoload script so that `phpseclib` loads
and avoids causing a fatal error when a new user attempts to run the
examples to learn how to use the library.

This commit also updates the Travis `before_script` build script, dropping
the `--dev` argument to the `composer install` command. Current versions
of Composer emit a deprecation notice when `--dev` is passed.
2018-12-20 13:42:15 -07:00
Stephen Paul Weber
fb671e183d Wordwrap enarmor output
Closes #74
2018-11-20 20:22:24 -05:00
Stephen Paul Weber
f43fbdc053 Put version into code
In case anyone wants to check it, I guess?

Closes #12
2018-07-25 20:18:44 -05:00
Stephen Paul Weber
cd33ba1af1 Upstream isn't coming back 2018-07-25 19:26:57 -05:00
Stephen Paul Weber
34ffc765ec
Merge pull request #62 from Rotzbua/patch-2
add travis build status to readme
2018-07-25 19:13:50 -05:00
Stephen Paul Weber
752d80f14a S2K salt is *always* 8 bytes
Closes #33
2018-07-25 15:04:49 -05:00
Stephen Paul Weber
575baaf3f2 Set up Travis to check combinations that all work 2018-07-25 14:45:16 -05:00
Stephen Paul Weber
aeb919abc3 Whitespace 2018-07-25 14:37:07 -05:00
Stephen Paul Weber
cba1ecce8a Do not rely on asserts for behaviour
Closes #35
2018-07-25 14:35:51 -05:00
Stephen Paul Weber
f2e1710da5 Tell composer what versions of PHP we test against
Closes #61
2018-07-25 14:16:18 -05:00
Stephen Paul Weber
44e1bb2902 Do not throw when CAST5 unsupported
While this message may be more helpful, it will break some cases, such
as when there are multiple ciphers that could be used and we can just
skip CAST5 and move on.  Return NULL when CAST5 unsupported, just like
for other unsupported ciphers.
2018-07-25 14:08:00 -05:00
Stephen Paul Weber
43497a15c0 Use OpenSSL for CAST5
Mcrypt is deprecated, so use OpenSSL when we can, mcrypt when we can't.
2018-07-25 14:08:00 -05:00
Stephen Paul Weber
5a6b605710 Support Twofish and Blowfish 2018-07-25 14:07:16 -05:00
Stephen Paul Weber
d756110821 Test support for all newer versions of phpseclib
It seems only 2.0.8 is broken
2018-07-25 11:06:11 -05:00
Stephen Paul Weber
724d5b16f3 Newer phpunit can't support older PHP
They're very old, and 5.6 is available in Debian stable and oldstable,
so drop support.
2018-07-25 10:31:52 -05:00
Stephen Paul Weber
26560f7bca Upgrade phpunit version to use in Travis 2018-07-25 10:27:30 -05:00
Stephen Paul Weber
498e60602b If session decryption fails, return NULL
Otherwise it returns false, we try to unpack that, and generally bad
things happen.
2018-07-25 09:57:33 -05:00
Stephen Paul Weber
413741fa84 Throw more helpful exception when already decrypted 2018-07-25 09:56:57 -05:00
Stephen Paul Weber
a9fc3f9322
Merge pull request #64 from Rotzbua/Rotzbua-patch-2
add suggestion to composer
2018-07-25 09:00:09 -05:00
Stephen Paul Weber
facaaa4dbb
Merge pull request #63 from Rotzbua/Rotzbua-patch-1
add php 7.1 7.2 to travis
2017-12-26 18:11:29 -05:00
Rotzbua
ba3c3fd42d
add suggestion to composer 2017-12-26 21:53:31 +01:00
Rotzbua
ebce9c014c
add travis build status to reamde 2017-12-15 16:37:53 +01:00
Rotzbua
f42afa0ca3
Update .travis.yml 2017-12-15 16:35:33 +01:00
Rotzbua
e8a56241a5
Update .travis.yml 2017-12-15 16:30:18 +01:00
Rotzbua
69d935435c
add php 7.1 7.2 2017-12-15 16:10:39 +01:00
Stephen Paul Weber
541328576a
Merge pull request #60 from Rotzbua/patch-1
[doc] change links to https
2017-12-15 09:28:05 -05:00
Rotzbua
a8e7690a69
change links to https 2017-12-15 15:23:00 +01:00
Stephen Paul Weber
c9ae8251b6 New list of supported PHPs on Travis 2017-07-22 17:03:04 -05:00
Stephen Paul Weber
95facfb57f Travis wants trusty for HHVM now 2017-07-18 19:35:20 -05:00
73 changed files with 3700 additions and 4407 deletions

View File

@ -1,18 +1,51 @@
---
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
- hhvm
# - nightly
- 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"
before_script: 'sed -i "s/\"phpseclib\/phpseclib\": \"[^\"]*/\"phpseclib\/phpseclib\": \"$PHPSECLIB/" composer.json && composer install --prefer-source --dev'
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'

View File

@ -1,2 +0,0 @@
* Arto Bendiken <arto.bendiken@gmail.com>
* Stephen Paul Weber <singpolyma@singpolyma.net>

1890
Doxyfile

File diff suppressed because it is too large Load Diff

1
README
View File

@ -1 +0,0 @@
README.md

View File

@ -1,11 +1,16 @@
[![Build Status](https://travis-ci.org/singpolyma/openpgp-php.svg?branch=master)](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).
* <http://github.com/bendiken/openpgp-php>
This repository is forked from singpolyma/openpgp-php, and was only created to learn and understand the code.
### About OpenPGP
* <https://github.com/singpolyma/openpgp-php>
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
@ -13,8 +18,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.
* <http://tools.ietf.org/html/rfc4880>
* <http://www.openpgp.org/>
* <https://tools.ietf.org/html/rfc4880>
* <https://www.openpgp.org/>
Features
--------
@ -22,15 +27,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 using Crypt_RSA from <http://phpseclib.sourceforge.net>
* Helper class for encrypting and decrypting messages and keys using Crypt_AES and Crypt_TripleDES from <http://phpseclib.sourceforge.net>
* 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://drupal.org/project/openpgp>
* <https://wordpress.org/plugins/wp-pgp-encrypted-emails/>
Download
@ -38,21 +54,21 @@ Download
To get a local working copy of the development repository, do:
% git clone git://github.com/bendiken/openpgp-php.git
git clone https://github.com/singpolyma/openpgp-php.git
Alternatively, you can download the latest development version as a tarball
as follows:
% wget http://github.com/bendiken/openpgp-php/tarball/master
wget https://github.com/singpolyma/openpgp-php/tarball/master
Authors
-------
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) - <http://ar.to/>
* [Stephen Paul Weber](mailto:singpolyma@singpolyma.net) - <http://singpolyma.net/>
* [Arto Bendiken](mailto:arto.bendiken@gmail.com) (Original author) - <http://ar.to/>
* [Stephen Paul Weber](mailto:singpolyma@singpolyma.net) (Maintainer) - <https://singpolyma.net/>
License
-------
OpenPGP.php is free and unencumbered public domain software. For more
information, see <http://unlicense.org/> or the accompanying UNLICENSE file.
information, see <https://unlicense.org/> or the accompanying UNLICENSE file.

View File

@ -1 +0,0 @@
0.3.0

View File

@ -1,5 +1,5 @@
{
"name": "singpolyma/openpgp-php",
"name": "leenooks/openpgp-php",
"description": "Pure-PHP implementation of the OpenPGP Message Format (RFC 4880)",
"license": "Unlicense",
"authors": [
@ -10,13 +10,22 @@
{
"name": "Stephen Paul Weber",
"email": "singpolyma@singpolyma.net"
},
{
"name": "Deon George",
"email": "deon@leenooks.net"
}
],
"require": {
"phpseclib/phpseclib": ">=2.0.0 <=2.0.4"
"php": "^5.6 || ^7.0",
"phpseclib/phpseclib": "^2.0 !=2.0.8"
},
"require-dev": {
"phpunit/phpunit": "~4.0"
"symfony/var-dumper": "^5.0",
"phpunit/phpunit": "^8.0"
},
"suggest": {
"ext-mcrypt": "required if you use encryption cast5"
},
"autoload": {
"classmap": ["lib/"]

22
examples/README.md Normal file
View File

@ -0,0 +1,22 @@
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
```

View File

@ -1,5 +1,6 @@
<?php
@include_once dirname(__FILE__).'/../vendor/autoload.php';
require_once dirname(__FILE__).'/../lib/openpgp.php';
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
@ -27,5 +28,3 @@ echo "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n";
// trailing whitespace to lines.
echo preg_replace("/^-/", "- -", $packets[0]->data)."\n";
echo OpenPGP::enarmor($packets[1][0]->to_bytes(), "PGP SIGNATURE");
?>

View File

@ -3,6 +3,7 @@
// 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';

View File

@ -1,5 +1,6 @@
<?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';

View File

@ -1,5 +1,6 @@
<?php
@include_once dirname(__FILE__).'/../vendor/autoload.php';
require_once dirname(__FILE__).'/../lib/openpgp.php';
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';

View File

@ -0,0 +1,28 @@
<?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();

View File

@ -1,5 +1,6 @@
<?php
@include_once dirname(__FILE__).'/../vendor/autoload.php';
require_once dirname(__FILE__).'/../lib/openpgp.php';
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
@ -18,5 +19,3 @@ $m = $sign->sign($data);
/* Output the raw message bytes to STDOUT */
echo $m->to_bytes();
?>

View File

@ -1,5 +1,6 @@
<?php
@include_once dirname(__FILE__).'/../vendor/autoload.php';
require_once dirname(__FILE__).'/../lib/openpgp.php';
require_once dirname(__FILE__).'/../lib/openpgp_crypt_rsa.php';
@ -14,5 +15,3 @@ $verify = new OpenPGP_Crypt_RSA($wkey);
/* Dump verification information to STDOUT */
var_dump($verify->verify($m));
?>

173
lib/OpenPGP.php Normal file
View File

@ -0,0 +1,173 @@
<?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));
}
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Public-Key Encrypted Session Key packet (tag 1).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.1
*/
class AsymmetricSessionKeyPacket extends Packet
{
protected $tag = 1;
public $version,$keyid,$key_algorithm,$encrypted_data;
function __construct($key_algorithm='',$keyid='',$encrypted_data='',$version=3)
{
parent::__construct();
$this->version = $version;
$this->keyid = substr($keyid,-16);
$this->key_algorithm = $key_algorithm;
$this->encrypted_data = $encrypted_data;
}
function read()
{
switch ($this->version = ord($this->read_byte())) {
case 3:
$rawkeyid = $this->read_bytes(8);
$this->keyid = '';
// Store KeyID in Hex
for ($i=0;$i<strlen($rawkeyid);$i++) {
$this->keyid .= sprintf('%02X',ord($rawkeyid[$i]));
}
$this->key_algorithm = ord($this->read_byte());
$this->encrypted_data = $this->input;
break;
default:
throw new Exception("Unsupported AsymmetricSessionKeyPacket version: ".$this->version);
}
}
function body()
{
$bytes = chr($this->version);
for ($i=0;$i<strlen($this->keyid);$i+= 2) {
$bytes .= chr(hexdec($this->keyid[$i].$this->keyid[$i+1]));
}
$bytes .= chr($this->key_algorithm);
$bytes .= $this->encrypted_data;
return $bytes;
}
}

View File

@ -0,0 +1,102 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Compressed Data packet (tag 8).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.6
*/
class CompressedDataPacket extends Packet implements \IteratorAggregate, \ArrayAccess
{
protected $tag = 8;
public $algorithm;
/* see http://tools.ietf.org/html/rfc4880#section-9.3 */
static $algorithms = [
0 => 'Uncompressed',
1 => 'ZIP',
2 => 'ZLIB',
3 => 'BZip2',
];
// IteratorAggregate interface
function getIterator()
{
return new \ArrayIterator($this->data->packets);
}
// ArrayAccess interface
function offsetExists($offset) {
return isset($this->data[$offset]);
}
function offsetGet($offset) {
return $this->data[$offset];
}
function offsetSet($offset, $value) {
return is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value;
}
function offsetUnset($offset) {
unset($this->data[$offset]);
}
function body()
{
$body = chr($this->algorithm);
switch($this->algorithm) {
case 0:
$body .= $this->data->to_bytes();
break;
case 1:
$body .= gzdeflate($this->data->to_bytes());
break;
case 2:
$body .= gzcompress($this->data->to_bytes());
break;
case 3:
$body .= bzcompress($this->data->to_bytes());
break;
default:
/* @todo error? */
}
return $body;
}
function read()
{
$this->algorithm = ord($this->read_byte());
$this->data = $this->read_bytes($this->length);
switch($this->algorithm) {
case 0:
$this->data = 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? */
}
}
}

358
lib/OpenPgP/Crypt/RSA.php Normal file
View File

@ -0,0 +1,358 @@
<?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);
}
}

View File

@ -0,0 +1,327 @@
<?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;
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Symmetrically Encrypted Data packet (tag 9).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.7
*/
class EncryptedDataPacket extends Packet
{
protected $tag = 9;
function read()
{
$this->data = $this->input;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Leenooks\OpenPGP\Exceptions;
class PacketTagException extends \Exception
{
protected $message = 'Packet class missing tag value';
public function __construct($message, $code = 0, Exception $previous = null)
{
if ($message)
$this->message = $message;
}
}

View File

@ -0,0 +1,11 @@
<?php
/**
* OpenPGP Private or Experimental packet (tags 60..63).
*
* @see http://tools.ietf.org/html/rfc4880#section-4.3
*/
class ExperimentalPacket extends Packet
{
protected $tag = 60;
}

View File

@ -0,0 +1,31 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Sym. Encrypted Integrity Protected Data packet (tag 18).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.13
*/
class IntegrityProtectedDataPacket extends EncryptedDataPacket
{
protected $tag = 18;
public $version;
function __construct($data='',$version=1) {
parent::__construct($data);
$this->version = $version;
}
function body()
{
return chr($this->version).$this->data;
}
function read()
{
$this->version = ord($this->read_byte());
$this->data = $this->input;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Literal Data packet (tag 11).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.9
*/
class LiteralDataPacket extends Packet
{
protected $tag = 11;
public $format,$filename,$timestamp;
function __construct($data=NULL, $opt=array())
{
parent::__construct($data);
$this->format = isset($opt['format']) ? $opt['format'] : 'b';
$this->filename = isset($opt['filename']) ? $opt['filename'] : 'data';
$this->timestamp = isset($opt['timestamp']) ? $opt['timestamp'] : time();
}
function body()
{
return $this->format.chr(strlen($this->filename)).$this->filename.pack('N', $this->timestamp).$this->data;
}
function normalize($clearsign=false)
{
if ($clearsign && ($this->format != 'u' && $this->format != 't')) {
$this->format = 'u'; // Clearsign must be text
}
if ($this->format == 'u' || $this->format == 't') { // Normalize line endings
$this->data = str_replace("\n", "\r\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $this->data)));
}
if ($clearsign) {
// When clearsigning, do not sign over trailing whitespace
$this->data = preg_replace('/\s+\r/', "\r", $this->data);
}
}
function read()
{
$this->size = $this->length - 1 - 4;
$this->format = $this->read_byte();
$filename_length = ord($this->read_byte());
$this->size -= $filename_length;
$this->filename = $this->read_bytes($filename_length);
$this->timestamp = $this->read_timestamp();
$this->data = $this->read_bytes($this->size);
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Marker packet (tag 10).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.8
*/
class MarkerPacket extends Packet
{
// TODO
protected $tag = 10;
}

256
lib/OpenPgP/Message.php Normal file
View File

@ -0,0 +1,256 @@
<?php
namespace Leenooks\OpenPGP;
/**
* @see http://tools.ietf.org/html/rfc4880#section-4.1
* @see http://tools.ietf.org/html/rfc4880#section-11
* @see http://tools.ietf.org/html/rfc4880#section-11.3
*/
class Message implements \IteratorAggregate,\ArrayAccess
{
protected $packets;
protected $uri = NULL;
// IteratorAggregate interface
function getIterator()
{
return new \ArrayIterator($this->packets);
}
// ArrayAccess interface
function offsetExists($offset)
{
return isset($this->packets[$offset]);
}
function offsetGet($offset)
{
return $this->packets[$offset];
}
function offsetSet($offset,$value)
{
return is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value;
}
function offsetUnset($offset)
{
unset($this->packets[$offset]);
}
// Class
function __construct(array $packets=[])
{
$this->packets = $packets;
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-4.1
* @see http://tools.ietf.org/html/rfc4880#section-4.2
*/
static function parse($input): self
{
if (is_resource($input)) {
return self::parse_stream($input);
}
if (is_string($input)) {
return self::parse_string($input);
}
}
static function parse_file($path): self
{
if (($msg=self::parse(file_get_contents($path)))) {
$msg->uri = preg_match('!^[\w\d]+://!',$path) ? $path : 'file://'.realpath($path);
return $msg;
}
}
static function parse_stream($input): self
{
return self::parse_string(stream_get_contents($input));
}
static function parse_string($input): self
{
$msg = new self;
while (($length=strlen($input)) > 0) {
if (($packet=Packet::parse($input))) {
$msg[] = $packet;
}
// is parsing stuck?
if ($length == strlen($input)) {
break;
}
}
return $msg;
}
/**
* Extract signed objects from a well-formatted message
*
* Recurses into CompressedDataPacket
*
* @see http://tools.ietf.org/html/rfc4880#section-11
*/
public function signatures(): array
{
$msg = $this;
$key = NULL;
$userid = NULL;
$subkey = NULL;
$sigs = [];
$final_sigs = [];
while ($msg[0] instanceof CompressedDataPacket)
$msg = $msg[0]->data;
foreach ($msg as $idx => $p) {
if ($p instanceof LiteralDataPacket) {
return [
[
$p,
array_values(array_filter($msg->packets,function($p)
{
return $p instanceof SignaturePacket;
}))
]
];
} elseif ($p instanceof PublicSubkeyPacket || $p instanceof SecretSubkeyPacket) {
if ($userid) {
array_push($final_sigs,[$key,$userid,$sigs]);
$userid = NULL;
} elseif ($subkey) {
array_push($final_sigs,[$key,$subkey,$sigs]);
$key = NULL;
}
$sigs = [];
$subkey = $p;
} elseif ($p instanceof PublicKeyPacket) {
if ($userid) {
array_push($final_sigs,[$key,$userid,$sigs]);
$userid = NULL;
} elseif ($subkey) {
array_push($final_sigs,[$key,$subkey,$sigs]);
$subkey = NULL;
} elseif ($key) {
array_push($final_sigs,[$key,$sigs]);
$key = NULL;
}
$sigs = [];
$key = $p;
} elseif ($p instanceof UserIDPacket) {
if ($userid) {
array_push($final_sigs,[$key,$userid,$sigs]);
$userid = NULL;
} elseif ($key) {
array_push($final_sigs,[$key,$sigs]);
}
$sigs = [];
$userid = $p;
} elseif ($p instanceof SignaturePacket) {
$sigs[] = $p;
}
}
if ($userid) {
array_push($final_sigs,[$key,$userid,$sigs]);
} elseif ($subkey) {
array_push($final_sigs,[$key,$subkey,$sigs]);
} elseif ($key) {
array_push($final_sigs,[$key,$sigs]);
}
return $final_sigs;
}
public function to_bytes(): string
{
$bytes = '';
foreach ($this as $p) {
$bytes .= $p->to_bytes();
}
return $bytes;
}
/**
* Function to extract verified signatures
*
* $verifiers is an array of callbacks formatted like array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: raw message and signature packet
*/
function verified_signatures($verifiers): array
{
$signed = $this->signatures();
$vsigned = [];
foreach ($signed as $sign) {
$signatures = array_pop($sign);
$vsigs = [];
foreach ($signatures as $sig) {
$verifier = $verifiers[$sig->key_algorithm_name()][$sig->hash_algorithm_name()];
if ($verifier && $this->verify_one($verifier,$sign,$sig)) {
$vsigs[] = $sig;
}
}
array_push($sign,$vsigs);
$vsigned[] = $sign;
}
return $vsigned;
}
function verify_one($verifier,$sign,$sig)
{
if ($sign[0] instanceof LiteralDataPacket) {
$sign[0]->normalize();
$raw = $sign[0]->data;
} elseif (isset($sign[1]) && $sign[1] instanceof UserIDPacket) {
$raw = implode(
'',
array_merge(
$sign[0]->fingerprint_material(),
array(chr(0xB4),pack('N',strlen($sign[1]->body())),$sign[1]->body())
));
} elseif (isset($sign[1]) && ($sign[1] instanceof PublicSubkeyPacket || $sign[1] instanceof SecretSubkeyPacket)) {
$raw = implode('',array_merge($sign[0]->fingerprint_material(),$sign[1]->fingerprint_material()));
} elseif ($sign[0] instanceof PublicKeyPacket) {
$raw = implode('',$sign[0]->fingerprint_material());
} else {
return NULL;
}
return call_user_func($verifier,$raw.$sig->trailer,$sig);
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Modification Detection Code packet (tag 19).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.14
*/
class ModificationDetectionCodePacket extends Packet
{
protected $tag = 19;
function header_and_body(): array
{
// Get body first, we will need it's length
$body = $this->body();
if (strlen($body) != 20)
throw new Exception("Bad ModificationDetectionCodePacket");
return ['header'=>"\xD3\x14",'body'=>$body];
}
function read()
{
$this->data = $this->input;
if (strlen($this->input) != 20)
throw new Exception("Bad ModificationDetectionCodePacket");
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP One-Pass Signature packet (tag 4).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.4
*/
class OnePassSignaturePacket extends Packet
{
protected $tag = 4;
public $version, $signature_type, $hash_algorithm, $key_algorithm, $key_id, $nested;
function body()
{
$body = chr($this->version).chr($this->signature_type).chr($this->hash_algorithm).chr($this->key_algorithm);
for($i = 0; $i < strlen($this->key_id); $i += 2) {
$body .= chr(hexdec($this->key_id[$i].$this->key_id[$i+1]));
}
$body .= chr((int)$this->nested);
return $body;
}
function read()
{
$this->version = ord($this->read_byte());
$this->signature_type = ord($this->read_byte());
$this->hash_algorithm = ord($this->read_byte());
$this->key_algorithm = ord($this->read_byte());
// Store KeyID in Hex
for ($i=0;$i<8;$i++) {
$this->key_id .= sprintf('%02X',ord($this->read_byte()));
}
$this->nested = ord($this->read_byte());
}
}

279
lib/OpenPgP/Packet.php Normal file
View File

@ -0,0 +1,279 @@
<?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;
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace Leenooks\OpenPGP;
use Leenooks\OpenPGP;
/**
* OpenPGP Public-Key packet (tag 6).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.5.1.1
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
* @see http://tools.ietf.org/html/rfc4880#section-11.1
* @see http://tools.ietf.org/html/rfc4880#section-12
*/
class PublicKeyPacket extends Packet
{
protected $tag = 6;
public $version, $timestamp, $algorithm;
public $key, $key_id, $fingerprint;
public $v3_days_of_validity;
static $key_fields = array(
1 => array('n', 'e'), // RSA
16 => array('p', 'g', 'y'), // ELG-E
17 => array('p', 'q', 'g', 'y'), // DSA
);
static $algorithms = [
1 => 'RSA',
2 => 'RSA',
3 => 'RSA',
16 => 'ELGAMAL',
17 => 'DSA',
18 => 'ECC',
19 => 'ECDSA',
21 => 'DH'
];
function __construct($key=[],$algorithm='RSA',$timestamp=NULL,$version=4)
{
parent::__construct();
if ($key instanceof PublicKeyPacket) {
$this->algorithm = $key->algorithm;
$this->key = array();
// Restrict to only the fields we need
foreach (self::$key_fields[$this->algorithm] as $field) {
$this->key[$field] = $key->key[$field];
}
$this->key_id = $key->key_id;
$this->fingerprint = $key->fingerprint;
$this->timestamp = $key->timestamp;
$this->version = $key->version;
$this->v3_days_of_validity = $key->v3_days_of_validity;
} else {
$this->key = $key;
if (is_string($this->algorithm = $algorithm)) {
$this->algorithm = array_search($this->algorithm,self::$algorithms);
}
$this->timestamp = $timestamp ? $timestamp : time();
$this->version = $version;
if (count($this->key) > 0) {
$this->key_id = substr($this->fingerprint(),-8);
}
}
}
function body()
{
switch ($this->version) {
case 2:
case 3:
return implode('', array_merge(array(
chr($this->version) . pack('N', $this->timestamp) .
pack('n', $this->v3_days_of_validity) . chr($this->algorithm)
), $this->fingerprint_material())
);
case 4:
return implode('', array_slice($this->fingerprint_material(), 2));
}
}
// Find expiry time of this key based on the self signatures in a message
function expires($message)
{
foreach ($this->self_signatures($message) as $p) {
foreach (array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) {
if ($s instanceof SignaturePacket\KeyExpirationTimePacket) {
return $this->timestamp + $s->data;
}
}
}
// Never expires
return NULL;
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-12.2
* @see http://tools.ietf.org/html/rfc4880#section-3.3
*/
function fingerprint()
{
switch ($this->version) {
case 2:
case 3:
return $this->fingerprint = strtoupper(md5(implode('', $this->fingerprint_material())));
case 4:
return $this->fingerprint = strtoupper(sha1(implode('', $this->fingerprint_material())));
}
}
function fingerprint_material()
{
switch ($this->version) {
case 3:
$material = array();
foreach (self::$key_fields[$this->algorithm] as $i) {
$material[] = pack('n', OpenPGP::bitlength($this->key[$i]));
$material[] = $this->key[$i];
}
return $material;
case 4:
$head = array(
chr(0x99), NULL,
chr($this->version), pack('N', $this->timestamp),
chr($this->algorithm),
);
$material = [];
foreach (self::$key_fields[$this->algorithm] as $i) {
$material[] = pack('n', OpenPGP::bitlength($this->key[$i]));
$material[] = $this->key[$i];
}
$material = implode('', $material);
$head[1] = pack('n', 6 + strlen($material));
$head[] = $material;
return $head;
}
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
*/
function read()
{
switch ($this->version = ord($this->read_byte())) {
case 3:
$this->timestamp = $this->read_timestamp();
$this->v3_days_of_validity = $this->read_unpacked(2, 'n');
$this->algorithm = ord($this->read_byte());
$this->read_key_material();
break;
case 4:
$this->timestamp = $this->read_timestamp();
$this->algorithm = ord($this->read_byte());
$this->read_key_material();
break;
}
}
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
*/
function read_key_material()
{
foreach (self::$key_fields[$this->algorithm] as $field) {
$this->key[$field] = $this->read_mpi();
}
$this->key_id = substr($this->fingerprint(), -8);
}
// Find self signatures in a message, these often contain metadata about the key
function self_signatures($message)
{
$sigs = [];
$keyid16 = strtoupper(substr($this->fingerprint,-16));
foreach ($message as $p) {
if ($p instanceof OpenPGP\SignaturePacket) {
if (strtoupper($p->issuer()) == $keyid16) {
$sigs[] = $p;
} else {
foreach (array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) {
if ($s instanceof SignaturePacket\EmbeddedSignaturePacket && strtoupper($s->issuer()) == $keyid16) {
$sigs[] = $p;
break;
}
}
}
// After we've seen a self sig, the next non-sig stop all self-sigs
} elseif (count($sigs))
break;
}
return $sigs;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Public-Subkey packet (tag 14).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.5.1.2
* @see http://tools.ietf.org/html/rfc4880#section-5.5.2
* @see http://tools.ietf.org/html/rfc4880#section-11.1
* @see http://tools.ietf.org/html/rfc4880#section-12
*/
class PublicSubkeyPacket extends PublicKeyPacket
{
// TODO
protected $tag = 14;
}

116
lib/OpenPgP/S2K.php Normal file
View File

@ -0,0 +1,116 @@
<?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);
}
}
}

View File

@ -0,0 +1,80 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Secret-Key packet (tag 5).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.5.1.3
* @see http://tools.ietf.org/html/rfc4880#section-5.5.3
* @see http://tools.ietf.org/html/rfc4880#section-11.2
* @see http://tools.ietf.org/html/rfc4880#section-12
*/
class SecretKeyPacket extends PublicKeyPacket
{
protected $tag = 5;
public $s2k_useage, $s2k, $symmetric_algorithm, $private_hash, $encrypted_data;
static $secret_key_fields = array(
1 => array('d', 'p', 'q', 'u'), // RSA
2 => array('d', 'p', 'q', 'u'), // RSA-E
3 => array('d', 'p', 'q', 'u'), // RSA-S
16 => array('x'), // ELG-E
17 => array('x'), // DSA
);
function body()
{
$bytes = parent::body() . chr($this->s2k_useage);
$secret_material = NULL;
if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
$bytes .= chr($this->symmetric_algorithm);
$bytes .= $this->s2k->to_bytes();
}
if($this->s2k_useage > 0) {
$bytes .= $this->encrypted_data;
} else {
$secret_material = '';
foreach(self::$secret_key_fields[$this->algorithm] as $f) {
$f = $this->key[$f];
$secret_material .= pack('n', \Leenooks\OpenPGP::bitlength($f));
$secret_material .= $f;
}
$bytes .= $secret_material;
// 2-octet checksum
$chk = 0;
for($i = 0; $i < strlen($secret_material); $i++) {
$chk = ($chk + ord($secret_material[$i])) % 65536;
}
$bytes .= pack('n', $chk);
}
return $bytes;
}
function key_from_input()
{
foreach(self::$secret_key_fields[$this->algorithm] as $field) {
$this->key[$field] = $this->read_mpi();
}
}
function read()
{
parent::read(); // All the fields from PublicKey
$this->s2k_useage = ord($this->read_byte());
if($this->s2k_useage == 255 || $this->s2k_useage == 254) {
$this->symmetric_algorithm = ord($this->read_byte());
$this->s2k = 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?
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Secret-Subkey packet (tag 7).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.5.1.4
* @see http://tools.ietf.org/html/rfc4880#section-5.5.3
* @see http://tools.ietf.org/html/rfc4880#section-11.2
* @see http://tools.ietf.org/html/rfc4880#section-12
*/
class SecretSubkeyPacket extends SecretKeyPacket
{
// TODO
protected $tag = 7;
}

View File

@ -0,0 +1,366 @@
<?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);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
use Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.26
*/
class EmbeddedSignaturePacket extends SignaturePacket
{
protected $tag = 32;
function header_and_body(): array
{
$body = $this->body(); // Get body first, we will need it's length
$size = chr(255).pack('N',strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet
$tag = chr($this->tag);
return ['header'=>$size.$tag,'body'=>$body];
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.11
*/
class ExportableCertificationPacket extends Subpacket
{
protected $tag = 4;
function body()
{
return chr($this->data ? 1 : 0);
}
function read()
{
$this->data = (ord($this->input) != 0);
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.24
* @note Identical functionality to parent
*/
class FeaturesPacket extends KeyFlagsPacket
{
protected $tag = 30;
}

View File

@ -0,0 +1,27 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.5
*/
class IssuerPacket extends Subpacket
{
protected $tag = 16;
function body()
{
$bytes = '';
for($i = 0; $i < strlen($this->data); $i += 2) {
$bytes .= chr(hexdec($this->data[$i].$this->data[$i+1]));
}
return $bytes;
}
function read()
{
for($i = 0; $i < 8; $i++) { // Store KeyID in Hex
$this->data .= sprintf('%02X',ord($this->read_byte()));
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.6
*/
class KeyExpirationTimePacket extends Subpacket
{
protected $tag = 9;
function body()
{
return pack('N', $this->data);
}
function read()
{
$this->data = $this->read_timestamp();
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.21
*/
class KeyFlagsPacket extends Subpacket
{
protected $tag = 27;
function __construct($flags=[])
{
parent::__construct();
$this->flags = $flags;
}
function body()
{
$bytes = '';
foreach($this->flags as $f) {
$bytes .= chr($f);
}
return $bytes;
}
function read()
{
$this->flags = array();
while($this->input) {
$this->flags[] = ord($this->read_byte());
}
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.17
*/
class KeyServerPreferencesPacket extends Subpacket
{
protected $tag = 23;
public $no_modify;
function body()
{
return chr($this->no_modify ? 0x80 : 0x00);
}
function read()
{
$flags = ord($this->input);
$this->no_modify = $flags & 0x80 == 0x80;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.16
*/
class NotationDataPacket extends Subpacket
{
protected $tag = 20;
public $human_readable, $name;
function body()
{
return chr($this->human_readable ? 0x80 : 0x00) . "\0\0\0" .
pack('n', strlen($this->name)) . pack('n', strlen($this->data)) .
$this->name . $this->data;
}
function read()
{
$flags = $this->read_bytes(4);
$namelen = $this->read_unpacked(2, 'n');
$datalen = $this->read_unpacked(2, 'n');
$this->human_readable = ord($flags[0]) & 0x80 == 0x80;
$this->name = $this->read_bytes($namelen);
$this->data = $this->read_bytes($datalen);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.20
*/
class PolicyURIPacket extends Subpacket
{
protected $tag = 26;
function body()
{
return $this->data;
}
function read()
{
$this->data = $this->input;
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.9
*/
class PreferredCompressionAlgorithmsPacket extends Subpacket
{
protected $tag = 22;
function body()
{
$bytes = '';
foreach($this->data as $algo) {
$bytes .= chr($algo);
}
return $bytes;
}
function read()
{
$this->data = array();
while(strlen($this->input) > 0) {
$this->data[] = ord($this->read_byte());
}
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.8
*/
class PreferredHashAlgorithmsPacket extends Subpacket
{
protected $tag = 21;
function body()
{
$bytes = '';
foreach($this->data as $algo) {
$bytes .= chr($algo);
}
return $bytes;
}
function read()
{
$this->data = array();
while(strlen($this->input) > 0) {
$this->data[] = ord($this->read_byte());
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.18
*/
class PreferredKeyServerPacket extends Subpacket
{
protected $tag = 24;
function body()
{
return $this->data;
}
function read()
{
$this->data = $this->input;
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.7
*/
class PreferredSymmetricAlgorithmsPacket extends Subpacket
{
protected $tag = 11;
function body()
{
$bytes = '';
foreach($this->data as $algo) {
$bytes .= chr($algo);
}
return $bytes;
}
function read()
{
$this->data = array();
while(strlen($this->input) > 0) {
$this->data[] = ord($this->read_byte());
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.19
*/
class PrimaryUserIDPacket extends Subpacket
{
protected $tag = 25;
function body()
{
return chr($this->data ? 1 : 0);
}
function read()
{
$this->data = (ord($this->input) != 0);
}
}

View File

@ -0,0 +1,23 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.23
*/
class ReasonforRevocationPacket extends Subpacket
{
protected $tag = 29;
public $code;
function body()
{
return chr($this->code) . $this->data;
}
function read()
{
$this->code = ord($this->read_byte());
$this->data = $this->input;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.14
*/
class RegularExpressionPacket extends Subpacket
{
protected $tag = 6;
function body()
{
return $this->data . chr(0);
}
function read()
{
$this->data = substr($this->input, 0, -1);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.15
*/
class RevocablePacket extends Subpacket
{
protected $tag = 7;
function body()
{
return chr($this->data ? 1 : 0);
}
function read()
{
$this->data = (ord($this->input) != 0);
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.15
*/
class RevocationKeyPacket extends Subpacket
{
protected $tag = 12;
public $key_algorithm, $fingerprint, $sensitive;
function body()
{
$bytes = '';
$bytes .= chr(0x80 | ($this->sensitive ? 0x40 : 0x00));
$bytes .= chr($this->key_algorithm);
for($i = 0; $i < strlen($this->fingerprint); $i += 2) {
$bytes .= chr(hexdec($this->fingerprint[$i].$this->fingerprint[$i+1]));
}
return $bytes;
}
function read()
{
// bitfield must have bit 0x80 set, says the spec
$bitfield = ord($this->read_byte());
$this->sensitive = $bitfield & 0x40 == 0x40;
$this->key_algorithm = ord($this->read_byte());
$this->fingerprint = '';
while(strlen($this->input) > 0) {
$this->fingerprint .= sprintf('%02X',ord($this->read_byte()));
}
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.4
*/
class SignatureCreationTimePacket extends Subpacket
{
protected $tag = 2;
function body()
{
return pack('N',$this->data);
}
function read()
{
$this->data = $this->read_timestamp();
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.6
*/
class SignatureExpirationTimePacket extends Subpacket
{
protected $tag = 3;
function body()
{
return pack('N', $this->data);
}
function read()
{
$this->data = $this->read_timestamp();
}
}

View File

@ -0,0 +1,24 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.25
*/
class SignatureTargetPacket extends Subpacket
{
protected $tag = 31;
public $key_algorithm, $hash_algorithm;
function body()
{
return chr($this->key_algorithm) . chr($this->hash_algorithm) . $this->data;
}
function read()
{
$this->key_algorithm = ord($this->read_byte());
$this->hash_algorithm = ord($this->read_byte());
$this->data = $this->input;
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.22
*/
class SignersUserIDPacket extends Subpacket
{
protected $tag = 28;
function body()
{
return $this->data;
}
function read()
{
$this->data = $this->input;
}
}

View File

@ -0,0 +1,39 @@
<?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;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Leenooks\OpenPGP\SignaturePacket;
/**
* @see http://tools.ietf.org/html/rfc4880#section-5.2.3.13
*/
class TrustSignaturePacket extends Subpacket
{
protected $tag = 5;
function body()
{
return chr($this->depth) . chr($this->trust);
}
function read()
{
$this->depth = ord($this->input{0});
$this->trust = ord($this->input{1});
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Symmetric-Key Encrypted Session Key packet (tag 3).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.3
*/
class SymmetricSessionKeyPacket extends Packet
{
protected $tag = 3;
public $version, $symmetric_algorithm, $s2k, $encrypted_data;
function __construct($s2k=NULL,$encrypted_data='',$symmetric_algorithm=9,$version=3)
{
parent::__construct();
$this->version = $version;
$this->symmetric_algorithm = $symmetric_algorithm;
$this->s2k = $s2k;
$this->encrypted_data = $encrypted_data;
}
function body()
{
return chr($this->version) . chr($this->symmetric_algorithm) .
$this->s2k->to_bytes() . $this->encrypted_data;
}
function read()
{
$this->version = ord($this->read_byte());
$this->symmetric_algorithm = ord($this->read_byte());
$this->s2k = S2k::parse($this->input);
$this->encrypted_data = $this->input;
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP Trust packet (tag 12).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.10
*/
class TrustPacket extends Packet
{
protected $tag = 12;
function read() {
$this->data = $this->input;
}
}

View File

@ -0,0 +1,16 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP User Attribute packet (tag 17).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.12
* @see http://tools.ietf.org/html/rfc4880#section-11.1
*/
class UserAttributePacket extends Packet
{
// TODO
protected $tag = 17;
public $packets;
}

View File

@ -0,0 +1,78 @@
<?php
namespace Leenooks\OpenPGP;
/**
* OpenPGP User ID packet (tag 13).
*
* @see http://tools.ietf.org/html/rfc4880#section-5.11
* @see http://tools.ietf.org/html/rfc2822
*/
class UserIDPacket extends Packet
{
protected $tag = 13;
public $name, $comment, $email;
function __construct($name='',$comment='',$email='')
{
parent::__construct();
if (! $comment && ! $email) {
$this->input = $name;
$this->read();
} else {
$this->name = $name;
$this->comment = $comment;
$this->email = $email;
}
}
function __toString()
{
$text = [];
if ($this->name) { $text[] = $this->name; }
if ($this->comment) { $text[] = "({$this->comment})"; }
if ($this->email) { $text[] = "<{$this->email}>"; }
return implode(' ', $text);
}
function body()
{
return ''.$this; // Convert to string is the body
}
function read()
{
$this->data = $this->input;
// User IDs of the form: "name (comment) <email>"
if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->data, $matches)) {
$this->name = trim($matches[1]);
$this->comment = trim($matches[2]);
$this->email = trim($matches[3]);
}
// User IDs of the form: "name <email>"
else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->data, $matches)) {
$this->name = trim($matches[1]);
$this->comment = NULL;
$this->email = trim($matches[2]);
}
// User IDs of the form: "name"
else if (preg_match('/^([^<]+)$/', $this->data, $matches)) {
$this->name = trim($matches[1]);
$this->comment = NULL;
$this->email = NULL;
}
// User IDs of the form: "<email>"
else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) {
$this->name = NULL;
$this->comment = NULL;
$this->email = trim($matches[2]);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,275 +0,0 @@
<?php
// This is free and unencumbered software released into the public domain.
/**
* OpenPGP_Crypt_RSA.php is a wrapper for using the classes from OpenPGP.php with Crypt_RSA
*
* @package OpenPGP
*/
// From http://phpseclib.sourceforge.net/
use phpseclib\Crypt\RSA as Crypt_RSA;
use phpseclib\Math\BigInteger as Math_BigInteger;
define('CRYPT_RSA_ENCRYPTION_PKCS1', Crypt_RSA::ENCRYPTION_PKCS1);
define('CRYPT_RSA_SIGNATURE_PKCS1', Crypt_RSA::SIGNATURE_PKCS1);
require_once dirname(__FILE__).'/openpgp.php';
@include_once dirname(__FILE__).'/openpgp_crypt_symmetric.php'; /* For encrypt/decrypt */
class OpenPGP_Crypt_RSA {
protected $key, $message;
// Construct a wrapper object from a key or a message packet
function __construct($packet) {
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if($packet instanceof OpenPGP_PublicKeyPacket || $packet[0] instanceof OpenPGP_PublicKeyPacket) { // If it's a key (other keys are subclasses of this one)
$this->key = $packet;
} else {
$this->message = $packet;
}
}
function key($keyid=NULL) {
if(!$this->key) return NULL; // No key
if($this->key instanceof OpenPGP_Message) {
foreach($this->key as $p) {
if($p instanceof OpenPGP_PublicKeyPacket) {
if(!$keyid || strtoupper(substr($p->fingerprint, strlen($keyid)*-1)) == strtoupper($keyid)) return $p;
}
}
}
return $this->key;
}
// Get Crypt_RSA for the public key
function public_key($keyid=NULL) {
return self::convert_public_key($this->key($keyid));
}
// Get Crypt_RSA for the private key
function private_key($keyid=NULL) {
return self::convert_private_key($this->key($keyid));
}
// Pass a message to verify with this key, or a key (OpenPGP or Crypt_RSA) to check this message with
// Second optional parameter to specify which signature to verify (if there is more than one)
function verify($packet) {
$self = $this; // For old PHP
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if(!$this->message) {
$m = $packet;
$verifier = function($m, $s) use($self) {
$key = $self->public_key($s->issuer());
if(!$key) return false;
$key->setHash(strtolower($s->hash_algorithm_name()));
return $key->verify($m, reset($s->data));
};
} else {
if(!($packet instanceof Crypt_RSA)) {
$packet = new self($packet);
}
$m = $this->message;
$verifier = function($m, $s) use($self, $packet) {
if(!($packet instanceof Crypt_RSA)) {
$key = $packet->public_key($s->issuer());
}
if(!$key) return false;
$key->setHash(strtolower($s->hash_algorithm_name()));
return $key->verify($m, reset($s->data));
};
}
return $m->verified_signatures(array('RSA' => array(
'MD5' => $verifier,
'SHA1' => $verifier,
'SHA224' => $verifier,
'SHA256' => $verifier,
'SHA384' => $verifier,
'SHA512' => $verifier
)));
}
// Pass a message to sign with this key, or a secret key to sign this message with
// Second parameter is hash algorithm to use (default SHA256)
// Third parameter is the 16-digit key ID to use... defaults to the key id in the key packet
function sign($packet, $hash='SHA256', $keyid=NULL) {
if(!is_object($packet)) {
if($this->key) {
$packet = new OpenPGP_LiteralDataPacket($packet);
} else {
$packet = OpenPGP_Message::parse($packet);
}
}
if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA
|| ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) {
$key = $packet;
$message = $this->message;
} else {
$key = $this->key;
$message = $packet;
}
if(!$key || !$message) return NULL; // Missing some data
if($message instanceof OpenPGP_Message) {
$sign = $message->signatures();
$message = $sign[0][0];
}
if(!($key instanceof Crypt_RSA)) {
$key = new self($key);
if(!$keyid) $keyid = substr($key->key()->fingerprint, -16, 16);
$key = $key->private_key($keyid);
}
$key->setHash(strtolower($hash));
$sig = new OpenPGP_SignaturePacket($message, 'RSA', strtoupper($hash));
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
$sig->sign_data(array('RSA' => array($hash => function($data) use($key) {return array($key->sign($data));})));
return new OpenPGP_Message(array($sig, $message));
}
/** Pass a message with a key and userid packet to sign */
// TODO: merge this with the normal sign function
function sign_key_userid($packet, $hash='SHA256', $keyid=NULL) {
if(is_array($packet)) {
$packet = new OpenPGP_Message($packet);
} else if(!is_object($packet)) {
$packet = OpenPGP_Message::parse($packet);
}
$key = $this->private_key($keyid);
if(!$key || !$packet) return NULL; // Missing some data
if(!$keyid) $keyid = substr($this->key->fingerprint, -16);
$key->setHash(strtolower($hash));
$sig = NULL;
foreach($packet as $p) {
if($p instanceof OpenPGP_SignaturePacket) $sig = $p;
}
if(!$sig) {
$sig = new OpenPGP_SignaturePacket($packet, 'RSA', strtoupper($hash));
$sig->signature_type = 0x13;
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02));
$sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid);
$packet[] = $sig;
}
$sig->sign_data(array('RSA' => array($hash => function($data) use($key) {return array($key->sign($data));})));
return $packet;
}
function decrypt($packet) {
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA
|| ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) {
$keys = $packet;
$message = $this->message;
} else {
$keys = $this->key;
$message = $packet;
}
if(!$keys || !$message) return NULL; // Missing some data
if(!($keys instanceof Crypt_RSA)) {
$keys = new self($keys);
}
foreach($message as $p) {
if($p instanceof OpenPGP_AsymmetricSessionKeyPacket) {
if($keys instanceof Crypt_RSA) {
$sk = self::try_decrypt_session($keys, substr($p->encrypted_data, 2));
} else if(strlen(str_replace('0', '', $p->keyid)) < 1) {
foreach($keys->key as $k) {
$sk = self::try_decrypt_session(self::convert_private_key($k), substr($p->encrypted_data, 2));
if($sk) break;
}
} else {
$key = $keys->private_key($p->keyid);
$sk = self::try_decrypt_session($key, substr($p->encrypted_data, 2));
}
if(!$sk) continue;
$r = OpenPGP_Crypt_Symmetric::decryptPacket(OpenPGP_Crypt_Symmetric::getEncryptedData($message), $sk[0], $sk[1]);
if($r) return $r;
}
}
return NULL; /* Failed */
}
static function try_decrypt_session($key, $edata) {
$key->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$data = $key->decrypt($edata);
$sk = substr($data, 1, strlen($data)-3);
$chk = unpack('n', substr($data, -2));
$chk = reset($chk);
$sk_chk = 0;
for($i = 0; $i < strlen($sk); $i++) {
$sk_chk = ($sk_chk + ord($sk{$i})) % 65536;
}
if($sk_chk != $chk) return NULL;
return array(ord($data{0}), $sk);
}
static function crypt_rsa_key($mod, $exp, $hash='SHA256') {
$rsa = new Crypt_RSA();
$rsa->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
$rsa->setHash(strtolower($hash));
$rsa->modulus = new Math_BigInteger($mod, 256);
$rsa->k = strlen($rsa->modulus->toBytes());
$rsa->exponent = new Math_BigInteger($exp, 256);
$rsa->setPublicKey();
return $rsa;
}
static function convert_key($packet, $private=false) {
if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet);
if($packet instanceof OpenPGP_Message) $packet = $packet[0];
$mod = $packet->key['n'];
$exp = $packet->key['e'];
if($private) $exp = $packet->key['d'];
if(!$exp) return NULL; // Packet doesn't have needed data
$rsa = self::crypt_rsa_key($mod, $exp);
if($private) {
/**
* @see https://github.com/phpseclib/phpseclib/issues/1113
* Primes and coefficients now use BigIntegers.
**/
//set the primes
if($packet->key['p'] && $packet->key['q'])
$rsa->primes = array(
1 => new Math_BigInteger($packet->key['p'], 256),
2 => new Math_BigInteger($packet->key['q'], 256)
);
// set the coefficients
if($packet->key['u']) $rsa->coefficients = array(2 => new Math_BigInteger($packet->key['u'], 256));
}
return $rsa;
}
static function convert_public_key($packet) {
return self::convert_key($packet, false);
}
static function convert_private_key($packet) {
return self::convert_key($packet, true);
}
}
?>

View File

@ -1,194 +0,0 @@
<?php
use phpseclib\Crypt\TripleDES as Crypt_TripleDES;
use phpseclib\Crypt\AES as Crypt_AES;
use phpseclib\Crypt\Random;
define('CRYPT_DES_MODE_CFB', Crypt_TripleDES::MODE_CFB);
define('CRYPT_AES_MODE_CFB', Crypt_AES::MODE_CFB);
require_once dirname(__FILE__).'/openpgp.php';
@include_once dirname(__FILE__).'/openpgp_crypt_rsa.php';
@include_once dirname(__FILE__).'/openpgp_mcrypt_wrapper.php';
class OpenPGP_Crypt_Symmetric {
public static function encrypt($passphrases_and_keys, $message, $symmetric_algorithm=9) {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm);
if(!$cipher) throw new Exception("Unsupported cipher");
$prefix = Random::string($key_block_bytes);
$prefix .= substr($prefix, -2);
$key = Random::string($key_bytes);
$cipher->setKey($key);
$to_encrypt = $prefix . $message->to_bytes();
$mdc = new OpenPGP_ModificationDetectionCodePacket(hash('sha1', $to_encrypt . "\xD3\x14", true));
$to_encrypt .= $mdc->to_bytes();
$encrypted = array(new OpenPGP_IntegrityProtectedDataPacket($cipher->encrypt($to_encrypt)));
if(!is_array($passphrases_and_keys) && !($passphrases_and_keys instanceof IteratorAggregate)) {
$passphrases_and_keys = (array)$passphrases_and_keys;
}
foreach($passphrases_and_keys as $pass) {
if($pass instanceof OpenPGP_PublicKeyPacket) {
if(!in_array($pass->algorithm, array(1,2,3))) throw new Exception("Only RSA keys are supported.");
$crypt_rsa = new OpenPGP_Crypt_RSA($pass);
$rsa = $crypt_rsa->public_key();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$esk = $rsa->encrypt(chr($symmetric_algorithm) . $key . pack('n', self::checksum($key)));
$esk = pack('n', OpenPGP::bitlength($esk)) . $esk;
array_unshift($encrypted, new OpenPGP_AsymmetricSessionKeyPacket($pass->algorithm, $pass->fingerprint(), $esk));
} else if(is_string($pass)) {
$s2k = new OpenPGP_S2K(Random::string(10));
$cipher->setKey($s2k->make_key($pass, $key_bytes));
$esk = $cipher->encrypt(chr($symmetric_algorithm) . $key);
array_unshift($encrypted, new OpenPGP_SymmetricSessionKeyPacket($s2k, $esk, $symmetric_algorithm));
}
}
return new OpenPGP_Message($encrypted);
}
public static function decryptSymmetric($pass, $m) {
$epacket = self::getEncryptedData($m);
foreach($m as $p) {
if($p instanceof OpenPGP_SymmetricSessionKeyPacket) {
if(strlen($p->encrypted_data) > 0) {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
if(!$cipher) continue;
$cipher->setKey($p->s2k->make_key($pass, $key_bytes));
$padAmount = $key_block_bytes - (strlen($p->encrypted_data) % $key_block_bytes);
$data = substr($cipher->decrypt($p->encrypted_data . str_repeat("\0", $padAmount)), 0, strlen($p->encrypted_data));
$decrypted = self::decryptPacket($epacket, ord($data{0}), substr($data, 1));
} else {
list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm);
$decrypted = self::decryptPacket($epacket, $p->symmetric_algorithm, $p->s2k->make_key($pass, $key_bytes));
}
if($decrypted) return $decrypted;
}
}
return NULL; /* If we get here, we failed */
}
public static function 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_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 2:
$cipher = new Crypt_TripleDES(CRYPT_DES_MODE_CFB);
$key_bytes = 24;
$key_block_bytes = 8;
break;
case 3:
if(defined('MCRYPT_CAST_128')) {
$cipher = new MCryptWrapper(MCRYPT_CAST_128);
} else {
throw new Exception("Unsupported cipher: you must have mcrypt installed to use CAST5");
}
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;
}
if(!$cipher) return array(NULL, NULL, NULL); // Unsupported cipher
if(!isset($key_bytes)) $key_bytes = isset($cipher->key_size)?$cipher->key_size:$cipher->key_length;
if(!isset($key_block_bytes)) $key_block_bytes = $cipher->block_size;
return array($cipher, $key_bytes, $key_block_bytes);
}
public static function getEncryptedData($m) {
foreach($m as $p) {
if($p instanceof OpenPGP_EncryptedDataPacket) return $p;
}
throw new Exception("Can only decrypt EncryptedDataPacket");
}
public static function checksum($s) {
$mkChk = 0;
for($i = 0; $i < strlen($s); $i++) {
$mkChk = ($mkChk + ord($s{$i})) % 65536;
}
return $mkChk;
}
}

View File

@ -0,0 +1,33 @@
<?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);
}
}
}

3
test.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
vendor/bin/phpunit --stop-on-defect --stop-on-error

View File

@ -0,0 +1 @@
<EFBFBD> σθΆh<CE86>±Ο³ΦΙfuάhθύsΕώ®Ωψ°Όώ_VF•4Σ

View File

@ -0,0 +1,3 @@
Œ 
cýІ èÑÔÖÒ9=õ­Çâ]¼TföA ¼c«vìåeøkº€Èʲõ¡©n}%.<16>lòëuÛ?\êåI
ð[øõblÊ

View File

@ -1,16 +1,12 @@
<?php
/* The tests which require phpseclib */
use Leenooks\OpenPGP;
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';
class MessageVerification extends PHPUnit_Framework_TestCase {
class MessageVerification extends PHPUnit\Framework\TestCase {
public function oneMessageRSA($pkey, $path) {
$pkeyM = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $pkey));
$m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$verify = new OpenPGP_Crypt_RSA($pkeyM);
$pkeyM = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $pkey));
$m = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$verify = new OpenPGP\Crypt\RSA($pkeyM);
$this->assertSame($verify->verify($m), $m->signatures());
}
@ -31,11 +27,11 @@ class MessageVerification extends PHPUnit_Framework_TestCase {
}
public function testSigningMessages() {
$wkey = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$sign = new OpenPGP_Crypt_RSA($wkey);
$wkey = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$sign = new OpenPGP\Crypt\RSA($wkey);
$m = $sign->sign($data)->to_bytes();
$reparsedM = OpenPGP_Message::parse($m);
$reparsedM = OpenPGP\Message::parse($m);
$this->assertSame($sign->verify($reparsedM), $reparsedM->signatures());
}
@ -51,10 +47,10 @@ class MessageVerification extends PHPUnit_Framework_TestCase {
}
class KeyVerification extends PHPUnit_Framework_TestCase {
class KeyVerification extends PHPUnit\Framework\TestCase {
public function oneKeyRSA($path) {
$m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$verify = new OpenPGP_Crypt_RSA($m);
$m = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$verify = new OpenPGP\Crypt\RSA($m);
$this->assertSame($verify->verify($m), $m->signatures());
}
@ -64,30 +60,40 @@ class KeyVerification extends PHPUnit_Framework_TestCase {
}
class Decryption extends PHPUnit_Framework_TestCase {
class Decryption extends PHPUnit\Framework\TestCase {
public function oneSymmetric($pass, $cnt, $path) {
$m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$m2 = OpenPGP_Crypt_Symmetric::decryptSymmetric($pass, $m);
while($m2[0] instanceof OpenPGP_CompressedDataPacket) $m2 = $m2[0]->data;
$m = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$m2 = OpenPGP\Crypt\Symmetric::decryptSymmetric($pass, $m);
while($m2[0] instanceof OpenPGP\CompressedDataPacket) $m2 = $m2[0]->data;
foreach($m2 as $p) {
if($p instanceof OpenPGP_LiteralDataPacket) {
if($p instanceof OpenPGP\LiteralDataPacket) {
$this->assertEquals($p->data, $cnt);
}
}
}
public function testDecryptAES() {
$this->oneSymmetric("hello", "PGP\n", "symmetric-aes.gpg");
}
public function testDecrypt3DES() {
$this->oneSymmetric("hello", "PGP\n", "symmetric-3des.gpg");
}
public function testDecryptCAST5() { // Requires mcrypt
public function testDecryptCAST5() { // Requires mcrypt or openssl
$this->oneSymmetric("hello", "PGP\n", "symmetric-cast5.gpg");
}
public function testDecryptBlowfish() {
$this->oneSymmetric("hello", "PGP\n", "symmetric-blowfish.gpg");
}
public function testDecryptAES() {
$this->oneSymmetric("hello", "PGP\n", "symmetric-aes.gpg");
}
public function testDecryptTwofish() {
if(OpenPGP\Crypt\Symmetric::getCipher(10)[0]) {
$this->oneSymmetric("hello", "PGP\n", "symmetric-twofish.gpg");
}
}
public function testDecryptSessionKey() {
$this->oneSymmetric("hello", "PGP\n", "symmetric-with-session-key.gpg");
}
@ -97,38 +103,102 @@ class Decryption extends PHPUnit_Framework_TestCase {
}
public function testDecryptAsymmetric() {
$m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/hello.gpg'));
$key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$decryptor = new OpenPGP_Crypt_RSA($key);
$m = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/hello.gpg'));
$key = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$decryptor = new OpenPGP\Crypt\RSA($key);
$m2 = $decryptor->decrypt($m);
while($m2[0] instanceof OpenPGP_CompressedDataPacket) $m2 = $m2[0]->data;
while($m2[0] instanceof OpenPGP\CompressedDataPacket) $m2 = $m2[0]->data;
foreach($m2 as $p) {
if($p instanceof OpenPGP_LiteralDataPacket) {
if($p instanceof OpenPGP\LiteralDataPacket) {
$this->assertEquals($p->data, "hello\n");
}
}
}
public function testDecryptRoundtrip() {
$m = new OpenPGP\Message(array(new OpenPGP\LiteralDataPacket("hello\n")));
$key = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$em = OpenPGP\Crypt\Symmetric::encrypt($key, $m);
foreach($key as $packet) {
if(!($packet instanceof OpenPGP\SecretKeyPacket)) continue;
$decryptor = new OpenPGP\Crypt\RSA($packet);
$m2 = $decryptor->decrypt($em);
foreach($m2 as $p) {
if($p instanceof OpenPGP\LiteralDataPacket) {
$this->assertEquals($p->data, "hello\n");
}
}
}
}
public function testDecryptSecretKey() {
$key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/encryptedSecretKey.gpg'));
$skey = OpenPGP_Crypt_Symmetric::decryptSecretKey("hello", $key[0]);
$key = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/encryptedSecretKey.gpg'));
$skey = OpenPGP\Crypt\Symmetric::decryptSecretKey("hello", $key[0]);
$this->assertSame(!!$skey, true);
}
public function testEncryptSecretKeyRoundtrip() {
$key = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$enkey = OpenPGP\Crypt\Symmetric::encryptSecretKey("password", $key[0]);
$skey = OpenPGP\Crypt\Symmetric::decryptSecretKey("password", $enkey);
$this->assertEquals($key[0], $skey);
}
public function testAlreadyDecryptedSecretKey() {
$this->expectException(Exception::class);
$this->expectExceptionMessage("Data is already unencrypted");
$key = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
OpenPGP\Crypt\Symmetric::decryptSecretKey("hello", $key[0]);
}
}
class Encryption extends PHPUnit_Framework_TestCase {
public function testEncryptSymmetric() {
$data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$encrypted = OpenPGP_Crypt_Symmetric::encrypt('secret', new OpenPGP_Message(array($data)));
$decrypted = OpenPGP_Crypt_Symmetric::decryptSymmetric('secret', $encrypted);
class Encryption extends PHPUnit\Framework\TestCase {
public function oneSymmetric($algorithm) {
$data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$encrypted = OpenPGP\Crypt\Symmetric::encrypt('secret', new OpenPGP\Message(array($data)), $algorithm);
$encrypted = OpenPGP\Message::parse($encrypted->to_bytes());
$decrypted = OpenPGP\Crypt\Symmetric::decryptSymmetric('secret', $encrypted);
$this->assertEquals($decrypted[0]->data, 'This is text.');
}
public function testEncryptSymmetric3DES() {
$this->oneSymmetric(2);
}
public function testEncryptSymmetricCAST5() {
$this->oneSymmetric(3);
}
public function testEncryptSymmetricBlowfish() {
$this->oneSymmetric(4);
}
public function testEncryptSymmetricAES128() {
$this->oneSymmetric(7);
}
public function testEncryptSymmetricAES192() {
$this->oneSymmetric(8);
}
public function testEncryptSymmetricAES256() {
$this->oneSymmetric(9);
}
public function testEncryptSymmetricTwofish() {
if(OpenPGP\Crypt\Symmetric::getCipher(10)[0]) {
$this->oneSymmetric(10);
}
}
public function testEncryptAsymmetric() {
$key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$encrypted = OpenPGP_Crypt_Symmetric::encrypt($key, new OpenPGP_Message(array($data)));
$decryptor = new OpenPGP_Crypt_RSA($key);
$key = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg'));
$data = new OpenPGP\LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt'));
$encrypted = OpenPGP\Crypt\Symmetric::encrypt($key, new OpenPGP\Message(array($data)));
$encrypted = OpenPGP\Message::parse($encrypted->to_bytes());
$decryptor = new OpenPGP\Crypt\RSA($key);
$decrypted = $decryptor->decrypt($encrypted);
$this->assertEquals($decrypted[0]->data, 'This is text.');
}

View File

@ -1,12 +1,12 @@
<?php
require_once dirname(__FILE__).'/../lib/openpgp.php';
use Leenooks\OpenPGP;
class Serialization extends PHPUnit_Framework_TestCase {
class Serialization extends PHPUnit\Framework\TestCase {
public function oneSerialization($path) {
$in = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$in = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$mid = $in->to_bytes();
$out = OpenPGP_Message::parse($mid);
$out = OpenPGP\Message::parse($mid);
$this->assertEquals($in, $out);
}
@ -14,355 +14,354 @@ class Serialization extends PHPUnit_Framework_TestCase {
$this->oneSerialization("000001-006.public_key");
}
public function test000002013user_id() {
$this->oneSerialization("000002-013.user_id");
}
public function test000003002sig() {
$this->oneSerialization("000003-002.sig");
}
public function test000004012ring_trust() {
$this->oneSerialization("000004-012.ring_trust");
}
public function test000005002sig() {
$this->oneSerialization("000005-002.sig");
}
public function test000006012ring_trust() {
$this->oneSerialization("000006-012.ring_trust");
}
public function test000007002sig() {
$this->oneSerialization("000007-002.sig");
}
public function test000008012ring_trust() {
$this->oneSerialization("000008-012.ring_trust");
}
public function test000009002sig() {
$this->oneSerialization("000009-002.sig");
}
public function test000010012ring_trust() {
$this->oneSerialization("000010-012.ring_trust");
}
public function test000011002sig() {
$this->oneSerialization("000011-002.sig");
}
public function test000012012ring_trust() {
$this->oneSerialization("000012-012.ring_trust");
}
public function test000013014public_subkey() {
$this->oneSerialization("000013-014.public_subkey");
}
public function test000014002sig() {
$this->oneSerialization("000014-002.sig");
}
public function test000015012ring_trust() {
$this->oneSerialization("000015-012.ring_trust");
}
public function test000016006public_key() {
$this->oneSerialization("000016-006.public_key");
}
public function test000017002sig() {
$this->oneSerialization("000017-002.sig");
}
public function test000018012ring_trust() {
$this->oneSerialization("000018-012.ring_trust");
}
public function test000019013user_id() {
$this->oneSerialization("000019-013.user_id");
}
public function test000020002sig() {
$this->oneSerialization("000020-002.sig");
}
public function test000021012ring_trust() {
$this->oneSerialization("000021-012.ring_trust");
}
public function test000022002sig() {
$this->oneSerialization("000022-002.sig");
}
public function test000023012ring_trust() {
$this->oneSerialization("000023-012.ring_trust");
}
public function test000024014public_subkey() {
$this->oneSerialization("000024-014.public_subkey");
}
public function test000025002sig() {
$this->oneSerialization("000025-002.sig");
}
public function test000026012ring_trust() {
$this->oneSerialization("000026-012.ring_trust");
}
public function test000027006public_key() {
$this->oneSerialization("000027-006.public_key");
}
public function test000028002sig() {
$this->oneSerialization("000028-002.sig");
}
public function test000029012ring_trust() {
$this->oneSerialization("000029-012.ring_trust");
}
public function test000030013user_id() {
$this->oneSerialization("000030-013.user_id");
}
public function test000031002sig() {
$this->oneSerialization("000031-002.sig");
}
public function test000032012ring_trust() {
$this->oneSerialization("000032-012.ring_trust");
}
public function test000033002sig() {
$this->oneSerialization("000033-002.sig");
}
public function test000034012ring_trust() {
$this->oneSerialization("000034-012.ring_trust");
}
public function test000035006public_key() {
$this->oneSerialization("000035-006.public_key");
}
public function test000036013user_id() {
$this->oneSerialization("000036-013.user_id");
}
public function test000037002sig() {
$this->oneSerialization("000037-002.sig");
}
public function test000038012ring_trust() {
$this->oneSerialization("000038-012.ring_trust");
}
public function test000039002sig() {
$this->oneSerialization("000039-002.sig");
}
public function test000040012ring_trust() {
$this->oneSerialization("000040-012.ring_trust");
}
public function test000041017attribute() {
$this->oneSerialization("000041-017.attribute");
}
public function test000042002sig() {
$this->oneSerialization("000042-002.sig");
}
public function test000043012ring_trust() {
$this->oneSerialization("000043-012.ring_trust");
}
public function test000044014public_subkey() {
$this->oneSerialization("000044-014.public_subkey");
}
public function test000045002sig() {
$this->oneSerialization("000045-002.sig");
}
public function test000046012ring_trust() {
$this->oneSerialization("000046-012.ring_trust");
}
public function test000047005secret_key() {
$this->oneSerialization("000047-005.secret_key");
}
public function test000048013user_id() {
$this->oneSerialization("000048-013.user_id");
}
public function test000049002sig() {
$this->oneSerialization("000049-002.sig");
}
public function test000050012ring_trust() {
$this->oneSerialization("000050-012.ring_trust");
}
public function test000051007secret_subkey() {
$this->oneSerialization("000051-007.secret_subkey");
}
public function test000052002sig() {
$this->oneSerialization("000052-002.sig");
}
public function test000053012ring_trust() {
$this->oneSerialization("000053-012.ring_trust");
}
public function test000054005secret_key() {
$this->oneSerialization("000054-005.secret_key");
}
public function test000055002sig() {
$this->oneSerialization("000055-002.sig");
}
public function test000056012ring_trust() {
$this->oneSerialization("000056-012.ring_trust");
}
public function test000057013user_id() {
$this->oneSerialization("000057-013.user_id");
}
public function test000058002sig() {
$this->oneSerialization("000058-002.sig");
}
public function test000059012ring_trust() {
$this->oneSerialization("000059-012.ring_trust");
}
public function test000060007secret_subkey() {
$this->oneSerialization("000060-007.secret_subkey");
}
public function test000061002sig() {
$this->oneSerialization("000061-002.sig");
}
public function test000062012ring_trust() {
$this->oneSerialization("000062-012.ring_trust");
}
public function test000063005secret_key() {
$this->oneSerialization("000063-005.secret_key");
}
public function test000064002sig() {
$this->oneSerialization("000064-002.sig");
}
public function test000065012ring_trust() {
$this->oneSerialization("000065-012.ring_trust");
}
public function test000066013user_id() {
$this->oneSerialization("000066-013.user_id");
}
public function test000067002sig() {
$this->oneSerialization("000067-002.sig");
}
public function test000068012ring_trust() {
$this->oneSerialization("000068-012.ring_trust");
}
public function test000069005secret_key() {
$this->oneSerialization("000069-005.secret_key");
}
public function test000070013user_id() {
$this->oneSerialization("000070-013.user_id");
}
public function test000071002sig() {
$this->oneSerialization("000071-002.sig");
}
public function test000072012ring_trust() {
$this->oneSerialization("000072-012.ring_trust");
}
public function test000073017attribute() {
$this->oneSerialization("000073-017.attribute");
}
public function test000074002sig() {
$this->oneSerialization("000074-002.sig");
}
public function test000075012ring_trust() {
$this->oneSerialization("000075-012.ring_trust");
}
public function test000076007secret_subkey() {
$this->oneSerialization("000076-007.secret_subkey");
}
public function test000077002sig() {
$this->oneSerialization("000077-002.sig");
}
public function test000078012ring_trust() {
$this->oneSerialization("000078-012.ring_trust");
}
public function test002182002sig() {
$this->oneSerialization("002182-002.sig");
}
public function testpubringgpg() {
$this->oneSerialization("pubring.gpg");
}
public function testsecringgpg() {
$this->oneSerialization("secring.gpg");
}
public function testcompressedsiggpg() {
$this->oneSerialization("compressedsig.gpg");
}
public function testcompressedsigzlibgpg() {
$this->oneSerialization("compressedsig-zlib.gpg");
}
public function testcompressedsigbzip2gpg() {
$this->oneSerialization("compressedsig-bzip2.gpg");
}
public function testonepass_sig() {
$this->oneSerialization("onepass_sig");
}
public function testsymmetrically_encrypted() {
$this->oneSerialization("symmetrically_encrypted");
}
public function testuncompressedopsdsagpg() {
$this->oneSerialization("uncompressed-ops-dsa.gpg");
}
public function testuncompressedopsdsasha384txtgpg() {
$this->oneSerialization("uncompressed-ops-dsa-sha384.txt.gpg");
}
public function testuncompressedopsrsagpg() {
$this->oneSerialization("uncompressed-ops-rsa.gpg");
}
@ -376,9 +375,9 @@ class Serialization extends PHPUnit_Framework_TestCase {
}
}
class Fingerprint extends PHPUnit_Framework_TestCase {
class Fingerprint extends PHPUnit\Framework\TestCase {
public function oneFingerprint($path, $kf) {
$m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$m = OpenPGP\Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path));
$this->assertEquals($m[0]->fingerprint(), $kf);
}