Completed
Push — master ( 2e557d...5e6163 )
by Roberto
07:24 queued 05:02
created

Pkcs12::__construct()   D

Complexity

Conditions 10
Paths 34

Size

Total Lines 38
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 10.3248

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 38
ccs 23
cts 27
cp 0.8519
rs 4.8196
cc 10
eloc 28
nc 34
nop 6
crap 10.3248

How to fix   Complexity   

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