Failed Conditions
Push — master ( d8f365...36b2f5 )
by Florent
05:46
created

JSONGeneralSerializer::checkPayloadEncoding()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 10
nc 8
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2017 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 Jose\Component\Core\Converter\JsonConverter;
18
use Jose\Component\Signature\JWS;
19
20
/**
21
 * Class JSONGeneralSerializer.
22
 */
23
final class JSONGeneralSerializer extends Serializer
24
{
25
    public const NAME = 'jws_json_general';
26
27
    /**
28
     * @var JsonConverter
29
     */
30
    private $jsonConverter;
31
32
    /**
33
     * JSONFlattenedSerializer constructor.
34
     *
35
     * @param JsonConverter $jsonConverter
36
     */
37
    public function __construct(JsonConverter $jsonConverter)
38
    {
39
        $this->jsonConverter = $jsonConverter;
40
    }
41
42
    /**
43
     * {@inheritdoc}
44
     */
45
    public function displayName(): string
46
    {
47
        return 'JWS JSON General';
48
    }
49
50
    /**
51
     * {@inheritdoc}
52
     */
53
    public function name(): string
54
    {
55
        return self::NAME;
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function serialize(JWS $jws, ?int $signatureIndex = null): string
62
    {
63
        if (0 === $jws->countSignatures()) {
64
            throw new \LogicException('No signature.');
65
        }
66
67
        $data = [];
68
        $this->checkPayloadEncoding($jws);
69
70
        if (false === $jws->isPayloadDetached()) {
71
            $data['payload'] = $jws->getEncodedPayload();
72
        }
73
74
        $data['signatures'] = [];
75
        foreach ($jws->getSignatures() as $signature) {
76
            $tmp = ['signature' => Base64Url::encode($signature->getSignature())];
77
            $values = [
78
                'protected' => $signature->getEncodedProtectedHeaders(),
79
                'header' => $signature->getHeaders(),
80
            ];
81
82
            foreach ($values as $key => $value) {
83
                if (!empty($value)) {
84
                    $tmp[$key] = $value;
85
                }
86
            }
87
            $data['signatures'][] = $tmp;
88
        }
89
90
        return $this->jsonConverter->encode($data);
91
    }
92
93
    /**
94
     * @param $data
95
     */
96
    private function checkData($data)
97
    {
98
        if (!is_array($data) || !array_key_exists('signatures', $data)) {
99
            throw new \InvalidArgumentException('Unsupported input.');
100
        }
101
    }
102
103
    /**
104
     * @param $signature
105
     */
106
    private function checkSignature($signature)
107
    {
108
        if (!is_array($signature) || !array_key_exists('signature', $signature)) {
109
            throw new \InvalidArgumentException('Unsupported input.');
110
        }
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116
    public function unserialize(string $input): JWS
117
    {
118
        $data = $this->jsonConverter->decode($input);
119
        $this->checkData($data);
120
121
        $isPayloadEncoded = null;
122
        $rawPayload = array_key_exists('payload', $data) ? $data['payload'] : null;
123
        $signatures = [];
124
        foreach ($data['signatures'] as $signature) {
125
            $this->checkSignature($signature);
126
            list($encodedProtectedHeaders, $protectedHeaders, $headers) = $this->processHeaders($signature);
127
            $signatures[] = [
128
                'signature' => Base64Url::decode($signature['signature']),
129
                'protected' => $protectedHeaders,
130
                'encoded_protected' => $encodedProtectedHeaders,
131
                'header' => $headers,
132
            ];
133
            $isPayloadEncoded = $this->processIsPayloadEncoded($isPayloadEncoded, $protectedHeaders);
134
        }
135
136
        $payload = $this->processPayload($rawPayload, $isPayloadEncoded);
137
        $jws = JWS::create($payload, $rawPayload);
138
        foreach ($signatures as $signature) {
139
            $jws = $jws->addSignature(
140
                $signature['signature'],
141
                $signature['protected'],
142
                $signature['encoded_protected'],
143
                $signature['header']
144
            );
145
        }
146
147
        return $jws;
148
    }
149
150
    /**
151
     * @param bool|null $isPayloadEncoded
152
     * @param array     $protectedHeaders
153
     *
154
     * @return bool
155
     */
156
    private function processIsPayloadEncoded(?bool $isPayloadEncoded, array $protectedHeaders): bool
157
    {
158
        if (null === $isPayloadEncoded) {
159
            return self::isPayloadEncoded($protectedHeaders);
160
        }
161
        if ($this->isPayloadEncoded($protectedHeaders) !== $isPayloadEncoded) {
162
            throw new \InvalidArgumentException('Foreign payload encoding detected.');
163
        }
164
165
        return $isPayloadEncoded;
166
    }
167
168
    /**
169
     * @param array $signature
170
     *
171
     * @return array
172
     */
173
    private function processHeaders(array $signature): array
174
    {
175
        $encodedProtectedHeaders = array_key_exists('protected', $signature) ? $signature['protected'] : null;
176
        $protectedHeaders = null !== $encodedProtectedHeaders ? $this->jsonConverter->decode(Base64Url::decode($encodedProtectedHeaders)) : [];
177
        $headers = array_key_exists('header', $signature) ? $signature['header'] : [];
178
179
        return [$encodedProtectedHeaders, $protectedHeaders, $headers];
180
    }
181
182
    /**
183
     * @param null|string $rawPayload
184
     * @param bool|null   $isPayloadEncoded
185
     *
186
     * @return null|string
187
     */
188
    private function processPayload(?string $rawPayload, ?bool $isPayloadEncoded): ?string
189
    {
190
        if (null === $rawPayload) {
191
            return null;
192
        }
193
        return false === $isPayloadEncoded ? $rawPayload : Base64Url::decode($rawPayload);
194
    }
195
196
    /**
197
     * @param JWS $jws
198
     */
199
    private function checkPayloadEncoding(JWS $jws)
200
    {
201
        if ($jws->isPayloadDetached()) {
202
            return;
203
        }
204
        $is_encoded = null;
205
        foreach ($jws->getSignatures() as $signature) {
206
            if (null === $is_encoded) {
207
                $is_encoded = $this->isPayloadEncoded($signature->getProtectedHeaders());
208
            }
209
            if (false === $jws->isPayloadDetached()) {
210
                if ($is_encoded !== $this->isPayloadEncoded($signature->getProtectedHeaders())) {
211
                    throw new \LogicException('Foreign payload encoding detected.');
212
                }
213
            }
214
        }
215
    }
216
}
217