What do Signed Properties Look Like When Hashing?

For the signing process, we have to hash the signed properties. In ZATCA’s samples the signed properties look like so:

                                  <xades:SignedSignatureProperties>
                                        <xades:SigningTime>2022-09-15T00:41:21Z</xades:SigningTime>
                                        <xades:SigningCertificate>
                                            <xades:Cert>
                                                <xades:CertDigest>
                                                    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                                                    <ds:DigestValue>YTJkM2JhYTcwZTBhZTAxOGYwODMyNzY3NTdkZDM3YzhjY2IxOTIyZDZhM2RlZGJiMGY0NDUzZWJhYWI4MDhmYg==</ds:DigestValue>
                                                </xades:CertDigest>
                                                <xades:IssuerSerial>
                                                    <ds:X509IssuerName>CN=TSZEINVOICE-SubCA-1, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName>
                                                    <ds:X509SerialNumber>2475382886904809774818644480820936050208702411</ds:X509SerialNumber>
                                                </xades:IssuerSerial>
                                            </xades:Cert>
                                        </xades:SigningCertificate>
                                    </xades:SignedSignatureProperties>
                                </xades:SignedProperties>

However in open source libraries like wes4m’s zatca-xml-js they got it working by adding attributes to some of the elements during the signing step. These attributes are not present in ZATCA’s samples provided with the SDK.

Mainly:

<ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:X509IssuerName>
<ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:X509SerialNumber>

Can we get an example of the whole signed properties block as it should look when it’s time for hashing?

We’re stuck at this step:

{
                    "type" => "ERROR",
                    "code" => "signed-properties-hashing",
                "category" => "CERTIFICATE_ERRORS",
                 "message" => "Invalid signed properties hashing, SignedProperties with id='xadesSignedProperties'",
                  "status" => "ERROR"
}

We made sure that the hashing steps are correct (compared with other SDKs that are passing) and that we canonicalize using C14N, so it must be the content that has an issue.

1 Like

For the signing process, we have to hash the signed properties. In ZATCA’s samples the signed properties look like so:

                                  <xades:SignedSignatureProperties>
                                        <xades:SigningTime>2022-09-15T00:41:21Z</xades:SigningTime>
                                        <xades:SigningCertificate>
                                            <xades:Cert>
                                                <xades:CertDigest>
                                                    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
                                                    <ds:DigestValue>YTJkM2JhYTcwZTBhZTAxOGYwODMyNzY3NTdkZDM3YzhjY2IxOTIyZDZhM2RlZGJiMGY0NDUzZWJhYWI4MDhmYg==</ds:DigestValue>
                                                </xades:CertDigest>
                                                <xades:IssuerSerial>
                                                    <ds:X509IssuerName>CN=TSZEINVOICE-SubCA-1, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName>
                                                    <ds:X509SerialNumber>2475382886904809774818644480820936050208702411</ds:X509SerialNumber>
                                                </xades:IssuerSerial>
                                            </xades:Cert>
                                        </xades:SigningCertificate>
                                    </xades:SignedSignatureProperties>
                                </xades:SignedProperties>

However in open source libraries like wes4m’s zatca-xml-js they got it working by adding attributes to some of the elements during the signing step. These attributes are not present in ZATCA’s samples provided with the SDK.

Mainly:

<ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:X509IssuerName>
<ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:X509SerialNumber>

Can we get an example of the whole signed properties block as it should look when it’s time for hashing?

We’re stuck at this step:

{
                    "type" => "ERROR",
                    "code" => "signed-properties-hashing",
                "category" => "CERTIFICATE_ERRORS",
                 "message" => "Invalid signed properties hashing, SignedProperties with id='xadesSignedProperties'",
                  "status" => "ERROR"
}

We made sure that the hashing steps are correct (compared with other SDKs that are passing), so it must be the content that has an issue.

Dear obahareth-mrool, please follow the steps below.

<xades:SignedProperties xmlns:xades=“Assigned ETSI XML URIs” Id=“xadesSignedProperties”>
xades:SignedSignatureProperties
xades:SigningTime</xades:SigningTime>
xades:SigningCertificate
xades:Cert
xades:CertDigest
<ds:DigestMethod xmlns:ds=“XML-Signature Syntax and Processing” Algorithm=“XML Encryption Syntax and Processing”/>
<ds:DigestValue xmlns:ds=“XML-Signature Syntax and Processing”></ds:DigestValue>
</xades:CertDigest>
xades:IssuerSerial
<ds:X509IssuerName xmlns:ds=“XML-Signature Syntax and Processing”></ds:X509IssuerName>
<ds:X509SerialNumber xmlns:ds=“XML-Signature Syntax and Processing”></ds:X509SerialNumber>
</xades:IssuerSerial>
</xades:Cert>
</xades:SigningCertificate>
</xades:SignedSignatureProperties>
</xades:SignedProperties>

Note:
-You shouldn’t include this tag in the invoice, we just using it to populate Signed Properties Hash.

1-To generate the Signed Properties Hash, you should use the tag and fill in the Populated Signed Properties in previous step (using the same values).

2-Hash the new property tag (After fill) using SHA-256 (output).
e.g.:99282555b5d79209be5883cc23eb234cd01bd33ea7d54d88f491248d33e321f1

3-Encode the hashed property using base64 (ENCODER BASE64 ) (output).
E.g.:OTkyODI1NTViNWQ3OTIwOWJlNTg4M2NjMjNlYjIzNGNkMDFiZDMzZWE3ZDU0ZDg4ZjQ5MTI0OGQzM2UzMjFmMQ==

Thank you.


<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties">
<xades:SignedSignatureProperties>
    <xades: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#"></ds:DigestValue>
            </xades:CertDigest>
            <xades:IssuerSerial>
                <ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:X509IssuerName>
                <ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:X509SerialNumber>
            </xades:IssuerSerial>
        </xades:Cert>
    </xades:SigningCertificate>
</xades:SignedSignatureProperties>
</xades:SignedProperties>

Thanks @MAl-tamimi, I will give this a try.

Just a note to the team, the samples included with ZATCA’s SDK are missing an XML attribute.

From the SDK samples:

<xades:SignedProperties Id="xadesSignedProperties">

The version you posted:

<xades:SignedProperties xmlns:xades=“http://uri.etsi.org/01903/v1.3.2#” Id=“xadesSignedProperties”>

This attribute specifically: xmlns:xades=“http://uri.etsi.org/01903/v1.3.2#” is not present in the samples included with the SDK, so developers won’t be able to know what exactly to hash.

We’ve been constructing XML that looks like the samples included in the SDK but it seems there was a change that added an attribute that should be hashed.

In the SDK implementation, we utilize the XML attribute to create a hash for signProperties. However, when we send back the signed invoice, we omit this attribute.

Note:

  • just to generate SignPropertied hash you should use the tag with attrebute .
    -You shouldn’t include this tag in the invoice.

I tried that approach but I am still getting errors so I’ll ask for more clarification.

We were told that we should canonicalize the SignedProperties using C14N. According to C14N, the xmlns:ds attribute on ds:digestMethod, ds:DigestValue, ds: X509IssuerName, and ds: X509SerialNumber should be removed, because it is already declared in a parent element (it’s already declared on the ds:Signature).

That’s what I understood the W3 doc on Superfluous Namespace Declarations in C14N.

So I would have two versions of the SignedProperties.

This canonicalized version only to generate a hash out of:

<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties"><xades:SignedSignatureProperties><xades:SigningTime>2023-09-19T12:35:45Z</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#">ZmQ2ZmVjZTFmOTE1NmM3NmFiZDNmOTJjNGU3YzFlMzNiMWM1OGJlMmViM2Q4Y2ZkMmVlMDc4ZTNkNzdiMDg3Yg==</ds:DigestValue></xades:CertDigest><xades:IssuerSerial><ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">CN=eInvoicing</ds:X509IssuerName><ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">1685368414756</ds:X509SerialNumber></xades:IssuerSerial></xades:Cert></xades:SigningCertificate></xades:SignedSignatureProperties></xades:SignedProperties>

And then this version which is included in the invoice that I submit to ZATCA (this is where the superfluous namespace declaration doc from W3 says to omit the duplicate namespaces):

<xades:SignedProperties Id="xadesSignedProperties">
  <xades:SignedSignatureProperties>
    <xades:SigningTime>2023-09-19T12:28:22Z</xades:SigningTime>
    <xades:SigningCertificate>
      <xades:Cert>
        <xades:CertDigest>
          <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
          <ds:DigestValue>ZmQ2ZmVjZTFmOTE1NmM3NmFiZDNmOTJjNGU3YzFlMzNiMWM1OGJlMmViM2Q4Y2ZkMmVlMDc4ZTNkNzdiMDg3Yg==</ds:DigestValue>
        </xades:CertDigest>
        <xades:IssuerSerial>
          <ds:X509IssuerName>CN=eInvoicing</ds:X509IssuerName>
          <ds:X509SerialNumber>1685368414756</ds:X509SerialNumber>
        </xades:IssuerSerial>
      </xades:Cert>
    </xades:SigningCertificate>
  </xades:SignedSignatureProperties>

Could you help me identify what exactly I need to tweak givne the structure I just shared?

Thanks a lot!

1 Like

@MAl-tamimi Got it.

One last question: Since you said I shouldn’t include the tag in the invoice, does this mean the whole SignedProperties block should be removed from the Simplified Invoice before I submit it to ZATCA?

Or do you mean I should only remove the xmlns:xades=“http://uri.etsi.org/01903/v1.3.2#” attribute on the SignedProperties element?

*To generate invoice hash:

UBLExtension [local-name()=‘Invoice’]//[local-name()=‘UBLExtensions’]
QR //*[local-name()=‘AdditionalDocumentReference’][cbc:ID[normalize-space(text()) = ‘QR’]]
Signature [local-name()=‘Invoice’]//[local-name()=‘Signature’]

1-Open the invoice XML file.
2-Remove the tags mentioned in the table above using the XPath.
3-Remove the XML version.
4-Canonicalize the Invoice using the C14N11 standard
5-Hash the new invoice body using SHA-256 (output). e.g.:a11b6fe587a50f7daffe3a7fb42dcccf32b43ee9b37d9f252d04243e54c11a3f
6-Encode the hashed invoice using base64 (output)
Using HEX-to Base64 Encoder
e.g.:oRtv5YelD32v/jp/tC3MzzK0PumzfZ8lLQQkPlTBGj8=

Note:
-All these values will be used in later steps.
-Please make sure that you have a copy of the original invoice before removing the above tags.

  • To Populate the Signed Properties:

1-Open the invoice before 1st step (before getting tags removed when you generate the invoice hash)
2-fill the sub tags in SignedProrertied tag (DigestValue, SigningTime, X509IssuerName, X509SerialNumber) if there are any old values already exist in the fields, please make sure to remove all of them and replace them with the new values only).

Note: SignedProperties tag should be without name space when you want to fill the sub tags, for example

<xades:SignedProperties Id="xadesSignedProperties">
	<xades:SignedSignatureProperties>
		<xades:SigningTime>2023-01-24T11:16:44Z</xades:SigningTime>
		<xades:SigningCertificate>
			<xades:Cert>
				<xades:CertDigest>
					<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
					<ds:DigestValue>YTJkM2JhYTcwZTBhZTAxOGYwODMyNzY3NTdkZDM3YzhjY2IxOTIyZDZhM2RlZGJiMGY0NDUzZWJhYWI4MDhmYg==</ds:DigestValue>
				</xades:CertDigest>
				<xades:IssuerSerial>
					<ds:X509IssuerName>CN=TSZEINVOICE-SubCA-1, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName>
					<ds:X509SerialNumber>2475382886904809774818644480820936050208702411</ds:X509SerialNumber>
				</xades:IssuerSerial>
			</xades:Cert>
		</xades:SigningCertificate>
	</xades:SignedSignatureProperties>
</xades:SignedProperties>

*After that to generate Signed Properties Hash:

1-Take this tag with a namespace just to generate a Signed Properties hash and fill in the same value you have. For example:

<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties">
                                    <xades:SignedSignatureProperties>
                                        <xades:SigningTime>2023-01-24T11:16:44Z</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#">YTJkM2JhYTcwZTBhZTAxOGYwODMyNzY3NTdkZDM3YzhjY2IxOTIyZDZhM2RlZGJiMGY0NDUzZWJhYWI4MDhmYg==</ds:DigestValue>
                                                </xades:CertDigest>
                                                <xades:IssuerSerial>
                                                    <ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">CN=TSZEINVOICE-SubCA-1, DC=extgazt, DC=gov, DC=local</ds:X509IssuerName>
                                                    <ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">2475382886904809774818644480820936050208702411</ds:X509SerialNumber>
                                                </xades:IssuerSerial>
                                            </xades:Cert>
                                        </xades:SigningCertificate>
                                    </xades:SignedSignatureProperties>
                                </xades:SignedProperties>

2-Hash the new property tag(After fill) using SHA-256 (output).
e.g.:99282555b5d79209be5883cc23eb234cd01bd33ea7d54d88f491248d33e321f1
3-Encode the hashed property using base64 (ENCODER BASE64 ) (output).
E.g.:OTkyODI1NTViNWQ3OTIwOWJlNTg4M2NjMjNlYjIzNGNkMDFiZDMzZWE3ZDU0ZDg4ZjQ5MTI0OGQzM2UzMjFmMQ==

Note: take the final value, and do not replace the tag with a namespace with the original SignedProperties tag in the invoice.

Thank you.

@MAl-tamimi Thanks for the very detailed answer, I have one important question.

Do I canonicalize the signed properties before hashing them or not?

I was told in a call with ZATCA that I should canonicalize them, however in the steps you mentioned there was no mention of canonicalization. So this means that white space matters. Am I correct?

If whitespace does matter, is the version you posted the one with the correct whitespace that should be used?

You are welcome.
No, we don’t need to canonicalize for the Signed Properties steps. Please just follow the steps I mentioned above to generate the Signed Properties Hash.

Note: Please don’t add any whitespace that you do not need; just take the tag and fill in the data.

Thank you.

1 Like

Sorry, I realize as I posted this that I made a mistake in the content, I couldn’t find a way to edit a pending post so I will just post the amendments here:

The certificate serial number had an extra } appended to it. I removed it but I am still getting the same error.

The hash is different however and so is the Base64 of course.

Hash (hexdigest): b408e78c45885aafcc1069b151ab6c3532d0938ad0db8b8748c8b82bc481f4ee
Base64: YjQwOGU3OGM0NTg4NWFhZmNjMTA2OWIxNTFhYjZjMzUzMmQwOTM4YWQwZGI4Yjg3NDhjOGI4MmJjNDgxZjRlZQ==

@MAl-tamimi

So I am still getting an error when submitting to the simulation environment.

Error

{
  "type": "ERROR",
  "code": "signed-properties-hashing",
  "category": "CERTIFICATE_ERRORS",
  "message": "Invalid signed properties hashing, SignedProperties with id='xadesSignedProperties'",
  "status": "ERROR"
}

Below are the steps I followed


Generating the Signed Properties Hash

This is what my signed properties look like only when I am generating a hash for them:

<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties">
                                    <xades:SignedSignatureProperties>
                                        <xades:SigningTime>2023-09-25T18:04:00Z</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#">ZmQ2ZmVjZTFmOTE1NmM3NmFiZDNmOTJjNGU3YzFlMzNiMWM1OGJlMmViM2Q4Y2ZkMmVlMDc4ZTNkNzdiMDg3Yg==</ds:DigestValue>
                                                </xades:CertDigest>
                                                <xades:IssuerSerial>
                                                    <ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#">CN=eInvoicing</ds:X509IssuerName>
                                                    <ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#">1685368414756}</ds:X509SerialNumber>
                                                </xades:IssuerSerial>
                                            </xades:Cert>
                                        </xades:SigningCertificate>
                                    </xades:SignedSignatureProperties>
                                </xades:SignedProperties>
  1. I get a hexdigest that looks like so: 50d40101eac89d118e8d71065a42945a306720207f83b9cfa7e58ab240ae0230
  2. I turn that hexdigest into Base64 and I get this: NTBkNDAxMDFlYWM4OWQxMThlOGQ3MTA2NWE0Mjk0NWEzMDY3MjAyMDdmODNiOWNmYTdlNThhYjI0MGFlMDIzMA==

Populating the Signed Properties for Submitting via ZATCA API

(Note: This is extracted from an invoice)

This is what my signed properties look like when I am submitting them to the ZATCA API:

<xades:SignedProperties Id="xadesSignedProperties">
  <xades:SignedSignatureProperties>
    <xades:SigningTime>2023-09-25T18:03:09Z</xades:SigningTime>
    <xades:SigningCertificate>
      <xades:Cert>
        <xades:CertDigest>
          <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256"/>
          <ds:DigestValue>ZmQ2ZmVjZTFmOTE1NmM3NmFiZDNmOTJjNGU3YzFlMzNiMWM1OGJlMmViM2Q4Y2ZkMmVlMDc4ZTNkNzdiMDg3Yg==</ds:DigestValue>
        </xades:CertDigest>
        <xades:IssuerSerial>
          <ds:X509IssuerName>CN=eInvoicing</ds:X509IssuerName>
          <ds:X509SerialNumber>1685368414756</ds:X509SerialNumber>
        </xades:IssuerSerial>
      </xades:Cert>
    </xades:SigningCertificate>
  </xades:SignedSignatureProperties>
</xades:SignedProperties>

Am I doing anything wrong? I believe I followed the steps you mentioned properly

@obahareth-mrsool
Please ensure that the values in the xades:SignedProperties tag match.

The value in the xades:SigningTime tag is different from the value you used to generate the Signed Properties Hash:

                                        <xades:SigningTime>2023-09-25T18:04:00Z</xades:SigningTime>

    <xades:SigningTime>2023-09-25T18:03:09Z</xades:SigningTime>

Note: Please ensure to remove the ‘}’ character from the X509SerialNumber value.

Thank you

Sorry @MAl-tamimi, I was just pasting things wrong because these two steps happen in very different phases in our SDK and are not designed to be easy to extract.

The content was actually identical it’s just that I ran one step before the other for copy pasting to the forum, but what was submitted to ZATCA’s servers was identical.

I ran a diff to show the output:

No need to worry @obahareth-mrsool

Please use the SDK to verify whether the Signed property hash is correct.
Take a valid invoice and use the SDK to sign it. On the other hand, take the same invoice before signing it in the SDK and attempt manual signing.
Compare the Signed property hash in the SDK-signed invoice with the Signed property hash generated manually.

Note:
1- To obtain the same hash, please follow the steps I mentioned above.
2- we are working on update the Sign documentation.

If you need further clarification, please reach out to your relationship manager to schedule a meeting with us.

thank you.

@MAl-tamimi

The issue with this approach you suggested is that the signing times will always be different, unless I can have a custom signing time in the SDK.

Does the ZATCA SDK support custom signing times?

If not, the hash will always be different based on the time I sign it at and I have no way to compare because hashing is one-way.

I noticed the ‘paste here’ tag, and it will undergo formatting changes, including the addition of new spaces. If the value is incorrect for this tag, please refer to your invoice. Take the SignedProperties tag to any text editor (e.g., NotePad++), then add 5 spaces for each tag to ensure correct formatting before generating the Signed Properties Hash.

Thank you.

<xades:SignedProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#" Id="xadesSignedProperties">
                                    <xades:SignedSignatureProperties>
                                        <xades: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#"></ds:DigestValue>
                                                </xades:CertDigest>
                                                <xades:IssuerSerial>
                                                    <ds:X509IssuerName xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:X509IssuerName>
                                                    <ds:X509SerialNumber xmlns:ds="http://www.w3.org/2000/09/xmldsig#"></ds:X509SerialNumber>
                                                </xades:IssuerSerial>
                                            </xades:Cert>
                                        </xades:SigningCertificate>
                                    </xades:SignedSignatureProperties>
                                </xades:SignedProperties>

@MAl-tamimi I compared the whitespace with the last version you shared and it is identical. This is the same whitespace I’m currently using to generate the hash.
SignedSignatureProperties is indented by 36 spaces, and then every new tag is indented by 4 spaces after that, I didn’t get what you mean by 5 spaces.

1 Like

I meant 5 namespaces (attribute tags). Please take the last signedProperty tag as is and follow the steps. You need to manually create the signature without using the SDK. However, please use the SDK to verify whether the generated Signed Property Hash is correct or not. Also, make sure to use the same signing time after signing the invoice using the SDK in your manual steps.

-Please If you need further clarification, please reach out to your relationship manager to schedule a meeting with us.

Thank you.

1 Like