Ver Mensaje Individual
  #538  
Antiguo 02-05-2021
bilbur bilbur is offline
Miembro
 
Registrado: dic 2019
Posts: 60
Reputación: 5
bilbur Va por buen camino
PHP Firma del xml

Nos hemos quedado aquí:

Código PHP:
$guardar_como $tbai_alb.'.xsig';
    
    require_once(
$_SERVER['DOCUMENT_ROOT'].'/xxx/ticketBAI/src/XmlTools.php' );
    require_once(
$_SERVER['DOCUMENT_ROOT'].'/xxx/ticketBAI/src/firmador.php' );
    
$dir_cert = ($_SERVER['DOCUMENT_ROOT'].'/xxx/_certificandos'.'/xxx.pfx' );    
    
$fac = new Firmador();
    
$xmlF $fac -> firmar($dir_cert,$cal_cert$xml$hacienda_foral);

    
$directorio_ventas $directorio_TBAI.$guardar_como;  
    
file_put_contents($directorio_ventas$xmlF); 
Lo que hago es indicar donde guardarlo, en mi caso con el mismo nombre número de orden de emisión de la factura.

XmlTools.php:
Código PHP:
class XmlTools {
    public function 
randomId() {
    if (
function_exists('random_int')) return random_int(0x100000000x7FFFFFFF);
    return 
rand(100000999999);
    }
    public function 
generateGUID($prefix='pfx')
    {
        
$uuid md5(uniqid(mt_rand(), true));
        
$guid $prefix.substr($uuid08)."-".
                
substr($uuid84)."-".
                
substr($uuid124)."-".
                
substr($uuid164)."-".
                
substr($uuid2012);
        return 
$guid;
    }
 } 
</div>

Este es el importante: firmador.php
Código PHP:
<?php
class Firmador{
    
    const 
POLITICA_FIRMA_BIZ = array(
    
"name" => "Politica de firma TicketBAI 1.0",
    
"url" => "https://www.batuz.eus/fitxategiak/batuz/ticketbai/sinadura_elektronikoaren_zehaztapenak_especificaciones_de_la_firma_electronica_v1_0.pdf"
    
"digest" => "Quzn98x3PMbSHwbUzaj5f5KOpiH0u8bvmwbbbNkO9Es="
    
);

    const 
POLITICA_FIRMA_GIP = array(
    
"name" => "Politica de firma TicketBAI 1.0",
    
"url" => "https://www.gipuzkoa.eus/ticketbai/sinadura",  
    
"digest" => "dTtPpv4fWTcejeVx7+91ILruFX3HysbngBlllJm4i/E="
    
);
    
    private 
$signTime         NULL;
    private 
$signPolicy       NULL;
    private 
$publicKey        NULL;
    private 
$privateKey       NULL;
    private 
$cerROOT          NULL;
    private 
$cerINTERMEDIO    NULL;
    private 
$tipoDoc          '01';

    public function 
retC14DigestSha1($strcadena)
    {
    
$strcadena    str_replace("\r"""str_replace("\n"""$strcadena));
    
$d1p        = new DOMDocument('1.0''UTF-8');
    
$d1p->loadXML($strcadena);
    
$strdata    $d1p->C14N();
    return 
base64_encode(hash('sha256' $strdatatrue ));
    }

    public function 
firmar($certificadop12$clavecertificado$xmlsinfirma$hacienda_foral)
    {
    if (!
$pfx file_get_contents($certificadop12))
    {
    echo 
"Error: No se puede leer el fichero del certificado o no existe en la ruta especificada\n";
    exit;
    }
    if (
openssl_pkcs12_read($pfx$key$clavecertificado))
    {
    
$this->publicKey    $key["cert"];
    
$this->privateKey   $key["pkey"];
    
$complem              openssl_pkey_get_details(openssl_pkey_get_private($this->privateKey));
    
$this->Modulus        base64_encode($complem['rsa']['n']);
    
$this->Exponent       base64_encode($complem['rsa']['e']);
    }
    else
    {
    echo 
"Error: No se puede leer el almacén de certificados o la clave no es la correcta.\n";
    exit;
    }

    if (
$hacienda_foral == "BIZ")    
    
$this->signPolicy             self::POLITICA_FIRMA_BIZ;
    if (
$hacienda_foral == "GIP")    
    
$this->signPolicy             self::POLITICA_FIRMA_GIP;
    
$tools = new XmlTools();
    
$this->signatureID             $tools->generateGUID('xmldsig-');
    
$this->signatureValue         $this->signatureID.'-sigvalue';
    
$this->XadesObjectId          "XadesObjectId-".$this->signatureID;
    
$this->KeyInfoId              "KeyInfoId-".$this->signatureID;
    
$this->Reference0Id           $this->signatureID.'-ref0';
    
$this->Reference1Id           "ReferenceKeyInfo";
    
$this->SignedProperties       $this->signatureID.'-signedprops';
    
$this->qualifyingProperties "QualifyingProperties-".$this->signatureID;
    
    
$xml1                       $xmlsinfirma;
    
$xml1                       $this->insertaFirma($xml1);
    return 
$xml1;
    }

    public function 
insertaFirma($xml){
      

      
    if (
is_null($this->publicKey) || is_null($this->privateKey))
    return 
$xml;

    
$d = new DOMDocument('1.0''UTF-8');
    
$d->loadXML($xml);
    
$canonizadoreal $d->C14N();

    
$xmlns 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#" ' .
             
'xmlns:T="urn:ticketbai:emision" ' .
             
'xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"';
            
    
$xmnls_signeg        'xmlns:T="urn:ticketbai:emision" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"';
    
$xmlns_keyinfo       'xmlns:T="urn:ticketbai:emision" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"';
    
$xmnls_signedprops 'xmlns:T="urn:ticketbai:emision" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"';

    
$signTime1 date('Y-m-d\TH:i:s+01:00');
    
$certData   openssl_x509_parse($this->publicKey);
    
$certDigest base64_encode(openssl_x509_fingerprint($this->publicKey"sha256"true));
    
$certIssuer = array();

    foreach (
$certData['issuer'] as $item=>$value)
    {
    
$certIssuer[] = $item '=' $value;
    }
    
$certIssuer implode(', 'array_reverse($certIssuer));

    
$prop '<xades:SignedProperties Id="'.$this->SignedProperties.'">' .
      
'<xades:SignedSignatureProperties>'.
          
'<xades:SigningTime>'.$signTime1.'</xades:SigningTime>' .
          
'<xades:SigningCertificate>'.
              
'<xades:Cert>'.
                  
'<xades:CertDigest>' .
                      
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>'.
                      
'<ds:DigestValue>'.$certDigest.'</ds:DigestValue>'.
                  
'</xades:CertDigest>'.
                  
'<xades:IssuerSerial>' .
                      
'<ds:X509IssuerName>'.$certIssuer.'</ds:X509IssuerName>'.
                      
'<ds:X509SerialNumber>'.$certData['serialNumber'].'</ds:X509SerialNumber>' .
                  
'</xades:IssuerSerial>'.
              
'</xades:Cert>'.
          
'</xades:SigningCertificate>' .
          
'<xades:SignaturePolicyIdentifier>'.
              
'<xades:SignaturePolicyId>' .
                  
'<xades:SigPolicyId>'.
                      
'<xades:Identifier>'.$this->signPolicy['url'].'</xades:Identifier>'.
                      
'<xades:Description>'.$this->signPolicy['name'].'</xades:Description>'.
                  
'</xades:SigPolicyId>'.
                  
'<xades:SigPolicyHash>' .
                      
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />'.
                      
'<ds:DigestValue>'.$this->signPolicy['digest'].'</ds:DigestValue>'.
                  
'</xades:SigPolicyHash>'.
                  
'<xades:SigPolicyQualifiers>'.
                    
'<xades:SigPolicyQualifier>'.
                      
'<xades:SPURI>'.$this->signPolicy['url'].'</xades:SPURI>'.
                    
'</xades:SigPolicyQualifier>'.
                  
'</xades:SigPolicyQualifiers>'.
              
'</xades:SignaturePolicyId>' .
          
'</xades:SignaturePolicyIdentifier>'.
      
'</xades:SignedSignatureProperties>'.
      
'<xades:SignedDataObjectProperties>'.
          
'<xades:DataObjectFormat ObjectReference="#'.$this->Reference0Id.'">'.
                  
'<xades:ObjectIdentifier>' .
                    
'<xades:Identifier Qualifier="OIDAsURN">urn:oid:1.2.840.10003.5.109.10</xades:Identifier>' .
                  
'</xades:ObjectIdentifier>' .          
              
'<xades:MimeType>text/xml</xades:MimeType>'.
              
'<xades:Encoding>UTF-8</xades:Encoding>'.
          
'</xades:DataObjectFormat>'.
      
'</xades:SignedDataObjectProperties>'.
      
'</xades:SignedProperties>';

    
// Prepare key info
    
$publicPEM "";
    
openssl_x509_export($this->publicKey$publicPEM);
    
$publicPEM str_replace("-----BEGIN CERTIFICATE-----"""$publicPEM);
    
$publicPEM str_replace("-----END CERTIFICATE-----"""$publicPEM);
    
$publicPEM str_replace("\r"""str_replace("\n"""$publicPEM));

    
$kInfo '<ds:KeyInfo Id="'.$this->KeyInfoId.'">' .
                
'<ds:X509Data>'  .
                    
'<ds:X509Certificate>'.$publicPEM.'</ds:X509Certificate>' .
                
'</ds:X509Data>' .
                
'<ds:KeyValue>'.
                
'<ds:RSAKeyValue>'.
                    
'<ds:Modulus>'.$this->Modulus.'</ds:Modulus>'.
                    
'<ds:Exponent>'.$this->Exponent.'</ds:Exponent>'.
                
'</ds:RSAKeyValue>'.
                
'</ds:KeyValue>'.                
             
'</ds:KeyInfo>';

    
$keyinfo_para_hash1 str_replace('<ds:KeyInfo''<ds:KeyInfo ' $xmlns_keyinfo$kInfo);
    
$kInfoDigest $this->retC14DigestSha1($keyinfo_para_hash1);
    
$aconop     str_replace('<xades:SignedProperties''<xades:SignedProperties ' $xmnls_signedprops$prop);
    
$propDigest $this->retC14DigestSha1($aconop);
    
$documentDigest base64_encode(hash('sha256'$canonizadorealtrue));
        
    
// Prepare signed info
    
$sInfo '<ds:SignedInfo>' .
        
'<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>' .
        
'<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"/>' .
 
        
'<ds:Reference URI="#'.$this->SignedProperties.'" Type="http://uri.etsi.org/01903#SignedProperties">' .
        
'<ds:Transforms>' .
        
'<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>' .
        
'</ds:Transforms>' .        
        
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>' .
        
'<ds:DigestValue>'.$propDigest.'</ds:DigestValue>' .
        
'</ds:Reference>' .

        
'<ds:Reference Id="'.$this->Reference1Id.'" URI="#'.$this->KeyInfoId.'">' .
        
'<ds:Transforms>' .
        
'<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>' .
        
'</ds:Transforms>' .        
        
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />' .
        
'<ds:DigestValue>'.$kInfoDigest.'</ds:DigestValue>' .
        
'</ds:Reference>' .
        
        
'<ds:Reference Id="'.$this->Reference0Id.'" URI=""
        Type="http://www.w3.org/2000/09/xmldsig#Object">' 
.
        
'<ds:Transforms>' .
        
'<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>' .        
        
'<ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>' .
        
'</ds:Transforms>' .
        
'<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>' .
        
'<ds:DigestValue>'.$documentDigest.'</ds:DigestValue>' .
        
'</ds:Reference>' .

        
'</ds:SignedInfo>';


    
$signaturePayload str_replace('<ds:SignedInfo''<ds:SignedInfo ' $xmnls_signeg$sInfo);

    
$d1p = new DOMDocument('1.0''UTF-8');
    
$d1p->loadXML($signaturePayload);
    
$signaturePayload $d1p->C14N();
    
    
$signatureResult "";
    
$algo "SHA256";
    
    
openssl_sign($signaturePayload$signatureResult$this->privateKey$algo);
    
$signatureResult base64_encode($signatureResult);
    
$sig '<ds:Signature Id="'.$this->signatureID.'" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'.
    
$sInfo.'<ds:SignatureValue Id="'.$this->signatureValue.'">'.$signatureResult'
    </ds:SignatureValue>'
.
    
$kInfo .
    
'<ds:Object Id="'.$this->XadesObjectId .'">'.
    
'<xades:QualifyingProperties Id="'.$this->qualifyingProperties.'" 
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Target="#'
.$this->signatureID.'" 
    xmlns:xades="http://uri.etsi.org/01903/v1.3.2#">'

    
$prop .
    
'</xades:QualifyingProperties>
    </ds:Object>
    </ds:Signature>'
;

    
$xml str_replace('</T:TicketBai>'$sig '</T:TicketBai>'$xml);
    return 
$xml;
    }
}
?>
Mucha atención al contenido de firmador.php
Es posible que algo sobre pero no me atrevo a tocar nada ya que finciona bien.
Proximamente hablaremos del envío de los xml firmados a GIP y a BIZ, este último tiene su miga.
A vuestra disposición para comentar y aclarar lo que requiráis


Sería bueno poder firmar sólo con los .pem y sin clave de certificado pero quizás no se pueda.


Un saludo
Responder Con Cita