Skip to content

Commit bd1eb71

Browse files
authored
Merge pull request #51 from eclipxe13/development
Create certificate using PEM contents (version 2.12.3)
2 parents 62a7ad3 + cde31ed commit bd1eb71

File tree

7 files changed

+66
-30
lines changed

7 files changed

+66
-30
lines changed

docs/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@
2828
- Change visibility of `CfdiUtils\Cleaner\Cleaner#removeIncompleteSchemaLocation()` to private.
2929

3030

31+
## Version 2.12.3 2019-09-26
32+
33+
- `CfdiUtils\Certificado\Certificado` can be created using PEM contents and not only a certificate path.
34+
- `CfdiUtils\Creator33` can use a certificate without associated filename.
35+
- `CfdiUtils\RetencionesCreator10` can use a certificate without associated filename.
36+
- `CfdiUtils\Certificado\NodeCertificado` can obtain the certificate without creating a temporary file.
37+
38+
3139
## Version 2.12.2 2019-09-24
3240

3341
- When cannot load an Xml string include `LibXMLError` information into exception, like:

src/CfdiUtils/Certificado/Certificado.php

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,36 @@ class Certificado
3838
/**
3939
* Certificado constructor.
4040
*
41-
* @param string $filename
41+
* @param string $filename Allows filename or certificate contents (PEM or DER)
4242
* @param OpenSSL $openSSL
43-
* @throws \UnexpectedValueException when the file does not exists or is not readable
44-
* @throws \UnexpectedValueException when cannot read the certificate file or is empty
45-
* @throws \RuntimeException when cannot parse the certificate file or is empty
43+
* @throws \UnexpectedValueException when the certificate does not exists or is not readable
44+
* @throws \UnexpectedValueException when cannot read the certificate or is empty
45+
* @throws \RuntimeException when cannot parse the certificate or is empty
4646
* @throws \RuntimeException when cannot get serialNumberHex or serialNumber from certificate
4747
*/
4848
public function __construct(string $filename, OpenSSL $openSSL = null)
4949
{
50-
$this->assertFileExists($filename);
51-
$contents = strval(file_get_contents($filename));
50+
$this->setOpenSSL($openSSL ?: new OpenSSL());
51+
$contents = $this->extractPemCertificate($filename);
52+
// using $filename as PEM content did not retrieve any result, so, use it as path
5253
if ('' === $contents) {
53-
throw new \UnexpectedValueException("File $filename is empty");
54+
$sourceName = 'file ' . $filename;
55+
$this->assertFileExists($filename);
56+
$contents = file_get_contents($filename) ?: '';
57+
if ('' === $contents) {
58+
throw new \UnexpectedValueException("File $filename is empty");
59+
}
60+
// this will take PEM contents or perform a PHP conversion from DER to PEM
61+
$contents = $this->obtainPemCertificate($contents);
62+
} else {
63+
$filename = '';
64+
$sourceName = '(contents)';
5465
}
55-
$this->setOpenSSL($openSSL ?: new OpenSSL());
56-
$contents = $this->obtainPemCertificate($contents);
5766

5867
// get the certificate data
5968
$data = openssl_x509_parse($contents, true);
6069
if (! is_array($data)) {
61-
throw new \RuntimeException("Cannot parse the certificate file $filename");
70+
throw new \RuntimeException("Cannot parse the certificate $sourceName");
6271
}
6372

6473
// get the public key
@@ -74,7 +83,7 @@ public function __construct(string $filename, OpenSSL $openSSL = null)
7483
} elseif (isset($data['serialNumber'])) {
7584
$serial->loadDecimal($data['serialNumber']);
7685
} else {
77-
throw new \RuntimeException("Cannot get serialNumberHex or serialNumber from certificate file $filename");
86+
throw new \RuntimeException("Cannot get serialNumberHex or serialNumber from certificate $sourceName");
7887
}
7988
$this->serial = $serial;
8089
$this->validFrom = $data['validFrom_time_t'] ?? 0;
@@ -84,12 +93,29 @@ public function __construct(string $filename, OpenSSL $openSSL = null)
8493
$this->filename = $filename;
8594
}
8695

96+
private function extractPemCertificate(string $contents): string
97+
{
98+
if (strlen($contents) < 2000) {
99+
return ''; // is too short to be a PEM certificate
100+
}
101+
$openssl = $this->getOpenSSL();
102+
$decoded = @base64_decode($contents, true) ?: '';
103+
if ($contents === base64_encode($decoded)) { // is a one liner certificate
104+
$doubleEncoded = $openssl->readPemContents($decoded)->certificate();
105+
if ($doubleEncoded !== '') {
106+
return $doubleEncoded;
107+
}
108+
$contents = $this->getOpenSSL()->derCerConvertPhp($decoded);
109+
}
110+
return $openssl->readPemContents($contents)->certificate();
111+
}
112+
87113
private function obtainPemCertificate(string $contents): string
88114
{
89115
$openssl = $this->getOpenSSL();
90116
$extracted = $openssl->readPemContents($contents)->certificate();
91117
if ('' === $extracted) { // cannot extract, could be on DER format
92-
$extracted = $openssl->derCerConvertPhp($contents);
118+
$extracted = $this->getOpenSSL()->derCerConvertPhp($contents);
93119
}
94120
return $extracted;
95121
}

src/CfdiUtils/Certificado/NodeCertificado.php

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<?php
22
namespace CfdiUtils\Certificado;
33

4-
use CfdiUtils\Internals\TemporaryFile;
54
use CfdiUtils\Nodes\NodeInterface;
65

76
class NodeCertificado
@@ -99,14 +98,10 @@ public function save(string $filename)
9998
*/
10099
public function obtain(): Certificado
101100
{
102-
$temporaryFile = TemporaryFile::create();
103-
// the temporary name was created
104-
try {
105-
$this->save($temporaryFile->getPath());
106-
$certificado = new Certificado($temporaryFile->getPath());
107-
return $certificado;
108-
} finally {
109-
$temporaryFile->remove();
101+
$certificado = $this->extract();
102+
if ('' === $certificado) {
103+
throw new \RuntimeException('The certificado attribute is empty');
110104
}
105+
return new Certificado(base64_encode($certificado));
111106
}
112107
}

src/CfdiUtils/CfdiCreator33.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,9 @@ public function comprobante(): Comprobante
7777
public function putCertificado(Certificado $certificado, bool $putEmisorRfcNombre = true)
7878
{
7979
$this->setCertificado($certificado);
80-
$cerfile = $certificado->getFilename();
8180
$this->comprobante['NoCertificado'] = $certificado->getSerial();
82-
if (file_exists($cerfile)) {
83-
$this->comprobante['Certificado'] = base64_encode(strval(file_get_contents($cerfile)));
84-
}
81+
$pemContents = implode('', preg_grep('/^((?!-).)*$/', explode(PHP_EOL, $certificado->getPemContents())));
82+
$this->comprobante['Certificado'] = $pemContents;
8583
if ($putEmisorRfcNombre) {
8684
$emisor = $this->comprobante->searchNode('cfdi:Emisor');
8785
if (null === $emisor) {

src/CfdiUtils/Retenciones/RetencionesCreator10.php

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,9 @@ public function retenciones(): Retenciones
4747
public function putCertificado(Certificado $certificado)
4848
{
4949
$this->setCertificado($certificado);
50-
$cerfile = $certificado->getFilename();
5150
$this->retenciones['NumCert'] = $certificado->getSerial();
52-
if (file_exists($cerfile)) {
53-
$this->retenciones['Cert'] = base64_encode(strval(file_get_contents($cerfile)));
54-
}
51+
$pemContents = implode('', preg_grep('/^((?!-).)*$/', explode(PHP_EOL, $certificado->getPemContents())));
52+
$this->retenciones['Cert'] = $pemContents;
5553
}
5654

5755
public function buildCadenaDeOrigen(): string

tests/CfdiUtilsTests/Certificado/CertificadoTest.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,17 @@ public function testVerifyWithKnownData()
6464
$this->assertTrue($verify);
6565
}
6666

67+
public function testConstructUsingPemContents()
68+
{
69+
$pemfile = $this->utilAsset('certs/CSD01_AAA010101AAA.cer.pem');
70+
$contents = file_get_contents($pemfile) ?: '';
71+
72+
$fromFile = new Certificado($pemfile);
73+
$fromContents = new Certificado($contents);
74+
75+
$this->assertSame($fromFile->getPemContents(), $fromContents->getPemContents());
76+
}
77+
6778
public function testVerifyWithInvalidData()
6879
{
6980
$dataFile = $this->utilAsset('certs/data-to-sign.txt');

tests/CfdiUtilsTests/Certificado/NodeCertificadoTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public function testObtain()
106106
$nodeCertificado = $this->createNodeCertificado(strval(file_get_contents($cfdiSample)));
107107

108108
$certificate = $nodeCertificado->obtain();
109-
$this->assertFileNotExists($certificate->getFilename());
109+
$this->assertEmpty($certificate->getFilename());
110110
$this->assertEquals('CTO021007DZ8', $certificate->getRfc());
111111
}
112112
}

0 commit comments

Comments
 (0)