Skip to content

Instantly share code, notes, and snippets.

@odan
Created June 1, 2021 14:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save odan/9ae2d36d5970cde6f5acc661b8b725f7 to your computer and use it in GitHub Desktop.
Save odan/9ae2d36d5970cde6f5acc661b8b725f7 to your computer and use it in GitHub Desktop.
Sign SOAP messages with xmlseclibs

Sign SOAP messages with xmlseclibs

Requirements

  • PHP 7.4+
  • OpenSSL

Generating Public and Private Keys

First we have to create a private key for signature creation, and a public key for verification. This means that it's fine to distribute your public key. However, the private key should remain secret.

Generate the private key with this OpenSSL command (enter a password):

openssl genrsa -out private.pem 2048

The private key is generated and saved in a file named "private.pem", located in the same directory.

The number "2048" in the above command indicates the size of the private key. You can choose one of these sizes: 512, 758, 1024, 2048 or 4096 (these numbers represent bits). The larger sizes offer greater security, but this is offset by a penalty in CPU performance.

Generating the Public Key

Later we need a public key for the token verification. To extract the public key file, type the following:

openssl rsa -in private.pem -outform PEM -pubout -out public.pem

The public key is saved in a file named public.pem located in the same directory.

Generating a Certificate

In order to append a X509 certificate to the SOAP signature you have to generate a cert file using the private key:

openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048 -keyout private.pem -out certificate.crt

PFX

Extract private key from a PFX file:

openssl pkcs12 -in filename.pfx -nocerts -out private.pem

Extract certificate from a PFX file:

openssl pkcs12 -in filename.pfx -clcerts -nokeys -out certificate.crt

Installation

Run:

composer require robrichards/xmlseclibs

Code

<?php

namespace Acme\XmlDSig\Soap\Test;

use DOMDocument;
use DOMXPath;
use PHPUnit\Framework\TestCase;
use RobRichards\XMLSecLibs\XMLSecurityDSig;
use RobRichards\XMLSecLibs\XMLSecurityKey;

/**
 * Test.
 */
class XmlDsigSoapTest extends TestCase
{
    /**
     * Test.
     *
     * @return void
     */
    public function testSignAndVerify()
    {
        $privateKeyPath = __DIR__ . '/private.pem';
        $certificatePath = __DIR__ . '/certificate.crt';

        $dom = $this->createSoapXmlMessage();

        $dsig = new XMLSecurityDSig();
        $dsig->setCanonicalMethod(XMLSecurityDSig::EXC_C14N);
        $dsig->addReference(
            $dom,
            XMLSecurityDSig::SHA256,
            ['http://www.w3.org/2000/09/xmldsig#enveloped-signature'],
            ['force_uri' => true]
        );

        $dsigKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA256, ['type' => 'private']);

        // Optional
        //$dsigKey->passphrase = 'secret';

        $dsigKey->loadKey(file_get_contents($privateKeyPath), false, false);
        $dsig->sign($dsigKey);

        // Add the associated public key to the signature
        $options = [
            'issuerSerial' => 'serial',
            'subjectName' => 'subject',
        ];

        $dsig->add509Cert(file_get_contents($certificatePath), true, false, $options);

        // Append signature to root element
        //$objDSig->appendSignature($dom->documentElement);

        // Append signature to RegisterTCRRequest element
        $dsig->appendSignature($dom->getElementsByTagName('RegisterTCRRequest')->item(0));

        // Register all namespace to find the Reference element
        $xpath = new DOMXPath($dom);
        $xpath->registerNamespace('SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/');
        $xpath->registerNamespace('ds', 'http://www.w3.org/2000/09/xmldsig#');
        $xpath->registerNamespace('xmlns', 'https://efi.tax.gov.me/fs/schema/');
        $xpath->registerNamespace('ns2', 'http://www.w3.org/2000/09/xmldsig#');

        // Set reference URI
        $elements = $xpath->query('//ds:Signature/ds:SignedInfo/ds:Reference');
        $elements->item(0)->attributes->item(0)->nodeValue = '#Request';

        // Get raw XML
        $xmlRaw = $dom->saveXML();

        $this->assertStringContainsString('<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">', $xmlRaw);
        $this->assertStringContainsString('ds:DigestValue', $xmlRaw);
        $this->assertStringContainsString('ds:SignatureValue', $xmlRaw);
        $this->assertStringContainsString('ds:X509Certificate', $xmlRaw);
        $this->assertStringContainsString('<ds:Reference URI="#Request">', $xmlRaw);
    }

    /**
     * Create soap message.
     *
     * @return DOMDocument The dom
     */
    private function createSoapXmlMessage(): DOMDocument
    {
        $dom = new DOMDocument('1.0', 'UTF-8');
        $dom->formatOutput = true;

        $envelope = $dom->createElementNS('http://schemas.xmlsoap.org/soap/envelope/', 'SOAP-ENV:Envelope');
        $dom->appendChild($envelope);

        $soapHeader = $dom->createElement('SOAP-ENV:Header');
        $envelope->appendChild($soapHeader);

        $body = $dom->createElement('SOAP-ENV:Body');
        $envelope->appendChild($body);

        $request = $dom->createElement('RegisterTCRRequest');
        $body->appendChild($request);

        $request->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns', 'https://efi.tax.gov.me/fs/schema/');
        $request->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:ns2', 'http://www.w3.org/2000/09/xmldsig#');
        $request->setAttribute('Id', 'Request');
        $request->setAttribute('Version', '1');

        $requestHeader = $dom->createElement('Header');
        $request->appendChild($requestHeader);

        $requestHeader->setAttribute('SendDateTime', '2021-05-22T13:21:01+01:00');
        $requestHeader->setAttribute('UUID', '53f0616c-3078-4678-8716-59353e7475ee');

        $tcr = $dom->createElement('TCR');
        $request->appendChild($tcr);

        $tcr->setAttribute('BusinUnitCode', 'cx614cf575');
        $tcr->setAttribute('IssuerTIN', '03204308');
        $tcr->setAttribute('MaintainerCode', 'ov571vp018');
        $tcr->setAttribute('SoftCode', 'ca181vu261');
        $tcr->setAttribute('TCRIntID', '1');
        $tcr->setAttribute('ValidFrom', '2021-05-21');
        $tcr->setAttribute('Type', 'REGULAR');

        return $dom;
    }
}

Testing

composer test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment