Use ABL structured error handling to catch an error that occurs during communication between the ABL client and a remote server. For example, when using HTTPS communication, the AVM may encounter error #9318, which is an internal error caused by a failure at the TLS layer (SSL). You can use the following pattern to recognize this error message:
Secure Socket Layer (SSL) failure. error code <internal_error_number>: <internal_error_message> (9318)

As part of the Client Authentication introduced in OpenEdge 12.4, a new error class is introduced to isolate and translate common internal errors thrown by this TLS layer (SSL) failure. For example, in case of error# 9318 the HTTP Client throws a Progress.Lang.Error object of type OpenEdge.Net.ServerConnection.TlsClientAuthenticationError, which is handled by either checking the TypeName of the class of the object or by explicitly catching the error by type.

An error object contains four internal messages as constructed from the original error, plus information relevant to the HTTPS request. These messages are always presented in the following order:
  1. The inner error number extracted from the SSL failure (9318) error.
  2. The hostname of the destination URI.
  3. The port number of the destination URI.
  4. The original SSL failure (9318) message with the full inner error number or message.
This structure of the error object helps the developer to easily map and translate inner errors for their usage but it is not necessarily meant for end-users. For instance, some error messages can reveal information that a malicious user can misuse. For example, OpenEdge 12.8 makes TLS v1.3 the default for HTTPS which results in several new error messages that the end-user may see more regularly. These error messages must be mapped to generic messages for security reasons. The exact reason and error numbers used for mapping these error messages to generic messages are subject to change though there are some categories which are trapped and returned in a generic manner. For example:
  • Group Authorization Failed—Returned when TLS Supported Groups are utilized but are not supported by the remote server.
  • User Authentication Failed—Returned when TLS Client Authentication is utilized but a client certificate is not found, or the passkey is incorrect.

For all other situations, you must use the inner error number and optionally the destination host or port to determine the best message and verbosity required to assist end-users.

Note: You can use the InnerError property of the object instance to access the original SysError object thrown from the #9318 SSL failure.
As of OpenEdge 12.8.4, the following modifications were adopted by the introduction of the TlsClientAuthenticationError object:
  1. The format of the fourth error message is now always a translated condition related to the inner error number from the SSL failure. In case the inner error cannot be mapped to a safe and known message, the generic Unknown TLS Error is supplied as the message.
  2. New instance properties are added to the object class to provide direct access to the internal values parsed and normally returned using the following message statements:
    1. Host—The hostname of the destination URI.
    2. Port—The port number of the destination URI.
    3. TlsErrorNum—The inner error number extracted from the SSL failure (9318) error.
    4. TlsError—The original SSL failure (9318) message with the full inner error number or message.
  3. A static property ReturnOriginalMessages is added, which forces the TlsClientAuthenticationError class to always return the original SysError messages, in order and exactly as formatted from the original error object.
    • To enable this behavior, set the TlsClientAuthenticationError:ReturnOriginalMessages static property to true.
    • To return to the new message statement behavior, return the property to its default of false.
    • You can change the property before and then after HttpClient request to force specific behavior for only that request, or set to true once at the start of an AVM session, such as by use of a sessionStartupProc in PAS for OpenEdge, to globally enforce the behavior for an application.

Example

In this example, making a request to an unknown host will result in error #9318 that is returned with an inner TLS error of 11001, which indicates the request was unable to find the destination host.

As a developer, you can trap this error number for further translation and either return it to the end-user or send the details to a log file. This allows the developer to decide when and which messages to return to an end-user as some of the information can be considered useful by a potential attacker. In such situations, it is always good to provide the least amount of information in the error messages.

In this example code, the class type of the error object is checked to know if it is of type TlsClientAuthenticationError which inherits OpenEdge.Core.System.ApplicationError and thus makes the InnerError property available. This InnerError property contains the original Progress.Lang.SysError object. The following ABL code would be most useful when the application code must support multiple OpenEdge releases such as those prior to OpenEdge 12.4, when this error object was introduced, to those releases available after the introduction of the error object.
using OpenEdge.Net.HTTP.Lib.ClientLibraryBuilder.
using OpenEdge.Net.HTTP.IHttpClientLibrary.
using OpenEdge.Net.HTTP.IHttpRequest.
using OpenEdge.Net.HTTP.IHttpResponse.
using OpenEdge.Net.HTTP.ClientBuilder.
using OpenEdge.Net.HTTP.Lib.ClientLibraryBuilder.
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.URI.

define variable oClientLib as IHttpClientLibrary no-undo.
define variable oURI       as URI                no-undo.
define variable oRequest   as IHttpRequest       no-undo.
define variable oResponse  as IHttpResponse      no-undo.

// For this example we use a domain which doesn't exist so that we can demonstrate
// mapping a specific error # during the HTTPS connection to a user-friendly message.

assign
  oURI       = URI:Parse("https://my.nonexistentdomain.com/")
  oClientLib = ClientLibraryBuilder:Build()
                                   :SslVerifyHost(false)
                                   :Library
  oRequest   = RequestBuilder:Get(oURI)
                             :AcceptJson()
                             :Request
  .

oResponse = ClientBuilder:Build()
                         :UsingLibrary(oClientLib)
                         :Client
                         :Execute(oRequest).

catch oError as Progress.Lang.Error:
    // First check if this error is of type TlsClientAuthenticationError as introduced in OpenEdge 12.4:
    if oError:GetClass():TypeName eq "OpenEdge.Net.ServerConnection.TlsClientAuthenticationError" then do:
        /**
         * This is a special error class which indicates an "SSL Failure" (error #9318) was encountered.
         * The underlying cause is represented by an inner error which is reported after the generic
         * OpenEdge error, and includes an error number as returned by the ABLSocket layer and/or TLS.
         *
         * This information is parsed for use via the following messages (4) returned by this error:
         *  1) The internal TLS error number for custom formatting
         *  2) The hostname of the intended destination
         *  3) The port # of the intended destination
         *  4) Either a pre-mapped, genericized error message, or the full OpenEdge error with SSL Failure reason
         */

        case oError:GetMessage(1):
            when "11001" then
                // Example of a custom error message as based on the internal error code returned.
                message substitute("Unable to resolve destination host '&1' (Port: &2)",
                                   oError:GetMessage(2), oError:GetMessage(3)) view-as alert-box.
            otherwise
                message cast(oError, OpenEdge.Core.System.ApplicationError):InnerError:GetMessage(1) view-as alert-box. 
        // Use the standard #9318 error message if desired, otherwise use oError:GetMessage(4) for a more generic message.
        end case.
    end.
    else do:
        // Otherwise this is a standard Progress.Lang.AppError class.
        // Will handle any other errors raised by the HttpClient request.
        if oError:NumMessages eq 2 then do:
            message "oError:GetMessage(1): " oError:GetMessage(1) view-as alert-box. // Standard #9318 error message
            message "oError:GetMessage(2): " oError:GetMessage(2) view-as alert-box. // Additional context message
        end.
        else
            message
                "oError:NumMessages: " oError:NumMessages skip
                "oError:GetMessage(" + string(oError:NumMessages) + "): " oError:GetMessage(oError:NumMessages)
                view-as alert-box. // Return the last error message encountered
    end.
end catch.
Alternatively, if you are using OpenEdge 12.4 or later, you can first attempt to catch any errors that are explicitly thrown as type OpenEdge.Net.ServerConnection.TlsClientAuthenticationError followed by a more general catch block for all other error types.
using OpenEdge.Net.HTTP.Lib.ClientLibraryBuilder.
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.URI.

define variable oClientLib as IHttpClientLibrary no-undo.
define variable oURI       as URI                no-undo.
define variable oRequest   as IHttpRequest       no-undo.
define variable oResponse  as IHttpResponse      no-undo.

// For this example we use a domain which doesn't exist so that we can demonstrate
// mapping a specific error # during the HTTPS connection to a user-friendly message.

assign
  oURI       = URI:Parse("https://my.nonexistentdomain.com/")
  oClientLib = ClientLibraryBuilder:Build()
                                   :SslVerifyHost(false)
                                   :Library
  oRequest   = RequestBuilder:Get(oURI)
                             :AcceptJson()
                             :Request
  .

oResponse = ClientBuilder:Build()
                         :UsingLibrary(oClientLib)
                         :Client
                         :Execute(oRequest).

// Catch errors explicitly of type TlsClientAuthenticationError as introduced in OpenEdge 12.4:
catch oTlsError as OpenEdge.Net.ServerConnection.TlsClientAuthenticationError:
    /**
     * This is a special error class which indicates an "SSL Failure" (error #9318) was encountered.
     * The underlying cause is represented by an inner error which is reported after the generic
     * OpenEdge error, and includes an error number as returned by the ABLSocket layer and/or TLS.
     *
     * This information is parsed for use via the following messages (4) returned by this error:
     *  1) The internal TLS error number for custom formatting
     *  2) The hostname of the intended destination
     *  3) The port # of the intended destination
     *  4) Either a pre-mapped, genericized error message, or the full OpenEdge error with SSL Failure reason
     */

    case oTlsError:GetMessage(1):
        when "11001" then
            // Example of a custom error message as based on the internal error code returned.
            message substitute("Unable to resolve destination host '&1' (Port: &2)",
                               oTlsError:GetMessage(2), oTlsError:GetMessage(3)) view-as alert-box.
        otherwise
            message oTlsError:InnerError:GetMessage(1) view-as alert-box. 
            // Use the standard #9318 error message if desired, otherwise use oTlsError:GetMessage(4) for a more generic message.
    end case.
end catch.

// Catch all other errors raised by the HttpClient library.
catch oError as Progress.Lang.Error:
    // Otherwise this is a standard Progress.Lang.AppError class.
    // Will handle any other errors raised by the HttpClient request.
    if oError:NumMessages eq 2 then do:
        message "oError:GetMessage(1): " oError:GetMessage(1) view-as alert-box. // Standard #9318 error message
        message "oError:GetMessage(2): " oError:GetMessage(2) view-as alert-box. // Additional context message
    end.
    else    
        message
            "oError:NumMessages: " oError:NumMessages skip
            "oError:GetMessage(" + string(oError:NumMessages) + "): " oError:GetMessage(oError:NumMessages)
            view-as alert-box. // Return the last error message encountered
end catch.