Completed
Push — master ( 432665...da164d )
by Roberto
07:26 queued 04:51
created

Pkcs12::zDigCheck()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 5.4042
Metric Value
dl 0
loc 29
ccs 10
cts 18
cp 0.5556
rs 8.5806
cc 4
eloc 17
nc 4
nop 2
crap 5.4042
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
     * Mensagem de erro da classe
98
     * @var string
99
     */
100
    public $error = '';
101
    
102
    /**
103
     * Id do docimento sendo assinado
104
     * @var string
105
     */
106
    public $docId = '';
107
108
    /**
109
     * Método de construção da classe
110
     * @param string $pathCerts Path para a pasta que contêm os certificados digitais
111
     * @param string $cnpj CNPJ do emitente, sem  ./-, apenas os numeros
112
     * @param string $pubKey Chave publica em formato PEM, não o path mas a chave em si
113
     * @param string $priKey Chave privada em formato PEM, não o path mas a chave em si
114
     * @param string $certKey Certificado em formato PEM, não o path mas a chave em si
115
     * @param bool $ignoreValidCert
116
     * @param boolean $ignoreValidCert Ignora a validade do certificado, mais usado para fins de teste
117
     */
118 24
    public function __construct(
119
        $pathCerts = '',
120
        $cnpj = '',
121
        $pubKey = '',
122
        $priKey = '',
123
        $certKey = '',
124
        $ignoreValidCert = false
125
    ) {
126 24
        $ncnpj = preg_replace('/[^0-9]/', '', $cnpj);
127 24
        if (empty($pathCerts)) {
128
            //estabelecer diretorio default
129
            $pathCerts = dirname(dirname(dirname(dirname(__FILE__)))).DIRECTORY_SEPARATOR.'certs'.DIRECTORY_SEPARATOR;
130
        }
131 24
        if (! empty($pathCerts)) {
132 24
            if (!is_dir(trim($pathCerts))) {
133 3
                throw new Exception\InvalidArgumentException(
134
                    "Um path válido para os certificados deve ser passado."
135 3
                    . " Diretório [$pathCerts] não foi localizado."
136 3
                );
137
            }
138 21
            $this->pathCerts = trim($pathCerts);
139 21
        }
140 21
        $this->ignoreValidCert = $ignoreValidCert;
141 21
        $flagCert = false;
142 21
        if ($pubKey != '' && $priKey != '' && strlen($pubKey) > 500 && strlen($priKey) > 500) {
143 9
            $this->pubKey = $pubKey;
144 9
            $this->priKey = $priKey;
145 9
            $this->certKey = $priKey."\r\n".$pubKey;
146 9
            $flagCert = true;
147 9
        }
148 21
        if ($certKey != '') {
149
            $this->certKey = $certKey;
150
        }
151 21
        $this->cnpj = $ncnpj;
152 21
        if (! $this->zInit($flagCert)) {
153 3
            throw new Exception\RuntimeException($this->error);
154
        }
155 18
    }
156
    
157
    /**
158
     * zInit
159
     * Método de inicialização da classe irá verificar
160
     * os parâmetros, arquivos e validade dos mesmos
161
     * Em caso de erro o motivo da falha será indicada na parâmetro
162
     * error da classe, os outros parâmetros serão limpos e os
163
     * arquivos inválidos serão removidos da pasta
164
     * @param boolean $flagCert indica que as chaves já foram passas como strings
165
     * @return boolean
166
     */
167 21
    private function zInit($flagCert = false)
168
    {
169
        //se as chaves foram passadas na forma de strings então verificar a validade
170 21
        if ($flagCert) {
171
            //já que o certificado existe, verificar seu prazo de validade
172
            //o certificado será removido se estiver vencido
173 9
            if (!$this->ignoreValidCert) {
174 3
                return $this->zValidCerts($this->pubKey);
175
            }
176 6
        } else {
177 12
            if (substr($this->pathCerts, -1) !== DIRECTORY_SEPARATOR) {
178
                $this->pathCerts .= DIRECTORY_SEPARATOR;
179
            }
180
            //monta o path completo com o nome da chave privada
181 12
            $this->priKeyFile = $this->pathCerts.$this->cnpj.'_priKEY.pem';
182
            //monta o path completo com o nome da chave publica
183 12
            $this->pubKeyFile =  $this->pathCerts.$this->cnpj.'_pubKEY.pem';
184
            //monta o path completo com o nome do certificado (chave publica e privada) em formato pem
185 12
            $this->certKeyFile = $this->pathCerts.$this->cnpj.'_certKEY.pem';
186
            //se as chaves não foram passadas em strings, verifica se os certificados existem
187 12
            if (is_file($this->priKeyFile) && is_file($this->pubKeyFile) && is_file($this->certKeyFile)) {
188
                //se as chaves existem deve ser verificado sua validade
189
                $this->pubKey = file_get_contents($this->pubKeyFile);
190
                $this->priKey = file_get_contents($this->priKeyFile);
191
                $this->certKey = file_get_contents($this->certKeyFile);
192
                //já que o certificado existe, verificar seu prazo de validade
193
                if (! $this->ignoreValidCert) {
194
                    return $this->zValidCerts($this->pubKey);
195
                }
196
            }
197
        }
198 18
        return true;
199
    }//fim init
200
201
    /**
202
     * loadPfxFile
203
     * @param string $pathPfx caminho completo para o arquivo pfx
204
     * @param string $password senha para abrir o certificado pfx
205
     * @param bool $createFiles
206
     * @param bool $ignoreValidity
207
     * @param bool $ignoreOwner
208
     * @return bool
209
     */
210 3
    public function loadPfxFile(
211
        $pathPfx = '',
212
        $password = '',
213
        $createFiles = true,
214
        $ignoreValidity = false,
215
        $ignoreOwner = false
216
    ) {
217 3
        if (! is_file($pathPfx)) {
218
            throw new Exception\InvalidArgumentException(
219
                "O nome do arquivo PFX deve ser passado. Não foi localizado o arquivo [$pathPfx]."
220
            );
221
        }
222 3
        $this->pfxCert = file_get_contents($pathPfx);
223 3
        return $this->loadPfx($this->pfxCert, $password, $createFiles, $ignoreValidity, $ignoreOwner);
224
    }
225
226
    /**
227
     * loadPfx
228
     * Carrega um novo certificado no formato PFX
229
     * Isso deverá ocorrer a cada atualização do certificado digital, ou seja,
230
     * pelo menos uma vez por ano, uma vez que a validade do certificado
231
     * é anual.
232
     * Será verificado também se o certificado pertence realmente ao CNPJ
233
     * Essa verificação checa apenas se o certificado pertence a matriz ou filial
234
     * comparando apenas os primeiros 8 digitos do CNPJ, dessa forma ambas a
235
     * matriz e as filiais poderão usar o mesmo certificado indicado na instanciação
236
     * da classe, se não for um erro irá ocorrer e
237
     * o certificado não será convertido para o formato PEM.
238
     * Em caso de erros, será retornado false e o motivo será indicado no
239
     * parâmetro error da classe.
240
     * Os certificados serão armazenados como <CNPJ>-<tipo>.pem
241
     * @param string $pfxContent arquivo PFX
242
     * @param string $password Senha de acesso ao certificado PFX
243
     * @param boolean $createFiles se true irá criar os arquivos pem das chaves digitais, caso contrario não
244
     * @param bool $ignoreValidity
245
     * @param bool $ignoreOwner
246
     * @return bool
247
     */
248 9
    public function loadPfx(
249
        $pfxContent = '',
250
        $password = '',
251
        $createFiles = true,
252
        $ignoreValidity = false,
253
        $ignoreOwner = false
254
    ) {
255 9
        if ($password == '') {
256
            throw new Exception\InvalidArgumentException(
257
                "A senha de acesso para o certificado pfx não pode ser vazia."
258
            );
259
        }
260
        //carrega os certificados e chaves para um array denominado $x509certdata
261 9
        $x509certdata = array();
262 9
        if (!openssl_pkcs12_read($pfxContent, $x509certdata, $password)) {
263
            throw new Exception\RuntimeException(
264
                "O certificado não pode ser lido!! Senha errada ou arquivo corrompido ou formato inválido!!"
265
            );
266
        }
267 9
        $this->pfxCert = $pfxContent;
268 9
        if (!$ignoreValidity) {
269
            //verifica sua data de validade
270
            if (! $this->zValidCerts($x509certdata['cert'])) {
271
                throw new Exception\RuntimeException($this->error);
272
            }
273
        }
274 9
        if (!$ignoreOwner) {
275 3
            $cnpjCert = Asn::getCNPJCert($x509certdata['cert']);
276 3
            if (substr($this->cnpj, 0, 8) != substr($cnpjCert, 0, 8)) {
277 3
                throw new Exception\InvalidArgumentException(
278
                    "O Certificado fornecido pertence a outro CNPJ!!"
279 3
                );
280
            }
281
        }
282
        //monta o path completo com o nome da chave privada
283 6
        $this->priKeyFile = $this->pathCerts.$this->cnpj.'_priKEY.pem';
284
        //monta o path completo com o nome da chave publica
285 6
        $this->pubKeyFile =  $this->pathCerts.$this->cnpj.'_pubKEY.pem';
286
        //monta o path completo com o nome do certificado (chave publica e privada) em formato pem
287 6
        $this->certKeyFile = $this->pathCerts.$this->cnpj.'_certKEY.pem';
288 6
        $this->zRemovePemFiles();
289 6
        if ($createFiles) {
290
            $this->zSavePemFiles($x509certdata);
291
        }
292 6
        $this->pubKey=$x509certdata['cert'];
293 6
        $this->priKey=$x509certdata['pkey'];
294 6
        $this->certKey=$x509certdata['pkey']."\r\n".$x509certdata['cert'];
295 6
        return true;
296
    }
297
    
298
    /**
299
     * zSavePemFiles
300
     * @param array $x509certdata
301
     * @throws Exception\InvalidArgumentException
302
     * @throws Exception\RuntimeException
303
     */
304
    private function zSavePemFiles($x509certdata)
305
    {
306
        if (empty($this->pathCerts)) {
307
            throw new Exception\InvalidArgumentException(
308
                "Não está definido o diretório para armazenar os certificados."
309
            );
310
        }
311
        if (! is_dir($this->pathCerts)) {
312
            throw new Exception\InvalidArgumentException(
313
                "Não existe o diretório para armazenar os certificados."
314
            );
315
        }
316
        //recriar os arquivos pem com o arquivo pfx
317
        if (!file_put_contents($this->priKeyFile, $x509certdata['pkey'])) {
318
            throw new Exception\RuntimeException(
319
                "Falha de permissão de escrita na pasta dos certificados!!"
320
            );
321
        }
322
        file_put_contents($this->pubKeyFile, $x509certdata['cert']);
323
        file_put_contents($this->certKeyFile, $x509certdata['pkey']."\r\n".$x509certdata['cert']);
324
    }
325
    
326
    /**
327
     * aadChain
328
     * @param array $aCerts Array com os caminhos completos para cada certificado da cadeia
329
     *                     ou um array com o conteúdo desses certificados
330
     * @return void
331
     */
332 3
    public function aadChain($aCerts = array())
333
    {
334 3
        $certificate = $this->certKey;
335 3
        foreach ($aCerts as $cert) {
336 3
            if (is_file($cert)) {
337 3
                $dados = file_get_contents($cert);
338 3
                $certificate .= "\r\n" . $dados;
339 3
            } else {
340
                $certificate .= "\r\n" . $cert;
341
            }
342 3
        }
343 3
        $this->certKey = $certificate;
344 3
        if (is_file($this->certKeyFile)) {
345
            file_put_contents($this->certKeyFile, $certificate);
346
        }
347 3
    }
348
    
349
    /**
350
     * signXML
351
     * @param string $docxml
352
     * @param string $tagid
353
     * @param string $marcador
354
     * @param string $algorithm
355
     * @return string xml assinado
356
     * @throws Exception\InvalidArgumentException
357
     * @throws Exception\RuntimeException
358
     */
359 3
    public function signXML($docxml, $tagid = '', $marcador = 'Id', $algorithm = 'SHA1')
360
    {
361
        //caso não tenha as chaves cai fora
362 3
        if ($this->pubKey == '' || $this->priKey == '') {
363
            $msg = "As chaves não estão disponíveis.";
364
            throw new Exception\InvalidArgumentException($msg);
365
        }
366
        //caso não seja informada a tag a ser assinada cai fora
367 3
        if ($tagid == '') {
368
            $msg = "A tag a ser assinada deve ser indicada.";
369
            throw new Exception\InvalidArgumentException($msg);
370
        }
371
        //carrega a chave privada no openssl
372 3
        $objSSLPriKey = openssl_get_privatekey($this->priKey);
373 3
        if ($objSSLPriKey === false) {
374
            $msg = "Houve erro no carregamento da chave privada.";
375
            $this->zGetOpenSSLError($msg);
376
            //while ($erro = openssl_error_string()) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
377
            //    $msg .= $erro . "\n";
378
            //}
379
            //throw new Exception\RuntimeException($msg);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
380
        }
381 3
        $xml = $docxml;
382 3
        if (is_file($docxml)) {
383
            $xml = file_get_contents($docxml);
384
        }
385
        //remove sujeiras do xml
386 3
        $order = array("\r\n", "\n", "\r", "\t");
387 3
        $xml = str_replace($order, '', $xml);
388 3
        $xmldoc = new Dom();
389 3
        $xmldoc->loadXMLString($xml);
390
        //coloca o node raiz em uma variável
391 3
        $root = $xmldoc->documentElement;
392
        //extrair a tag com os dados a serem assinados
393 3
        $node = $xmldoc->getElementsByTagName($tagid)->item(0);
394 3
        if (!isset($node)) {
395
            throw new Exception\RuntimeException(
396
                "A tag < $tagid > não existe no XML!!"
397
            );
398
        }
399 3
        $this->docId = $node->getAttribute($marcador);
400 3
        $xmlResp = $xml;
401 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...
402
            //executa a assinatura
403 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...
404 3
        }
405
        //libera a chave privada
406 3
        openssl_free_key($objSSLPriKey);
407 3
        return $xmlResp;
408
    }
409
410
    /**
411
     * zSignXML
412
     * Método que provê a assinatura do xml conforme padrão SEFAZ
413
     * @param DOMDocument $xmldoc
414
     * @param DOMElement $root
415
     * @param DOMElement $node
416
     * @param resource $objSSLPriKey
417
     * @param string $marcador
418
     * @param string $algorithm
419
     * @return string xml assinado
420
     * @internal param DOMDocument $xmlDoc
421
     */
422 3
    private function zSignXML($xmldoc, $root, $node, $objSSLPriKey, $marcador, $algorithm = 'SHA1')
423
    {
424 3
        $nsDSIG = 'http://www.w3.org/2000/09/xmldsig#';
425 3
        $nsCannonMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
426 3
        $nsSignatureMethod = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1';
427 3
        $nsDigestMethod = 'http://www.w3.org/2000/09/xmldsig#sha1';
428 3
        $signAlgorithm = OPENSSL_ALGO_SHA1;
429
        //incluido para atender requisitos de assinatura do sped-efinanceira
430 3
        if ($algorithm == 'SHA256') {
431
            $signAlgorithm = OPENSSL_ALGO_SHA256;
432
            $nsSignatureMethod = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256';
433
            $nsDigestMethod = 'http://www.w3.org/2001/04/xmlenc#sha256';
434
        }
435 3
        $nsTransformMethod1 ='http://www.w3.org/2000/09/xmldsig#enveloped-signature';
436 3
        $nsTransformMethod2 = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
437
        //pega o atributo id do node a ser assinado
438 3
        $idSigned = trim($node->getAttribute($marcador));
439
        //extrai os dados da tag para uma string na forma canonica
440 3
        $dados = $node->C14N(true, false, null, null);
441
        //calcular o hash dos dados
442 3
        if ($algorithm == 'SHA256') {
443
            $hashValue = hash('sha256', $dados, true);
444
        } else {
445 3
            $hashValue = hash('sha1', $dados, true);
446
        }
447
        //converter o hash para base64
448 3
        $digValue = base64_encode($hashValue);
449
        //cria o node <Signature>
450 3
        $signatureNode = $xmldoc->createElementNS($nsDSIG, 'Signature');
451
        //adiciona a tag <Signature> ao node raiz
452 3
        $root->appendChild($signatureNode);
453
        //cria o node <SignedInfo>
454 3
        $signedInfoNode = $xmldoc->createElement('SignedInfo');
455
        //adiciona o node <SignedInfo> ao <Signature>
456 3
        $signatureNode->appendChild($signedInfoNode);
457
        //cria no node com o método de canonização dos dados
458 3
        $canonicalNode = $xmldoc->createElement('CanonicalizationMethod');
459
        //adiona o <CanonicalizationMethod> ao node <SignedInfo>
460 3
        $signedInfoNode->appendChild($canonicalNode);
461
        //seta o atributo ao node <CanonicalizationMethod>
462 3
        $canonicalNode->setAttribute('Algorithm', $nsCannonMethod);
463
        //cria o node <SignatureMethod>
464 3
        $signatureMethodNode = $xmldoc->createElement('SignatureMethod');
465
        //adiciona o node <SignatureMethod> ao node <SignedInfo>
466 3
        $signedInfoNode->appendChild($signatureMethodNode);
467
        //seta o atributo Algorithm ao node <SignatureMethod>
468 3
        $signatureMethodNode->setAttribute('Algorithm', $nsSignatureMethod);
469
        //cria o node <Reference>
470 3
        $referenceNode = $xmldoc->createElement('Reference');
471
        //adiciona o node <Reference> ao node <SignedInfo>
472 3
        $signedInfoNode->appendChild($referenceNode);
473
        //seta o atributo URI a node <Reference>
474 3
        $referenceNode->setAttribute('URI', '#'.$idSigned);
475
        //cria o node <Transforms>
476 3
        $transformsNode = $xmldoc->createElement('Transforms');
477
        //adiciona o node <Transforms> ao node <Reference>
478 3
        $referenceNode->appendChild($transformsNode);
479
        //cria o primeiro node <Transform> OBS: no singular
480 3
        $transfNode1 = $xmldoc->createElement('Transform');
481
        //adiciona o primeiro node <Transform> ao node <Transforms>
482 3
        $transformsNode->appendChild($transfNode1);
483
        //set o atributo Algorithm ao primeiro node <Transform>
484 3
        $transfNode1->setAttribute('Algorithm', $nsTransformMethod1);
485
        //cria outro node <Transform> OBS: no singular
486 3
        $transfNode2 = $xmldoc->createElement('Transform');
487
        //adiciona o segundo node <Transform> ao node <Transforms>
488 3
        $transformsNode->appendChild($transfNode2);
489
        //set o atributo Algorithm ao segundo node <Transform>
490 3
        $transfNode2->setAttribute('Algorithm', $nsTransformMethod2);
491
        //cria o node <DigestMethod>
492 3
        $digestMethodNode = $xmldoc->createElement('DigestMethod');
493
        //adiciona o node <DigestMethod> ao node <Reference>
494 3
        $referenceNode->appendChild($digestMethodNode);
495
        //seta o atributo Algorithm ao node <DigestMethod>
496 3
        $digestMethodNode->setAttribute('Algorithm', $nsDigestMethod);
497
        //cria o node <DigestValue>
498 3
        $digestValueNode = $xmldoc->createElement('DigestValue', $digValue);
499
        //adiciona o node <DigestValue> ao node <Reference>
500 3
        $referenceNode->appendChild($digestValueNode);
501
        //extrai node <SignedInfo> para uma string na sua forma canonica
502 3
        $cnSignedInfoNode = $signedInfoNode->C14N(true, false, null, null);
503
        //cria uma variavel vazia que receberá a assinatura
504 3
        $signature = '';
505
        //calcula a assinatura do node canonizado <SignedInfo>
506
        //usando a chave privada em formato PEM
507 3
        if (! openssl_sign($cnSignedInfoNode, $signature, $objSSLPriKey, $signAlgorithm)) {
508
            $msg = "Houve erro durante a assinatura digital.\n";
509
            $this->zGetOpenSSLError($msg);
510
            //while ($erro = openssl_error_string()) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
511
            //    $msg .= $erro . "\n";
512
            //}
513
            //throw new Exception\RuntimeException($msg);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
514
        }
515
        //converte a assinatura em base64
516 3
        $signatureValue = base64_encode($signature);
517
        //cria o node <SignatureValue>
518 3
        $signatureValueNode = $xmldoc->createElement('SignatureValue', $signatureValue);
519
        //adiciona o node <SignatureValue> ao node <Signature>
520 3
        $signatureNode->appendChild($signatureValueNode);
521
        //cria o node <KeyInfo>
522 3
        $keyInfoNode = $xmldoc->createElement('KeyInfo');
523
        //adiciona o node <KeyInfo> ao node <Signature>
524 3
        $signatureNode->appendChild($keyInfoNode);
525
        //cria o node <X509Data>
526 3
        $x509DataNode = $xmldoc->createElement('X509Data');
527
        //adiciona o node <X509Data> ao node <KeyInfo>
528 3
        $keyInfoNode->appendChild($x509DataNode);
529
        //remove linhas desnecessárias do certificado
530 3
        $pubKeyClean = $this->zCleanPubKey();
531
        //cria o node <X509Certificate>
532 3
        $x509CertificateNode = $xmldoc->createElement('X509Certificate', $pubKeyClean);
533
        //adiciona o node <X509Certificate> ao node <X509Data>
534 3
        $x509DataNode->appendChild($x509CertificateNode);
535
        //salva o xml completo em uma string
536 3
        $xmlResp = $xmldoc->saveXML();
537
        //retorna o documento assinado
538 3
        return $xmlResp;
539
    }
540
   
541
    /**
542
     * signatureExists
543
     * Check se o xml possi a tag Signature
544
     * @param DOMDocument $dom
545
     * @return boolean
546
     */
547 6
    private function zSignatureExists($dom)
548
    {
549 6
        $signature = $dom->getElementsByTagName('Signature')->item(0);
550 6
        if (! isset($signature)) {
551 3
            return false;
552
        }
553 3
        return true;
554
    }
555
    
556
    /**
557
     * verifySignature
558
     * Verifica a validade da assinatura digital contida no xml
559
     * @param string $docxml conteudo do xml a ser verificado ou o path completo
560
     * @param string $tagid tag que foi assinada no documento xml
561
     * @return boolean
562
     * @throws Exception\InvalidArgumentException
563
     * @throws Exception\RuntimeException
564
     */
565 3
    public function verifySignature($docxml = '', $tagid = '')
566
    {
567 3
        if ($docxml == '') {
568
            $msg = "Não foi passado um xml para a verificação.";
569
            throw new Exception\InvalidArgumentException($msg);
570
        }
571 3
        if ($tagid == '') {
572
            $msg = "Não foi indicada a TAG a ser verificada.";
573
            throw new Exception\InvalidArgumentException($msg);
574
        }
575 3
        $xml = $docxml;
576 3
        if (is_file($docxml)) {
577 3
            $xml = file_get_contents($docxml);
578 3
        }
579 3
        $dom = new Dom();
580 3
        $dom->loadXMLString($xml);
581 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...
582 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...
583 3
        return $flag;
584
    }
585
    
586
    /**
587
     * zSignCheck
588
     * @param DOMDocument $dom
589
     * @return boolean
590
     * @throws Exception\RuntimeException
591
     */
592 3
    private function zSignCheck($dom)
593
    {
594
        //SignatureMethod attribute Algorithm
595
        //<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
596 3
        $sigMethAlgo = $dom->getNode('SignatureMethod', 0)->getAttribute('Algorithm');
597 3
        if ($sigMethAlgo == 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') {
598 3
            $signAlgorithm = OPENSSL_ALGO_SHA1;
599 3
        } else {
600
            $signAlgorithm = OPENSSL_ALGO_SHA256;
601
        }
602
        // Obter e remontar a chave publica do xml
603 3
        $x509Certificate = $dom->getNodeValue('X509Certificate');
604
        $x509Certificate =  "-----BEGIN CERTIFICATE-----\n"
605 3
            . $this->zSplitLines($x509Certificate)
606 3
            . "\n-----END CERTIFICATE-----\n";
607
        //carregar a chave publica remontada
608 3
        $objSSLPubKey = openssl_pkey_get_public($x509Certificate);
609 3
        if ($objSSLPubKey === false) {
610
            $msg = "Ocorreram problemas ao carregar a chave pública. Certificado incorreto ou corrompido!!";
611
            $this->zGetOpenSSLError($msg);
612
        }
613
        //remontando conteudo que foi assinado
614 3
        $signContent = $dom->getElementsByTagName('SignedInfo')->item(0)->C14N(true, false, null, null);
615
        // validando assinatura do conteudo
616 3
        $signatureValueXML = $dom->getElementsByTagName('SignatureValue')->item(0)->nodeValue;
617 3
        $decodedSignature = base64_decode(str_replace(array("\r", "\n"), '', $signatureValueXML));
618 3
        $resp = openssl_verify($signContent, $decodedSignature, $objSSLPubKey, $signAlgorithm);
619 3
        if ($resp != 1) {
620
            $msg = "Problema ({$resp}) ao verificar a assinatura do digital!!";
621
            $this->zGetOpenSSLError($msg);
622
        }
623 3
        return true;
624
    }
625
    
626
    /**
627
     * zDigCheck
628
     * @param DOMDocument $dom
629
     * @param string $tagid
630
     * @return boolean
631
     * @throws Exception\RuntimeException
632
     */
633 3
    private function zDigCheck($dom, $tagid = '')
634
    {
635 3
        $node = $dom->getNode($tagid, 0);
636 3
        if (empty($node)) {
637
            throw new Exception\RuntimeException(
638
                "A tag < $tagid > não existe no XML!!"
639
            );
640
        }
641 3
        if (! $this->zSignatureExists($dom)) {
642
            $msg = "O xml não contêm nenhuma assinatura para ser verificada.";
643
            throw new Exception\RuntimeException($msg);
644
        }
645
        //carregar o node em sua forma canonica
646 3
        $tagInf = $node->C14N(true, false, null, null);
647
        //calcular o hash sha1
648 3
        $hashValue = hash('sha1', $tagInf, true);
649
        //converter o hash para base64 para obter o digest do node
650 3
        $digestCalculado = base64_encode($hashValue);
651
        //pegar o digest informado no xml
652 3
        $digestInformado = $dom->getNodeValue('DigestValue');
653
        //compara os digests calculados e informados
654 3
        if ($digestCalculado != $digestInformado) {
655
            $msg = "O conteúdo do XML não confere com o Digest Value.\n
656
                Digest calculado [{$digestCalculado}], digest informado no XML [{$digestInformado}].\n
657
                O arquivo pode estar corrompido ou ter sido adulterado.";
658
            throw new Exception\RuntimeException($msg);
659
        }
660 3
        return true;
661
    }
662
    
663
    /**
664
     * zValidCerts
665
     * Verifica a data de validade do certificado digital
666
     * e compara com a data de hoje.
667
     * Caso o certificado tenha expirado o mesmo será removido das
668
     * pastas e o método irá retornar false.
669
     * @param string $pubKey chave publica
670
     * @return boolean
671
     */
672 3
    protected function zValidCerts($pubKey)
673
    {
674 3
        if (! $data = openssl_x509_read($pubKey)) {
675
                //o dado não é uma chave válida
676
                $this->zRemovePemFiles();
677
                $this->zLeaveParam();
678
                $this->error = "A chave passada está corrompida ou não é uma chave. Obtenha s chaves corretas!!";
679
                return false;
680
        }
681 3
        $certData = openssl_x509_parse($data);
682
        // reformata a data de validade;
683 3
        $ano = substr($certData['validTo'], 0, 2);
684 3
        $mes = substr($certData['validTo'], 2, 2);
685 3
        $dia = substr($certData['validTo'], 4, 2);
686
        //obtem o timestamp da data de validade do certificado
687 3
        $dValid = gmmktime(0, 0, 0, $mes, $dia, $ano);
688
        // obtem o timestamp da data de hoje
689 3
        $dHoje = gmmktime(0, 0, 0, date("m"), date("d"), date("Y"));
690
        // compara a data de validade com a data atual
691 3
        $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...
692 3
        if ($dHoje > $dValid) {
693 3
            $this->zRemovePemFiles();
694 3
            $this->zLeaveParam();
695 3
            $msg = "Data de validade vencida! [Valido até $dia/$mes/$ano]";
696 3
            $this->error = $msg;
697 3
            return false;
698
        }
699
        return true;
700
    }
701
    
702
    /**
703
     * zCleanPubKey
704
     * Remove a informação de inicio e fim do certificado
705
     * contido no formato PEM, deixando o certificado (chave publica) pronta para ser
706
     * anexada ao xml da NFe
707
     * @return string contendo o certificado limpo
708
     */
709 3
    protected function zCleanPubKey()
710
    {
711
        //inicializa variavel
712 3
        $data = '';
713
        //carregar a chave publica
714 3
        $pubKey = $this->pubKey;
715
        //carrega o certificado em um array usando o LF como referencia
716 3
        $arCert = explode("\n", $pubKey);
717 3
        foreach ($arCert as $curData) {
718
            //remove a tag de inicio e fim do certificado
719 3
            if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) != 0 &&
720 3
                    strncmp($curData, '-----END CERTIFICATE', 20) != 0 ) {
721
                //carrega o resultado numa string
722 3
                $data .= trim($curData);
723 3
            }
724 3
        }
725 3
        return $data;
726
    }
727
    
728
    /**
729
     * zSplitLines
730
     * Divide a string do certificado publico em linhas
731
     * com 76 caracteres (padrão original)
732
     * @param string $cntIn certificado
733
     * @return string certificado reformatado
734
     */
735 3
    protected function zSplitLines($cntIn = '')
736
    {
737 3
        if ($cntIn != '') {
738 3
            $cnt = rtrim(chunk_split(str_replace(array("\r", "\n"), '', $cntIn), 76, "\n"));
739 3
        } else {
740
            $cnt = $cntIn;
741
        }
742 3
        return $cnt;
743
    }
744
    
745
    /**
746
     * zRemovePemFiles
747
     * Apaga os arquivos PEM do diretório
748
     * Isso deve ser feito quando um novo certificado é carregado
749
     * ou quando a validade do certificado expirou.
750
     */
751 9
    private function zRemovePemFiles()
752
    {
753 9
        if (is_file($this->pubKeyFile)) {
754
            unlink($this->pubKeyFile);
755
        }
756 9
        if (is_file($this->priKeyFile)) {
757
            unlink($this->priKeyFile);
758
        }
759 9
        if (is_file($this->certKeyFile)) {
760
            unlink($this->certKeyFile);
761
        }
762 9
    }
763
    
764
    /**
765
     * zLeaveParam
766
     * Limpa os parametros da classe
767
     */
768 3
    private function zLeaveParam()
769
    {
770 3
        $this->pfxCert='';
771 3
        $this->pubKey='';
772 3
        $this->priKey='';
773 3
        $this->certKey='';
774 3
        $this->pubKeyFile='';
775 3
        $this->priKeyFile='';
776 3
        $this->certKeyFile='';
777 3
        $this->expireTimestamp='';
0 ignored issues
show
Documentation Bug introduced by
The property $expireTimestamp was declared of type double, but '' is of type string. 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...
778 3
    }
779
    
780
    /**
781
     * zGetOpenSSLError
782
     * @param string $msg
783
     * @return string
784
     */
785
    protected function zGetOpenSSLError($msg = '')
786
    {
787
        while ($erro = openssl_error_string()) {
788
            $msg .= $erro . "\n";
789
        }
790
        throw new Exception\RuntimeException($msg);
791
    }
792
}
793