Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4']
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']
name: PHP ${{ matrix.php-versions }} Test
steps:
- name: Checkout Code
Expand Down
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
### .: Fork with verified support for PHP 7.2, 7.3, 7.4, 8.0, 8.1, 8.2, 8.4

# JOSE

PHP JOSE (Javascript Object Signing and Encryption) Implementation
> **Fork Notice:** This is a maintained fork of the abandoned [`gree/jose`](https://github.com/nov/jose-php) package. Updated with verified support for PHP 7.2 through 8.5.

PHP JOSE (JSON Object Signing and Encryption) Implementation

[![CI](https://github.com/maksimovic/jose-php/actions/workflows/ci.yml/badge.svg)](https://github.com/maksimovic/jose-php/actions/workflows/ci.yml)
[![codecov](https://codecov.io/github/maksimovic/jose-php/graph/badge.svg?token=OJY9BDDILN)](https://codecov.io/github/maksimovic/jose-php)

## Installation

`composer install maksimovic/jose-php`
```sh
composer require maksimovic/jose-php
```

## Requirements

phpseclib is required.
http://phpseclib.sourceforge.net
PHP 7.2 or later. [phpseclib](https://github.com/phpseclib/phpseclib) v2 is required (installed automatically via Composer).

## Example

Expand Down Expand Up @@ -47,7 +49,7 @@ $jwt = new JOSE_JWT(array(
$jws = $jwt->sign($private_key, 'RS256');
```

NOTE: `$private_key` can be `phpseclib\Crypt\RSA` instance.
NOTE: `$private_key` can be a `phpseclib\Crypt\RSA` instance.

#### Verification

Expand All @@ -58,7 +60,7 @@ $jws = JOSE_JWT::decode($jwt_string);
$jws->verify($public_key, 'RS256');
```

NOTE: `$public_key` can be `JOSE_JWK` or `phpseclib\Crypt\RSA` instance.
NOTE: `$public_key` can be a `JOSE_JWK` or `phpseclib\Crypt\RSA` instance.

### JWE

Expand Down Expand Up @@ -104,7 +106,6 @@ JOSE_JWK::encode($private_key); # => JOSE_JWK instance
##### RSA Public Key

```php
# public key
$components = array(
'kty' => 'RSA',
'e' => 'AQAB',
Expand All @@ -117,15 +118,15 @@ JOSE_JWK::decode($components); # => phpseclib\Crypt\RSA instance

Not supported.

## Run Test
## Development

```bash
git clone git://github.com/maksimovic/jose-php.git
```sh
git clone https://github.com/maksimovic/jose-php.git
cd jose-php
composer install
vendor/bin/phpunit test
```

## Copyright
## License

Copyright © 2013 Nov Matake & GREE Inc. See LICENSE for details.
MIT. Copyright © 2013 Nov Matake & GREE Inc. See LICENSE for details.
236 changes: 236 additions & 0 deletions test/JWETest.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,240 @@ function testDecode()
"enc" => "A128CBC-HS256"
), $jwe->header);
}

function testConstructFromJWT()
{
$jwt = new JOSE_JWT(array('foo' => 'bar'));
$jwe = new JOSE_JWE($jwt);
$jwe->encrypt($this->rsa_keys['public']);
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$decrypted = $jwe_decoded->decrypt($this->rsa_keys['private']);
$this->assertEquals($jwt->toString(), $decrypted->plain_text);
}

function testConstructFromNull()
{
$jwe = new JOSE_JWE();
$this->assertArrayNotHasKey('typ', $jwe->header);
}

function testEncryptDir_A256CBCHS512()
{
$secret = Random::string(512 / 8);
$jwe = new JOSE_JWE($this->plain_text);
$jwe = $jwe->encrypt($secret, 'dir', 'A256CBC-HS512');
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$this->assertEquals($this->plain_text, $jwe_decoded->decrypt($secret)->plain_text);
}

function testEncryptWithJWKSetsKid()
{
$rsa = new \phpseclib\Crypt\RSA();
$rsa->loadKey($this->rsa_keys['public']);
$jwk = \JOSE_JWK::encode($rsa, array('kid' => 'test-key-id'));
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($jwk);
$this->assertEquals('test-key-id', $jwe->header['kid']);
}

function testEncryptWithCryptRSA()
{
$rsa = new \phpseclib\Crypt\RSA();
$rsa->loadKey($this->rsa_keys['public']);
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($rsa);
$segments = explode('.', $jwe->toString());
$this->assertEquals(5, count($segments));
}

function testEncryptA256KW()
{
$jwe = new JOSE_JWE($this->plain_text);
$this->expectException('JOSE_Exception_UnexpectedAlgorithm');
$jwe->encrypt($this->rsa_keys['public'], 'A256KW');
}

function testEncryptECDHES()
{
$jwe = new JOSE_JWE($this->plain_text);
$this->expectException('JOSE_Exception_UnexpectedAlgorithm');
$jwe->encrypt($this->rsa_keys['public'], 'ECDH-ES');
}

function testEncryptECDHES_A128KW()
{
$jwe = new JOSE_JWE($this->plain_text);
$this->expectException('JOSE_Exception_UnexpectedAlgorithm');
$jwe->encrypt($this->rsa_keys['public'], 'ECDH-ES+A128KW');
}

function testEncryptECDHES_A256KW()
{
$jwe = new JOSE_JWE($this->plain_text);
$this->expectException('JOSE_Exception_UnexpectedAlgorithm');
$jwe->encrypt($this->rsa_keys['public'], 'ECDH-ES+A256KW');
}

function testDecryptRSAOAEP_A128CBCHS256()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public'], 'RSA-OAEP');
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$decrypted = $jwe_decoded->decrypt($this->rsa_keys['private']);
$this->assertEquals($this->plain_text, $decrypted->plain_text);
}

function testDecryptRSAOAEP_A256CBCHS512()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public'], 'RSA-OAEP', 'A256CBC-HS512');
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$decrypted = $jwe_decoded->decrypt($this->rsa_keys['private']);
$this->assertEquals($this->plain_text, $decrypted->plain_text);
}

function testDecryptRSA15_A256CBCHS512()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public'], 'RSA1_5', 'A256CBC-HS512');
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$decrypted = $jwe_decoded->decrypt($this->rsa_keys['private']);
$this->assertEquals($this->plain_text, $decrypted->plain_text);
}

function testDecryptWithCryptRSA()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public']);
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$rsa = new \phpseclib\Crypt\RSA();
$rsa->loadKey($this->rsa_keys['private']);
$decrypted = $jwe_decoded->decrypt($rsa);
$this->assertEquals($this->plain_text, $decrypted->plain_text);
}

function testDecryptDir_A128CBCHS256()
{
$secret = Random::string(256 / 8);
$jwe = new JOSE_JWE($this->plain_text);
$jwe = $jwe->encrypt($secret, 'dir');
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$decrypted = $jwe_decoded->decrypt($secret);
$this->assertEquals($this->plain_text, $decrypted->plain_text);
}

function testDecryptDir_A256CBCHS512()
{
$secret = Random::string(512 / 8);
$jwe = new JOSE_JWE($this->plain_text);
$jwe = $jwe->encrypt($secret, 'dir', 'A256CBC-HS512');
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$decrypted = $jwe_decoded->decrypt($secret);
$this->assertEquals($this->plain_text, $decrypted->plain_text);
}

function testDecryptA128KW()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public']);
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
// Manually override the header to simulate an A128KW token
$jwe_decoded->header['alg'] = 'A128KW';
$this->expectException('JOSE_Exception_UnexpectedAlgorithm');
$jwe_decoded->decrypt($this->rsa_keys['private']);
}

function testDecryptUnknownAlgorithm()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public']);
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$jwe_decoded->header['alg'] = 'Unknown';
$this->expectException('JOSE_Exception_UnexpectedAlgorithm');
$jwe_decoded->decrypt($this->rsa_keys['private']);
}

function testDecryptInvalidAuthenticationTag()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public']);
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
// Corrupt the authentication tag
$jwe_decoded->authentication_tag = 'invalid';
$this->expectException('JOSE_Exception_UnexpectedAlgorithm');
$jwe_decoded->decrypt($this->rsa_keys['private']);
}

function testEncryptReturnsJWE()
{
$jwe = new JOSE_JWE($this->plain_text);
$result = $jwe->encrypt($this->rsa_keys['public']);
$this->assertInstanceOf('JOSE_JWE', $result);
}

function testDecryptReturnsJWE()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public']);
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$result = $jwe_decoded->decrypt($this->rsa_keys['private']);
$this->assertInstanceOf('JOSE_JWE', $result);
}

function testEncryptFromJWTEncryptMethod()
{
$jwt = new JOSE_JWT(array('foo' => 'bar'));
$jwe = $jwt->encrypt($this->rsa_keys['public'], 'RSA-OAEP', 'A256CBC-HS512');
$this->assertInstanceOf('JOSE_JWE', $jwe);
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$decrypted = $jwe_decoded->decrypt($this->rsa_keys['private']);
$this->assertEquals($jwt->toString(), $decrypted->plain_text);
}

function testHeaderDoesNotContainTyp()
{
$jwe = new JOSE_JWE($this->plain_text);
$this->assertArrayNotHasKey('typ', $jwe->header);
}

function testDirEncryptedKeyIsEmpty()
{
$secret = Random::string(256 / 8);
$jwe = new JOSE_JWE($this->plain_text);
$jwe = $jwe->encrypt($secret, 'dir');
$this->assertEmpty($jwe->jwe_encrypted_key);
}

function testToStringHasFiveSegments()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public']);
$segments = explode('.', $jwe->toString());
$this->assertEquals(5, count($segments));
// Verify each segment is non-empty except possibly encrypted key
$this->assertNotEmpty($segments[0]); // header
$this->assertNotEmpty($segments[1]); // encrypted key
$this->assertNotEmpty($segments[2]); // IV
$this->assertNotEmpty($segments[3]); // cipher text
$this->assertNotEmpty($segments[4]); // auth tag
}

function testDecodeHeaderValues()
{
$jwe = new JOSE_JWE($this->plain_text);
$jwe->encrypt($this->rsa_keys['public'], 'RSA-OAEP', 'A256CBC-HS512');
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$this->assertEquals('RSA-OAEP', $jwe_decoded->header['alg']);
$this->assertEquals('A256CBC-HS512', $jwe_decoded->header['enc']);
}

function testRoundTripLongPlainText()
{
$long_text = str_repeat('The quick brown fox jumps over the lazy dog. ', 100);
$jwe = new JOSE_JWE($long_text);
$jwe->encrypt($this->rsa_keys['public']);
$jwe_decoded = JOSE_JWT::decode($jwe->toString());
$decrypted = $jwe_decoded->decrypt($this->rsa_keys['private']);
$this->assertEquals($long_text, $decrypted->plain_text);
}
}
15 changes: 11 additions & 4 deletions test/JWSTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,16 @@ function testVerifyMalformedJWS_RS256_to_HS256_with_explicit_alg()
$this->plain_jwt->sign($this->rsa_keys['public'], 'HS256')->toString()
);

$this->expectException(\PHPUnit\Framework\Error\Notice::class);
$this->expectExceptionMessage('Invalid signature');

$malformed_jwt->verify($this->rsa_keys['public'], 'RS256');
set_error_handler(function ($errno, $errstr) {
throw new \RuntimeException($errstr, $errno);
}, E_NOTICE | E_USER_NOTICE);

try {
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Invalid signature');
$malformed_jwt->verify($this->rsa_keys['public'], 'RS256');
} finally {
restore_error_handler();
}
}
}
Loading