Uitlezen KBO Public Search API geeft foutmelding ns1:SecurityError

Ik probeer via de volgende code om de KBO Public Search Webservice uit te lezen:

--
-- 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_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 := '0314595348';
  l_username             varchar2 := 'wsop9999';
  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' || to_char(l_created_date, 'HH24:MI:SS') || '.000Z';
  l_expires := to_char(l_expires_date, 'YYYY-MM-DD') || 'T' || to_char(l_expires_date, 'HH24:MI:SS') || '.000Z';
  --
  --
  l_nonce := random_blob(16);
  --
  -- Calculate digested password.
  --
  l_password_digest := base64_encode(hex_to_blob(sha1(l_nonce || to_binary(l_created) || to_binary(l_password))));
  --
  l_xml := '<?xml version="1.0" encoding="UTF-8"?>';
  l_xml := l_xml || chr(13) || chr(10) || '<soapenv:Envelope';
  l_xml := l_xml || chr(13) || chr(10) || '  xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"';
  l_xml := l_xml || chr(13) || chr(10) || '  xmlns:mes="http://economie.fgov.be/kbopub/webservices/v1/messages"';
  l_xml := l_xml || chr(13) || chr(10) || '  xmlns:dat="http://economie.fgov.be/kbopub/webservices/v1/datamodel"';
  l_xml := l_xml || chr(13) || chr(10) || '  xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext"';
  l_xml := l_xml || chr(13) || chr(10) || '  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) || '>';
  l_xml := l_xml || chr(13) || chr(10) || '  <soapenv:Header>';
  l_xml := l_xml || chr(13) || chr(10) || '    <wsse:Security>';
  l_xml := l_xml || chr(13) || chr(10) || '      <wsu:Timestamp>';
  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>';
  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_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>';
  dbms_output.put_line(l_xml);
  --
  select htp.CONTENTS_CHAR
  ,      htp.http_status_code
  into   l_out_contents_char
  ,      l_out_http_status_code
  from   HTTPDownload@DataDictionary
         ( url => 'https://kbopub.economie.fgov.be/kbopubws110000/services/wsKBOPub'
         , acceptMimeType => 'application/xml;charset=UTF-8'
         , contentType => 'application/xml;charset=UTF-8'
         , method => 'POST'
         , textPayload => l_xml
         , ignoreWebError => false
         ) htp
  ;
  dbms_output.put_line(l_out_http_status_code);
  dbms_output.put_line(l_out_contents_char);
end;

Er treedt echter een HTTP 500 statuscode op met de inhoud:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <soap:Fault>
            <faultcode xmlns:ns1="http://ws.apache.org/wss4j">ns1:SecurityError</faultcode>
            <faultstring>A security error was encountered when verifying the message</faultstring>
        </soap:Fault>
    </soap:Body>
</soap:Envelope>

Het gehele verzoek is:

<?xml version="1.0" encoding="UTF-8"?>
<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"
  xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext"
  xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
>
  <soapenv:Header>
    <wsse:Security>
      <wsu:Timestamp>
        <wsu:Created>2023-05-23T16:01:32.000Z</wsu:Created>
        <wsu:Expires>2023-05-23T16:06:32.000Z</wsu:Expires>
      </wsu:Timestamp>
      <wsse:UsernameToken>
        <wsse:Username>wsop9999</wsse:Username>
        <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">xxxxxxxxx=</wsse:Password>
        <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">xLwxqqvwAxkivJ51nYDEOQ==</wsse:Nonce>
        <wsu:Created>2023-05-23T16:01:32.000Z</wsu:Created>
      </wsse:UsernameToken>
    </wsse:Security>
    <mes:RequestContext>
      <mes:Id>91f25327-edea-45f1-a1c0-e9174cf47819</mes:Id>
      <mes:Language>nl</mes:Language>
    </mes:RequestContext>
  </soapenv:Header>
  <soapenv:Body>
    <mes:ReadEnterpriseRequest>
      <dat:EnterpriseNumber>0314595348</dat:EnterpriseNumber>
    </mes:ReadEnterpriseRequest>
  </soapenv:Body>
</soapenv:Envelope>

Dezelfde foutmelding treedt op als ik via SOAP UI probeer om het request uit te voeren gebruik makend van password digest authenticatie met timestamp met maximale levensduur van 300 seconden.

Weet iemand hoe de gegevens van deze KBO Webservice toch uit te lezen zijn?

Het algoritme lijkt het juiste password digest en nonce te generen voor de tijd en het wachtwoord. Met de volgende code is het namelijk mogelijk om een gegenereerde password digest te valideren tegen de meegeleverde nonce, timestamp en het bekende echte referentiewachtwoord:

--
-- Validate a nonce, creation timestamp and expected password
-- following wsse:Password Digest string against
-- the provided password digest.
--
-- See:
-- Web Services Security, Username Token Profile 1.1
-- 28 June 2005 
--
-- Values taken from sample:
-- http://www.herongyang.com/Web-Services/WS-Security-Validate-Password-Digest-String.html
-- 
select base64_encode
       ( hex_to_blob
         ( sha1
           ( base64_decode('0TBQcVnd9H4uGi1jGxqJWg==') /* wsse:Nonce from SOAP */
             || to_binary('2014-06-21T12:43:21.791Z') /* wsu:Created timestamp */
             || to_binary('iLoveDogs') /* Expected password */
           )
         )
       ) = 'PfZyE8nQQR2rAsODn7iVGaf8hD8=' /* wsse:Password password digest */
       authentication_success

Het result is true indien de combinatie bij elkaar hoort, en false anders. Dit algoritme is getest tegen een werkende koppeling met KBO via SoapUI.

Maar de web service is erg gevoelig voor afwijkingen. En elke soort afwijking leidt tot dezelfde foutmelding ns1:SecurityError.

De makkelijkste manier om de KBO Public Search API uit te lezen is om eerst in de SoapUI de requests werkend te krijgen, daarna de boodschappen te sniffen via de MockService, en daarna na te bouwen.

Merk op dat elk bericht dat in de MockService zichtbaar is, maximaal 1 keer opgevraagd kan worden. Een herhaling van het bericht zal ook weer leiden tot ns1:SecurityError. Met SoapUI kan iedere keer handmatig een nieuw gewenst bericht samengesteld worden.

Een werkende implementatie is:

--
-- 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;

Meer informatie is beschikbaar in:

Deze vraag is automatisch gesloten na 2 weken inactiviteit. Het laatste gegeven antwoord is gemarkeerd als oplossing.

Gelieve een nieuwe vraag te stellen via een apart topic als het probleem opnieuw optreedt. Gelieve in de nieuwe vraag een link naar dit topic op te nemen door de URL er van in de tekst te plakken.