skip to Main Content

I’m a little bit out of my comfort zone. I’m trying to create a SOAP request with PHP, and I’m using the famous API of KBO, here’s the documentation:

https://economie.fgov.be/sites/default/files/Files/Entreprises/CBE/Cookbook-CBE-Public-Search-Webservice.pdf

So essentially everything went well, but I’m still struggling with something about the authentication, which makes me fail my entire small framework:

<?php

$endpoint = 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub';
$wsdl = 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl';
$username = 'myusername';
$password = 'mypassword';

$timestamp = gmdate('Y-m-dTH:i:sZ');
$nonce = base64_encode(random_bytes(16));
$passwordDigest = base64_encode(sha1($nonce . $timestamp . $password, true));
$header = '
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
    <wsse:UsernameToken>
        <wsse:Username>' . $username . '</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' . $passwordDigest . '</wsse:Password>
        <wsse:Nonce>' . $nonce . '</wsse:Nonce>
        <wsu:Created xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">' . $timestamp . '</wsu:Created>
    </wsse:UsernameToken>
</wsse:Security>';
$options = array(
    'soap_version' => SOAP_1_1,
    'trace' => true,
    'exceptions' => true,
    'encoding' => 'UTF-8',
    'cache_wsdl' => WSDL_CACHE_NONE,
    'stream_context' => stream_context_create(array(
        'http' => array(
            'header' => 'Authorization: WSSE profile=' . $header,
            'user_agent' => 'PHPSoapClient'
        ),
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false
        )
    ))
);
$context = stream_context_create(array(
    'ssl' => array(
        'verify_peer' => false,
        'verify_peer_name' => false,
    ),
));
$options['stream_context'] = $context;
// $options = array(
//     'soap_version' => SOAP_1_1
// );
$client = new SoapClient($wsdl, $options);

// Set up the request parameters
$enterpriseNumber = '0810.002.854';
$request = array('EnterpriseNumber' => $enterpriseNumber);

// Call the SOAP operation with the request parameters
$response = $client->__soapCall('ReadEnterprise', array($request));

// Get the request and response XML
$requestXML = $client->__getLastRequest();
$responseXML = $client->__getLastResponse();

var_dump($response);

I also followed some suggestions from my PHP log file, where it was written to use a different version of the soap version: SOAP_1_1 instead of the SOAP_1_2.

What I find really challenging is the fact that I can’t debug it in any way; I have no idea on how I can check the error message, or I can’t have any hint in my error log file, it simply mentions the security check, but nothing much.

Has anyone had the same issue?

By the way, the error that I have encountered is the following:

[14-Feb-2023 17:09:24 UTC] PHP Fatal error: Uncaught SoapFault exception: [ns1:SecurityError] A security error was encountered when verifying the message in
Stack trace:
#0 /Users/mymac/Sites/cboxform/api-call/index.php(55): SoapClient->__soapCall(‘ReadEnterprise’, Array)
#1 {main}
thrown in /Users/mymac/Sites/cboxform/api-call/index.php on line 55

TLTR:

To summarize, I would need a request that looks like:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mes="http://economie.fgov.be/kbopub/webservices/v1/messages" xmlns:dat="http://economie.fgov.be/kbopub/webservices/v1/datamodel">
   <soapenv:Header>
 <mes:RequestContext>
<mes:Id>myid</mes:Id>
<mes:Language>fr</mes:Language>
 </mes:RequestContext>
 </soapenv:Header>
 <soapenv:Body>
 <mes:ReadEnterpriseRequest>
 <dat:EnterpriseNumber>0206231995</dat:EnterpriseNumber>
 </mes:ReadEnterpriseRequest>
 </soapenv:Body>
</soapenv:Envelope>

with a header with a digest password, with timestamp and nonce that will expire in 300 seconds to this endpoint:

https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub?wsdl

with also a username.

2

Answers


  1. It looks like an issue with the security token that you’re using to authenticate your SOAP request.
    Please check timezone format as UTC (YYYY-MM-DDTHH:MM:SSZ), Nonce should be a random value that is generated for each request, and PasswordDigest value is calculated correctly, PasswordDigest (SHA-1 hash) and concatenated values for Nonce, Created timestamp, password in binary format.

    Also you can try to print out the values of the timestamp, nonce, and passwordDigest for easy to debugging

    Can you try replace at line 20 within below code:

    $options = array(
        'soap_version' => SOAP_1_1,
        'trace' => true,
        'exceptions' => true,
        'encoding' => 'UTF-8',
        'cache_wsdl' => WSDL_CACHE_NONE,
        'stream_context' => stream_context_create(array(
            'http' => array(
                'header' => 'Authorization: WSSE ' . $header,
                'user_agent' => 'PHPSoapClient'
            ),
            'ssl' => array(
                'verify_peer' => false,
                'verify_peer_name' => false
            )
        ))
    );
    $context = stream_context_create(array(
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false,
        ),
    ));
    

    WSSE header to use the correct profile value within space after it

    Login or Signup to reply.
  2. The KBO Public Webservice uses ns1:SecurityError as a catch-all exception, which makes it hard to determine the actual cause.

    As a first step, I have found it working:

    This typically works when the language code and enterprise number have been set.

    But what is the actual payload?

    • On the request window click: Add request to mockservice (third button top left menu)
    • Start the mock service.
    • On request window select ‘http://localhost:8080’ or whatever port the mockservice is using.
    • On the mock service window: Go to "OnRequest Script".
    • Specify log.info mockRequest.requestContent.
    • Run request in request window.
    • In Mock service window: go to script log.
    • The actual payload is displayed.

    SoapUI of KBO

    Note that a password digest can only be used ONCE. On subsequent use it will also return the catch-all ns1:SecurityError. A sample of a working SQL-implementation is (shown on https://forums.invantive.com/t/uitlezen-kbo-public-search-api-geeft-foutmelding-ns1-securityerror/3424/2):

    --
    -- Tested according to http://www.herongyang.com/Web-Services/WS-Security-Validate-Password-Digest-String.html
    -- using:
    --  l_created := '2014-06-21T12:43:21.791Z';
    --  l_password := 'iLoveDogs';
    --  l_username := 'herong';
    --
    declare
      l_nonce                blob;
      l_created              varchar2;
      l_expires              varchar2;
      l_wsse_created         varchar2;
      l_password_digest      varchar2;
      l_xml                  varchar2;
      l_out_contents_char    varchar2;
      l_out_http_status_code int32;
      l_created_date         datetime;
      l_expires_date         datetime;
      --
      l_enterprise_number    varchar2 := '0670979187';
      l_username             varchar2 := 'wsot9999';
      l_password             varchar2 := 'secret';
      l_language_code        varchar2 := 'nl';
      l_msg_id               varchar2 := to_char(newid());
    begin
      l_created_date := sysdateutc;
      --
      -- Default maximum offset of wss4j is 300 seconds.
      --
      l_expires_date := l_created_date + 300/86400;
      --
      l_created := to_char(l_created_date, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"');
      l_expires := to_char(l_expires_date, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"');
      l_wsse_created := to_char(l_created_date, 'YYYY-MM-DD"T"HH24:MI:SS.FF3"Z"');
      --
      l_nonce:=random_blob(24);
      --
      -- Calculate digested password.
      --
      l_password_digest := base64_encode(hex_to_blob(sha1(l_nonce || to_binary(l_wsse_created) || to_binary(l_password))));
      --
      l_xml := '<soapenv:Envelope xmlns:dat="http://economie.fgov.be/kbopub/webservices/v1/datamodel" xmlns:mes="http://economie.fgov.be/kbopub/webservices/v1/messages" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">';
      l_xml := l_xml || chr(13) || chr(10) || '  <soapenv:Header>';
      l_xml := l_xml || chr(13) || chr(10) || '    <wsse:Security soapenv:mustUnderstand="1" 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">';
      l_xml := l_xml || chr(13) || chr(10) || '      <wsu:Timestamp wsu:Id="TS-A4DC6AF47AFDBFB025168519485171276">';
      l_xml := l_xml || chr(13) || chr(10) || '        <wsu:Created>' || l_created || '</wsu:Created>';
      l_xml := l_xml || chr(13) || chr(10) || '        <wsu:Expires>' || l_expires || '</wsu:Expires>';
      l_xml := l_xml || chr(13) || chr(10) || '      </wsu:Timestamp>';
      l_xml := l_xml || chr(13) || chr(10) || '      <wsse:UsernameToken wsu:Id="UsernameToken-A4DC6AF47AFDBFB025168519485171275">';
      l_xml := l_xml || chr(13) || chr(10) || '        <wsse:Username>' || xmlencode(l_username) || '</wsse:Username>';
      l_xml := l_xml || chr(13) || chr(10) || '        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">' || xmlencode(l_password_digest) || '</wsse:Password>';
      l_xml := l_xml || chr(13) || chr(10) || '        <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">' || xmlencode(l_nonce) || '</wsse:Nonce>';
      l_xml := l_xml || chr(13) || chr(10) || '        <wsu:Created>' || l_wsse_created || '</wsu:Created>';
      l_xml := l_xml || chr(13) || chr(10) || '      </wsse:UsernameToken>';
      l_xml := l_xml || chr(13) || chr(10) || '    </wsse:Security>';
      l_xml := l_xml || chr(13) || chr(10) || '    <mes:RequestContext>';
      l_xml := l_xml || chr(13) || chr(10) || '      <mes:Id>' || xmlencode(l_msg_id) || '</mes:Id>';
      l_xml := l_xml || chr(13) || chr(10) || '      <mes:Language>' || xmlencode(l_language_code) || '</mes:Language>';
      l_xml := l_xml || chr(13) || chr(10) || '    </mes:RequestContext>';
      l_xml := l_xml || chr(13) || chr(10) || '  </soapenv:Header>';
      l_xml := l_xml || chr(13) || chr(10) || '  <soapenv:Body>';
      l_xml := l_xml || chr(13) || chr(10) || '    <mes:ReadEnterpriseRequest>';
      l_xml := l_xml || chr(13) || chr(10) || '      <dat:EnterpriseNumber>' || xmlencode(l_enterprise_number) || '</dat:EnterpriseNumber>';
      l_xml := l_xml || chr(13) || chr(10) || '    </mes:ReadEnterpriseRequest>';
      l_xml := l_xml || chr(13) || chr(10) || '  </soapenv:Body>';
      l_xml := l_xml || chr(13) || chr(10) || '</soapenv:Envelope>';
      --
      select htp.CONTENTS_CHAR
      ,      htp.http_status_code
      into   l_out_contents_char
      ,      l_out_http_status_code
      from   HTTPDownload@DataDictionary
             ( url => 'https://kbopub-acc.economie.fgov.be/kbopubws110000/services/wsKBOPub'
             , acceptMimeType => '*/*'
             , contentType => 'text/xml;charset=UTF-8'
             , method => 'POST'
             , textPayload => l_xml
             , ignoreWebError => false
             ) htp
      ;
      dbms_output.put_line(l_out_contents_char);
    end;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search