welcome back to dyb-tech

This commit is contained in:
Daniel Guzman
2024-05-18 02:28:01 +02:00
parent 9513cdba09
commit 9f30bc98c7
6149 changed files with 668407 additions and 0 deletions
+36
View File
@@ -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));
}
}
+15
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+47
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}