CVE-2021-41106 (published 2021-09-28T21:15Z)

JWT is a library to work with JSON Web Token and JSON Web Signature. Prior to versions 3.4.6, 4.0.4, and 4.1.5, users of HMAC-based algorithms (HS256, HS384, and HS512) combined with `Lcobucci\\JWT\\Signer\\Key\\LocalFileReference` as key are having their tokens issued/validated using the file path as hashing key - instead of the contents. The HMAC hashing functions take any string as input and, since users can issue and validate tokens, users are lead to believe that everything works properly. Versions 3.4.6, 4.0.4, and 4.1.5 have been patched to always load the file contents, deprecated the `Lcobucci\\JWT\\Signer\\Key\\LocalFileReference`, and suggest `Lcobucci\\JWT\\Signer\\Key\\InMemory` as the alternative. As a workaround, use `Lcobucci\\JWT\\Signer\\Key\\InMemory` instead of `Lcobucci\\JWT\\Signer\\Key\\LocalFileReference` to create the instances of one's keys.

Severity: LOW
CVSS v2 base score: 2.1 (AV:L/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 3.3, LOW (CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N)

Weakness information:
cwe_id: CWE-345
cwe_name: Insufficient Verification of Data Authenticity
description: The product does not sufficiently verify the origin or authenticity of data, in a way that causes it to accept invalid data.
extended_description: 
            
         
         

Fix information:
repo_url: https://github.com/lcobucci/jwt
commit_details: repo_url: https://github.com/lcobucci/jwt
author: Luís Cobucci
author_date: 2021-09-27 15:36:39+02:00
msg: Ensure key contents is used for all hashing algorithms

On v3.4.0 we introduced the
`Lcobucci\JWT\Signer\Key\LocalFileReference`, which was designed to be
used with OpenSSL-based algorithms and avoid having to load the file
contents into user-land to sign/verify tokens.

However, other algorithms don't understand the scheme `file://` and will
incorrectly use the file path as the signing key.

This modifies and deprecates `LocalFileReference` to avoid that
misleading behaviour.

Co-authored-by: Anton Smirnov <sandfox@sandfox.me>
Co-authored-by: Marco Pivetta <ocramius@gmail.com>
repository: repo_url: https://github.com/lcobucci/jwt
repo_name: lcobucci/jwt
description: A simple library to work with JSON Web Token and JSON Web Signature
repo_language: PHP
owner: lcobucci
file_changes: filename: configuration.md
change_type: ModificationType.MODIFY
diff: @@ -41,10 +41,8 @@ Or provide a file path:
 
 ```php
 use Lcobucci\JWT\Signer\Key\InMemory;
-use Lcobucci\JWT\Signer\Key\LocalFileReference;
 
 $key = InMemory::file(__DIR__ . '/path-to-my-key-stored-in-a-file.pem'); // this reads the file and keeps its contents in memory
-$key = LocalFileReference::file(__DIR__ . '/path-to-my-key-stored-in-a-file.pem'); // this just keeps a reference to file
 ```
 
 #### For symmetric algorithms
@@ -78,13 +76,12 @@ This means that it's fine to distribute your **public key**. However, the **priv
 ```php
 use Lcobucci\JWT\Configuration;
 use Lcobucci\JWT\Signer;
-use Lcobucci\JWT\Signer\Key\LocalFileReference;
 use Lcobucci\JWT\Signer\Key\InMemory;
 
 $configuration = Configuration::forAsymmetricSigner(
     // You may use RSA or ECDSA and all their variations (256, 384, and 512)
     new Signer\Rsa\Sha256(),
-    LocalFileReference::file(__DIR__ . '/my-private-key.pem'),
+    InMemory::file(__DIR__ . '/my-private-key.pem'),
     InMemory::base64Encoded('mBC5v1sOKVvbdEitdSBenu59nfNfhwkedkJVNabosTw=')
     // You may also override the JOSE encoder/decoder if needed by providing extra arguments here
 );

programming_language: Markdown
filename: upgrading.md
change_type: ModificationType.MODIFY
diff: @@ -96,9 +96,8 @@ You can find more information on how to use the configuration object, [here](con
 ### Use new `Key` objects
 
 `Lcobucci\JWT\Signer\Key` has been converted to an interface in `v4.0`.
-We provide two new implementations: `Lcobucci\JWT\Signer\Key\InMemory` and `Lcobucci\JWT\Signer\Key\LocalFileReference`.
 
-`Lcobucci\JWT\Signer\Key\InMemory` is a drop-in replacement of the behaviour for `Lcobucci\JWT\Signer\Key` in `v3.x`.
+We provide `Lcobucci\JWT\Signer\Key\InMemory`, a drop-in replacement of the behaviour for `Lcobucci\JWT\Signer\Key` in `v3.x`.
 You will need to pick the appropriated named constructor to migrate your code:
 
 ```diff

programming_language: Markdown
filename: phpstan.neon.dist
change_type: ModificationType.MODIFY
diff: @@ -6,3 +6,4 @@ parameters:
 
     ignoreErrors:
         - '#.*OpenSSLAsymmetricKey.*#'
+        - '#Call to method .* of deprecated class Lcobucci\\JWT\\Signer\\Key\\LocalFileReference#'

programming_language: Markdown
filename: LocalFileReference.php
change_type: ModificationType.MODIFY
diff: @@ -9,12 +9,14 @@ use function file_exists;
 use function strpos;
 use function substr;
 
+/** @deprecated please use {@see InMemory} instead */
 final class LocalFileReference implements Key
 {
     private const PATH_PREFIX = 'file://';
 
     private string $path;
     private string $passphrase;
+    private string $contents;
 
     private function __construct(string $path, string $passphrase)
     {
@@ -38,7 +40,11 @@ final class LocalFileReference implements Key
 
     public function contents(): string
     {
-        return self::PATH_PREFIX . $this->path;
+        if (! isset($this->contents)) {
+            $this->contents = InMemory::file($this->path)->contents();
+        }
+
+        return $this->contents;
     }
 
     public function passphrase(): string

programming_language: PHP
filename: HmacTokenTest.php
change_type: ModificationType.MODIFY
diff: @@ -7,12 +7,17 @@ use Lcobucci\JWT\Configuration;
 use Lcobucci\JWT\Signer\Hmac\Sha256;
 use Lcobucci\JWT\Signer\Hmac\Sha512;
 use Lcobucci\JWT\Signer\Key\InMemory;
+use Lcobucci\JWT\Signer\Key\LocalFileReference;
 use Lcobucci\JWT\Token;
 use Lcobucci\JWT\Validation\Constraint\SignedWith;
 use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
 use PHPUnit\Framework\TestCase;
 
 use function assert;
+use function file_put_contents;
+use function is_string;
+use function sys_get_temp_dir;
+use function tempnam;
 
 /**
  * @covers \Lcobucci\JWT\Configuration
@@ -131,4 +136,39 @@ class HmacTokenTest extends TestCase
         self::assertTrue($this->config->validator()->validate($token, $constraint));
         self::assertEquals('world', $token->claims()->get('hello'));
     }
+
+    /** @test */
+    public function signatureValidationWithLocalFileKeyReferenceWillOperateWithKeyContents(): void
+    {
+        $key = tempnam(sys_get_temp_dir(), 'key');
+        assert(is_string($key));
+
+        file_put_contents($key, 'just a dummy key');
+
+        $validKey      = LocalFileReference::file($key);
+        $invalidKey    = InMemory::plainText('file://' . $key);
+        $signer        = new Sha256();
+        $configuration = Configuration::forSymmetricSigner($signer, $validKey);
+        $validator     = $configuration->validator();
+
+        $token = $configuration->builder()
+            ->withClaim('foo', 'bar')
+            ->getToken($configuration->signer(), $configuration->signingKey());
+
+        self::assertFalse(
+            $validator->validate(
+                $token,
+                new SignedWith($signer, $invalidKey)
+            ),
+            'Token cannot be validated against the **path** of the key'
+        );
+
+        self::assertTrue(
+            $validator->validate(
+                $token,
+                new SignedWith($signer, $validKey)
+            ),
+            'Token can be validated against the **contents** of the key'
+        );
+    }
 }

programming_language: PHP
filename: EcdsaTest.php
change_type: ModificationType.MODIFY
diff: @@ -56,6 +56,7 @@ final class EcdsaTest extends TestCase
      *
      * @uses \Lcobucci\JWT\Signer\Ecdsa::__construct
      * @uses \Lcobucci\JWT\Signer\Key\LocalFileReference
+     * @uses \Lcobucci\JWT\Signer\Key\InMemory
      */
     public function signShouldReturnTheAHashBasedOnTheOpenSslSignature(): void
     {
@@ -88,6 +89,7 @@ final class EcdsaTest extends TestCase
      *
      * @uses \Lcobucci\JWT\Signer\Ecdsa::__construct
      * @uses \Lcobucci\JWT\Signer\Key\LocalFileReference
+     * @uses \Lcobucci\JWT\Signer\Key\InMemory
      */
     public function verifyShouldDelegateToEcdsaSignerUsingPublicKey(): void
     {

programming_language: PHP
filename: LocalFileReferenceTest.php
change_type: ModificationType.MODIFY
diff: @@ -6,7 +6,11 @@ namespace Lcobucci\JWT\Signer\Key;
 use org\bovigo\vfs\vfsStream;
 use PHPUnit\Framework\TestCase;
 
-/** @coversDefaultClass \Lcobucci\JWT\Signer\Key\LocalFileReference */
+/**
+ * @coversDefaultClass \Lcobucci\JWT\Signer\Key\LocalFileReference
+ *
+ * @uses \Lcobucci\JWT\Signer\Key\InMemory
+ */
 final class LocalFileReferenceTest extends TestCase
 {
     /** @before */
@@ -33,20 +37,6 @@ final class LocalFileReferenceTest extends TestCase
         LocalFileReference::file(vfsStream::url('root/test2.pem'));
     }
 
-    /**
-     * @test
-     *
-     * @covers ::file
-     * @covers ::__construct
-     * @covers ::contents
-     */
-    public function pathShouldBeNormalised(): void
-    {
-        $key = LocalFileReference::file('file://' . vfsStream::url('root/test.pem'));
-
-        self::assertSame('file://vfs://root/test.pem', $key->contents());
-    }
-
     /**
      * @test
      *
@@ -55,11 +45,11 @@ final class LocalFileReferenceTest extends TestCase
      * @covers ::contents
      * @covers ::passphrase
      */
-    public function contentsShouldReturnOnlyTheReferenceToTheFile(): void
+    public function pathShouldBeNormalised(): void
     {
-        $key = LocalFileReference::file(vfsStream::url('root/test.pem'), 'test');
+        $key = LocalFileReference::file('file://' . vfsStream::url('root/test.pem'), 'test');
 
-        self::assertSame('file://vfs://root/test.pem', $key->contents());
+        self::assertSame('testing', $key->contents());
         self::assertSame('test', $key->passphrase());
     }
 }

programming_language: PHP
filename: RsaTest.php
change_type: ModificationType.MODIFY
diff: @@ -30,6 +30,7 @@ final class RsaTest extends TestCase
      * @covers \Lcobucci\JWT\Signer\OpenSSL
      *
      * @uses \Lcobucci\JWT\Signer\Key\LocalFileReference
+     * @uses \Lcobucci\JWT\Signer\Key\InMemory
      */
     public function signShouldReturnAValidOpensslSignature(): void
     {
@@ -100,6 +101,7 @@ KEY;
      * @covers \Lcobucci\JWT\Signer\InvalidKeyProvided
      *
      * @uses \Lcobucci\JWT\Signer\Key\LocalFileReference
+     * @uses \Lcobucci\JWT\Signer\Key\InMemory
      */
     public function signShouldRaiseAnExceptionWhenKeyTypeIsNotRsa(): void
     {
@@ -119,6 +121,7 @@ KEY;
      * @covers \Lcobucci\JWT\Signer\OpenSSL
      *
      * @uses \Lcobucci\JWT\Signer\Key\LocalFileReference
+     * @uses \Lcobucci\JWT\Signer\Key\InMemory
      */
     public function verifyShouldReturnTrueWhenSignatureIsValid(): void
     {
@@ -161,6 +164,7 @@ KEY;
      * @covers \Lcobucci\JWT\Signer\InvalidKeyProvided
      *
      * @uses \Lcobucci\JWT\Signer\Key\LocalFileReference
+     * @uses \Lcobucci\JWT\Signer\Key\InMemory
      */
     public function verifyShouldRaiseAnExceptionWhenKeyTypeIsNotRsa(): void
     {

programming_language: PHP