Completed
Push — master ( 9be02b...07dee3 )
by Roberto
07:17 queued 04:47
created

Pkcs12::zValidCerts()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3.0987

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 27
ccs 14
cts 18
cp 0.7778
rs 8.8571
cc 3
eloc 18
nc 3
nop 1
crap 3.0987
1
<?php
2
3
namespace NFePHP\Common\Certificate;
4
5
/**
6
 * Classe para tratamento e uso dos certificados digitais modelo A1 (PKCS12)
7
 *
8
 * @category   NFePHP
9
 * @package    NFePHP\Common\Certificate
10
 * @copyright  Copyright (c) 2008-2014
11
 * @license    http://www.gnu.org/licenses/lesser.html LGPL v3
12
 * @author     Roberto L. Machado <linux.rlm at gmail dot com>
13
 * @link       http://github.com/nfephp-org/nfephp for the canonical source repository
14
 */
15
16
use NFePHP\Common\Certificate\Asn;
17
use NFePHP\Common\Exception;
18
use NFePHP\Common\Dom\Dom;
19
20
class Pkcs12
21
{
22
    /**
23
     * Path para o diretorio onde o arquivo pfx está localizado
24
     * @var string
25
     */
26
    public $pathCerts = '';
27
    
28
    /**
29
     * Path para o arquivo pfx (certificado digital em formato de transporte)
30
     * @var string
31
     */
32
    public $pfxFileName = '';
33
    
34
    /**
35
     * Conteudo do arquivo pfx
36
     * @var string
37
     */
38
    public $pfxCert = '';
39
    
40
    /**
41
     * Numero do CNPJ do emitente
42
     * @var string
43
     */
44
    public $cnpj = '';
45
    
46
    /**
47
     * String que contêm a chave publica em formato PEM
48
     * @var string
49
     */
50
    public $pubKey = '';
51
    
52
    /**
53
     * String quem contêm a chave privada em formato PEM
54
     * @var string
55
     */
56
    public $priKey = '';
57
    
58
    /**
59
     * String que conten a combinação da chave publica e privada em formato PEM
60
     * e a cadeida completa de certificação caso exista
61
     * @var string
62
     */
63
    public $certKey = '';
64
    
65
    /**
66
     * Flag para ignorar testes de validade do certificado
67
     * isso é usado apenas para fins de testes
68
     * @var boolean
69
     */
70
    public $ignoreValidCert = false;
71
    
72
    /**
73
     * Path para a chave publica em arquivo
74
     * @var string
75
     */
76
    public $pubKeyFile = '';
77
    
78
    /**
79
     * Path para a chave privada em arquivo
80
     * @var string
81
     */
82
    public $priKeyFile = '';
83
    
84
    /**
85
     * Path para o certificado em arquivo
86
     * @var string
87
     */
88
    public $certKeyFile = '';
89
    
90
    /**
91
     * Timestamp da data de validade do certificado
92
     * @var float
93
     */
94
    public $expireTimestamp = 0;
95
    
96
    /**
97
     * CNPJ do certificado
98
     * @var string
99
     */
100
    public $cnpjCert = '';
101
    
102
    /**
103
     * Mensagem de erro da classe
104
     * @var string
105
     */
106
    public $error = '';
107
    
108
    /**
109
     * Id do documento sendo assinado
110
     * @var string
111
     */
112
    public $docId = '';
113
114
    /**
115
     * Método de construção da classe
116
     * @param string $pathCerts Path para a pasta que contêm os certificados digitais
117
     * @param string $cnpj CNPJ do emitente, sem  ./-, apenas os numeros
118
     * @param string $pubKey Chave publica em formato PEM, não o path mas a chave em si
119
     * @param string $priKey Chave privada em formato PEM, não o path mas a chave em si
120
     * @param string $certKey Certificado em formato PEM, não o path mas a chave em si
121
     * @param bool $ignoreValidCert
122
     * @param boolean $ignoreValidCert Ignora a validade do certificado, mais usado para fins de teste
123
     */
124 24
    public function __construct(
125
        $pathCerts = '',
126
        $cnpj = '',
127
        $pubKey = '',
128
        $priKey = '',
129
        $certKey = '',
130
        $ignoreValidCert = false
131
    ) {
132 24
        $ncnpj = preg_replace('/[^0-9]/', '', $cnpj);
0 ignored issues
show
Unused Code introduced by
$ncnpj is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
133 24
        if (empty($pathCerts)) {
134
            //estabelecer diretorio default
135
            $pathCerts = dirname(dirname(dirname(dirname(__FILE__)))).DIRECTORY_SEPARATOR.'certs'.DIRECTORY_SEPARATOR;
136
        }
137 24
        if (! empty($pathCerts)) {
138 24
            if (!is_dir(trim($pathCerts))) {
139 3
                throw new Exception\InvalidArgumentException(
140
                    "Um path válido para os certificados deve ser passado."
141 3
                    . " Diretório [$pathCerts] não foi localizado."
142 3
                );
143
            }
144 21
            $this->pathCerts = trim($pathCerts);
145 21
        }
146 21
        $this->ignoreValidCert = $ignoreValidCert;
147 21
        $flagCert = false;
148 21
        if ($pubKey != '' && $priKey != '' && strlen($pubKey) > 500 && strlen($priKey) > 500) {
149 9
            $this->pubKey = $pubKey;
150 9
            $this->priKey = $priKey;
151 9
            $this->certKey = $priKey."\r\n".$pubKey;
152 9
            $flagCert = true;
153 9
        }
154 21
        if ($certKey != '') {
155
            $this->certKey = $certKey;
156
        }
157 21
        $this->cnpj = $cnpj;
158 21
        if (! $this->zInit($flagCert)) {
159 3
            throw new Exception\RuntimeException($this->error);
160
        }
161 18
    }
162
    
163
    /**
164
     * zInit
165
     * Método de inicialização da classe irá verificar
166
     * os parâmetros, arquivos e validade dos mesmos
167
     * Em caso de erro o motivo da falha será indicada na parâmetro
168
     * error da classe, os outros parâmetros serão limpos e os
169
     * arquivos inválidos serão removidos da pasta
170
     * @param boolean $flagCert indica que as chaves já foram passas como strings
171
     * @return boolean
172
     */
173 21
    private function zInit($flagCert = false)
174
    {
175
        //se as chaves foram passadas na forma de strings então verificar a validade
176 21
        if ($flagCert) {
177
            //já que o certificado existe, verificar seu prazo de validade
178
            //o certificado será removido se estiver vencido
179 9
            return $this->zValidCerts($this->pubKey);
180
        } else {
181 12
            if (substr($this->pathCerts, -1) !== DIRECTORY_SEPARATOR) {
182
                $this->pathCerts .= DIRECTORY_SEPARATOR;
183
            }
184
            //monta o path completo com o nome da chave privada
185 12
            $this->priKeyFile = $this->pathCerts.$this->cnpj.'_priKEY.pem';
186
            //monta o path completo com o nome da chave publica
187 12
            $this->pubKeyFile =  $this->pathCerts.$this->cnpj.'_pubKEY.pem';
188
            //monta o path completo com o nome do certificado (chave publica e privada) em formato pem
189 12
            $this->certKeyFile = $this->pathCerts.$this->cnpj.'_certKEY.pem';
190
            //se as chaves não foram passadas em strings, verifica se os certificados existem
191 12
            if (is_file($this->priKeyFile) && is_file($this->pubKeyFile) && is_file($this->certKeyFile)) {
192
                //se as chaves existem deve ser verificado sua validade
193
                $this->pubKey = file_get_contents($this->pubKeyFile);
194
                $this->priKey = file_get_contents($this->priKeyFile);
195
                $this->certKey = file_get_contents($this->certKeyFile);
196
                //já que o certificado existe, verificar seu prazo de validade
197
                return $this->zValidCerts($this->pubKey);
198
            }
199
        }
200 12
        return true;
201
    }
202
203
    /**
204
     * loadPfxFile
205
     * @param string $pathPfx caminho completo para o arquivo pfx
206
     * @param string $password senha para abrir o certificado pfx
207
     * @param bool $createFiles
208
     * @param bool $ignoreValidity
209
     * @param bool $ignoreOwner
210
     * @return bool
211
     */
212 3
    public function loadPfxFile(
213
        $pathPfx = '',
214
        $password = '',
215
        $createFiles = true,
216
        $ignoreValidity = false,
217
        $ignoreOwner = false
218
    ) {
219 3
        if (! is_file($pathPfx)) {
220
            throw new Exception\InvalidArgumentException(
221
                "O nome do arquivo PFX deve ser passado. Não foi localizado o arquivo [$pathPfx]."
222
            );
223
        }
224 3
        $this->pfxCert = file_get_contents($pathPfx);
225 3
        return $this->loadPfx($this->pfxCert, $password, $createFiles, $ignoreValidity, $ignoreOwner);
226
    }
227
228
    /**
229
     * loadPfx
230
     * Carrega um novo certificado no formato PFX
231
     * Isso deverá ocorrer a cada atualização do certificado digital, ou seja,
232
     * pelo menos uma vez por ano, uma vez que a validade do certificado
233
     * é anual.
234
     * Será verificado também se o certificado pertence realmente ao CNPJ
235
     * Essa verificação checa apenas se o certificado pertence a matriz ou filial
236
     * comparando apenas os primeiros 8 digitos do CNPJ, dessa forma ambas a
237
     * matriz e as filiais poderão usar o mesmo certificado indicado na instanciação
238
     * da classe, se não for um erro irá ocorrer e
239
     * o certificado não será convertido para o formato PEM.
240
     * Em caso de erros, será retornado false e o motivo será indicado no
241
     * parâmetro error da classe.
242
     * Os certificados serão armazenados como <CNPJ>-<tipo>.pem
243
     * @param string $pfxContent arquivo PFX
244
     * @param string $password Senha de acesso ao certificado PFX
245
     * @param boolean $createFiles se true irá criar os arquivos pem das chaves digitais, caso contrario não
246
     * @param bool $ignoreValidity
247
     * @param bool $ignoreOwner
248
     * @return bool
249
     */
250 9
    public function loadPfx(
251
        $pfxContent = '',
252
        $password = '',
253
        $createFiles = true,
254
        $ignoreValidity = false,
255
        $ignoreOwner = false
256
    ) {
257 9
        $this->ignoreValidCert = $ignoreValidity;
258 9
        if ($password == '') {
259
            throw new Exception\InvalidArgumentException(
260
                "A senha de acesso para o certificado pfx não pode ser vazia."
261
            );
262
        }
263
        //carrega os certificados e chaves para um array denominado $x509certdata
264 9
        $x509certdata = array();
265 9
        if (!openssl_pkcs12_read($pfxContent, $x509certdata, $password)) {
266
            throw new Exception\RuntimeException(
267
                "O certificado não pode ser lido!! Senha errada ou arquivo corrompido ou formato inválido!!"
268
            );
269
        }
270 9
        $this->pfxCert = $pfxContent;
271
        //verifica sua data de validade
272 9
        if (! $this->zValidCerts($x509certdata['cert'])) {
273
            throw new Exception\RuntimeException($this->error);
274
        }
275 9
        $this->cnpjCert = Asn::getCNPJCert($x509certdata['cert']);
276 9
        if (!$ignoreOwner) {
277 3
            if (substr($this->cnpj, 0, 8) != substr($this->cnpjCert, 0, 8)) {
278 3
                throw new Exception\InvalidArgumentException(
279
                    "O Certificado fornecido pertence a outro CNPJ!!"
280 3
                );
281
            }
282
        }
283
        //monta o path completo com o nome da chave privada
284 6
        $this->priKeyFile = $this->pathCerts.$this->cnpj.'_priKEY.pem';
285
        //monta o path completo com o nome da chave publica
286 6
        $this->pubKeyFile =  $this->pathCerts.$this->cnpj.'_pubKEY.pem';
287
        //monta o path completo com o nome do certificado (chave publica e privada) em formato pem
288 6
        $this->certKeyFile = $this->pathCerts.$this->cnpj.'_certKEY.pem';
289 6
        $this->zRemovePemFiles();
290 6
        if ($createFiles) {
291
            $this->zSavePemFiles($x509certdata);
292
        }
293 6
        $this->pubKey=$x509certdata['cert'];
294 6
        $this->priKey=$x509certdata['pkey'];
295 6
        $this->certKey=$x509certdata['pkey']."\r\n".$x509certdata['cert'];
296 6
        return true;
297
    }
298
    
299
    /**
300
     * zSavePemFiles
301
     * @param array $x509certdata
302
     * @throws Exception\InvalidArgumentException
303
     * @throws Exception\RuntimeException
304
     */
305
    private function zSavePemFiles($x509certdata)
306
    {
307
        if (empty($this->pathCerts)) {
308
            throw new Exception\InvalidArgumentException(
309
                "Não está definido o diretório para armazenar os certificados."
310
            );
311
        }
312
        if (! is_dir($this->pathCerts)) {
313
            throw new Exception\InvalidArgumentException(
314
                "Não existe o diretório para armazenar os certificados."
315
            );
316
        }
317
        //recriar os arquivos pem com o arquivo pfx
318
        if (!file_put_contents($this->priKeyFile, $x509certdata['pkey'])) {
319
            throw new Exception\RuntimeException(
320
                "Falha de permissão de escrita na pasta dos certificados!!"
321
            );
322
        }
323
        file_put_contents($this->pubKeyFile, $x509certdata['cert']);
324
        file_put_contents($this->certKeyFile, $x509certdata['pkey']."\r\n".$x509certdata['cert']);
325
    }
326
    
327
    /**
328
     * Retorna o timestamp da validade do certificado
329
     * @return int
330
     */
331
    public function getValidate()
332
    {
333
        return $this->expireTimestamp;
334
    }
335
    
336
    /**
337
     * Retorna o CNPJ do certificado
338
     * @return string
339
     */
340
    public function getCNPJCert()
341
    {
342
        return $this->cnpjCert;
343
    }
344
    
345
    /**
346
     * aadChain
347
     * @param array $aCerts Array com os caminhos completos para cada certificado da cadeia
348
     *                     ou um array com o conteúdo desses certificados
349
     * @return void
350
     */
351 3
    public function aadChain($aCerts = array())
352
    {
353 3
        $certificate = $this->certKey;
354 3
        foreach ($aCerts as $cert) {
355 3
            if (is_file($cert)) {
356 3
                $dados = file_get_contents($cert);
357 3
                $certificate .= "\r\n" . $dados;
358 3
            } else {
359
                if (trim($cert) != '') {
360
                    $certificate .= "\r\n" . $cert;
361
                }    
362
            }
363 3
        }
364 3
        $this->certKey = $certificate;
365 3
        if (is_file($this->certKeyFile)) {
366
            file_put_contents($this->certKeyFile, $certificate);
367
        }
368 3
    }
369
    
370
    /**
371
     * signXML
372
     * @param string $docxml
373
     * @param string $tagid
374
     * @param string $marcador
375
     * @param string $algorithm
376
     * @return string xml assinado
377
     * @throws Exception\InvalidArgumentException
378
     * @throws Exception\RuntimeException
379
     */
380 3
    public function signXML($docxml, $tagid = '', $marcador = 'Id', $algorithm = 'SHA1')
381
    {
382
        //caso não tenha as chaves cai fora
383 3
        if ($this->pubKey == '' || $this->priKey == '') {
384
            $msg = "As chaves não estão disponíveis.";
385
            throw new Exception\InvalidArgumentException($msg);
386
        }
387
        //caso não seja informada a tag a ser assinada cai fora
388 3
        if ($tagid == '') {
389
            $msg = "A tag a ser assinada deve ser indicada.";
390
            throw new Exception\InvalidArgumentException($msg);
391
        }
392
        //carrega a chave privada no openssl
393 3
        $objSSLPriKey = openssl_get_privatekey($this->priKey);
394 3
        if ($objSSLPriKey === false) {
395
            $msg = "Houve erro no carregamento da chave privada.";
396
            $this->zGetOpenSSLError($msg);
397
        }
398 3
        $xml = $docxml;
399 3
        if (is_file($docxml)) {
400
            $xml = file_get_contents($docxml);
401
        }
402
        //remove sujeiras do xml
403 3
        $order = array("\r\n", "\n", "\r", "\t");
404 3
        $xml = str_replace($order, '', $xml);
405 3
        $xmldoc = new Dom();
406 3
        $xmldoc->loadXMLString($xml);
407
        //coloca o node raiz em uma variável
408 3
        $root = $xmldoc->documentElement;
409
        //extrair a tag com os dados a serem assinados
410 3
        $node = $xmldoc->getElementsByTagName($tagid)->item(0);
411 3
        if (!isset($node)) {
412
            throw new Exception\RuntimeException(
413
                "A tag < $tagid > não existe no XML!!"
414
            );
415
        }
416 3
        $this->docId = $node->getAttribute($marcador);
417 3
        $xmlResp = $xml;
418 3
        if (! $this->zSignatureExists($xmldoc)) {
0 ignored issues
show
Documentation introduced by
$xmldoc is of type object<NFePHP\Common\Dom\Dom>, but the function expects a object<NFePHP\Common\Certificate\DOMDocument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
419
            //executa a assinatura
420 3
            $xmlResp = $this->zSignXML($xmldoc, $root, $node, $objSSLPriKey, $marcador, $algorithm);
0 ignored issues
show
Documentation introduced by
$xmldoc is of type object<NFePHP\Common\Dom\Dom>, but the function expects a object<NFePHP\Common\Certificate\DOMDocument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$root is of type object<DOMElement>, but the function expects a object<NFePHP\Common\Certificate\DOMElement>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$node is of type object<DOMNode>, but the function expects a object<NFePHP\Common\Certificate\DOMElement>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
421 3
        }
422
        //libera a chave privada
423 3
        openssl_free_key($objSSLPriKey);
424 3
        return $xmlResp;
425
    }
426
427
    /**
428
     * zSignXML
429
     * Método que provê a assinatura do xml conforme padrão SEFAZ
430
     * @param DOMDocument $xmldoc
431
     * @param DOMElement $root
432
     * @param DOMElement $node
433
     * @param resource $objSSLPriKey
434
     * @param string $marcador
435
     * @param string $algorithm
436
     * @return string xml assinado
437
     * @internal param DOMDocument $xmlDoc
438
     */
439 3
    private function zSignXML($xmldoc, $root, $node, $objSSLPriKey, $marcador, $algorithm = 'SHA1')
440
    {
441 3
        $nsDSIG = 'http://www.w3.org/2000/09/xmldsig#';
442 3
        $nsCannonMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
443 3
        $nsSignatureMethod = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
444 3
        $nsDigestMethod = 'http://www.w3.org/2000/09/xmldsig#sha1';
445 3
        $signAlgorithm = OPENSSL_ALGO_SHA1;
446
        //incluido para atender requisitos de assinatura do sped-efinanceira
447 3
        if ($algorithm == 'SHA256') {
448
            $signAlgorithm = OPENSSL_ALGO_SHA256;
449
            $nsSignatureMethod = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
450
            $nsDigestMethod = 'http://www.w3.org/2001/04/xmlenc#sha256';
451
        }
452 3
        $nsTransformMethod1 ='http://www.w3.org/2000/09/xmldsig#enveloped-signature';
453 3
        $nsTransformMethod2 = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
454
        //pega o atributo id do node a ser assinado
455 3
        $idSigned = trim($node->getAttribute($marcador));
456
        //extrai os dados da tag para uma string na forma canonica
457 3
        $dados = $node->C14N(true, false, null, null);
458
        //calcular o hash dos dados
459 3
        if ($algorithm == 'SHA256') {
460
            $hashValue = hash('sha256', $dados, true);
461
        } else {
462 3
            $hashValue = hash('sha1', $dados, true);
463
        }
464
        //converter o hash para base64
465 3
        $digValue = base64_encode($hashValue);
466
        //cria o node <Signature>
467 3
        $signatureNode = $xmldoc->createElementNS($nsDSIG, 'Signature');
468
        //adiciona a tag <Signature> ao node raiz
469 3
        $root->appendChild($signatureNode);
470
        //cria o node <SignedInfo>
471 3
        $signedInfoNode = $xmldoc->createElement('SignedInfo');
472
        //adiciona o node <SignedInfo> ao <Signature>
473 3
        $signatureNode->appendChild($signedInfoNode);
474
        //cria no node com o método de canonização dos dados
475 3
        $canonicalNode = $xmldoc->createElement('CanonicalizationMethod');
476
        //adiona o <CanonicalizationMethod> ao node <SignedInfo>
477 3
        $signedInfoNode->appendChild($canonicalNode);
478
        //seta o atributo ao node <CanonicalizationMethod>
479 3
        $canonicalNode->setAttribute('Algorithm', $nsCannonMethod);
480
        //cria o node <SignatureMethod>
481 3
        $signatureMethodNode = $xmldoc->createElement('SignatureMethod');
482
        //adiciona o node <SignatureMethod> ao node <SignedInfo>
483 3
        $signedInfoNode->appendChild($signatureMethodNode);
484
        //seta o atributo Algorithm ao node <SignatureMethod>
485 3
        $signatureMethodNode->setAttribute('Algorithm', $nsSignatureMethod);
486
        //cria o node <Reference>
487 3
        $referenceNode = $xmldoc->createElement('Reference');
488
        //adiciona o node <Reference> ao node <SignedInfo>
489 3
        $signedInfoNode->appendChild($referenceNode);
490
        //seta o atributo URI a node <Reference>
491 3
        $referenceNode->setAttribute('URI', '#'.$idSigned);
492
        //cria o node <Transforms>
493 3
        $transformsNode = $xmldoc->createElement('Transforms');
494
        //adiciona o node <Transforms> ao node <Reference>
495 3
        $referenceNode->appendChild($transformsNode);
496
        //cria o primeiro node <Transform> OBS: no singular
497 3
        $transfNode1 = $xmldoc->createElement('Transform');
498
        //adiciona o primeiro node <Transform> ao node <Transforms>
499 3
        $transformsNode->appendChild($transfNode1);
500
        //set o atributo Algorithm ao primeiro node <Transform>
501 3
        $transfNode1->setAttribute('Algorithm', $nsTransformMethod1);
502
        //cria outro node <Transform> OBS: no singular
503 3
        $transfNode2 = $xmldoc->createElement('Transform');
504
        //adiciona o segundo node <Transform> ao node <Transforms>
505 3
        $transformsNode->appendChild($transfNode2);
506
        //set o atributo Algorithm ao segundo node <Transform>
507 3
        $transfNode2->setAttribute('Algorithm', $nsTransformMethod2);
508
        //cria o node <DigestMethod>
509 3
        $digestMethodNode = $xmldoc->createElement('DigestMethod');
510
        //adiciona o node <DigestMethod> ao node <Reference>
511 3
        $referenceNode->appendChild($digestMethodNode);
512
        //seta o atributo Algorithm ao node <DigestMethod>
513 3
        $digestMethodNode->setAttribute('Algorithm', $nsDigestMethod);
514
        //cria o node <DigestValue>
515 3
        $digestValueNode = $xmldoc->createElement('DigestValue', $digValue);
516
        //adiciona o node <DigestValue> ao node <Reference>
517 3
        $referenceNode->appendChild($digestValueNode);
518
        //extrai node <SignedInfo> para uma string na sua forma canonica
519 3
        $cnSignedInfoNode = $signedInfoNode->C14N(true, false, null, null);
520
        //cria uma variavel vazia que receberá a assinatura
521 3
        $signature = '';
522
        //calcula a assinatura do node canonizado <SignedInfo>
523
        //usando a chave privada em formato PEM
524 3
        if (! openssl_sign($cnSignedInfoNode, $signature, $objSSLPriKey, $signAlgorithm)) {
525
            $msg = "Houve erro durante a assinatura digital.\n";
526
            $this->zGetOpenSSLError($msg);
527
        }
528
        //converte a assinatura em base64
529 3
        $signatureValue = base64_encode($signature);
530
        //cria o node <SignatureValue>
531 3
        $signatureValueNode = $xmldoc->createElement('SignatureValue', $signatureValue);
532
        //adiciona o node <SignatureValue> ao node <Signature>
533 3
        $signatureNode->appendChild($signatureValueNode);
534
        //cria o node <KeyInfo>
535 3
        $keyInfoNode = $xmldoc->createElement('KeyInfo');
536
        //adiciona o node <KeyInfo> ao node <Signature>
537 3
        $signatureNode->appendChild($keyInfoNode);
538
        //cria o node <X509Data>
539 3
        $x509DataNode = $xmldoc->createElement('X509Data');
540
        //adiciona o node <X509Data> ao node <KeyInfo>
541 3
        $keyInfoNode->appendChild($x509DataNode);
542
        //remove linhas desnecessárias do certificado
543 3
        $pubKeyClean = $this->zCleanPubKey();
544
        //cria o node <X509Certificate>
545 3
        $x509CertificateNode = $xmldoc->createElement('X509Certificate', $pubKeyClean);
546
        //adiciona o node <X509Certificate> ao node <X509Data>
547 3
        $x509DataNode->appendChild($x509CertificateNode);
548
        //salva o xml completo em uma string
549 3
        $xmlResp = $xmldoc->saveXML();
550
        //retorna o documento assinado
551 3
        return $xmlResp;
552
    }
553
   
554
    /**
555
     * signatureExists
556
     * Check se o xml possi a tag Signature
557
     * @param DOMDocument $dom
558
     * @return boolean
559
     */
560 6
    private function zSignatureExists($dom)
561
    {
562 6
        $signature = $dom->getElementsByTagName('Signature')->item(0);
563 6
        if (! isset($signature)) {
564 3
            return false;
565
        }
566 3
        return true;
567
    }
568
    
569
    /**
570
     * verifySignature
571
     * Verifica a validade da assinatura digital contida no xml
572
     * @param string $docxml conteudo do xml a ser verificado ou o path completo
573
     * @param string $tagid tag que foi assinada no documento xml
574
     * @return boolean
575
     * @throws Exception\InvalidArgumentException
576
     * @throws Exception\RuntimeException
577
     */
578 3
    public function verifySignature($docxml = '', $tagid = '')
579
    {
580 3
        if ($docxml == '') {
581
            $msg = "Não foi passado um xml para a verificação.";
582
            throw new Exception\InvalidArgumentException($msg);
583
        }
584 3
        if ($tagid == '') {
585
            $msg = "Não foi indicada a TAG a ser verificada.";
586
            throw new Exception\InvalidArgumentException($msg);
587
        }
588 3
        $xml = $docxml;
589 3
        if (is_file($docxml)) {
590 3
            $xml = file_get_contents($docxml);
591 3
        }
592 3
        $dom = new Dom();
593 3
        $dom->loadXMLString($xml);
594 3
        $flag = $this->zDigCheck($dom, $tagid);
0 ignored issues
show
Documentation introduced by
$dom is of type object<NFePHP\Common\Dom\Dom>, but the function expects a object<NFePHP\Common\Certificate\DOMDocument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Unused Code introduced by
$flag is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
595 3
        $flag = $this->zSignCheck($dom);
0 ignored issues
show
Documentation introduced by
$dom is of type object<NFePHP\Common\Dom\Dom>, but the function expects a object<NFePHP\Common\Certificate\DOMDocument>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
596 3
        return $flag;
597
    }
598
    
599
    /**
600
     * zSignCheck
601
     * @param DOMDocument $dom
602
     * @return boolean
603
     * @throws Exception\RuntimeException
604
     */
605 3
    private function zSignCheck($dom)
606
    {
607
        //SignatureMethod attribute Algorithm
608
        //<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
609 3
        $sigMethAlgo = $dom->getNode('SignatureMethod', 0)->getAttribute('Algorithm');
610 3
        if ($sigMethAlgo == 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
611 3
            $signAlgorithm = OPENSSL_ALGO_SHA1;
612 3
        } else {
613
            $signAlgorithm = OPENSSL_ALGO_SHA256;
614
        }
615
        // Obter e remontar a chave publica do xml
616 3
        $x509Certificate = $dom->getNodeValue('X509Certificate');
617
        $x509Certificate =  "-----BEGIN CERTIFICATE-----\n"
618 3
            . $this->zSplitLines($x509Certificate)
619 3
            . "\n-----END CERTIFICATE-----\n";
620
        //carregar a chave publica remontada
621 3
        $objSSLPubKey = openssl_pkey_get_public($x509Certificate);
622 3
        if ($objSSLPubKey === false) {
623
            $msg = "Ocorreram problemas ao carregar a chave pública. Certificado incorreto ou corrompido!!";
624
            $this->zGetOpenSSLError($msg);
625
        }
626
        //remontando conteudo que foi assinado
627 3
        $signContent = $dom->getElementsByTagName('SignedInfo')->item(0)->C14N(true, false, null, null);
628
        // validando assinatura do conteudo
629 3
        $signatureValueXML = $dom->getElementsByTagName('SignatureValue')->item(0)->nodeValue;
630 3
        $decodedSignature = base64_decode(str_replace(array("\r", "\n"), '', $signatureValueXML));
631 3
        $resp = openssl_verify($signContent, $decodedSignature, $objSSLPubKey, $signAlgorithm);
632 3
        if ($resp != 1) {
633
            $msg = "Problema ({$resp}) ao verificar a assinatura do digital!!";
634
            $this->zGetOpenSSLError($msg);
635
        }
636 3
        return true;
637
    }
638
    
639
    /**
640
     * zDigCheck
641
     * @param DOMDocument $dom
642
     * @param string $tagid
643
     * @return boolean
644
     * @throws Exception\RuntimeException
645
     */
646 3
    private function zDigCheck($dom, $tagid = '')
647
    {
648 3
        $node = $dom->getNode($tagid, 0);
649 3
        if (empty($node)) {
650
            throw new Exception\RuntimeException(
651
                "A tag < $tagid > não existe no XML!!"
652
            );
653
        }
654 3
        if (! $this->zSignatureExists($dom)) {
655
            $msg = "O xml não contêm nenhuma assinatura para ser verificada.";
656
            throw new Exception\RuntimeException($msg);
657
        }
658 3
        $sigMethAlgo = $dom->getNode('SignatureMethod', 0)->getAttribute('Algorithm');
659 3
        if ($sigMethAlgo == 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
660 3
            $hashAlgorithm = 'sha1';
661 3
        } else {
662
            $hashAlgorithm = 'sha256';
663
        }
664
        //carregar o node em sua forma canonica
665 3
        $tagInf = $node->C14N(true, false, null, null);
666
        //calcular o hash sha1
667 3
        $hashValue = hash($hashAlgorithm, $tagInf, true);
668
        //converter o hash para base64 para obter o digest do node
669 3
        $digestCalculado = base64_encode($hashValue);
670
        //pegar o digest informado no xml
671 3
        $digestInformado = $dom->getNodeValue('DigestValue');
672
        //compara os digests calculados e informados
673 3
        if ($digestCalculado != $digestInformado) {
674
            $msg = "O conteúdo do XML não confere com o Digest Value.\n
675
                Digest calculado [{$digestCalculado}], digest informado no XML [{$digestInformado}].\n
676
                O arquivo pode estar corrompido ou ter sido adulterado.";
677
            throw new Exception\RuntimeException($msg);
678
        }
679 3
        return true;
680
    }
681
    
682
    /**
683
     * zValidCerts
684
     * Verifica a data de validade do certificado digital
685
     * e compara com a data de hoje.
686
     * Caso o certificado tenha expirado o mesmo será removido das
687
     * pastas e o método irá retornar false.
688
     * @param string $pubKey chave publica
689
     * @return boolean
690
     */
691 18
    protected function zValidCerts($pubKey)
692
    {
693 18
        if (! $data = openssl_x509_read($pubKey)) {
694
                //o dado não é uma chave válida
695
                $this->zRemovePemFiles();
696
                $this->error = "A chave passada está corrompida ou não é uma chave. Obtenha s chaves corretas!!";
697
                return false;
698
        }
699 18
        $certData = openssl_x509_parse($data);
700
        // reformata a data de validade;
701 18
        $ano = substr($certData['validTo'], 0, 2);
702 18
        $mes = substr($certData['validTo'], 2, 2);
703 18
        $dia = substr($certData['validTo'], 4, 2);
704
        //obtem o timestamp da data de validade do certificado
705 18
        $dValid = gmmktime(0, 0, 0, $mes, $dia, $ano);
706
        // obtem o timestamp da data de hoje
707 18
        $dHoje = gmmktime(0, 0, 0, date("m"), date("d"), date("Y"));
708
        // compara a data de validade com a data atual
709 18
        $this->expireTimestamp = $dValid;
0 ignored issues
show
Documentation Bug introduced by
The property $expireTimestamp was declared of type double, but $dValid is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
710 18
        if ($dHoje > $dValid) {
711 18
            $this->zRemovePemFiles();
712 18
            $msg = "Data de validade vencida! [Valido até $dia/$mes/$ano]";
713 18
            $this->error = $msg;
714 18
            return $this->ignoreValidCert;
715
        }
716
        return true;
717
    }
718
    
719
    /**
720
     * zCleanPubKey
721
     * Remove a informação de inicio e fim do certificado
722
     * contido no formato PEM, deixando o certificado (chave publica) pronta para ser
723
     * anexada ao xml da NFe
724
     * @return string contendo o certificado limpo
725
     */
726 3
    protected function zCleanPubKey()
727
    {
728
        //inicializa variavel
729 3
        $data = '';
730
        //carregar a chave publica
731 3
        $pubKey = $this->pubKey;
732
        //carrega o certificado em um array usando o LF como referencia
733 3
        $arCert = explode("\n", $pubKey);
734 3
        foreach ($arCert as $curData) {
735
            //remove a tag de inicio e fim do certificado
736 3
            if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) != 0 &&
737 3
                    strncmp($curData, '-----END CERTIFICATE', 20) != 0 ) {
738
                //carrega o resultado numa string
739 3
                $data .= trim($curData);
740 3
            }
741 3
        }
742 3
        return $data;
743
    }
744
    
745
    /**
746
     * zSplitLines
747
     * Divide a string do certificado publico em linhas
748
     * com 76 caracteres (padrão original)
749
     * @param string $cntIn certificado
750
     * @return string certificado reformatado
751
     */
752 3
    protected function zSplitLines($cntIn = '')
753
    {
754 3
        if ($cntIn != '') {
755 3
            $cnt = rtrim(chunk_split(str_replace(array("\r", "\n"), '', $cntIn), 76, "\n"));
756 3
        } else {
757
            $cnt = $cntIn;
758
        }
759 3
        return $cnt;
760
    }
761
    
762
    /**
763
     * zRemovePemFiles
764
     * Apaga os arquivos PEM do diretório
765
     * Isso deve ser feito quando um novo certificado é carregado
766
     * ou quando a validade do certificado expirou.
767
     */
768 18
    private function zRemovePemFiles()
769
    {
770 18
        if (is_file($this->pubKeyFile)) {
771
            unlink($this->pubKeyFile);
772
        }
773 18
        if (is_file($this->priKeyFile)) {
774
            unlink($this->priKeyFile);
775
        }
776 18
        if (is_file($this->certKeyFile)) {
777
            unlink($this->certKeyFile);
778
        }
779 18
    }
780
    
781
    /**
782
     * zGetOpenSSLError
783
     * @param string $msg
784
     * @return string
785
     */
786
    protected function zGetOpenSSLError($msg = '')
787
    {
788
        while ($erro = openssl_error_string()) {
789
            $msg .= $erro . "\n";
790
        }
791
        throw new Exception\RuntimeException($msg);
792
    }
793
}
794