<?php

require __DIR__.'/lib/xmlseclibs/src/XMLSecurityDSig.php';
require __DIR__.'/lib/xmlseclibs/src/XMLSecurityKey.php';

use RobRichards\XMLSecLibs\XMLSecurityDSig;
use RobRichards\XMLSecLibs\XMLSecurityKey;
/*
$fir=new Generadorfirma();
$fir->Firmacomprobante();
*/
class Generadorfirma
{

   public function Firmacomprobante($xmlmues)
    {

/* =================== CONFIG =================== */
$xmlIn   = __DIR__.'/../generadorxmlfactura/xml/'.$xmlmues;   // XML de entrada (sin firma)

$p12File = __DIR__.'/';                              // tu .p12/.pfx
$p12Pass = '';                                                      // contraseña del .p12
$xmlOut  = __DIR__.'/firmado/'.$xmlmues;   // XML de salida (puede sobrescribir)
$sigId   = 'Sig1';
$spId    = $sigId.'-SignedProperties';
$xadesNS = 'http://uri.etsi.org/01903/v1.3.2#';


function dnFromIssuerArray(array $issuer): string {
  $order = ['C','ST','L','O','OU','CN','emailAddress'];
  $parts = [];
  foreach ($order as $k) {
    if (!isset($issuer[$k])) continue;
    $v = $issuer[$k];
    if (is_array($v)) { foreach ($v as $vv) $parts[] = "$k=$vv"; }
    else $parts[] = "$k=$v";
  }
  return implode(', ', $parts);
}


$dom = new DOMDocument();
$dom->preserveWhiteSpace = true;
$dom->formatOutput = false;
if (!$dom->load($xmlIn)) die("No se pudo cargar $xmlIn\n");
$root = $dom->documentElement;
if (!$root) die("XML sin raíz\n");




$p12 = @file_get_contents($p12File);
if (!$p12) die("No se puede leer $p12File\n");
if (!openssl_pkcs12_read($p12, $certs, $p12Pass)) die("Error al abrir .p12 o contraseña inválida\n");
$privKeyPem = $certs['pkey'];
$certPem    = $certs['cert'];


$certDer       = base64_decode(preg_replace('#-+BEGIN CERTIFICATE-+|-+END CERTIFICATE-+|\s+#','', $certPem));
$certDigestB64 = base64_encode(hash('sha256', $certDer, true));
$ci            = openssl_x509_parse($certPem);
$issuerName    = dnFromIssuerArray($ci['issuer'] ?? []);
$serialNumber  = isset($ci['serialNumber'])
                 ? (string)$ci['serialNumber']
                 : (isset($ci['serialNumberHex']) ? (string)hexdec($ci['serialNumberHex']) : '');


$dsig = new XMLSecurityDSig();
/* Reconocer atributo 'id' como ID */
$dsig->idKeys[] = 'id';


$insertedSig = $dsig->appendSignature($root);
$dsig->sigNode = $insertedSig;
$dsig->sigNode->setAttribute('Id', $sigId); // Id del nodo Signature


$dsig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
$dsigNS = XMLSecurityDSig::XMLDSIGNS;


$obj = $dom->createElementNS($dsigNS, 'ds:Object');
$dsig->sigNode->appendChild($obj);

$qp = $dom->createElementNS($xadesNS, 'xades:QualifyingProperties');
$qp->setAttribute('Target', '#'.$sigId);
$obj->appendChild($qp);

$sp = $dom->createElementNS($xadesNS, 'xades:SignedProperties');
$sp->setAttribute('Id', $spId);
$qp->appendChild($sp);

$ssp = $dom->createElementNS($xadesNS, 'xades:SignedSignatureProperties');
$sp->appendChild($ssp);

$ssp->appendChild($dom->createElementNS($xadesNS, 'xades:SigningTime', gmdate('c')));

$sc = $dom->createElementNS($xadesNS, 'xades:SigningCertificate');
$ssp->appendChild($sc);

$certEl = $dom->createElementNS($xadesNS, 'xades:Cert');
$sc->appendChild($certEl);

$cd = $dom->createElementNS($xadesNS, 'xades:CertDigest');
$certEl->appendChild($cd);

$dm = $dom->createElementNS($dsigNS, 'ds:DigestMethod');
$dm->setAttribute('Algorithm', XMLSecurityDSig::SHA256);
$cd->appendChild($dm);

$cd->appendChild($dom->createElementNS($dsigNS, 'ds:DigestValue', $certDigestB64));

$is = $dom->createElementNS($xadesNS, 'xades:IssuerSerial');
$certEl->appendChild($is);
$is->appendChild($dom->createElementNS($dsigNS, 'ds:X509IssuerName', $issuerName));
$is->appendChild($dom->createElementNS($dsigNS, 'ds:X509SerialNumber', $serialNumber));


$dsig->addReference(
  $root,
  XMLSecurityDSig::SHA256,
  ['http://www.w3.org/2000/09/xmldsig#enveloped-signature', XMLSecurityDSig::EXC_C14N],
  [
    'id_name'   => 'id',   // <<< minúscula
    'overwrite' => false,
    'force_uri' => true
  ]
);


$dsig->addReference(
  $sp,
  XMLSecurityDSig::SHA256,
  [ XMLSecurityDSig::EXC_C14N ],
  [
    'id_name'   => 'Id',   // el elemento SignedProperties usa atributo Id por convención
    'overwrite' => false,
    'force_uri' => true
  ]
);


$xp = new DOMXPath($dsig->sigNode->ownerDocument);
$xp->registerNamespace('ds', $dsigNS);
$refs = $xp->query(".//ds:Signature[@Id='$sigId']/ds:SignedInfo/ds:Reference");
foreach ($refs as $ref) {
  if ($ref->getAttribute('URI') === '#'.$spId) {
    $ref->setAttribute('Type', 'http://uri.etsi.org/01903#SignedProperties');
    break;
  }
}


$key = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type'=>'private']);
$key->loadKey($privKeyPem, false);

$dsig->sign($key);                                  // <ds:SignatureValue>
$dsig->add509Cert($certPem, true, false, ['subjectName'=>true]); // <ds:X509Data>


if (!$dom->save($xmlOut)) die("No se pudo guardar $xmlOut\n");
echo "OK: firmado en $xmlOut\n";

}

}

