From cdd953b4a4d46b2d5fc0d49e38501eaad135eb7e Mon Sep 17 00:00:00 2001 From: Fernando Herrero Date: Fri, 22 Dec 2017 04:35:03 +0100 Subject: [PATCH 1/8] Add PKCS#12 Certificate Store support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added support for certificates in PKCS # 12 format (p12, pfx) Use of random_int if available. Reformatting the definition of the namespace Refactoring the generation of $ certIssuer Se añade soporte para certificados en formato PKCS#12 (p12, pfx) Utilización de random_int si está disponible. Reformateo de la definición del namespace. Refactorización de la generación de $certIssuer. --- src/Facturae.php | 100 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 13 deletions(-) diff --git a/src/Facturae.php b/src/Facturae.php index ace2b87..8940f55 100644 --- a/src/Facturae.php +++ b/src/Facturae.php @@ -104,7 +104,11 @@ public function __construct($schemaVersion=self::SCHEMA_3_2_1) { * @return int Random number */ private function random() { - return rand(100000, 999999); + if (function_exists('random_int')) { + return random_int(0x10000000, 0x7FFFFFFF); + } else { + return rand(100000, 999999); + } } @@ -347,6 +351,48 @@ public function setSignTime($time) { } + /** + * Load a PKCS#12 Certificate Store + * + * @param string $pkcs12_file The certificate store file name + * @param string $pkcs12_pass Encryption password for unlocking the PKCS#12 file + * @return bool true on success or FALSE on failure. + */ + public function load_pkcs12 ($pkcs12_file, $pkcs12_pass) { + $this->publicKey = null; + $this->privateKey = null; + + if (is_file($pkcs12_file) and !empty($pkcs12_pass)) { + if (openssl_pkcs12_read(file_get_contents($pkcs12_file), $certs, $pkcs12_pass)) { + $this->publicKey = $certs['cert']; + $this->privateKey = openssl_pkey_get_private($certs['pkey']); + } + } + + return (!empty($this->publicKey) and !empty($this->privateKey)); + } + + /** + * Load a X.509 certificate and PEM encoded private key + * + * @param string $publicPath Path to public key PEM file + * @param string $privatePath Path to private key PEM file + * @param string $passphrase Private key passphrase + * @return bool true on success or FALSE on failure. + */ + public function load_x509 ($publicPath, $privatePath, $passphrase = '') { + $this->publicKey = null; + $this->privateKey = null; + + if (is_file($publicPath) and is_file($privatePath)) { + $this->publicKey = openssl_x509_read(file_get_contents($publicPath)); + $this->privateKey = openssl_pkey_get_private(file_get_contents($privatePath), $passphrase); + } + + return (!empty($this->publicKey) and !empty($this->privateKey)); + } + + /** * Sign * @@ -354,11 +400,9 @@ public function setSignTime($time) { * @param string $privatePath Path to private key PEM file * @param string $passphrase Private key passphrase * @param array $policy Facturae sign policy + * @return bool true on success or FALSE on failure. */ public function sign($publicPath, $privatePath, $passphrase, $policy=self::SIGN_POLICY_3_1) { - $this->publicKey = openssl_x509_read(file_get_contents($publicPath)); - $this->privateKey = openssl_pkey_get_private( - file_get_contents($privatePath), $passphrase); $this->signPolicy = $policy; // Generate random IDs @@ -370,6 +414,33 @@ public function sign($publicPath, $privatePath, $passphrase, $policy=self::SIGN_ $this->referenceID = $this->random(); $this->signatureSignedPropertiesID = $this->random(); $this->signatureObjectID = $this->random(); + + return $this->loadx509($publicPath, $privatePath, $passphrase); + } + + + /** + * Sign with PKCS#12 Certificate Store + * + * @param string $pkcs12_file The certificate store file name + * @param string $pkcs12_pass Encryption password for unlocking the PKCS#12 file + * @param array $policy Facturae sign policy + * @return bool true on success or FALSE on failure. + */ + public function sign_pkcs12($pkcs12_file, $pkcs12_pass, $policy=self::SIGN_POLICY_3_1) { + $this->signPolicy = $policy; + + // Generate random IDs + $this->signatureID = $this->random(); + $this->signedInfoID = $this->random(); + $this->signedPropertiesID = $this->random(); + $this->signatureValueID = $this->random(); + $this->certificateID = $this->random(); + $this->referenceID = $this->random(); + $this->signatureSignedPropertiesID = $this->random(); + $this->signatureObjectID = $this->random(); + + return load_pkcs12($pkcs12_file, $pkcs12_pass); } @@ -386,20 +457,23 @@ private function injectSignature($xml) { $xml = str_replace("\r", "", $xml); // Define namespace - $xmlns = 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ' . - 'xmlns:etsi="http://uri.etsi.org/01903/v1.3.2#" ' . - 'xmlns:fe="http://www.facturae.es/Facturae/2014/v' . - $this->version . '/Facturae"'; + $xmlns = null; + $xmlns .= 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" '; + $xmlns .= 'xmlns:xades="http://uri.xades.org/01903/v1.3.2#" '; + $xmlns .= 'xmlns:fe="http://www.facturae.es/Facturae/2014/v' . $this->version . '/Facturae"'; // Prepare signed properties $signTime = is_null($this->signTime) ? time() : $this->signTime; + $certData = openssl_x509_parse($this->publicKey); - $certDigest = openssl_x509_fingerprint($this->publicKey, "sha1", true); + $certDigest = openssl_x509_fingerprint($this->publicKey, 'sha1', true); $certDigest = base64_encode($certDigest); - $certIssuer = "CN=" . $certData['issuer']['CN'] . "," . - "OU=" . $certData['issuer']['OU'] . "," . - "O=" . $certData['issuer']['O'] . "," . - "C=" . $certData['issuer']['C']; + + foreach (['CN', 'OU', 'O', 'C'] as $item) { + $certIssuer[] = $item . '=' . $certData['issuer'][$item]; + } + $certIssuer = implode(',', $certIssuer); + $prop = '' . '' . From 3972e0c7e8251c8ca3c76454267f147d85f1c898 Mon Sep 17 00:00:00 2001 From: Fernando Herrero Date: Fri, 22 Dec 2017 04:53:23 +0100 Subject: [PATCH 2/8] Fix line 443: "$this->" forgotten Change return load_pkcs12($pkcs12_file, $pkcs12_pass); For return $this->load_pkcs12($pkcs12_file, $pkcs12_pass); --- src/Facturae.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facturae.php b/src/Facturae.php index 8940f55..e19fcd9 100644 --- a/src/Facturae.php +++ b/src/Facturae.php @@ -440,7 +440,7 @@ public function sign_pkcs12($pkcs12_file, $pkcs12_pass, $policy=self::SIGN_POLIC $this->signatureSignedPropertiesID = $this->random(); $this->signatureObjectID = $this->random(); - return load_pkcs12($pkcs12_file, $pkcs12_pass); + return $this->load_pkcs12($pkcs12_file, $pkcs12_pass); } From 5379d00cb8f40ede786b7de03b2c6a538b0474c0 Mon Sep 17 00:00:00 2001 From: Fernando Herrero Date: Fri, 22 Dec 2017 05:38:59 +0100 Subject: [PATCH 3/8] Missing openssl_x509_read on load_pkcs12 --- src/Facturae.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facturae.php b/src/Facturae.php index e19fcd9..8888ee6 100644 --- a/src/Facturae.php +++ b/src/Facturae.php @@ -364,7 +364,7 @@ public function load_pkcs12 ($pkcs12_file, $pkcs12_pass) { if (is_file($pkcs12_file) and !empty($pkcs12_pass)) { if (openssl_pkcs12_read(file_get_contents($pkcs12_file), $certs, $pkcs12_pass)) { - $this->publicKey = $certs['cert']; + $this->publicKey = openssl_x509_read($certs['cert']); $this->privateKey = openssl_pkey_get_private($certs['pkey']); } } From f27eefa18d431afec05e4e275e2a9040eb643f7b Mon Sep 17 00:00:00 2001 From: Fernando Herrero Date: Fri, 22 Dec 2017 05:48:39 +0100 Subject: [PATCH 4/8] Fix call of load_x509 function --- src/Facturae.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Facturae.php b/src/Facturae.php index 8888ee6..3399efa 100644 --- a/src/Facturae.php +++ b/src/Facturae.php @@ -415,7 +415,7 @@ public function sign($publicPath, $privatePath, $passphrase, $policy=self::SIGN_ $this->signatureSignedPropertiesID = $this->random(); $this->signatureObjectID = $this->random(); - return $this->loadx509($publicPath, $privatePath, $passphrase); + return $this->load_x509($publicPath, $privatePath, $passphrase); } From bc9261b8d8354d4152a7b0fae6881018a4344082 Mon Sep 17 00:00:00 2001 From: josemmo Date: Sat, 23 Dec 2017 13:15:17 +0100 Subject: [PATCH 5/8] Sintaxis y compatibilidad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Normalizada sintaxis de los nuevos commits - Universalizado método `sign` para poder añadir un archivo PFX desde el mismo método - Cambiada visibilidad de métodos `loadX509` y `loadPcks12` de `public` a `private` --- src/Facturae.php | 111 +++++++++++++++++++---------------------------- 1 file changed, 45 insertions(+), 66 deletions(-) diff --git a/src/Facturae.php b/src/Facturae.php index 3399efa..d9de24b 100644 --- a/src/Facturae.php +++ b/src/Facturae.php @@ -101,7 +101,7 @@ public function __construct($schemaVersion=self::SCHEMA_3_2_1) { * This method is used for generating random IDs required when signing the * document. * - * @return int Random number + * @return int Random number */ private function random() { if (function_exists('random_int')) { @@ -354,55 +354,55 @@ public function setSignTime($time) { /** * Load a PKCS#12 Certificate Store * - * @param string $pkcs12_file The certificate store file name - * @param string $pkcs12_pass Encryption password for unlocking the PKCS#12 file - * @return bool true on success or FALSE on failure. + * @param string $pkcs12File The certificate store file name + * @param string $pkcs12Pass Encryption password for unlocking the PKCS#12 file + * @return bool Success */ - public function load_pkcs12 ($pkcs12_file, $pkcs12_pass) { - $this->publicKey = null; - $this->privateKey = null; - - if (is_file($pkcs12_file) and !empty($pkcs12_pass)) { - if (openssl_pkcs12_read(file_get_contents($pkcs12_file), $certs, $pkcs12_pass)) { - $this->publicKey = openssl_x509_read($certs['cert']); - $this->privateKey = openssl_pkey_get_private($certs['pkey']); - } + private function loadPkcs12($pkcs12File, $pkcs12Pass="") { + if (!is_file($pkcs12File)) return false; + + // Extract public and private keys from store + if (openssl_pkcs12_read(file_get_contents($pkcs12File), $certs, $pkcs12Pass)) { + $this->publicKey = openssl_x509_read($certs['cert']); + $this->privateKey = openssl_pkey_get_private($certs['pkey']); } - return (!empty($this->publicKey) and !empty($this->privateKey)); + return (!empty($this->publicKey) && !empty($this->privateKey)); } + /** - * Load a X.509 certificate and PEM encoded private key + * Load a X.509 certificate and PEM encoded private key * * @param string $publicPath Path to public key PEM file * @param string $privatePath Path to private key PEM file * @param string $passphrase Private key passphrase - * @return bool true on success or FALSE on failure. + * @return bool Success */ - public function load_x509 ($publicPath, $privatePath, $passphrase = '') { - $this->publicKey = null; - $this->privateKey = null; - - if (is_file($publicPath) and is_file($privatePath)) { + private function loadX509($publicPath, $privatePath, $passphrase="") { + if (is_file($publicPath) && is_file($privatePath)) { $this->publicKey = openssl_x509_read(file_get_contents($publicPath)); - $this->privateKey = openssl_pkey_get_private(file_get_contents($privatePath), $passphrase); + $this->privateKey = openssl_pkey_get_private( + file_get_contents($privatePath), + $passphrase + ); } - - return (!empty($this->publicKey) and !empty($this->privateKey)); + return (!empty($this->publicKey) && !empty($this->privateKey)); } /** * Sign * - * @param string $publicPath Path to public key PEM file - * @param string $privatePath Path to private key PEM file + * @param string $publicPath Path to public key PEM file or PKCS#12 certificate store + * @param string $privatePath Path to private key PEM file (should be NULL in case of PKCS#12) * @param string $passphrase Private key passphrase * @param array $policy Facturae sign policy - * @return bool true on success or FALSE on failure. + * @return bool Success */ - public function sign($publicPath, $privatePath, $passphrase, $policy=self::SIGN_POLICY_3_1) { + public function sign($publicPath, $privatePath=NULL, $passphrase="", $policy=self::SIGN_POLICY_3_1) { + $this->publicKey = NULL; + $this->privateKey = NULL; $this->signPolicy = $policy; // Generate random IDs @@ -414,33 +414,13 @@ public function sign($publicPath, $privatePath, $passphrase, $policy=self::SIGN_ $this->referenceID = $this->random(); $this->signatureSignedPropertiesID = $this->random(); $this->signatureObjectID = $this->random(); - - return $this->load_x509($publicPath, $privatePath, $passphrase); - } - - - /** - * Sign with PKCS#12 Certificate Store - * - * @param string $pkcs12_file The certificate store file name - * @param string $pkcs12_pass Encryption password for unlocking the PKCS#12 file - * @param array $policy Facturae sign policy - * @return bool true on success or FALSE on failure. - */ - public function sign_pkcs12($pkcs12_file, $pkcs12_pass, $policy=self::SIGN_POLICY_3_1) { - $this->signPolicy = $policy; - // Generate random IDs - $this->signatureID = $this->random(); - $this->signedInfoID = $this->random(); - $this->signedPropertiesID = $this->random(); - $this->signatureValueID = $this->random(); - $this->certificateID = $this->random(); - $this->referenceID = $this->random(); - $this->signatureSignedPropertiesID = $this->random(); - $this->signatureObjectID = $this->random(); - - return $this->load_pkcs12($pkcs12_file, $pkcs12_pass); + // Load public and private keys + if (empty($privatePath)) { + return $this->loadPkcs12($publicPath, $passphrase); + } else { + return $this->loadX509($publicPath, $privatePath, $passphrase); + } } @@ -451,29 +431,28 @@ public function sign_pkcs12($pkcs12_file, $pkcs12_pass, $policy=self::SIGN_POLIC */ private function injectSignature($xml) { // Make sure we have all we need to sign the document - if (is_null($this->publicKey) || is_null($this->privateKey)) return $xml; + if (empty($this->publicKey) || empty($this->privateKey)) return $xml; // Normalize document $xml = str_replace("\r", "", $xml); // Define namespace - $xmlns = null; - $xmlns .= 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" '; - $xmlns .= 'xmlns:xades="http://uri.xades.org/01903/v1.3.2#" '; - $xmlns .= 'xmlns:fe="http://www.facturae.es/Facturae/2014/v' . $this->version . '/Facturae"'; + $xmlns = 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ' . + 'xmlns:etsi="http://uri.etsi.org/01903/v1.3.2#" ' . + 'xmlns:fe="http://www.facturae.es/Facturae/2014/v' . + $this->version . '/Facturae"'; // Prepare signed properties $signTime = is_null($this->signTime) ? time() : $this->signTime; - $certData = openssl_x509_parse($this->publicKey); - $certDigest = openssl_x509_fingerprint($this->publicKey, 'sha1', true); + $certDigest = openssl_x509_fingerprint($this->publicKey, "sha1", true); $certDigest = base64_encode($certDigest); + $certIssuer = "CN=" . $certData['issuer']['CN'] . "," . + "OU=" . $certData['issuer']['OU'] . "," . + "O=" . $certData['issuer']['O'] . "," . + "C=" . $certData['issuer']['C']; - foreach (['CN', 'OU', 'O', 'C'] as $item) { - $certIssuer[] = $item . '=' . $certData['issuer'][$item]; - } - $certIssuer = implode(',', $certIssuer); - + // Generate signed properties $prop = '' . '' . From 78ac7c10a28b63888c9adfed6558a32bbae222be Mon Sep 17 00:00:00 2001 From: josemmo Date: Sat, 23 Dec 2017 13:35:40 +0100 Subject: [PATCH 6/8] =?UTF-8?q?A=C3=B1adidas=20pruebas=20de=20PKCS#12?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Añadida prueba adicional de PHPUnit para probar la firma con archivos .pfx --- tests/FacturaeTest.php | 25 ++++++++++++++++++++++--- tests/test.pfx | Bin 0 -> 2677 bytes 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 tests/test.pfx diff --git a/tests/FacturaeTest.php b/tests/FacturaeTest.php index 710a410..849b236 100644 --- a/tests/FacturaeTest.php +++ b/tests/FacturaeTest.php @@ -1,5 +1,4 @@ addLegalLiteral("Y este, otro (se pueden añadir varios)"); // Ya solo queda firmar la factura ... - $fac->sign(__DIR__ . "/public.pem", __DIR__ . "/private.pem", "12345"); + if ($isPfx) { + $fac->sign(__DIR__ . "/test.pfx", NULL, "12345"); + } else { + $fac->sign(__DIR__ . "/public.pem", __DIR__ . "/private.pem", "12345"); + } // ... y exportarlo a un archivo $res = $fac->export(self::FILE_PATH); @@ -105,6 +113,17 @@ public function testCreateInvoice() { } + /** + * Test PFX + */ + public function testPfx() { + $this->testCreateInvoice(true); + } + + + /** + * Test Invoice Complies with Format + */ public function testInvoiceCompliesWithFormat() { // Prepare file to upload if (function_exists('curl_file_create')) { diff --git a/tests/test.pfx b/tests/test.pfx new file mode 100644 index 0000000000000000000000000000000000000000..97e39d345409197128a2edc636249430c3172ea4 GIT binary patch literal 2677 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 literal 0 HcmV?d00001 From a37dcece6ea969509af4dc8176241f60c81ef1bb Mon Sep 17 00:00:00 2001 From: josemmo Date: Sat, 23 Dec 2017 13:37:10 +0100 Subject: [PATCH 7/8] Cambiadas versiones MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Incrementada versión de Facturae-PHP - Añadida constante `SCHEMA_3_2_2` a la espera de que el Gobierno de España amplie el soporte en producción --- src/Facturae.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Facturae.php b/src/Facturae.php index d9de24b..f10055f 100644 --- a/src/Facturae.php +++ b/src/Facturae.php @@ -8,7 +8,7 @@ * This file contains everything you need to create invoices. * * @package josemmo\Facturae - * @version 1.0.4 + * @version 1.0.5 * @license http://www.opensource.org/licenses/mit-license.php MIT License * @author josemmo */ @@ -24,6 +24,7 @@ class Facturae { /* CONSTANTS */ const SCHEMA_3_2_1 = "3.2.1"; + const SCHEMA_3_2_2 = "3.2.2"; const SIGN_POLICY_3_1 = array( "name" => "Política de Firma FacturaE v3.1", "url" => "http://www.facturae.es/politica_de_firma_formato_facturae/politica_de_firma_formato_facturae_v3_1.pdf", From bdb2b52f6786a33fca3fda0742152b0e40f84762 Mon Sep 17 00:00:00 2001 From: josemmo Date: Sat, 23 Dec 2017 13:50:00 +0100 Subject: [PATCH 8/8] =?UTF-8?q?Actualizada=20documentaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Añadida constante `SCHEMA_3_2_2` - Modificada sección "Firmado de facturas" --- README.md | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9c3d31b..64ed84c 100644 --- a/README.md +++ b/README.md @@ -295,17 +295,34 @@ Por defecto, si se establece una forma de pago y no se indica la fecha de vencim ### Firmado de facturas Aunque es posible exportar las facturas sin firmarlas, es un paso obligatorio para prácticamente cualquier trámite relacionado con la Administración Pública. Para firmar facturas se necesita un certificado electrónico (generalmente expedido por la FNMT) del que extraer su clave pública y su clave privada. -Los siguientes comandos permiten extraer el certificado (clave pública) y la clave privada de un archivo PFX: +Por defecto, al firmar una factura se utilizan la fecha y hora actuales como sello de tiempo. Si se quiere indicar otro valor, se debe utilizar el siguiente método: + +```php +$fac->setSignTime("2017-01-01T12:34:56+02:00"); ``` -openssl pkcs12 -in certificado_de_entrada.pfx -clcerts -nokeys -out clave_publica.pem -openssl pkcs12 -in certificado_de_entrada.pfx -nocerts -out clave_privada.pem + +Llegados a este punto hay dos formas de facilitar estos datos a Facturae-PHP: + +#### Firmado con clave pública y privada X.509 +Si se tienen las clave pública y privada en archivos independientes se debe utilizar este método con los siguientes argumentos: + +```php +$fac->sign("clave_publica.pem", "clave_privada.pem", "passphrase"); ``` -Por defecto, al firmar una factura se utilizan la fecha y hora actuales como sello de tiempo. Si se quiere indicar otro valor, se debe utilizar el siguiente método: +> Los siguientes comandos permiten extraer el certificado (clave pública) y la clave privada de un archivo PFX: +> +> ``` +> openssl pkcs12 -in certificado_de_entrada.pfx -clcerts -nokeys -out clave_publica.pem +> openssl pkcs12 -in certificado_de_entrada.pfx -nocerts -out clave_privada.pem +> ``` + +#### Firmado con PKCS#12 +Desde la versión 1.0.5 de Facturae-PHP ya es posible cargar un banco de certificados desde un archivo `.pfx` o `.p12` sin necesidad de convertirlo previamente a X.509: ```php -$fac->setSignTime("2017-01-01T12:34:56+02:00"); +$fac->sign("certificado.pfx", NULL, "passphrase"); ``` ### Otros métodos @@ -354,6 +371,7 @@ $totales = $fac->getTotals(); |Constante|Descripción| |--------:|:----------| |`Facturae::SCHEMA_3_2_1`|Formato de Facturae 3.2.1| +|`Facturae::SCHEMA_3_2_2`|Formato de Facturae 3.2.2| |`Facturae::SIGN_POLICY_3_1`|Formato de firma 3.1| ||| |`Facturae::PAYMENT_CASH`|Pago al contado|