Invalid SNI – What is it and how to fix it

Everyone was happy. The web application uptime and response rates have been well within the Service Level Agreements for months. Uptake of the published REST Services was on the rise. Both internally and externally, more and more systems were consuming the services and the three ORDS instances behind an NGINX load balancer took it all in their stride. No long running queries or inefficient PL/SQL code locking DB resources. Everything humming along like a finely tuned engine. Then it came time to upgrade…

The ORDS product team have been operating to the usual cadence of one major release every 3 months. Although not ancient, our system is a few releases behind and not only are there interesting new features that are worth exploring but there’s always the security fixes to recent critical 3rd party Common Vulnerabilities and Exposures ( CVE ) that justifies being at the most recent stable release.

Upgrade time comes around so the test environment is setup to run through the ORDS upgrade process before applying the same to production. Then this happens for all the requests: HTTP ERROR 400 Invalid SNI

But this was working before I upgraded !

What is my SNI and how did it get invalid?

The same web server could be hosting multiple sites. In the case of ORDS, the same ORDS instance could have multiple domain name entries that route to it. Server Name Indication ( SNI ) is an extension to the Transport Layer Security (TLS) networking protocol for a client ( such as your browser ) to indicate to the server ( ORDS running standalone mode for example ) which site it wishes to talk to. To put it simply, SNI is used at the start of the secure connection handshake between client and server. The client sends a Host header in the request to indicate what site it is requesting and the server goes looking for its certificate for that server name. See https://datatracker.ietf.org/doc/html/rfc4366#section-3.1 for more details on the role of Server Name Indication in keeping communication secure.

You’ll notice from the stack trace and the error page that the Invalid SNI response does not look like a standard ORDS error page. It is in fact generated by the Eclipse Jetty embedded in ORDS when running in standalone mode.

org.eclipse.jetty.http.BadMessageException: 400: Invalid SNI
A typical ORDS generated error page. It looks a lot different from the Invalid SNI error page!

It is a feature, not a bug – Jetty 10

When running ORDS in standalone mode an embedded Eclipse Jetty server is used. Before ORDS 22.3.0 that was Jetty 9 but since that release ORDS has been using Jetty 10 and for very good reason: more secure. As mentioned in the second paragraph at the start of this article there a fixes and optimisation that are worth making the upgrade for. Jetty 10 addressed some issues in the TLS handshake. Not least Better handling for wrong SNI #5379. Beforehand it didn’t really matter what the client sent in the Host header, Jetty would return any certificate it had.

What is in that certificate anyway?

The certificate, self-signed or otherwise, holds some important information about the server and is essential in establishing trust. To see the contents of the certificate, use the keytool utility in your Java distribution.

keytool -printcert -file self-signed.pem 
Owner: CN=localhost
Issuer: CN=localhost
Serial number: 5e90f747912dd350
Valid from: Thu Feb 08 17:55:19 GMT 2024 until: Fri Feb 07 17:55:19 GMT 2025
Certificate fingerprints:
	 SHA1: FB:F2:E7:30:B5:3F:D1:8B:AC:D0:8E:C3:49:15:3B:B2:75:F1:6E:AD
	 SHA256: 54:B1:4E:6E:92:DC:7F:88:E8:66:6B:69:91:C9:E1:01:CB:69:97:4A:B7:24:BA:F9:A0:52:AC:F3:C3:15:AB:39
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 3072-bit RSA key
Version: 3

The above output shows that the Owner of this certificate has a Common Name ( CN ) value localhost which is the default standalone.https.host configuration setting value.

What to do about it

Now that you know the root cause how do you go about resolving it? It is as simple as ensuring the Common Name ( CN ) or Subject Alternative Name ( SAN ) field in the certificate matches what the client is sending the in Host header of the request. For more information on having more than one hostname in a single certificate see a previous article: Beyond the Common Name: Secure multiple domains with a single self-signed certificate

ORDS will generate a self-signed certificate if necessary when it is started in standalone mode and configured for HTTPS. In other words, the standalone.https.port is specified in the global configuration settings or --secure option is used in the ords serve command. If no certificate exists at the configured standalone.https.cert location, ORDS will generate the self-signed certificate and key file.

The Common Name used for the self-signed certificate is taken from the standalone.https.host configuration setting. If not set, the value is localhost. Traditional certificate practices often rely solely on the Common Name (CN) field. That’s the approach taken by ORDS by default when generating the self-signed certificate.

This can sometimes catch people out when they start up ORDS in secure standalone mode the first time. The self-signed certificate is generated but they may not have specified the standalone.https.host configuration setting and when they do later, they still get HTTP 400 Invalid SNI responses. That’s because the self-signed certificate is already generated so no matter what the standalone.https.host configuration setting says, the certificate will not be generated again.

Simple steps

The simplest course of action is to rename the self-signed certificate and key files and restart ORDS. It will not find the files and therefore will attempt to generate new self-signed certificates.

In summary, make sure the standalone.https.host configuration setting matches the Host header value that clients will send for requests being routed to your ORDS standalone server.

Putting the squeeze on: Compressing ORDS response size with GZIP

In the previous article, “Optimise Java settings with Application Process Monitoring“, we discussed how to get an insight on memory usage, CPU load, and response times. In this article, we will build on this knowledge by exploring the why, how and when on reducing response size by compressing ORDS responses with GZIP. This can be an effective way to reduce response size and ultimately improve response times as experienced by the end users. That can be especially important in systems where network latency to clients is a challenge. We will look at how to configure GZIP, and explore the trade-offs associated with response size versus CPU load.

GZIP Compression

Http clients, such as a web browser, indicate that they can accept a compressed response by listing the encoding algorithms they understand in the Accepts-Encoding header of the request. GZIP compression is a widely used compression algorithm for unix-based systems that has been around for more than two decades. It allows for reduced bandwidth between a web server and web client, resulting in faster page loading times.

Configuring a web server to compress content responses prior to transmission is beneficial, but it should not be done indiscriminately as it does consume CPU resources. It should be noted that compression can apply to the request received, as well as the response returned but in this article the focus is on making that response size smaller.

How much smaller? On average, one could be looking at response sizes being approximately 15% of the uncompressed file size.

# Uncompressed response size
curl http://localhost:8080/ords/hr/employees/ \
     --silent --write-out "%{size_download}\n" \
     --output /dev/null                         

8317

# Compressed response size
curl http://localhost:8080/ords/hr/employees/ \
     -H "Accept-Encoding: gzip" \
     --silent --write-out "%{size_download}\n" \
     --output /dev/null                         
1401

Compression is not a silver bullet. There are file types, such as most image files and PDF documents, that are already compressed so attempting to compress them again is a waste of CPU cycles. Similarly, small files may not justify the computational cost for a relatively insignificant gain. In fact, if you’re compressing files that are smaller than the Maximum Transmission Unit (MTU) size of a TCP packet (1500 bytes), you are wasting CPU cycles. To ensure that the compression is effective, you should limit it to files with a size greater than 1.4KB (1400 bytes).

Let Jetty Handle Compression

When ORDS is running in standalone mode it is running a specially configured instance of Eclipse Jetty as the web server for receiving HTTP(S) requests. GZIP is so widely used that Eclipse Jetty has a dedicated GZipHandler for requests and responses. In this article we will extend the ORDS standalone jetty server, using a jetty XML configuration file, to apply the handler to responses. When using Eclipse Jetty, you can configure your server to use compression for all responses, or only for responses that meet certain criteria. This flexibility allows you to tailor your server response compression settings to fit your specific needs. In this case, compression will be applied for certain mime-types where compression could help, and when the response size is greater than 128 bytes. That’s quite a low maximum size and there may not be any performance improvement gained but it does allow the demonstration of compression being applied to almost every response.

Note that in this example compression will be applied only for responses to GET requests. This is the default behaviour. However, you can use the GZipHandler documentation to guide you on configuring for more complicated scenarios.

When ORDS is running in a standalone mode, the Eclipse Jetty Home is ${configuration.directory}/global/standalone/. The Jetty XML syntax can be used to configure the Jetty Server for additional functionality by placing configuration XML files in the Jetty Home etc directory. The capability to do this is provided through the Eclipse Jetty server product.

ORDS 22.4 Installation and Configuration Guide

Here’s my ${configuration.directory}/global/standalone/etc/jetty-compression.xml configuration file that inserts the GzipHandler to the ORDS standalone jetty server instance.

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
<Configure id="Server" class="org.eclipse.jetty.server.Server">
      <Call name="insertHandler">
        <Arg>
          <New id="GZipHanlder" class="org.eclipse.jetty.server.handler.gzip.GzipHandler">
            <Set name="IncludedMimeTypes">
              <Array type="java.lang.String">
                <Item>text/html</Item>
                <Item>text/xml</Item>
                <Item>text/css</Item>
                <Item>text/javascript</Item>
                <Item>application/javascript</Item>
                <Item>application/json</Item>
              </Array>
            </Set>
            <Set name="minGzipSize">128</Set>
          </New>
        </Arg>
      </Call>
</Configure>

When ORDS is started in standalone mode with the above file in the configuration directory Eclipse Jetty Home etc folder, all responses will pass through the GzipHandler instance.

> ords --config /path/to/config serve

ORDS: Release 22.4 Production on Fri Feb 03 22:15:56 2023
...
Mapped local pools from /path/to/config/databases:
  /ords/                              => default                        => VALID     


2023-02-03T22:16:12.660Z INFO        Oracle REST Data Services initialized
Oracle REST Data Services version : 22.4.3.r0331239
Oracle REST Data Services server info: jetty/10.0.12
Oracle REST Data Services java info: Java HotSpot(TM) 64-Bit Server VM 11.0.13+10-LTS-370

As shown earlier, that applies to application/json responses such as for ORDS REST Services. It also applies to text/html responses for ORDS PL/SQL Gateway and even static content such as the APEX images.

Browser showing compressed content returned for static content such as style sheets.

Conclusion

This article has focused on ORDS in standalone mode and configuring the embedded Eclipse Jetty server instance. Similar compression options can be configured when ORDS is deployed on Apache Tomcat or Oracle WebLogic Server but the settings will be specific to those containers. Check their product documentation for information on how to configure that. The above Eclipse Jetty extension example is only applicable to ORDS standalone mode.

To reiterate, choosing the right compression configuration is important. It will take time, as well as monitoring of the additional resources involved. There may be specific paths where compression is too costly or the GZipHandler interferes with the successful processing of the response.

However, when using compression one should see benefits that include faster page loading times, improved user experience, and reduced bandwidth usage, all of which can help to improve overall performance of your ORDS based web application. Now that you know how, go make efficient use of your bandwidth!