Попытка цифровой подписи вызова SOAP с сертификатом x.509 в PHP

Я работаю с клиентом, пытаясь отправлять и получать вызовы с использованием PHP. Они настроены на ws-security и используют сертификат x.509 для аутентификации. Я смог заставить это работать с помощью SoapUI, но я не смог работать с ним в PHP.

Проблема, с которой я столкнулась, заключается в том, что они не используют стандартный двоичный токен безопасности или комбинацию имени пользователя и пароля. Вместо этого они подписывают XML файл в Reference Token Reference.

Я пытаюсь использовать библиотеку Роб Ричардса для генерации хэша, и, похоже, код, включенный в него, выполняет то, что я пытаюсь сделать, но я не увенчался успехом в его реализации. (https://github.com/robrichards/wse-php)

Здесь мы должны получить:

<soapenv:Envelope xmlns:ord="http://order.pine.cypresscare.com" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
        <ds:Signature Id="SIG-17020931F46DA4F12E144355764463230" xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo>
                <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                    <ec:InclusiveNamespaces PrefixList="ord soapenv" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                </ds:CanonicalizationMethod>
                <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/>
                <ds:Reference URI="#id-17020931F46DA4F12E144355764463229">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#">
                            <ec:InclusiveNamespaces PrefixList="ord" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                        </ds:Transform>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <ds:DigestValue>BZc+DagseonF6kbBdtONG73wjcE=</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>eIICrWiZerxelcSNUack5OKgvdSKYS3p5KdblFLVztYksExNoZ9wLQ==</ds:SignatureValue>
            <ds:KeyInfo Id="KI-17020931F46DA4F12E144355764463227">
                <wsse:SecurityTokenReference wsu:Id="STR-17020931F46DA4F12E144355764463228">
                    <wsse:KeyIdentifier EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">
                         MIID... (Hash goes here)
                    </wsse:KeyIdentifier>
                </wsse:SecurityTokenReference>
            </ds:KeyInfo>
        </ds:Signature>
    </wsse:Security>
</soapenv:Header>

Но лучшее, что я смог получить, это:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ord="http://order.pine.cypresscare.com">
<soapenv:Header>
    <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
        <wsse:BinarySecurityToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" wsu:Id="pfx7b827e06-1662-e6e4-78fd-6b4bb95aeb96" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3">
            MIIC... (Hash goes here)
        </wsse:BinarySecurityToken>
        <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
            <ds:SignedInfo>
                <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
                <ds:Reference URI="#pfx0b88133b-03ed-8bbc-8c8a-4998ef427a3a">
                    <ds:Transforms>
                        <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
                    </ds:Transforms>
                    <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
                    <ds:DigestValue>fPmwf05DIdXW4K9muNYR6LMXjnI=</ds:DigestValue>
                </ds:Reference>
            </ds:SignedInfo>
            <ds:SignatureValue>q36Dr2TIl1OE0/6bBMPb0dQRVCimwpOx7KeYyUCfxMZVIMvDBXxH+lCiB5xEgEH/aceUsn19b0GTU1LqISOk4/rhVBHGw2Wpq/jBcRZWOO54xZYdpGkqzepagazJWOWVVdDCAD7WpQV34KRu1rT4S4ZCjaOeApVIlI2nhPWRXVQ=</ds:SignatureValue>
            <ds:KeyInfo>
                <wsse:SecurityTokenReference>
                    <wsse:Reference ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" URI="#pfx7b827e06-1662-e6e4-78fd-6b4bb95aeb96"/>
                </wsse:SecurityTokenReference>
            </ds:KeyInfo>
        </ds:Signature>
    </wsse:Security>
</soapenv:Header>

И класс PHP, чтобы все это работало:

<?php
class MySoap extends SoapClient {
    public function __doRequest($request, $location, $saction, $version) {
        $doc = new DOMDocument('1.0');
        $doc->loadXML($request);

        $objWSSE = new WSSESoap($doc);

        /* add Timestamp with no expiration timestamp */
        $objWSSE->addTimestamp();

        /* create new XMLSec Key using AES256_CBC and type is private key */
        $objKey = new XMLSecurityKey(XMLSecurityKey::RSA_SHA1, array('type' => 'public'));

        /* load the private key from file - last arg is bool if key in file (true) or is string (false) */
        $objKey->loadKey("c:\\xampp\htdocs\\XMLSoapApp\\cert1.pem", $isFile=true, true);

        /* Sign the message - also signs appropiate WS-Security items */
        $options = array("insertBefore" => true);

        $objWSSE->signSoapDoc($objKey, $options);

        /* Add certificate (BinarySecurityToken) to the message */ 
        $token = $objWSSE->addBinaryToken(file_get_contents(combine_key));

        /* Attach pointer to Signature */
        $objWSSE->attachTokentoSig($token);
        return $doc->saveXML();
    }
}

Должен быть способ сделать это без особых трудностей, но мне кажется, что он отсутствует. Кто-нибудь сделал это раньше?

Ответ 1

Из того, что я вижу, вы используете неправильный тип ключа: это должен быть XMLSecurityKey::DSA_SHA1, но вы используете XMLSecurityKey::RSA_SHA1. Первый не поддерживается lib, кстати. Но это все еще можно решить. Ниже вы можете найти код, который я использовал для проверки этого.

  1. Генерация ключей (отличный совет):

    openssl dsa -in dsakey.private openssl req -x509 -новый -days 3650 -key dsakey.private -out dsakey.cert openssl dsa -in dsakey.private -pubout -out dsakey.pub

  2. Патч lib:

в vendor/robrichards/xmlseclibs/src/XMLSecurityKey.php 216 добавьте следующий блок case:

case (self::DSA_SHA1):
  $this->cryptParams['library'] = 'openssl';
  $this->cryptParams['method']  = 'http://www.w3.org/2000/09/xmldsig#dsa-sha1';
  $this->cryptParams['padding'] = OPENSSL_PKCS1_PADDING;
  $this->cryptParams['digest']  = OPENSSL_ALGO_SHA1;
  if (is_array($params) && ! empty($params['type'])) {
    if ($params['type'] == 'public' || $params['type'] == 'private') {
      $this->cryptParams['type'] = $params['type'];
      break;
    }
  }
  throw new Exception('Certificate "type" (private/public) must be passed via parameters');
break;
  1. Запустите функцию подписи:

use RobRichards\WsePhp\WSSESoap;
use RobRichards\XMLSecLibs\XMLSecurityKey;

$doc = new DOMDocument('1.0');
$doc->loadXML(file_get_contents('/request.xml'));

$objWSSE = new WSSESoap($doc);
$objWSSE->addTimestamp();

$objKey = new XMLSecurityKey(XMLSecurityKey::DSA_SHA1, ['type' => 'private']);

$objKey->loadKey('/dsakey.private', true);
$options = ['insertBefore' => true];
$objWSSE->signSoapDoc($objKey, $options);

$token = $objWSSE->addBinaryToken(file_get_contents('/dsakey.cret'));
$objWSSE->attachTokentoSig($token);

echo $doc->saveXML();