Completed
Push — master ( fd4de3...70997f )
by Roberto
04:48
created

Pkcs12::zSignXML()   B

Complexity

Conditions 4
Paths 8

Size

Total Lines 117
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 55
CRAP Score 4.0444

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 117
ccs 55
cts 64
cp 0.8594
rs 8.1935
cc 4
eloc 62
nc 8
nop 6
crap 4.0444

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 3
        if ($algorithm == 'SHA256') {
430
            $signAlgorithm = OPENSSL_ALGO_SHA256;
431
            $nsSignatureMethod = 'http://www.w3.org/2000/09/xmldsig#rsa-sha256';
432
            $nsDigestMethod = 'http://www.w3.org/2000/09/xmldsig#sha256';
433
        }
434 3
        $nsTransformMethod1 ='http://www.w3.org/2000/09/xmldsig#enveloped-signature';
435 3
        $nsTransformMethod2 = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315';
436
        //pega o atributo id do node a ser assinado
437 3
        $idSigned = trim($node->getAttribute($marcador));
438
        //extrai os dados da tag para uma string na forma canonica
439 3
        $dados = $node->C14N(true, false, null, null);
440
        //calcular o hash dos dados
441 3
        if ($algorithm == 'SHA256') {
442
            $hashValue = hash('sha256', $dados, true);
443
        } else {
444 3
            $hashValue = hash('sha1', $dados, true);    
445
        }
446
        //converter o hash para base64
447 3
        $digValue = base64_encode($hashValue);
448
        //cria o node <Signature>
449 3
        $signatureNode = $xmldoc->createElementNS($nsDSIG, 'Signature');
450
        //adiciona a tag <Signature> ao node raiz
451 3
        $root->appendChild($signatureNode);
452
        //cria o node <SignedInfo>
453 3
        $signedInfoNode = $xmldoc->createElement('SignedInfo');
454
        //adiciona o node <SignedInfo> ao <Signature>
455 3
        $signatureNode->appendChild($signedInfoNode);
456
        //cria no node com o método de canonização dos dados
457 3
        $canonicalNode = $xmldoc->createElement('CanonicalizationMethod');
458
        //adiona o <CanonicalizationMethod> ao node <SignedInfo>
459 3
        $signedInfoNode->appendChild($canonicalNode);
460
        //seta o atributo ao node <CanonicalizationMethod>
461 3
        $canonicalNode->setAttribute('Algorithm', $nsCannonMethod);
462
        //cria o node <SignatureMethod>
463 3
        $signatureMethodNode = $xmldoc->createElement('SignatureMethod');
464
        //adiciona o node <SignatureMethod> ao node <SignedInfo>
465 3
        $signedInfoNode->appendChild($signatureMethodNode);
466
        //seta o atributo Algorithm ao node <SignatureMethod>
467 3
        $signatureMethodNode->setAttribute('Algorithm', $nsSignatureMethod);
468
        //cria o node <Reference>
469 3
        $referenceNode = $xmldoc->createElement('Reference');
470
        //adiciona o node <Reference> ao node <SignedInfo>
471 3
        $signedInfoNode->appendChild($referenceNode);
472
        //seta o atributo URI a node <Reference>
473 3
        $referenceNode->setAttribute('URI', '#'.$idSigned);
474
        //cria o node <Transforms>
475 3
        $transformsNode = $xmldoc->createElement('Transforms');
476
        //adiciona o node <Transforms> ao node <Reference>
477 3
        $referenceNode->appendChild($transformsNode);
478
        //cria o primeiro node <Transform> OBS: no singular
479 3
        $transfNode1 = $xmldoc->createElement('Transform');
480
        //adiciona o primeiro node <Transform> ao node <Transforms>
481 3
        $transformsNode->appendChild($transfNode1);
482
        //set o atributo Algorithm ao primeiro node <Transform>
483 3
        $transfNode1->setAttribute('Algorithm', $nsTransformMethod1);
484
        //cria outro node <Transform> OBS: no singular
485 3
        $transfNode2 = $xmldoc->createElement('Transform');
486
        //adiciona o segundo node <Transform> ao node <Transforms>
487 3
        $transformsNode->appendChild($transfNode2);
488
        //set o atributo Algorithm ao segundo node <Transform>
489 3
        $transfNode2->setAttribute('Algorithm', $nsTransformMethod2);
490
        //cria o node <DigestMethod>
491 3
        $digestMethodNode = $xmldoc->createElement('DigestMethod');
492
        //adiciona o node <DigestMethod> ao node <Reference>
493 3
        $referenceNode->appendChild($digestMethodNode);
494
        //seta o atributo Algorithm ao node <DigestMethod>
495 3
        $digestMethodNode->setAttribute('Algorithm', $nsDigestMethod);
496
        //cria o node <DigestValue>
497 3
        $digestValueNode = $xmldoc->createElement('DigestValue', $digValue);
498
        //adiciona o node <DigestValue> ao node <Reference>
499 3
        $referenceNode->appendChild($digestValueNode);
500
        //extrai node <SignedInfo> para uma string na sua forma canonica
501 3
        $cnSignedInfoNode = $signedInfoNode->C14N(true, false, null, null);
502
        //cria uma variavel vazia que receberá a assinatura
503 3
        $signature = '';
504
        //calcula a assinatura do node canonizado <SignedInfo>
505
        //usando a chave privada em formato PEM
506 3
        if (! openssl_sign($cnSignedInfoNode, $signature, $objSSLPriKey, $signAlgorithm)) {
507
            $msg = "Houve erro durante a assinatura digital.\n";
508
            $this->zGetOpenSSLError($msg);
509
            //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...
510
            //    $msg .= $erro . "\n";
511
            //}
512
            //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...
513
        }
514
        //converte a assinatura em base64
515 3
        $signatureValue = base64_encode($signature);
516
        //cria o node <SignatureValue>
517 3
        $signatureValueNode = $xmldoc->createElement('SignatureValue', $signatureValue);
518
        //adiciona o node <SignatureValue> ao node <Signature>
519 3
        $signatureNode->appendChild($signatureValueNode);
520
        //cria o node <KeyInfo>
521 3
        $keyInfoNode = $xmldoc->createElement('KeyInfo');
522
        //adiciona o node <KeyInfo> ao node <Signature>
523 3
        $signatureNode->appendChild($keyInfoNode);
524
        //cria o node <X509Data>
525 3
        $x509DataNode = $xmldoc->createElement('X509Data');
526
        //adiciona o node <X509Data> ao node <KeyInfo>
527 3
        $keyInfoNode->appendChild($x509DataNode);
528
        //remove linhas desnecessárias do certificado
529 3
        $pubKeyClean = $this->zCleanPubKey();
530
        //cria o node <X509Certificate>
531 3
        $x509CertificateNode = $xmldoc->createElement('X509Certificate', $pubKeyClean);
532
        //adiciona o node <X509Certificate> ao node <X509Data>
533 3
        $x509DataNode->appendChild($x509CertificateNode);
534
        //salva o xml completo em uma string
535 3
        $xmlResp = $xmldoc->saveXML();
536
        //retorna o documento assinado
537 3
        return $xmlResp;
538
    }
539
   
540
    /**
541
     * signatureExists
542
     * Check se o xml possi a tag Signature
543
     * @param DOMDocument $dom
544
     * @return boolean
545
     */
546 6
    private function zSignatureExists($dom)
547
    {
548 6
        $signature = $dom->getElementsByTagName('Signature')->item(0);
549 6
        if (! isset($signature)) {
550 3
            return false;
551
        }
552 3
        return true;
553
    }
554
    
555
    /**
556
     * verifySignature
557
     * Verifica a validade da assinatura digital contida no xml
558
     * @param string $docxml conteudo do xml a ser verificado ou o path completo
559
     * @param string $tagid tag que foi assinada no documento xml
560
     * @return boolean
561
     * @throws Exception\InvalidArgumentException
562
     * @throws Exception\RuntimeException
563
     */
564 3
    public function verifySignature($docxml = '', $tagid = '')
565
    {
566 3
        if ($docxml == '') {
567
            $msg = "Não foi passado um xml para a verificação.";
568
            throw new Exception\InvalidArgumentException($msg);
569
        }
570 3
        if ($tagid == '') {
571
            $msg = "Não foi indicada a TAG a ser verificada.";
572
            throw new Exception\InvalidArgumentException($msg);
573
        }
574 3
        $xml = $docxml;
575 3
        if (is_file($docxml)) {
576 3
            $xml = file_get_contents($docxml);
577 3
        }
578 3
        $dom = new Dom();
579 3
        $dom->loadXMLString($xml);
580 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...
581 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...
582 3
        return $flag;
583
    }
584
    
585
    /**
586
     * zSignCheck
587
     * @param DOMDocument $dom
588
     * @return boolean
589
     * @throws Exception\RuntimeException
590
     */
591 3
    private function zSignCheck($dom)
592
    {
593
        // Obter e remontar a chave publica do xml
594 3
        $x509Certificate = $dom->getNodeValue('X509Certificate');
595
        $x509Certificate =  "-----BEGIN CERTIFICATE-----\n"
596 3
            . $this->zSplitLines($x509Certificate)
597 3
            . "\n-----END CERTIFICATE-----\n";
598
        //carregar a chave publica remontada
599 3
        $objSSLPubKey = openssl_pkey_get_public($x509Certificate);
600 3
        if ($objSSLPubKey === false) {
601
            $msg = "Ocorreram problemas ao carregar a chave pública. Certificado incorreto ou corrompido!!";
602
            $this->zGetOpenSSLError($msg);
603
        }
604
        //remontando conteudo que foi assinado
605 3
        $signContent = $dom->getElementsByTagName('SignedInfo')->item(0)->C14N(true, false, null, null);
606
        // validando assinatura do conteudo
607 3
        $signatureValueXML = $dom->getElementsByTagName('SignatureValue')->item(0)->nodeValue;
608 3
        $decodedSignature = base64_decode(str_replace(array("\r", "\n"), '', $signatureValueXML));
609 3
        $resp = openssl_verify($signContent, $decodedSignature, $objSSLPubKey);
610 3
        if ($resp != 1) {
611
            $msg = "Problema ({$resp}) ao verificar a assinatura do digital!!";
612
            $this->zGetOpenSSLError($msg);
613
        }
614 3
        return true;
615
    }
616
    
617
    /**
618
     * zDigCheck
619
     * @param DOMDocument $dom
620
     * @param string $tagid
621
     * @return boolean
622
     * @throws Exception\RuntimeException
623
     */
624 3
    private function zDigCheck($dom, $tagid = '')
625
    {
626 3
        $node = $dom->getNode($tagid, 0);
627 3
        if (empty($node)) {
628
            throw new Exception\RuntimeException(
629
                "A tag < $tagid > não existe no XML!!"
630
            );
631
        }
632 3
        if (! $this->zSignatureExists($dom)) {
633
            $msg = "O xml não contêm nenhuma assinatura para ser verificada.";
634
            throw new Exception\RuntimeException($msg);
635
        }
636
        //carregar o node em sua forma canonica
637 3
        $tagInf = $node->C14N(true, false, null, null);
638
        //calcular o hash sha1
639 3
        $hashValue = hash('sha1', $tagInf, true);
640
        //converter o hash para base64 para obter o digest do node
641 3
        $digestCalculado = base64_encode($hashValue);
642
        //pegar o digest informado no xml
643 3
        $digestInformado = $dom->getNodeValue('DigestValue');
644
        //compara os digests calculados e informados
645 3
        if ($digestCalculado != $digestInformado) {
646
            $msg = "O conteúdo do XML não confere com o Digest Value.\n
647
                Digest calculado [{$digestCalculado}], digest informado no XML [{$digestInformado}].\n
648
                O arquivo pode estar corrompido ou ter sido adulterado.";
649
            throw new Exception\RuntimeException($msg);
650
        }
651 3
        return true;
652
    }
653
    
654
    /**
655
     * zValidCerts
656
     * Verifica a data de validade do certificado digital
657
     * e compara com a data de hoje.
658
     * Caso o certificado tenha expirado o mesmo será removido das
659
     * pastas e o método irá retornar false.
660
     * @param string $pubKey chave publica
661
     * @return boolean
662
     */
663 3
    protected function zValidCerts($pubKey)
664
    {
665 3
        if (! $data = openssl_x509_read($pubKey)) {
666
                //o dado não é uma chave válida
667
                $this->zRemovePemFiles();
668
                $this->zLeaveParam();
669
                $this->error = "A chave passada está corrompida ou não é uma chave. Obtenha s chaves corretas!!";
670
                return false;
671
        }
672 3
        $certData = openssl_x509_parse($data);
673
        // reformata a data de validade;
674 3
        $ano = substr($certData['validTo'], 0, 2);
675 3
        $mes = substr($certData['validTo'], 2, 2);
676 3
        $dia = substr($certData['validTo'], 4, 2);
677
        //obtem o timestamp da data de validade do certificado
678 3
        $dValid = gmmktime(0, 0, 0, $mes, $dia, $ano);
679
        // obtem o timestamp da data de hoje
680 3
        $dHoje = gmmktime(0, 0, 0, date("m"), date("d"), date("Y"));
681
        // compara a data de validade com a data atual
682 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...
683 3
        if ($dHoje > $dValid) {
684 3
            $this->zRemovePemFiles();
685 3
            $this->zLeaveParam();
686 3
            $msg = "Data de validade vencida! [Valido até $dia/$mes/$ano]";
687 3
            $this->error = $msg;
688 3
            return false;
689
        }
690
        return true;
691
    }
692
    
693
    /**
694
     * zCleanPubKey
695
     * Remove a informação de inicio e fim do certificado
696
     * contido no formato PEM, deixando o certificado (chave publica) pronta para ser
697
     * anexada ao xml da NFe
698
     * @return string contendo o certificado limpo
699
     */
700 3
    protected function zCleanPubKey()
701
    {
702
        //inicializa variavel
703 3
        $data = '';
704
        //carregar a chave publica
705 3
        $pubKey = $this->pubKey;
706
        //carrega o certificado em um array usando o LF como referencia
707 3
        $arCert = explode("\n", $pubKey);
708 3
        foreach ($arCert as $curData) {
709
            //remove a tag de inicio e fim do certificado
710 3
            if (strncmp($curData, '-----BEGIN CERTIFICATE', 22) != 0 &&
711 3
                    strncmp($curData, '-----END CERTIFICATE', 20) != 0 ) {
712
                //carrega o resultado numa string
713 3
                $data .= trim($curData);
714 3
            }
715 3
        }
716 3
        return $data;
717
    }
718
    
719
    /**
720
     * zSplitLines
721
     * Divide a string do certificado publico em linhas
722
     * com 76 caracteres (padrão original)
723
     * @param string $cntIn certificado
724
     * @return string certificado reformatado
725
     */
726 3
    protected function zSplitLines($cntIn = '')
727
    {
728 3
        if ($cntIn != '') {
729 3
            $cnt = rtrim(chunk_split(str_replace(array("\r", "\n"), '', $cntIn), 76, "\n"));
730 3
        } else {
731
            $cnt = $cntIn;
732
        }
733 3
        return $cnt;
734
    }
735
    
736
    /**
737
     * zRemovePemFiles
738
     * Apaga os arquivos PEM do diretório
739
     * Isso deve ser feito quando um novo certificado é carregado
740
     * ou quando a validade do certificado expirou.
741
     */
742 9
    private function zRemovePemFiles()
743
    {
744 9
        if (is_file($this->pubKeyFile)) {
745
            unlink($this->pubKeyFile);
746
        }
747 9
        if (is_file($this->priKeyFile)) {
748
            unlink($this->priKeyFile);
749
        }
750 9
        if (is_file($this->certKeyFile)) {
751
            unlink($this->certKeyFile);
752
        }
753 9
    }
754
    
755
    /**
756
     * zLeaveParam
757
     * Limpa os parametros da classe
758
     */
759 3
    private function zLeaveParam()
760
    {
761 3
        $this->pfxCert='';
762 3
        $this->pubKey='';
763 3
        $this->priKey='';
764 3
        $this->certKey='';
765 3
        $this->pubKeyFile='';
766 3
        $this->priKeyFile='';
767 3
        $this->certKeyFile='';
768 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...
769 3
    }
770
    
771
    /**
772
     * zGetOpenSSLError
773
     * @param string $msg
774
     * @return string
775
     */
776
    protected function zGetOpenSSLError($msg = '')
777
    {
778
        while ($erro = openssl_error_string()) {
779
            $msg .= $erro . "\n";
780
        }
781
        throw new Exception\RuntimeException($msg);
782
    }
783
}
784