Skip to content

Commit

Permalink
KeyRings: Restrict version/purpose for all keys
Browse files Browse the repository at this point in the history
This is a defense-in-depth that will catch accidental misuse before tokens are processed.
  • Loading branch information
paragonie-security committed Sep 7, 2021
1 parent ef203a0 commit 8d36811
Show file tree
Hide file tree
Showing 5 changed files with 346 additions and 19 deletions.
3 changes: 3 additions & 0 deletions src/Exception/ExceptionCode.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ abstract class ExceptionCode
const IMPOSSIBLE_CONDITION = 0x3142EE1B;
const INVOKED_RAW_ON_MULTIKEY = 0x3142EE1C;
const KEY_NOT_IN_KEYRING = 0x3142EE1D;
const UNDEFINED_PROPERTY = 0x3142EE1E;

/**
* @param int $code
Expand Down Expand Up @@ -89,6 +90,8 @@ public static function explainErrorCode(int $code): string
"and pass that instead.";
case self::KEY_NOT_IN_KEYRING:
return "The key you requested is not in this keyring.";
case self::UNDEFINED_PROPERTY:
return "An expected property was not defined at runtime.";

default:
return 'Unknown error code';
Expand Down
10 changes: 0 additions & 10 deletions src/Purpose.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,11 +228,6 @@ public static function isValid(string $rawString): bool
*/
public function isSendingKeyValid(SendingKey $key): bool
{
/*
if ($key instanceof SendingKeyRing) {
return true;
}
*/
$expectedKeyType = $this->expectedSendingKeyType();
return $key instanceof $expectedKeyType;
}
Expand All @@ -246,11 +241,6 @@ public function isSendingKeyValid(SendingKey $key): bool
*/
public function isReceivingKeyValid(ReceivingKey $key): bool
{
/*
if ($key instanceof ReceivingKeyRing) {
return true;
}
*/
$expectedKeyType = $this->expectedReceivingKeyType();
return $key instanceof $expectedKeyType;
}
Expand Down
3 changes: 2 additions & 1 deletion src/SendingKeyRing.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
declare(strict_types=1);
namespace ParagonIE\Paseto;

use Exception;
use ParagonIE\Paseto\Exception\InvalidKeyException;
use ParagonIE\Paseto\Exception\PasetoException;
use ParagonIE\Paseto\Keys\AsymmetricSecretKey;
Expand All @@ -17,7 +18,6 @@ class SendingKeyRing implements KeyRingInterface, SendingKey
/** @var array<string, SendingKey> */
protected $keys = [];


/**
* Add a key to this KeyID.
*
Expand All @@ -43,6 +43,7 @@ public function addKey(string $keyId, SendingKey $key): self
*
* @throws InvalidKeyException
* @throws PasetoException
* @throws Exception
*/
public function deriveReceivingKeyRing(): ReceivingKeyRing
{
Expand Down
71 changes: 68 additions & 3 deletions src/Traits/MultiKeyTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,48 @@
use ParagonIE\Paseto\Exception\PasetoException;
use ParagonIE\Paseto\KeyInterface;
use ParagonIE\Paseto\ProtocolInterface;
use ParagonIE\Paseto\Purpose;
use ParagonIE\Paseto\ReceivingKey;
use ParagonIE\Paseto\SendingKey;
use TypeError;
use function is_null;

/**
* @var array<string, KeyInterface> $keys
*/
trait MultiKeyTrait
{
/** @var ProtocolInterface $version */
/** @var ?Purpose $purpose */
protected $purpose = null;

/** @var ?ProtocolInterface $version */
protected $version = null;

/**
* The intended version for this protocol. Currently only meaningful
* in asymmetric cryptography.
* The intended version for this key.
*
* @return ProtocolInterface
*/
public function getProtocol(): ProtocolInterface
{
if (is_null($this->version)) {
throw new TypeError(
"Version must not be NULL.",
ExceptionCode::UNDEFINED_PROPERTY
);
}
return $this->version;
}

/**
* The intended purpose for this key.
* @return ?Purpose
*/
public function getPurpose(): ?Purpose
{
return $this->purpose;
}

/**
* Throw an exception if the key is wrong.
*
Expand All @@ -46,12 +68,45 @@ protected function typeCheckKey(KeyInterface $key): void
ExceptionCode::IMPOSSIBLE_CONDITION
);
}

if (!$key instanceof $type) {
throw new InvalidKeyException(
"The provided key is the wrong type",
ExceptionCode::PASETO_KEY_TYPE_ERROR
);
}

if (!is_null($this->version)) {
if (!($key->getProtocol() instanceof $this->version)) {
throw new InvalidKeyException(
"The provided key is for the wrong version",
ExceptionCode::WRONG_KEY_FOR_VERSION
);
}
}

if (!is_null($this->purpose)) {
$valid = false;
try {
if ($key instanceof ReceivingKey) {
$valid = $this->purpose->isReceivingKeyValid($key);
} elseif ($key instanceof SendingKey) {
$valid = $this->purpose->isSendingKeyValid($key);
}
} catch (TypeError $ex) {
throw new InvalidKeyException(
"The provided key is not appropriate for the expected purpose.",
ExceptionCode::PURPOSE_WRONG_FOR_KEY,
$ex
);
}
if (!$valid) {
throw new InvalidKeyException(
"The provided key is not appropriate for the expected purpose.",
ExceptionCode::PURPOSE_WRONG_FOR_KEY
);
}
}
}

/**
Expand Down Expand Up @@ -91,6 +146,16 @@ public function raw(): string
);
}

/**
* @param Purpose $purpose
* @return static
*/
public function setPurpose(Purpose $purpose): self
{
$this->purpose = $purpose;
return $this;
}

/**
* @param ProtocolInterface $version
* @return static
Expand Down
Loading

0 comments on commit 8d36811

Please sign in to comment.