Skip to content

JWEBuilder::withSenderKey() cannot be used with direct key agreement (ECDH-SS) #680

Description

@Spomky

Version(s) affected

4.2.0 (and every previous 4.x)

Description

JWEBuilder::withSenderKey() cannot be used with a direct key agreement algorithm such as ECDH-SS.
Both possible call orders fail:

  1. withSenderKey() before addRecipient()InvalidArgumentException: Invalid content encryption algorithm,
    because withSenderKey() calls checkKey(), which requires $contentEncryptionAlgorithm, and that
    property is only populated by addRecipient().
  2. withSenderKey() after addRecipient()InvalidArgumentException: Foreign key management mode forbidden.,
    because withSenderKey() re-derives the key management mode and
    areKeyManagementModesCompatible('agree', 'agree') returns false.

On top of that, even if the mode check were bypassed, the sender key would still be dropped:
determineCEK() reads the sender key from a recipient entry that is never written anywhere:

ECDH-SS therefore always ends up in ECDHSS::getAgreementKey() with $senderKey === null, which
throws LogicException: The sender key shall be set.

ECDH-SS+A128KW / +A192KW / +A256KW are unaffected: they go through processRecipient(), whose
?? $this->senderKey fallback recovers the value.

How to reproduce

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Encryption\Algorithm\ContentEncryption\A128CBCHS256;
use Jose\Component\Encryption\Algorithm\KeyEncryption\ECDHSS;
use Jose\Component\Encryption\JWEBuilder;
use Jose\Component\KeyManagement\JWKFactory;

$recipient = JWKFactory::createECKey('P-256');
$sender = JWKFactory::createECKey('P-256');
$builder = new JWEBuilder(new AlgorithmManager([new ECDHSS(), new A128CBCHS256()]));

// (a) sender key first
$builder->withPayload('hello')
    ->withSharedProtectedHeader(['alg' => 'ECDH-SS', 'enc' => 'A128CBC-HS256'])
    ->withSenderKey($sender)
    ->addRecipient($recipient)
    ->build();
// InvalidArgumentException: Invalid content encryption algorithm

// (b) recipient first
$builder->withPayload('hello')
    ->withSharedProtectedHeader(['alg' => 'ECDH-SS', 'enc' => 'A128CBC-HS256'])
    ->addRecipient($recipient)
    ->withSenderKey($sender)
    ->build();
// InvalidArgumentException: Foreign key management mode forbidden.

Possible Solution

  • Do not re-derive/re-check the key management mode in withSenderKey(): the sender key does not add a
    recipient, so it must not go through the compatibility matrix. Defer the checkKey() call to
    build(), where the content encryption algorithm is known whatever the call order is.
  • Make determineCEK() fall back to $this->senderKey like processRecipient() does, or remove the
    sender_key recipient entry entirely if no per-recipient sender key is intended.

Additional Context

withSenderKey() currently has no test coverage at all (grep -r withSenderKey tests/ returns
nothing), and no test exercises ECDH-SS. Tests for both should come with the fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions