Skip to content
Open
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
15 changes: 5 additions & 10 deletions app/Models/OAuth2/ServerPrivateKey.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use jwk\impl\RSAJWKPEMPrivateKeySpecification;
use OAuth2\Models\IServerPrivateKey;
use DateTime;
use phpseclib\Crypt\RSA;
use phpseclib3\Crypt\PublicKeyLoader;
use Illuminate\Support\Facades\Crypt;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping AS ORM;
Expand Down Expand Up @@ -108,15 +108,10 @@ static function build

public function getPublicKeyPEM()
{
$private_key_pem = $this->pem_content;
$rsa = new RSA();

if(!empty($this->password)){
$rsa->setPassword($this->password);
}

$rsa->loadKey($private_key_pem);
return $rsa->getPublicKey();
// phpseclib 2 getPublicKey() defaulted to PKCS8 ("-----BEGIN PUBLIC KEY-----"); keep it so the output does not change
return PublicKeyLoader::load($this->pem_content, !empty($this->password) ? $this->password : false)
->getPublicKey()
->toString('PKCS8');
}

/**
Expand Down
33 changes: 21 additions & 12 deletions app/Services/OAuth2/HttpIClientJWKSetReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@
use OAuth2\Models\IClient;
use OAuth2\Services\IClientJWKSetReader;
use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Exception\RequestException as HttpRequestException;
use Utils\Http\HttpContentType;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Config;
/**
Expand All @@ -26,6 +25,10 @@
*/
final class HttpIClientJWKSetReader implements IClientJWKSetReader
{
/**
* @see https://www.rfc-editor.org/rfc/rfc7517#section-8.5.1
*/
private const JsonMediaTypes = ['application/json', 'application/jwk-set+json'];

/**
* @param IClient $client
Expand All @@ -40,24 +43,30 @@ public function read(IClient $client)
if (empty($jwk_set_uri)) return null;

$client = new HttpClient([
'defaults' => [
'timeout' => Config::get('curl.timeout', 60),
'allow_redirects' => Config::get('curl.allow_redirects', false),
'verify' => Config::get('curl.verify_ssl_cert', true)
]
'timeout' => Config::get('curl.timeout', 60),
'allow_redirects' => Config::get('curl.allow_redirects', false),
'verify' => Config::get('curl.verify_ssl_cert', true)
]);

$response = $client->get($jwk_set_uri);
if ($response->getStatusCode() !== 200) return null;
$content_type = $response->getHeader('content-type');
if (is_null($content_type)) return null;
if (!$content_type->hasValue(HttpContentType::Json)) return null;
return JWKSet::fromJson($response->getBody());
if (!self::isJson($response->getHeaderLine('content-type'))) return null;
return JWKSet::fromJson((string)$response->getBody());
}
catch (HttpRequestException $ex)
catch (GuzzleException $ex)
{
Log::warning($ex->getMessage());
return null;
}
}

/**
* @param string $content_type
* @return bool
*/
private static function isJson(string $content_type): bool
{
$media_type = strtolower(trim(explode(';', $content_type)[0]));
return in_array($media_type, self::JsonMediaTypes, true);
}
}
22 changes: 5 additions & 17 deletions app/Services/OAuth2/ServerPrivateKeyService.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use Utils\Db\ITransactionService;
use Models\OAuth2\ServerPrivateKey;
use DateTime;
use phpseclib\Crypt\RSA;
use phpseclib3\Crypt\RSA;
use models\exceptions\ValidationException;
/**
* Class ServerPrivateKeyService
Expand All @@ -26,11 +26,6 @@
final class ServerPrivateKeyService extends AsymmetricKeyService implements IServerPrivateKeyService
{

/**
* @var RSA
*/
private $rsa;

/**
* ServerPrivateKeyService constructor.
* @param IServerPrivateKeyRepository $repository
Expand All @@ -43,7 +38,6 @@ public function __construct
)
{
parent::__construct($repository, $tx_service);
$this->rsa = new RSA();
}

/**
Expand Down Expand Up @@ -72,17 +66,11 @@ public function create(array $params):IEntity

if(empty($pem))
{
$rsa_key = RSA::createKey(2048);
if(!empty($password))
$this->rsa->setPassword($password);
/**
* array(
* 'privatekey' => $privatekey,
* 'publickey' => $publickey,
* 'partialkey' => false
* );
*/
$res = $this->rsa->createKey(2048);
$pem = $res['privatekey'];
$rsa_key = $rsa_key->withPassword($password);
// PKCS1 ("-----BEGIN RSA PRIVATE KEY-----"), encrypted when a password is set
$pem = $rsa_key->toString('PKCS1');
}

$key = ServerPrivateKey::build
Expand Down
60 changes: 32 additions & 28 deletions app/Validators/CustomValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
use OAuth2\OAuth2Protocol;
use OAuth2\Models\IClient;
use Utils\Services\IAuthService;
use phpseclib\Crypt\RSA;
use phpseclib3\Crypt\PublicKeyLoader;
use phpseclib3\Crypt\RSA\PrivateKey as RSAPrivateKey;
use phpseclib3\Crypt\RSA\PublicKey as RSAPublicKey;
use phpseclib3\Exception\NoKeyLoadedException;

/**
* Class CustomValidator
Expand All @@ -43,7 +46,7 @@ public function __construct(Translator $translator, $data, $rules, $messages = [
$this->isImplicit('fail');
}

public function validateBoolean($attribute, $value)
public function validateBoolean($attribute, $value, $parameters = [])
{
if (is_bool($value)) {
return true;
Expand Down Expand Up @@ -211,6 +214,24 @@ public function validateEncryptedEnc($attribute, $value)
return in_array($value,OAuth2Protocol::$supported_content_encryption_algorithms);
}

/**
* Loads an RSA key the way phpseclib 2 loadKey() did: returns null instead of throwing.
* @param string|null $pem
* @param string|false $password
* @return RSAPublicKey|RSAPrivateKey|null
*/
private function loadRSAKey(?string $pem, $password = false)
{
if (is_null($pem)) return null;
try {
$key = PublicKeyLoader::load($pem, $password);
} catch (NoKeyLoadedException $ex) {
return null;
}
// only RSA keys are valid here (phpseclib 2 RSA::loadKey() also accepted EC keys)
return ($key instanceof RSAPublicKey || $key instanceof RSAPrivateKey) ? $key : null;
}

public function validatePublicKeyPem($attribute, $value)
{
$res1 = strpos($value,'-----BEGIN PUBLIC KEY-----');
Expand All @@ -221,18 +242,14 @@ public function validatePublicKeyPem($attribute, $value)
$PKCS8 = $res1 !== false && $res3 !== false;
$PKCS1 = $res2 !== false && $res4 !== false;

$rsa = new RSA;
$parsed = $rsa->loadKey($value);

return ($PKCS8 || $PKCS1) && $parsed;
return ($PKCS8 || $PKCS1) && !is_null($this->loadRSAKey($value));
}

public function validatePublicKeyPemLength($attribute, $value)
{
$rsa = new RSA();
$parsed = $rsa->loadKey($value);
$key = $this->loadRSAKey($value);

return $parsed && $rsa->getSize() > 1024;
return !is_null($key) && $key->getLength() > 1024;
}

public function validatePrivateKeyPem($attribute, $value, $parameters)
Expand All @@ -247,29 +264,20 @@ public function validatePrivateKeyPem($attribute, $value, $parameters)

$encrypted = strpos($value,'ENCRYPTED') !== false ;
$password_param = $parameters[0];
$rsa = new RSA;
if(isset($this->data[$password_param]) && $encrypted){
$rsa->setPassword($this->data[$password_param]);
}

$parsed = $rsa->loadKey($value);
$password = (isset($this->data[$password_param]) && $encrypted) ? $this->data[$password_param] : false;

return ($PKCS8 || $PKCS1) && $parsed;
return ($PKCS8 || $PKCS1) && !is_null($this->loadRSAKey($value, $password));
}

public function validatePrivateKeyPemLength($attribute, $value, $parameters)
{

$encrypted = strpos($value,'ENCRYPTED') !== false ;
$password_param = $parameters[0];
$rsa = new RSA;
if(isset($this->data[$password_param]) && $encrypted){
$rsa->setPassword($this->data[$password_param]);
}

$parsed = $rsa->loadKey($value);
$password = (isset($this->data[$password_param]) && $encrypted) ? $this->data[$password_param] : false;
$key = $this->loadRSAKey($value, $password);

return $parsed && $rsa->getSize() >= 2048;
return !is_null($key) && $key->getLength() >= 2048;
}

public function validatePublicKeyUsage($attribute, $value, $parameters)
Expand All @@ -285,11 +293,7 @@ public function validatePublicKeyType($attribute, $value, $parameters)
public function validatePrivateKeyPassword($attribute, $value, $parameters){
$pem_param = $parameters[0];
if(!isset($this->data[$pem_param])) return true;
$pem_content = $this->data[$pem_param];
$rsa = new RSA;
$rsa->setPassword($value);
$parsed = $rsa->loadKey($pem_content);
return $parsed;
return !is_null($this->loadRSAKey($this->data[$pem_param], $value));
}

public function validateCustomUrlSet($attribute, $value, $parameters)
Expand Down
2 changes: 1 addition & 1 deletion app/libs/OAuth2/GrantTypes/InteractiveGrantType.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
use utils\json_types\NumericDate;
use Utils\Services\IAuthService;
use Utils\Services\ILogService;
use phpseclib\Crypt\Random;
use phpseclib3\Crypt\Random;

/**
* Class InteractiveGrantType
Expand Down
6 changes: 3 additions & 3 deletions app/libs/OAuth2/Heuristics/ClientEncryptionKeyFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ public function find(IClient $client, ICryptoAlgorithm $alg, ?string $kid_hint =
if(!is_null($kid_hint))
{
$recipient_key = $client->getPublicKeyByIdentifier($kid_hint);
if(!$recipient_key->isActive()) $recipient_key = null;
if($recipient_key->getAlg()->getName() !== $alg->getName()) $recipient_key = null;
if(!is_null($recipient_key) && !$recipient_key->isActive()) $recipient_key = null;
if(!is_null($recipient_key) && $recipient_key->getAlg()->getName() !== $alg->getName()) $recipient_key = null;
}

if(is_null($recipient_key))
Expand Down Expand Up @@ -118,7 +118,7 @@ public function find(IClient $client, ICryptoAlgorithm $alg, ?string $kid_hint =
{
if
(
$jwk->getKeyUse() === JSONWebKeyPublicKeyUseValues::Encryption &&
$jwk->getKeyUse()->getString() === JSONWebKeyPublicKeyUseValues::Encryption &&
$jwk->getAlgorithm()->getString() === $alg->getName()
)
{
Expand Down
6 changes: 3 additions & 3 deletions app/libs/OAuth2/Heuristics/ClientSigningKeyFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ public function find(IClient $client, ICryptoAlgorithm $alg, ?string $kid_hint =
if(!is_null($kid_hint))
{
$recipient_key = $client->getPublicKeyByIdentifier($kid_hint);
if(!$recipient_key->isActive()) $recipient_key = null;
if($recipient_key->getAlg()->getName() !== $alg->getName()) $recipient_key = null;
if(!is_null($recipient_key) && !$recipient_key->isActive()) $recipient_key = null;
if(!is_null($recipient_key) && $recipient_key->getAlg()->getName() !== $alg->getName()) $recipient_key = null;
}

if(is_null($recipient_key))
Expand Down Expand Up @@ -110,7 +110,7 @@ public function find(IClient $client, ICryptoAlgorithm $alg, ?string $kid_hint =
{
if
(
$jwk->getKeyUse() === JSONWebKeyPublicKeyUseValues::Signature &&
$jwk->getKeyUse()->getString() === JSONWebKeyPublicKeyUseValues::Signature &&
$jwk->getAlgorithm()->getString() === $alg->getName()
)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ protected function getKey(ClientAuthenticationContext $context, JsonValue $kid =
if(!is_null($kid))
{
$key = $client->getPublicKeyByIdentifier($kid->getValue());
if($key->isActive() && !$key->isExpired()) return $key->toJWK();
if(!is_null($key) && $key->isActive() && !$key->isExpired()) return $key->toJWK();
}
$alg = $jws->getJOSEHeader()->getAlgorithm()->getString();

Expand All @@ -84,6 +84,8 @@ protected function getKey(ClientAuthenticationContext $context, JsonValue $kid =

$jwk_set = $this->jwks_reader->read($client);

if(is_null($jwk_set)) return null;

if(!is_null($kid))
{
$key = $jwk_set->getKeyById($kid->getValue());
Expand All @@ -94,7 +96,7 @@ protected function getKey(ClientAuthenticationContext $context, JsonValue $kid =
{
if
(
$key->getKeyUse() === JSONWebKeyPublicKeyUseValues::Signature &&
$key->getKeyUse()->getString() === JSONWebKeyPublicKeyUseValues::Signature &&
$key->getAlgorithm()->getString() === $alg
)
{
Expand Down
18 changes: 9 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@
"behat/transliterator": "1.5.0",
"darkaonline/l5-swagger": "^9.0",
"ezyang/htmlpurifier": "v4.17.0",
"firebase/php-jwt": "6.11.1",
"firebase/php-jwt": "v7.2.1",
"get-stream/stream-chat": "^3.10.0",
"glenscott/url-normalizer": "1.4.0",
"guzzlehttp/guzzle": "7.9.3",
"guzzlehttp/guzzle": "7.16.1",
"guzzlehttp/uri-template": "^1.0",
"ircmaxell/random-lib": "1.2.0",
"jenssegers/agent": "2.6.3",
Expand All @@ -33,18 +33,18 @@
"laravel-doctrine/extensions": "2.0.1",
"laravel-doctrine/migrations": "3.4.0",
"laravel-doctrine/orm": "3.1.1",
"laravel/framework": "12.0",
"laravel/framework": "12.70.2",
"laravel/helpers": "^1.7.0",
"laravel/socialite": "^5.21.0",
"laravel/tinker": "2.10.1",
"league/flysystem": "3.25.1",
"league/flysystem-aws-s3-v3": "3.8.0",
"laravel/tinker": "v2.11.1",
"league/flysystem": "3.36.0",
"league/flysystem-aws-s3-v3": "3.35.3",
"php-opencloud/openstack": "3.10.0",
"phpseclib/phpseclib": "^3.0.43",
"phpseclib/phpseclib": "^3.0.57",
"predis/predis": "v2.2.2",
"ryangjchandler/laravel-cloudflare-turnstile": "~3.0.3",
"s-ichikawa/laravel-sendgrid-driver": "^4.0",
"smarcet/jose4php": "2.0.0",
"smarcet/jose4php": "3.0.0-beta.2",
"socialiteproviders/apple": "^5.6.1",
"socialiteproviders/auth0": "^4.2",
"socialiteproviders/facebook": "^4.1.0",
Expand All @@ -54,7 +54,7 @@
"socialiteproviders/okta": "^4.5.0",
"socialiteproviders/zoho": "^4.1",
"sokil/php-isocodes": "^3.0",
"vladimir-yuldashev/laravel-queue-rabbitmq": "v14.2.0",
"vladimir-yuldashev/laravel-queue-rabbitmq": "v14.5.3",
"zircote/swagger-php": "^5.8"
},
"require-dev": {
Expand Down
Loading
Loading