Reputation: 447
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:
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-d\TH:i:s\Z');
$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.
Upvotes: 2
Views: 1629
Reputation: 4262
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?
log.info mockRequest.requestContent
.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;
Upvotes: 1
Reputation: 364
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
Upvotes: 1