├── .github └── FUNDING.yml ├── .gitignore ├── .travis.dhall ├── .travis.yml ├── AUTHORS ├── Doxyfile ├── README ├── README.md ├── UNLICENSE ├── VERSION ├── composer.json ├── examples ├── README.md ├── armorEncryptSignCompress.php ├── clearsign.php ├── deASCIIdeCrypt.php ├── encryptDecrypt.php ├── keygen.php ├── keygenEncrypted.php ├── keygenSubkeys.php ├── sign.php └── verify.php ├── lib ├── openpgp.php ├── openpgp_crypt_rsa.php ├── openpgp_crypt_symmetric.php ├── openpgp_mcrypt_wrapper.php ├── openpgp_openssl_wrapper.php └── openpgp_sodium.php ├── phpunit.xml └── tests ├── bootstrap.php ├── data ├── 000001-006.public_key ├── 000002-013.user_id ├── 000003-002.sig ├── 000004-012.ring_trust ├── 000005-002.sig ├── 000006-012.ring_trust ├── 000007-002.sig ├── 000008-012.ring_trust ├── 000009-002.sig ├── 000010-012.ring_trust ├── 000011-002.sig ├── 000012-012.ring_trust ├── 000013-014.public_subkey ├── 000014-002.sig ├── 000015-012.ring_trust ├── 000016-006.public_key ├── 000017-002.sig ├── 000018-012.ring_trust ├── 000019-013.user_id ├── 000020-002.sig ├── 000021-012.ring_trust ├── 000022-002.sig ├── 000023-012.ring_trust ├── 000024-014.public_subkey ├── 000025-002.sig ├── 000026-012.ring_trust ├── 000027-006.public_key ├── 000028-002.sig ├── 000029-012.ring_trust ├── 000030-013.user_id ├── 000031-002.sig ├── 000032-012.ring_trust ├── 000033-002.sig ├── 000034-012.ring_trust ├── 000035-006.public_key ├── 000036-013.user_id ├── 000037-002.sig ├── 000038-012.ring_trust ├── 000039-002.sig ├── 000040-012.ring_trust ├── 000041-017.attribute ├── 000042-002.sig ├── 000043-012.ring_trust ├── 000044-014.public_subkey ├── 000045-002.sig ├── 000046-012.ring_trust ├── 000047-005.secret_key ├── 000048-013.user_id ├── 000049-002.sig ├── 000050-012.ring_trust ├── 000051-007.secret_subkey ├── 000052-002.sig ├── 000053-012.ring_trust ├── 000054-005.secret_key ├── 000055-002.sig ├── 000056-012.ring_trust ├── 000057-013.user_id ├── 000058-002.sig ├── 000059-012.ring_trust ├── 000060-007.secret_subkey ├── 000061-002.sig ├── 000062-012.ring_trust ├── 000063-005.secret_key ├── 000064-002.sig ├── 000065-012.ring_trust ├── 000066-013.user_id ├── 000067-002.sig ├── 000068-012.ring_trust ├── 000069-005.secret_key ├── 000070-013.user_id ├── 000071-002.sig ├── 000072-012.ring_trust ├── 000073-017.attribute ├── 000074-002.sig ├── 000075-012.ring_trust ├── 000076-007.secret_subkey ├── 000077-002.sig ├── 000078-012.ring_trust ├── 000079-002.sig ├── 000080-006.public_key ├── 000081-002.sig ├── 000082-006.public_key ├── 000083-002.sig ├── 002182-002.sig ├── compressedsig-bzip2.gpg ├── compressedsig-zlib.gpg ├── compressedsig.gpg ├── ed25519.public_key ├── ed25519.secret_key ├── ed25519.sig ├── encryptedSecretKey.gpg ├── hello.gpg ├── helloKey.gpg ├── onepass_sig ├── pubring.gpg ├── secring.gpg ├── symmetric-3des.gpg ├── symmetric-aes.gpg ├── symmetric-blowfish.gpg ├── symmetric-cast5.gpg ├── symmetric-no-mdc.gpg ├── symmetric-twofish.gpg ├── symmetric-with-session-key.gpg ├── symmetrically_encrypted ├── uncompressed-ops-dsa-sha384.txt.gpg ├── uncompressed-ops-dsa.gpg └── uncompressed-ops-rsa.gpg ├── phpseclib_suite.php ├── sodium_suite.php └── suite.php /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: singpolyma 2 | liberapay: singpolyma 3 | patreon: singpolyma 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .tmp 3 | pkg 4 | tmp 5 | composer.lock 6 | .phpunit.result.cache 7 | .idea 8 | vendor/ 9 | -------------------------------------------------------------------------------- /.travis.dhall: -------------------------------------------------------------------------------- 1 | let Prelude = https://prelude.dhall-lang.org/v17.0.0/package.dhall 2 | let phpseclib = \(max: Natural) -> \(filter: (Natural -> Bool)) -> 3 | Prelude.List.map Natural Text 4 | (\(m: Natural) -> "PHPSECLIB='2.0.${Prelude.Natural.show m}'") 5 | (Prelude.List.filter Natural filter (Prelude.Natural.enumerate max)) 6 | let Exclusion = { php: Text, env: Text } 7 | in 8 | { 9 | language = "php", 10 | php = [ 11 | "7.3", 12 | "7.4", 13 | "8.0", 14 | "8.1", 15 | "8.2" 16 | ], 17 | dist = "xenial", 18 | env = [ 19 | "PHPSECLIB='^2.0 !=2.0.8'" 20 | ] # (phpseclib 28 (\(m: Natural) -> Prelude.Bool.not (Prelude.Natural.equal m 8)) 21 | ), 22 | matrix = { 23 | exclude = Prelude.List.concatMap Text Exclusion (\(php: Text) -> 24 | Prelude.List.map Text Exclusion (\(env: Text) -> 25 | { php = php, env = env } 26 | ) (phpseclib 7 (\(_: Natural) -> True)) 27 | ) ["7.3", "7.4", "8.0", "8.1", "8.2"], 28 | fast_finish = True 29 | }, 30 | before_script = '' 31 | sed -i "s/\"phpseclib\/phpseclib\": \"[^\"]*/\"phpseclib\/phpseclib\": \"$PHPSECLIB/" composer.json && composer install --prefer-source'' 32 | } 33 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Code generated by dhall-to-yaml. DO NOT EDIT. 2 | before_script: "sed -i \"s/\\\"phpseclib\\/phpseclib\\\": \\\"[^\\\"]*/\\\"phpseclib\\/phpseclib\\\": \\\"$PHPSECLIB/\" composer.json && composer install --prefer-source" 3 | dist: xenial 4 | env: 5 | - "PHPSECLIB='^2.0 !=2.0.8'" 6 | - "PHPSECLIB='2.0.0'" 7 | - "PHPSECLIB='2.0.1'" 8 | - "PHPSECLIB='2.0.2'" 9 | - "PHPSECLIB='2.0.3'" 10 | - "PHPSECLIB='2.0.4'" 11 | - "PHPSECLIB='2.0.5'" 12 | - "PHPSECLIB='2.0.6'" 13 | - "PHPSECLIB='2.0.7'" 14 | - "PHPSECLIB='2.0.9'" 15 | - "PHPSECLIB='2.0.10'" 16 | - "PHPSECLIB='2.0.11'" 17 | - "PHPSECLIB='2.0.12'" 18 | - "PHPSECLIB='2.0.13'" 19 | - "PHPSECLIB='2.0.14'" 20 | - "PHPSECLIB='2.0.15'" 21 | - "PHPSECLIB='2.0.16'" 22 | - "PHPSECLIB='2.0.17'" 23 | - "PHPSECLIB='2.0.18'" 24 | - "PHPSECLIB='2.0.19'" 25 | - "PHPSECLIB='2.0.20'" 26 | - "PHPSECLIB='2.0.21'" 27 | - "PHPSECLIB='2.0.22'" 28 | - "PHPSECLIB='2.0.23'" 29 | - "PHPSECLIB='2.0.24'" 30 | - "PHPSECLIB='2.0.25'" 31 | - "PHPSECLIB='2.0.26'" 32 | - "PHPSECLIB='2.0.27'" 33 | language: php 34 | matrix: 35 | exclude: 36 | - env: "PHPSECLIB='2.0.0'" 37 | php: '7.3' 38 | - env: "PHPSECLIB='2.0.1'" 39 | php: '7.3' 40 | - env: "PHPSECLIB='2.0.2'" 41 | php: '7.3' 42 | - env: "PHPSECLIB='2.0.3'" 43 | php: '7.3' 44 | - env: "PHPSECLIB='2.0.4'" 45 | php: '7.3' 46 | - env: "PHPSECLIB='2.0.5'" 47 | php: '7.3' 48 | - env: "PHPSECLIB='2.0.6'" 49 | php: '7.3' 50 | - env: "PHPSECLIB='2.0.0'" 51 | php: '7.4' 52 | - env: "PHPSECLIB='2.0.1'" 53 | php: '7.4' 54 | - env: "PHPSECLIB='2.0.2'" 55 | php: '7.4' 56 | - env: "PHPSECLIB='2.0.3'" 57 | php: '7.4' 58 | - env: "PHPSECLIB='2.0.4'" 59 | php: '7.4' 60 | - env: "PHPSECLIB='2.0.5'" 61 | php: '7.4' 62 | - env: "PHPSECLIB='2.0.6'" 63 | php: '7.4' 64 | - env: "PHPSECLIB='2.0.0'" 65 | php: '8.0' 66 | - env: "PHPSECLIB='2.0.1'" 67 | php: '8.0' 68 | - env: "PHPSECLIB='2.0.2'" 69 | php: '8.0' 70 | - env: "PHPSECLIB='2.0.3'" 71 | php: '8.0' 72 | - env: "PHPSECLIB='2.0.4'" 73 | php: '8.0' 74 | - env: "PHPSECLIB='2.0.5'" 75 | php: '8.0' 76 | - env: "PHPSECLIB='2.0.6'" 77 | php: '8.0' 78 | - env: "PHPSECLIB='2.0.0'" 79 | php: '8.1' 80 | - env: "PHPSECLIB='2.0.1'" 81 | php: '8.1' 82 | - env: "PHPSECLIB='2.0.2'" 83 | php: '8.1' 84 | - env: "PHPSECLIB='2.0.3'" 85 | php: '8.1' 86 | - env: "PHPSECLIB='2.0.4'" 87 | php: '8.1' 88 | - env: "PHPSECLIB='2.0.5'" 89 | php: '8.1' 90 | - env: "PHPSECLIB='2.0.6'" 91 | php: '8.1' 92 | - env: "PHPSECLIB='2.0.0'" 93 | php: '8.2' 94 | - env: "PHPSECLIB='2.0.1'" 95 | php: '8.2' 96 | - env: "PHPSECLIB='2.0.2'" 97 | php: '8.2' 98 | - env: "PHPSECLIB='2.0.3'" 99 | php: '8.2' 100 | - env: "PHPSECLIB='2.0.4'" 101 | php: '8.2' 102 | - env: "PHPSECLIB='2.0.5'" 103 | php: '8.2' 104 | - env: "PHPSECLIB='2.0.6'" 105 | php: '8.2' 106 | fast_finish: true 107 | php: 108 | - '7.3' 109 | - '7.4' 110 | - '8.0' 111 | - '8.1' 112 | - '8.2' 113 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | * Arto Bendiken 2 | * Stephen Paul Weber 3 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/singpolyma/openpgp-php.svg?branch=master)](https://travis-ci.org/singpolyma/openpgp-php) 2 | 3 | OpenPGP.php: OpenPGP for PHP 4 | ============================ 5 | 6 | This is a pure-PHP implementation of the OpenPGP Message Format (RFC 4880). 7 | 8 | * 9 | 10 | About OpenPGP 11 | ------------- 12 | 13 | OpenPGP is the most widely-used e-mail encryption standard in the world. It 14 | is defined by the OpenPGP Working Group of the Internet Engineering Task 15 | Force (IETF) Proposed Standard RFC 4880. The OpenPGP standard was originally 16 | derived from PGP (Pretty Good Privacy), first created by Phil Zimmermann in 17 | 1991. 18 | 19 | * 20 | * 21 | 22 | Features 23 | -------- 24 | 25 | * Encodes and decodes ASCII-armored OpenPGP messages. 26 | * Parses OpenPGP messages into their constituent packets. 27 | * Supports both old-format (PGP 2.6.x) and new-format (RFC 4880) packets. 28 | * Helper class for verifying, signing, encrypting, and decrypting messages 29 | * Helper class for encrypting and decrypting messages and keys using 30 | * openssl or mcrypt required for CAST5 encryption and decryption 31 | 32 | Bugs, Feature Requests, Patches 33 | ------------------------------- 34 | 35 | This project is primarily maintained by a single volunteer with many other 36 | things vying for their attention, please be patient. 37 | 38 | Bugs, feature request, pull requests, patches, and general discussion may 39 | be submitted publicly via email to: dev@singpolyma.net 40 | 41 | Github users may alternately submit on the web there. 42 | 43 | Users 44 | ----- 45 | 46 | OpenPGP.php is currently being used in the following projects: 47 | 48 | * 49 | * [Passbolt API](https://github.com/passbolt/passbolt_api) 50 | 51 | Download 52 | -------- 53 | 54 | To get a local working copy of the development repository, do: 55 | 56 | git clone https://github.com/singpolyma/openpgp-php.git 57 | 58 | Alternatively, you can download the latest development version as a tarball 59 | as follows: 60 | 61 | wget https://github.com/singpolyma/openpgp-php/tarball/master 62 | 63 | Authors 64 | ------- 65 | 66 | * [Arto Bendiken](mailto:arto.bendiken@gmail.com) (Original author) - 67 | * [Stephen Paul Weber](mailto:singpolyma@singpolyma.net) (Maintainer) - 68 | 69 | License 70 | ------- 71 | 72 | OpenPGP.php is free and unencumbered public domain software. For more 73 | information, see or the accompanying UNLICENSE file. 74 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 0.3.0 2 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "singpolyma/openpgp-php", 3 | "description": "Pure-PHP implementation of the OpenPGP Message Format (RFC 4880)", 4 | "license": "Unlicense", 5 | "authors": [ 6 | { 7 | "name": "Arto Bendiken", 8 | "email": "arto.bendiken@gmail.com" 9 | }, 10 | { 11 | "name": "Stephen Paul Weber", 12 | "email": "singpolyma@singpolyma.net" 13 | } 14 | ], 15 | "require": { 16 | "php": "^5.6 || ^7.0 || ^8.0", 17 | "phpseclib/phpseclib": "^3.0.14" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "^9.0" 21 | }, 22 | "suggest": { 23 | "ext-mcrypt": "required if you use encryption cast5", 24 | "ext-openssl": "required if you use encryption cast5" 25 | }, 26 | "autoload": { 27 | "classmap": ["lib/"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | OpenPGP.php Examples 2 | ==================== 3 | 4 | The scripts in this folder show how to use this library to perform various tasks 5 | such as [generating a new key](keygen.php), [signing a message](sign.php), and 6 | [verifying a message](verify.php) that has been signed. 7 | 8 | To use these examples, make sure [`phpseclib`](http://phpseclib.sourceforge.net/) is available. You can install it 9 | using [Composer](https://getcomposer.org/): 10 | 11 | ```sh 12 | git clone https://github.com/singpolyma/openpgp-php.git # Clone the repository. 13 | cd openpgp-php 14 | composer install # Use Composer to install the requirements. 15 | ``` 16 | 17 | Once Composer has installed the requirements, run the examples using PHP: 18 | 19 | ```sh 20 | # Generate a new OpenPGP key; see the `keygen.php` file for parameters. 21 | php ./examples/keygen.php > mykey.gpg 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/armorEncryptSignCompress.php: -------------------------------------------------------------------------------- 1 | 'u']); 14 | $signed = $signer->sign($data); 15 | 16 | $compressed = new OpenPGP_CompressedDataPacket($signed); 17 | $encrypted = OpenPGP_Crypt_Symmetric::encrypt([$recipientPublicKey, $key], new OpenPGP_Message([$compressed])); 18 | 19 | echo OpenPGP::enarmor($encrypted->to_bytes(), 'PGP MESSAGE'); 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/clearsign.php: -------------------------------------------------------------------------------- 1 | 'u', 'filename' => 'stuff.txt')); 15 | $data->normalize(true); // Clearsign-style normalization of the LiteralDataPacket 16 | 17 | /* Create a signer from the key */ 18 | $sign = new OpenPGP_Crypt_RSA($wkey); 19 | 20 | /* The message is the signed data packet */ 21 | $m = $sign->sign($data); 22 | 23 | /* Generate clearsigned data */ 24 | $packets = $m->signatures()[0]; 25 | echo "-----BEGIN PGP SIGNED MESSAGE-----\nHash: SHA256\n\n"; 26 | // Output normalised data. You could convert line endings here 27 | // without breaking the signature, but do not add any 28 | // trailing whitespace to lines. 29 | echo preg_replace("/^-/", "- -", $packets[0]->data)."\n"; 30 | echo OpenPGP::enarmor($packets[1][0]->to_bytes(), "PGP SIGNATURE"); 31 | -------------------------------------------------------------------------------- /examples/deASCIIdeCrypt.php: -------------------------------------------------------------------------------- 1 | decrypt($msg); 26 | 27 | var_dump($decrypted); 28 | } 29 | -------------------------------------------------------------------------------- /examples/encryptDecrypt.php: -------------------------------------------------------------------------------- 1 | 'u', 'filename' => 'stuff.txt')); 10 | $encrypted = OpenPGP_Crypt_Symmetric::encrypt($key, new OpenPGP_Message(array($data))); 11 | 12 | // Now decrypt it with the same key 13 | $decryptor = new OpenPGP_Crypt_RSA($key); 14 | $decrypted = $decryptor->decrypt($encrypted); 15 | 16 | var_dump($decrypted); 17 | -------------------------------------------------------------------------------- /examples/keygen.php: -------------------------------------------------------------------------------- 1 | getPublicKey(); 12 | 13 | $privateKeyComponents = PKCS1::load($privateKey->toString('PKCS1')); 14 | 15 | $nkey = new OpenPGP_SecretKeyPacket(array( 16 | 'n' => $privateKeyComponents["modulus"]->toBytes(), 17 | 'e' => $privateKeyComponents["publicExponent"]->toBytes(), 18 | 'd' => $privateKeyComponents["privateExponent"]->toBytes(), 19 | 'p' => $privateKeyComponents["primes"][1]->toBytes(), 20 | 'q' => $privateKeyComponents["primes"][2]->toBytes(), 21 | 'u' => $privateKeyComponents["coefficients"][2]->toBytes() 22 | )); 23 | 24 | $uid = new OpenPGP_UserIDPacket('Test '); 25 | 26 | $wkey = new OpenPGP_Crypt_RSA($nkey); 27 | $m = $wkey->sign_key_userid(array($nkey, $uid)); 28 | 29 | // Serialize private key 30 | print $m->to_bytes(); 31 | 32 | // Serialize public key message 33 | $pubm = clone($m); 34 | $pubm[0] = new OpenPGP_PublicKeyPacket($pubm[0]); 35 | 36 | $public_bytes = $pubm->to_bytes(); 37 | -------------------------------------------------------------------------------- /examples/keygenEncrypted.php: -------------------------------------------------------------------------------- 1 | getPublicKey(); 13 | 14 | $privateKeyComponents = PKCS1::load($privateKey->toString('PKCS1')); 15 | 16 | $nkey = new OpenPGP_SecretKeyPacket(array( 17 | 'n' => $privateKeyComponents["modulus"]->toBytes(), 18 | 'e' => $privateKeyComponents["publicExponent"]->toBytes(), 19 | 'd' => $privateKeyComponents["privateExponent"]->toBytes(), 20 | 'p' => $privateKeyComponents["primes"][1]->toBytes(), 21 | 'q' => $privateKeyComponents["primes"][2]->toBytes(), 22 | 'u' => $privateKeyComponents["coefficients"][2]->toBytes() 23 | )); 24 | 25 | $uid = new OpenPGP_UserIDPacket('Test '); 26 | 27 | $wkey = new OpenPGP_Crypt_RSA($nkey); 28 | $m = $wkey->sign_key_userid(array($nkey, $uid)); 29 | $m[0] = OpenPGP_Crypt_Symmetric::encryptSecretKey("password", $nkey); 30 | 31 | // Serialize encrypted private key 32 | print $m->to_bytes(); 33 | -------------------------------------------------------------------------------- /examples/keygenSubkeys.php: -------------------------------------------------------------------------------- 1 | toString('PKCS1')); 16 | 17 | $nkey = new OpenPGP_SecretKeyPacket(array( 18 | 'n' => $privateKeyComponents["modulus"]->toBytes(), 19 | 'e' => $privateKeyComponents["publicExponent"]->toBytes(), 20 | 'd' => $privateKeyComponents["privateExponent"]->toBytes(), 21 | 'p' => $privateKeyComponents["primes"][1]->toBytes(), 22 | 'q' => $privateKeyComponents["primes"][2]->toBytes(), 23 | 'u' => $privateKeyComponents["coefficients"][2]->toBytes() 24 | )); 25 | 26 | // Start assembling packets for our eventual OpenPGP_Message 27 | $packets = array($nkey); 28 | 29 | $wkey = new OpenPGP_Crypt_RSA($nkey); 30 | $fingerprint = $wkey->key()->fingerprint; 31 | $key = $wkey->private_key(); 32 | $key = $key->withHash('sha256'); 33 | $keyid = substr($fingerprint, -16); 34 | 35 | // Add multiple UID packets and signatures 36 | 37 | $uids = array( 38 | new OpenPGP_UserIDPacket('Support', '', 'support@example.com'), 39 | new OpenPGP_UserIDPacket('Security', '', 'security@example.com'), 40 | ); 41 | 42 | foreach($uids as $uid) { 43 | // Append the UID packet 44 | $packets[] = $uid; 45 | 46 | $sig = new OpenPGP_SignaturePacket(new OpenPGP_Message(array($nkey, $uid)), 'RSA', 'SHA256'); 47 | $sig->signature_type = 0x13; 48 | $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02)); // Certify + sign bits 49 | $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); 50 | $m = $wkey->sign_key_userid(array($nkey, $uid, $sig)); 51 | 52 | // Append the UID signature from the master key 53 | $packets[] = $m->packets[2]; 54 | } 55 | 56 | // Generate an encryption subkey 57 | 58 | $rsa_subkey = RSA::createKey(512); 59 | $privateKeyComponents = PKCS1::load($rsa_subkey->toString('PKCS1')); 60 | 61 | $subkey = new OpenPGP_SecretKeyPacket(array( 62 | 'n' => $privateKeyComponents["modulus"]->toBytes(), 63 | 'e' => $privateKeyComponents["publicExponent"]->toBytes(), 64 | 'd' => $privateKeyComponents["privateExponent"]->toBytes(), 65 | 'p' => $privateKeyComponents["primes"][2]->toBytes(), 66 | 'q' => $privateKeyComponents["primes"][1]->toBytes(), 67 | 'u' => $privateKeyComponents["coefficients"][2]->toBytes() 68 | )); 69 | 70 | // Append the encryption subkey 71 | $packets[] = $subkey; 72 | 73 | $sub_wkey = new OpenPGP_Crypt_RSA($subkey); 74 | 75 | /* 76 | * Sign the encryption subkey with the master key 77 | * 78 | * OpenPGP_SignaturePacket assumes any message starting with an 79 | * OpenPGP_PublicKeyPacket is followed by a OpenPGP_UserIDPacket. We need 80 | * to pass `null` in the constructor and generate the `->data` ourselves. 81 | */ 82 | $sub_sig = new OpenPGP_SignaturePacket(null, 'RSA', 'SHA256'); 83 | $sub_sig->signature_type = 0x18; 84 | $sub_sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_SignatureCreationTimePacket(time()); 85 | $sub_sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x0C)); // Encrypt bits 86 | $sub_sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); 87 | $sub_sig->data = implode('', $nkey->fingerprint_material()) . implode('', $subkey->fingerprint_material()); 88 | $sub_sig->sign_data(array('RSA' => array('SHA256' => function($data) use($key) { 89 | return [ "signed" => $key->sign($data), "hash" => $key->getHash()->hash($data) ]; 90 | }))); 91 | 92 | // Append the subkey signature 93 | $packets[] = $sub_sig; 94 | 95 | // Build the OpenPGP_Message for the secret key from our packets 96 | $m = new OpenPGP_Message($packets); 97 | 98 | // Serialize the private key 99 | print $m->to_bytes(); 100 | 101 | // Clone a public key message from the secret key 102 | $pubm = clone($m); 103 | 104 | // Convert the private key packets to public so we only export public data 105 | // (n+e in RSA) 106 | foreach($pubm as $idx => $p) { 107 | if($p instanceof OpenPGP_SecretSubkeyPacket) { 108 | $pubm[$idx] = new OpenPGP_PublicSubkeyPacket($p); 109 | } else if($p instanceof OpenPGP_SecretKeyPacket) { 110 | $pubm[$idx] = new OpenPGP_PublicKeyPacket($p); 111 | } 112 | } 113 | 114 | // Serialize the public key 115 | $public_bytes = $pubm->to_bytes(); 116 | 117 | // Note: If using PHP 7.4 CLI, disable deprecated warnings: 118 | // php -d error_reporting="E_ALL & ~E_DEPRECATED" examples/keygenSubkeys.php > mykey.gpg 119 | -------------------------------------------------------------------------------- /examples/sign.php: -------------------------------------------------------------------------------- 1 | 'u', 'filename' => 'stuff.txt')); 13 | 14 | /* Create a signer from the key */ 15 | $sign = new OpenPGP_Crypt_RSA($wkey); 16 | 17 | /* The message is the signed data packet */ 18 | $m = $sign->sign($data); 19 | 20 | /* Output the raw message bytes to STDOUT */ 21 | echo $m->to_bytes(); 22 | -------------------------------------------------------------------------------- /examples/verify.php: -------------------------------------------------------------------------------- 1 | verify($m)); 18 | -------------------------------------------------------------------------------- /lib/openpgp.php: -------------------------------------------------------------------------------- 1 | 10 | * @author Stephen Paul Weber 11 | * @see http://github.com/bendiken/openpgp-php 12 | */ 13 | 14 | ////////////////////////////////////////////////////////////////////////////// 15 | // OpenPGP utilities 16 | 17 | /** 18 | * @see http://tools.ietf.org/html/rfc4880 19 | */ 20 | class OpenPGP { 21 | const VERSION = array(0, 7, 0); 22 | 23 | /** 24 | * @see http://tools.ietf.org/html/rfc4880#section-6 25 | * @see http://tools.ietf.org/html/rfc4880#section-6.2 26 | * @see http://tools.ietf.org/html/rfc2045 27 | */ 28 | static function enarmor($data, $marker = 'MESSAGE', array $headers = array()) { 29 | $text = self::header($marker) . "\n"; 30 | foreach ($headers as $key => $value) { 31 | $text .= $key . ': ' . (string)$value . "\n"; 32 | } 33 | $text .= "\n" . wordwrap(base64_encode($data), 76, "\n", true); 34 | $text .= "\n".'=' . base64_encode(substr(pack('N', self::crc24($data)), 1)) . "\n"; 35 | $text .= self::footer($marker) . "\n"; 36 | return $text; 37 | } 38 | 39 | /** 40 | * @see http://tools.ietf.org/html/rfc4880#section-6 41 | * @see http://tools.ietf.org/html/rfc2045 42 | */ 43 | static function unarmor($text, $header = 'PGP PUBLIC KEY BLOCK') { 44 | $header = self::header($header); 45 | $text = str_replace(array("\r\n", "\r"), array("\n", ''), $text); 46 | if (($pos1 = strpos($text, $header)) !== FALSE && 47 | ($pos1 = strpos($text, "\n\n", $pos1 += strlen($header))) !== FALSE) { 48 | $pos2 = strpos($text, "\n=", $pos1 += 2); 49 | if ($pos2 === FALSE) { 50 | trigger_error("Invalid ASCII armor, missing CRC"); 51 | $pos2 = strpos($text, "-----END"); 52 | if ($pos2 === FALSE) return NULL; 53 | } 54 | $text = substr($text, $pos1, $pos2 - $pos1); 55 | return base64_decode($text, true); 56 | } 57 | } 58 | 59 | /** 60 | * @see http://tools.ietf.org/html/rfc4880#section-6.2 61 | */ 62 | static function header($marker) { 63 | return '-----BEGIN ' . strtoupper((string)$marker) . '-----'; 64 | } 65 | 66 | /** 67 | * @see http://tools.ietf.org/html/rfc4880#section-6.2 68 | */ 69 | static function footer($marker) { 70 | return '-----END ' . strtoupper((string)$marker) . '-----'; 71 | } 72 | 73 | /** 74 | * @see http://tools.ietf.org/html/rfc4880#section-6 75 | * @see http://tools.ietf.org/html/rfc4880#section-6.1 76 | */ 77 | static function crc24($data) { 78 | $crc = 0x00b704ce; 79 | for ($i = 0; $i < strlen($data); $i++) { 80 | $crc ^= (ord($data[$i]) & 255) << 16; 81 | for ($j = 0; $j < 8; $j++) { 82 | $crc <<= 1; 83 | if ($crc & 0x01000000) { 84 | $crc ^= 0x01864cfb; 85 | } 86 | } 87 | } 88 | return $crc & 0x00ffffff; 89 | } 90 | 91 | /** 92 | * @see http://tools.ietf.org/html/rfc4880#section-12.2 93 | */ 94 | static function bitlength($data) { 95 | return (strlen($data) - 1) * 8 + (int)floor(log(ord($data[0]), 2)) + 1; 96 | } 97 | 98 | static function decode_s2k_count($c) { 99 | return ((int)16 + ($c & 15)) << (($c >> 4) + 6); 100 | } 101 | 102 | static function encode_s2k_count($iterations) { 103 | if($iterations >= 65011712) return 255; 104 | 105 | $count = $iterations >> 6; 106 | $c = 0; 107 | while($count >= 32) { 108 | $count = $count >> 1; 109 | $c++; 110 | } 111 | $result = ($c << 4) | ($count - 16); 112 | 113 | if(OpenPGP::decode_s2k_count($result) < $iterations) { 114 | return $result + 1; 115 | } 116 | 117 | return $result; 118 | } 119 | } 120 | 121 | class OpenPGP_S2K { 122 | public $type, $hash_algorithm, $salt, $count; 123 | 124 | function __construct($salt='BADSALT', $hash_algorithm=10, $count=65536, $type=3) { 125 | $this->type = $type; 126 | $this->hash_algorithm = $hash_algorithm; 127 | $this->salt = $salt; 128 | $this->count = $count; 129 | } 130 | 131 | static function parse(&$input) { 132 | $s2k = new OpenPGP_S2k(); 133 | switch($s2k->type = ord($input[0])) { 134 | case 0: 135 | $s2k->hash_algorithm = ord($input[1]); 136 | $input = substr($input, 2); 137 | break; 138 | case 1: 139 | $s2k->hash_algorithm = ord($input[1]); 140 | $s2k->salt = substr($input, 2, 8); 141 | $input = substr($input, 10); 142 | break; 143 | case 3: 144 | $s2k->hash_algorithm = ord($input[1]); 145 | $s2k->salt = substr($input, 2, 8); 146 | $s2k->count = OpenPGP::decode_s2k_count(ord($input[10])); 147 | $input = substr($input, 11); 148 | break; 149 | } 150 | 151 | return $s2k; 152 | } 153 | 154 | function to_bytes() { 155 | $bytes = chr($this->type); 156 | switch($this->type) { 157 | case 0: 158 | $bytes .= chr($this->hash_algorithm); 159 | break; 160 | case 1: 161 | if(strlen($this->salt) != 8) throw new Exception("Invalid salt length"); 162 | $bytes .= chr($this->hash_algorithm); 163 | $bytes .= $this->salt; 164 | break; 165 | case 3: 166 | if(strlen($this->salt) != 8) throw new Exception("Invalid salt length"); 167 | $bytes .= chr($this->hash_algorithm); 168 | $bytes .= $this->salt; 169 | $bytes .= chr(OpenPGP::encode_s2k_count($this->count)); 170 | break; 171 | } 172 | return $bytes; 173 | } 174 | 175 | function raw_hash($s) { 176 | return hash(strtolower(OpenPGP_SignaturePacket::$hash_algorithms[$this->hash_algorithm]), $s, true); 177 | } 178 | 179 | function sized_hash($s, $size) { 180 | $hash = $this->raw_hash($s); 181 | while(strlen($hash) < $size) { 182 | $s = "\0" . $s; 183 | $hash .= $this->raw_hash($s); 184 | } 185 | 186 | return substr($hash, 0, $size); 187 | } 188 | 189 | function iterate($s) { 190 | if(strlen($s) >= $this->count) return $s; 191 | $s = str_repeat($s, ceil($this->count / strlen($s))); 192 | return substr($s, 0, $this->count); 193 | } 194 | 195 | function make_key($pass, $size) { 196 | switch($this->type) { 197 | case 0: 198 | return $this->sized_hash($pass, $size); 199 | case 1: 200 | return $this->sized_hash($this->salt . $pass, $size); 201 | case 3: 202 | return $this->sized_hash($this->iterate($this->salt . $pass), $size); 203 | } 204 | } 205 | } 206 | 207 | ////////////////////////////////////////////////////////////////////////////// 208 | // OpenPGP messages 209 | 210 | /** 211 | * @see http://tools.ietf.org/html/rfc4880#section-4.1 212 | * @see http://tools.ietf.org/html/rfc4880#section-11 213 | * @see http://tools.ietf.org/html/rfc4880#section-11.3 214 | */ 215 | class OpenPGP_Message implements IteratorAggregate, ArrayAccess { 216 | public $uri = NULL; 217 | public $packets = array(); 218 | 219 | static function parse_file($path) { 220 | if (($msg = self::parse(file_get_contents($path)))) { 221 | $msg->uri = preg_match('!^[\w\d]+://!', $path) ? $path : 'file://' . realpath($path); 222 | return $msg; 223 | } 224 | } 225 | 226 | /** 227 | * @see http://tools.ietf.org/html/rfc4880#section-4.1 228 | * @see http://tools.ietf.org/html/rfc4880#section-4.2 229 | */ 230 | static function parse($input) { 231 | if (is_resource($input)) { 232 | return self::parse_stream($input); 233 | } 234 | if (is_string($input)) { 235 | return self::parse_string($input); 236 | } 237 | } 238 | 239 | static function parse_stream($input) { 240 | return self::parse_string(stream_get_contents($input)); 241 | } 242 | 243 | static function parse_string($input) { 244 | $msg = new self; 245 | while (($length = strlen($input)) > 0) { 246 | if (($packet = OpenPGP_Packet::parse($input))) { 247 | $msg[] = $packet; 248 | } 249 | if ($length == strlen($input)) { // is parsing stuck? 250 | break; 251 | } 252 | } 253 | return $msg; 254 | } 255 | 256 | function __construct(array $packets = array()) { 257 | $this->packets = $packets; 258 | } 259 | 260 | function to_bytes() { 261 | $bytes = ''; 262 | foreach($this as $p) { 263 | $bytes .= $p->to_bytes(); 264 | } 265 | return $bytes; 266 | } 267 | 268 | /** 269 | * Extract signed objects from a well-formatted message 270 | * 271 | * Recurses into CompressedDataPacket 272 | * 273 | * @see http://tools.ietf.org/html/rfc4880#section-11 274 | */ 275 | function signatures() { 276 | $msg = $this; 277 | while($msg[0] instanceof OpenPGP_CompressedDataPacket) $msg = $msg[0]->data; 278 | 279 | $key = NULL; 280 | $userid = NULL; 281 | $subkey = NULL; 282 | $sigs = array(); 283 | $final_sigs = array(); 284 | 285 | foreach($msg as $idx => $p) { 286 | if($p instanceof OpenPGP_LiteralDataPacket) { 287 | return array(array($p, array_values(array_filter($msg->packets, function($p) { 288 | return $p instanceof OpenPGP_SignaturePacket; 289 | })))); 290 | } else if($p instanceof OpenPGP_PublicSubkeyPacket || $p instanceof OpenPGP_SecretSubkeyPacket) { 291 | if($userid) { 292 | array_push($final_sigs, array($key, $userid, $sigs)); 293 | $userid = NULL; 294 | } else if($subkey) { 295 | array_push($final_sigs, array($key, $subkey, $sigs)); 296 | $key = NULL; 297 | } 298 | $sigs = array(); 299 | $subkey = $p; 300 | } else if($p instanceof OpenPGP_PublicKeyPacket) { 301 | if($userid) { 302 | array_push($final_sigs, array($key, $userid, $sigs)); 303 | $userid = NULL; 304 | } else if($subkey) { 305 | array_push($final_sigs, array($key, $subkey, $sigs)); 306 | $subkey = NULL; 307 | } else if($key) { 308 | array_push($final_sigs, array($key, $sigs)); 309 | $key = NULL; 310 | } 311 | $sigs = array(); 312 | $key = $p; 313 | } else if($p instanceof OpenPGP_UserIDPacket) { 314 | if($userid) { 315 | array_push($final_sigs, array($key, $userid, $sigs)); 316 | $userid = NULL; 317 | } else if($key) { 318 | array_push($final_sigs, array($key, $sigs)); 319 | } 320 | $sigs = array(); 321 | $userid = $p; 322 | } else if($p instanceof OpenPGP_SignaturePacket) { 323 | $sigs[] = $p; 324 | } 325 | } 326 | 327 | if($userid) { 328 | array_push($final_sigs, array($key, $userid, $sigs)); 329 | } else if($subkey) { 330 | array_push($final_sigs, array($key, $subkey, $sigs)); 331 | } else if($key) { 332 | array_push($final_sigs, array($key, $sigs)); 333 | } 334 | 335 | return $final_sigs; 336 | } 337 | 338 | /** 339 | * Function to extract verified signatures 340 | * $verifiers is an array of callbacks formatted like array('RSA' => CALLBACK) or array('RSA' => array('SHA256' => CALLBACK)) that take two parameters: raw message and signature packet 341 | */ 342 | function verified_signatures($verifiers) { 343 | $signed = $this->signatures(); 344 | $vsigned = array(); 345 | 346 | foreach($signed as $sign) { 347 | $signatures = array_pop($sign); 348 | $vsigs = array(); 349 | 350 | foreach($signatures as $sig) { 351 | $verifier = $verifiers[$sig->key_algorithm_name()]; 352 | if(is_array($verifier)) $verifier = $verifier[$sig->hash_algorithm_name()]; 353 | if($verifier && $this->verify_one($verifier, $sign, $sig)) { 354 | $vsigs[] = $sig; 355 | } 356 | } 357 | array_push($sign, $vsigs); 358 | $vsigned[] = $sign; 359 | } 360 | 361 | return $vsigned; 362 | } 363 | 364 | function verify_one($verifier, $sign, $sig) { 365 | if($sign[0] instanceof OpenPGP_LiteralDataPacket) { 366 | $sign[0]->normalize(); 367 | $raw = $sign[0]->data; 368 | } else if(isset($sign[1]) && $sign[1] instanceof OpenPGP_UserIDPacket) { 369 | $raw = implode('', array_merge($sign[0]->fingerprint_material(), array(chr(0xB4), 370 | pack('N', strlen($sign[1]->body())), $sign[1]->body()))); 371 | } else if(isset($sign[1]) && ($sign[1] instanceof OpenPGP_PublicSubkeyPacket || $sign[1] instanceof OpenPGP_SecretSubkeyPacket)) { 372 | $raw = implode('', array_merge($sign[0]->fingerprint_material(), $sign[1]->fingerprint_material())); 373 | } else if($sign[0] instanceof OpenPGP_PublicKeyPacket) { 374 | $raw = implode('', $sign[0]->fingerprint_material()); 375 | } else { 376 | return NULL; 377 | } 378 | return call_user_func($verifier, $raw.$sig->trailer, $sig); 379 | } 380 | 381 | // IteratorAggregate interface 382 | 383 | // function getIterator(): \Traversable { // when php 5 support is dropped 384 | #[\ReturnTypeWillChange] 385 | function getIterator() { 386 | return new ArrayIterator($this->packets); 387 | } 388 | 389 | // ArrayAccess interface 390 | 391 | // function offsetExists($offset): bool // when php 5 support is dropped 392 | #[\ReturnTypeWillChange] 393 | function offsetExists($offset) { 394 | return isset($this->packets[$offset]); 395 | } 396 | 397 | // function offsetGet($offset): mixed // when php 7.4 support is dropped 398 | #[\ReturnTypeWillChange] 399 | function offsetGet($offset) { 400 | return $this->packets[$offset]; 401 | } 402 | 403 | // function offsetSet($offset, $value): void // when php 5 support is dropped 404 | #[\ReturnTypeWillChange] 405 | function offsetSet($offset, $value) { 406 | is_null($offset) ? $this->packets[] = $value : $this->packets[$offset] = $value; 407 | } 408 | 409 | // function offsetUnset($offset): void // when php 5 support is dropped 410 | #[\ReturnTypeWillChange] 411 | function offsetUnset($offset) { 412 | unset($this->packets[$offset]); 413 | } 414 | } 415 | 416 | ////////////////////////////////////////////////////////////////////////////// 417 | // OpenPGP packets 418 | 419 | /** 420 | * OpenPGP packet. 421 | * 422 | * @see http://tools.ietf.org/html/rfc4880#section-4.1 423 | * @see http://tools.ietf.org/html/rfc4880#section-4.3 424 | */ 425 | class OpenPGP_Packet { 426 | public $tag, $size, $data; 427 | 428 | static function class_for($tag) { 429 | return isset(self::$tags[$tag]) && class_exists( 430 | $class = 'OpenPGP_' . self::$tags[$tag] . 'Packet') ? $class : __CLASS__; 431 | } 432 | 433 | /** 434 | * Parses an OpenPGP packet. 435 | * 436 | * Partial body lengths based on https://github.com/toofishes/python-pgpdump/blob/master/pgpdump/packet.py 437 | * 438 | * @see http://tools.ietf.org/html/rfc4880#section-4.2 439 | */ 440 | static function parse(&$input) { 441 | $packet = NULL; 442 | if (strlen($input) > 0) { 443 | $parser = ord($input[0]) & 64 ? 'parse_new_format' : 'parse_old_format'; 444 | 445 | $header_start0 = 0; 446 | $consumed = 0; 447 | $packet_data = ""; 448 | do { 449 | list($tag, $data_offset, $data_length, $partial) = self::$parser($input, $header_start0); 450 | 451 | $data_start0 = $header_start0 + $data_offset; 452 | $header_start0 = $data_start0 + $data_length - 1; 453 | $packet_data .= substr($input, $data_start0, $data_length); 454 | 455 | $consumed += $data_offset + $data_length; 456 | if ($partial) { 457 | $consumed -= 1; 458 | } 459 | } while ($partial === true && $parser === 'parse_new_format'); 460 | 461 | if ($tag && ($class = self::class_for($tag))) { 462 | $packet = new $class(); 463 | $packet->tag = $tag; 464 | $packet->input = $packet_data; 465 | $packet->length = strlen($packet_data); 466 | $packet->read(); 467 | unset($packet->input); 468 | unset($packet->length); 469 | } 470 | $input = substr($input, $consumed); 471 | } 472 | return $packet; 473 | } 474 | 475 | /** 476 | * Parses a new-format (RFC 4880) OpenPGP packet. 477 | * 478 | * @see http://tools.ietf.org/html/rfc4880#section-4.2.2 479 | */ 480 | static function parse_new_format($input, $header_start = 0) { 481 | $tag = ord($input[0]) & 63; 482 | $len = ord($input[$header_start + 1]); 483 | if($len < 192) { // One octet length 484 | return array($tag, 2, $len, false); 485 | } 486 | if($len > 191 && $len < 224) { // Two octet length 487 | return array($tag, 3, (($len - 192) << 8) + ord($input[$header_start + 2]) + 192, false); 488 | } 489 | if($len == 255) { // Five octet length 490 | $unpacked = unpack('N', substr($input, $header_start + 2, 4)); 491 | return array($tag, 6, reset($unpacked), false); 492 | } 493 | // Partial body lengths 494 | return array($tag, 2, 1 << ($len & 0x1f), true); 495 | } 496 | 497 | /** 498 | * Parses an old-format (PGP 2.6.x) OpenPGP packet. 499 | * 500 | * @see http://tools.ietf.org/html/rfc4880#section-4.2.1 501 | */ 502 | static function parse_old_format($input) { 503 | $len = ($tag = ord($input[0])) & 3; 504 | $tag = ($tag >> 2) & 15; 505 | switch ($len) { 506 | case 0: // The packet has a one-octet length. The header is 2 octets long. 507 | $head_length = 2; 508 | $data_length = ord($input[1]); 509 | break; 510 | case 1: // The packet has a two-octet length. The header is 3 octets long. 511 | $head_length = 3; 512 | $data_length = unpack('n', substr($input, 1, 2)); 513 | $data_length = $data_length[1]; 514 | break; 515 | case 2: // The packet has a four-octet length. The header is 5 octets long. 516 | $head_length = 5; 517 | $data_length = unpack('N', substr($input, 1, 4)); 518 | $data_length = $data_length[1]; 519 | break; 520 | case 3: // The packet is of indeterminate length. The header is 1 octet long. 521 | $head_length = 1; 522 | $data_length = strlen($input) - $head_length; 523 | break; 524 | } 525 | return array($tag, $head_length, $data_length, false); 526 | } 527 | 528 | function __construct($data=NULL) { 529 | $this->tag = array_search(substr(substr(get_class($this), 8), 0, -6), self::$tags); 530 | $this->data = $data; 531 | } 532 | 533 | function read() { 534 | } 535 | 536 | function body() { 537 | return $this->data; // Will normally be overridden by subclasses 538 | } 539 | 540 | function header_and_body() { 541 | $body = $this->body() ?? ''; // Get body first, we will need it's length 542 | $tag = chr($this->tag | 0xC0); // First two bits are 1 for new packet format 543 | $size = chr(255).pack('N', strlen($body)); // Use 5-octet lengths 544 | return array('header' => $tag.$size, 'body' => $body); 545 | } 546 | 547 | function to_bytes() { 548 | $data = $this->header_and_body(); 549 | return $data['header'].$data['body']; 550 | } 551 | 552 | /** 553 | * @see http://tools.ietf.org/html/rfc4880#section-3.5 554 | */ 555 | function read_timestamp() { 556 | return $this->read_unpacked(4, 'N'); 557 | } 558 | 559 | /** 560 | * @see http://tools.ietf.org/html/rfc4880#section-3.2 561 | */ 562 | function read_mpi() { 563 | $length = $this->read_unpacked(2, 'n'); // length in bits 564 | $length = (int)floor(($length + 7) / 8); // length in bytes 565 | return $this->read_bytes($length); 566 | } 567 | 568 | /** 569 | * @see http://php.net/manual/en/function.unpack.php 570 | */ 571 | function read_unpacked($count, $format) { 572 | $unpacked = unpack($format, $this->read_bytes($count)); 573 | return is_array($unpacked) ? reset($unpacked) : NULL; 574 | } 575 | 576 | function read_byte() { 577 | return !is_null($bytes = $this->read_bytes()) ? $bytes[0] : NULL; 578 | } 579 | 580 | function read_bytes($count = 1) { 581 | $bytes = substr($this->input, 0, $count); 582 | $this->input = substr($this->input, $count); 583 | return $bytes; 584 | } 585 | 586 | static $tags = array( 587 | 1 => 'AsymmetricSessionKey', // Public-Key Encrypted Session Key 588 | 2 => 'Signature', // Signature Packet 589 | 3 => 'SymmetricSessionKey', // Symmetric-Key Encrypted Session Key Packet 590 | 4 => 'OnePassSignature', // One-Pass Signature Packet 591 | 5 => 'SecretKey', // Secret-Key Packet 592 | 6 => 'PublicKey', // Public-Key Packet 593 | 7 => 'SecretSubkey', // Secret-Subkey Packet 594 | 8 => 'CompressedData', // Compressed Data Packet 595 | 9 => 'EncryptedData', // Symmetrically Encrypted Data Packet 596 | 10 => 'Marker', // Marker Packet 597 | 11 => 'LiteralData', // Literal Data Packet 598 | 12 => 'Trust', // Trust Packet 599 | 13 => 'UserID', // User ID Packet 600 | 14 => 'PublicSubkey', // Public-Subkey Packet 601 | 17 => 'UserAttribute', // User Attribute Packet 602 | 18 => 'IntegrityProtectedData', // Sym. Encrypted and Integrity Protected Data Packet 603 | 19 => 'ModificationDetectionCode', // Modification Detection Code Packet 604 | 60 => 'Experimental', // Private or Experimental Values 605 | 61 => 'Experimental', // Private or Experimental Values 606 | 62 => 'Experimental', // Private or Experimental Values 607 | 63 => 'Experimental', // Private or Experimental Values 608 | ); 609 | } 610 | 611 | /** 612 | * OpenPGP Public-Key Encrypted Session Key packet (tag 1). 613 | * 614 | * @see http://tools.ietf.org/html/rfc4880#section-5.1 615 | */ 616 | class OpenPGP_AsymmetricSessionKeyPacket extends OpenPGP_Packet { 617 | public $version, $keyid, $key_algorithm, $encrypted_data; 618 | 619 | public $input; 620 | 621 | public $length; 622 | 623 | function __construct($key_algorithm='', $keyid='', $encrypted_data='', $version=3) { 624 | parent::__construct(); 625 | $this->version = $version; 626 | $this->keyid = substr($keyid, -16); 627 | $this->key_algorithm = $key_algorithm; 628 | $this->encrypted_data = $encrypted_data; 629 | } 630 | 631 | function read() { 632 | switch($this->version = ord($this->read_byte())) { 633 | case 3: 634 | $rawkeyid = $this->read_bytes(8); 635 | $this->keyid = ''; 636 | for($i = 0; $i < strlen($rawkeyid); $i++) { // Store KeyID in Hex 637 | $this->keyid .= sprintf('%02X',ord($rawkeyid[$i])); 638 | } 639 | 640 | $this->key_algorithm = ord($this->read_byte()); 641 | 642 | $this->encrypted_data = $this->input; 643 | break; 644 | default: 645 | throw new Exception("Unsupported AsymmetricSessionKeyPacket version: " . $this->version); 646 | } 647 | } 648 | 649 | function body() { 650 | $bytes = chr($this->version); 651 | 652 | for($i = 0; $i < strlen($this->keyid); $i += 2) { 653 | $bytes .= chr(hexdec($this->keyid[$i].$this->keyid[$i+1])); 654 | } 655 | 656 | $bytes .= chr($this->key_algorithm); 657 | $bytes .= $this->encrypted_data; 658 | return $bytes; 659 | } 660 | } 661 | 662 | /** 663 | * OpenPGP Signature packet (tag 2). 664 | * Be sure to NULL the trailer if you update a signature packet! 665 | * 666 | * @see http://tools.ietf.org/html/rfc4880#section-5.2 667 | */ 668 | class OpenPGP_SignaturePacket extends OpenPGP_Packet { 669 | public $version, $signature_type, $hash_algorithm, $key_algorithm, $hashed_subpackets, $unhashed_subpackets, $hash_head; 670 | public $trailer; // This is the literal bytes that get tacked on the end of the message when verifying the signature 671 | 672 | public $input; 673 | 674 | public $length; 675 | 676 | function __construct($data=NULL, $key_algorithm=NULL, $hash_algorithm=NULL) { 677 | parent::__construct(); 678 | $this->version = 4; // Default to version 4 sigs 679 | if(is_string($this->hash_algorithm = $hash_algorithm)) { 680 | $this->hash_algorithm = array_search($this->hash_algorithm, self::$hash_algorithms); 681 | } 682 | if(is_string($this->key_algorithm = $key_algorithm)) { 683 | $this->key_algorithm = array_search($this->key_algorithm, OpenPGP_PublicKeyPacket::$algorithms); 684 | } 685 | if($data) { // If we have any data, set up the creation time 686 | $this->hashed_subpackets = array(new OpenPGP_SignaturePacket_SignatureCreationTimePacket(time())); 687 | } 688 | if($data instanceof OpenPGP_LiteralDataPacket) { 689 | $this->signature_type = ($data->format == 'b') ? 0x00 : 0x01; 690 | $data->normalize(); 691 | $data = $data->data; 692 | } else if($data instanceof OpenPGP_Message && $data[0] instanceof OpenPGP_PublicKeyPacket) { 693 | // $data is a message with PublicKey first, UserID second 694 | $key = implode('', $data[0]->fingerprint_material()); 695 | $user_id = $data[1]->body(); 696 | $data = $key . chr(0xB4) . pack('N', strlen($user_id)) . $user_id; 697 | } 698 | $this->data = $data; // Store to-be-signed data in here until the signing happens 699 | } 700 | 701 | /** 702 | * $this->data must be set to the data to sign (done by constructor) 703 | * $signers in the same format as $verifiers for OpenPGP_Message. 704 | */ 705 | function sign_data($signers) { 706 | $this->trailer = $this->calculate_trailer(); 707 | $signer = $signers[$this->key_algorithm_name()][$this->hash_algorithm_name()]; 708 | $signed = call_user_func($signer, $this->data.$this->trailer); 709 | $this->data = array($signed["signed"]); 710 | $unpacked = unpack('n', substr($signed["hash"], 0, 2)); 711 | $this->hash_head = reset($unpacked); 712 | } 713 | 714 | function read() { 715 | switch($this->version = ord($this->read_byte())) { 716 | case 2: 717 | case 3: 718 | if(ord($this->read_byte()) != 5) { 719 | throw new Exception("Invalid version 2 or 3 SignaturePacket"); 720 | } 721 | $this->signature_type = ord($this->read_byte()); 722 | $creation_time = $this->read_timestamp(); 723 | $keyid = $this->read_bytes(8); 724 | $keyidHex = ''; 725 | for($i = 0; $i < strlen($keyid); $i++) { // Store KeyID in Hex 726 | $keyidHex .= sprintf('%02X',ord($keyid[$i])); 727 | } 728 | 729 | $this->hashed_subpackets = array(); 730 | $this->unhashed_subpackets = array( 731 | new OpenPGP_SignaturePacket_SignatureCreationTimePacket($creation_time), 732 | new OpenPGP_SignaturePacket_IssuerPacket($keyidHex) 733 | ); 734 | 735 | $this->key_algorithm = ord($this->read_byte()); 736 | $this->hash_algorithm = ord($this->read_byte()); 737 | $this->hash_head = $this->read_unpacked(2, 'n'); 738 | $this->data = array(); 739 | while(strlen($this->input) > 0) { 740 | $this->data[] = $this->read_mpi(); 741 | } 742 | break; 743 | case 4: 744 | $this->signature_type = ord($this->read_byte()); 745 | $this->key_algorithm = ord($this->read_byte()); 746 | $this->hash_algorithm = ord($this->read_byte()); 747 | $this->trailer = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm); 748 | 749 | $hashed_size = $this->read_unpacked(2, 'n'); 750 | $hashed_subpackets = $this->read_bytes($hashed_size); 751 | $this->trailer .= pack('n', $hashed_size).$hashed_subpackets; 752 | $this->hashed_subpackets = self::get_subpackets($hashed_subpackets); 753 | 754 | $this->trailer .= chr(4).chr(0xff).pack('N', 6 + $hashed_size); 755 | 756 | $unhashed_size = $this->read_unpacked(2, 'n'); 757 | $this->unhashed_subpackets = self::get_subpackets($this->read_bytes($unhashed_size)); 758 | 759 | $this->hash_head = $this->read_unpacked(2, 'n'); 760 | 761 | $this->data = array(); 762 | while(strlen($this->input) > 0) { 763 | $this->data[] = $this->read_mpi(); 764 | } 765 | break; 766 | } 767 | } 768 | 769 | function calculate_trailer() { 770 | // The trailer is just the top of the body plus some crap 771 | $body = $this->body_start(); 772 | return $body.chr(4).chr(0xff).pack('N', strlen($body)); 773 | } 774 | 775 | function body_start() { 776 | $body = chr(4).chr($this->signature_type).chr($this->key_algorithm).chr($this->hash_algorithm); 777 | 778 | $hashed_subpackets = ''; 779 | foreach((array)$this->hashed_subpackets as $p) { 780 | $hashed_subpackets .= $p->to_bytes(); 781 | } 782 | $body .= pack('n', strlen($hashed_subpackets)).$hashed_subpackets; 783 | 784 | return $body; 785 | } 786 | 787 | function body() { 788 | switch($this->version) { 789 | case 2: 790 | case 3: 791 | $body = chr($this->version) . chr(5) . chr($this->signature_type); 792 | 793 | foreach((array)$this->unhashed_subpackets as $p) { 794 | if($p instanceof OpenPGP_SignaturePacket_SignatureCreationTimePacket) { 795 | $body .= pack('N', $p->data); 796 | break; 797 | } 798 | } 799 | 800 | foreach((array)$this->unhashed_subpackets as $p) { 801 | if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) { 802 | for($i = 0; $i < strlen($p->data); $i += 2) { 803 | $body .= chr(hexdec($p->data[$i].$p->data[$i+1])); 804 | } 805 | break; 806 | } 807 | } 808 | 809 | $body .= chr($this->key_algorithm); 810 | $body .= chr($this->hash_algorithm); 811 | $body .= pack('n', $this->hash_head); 812 | 813 | foreach($this->data as $mpi) { 814 | $body .= pack('n', OpenPGP::bitlength($mpi)).$mpi; 815 | } 816 | 817 | return $body; 818 | case 4: 819 | if(!$this->trailer) $this->trailer = $this->calculate_trailer(); 820 | $body = substr($this->trailer, 0, -6); 821 | 822 | $unhashed_subpackets = ''; 823 | foreach((array)$this->unhashed_subpackets as $p) { 824 | $unhashed_subpackets .= $p->to_bytes(); 825 | } 826 | $body .= pack('n', strlen($unhashed_subpackets)).$unhashed_subpackets; 827 | 828 | $body .= pack('n', $this->hash_head); 829 | 830 | foreach((array)$this->data as $mpi) { 831 | $body .= pack('n', OpenPGP::bitlength($mpi)).$mpi; 832 | } 833 | 834 | return $body; 835 | } 836 | } 837 | 838 | function key_algorithm_name() { 839 | return OpenPGP_PublicKeyPacket::$algorithms[$this->key_algorithm]; 840 | } 841 | 842 | function hash_algorithm_name() { 843 | return self::$hash_algorithms[$this->hash_algorithm]; 844 | } 845 | 846 | function issuer() { 847 | foreach($this->hashed_subpackets as $p) { 848 | if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) return $p->data; 849 | } 850 | foreach($this->unhashed_subpackets as $p) { 851 | if($p instanceof OpenPGP_SignaturePacket_IssuerPacket) return $p->data; 852 | } 853 | return NULL; 854 | } 855 | 856 | /** 857 | * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.1 858 | */ 859 | static function get_subpackets($input) { 860 | $subpackets = array(); 861 | while(($length = strlen($input)) > 0) { 862 | $subpackets[] = self::get_subpacket($input); 863 | if($length == strlen($input)) { // Parsing stuck? 864 | break; 865 | } 866 | } 867 | return $subpackets; 868 | } 869 | 870 | static function get_subpacket(&$input) { 871 | $len = ord($input[0]); 872 | $length_of_length = 1; 873 | // if($len < 192) One octet length, no furthur processing 874 | if($len > 190 && $len < 255) { // Two octet length 875 | $length_of_length = 2; 876 | $len = (($len - 192) << 8) + ord($input[1]) + 192; 877 | } 878 | if($len == 255) { // Five octet length 879 | $length_of_length = 5; 880 | $unpacked = unpack('N', substr($input, 1, 4)); 881 | $len = reset($unpacked); 882 | } 883 | $input = substr($input, $length_of_length); // Chop off length header 884 | $tag = ord($input[0]); 885 | // Is the subpacket critical? 886 | $criticalFlagMask = 0x80; 887 | $typeMask = 0x7F; 888 | $isCritical = ($tag & $criticalFlagMask) === $criticalFlagMask; 889 | $tag = $tag & $typeMask; 890 | $class = self::class_for($tag); 891 | if($class) { 892 | $packet = new $class(); 893 | $packet->tag = $tag; 894 | $packet->isCritical = $isCritical; 895 | $packet->input = substr($input, 1, $len-1); 896 | $packet->length = $len-1; 897 | $packet->read(); 898 | unset($packet->input); 899 | unset($packet->length); 900 | } 901 | $input = substr($input, $len); // Chop off the data from this packet 902 | return $packet; 903 | } 904 | 905 | static $hash_algorithms = array( 906 | 1 => 'MD5', 907 | 2 => 'SHA1', 908 | 3 => 'RIPEMD160', 909 | 8 => 'SHA256', 910 | 9 => 'SHA384', 911 | 10 => 'SHA512', 912 | 11 => 'SHA224' 913 | ); 914 | 915 | static $subpacket_types = array( 916 | //0 => 'Reserved', 917 | //1 => 'Reserved', 918 | 2 => 'SignatureCreationTime', 919 | 3 => 'SignatureExpirationTime', 920 | 4 => 'ExportableCertification', 921 | 5 => 'TrustSignature', 922 | 6 => 'RegularExpression', 923 | 7 => 'Revocable', 924 | //8 => 'Reserved', 925 | 9 => 'KeyExpirationTime', 926 | //10 => 'Placeholder for backward compatibility', 927 | 11 => 'PreferredSymmetricAlgorithms', 928 | 12 => 'RevocationKey', 929 | //13 => 'Reserved', 930 | //14 => 'Reserved', 931 | //15 => 'Reserved', 932 | 16 => 'Issuer', 933 | //17 => 'Reserved', 934 | //18 => 'Reserved', 935 | //19 => 'Reserved', 936 | 20 => 'NotationData', 937 | 21 => 'PreferredHashAlgorithms', 938 | 22 => 'PreferredCompressionAlgorithms', 939 | 23 => 'KeyServerPreferences', 940 | 24 => 'PreferredKeyServer', 941 | 25 => 'PrimaryUserID', 942 | 26 => 'PolicyURI', 943 | 27 => 'KeyFlags', 944 | 28 => 'SignersUserID', 945 | 29 => 'ReasonforRevocation', 946 | 30 => 'Features', 947 | 31 => 'SignatureTarget', 948 | 32 => 'EmbeddedSignature', 949 | ); 950 | 951 | static function class_for($tag) { 952 | if(!isset(self::$subpacket_types[$tag])) return 'OpenPGP_SignaturePacket_Subpacket'; 953 | return 'OpenPGP_SignaturePacket_'.self::$subpacket_types[$tag].'Packet'; 954 | } 955 | 956 | } 957 | 958 | class OpenPGP_SignaturePacket_Subpacket extends OpenPGP_Packet { 959 | public $input; 960 | 961 | public $isCritical = false; 962 | 963 | public $length; 964 | 965 | function __construct($data=NULL) { 966 | parent::__construct($data); 967 | $this->tag = array_search(substr(substr(get_class($this), 8+16), 0, -6), OpenPGP_SignaturePacket::$subpacket_types); 968 | } 969 | 970 | function header_and_body() { 971 | $body = $this->body(); // Get body first, we will need it's length 972 | $size = chr(255).pack('N', strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet 973 | $tag = chr($this->tag); 974 | return array('header' => $size.$tag, 'body' => $body); 975 | } 976 | 977 | /* Defaults for unsupported packets */ 978 | function read() { 979 | $this->data = $this->input; 980 | } 981 | 982 | function body() { 983 | return $this->data; 984 | } 985 | } 986 | 987 | /** 988 | * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.4 989 | */ 990 | class OpenPGP_SignaturePacket_SignatureCreationTimePacket extends OpenPGP_SignaturePacket_Subpacket { 991 | function read() { 992 | $this->data = $this->read_timestamp(); 993 | } 994 | 995 | function body() { 996 | return pack('N', $this->data); 997 | } 998 | } 999 | 1000 | class OpenPGP_SignaturePacket_SignatureExpirationTimePacket extends OpenPGP_SignaturePacket_Subpacket { 1001 | function read() { 1002 | $this->data = $this->read_timestamp(); 1003 | } 1004 | 1005 | function body() { 1006 | return pack('N', $this->data); 1007 | } 1008 | } 1009 | 1010 | class OpenPGP_SignaturePacket_ExportableCertificationPacket extends OpenPGP_SignaturePacket_Subpacket { 1011 | function read() { 1012 | $this->data = (ord($this->input) != 0); 1013 | } 1014 | 1015 | function body() { 1016 | return chr($this->data ? 1 : 0); 1017 | } 1018 | } 1019 | 1020 | class OpenPGP_SignaturePacket_TrustSignaturePacket extends OpenPGP_SignaturePacket_Subpacket { 1021 | function read() { 1022 | $this->depth = ord($this->input[0]); 1023 | $this->trust = ord($this->input[1]); 1024 | } 1025 | 1026 | function body() { 1027 | return chr($this->depth) . chr($this->trust); 1028 | } 1029 | } 1030 | 1031 | class OpenPGP_SignaturePacket_RegularExpressionPacket extends OpenPGP_SignaturePacket_Subpacket { 1032 | function read() { 1033 | $this->data = substr($this->input, 0, -1); 1034 | } 1035 | 1036 | function body() { 1037 | return $this->data . chr(0); 1038 | } 1039 | } 1040 | 1041 | class OpenPGP_SignaturePacket_RevocablePacket extends OpenPGP_SignaturePacket_Subpacket { 1042 | function read() { 1043 | $this->data = (ord($this->input) != 0); 1044 | } 1045 | 1046 | function body() { 1047 | return chr($this->data ? 1 : 0); 1048 | } 1049 | } 1050 | 1051 | class OpenPGP_SignaturePacket_KeyExpirationTimePacket extends OpenPGP_SignaturePacket_Subpacket { 1052 | function read() { 1053 | $this->data = $this->read_timestamp(); 1054 | } 1055 | 1056 | function body() { 1057 | return pack('N', $this->data); 1058 | } 1059 | } 1060 | 1061 | class OpenPGP_SignaturePacket_PreferredSymmetricAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket { 1062 | function read() { 1063 | $this->data = array(); 1064 | while(strlen($this->input) > 0) { 1065 | $this->data[] = ord($this->read_byte()); 1066 | } 1067 | } 1068 | 1069 | function body() { 1070 | $bytes = ''; 1071 | foreach($this->data as $algo) { 1072 | $bytes .= chr($algo); 1073 | } 1074 | return $bytes; 1075 | } 1076 | } 1077 | 1078 | class OpenPGP_SignaturePacket_RevocationKeyPacket extends OpenPGP_SignaturePacket_Subpacket { 1079 | public $key_algorithm, $fingerprint, $sensitive; 1080 | 1081 | function read() { 1082 | // bitfield must have bit 0x80 set, says the spec 1083 | $bitfield = ord($this->read_byte()); 1084 | $this->sensitive = $bitfield & 0x40 == 0x40; 1085 | $this->key_algorithm = ord($this->read_byte()); 1086 | 1087 | $this->fingerprint = ''; 1088 | while(strlen($this->input) > 0) { 1089 | $this->fingerprint .= sprintf('%02X',ord($this->read_byte())); 1090 | } 1091 | } 1092 | 1093 | function body() { 1094 | $bytes = ''; 1095 | $bytes .= chr(0x80 | ($this->sensitive ? 0x40 : 0x00)); 1096 | $bytes .= chr($this->key_algorithm); 1097 | 1098 | for($i = 0; $i < strlen($this->fingerprint); $i += 2) { 1099 | $bytes .= chr(hexdec($this->fingerprint[$i].$this->fingerprint[$i+1])); 1100 | } 1101 | 1102 | return $bytes; 1103 | } 1104 | } 1105 | 1106 | /** 1107 | * @see http://tools.ietf.org/html/rfc4880#section-5.2.3.5 1108 | */ 1109 | class OpenPGP_SignaturePacket_IssuerPacket extends OpenPGP_SignaturePacket_Subpacket { 1110 | function read() { 1111 | for($i = 0; $i < 8; $i++) { // Store KeyID in Hex 1112 | $this->data .= sprintf('%02X',ord($this->read_byte())); 1113 | } 1114 | } 1115 | 1116 | function body() { 1117 | $bytes = ''; 1118 | for($i = 0; $i < strlen($this->data); $i += 2) { 1119 | $bytes .= chr(hexdec($this->data[$i].$this->data[$i+1])); 1120 | } 1121 | return $bytes; 1122 | } 1123 | } 1124 | 1125 | class OpenPGP_SignaturePacket_NotationDataPacket extends OpenPGP_SignaturePacket_Subpacket { 1126 | public $human_readable, $name; 1127 | 1128 | function read() { 1129 | $flags = $this->read_bytes(4); 1130 | $namelen = $this->read_unpacked(2, 'n'); 1131 | $datalen = $this->read_unpacked(2, 'n'); 1132 | $this->human_readable = ord($flags[0]) & 0x80 == 0x80; 1133 | $this->name = $this->read_bytes($namelen); 1134 | $this->data = $this->read_bytes($datalen); 1135 | } 1136 | 1137 | function body () { 1138 | return chr($this->human_readable ? 0x80 : 0x00) . "\0\0\0" . 1139 | pack('n', strlen($this->name)) . pack('n', strlen($this->data)) . 1140 | $this->name . $this->data; 1141 | } 1142 | } 1143 | 1144 | class OpenPGP_SignaturePacket_PreferredHashAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket { 1145 | function read() { 1146 | $this->data = array(); 1147 | while(strlen($this->input) > 0) { 1148 | $this->data[] = ord($this->read_byte()); 1149 | } 1150 | } 1151 | 1152 | function body() { 1153 | $bytes = ''; 1154 | foreach($this->data as $algo) { 1155 | $bytes .= chr($algo); 1156 | } 1157 | return $bytes; 1158 | } 1159 | } 1160 | 1161 | class OpenPGP_SignaturePacket_PreferredCompressionAlgorithmsPacket extends OpenPGP_SignaturePacket_Subpacket { 1162 | function read() { 1163 | $this->data = array(); 1164 | while(strlen($this->input) > 0) { 1165 | $this->data[] = ord($this->read_byte()); 1166 | } 1167 | } 1168 | 1169 | function body() { 1170 | $bytes = ''; 1171 | foreach($this->data as $algo) { 1172 | $bytes .= chr($algo); 1173 | } 1174 | return $bytes; 1175 | } 1176 | } 1177 | 1178 | class OpenPGP_SignaturePacket_KeyServerPreferencesPacket extends OpenPGP_SignaturePacket_Subpacket { 1179 | public $no_modify; 1180 | 1181 | function read() { 1182 | $flags = ord($this->input); 1183 | $this->no_modify = $flags & 0x80 == 0x80; 1184 | } 1185 | 1186 | function body() { 1187 | return chr($this->no_modify ? 0x80 : 0x00); 1188 | } 1189 | } 1190 | 1191 | class OpenPGP_SignaturePacket_PreferredKeyServerPacket extends OpenPGP_SignaturePacket_Subpacket { 1192 | function read() { 1193 | $this->data = $this->input; 1194 | } 1195 | 1196 | function body() { 1197 | return $this->data; 1198 | } 1199 | } 1200 | 1201 | class OpenPGP_SignaturePacket_PrimaryUserIDPacket extends OpenPGP_SignaturePacket_Subpacket { 1202 | function read() { 1203 | $this->data = (ord($this->input) != 0); 1204 | } 1205 | 1206 | function body() { 1207 | return chr($this->data ? 1 : 0); 1208 | } 1209 | 1210 | } 1211 | 1212 | class OpenPGP_SignaturePacket_PolicyURIPacket extends OpenPGP_SignaturePacket_Subpacket { 1213 | function read() { 1214 | $this->data = $this->input; 1215 | } 1216 | 1217 | function body() { 1218 | return $this->data; 1219 | } 1220 | } 1221 | 1222 | class OpenPGP_SignaturePacket_KeyFlagsPacket extends OpenPGP_SignaturePacket_Subpacket { 1223 | public $flags; 1224 | 1225 | function __construct($flags=array()) { 1226 | parent::__construct(); 1227 | $this->flags = $flags; 1228 | } 1229 | 1230 | function read() { 1231 | $this->flags = array(); 1232 | while($this->input) { 1233 | $this->flags[] = ord($this->read_byte()); 1234 | } 1235 | } 1236 | 1237 | function body() { 1238 | $bytes = ''; 1239 | foreach($this->flags as $f) { 1240 | $bytes .= chr($f); 1241 | } 1242 | return $bytes; 1243 | } 1244 | } 1245 | 1246 | class OpenPGP_SignaturePacket_SignersUserIDPacket extends OpenPGP_SignaturePacket_Subpacket { 1247 | function read() { 1248 | $this->data = $this->input; 1249 | } 1250 | 1251 | function body() { 1252 | return $this->data; 1253 | } 1254 | } 1255 | 1256 | class OpenPGP_SignaturePacket_ReasonforRevocationPacket extends OpenPGP_SignaturePacket_Subpacket { 1257 | public $code; 1258 | 1259 | function read() { 1260 | $this->code = ord($this->read_byte()); 1261 | $this->data = $this->input; 1262 | } 1263 | 1264 | function body() { 1265 | return chr($this->code) . $this->data; 1266 | } 1267 | } 1268 | 1269 | 1270 | class OpenPGP_SignaturePacket_FeaturesPacket extends OpenPGP_SignaturePacket_KeyFlagsPacket { 1271 | // Identical functionality to parent 1272 | } 1273 | 1274 | class OpenPGP_SignaturePacket_SignatureTargetPacket extends OpenPGP_SignaturePacket_Subpacket { 1275 | public $key_algorithm, $hash_algorithm; 1276 | 1277 | function read() { 1278 | $this->key_algorithm = ord($this->read_byte()); 1279 | $this->hash_algorithm = ord($this->read_byte()); 1280 | $this->data = $this->input; 1281 | } 1282 | 1283 | function body() { 1284 | return chr($this->key_algorithm) . chr($this->hash_algorithm) . $this->data; 1285 | } 1286 | 1287 | } 1288 | 1289 | class OpenPGP_SignaturePacket_EmbeddedSignaturePacket extends OpenPGP_SignaturePacket { 1290 | // TODO: This is duplicated from subpacket... improve? 1291 | function __construct($data=NULL) { 1292 | parent::__construct($data); 1293 | $this->tag = array_search(substr(substr(get_class($this), 8+16), 0, -6), OpenPGP_SignaturePacket::$subpacket_types); 1294 | } 1295 | 1296 | function header_and_body() { 1297 | $body = $this->body(); // Get body first, we will need it's length 1298 | $size = chr(255).pack('N', strlen($body)+1); // Use 5-octet lengths + 1 for tag as first packet body octet 1299 | $tag = chr($this->tag); 1300 | return array('header' => $size.$tag, 'body' => $body); 1301 | } 1302 | } 1303 | 1304 | /** 1305 | * OpenPGP Symmetric-Key Encrypted Session Key packet (tag 3). 1306 | * 1307 | * @see http://tools.ietf.org/html/rfc4880#section-5.3 1308 | */ 1309 | class OpenPGP_SymmetricSessionKeyPacket extends OpenPGP_Packet { 1310 | public $version, $symmetric_algorithm, $s2k, $encrypted_data; 1311 | 1312 | public $input; 1313 | 1314 | public $length; 1315 | 1316 | function __construct($s2k=NULL, $encrypted_data='', $symmetric_algorithm=9, $version=3) { 1317 | parent::__construct(); 1318 | $this->version = $version; 1319 | $this->symmetric_algorithm = $symmetric_algorithm; 1320 | $this->s2k = $s2k; 1321 | $this->encrypted_data = $encrypted_data; 1322 | } 1323 | 1324 | function read() { 1325 | $this->version = ord($this->read_byte()); 1326 | $this->symmetric_algorithm = ord($this->read_byte()); 1327 | $this->s2k = OpenPGP_S2k::parse($this->input); 1328 | $this->encrypted_data = $this->input; 1329 | } 1330 | 1331 | function body() { 1332 | return chr($this->version) . chr($this->symmetric_algorithm) . 1333 | $this->s2k->to_bytes() . $this->encrypted_data; 1334 | } 1335 | } 1336 | 1337 | /** 1338 | * OpenPGP One-Pass Signature packet (tag 4). 1339 | * 1340 | * @see http://tools.ietf.org/html/rfc4880#section-5.4 1341 | */ 1342 | class OpenPGP_OnePassSignaturePacket extends OpenPGP_Packet { 1343 | public $version, $signature_type, $hash_algorithm, $key_algorithm, $key_id, $nested; 1344 | 1345 | public $input; 1346 | 1347 | public $length; 1348 | 1349 | function read() { 1350 | $this->version = ord($this->read_byte()); 1351 | $this->signature_type = ord($this->read_byte()); 1352 | $this->hash_algorithm = ord($this->read_byte()); 1353 | $this->key_algorithm = ord($this->read_byte()); 1354 | for($i = 0; $i < 8; $i++) { // Store KeyID in Hex 1355 | $this->key_id .= sprintf('%02X',ord($this->read_byte())); 1356 | } 1357 | $this->nested = ord($this->read_byte()); 1358 | } 1359 | 1360 | function body() { 1361 | $body = chr($this->version).chr($this->signature_type).chr($this->hash_algorithm).chr($this->key_algorithm); 1362 | for($i = 0; $i < strlen($this->key_id); $i += 2) { 1363 | $body .= chr(hexdec($this->key_id[$i].$this->key_id[$i+1])); 1364 | } 1365 | $body .= chr((int)$this->nested); 1366 | return $body; 1367 | } 1368 | } 1369 | 1370 | /** 1371 | * OpenPGP Public-Key packet (tag 6). 1372 | * 1373 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.1 1374 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 1375 | * @see http://tools.ietf.org/html/rfc4880#section-11.1 1376 | * @see http://tools.ietf.org/html/rfc4880#section-12 1377 | */ 1378 | class OpenPGP_PublicKeyPacket extends OpenPGP_Packet { 1379 | public $version, $timestamp, $algorithm; 1380 | public $key, $key_id, $fingerprint; 1381 | public $v3_days_of_validity; 1382 | 1383 | public $input; 1384 | 1385 | public $length; 1386 | 1387 | function __construct($key=array(), $algorithm='RSA', $timestamp=NULL, $version=4) { 1388 | parent::__construct(); 1389 | 1390 | if($key instanceof OpenPGP_PublicKeyPacket) { 1391 | $this->algorithm = $key->algorithm; 1392 | $this->key = array(); 1393 | 1394 | // Restrict to only the fields we need 1395 | foreach (self::$key_fields[$this->algorithm] as $field) { 1396 | $this->key[$field] = $key->key[$field]; 1397 | } 1398 | 1399 | $this->key_id = $key->key_id; 1400 | $this->fingerprint = $key->fingerprint; 1401 | $this->timestamp = $key->timestamp; 1402 | $this->version = $key->version; 1403 | $this->v3_days_of_validity = $key->v3_days_of_validity; 1404 | } else { 1405 | $this->key = $key; 1406 | if(is_string($this->algorithm = $algorithm)) { 1407 | $this->algorithm = array_search($this->algorithm, self::$algorithms); 1408 | } 1409 | $this->timestamp = $timestamp ? $timestamp : time(); 1410 | $this->version = $version; 1411 | 1412 | if(count($this->key) > 0) { 1413 | $this->key_id = substr($this->fingerprint(), -8); 1414 | } 1415 | } 1416 | } 1417 | 1418 | // Find self signatures in a message, these often contain metadata about the key 1419 | function self_signatures($message) { 1420 | $sigs = array(); 1421 | $keyid16 = strtoupper(substr($this->fingerprint, -16)); 1422 | foreach($message as $p) { 1423 | if($p instanceof OpenPGP_SignaturePacket) { 1424 | if(strtoupper($p->issuer()) == $keyid16) { 1425 | $sigs[] = $p; 1426 | } else { 1427 | if(!is_array($p->hashed_subpackets)) { 1428 | break; 1429 | } 1430 | foreach(array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) { 1431 | if($s instanceof OpenPGP_SignaturePacket_EmbeddedSignaturePacket && strtoupper($s->issuer()) == $keyid16) { 1432 | $sigs[] = $p; 1433 | break; 1434 | } 1435 | } 1436 | } 1437 | } else if(count($sigs)) break; // After we've seen a self sig, the next non-sig stop all self-sigs 1438 | } 1439 | return $sigs; 1440 | } 1441 | 1442 | // Find expiry time of this key based on the self signatures in a message 1443 | function expires($message) { 1444 | foreach($this->self_signatures($message) as $p) { 1445 | foreach(array_merge($p->hashed_subpackets, $p->unhashed_subpackets) as $s) { 1446 | if($s instanceof OpenPGP_SignaturePacket_KeyExpirationTimePacket) { 1447 | return $this->timestamp + $s->data; 1448 | } 1449 | } 1450 | } 1451 | return NULL; // Never expires 1452 | } 1453 | 1454 | /** 1455 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 1456 | */ 1457 | function read() { 1458 | switch ($this->version = ord($this->read_byte())) { 1459 | case 3: 1460 | $this->timestamp = $this->read_timestamp(); 1461 | $this->v3_days_of_validity = $this->read_unpacked(2, 'n'); 1462 | $this->algorithm = ord($this->read_byte()); 1463 | $this->read_key_material(); 1464 | break; 1465 | case 4: 1466 | $this->timestamp = $this->read_timestamp(); 1467 | $this->algorithm = ord($this->read_byte()); 1468 | $this->read_key_material(); 1469 | } 1470 | } 1471 | 1472 | /** 1473 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 1474 | */ 1475 | function read_key_material() { 1476 | foreach (self::$key_fields[$this->algorithm] as $field) { 1477 | if (strlen($field) == 1) { 1478 | $this->key[$field] = $this->read_mpi(); 1479 | } else if ($field == 'oid') { 1480 | $len = ord($this->read_byte()); 1481 | $this->key[$field] = $this->read_bytes($len); 1482 | } else { 1483 | $this->key[$field] = ord($this->read_byte()); 1484 | } 1485 | } 1486 | $this->key_id = substr($this->fingerprint(), -8); 1487 | } 1488 | 1489 | function fingerprint_material() { 1490 | switch ($this->version) { 1491 | case 3: 1492 | $material = array(); 1493 | foreach (self::$key_fields[$this->algorithm] as $i) { 1494 | $material[] = pack('n', OpenPGP::bitlength($this->key[$i])); 1495 | $material[] = $this->key[$i]; 1496 | } 1497 | return $material; 1498 | case 4: 1499 | $head = array( 1500 | chr(0x99), NULL, 1501 | chr($this->version), pack('N', $this->timestamp), 1502 | chr($this->algorithm), 1503 | ); 1504 | $material = array(); 1505 | foreach (self::$key_fields[$this->algorithm] as $i) { 1506 | if (strlen($i) == 1) { 1507 | $material[] = pack('n', OpenPGP::bitlength($this->key[$i])); 1508 | $material[] = $this->key[$i]; 1509 | } else if ($i == 'oid') { 1510 | $material[] = chr(strlen($this->key[$i])); 1511 | $material[] = $this->key[$i]; 1512 | } else { 1513 | $material[] = chr($this->key[$i]); 1514 | } 1515 | } 1516 | $material = implode('', $material); 1517 | $head[1] = pack('n', 6 + strlen($material)); 1518 | $head[] = $material; 1519 | return $head; 1520 | } 1521 | } 1522 | 1523 | /** 1524 | * @see http://tools.ietf.org/html/rfc4880#section-12.2 1525 | * @see http://tools.ietf.org/html/rfc4880#section-3.3 1526 | */ 1527 | function fingerprint() { 1528 | switch ($this->version) { 1529 | case 2: 1530 | case 3: 1531 | return $this->fingerprint = strtoupper(md5(implode('', $this->fingerprint_material()))); 1532 | case 4: 1533 | return $this->fingerprint = strtoupper(sha1(implode('', $this->fingerprint_material()))); 1534 | } 1535 | } 1536 | 1537 | function body() { 1538 | switch ($this->version) { 1539 | case 2: 1540 | case 3: 1541 | return implode('', array_merge(array( 1542 | chr($this->version) . pack('N', $this->timestamp) . 1543 | pack('n', $this->v3_days_of_validity) . chr($this->algorithm) 1544 | ), $this->fingerprint_material()) 1545 | ); 1546 | case 4: 1547 | return implode('', array_slice($this->fingerprint_material(), 2)); 1548 | } 1549 | } 1550 | 1551 | static $key_fields = array( 1552 | 1 => array('n', 'e'), 1553 | 16 => array('p', 'g', 'y'), 1554 | 17 => array('p', 'q', 'g', 'y'), 1555 | 18 => array('oid', 'p', 'len', 'future', 'hash', 'algorithm'), 1556 | 19 => array('oid', 'p'), 1557 | 22 => array('oid', 'p') 1558 | ); 1559 | 1560 | static $algorithms = array( 1561 | 1 => 'RSA', 1562 | 2 => 'RSA', 1563 | 3 => 'RSA', 1564 | 16 => 'ELGAMAL', 1565 | 17 => 'DSA', 1566 | 18 => 'ECC', 1567 | 19 => 'ECDSA', 1568 | 21 => 'DH', 1569 | 22 => 'EdDSA' 1570 | ); 1571 | 1572 | } 1573 | 1574 | /** 1575 | * OpenPGP Public-Subkey packet (tag 14). 1576 | * 1577 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.2 1578 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.2 1579 | * @see http://tools.ietf.org/html/rfc4880#section-11.1 1580 | * @see http://tools.ietf.org/html/rfc4880#section-12 1581 | */ 1582 | class OpenPGP_PublicSubkeyPacket extends OpenPGP_PublicKeyPacket { 1583 | public $input; 1584 | 1585 | public $length; 1586 | 1587 | // TODO 1588 | } 1589 | 1590 | /** 1591 | * OpenPGP Secret-Key packet (tag 5). 1592 | * 1593 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.3 1594 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.3 1595 | * @see http://tools.ietf.org/html/rfc4880#section-11.2 1596 | * @see http://tools.ietf.org/html/rfc4880#section-12 1597 | */ 1598 | class OpenPGP_SecretKeyPacket extends OpenPGP_PublicKeyPacket { 1599 | public $s2k_useage, $s2k, $symmetric_algorithm, $private_hash, $encrypted_data; 1600 | function read() { 1601 | parent::read(); // All the fields from PublicKey 1602 | $this->s2k_useage = ord($this->read_byte()); 1603 | if($this->s2k_useage == 255 || $this->s2k_useage == 254) { 1604 | $this->symmetric_algorithm = ord($this->read_byte()); 1605 | $this->s2k = OpenPGP_S2k::parse($this->input); 1606 | } else if($this->s2k_useage > 0) { 1607 | $this->symmetric_algorithm = $this->s2k_useage; 1608 | } 1609 | if($this->s2k_useage > 0) { 1610 | $this->encrypted_data = $this->input; // Rest of input is MPIs and checksum (encrypted) 1611 | } else { 1612 | $this->key_from_input(); 1613 | $this->private_hash = $this->read_bytes(2); // TODO: Validate checksum? 1614 | } 1615 | } 1616 | 1617 | static $secret_key_fields = array( 1618 | 1 => array('d', 'p', 'q', 'u'), // RSA 1619 | 2 => array('d', 'p', 'q', 'u'), // RSA-E 1620 | 3 => array('d', 'p', 'q', 'u'), // RSA-S 1621 | 16 => array('x'), // ELG-E 1622 | 17 => array('x'), // DSA 1623 | 18 => array('x'), // ECDH 1624 | 19 => array('x'), // ECDSA 1625 | 22 => array('x'), // EdDSA 1626 | ); 1627 | 1628 | function key_from_input() { 1629 | foreach(self::$secret_key_fields[$this->algorithm] as $field) { 1630 | $this->key[$field] = $this->read_mpi(); 1631 | } 1632 | } 1633 | 1634 | function body() { 1635 | $bytes = parent::body() . chr($this->s2k_useage); 1636 | $secret_material = NULL; 1637 | if($this->s2k_useage == 255 || $this->s2k_useage == 254) { 1638 | $bytes .= chr($this->symmetric_algorithm); 1639 | $bytes .= $this->s2k->to_bytes(); 1640 | } 1641 | if($this->s2k_useage > 0) { 1642 | $bytes .= $this->encrypted_data; 1643 | } else { 1644 | $secret_material = ''; 1645 | foreach(self::$secret_key_fields[$this->algorithm] as $f) { 1646 | $f = $this->key[$f]; 1647 | $secret_material .= pack('n', OpenPGP::bitlength($f)); 1648 | $secret_material .= $f; 1649 | } 1650 | $bytes .= $secret_material; 1651 | 1652 | // 2-octet checksum 1653 | $chk = 0; 1654 | for($i = 0; $i < strlen($secret_material); $i++) { 1655 | $chk = ($chk + ord($secret_material[$i])) % 65536; 1656 | } 1657 | $bytes .= pack('n', $chk); 1658 | } 1659 | return $bytes; 1660 | } 1661 | } 1662 | 1663 | /** 1664 | * OpenPGP Secret-Subkey packet (tag 7). 1665 | * 1666 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.1.4 1667 | * @see http://tools.ietf.org/html/rfc4880#section-5.5.3 1668 | * @see http://tools.ietf.org/html/rfc4880#section-11.2 1669 | * @see http://tools.ietf.org/html/rfc4880#section-12 1670 | */ 1671 | class OpenPGP_SecretSubkeyPacket extends OpenPGP_SecretKeyPacket { 1672 | // TODO 1673 | } 1674 | 1675 | /** 1676 | * OpenPGP Compressed Data packet (tag 8). 1677 | * 1678 | * @see http://tools.ietf.org/html/rfc4880#section-5.6 1679 | */ 1680 | class OpenPGP_CompressedDataPacket extends OpenPGP_Packet implements IteratorAggregate, ArrayAccess { 1681 | public $algorithm; 1682 | /* see http://tools.ietf.org/html/rfc4880#section-9.3 */ 1683 | static $algorithms = array(0 => 'Uncompressed', 1 => 'ZIP', 2 => 'ZLIB', 3 => 'BZip2'); 1684 | 1685 | public $input; 1686 | 1687 | public $length; 1688 | 1689 | function __construct($m=NULL, $algorithm=1) { 1690 | parent::__construct(); 1691 | $this->algorithm = $algorithm; 1692 | $this->data = $m ? $m : new OpenPGP_Message(); 1693 | } 1694 | 1695 | function read() { 1696 | $this->algorithm = ord($this->read_byte()); 1697 | $this->data = $this->read_bytes($this->length); 1698 | switch($this->algorithm) { 1699 | case 0: 1700 | $this->data = OpenPGP_Message::parse($this->data); 1701 | break; 1702 | case 1: 1703 | $this->data = OpenPGP_Message::parse(gzinflate($this->data)); 1704 | break; 1705 | case 2: 1706 | $this->data = OpenPGP_Message::parse(gzuncompress($this->data)); 1707 | break; 1708 | case 3: 1709 | $this->data = OpenPGP_Message::parse(bzdecompress($this->data)); 1710 | break; 1711 | default: 1712 | /* TODO error? */ 1713 | } 1714 | } 1715 | 1716 | function body() { 1717 | $body = chr($this->algorithm); 1718 | switch($this->algorithm) { 1719 | case 0: 1720 | $body .= $this->data->to_bytes(); 1721 | break; 1722 | case 1: 1723 | $body .= gzdeflate($this->data->to_bytes()); 1724 | break; 1725 | case 2: 1726 | $body .= gzcompress($this->data->to_bytes()); 1727 | break; 1728 | case 3: 1729 | $body .= bzcompress($this->data->to_bytes()); 1730 | break; 1731 | default: 1732 | /* TODO error? */ 1733 | } 1734 | return $body; 1735 | } 1736 | 1737 | // IteratorAggregate interface 1738 | // function getIterator(): \Traversable { // when PHP 5 support is dropped 1739 | #[\ReturnTypeWillChange] 1740 | function getIterator() { 1741 | return new ArrayIterator($this->data->packets); 1742 | } 1743 | 1744 | // ArrayAccess interface 1745 | // function offsetExists($offset): bool { // when PHP 5 support is dropped 1746 | #[\ReturnTypeWillChange] 1747 | function offsetExists($offset) { 1748 | return isset($this->data[$offset]); 1749 | } 1750 | 1751 | // function offsetGet($offset): mixed { // when PHP 7 support is dropped 1752 | #[\ReturnTypeWillChange] 1753 | function offsetGet($offset) { 1754 | return $this->data[$offset]; 1755 | } 1756 | 1757 | // function offsetSet($offset, $value): void { // when PHP 5 support is dropped 1758 | #[\ReturnTypeWillChange] 1759 | function offsetSet($offset, $value) { 1760 | is_null($offset) ? $this->data[] = $value : $this->data[$offset] = $value; 1761 | } 1762 | 1763 | #[\ReturnTypeWillChange] 1764 | // function offsetUnset($offset): void { // PHP 5 support is dropped 1765 | function offsetUnset($offset) { 1766 | unset($this->data[$offset]); 1767 | } 1768 | 1769 | } 1770 | 1771 | /** 1772 | * OpenPGP Symmetrically Encrypted Data packet (tag 9). 1773 | * 1774 | * @see http://tools.ietf.org/html/rfc4880#section-5.7 1775 | */ 1776 | class OpenPGP_EncryptedDataPacket extends OpenPGP_Packet { 1777 | public $input; 1778 | 1779 | public $length; 1780 | 1781 | function read() { 1782 | $this->data = $this->input; 1783 | } 1784 | 1785 | function body() { 1786 | return $this->data; 1787 | } 1788 | } 1789 | 1790 | /** 1791 | * OpenPGP Marker packet (tag 10). 1792 | * 1793 | * @see http://tools.ietf.org/html/rfc4880#section-5.8 1794 | */ 1795 | class OpenPGP_MarkerPacket extends OpenPGP_Packet { 1796 | // TODO 1797 | } 1798 | 1799 | /** 1800 | * OpenPGP Literal Data packet (tag 11). 1801 | * 1802 | * @see http://tools.ietf.org/html/rfc4880#section-5.9 1803 | */ 1804 | class OpenPGP_LiteralDataPacket extends OpenPGP_Packet { 1805 | public $format, $filename, $timestamp; 1806 | 1807 | public $input; 1808 | 1809 | public $length; 1810 | 1811 | function __construct($data=NULL, $opt=array()) { 1812 | parent::__construct(); 1813 | $this->data = $data; 1814 | $this->format = isset($opt['format']) ? $opt['format'] : 'b'; 1815 | $this->filename = isset($opt['filename']) ? $opt['filename'] : 'data'; 1816 | $this->timestamp = isset($opt['timestamp']) ? $opt['timestamp'] : time(); 1817 | } 1818 | 1819 | function normalize($clearsign=false) { 1820 | if($clearsign && ($this->format != 'u' && $this->format != 't')) { 1821 | $this->format = 'u'; // Clearsign must be text 1822 | } 1823 | 1824 | if($this->format == 'u' || $this->format == 't') { // Normalize line endings 1825 | $this->data = str_replace("\n", "\r\n", str_replace("\r", "\n", str_replace("\r\n", "\n", $this->data))); 1826 | } 1827 | 1828 | if($clearsign) { 1829 | // When clearsigning, do not sign over trailing whitespace 1830 | $this->data = preg_replace('/\s+\r/', "\r", $this->data); 1831 | } 1832 | } 1833 | 1834 | function read() { 1835 | $this->size = $this->length - 1 - 4; 1836 | $this->format = $this->read_byte(); 1837 | $filename_length = ord($this->read_byte()); 1838 | $this->size -= $filename_length; 1839 | $this->filename = $this->read_bytes($filename_length); 1840 | $this->timestamp = $this->read_timestamp(); 1841 | $this->data = $this->read_bytes($this->size); 1842 | } 1843 | 1844 | function body() { 1845 | return $this->format.chr(strlen($this->filename)).$this->filename.pack('N', $this->timestamp).$this->data; 1846 | } 1847 | } 1848 | 1849 | /** 1850 | * OpenPGP Trust packet (tag 12). 1851 | * 1852 | * @see http://tools.ietf.org/html/rfc4880#section-5.10 1853 | */ 1854 | class OpenPGP_TrustPacket extends OpenPGP_Packet { 1855 | public $input; 1856 | 1857 | public $length; 1858 | 1859 | function read() { 1860 | $this->data = $this->input; 1861 | } 1862 | 1863 | function body() { 1864 | return $this->data; 1865 | } 1866 | } 1867 | 1868 | /** 1869 | * OpenPGP User ID packet (tag 13). 1870 | * 1871 | * @see http://tools.ietf.org/html/rfc4880#section-5.11 1872 | * @see http://tools.ietf.org/html/rfc2822 1873 | */ 1874 | class OpenPGP_UserIDPacket extends OpenPGP_Packet { 1875 | public $name, $comment, $email; 1876 | 1877 | public $input; 1878 | 1879 | public $length; 1880 | 1881 | function __construct($name='', $comment='', $email='') { 1882 | parent::__construct(); 1883 | if(!$comment && !$email) { 1884 | $this->input = $name; 1885 | $this->read(); 1886 | } else { 1887 | $this->name = $name; 1888 | $this->comment = $comment; 1889 | $this->email = $email; 1890 | } 1891 | } 1892 | 1893 | function read() { 1894 | $this->data = $this->input; 1895 | // User IDs of the form: "name (comment) " 1896 | if (preg_match('/^([^\(]+)\(([^\)]+)\)\s+<([^>]+)>$/', $this->data, $matches)) { 1897 | $this->name = trim($matches[1]); 1898 | $this->comment = trim($matches[2]); 1899 | $this->email = trim($matches[3]); 1900 | } 1901 | // User IDs of the form: "name " 1902 | else if (preg_match('/^([^<]+)\s+<([^>]+)>$/', $this->data, $matches)) { 1903 | $this->name = trim($matches[1]); 1904 | $this->comment = NULL; 1905 | $this->email = trim($matches[2]); 1906 | } 1907 | // User IDs of the form: "name" 1908 | else if (preg_match('/^([^<]+)$/', $this->data, $matches)) { 1909 | $this->name = trim($matches[1]); 1910 | $this->comment = NULL; 1911 | $this->email = NULL; 1912 | } 1913 | // User IDs of the form: "" 1914 | else if (preg_match('/^<([^>]+)>$/', $this->data, $matches)) { 1915 | $this->name = NULL; 1916 | $this->comment = NULL; 1917 | $this->email = trim($matches[2]); 1918 | } 1919 | } 1920 | 1921 | function __toString() { 1922 | $text = array(); 1923 | if ($this->name) { $text[] = $this->name; } 1924 | if ($this->comment) { $text[] = "({$this->comment})"; } 1925 | if ($this->email) { $text[] = "<{$this->email}>"; } 1926 | return implode(' ', $text); 1927 | } 1928 | 1929 | function body() { 1930 | return ''.$this; // Convert to string is the body 1931 | } 1932 | } 1933 | 1934 | /** 1935 | * OpenPGP User Attribute packet (tag 17). 1936 | * 1937 | * @see http://tools.ietf.org/html/rfc4880#section-5.12 1938 | * @see http://tools.ietf.org/html/rfc4880#section-11.1 1939 | */ 1940 | class OpenPGP_UserAttributePacket extends OpenPGP_Packet { 1941 | public $packets; 1942 | 1943 | public $input; 1944 | 1945 | public $length; 1946 | 1947 | // TODO 1948 | } 1949 | 1950 | /** 1951 | * OpenPGP Sym. Encrypted Integrity Protected Data packet (tag 18). 1952 | * 1953 | * @see http://tools.ietf.org/html/rfc4880#section-5.13 1954 | */ 1955 | class OpenPGP_IntegrityProtectedDataPacket extends OpenPGP_EncryptedDataPacket { 1956 | public $version; 1957 | 1958 | public $input; 1959 | 1960 | public $length; 1961 | 1962 | function __construct($data='', $version=1) { 1963 | parent::__construct(); 1964 | $this->version = $version; 1965 | $this->data = $data; 1966 | } 1967 | 1968 | function read() { 1969 | $this->version = ord($this->read_byte()); 1970 | $this->data = $this->input; 1971 | } 1972 | 1973 | function body() { 1974 | return chr($this->version) . $this->data; 1975 | } 1976 | } 1977 | 1978 | /** 1979 | * OpenPGP Modification Detection Code packet (tag 19). 1980 | * 1981 | * @see http://tools.ietf.org/html/rfc4880#section-5.14 1982 | */ 1983 | class OpenPGP_ModificationDetectionCodePacket extends OpenPGP_Packet { 1984 | function __construct($sha1='') { 1985 | parent::__construct(); 1986 | $this->data = $sha1; 1987 | } 1988 | 1989 | function read() { 1990 | $this->data = $this->input; 1991 | if(strlen($this->input) != 20) throw new Exception("Bad ModificationDetectionCodePacket"); 1992 | } 1993 | 1994 | function header_and_body() { 1995 | $body = $this->body(); // Get body first, we will need it's length 1996 | if(strlen($body) != 20) throw new Exception("Bad ModificationDetectionCodePacket"); 1997 | return array('header' => "\xD3\x14", 'body' => $body); 1998 | } 1999 | 2000 | function body() { 2001 | return $this->data; 2002 | } 2003 | } 2004 | 2005 | /** 2006 | * OpenPGP Private or Experimental packet (tags 60..63). 2007 | * 2008 | * @see http://tools.ietf.org/html/rfc4880#section-4.3 2009 | */ 2010 | class OpenPGP_ExperimentalPacket extends OpenPGP_Packet {} 2011 | -------------------------------------------------------------------------------- /lib/openpgp_crypt_rsa.php: -------------------------------------------------------------------------------- 1 | key = $packet; 29 | } else { 30 | $this->message = $packet; 31 | } 32 | } 33 | 34 | function key($keyid=NULL) { 35 | if(!$this->key) return NULL; // No key 36 | if($this->key instanceof OpenPGP_Message) { 37 | foreach($this->key as $p) { 38 | if($p instanceof OpenPGP_PublicKeyPacket) { 39 | if(!$keyid || strtoupper(substr($p->fingerprint, strlen($keyid)*-1)) == strtoupper($keyid)) return $p; 40 | } 41 | } 42 | } 43 | return $this->key; 44 | } 45 | 46 | // Get Crypt_RSA for the public key 47 | function public_key($keyid=NULL) { 48 | return self::convert_public_key($this->key($keyid)); 49 | } 50 | 51 | // Get Crypt_RSA for the private key 52 | function private_key($keyid=NULL) { 53 | return self::convert_private_key($this->key($keyid)); 54 | } 55 | 56 | // Pass a message to verify with this key, or a key (OpenPGP or Crypt_RSA) to check this message with 57 | // Second optional parameter to specify which signature to verify (if there is more than one) 58 | function verify($packet) { 59 | $self = $this; // For old PHP 60 | if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); 61 | if(!$this->message) { 62 | $m = $packet; 63 | $verifier = function($m, $s) use($self) { 64 | $key = $self->public_key($s->issuer()); 65 | if(!$key) return false; 66 | $key = $key->withHash(strtolower($s->hash_algorithm_name())); 67 | return $key->verify($m, reset($s->data)); 68 | }; 69 | } else { 70 | if(!($packet instanceof Crypt_RSA)) { 71 | $packet = new self($packet); 72 | } 73 | 74 | $m = $this->message; 75 | $verifier = function($m, $s) use($self, $packet) { 76 | if(!($packet instanceof Crypt_RSA)) { 77 | $key = $packet->public_key($s->issuer()); 78 | } 79 | if(!$key) return false; 80 | $key = $key->withHash(strtolower($s->hash_algorithm_name())); 81 | return $key->verify($m, reset($s->data)); 82 | }; 83 | } 84 | 85 | return $m->verified_signatures(array('RSA' => array( 86 | 'MD5' => $verifier, 87 | 'SHA1' => $verifier, 88 | 'SHA224' => $verifier, 89 | 'SHA256' => $verifier, 90 | 'SHA384' => $verifier, 91 | 'SHA512' => $verifier 92 | ))); 93 | } 94 | 95 | // Pass a message to sign with this key, or a secret key to sign this message with 96 | // Second parameter is hash algorithm to use (default SHA256) 97 | // Third parameter is the 16-digit key ID to use... defaults to the key id in the key packet 98 | function sign($packet, $hash='SHA256', $keyid=NULL) { 99 | if(!is_object($packet)) { 100 | if($this->key) { 101 | $packet = new OpenPGP_LiteralDataPacket($packet); 102 | } else { 103 | $packet = OpenPGP_Message::parse($packet); 104 | } 105 | } 106 | 107 | if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA 108 | || ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) { 109 | $key = $packet; 110 | $message = $this->message; 111 | } else { 112 | $key = $this->key; 113 | $message = $packet; 114 | } 115 | 116 | if(!$key || !$message) return NULL; // Missing some data 117 | 118 | if($message instanceof OpenPGP_Message) { 119 | $sign = $message->signatures(); 120 | $message = $sign[0][0]; 121 | } 122 | 123 | if(!($key instanceof Crypt_RSA)) { 124 | $key = new self($key); 125 | if(!$keyid) $keyid = substr($key->key()->fingerprint, -16, 16); 126 | $key = $key->private_key($keyid); 127 | } 128 | $key = $key->withHash(strtolower($hash)); 129 | 130 | $sig = new OpenPGP_SignaturePacket($message, 'RSA', strtoupper($hash)); 131 | $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); 132 | $sig->sign_data(array('RSA' => array($hash => function($data) use($key) { 133 | return [ "signed" => $key->sign($data), "hash" => $key->getHash()->hash($data) ]; 134 | }))); 135 | 136 | return new OpenPGP_Message(array($sig, $message)); 137 | } 138 | 139 | /** Pass a message with a key and userid packet to sign */ 140 | // TODO: merge this with the normal sign function 141 | function sign_key_userid($packet, $hash='SHA256', $keyid=NULL) { 142 | if(is_array($packet)) { 143 | $packet = new OpenPGP_Message($packet); 144 | } else if(!is_object($packet)) { 145 | $packet = OpenPGP_Message::parse($packet); 146 | } 147 | 148 | $key = $this->private_key($keyid); 149 | if(!$key || !$packet) return NULL; // Missing some data 150 | 151 | if(!$keyid) $keyid = substr($this->key->fingerprint, -16); 152 | $key = $key->withHash(strtolower($hash)); 153 | 154 | $sig = NULL; 155 | foreach($packet as $p) { 156 | if($p instanceof OpenPGP_SignaturePacket) $sig = $p; 157 | } 158 | if(!$sig) { 159 | $sig = new OpenPGP_SignaturePacket($packet, 'RSA', strtoupper($hash)); 160 | $sig->signature_type = 0x13; 161 | $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_KeyFlagsPacket(array(0x01 | 0x02)); 162 | $sig->hashed_subpackets[] = new OpenPGP_SignaturePacket_IssuerPacket($keyid); 163 | $packet[] = $sig; 164 | } 165 | 166 | $sig->sign_data(array('RSA' => array($hash => function($data) use($key) { 167 | return [ "signed" => $key->sign($data), "hash" => $key->getHash()->hash($data) ]; 168 | }))); 169 | 170 | return $packet; 171 | } 172 | 173 | function decrypt($packet) { 174 | if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); 175 | 176 | if($packet instanceof OpenPGP_SecretKeyPacket || $packet instanceof Crypt_RSA 177 | || ($packet instanceof ArrayAccess && $packet[0] instanceof OpenPGP_SecretKeyPacket)) { 178 | $keys = $packet; 179 | $message = $this->message; 180 | } else { 181 | $keys = $this->key; 182 | $message = $packet; 183 | } 184 | 185 | if(!$keys || !$message) return NULL; // Missing some data 186 | 187 | if(!($keys instanceof Crypt_RSA)) { 188 | $keys = new self($keys); 189 | } 190 | 191 | $session_key = NULL; 192 | foreach($message as $p) { 193 | if($p instanceof OpenPGP_AsymmetricSessionKeyPacket) { 194 | $session_key = $p; 195 | if($keys instanceof Crypt_RSA) { 196 | $sk = self::try_decrypt_session($keys, substr($p->encrypted_data, 2)); 197 | } else if(strlen(str_replace('0', '', $p->keyid)) < 1) { 198 | foreach($keys->key as $k) { 199 | $sk = self::try_decrypt_session(self::convert_private_key($k), substr($p->encrypted_data, 2)); 200 | if($sk) break; 201 | } 202 | } else { 203 | $key = $keys->private_key($p->keyid); 204 | $sk = self::try_decrypt_session($key, substr($p->encrypted_data, 2)); 205 | } 206 | 207 | if(!$sk) continue; 208 | 209 | $r = OpenPGP_Crypt_Symmetric::decryptPacket(OpenPGP_Crypt_Symmetric::getEncryptedData($message), $sk[0], $sk[1]); 210 | if($r) return $r; 211 | } 212 | } 213 | 214 | if (!$session_key) throw new Exception("Not an asymmetrically encrypted message"); 215 | 216 | return NULL; /* Failed */ 217 | } 218 | 219 | static function try_decrypt_session($key, $edata) { 220 | $key = $key->withPadding(CRYPT_RSA_ENCRYPTION_PKCS1 | CRYPT_RSA_SIGNATURE_PKCS1); 221 | try { 222 | $data = $key->decrypt($edata); 223 | } catch (\RuntimeException $e) { 224 | return NULL; 225 | } 226 | 227 | if(!$data) return NULL; 228 | $sk = substr($data, 1, strlen($data)-3); 229 | $chk = unpack('n', substr($data, -2)); 230 | $chk = reset($chk); 231 | 232 | $sk_chk = 0; 233 | for($i = 0; $i < strlen($sk); $i++) { 234 | $sk_chk = ($sk_chk + ord($sk[$i])) % 65536; 235 | } 236 | 237 | if($sk_chk != $chk) return NULL; 238 | return array(ord($data[0]), $sk); 239 | } 240 | 241 | static function crypt_rsa_key($mod, $exp, $hash='SHA256') { 242 | return Crypt_RSA::loadPublicKey([ 243 | 'e' => new Math_BigInteger($exp, 256), 244 | 'n' => new Math_BigInteger($mod, 256), 245 | ]) 246 | ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) 247 | ->withHash(strtolower($hash)); 248 | } 249 | 250 | static function convert_key($packet, $private=false) { 251 | if(!is_object($packet)) $packet = OpenPGP_Message::parse($packet); 252 | if($packet instanceof OpenPGP_Message) $packet = $packet[0]; 253 | 254 | $exp = $packet->key['e']; 255 | if($private) $exp = $packet->key['d']; 256 | if(!$exp) return NULL; // Packet doesn't have needed data 257 | 258 | /** 259 | * @see https://github.com/phpseclib/phpseclib/issues/1113 260 | * Primes and coefficients now use BigIntegers. 261 | **/ 262 | 263 | if($private) { 264 | // Invert p and q to make u work out as q' 265 | $rawKey = [ 266 | 'e' => new Math_BigInteger($packet->key['e'], 256), 267 | 'n' => new Math_BigInteger($packet->key['n'], 256), 268 | 'd' => new Math_BigInteger($packet->key['d'], 256), 269 | 'q' => new Math_BigInteger($packet->key['p'], 256), 270 | 'p' => new Math_BigInteger($packet->key['q'], 256), 271 | ]; 272 | if (array_key_exists('u', $packet->key)) { 273 | // possible keys for 'u': https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Crypt/RSA/Formats/Keys/Raw.php#L108 274 | $rawKey['inerseq'] = new Math_BigInteger($packet->key['u'], 256); 275 | } 276 | 277 | return publickeyloader::loadPrivateKey($rawKey) 278 | ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) 279 | ->withHash('sha256'); 280 | } else { 281 | 282 | return publickeyloader::loadPublicKey([ 283 | 'e' => new Math_BigInteger($packet->key['e'], 256), 284 | 'n' => new Math_BigInteger($packet->key['n'], 256), 285 | ]) 286 | ->withPadding(CRYPT_RSA_SIGNATURE_PKCS1 | CRYPT_RSA_ENCRYPTION_PKCS1) 287 | ->withHash('sha256'); 288 | } 289 | } 290 | 291 | static function convert_public_key($packet) { 292 | return self::convert_key($packet, false); 293 | } 294 | 295 | static function convert_private_key($packet) { 296 | return self::convert_key($packet, true); 297 | } 298 | 299 | } 300 | 301 | ?> 302 | -------------------------------------------------------------------------------- /lib/openpgp_crypt_symmetric.php: -------------------------------------------------------------------------------- 1 | setKey($key); 23 | 24 | $to_encrypt = $prefix . $message->to_bytes(); 25 | $mdc = new OpenPGP_ModificationDetectionCodePacket(hash('sha1', $to_encrypt . "\xD3\x14", true)); 26 | $to_encrypt .= $mdc->to_bytes(); 27 | $encrypted = array(new OpenPGP_IntegrityProtectedDataPacket($cipher->encrypt($to_encrypt))); 28 | 29 | if(!is_array($passphrases_and_keys) && !($passphrases_and_keys instanceof IteratorAggregate)) { 30 | $passphrases_and_keys = (array)$passphrases_and_keys; 31 | } 32 | 33 | foreach($passphrases_and_keys as $pass) { 34 | if($pass instanceof OpenPGP_PublicKeyPacket) { 35 | if(!in_array($pass->algorithm, array(1,2,3))) throw new Exception("Only RSA keys are supported."); 36 | $crypt_rsa = new OpenPGP_Crypt_RSA($pass); 37 | $rsa = $crypt_rsa->public_key()->withPadding(CRYPT_RSA_ENCRYPTION_PKCS1 | CRYPT_RSA_SIGNATURE_PKCS1); 38 | $esk = $rsa->encrypt(chr($symmetric_algorithm) . $key . pack('n', self::checksum($key))); 39 | $esk = pack('n', OpenPGP::bitlength($esk)) . $esk; 40 | array_unshift($encrypted, new OpenPGP_AsymmetricSessionKeyPacket($pass->algorithm, $pass->fingerprint(), $esk)); 41 | } else if(is_string($pass)) { 42 | $s2k = new OpenPGP_S2K(Random::string(8)); 43 | $cipher->setKey($s2k->make_key($pass, $key_bytes)); 44 | $esk = $cipher->encrypt(chr($symmetric_algorithm) . $key); 45 | array_unshift($encrypted, new OpenPGP_SymmetricSessionKeyPacket($s2k, $esk, $symmetric_algorithm)); 46 | } 47 | } 48 | 49 | return new OpenPGP_Message($encrypted); 50 | } 51 | 52 | public static function decryptSymmetric($pass, $m) { 53 | $epacket = self::getEncryptedData($m); 54 | 55 | foreach($m as $p) { 56 | if($p instanceof OpenPGP_SymmetricSessionKeyPacket) { 57 | if(strlen($p->encrypted_data) > 0) { 58 | list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm); 59 | if(!$cipher) continue; 60 | $cipher->setKey($p->s2k->make_key($pass, $key_bytes)); 61 | 62 | $padAmount = $key_block_bytes - (strlen($p->encrypted_data) % $key_block_bytes); 63 | $data = substr($cipher->decrypt($p->encrypted_data . str_repeat("\0", $padAmount)), 0, strlen($p->encrypted_data)); 64 | $decrypted = self::decryptPacket($epacket, ord($data[0]), substr($data, 1)); 65 | } else { 66 | list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($p->symmetric_algorithm); 67 | $decrypted = self::decryptPacket($epacket, $p->symmetric_algorithm, $p->s2k->make_key($pass, $key_bytes)); 68 | } 69 | 70 | if($decrypted) return $decrypted; 71 | } 72 | } 73 | 74 | return NULL; /* If we get here, we failed */ 75 | } 76 | 77 | public static function encryptSecretKey($pass, $packet, $symmetric_algorithm=9) { 78 | $packet = clone $packet; // Do not mutate original 79 | $packet->s2k_useage = 254; 80 | $packet->symmetric_algorithm = $symmetric_algorithm; 81 | 82 | list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($packet->symmetric_algorithm); 83 | if(!$cipher) throw new Exception("Unsupported cipher"); 84 | 85 | $material = ''; 86 | foreach(OpenPGP_SecretKeyPacket::$secret_key_fields[$packet->algorithm] as $field) { 87 | $f = $packet->key[$field]; 88 | $material .= pack('n', OpenPGP::bitlength($f)) . $f; 89 | unset($packet->key[$field]); 90 | } 91 | $material .= hash('sha1', $material, true); 92 | 93 | $iv = Random::string($key_block_bytes); 94 | if(!$packet->s2k) $packet->s2k = new OpenPGP_S2K(Random::string(8)); 95 | $cipher->setKey($packet->s2k->make_key($pass, $key_bytes)); 96 | $cipher->setIV($iv); 97 | $packet->encrypted_data = $iv . $cipher->encrypt($material); 98 | 99 | return $packet; 100 | } 101 | 102 | public static function decryptSecretKey($pass, $packet) { 103 | $packet = clone $packet; // Do not mutate orinigal 104 | 105 | list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($packet->symmetric_algorithm); 106 | if(!$cipher) throw new Exception("Unsupported cipher"); 107 | $cipher->setKey($packet->s2k->make_key($pass, $key_bytes)); 108 | $cipher->setIV(substr($packet->encrypted_data, 0, $key_block_bytes)); 109 | $material = $cipher->decrypt(substr($packet->encrypted_data, $key_block_bytes)); 110 | 111 | if($packet->s2k_useage == 254) { 112 | $chk = substr($material, -20); 113 | $material = substr($material, 0, -20); 114 | if($chk != hash('sha1', $material, true)) return NULL; 115 | } else { 116 | $chk = unpack('n', substr($material, -2)); 117 | $chk = reset($chk); 118 | $material = substr($material, 0, -2); 119 | 120 | $mkChk = self::checksum($material); 121 | if($chk != $mkChk) return NULL; 122 | } 123 | 124 | $packet->s2k = NULL; 125 | $packet->s2k_useage = 0; 126 | $packet->symmetric_algorithm = 0; 127 | $packet->encrypted_data = NULL; 128 | $packet->input = $material; 129 | $packet->key_from_input(); 130 | unset($packet->input); 131 | return $packet; 132 | } 133 | 134 | public static function decryptPacket($epacket, $symmetric_algorithm, $key) { 135 | list($cipher, $key_bytes, $key_block_bytes) = self::getCipher($symmetric_algorithm); 136 | if(!$cipher) return NULL; 137 | $cipher->setKey($key); 138 | 139 | if($epacket instanceof OpenPGP_IntegrityProtectedDataPacket) { 140 | $padAmount = $key_block_bytes - (strlen($epacket->data) % $key_block_bytes); 141 | $data = substr($cipher->decrypt($epacket->data . str_repeat("\0", $padAmount)), 0, strlen($epacket->data)); 142 | $prefix = substr($data, 0, $key_block_bytes + 2); 143 | $mdc = substr(substr($data, -22, 22), 2); 144 | $data = substr($data, $key_block_bytes + 2, -22); 145 | 146 | $mkMDC = hash("sha1", $prefix . $data . "\xD3\x14", true); 147 | if($mkMDC !== $mdc) return false; 148 | 149 | try { 150 | $msg = OpenPGP_Message::parse($data); 151 | } catch (Exception $ex) { $msg = NULL; } 152 | if($msg) return $msg; /* Otherwise keep trying */ 153 | } else { 154 | // No MDC mean decrypt with resync 155 | $iv = substr($epacket->data, 2, $key_block_bytes); 156 | $edata = substr($epacket->data, $key_block_bytes + 2); 157 | $padAmount = $key_block_bytes - (strlen($edata) % $key_block_bytes); 158 | 159 | $cipher->setIV($iv); 160 | $data = substr($cipher->decrypt($edata . str_repeat("\0", $padAmount)), 0, strlen($edata)); 161 | 162 | try { 163 | $msg = OpenPGP_Message::parse($data); 164 | } catch (Exception $ex) { $msg = NULL; } 165 | if($msg) return $msg; /* Otherwise keep trying */ 166 | } 167 | 168 | return NULL; /* Failed */ 169 | } 170 | 171 | public static function getCipher($algo) { 172 | $cipher = NULL; 173 | 174 | // https://datatracker.ietf.org/doc/html/rfc4880#section-13.9 175 | // " 1. The feedback register (FR) is set to the IV, which is all zeros." 176 | switch($algo) { 177 | case NULL: 178 | case 0: 179 | throw new Exception("Data is already unencrypted"); 180 | case 2: 181 | $cipher = new Crypt_TripleDES('cfb'); 182 | $cipher->setIV(str_repeat(pack('x'), 8)); 183 | $key_bytes = 24; 184 | $key_block_bytes = 8; 185 | break; 186 | case 3: 187 | if(class_exists('OpenSSLWrapper')) { 188 | $cipher = new OpenSSLWrapper("CAST5-CFB"); 189 | } else if(defined('MCRYPT_CAST_128')) { 190 | $cipher = new MCryptWrapper(MCRYPT_CAST_128); 191 | } 192 | break; 193 | case 4: 194 | $cipher = new Crypt_Blowfish('cfb'); 195 | $cipher->setIV(str_repeat(pack('x'), 8)); 196 | $key_bytes = 16; 197 | $key_block_bytes = 8; 198 | break; 199 | case 7: 200 | $cipher = new Crypt_AES('cfb'); 201 | $cipher->setKeyLength(128); 202 | $cipher->setIV(str_repeat(pack('x'), 16)); 203 | break; 204 | case 8: 205 | $cipher = new Crypt_AES('cfb'); 206 | $cipher->setKeyLength(192); 207 | $cipher->setIV(str_repeat(pack('x'), 16)); 208 | break; 209 | case 9: 210 | $cipher = new Crypt_AES('cfb'); 211 | $cipher->setKeyLength(256); 212 | $cipher->setIV(str_repeat(pack('x'), 16)); 213 | break; 214 | case 10: 215 | $cipher = new Crypt_Twofish('cfb'); 216 | $cipher->setIV(str_repeat(pack('x'), 16)); 217 | $key_bytes = 32; 218 | break; 219 | } 220 | if(!$cipher) return array(NULL, NULL, NULL); // Unsupported cipher 221 | 222 | 223 | if(!isset($key_bytes)) $key_bytes = $cipher->getKeyLength() >> 3; 224 | if(!isset($key_block_bytes)) $key_block_bytes = $cipher->getBlockLengthInBytes(); 225 | return array($cipher, $key_bytes, $key_block_bytes); 226 | } 227 | 228 | public static function getEncryptedData($m) { 229 | foreach($m as $p) { 230 | if($p instanceof OpenPGP_EncryptedDataPacket) return $p; 231 | } 232 | throw new Exception("Can only decrypt EncryptedDataPacket"); 233 | } 234 | 235 | public static function checksum($s) { 236 | $mkChk = 0; 237 | for($i = 0; $i < strlen($s); $i++) { 238 | $mkChk = ($mkChk + ord($s[$i])) % 65536; 239 | } 240 | return $mkChk; 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /lib/openpgp_mcrypt_wrapper.php: -------------------------------------------------------------------------------- 1 | cipher = $cipher; 10 | $this->key_size = mcrypt_module_get_algo_key_size($cipher); 11 | $this->block_size = mcrypt_module_get_algo_block_size($cipher); 12 | $this->iv = str_repeat("\0", mcrypt_get_iv_size($cipher, 'ncfb')); 13 | } 14 | 15 | function getBlockLengthInBytes() 16 | { 17 | return $this->block_size; 18 | } 19 | 20 | function getKeyLength() { 21 | return $this->key_size << 3; 22 | } 23 | 24 | function setKey($key) { 25 | $this->key = $key; 26 | } 27 | 28 | function setIV($iv) { 29 | $this->iv = $iv; 30 | } 31 | 32 | function encrypt($data) { 33 | return mcrypt_encrypt($this->cipher, $this->key, $data, 'ncfb', $this->iv); 34 | } 35 | 36 | function decrypt($data) { 37 | return mcrypt_decrypt($this->cipher, $this->key, $data, 'ncfb', $this->iv); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/openpgp_openssl_wrapper.php: -------------------------------------------------------------------------------- 1 | cipher = $cipher; 12 | $this->key_size = 16; 13 | $this->block_size = 8; 14 | $this->iv = str_repeat("\0", 8); 15 | } 16 | 17 | function getBlockLengthInBytes() 18 | { 19 | return $this->block_size; 20 | } 21 | 22 | function getKeyLength() { 23 | return $this->key_size << 3; 24 | } 25 | 26 | function setKey($key) { 27 | $this->key = $key; 28 | } 29 | 30 | function setIV($iv) { 31 | $this->iv = $iv; 32 | } 33 | 34 | function encrypt($data) { 35 | return openssl_encrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $this->iv); 36 | } 37 | 38 | function decrypt($data) { 39 | return openssl_decrypt($data, $this->cipher, $this->key, OPENSSL_RAW_DATA|OPENSSL_ZERO_PADDING, $this->iv); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/openpgp_sodium.php: -------------------------------------------------------------------------------- 1 | fingerprint, strlen($s->issuer())*-1) == $s->issuer()) { 9 | $pk = $p; 10 | break; 11 | } 12 | } 13 | } 14 | } 15 | 16 | if ($pk->algorithm != 22) throw new Exception("Only EdDSA supported"); 17 | if (bin2hex($pk->key['oid']) != '2b06010401da470f01') throw new Exception("Only ed25519 supported"); 18 | return sodium_crypto_sign_verify_detached( 19 | implode($s->data), 20 | hash($s->hash_algorithm_name(), $m, true), 21 | substr($pk->key['p'], 1) 22 | ); 23 | }; 24 | } -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | tests/suite.php 5 | 6 | 7 | 8 | tests/suite.php 9 | 10 | 11 | 12 | tests/suite.php 13 | 14 | 15 | 16 | tests/phpseclib_suite.php 17 | 18 | 19 | 20 | tests/phpseclib_suite.php 21 | 22 | 23 | 24 | tests/phpseclib_suite.php 25 | 26 | 27 | 28 | tests/phpseclib_suite.php 29 | 30 | 31 | 32 | tests/sodium_suite.php 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | assertSame($verify->verify($m), $m->signatures()); 16 | } 17 | 18 | public function testUncompressedOpsRSA() { 19 | $this->oneMessageRSA('pubring.gpg', 'uncompressed-ops-rsa.gpg'); 20 | } 21 | 22 | public function testCompressedSig() { 23 | $this->oneMessageRSA('pubring.gpg', 'compressedsig.gpg'); 24 | } 25 | 26 | public function testCompressedSigZLIB() { 27 | $this->oneMessageRSA('pubring.gpg', 'compressedsig-zlib.gpg'); 28 | } 29 | 30 | public function testCompressedSigBzip2() { 31 | $this->oneMessageRSA('pubring.gpg', 'compressedsig-bzip2.gpg'); 32 | } 33 | 34 | public function testSigningMessages() { 35 | $wkey = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); 36 | if (function_exists('uopz_set_return')) uopz_set_return('time', 0); 37 | $data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); 38 | $sign = new OpenPGP_Crypt_RSA($wkey); 39 | $m = $sign->sign($data)->to_bytes(); 40 | $reparsedM = OpenPGP_Message::parse($m); 41 | if (function_exists('uopz_unset_return')) { 42 | uopz_unset_return('time'); 43 | $this->assertSame(4871, $reparsedM[0]->hash_head); 44 | } 45 | $this->assertSame($sign->verify($reparsedM), $reparsedM->signatures()); 46 | } 47 | 48 | /* 49 | public function testUncompressedOpsDSA() { 50 | $this->oneMessageDSA('pubring.gpg', 'uncompressed-ops-dsa.gpg'); 51 | } 52 | 53 | public function testUncompressedOpsDSAsha384() { 54 | $this->oneMessageDSA('pubring.gpg', 'uncompressed-ops-dsa-sha384.gpg'); 55 | } 56 | */ 57 | } 58 | 59 | 60 | class KeyVerification extends TestCase { 61 | public function oneKeyRSA($path) { 62 | $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); 63 | $verify = new OpenPGP_Crypt_RSA($m); 64 | $this->assertSame($verify->verify($m), $m->signatures()); 65 | } 66 | 67 | public function testHelloKey() { 68 | $this->oneKeyRSA("helloKey.gpg"); 69 | } 70 | } 71 | 72 | abstract class LibTestCase extends TestCase { 73 | public function assertCast5Support() { 74 | if(in_array('mcrypt', get_loaded_extensions())) { 75 | return; 76 | } 77 | if(in_array('cast5-cfb', openssl_get_cipher_methods()) || in_array('CAST5-CFB', openssl_get_cipher_methods())) { 78 | return; 79 | } 80 | $this->markTestSkipped('Not supported'); 81 | } 82 | } 83 | 84 | class Decryption extends LibTestCase { 85 | public function oneSymmetric($pass, $cnt, $path) { 86 | $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); 87 | $m2 = OpenPGP_Crypt_Symmetric::decryptSymmetric($pass, $m); 88 | while($m2[0] instanceof OpenPGP_CompressedDataPacket) $m2 = $m2[0]->data; 89 | foreach($m2 as $p) { 90 | if($p instanceof OpenPGP_LiteralDataPacket) { 91 | $this->assertEquals($p->data, $cnt); 92 | } 93 | } 94 | } 95 | 96 | public function testDecrypt3DES() { 97 | $this->oneSymmetric("hello", "PGP\n", "symmetric-3des.gpg"); 98 | } 99 | 100 | public function testDecryptCAST5() { // Requires mcrypt or openssl 101 | $this->assertCast5Support(); 102 | $this->oneSymmetric("hello", "PGP\n", "symmetric-cast5.gpg"); 103 | } 104 | 105 | public function testDecryptBlowfish() { 106 | $this->oneSymmetric("hello", "PGP\n", "symmetric-blowfish.gpg"); 107 | } 108 | 109 | public function testDecryptAES() { 110 | $this->oneSymmetric("hello", "PGP\n", "symmetric-aes.gpg"); 111 | } 112 | 113 | public function testDecryptTwofish() { 114 | if(OpenPGP_Crypt_Symmetric::getCipher(10)[0]) { 115 | $this->oneSymmetric("hello", "PGP\n", "symmetric-twofish.gpg"); 116 | } 117 | } 118 | 119 | public function testDecryptSessionKey() { 120 | $this->oneSymmetric("hello", "PGP\n", "symmetric-with-session-key.gpg"); 121 | } 122 | 123 | public function testDecryptNoMDC() { 124 | $this->oneSymmetric("hello", "PGP\n", "symmetric-no-mdc.gpg"); 125 | } 126 | 127 | public function testDecryptAsymmetric() { 128 | $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/hello.gpg')); 129 | $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); 130 | $decryptor = new OpenPGP_Crypt_RSA($key); 131 | $m2 = $decryptor->decrypt($m); 132 | while($m2[0] instanceof OpenPGP_CompressedDataPacket) $m2 = $m2[0]->data; 133 | foreach($m2 as $p) { 134 | if($p instanceof OpenPGP_LiteralDataPacket) { 135 | $this->assertEquals($p->data, "hello\n"); 136 | } 137 | } 138 | } 139 | 140 | public function testDecryptRoundtrip() { 141 | $m = new OpenPGP_Message(array(new OpenPGP_LiteralDataPacket("hello\n"))); 142 | $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); 143 | $em = OpenPGP_Crypt_Symmetric::encrypt($key, $m); 144 | 145 | foreach($key as $packet) { 146 | if(!($packet instanceof OpenPGP_SecretKeyPacket)) continue; 147 | $decryptor = new OpenPGP_Crypt_RSA($packet); 148 | $m2 = $decryptor->decrypt($em); 149 | 150 | foreach($m2 as $p) { 151 | if($p instanceof OpenPGP_LiteralDataPacket) { 152 | $this->assertEquals($p->data, "hello\n"); 153 | } 154 | } 155 | } 156 | } 157 | 158 | public function testDecryptSecretKey() { 159 | $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/encryptedSecretKey.gpg')); 160 | $skey = OpenPGP_Crypt_Symmetric::decryptSecretKey("hello", $key[0]); 161 | $this->assertSame(!!$skey, true); 162 | } 163 | 164 | public function testEncryptSecretKeyRoundtrip() { 165 | $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); 166 | $enkey = OpenPGP_Crypt_Symmetric::encryptSecretKey("password", $key[0]); 167 | $skey = OpenPGP_Crypt_Symmetric::decryptSecretKey("password", $enkey); 168 | $this->assertEquals($key[0], $skey); 169 | } 170 | 171 | public function testAlreadyDecryptedSecretKey() { 172 | $this->expectException(Exception::class); 173 | $this->expectExceptionMessage("Data is already unencrypted"); 174 | $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); 175 | OpenPGP_Crypt_Symmetric::decryptSecretKey("hello", $key[0]); 176 | } 177 | } 178 | 179 | class Encryption extends LibTestCase { 180 | public function oneSymmetric($algorithm) { 181 | $data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); 182 | $encrypted = OpenPGP_Crypt_Symmetric::encrypt('secret', new OpenPGP_Message(array($data)), $algorithm); 183 | $encrypted = OpenPGP_Message::parse($encrypted->to_bytes()); 184 | $decrypted = OpenPGP_Crypt_Symmetric::decryptSymmetric('secret', $encrypted); 185 | $this->assertEquals($decrypted[0]->data, 'This is text.'); 186 | } 187 | 188 | public function testEncryptSymmetric3DES() { 189 | $this->oneSymmetric(2); 190 | } 191 | 192 | public function testEncryptSymmetricCAST5() { 193 | $this->assertCast5Support(); 194 | $this->oneSymmetric(3); 195 | } 196 | 197 | public function testEncryptSymmetricBlowfish() { 198 | $this->oneSymmetric(4); 199 | } 200 | 201 | public function testEncryptSymmetricAES128() { 202 | $this->oneSymmetric(7); 203 | } 204 | 205 | public function testEncryptSymmetricAES192() { 206 | $this->oneSymmetric(8); 207 | } 208 | 209 | public function testEncryptSymmetricAES256() { 210 | $this->oneSymmetric(9); 211 | } 212 | 213 | public function testEncryptSymmetricTwofish() { 214 | if(OpenPGP_Crypt_Symmetric::getCipher(10)[0]) { 215 | $this->oneSymmetric(10); 216 | } 217 | } 218 | 219 | public function testEncryptAsymmetric() { 220 | $key = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/helloKey.gpg')); 221 | $data = new OpenPGP_LiteralDataPacket('This is text.', array('format' => 'u', 'filename' => 'stuff.txt')); 222 | $encrypted = OpenPGP_Crypt_Symmetric::encrypt($key, new OpenPGP_Message(array($data))); 223 | $encrypted = OpenPGP_Message::parse($encrypted->to_bytes()); 224 | $decryptor = new OpenPGP_Crypt_RSA($key); 225 | $decrypted = $decryptor->decrypt($encrypted); 226 | $this->assertEquals($decrypted[0]->data, 'This is text.'); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /tests/sodium_suite.php: -------------------------------------------------------------------------------- 1 | assertSame($m->verified_signatures(array('EdDSA' => $verify)), $m->signatures()); 15 | } 16 | 17 | public function tested25519() { 18 | $this->oneMessageEdDSA('ed25519.public_key', 'ed25519.sig'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /tests/suite.php: -------------------------------------------------------------------------------- 1 | to_bytes(); 10 | $out = OpenPGP_Message::parse($mid); 11 | $this->assertEquals($in, $out); 12 | } 13 | 14 | public function test000001006public_key() { 15 | $this->oneSerialization("000001-006.public_key"); 16 | } 17 | 18 | public function test000002013user_id() { 19 | $this->oneSerialization("000002-013.user_id"); 20 | } 21 | 22 | public function test000003002sig() { 23 | $this->oneSerialization("000003-002.sig"); 24 | } 25 | 26 | public function test000004012ring_trust() { 27 | $this->oneSerialization("000004-012.ring_trust"); 28 | } 29 | 30 | public function test000005002sig() { 31 | $this->oneSerialization("000005-002.sig"); 32 | } 33 | 34 | public function test000006012ring_trust() { 35 | $this->oneSerialization("000006-012.ring_trust"); 36 | } 37 | 38 | public function test000007002sig() { 39 | $this->oneSerialization("000007-002.sig"); 40 | } 41 | 42 | public function test000008012ring_trust() { 43 | $this->oneSerialization("000008-012.ring_trust"); 44 | } 45 | 46 | public function test000009002sig() { 47 | $this->oneSerialization("000009-002.sig"); 48 | } 49 | 50 | public function test000010012ring_trust() { 51 | $this->oneSerialization("000010-012.ring_trust"); 52 | } 53 | 54 | public function test000011002sig() { 55 | $this->oneSerialization("000011-002.sig"); 56 | } 57 | 58 | public function test000012012ring_trust() { 59 | $this->oneSerialization("000012-012.ring_trust"); 60 | } 61 | 62 | public function test000013014public_subkey() { 63 | $this->oneSerialization("000013-014.public_subkey"); 64 | } 65 | 66 | public function test000014002sig() { 67 | $this->oneSerialization("000014-002.sig"); 68 | } 69 | 70 | public function test000015012ring_trust() { 71 | $this->oneSerialization("000015-012.ring_trust"); 72 | } 73 | 74 | public function test000016006public_key() { 75 | $this->oneSerialization("000016-006.public_key"); 76 | } 77 | 78 | public function test000017002sig() { 79 | $this->oneSerialization("000017-002.sig"); 80 | } 81 | 82 | public function test000018012ring_trust() { 83 | $this->oneSerialization("000018-012.ring_trust"); 84 | } 85 | 86 | public function test000019013user_id() { 87 | $this->oneSerialization("000019-013.user_id"); 88 | } 89 | 90 | public function test000020002sig() { 91 | $this->oneSerialization("000020-002.sig"); 92 | } 93 | 94 | public function test000021012ring_trust() { 95 | $this->oneSerialization("000021-012.ring_trust"); 96 | } 97 | 98 | public function test000022002sig() { 99 | $this->oneSerialization("000022-002.sig"); 100 | } 101 | 102 | public function test000023012ring_trust() { 103 | $this->oneSerialization("000023-012.ring_trust"); 104 | } 105 | 106 | public function test000024014public_subkey() { 107 | $this->oneSerialization("000024-014.public_subkey"); 108 | } 109 | 110 | public function test000025002sig() { 111 | $this->oneSerialization("000025-002.sig"); 112 | } 113 | 114 | public function test000026012ring_trust() { 115 | $this->oneSerialization("000026-012.ring_trust"); 116 | } 117 | 118 | public function test000027006public_key() { 119 | $this->oneSerialization("000027-006.public_key"); 120 | } 121 | 122 | public function test000028002sig() { 123 | $this->oneSerialization("000028-002.sig"); 124 | } 125 | 126 | public function test000029012ring_trust() { 127 | $this->oneSerialization("000029-012.ring_trust"); 128 | } 129 | 130 | public function test000030013user_id() { 131 | $this->oneSerialization("000030-013.user_id"); 132 | } 133 | 134 | public function test000031002sig() { 135 | $this->oneSerialization("000031-002.sig"); 136 | } 137 | 138 | public function test000032012ring_trust() { 139 | $this->oneSerialization("000032-012.ring_trust"); 140 | } 141 | 142 | public function test000033002sig() { 143 | $this->oneSerialization("000033-002.sig"); 144 | } 145 | 146 | public function test000034012ring_trust() { 147 | $this->oneSerialization("000034-012.ring_trust"); 148 | } 149 | 150 | public function test000035006public_key() { 151 | $this->oneSerialization("000035-006.public_key"); 152 | } 153 | 154 | public function test000036013user_id() { 155 | $this->oneSerialization("000036-013.user_id"); 156 | } 157 | 158 | public function test000037002sig() { 159 | $this->oneSerialization("000037-002.sig"); 160 | } 161 | 162 | public function test000038012ring_trust() { 163 | $this->oneSerialization("000038-012.ring_trust"); 164 | } 165 | 166 | public function test000039002sig() { 167 | $this->oneSerialization("000039-002.sig"); 168 | } 169 | 170 | public function test000040012ring_trust() { 171 | $this->oneSerialization("000040-012.ring_trust"); 172 | } 173 | 174 | public function test000041017attribute() { 175 | $this->oneSerialization("000041-017.attribute"); 176 | } 177 | 178 | public function test000042002sig() { 179 | $this->oneSerialization("000042-002.sig"); 180 | } 181 | 182 | public function test000043012ring_trust() { 183 | $this->oneSerialization("000043-012.ring_trust"); 184 | } 185 | 186 | public function test000044014public_subkey() { 187 | $this->oneSerialization("000044-014.public_subkey"); 188 | } 189 | 190 | public function test000045002sig() { 191 | $this->oneSerialization("000045-002.sig"); 192 | } 193 | 194 | public function test000046012ring_trust() { 195 | $this->oneSerialization("000046-012.ring_trust"); 196 | } 197 | 198 | public function test000047005secret_key() { 199 | $this->oneSerialization("000047-005.secret_key"); 200 | } 201 | 202 | public function test000048013user_id() { 203 | $this->oneSerialization("000048-013.user_id"); 204 | } 205 | 206 | public function test000049002sig() { 207 | $this->oneSerialization("000049-002.sig"); 208 | } 209 | 210 | public function test000050012ring_trust() { 211 | $this->oneSerialization("000050-012.ring_trust"); 212 | } 213 | 214 | public function test000051007secret_subkey() { 215 | $this->oneSerialization("000051-007.secret_subkey"); 216 | } 217 | 218 | public function test000052002sig() { 219 | $this->oneSerialization("000052-002.sig"); 220 | } 221 | 222 | public function test000053012ring_trust() { 223 | $this->oneSerialization("000053-012.ring_trust"); 224 | } 225 | 226 | public function test000054005secret_key() { 227 | $this->oneSerialization("000054-005.secret_key"); 228 | } 229 | 230 | public function test000055002sig() { 231 | $this->oneSerialization("000055-002.sig"); 232 | } 233 | 234 | public function test000056012ring_trust() { 235 | $this->oneSerialization("000056-012.ring_trust"); 236 | } 237 | 238 | public function test000057013user_id() { 239 | $this->oneSerialization("000057-013.user_id"); 240 | } 241 | 242 | public function test000058002sig() { 243 | $this->oneSerialization("000058-002.sig"); 244 | } 245 | 246 | public function test000059012ring_trust() { 247 | $this->oneSerialization("000059-012.ring_trust"); 248 | } 249 | 250 | public function test000060007secret_subkey() { 251 | $this->oneSerialization("000060-007.secret_subkey"); 252 | } 253 | 254 | public function test000061002sig() { 255 | $this->oneSerialization("000061-002.sig"); 256 | } 257 | 258 | public function test000062012ring_trust() { 259 | $this->oneSerialization("000062-012.ring_trust"); 260 | } 261 | 262 | public function test000063005secret_key() { 263 | $this->oneSerialization("000063-005.secret_key"); 264 | } 265 | 266 | public function test000064002sig() { 267 | $this->oneSerialization("000064-002.sig"); 268 | } 269 | 270 | public function test000065012ring_trust() { 271 | $this->oneSerialization("000065-012.ring_trust"); 272 | } 273 | 274 | public function test000066013user_id() { 275 | $this->oneSerialization("000066-013.user_id"); 276 | } 277 | 278 | public function test000067002sig() { 279 | $this->oneSerialization("000067-002.sig"); 280 | } 281 | 282 | public function test000068012ring_trust() { 283 | $this->oneSerialization("000068-012.ring_trust"); 284 | } 285 | 286 | public function test000069005secret_key() { 287 | $this->oneSerialization("000069-005.secret_key"); 288 | } 289 | 290 | public function test000070013user_id() { 291 | $this->oneSerialization("000070-013.user_id"); 292 | } 293 | 294 | public function test000071002sig() { 295 | $this->oneSerialization("000071-002.sig"); 296 | } 297 | 298 | public function test000072012ring_trust() { 299 | $this->oneSerialization("000072-012.ring_trust"); 300 | } 301 | 302 | public function test000073017attribute() { 303 | $this->oneSerialization("000073-017.attribute"); 304 | } 305 | 306 | public function test000074002sig() { 307 | $this->oneSerialization("000074-002.sig"); 308 | } 309 | 310 | public function test000075012ring_trust() { 311 | $this->oneSerialization("000075-012.ring_trust"); 312 | } 313 | 314 | public function test000076007secret_subkey() { 315 | $this->oneSerialization("000076-007.secret_subkey"); 316 | } 317 | 318 | public function test000077002sig() { 319 | $this->oneSerialization("000077-002.sig"); 320 | } 321 | 322 | public function test000078012ring_trust() { 323 | $this->oneSerialization("000078-012.ring_trust"); 324 | } 325 | 326 | public function test002182002sig() { 327 | $this->oneSerialization("002182-002.sig"); 328 | } 329 | 330 | public function testpubringgpg() { 331 | $this->oneSerialization("pubring.gpg"); 332 | } 333 | 334 | public function testsecringgpg() { 335 | $this->oneSerialization("secring.gpg"); 336 | } 337 | 338 | public function testcompressedsiggpg() { 339 | $this->oneSerialization("compressedsig.gpg"); 340 | } 341 | 342 | public function testcompressedsigzlibgpg() { 343 | $this->oneSerialization("compressedsig-zlib.gpg"); 344 | } 345 | 346 | public function testcompressedsigbzip2gpg() { 347 | $this->oneSerialization("compressedsig-bzip2.gpg"); 348 | } 349 | 350 | public function testonepass_sig() { 351 | $this->oneSerialization("onepass_sig"); 352 | } 353 | 354 | public function testsymmetrically_encrypted() { 355 | $this->oneSerialization("symmetrically_encrypted"); 356 | } 357 | 358 | public function testuncompressedopsdsagpg() { 359 | $this->oneSerialization("uncompressed-ops-dsa.gpg"); 360 | } 361 | 362 | public function testuncompressedopsdsasha384txtgpg() { 363 | $this->oneSerialization("uncompressed-ops-dsa-sha384.txt.gpg"); 364 | } 365 | 366 | public function testuncompressedopsrsagpg() { 367 | $this->oneSerialization("uncompressed-ops-rsa.gpg"); 368 | } 369 | 370 | public function testSymmetricAES() { 371 | $this->oneSerialization("symmetric-aes.gpg"); 372 | } 373 | 374 | public function testSymmetricNoMDC() { 375 | $this->oneSerialization("symmetric-no-mdc.gpg"); 376 | } 377 | 378 | public function tested25519_public() { 379 | $this->oneSerialization("ed25519.public_key"); 380 | } 381 | 382 | public function tested25519_secret() { 383 | $this->oneSerialization("ed25519.secret_key"); 384 | } 385 | } 386 | 387 | class Fingerprint extends TestCase { 388 | public function oneFingerprint($path, $kf) { 389 | $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); 390 | $this->assertEquals($m[0]->fingerprint(), $kf); 391 | } 392 | 393 | public function test000001006public_key() { 394 | $this->oneFingerprint("000001-006.public_key", "421F28FEAAD222F856C8FFD5D4D54EA16F87040E"); 395 | } 396 | 397 | public function test000016006public_key() { 398 | $this->oneFingerprint("000016-006.public_key", "AF95E4D7BAC521EE9740BED75E9F1523413262DC"); 399 | } 400 | 401 | public function test000027006public_key() { 402 | $this->oneFingerprint("000027-006.public_key", "1EB20B2F5A5CC3BEAFD6E5CB7732CF988A63EA86"); 403 | } 404 | 405 | public function test000035006public_key() { 406 | $this->oneFingerprint("000035-006.public_key", "CB7933459F59C70DF1C3FBEEDEDC3ECF689AF56D"); 407 | } 408 | 409 | public function test000080006public_key() { 410 | $this->oneFingerprint("000080-006.public_key", "AEDA0C4468AE265E8B7CCA1C3047D4A7B15467AB"); 411 | } 412 | 413 | public function test000082006public_key() { 414 | $this->oneFingerprint("000082-006.public_key", "589D7E6884A9235BBE821D35BD7BA7BC5547FD09"); 415 | } 416 | 417 | public function tested25519() { 418 | $this->oneFingerprint("ed25519.public_key", "88771946427EC2E24569C1D86208BE2B78C27E94"); 419 | } 420 | } 421 | 422 | class Signature extends TestCase { 423 | public function oneIssuer($path, $kf) { 424 | $m = OpenPGP_Message::parse(file_get_contents(dirname(__FILE__) . '/data/' . $path)); 425 | $this->assertEquals($m[0]->issuer(), $kf); 426 | } 427 | 428 | public function test000079002sig() { 429 | $this->oneIssuer("000079-002.sig", "C25059FA8730BC38"); 430 | } 431 | 432 | public function test000081002sig() { 433 | $this->oneIssuer("000081-002.sig", "6B799484725130FE"); 434 | } 435 | 436 | public function test000083002sig() { 437 | $this->oneIssuer("000083-002.sig", "BD7BA7BC5547FD09"); 438 | } 439 | } 440 | 441 | class Armor extends TestCase { 442 | public function testRoundTrip() { 443 | $bytes = "abcd\0\xff"; 444 | $this->assertEquals($bytes, OpenPGP::unarmor(OpenPGP::enarmor($bytes), 'MESSAGE')); 445 | } 446 | 447 | public function testInvalidBase64() { 448 | $input = OpenPGP::header('MESSAGE') . "\n\nY~WJjZAD/\n=PE3Q\n" . OpenPGP::footer('MESSAGE'); 449 | $this->assertEquals(false, OpenPGP::unarmor($input, 'MESSAGE')); 450 | } 451 | } --------------------------------------------------------------------------------