I know this issue has been pulled many times, but hear me out please.
As usual, the error is:
{\n "type": "ERROR",\n "code": "signed-properties-hashing",\n "category": "CERTIFICATE_ERRORS",\n "message": "Invalid signed properties hashing, SignedProperties with id='xadesSignedProperties'",\n "status": "ERROR"\n }\n ],\n "status": "ERROR"\n },\n "reportingStatus": "NOT_REPORTED"\n}
I was following official guidelines from here, as well as instructions from this post.
Here is what I am hashing:
<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties"><xades:SignedSignatureProperties><xades:SigningTime>2025-01-24T16:23:43</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"></ds:DigestMethod><ds:DigestValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">OWQ5ODc1NmQxZTljMzM0ODNiYTkwOTI1NjFhY2RlNTAwNzhjZGVkY2MyNWFjZTBkMTA4NDFjNTRiMGU4N2MzNg==</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">CN=PEZEINVOICESCA1-CA, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName><ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">2207773856276085586394728823149103726765030759</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties>
and here is the same tag in the invoice xml:
<xades:SignedProperties Id="xadesSignedProperties"><xades:SignedSignatureProperties><xades:SigningTime>2025-01-24T16:23:43</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue>OWQ5ODc1NmQxZTljMzM0ODNiYTkwOTI1NjFhY2RlNTAwNzhjZGVkY2MyNWFjZTBkMTA4NDFjNTRiMGU4N2MzNg==</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName>CN=PEZEINVOICESCA1-CA, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName><ds:X509SerialNumber>2207773856276085586394728823149103726765030759</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties>
Neat and clear, no spaces, no extra stuffâŚ
However, the difference is in prefixes (namespaces) for hashed part, as instructed in the ZATCA guidelines.
The function that do hashing:
public function zatcaGenerateCertificateHash()
{
$certificate = Cfg::get('zatca_certificate');
$hashedCertificate = hash('sha256', $certificate);
$base64EncodedHash = base64_encode($hashedCertificate);
return $base64EncodedHash;
}
public function zatcaSignedPropertiesHash($issueTime)
{
$signingTime = date('Y-m-d\TH:i:s', strtotime($issueTime)); // provided on function call
$certificateHash = $this->zatcaGenerateCertificateHash(); // look above
$issuerName = Cfg::get('zatca_issuer_name'); // extracted from the certificate
$serialNumber = Cfg::get('zatca_serialnumber'); // extracted from the certificate
// Construct the SignedProperties XML block
$xml =
'<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties"><xades:SignedSignatureProperties><xades:SigningTime>'.$signingTime.'</xades:SigningTime><xades:SigningCertificate><xades:Cert><xades:CertDigest><ds:DigestMethod xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/><ds:DigestValue xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'.$certificateHash.'</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'.$issuerName.'</ds:X509IssuerName><ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">'.$serialNumber.'</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties>';
// Load XML into DOMDocument with UTF-8 encoding
$dom = new \DOMDocument('1.0', 'UTF-8');
$dom->loadXML($xml, LIBXML_NOBLANKS);
// Apply canonicalization
$canonicalizedXml = $dom->C14N(true, false);
// Hash the canonicalized XML using SHA-256 (raw binary output)
$hashedPropertyTag = hash('sha256', $canonicalizedXml, true);
// Encode the hash using Base64
return base64_encode($hashedPropertyTag);
}
What I may be doing wrong?
Thank you for your support.