Certificate::getKeys()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
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\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 22
    public function __construct(string $publicKey, string $privateKey)
74
    {
75 22
        $this->publicKey = AsymmetricKey::normalizePublicKey($publicKey);
76 22
        $this->privateKey = AsymmetricKey::normalizePrivateKey($privateKey);
77
    }
78
79
    /**
80
     * {@inheritDoc}
81
     */
82 3
    public function getKeys(bool $clean = false): array
83
    {
84 3
        return [
85 3
            'cert' => $this->getPublicKey($clean),
86 3
            'pkey' => $this->getPrivateKey($clean),
87 3
        ];
88
    }
89
90
    /**
91
     * {@inheritDoc}
92
     */
93 8
    public function getPublicKey(bool $clean = false): string
94
    {
95 8
        if ($clean) {
96 3
            return trim(str_replace(
97 3
                ['-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----'],
98 3
                '',
99 3
                $this->publicKey
100 3
            ));
101
        }
102
103 5
        return $this->publicKey;
104
    }
105
106
    /**
107
     * {@inheritDoc}
108
     */
109 3
    public function getCertificate(bool $clean = false): string
110
    {
111 3
        return $this->getPublicKey($clean);
112
    }
113
114
    /**
115
     * {@inheritDoc}
116
     */
117 8
    public function getPrivateKey(bool $clean = false): string
118
    {
119 8
        if ($clean) {
120
            return trim(str_replace(
121
                ['-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----'],
122
                '',
123
                $this->privateKey
124
            ));
125
        }
126
127 8
        return $this->privateKey;
128
    }
129
130
    /**
131
     * {@inheritDoc}
132
     */
133 5
    public function getPrivateKeyDetails(): array
134
    {
135 5
        if (!isset($this->privateKeyDetails)) {
136 5
            $this->privateKeyDetails = openssl_pkey_get_details(
137 5
                openssl_pkey_get_private($this->privateKey)
138 5
            );
139
        }
140
141 5
        return $this->privateKeyDetails;
142
    }
143
144
    /**
145
     * {@inheritDoc}
146
     */
147 9
    public function getData(): array
148
    {
149 9
        if (!isset($this->data)) {
150 9
            $this->data = openssl_x509_parse($this->publicKey);
151
        }
152
153 9
        return $this->data;
154
    }
155
156
    /**
157
     * {@inheritDoc}
158
     */
159 2
    public function getPkcs12(string $password): string
160
    {
161
        // Exportar el certificado final en formato PKCS#12.
162 2
        openssl_pkcs12_export(
163 2
            $this->getPublicKey(),
164 2
            $data,
165 2
            $this->getPrivateKey(),
166 2
            $password
167 2
        );
168
169
        // Entregar los datos del certificado digital.
170 2
        return $data;
171
    }
172
173
    /**
174
     * {@inheritDoc}
175
     */
176 6
    public function getId(bool $forceUpper = true): string
177
    {
178
        // Verificar el serialNumber en el subject del certificado.
179 6
        $serialNumber = $this->getData()['subject']['serialNumber'] ?? null;
180 6
        if ($serialNumber !== null) {
181 6
            $serialNumber = ltrim(trim($serialNumber), '0');
182 6
            return $forceUpper ? strtoupper($serialNumber) : $serialNumber;
183
        }
184
185
        // Obtener las extensiones del certificado.
186
        $x509 = new X509();
187
        $cert = $x509->loadX509($this->publicKey);
188
        if (isset($cert['tbsCertificate']['extensions'])) {
189
            foreach ($cert['tbsCertificate']['extensions'] as $extension) {
190
                if (
191
                    $extension['extnId'] === 'id-ce-subjectAltName'
192
                    && isset($extension['extnValue'][0]['otherName']['value']['ia5String'])
193
                ) {
194
                    $id = ltrim(
195
                        trim($extension['extnValue'][0]['otherName']['value']['ia5String']),
196
                        '0'
197
                    );
198
                    return $forceUpper ? strtoupper($id) : $id;
199
                }
200
            }
201
        }
202
203
        // No se encontró el ID, se lanza excepción.
204
        throw new CertificateException(
205
            'No fue posible obtener el ID (RUN) del certificado digital (firma electrónica). Se recomienda verificar el formato y contraseña del certificado.'
206
        );
207
    }
208
209
    /**
210
     * {@inheritDoc}
211
     */
212 1
    public function getName(): string
213
    {
214 1
        $name = $this->getData()['subject']['CN'] ?? null;
215 1
        if ($name === null) {
216
            throw new CertificateException(
217
                'No fue posible obtener el Name (subject.CN) de la firma.'
218
            );
219
        }
220
221 1
        return $name;
222
    }
223
224
    /**
225
     * {@inheritDoc}
226
     */
227 1
    public function getEmail(): string
228
    {
229 1
        $email = $this->getData()['subject']['emailAddress'] ?? null;
230 1
        if ($email === null) {
231
            throw new CertificateException(
232
                'No fue posible obtener el Email (subject.emailAddress) de la firma.'
233
            );
234
        }
235
236 1
        return $email;
237
    }
238
239
    /**
240
     * {@inheritDoc}
241
     */
242 3
    public function getFrom(): string
243
    {
244 3
        return date('Y-m-d\TH:i:s', $this->getData()['validFrom_time_t']);
245
    }
246
247
    /**
248
     * {@inheritDoc}
249
     */
250 4
    public function getTo(): string
251
    {
252 4
        return date('Y-m-d\TH:i:s', $this->getData()['validTo_time_t']);
253
    }
254
255
    /**
256
     * {@inheritDoc}
257
     */
258
    public function getTotalDays(): int
259
    {
260
        $start = new DateTime($this->getFrom());
261
        $end = new DateTime($this->getTo());
262
        $diff = $start->diff($end);
263
        return (int) $diff->format('%a');
264
    }
265
266
    /**
267
     * {@inheritDoc}
268
     */
269 1
    public function getExpirationDays(?string $from = null): int
270
    {
271 1
        if ($from === null) {
272 1
            $from = date('Y-m-d\TH:i:s');
273
        }
274 1
        $start = new DateTime($from);
275 1
        $end = new DateTime($this->getTo());
276 1
        $diff = $start->diff($end);
277 1
        return (int) $diff->format('%a');
278
    }
279
280
    /**
281
     * {@inheritDoc}
282
     */
283 3
    public function isActive(?string $when = null): bool
284
    {
285 3
        if ($when === null) {
286 2
            $when = date('Y-m-d');
287
        }
288
289 3
        if (!isset($when[10])) {
290 3
            $when .= 'T23:59:59';
291
        }
292
293 3
        return $when >= $this->getFrom() && $when <= $this->getTo();
294
    }
295
296
    /**
297
     * {@inheritDoc}
298
     */
299 1
    public function getIssuer(): string
300
    {
301 1
        return $this->getData()['issuer']['CN'];
302
    }
303
304
    /**
305
     * {@inheritDoc}
306
     */
307 4
    public function getModulus(int $wordwrap = Str::WORDWRAP): string
308
    {
309 4
        $modulus = $this->getPrivateKeyDetails()['rsa']['n'] ?? null;
310
311 4
        if ($modulus === null) {
312
            throw new CertificateException(
313
                'No fue posible obtener el módulo de la clave privada.'
314
            );
315
        }
316
317 4
        return Str::wordWrap(base64_encode($modulus), $wordwrap);
318
    }
319
320
    /**
321
     * {@inheritDoc}
322
     */
323 4
    public function getExponent(int $wordwrap = Str::WORDWRAP): string
324
    {
325 4
        $exponent = $this->getPrivateKeyDetails()['rsa']['e'] ?? null;
326
327 4
        if ($exponent === null) {
328
            throw new CertificateException(
329
                'No fue posible obtener el exponente de la clave privada.'
330
            );
331
        }
332
333 4
        return Str::wordWrap(base64_encode($exponent), $wordwrap);
334
    }
335
}
336