Complex classes like Pkcs12 often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Pkcs12, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 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 | 8 | public function __construct( |
|
| 125 | $pathCerts = '', |
||
| 126 | $cnpj = '', |
||
| 127 | $pubKey = '', |
||
| 128 | $priKey = '', |
||
| 129 | $certKey = '', |
||
| 130 | $ignoreValidCert = false |
||
| 131 | ) { |
||
| 132 | 8 | $ncnpj = preg_replace('/[^0-9]/', '', $cnpj); |
|
|
|
|||
| 133 | 8 | if (empty($pathCerts)) { |
|
| 134 | //estabelecer diretorio default |
||
| 135 | $pathCerts = dirname(dirname(dirname(dirname(__FILE__)))).DIRECTORY_SEPARATOR.'certs'.DIRECTORY_SEPARATOR; |
||
| 136 | } |
||
| 137 | 8 | if (! empty($pathCerts)) { |
|
| 138 | 8 | if (!is_dir(trim($pathCerts))) { |
|
| 139 | 1 | throw new Exception\InvalidArgumentException( |
|
| 140 | "Um path válido para os certificados deve ser passado." |
||
| 141 | 1 | . " Diretório [$pathCerts] não foi localizado." |
|
| 142 | ); |
||
| 143 | } |
||
| 144 | 7 | $this->pathCerts = trim($pathCerts); |
|
| 145 | } |
||
| 146 | 7 | $this->ignoreValidCert = $ignoreValidCert; |
|
| 147 | 7 | $flagCert = false; |
|
| 148 | 7 | if ($pubKey != '' && $priKey != '' && strlen($pubKey) > 500 && strlen($priKey) > 500) { |
|
| 149 | 3 | $this->pubKey = $pubKey; |
|
| 150 | 3 | $this->priKey = $priKey; |
|
| 151 | 3 | $this->certKey = $priKey."\r\n".$pubKey; |
|
| 152 | 3 | $flagCert = true; |
|
| 153 | } |
||
| 154 | 7 | if ($certKey != '') { |
|
| 155 | $this->certKey = $certKey; |
||
| 156 | } |
||
| 157 | 7 | $this->cnpj = $cnpj; |
|
| 158 | 7 | if (! $this->zInit($flagCert)) { |
|
| 159 | 1 | throw new Exception\RuntimeException($this->error); |
|
| 160 | } |
||
| 161 | 6 | } |
|
| 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 | 7 | private function zInit($flagCert = false) |
|
| 174 | { |
||
| 175 | //se as chaves foram passadas na forma de strings então verificar a validade |
||
| 176 | 7 | if ($flagCert) { |
|
| 177 | //já que o certificado existe, verificar seu prazo de validade |
||
| 178 | //o certificado será removido se estiver vencido |
||
| 179 | 3 | return $this->zValidCerts($this->pubKey); |
|
| 180 | } else { |
||
| 181 | 4 | 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 | 4 | $this->priKeyFile = $this->pathCerts.$this->cnpj.'_priKEY.pem'; |
|
| 186 | //monta o path completo com o nome da chave publica |
||
| 187 | 4 | $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 | 4 | $this->certKeyFile = $this->pathCerts.$this->cnpj.'_certKEY.pem'; |
|
| 190 | //se as chaves não foram passadas em strings, verifica se os certificados existem |
||
| 191 | 4 | 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 | 4 | 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 | 1 | public function loadPfxFile( |
|
| 213 | $pathPfx = '', |
||
| 214 | $password = '', |
||
| 215 | $createFiles = true, |
||
| 216 | $ignoreValidity = false, |
||
| 217 | $ignoreOwner = false |
||
| 218 | ) { |
||
| 219 | 1 | 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 | 1 | $this->pfxCert = file_get_contents($pathPfx); |
|
| 225 | 1 | 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 | 3 | public function loadPfx( |
|
| 251 | $pfxContent = '', |
||
| 252 | $password = '', |
||
| 253 | $createFiles = true, |
||
| 254 | $ignoreValidity = false, |
||
| 255 | $ignoreOwner = false |
||
| 256 | ) { |
||
| 257 | 3 | $this->ignoreValidCert = $ignoreValidity; |
|
| 258 | 3 | 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 | 3 | $x509certdata = array(); |
|
| 265 | 3 | 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 | 3 | $this->pfxCert = $pfxContent; |
|
| 271 | //verifica sua data de validade |
||
| 272 | 3 | if (! $this->zValidCerts($x509certdata['cert'])) { |
|
| 273 | throw new Exception\RuntimeException($this->error); |
||
| 274 | } |
||
| 275 | 3 | $this->cnpjCert = Asn::getCNPJCert($x509certdata['cert']); |
|
| 276 | 3 | if (!$ignoreOwner) { |
|
| 277 | 1 | if (substr($this->cnpj, 0, 8) != substr($this->cnpjCert, 0, 8)) { |
|
| 278 | 1 | throw new Exception\InvalidArgumentException( |
|
| 279 | 1 | "O Certificado fornecido pertence a outro CNPJ!!" |
|
| 280 | ); |
||
| 281 | } |
||
| 282 | } |
||
| 283 | //monta o path completo com o nome da chave privada |
||
| 284 | 2 | $this->priKeyFile = $this->pathCerts.$this->cnpj.'_priKEY.pem'; |
|
| 285 | //monta o path completo com o nome da chave publica |
||
| 286 | 2 | $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 | 2 | $this->certKeyFile = $this->pathCerts.$this->cnpj.'_certKEY.pem'; |
|
| 289 | 2 | $this->zRemovePemFiles(); |
|
| 290 | 2 | if ($createFiles) { |
|
| 291 | $this->zSavePemFiles($x509certdata); |
||
| 292 | } |
||
| 293 | 2 | $this->pubKey=$x509certdata['cert']; |
|
| 294 | 2 | $this->priKey=$x509certdata['pkey']; |
|
| 295 | 2 | $this->certKey=$x509certdata['pkey']."\r\n".$x509certdata['cert']; |
|
| 296 | 2 | 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() |
||
| 335 | |||
| 336 | /** |
||
| 337 | * Retorna o CNPJ do certificado |
||
| 338 | * @return string |
||
| 339 | */ |
||
| 340 | public function getCNPJCert() |
||
| 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 | 1 | public function aadChain($aCerts = array()) |
|
| 352 | { |
||
| 353 | 1 | $certificate = $this->certKey; |
|
| 354 | 1 | foreach ($aCerts as $cert) { |
|
| 355 | 1 | if (is_file($cert)) { |
|
| 356 | 1 | $dados = file_get_contents($cert); |
|
| 357 | 1 | $certificate .= "\r\n" . $dados; |
|
| 358 | } else { |
||
| 359 | if (trim($cert) != '') { |
||
| 360 | 1 | $certificate .= "\r\n" . $cert; |
|
| 361 | } |
||
| 362 | } |
||
| 363 | } |
||
| 364 | 1 | $this->certKey = $certificate; |
|
| 365 | 1 | if (is_file($this->certKeyFile)) { |
|
| 366 | file_put_contents($this->certKeyFile, $certificate); |
||
| 367 | } |
||
| 368 | 1 | } |
|
| 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 | 1 | public function signXML($docxml, $tagid = '', $marcador = 'Id', $algorithm = 'SHA1') |
|
| 381 | { |
||
| 382 | //caso não tenha as chaves cai fora |
||
| 383 | 1 | 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 | 1 | 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 | 1 | $objSSLPriKey = openssl_get_privatekey($this->priKey); |
|
| 394 | 1 | if ($objSSLPriKey === false) { |
|
| 395 | $msg = "Houve erro no carregamento da chave privada."; |
||
| 396 | $this->zGetOpenSSLError($msg); |
||
| 397 | } |
||
| 398 | 1 | $xml = $docxml; |
|
| 399 | 1 | if (is_file($docxml)) { |
|
| 400 | $xml = file_get_contents($docxml); |
||
| 401 | } |
||
| 402 | //remove sujeiras do xml |
||
| 403 | 1 | $order = array("\r\n", "\n", "\r", "\t"); |
|
| 404 | 1 | $xml = str_replace($order, '', $xml); |
|
| 405 | 1 | $xmldoc = new Dom(); |
|
| 406 | 1 | $xmldoc->loadXMLString($xml); |
|
| 407 | //coloca o node raiz em uma variável |
||
| 408 | 1 | $root = $xmldoc->documentElement; |
|
| 409 | //extrair a tag com os dados a serem assinados |
||
| 410 | 1 | $node = $xmldoc->getElementsByTagName($tagid)->item(0); |
|
| 411 | 1 | if (!isset($node)) { |
|
| 412 | throw new Exception\RuntimeException( |
||
| 413 | "A tag < $tagid > não existe no XML!!" |
||
| 414 | ); |
||
| 415 | } |
||
| 416 | 1 | $this->docId = $node->getAttribute($marcador); |
|
| 417 | 1 | $xmlResp = $xml; |
|
| 418 | 1 | if (! $this->zSignatureExists($xmldoc)) { |
|
| 419 | //executa a assinatura |
||
| 420 | 1 | $xmlResp = $this->zSignXML($xmldoc, $root, $node, $objSSLPriKey, $marcador, $algorithm); |
|
| 421 | } |
||
| 422 | //libera a chave privada |
||
| 423 | 1 | openssl_free_key($objSSLPriKey); |
|
| 424 | 1 | 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 | 1 | private function zSignXML($xmldoc, $root, $node, $objSSLPriKey, $marcador, $algorithm = 'SHA1') |
|
| 440 | { |
||
| 441 | 1 | $nsDSIG = 'http://www.w3.org/2000/09/xmldsig#'; |
|
| 442 | 1 | $nsCannonMethod = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; |
|
| 443 | 1 | $nsSignatureMethod = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'; |
|
| 444 | 1 | $nsDigestMethod = 'http://www.w3.org/2000/09/xmldsig#sha1'; |
|
| 445 | 1 | $signAlgorithm = OPENSSL_ALGO_SHA1; |
|
| 446 | //incluido para atender requisitos de assinatura do sped-efinanceira |
||
| 447 | 1 | 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 | 1 | $nsTransformMethod1 ='http://www.w3.org/2000/09/xmldsig#enveloped-signature'; |
|
| 453 | 1 | $nsTransformMethod2 = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'; |
|
| 454 | //pega o atributo id do node a ser assinado |
||
| 455 | 1 | $idSigned = trim($node->getAttribute($marcador)); |
|
| 456 | //extrai os dados da tag para uma string na forma canonica |
||
| 457 | 1 | $dados = $node->C14N(true, false, null, null); |
|
| 458 | //calcular o hash dos dados |
||
| 459 | 1 | if ($algorithm == 'SHA256') { |
|
| 460 | $hashValue = hash('sha256', $dados, true); |
||
| 461 | } else { |
||
| 462 | 1 | $hashValue = hash('sha1', $dados, true); |
|
| 463 | } |
||
| 464 | //converter o hash para base64 |
||
| 465 | 1 | $digValue = base64_encode($hashValue); |
|
| 466 | //cria o node <Signature> |
||
| 467 | 1 | $signatureNode = $xmldoc->createElementNS($nsDSIG, 'Signature'); |
|
| 468 | //adiciona a tag <Signature> ao node raiz |
||
| 469 | 1 | $root->appendChild($signatureNode); |
|
| 470 | //cria o node <SignedInfo> |
||
| 471 | 1 | $signedInfoNode = $xmldoc->createElement('SignedInfo'); |
|
| 472 | //adiciona o node <SignedInfo> ao <Signature> |
||
| 473 | 1 | $signatureNode->appendChild($signedInfoNode); |
|
| 474 | //cria no node com o método de canonização dos dados |
||
| 475 | 1 | $canonicalNode = $xmldoc->createElement('CanonicalizationMethod'); |
|
| 476 | //adiona o <CanonicalizationMethod> ao node <SignedInfo> |
||
| 477 | 1 | $signedInfoNode->appendChild($canonicalNode); |
|
| 478 | //seta o atributo ao node <CanonicalizationMethod> |
||
| 479 | 1 | $canonicalNode->setAttribute('Algorithm', $nsCannonMethod); |
|
| 480 | //cria o node <SignatureMethod> |
||
| 481 | 1 | $signatureMethodNode = $xmldoc->createElement('SignatureMethod'); |
|
| 482 | //adiciona o node <SignatureMethod> ao node <SignedInfo> |
||
| 483 | 1 | $signedInfoNode->appendChild($signatureMethodNode); |
|
| 484 | //seta o atributo Algorithm ao node <SignatureMethod> |
||
| 485 | 1 | $signatureMethodNode->setAttribute('Algorithm', $nsSignatureMethod); |
|
| 486 | //cria o node <Reference> |
||
| 487 | 1 | $referenceNode = $xmldoc->createElement('Reference'); |
|
| 488 | //adiciona o node <Reference> ao node <SignedInfo> |
||
| 489 | 1 | $signedInfoNode->appendChild($referenceNode); |
|
| 490 | //seta o atributo URI a node <Reference> |
||
| 491 | 1 | $referenceNode->setAttribute('URI', '#'.$idSigned); |
|
| 492 | //cria o node <Transforms> |
||
| 493 | 1 | $transformsNode = $xmldoc->createElement('Transforms'); |
|
| 494 | //adiciona o node <Transforms> ao node <Reference> |
||
| 495 | 1 | $referenceNode->appendChild($transformsNode); |
|
| 496 | //cria o primeiro node <Transform> OBS: no singular |
||
| 497 | 1 | $transfNode1 = $xmldoc->createElement('Transform'); |
|
| 498 | //adiciona o primeiro node <Transform> ao node <Transforms> |
||
| 499 | 1 | $transformsNode->appendChild($transfNode1); |
|
| 500 | //set o atributo Algorithm ao primeiro node <Transform> |
||
| 501 | 1 | $transfNode1->setAttribute('Algorithm', $nsTransformMethod1); |
|
| 502 | //cria outro node <Transform> OBS: no singular |
||
| 503 | 1 | $transfNode2 = $xmldoc->createElement('Transform'); |
|
| 504 | //adiciona o segundo node <Transform> ao node <Transforms> |
||
| 505 | 1 | $transformsNode->appendChild($transfNode2); |
|
| 506 | //set o atributo Algorithm ao segundo node <Transform> |
||
| 507 | 1 | $transfNode2->setAttribute('Algorithm', $nsTransformMethod2); |
|
| 508 | //cria o node <DigestMethod> |
||
| 509 | 1 | $digestMethodNode = $xmldoc->createElement('DigestMethod'); |
|
| 510 | //adiciona o node <DigestMethod> ao node <Reference> |
||
| 511 | 1 | $referenceNode->appendChild($digestMethodNode); |
|
| 512 | //seta o atributo Algorithm ao node <DigestMethod> |
||
| 513 | 1 | $digestMethodNode->setAttribute('Algorithm', $nsDigestMethod); |
|
| 514 | //cria o node <DigestValue> |
||
| 515 | 1 | $digestValueNode = $xmldoc->createElement('DigestValue', $digValue); |
|
| 516 | //adiciona o node <DigestValue> ao node <Reference> |
||
| 517 | 1 | $referenceNode->appendChild($digestValueNode); |
|
| 518 | //extrai node <SignedInfo> para uma string na sua forma canonica |
||
| 519 | 1 | $cnSignedInfoNode = $signedInfoNode->C14N(true, false, null, null); |
|
| 520 | //cria uma variavel vazia que receberá a assinatura |
||
| 521 | 1 | $signature = ''; |
|
| 522 | //calcula a assinatura do node canonizado <SignedInfo> |
||
| 523 | //usando a chave privada em formato PEM |
||
| 524 | 1 | if (! openssl_sign($cnSignedInfoNode, $signature, $objSSLPriKey, $signAlgorithm)) { |
|
| 525 | $msg = "Houve erro durante a assinatura digital.\n"; |
||
| 526 | $this->zGetOpenSSLError($msg); |
||
| 527 | } |
||
| 528 | //converte a assinatura em base64 |
||
| 529 | 1 | $signatureValue = base64_encode($signature); |
|
| 530 | //cria o node <SignatureValue> |
||
| 531 | 1 | $signatureValueNode = $xmldoc->createElement('SignatureValue', $signatureValue); |
|
| 532 | //adiciona o node <SignatureValue> ao node <Signature> |
||
| 533 | 1 | $signatureNode->appendChild($signatureValueNode); |
|
| 534 | //cria o node <KeyInfo> |
||
| 535 | 1 | $keyInfoNode = $xmldoc->createElement('KeyInfo'); |
|
| 536 | //adiciona o node <KeyInfo> ao node <Signature> |
||
| 537 | 1 | $signatureNode->appendChild($keyInfoNode); |
|
| 538 | //cria o node <X509Data> |
||
| 539 | 1 | $x509DataNode = $xmldoc->createElement('X509Data'); |
|
| 540 | //adiciona o node <X509Data> ao node <KeyInfo> |
||
| 541 | 1 | $keyInfoNode->appendChild($x509DataNode); |
|
| 542 | //remove linhas desnecessárias do certificado |
||
| 543 | 1 | $pubKeyClean = $this->zCleanPubKey(); |
|
| 544 | //cria o node <X509Certificate> |
||
| 545 | 1 | $x509CertificateNode = $xmldoc->createElement('X509Certificate', $pubKeyClean); |
|
| 546 | //adiciona o node <X509Certificate> ao node <X509Data> |
||
| 547 | 1 | $x509DataNode->appendChild($x509CertificateNode); |
|
| 548 | //salva o xml completo em uma string |
||
| 549 | 1 | $xmlResp = $xmldoc->saveXML(); |
|
| 550 | //retorna o documento assinado |
||
| 551 | 1 | return $xmlResp; |
|
| 552 | } |
||
| 553 | |||
| 554 | /** |
||
| 555 | * signatureExists |
||
| 556 | * Check se o xml possi a tag Signature |
||
| 557 | * @param DOMDocument $dom |
||
| 558 | * @return boolean |
||
| 559 | */ |
||
| 560 | 2 | private function zSignatureExists($dom) |
|
| 561 | { |
||
| 562 | 2 | $signature = $dom->getElementsByTagName('Signature')->item(0); |
|
| 563 | 2 | if (! isset($signature)) { |
|
| 564 | 1 | return false; |
|
| 565 | } |
||
| 566 | 1 | return true; |
|
| 567 | } |
||
| 568 | |||
| 569 | /** |
||
| 570 | * verifySignature |
||
| 571 | * Verifica a validade da assinatura digital contida no xml |
||
| 572 | * @param string $docxml conteudo do xml a ser verificado ou o path completo |
||
| 573 | * @param string $tagid tag que foi assinada no documento xml |
||
| 574 | * @return boolean |
||
| 575 | * @throws Exception\InvalidArgumentException |
||
| 576 | * @throws Exception\RuntimeException |
||
| 577 | */ |
||
| 578 | 1 | public function verifySignature($docxml = '', $tagid = '') |
|
| 579 | { |
||
| 580 | 1 | if ($docxml == '') { |
|
| 581 | $msg = "Não foi passado um xml para a verificação."; |
||
| 582 | throw new Exception\InvalidArgumentException($msg); |
||
| 583 | } |
||
| 584 | 1 | if ($tagid == '') { |
|
| 585 | $msg = "Não foi indicada a TAG a ser verificada."; |
||
| 586 | throw new Exception\InvalidArgumentException($msg); |
||
| 587 | } |
||
| 588 | 1 | $xml = $docxml; |
|
| 589 | 1 | if (is_file($docxml)) { |
|
| 590 | 1 | $xml = file_get_contents($docxml); |
|
| 591 | } |
||
| 592 | 1 | $dom = new Dom(); |
|
| 593 | 1 | $dom->loadXMLString($xml); |
|
| 594 | 1 | $flag = $this->zDigCheck($dom, $tagid); |
|
| 595 | 1 | $flag = $this->zSignCheck($dom); |
|
| 596 | 1 | return $flag; |
|
| 597 | } |
||
| 598 | |||
| 599 | /** |
||
| 600 | * zSignCheck |
||
| 601 | * @param DOMDocument $dom |
||
| 602 | * @return boolean |
||
| 603 | * @throws Exception\RuntimeException |
||
| 604 | */ |
||
| 605 | 1 | private function zSignCheck($dom) |
|
| 606 | { |
||
| 607 | //SignatureMethod attribute Algorithm |
||
| 608 | //<SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> |
||
| 609 | 1 | $sigMethAlgo = $dom->getNode('SignatureMethod', 0)->getAttribute('Algorithm'); |
|
| 610 | 1 | if ($sigMethAlgo == 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') { |
|
| 611 | 1 | $signAlgorithm = OPENSSL_ALGO_SHA1; |
|
| 612 | } else { |
||
| 613 | $signAlgorithm = OPENSSL_ALGO_SHA256; |
||
| 614 | } |
||
| 615 | // Obter e remontar a chave publica do xml |
||
| 616 | 1 | $x509Certificate = $dom->getNodeValue('X509Certificate'); |
|
| 617 | $x509Certificate = "-----BEGIN CERTIFICATE-----\n" |
||
| 618 | 1 | . $this->zSplitLines($x509Certificate) |
|
| 619 | 1 | . "\n-----END CERTIFICATE-----\n"; |
|
| 620 | //carregar a chave publica remontada |
||
| 621 | 1 | $objSSLPubKey = openssl_pkey_get_public($x509Certificate); |
|
| 622 | 1 | if ($objSSLPubKey === false) { |
|
| 623 | $msg = "Ocorreram problemas ao carregar a chave pública. Certificado incorreto ou corrompido!!"; |
||
| 624 | $this->zGetOpenSSLError($msg); |
||
| 625 | } |
||
| 626 | //remontando conteudo que foi assinado |
||
| 627 | 1 | $signContent = $dom->getElementsByTagName('SignedInfo')->item(0)->C14N(true, false, null, null); |
|
| 628 | // validando assinatura do conteudo |
||
| 629 | 1 | $signatureValueXML = $dom->getElementsByTagName('SignatureValue')->item(0)->nodeValue; |
|
| 630 | 1 | $decodedSignature = base64_decode(str_replace(array("\r", "\n"), '', $signatureValueXML)); |
|
| 631 | 1 | $resp = openssl_verify($signContent, $decodedSignature, $objSSLPubKey, $signAlgorithm); |
|
| 632 | 1 | if ($resp != 1) { |
|
| 633 | $msg = "Problema ({$resp}) ao verificar a assinatura do digital!!"; |
||
| 634 | $this->zGetOpenSSLError($msg); |
||
| 635 | } |
||
| 636 | 1 | return true; |
|
| 637 | } |
||
| 638 | |||
| 639 | /** |
||
| 640 | * zDigCheck |
||
| 641 | * @param DOMDocument $dom |
||
| 642 | * @param string $tagid |
||
| 643 | * @return boolean |
||
| 644 | * @throws Exception\RuntimeException |
||
| 645 | */ |
||
| 646 | 1 | private function zDigCheck($dom, $tagid = '') |
|
| 647 | { |
||
| 648 | 1 | $node = $dom->getNode($tagid, 0); |
|
| 649 | 1 | if (empty($node)) { |
|
| 650 | throw new Exception\RuntimeException( |
||
| 651 | "A tag < $tagid > não existe no XML!!" |
||
| 652 | ); |
||
| 653 | } |
||
| 654 | 1 | if (! $this->zSignatureExists($dom)) { |
|
| 655 | $msg = "O xml não contêm nenhuma assinatura para ser verificada."; |
||
| 656 | throw new Exception\RuntimeException($msg); |
||
| 657 | } |
||
| 658 | 1 | $sigMethAlgo = $dom->getNode('SignatureMethod', 0)->getAttribute('Algorithm'); |
|
| 659 | 1 | if ($sigMethAlgo == 'http://www.w3.org/2000/09/xmldsig#rsa-sha1') { |
|
| 660 | 1 | $hashAlgorithm = 'sha1'; |
|
| 661 | } else { |
||
| 662 | $hashAlgorithm = 'sha256'; |
||
| 663 | } |
||
| 664 | //carregar o node em sua forma canonica |
||
| 665 | 1 | $tagInf = $node->C14N(true, false, null, null); |
|
| 666 | //calcular o hash sha1 |
||
| 667 | 1 | $hashValue = hash($hashAlgorithm, $tagInf, true); |
|
| 668 | //converter o hash para base64 para obter o digest do node |
||
| 669 | 1 | $digestCalculado = base64_encode($hashValue); |
|
| 670 | //pegar o digest informado no xml |
||
| 671 | 1 | $digestInformado = $dom->getNodeValue('DigestValue'); |
|
| 672 | //compara os digests calculados e informados |
||
| 673 | 1 | if ($digestCalculado != $digestInformado) { |
|
| 674 | $msg = "O conteúdo do XML não confere com o Digest Value.\n |
||
| 675 | Digest calculado [{$digestCalculado}], digest informado no XML [{$digestInformado}].\n |
||
| 676 | O arquivo pode estar corrompido ou ter sido adulterado."; |
||
| 677 | throw new Exception\RuntimeException($msg); |
||
| 678 | } |
||
| 679 | 1 | return true; |
|
| 680 | } |
||
| 681 | |||
| 682 | /** |
||
| 683 | * zValidCerts |
||
| 684 | * Verifica a data de validade do certificado digital |
||
| 685 | * e compara com a data de hoje. |
||
| 686 | * Caso o certificado tenha expirado o mesmo será removido das |
||
| 687 | * pastas e o método irá retornar false. |
||
| 688 | * @param string $pubKey chave publica |
||
| 689 | * @return boolean |
||
| 690 | */ |
||
| 691 | 6 | protected function zValidCerts($pubKey) |
|
| 718 | |||
| 719 | /** |
||
| 720 | * zCleanPubKey |
||
| 721 | * Remove a informação de inicio e fim do certificado |
||
| 722 | * contido no formato PEM, deixando o certificado (chave publica) pronta para ser |
||
| 723 | * anexada ao xml da NFe |
||
| 724 | * @return string contendo o certificado limpo |
||
| 725 | */ |
||
| 726 | 1 | protected function zCleanPubKey() |
|
| 727 | { |
||
| 728 | //inicializa variavel |
||
| 729 | 1 | $data = ''; |
|
| 730 | //carregar a chave publica |
||
| 744 | |||
| 745 | /** |
||
| 746 | * zSplitLines |
||
| 747 | * Divide a string do certificado publico em linhas |
||
| 748 | * com 76 caracteres (padrão original) |
||
| 749 | * @param string $cntIn certificado |
||
| 750 | * @return string certificado reformatado |
||
| 751 | */ |
||
| 752 | 1 | protected function zSplitLines($cntIn = '') |
|
| 761 | |||
| 762 | /** |
||
| 763 | * zRemovePemFiles |
||
| 764 | * Apaga os arquivos PEM do diretório |
||
| 765 | * Isso deve ser feito quando um novo certificado é carregado |
||
| 766 | * ou quando a validade do certificado expirou. |
||
| 767 | */ |
||
| 768 | 6 | private function zRemovePemFiles() |
|
| 780 | |||
| 781 | /** |
||
| 782 | * zGetOpenSSLError |
||
| 783 | * @param string $msg |
||
| 784 | * @return string |
||
| 785 | */ |
||
| 786 | protected function zGetOpenSSLError($msg = '') |
||
| 793 | } |
||
| 794 |
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.
Both the
$myVarassignment in line 1 and the$higherassignment in line 2 are dead. The first because$myVaris never used and the second because$higheris always overwritten for every possible time line.