welcome back to dyb-tech
This commit is contained in:
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
use Lcobucci\JWT\Signer;
|
||||
|
||||
use function hash_equals;
|
||||
use function sodium_crypto_generichash;
|
||||
use function strlen;
|
||||
|
||||
final class Blake2b implements Signer
|
||||
{
|
||||
private const MINIMUM_KEY_LENGTH_IN_BITS = 256;
|
||||
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'BLAKE2B';
|
||||
}
|
||||
|
||||
public function sign(string $payload, Key $key): string
|
||||
{
|
||||
$actualKeyLength = 8 * strlen($key->contents());
|
||||
|
||||
if ($actualKeyLength < self::MINIMUM_KEY_LENGTH_IN_BITS) {
|
||||
throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH_IN_BITS, $actualKeyLength);
|
||||
}
|
||||
|
||||
return sodium_crypto_generichash($payload, $key->contents());
|
||||
}
|
||||
|
||||
public function verify(string $expected, string $payload, Key $key): bool
|
||||
{
|
||||
return hash_equals($expected, $this->sign($payload, $key));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Lcobucci\JWT\Exception;
|
||||
|
||||
final class CannotSignPayload extends InvalidArgumentException implements Exception
|
||||
{
|
||||
public static function errorHappened(string $error): self
|
||||
{
|
||||
return new self('There was an error while creating the signature:' . $error);
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
use Lcobucci\JWT\Signer\Ecdsa\MultibyteStringConverter;
|
||||
use Lcobucci\JWT\Signer\Ecdsa\SignatureConverter;
|
||||
|
||||
use const OPENSSL_KEYTYPE_EC;
|
||||
|
||||
abstract class Ecdsa extends OpenSSL
|
||||
{
|
||||
public function __construct(
|
||||
private readonly SignatureConverter $converter = new MultibyteStringConverter(),
|
||||
) {
|
||||
}
|
||||
|
||||
final public function sign(string $payload, Key $key): string
|
||||
{
|
||||
return $this->converter->fromAsn1(
|
||||
$this->createSignature($key->contents(), $key->passphrase(), $payload),
|
||||
$this->pointLength(),
|
||||
);
|
||||
}
|
||||
|
||||
final public function verify(string $expected, string $payload, Key $key): bool
|
||||
{
|
||||
return $this->verifySignature(
|
||||
$this->converter->toAsn1($expected, $this->pointLength()),
|
||||
$payload,
|
||||
$key->contents(),
|
||||
);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void
|
||||
{
|
||||
if ($type !== OPENSSL_KEYTYPE_EC) {
|
||||
throw InvalidKeyProvided::incompatibleKeyType(
|
||||
self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_EC],
|
||||
self::KEY_TYPE_MAP[$type],
|
||||
);
|
||||
}
|
||||
|
||||
$expectedKeyLength = $this->expectedKeyLength();
|
||||
|
||||
if ($lengthInBits !== $expectedKeyLength) {
|
||||
throw InvalidKeyProvided::incompatibleKeyLength($expectedKeyLength, $lengthInBits);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return positive-int
|
||||
*/
|
||||
abstract public function expectedKeyLength(): int;
|
||||
|
||||
/**
|
||||
* Returns the length of each point in the signature, so that we can calculate and verify R and S points properly
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return positive-int
|
||||
*/
|
||||
abstract public function pointLength(): int;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Lcobucci\JWT\Exception;
|
||||
|
||||
final class ConversionFailed extends InvalidArgumentException implements Exception
|
||||
{
|
||||
public static function invalidLength(): self
|
||||
{
|
||||
return new self('Invalid signature length.');
|
||||
}
|
||||
|
||||
public static function incorrectStartSequence(): self
|
||||
{
|
||||
return new self('Invalid data. Should start with a sequence.');
|
||||
}
|
||||
|
||||
public static function integerExpected(): self
|
||||
{
|
||||
return new self('Invalid data. Should contain an integer.');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014-2018 Spomky-Labs
|
||||
*
|
||||
* This software may be modified and distributed under the terms
|
||||
* of the MIT license. See the LICENSE file for details.
|
||||
*
|
||||
* @link https://github.com/web-token/jwt-framework/blob/v1.2/src/Component/Core/Util/ECSignature.php
|
||||
*/
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
use function assert;
|
||||
use function bin2hex;
|
||||
use function dechex;
|
||||
use function hex2bin;
|
||||
use function hexdec;
|
||||
use function is_string;
|
||||
use function str_pad;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
|
||||
use const STR_PAD_LEFT;
|
||||
|
||||
/**
|
||||
* ECDSA signature converter using ext-mbstring
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MultibyteStringConverter implements SignatureConverter
|
||||
{
|
||||
private const ASN1_SEQUENCE = '30';
|
||||
private const ASN1_INTEGER = '02';
|
||||
private const ASN1_MAX_SINGLE_BYTE = 128;
|
||||
private const ASN1_LENGTH_2BYTES = '81';
|
||||
private const ASN1_BIG_INTEGER_LIMIT = '7f';
|
||||
private const ASN1_NEGATIVE_INTEGER = '00';
|
||||
private const BYTE_SIZE = 2;
|
||||
|
||||
public function toAsn1(string $points, int $length): string
|
||||
{
|
||||
$points = bin2hex($points);
|
||||
|
||||
if (self::octetLength($points) !== $length) {
|
||||
throw ConversionFailed::invalidLength();
|
||||
}
|
||||
|
||||
$pointR = self::preparePositiveInteger(substr($points, 0, $length));
|
||||
$pointS = self::preparePositiveInteger(substr($points, $length, null));
|
||||
|
||||
$lengthR = self::octetLength($pointR);
|
||||
$lengthS = self::octetLength($pointS);
|
||||
|
||||
$totalLength = $lengthR + $lengthS + self::BYTE_SIZE + self::BYTE_SIZE;
|
||||
$lengthPrefix = $totalLength > self::ASN1_MAX_SINGLE_BYTE ? self::ASN1_LENGTH_2BYTES : '';
|
||||
|
||||
$asn1 = hex2bin(
|
||||
self::ASN1_SEQUENCE
|
||||
. $lengthPrefix . dechex($totalLength)
|
||||
. self::ASN1_INTEGER . dechex($lengthR) . $pointR
|
||||
. self::ASN1_INTEGER . dechex($lengthS) . $pointS,
|
||||
);
|
||||
assert(is_string($asn1));
|
||||
assert($asn1 !== '');
|
||||
|
||||
return $asn1;
|
||||
}
|
||||
|
||||
private static function octetLength(string $data): int
|
||||
{
|
||||
return (int) (strlen($data) / self::BYTE_SIZE);
|
||||
}
|
||||
|
||||
private static function preparePositiveInteger(string $data): string
|
||||
{
|
||||
if (substr($data, 0, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT) {
|
||||
return self::ASN1_NEGATIVE_INTEGER . $data;
|
||||
}
|
||||
|
||||
while (
|
||||
substr($data, 0, self::BYTE_SIZE) === self::ASN1_NEGATIVE_INTEGER
|
||||
&& substr($data, 2, self::BYTE_SIZE) <= self::ASN1_BIG_INTEGER_LIMIT
|
||||
) {
|
||||
$data = substr($data, 2, null);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function fromAsn1(string $signature, int $length): string
|
||||
{
|
||||
$message = bin2hex($signature);
|
||||
$position = 0;
|
||||
|
||||
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_SEQUENCE) {
|
||||
throw ConversionFailed::incorrectStartSequence();
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) === self::ASN1_LENGTH_2BYTES) {
|
||||
$position += self::BYTE_SIZE;
|
||||
}
|
||||
|
||||
$pointR = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
|
||||
$pointS = self::retrievePositiveInteger(self::readAsn1Integer($message, $position));
|
||||
|
||||
$points = hex2bin(str_pad($pointR, $length, '0', STR_PAD_LEFT) . str_pad($pointS, $length, '0', STR_PAD_LEFT));
|
||||
assert(is_string($points));
|
||||
assert($points !== '');
|
||||
|
||||
return $points;
|
||||
}
|
||||
|
||||
private static function readAsn1Content(string $message, int &$position, int $length): string
|
||||
{
|
||||
$content = substr($message, $position, $length);
|
||||
$position += $length;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private static function readAsn1Integer(string $message, int &$position): string
|
||||
{
|
||||
if (self::readAsn1Content($message, $position, self::BYTE_SIZE) !== self::ASN1_INTEGER) {
|
||||
throw ConversionFailed::integerExpected();
|
||||
}
|
||||
|
||||
$length = (int) hexdec(self::readAsn1Content($message, $position, self::BYTE_SIZE));
|
||||
|
||||
return self::readAsn1Content($message, $position, $length * self::BYTE_SIZE);
|
||||
}
|
||||
|
||||
private static function retrievePositiveInteger(string $data): string
|
||||
{
|
||||
while (
|
||||
substr($data, 0, self::BYTE_SIZE) === self::ASN1_NEGATIVE_INTEGER
|
||||
&& substr($data, 2, self::BYTE_SIZE) > self::ASN1_BIG_INTEGER_LIMIT
|
||||
) {
|
||||
$data = substr($data, 2, null);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
use Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
use const OPENSSL_ALGO_SHA256;
|
||||
|
||||
final class Sha256 extends Ecdsa
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'ES256';
|
||||
}
|
||||
|
||||
public function algorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA256;
|
||||
}
|
||||
|
||||
public function pointLength(): int
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
|
||||
public function expectedKeyLength(): int
|
||||
{
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
use Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
use const OPENSSL_ALGO_SHA384;
|
||||
|
||||
final class Sha384 extends Ecdsa
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'ES384';
|
||||
}
|
||||
|
||||
public function algorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA384;
|
||||
}
|
||||
|
||||
public function pointLength(): int
|
||||
{
|
||||
return 96;
|
||||
}
|
||||
|
||||
public function expectedKeyLength(): int
|
||||
{
|
||||
return 384;
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
use Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
use const OPENSSL_ALGO_SHA512;
|
||||
|
||||
final class Sha512 extends Ecdsa
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'ES512';
|
||||
}
|
||||
|
||||
public function algorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA512;
|
||||
}
|
||||
|
||||
public function pointLength(): int
|
||||
{
|
||||
return 132;
|
||||
}
|
||||
|
||||
public function expectedKeyLength(): int
|
||||
{
|
||||
// ES512 means ECDSA using P-521 and SHA-512.
|
||||
// The key size is indeed 521 bits.
|
||||
return 521;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Ecdsa;
|
||||
|
||||
/**
|
||||
* Manipulates the result of a ECDSA signature (points R and S) according to the
|
||||
* JWA specs.
|
||||
*
|
||||
* OpenSSL creates a signature using the ASN.1 format and, according the JWA specs,
|
||||
* the signature for JWTs must be the concatenated values of points R and S (in
|
||||
* big-endian octet order).
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc7518#page-9
|
||||
* @see https://en.wikipedia.org/wiki/Abstract_Syntax_Notation_One
|
||||
*/
|
||||
interface SignatureConverter
|
||||
{
|
||||
/**
|
||||
* Converts the signature generated by OpenSSL into what JWA defines
|
||||
*
|
||||
* @return non-empty-string
|
||||
*
|
||||
* @throws ConversionFailed When there was an issue during the format conversion.
|
||||
*/
|
||||
public function fromAsn1(string $signature, int $length): string;
|
||||
|
||||
/**
|
||||
* Converts the JWA signature into something OpenSSL understands
|
||||
*
|
||||
* @param non-empty-string $points
|
||||
*
|
||||
* @return non-empty-string
|
||||
*
|
||||
* @throws ConversionFailed When there was an issue during the format conversion.
|
||||
*/
|
||||
public function toAsn1(string $points, int $length): string;
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
use Lcobucci\JWT\Signer;
|
||||
use SodiumException;
|
||||
|
||||
use function sodium_crypto_sign_detached;
|
||||
use function sodium_crypto_sign_verify_detached;
|
||||
|
||||
final class Eddsa implements Signer
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'EdDSA';
|
||||
}
|
||||
|
||||
public function sign(string $payload, Key $key): string
|
||||
{
|
||||
try {
|
||||
return sodium_crypto_sign_detached($payload, $key->contents());
|
||||
} catch (SodiumException $sodiumException) {
|
||||
throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException);
|
||||
}
|
||||
}
|
||||
|
||||
public function verify(string $expected, string $payload, Key $key): bool
|
||||
{
|
||||
try {
|
||||
return sodium_crypto_sign_verify_detached($expected, $payload, $key->contents());
|
||||
} catch (SodiumException $sodiumException) {
|
||||
throw new InvalidKeyProvided($sodiumException->getMessage(), 0, $sodiumException);
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
use Lcobucci\JWT\Signer;
|
||||
|
||||
use function hash_equals;
|
||||
use function hash_hmac;
|
||||
use function strlen;
|
||||
|
||||
abstract class Hmac implements Signer
|
||||
{
|
||||
final public function sign(string $payload, Key $key): string
|
||||
{
|
||||
$actualKeyLength = 8 * strlen($key->contents());
|
||||
$expectedKeyLength = $this->minimumBitsLengthForKey();
|
||||
|
||||
if ($actualKeyLength < $expectedKeyLength) {
|
||||
throw InvalidKeyProvided::tooShort($expectedKeyLength, $actualKeyLength);
|
||||
}
|
||||
|
||||
return hash_hmac($this->algorithm(), $payload, $key->contents(), true);
|
||||
}
|
||||
|
||||
final public function verify(string $expected, string $payload, Key $key): bool
|
||||
{
|
||||
return hash_equals($expected, $this->sign($payload, $key));
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return non-empty-string
|
||||
*/
|
||||
abstract public function algorithm(): string;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*
|
||||
* @return positive-int
|
||||
*/
|
||||
abstract public function minimumBitsLengthForKey(): int;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Hmac;
|
||||
|
||||
use Lcobucci\JWT\Signer\Hmac;
|
||||
|
||||
final class Sha256 extends Hmac
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'HS256';
|
||||
}
|
||||
|
||||
public function algorithm(): string
|
||||
{
|
||||
return 'sha256';
|
||||
}
|
||||
|
||||
public function minimumBitsLengthForKey(): int
|
||||
{
|
||||
return 256;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Hmac;
|
||||
|
||||
use Lcobucci\JWT\Signer\Hmac;
|
||||
|
||||
final class Sha384 extends Hmac
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'HS384';
|
||||
}
|
||||
|
||||
public function algorithm(): string
|
||||
{
|
||||
return 'sha384';
|
||||
}
|
||||
|
||||
public function minimumBitsLengthForKey(): int
|
||||
{
|
||||
return 384;
|
||||
}
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Hmac;
|
||||
|
||||
use Lcobucci\JWT\Signer\Hmac;
|
||||
|
||||
final class Sha512 extends Hmac
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'HS512';
|
||||
}
|
||||
|
||||
public function algorithm(): string
|
||||
{
|
||||
return 'sha512';
|
||||
}
|
||||
|
||||
public function minimumBitsLengthForKey(): int
|
||||
{
|
||||
return 512;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Lcobucci\JWT\Exception;
|
||||
|
||||
final class InvalidKeyProvided extends InvalidArgumentException implements Exception
|
||||
{
|
||||
public static function cannotBeParsed(string $details): self
|
||||
{
|
||||
return new self('It was not possible to parse your key, reason:' . $details);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $expectedType
|
||||
* @param non-empty-string $actualType
|
||||
*/
|
||||
public static function incompatibleKeyType(string $expectedType, string $actualType): self
|
||||
{
|
||||
return new self(
|
||||
'The type of the provided key is not "' . $expectedType
|
||||
. '", "' . $actualType . '" provided',
|
||||
);
|
||||
}
|
||||
|
||||
/** @param positive-int $expectedLength */
|
||||
public static function incompatibleKeyLength(int $expectedLength, int $actualLength): self
|
||||
{
|
||||
return new self(
|
||||
'The length of the provided key is different than ' . $expectedLength . ' bits, '
|
||||
. $actualLength . ' bits provided',
|
||||
);
|
||||
}
|
||||
|
||||
public static function cannotBeEmpty(): self
|
||||
{
|
||||
return new self('Key cannot be empty');
|
||||
}
|
||||
|
||||
public static function tooShort(int $expectedLength, int $actualLength): self
|
||||
{
|
||||
return new self('Key provided is shorter than ' . $expectedLength . ' bits,'
|
||||
. ' only ' . $actualLength . ' bits provided');
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
interface Key
|
||||
{
|
||||
/** @return non-empty-string */
|
||||
public function contents(): string;
|
||||
|
||||
public function passphrase(): string;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Key;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Lcobucci\JWT\Exception;
|
||||
use Throwable;
|
||||
|
||||
final class FileCouldNotBeRead extends InvalidArgumentException implements Exception
|
||||
{
|
||||
/** @param non-empty-string $path */
|
||||
public static function onPath(string $path, ?Throwable $cause = null): self
|
||||
{
|
||||
return new self(
|
||||
message: 'The path "' . $path . '" does not contain a valid key file',
|
||||
previous: $cause,
|
||||
);
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Key;
|
||||
|
||||
use Lcobucci\JWT\Signer\InvalidKeyProvided;
|
||||
use Lcobucci\JWT\Signer\Key;
|
||||
use Lcobucci\JWT\SodiumBase64Polyfill;
|
||||
use SplFileObject;
|
||||
use Throwable;
|
||||
|
||||
use function assert;
|
||||
use function is_string;
|
||||
|
||||
final class InMemory implements Key
|
||||
{
|
||||
/** @param non-empty-string $contents */
|
||||
private function __construct(public readonly string $contents, public readonly string $passphrase)
|
||||
{
|
||||
}
|
||||
|
||||
/** @param non-empty-string $contents */
|
||||
public static function plainText(string $contents, string $passphrase = ''): self
|
||||
{
|
||||
self::guardAgainstEmptyKey($contents);
|
||||
|
||||
return new self($contents, $passphrase);
|
||||
}
|
||||
|
||||
/** @param non-empty-string $contents */
|
||||
public static function base64Encoded(string $contents, string $passphrase = ''): self
|
||||
{
|
||||
$decoded = SodiumBase64Polyfill::base642bin(
|
||||
$contents,
|
||||
SodiumBase64Polyfill::SODIUM_BASE64_VARIANT_ORIGINAL,
|
||||
);
|
||||
|
||||
self::guardAgainstEmptyKey($decoded);
|
||||
|
||||
return new self($decoded, $passphrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param non-empty-string $path
|
||||
*
|
||||
* @throws FileCouldNotBeRead
|
||||
*/
|
||||
public static function file(string $path, string $passphrase = ''): self
|
||||
{
|
||||
try {
|
||||
$file = new SplFileObject($path);
|
||||
} catch (Throwable $exception) {
|
||||
throw FileCouldNotBeRead::onPath($path, $exception);
|
||||
}
|
||||
|
||||
$fileSize = $file->getSize();
|
||||
$contents = $fileSize > 0 ? $file->fread($file->getSize()) : '';
|
||||
assert(is_string($contents));
|
||||
|
||||
self::guardAgainstEmptyKey($contents);
|
||||
|
||||
return new self($contents, $passphrase);
|
||||
}
|
||||
|
||||
/** @phpstan-assert non-empty-string $contents */
|
||||
private static function guardAgainstEmptyKey(string $contents): void
|
||||
{
|
||||
if ($contents === '') {
|
||||
throw InvalidKeyProvided::cannotBeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public function contents(): string
|
||||
{
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
public function passphrase(): string
|
||||
{
|
||||
return $this->passphrase;
|
||||
}
|
||||
}
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
use Lcobucci\JWT\Signer;
|
||||
use OpenSSLAsymmetricKey;
|
||||
|
||||
use function array_key_exists;
|
||||
use function assert;
|
||||
use function is_array;
|
||||
use function is_bool;
|
||||
use function is_int;
|
||||
use function openssl_error_string;
|
||||
use function openssl_pkey_get_details;
|
||||
use function openssl_pkey_get_private;
|
||||
use function openssl_pkey_get_public;
|
||||
use function openssl_sign;
|
||||
use function openssl_verify;
|
||||
|
||||
use const OPENSSL_KEYTYPE_DH;
|
||||
use const OPENSSL_KEYTYPE_DSA;
|
||||
use const OPENSSL_KEYTYPE_EC;
|
||||
use const OPENSSL_KEYTYPE_RSA;
|
||||
use const PHP_EOL;
|
||||
|
||||
abstract class OpenSSL implements Signer
|
||||
{
|
||||
protected const KEY_TYPE_MAP = [
|
||||
OPENSSL_KEYTYPE_RSA => 'RSA',
|
||||
OPENSSL_KEYTYPE_DSA => 'DSA',
|
||||
OPENSSL_KEYTYPE_DH => 'DH',
|
||||
OPENSSL_KEYTYPE_EC => 'EC',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return non-empty-string
|
||||
*
|
||||
* @throws CannotSignPayload
|
||||
* @throws InvalidKeyProvided
|
||||
*/
|
||||
final protected function createSignature(
|
||||
string $pem,
|
||||
string $passphrase,
|
||||
string $payload,
|
||||
): string {
|
||||
$key = $this->getPrivateKey($pem, $passphrase);
|
||||
|
||||
$signature = '';
|
||||
|
||||
if (! openssl_sign($payload, $signature, $key, $this->algorithm())) {
|
||||
throw CannotSignPayload::errorHappened($this->fullOpenSSLErrorString());
|
||||
}
|
||||
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/** @throws CannotSignPayload */
|
||||
private function getPrivateKey(string $pem, string $passphrase): OpenSSLAsymmetricKey
|
||||
{
|
||||
return $this->validateKey(openssl_pkey_get_private($pem, $passphrase));
|
||||
}
|
||||
|
||||
/** @throws InvalidKeyProvided */
|
||||
final protected function verifySignature(
|
||||
string $expected,
|
||||
string $payload,
|
||||
string $pem,
|
||||
): bool {
|
||||
$key = $this->getPublicKey($pem);
|
||||
$result = openssl_verify($payload, $expected, $key, $this->algorithm());
|
||||
|
||||
return $result === 1;
|
||||
}
|
||||
|
||||
/** @throws InvalidKeyProvided */
|
||||
private function getPublicKey(string $pem): OpenSSLAsymmetricKey
|
||||
{
|
||||
return $this->validateKey(openssl_pkey_get_public($pem));
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises an exception when the key type is not the expected type
|
||||
*
|
||||
* @throws InvalidKeyProvided
|
||||
*/
|
||||
private function validateKey(OpenSSLAsymmetricKey|bool $key): OpenSSLAsymmetricKey
|
||||
{
|
||||
if (is_bool($key)) {
|
||||
throw InvalidKeyProvided::cannotBeParsed($this->fullOpenSSLErrorString());
|
||||
}
|
||||
|
||||
$details = openssl_pkey_get_details($key);
|
||||
assert(is_array($details));
|
||||
|
||||
assert(array_key_exists('bits', $details));
|
||||
assert(is_int($details['bits']));
|
||||
assert(array_key_exists('type', $details));
|
||||
assert(is_int($details['type']));
|
||||
|
||||
$this->guardAgainstIncompatibleKey($details['type'], $details['bits']);
|
||||
|
||||
return $key;
|
||||
}
|
||||
|
||||
private function fullOpenSSLErrorString(): string
|
||||
{
|
||||
$error = '';
|
||||
|
||||
while ($msg = openssl_error_string()) {
|
||||
$error .= PHP_EOL . '* ' . $msg;
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/** @throws InvalidKeyProvided */
|
||||
abstract protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void;
|
||||
|
||||
/**
|
||||
* Returns which algorithm to be used to create/verify the signature (using OpenSSL constants)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract public function algorithm(): int;
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer;
|
||||
|
||||
use const OPENSSL_KEYTYPE_RSA;
|
||||
|
||||
abstract class Rsa extends OpenSSL
|
||||
{
|
||||
private const MINIMUM_KEY_LENGTH = 2048;
|
||||
|
||||
final public function sign(string $payload, Key $key): string
|
||||
{
|
||||
return $this->createSignature($key->contents(), $key->passphrase(), $payload);
|
||||
}
|
||||
|
||||
final public function verify(string $expected, string $payload, Key $key): bool
|
||||
{
|
||||
return $this->verifySignature($expected, $payload, $key->contents());
|
||||
}
|
||||
|
||||
final protected function guardAgainstIncompatibleKey(int $type, int $lengthInBits): void
|
||||
{
|
||||
if ($type !== OPENSSL_KEYTYPE_RSA) {
|
||||
throw InvalidKeyProvided::incompatibleKeyType(
|
||||
self::KEY_TYPE_MAP[OPENSSL_KEYTYPE_RSA],
|
||||
self::KEY_TYPE_MAP[$type],
|
||||
);
|
||||
}
|
||||
|
||||
if ($lengthInBits < self::MINIMUM_KEY_LENGTH) {
|
||||
throw InvalidKeyProvided::tooShort(self::MINIMUM_KEY_LENGTH, $lengthInBits);
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Rsa;
|
||||
|
||||
use Lcobucci\JWT\Signer\Rsa;
|
||||
|
||||
use const OPENSSL_ALGO_SHA256;
|
||||
|
||||
final class Sha256 extends Rsa
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'RS256';
|
||||
}
|
||||
|
||||
public function algorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA256;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Rsa;
|
||||
|
||||
use Lcobucci\JWT\Signer\Rsa;
|
||||
|
||||
use const OPENSSL_ALGO_SHA384;
|
||||
|
||||
final class Sha384 extends Rsa
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'RS384';
|
||||
}
|
||||
|
||||
public function algorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA384;
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Lcobucci\JWT\Signer\Rsa;
|
||||
|
||||
use Lcobucci\JWT\Signer\Rsa;
|
||||
|
||||
use const OPENSSL_ALGO_SHA512;
|
||||
|
||||
final class Sha512 extends Rsa
|
||||
{
|
||||
public function algorithmId(): string
|
||||
{
|
||||
return 'RS512';
|
||||
}
|
||||
|
||||
public function algorithm(): int
|
||||
{
|
||||
return OPENSSL_ALGO_SHA512;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user