JSONGeneralSerializer::name()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2019 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\Signature\Serializer;
15
16
use Base64Url\Base64Url;
17
use InvalidArgumentException;
18
use Jose\Component\Core\Util\JsonConverter;
19
use Jose\Component\Signature\JWS;
20
use LogicException;
21
22
final class JSONGeneralSerializer extends Serializer
23
{
24
    public const NAME = 'jws_json_general';
25
26
    public function displayName(): string
27
    {
28
        return 'JWS JSON General';
29
    }
30
31
    public function name(): string
32
    {
33
        return self::NAME;
34
    }
35
36
    /**
37
     * @throws LogicException if no signature is attached
38
     */
39
    public function serialize(JWS $jws, ?int $signatureIndex = null): string
40
    {
41
        if (0 === $jws->countSignatures()) {
42
            throw new LogicException('No signature.');
43
        }
44
45
        $data = [];
46
        $this->checkPayloadEncoding($jws);
47
48
        if (false === $jws->isPayloadDetached()) {
49
            $data['payload'] = $jws->getEncodedPayload();
50
        }
51
52
        $data['signatures'] = [];
53
        foreach ($jws->getSignatures() as $signature) {
54
            $tmp = ['signature' => Base64Url::encode($signature->getSignature())];
55
            $values = [
56
                'protected' => $signature->getEncodedProtectedHeader(),
57
                'header' => $signature->getHeader(),
58
            ];
59
60
            foreach ($values as $key => $value) {
61
                if ((\is_string($value) && '' !== $value) || (\is_array($value) && 0 !== \count($value))) {
62
                    $tmp[$key] = $value;
63
                }
64
            }
65
            $data['signatures'][] = $tmp;
66
        }
67
68
        return JsonConverter::encode($data);
69
    }
70
71
    /**
72
     * @throws InvalidArgumentException if the input is not supported
73
     */
74
    public function unserialize(string $input): JWS
75
    {
76
        $data = JsonConverter::decode($input);
77
        if (!isset($data['signatures'])) {
78
            throw new InvalidArgumentException('Unsupported input.');
79
        }
80
81
        $isPayloadEncoded = null;
82
        $rawPayload = $data['payload'] ?? null;
83
        $signatures = [];
84
        foreach ($data['signatures'] as $signature) {
85
            if (!isset($signature['signature'])) {
86
                throw new InvalidArgumentException('Unsupported input.');
87
            }
88
            list($encodedProtectedHeader, $protectedHeader, $header) = $this->processHeaders($signature);
89
            $signatures[] = [
90
                'signature' => Base64Url::decode($signature['signature']),
91
                'protected' => $protectedHeader,
92
                'encoded_protected' => $encodedProtectedHeader,
93
                'header' => $header,
94
            ];
95
            $isPayloadEncoded = $this->processIsPayloadEncoded($isPayloadEncoded, $protectedHeader);
96
        }
97
98
        $payload = $this->processPayload($rawPayload, $isPayloadEncoded);
99
        $jws = new JWS($payload, $rawPayload);
100
        foreach ($signatures as $signature) {
101
            $jws = $jws->addSignature(
102
                $signature['signature'],
103
                $signature['protected'],
104
                $signature['encoded_protected'],
105
                $signature['header']
106
            );
107
        }
108
109
        return $jws;
110
    }
111
112
    /**
113
     * @throws InvalidArgumentException if the payload encoding is invalid
114
     */
115
    private function processIsPayloadEncoded(?bool $isPayloadEncoded, array $protectedHeader): bool
116
    {
117
        if (null === $isPayloadEncoded) {
118
            return $this->isPayloadEncoded($protectedHeader);
119
        }
120
        if ($this->isPayloadEncoded($protectedHeader) !== $isPayloadEncoded) {
121
            throw new InvalidArgumentException('Foreign payload encoding detected.');
122
        }
123
124
        return $isPayloadEncoded;
125
    }
126
127
    private function processHeaders(array $signature): array
128
    {
129
        $encodedProtectedHeader = $signature['protected'] ?? null;
130
        $protectedHeader = null === $encodedProtectedHeader ? [] : JsonConverter::decode(Base64Url::decode($encodedProtectedHeader));
131
        $header = \array_key_exists('header', $signature) ? $signature['header'] : [];
132
133
        return [$encodedProtectedHeader, $protectedHeader, $header];
134
    }
135
136
    private function processPayload(?string $rawPayload, ?bool $isPayloadEncoded): ?string
137
    {
138
        if (null === $rawPayload) {
139
            return null;
140
        }
141
142
        return false === $isPayloadEncoded ? $rawPayload : Base64Url::decode($rawPayload);
143
    }
144
145
    // @throws LogicException if the payload encoding is invalid
146
    private function checkPayloadEncoding(JWS $jws): void
147
    {
148
        if ($jws->isPayloadDetached()) {
149
            return;
150
        }
151
        $is_encoded = null;
152
        foreach ($jws->getSignatures() as $signature) {
153
            if (null === $is_encoded) {
154
                $is_encoded = $this->isPayloadEncoded($signature->getProtectedHeader());
155
            }
156
            if (false === $jws->isPayloadDetached()) {
157
                if ($is_encoded !== $this->isPayloadEncoded($signature->getProtectedHeader())) {
158
                    throw new LogicException('Foreign payload encoding detected.');
159
                }
160
            }
161
        }
162
    }
163
}
164