Passed
Push — master ( 49a409...d46be2 )
by Esteban De La Fuente
03:22
created

Certificate::getId()   B

Complexity

Conditions 8
Paths 6

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 32.1075

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 17
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 30
ccs 5
cts 18
cp 0.2778
crap 32.1075
rs 8.4444
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\Certificate\Entity;
26
27
use DateTime;
28
use Derafu\Lib\Core\Helper\AsymmetricKey;
29
use Derafu\Lib\Core\Helper\Str;
30
use Derafu\Lib\Core\Package\Prime\Component\Certificate\Contract\CertificateInterface;
31
use Derafu\Lib\Core\Package\Prime\Component\Certificate\Exception\CertificateException;
32
use phpseclib3\File\X509;
33
34
/**
35
 * Clase que representa un certificado digital.
36
 */
37
class Certificate implements CertificateInterface
38
{
39
    /**
40
     * Clave pública (certificado).
41
     *
42
     * @var string
43
     */
44
    private string $publicKey;
45
46
    /**
47
     * Clave privada.
48
     *
49
     * @var string
50
     */
51
    private string $privateKey;
52
53
    /**
54
     * Detalles de la clave privada.
55
     *
56
     * @var array
57
     */
58
    private array $privateKeyDetails;
59
60
    /**
61
     * Datos parseados del certificado X509.
62
     *
63
     * @var array
64
     */
65
    private array $data;
66
67
    /**
68
     * Contructor del certificado digital.
69
     *
70
     * @param string $publicKey Clave pública (certificado).
71
     * @param string $privateKey Clave privada.
72
     */
73 20
    public function __construct(string $publicKey, string $privateKey)
74
    {
75 20
        $this->publicKey = AsymmetricKey::normalizePublicKey($publicKey);
76 20
        $this->privateKey = AsymmetricKey::normalizePrivateKey($privateKey);
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82 3
    public function getPublicKey(bool $clean = false): string
83
    {
84 3
        if ($clean) {
85 3
            return trim(str_replace(
86 3
                ['-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----'],
87 3
                '',
88 3
                $this->publicKey
89 3
            ));
90
        }
91
92
        return $this->publicKey;
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 3
    public function getCertificate(bool $clean = false): string
99
    {
100 3
        return $this->getPublicKey($clean);
101
    }
102
103
    /**
104
     * {@inheritdoc}
105
     */
106 3
    public function getPrivateKey(bool $clean = false): string
107
    {
108 3
        if ($clean) {
109
            return trim(str_replace(
110
                ['-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----'],
111
                '',
112
                $this->privateKey
113
            ));
114
        }
115
116 3
        return $this->privateKey;
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 5
    public function getPrivateKeyDetails(): array
123
    {
124 5
        if (!isset($this->privateKeyDetails)) {
125 5
            $this->privateKeyDetails = openssl_pkey_get_details(
126 5
                openssl_pkey_get_private($this->privateKey)
127 5
            );
128
        }
129
130 5
        return $this->privateKeyDetails;
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136 9
    public function getData(): array
137
    {
138 9
        if (!isset($this->data)) {
139 9
            $this->data = openssl_x509_parse($this->publicKey);
140
        }
141
142 9
        return $this->data;
143
    }
144
145
    /**
146
     * {@inheritdoc}
147
     */
148 6
    public function getId(bool $forceUpper = true): string
149
    {
150
        // Verificar el serialNumber en el subject del certificado.
151 6
        $serialNumber = $this->getData()['subject']['serialNumber'] ?? null;
152 6
        if ($serialNumber !== null) {
153 6
            $serialNumber = ltrim(trim($serialNumber), '0');
154 6
            return $forceUpper ? strtoupper($serialNumber) : $serialNumber;
155
        }
156
157
        // Obtener las extensiones del certificado.
158
        $x509 = new X509();
159
        $cert = $x509->loadX509($this->publicKey);
160
        if (isset($cert['tbsCertificate']['extensions'])) {
161
            foreach ($cert['tbsCertificate']['extensions'] as $extension) {
162
                if (
163
                    $extension['extnId'] === 'id-ce-subjectAltName'
164
                    && isset($extension['extnValue'][0]['otherName']['value']['ia5String'])
165
                ) {
166
                    $id = ltrim(
167
                        trim($extension['extnValue'][0]['otherName']['value']['ia5String']),
168
                        '0'
169
                    );
170
                    return $forceUpper ? strtoupper($id) : $id;
171
                }
172
            }
173
        }
174
175
        // No se encontró el ID, se lanza excepción.
176
        throw new CertificateException(
177
            'No fue posible obtener el ID (RUN) del certificado digital (firma electrónica). Se recomienda verificar el formato y contraseña del certificado.'
178
        );
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184 1
    public function getName(): string
185
    {
186 1
        $name = $this->getData()['subject']['CN'] ?? null;
187 1
        if ($name === null) {
188
            throw new CertificateException(
189
                'No fue posible obtener el Name (subject.CN) de la firma.'
190
            );
191
        }
192
193 1
        return $name;
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199 1
    public function getEmail(): string
200
    {
201 1
        $email = $this->getData()['subject']['emailAddress'] ?? null;
202 1
        if ($email === null) {
203
            throw new CertificateException(
204
                'No fue posible obtener el Email (subject.emailAddress) de la firma.'
205
            );
206
        }
207
208 1
        return $email;
209
    }
210
211
    /**
212
     * {@inheritdoc}
213
     */
214 3
    public function getFrom(): string
215
    {
216 3
        return date('Y-m-d\TH:i:s', $this->getData()['validFrom_time_t']);
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222 4
    public function getTo(): string
223
    {
224 4
        return date('Y-m-d\TH:i:s', $this->getData()['validTo_time_t']);
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230
    public function getTotalDays(): int
231
    {
232
        $start = new DateTime($this->getFrom());
233
        $end = new DateTime($this->getTo());
234
        $diff = $start->diff($end);
235
        return (int) $diff->format('%a');
236
    }
237
238
    /**
239
     * {@inheritdoc}
240
     */
241 1
    public function getExpirationDays(?string $from = null): int
242
    {
243 1
        if ($from === null) {
244 1
            $from = date('Y-m-d\TH:i:s');
245
        }
246 1
        $start = new DateTime($from);
247 1
        $end = new DateTime($this->getTo());
248 1
        $diff = $start->diff($end);
249 1
        return (int) $diff->format('%a');
250
    }
251
252
    /**
253
     * {@inheritdoc}
254
     */
255 3
    public function isActive(?string $when = null): bool
256
    {
257 3
        if ($when === null) {
258 2
            $when = date('Y-m-d');
259
        }
260
261 3
        if (!isset($when[10])) {
262 3
            $when .= 'T23:59:59';
263
        }
264
265 3
        return $when >= $this->getFrom() && $when <= $this->getTo();
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271 1
    public function getIssuer(): string
272
    {
273 1
        return $this->getData()['issuer']['CN'];
274
    }
275
276
    /**
277
     * {@inheritdoc}
278
     */
279 4
    public function getModulus(int $wordwrap = Str::WORDWRAP): string
280
    {
281 4
        $modulus = $this->getPrivateKeyDetails()['rsa']['n'] ?? null;
282
283 4
        if ($modulus === null) {
284
            throw new CertificateException(
285
                'No fue posible obtener el módulo de la clave privada.'
286
            );
287
        }
288
289 4
        return Str::wordWrap(base64_encode($modulus), $wordwrap);
290
    }
291
292
    /**
293
     * {@inheritdoc}
294
     */
295 4
    public function getExponent(int $wordwrap = Str::WORDWRAP): string
296
    {
297 4
        $exponent = $this->getPrivateKeyDetails()['rsa']['e'] ?? null;
298
299 4
        if ($exponent === null) {
300
            throw new CertificateException(
301
                'No fue posible obtener el exponente de la clave privada.'
302
            );
303
        }
304
305 4
        return Str::wordWrap(base64_encode($exponent), $wordwrap);
306
    }
307
}
308