Consuming WebServices in combination with oAuth2.0

Cecil

19+ years progress programming and still learning.
Working on a new project that requires consuming WebServices (WSDL, not REST). However, I need to include some extra information in the HTTP Headers for the access token. But the ABL does not expose any methods with adding extra HTTP headers.

Apart from using sockets or OPENEDGE.HTTP.NET.PL and posting the SOAP payload myself, what are the alternatives??
 

RealHeavyDude

Well-Known Member
Since the ABL does not support the SSL client certificate I use the .NET HttpWebRequest object. Obviously our client is running on Windoze.

On Solaris we use wget.
 

Cecil

19+ years progress programming and still learning.
Since the ABL does not support the SSL client certificate I use the .NET HttpWebRequest object. Obviously our client is running on Windoze.

On Solaris we use wget.


Can you explain a bit more about SSL client Certificate support issue, is that about using CA x.509 certificates??

Do you have any sample ABL code which uses NET HttpWebRequest>
 

RealHeavyDude

Well-Known Member
The ABl (at least with OE 11.6) does not support the SSL client certificate except for WebService calls. With standard HTTPS get and post requests you cannot use it. Here @ my employer we all login to our Windows 7 systems with a smartcard. During login to Windows the SSL client certificate residing on the smartcard is copied into Windows certificate store. Our single-sign-on infrastructure is based in CAs SiteMinder and each web server requests the SSL client certificate to be present for authentication.

Code:
define input authenticationUrl  as character   no-undo.  /* Authentication URL */
define input hostName           as character   no-undo.  /* Name of the host (DNS alias) */

define variable cookieContainer   as class CookieContainer    no-undo.
define variable cookieCollection  as class CookieCollection   no-undo.
define variable cookie            as class Cookie             no-undo.
define variable requestUri        as class Uri                no-undo.
define variable request           as class HttpWebRequest     no-undo.
define variable response          as class HttpWebResponse    no-undo.
define variable responseReader    as class StreamReader       no-undo.
define variable enumHelper1       as class System.Enum        no-undo.
define variable enumHelper2       as class System.Enum        no-undo.
define variable certificate        as class auth.ClientCertificate  no-undo.
define variable issoEndpoint       as class auth.ISSOEndpoint       no-undo.
define variable xmlContent         as longchar    no-undo.
define variable contentType        as character   no-undo.
define variable statusCode         as character   no-undo.
define variable statusDescription  as character   no-undo.
define variable debugFileName      as character   no-undo.
define variable cookieCount        as integer     no-undo.
define variable okay               as logical     no-undo.
/* Get the certificate */
assign certificate = auth.ClientCertificate:getInstance ( ).
assign x509Certificate = certificate:getCetificate ( ).
/* The Cookie container which will hold the C<X>SESSION cookie */
assign cookieContainer = new CookieContainer ( ).
/* Security protocols */
assign enumHelper1 = Progress.Util.EnumHelper:or ( SecurityProtocolType:Tls, SecurityProtocolType:Tls11 ).
assign enumHelper2 = Progress.Util.EnumHelper:or ( cast ( enumHelper1, SecurityProtocolType ), SecurityProtocolType:Tls12 ).
assign ServicePointManager:SecurityProtocol = cast ( enumHelper2, SecurityProtocolType ).
/* This is the request */
assign requestUri = new Uri ( issoEndPointURL )
       request    = cast ( WebRequest:Create ( requestUri ), HttpWebRequest ).
assign request:Method            = 'GET'
       request:AllowAutoRedirect = true
       request:CookieContainer   = cookieContainer.
/* Add the certificate to the request */
request:ClientCertificates:Add ( x509Certificate ).
           
/* This is the response */
assign response = cast ( request:GetResponse ( ), HttpWebResponse ).
/* Check the HTTP status code whether the reqeust succeeded */
assign statusCode = response:StatusCode:ToString ( ).
/* Fetch the response in any case */
assign responseReader = new StreamReader ( response:GetResponseStream ( ), Encoding:ASCII ).
assign xmlContent = responseReader:ReadToEnd ( ).

HTH - RealHeavyDude.
 

RealHeavyDude

Well-Known Member
Ooops, forgot

Code:
using System.*.
using System.Text.*.
using System.IO.*.
using System.Net.*.
using System.Security.Cryptography.X509Certificates.*.
using System.Security.Cryptography.*.
 

Cecil

19+ years progress programming and still learning.
:(
1516091659739.png
Is there something extra I need to add to the assemblies .xml file?

I've added Sytem,Security just to "suck it and see", but that did not work.
 

RealHeavyDude

Well-Known Member
The auth.ClientCertificate is a class which I developed to wrap the logic which fetches the SSL certificate from the Windows certificate store. I did not include it. Do you need a sample on how to get the certificate from the Windows certificate store?
 

Cecil

19+ years progress programming and still learning.
The auth.ClientCertificate is a class which I developed to wrap the logic which fetches the SSL certificate from the Windows certificate store. I did not include it. Do you need a sample on how to get the certificate from the Windows certificate store?
Quite possibly yes. The service I’m consuming has/will issue me a CA x.905 file. Not quite shure how to utilise the file yet. Does .NET need access these files from some sort of repository I.e. ‘Windows certificate store’? Or can .NET reference this file directly using a file path?
 
Last edited:

Cecil

19+ years progress programming and still learning.
Just to prove I'm not freeloading off your hard work RHD, I have actually attempted to try an write my own GetClientCertificate() method, but I'm lacking in the required skills in trying to interact with .NET and the ABL. Side Note: I find the OpenEdge Documentation quite bloated and overwhelming to understand.

Code:
using System.*.
USING System.Security.Cryptography.*.
USING System.Security.Cryptography.X509Certificates.*.

 
CLASS X509Certificate2:

  
    
    METHOD PRIVATE STATIC CHARACTER GetClientCertificate():
        DEFINE VARIABLE userCaStore AS CLASS X509Store NO-UNDO.
        DEFINE VARIABLE certificatesInStore  AS CLASS   X509Certificate2Collection  NO-UNDO.
        DEFINE VARIABLE findResult           AS CLASS   "X509Certificate2Collection[]"  NO-UNDO.
        
        userCaStore = NEW X509Store( StoreName:My, StoreLocation:CurrentUser).
        
         userCaStore:OPEN( OpenFlags:ReadOnly ).
        
         certificatesInStore = userCaStore:Certificates.
        
         DO i = 0 TO certificatesInStore:LENGTH - 1:
            findResult = CAST().
         END.
        
         findResult          = certificatesInStore:Find(X509FindType:FindBySubjectName, "localtestclientcert", true).

         IF findResult:COUNT = 1 THEN
            clientCertificate = findResult[0].   
        
        
         userCaStore:Close().
        
         RETURN clientCertificate.
    END METHOD.
    
END CLASS.
 

RealHeavyDude

Well-Known Member
Code:
routine-level on error undo, throw.
using System.Security.Cryptography.X509Certificates.*.
using System.Security.Cryptography.*.
using System.Security.Permissions.*.
class auth.ClientCertificate final inherits AoSBase:
   
    &scoped-define sub-system 'CLNT_CERT'
    &scoped-define certificate-issuer 'CN=*** your CN ***'
    /* XCN_OID_PKIX_KP_CLIENT_AUTH - 1.3.6.1.5.5.7.3.2: The certificate can be used for authenticating a client. */
    &scoped-define certificate-oid '1.3.6.1.5.5.7.3.2'
    &scoped-define http-status-code-def '{&package}/HTTP_status_codes.xml'
   
    define private variable x509Certificate  as class X509Certificate2  no-undo.
    define private static variable instance  as class auth.ClientCertificate  no-undo.
   
    define public property certificateString  as character   no-undo get. private set.
   
    method public System.Security.Cryptography.X509Certificates.X509Certificate2 getCetificate ( ):
       
        /*------------------------------------------------------------------------------
           Purpose:  Get the certificate from the certificate store
             Notes:  
        ------------------------------------------------------------------------------*/
       
        &scoped-define bail-out do: assign x509Certificate = ?. leave get-cetificate-blk. end
       
         /* ------------------------------ .NET classes ------------------------------ */
        define variable myStore               as class X509Store                      no-undo.
        define variable readOnly              as class OpenFlags                      no-undo.
        define variable openExistinOnly       as class OpenFlags                      no-undo.
        define variable certificates          as class X509Certificate2Collection     no-undo.
        define variable certificate           as class X509Certificate2               no-undo.
         define variable asymmetricAlgorythm   as class AsymmetricAlgorithm            no-undo.
        define variable extensions            as class X509ExtensionCollection        no-undo.
        define variable extension             as class X509Extension                  no-undo.
        define variable keyUsageExtension     as class X509EnhancedKeyUsageExtension  no-undo.
        define variable oids                  as class OidCollection                  no-undo.
        define variable oid                   as class Oid                            no-undo.
        /* ------------------------------ .NET classes ------------------------------ */
       
        define variable certNumber            as integer     no-undo.
        define variable certCount             as integer     no-undo.
        define variable extensionNumber       as integer     no-undo.
        define variable oidNumber             as integer     no-undo.
        define variable confirmation          as logical     no-undo.
       
        define buffer CertificateCollectionBuffer for CertificateCollection.
        get-cetificate-blk:
        do on error undo get-cetificate-blk, leave get-cetificate-blk
            on stop undo get-cetificate-blk, retry get-cetificate-blk:
               
            /* STOP Conditon trapped */
            if retry then do:
                errorMessage ( substitute ( 'Exception getting X509 certificate from DTP Smartcard [&1].', stopCondition ), {&sub-system} ) no-error.
                {&bail-out}.
            end.                
       
            /* Only necessary if the certificate is not valid ( has not been retrieved yet ) */
            if not valid-object ( x509Certificate ) then do:
       
                /* Access the certificate store */
                assign myStore = new X509Store ( StoreName:My, StoreLocation:CurrentUser ).
                myStore:Open ( cast ( Progress.Util.EnumHelper:And ( OpenFlags:ReadOnly, OpenFlags:OpenExistingOnly ), OpenFlags ) ).
       
                /* Fetch the certificates */
                assign certificates = myStore:Certificates.
   
                /* Loop through the certificates until we've got the one we need */
                do certNumber = 0 to certificates:Count - 1:
   
                    assign certificate = certificates:Item [ certNumber ].
   
                    /* Does the certificate have a private key and is it issued by one of the know issuers of
                       smartcard certificates */
                    if certificate:HasPrivateKey and certificate:Issuer begins {&certificate-issuer} then do:
                         /* This will enforce the access to the private key on the certificate and thus ask the user
                            for the PIN whenever there is a problem with the smartcard middleware ... */
                         assign asymmetricAlgorythm = certificate:PrivateKey.
   
                        /* Loop through the cetificate's extension and check if the key usae extension carries the
                           client authentication OID */
                        assign extensions = certificate:Extensions.
                        do extensionNumber = 0 to extensions:Count - 1:
   
                            assign extension = extensions:Item [ extensionNumber ].
                            assign keyUsageExtension = cast ( extension, X509EnhancedKeyUsageExtension ) no-error.
                            if valid-object ( keyUsageExtension ) then do:
   
                                assign oids = keyUsageExtension:EnhancedKeyUsages.
                                do oidNumber = 0 to oids:Count - 1:
   
                                    assign oid = oids:Item [ oidNumber ].
   
                                    /* Finally, that's the one */
                                    if oid:Value = {&certificate-oid} then do:
                                        assign x509Certificate   = certificate
                                               certificateString = certificate:ToString ( ).
                                        /* In case there is more than one certificate in the Windows store ... */
                                        do for CertificateCollectionBuffer:
                                            create CertificateCollectionBuffer.
                                            assign CertificateCollectionBuffer.certificateNumber = certNumber
                                                   CertificateCollectionBuffer.issuedToProperty  = getProperty ( certificate:Subject )
                                                   CertificateCollectionBuffer.issuedByProperty  = getProperty ( certificate:Issuer )
                                                   CertificateCollectionBuffer.serialNumber      = certificate:SerialNumber.
                                            infoMessage ( substitute ( 'X509 certificate for &1 successfuly fetched form DTP Smartcard.', CertificateCollectionBuffer.issuedToProperty ), {&sub-system} ).
                                            assign certCount = certCount + 1.
                                        end.
                                    end.
                                end.
   
                            end.  /* Loop through the cetificate's extension */
   
                        end. /* Loop through the cetificate's extension */
   
                    end. /* Certificate has a private key and is it issued by one of the know issuers of smartcard certificates */
   
                end. /* Loop through the certificates until we've got the one we need */
                /* Close the certificate store */
                myStore:Close ( ).
            end. /* Only necessary if the certificate is not valid ( has not been retrieved yet ) */
            /* Error handling */
            catch anyError as Progress.Lang.Error:
                assign x509Certificate = ?.
                errorMessage ( substitute ( 'Exception getting X509 certificate from DTP Smartcard [&1].', anyError:getMessage ( 1 ) ), {&sub-system} ) no-error.
                delete object anyError.
            end catch.
            finally:
                empty temp-table CertificateCollection.
            end finally.
       
        end. /* get-cetificate-blk */
       
        return x509Certificate.
       
        &undefine bail-out
   
    end method. /* getCetificate */
   
    method public static auth.ClientCertificate getInstance ( ):
       
        /*------------------------------------------------------------------------------
           Purpose:  Return the reference to the one and only instance
             Notes:  
        ------------------------------------------------------------------------------*/
       
        if not valid-object ( instance ) then
            assign instance = new auth.ClientCertificate ( ).
       
        return instance.
       
    end method. /* getInstance */
end class.
 

Cecil

19+ years progress programming and still learning.
Thanks for all the sample code. I not going to be able to test it until next week. Got it to compile with a few tweaks but out of curiosity, do you think you could have archived the same thing with just using a curl command?
 

Ashwani Mishra

New Member
Hi,

How can we write xml body to request? I used your first program and tried it but getting error "System.Net.ProtocolViolationException: You must write ContentLength bytes to the request stream before calling [Begin]GetResponse."

Basically stuck at how can we use .NET bytes.length in progress 4gl.

BTW: Thanks for the code example above

Code:
DEF VAR CreateRequest AS LONGCHAR NO-UNDO.
createRequest =  'XML BODY TAGS'.
   
ASSIGN
    request:Method            = 'POST'
    request:AllowAutoRedirect = TRUE
    REQUEST:ContentType       = "text/xml; charset=utf-8"
    REQUEST:Contentlength     = ???? /* tried with Encoding:UTF8:GetBytes(CreateRequest):LENGTH  but no luck */
    request:CookieContainer   = cookieContainer
    .
/* Add the certificate to the request */
request:ClientCertificates:Add ( x509Certificate ).
   
writer = NEW StreamWriter(REQUEST:GetRequestStream()).
writer:WRITE(CreateRequest, 0, Encoding:UTF8:GetBytes(CreateRequest):LENGTH ). /* This is also not working */

/* This is the response */
ASSIGN
    response = CAST ( request:GetResponse ( ), HttpWebResponse ).

Any help on this? I am not much familiar with .NET classes.
 
Last edited:

Ashwani Mishra

New Member
Hi,

I did some digging around .NET classes and finally make it work. Below is the modified source code where it fetch windows store certificate, connect to secure https wsdl, create xml, add certificate to it and call webservice.

Thanks a ton @RealHeavyDude for code you shared in 2018 :)

Code:
USING PROGRESS.Lang.*.
USING OpenEdge.Core.*.
USING OpenEdge.Net.HTTP.*.
USING OpenEdge.Net.HTTP.Lib.ClientLibraryBuilder.

USING System.*.
USING System.Text.*.
USING System.IO.*.
USING System.Net.*.

USING System.Security.Cryptography.X509Certificates.*.
USING System.Security.Cryptography.*.

DEFINE VARIABLE oRequest          AS IHttpRequest         NO-UNDO.
DEFINE VARIABLE oResponse         AS IHttpResponse        NO-UNDO.
DEFINE VARIABLE oURI              AS CLASS                Uri                                                            NO-UNDO.
DEFINE VARIABLE oRequestBody      AS OpenEdge.Core.String NO-UNDO.
DEFINE VARIABLE hXMLHandle        AS HANDLE               NO-UNDO.
DEFINE VARIABLE lcXML             AS LONGCHAR             NO-UNDO.

DEFINE VARIABLE cookieContainer   AS CLASS                CookieContainer                                                NO-UNDO.
DEFINE VARIABLE cookieCollection  AS CLASS                CookieCollection                                               NO-UNDO.
DEFINE VARIABLE cookie            AS CLASS                Cookie                                                         NO-UNDO.
DEFINE VARIABLE requestUri        AS CLASS                Uri                                                            NO-UNDO.
DEFINE VARIABLE MyHttpWebRequest  AS CLASS                HttpWebRequest                                                 NO-UNDO.

DEFINE VARIABLE response          AS CLASS                HttpWebResponse                                                NO-UNDO.
DEFINE VARIABLE responseReader    AS CLASS                StreamReader                                                   NO-UNDO.
DEFINE VARIABLE requestReader     AS CLASS                StreamReader                                                   NO-UNDO.
DEFINE VARIABLE enumHelper1       AS CLASS                System.Enum                                                    NO-UNDO.
DEFINE VARIABLE enumHelper2       AS CLASS                System.Enum                                                    NO-UNDO.
DEFINE VARIABLE NewStream         AS CLASS                System.IO.Stream                                               NO-UNDO.
DEFINE VARIABLE certificate       AS CLASS                auth.ClientCertificate                                         NO-UNDO.
DEFINE VARIABLE aByteArray        AS "System.Byte[]"      NO-UNDO.
DEFINE VARIABLE x509Certificate   AS CLASS                System.Security.Cryptography.X509Certificates.X509Certificate2 NO-UNDO.
DEFINE VARIABLE issoEndpoint      AS CLASS                auth.ISSOEndpoint                                              NO-UNDO.
DEFINE VARIABLE xmlContent        AS LONGCHAR             NO-UNDO.
DEFINE VARIABLE contentType       AS CHARACTER            NO-UNDO.
DEFINE VARIABLE statusCode        AS CHARACTER            NO-UNDO.
DEFINE VARIABLE statusDescription AS CHARACTER            NO-UNDO.
DEFINE VARIABLE debugFileName     AS CHARACTER            NO-UNDO.
DEFINE VARIABLE cookieCount       AS INTEGER              NO-UNDO.
DEFINE VARIABLE okay              AS LOGICAL              NO-UNDO.
DEF    VAR      CreateRequest     AS CHAR                 NO-UNDO.

/* Get the certificate */
ASSIGN
    certificate     = auth.ClientCertificate:getInstance ( )
    x509Certificate = certificate:getCertificate ( )
    .

/* The Cookie container which will hold the C<X>SESSION cookie */
ASSIGN
    cookieContainer = NEW CookieContainer ( ).
/* Security protocols */
ASSIGN
    enumHelper1 = Progress.Util.EnumHelper:or ( SecurityProtocolType:Tls, SecurityProtocolType:Tls11 ).
ASSIGN
    enumHelper2 = Progress.Util.EnumHelper:or ( CAST ( enumHelper1, SecurityProtocolType ), SecurityProtocolType:Tls12 ).
ASSIGN
    ServicePointManager:SecurityProtocol = CAST ( enumHelper2, SecurityProtocolType ).
  
/* This is the request */
createRequest =  /* XML ENVELOP, BODY */ .

aByteArray = Encoding:UTF8:GetBytes(INPUT createrequest). 

ASSIGN
    requestUri       = NEW Uri ( "HTTPS WSDL" )
    MyHttpWebRequest = CAST ( WebRequest:Create ( requestUri ), HttpWebRequest ).
  
ASSIGN
  
    MyHttpWebRequest:Method            = 'POST'
    MyHttpWebRequest:AllowAutoRedirect = TRUE
    MyHttpWebRequest:ContentType       = "text/xml; charset=utf-8"
    MyHttpWebRequest:Accept            = "text/xml; charset=utf-8"
    MyHttpWebRequest:CookieContainer   = cookieContainer
    MyHttpWebRequest:Contentlength     = aByteArray:LENGTH
    .
MyHttpWebRequest:Headers:add("SOAPAction","SOAPAction in wsdl"). 
/* Add the certificate to the request */
MyHttpWebRequest:ClientCertificates:Add ( x509Certificate ).  /* This is awesome, Thanks a ton to RealHeavyDude */
  
NewStream = MyHttpWebRequest:GetRequestStream().
NewStream:Write(aByteArray, 0, aByteArray:Length). 

NewStream:Close().   
/* This is the response */
ASSIGN
    response = CAST ( MyHttpWebRequest:GetResponse ( ), HttpWebResponse ).
/* Check the HTTP status code whether the reqeust succeeded */

ASSIGN
    statusCode = response:StatusCode:ToString ( ).
/* Fetch the response in any case */
ASSIGN
    responseReader = NEW StreamReader ( response:GetResponseStream ( ), Encoding:ASCII ).
ASSIGN
    xmlContent = responseReader:ReadToEnd ( ).
  
MESSAGE "******statusCode : " statusCode SKIP
    "******esponseReader : " responseReader SKIP
    "******xmlContent : " STRING(xmlContent)
    VIEW-AS ALERT-BOX.
 
Last edited:
Top