1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* LibreDTE |
5
|
|
|
* Copyright (C) SASCO SpA (https://sasco.cl) |
6
|
|
|
* |
7
|
|
|
* Este programa es software libre: usted puede redistribuirlo y/o |
8
|
|
|
* modificarlo bajo los términos de la Licencia Pública General Affero de GNU |
9
|
|
|
* publicada por la Fundación para el Software Libre, ya sea la versión |
10
|
|
|
* 3 de la Licencia, o (a su elección) cualquier versión posterior de la |
11
|
|
|
* misma. |
12
|
|
|
* |
13
|
|
|
* Este programa se distribuye con la esperanza de que sea útil, pero |
14
|
|
|
* SIN GARANTÍA ALGUNA; ni siquiera la garantía implícita |
15
|
|
|
* MERCANTIL o de APTITUD PARA UN PROPÓSITO DETERMINADO. |
16
|
|
|
* Consulte los detalles de la Licencia Pública General Affero de GNU para |
17
|
|
|
* 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
|
|
|
* En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>. |
22
|
|
|
*/ |
23
|
|
|
|
24
|
|
|
namespace sasco\LibreDTE; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Clase para trabajar con firma electrónica, permite firmar y verificar firmas. |
28
|
|
|
* Provee los métodos: sign(), verify(), signXML() y verifyXML() |
29
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
30
|
|
|
* @version 2019-02-12 |
31
|
|
|
*/ |
32
|
|
|
class FirmaElectronica |
33
|
|
|
{ |
34
|
|
|
|
35
|
|
|
private $config; ///< Configuración de la firma electrónica |
36
|
|
|
private $certs; ///< Certificados digitales de la firma |
37
|
|
|
private $data; ///< Datos del certificado digial |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* Constructor para la clase: crea configuración y carga certificado digital |
41
|
|
|
* |
42
|
|
|
* Si se desea pasar una configuración específica para la firma electrónica |
43
|
|
|
* se debe hacer a través de un arreglo con los índices file y pass, donde |
44
|
|
|
* file es la ruta hacia el archivo .p12 que contiene tanto la clave privada |
45
|
|
|
* como la pública y pass es la contraseña para abrir dicho archivo. |
46
|
|
|
* Ejemplo: |
47
|
|
|
* |
48
|
|
|
* \code{.php} |
49
|
|
|
* $firma_config = ['file'=>'/ruta/al/certificado.p12', 'pass'=>'contraseña']; |
50
|
|
|
* $firma = new \sasco\LibreDTE\FirmaElectronica($firma_config); |
51
|
|
|
* \endcode |
52
|
|
|
* |
53
|
|
|
* También se permite que en vez de pasar la ruta al certificado p12 se pase |
54
|
|
|
* el contenido del certificado, esto servirá por ejemplo si los datos del |
55
|
|
|
* archivo están almacenados en una base de datos. Ejemplo: |
56
|
|
|
* |
57
|
|
|
* \code{.php} |
58
|
|
|
* $firma_config = ['data'=>file_get_contents('/ruta/al/certificado.p12'), 'pass'=>'contraseña']; |
59
|
|
|
* $firma = new \sasco\LibreDTE\FirmaElectronica($firma_config); |
60
|
|
|
* \endcode |
61
|
|
|
* |
62
|
|
|
* @param config Configuración para la clase, si no se especifica se tratará de determinar |
63
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
64
|
|
|
* @version 2015-09-15 |
65
|
|
|
*/ |
66
|
|
|
public function __construct(array $config = []) |
67
|
|
|
{ |
68
|
|
|
// crear configuración |
69
|
|
|
if (!$config) { |
|
|
|
|
70
|
|
|
if (class_exists('\sowerphp\core\Configure')) { |
71
|
|
|
$config = (array)\sowerphp\core\Configure::read('firma_electronica.default'); |
72
|
|
|
} else { |
73
|
|
|
$config = []; |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
$this->config = array_merge([ |
77
|
|
|
'file' => null, |
78
|
|
|
'pass' => null, |
79
|
|
|
'data' => null, |
80
|
|
|
'wordwrap' => 64, |
81
|
|
|
], $config); |
82
|
|
|
// cargar firma electrónica desde el contenido del archivo .p12 si no |
83
|
|
|
// se pasaron como datos del arreglo de configuración |
84
|
|
|
if (!$this->config['data'] and $this->config['file']) { |
85
|
|
|
if (is_readable($this->config['file'])) { |
86
|
|
|
$this->config['data'] = file_get_contents($this->config['file']); |
87
|
|
|
} else { |
88
|
|
|
return $this->error('Archivo de la firma electrónica '.basename($this->config['file']).' no puede ser leído'); |
|
|
|
|
89
|
|
|
} |
90
|
|
|
} |
91
|
|
|
// leer datos de la firma electrónica |
92
|
|
|
if ($this->config['data'] and openssl_pkcs12_read($this->config['data'], $this->certs, $this->config['pass'])===false) { |
93
|
|
|
return $this->error('No fue posible leer los datos de la firma electrónica (verificar la contraseña)'); |
|
|
|
|
94
|
|
|
} |
95
|
|
|
$this->data = openssl_x509_parse($this->certs['cert']); |
96
|
|
|
// quitar datos del contenido del archivo de la firma |
97
|
|
|
unset($this->config['data']); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Método para generar un error usando una excepción de SowerPHP o terminar |
102
|
|
|
* el script si no se está usando el framework |
103
|
|
|
* @param msg Mensaje del error |
104
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
105
|
|
|
* @version 2017-08-04 |
106
|
|
|
*/ |
107
|
|
|
private function error($msg) |
108
|
|
|
{ |
109
|
|
|
if (class_exists('\sasco\LibreDTE\Estado') and class_exists('\sasco\LibreDTE\Log')) { |
110
|
|
|
$msg = \sasco\LibreDTE\Estado::get(\sasco\LibreDTE\Estado::FIRMA_ERROR, $msg); |
111
|
|
|
\sasco\LibreDTE\Log::write(\sasco\LibreDTE\Estado::FIRMA_ERROR, $msg); |
|
|
|
|
112
|
|
|
return false; |
113
|
|
|
} else { |
114
|
|
|
throw new \Exception($msg); |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Método que agrega el inicio y fin de un certificado (clave pública) |
120
|
|
|
* @param cert Certificado que se desea normalizar |
121
|
|
|
* @return Certificado con el inicio y fin correspondiente |
122
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
123
|
|
|
* @version 2015-08-20 |
124
|
|
|
*/ |
125
|
|
|
private function normalizeCert($cert) |
126
|
|
|
{ |
127
|
|
|
if (strpos($cert, '-----BEGIN CERTIFICATE-----')===false) { |
128
|
|
|
$body = trim($cert); |
129
|
|
|
$cert = '-----BEGIN CERTIFICATE-----'."\n"; |
130
|
|
|
$cert .= wordwrap($body, $this->config['wordwrap'], "\n", true)."\n"; |
131
|
|
|
$cert .= '-----END CERTIFICATE-----'."\n"; |
132
|
|
|
} |
133
|
|
|
return $cert; |
|
|
|
|
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
/** |
137
|
|
|
* Método que entrega el RUN/RUT asociado al certificado |
138
|
|
|
* @return RUN/RUT asociado al certificado en formato: 11222333-4 |
|
|
|
|
139
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
140
|
|
|
* @version 2016-02-12 |
141
|
|
|
*/ |
142
|
|
|
public function getID() |
143
|
|
|
{ |
144
|
|
|
// RUN/RUT se encuentra en la extensión del certificado, esto de acuerdo |
145
|
|
|
// a Ley 19.799 sobre documentos electrónicos y firma electrónica |
146
|
|
|
$x509 = new \phpseclib\File\X509(); |
147
|
|
|
$cert = $x509->loadX509($this->certs['cert']); |
148
|
|
|
if (isset($cert['tbsCertificate']['extensions'])) { |
149
|
|
|
foreach ($cert['tbsCertificate']['extensions'] as $e) { |
150
|
|
|
if ($e['extnId']=='id-ce-subjectAltName') { |
151
|
|
|
return ltrim($e['extnValue'][0]['otherName']['value']['ia5String'], '0'); |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
} |
155
|
|
|
// se obtiene desde serialNumber (esto es sólo para que funcione la firma para tests) |
156
|
|
|
if (isset($this->data['subject']['serialNumber'])) { |
157
|
|
|
return ltrim($this->data['subject']['serialNumber'], '0'); |
158
|
|
|
} |
159
|
|
|
// no se encontró el RUN |
160
|
|
|
return $this->error('No fue posible obtener el ID de la firma'); |
|
|
|
|
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Método que entrega el CN del subject |
165
|
|
|
* @return CN del subject |
166
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
167
|
|
|
* @version 2016-02-12 |
168
|
|
|
*/ |
169
|
|
|
public function getName() |
170
|
|
|
{ |
171
|
|
|
if (isset($this->data['subject']['CN'])) |
172
|
|
|
return $this->data['subject']['CN']; |
173
|
|
|
return $this->error('No fue posible obtener el Name (subject.CN) de la firma'); |
|
|
|
|
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Método que entrega el emailAddress del subject |
178
|
|
|
* @return emailAddress del subject |
179
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
180
|
|
|
* @version 2016-02-12 |
181
|
|
|
*/ |
182
|
|
|
public function getEmail() |
183
|
|
|
{ |
184
|
|
|
if (isset($this->data['subject']['emailAddress'])) { |
185
|
|
|
return $this->data['subject']['emailAddress']; |
186
|
|
|
} |
187
|
|
|
return $this->error('No fue posible obtener el Email (subject.emailAddress) de la firma'); |
|
|
|
|
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Método que entrega desde cuando es válida la firma |
192
|
|
|
* @return validFrom_time_t |
193
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
194
|
|
|
* @version 2015-09-22 |
195
|
|
|
*/ |
196
|
|
|
public function getFrom() |
197
|
|
|
{ |
198
|
|
|
return date('Y-m-d H:i:s', $this->data['validFrom_time_t']); |
199
|
|
|
} |
200
|
|
|
|
201
|
|
|
/** |
202
|
|
|
* Método que entrega hasta cuando es válida la firma |
203
|
|
|
* @return validTo_time_t |
204
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
205
|
|
|
* @version 2015-09-22 |
206
|
|
|
*/ |
207
|
|
|
public function getTo() |
208
|
|
|
{ |
209
|
|
|
return date('Y-m-d H:i:s', $this->data['validTo_time_t']); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
/** |
213
|
|
|
* Método que entrega los días totales que la firma es válida |
214
|
|
|
* @return int Días totales en que la firma es válida |
215
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
216
|
|
|
* @version 2019-02-12 |
217
|
|
|
*/ |
218
|
|
|
public function getTotalDays() |
219
|
|
|
{ |
220
|
|
|
$start = new \DateTime($this->getFrom()); |
221
|
|
|
$end = new \DateTime($this->getTo()); |
222
|
|
|
$diff = $start->diff($end); |
223
|
|
|
return $diff->format('%a'); |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Método que entrega los días que faltan para que la firma expire |
228
|
|
|
* @return int Días que faltan para que la firma expire |
229
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
230
|
|
|
* @version 2019-02-12 |
231
|
|
|
*/ |
232
|
|
|
public function getExpirationDays($desde = null) |
233
|
|
|
{ |
234
|
|
|
if (!$desde) { |
235
|
|
|
$desde = date('Y-m-d H:i:s'); |
236
|
|
|
} |
237
|
|
|
$start = new \DateTime($desde); |
238
|
|
|
$end = new \DateTime($this->getTo()); |
239
|
|
|
$diff = $start->diff($end); |
240
|
|
|
return $diff->format('%a'); |
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Método que entrega el nombre del emisor de la firma |
245
|
|
|
* @return CN del issuer |
246
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
247
|
|
|
* @version 2015-09-22 |
248
|
|
|
*/ |
249
|
|
|
public function getIssuer() |
250
|
|
|
{ |
251
|
|
|
return $this->data['issuer']['CN']; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* Método que entrega los datos del certificado |
256
|
|
|
* @return Arreglo con todo los datos del certificado |
257
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
258
|
|
|
* @version 2015-09-11 |
259
|
|
|
*/ |
260
|
|
|
public function getData() |
261
|
|
|
{ |
262
|
|
|
return $this->data; |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Método que obtiene el módulo de la clave privada |
267
|
|
|
* @return Módulo en base64 |
268
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
269
|
|
|
* @version 2014-12-07 |
270
|
|
|
*/ |
271
|
|
|
public function getModulus() |
272
|
|
|
{ |
273
|
|
|
$details = openssl_pkey_get_details(openssl_pkey_get_private($this->certs['pkey'])); |
274
|
|
|
return wordwrap(base64_encode($details['rsa']['n']), $this->config['wordwrap'], "\n", true); |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
/** |
278
|
|
|
* Método que obtiene el exponente público de la clave privada |
279
|
|
|
* @return Exponente público en base64 |
280
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
281
|
|
|
* @version 2014-12-06 |
282
|
|
|
*/ |
283
|
|
|
public function getExponent() |
284
|
|
|
{ |
285
|
|
|
$details = openssl_pkey_get_details(openssl_pkey_get_private($this->certs['pkey'])); |
286
|
|
|
return wordwrap(base64_encode($details['rsa']['e']), $this->config['wordwrap'], "\n", true); |
287
|
|
|
} |
288
|
|
|
|
289
|
|
|
/** |
290
|
|
|
* Método que entrega el certificado de la firma |
291
|
|
|
* @return Contenido del certificado, clave pública del certificado digital, en base64 |
292
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
293
|
|
|
* @version 2015-08-24 |
294
|
|
|
*/ |
295
|
|
View Code Duplication |
public function getCertificate($clean = false) |
|
|
|
|
296
|
|
|
{ |
297
|
|
|
if ($clean) { |
298
|
|
|
return trim(str_replace( |
|
|
|
|
299
|
|
|
['-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----'], |
300
|
|
|
'', |
301
|
|
|
$this->certs['cert'] |
302
|
|
|
)); |
303
|
|
|
} else { |
304
|
|
|
return $this->certs['cert']; |
305
|
|
|
} |
306
|
|
|
} |
307
|
|
|
|
308
|
|
|
/** |
309
|
|
|
* Método que entrega la clave privada de la firma |
310
|
|
|
* @return Contenido de la clave privada del certificado digital en base64 |
311
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
312
|
|
|
* @version 2015-08-24 |
313
|
|
|
*/ |
314
|
|
View Code Duplication |
public function getPrivateKey($clean = false) |
|
|
|
|
315
|
|
|
{ |
316
|
|
|
if ($clean) { |
317
|
|
|
return trim(str_replace( |
|
|
|
|
318
|
|
|
['-----BEGIN PRIVATE KEY-----', '-----END PRIVATE KEY-----'], |
319
|
|
|
'', |
320
|
|
|
$this->certs['pkey'] |
321
|
|
|
)); |
322
|
|
|
} else { |
323
|
|
|
return $this->certs['pkey']; |
324
|
|
|
} |
325
|
|
|
} |
326
|
|
|
|
327
|
|
|
/** |
328
|
|
|
* Método para realizar la firma de datos |
329
|
|
|
* @param data Datos que se desean firmar |
330
|
|
|
* @param signature_alg Algoritmo que se utilizará para firmar (por defect SHA1) |
331
|
|
|
* @return Firma digital de los datos en base64 o =false si no se pudo firmar |
332
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
333
|
|
|
* @version 2014-12-08 |
334
|
|
|
*/ |
335
|
|
|
public function sign($data, $signature_alg = OPENSSL_ALGO_SHA1) |
336
|
|
|
{ |
337
|
|
|
$signature = null; |
338
|
|
|
if (openssl_sign($data, $signature, $this->certs['pkey'], $signature_alg)==false) { |
|
|
|
|
339
|
|
|
return $this->error('No fue posible firmar los datos'); |
|
|
|
|
340
|
|
|
} |
341
|
|
|
return base64_encode($signature); |
342
|
|
|
} |
343
|
|
|
|
344
|
|
|
/** |
345
|
|
|
* Método que verifica la firma digital de datos |
346
|
|
|
* @param data Datos que se desean verificar |
347
|
|
|
* @param signature Firma digital de los datos en base64 |
348
|
|
|
* @param pub_key Certificado digital, clave pública, de la firma |
349
|
|
|
* @param signature_alg Algoritmo que se usó para firmar (por defect SHA1) |
350
|
|
|
* @return =true si la firma está ok, =false si está mal o no se pudo determinar |
|
|
|
|
351
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
352
|
|
|
* @version 2014-12-08 |
353
|
|
|
*/ |
354
|
|
|
public function verify($data, $signature, $pub_key = null, $signature_alg = OPENSSL_ALGO_SHA1) |
355
|
|
|
{ |
356
|
|
|
if ($pub_key === null) |
357
|
|
|
$pub_key = $this->certs['cert']; |
358
|
|
|
$pub_key = $this->normalizeCert($pub_key); |
359
|
|
|
return openssl_verify($data, base64_decode($signature), $pub_key, $signature_alg) == 1 ? true : false; |
360
|
|
|
} |
361
|
|
|
|
362
|
|
|
/** |
363
|
|
|
* Método que firma un XML utilizando RSA y SHA1 |
364
|
|
|
* |
365
|
|
|
* Referencia: http://www.di-mgt.com.au/xmldsig2.html |
366
|
|
|
* |
367
|
|
|
* @param xml Datos XML que se desean firmar |
368
|
|
|
* @param reference Referencia a la que hace la firma |
369
|
|
|
* @return XML firmado o =false si no se pudo fimar |
370
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
371
|
|
|
* @version 2017-10-22 |
372
|
|
|
*/ |
373
|
|
|
public function signXML($xml, $reference = '', $tag = null, $xmlns_xsi = false) |
374
|
|
|
{ |
375
|
|
|
// normalizar 4to parámetro que puede ser boolean o array |
376
|
|
|
if (is_array($xmlns_xsi)) { |
377
|
|
|
$namespace = $xmlns_xsi; |
378
|
|
|
$xmlns_xsi = false; |
379
|
|
|
} else { |
380
|
|
|
$namespace = null; |
381
|
|
|
} |
382
|
|
|
// obtener objeto del XML que se va a firmar |
383
|
|
|
$doc = new XML(); |
384
|
|
|
$doc->loadXML($xml); |
385
|
|
|
if (!$doc->documentElement) { |
386
|
|
|
return $this->error('No se pudo obtener el documentElement desde el XML a firmar (posible XML mal formado)'); |
|
|
|
|
387
|
|
|
} |
388
|
|
|
// crear nodo para la firma |
389
|
|
|
$Signature = $doc->importNode((new XML())->generate([ |
390
|
|
|
'Signature' => [ |
391
|
|
|
'@attributes' => $namespace ? false : [ |
392
|
|
|
'xmlns' => 'http://www.w3.org/2000/09/xmldsig#', |
393
|
|
|
], |
394
|
|
|
'SignedInfo' => [ |
395
|
|
|
'@attributes' => $namespace ? false : [ |
396
|
|
|
'xmlns' => 'http://www.w3.org/2000/09/xmldsig#', |
397
|
|
|
'xmlns:xsi' => $xmlns_xsi ? 'http://www.w3.org/2001/XMLSchema-instance' : false, |
398
|
|
|
], |
399
|
|
|
'CanonicalizationMethod' => [ |
400
|
|
|
'@attributes' => [ |
401
|
|
|
'Algorithm' => 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315', |
402
|
|
|
], |
403
|
|
|
], |
404
|
|
|
'SignatureMethod' => [ |
405
|
|
|
'@attributes' => [ |
406
|
|
|
'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1', |
407
|
|
|
], |
408
|
|
|
], |
409
|
|
|
'Reference' => [ |
410
|
|
|
'@attributes' => [ |
411
|
|
|
'URI' => $reference, |
412
|
|
|
], |
413
|
|
|
'Transforms' => [ |
414
|
|
|
'Transform' => [ |
415
|
|
|
'@attributes' => [ |
416
|
|
|
'Algorithm' => $namespace ? 'http://www.altova.com' : 'http://www.w3.org/2000/09/xmldsig#enveloped-signature', |
417
|
|
|
], |
418
|
|
|
], |
419
|
|
|
], |
420
|
|
|
'DigestMethod' => [ |
421
|
|
|
'@attributes' => [ |
422
|
|
|
'Algorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1', |
423
|
|
|
], |
424
|
|
|
], |
425
|
|
|
'DigestValue' => null, |
426
|
|
|
], |
427
|
|
|
], |
428
|
|
|
'SignatureValue' => null, |
429
|
|
|
'KeyInfo' => [ |
430
|
|
|
'KeyValue' => [ |
431
|
|
|
'RSAKeyValue' => [ |
432
|
|
|
'Modulus' => null, |
433
|
|
|
'Exponent' => null, |
434
|
|
|
], |
435
|
|
|
], |
436
|
|
|
'X509Data' => [ |
437
|
|
|
'X509Certificate' => null, |
438
|
|
|
], |
439
|
|
|
], |
440
|
|
|
], |
441
|
|
|
], $namespace)->documentElement, true); |
442
|
|
|
// calcular DigestValue |
443
|
|
|
if ($tag) { |
444
|
|
|
$item = $doc->documentElement->getElementsByTagName($tag)->item(0); |
445
|
|
|
if (!$item) { |
446
|
|
|
return $this->error('No fue posible obtener el nodo con el tag '.$tag); |
|
|
|
|
447
|
|
|
} |
448
|
|
|
$digest = base64_encode(sha1($item->C14N(), true)); |
449
|
|
|
} else { |
450
|
|
|
$digest = base64_encode(sha1($doc->C14N(), true)); |
451
|
|
|
} |
452
|
|
|
$Signature->getElementsByTagName('DigestValue')->item(0)->nodeValue = $digest; |
453
|
|
|
// calcular SignatureValue |
454
|
|
|
$SignedInfo = $doc->saveHTML($Signature->getElementsByTagName('SignedInfo')->item(0)); |
455
|
|
|
$firma = $this->sign($SignedInfo); |
|
|
|
|
456
|
|
|
if (!$firma) |
457
|
|
|
return false; |
458
|
|
|
$signature = wordwrap($firma, $this->config['wordwrap'], "\n", true); |
459
|
|
|
// reemplazar valores en la firma de |
460
|
|
|
$Signature->getElementsByTagName('SignatureValue')->item(0)->nodeValue = $signature; |
461
|
|
|
$Signature->getElementsByTagName('Modulus')->item(0)->nodeValue = $this->getModulus(); |
462
|
|
|
$Signature->getElementsByTagName('Exponent')->item(0)->nodeValue = $this->getExponent(); |
463
|
|
|
$Signature->getElementsByTagName('X509Certificate')->item(0)->nodeValue = $this->getCertificate(true); |
464
|
|
|
// agregar y entregar firma |
465
|
|
|
$doc->documentElement->appendChild($Signature); |
466
|
|
|
return $doc->saveXML(); |
467
|
|
|
} |
468
|
|
|
|
469
|
|
|
/** |
470
|
|
|
* Método que verifica la validez de la firma de un XML utilizando RSA y SHA1 |
471
|
|
|
* @param xml_data Archivo XML que se desea validar |
472
|
|
|
* @return =true si la firma del documento XML es válida o =false si no lo es |
|
|
|
|
473
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
474
|
|
|
* @version 2015-09-02 |
475
|
|
|
*/ |
476
|
|
|
public function verifyXML($xml_data, $tag = null) |
477
|
|
|
{ |
478
|
|
|
$doc = new XML(); |
479
|
|
|
$doc->loadXML($xml_data); |
480
|
|
|
// preparar datos que se verificarán |
481
|
|
|
$SignaturesElements = $doc->documentElement->getElementsByTagName('Signature'); |
482
|
|
|
$Signature = $doc->documentElement->removeChild($SignaturesElements->item($SignaturesElements->length-1)); |
483
|
|
|
$SignedInfo = $Signature->getElementsByTagName('SignedInfo')->item(0); |
484
|
|
|
$SignedInfo->setAttribute('xmlns', $Signature->getAttribute('xmlns')); |
485
|
|
|
$signed_info = $doc->saveHTML($SignedInfo); |
486
|
|
|
$signature = $Signature->getElementsByTagName('SignatureValue')->item(0)->nodeValue; |
487
|
|
|
$pub_key = $Signature->getElementsByTagName('X509Certificate')->item(0)->nodeValue; |
488
|
|
|
// verificar firma |
489
|
|
|
if (!$this->verify($signed_info, $signature, $pub_key)) |
|
|
|
|
490
|
|
|
return false; |
491
|
|
|
// verificar digest |
492
|
|
|
$digest_original = $Signature->getElementsByTagName('DigestValue')->item(0)->nodeValue; |
493
|
|
|
if ($tag) { |
494
|
|
|
$digest_calculado = base64_encode(sha1($doc->documentElement->getElementsByTagName($tag)->item(0)->C14N(), true)); |
495
|
|
|
} else { |
496
|
|
|
$digest_calculado = base64_encode(sha1($doc->C14N(), true)); |
497
|
|
|
} |
498
|
|
|
return $digest_original == $digest_calculado; |
499
|
|
|
} |
500
|
|
|
|
501
|
|
|
/** |
502
|
|
|
* Método que obtiene la clave asociada al módulo y exponente entregados |
503
|
|
|
* @param modulus Módulo de la clave |
504
|
|
|
* @param exponent Exponente de la clave |
505
|
|
|
* @return Entrega la clave asociada al módulo y exponente |
506
|
|
|
* @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl) |
507
|
|
|
* @version 2015-09-19 |
508
|
|
|
*/ |
509
|
|
|
public static function getFromModulusExponent($modulus, $exponent) |
510
|
|
|
{ |
511
|
|
|
$rsa = new \phpseclib\Crypt\RSA(); |
512
|
|
|
$modulus = new \phpseclib\Math\BigInteger(base64_decode($modulus), 256); |
513
|
|
|
$exponent = new \phpseclib\Math\BigInteger(base64_decode($exponent), 256); |
514
|
|
|
$rsa->loadKey(['n' => $modulus, 'e' => $exponent]); |
515
|
|
|
$rsa->setPublicKey(); |
516
|
|
|
return $rsa->getPublicKey(); |
517
|
|
|
} |
518
|
|
|
|
519
|
|
|
} |
520
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.