168 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
declare(strict_types=1);
 | 
						|
 | 
						|
/*
 | 
						|
 * The MIT License (MIT)
 | 
						|
 *
 | 
						|
 * Copyright (c) 2014-2020 Spomky-Labs
 | 
						|
 *
 | 
						|
 * This software may be modified and distributed under the terms
 | 
						|
 * of the MIT license.  See the LICENSE file for details.
 | 
						|
 */
 | 
						|
 | 
						|
namespace Jose\Component\Signature\Serializer;
 | 
						|
 | 
						|
use function array_key_exists;
 | 
						|
use Base64Url\Base64Url;
 | 
						|
use function count;
 | 
						|
use InvalidArgumentException;
 | 
						|
use function is_array;
 | 
						|
use function is_string;
 | 
						|
use Jose\Component\Core\Util\JsonConverter;
 | 
						|
use Jose\Component\Signature\JWS;
 | 
						|
use LogicException;
 | 
						|
 | 
						|
final class JSONGeneralSerializer extends Serializer
 | 
						|
{
 | 
						|
    public const NAME = 'jws_json_general';
 | 
						|
 | 
						|
    public function displayName(): string
 | 
						|
    {
 | 
						|
        return 'JWS JSON General';
 | 
						|
    }
 | 
						|
 | 
						|
    public function name(): string
 | 
						|
    {
 | 
						|
        return self::NAME;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @throws LogicException if no signature is attached
 | 
						|
     */
 | 
						|
    public function serialize(JWS $jws, ?int $signatureIndex = null): string
 | 
						|
    {
 | 
						|
        if (0 === $jws->countSignatures()) {
 | 
						|
            throw new LogicException('No signature.');
 | 
						|
        }
 | 
						|
 | 
						|
        $data = [];
 | 
						|
        $this->checkPayloadEncoding($jws);
 | 
						|
 | 
						|
        if (false === $jws->isPayloadDetached()) {
 | 
						|
            $data['payload'] = $jws->getEncodedPayload();
 | 
						|
        }
 | 
						|
 | 
						|
        $data['signatures'] = [];
 | 
						|
        foreach ($jws->getSignatures() as $signature) {
 | 
						|
            $tmp = ['signature' => Base64Url::encode($signature->getSignature())];
 | 
						|
            $values = [
 | 
						|
                'protected' => $signature->getEncodedProtectedHeader(),
 | 
						|
                'header' => $signature->getHeader(),
 | 
						|
            ];
 | 
						|
 | 
						|
            foreach ($values as $key => $value) {
 | 
						|
                if ((is_string($value) && '' !== $value) || (is_array($value) && 0 !== count($value))) {
 | 
						|
                    $tmp[$key] = $value;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            $data['signatures'][] = $tmp;
 | 
						|
        }
 | 
						|
 | 
						|
        return JsonConverter::encode($data);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @throws InvalidArgumentException if the input is not supported
 | 
						|
     */
 | 
						|
    public function unserialize(string $input): JWS
 | 
						|
    {
 | 
						|
        $data = JsonConverter::decode($input);
 | 
						|
        if (!isset($data['signatures'])) {
 | 
						|
            throw new InvalidArgumentException('Unsupported input.');
 | 
						|
        }
 | 
						|
 | 
						|
        $isPayloadEncoded = null;
 | 
						|
        $rawPayload = $data['payload'] ?? null;
 | 
						|
        $signatures = [];
 | 
						|
        foreach ($data['signatures'] as $signature) {
 | 
						|
            if (!isset($signature['signature'])) {
 | 
						|
                throw new InvalidArgumentException('Unsupported input.');
 | 
						|
            }
 | 
						|
            [$encodedProtectedHeader, $protectedHeader, $header] = $this->processHeaders($signature);
 | 
						|
            $signatures[] = [
 | 
						|
                'signature' => Base64Url::decode($signature['signature']),
 | 
						|
                'protected' => $protectedHeader,
 | 
						|
                'encoded_protected' => $encodedProtectedHeader,
 | 
						|
                'header' => $header,
 | 
						|
            ];
 | 
						|
            $isPayloadEncoded = $this->processIsPayloadEncoded($isPayloadEncoded, $protectedHeader);
 | 
						|
        }
 | 
						|
 | 
						|
        $payload = $this->processPayload($rawPayload, $isPayloadEncoded);
 | 
						|
        $jws = new JWS($payload, $rawPayload);
 | 
						|
        foreach ($signatures as $signature) {
 | 
						|
            $jws = $jws->addSignature(
 | 
						|
                $signature['signature'],
 | 
						|
                $signature['protected'],
 | 
						|
                $signature['encoded_protected'],
 | 
						|
                $signature['header']
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        return $jws;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * @throws InvalidArgumentException if the payload encoding is invalid
 | 
						|
     */
 | 
						|
    private function processIsPayloadEncoded(?bool $isPayloadEncoded, array $protectedHeader): bool
 | 
						|
    {
 | 
						|
        if (null === $isPayloadEncoded) {
 | 
						|
            return $this->isPayloadEncoded($protectedHeader);
 | 
						|
        }
 | 
						|
        if ($this->isPayloadEncoded($protectedHeader) !== $isPayloadEncoded) {
 | 
						|
            throw new InvalidArgumentException('Foreign payload encoding detected.');
 | 
						|
        }
 | 
						|
 | 
						|
        return $isPayloadEncoded;
 | 
						|
    }
 | 
						|
 | 
						|
    private function processHeaders(array $signature): array
 | 
						|
    {
 | 
						|
        $encodedProtectedHeader = $signature['protected'] ?? null;
 | 
						|
        $protectedHeader = null === $encodedProtectedHeader ? [] : JsonConverter::decode(Base64Url::decode($encodedProtectedHeader));
 | 
						|
        $header = array_key_exists('header', $signature) ? $signature['header'] : [];
 | 
						|
 | 
						|
        return [$encodedProtectedHeader, $protectedHeader, $header];
 | 
						|
    }
 | 
						|
 | 
						|
    private function processPayload(?string $rawPayload, ?bool $isPayloadEncoded): ?string
 | 
						|
    {
 | 
						|
        if (null === $rawPayload) {
 | 
						|
            return null;
 | 
						|
        }
 | 
						|
 | 
						|
        return false === $isPayloadEncoded ? $rawPayload : Base64Url::decode($rawPayload);
 | 
						|
    }
 | 
						|
 | 
						|
    // @throws LogicException if the payload encoding is invalid
 | 
						|
    private function checkPayloadEncoding(JWS $jws): void
 | 
						|
    {
 | 
						|
        if ($jws->isPayloadDetached()) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
        $is_encoded = null;
 | 
						|
        foreach ($jws->getSignatures() as $signature) {
 | 
						|
            if (null === $is_encoded) {
 | 
						|
                $is_encoded = $this->isPayloadEncoded($signature->getProtectedHeader());
 | 
						|
            }
 | 
						|
            if (false === $jws->isPayloadDetached()) {
 | 
						|
                if ($is_encoded !== $this->isPayloadEncoded($signature->getProtectedHeader())) {
 | 
						|
                    throw new LogicException('Foreign payload encoding detected.');
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
}
 |