Passed
Push — master ( a8bdfa...0cad3e )
by Esteban De La Fuente
09:10
created

CertificateFaker::setPassword()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * LibreDTE: Biblioteca PHP (Núcleo).
7
 * Copyright (C) LibreDTE <https://www.libredte.cl>
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
20
 * GNU junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace libredte\lib\Core\Signature;
26
27
use OpenSSLAsymmetricKey;
28
29
/**
30
 * Clase que se encarga de generar certificados autofirmados y retornarlos como
31
 * un string de datos, un arreglo o una instancia de Certificate.
32
 */
33
class CertificateFaker
34
{
35
    /**
36
     * Datos del sujeto del certificado.
37
     *
38
     * @var array
39
     */
40
    private array $subject;
41
42
    /**
43
     * Datos del emisor del certificado.
44
     *
45
     * @var array
46
     */
47
    private array $issuer;
48
49
    /**
50
     * Validez del certificado en formato UNIX timestamp.
51
     *
52
     * @var array
53
     */
54
    private array $validity;
55
56
    /**
57
     * Contraseña para proteger la clave privada en el certificado.
58
     *
59
     * @var string
60
     */
61
    private string $password;
62
63
    /**
64
     * Constructor de la clase.
65
     *
66
     * Establece valores por defecto para el sujeto, emisor, validez y
67
     * contraseña.
68
     */
69 80
    public function __construct()
70
    {
71 80
        $this->setSubject();
72 80
        $this->setIssuer();
73 80
        $this->setValidity();
74 80
        $this->setPassword();
75
    }
76
77
    /**
78
     * Configura los datos del sujeto del certificado.
79
     *
80
     * @param string $C País del sujeto.
81
     * @param string $ST Estado o provincia del sujeto.
82
     * @param string $L Localidad del sujeto.
83
     * @param string $O Organización del sujeto.
84
     * @param string $OU Unidad organizativa del sujeto.
85
     * @param string $CN Nombre común del sujeto.
86
     * @param string $emailAddress Correo electrónico del sujeto.
87
     * @param string $serialNumber Número de serie del sujeto.
88
     * @param string $title Título del sujeto.
89
     * @return self
90
     */
91 80
    public function setSubject(
92
        string $C = 'CL',
93
        string $ST = 'Colchagua',
94
        string $L = 'Santa Cruz',
95
        string $O = 'Organización Intergaláctica de Robots',
96
        string $OU = 'Tecnología',
97
        string $CN = 'Daniel',
98
        string $emailAddress = '[email protected]',
99
        string $serialNumber = '11222333-9',
100
        string $title = 'Bot',
101
    ): self {
102 80
        if (empty($CN) || empty($emailAddress) || empty($serialNumber)) {
103 2
            throw new CertificateException(
104 2
                'El CN, emailAddress y serialNumber son obligatorios.'
105 2
            );
106
        }
107
108 80
        $this->subject = [
109 80
            'C' => $C,
110 80
            'ST' => $ST,
111 80
            'L' => $L,
112 80
            'O' => $O,
113 80
            'OU' => $OU,
114 80
            'CN' => $CN,
115 80
            'emailAddress' => $emailAddress,
116 80
            'serialNumber' => strtoupper($serialNumber),
117 80
            'title' => $title,
118 80
        ];
119
120 80
        return $this;
121
    }
122
123
    /**
124
     * Configura los datos del emisor del certificado.
125
     *
126
     * @param string $C País del emisor.
127
     * @param string $ST Estado o provincia del emisor.
128
     * @param string $L Localidad del emisor.
129
     * @param string $O Organización del emisor.
130
     * @param string $OU Unidad organizativa del emisor.
131
     * @param string $CN Nombre común del emisor.
132
     * @param string $emailAddress Correo electrónico del emisor.
133
     * @param string $serialNumber Número de serie del emisor.
134
     * @return self
135
     */
136 80
    public function setIssuer(
137
        string $C = 'CL',
138
        string $ST = 'Colchagua',
139
        string $L = 'Santa Cruz',
140
        string $O = 'LibreDTE',
141
        string $OU = 'Facturación Electrónica',
142
        string $CN = 'LibreDTE Autoridad Certificadora de Pruebas',
143
        string $emailAddress = '[email protected]',
144
        string $serialNumber = '76192083-9',
145
    ): self {
146 80
        $this->issuer = [
147 80
            'C' => $C,
148 80
            'ST' => $ST,
149 80
            'L' => $L,
150 80
            'O' => $O,
151 80
            'OU' => $OU,
152 80
            'CN' => $CN,
153 80
            'emailAddress' => $emailAddress,
154 80
            'serialNumber' => strtoupper($serialNumber),
155 80
        ];
156
157 80
        return $this;
158
    }
159
160
    /**
161
     * Configura la validez del certificado.
162
     *
163
     * @param string|null $days Días que el certificado será válido desde la
164
     * fecha actual. Si no se proporciona, tendrá validez de 365 días.
165
     * @return self
166
     */
167 80
    public function setValidity(int $days = 365): self
168
    {
169 80
        $this->validity = [
170 80
            'days' => $days,
171 80
        ];
172
173 80
        return $this;
174
    }
175
176
    /**
177
     * Configura la contraseña para proteger la clave privada.
178
     *
179
     * @param string $password Contraseña para proteger la clave privada.
180
     * @return void
181
     */
182 80
    public function setPassword(string $password = 'i_love_libredte')
183
    {
184 80
        $this->password = $password;
185
    }
186
187
    /**
188
     * Obtiene la contraseña configurada.
189
     *
190
     * @return string Contraseña configurada.
191
     */
192 2
    public function getPassword(): string
193
    {
194 2
        return $this->password;
195
    }
196
197
    /**
198
     * Genera un certificado digital en formato PKCS#12 y lo devuelve como un
199
     * string.
200
     *
201
     * @return string Certificado digital en formato PKCS#12.
202
     */
203 78
    public function createAsString(): string
204
    {
205
        // Días de validez del certificado (emisor y sujeto).
206 78
        $days = $this->validity['days'];
207
208
        // Crear clave privada y CSR para el emisor.
209 78
        $issuerPrivateKey = openssl_pkey_new();
210 78
        if (!$issuerPrivateKey instanceof OpenSSLAsymmetricKey) {
211
            throw new CertificateException(
212
                'No fue posible generar la llave privada del emisor del certificado.'
213
            );
214
        }
215 78
        $issuerCsr = openssl_csr_new($this->issuer, $issuerPrivateKey);
216
217
        // Crear certificado autofirmado para el emisor (CA).
218 78
        $issuerCert = openssl_csr_sign(
219 78
            $issuerCsr,         // CSR del emisor.
220 78
            null,               // Certificado emisor (null indica que es autofirmado).
221 78
            $issuerPrivateKey,  // Clave privada del emisor.
222 78
            $days,              // Número de días de validez (misma sujeto).
223 78
            [],                 // Opciones adicionales.
224 78
            666                 // Número de serie del certificado.
225 78
        );
226
227
        // Validar que se haya podido crear el certificado del emisor (CA).
228 78
        if ($issuerCert === false) {
229
            throw new CertificateException(
230
                'No fue posible generar el certificado del emisor (CA).'
231
            );
232
        }
233
234
        // Crear clave privada y CSR para el sujeto.
235 78
        $subjectPrivateKey = openssl_pkey_new();
236 78
        if (!$subjectPrivateKey instanceof OpenSSLAsymmetricKey) {
237
            throw new CertificateException(
238
                'No fue posible generar la llave privada del certificado.'
239
            );
240
        }
241 78
        $subjectCsr = openssl_csr_new($this->subject, $subjectPrivateKey);
242
243
        // Usar el certificado del emisor para firmar el CSR del sujeto.
244 78
        $subjectCert = openssl_csr_sign(
245 78
            $subjectCsr,        // La solicitud de firma del certificado (CSR).
246 78
            $issuerCert,        // Certificado emisor.
247 78
            $issuerPrivateKey,  // Clave privada del emisor.
248 78
            $days,              // Número de días de validez.
249 78
            [],                 // Opciones adicionales.
250 78
            69                  // Número de serie del certificado.
251 78
        );
252
253
        // Validar que se haya podido crear el certificado del usuario.
254 78
        if ($subjectCert === false) {
255
            throw new CertificateException(
256
                'No fue posible generar el certificado del usuario.'
257
            );
258
        }
259
260
        // Exportar el certificado final en formato PKCS#12.
261 78
        openssl_pkcs12_export(
262 78
            $subjectCert,
263 78
            $data,
264 78
            $subjectPrivateKey,
265 78
            $this->password
266 78
        );
267
268
        // Entregar los datos del certificado digital.
269 78
        return $data;
270
    }
271
272
    /**
273
     * Genera un certificado digital en formato PKCS#12 y lo devuelve como un
274
     * arreglo.
275
     *
276
     * @return array Certificado digital en formato PKCS#12.
277
     */
278 76
    public function createAsArray(): array
279
    {
280 76
        $data = $this->createAsString();
281 76
        $array = [];
282 76
        openssl_pkcs12_read($data, $array, $this->password);
283
284 76
        return $array;
285
    }
286
287
    /**
288
     * Genera un certificado digital y lo devuelve como una instancia de
289
     * Certificate.
290
     *
291
     * @return Certificate Instancia de Certificate.
292
     */
293 73
    public function create(): Certificate
294
    {
295 73
        $array = $this->createAsArray();
296
297 73
        return CertificateLoader::createFromArray($array);
298
    }
299
}
300