Signature::getXml()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.5

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 11
ccs 3
cts 6
cp 0.5
crap 2.5
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Derafu: Biblioteca PHP (Núcleo).
7
 * Copyright (C) Derafu <https://www.derafu.org>
8
 *
9
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
10
 * bajo los términos de la Licencia Pública General Affero de GNU publicada por
11
 * la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, o
12
 * (a su elección) cualquier versión posterior de la misma.
13
 *
14
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
15
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
16
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la Licencia Pública
17
 * General Affero de GNU para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de GNU
20
 * junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace Derafu\Lib\Core\Package\Prime\Component\Signature\Entity;
26
27
use Derafu\Lib\Core\Helper\Str;
28
use Derafu\Lib\Core\Package\Prime\Component\Certificate\Contract\CertificateInterface;
29
use Derafu\Lib\Core\Package\Prime\Component\Signature\Contract\SignatureInterface;
30
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
31
use LogicException;
32
33
/**
34
 * Clase que representa el nodo `Signature` en un XML firmado electrónicamente
35
 * utilizando el estándar de firma digital de XML (XML DSIG).
36
 */
37
class Signature implements SignatureInterface
38
{
39
    /**
40
     * Documento XML que representa el nodo de la firma electrónica.
41
     *
42
     * @var XmlInterface
43
     */
44
    private XmlInterface $xml;
45
46
    /**
47
     * Datos del nodo Signature.
48
     *
49
     * Por defecto se dejan vacíos los datos que se completarán posteriormente.
50
     * Ya sea mediante una asignación de los datos o bien mediante la carga de
51
     * un nuevo XML con los datos.
52
     *
53
     * @var array
54
     */
55
    private array $data = [
56
        // Nodo raíz es Signature.
57
        // Este es el nodo que se incluirá en los XML firmados.
58
        'Signature' => [
59
            '@attributes' => [
60
                'xmlns' => 'http://www.w3.org/2000/09/xmldsig#',
61
            ],
62
            // Datos que se firmarán. Acá el más importante es el tag
63
            // "DigestValue" que contiene un "resumen" (digestión) del C14N
64
            // del nodo de la referencia.
65
            'SignedInfo' => [
66
                '@attributes' => [
67
                    'xmlns:xsi' => 'http://www.w3.org/2001/XMLSchema-instance',
68
                ],
69
                'CanonicalizationMethod' => [
70
                    '@attributes' => [
71
                        'Algorithm' => 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315',
72
                    ],
73
                ],
74
                'SignatureMethod' => [
75
                    '@attributes' => [
76
                        'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1',
77
                    ],
78
                ],
79
                'Reference' => [
80
                    '@attributes' => [
81
                        // Indica cuál es el nodo de la referencia, debe tener
82
                        // como prefijo un "#". Si está vacío se entiende que
83
                        // se desea firmar todo el XML.
84
                        'URI' => '', // Opcional.
85
                    ],
86
                    'Transforms' => [
87
                        'Transform' => [
88
                            '@attributes' => [
89
                                'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature',
90
                            ],
91
                        ],
92
                    ],
93
                    'DigestMethod' => [
94
                        '@attributes' => [
95
                            'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1',
96
                        ],
97
                    ],
98
                    'DigestValue' => '', // Obligatorio.
99
                ],
100
            ],
101
            // Firma del C14N del nodo `SignedInfo`.
102
            // Se agrega después de construir el C14N del SignedInfo y firmar.
103
            'SignatureValue' => '', // Obligatorio.
104
            // Información de la clave pública para la validación posterior
105
            // de la firma electrónica.
106
            'KeyInfo' => [
107
                'KeyValue' => [
108
                    'RSAKeyValue' => [
109
                        'Modulus' => '', // Obligatorio.
110
                        'Exponent' => '', // Obligatorio.
111
                    ],
112
                ],
113
                'X509Data' => [
114
                    'X509Certificate' => '', // Obligatorio.
115
                ],
116
            ],
117
        ],
118
    ];
119
120
    /**
121
     * {@inheritDoc}
122
     */
123 5
    public function setData(array $data): static
124
    {
125
        // Asignar los datos.
126 5
        $this->data = $data;
127
128
        // Invalidar el documento XML del nodo Signature.
129 5
        $this->invalidateXml();
130
131
        // Retornar instancia para encadenamiento.
132 5
        return $this;
133
    }
134
135
    /**
136
     * {@inheritDoc}
137
     */
138 3
    public function getData(): array
139
    {
140 3
        return $this->data;
141
    }
142
143
    /**
144
     * {@inheritDoc}
145
     */
146 3
    public function configureSignatureData(
147
        string $digestValue,
148
        CertificateInterface $certificate,
149
        ?string $reference = null
150
    ): static {
151 3
        return $this
152 3
            ->setReference($reference)
153 3
            ->setDigestValue($digestValue)
154 3
            ->setCertificate($certificate)
155 3
        ;
156
    }
157
158
    /**
159
     * {@inheritDoc}
160
     */
161 5
    public function setXml(XmlInterface $xml): static
162
    {
163 5
        $this->xml = $xml;
164
165 5
        return $this;
166
    }
167
168
    /**
169
     * {@inheritDoc}
170
     */
171 4
    public function getXml(): XmlInterface
172
    {
173
        // Si la instancia no ha sido asignada previamente se lanza una
174
        // excepción.
175 4
        if (!isset($this->xml)) {
176
            throw new LogicException(
177
                'La instancia de Xml no está disponible en Signature.'
178
            );
179
        }
180
181 4
        return $this->xml;
182
    }
183
184
    /**
185
     * Establece la referencia URI para la firma electrónica.
186
     *
187
     * @param string|null $reference La referencia URI, la cual debe incluir el
188
     * prefijo "#".
189
     * @return static La instancia actual para encadenamiento de métodos.
190
     */
191 3
    private function setReference(?string $reference = null): static
192
    {
193
        // Asignar URI de la referencia (o vacia si se firma todo el XML).
194 3
        $uri = $reference ? ('#' . ltrim($reference, '#')) : '';
195 3
        $this->data['Signature']['SignedInfo']['Reference']['@attributes']['URI'] = $uri;
196
197
        // Asignar algoritmo de transformación al momento de obtener el C14N.
198 3
        $this->data['Signature']['SignedInfo']['Reference']['Transforms']['Transform']['@attributes']['Algorithm'] = $reference
199 1
            ? 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
200 2
            : 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'
201 3
        ;
202
203
        // Invalidar el documento XML del nodo Signature.
204 3
        $this->invalidateXml();
205
206
        // Retornar instancia para encadenamiento.
207 3
        return $this;
208
    }
209
210
    /**
211
     * {@inheritDoc}
212
     */
213 5
    public function getReference(): ?string
214
    {
215 5
        $uri = $this->data['Signature']['SignedInfo']['Reference']['@attributes']['URI'];
216
217 5
        return $uri ? ltrim($uri, '#') : null;
218
    }
219
220
    /**
221
     * Establece el valor del DigestValue del nodo `Reference`.
222
     *
223
     * @param string $digestValue El DigestValue calculado.
224
     * @return static La instancia actual para encadenamiento de métodos.
225
     */
226 3
    private function setDigestValue(string $digestValue): static
227
    {
228
        // Asignar el digest value.
229 3
        $this->data['Signature']['SignedInfo']['Reference']['DigestValue'] = $digestValue;
230
231
        // Invalidar el documento XML del nodo Signature.
232 3
        $this->invalidateXml();
233
234
        // Retornar instancia para encadenamiento.
235 3
        return $this;
236
    }
237
238
    /**
239
     * {@inheritDoc}
240
     */
241 5
    public function getDigestValue(): ?string
242
    {
243 5
        $digestValue = $this->data['Signature']['SignedInfo']['Reference']['DigestValue'];
244
245 5
        return $digestValue ?: null;
246
    }
247
248
    /**
249
     * Asigna un certificado digital a la instancia actual y actualiza los
250
     * valores correspondientes en el nodo `KeyInfo` (módulo, exponente y
251
     * certificado en formato X509).
252
     *
253
     * @param CertificateInterface $certificate El certificado digital a asignar.
254
     * @return static La instancia actual para encadenamiento de métodos.
255
     */
256 3
    private function setCertificate(CertificateInterface $certificate): static
257
    {
258
        // Agregar módulo, exponente y certificado. Este último contiene la
259
        // clave pública que permitirá a otros validar la firma del XML.
260 3
        $this->data['Signature']['KeyInfo']['KeyValue']['RSAKeyValue']['Modulus'] =
261 3
            $certificate->getModulus()
262 3
        ;
263 3
        $this->data['Signature']['KeyInfo']['KeyValue']['RSAKeyValue']['Exponent'] =
264 3
            $certificate->getExponent()
265 3
        ;
266 3
        $this->data['Signature']['KeyInfo']['X509Data']['X509Certificate'] =
267 3
            $certificate->getCertificate(true)
268 3
        ;
269
270
        // Invalidar el documento XML del nodo Signature.
271 3
        $this->invalidateXml();
272
273
        // Retornar instancia para encadenamiento.
274 3
        return $this;
275
    }
276
277
    /**
278
     * {@inheritDoc}
279
     */
280 4
    public function getX509Certificate(): ?string
281
    {
282 4
        $x509 = $this->data['Signature']['KeyInfo']['X509Data']['X509Certificate'];
283
284 4
        return $x509 ?: null;
285
    }
286
287
    /**
288
     * {@inheritDoc}
289
     */
290 3
    public function setSignatureValue(string $signatureValue): static
291
    {
292
        // Asignar firma electrónica del nodo `SignedInfo`.
293 3
        $this->data['Signature']['SignatureValue'] =
294 3
            Str::wordWrap($signatureValue)
295 3
        ;
296
297
        // Invalidar el documento XML del nodo Signature.
298 3
        $this->invalidateXml();
299
300
        // Retornar instancia para encadenamiento.
301 3
        return $this;
302
    }
303
304
    /**
305
     * {@inheritDoc}
306
     */
307 4
    public function getSignatureValue(): ?string
308
    {
309 4
        $signatureValue = $this->data['Signature']['SignatureValue'];
310
311 4
        return $signatureValue ?: null;
312
    }
313
314
    /**
315
     * Invalida el Xml asociado al nodo de la firma.
316
     *
317
     * Este método se utiliza al asignar datos al nodo, pues el Xml
318
     * deberá ser regenerado (esto se hace fuera y se debe volver a asignar).
319
     *
320
     * La invalidación se realiza aplicando `unset()` al Xml.
321
     *
322
     * @return void
323
     */
324 5
    private function invalidateXml(): void
325
    {
326 5
        unset($this->xml);
327
    }
328
}
329