hmacKey, 'hmacKey must be set'); Assert::notEmpty($this->privateKeyPath, 'privateKeyPath must be set'); Assert::notEmpty($this->publicKeyPath, 'publicKeyPath must be set'); Assert::notEmpty($this->encryptionKey, 'encryptionKey must be set'); } public function create(array $payloads = [], array $headers = []): Token { $now = Carbon::now(); $builder = (new Builder())->issuedAt($now->getTimestamp()); if (isset($payloads['exp'])) { $builder->expiresAt($payloads['exp']); } foreach ($payloads as $claim => $value) { $builder->withClaim($claim, $this->prepareValue($value)); } foreach ($headers as $claim => $value) { $builder->withHeader($claim, $this->prepareValue($value)); } /** @noinspection PhpUnhandledExceptionInspection */ $algorithm = $this->getAlgorithmManager()->get(self::PREFERRED_ALGORITHM); return $builder->getToken($algorithm->getSigner(), $algorithm->getPrivateKey()); } /** * @param string $jwt * * @return Token * @throws \InvalidArgumentException */ public function parse(string $jwt): Token { return (new Parser())->parse($jwt); } public function verify(Token $token): bool { try { $algorithm = $this->getAlgorithmManager()->get($token->getHeader('alg')); return $token->verify($algorithm->getSigner(), $algorithm->getPublicKey()); } catch (Exception $e) { return false; } } public function encryptValue(string $rawValue): string { /** @noinspection PhpUnhandledExceptionInspection */ $nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES); $cipher = $this->base64UrlEncode($nonce . sodium_crypto_secretbox($rawValue, $nonce, $this->encryptionKey)); sodium_memzero($rawValue); return $cipher; } public function decryptValue(string $encryptedValue): string { $decoded = $this->base64UrlDecode($encryptedValue); Assert::true($decoded !== false, 'passed value has an invalid base64 encoding'); Assert::true(mb_strlen($decoded, '8bit') >= (SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES)); $nonce = mb_substr($decoded, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, '8bit'); $cipherText = mb_substr($decoded, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES, null, '8bit'); $rawValue = sodium_crypto_secretbox_open($cipherText, $nonce, $this->encryptionKey); Assert::true($rawValue !== false); sodium_memzero($cipherText); return $rawValue; } private function getAlgorithmManager(): AlgorithmsManager { if ($this->algorithmManager === null) { $this->algorithmManager = new AlgorithmsManager([ new Algorithms\HS256($this->hmacKey), new Algorithms\ES256( "file://{$this->privateKeyPath}", $this->privateKeyPass, "file://{$this->publicKeyPath}" ), ]); } return $this->algorithmManager; } private function prepareValue($value) { if ($value instanceof EncryptedValue) { return $this->encryptValue($value->getValue()); } return $value; } private function base64UrlEncode(string $rawValue): string { return rtrim(strtr(base64_encode($rawValue), '+/', '-_'), '='); } private function base64UrlDecode(string $encodedValue): string { return base64_decode(str_pad(strtr($encodedValue, '-_', '+/'), strlen($encodedValue) % 4, '=', STR_PAD_RIGHT)); } }