Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Ed25519 support to JWT #343

Merged
merged 15 commits into from
Jun 23, 2021
Merged
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ Use composer to manage your dependencies and download PHP-JWT:
composer require firebase/php-jwt
```

Optionally, install the paragonie/sodium_compat package from composer if your
bshaffer marked this conversation as resolved.
Show resolved Hide resolved
php is < 7.2 or does not have libsodium installed:
bshaffer marked this conversation as resolved.
Show resolved Hide resolved

```bash
composer require paragonie/sodium_compat
```

Example
-------
```php
Expand Down Expand Up @@ -144,6 +151,36 @@ $decoded = JWT::decode($jwt, $publicKey, array('RS256'));
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
```

Example with EdDSA (libsodium and Ed25519 signature)
----------------------------
```php
use Firebase\JWT\JWT;

// Public and private keys are expected to be Base 64 encoded. The last
bshaffer marked this conversation as resolved.
Show resolved Hide resolved
// non-empty line is used so that keys can be generated with
// sodium_crypto_sign_keypair(). The secret keys generated by other tools may
// need to be adjusted to match the input expected by libsodium.

$keyPair = sodium_crypto_sign_keypair();

$privateKey = base64_encode(sodium_crypto_sign_secretkey($keyPair));

$publicKey = base64_encode(sodium_crypto_sign_publickey($keyPair));

$payload = array(
"iss" => "example.org",
"aud" => "example.com",
"iat" => 1356999524,
"nbf" => 1357000000
);

$jwt = JWT::encode($payload, $privateKey, 'EdDSA');
echo "Encode:\n" . print_r($jwt, true) . "\n";

$decoded = JWT::decode($jwt, $publicKey, array('EdDSA'));
echo "Decode:\n" . print_r((array) $decoded, true) . "\n";
````

Using JWKs
----------

Expand Down
3 changes: 3 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"require": {
"php": ">=5.3.0"
},
"suggest": {
"paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present"
},
"autoload": {
"psr-4": {
"Firebase\\JWT\\": "src"
Expand Down
30 changes: 28 additions & 2 deletions src/JWT.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Firebase\JWT;

use DomainException;
use Exception;
use InvalidArgumentException;
use UnexpectedValueException;
use DateTime;
Expand Down Expand Up @@ -50,6 +51,7 @@ class JWT
'RS256' => array('openssl', 'SHA256'),
'RS384' => array('openssl', 'SHA384'),
'RS512' => array('openssl', 'SHA512'),
'EdDSA' => array('sodium_crypto', 'EdDSA'),
);

/**
Expand Down Expand Up @@ -198,7 +200,7 @@ public static function encode($payload, $key, $alg = 'HS256', $keyId = null, $he
*
* @return string An encrypted message
*
* @throws DomainException Unsupported algorithm was specified
* @throws DomainException Unsupported algorithm or bad key was specified
*/
public static function sign($msg, $key, $alg = 'HS256')
{
Expand All @@ -223,6 +225,18 @@ public static function sign($msg, $key, $alg = 'HS256')
}
return $signature;
}
case 'sodium_crypto':
if (!function_exists('sodium_crypto_sign_detached')) {
throw new DomainException('libsodium is not available');
}
try {
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $key));
$key = base64_decode(end($lines));
return sodium_crypto_sign_detached($msg, $key);
} catch (Exception $e) {
throw new DomainException($e->getMessage(), 0, $e);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason (other than staying consistent with the other exceptions thrown from this method) that we are wrapping the libsodium exceptions in DomainException?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for consistency and so we are not adding new exception types for BC. Also the other exception types may not be defined in PHP without the library or shim present, so anyone trying to write generic try/catch code will have a harder time.

}
}
}

Expand All @@ -237,7 +251,7 @@ public static function sign($msg, $key, $alg = 'HS256')
*
* @return bool
*
* @throws DomainException Invalid Algorithm or OpenSSL failure
* @throws DomainException Invalid Algorithm, bad key, or OpenSSL failure
*/
private static function verify($msg, $signature, $key, $alg)
{
Expand All @@ -258,6 +272,18 @@ private static function verify($msg, $signature, $key, $alg)
throw new DomainException(
'OpenSSL error: ' . \openssl_error_string()
);
case 'sodium_crypto':
if (!function_exists('sodium_crypto_sign_verify_detached')) {
throw new DomainException('libsodium is not available');
}
try {
// The last non-empty line is used as the key.
$lines = array_filter(explode("\n", $key));
$key = base64_decode(end($lines));
return sodium_crypto_sign_verify_detached($signature, $msg, $key);
} catch (Exception $e) {
throw new DomainException($e->getMessage(), 0, $e);
}
case 'hash_hmac':
default:
$hash = \hash_hmac($algorithm, $msg, $key, true);
Expand Down
2 changes: 2 additions & 0 deletions tests/JWTTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,8 @@ public function provideEncodeDecode()
return array(
array(__DIR__ . '/ecdsa-private.pem', __DIR__ . '/ecdsa-public.pem', 'ES256'),
array(__DIR__ . '/ecdsa384-private.pem', __DIR__ . '/ecdsa384-public.pem', 'ES384'),
array(__DIR__ . '/rsa1-private.pem', __DIR__ . '/rsa1-public.pem', 'RS512'),
array(__DIR__ . '/ed25519-1.sec', __DIR__ . '/ed25519-1.pub', 'EdDSA'),
);
}
}
1 change: 1 addition & 0 deletions tests/ed25519-1.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uOSJMhbKSG4V5xUHS7B9YHmVg/1yVd+G+Io6oBFhSfY=
1 change: 1 addition & 0 deletions tests/ed25519-1.sec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
i4eTKkWNIISKumdk3v90cPDrY/g8WRTJWy7DmGDsdzC45IkyFspIbhXnFQdLsH1geZWD/XJV34b4ijqgEWFJ9g==