Transport Layer Security (TLS) is a security protocol that specifies the use of digital certificates to verify identity and encrypt messages transmitted between client and server applications.

HTTPS is a combination of HTTP and TLS. The TLS protocol specifies that a client and server application must first perform a TLS 'handshake' before exchanging request or response messages. The TLS handshake is a process wherein the client and server:

  • Prove their identity to each other through public key certificates.
  • Agree upon a set of cipher suites (encryption algorithms).
  • Generate a session key that will be used to encrypt messages.

In most cases, only the server proves its identity to the client. However, an organization's security policy may require a client application to also authenticate itself. In such situations, the client application must submit a public key certificate to the server.

If the handshake is successful, the client and server begin sending each other encrypted request or response messages. If the handshake fails, no further communication can happen between the client and the server.

The ABL HTTP client library supports two-way authentication. You can use it to verify a server certificate as well as send a client certificate to the server. The ABL HTTP client library performs the steps described in the handshake process under the covers.

Server authentication

To establish a successful HTTPS connection, a server sends its public key certificate to the client. The certificate must be signed by a Certificate Authority (CA) that is trusted by the client.

An ABL HTTP client verifies the identity of a server by checking the server's root certificate against the CA certificates installed in the OpenEdge certificate store. A root certificate is the public key certificate of the CA that has signed the server's certificate.

Tip: The HTTP client does not automatically install root certificates. To retrieve the root certificate for a site, use a browser to navigate to the URL. Modern browsers indicate an TLS connection with a padlock or some similar icon. This icon is usually clickable and includes a means to inspect and export the certificates for that site. Make sure you export all of the certificates for the site and import them into the OpenEdge certificate store.

To learn more about installing root certificates, see Install trusted CA/root certificates in the Manage OpenEdge Keys and Certificates guide.

Client authentication

A server may have security policies that require a client to submit a client certificate during the TLS handshake. This process is called two-way TLS authentication or mutual TLS authentication.

If your ABL HTTP client needs to participate in mutual TLS authentication, you must, in addition to installing the server's root certificate in the OpenEdge certificate store, obtain a public/private key pair and a digital certificate containing the public key to prove the client's identity. You can do this in one of the following ways:

  • You can create a self-signed certificate, where you act as your own CA. A self-signed certificate is often used in development and testing environments.
  • Generate a public/private key pair, create a certificate signing request (CSR) containing the public key, and submit the CSR to a Certificate Authority. The CA will return a signed public key certificate for you to use. A public key certificate signed by a widely trusted CA is typically required in a production system.
Tip: You must ensure that the client-side root CA certificate is added to the server's trusted certificate store. This is especially true for self-signed certificates. If your client public key certificate is signed by a well-known CA, it is likely that the CA's root certificate is already in the server's certificate store. If not, you must work with the server administrator to ensure that the root CA certificate is included in the server's certificate store.
Note: For client authentication using proxy server, ensure that the proxy is configured between the client and the PAS for OpenEdge. Here, the proxy is used to authenticate the client certificate with the trusted CA (Certificate Authority) certificates and route the client request to the PAS for OpenEdge.
For example, to support the client authentication for PAS for OpenEdge using an Ngnix proxy, add the following to the default configuration and provide the correct certificate details to ensure the client authentication request is addressed correctly.
server {
    listen       443 ssl http2 default_server;
    listen       [::]:443 ssl http2 default_server;
    root         /usr/share/<proxy name>/html;

    ssl_certificate "<path-to-server-certificate>";
    ssl_certificate_key "<path-to-server-key>";
    ssl_client_certificate "<path-to-CA-file-for-client-certificate-in-PEM-format>";

    ssl_verify_client on;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout  10m;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
   

    location / {
         proxy_pass https:<PAS server IP address : port>/rest; 
    }

}

You can use a similar approach to configure a proxy from other vendors.

OpenEdge provides a utility called pkiutil that enables you to create certificates as well as generate CSRs.

To learn more about the pkiutil utility, see pkiutil.

To learn about creating self-signed certificates, see https://community.progress.com/articles/Knowledge/000027719.

The ABL HTTP client requires access to a PEM file that contains both the public and the private key. You can generate a PEM file using the pkiutil utility.

Use TLS connections with ABL HttpClient

Once you have a PEM file ready, perform the following steps in your ABL HTTP client code:

  1. Create a TlsClientCredentials object containing two properties:
    • A path to the PEM file. You can specify either an absolute path or a path relative to a directory listed in the PROPATH. When no path is provided, the system searches for the PEM file in the %DLC%\keys folder (Windows) or $DLC/keys directory (Linux).
    • An encoded passphrase to enable the HTTP client to access the private key in the PEM file.
    
    var EncodedString oEncSt.
    var TlsClientCredentials oCred.
    
    // Create a passphrase for the client certificate using plain-text encoding.
    oEncSt = new EncodedString().
    oEncSt:encoding = EncodingTypeEnum:nopr0.
    oEncSt:value = "password".
    
    // Create credentials which consists of certificate path and encoded passphrase.
    oCred = new TlsClientCredentials().
    
    oCred:identity = new OpenEdge.Core.String("PAS-default-cert.pem").
    
    oCred:secret = oEncSt.
  2. Pass the TlsClientCredentials object to the UsingClientCredentials() method during the construction of the HTTP request object.
    ...
    // Create credentials which consists of certificate path and encoded passphrase.
    oCred = new TlsClientCredentials().
    oCred:identity = new OpenEdge.Core.String("C:\OpenEdge\WRK\oepas1\bin\PAS-default-cert.pem").
    oCred:secret = oEncSt.
    
    // Create the request with stated credentials, calling the built-in REST ping service.
    oRequest = RequestBuilder:Get("https://httpbin.org/get")
                             :UsingClientCredentials(oCred)
                             :Request.

Here is the complete code example:


using OpenEdge.Core.EncodedString.
using OpenEdge.Core.EncodingTypeEnum.
using OpenEdge.Net.HTTP.*.
using OpenEdge.Net.ServerConnection.TlsClientCredentials.

BLOCK-LEVEL ON ERROR UNDO, THROW.

var IHttpRequest oRequest.
var IHttpResponse oResponse.
var IHttpClient oClient.
var EncodedString oEncSt.
var TlsClientCredentials oCred.

// Create a passphrase for the client certificate using plain-text encoding.
oEncSt = new EncodedString().
oEncSt:encoding = EncodingTypeEnum:nopr0.
oEncSt:value = "password".

// Create credentials which consists of certificate path and encoded passphrase.
oCred = new TlsClientCredentials().
oCred:identity = new OpenEdge.Core.String("PAS-default-cert.pem").
oCred:secret = oEncSt.

// Create the request with stated credentials, calling the built-in REST ping service.
oRequest = RequestBuilder:Get("https://httpbin.org/get")
                         :UsingClientCredentials(oCred)
                         :Request.

// Build the client using the library/options previously defined.
oClient = ClientBuilder:Build()
                       :Client.

// Execute the client request.
oResponse = oClient:Execute(oRequest).

// Display the server's response status code.
message oResponse:StatusCode.
Note: Based on the example displayed for TLS connection with ABL HttpClient, you can also establish a TLS connection using ABL Socket and other transport types like SOAP and APSV.

TLS Supported Groups

TLS supported groups are named groups used for key exchange during TLS handshake. A list of supported group is sent from the ABL HTTP client to any OpenEdge or non-OpenEdge server, arranged in order of the most preferred list to the least preferred. Each TLS vendor have their own preferred list of supported groups.

TLS supported group helps in reducing an additional cycle of TLS negotiation during the TLS handshake. If the preferred supported group list of a client does not match the preferred supported group list of the server, then the key exchange handshake may include an additional additional cycle of key exchange negotitation.

During a handshake between the client and the server, if the preferred list of the TLS vendor of the client does not match that of the TLS vendor of the server, then the handshake process involves an additional cycle of key exchange negotiations. Use TLS supported groups to reduce this additional cycle of TLS negotiation during the TLS handshake.

The client, with (TLSv1.2,TLSv1.3) as default, communicates with the server, which uses either (TLSv1.2,TLSv1.3) or (TLSv1.3). In such a scenario, the higher version (TLSv1.3) takes precedence.

Set the preferred list of supported-groups for the client and the server using the following methods:
  • Environment variables—Used by the client to set the environment variables that will match the preferred list used by the server. For more information, see Change the default protocols and ciphers for Progress OpenEdge clients.
  • Connection parameters—Used by the client to connect to the server with new parameters for the supported group. This allows the client to use the supported group set in the connection parameter to connect with the server. For more information, see CONNECT () method (Socket object).
Perform the following steps in your ABL HTTP clinet code:
  1. Configure the client with matching supported group of the server. .
    
    // Connect to Server using matching Supported Groups P-521:P-384.
    extent(tlsGroup) = 2.
    assign
        tlsGroup[1] = "P-521"    
    	tlsGroup[2] = "P-384"    
    	.
    	
    clib = ClientLibraryBuilder:Build()
                  :SslVerifyHost(false)
            :TlsSupportedGroups(tlsGroup)
    		      :Library.
    
    client = ClientBuilder:Build()
    			:UsingLibrary(clib)
    			:Client.
    			
    req = RequestBuilder:Build("get", URI:Parse(cInstance))
    			:Request.
    			
    
    
  2. Run the request.
    resp = client:Execute(req).
    
    message "Status Code:" resp:StatusCode.
    
    catch err as Progress.Lang.Error:    
    		message "Error:" err:GetMessage(1).    
    		message "Callstack:" err:CallStack.
    
    end catch.
Here is the complete code example:
block-level on error undo, throw.

using OpenEdge.Core.Assert.
using OpenEdge.Net.HTTP.IHttpClient.
using OpenEdge.Net.HTTP.IHttpClientLibrary.
using OpenEdge.Net.HTTP.IHttpRequest.
using OpenEdge.Net.HTTP.IHttpResponse.
using OpenEdge.Net.HTTP.ClientBuilder.
using OpenEdge.Net.HTTP.RequestBuilder.
using OpenEdge.Net.HTTP.Lib.ClientLibraryBuilder.
using OpenEdge.Net.URI.

var character[] tlsGroup.
var character cInstance = os-getenv("httpsURL").
var IHttpClient client.
var IHttpClientLibrary clib.
var IHttpRequest req.
var IHttpResponse resp.session:error-stack-trace = true.

// Connect to Server using matching Supported Groups P-521:P-384.
extent(tlsGroup) = 2.
assign
    tlsGroup[1] = "P-521"    
	tlsGroup[2] = "P-384"    
	.
	
clib = ClientLibraryBuilder:Build()
              :SslVerifyHost(false)
        :TlsSupportedGroups(tlsGroup)
		      :Library.

client = ClientBuilder:Build()
			:UsingLibrary(clib)
			:Client.
			
req = RequestBuilder:Build("get", URI:Parse(cInstance))
			:Request.
			

resp = client:Execute(req).

message "Status Code:" resp:StatusCode.

catch err as Progress.Lang.Error:    
		message "Error:" err:GetMessage(1).    
		message "Callstack:" err:CallStack.

end catch.

Use a callback to get client credentials

In the previous code example illustrating client-side TLS authentication, the TlsClientCredentials object was constructed inside the ABL HTTP client code. This approach works well if the same client certificate is used for every TLS connection to the server. However, if you need to use different client certificates—for example, if you have multiple users connecting to the server and each user has their own client certificate—you should:

  • Delegate the task of constructing the TlsClientCredentials object to an ABL class that implements ISocketConnectionCredentialsHandler.
  • Use a callback from within the ABL HTTP client to obtain the TlsClientCredentials object.

Here is an example of a callback class that implements ISocketConnectionCredentialsHandler:

class Callback implements ISocketConnectionCredentialsHandler: 

    method public void ClientSocket_ConnectionCredentialsHandler (input pSender as Progress.Lang.Object, input pEventArgs as CredentialsEventArgs):
        var EncodedString oEncSt.
        var TlsClientCredentials oCred.

        // Create a passphrase for the client certificate using plain-text encoding.
        oEncSt = new EncodedString().
        oEncSt:encoding = EncodingTypeEnum:nopr0.
        oEncSt:value = "password".

        // Create credentials which consists of certificate path and encoded passphrase.
        oCred = new TlsClientCredentials().
        oCred:identity = new OpenEdge.Core.String('PAS-default-cert.pem').
        oCred:secret = oEncSt.

        assign pEventArgs:Credentials = oCred.
        
    end method.

end class.

The callback class must implement the ClientSocket_ConnectionCredentialsHandler() method. This method takes two input parameters—a Progress.Lang.Object object and a CredentialsEventArgs object. Inside the method, you must construct the TlsClientCredentials object and assign it to the Credentials property of the CredentialsEventArgs object.

Then, from within the ABL HTTP client program, you create an object of the callback class and specify the callback object when building the HTTP request:


...
VAR ISocketConnectionCredentialsHandler oCallback = NEW Callback().
...
oRequest = RequestBuilder:Get("https://httpbin.org/get")
                         :UsingClientCredentials(oCallback)
                         :Request.

Use multiple callbacks to get client credentials from different sources

You can chain multiple client credential callbacks when building an HTTP request object. This is useful when credentials are stored in different locations, such as a database and a file system, and your code must search each location for a credential.

To use multiple callbacks, perform the following steps:

  1. Create a callback handler class for each source of credentials. For example, you might create a DatabaseCredentialsHandler class and a FileCredentialsHandler class to get credentials from a database and a file system respectively. The handler classes should implement ISocketConnectionCredentialsHandler.
    class DatabaseCredentialsHandler implements ISocketConnectionCredentialsHandler:
        
    ...
    ...
    end class.
    
  2. In each handler class, write the logic to obtain the credential and implement the ClientSocket_ConnectionCredentialsHandler() method, assigning the credential to the CredentialsEventArgs:Credentials property.
    Note: You can also set the CredentialsEventArgs:Cancel property to true to prevent other callbacks from executing if the credential is found in the current callback.
    class DatabaseCredentialsHandler implements ISocketConnectionCredentialsHandler:
    			...
    			method public void ClientSocket_ConnectionCredentialsHandler (input pSender as Progress.Lang.Object, input pEventArgs as CredentialsEventArgs).
    				// Get credentials from the DB
                		var TlsClientCredentials oCred. 
    					...
    				pEventArgs:Credentials = oCred.
    				pEventArgs:Cancel = yes.
    			end method.
    ...
    end class.
  3. In the ABL HTTP client program, instantiate the handler classes and chain them as shown in this example:
    var IHttpRequest oRequest.
    var DatabaseCredentialsHandler dbCallback = new DatabaseCredentialsHandler('sports2000').
    var FileCredentialsHandler fileCallback = new FileCredentialsHandler('/path/to/certs').
    
    oRequest = RequestBuilder:Get('http://localhost/')
                        :UsingClientCredentials(dbCallback)  // first check in the database
    				:UsingClientCredentials(fileCallback)// then check in the files
                        :Request.
    

Turn off host verification

If HTTPS is used as the communication protocol, the HTTP client verifies the server certificate by default. However, based on your needs, you may want to turn off this verification. To turn off host verification, create a custom ClientLibrary and set the sslVerifyHost property to NO. For example:
USING OpenEdge.Net.HTTP.IHttpClientLibrary.
USING OpenEdge.Net.HTTP.Lib.ClientLibraryBuilder.

DEFINE VARIABLE oLib AS IHttpClientLibrary NO-UNDO.

oLib = ClientLibraryBuilder:Build():sslVerifyHost(NO):Library.

oHttpClient = ClientBuilder:Build():UsingLibrary(oLib):Client.

Set TLS ciphers and protocols

An HTTP client begins communication with a server application over HTTPS by negotiating security settings. This includes the selection of a cipher suite — a set of algorithms that determine how keys are exchanged and how messages are encrypted.
If you need to specify TLS ciphers and protocols in your ABL HTTP client, create a custom ClientLibrary and use the SetSSLProtocols() and SetSSLCiphers() methods as shown in the following example.
USING OpenEdge.Net.HTTP.IHttpClient.
USING OpenEdge.Net.HTTP.IHttpClientLibrary.
USING OpenEdge.Net.HTTP.ClientBuilder.
USING OpenEdge.Net.HTTP.Lib.ClientLibraryBuilder.

/* ***************************  Main Block  *************************** */
DEFINE VARIABLE oLib          AS IHttpClientLibrary NO-UNDO.
DEFINE VARIABLE oClient       AS IHttpClient        NO-UNDO.
DEFINE VARIABLE cSSLProtocols AS CHARACTER EXTENT   NO-UNDO.
DEFINE VARIABLE cSSLCiphers   AS CHARACTER EXTENT   NO-UNDO.

// the size and values of the TLS protocols and ciphers depend on the server
EXTENT(cTLSProtocols) = 2.
EXTENT(cTLSCiphers) = 10.


ASSIGN cTLSProtocols[1] = 'TLSv1.2'
       cTLSProtocols[2] = 'TLSv1.1'
       cTLSCiphers[1]  = 'AES128-SHA256'
       cTLSCiphers[2]  = 'DHE-RSA-AES128-SHA256'
       cTLSCiphers[3]  = 'AES128-GCM-SHA256' 
       cTLSCiphers[4]  = 'DHE-RSA-AES128-GCM-SHA256'
       cTLSCiphers[5]  = 'ADH-AES128-SHA256'
       cTLSCiphers[6]  = 'ADH-AES128-GCM-SHA256'
       cTLSCiphers[7]  = 'ADH-AES256-SHA256'
       cTLSCiphers[8]  = 'AES256-SHA256' 
       cTLSCiphers[9]  = 'DHE-RSA-AES256-SHA256'
       cTLSCiphers[10] = 'AES128-SHA'
       .
oLib = ClientLibraryBuilder:Build():
                    :SetSslProtocols(cSSLProtocols)
                    :SetSslCiphers(cSSLCiphers)
                    :Library.
                    
oClient = ClientBuilder:Build()
                :UsingLibrary(oLib)
                :Client.
For a list of supported ciphers and protocols, see Supported protocols, ciphers, and certificates for Progress OpenEdge clients and servers.

API reference documentation

To view the complete list of methods that you can use in a custom Client Library, refer to the ClientLibraryBuilder documentation or see the OpenEdge.Net API Reference documentation.