Advanced KaiOS Development - CORS, TCP Sockets, and Let's Encrypt SSL Certificates

Posted by Tom Barrasso on

Learn more about disabling CORS, Navigator.mozTCPSocket, and issues with Let’s Encrypt certificates on KaiOS

XMLHttpRequest without CORS

There are several ways that dynamic web apps communicate with a back-end server. XMLHttpRequest (XHR) is the original method, used heavily in AJAX programming. XHR allows web apps to make a variety of HTTP requests like GET, PUT, POST, and DELETE, both synchronously (now deprecated) and asynchronously.

KaiOS fully supports XMLHttpRequest, including a few non-standard flags that can be provided to it’s constructor.

1let xhr = new XMLHttpRequest({
2    mozSystem: true,
3    mozAnon: true,
4    mozBackground: true,
5})

The first, and perhaps most useful flag is mozSystem. Privileged apps with the systemXHR permission are allowed to set this flag. Setting it to true disables the same origin policy for the request, allowing requests without the usual Content Security Policy (CSP) errors.

Content Security Policy: The page’s settings blocked the loading of a resource at https://podlp.com/ (“connect-src”).

The second flag, mozAnon, works in tandem with the first. In fact, this flag is enabled whenever the mozSystem flag is used (see nsXMLHttpRequest.h). Setting mozAnon ensures the request is sent anonymously, without cookies or authentication headers. It will also throw an error if you attempt to enable withCredentials.

The final flag, mozBackground, is for background service requests. This causes the request to fail in cases where a security dialog (such as authentication or a bad certificate notification) would be shown.

Fetch

In addition to XHR, KaiOS also supports the Fetch API. Similar to XHR, apps holding the systemXHR permission are granted special treatment for requests made with the mode no-cors. For instance, the header list is not constrained and a sucessful request will resolve to a basic response instead of an opaque response.

1fetch('https://google.com', {
2    method: 'GET',
3    mode: 'no-cors',
4})

Note: this behavior is only available on KaiOS 3.0 devices. For better compatability, developers will continue to prefer XMLHttpRequest with the mozSystem flag which works on both KaiOS 2.5 and 3.0.

TCPSocket API

KaiOS includes the non-standard navigator.mozTCPSocket API for establishing lower-level TCP socket connections. This API is exposed to Privileged apps with the tcp-socket permission and allows developers to implement protocols on top of TCP like IMAP, IRC, POP, or HTTP.

Apps can both open and listen for socket connections, sending and receiving data either in String or ArrayBuffer format. This allows apps to act as both clients and servers, exposing connections over localhost within and between apps (although multi-tasking on KaiOS is quite limited). Uses cases include:

  • Sending and retrieving emails over POP or IMAP
  • Chatting via an IRC client
  • Transferring files over FTP
  • Issuing direct DNS queries, instead of DNS over HTTPS (DoH)

Creating a local HTTP server also allows applications to serve up content requested by image, audio, and video elements, similar to a ServiceWorker but without limitations. Of course this comes with the added complexity of writing your own HTTP server, and for multimedia requests also requires handling Range requests.

Note: There is also a sibling UDPSocket API for UDP socket connections, accessible via the udp-socket permission.

Let’s Encrypt SSL Certificate Issue

Let’s Encrypt (LE) is an open certificate authority (CA) that issues free SSL certificates, allowing websites to serve content over HTTPS. LE is so popular that it has issued more than one billion certificates. SSL certificates are important for many reasons, and in 2023 they are effectively required to avoid visitors seeing scary warning messages stating, “This Connection is Untrusted.”

In September 2021, the IdenTrust DST Root CA X3 root certificate used by Let’s Encrypt expired. For most internet users, this was a non-event because modern web-enabled devices include new root certificates with regular updates. However, most budget mobile phones including KaiOS devices do not receive regular over-the-air (OTA) updates. That means that as of the time of writing, most phones running KaiOS 2.5 will flag Let’s Encrypt SSL certificates as insecure.

Untrusted Connection Warning on KaiOS
KaiOS screenshot of anchor.fm with expired LE root certificate

As a developer, this means that XMLHttpRequest and Fetch requests to hosts using LE certificates will silently fail with no catchable error. Fortunately, while XHR does not expose the necessary error messages, it is possible to detect invalid SSL Certificates on KaiOS using mozTCPSocket. The function below returns a Promise that resolves to true when the SecurityCertificate is thrown.

 1// @returns [Promise<Boolean|null>] True if the host has an invalid SSL certificate
 2function isInvalidCertificate(host, timeout = 1000) {
 3  // API not available, either not KaiOS or missing permission
 4  if (!navigator.mozTCPSocket) {
 5    return Promise.resolve(null);
 6  }
 7
 8  return new Promise((resolve) => {
 9    let timeoutId;
10    let isResolved = false;
11    const socket = navigator.mozTCPSocket
12      .open(host, 443, { useSecureTransport: true });
13
14    // Closes socket, if open, and resolves Promise
15    const closeAndResolve = (event) => {
16      if (socket.readyState !== 'closed') {
17        try {
18          socket.close();
19        } catch (e) {
20          console.warn(e);
21        }
22      }
23
24      // Handle SSL Certificate Scenario
25      if (event.type === 'error') {
26        if (event.name === 'SecurityError' && event.message === 'SecurityCertificate') {
27          isResolved = true;
28          clearTimeout(timeoutId);
29          return resolve(true); // True = Invalid
30        }
31      }
32
33      isResolved = true;
34      clearTimeout(timeoutId);
35      return resolve(false); // False = Valid
36    };
37
38    // Set socket timeout
39    timeoutId = setTimeout(() => {
40      if (!isResolved) {
41        closeAndResolve(false);
42      }
43    }, timeout);
44
45    socket.addEventListener('data', closeAndResolve);
46    socket.addEventListener('error', closeAndResolve);
47  });
48}

As expected, isInvalidCertificate resolves true for anchor.fm which uses a Let’s Encrypt SSL Certificate, and resolves false for google.com which uses a certificate issued by Google Trust Services.

Tip: in Firefox if you click the lock icon then the arrow under “Connection secure,” you can see SSL certificate details.

Connection details for Google.com
Screenshot of connection details for Google.com

Conclusion

KaiOS offers a variety of APIs to make external requests like a native app, including XHR and Fetch without CORS, as well as TCP and UDP Socket connections. All required permissions are only available to packaged Privileged applications, but enable your application to use third-party APIs, services, and protocols without running into Content Security Policy issues.

If you find these nuances daunting and need support developing tightly-integrated experiences on KaiOS, contact the author from the About page.