welcome back to dyb-tech

This commit is contained in:
Daniel Guzman
2024-05-18 02:28:01 +02:00
parent 9513cdba09
commit 9f30bc98c7
6149 changed files with 668407 additions and 0 deletions
@@ -0,0 +1,26 @@
<?php
namespace Namshi\JOSE\Base64;
class Base64Encoder implements Encoder
{
/**
* @param string $data
*
* @return string
*/
public function encode($data)
{
return base64_encode($data);
}
/**
* @param string $data
*
* @return string
*/
public function decode($data)
{
return base64_decode($data);
}
}
@@ -0,0 +1,16 @@
<?php
namespace Namshi\JOSE\Base64;
class Base64UrlSafeEncoder implements Encoder
{
public function encode($data)
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
public function decode($data)
{
return base64_decode(strtr($data, '-_', '+/'));
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace Namshi\JOSE\Base64;
interface Encoder
{
/**
* @param string $data
*
* @return string
*/
public function encode($data);
/**
* @param string $data
*
* @return string
*/
public function decode($data);
}
+240
View File
@@ -0,0 +1,240 @@
<?php
namespace Namshi\JOSE;
use InvalidArgumentException;
use Namshi\JOSE\Base64\Base64Encoder;
use Namshi\JOSE\Base64\Base64UrlSafeEncoder;
use Namshi\JOSE\Base64\Encoder;
use Namshi\JOSE\Signer\SignerInterface;
/**
* Class representing a JSON Web Signature.
*/
class JWS extends JWT
{
protected $signature;
protected $isSigned = false;
protected $originalToken;
protected $encodedSignature;
protected $encryptionEngine;
protected $supportedEncryptionEngines = array('OpenSSL', 'SecLib');
/**
* Constructor.
*
* @param array $header An associative array of headers. The value can be any type accepted by json_encode or a JSON serializable object
*
* @see http://php.net/manual/en/function.json-encode.php
* @see http://php.net/manual/en/jsonserializable.jsonserialize.php
* @see https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4
*
* @param string $encryptionEngine
* }
*/
public function __construct($header = array(), $encryptionEngine = 'OpenSSL')
{
if (!in_array($encryptionEngine, $this->supportedEncryptionEngines)) {
throw new InvalidArgumentException(sprintf('Encryption engine %s is not supported', $encryptionEngine));
}
if ('SecLib' === $encryptionEngine && version_compare(PHP_VERSION, '7.0.0-dev') >= 0) {
throw new InvalidArgumentException("phpseclib 1.0.0(LTS), even the latest 2.0.0, doesn't support PHP7 yet");
}
$this->encryptionEngine = $encryptionEngine;
parent::__construct(array(), $header);
}
/**
* Signs the JWS signininput.
*
* @param resource|string $key
* @param optional string $password
*
* @return string
*/
public function sign($key, $password = null)
{
$this->signature = $this->getSigner()->sign($this->generateSigninInput(), $key, $password);
$this->isSigned = true;
return $this->signature;
}
/**
* Returns the signature representation of the JWS.
*
* @return string
*/
public function getSignature()
{
if ($this->isSigned()) {
return $this->signature;
}
return;
}
/**
* Checks whether the JSW has already been signed.
*
* @return bool
*/
public function isSigned()
{
return (bool) $this->isSigned;
}
/**
* Returns the string representing the JWT.
*
* @return string
*/
public function getTokenString()
{
$signinInput = $this->generateSigninInput();
return sprintf('%s.%s', $signinInput, $this->encoder->encode($this->getSignature()));
}
/**
* Creates an instance of a JWS from a JWT.
*
* @param string $jwsTokenString
* @param bool $allowUnsecure
* @param Encoder $encoder
* @param string $encryptionEngine
*
* @return JWS
*
* @throws \InvalidArgumentException
*/
public static function load($jwsTokenString, $allowUnsecure = false, Encoder $encoder = null, $encryptionEngine = 'OpenSSL')
{
if ($encoder === null) {
$encoder = strpbrk($jwsTokenString, '+/=') ? new Base64Encoder() : new Base64UrlSafeEncoder();
}
$parts = explode('.', $jwsTokenString);
if (count($parts) === 3) {
$header = json_decode($encoder->decode($parts[0]), true);
$payload = json_decode($encoder->decode($parts[1]), true);
if (is_array($header) && is_array($payload)) {
if (strtolower($header['alg']) === 'none' && !$allowUnsecure) {
throw new InvalidArgumentException(sprintf('The token "%s" cannot be validated in a secure context, as it uses the unallowed "none" algorithm', $jwsTokenString));
}
$jws = new static($header, $encryptionEngine);
$jws->setEncoder($encoder)
->setHeader($header)
->setPayload($payload)
->setOriginalToken($jwsTokenString)
->setEncodedSignature($parts[2]);
return $jws;
}
}
throw new InvalidArgumentException(sprintf('The token "%s" is an invalid JWS', $jwsTokenString));
}
/**
* Verifies that the internal signin input corresponds to the encoded
* signature previously stored (@see JWS::load).
*
* @param resource|string $key
* @param string $algo The algorithms this JWS should be signed with. Use it if you want to restrict which algorithms you want to allow to be validated.
*
* @return bool
*/
public function verify($key, $algo = null)
{
if (empty($key) || ($algo && $this->header['alg'] !== $algo)) {
return false;
}
$decodedSignature = $this->encoder->decode($this->getEncodedSignature());
$signinInput = $this->getSigninInput();
return $this->getSigner()->verify($key, $decodedSignature, $signinInput);
}
/**
* Get the original token signin input if it exists, otherwise generate the
* signin input for the current JWS
*
* @return string
*/
private function getSigninInput()
{
$parts = explode('.', $this->originalToken);
if (count($parts) >= 2) {
return sprintf('%s.%s', $parts[0], $parts[1]);
}
return $this->generateSigninInput();
}
/**
* Sets the original base64 encoded token.
*
* @param string $originalToken
*
* @return JWS
*/
private function setOriginalToken($originalToken)
{
$this->originalToken = $originalToken;
return $this;
}
/**
* Returns the base64 encoded signature.
*
* @return string
*/
public function getEncodedSignature()
{
return $this->encodedSignature;
}
/**
* Sets the base64 encoded signature.
*
* @param string $encodedSignature
*
* @return JWS
*/
public function setEncodedSignature($encodedSignature)
{
$this->encodedSignature = $encodedSignature;
return $this;
}
/**
* Returns the signer responsible to encrypting / decrypting this JWS.
*
* @return SignerInterface
*
* @throws \InvalidArgumentException
*/
protected function getSigner()
{
$signerClass = sprintf('Namshi\\JOSE\\Signer\\%s\\%s', $this->encryptionEngine, $this->header['alg']);
if (class_exists($signerClass)) {
return new $signerClass();
}
throw new InvalidArgumentException(
sprintf("The algorithm '%s' is not supported for %s", $this->header['alg'], $this->encryptionEngine));
}
}
+107
View File
@@ -0,0 +1,107 @@
<?php
namespace Namshi\JOSE;
use Namshi\JOSE\Base64\Base64UrlSafeEncoder;
use Namshi\JOSE\Base64\Encoder;
/**
* Class representing a JSON Web Token.
*/
class JWT
{
/**
* @var array
*/
protected $payload;
/**
* @var array
*/
protected $header;
/**
* @var Encoder
*/
protected $encoder;
/**
* Constructor.
*
* @param array $payload
* @param array $header
*/
public function __construct(array $payload, array $header)
{
$this->setPayload($payload);
$this->setHeader($header);
$this->setEncoder(new Base64UrlSafeEncoder());
}
/**
* @param Encoder $encoder
*/
public function setEncoder(Encoder $encoder)
{
$this->encoder = $encoder;
return $this;
}
/**
* Generates the signininput for the current JWT.
*
* @return string
*/
public function generateSigninInput()
{
$base64payload = $this->encoder->encode(json_encode($this->getPayload(), JSON_UNESCAPED_SLASHES));
$base64header = $this->encoder->encode(json_encode($this->getHeader(), JSON_UNESCAPED_SLASHES));
return sprintf('%s.%s', $base64header, $base64payload);
}
/**
* Returns the payload of the JWT.
*
* @return array
*/
public function getPayload()
{
return $this->payload;
}
/**
* Sets the payload of the current JWT.
*
* @param array $payload
*/
public function setPayload(array $payload)
{
$this->payload = $payload;
return $this;
}
/**
* Returns the header of the JWT.
*
* @return array
*/
public function getHeader()
{
return $this->header;
}
/**
* Sets the header of this JWT.
*
* @param array $header
*/
public function setHeader(array $header)
{
$this->header = $header;
return $this;
}
}
@@ -0,0 +1,108 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
use phpseclib\File\ASN1;
/**
* Class responsible to sign inputs with the a ECDSA algorithm, after hashing it.
*/
abstract class ECDSA extends PublicKey
{
public function __construct()
{
if (version_compare(PHP_VERSION, '7.0.0-dev') >= 0) {
throw new \InvalidArgumentException("phpseclib 1.0.0(LTS), even the latest 2.0.0, doesn't support PHP7 yet");
}
}
/**
* {@inheritdoc}
*/
protected function supportsKey($key)
{
if (false === parent::supportsKey($key)) {
return false;
}
// openssl_sign with EC keys was introduced in this PHP release
$minVersions = array(
'5.4' => '5.4.26',
'5.5' => '5.5.10',
'5.6' => '5.6.0',
);
if (isset($minVersions[PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION]) &&
version_compare(PHP_VERSION, $minVersions[PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION], '<')) {
return false;
}
$keyDetails = openssl_pkey_get_details($key);
if (0 === preg_match('/-----BEGIN PUBLIC KEY-----([^-]+)-----END PUBLIC KEY-----/', $keyDetails['key'], $matches)) {
return false;
}
$publicKey = trim($matches[1]);
$asn1 = new ASN1();
/*
* http://tools.ietf.org/html/rfc3279#section-2.2.3
* AlgorithmIdentifier ::= SEQUENCE {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL
* }
* For ECDSA Signature Algorithm:
* algorithm: ansi-X9-62 => 1.2.840.10045.2.1
* parameters: id-ecSigType => 1.2.840.10045.x.y.z
*
*/
$asnAlgorithmIdentifier = array(
'type' => ASN1::TYPE_SEQUENCE,
'children' => array(
'ansi-X9-62' => array(
'type' => ASN1::TYPE_OBJECT_IDENTIFIER,
),
'id-ecSigType' => array(
'type' => ASN1::TYPE_OBJECT_IDENTIFIER,
),
),
);
/*
* http://tools.ietf.org/html/rfc5280#section-4.1
* SubjectPublicKeyInfo ::= SEQUENCE {
* algorithm AlgorithmIdentifier,
* subjectPublicKey BIT STRING
* }
*/
$asnSubjectPublicKeyInfo = array(
'type' => ASN1::TYPE_SEQUENCE,
'children' => array(
'algorithm' => $asnAlgorithmIdentifier,
'subjectPublicKey' => array(
'type' => ASN1::TYPE_BIT_STRING,
),
),
);
$decoded = $asn1->decodeBER(base64_decode($publicKey));
$mappedDetails = $asn1->asn1map($decoded[0], $asnSubjectPublicKeyInfo);
return isset($mappedDetails['algorithm']['id-ecSigType']) ? $this->getSupportedECDSACurve() === $mappedDetails['algorithm']['id-ecSigType'] : false;
}
/**
* {@inheritdoc}
*/
protected function getSupportedPrivateKeyType()
{
return defined('OPENSSL_KEYTYPE_EC') ? OPENSSL_KEYTYPE_EC : false;
}
/**
* Returns the ECDSA curve supported in this signer.
*
* @return string
*/
abstract protected function getSupportedECDSACurve();
}
@@ -0,0 +1,19 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* Class responsible to sign inputs with the ECDSA algorithm, after hashing it.
*/
class ES256 extends ECDSA
{
public function getHashingAlgorithm()
{
return version_compare(phpversion(), '5.4.8', '<') ? 'SHA256' : OPENSSL_ALGO_SHA256;
}
protected function getSupportedECDSACurve()
{
return '1.2.840.10045.3.1.7';
}
}
@@ -0,0 +1,19 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* Class responsible to sign inputs with the ECDSA algorithm, after hashing it.
*/
class ES384 extends ECDSA
{
public function getHashingAlgorithm()
{
return version_compare(phpversion(), '5.4.8', '<') ? 'SHA384' : OPENSSL_ALGO_SHA384;
}
protected function getSupportedECDSACurve()
{
return '1.3.132.0.34';
}
}
@@ -0,0 +1,19 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* Class responsible to sign inputs with the ECDSA algorithm, after hashing it.
*/
class ES512 extends ECDSA
{
public function getHashingAlgorithm()
{
return version_compare(phpversion(), '5.4.8', '<') ? 'SHA512' : OPENSSL_ALGO_SHA512;
}
protected function getSupportedECDSACurve()
{
return '1.3.132.0.35';
}
}
@@ -0,0 +1,56 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
use Namshi\JOSE\Signer\SignerInterface;
/**
* This class is the base of all HMAC Signers.
*/
abstract class HMAC implements SignerInterface
{
/**
* {@inheritdoc}
*/
public function sign($input, $key)
{
return hash_hmac($this->getHashingAlgorithm(), $input, (string) $key, true);
}
/**
* To prevent timing attacks we are using PHP 5.6 native function hash_equals,
* in case of PHP < 5.6 a timing safe equals comparison function.
*
* more info here:
* http://blog.ircmaxell.com/2014/11/its-all-about-time.html
*
*
* {@inheritdoc}
*/
public function verify($key, $signature, $input)
{
$signedInput = $this->sign($input, $key);
return $this->timingSafeEquals($signedInput, $signature);
}
/**
* A timing safe equals comparison.
*
* @param string $signature the internal signature to be checked
* @param string $signedInput The signed input submitted value
*
* @return bool true if the two strings are identical.
*/
public function timingSafeEquals($known, $input)
{
return hash_equals($known, $input);
}
/**
* Returns the hashing algorithm used in this signer.
*
* @return string
*/
abstract public function getHashingAlgorithm();
}
@@ -0,0 +1,14 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* HMAC Signer using SHA-256.
*/
class HS256 extends HMAC
{
public function getHashingAlgorithm()
{
return 'sha256';
}
}
@@ -0,0 +1,14 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* HMAC Signer using SHA-384.
*/
class HS384 extends HMAC
{
public function getHashingAlgorithm()
{
return 'sha384';
}
}
@@ -0,0 +1,14 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* HMAC Signer using SHA-512.
*/
class HS512 extends HMAC
{
public function getHashingAlgorithm()
{
return 'sha512';
}
}
@@ -0,0 +1,27 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
use Namshi\JOSE\Signer\SignerInterface;
/**
* None Signer.
*/
class None implements SignerInterface
{
/**
* {@inheritdoc}
*/
public function sign($input, $key)
{
return '';
}
/**
* {@inheritdoc}
*/
public function verify($key, $signature, $input)
{
return $signature === '';
}
}
@@ -0,0 +1,98 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
use InvalidArgumentException;
use Namshi\JOSE\Signer\SignerInterface;
use RuntimeException;
/**
* Class responsible to sign inputs with the a public key algorithm, after hashing it.
*/
abstract class PublicKey implements SignerInterface
{
/**
* {@inheritdoc}
*/
public function sign($input, $key, $password = null)
{
$keyResource = $this->getKeyResource($key, $password);
if (!$this->supportsKey($keyResource)) {
throw new InvalidArgumentException('Invalid key supplied.');
}
$signature = null;
openssl_sign($input, $signature, $keyResource, $this->getHashingAlgorithm());
return $signature;
}
/**
* {@inheritdoc}
*/
public function verify($key, $signature, $input)
{
$keyResource = $this->getKeyResource($key);
if (!$this->supportsKey($keyResource)) {
throw new InvalidArgumentException('Invalid key supplied.');
}
$result = openssl_verify($input, $signature, $keyResource, $this->getHashingAlgorithm());
if ($result === -1) {
throw new RuntimeException('Unknown error during verification.');
}
return (bool) $result;
}
/**
* Converts a string representation of a key into an OpenSSL resource.
*
* @param string|resource $key
* @param string $password
*
* @return resource OpenSSL key resource
*/
protected function getKeyResource($key, $password = null)
{
if (is_resource($key)) {
return $key;
}
$resource = openssl_pkey_get_public($key) ?: openssl_pkey_get_private($key, $password);
if ($resource === false) {
throw new RuntimeException('Could not read key resource: ' . openssl_error_string());
}
return $resource;
}
/**
* Check if the key is supported by this signer.
*
* @param resource $key Public or private key
*
* @return bool
*/
protected function supportsKey($key)
{
// OpenSSL 0.9.8+
$keyDetails = openssl_pkey_get_details($key);
return isset($keyDetails['type']) ? $this->getSupportedPrivateKeyType() === $keyDetails['type'] : false;
}
/**
* Returns the hashing algorithm used in this signer.
*
* @return string
*/
abstract protected function getHashingAlgorithm();
/**
* Returns the private key type supported in this signer.
*
* @return string
*/
abstract protected function getSupportedPrivateKeyType();
}
@@ -0,0 +1,14 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* Class responsible to sign inputs with the RSA algorithm, after hashing it.
*/
class RS256 extends RSA
{
public function getHashingAlgorithm()
{
return version_compare(phpversion(), '5.4.8', '<') ? 'SHA256' : OPENSSL_ALGO_SHA256;
}
}
@@ -0,0 +1,14 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* Class responsible to sign inputs with the RSA algorithm, after hashing it.
*/
class RS384 extends RSA
{
public function getHashingAlgorithm()
{
return version_compare(phpversion(), '5.4.8', '<') ? 'SHA384' : OPENSSL_ALGO_SHA384;
}
}
@@ -0,0 +1,14 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* Class responsible to sign inputs with the RSA algorithm, after hashing it.
*/
class RS512 extends RSA
{
public function getHashingAlgorithm()
{
return version_compare(phpversion(), '5.4.8', '<') ? 'SHA512' : OPENSSL_ALGO_SHA512;
}
}
@@ -0,0 +1,17 @@
<?php
namespace Namshi\JOSE\Signer\OpenSSL;
/**
* Class responsible to sign inputs with the a RSA algorithm, after hashing it.
*/
abstract class RSA extends PublicKey
{
/**
* {@inheritdoc}
*/
protected function getSupportedPrivateKeyType()
{
return defined('OPENSSL_KEYTYPE_RSA') ? OPENSSL_KEYTYPE_RSA : false;
}
}
@@ -0,0 +1,39 @@
<?php
namespace Namshi\JOSE\Signer\SecLib;
use InvalidArgumentException;
use Namshi\JOSE\Signer\SignerInterface;
abstract class PublicKey implements SignerInterface
{
protected $encryptionAlgorithm;
/**
* {@inheritdoc}
*/
public function sign($input, $key, $password = null)
{
if ($password) {
$this->encryptionAlgorithm->setPassword($password);
}
if (!$this->encryptionAlgorithm->loadKey($key)) {
throw new InvalidArgumentException('Invalid key supplied.');
}
return $this->encryptionAlgorithm->sign($input);
}
/**
* {@inheritdoc}
*/
public function verify($key, $signature, $input)
{
if (!$this->encryptionAlgorithm->loadKey($key)) {
throw new InvalidArgumentException('Invalid key supplied.');
}
return $this->encryptionAlgorithm->verify($input, $signature);
}
}
@@ -0,0 +1,13 @@
<?php
namespace Namshi\JOSE\Signer\SecLib;
class RS256 extends RSA
{
public function __construct()
{
parent::__construct();
$this->encryptionAlgorithm->setHash('sha256');
$this->encryptionAlgorithm->setMGFHash('sha256');
}
}
@@ -0,0 +1,13 @@
<?php
namespace Namshi\JOSE\Signer\SecLib;
class RS384 extends RSA
{
public function __construct()
{
parent::__construct();
$this->encryptionAlgorithm->setHash('sha384');
$this->encryptionAlgorithm->setMGFHash('sha384');
}
}
@@ -0,0 +1,13 @@
<?php
namespace Namshi\JOSE\Signer\SecLib;
class RS512 extends RSA
{
public function __construct()
{
parent::__construct();
$this->encryptionAlgorithm->setHash('sha512');
$this->encryptionAlgorithm->setMGFHash('sha512');
}
}
@@ -0,0 +1,13 @@
<?php
namespace Namshi\JOSE\Signer\SecLib;
use phpseclib\Crypt\RSA as CryptRSA;
class RSA extends PublicKey
{
public function __construct()
{
$this->encryptionAlgorithm = new CryptRSA();
}
}
@@ -0,0 +1,28 @@
<?php
namespace Namshi\JOSE\Signer;
interface SignerInterface
{
/**
* Signs the $input with the $key, after hashing it.
*
* @param string $input
* @param resource|string $key
*
* @return string|null
*/
public function sign($input, $key);
/**
* Verifies that the input correspond to the $signature decrypted with the
* given public $key.
*
* @param resource|string $key
* @param string $signature
* @param string $input
*
* @return bool
*/
public function verify($key, $signature, $input);
}
+82
View File
@@ -0,0 +1,82 @@
<?php
namespace Namshi\JOSE;
/**
* Class providing an easy to use JWS implementation.
*/
class SimpleJWS extends JWS
{
/**
* Constructor.
*
* @param array $header An associative array of headers. The value can be any type accepted by json_encode or a JSON serializable object
*
* @see http://php.net/manual/en/function.json-encode.php
* @see http://php.net/manual/en/jsonserializable.jsonserialize.php
* @see https://tools.ietf.org/html/draft-ietf-jose-json-web-signature-41#section-4
* }
*/
public function __construct($header = array(), $encryptionEngine = 'OpenSSL')
{
if (!isset($header['typ'])) {
$header['typ'] = 'JWS';
}
parent::__construct($header, $encryptionEngine);
}
/**
* Sets the payload of the current JWS with an issued at value in the 'iat' property.
*
* @param array $payload
*
* @return $this
*/
public function setPayload(array $payload)
{
if (!isset($payload['iat'])) {
$payload['iat'] = time();
}
return parent::setPayload($payload);
}
/**
* Checks that the JWS has been signed with a valid private key by verifying it with a public $key
* and the token is not expired.
*
* @param resource|string $key
* @param string $algo The algorithms this JWS should be signed with. Use it if you want to restrict which algorithms you want to allow to be validated.
*
* @return bool
*/
public function isValid($key, $algo = null)
{
return $this->verify($key, $algo) && !$this->isExpired();
}
/**
* Checks whether the token is expired based on the 'exp' value.
*it.
*
* @return bool
*/
public function isExpired()
{
$payload = $this->getPayload();
if (isset($payload['exp'])) {
$now = new \DateTime('now');
if (is_int($payload['exp'])) {
return ($now->getTimestamp() - $payload['exp']) > 0;
}
if (is_numeric($payload['exp'])) {
return ($now->format('U') - $payload['exp']) > 0;
}
}
return false;
}
}