bseddon / xml-signer

Provides signing and verification of XML documents including support for XAdES
BSD 3-Clause "New" or "Revised" License
19 stars 8 forks source link

How to use transforms? #18

Closed underdpt closed 2 months ago

underdpt commented 2 months ago

Hello,

First, thanks for this tool!

I'm trying to sign a xml document with Xades, with the particularity that it only has to sign the first child element. This is easy as I can remove the root element, sign the document and then re-add the root element. But I have to add a transform like this:

<ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
    <ds:XPath>not(ancestor-or-self::ds:Signature)</ds:XPath>
</ds:Transform>

I'm unable to add such a transform. I tried to extend lyquidity\xmldsig\xml\Transform but then when overloading generateXml it calls lyquidity\xmldsig\xml\Transform::generateXml which ignores the $attributes I'm passing.

I'm pretty sure I'm doing something wrong. In the documentation it says that there's a way to use transforms to select the node to be signed (https://github.com/bseddon/xml-signer#signing-node-sub-set-by-id-value). Any clue on how to correctly use Transforms?

This is the code I'm using:

$xmlResource = new InputResourceInfo(
    $xmlString,
    InputResourceInfo::string,
    dirname($this->signedXmlPath),
    basename($this->signedXmlPath),
    new Transforms([
        new Transform(Transform::C14N),
        new Transform(Transform::ENV_SIG),
        new TransformCxPath(),
    ]),
    false
);

and this is my TransformCxPath class:

<?php

namespace App\Actions\DGT;

use lyquidity\xmldsig\xml\AttributeNames;
use lyquidity\xmldsig\xml\Transform;
use lyquidity\xmldsig\xml\XmlCore;
use lyquidity\xmldsig\XMLSecurityDSig;

class TransformCxPath extends XmlCore
{
    private string $algorithm;

    public function __construct()
    {
        $this->defaultNamespace = XMLSecurityDSig::XMLDSIGNS;

        $this->algorithm = Transform::CXPATH;
    }

    /**
     * Create <Transform> and any descendent elements
     *
     * @param \DOMElement $parentNode
     * @param string[] $attributes
     * @param \DOMElement $insertAfter
     * @return \DOMElement
     */
    public function generateXml($parentNode, $attributes = [], $insertAfter = null)
    {
        return parent::generateXml($parentNode, [
            AttributeNames::Algorithm => $this->algorithm,
            'XPath'                   => 'not(ancestor-or-self::ds:Signature)',
        ], $insertAfter);
    }
}
bseddon commented 2 months ago

I'm not able to visualize what your document looks like so my thoughts are going to be limited.

It seems the transform you are creating is functionally the same as the ENV_SIG transform you also include. If you need to include and exclude elements from the signature, have you considered using XMLDSig Filter2?. This allows you to include and exclude node in a more flexible way.

Ultimately, the various filters are used to create parameters for a call the DOMNode::C14N() function. It is this function that applies the transforms to generate a canonicalized version of the document which is then signed. Are you able to put a breakpoint on line 411 of XMLSecurityDSig.php to find out if this function is returning a string or FALSE?

underdpt commented 2 months ago

Hi @bseddon

Thanks for your answer.

This is the document I'm trying to generate (It's taken from a pdf file and then prettified so it won't pass any validation):

<?xml version="1.0" encoding="UTF-8"?>
<Solicitud_Registro_Entrada>
    <Datos_Firmados>
        <Datos_Genericos>
            <Remitente>
                <Nombre>MANIPULADOR</Nombre>
                <Apellidos>DE PLACAS</Apellidos>
                <Documento_Identificacion>
                    <Tipo>1</Tipo>
                    <Numero>DELETED</Numero>
                </Documento_Identificacion>
                <Correo_Electronico/>
            </Remitente>
            <Interesados/>
            <Asunto>
                <Codigo>OBCT</Codigo>
                <Descripcion>Operaciones Basicas de Gestion de Custodia Virtual de tarjetas eITV</Descripcion>
            </Asunto>
            <Destino>
                <Codigo>101001</Codigo>
                <Descripcion>DGT - Vehículos</Descripcion>
            </Destino>
        </Datos_Genericos>
        <Datos_Especificos>
            <operaciones>
                <operacion>
                    <codigoOperacion>EEFF_ALTA_INSCRIPCION_PLACAS_WS</codigoOperacion>
                    <datos>
                        <vehiculo>
                            <matricula>DELETED</matricula>
                            <bastidor>DELETED</bastidor>
                        </vehiculo>
                        <compradorplaca>
                            <dni>DELETED</dni>
                            <nombreApellidos>Plaquiforme</nombreApellidos>
                            <extranjero>No</extranjero>
                        </compradorplaca>
                        <expedicion>
                            <doifabricante>DELETED</doifabricante>
                            <numManipulador>Kurrupipi</numManipulador>
                            <numHomologacion/>
                            <numPlacasexpedir>5</numPlacasexpedir>
                            <fechaCompra>21/01/2022</fechaCompra>
                            <observaciones>Plaquetas 05</observaciones>
                        </expedicion>
                    </datos>
                </operacion>
            </operaciones>
        </Datos_Especificos>
    </Datos_Firmados>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#" Id="Signature-9da60454-7628-4ebe-ab2c-58690999aa31-Signature">
        <ds:SignedInfo>
            <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
            <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
            <ds:Reference Id="Reference-ce4ce2c2-33a5-4d0b-bcec-1b7477738d6c" URI="">
                <ds:Transforms>
                    <ds:Transform Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
                    <ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
                    <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
                        <ds:XPath>not(ancestor-or-self::ds:Signature)</ds:XPath>
                    </ds:Transform>
                </ds:Transforms>
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>
                <ds:DigestValue>B39ZTd1oybevaJGJ5gAfgeHBR9pD9XgCS3kS65V+geeqTnd/1Y6OqN5TLRCMFa4d7mV9GwEPCowvoh94kAIj6g==</ds:DigestValue>
            </ds:Reference>
            <ds:Reference Type="http://uri.etsi.org/01903#SignedProperties" URI="#Signature-9da60454-7628-4ebe-ab2c-58690999aa31-SignedProperties">
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>
                <ds:DigestValue>Gff8tbppOY0wVyxImMj9qetO64BjEgMr+JVyOZ2UOd8gVzv4qbnhQL0hfBRMUwj/JGFD1i5iLbIzeXALjzEgBA==</ds:DigestValue>
            </ds:Reference>
            <ds:Reference URI="#Signature-9da60454-7628-4ebe-ab2c-58690999aa31-KeyInfo">
                <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>
                <ds:DigestValue>uFd4AVHIscXaQQqA883SE/NCIElh2dybmoFq11q9BnbZ1yNIiTlKNqknP8LGIiq5DisztWaofOQmzgNYcSjBEA==</ds:DigestValue>
            </ds:Reference>
        </ds:SignedInfo>
        <ds:SignatureValue Id="Signature-9da60454-7628-4ebe-ab2c-58690999aa31-SignatureValue">
            dXf3Hpw2a/MsxmGpIgIbWRmgeMp18mf3yK7mPgJUlRcT2pO8XtSVy+WQmyulDHV16KUPVMbRXLV1puPZcbLWeyJNdRZt+77AX8C5hudwmKtwWmiWAEAcFPv3E//cTRZ9bxyWfB66F8LrAKdvcHQRTnAoZCniK+x7rLdl1kWpKMa2QE7qVAN+BV12lDjByrbUoIfEQfkwY0J2GGJkxHmU2LJlbucUYVxGBPe6lL+ydMIH4k8jvuWsjpCOG0aLIsFC4JBhw9b+onoqxRijYzLDWHHIWHagwWdd5gVl9OwpZ9a38ygV60/zYLURmgaYwutOyEtqmJbtyWdaZYj+tmx0KQ==
        </ds:SignatureValue>
        <ds:KeyInfo Id="Signature-9da60454-7628-4ebe-ab2c-58690999aa31-KeyInfo">
            <ds:X509Data>
                <ds:X509Certificate>
                    DELETED
                </ds:X509Certificate>
                <ds:X509Certificate>
                     DELETED
                </ds:X509Certificate>
                <ds:X509Certificate>
                    DELETED
                </ds:X509Certificate>
            </ds:X509Data>
            <ds:KeyValue>
                <ds:RSAKeyValue>
                    <ds:Modulus>
                        DELETED
                    </ds:Modulus>
                    <ds:Exponent>AQAB</ds:Exponent>
                </ds:RSAKeyValue>
            </ds:KeyValue>
        </ds:KeyInfo>
        <ds:Object>
            <xades:QualifyingProperties xmlns:xades="http://uri.etsi.org/01903/v1.3.2#"
                                        Id="Signature-9da60454-7628-4ebe-ab2c-58690999aa31-QualifyingProperties"
                                        Target="#Signature-9da60454-7628-4ebe-ab2c-58690999aa31-Signature">
                <xades:SignedProperties Id="Signature-9da60454-7628-4ebe-ab2c-58690999aa31-SignedProperties">
                    <xades:SignedSignatureProperties>
                        <xades:SigningTime>2022-01-24T09:33:58+01:00</xades:SigningTime>
                        <xades:SigningCertificate>
                            <xades:Cert>
                                <xades:CertDigest>
                                    <ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha512"/>                           <ds:DigestValue>Br+Id8Ou01FmK+UekIaO5D4euvRdzz0AtlU1wmnSXoVMT5GxcxQx2ES4D11w7pC4fjbE6kAl2Mi3+1MXXXV4vQ==
                                    </ds:DigestValue>
                                </xades:CertDigest>
                                <xades:IssuerSerial>
                                    <ds:X509IssuerName>CN=DELETED
                                        L=DELETED
                                    </ds:X509IssuerName>
                                    <ds:X509SerialNumber>DELETED</ds:X509SerialNumber>
                                </xades:IssuerSerial>
                            </xades:Cert>
                        </xades:SigningCertificate>
                    </xades:SignedSignatureProperties>
                    <xades:SignedDataObjectProperties>
                        <xades:DataObjectFormat ObjectReference="#Reference-ce4ce2c2-33a5-4d0b-bcec-1b7477738d6c">
                            <xades:Description/>
                            <xades:ObjectIdentifier>
                                <xades:Identifier Qualifier="OIDAsURN">urn:oid:1.2.840.10003.5.109.10</xades:Identifier>
                                <xades:Description/>
                            </xades:ObjectIdentifier>
                            <xades:MimeType>text/xml</xades:MimeType>
                            <xades:Encoding/>
                        </xades:DataObjectFormat>
                    </xades:SignedDataObjectProperties>
                </xades:SignedProperties>
            </xades:QualifyingProperties>
        </ds:Object>
    </ds:Signature>
</Solicitud_Registro_Entrada>

As I extract from the document, it's only signing the element <Datos_Firmados>. I have the unsigned document, and I'm, trying to find a way to sign only that element, not the full document. In the example they use all three transforms. I see that ENV_SIG removes the Signature element from the digest calculation, and it's the same that's doing the CXPATH transform on the original document, maybe they do this way to be sure it passes broken validators.

So, It's there a way to sign only an element? If so, could you point me on a way to do that?

Thanks!

bseddon commented 2 months ago

Here's an example. Suppose you want to create a signature for the element of you document. First you will create an InputResourceInfo instance (as you did) but include only one XPath transform.

The query in the transform is not a full XPath statement. The specification reference 'xpath filtering' so what is provided is the part of a query that can be used to filter elements (this is why an id cannot be used).

It's not necessary to add other transforms. In the final array parameter to the static signDocument function, the 'canonicalizationMethod' is specified as XMLSecurityDSig::EXC_C14N. This forces the document to be signed by first removing the signature.

The signature will be created and the required transforms will be added to the signature so a verifier application will know which transform to apply. This will include the EXC_SIG transform.

Hope this helps.

Bill

$inputResource = new InputResourceInfo(
    $xmlString,
    InputResourceInfo::string,
    dirname($this->signedXmlPath),
    basename($this->signedXmlPath),
    new Transforms(
        new TransformXPath('ancestor-or-self::Datos_Firmados')
    ),
    false, // Detatched
    XAdES::SignatureRootId,
    true
);

XAdES_DGFiP::signDocument(
    $inputResource,
    // You will set certificate and key resources that are suitable for you.  
    // In my example I'm using a certificate in a .p12 file which has been loaded into $store.
    new CertificateResourceInfo( $store['cert'], ResourceInfo::string | ResourceInfo::binary | ResourceInfo::pem ),
    new KeyResourceInfo( $store['pkey'], ResourceInfo::string | ResourceInfo::binary | ResourceInfo::pem ),
    new SignatureProductionPlace(
        'Madrid',
        null,
        '28071',
        'España'
    ),
    new SignerRole(
        new ClaimedRoles( new ClaimedRole('Capitán') )
    ),
    array( 
        'canonicalizationMethod' => XMLSecurityDSig::EXC_C14N,
        'addTimestamp' => false,
        // 'prefix' => 'ssd',
        'xadesPrefix' => 'xad'
    )
);
underdpt commented 2 months ago

Hello,

That did the trick!

Also, in case the receiver doesn't parse it (it's a government service, so I'm not sure about their system) I've been able to assign an Id to the target element and then:

$xmlResource = new InputResourceInfo(
    $xmlString,
    InputResourceInfo::string,
    dirname($this->signedXmlPath),
    basename($this->signedXmlPath),
    new Transforms([
        new Transform(Transform::C14N),
        new Transform(Transform::ENV_SIG),
    ]),
    false
);

$xmlResource->uri = 'DatosParaFirmar';

Finally, thanks, didn't know there were some Transforms already implemented, so I can implement my own if needed.