Passed
Push — master ( e584ed...a08a48 )
by Esteban De La Fuente
05:48
created

CertificateFaker::setValidity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 10
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 18
ccs 12
cts 12
cp 1
crap 2
rs 9.9332
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
/**
28
 * Clase que se encarga de generar certificados autofirmados y retornarlos como
29
 * un string de datos, un arreglo o una instancia de Certificate.
30
 */
31
class CertificateFaker
32
{
33
    /**
34
     * Datos del sujeto del certificado.
35
     *
36
     * @var array
37
     */
38
    private array $subject;
39
40
    /**
41
     * Datos del emisor del certificado.
42
     *
43
     * @var array
44
     */
45
    private array $issuer;
46
47
    /**
48
     * Validez del certificado en formato UNIX timestamp.
49
     *
50
     * @var array
51
     */
52
    private array $validity;
53
54
    /**
55
     * Contraseña para proteger la clave privada en el certificado.
56
     *
57
     * @var string
58
     */
59
    private string $password;
60
61
    /**
62
     * Constructor de la clase.
63
     *
64
     * Establece valores por defecto para el sujeto, emisor, validez y
65
     * contraseña.
66
     */
67 58
    public function __construct()
68
    {
69 58
        $this->setSubject();
70 58
        $this->setIssuer();
71 58
        $this->setValidity();
72 58
        $this->setPassword();
73
    }
74
75
    /**
76
     * Configura los datos del sujeto del certificado.
77
     *
78
     * @param string $C País del sujeto.
79
     * @param string $ST Estado o provincia del sujeto.
80
     * @param string $L Localidad del sujeto.
81
     * @param string $O Organización del sujeto.
82
     * @param string $OU Unidad organizativa del sujeto.
83
     * @param string $CN Nombre común del sujeto.
84
     * @param string $emailAddress Correo electrónico del sujeto.
85
     * @param string $serialNumber Número de serie del sujeto.
86
     * @param string $title Título del sujeto.
87
     * @return self
88
     */
89 58
    public function setSubject(
90
        string $C = 'CL',
91
        string $ST = 'Colchagua',
92
        string $L = 'Santa Cruz',
93
        string $O = 'Organización Intergaláctica de Robots',
94
        string $OU = 'Tecnología',
95
        string $CN = 'Daniel',
96
        string $emailAddress = '[email protected]',
97
        string $serialNumber = '11222333-9',
98
        string $title = 'Bot',
99
    ): self {
100 58
        if (empty($CN) || empty($emailAddress) || empty($serialNumber)) {
101 2
            throw new CertificateException(
102 2
                'El CN, emailAddress y serialNumber son obligatorios.'
103 2
            );
104
        }
105
106 58
        $this->subject = [
107 58
            'C' => $C,
108 58
            'ST' => $ST,
109 58
            'L' => $L,
110 58
            'O' => $O,
111 58
            'OU' => $OU,
112 58
            'CN' => $CN,
113 58
            'emailAddress' => $emailAddress,
114 58
            'serialNumber' => strtoupper($serialNumber),
115 58
            'title' => $title,
116 58
        ];
117
118 58
        return $this;
119
    }
120
121
    /**
122
     * Configura los datos del emisor del certificado.
123
     *
124
     * @param string $C País del emisor.
125
     * @param string $ST Estado o provincia del emisor.
126
     * @param string $L Localidad del emisor.
127
     * @param string $O Organización del emisor.
128
     * @param string $OU Unidad organizativa del emisor.
129
     * @param string $CN Nombre común del emisor.
130
     * @param string $emailAddress Correo electrónico del emisor.
131
     * @param string $serialNumber Número de serie del emisor.
132
     * @return self
133
     */
134 58
    public function setIssuer(
135
        string $C = 'CL',
136
        string $ST = 'Colchagua',
137
        string $L = 'Santa Cruz',
138
        string $O = 'LibreDTE',
139
        string $OU = 'Facturación Electrónica',
140
        string $CN = 'LibreDTE Autoridad Certificadora de Pruebas',
141
        string $emailAddress = '[email protected]',
142
        string $serialNumber = '76192083-9',
143
    ): self {
144 58
        $this->issuer = [
145 58
            'C' => $C,
146 58
            'ST' => $ST,
147 58
            'L' => $L,
148 58
            'O' => $O,
149 58
            'OU' => $OU,
150 58
            'CN' => $CN,
151 58
            'emailAddress' => $emailAddress,
152 58
            'serialNumber' => strtoupper($serialNumber),
153 58
        ];
154
155 58
        return $this;
156
    }
157
158
    /**
159
     * Configura la validez del certificado.
160
     *
161
     * @param string|null $validTo Fecha de validez hasta, en formato 'Y-m-d'.
162
     * Si no se proporciona, se establece un año a partir de la fecha actual.
163
     * @return self
164
     */
165 58
    public function setValidity(string $validTo = null): self
166
    {
167 58
        $validFrom = (int) date('U');
168
169 58
        if ($validTo === null) {
170 58
            $validTo = date('Y-m-d', strtotime('+1 year', $validFrom));
171
        }
172 58
        $validTo = strtotime($validTo);
173
174 58
        $days = (int) (($validTo - $validFrom) / (60 * 60 * 24));
175
176 58
        $this->validity = [
177 58
            'from' => $validFrom,
178 58
            'to' => $validTo,
179 58
            'days' => $days,
180 58
        ];
181
182 58
        return $this;
183
    }
184
185
    /**
186
     * Configura la contraseña para proteger la clave privada.
187
     *
188
     * @param string $password Contraseña para proteger la clave privada.
189
     * @return void
190
     */
191 58
    public function setPassword(string $password = 'i_love_libredte')
192
    {
193 58
        $this->password = $password;
194
    }
195
196
    /**
197
     * Obtiene la contraseña configurada.
198
     *
199
     * @return string Contraseña configurada.
200
     */
201 2
    public function getPassword(): string
202
    {
203 2
        return $this->password;
204
    }
205
206
    /**
207
     * Genera un certificado digital en formato PKCS#12 y lo devuelve como un
208
     * string.
209
     *
210
     * @return string Certificado digital en formato PKCS#12.
211
     */
212 56
    public function createAsString(): string
213
    {
214
        // Días de validez del certificado (emisor y sujeto).
215 56
        $days = $this->validity['days'];
216
217
        // Crear clave privada y CSR para el emisor.
218 56
        $issuerPrivateKey = openssl_pkey_new();
219 56
        if (!$issuerPrivateKey) {
220
            throw new CertificateException(
221
                'No fue posible generar la llave privada del emisor del certificado.'
222
            );
223
        }
224 56
        $issuerCsr = openssl_csr_new($this->issuer, $issuerPrivateKey);
0 ignored issues
show
Bug introduced by
It seems like $issuerPrivateKey can also be of type resource; however, parameter $private_key of openssl_csr_new() does only seem to accept OpenSSLAsymmetricKey, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

224
        $issuerCsr = openssl_csr_new($this->issuer, /** @scrutinizer ignore-type */ $issuerPrivateKey);
Loading history...
225
226
        // Crear certificado autofirmado para el emisor.
227 56
        $issuerCert = openssl_csr_sign(
228 56
            $issuerCsr,         // CSR del emisor.
229 56
            null,               // Certificado emisor (null indica que es autofirmado).
230 56
            $issuerPrivateKey,  // Clave privada del emisor.
231 56
            $days,              // Número de días de validez (misma sujeto).
232 56
            [],                 // Opciones adicionales.
233 56
            666                 // Número de serie del certificado.
234 56
        );
235
236
        // Crear clave privada y CSR para el sujeto.
237 56
        $subjectPrivateKey = openssl_pkey_new();
238 56
        if (!$subjectPrivateKey) {
239
            throw new CertificateException(
240
                'No fue posible generar la llave privada del certificado.'
241
            );
242
        }
243 56
        $subjectCsr = openssl_csr_new($this->subject, $subjectPrivateKey);
244
245
        // Usar el certificado del emisor para firmar el CSR del sujeto.
246 56
        $subjectCert = openssl_csr_sign(
247 56
            $subjectCsr,        // La solicitud de firma del certificado (CSR).
248 56
            $issuerCert,        // Certificado emisor.
249 56
            $issuerPrivateKey,  // Clave privada del emisor.
250 56
            $days,              // Número de días de validez.
251 56
            [],                 // Opciones adicionales.
252 56
            69                  // Número de serie del certificado.
253 56
        );
254
255
        // Exportar el certificado final en formato PKCS#12.
256 56
        openssl_pkcs12_export(
257 56
            $subjectCert,
258 56
            $data,
259 56
            $subjectPrivateKey,
260 56
            $this->password
261 56
        );
262
263
        // Entregar los datos del certificado digital.
264 56
        return $data;
265
    }
266
267
    /**
268
     * Genera un certificado digital en formato PKCS#12 y lo devuelve como un
269
     * arreglo.
270
     *
271
     * @return array Certificado digital en formato PKCS#12.
272
     */
273 54
    public function createAsArray(): array
274
    {
275 54
        $data = $this->createAsString();
276 54
        $array = [];
277 54
        openssl_pkcs12_read($data, $array, $this->password);
278
279 54
        return $array;
280
    }
281
282
    /**
283
     * Genera un certificado digital y lo devuelve como una instancia de
284
     * Certificate.
285
     *
286
     * @return Certificate Instancia de Certificate.
287
     */
288 51
    public function create(): Certificate
289
    {
290 51
        $array = $this->createAsArray();
291
292 51
        return CertificateLoader::createFromArray($array);
293
    }
294
}
295