lcobucci / jwt

A simple library to work with JSON Web Token and JSON Web Signature
https://lcobucci-jwt.readthedocs.io/en/stable/
BSD 3-Clause "New" or "Revised" License
7.29k stars 600 forks source link

"Key cannot be empty" error #991

Closed silverl closed 1 year ago

silverl commented 1 year ago

Hi,

I'm trying to enable the OAuth MediaWiki extension, which pulls in lcobucci/jwt. We're using MediaWiki v1_35.

The version of lcobucci/jwt in use is 4.2.1. The version here is not in my control as the extension pulls it in via composer.json.

I do see that the latest release is 4.3.0.

When using Postman to attempt an OAuth2 authorization code flow, I'm getting this error back: "Error: server_error, Description: The authorization server encountered an unexpected condition which prevented it from fulfilling the request: Key cannot be empty"

I think this is referring to a potential issue with the public/private key being used, but I can't spot the problem.

I generated the public and private keys using openssl "OpenSSL 1.1.1f 31 Mar 2020".

cd /etc/pki/oauth
openssl genrsa -out private.pem 4096
openssl rsa -pubout -in private.pem -out public.pem

I've also tried with 2048 bit keys.

The folder and keys are accessible by the apache2 process running under user www-data.

$ sudo ls -l /etc/pki/oauth/
total 8
-r--r----- 1 www-data www-data 3243 Jan 19 19:44 private.pem
-r--r--r-- 1 www-data www-data  800 Jan 19 19:44 public.pem

The private key is not password protected. I don't see an option for using a password-protected key in the MediaWiki OAuth extension anyway.

Can you think of any other thing I might check here? What am I missing?

Thanks.

lcobucci commented 1 year ago

I'm really unfamiliar with the library you're referring to.

It seems like the configuration you've provided isn't being passed on to the jwt library, causing the issue.

My suggestion would be double checking logs, stack traces, and documentation to try and find out what's happening.

Also verify that your error_reporting in php.ini, as we trigger deprecation errors which might be (incorrectly) considered as breaking errors and stop the flow of the application.

silverl commented 1 year ago

Thanks, you reminded me of something and I got this stack trace from the failed auth attempt:

023-01-19 21:44:59 ta-mediawiki devmediawiki: [17d87e707f9434b330fcbf0d] /w/rest.php/oauth2/access_token   Lcobucci\JWT\Signer\InvalidKeyProvided from line 34 of /var/www/devmediawiki-1.35.7/w/vendor/lcobucci/jwt/src/Signer/InvalidKeyProvided.php: Key cannot be empty
#0 /var/www/devmediawiki-1.35.7/w/vendor/lcobucci/jwt/src/Signer/Key/InMemory.php(25): Lcobucci\JWT\Signer\InvalidKeyProvided::cannotBeEmpty()
#1 /var/www/devmediawiki-1.35.7/w/vendor/lcobucci/jwt/src/Signer/Key/InMemory.php(44): Lcobucci\JWT\Signer\Key\InMemory->__construct()
#2 /var/www/devmediawiki-1.35.7/w/vendor/league/oauth2-server/src/Entities/Traits/AccessTokenTrait.php(50): Lcobucci\JWT\Signer\Key\InMemory::plainText()
#3 /var/www/devmediawiki-1.35.7/w/vendor/league/oauth2-server/src/Entities/Traits/AccessTokenTrait.php(61): MediaWiki\Extensions\OAuth\Entity\AccessTokenEntity->initJwtConfiguration()
#4 /var/www/devmediawiki-1.35.7/w/vendor/league/oauth2-server/src/Entities/Traits/AccessTokenTrait.php(79): MediaWiki\Extensions\OAuth\Entity\AccessTokenEntity->convertToJWT()
#5 /var/www/devmediawiki-1.35.7/w/vendor/league/oauth2-server/src/ResponseTypes/BearerTokenResponse.php(31): MediaWiki\Extensions\OAuth\Entity\AccessTokenEntity->__toString()
#6 /var/www/devmediawiki-1.35.7/w/vendor/league/oauth2-server/src/AuthorizationServer.php(202): League\OAuth2\Server\ResponseTypes\BearerTokenResponse->generateHttpResponse()
#7 /var/www/devmediawiki-1.35.7/w/extensions/OAuth/src/AuthorizationProvider/AccessToken.php(22): League\OAuth2\Server\AuthorizationServer->respondToAccessTokenRequest()
#8 /var/www/devmediawiki-1.35.7/w/extensions/OAuth/src/Rest/Handler/AccessToken.php(40): MediaWiki\Extensions\OAuth\AuthorizationProvider\AccessToken->getAccessTokens()
#9 /var/www/devmediawiki-1.35.7/w/includes/Rest/Router.php(365): MediaWiki\Extensions\OAuth\Rest\Handler\AccessToken->execute()
#10 /var/www/devmediawiki-1.35.7/w/includes/Rest/Router.php(320): MediaWiki\Rest\Router->executeHandler()
#11 /var/www/devmediawiki-1.35.7/w/includes/Rest/EntryPoint.php(144): MediaWiki\Rest\Router->execute()
#12 /var/www/devmediawiki-1.35.7/w/includes/Rest/EntryPoint.php(111): MediaWiki\Rest\EntryPoint->execute()
#13 /var/www/devmediawiki-1.35.7/w/rest.php(31): MediaWiki\Rest\EntryPoint::main()
#14 {main}
silverl commented 1 year ago

I'm seeing this code in dependency vendor/league/oauth2-server/src/Entities/Traits/AccessTokenTrait.php.

Could that empty in-memory key be to blame? What's the right thing to do here?

    /**
     * Initialise the JWT Configuration.
     */
    public function initJwtConfiguration()
    {
        $this->jwtConfiguration = Configuration::forAsymmetricSigner(
            new Sha256(),
            LocalFileReference::file($this->privateKey->getKeyPath(), $this->privateKey->getPassPhrase() ?? ''),
            InMemory::plainText('')
        );
    }
silverl commented 1 year ago

In the latest version of league/oauth2-server, they've changed it to this:

/**
     * Initialise the JWT Configuration.
     */
    public function initJwtConfiguration()
    {
        $this->jwtConfiguration = Configuration::forAsymmetricSigner(
            new Sha256(),
            InMemory::plainText($this->privateKey->getKeyContents(), $this->privateKey->getPassPhrase() ?? ''),
            InMemory::plainText('empty', 'empty')
        );
    }

This appears to allow authentication to proceed. Can that use of 'empty' for the InMemory be correct though?

ralbear commented 1 year ago

Adding empty works for me as well

lcobucci commented 1 year ago

Can that use of 'empty' for the InMemory be correct though?

It seems like that object is only being used as a dummy to fill in the required information to create the object.

Although it works fine, it is impacted on updates we do to the InMemory implementation. An alternative would be to use an anonymous class to provide that same dummy object and claim responsibility over its evolution.

Closing here since we've pinpointed the root cause 👍