Introduction

The ISARA Catalyst TLS library lets you establish TLS connections that will resist attacks by quantum computers.

For more information about ISARA and our quantum-safe solutions, please visit www.isara.com.

The Developer’s Guide covers the following topics:

  • Getting Started — Foundational information for using Catalyst TLS.

  • Cipher Suites — Information on the cipher suites provided by Catalyst TLS.

  • TLS: Importing Objects — Information on importing key, certificate and CRL objects for use in establishing TLS connections.

  • TLS: Configuration — Information on setting common configuration values used when establishing TLS connections.

  • TLS: Networking — Information on establishing underlying network transport connections needed by TLS connections.

  • TLS: Connections — Information on establishing secure connections using TLS.

  • Technical Info — Detailed information about the compiler options we’ve used, how to maximize code stripping when using the static libraries, and other details that might be helpful.

  • Building On Windows — How to use Catalyst TLS on the Windows platform.

TLS

Catalyst TLS provides an implementation of TLS 1.2, described in RFC 5246. To ensure the greatest level of security, it provides a minimal set of cipher suites, specifically the NSA Suite B (RFC 6460) cipher suites as well as the strongest Suite B cipher suite hybridized with several quantum-safe key agreement algorithms. The quantum-safe algorithms are Diffie-Hellman-like versions of selected submissions to the NIST Post-Quantum Standardization evaluation process.

X.509 certificate-based authentication is supported for server authentication and optionally for client authentication.

The following TLS extensions are also supported:

Classical and Quantum Security

Some classes of algorithm, such as key agreement schemes based on the discrete log problem, are thought to be easily breakable by adversaries equipped with quantum computers.

Some algorithms, such as cryptographic hashes and symmetric encryption schemes, are not known or believed to be efficiently breakable by a quantum adversary. Their quantum security strength is generally considered to be half of their classical strength. For example, SHA2-256 provides 256 bit classical security, and 128 bit quantum security.

A TLS connection uses a combination of different algorithms to provide security. The TLS standards specify symmetric algorithms with parameters large enough to withstand attacks from a quantum adversary. However, the key agreement and digital signature algorithms specified in TLS are vulnerable to quantum attacks. These algorithms can be combined with quantum-safe algorithms to provide key agreement or digital signature functionality that is secure against a quantum adversary. Catalyst TLS provides TLS 1.2 cipher suites which combine classical and quantum-safe key agreement algorithms. In a future version Catalyst TLS will also support quantum-safe digital signature algorithms.

Packaging

The Catalyst TLS archive contains the following files and directories:

  • README.html — Information about the Catalyst TLS package.

  • SECURITY.html — Information about reporting security issues, and ISARA’s PGP public key.

  • doc — API documentation and this Developer’s Guide document.

  • include — Catalyst TLS headers.

  • lib_<cpu> — Catalyst TLS static and shared libraries tuned for various CPU architectures.

  • samples — Sample programs demonstrating how to use Catalyst TLS.

ISARA Catalyst TLS ships with two libraries in the lib_<cpu> directory, libisara_tls containing the TLS implementation and associated functionality, and libiqr_toolkit containing ISARA’s quantum-safe algorithm implementations. Both libraries are needed to use Catalyst TLS. For more information about the toolkit, see ISARA Radiate™ Crypto Suite Documentation.

Cryptographic signatures for the installation archives are distributed with the archives. If you don’t have access to the signatures, please contact support@isara.com.

Getting Help

The latest version of the Catalyst TLS documentation is available on the ISARA website. You can also request support via email.

Reporting Security Issues

The ISARA team takes security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.

To report a security issue, email security@isara.com and include the word "SECURITY" in the subject line.

The ISARA team will send a response indicating the next steps in handling your report. After the initial reply to your report, the team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.

ISARA’s PGP Key

If you need to validate signatures on files coming from ISARA, or you want to encrypt a file you’re sending to us, you can grab our PGP public key from https://www.isara.com/public_key.asc.

It has the following digests:

Table 1. ISARA PGP Key Digests
Algorithm Digest

MD5

9bd6531fee9e6be983a22f0da3415029

SHA1

0fac805775c29bb23a769e4c28e36c1c01ec9325

SHA2-256

10edbd26d3e470f48ca24e404fa78f45f494ecf492fc6f76ea77a5eb433328d0

SHA2-512

726b71a09803801135b02ea4bb87d8bbef9bd9c0f0926ebab6aa7cd2cdaa1375 140f94d572c32ce1b16a10b771e444397882cb7c425e96cf95e22498e7c30161

Samples

The samples directory has a number of sub-directories, each with a self-contained program inside demonstrating how to use Catalyst TLS for a specific purpose:

  • ciphersuites — A simple program which prints cipher suite information for your Catalyst TLS libraries.

  • client — A TLS client which will connect to a TLS server, perform a quantum-safe TLS handshake and securely exchange data.

  • server — A TLS server which will listen for incoming connections, perform a quantum-safe TLS handshake on accepted connections and securely exchange data.

  • version — A simple program which prints version information for your Catalyst TLS libraries.

To compile the samples, you will need:

  • C99 compliant compiler; recent versions of clang or gcc are preferred

  • cmake 3.7 or newer

  • GNU make 3.8 or newer (or some other build tool supported by your version of cmake)

Note
Don’t build the samples on macOS using gcc 8, they will crash before main() due to a problem with -fstack-protector-all. Use clang to produce Mac binaries.

Set your ISARA_TLS_ROOT environment variable to the directory where you unpacked the installation archive. The build will expect to find include and lib inside the ISARA_TLS_ROOT directory.

Compile all of the samples with:

  1. cd to the samples directory.

  2. mkdir build

  3. cd to the build directory.

  4. cmake ..

  5. make

The sample build artifacts will be found in sample-specific subdirectories under build and should be run from the subdirectory in order to pick up the default input artifacts, such as certificates or private keys.

Getting Started

This section gives you an overview of things you’ll need to know to effectively use Catalyst TLS.

Objects follow a standard cycle:

  1. Create object.

  2. Do things with the object.

  3. Destroy the object.

Some objects have dependencies on other objects. For example, a config object may contain references to certificate objects, or a TLS context object will contain a reference to a config object. It is up to you to ensure that an object is not destroyed while another object may still be using it.

Standardized Return Values

All of the APIs return an itls_retval value, as defined in the itls_retval.h header.

On success, functions return ITLS_OK; if an error occurs, the return value will tell you why the error happened. Some functions, in particular I/O ones, will use other return values that don’t necessarily indicate an error. For example, ITLS_EREADAGAIN and ITLS_EWRITEAGAIN are returned when a non-blocking socket is used but data can’t be read or written at the moment. The operation should be retried again when the socket is ready for the operation. See the API documentation for information on specific return values which may returned by certain functions.

Use the itls_StrError() function (also in itls_retval.h) to convert an itls_retval value into an English string. Be sure to write your own return value messages if you need to support localization.

Note
Depending on your compiler flags, you must check the return values for Catalyst TLS functions.

Thread Safety

Objects in Catalyst TLS are intended to be used by a single thread. The exception to this is TLS config objects, X.509 certificates and CRLs. These may be created and populated on a single thread and then read by multiple TLS context objects on different threads. Shared internal data which is updated in the normal usage of TLS contexts, such as PRNG and entropy state, is controlled using platform-specific mutexes. Catalyst TLS APIs which may change state on objects should not need to be called on the same object from multiple threads. If for some reason this is needed, you must perform your own synchronization for those objects.

Data Hygiene

All objects in Catalyst TLS have their buffers wiped with 0x00 bytes prior to being deallocated.

Buffers (seed data, hash inputs, etc.) passed to Catalyst TLS functions are never modified unless explicitly stated in API documentation.

Version Information

The itls_version.h header gives you access to the library’s version numbers, plus some identifying information about the build.

  • ITLS_VERSION_MAJOR and ITLS_VERSION_MINOR provide the major and minor versions for the library. They can be combined as major.minor to produce a version string.

  • ITLS_VERSION_STRING is a verbose version string for the library.

The itls_VersionGetBuildTarget() function returns a string representation of the target OS, target CPU architecture, and compiler used to build Catalyst TLS, separated by / characters if you need to parse it into separate fields. Give this information to ISARA’s support team if you discover something that looks like a bug.

Note
The version sample is an easy way to get this information for ISARA’s support team.

The itls_VersionGetBuildHash() function returns a string representation of the library’s build hash and build time stamp, separated by a / character if you need to parse it into separate fields. Give this information to ISARA’s support team if you discover something that looks like a bug.

There’s also an itls_VersionCheck() function that you can use to ensure that your headers match the version of the library you’re using:

#include "itls_version.h"
...
itls_retval result = itls_VersionCheck(ITLS_VERSION_MAJOR, ITLS_VERSION_MINOR);
if (result == ITLS_OK) {
    // Your headers and library are the same version.
    ...
} else {
    // Your headers and library come from different versions. Your code is
    // not likely to compile and/or link properly.
    ...
}

What Next?

These sections can be read in any order, depending on what you need to do:

  • For information about the cipher suites provided by Catalyst TLS, see Cipher Suites.

  • For information on importing key, certificate and CRL objects used when establishing TLS connections, see TLS: Importing Objects.

  • For information on setting common configuration values shared by TLS connections, see TLS: Configuration.

  • For information on setting up the underlying transport connections needed by TLS connections, see TLS: Networking.

  • For information on establishing TLS connections, see TLS: Connections.

  • For detailed technical information about how the library was compiled, see Technical Info.

  • For information about building Catalyst TLS projects with Visual Studio, see Building on Windows.

Cipher Suites

Catalyst TLS provides a number of cipher suites, some of which are quantum-safe and some of which are not.

The following cipher suites are standard Suite B cipher suites and are not secure against a quantum-enabled attacker:

  • ITLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 is the Suite B cipher suite providing a 128-bit minimum level of classical security.

  • ITLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 is the Suite B cipher suite providing a 192-bit minimum level of classical security.

The following cipher suites are custom quantum-safe ones assigned from the IANA private range and are only compatible with other ISARA TLS implementations. The quantum-safe algorithms are Diffie-Hellman-like variants of selected submissions to the NIST Post-Quantum Standardization evaluation process.

  • ITLS_TLS_ECDHE_NHDH_ECDSA_WITH_AES_256_GCM_SHA384 is the strongest Suite B cipher suite, augmented to combine the classical ECDHE key agreement algorithm with the NewHope lattice-based key agreement algorithm which is believed to be quantum-safe.

  • ITLS_TLS_ECDHE_SIDH_ECDSA_WITH_AES_256_GCM_SHA384 is the strongest Suite B cipher suite, augmented to combine the classical ECDHE key agreement algorithm with the SIDH isogeny-based key agreement algorithm which is believed to be quantum-safe.

  • ITLS_TLS_ECDHE_NHDH_SIDH_ECDSA_WITH_AES_256_GCM_SHA384 is the strongest Suite B cipher suite, augmented to combine the classical ECDHE key agreement algorithm with both the NewHope lattice-based and the SIDH isogeny-based key agreement algorithms which are both believed to be quantum-safe. Combining two quantum-safe algorithms gives the same amount protection as the most secure one. Combing two additional algorithms may be useful in case one is discovered to be insecure, or you just want the extra assurance of algorithms based on two different types of mathematical constructions.

This is the default order of cipher suites in Catalyst TLS. Cipher suites earlier in the list will be preferred over ones later in the list. This list can be overridden using the itls_TLSConfigSetCiphersuites() function in itls_config.h. The list as cipher suite IDs that are defined in the itls_Ciphersuite enum in itls_ciphersuite.h is:

  • ITLS_TLS_ECDHE_NHDH_SIDH_ECDSA_WITH_AES_256_GCM_SHA384

  • ITLS_TLS_ECDHE_SIDH_ECDSA_WITH_AES_256_GCM_SHA384

  • ITLS_TLS_ECDHE_NHDH_ECDSA_WITH_AES_256_GCM_SHA384

  • ITLS_TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384

  • ITLS_TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256

The same list as strings that can be used as input to itls_CiphersuiteGetId() or are received as output from itls_CiphersuiteGetName() is:

  • TLS-ECDHE-NHDH-SIDH-ECDSA-WITH-AES-256-GCM-SHA384

  • TLS-ECDHE-SIDH-ECDSA-WITH-AES-256-GCM-SHA384

  • TLS-ECDHE-NHDH-ECDSA-WITH-AES-256-GCM-SHA384

  • TLS-ECDHE-ECDSA-WITH-AES-256-GCM-SHA384

  • TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256

Cipher Suite List

Use the itls_CiphersuiteListCreate() function to create a list of all cipher suites supported by Catalyst TLS. The cipher suite IDs returned in this list and elsewhere in the API can be translated to more informative strings using itls_CiphersuiteGetName().

#include "itls_ciphersuite.h"
...
itls_Ciphersuite *list = NULL;
size_t list_size = 0;
itls_retval result = itls_CiphersuiteListCreate(&list, &list_size);
if (result != ITLS_OK) {
    // Handle error.
}

printf("%zu cipher suites are supported:\n", list_size);
for (size_t i = 0; i < list_size; i++) {
    const char *name = NULL;
    result = itls_CiphersuiteGetName(list[i], &name);
    if (result != ITLS_OK) {
        // Handle error.
    }
    printf("  %s\n", name);
}

itls_CiphersuiteListDestroy(&list);

From String to ID

Use itls_CiphersuiteGetId() to translate from cipher suite names to IDs. This may be useful to translate from human-readable string, e.g. in config files, to cipher suite IDs to pass to other Catalyst TLS APIs, such as itls_TLSConfigSetCiphersuites().

#include "itls_ciphersuite.h"
...
const char *ciphersuite_names[] = {
    "TLS-ECDHE-NHDH-SIDH-ECDSA-WITH-AES-256-GCM-SHA384",
    "TLS-ECDHE-NHDH-ECDSA-WITH-AES-256-GCM-SHA384",
    NULL,
};

for (uint32_t i = 0; ciphersuite_names[i] != NULL; i++) {
    itls_Ciphersuite id;
    itls_retval result = itls_CiphersuiteGetId(ciphersuite_names[i], &id);;
    if (result != ITLS_OK) {
        // Handle error.
    }

    // You will likely use the IDs to build a list to pass to
    // itls_TLSConfigSetCiphersuites().
    printf("cipher suite %s has ID 0x%04X\n", ciphersuite_names[i], id);
}

TLS: Importing Objects

A TLS Configuration object requires several other objects so that a TLS Context can authenticate itself and its peer. These objects are all imported from a buffer, and it is your responsibility to load the buffer from your persistent storage.

Importing Private Keys

itls_publickey.h defines the itls_PrivateKey object and functions that operate on it. The itls_PrivateKeyImport() function creates an itls_PrivateKey object from a buffer containing a PEM or DER encoding of the private key. A private key is sensitive information, so you should securely wipe the buffer after importing the key. itls_PrivateKeyDestroy() destroys the itls_PrivateKey object and securely wipes the internal buffers containing the private key.

Encrypted private keys are currently not supported, but a future version of Catalyst TLS will add support for PKCS#5 and PKCS#8 encrypted private keys. itls_PrivateKeyImport() already has password and password length parameters so that the API won’t have to change when those features are added. For now the password parameter must be NULL and the password size parameter must be 0.

#include "itls_publickey.h"
...
size_t buffer_size = 0;
uint8_t *buffer = load_my_buffer("my_key.pem", &buffer_size);

itls_PrivateKey *private_key = NULL;
itls_retval result = itls_PrivateKeyImport(buffer, buffer_size,
    NULL, 0, &private_key);
if (result != ITLS_OK) {
    // Handle error.
}

// Securely wipe the buffer. You may need to substitute your  platform's
// version of memset_s() (a secure memset() that won't be optimized away
// by the compiler).
memset_s(buffer, 0, buffer_size);
free(buffer);

// Do stuff with the private key object, like adding it to a
// TLS configuration, creating a TLS context and establishing
// a TLS connection.

// Only destroy the key after all objects that refer to it
// (e.g. a TLS configuration object) have been destroyed.
result = itls_PrivateKeyDestroy(&private_key);
if (result != ITLS_OK) {
    // Handle error.
}

Importing Certificates

The itls_X509CertChain object can contain multiple X.509 certificates. itls_x509certificate.h defines functions that operate on an itls_X509CertChain. The itls_X509CertChainCreate() function creates an empty certificate chain, itls_X509CertChainImportCertificates() imports one or more certificates into the chain and itls_X509CertChainDestroy() destroys the chain and all certificates in it.

itls_X509CertChainImportCertificates() can import a single DER-encoded certificate at a time, or one or more PEM-encoded certificates all contained in your buffer. This function parses PEM-encoded certificates permissively. If a certificate can’t be loaded (e.g. the encoding is incorrect, it uses an unsupported algorithm, etc.) then it is skipped and the next certificate in the buffer is loaded. The itls_X509CertChainGetCertificateCount() function tells you how many certificates were successfully loaded and how many failed.

#include "itls_x509certificate.h"
...
itls_X509CertChain *certificates = NULL;
itls_retval result = itls_X509CertChainCreate(&certificates);
if (result != ITLS_OK) {
    // Handle error.
}

size_t buffer_size = 0;
uint8_t *buffer = load_my_buffer("my_certificates.pem", &buffer_size);
if (buffer == NULL) {
    // Handle error.
}

result = itls_X509CertChainImportCertificates(certificates,
    buffer, buffer_size);
if (result != ITLS_OK) {
    // Handle error.
}

free(buffer);

uint32_t num_loaded = 0;
uint32_t num_failures = 0;
result = itls_X509CertChainGetCertificateCount(certificates,
    &num_loaded, &num_failures);
if (result != ITLS_OK) {
    // Handle error.
}

if (num_failures != 0) {
    // Maybe you were expecting all certificates to load?
    // If so, handle error.
}

buffer = load_my_buffer("my_other_certificate.der", &buffer_size);
if (buffer == NULL) {
    // Handle error.
}

result = itls_X509CertChainImportCertificates(certificates,
    buffer, buffer_size);
if (result != ITLS_OK) {
    // Handle error.
}

free(buffer);

// Do stuff with the certificate chain, like adding it to a
// TLS configuration, creating a TLS context and establishing
// a TLS connection.

// Only destroy the chain after all objects that refer to it
// (e.g. a TLS configuration object) have been destroyed.
result = itls_X509CertChainDestroy(&certificates);
if (result != ITLS_OK) {
    // Handle error.
}

Importing Certificate Revocation Lists

The itls_X509CRLChain object can contain multiple X.509 certificate revocation lists. itls_x509crl.h defines functions that operate on an itls_X509CRLChain. The itls_X509CRLChainCreate() function creates an empty CRL, itls_X509CRLChainImportCRLs() imports one or more CRLs into the chain and itls_X509CRLChainDestroy() destroys the chain and all CRLs in it.

itls_X509CRLChainImportCRLs() can import a single DER-encoded CRL at a time, or one or more PEM-encoded CRLs all contained in your buffer. This function parses PEM-encoded CRLs permissively. If a CRL can’t be loaded (e.g. the encoding is incorrect, it uses an unsupported algorithm, etc.) then it is skipped and the next CRL in the buffer is loaded. The itls_X509CRLChainGetCRLCount() function tells you how many CRLs were successfully loaded and how many failed.

#include "itls_x509crl.h"
...
itls_X509CRLChain *crls = NULL;
itls_retval result = itls_X509CRLChainCreate(&crls);
if (result != ITLS_OK) {
    // Handle error.
}

size_t buffer_size = 0;
uint8_t *buffer = load_my_buffer("my_crls.pem", &buffer_size);
if (buffer == NULL) {
    // Handle error.
}

result = itls_X509CRLChainImportCRLs(crls,
    buffer, buffer_size);
if (result != ITLS_OK) {
    // Handle error.
}

free(buffer);

uint32_t num_loaded = 0;
uint32_t num_failures = 0;
result = itls_X509CRLChainGetCRLCount(crls,
    &num_loaded, &num_failures);
if (result != ITLS_OK) {
    // Handle error.
}

if (num_failures != 0) {
    // Maybe you were expecting all crls to load?
    // If so, handle error.
}

buffer = load_my_buffer("my_other_crl.der", &buffer_size);
if (buffer == NULL) {
    // Handle error.
}

result = itls_X509CRLChainImportCRLs(crls,
    buffer, buffer_size);
if (result != ITLS_OK) {
    // Handle error.
}

free(buffer);

// Do stuff with the CRL chain, like adding it to a  TLS
// configuration, creating a TLS context and establishing
// a TLS connection.

// Only destroy the chain after all objects that refer to it
// (e.g. a TLS configuration object) have been destroyed.
result = itls_X509CRLChainDestroy(&crls);
if (result != ITLS_OK) {
    // Handle error.
}

TLS: Configuration

Use the itls_TLSConfig object and associated functions in itls_config.h to create a TLS configuration. Then use the TLS configuration to create one or more TLS contexts. A TLS configuration has the following configurable properties:

  • Whether the configuration represents a client or server.

  • The TLS transport type to use (currently only TLS is supported).

  • A profile specifying a predefined set of cipher suites, hash algorithms, and elliptic curves to use.

  • Whether peer authentication is required.

  • A list of cipher suites you want to use instead of the cipher suites set in the profile.

  • A set of certificates that you trust.

  • A set of certificate revocation lists.

  • Your own certificate chain.

  • Your own private key for the end entity certificate.

  • A read timeout for TLS contexts using a blocking socket.

Use itls_TLSConfigCreate() to create a new TLS configuration object with three key properties set: the endpoint type (client/server), the TLS transport type (only TLS is available for now), and the profile. The object is populated with default values for all other properties, you can change them using functions in itls_config.h.

The profile you pass to itls_TLSConfigCreate() determines which cipher suites, hash algorithms, and elliptic curve parameters are used by TLS contexts created from the configuration. Currently, only the ITLS_PRESET_PROFILE_HYBRID_QS_SUITE_B profile is supported. This profile enables the NSA Suite B cipher suites and the ISARA hybridized Suite B cipher suites. See the Cipher Suites section for more information on these cipher suites. This profile also restricts the hash algorithms to SHA256 and SHA512, and the elliptic curves to secp256r1 and secp384r1 per the Suite B specification.

itls_TLSConfigSetVerificationMode() controls how the peer’s certificate is verified. In a client configuration, the verification mode is ITLS_VERIFY_REQUIRED and can’t be changed - the client must verify the server’s certificate. In a server configuration the verification mode defaults to ITLS_VERIFY_NONE. If it is changed to ITLS_VERIFY_REQUIRED the client is required to send its certificate during the handshake and the server will verify it.

Use itls_TLSConfigSetCiphersuites() to override the cipher suites set by the profile when the configuration object was created. See the Cipher Suites section for information on the available cipher suites.

When you configure an endpoint that requires its peer to authenticate itself, you must use itls_TLSConfigSetAuthorityChains() to set your trusted certificates and CRLs. Your trusted certificates are used as roots of trust when verifying the peer certificate. The peer certificate must follow a chain of trust that ends at one of these trusted certificates, otherwise it can’t be verified. The CRLs are used to determine if any certificates in the peer’s certificate chain have been revoked. A revoked certificate will cause the certificate chain verification to fail. Providing a set of CRLs is not required, but it is recommended that all trusted CA certificates have an associated CRL.

If your endpoint needs to use a certificate to authenticate itself with its peer (i.e. it’s a server or it’s a client and the peer server requires client authentication), you must use itls_TLSConfigSetAuthenticationInfo() to set your authentication information. Your authentication information consists of a certificate chain and a private key for the end entity certificate. The certificate chain is your end entity certificate followed by a number of intermediate CA certificates, each of which verifies the previous certificate in the chain. The certificate chain may optionally end with the root CA certificate, though it may be excluded to reduce the amount of data sent. The TLS context will send the certificate chain to your peer and will use the private key to sign the TLS handshake. A server may call itls_TLSConfigSetAuthenticationInfo() multiple times to set multiple certificate chains and private keys. The TLS context will select the appropriate certificate chain and private key to use during the TLS handshake.

Use itls_TLSConfigSetReadTimeout() to set the read timeout used by TLS contexts created from this configuration. The timeout is only used if the TLS context has a blocking I/O socket registered with it. The default timeout is 0 seconds. A timeout of 0 seconds means that a read operation will block indefinitely if no data is received.

An itls_TLSConfig object keeps a reference to the private key, certificates and CRLs set on it. Thus, the TLS configuration object must be destroyed before those other objects are destroyed. A TLS configuration object is used to create a TLS context object and the TLS context keeps a reference to the TLS configuration. Thus, the TLS context object must be destroyed before the TLS configuration object.

#include "itls_config.h"
#include "itls_publickey.h"
#include "itls_x509certificate.h"
#include "itls_x509crl.h"
...
itls_TLSConfig *config = NULL;

// This config is for clients using TLS and the hybrid QS Suite B
// profile.
itls_retval result = itls_TLSConfigCreate(ITLS_ENDPOINT_TYPE_CLIENT,
    ITLS_TRANSPORT_TYPE_TLS, ITLS_PRESET_PROFILE_HYBRID_QS_SUITE_B,
    &config);
if (result != ITLS_OK) {
    // Handle error.
}

// Since we're a client this isn't strictly necessary because
// clients must always verify the server.
result = itls_TLSConfigSetVerificationMode(config,
    ITLS_VERIFY_REQUIRED);
if (result != ITLS_OK) {
    // Handle error.
}

// Restrict the cipher suites to only the quantum-safe ones, with
// the ECDHE-NHDH-SIDH ciphersuite preferred first.
const itls_Ciphersuite ciphersuites[3] = {
    ITLS_TLS_ECDHE_NHDH_SIDH_ECDSA_WITH_AES_256_GCM_SHA384,
    ITLS_TLS_ECDHE_NHDH_ECDSA_WITH_AES_256_GCM_SHA384,
    ITLS_TLS_ECDHE_SIDH_ECDSA_WITH_AES_256_GCM_SHA384,
};
result = itls_TLSConfigSetCiphersuites(config, ciphersuites,
    sizeof(ciphersuites) / ciphersuites[0]);
if (result != ITLS_OK) {
    // Handle error.
}

itls_X509CertChain *ca_certificates = NULL;
itls_X509CRLChain *crls = NULL;

// You must load the certificates and CRLs before calling
// itls_TLSConfigSetAuthorityChains(). See the "TLS: Importing
// Objects" section of this document.

result = itls_TLSConfigSetAuthorityChains(config, ca_certificates, crls);
if (result != ITLS_OK) {
    // Handle error.
}

itls_X509CertChain *my_certificates = NULL;
itls_PrivateKey *my_private_key = NULL;

// You must load your certificate chain and private key before
// calling itls_TLSConfigSetAuthenticationInfo(). See the
// "TLS: Importing Objects" section of this document. This
// assumes that the server requires the client to authenticate
// itself. If client authentication isn't necessary then you
// can skip this step.

result = itls_TLSConfigSetAuthenticationInfo(config, my_certificates,
    my_private_key, NULL);
if (result != ITLS_OK) {
    // Handle error.
}

// 10 second read timeout, this depends on your application
// design. If you don't set the timeout (or set it to 0), reading
// will wait indefinitely. This timeout is only used if you
// register a blocking socket with the TLS context created from
// this configuration.
const uint32_t timeout = 10000;
result = itls_TLSConfigSetReadTimeout(config, timeout);
if (result != ITLS_OK) {
    // Handle error.
}

// Now create a TLS context and an I/O socket connected to the
// server, and associate the two. Then use the TLS context to
// perform a TLS handshake and exchange application data securely.
// Before destroying your TLS configuration you must destroy all
// TLS contexts created from it. You must destroy the private key,
// certificates and CRLs that were set on this configuration after
// the configuration is destroyed.

// Destroy TLS context.
// Destroy IO socket.

// Now you can destroy the config.
result = itls_TLSConfigDestroy(&config);
if (result != ITLS_OK) {
    // Handle error.
}

// Destroy my_private_key.
// Destroy my_certificates.
// Destroy crls.
// Destroy ca_certificates.

TLS: Networking

Catalyst TLS provides a basic cross-platform API in itls_net.h. With it you can establish connections with and perform I/O operations on TCP sockets. These sockets can be registered with TLS context objects that use them to perform the TLS handshake and securely exchange application data. A future version of Catalyst TLS will let you register your own I/O callbacks with TLS context objects so that you can have custom control of the transport used by TLS.

The itls_NetListenSocket and itls_NetIOSocket objects are abstractions of platform sockets. itls_NetListenSocket objects are used by TLS servers to listen for and accept incoming TCP connections. The accepted incoming connections are created as itls_NetIOSocket objects. itls_NetIOSocket objects are also created when a client is connecting to a server. The itls_NetIOSocket does the actual socket I/O such as reading and writing of data. When registered with a TLS context, the itls_NetIOSocket objects transmit and receive the TLS handshake and the secured application data.

Listening Sockets

The itls_NetListenSocketBindCreate() function is used to create a listening socket bound to a particular address and port. Currently it can only listen for incoming connections using the TCP protocol. The maximum length of the pending connections backlog is configurable; a large server servicing many incoming connections will likely want a large backlog, while an IOT device listening for peer connections may prefer a small backlog to reduce resource usage.

#include "itls_net.h"

#define LISTEN_BACKLOG 128
...
itls_NetListenSocket *listen_socket = NULL;
itls_retval result = itls_NetListenSocketBindCreate("myhost.example.com", "443",
    ITLS_NET_PROTOCOL_TCP, LISTEN_BACKLOG, &listen_socket);
if (result != ITLS_OK) {
    // Handle error.
}

An itls_NetListenSocket object is blocking by default, this can be changed using the itls_NetListenSocketSetBlocking() function. The blocking property of a listening socket affects whether the itls_NetIOSocketAcceptCreate() function blocks when accepting incoming connections.

#include "itls_net.h"
...
// Create an itls_NetListenSocket object.
...
itls_retval result = itls_NetListenSocketSetBlocking(listen_socket, false);
if (result != ITLS_OK) {
    // Handle error.
}

To destroy the listening socket:

#include "itls_net.h"
...
// Create an itls_NetListenSocket object.
// Accept incoming connections with it.
...
itls_retval result = itls_NetListenSocketDestroy(&listen_socket);
if (result != ITLS_OK) {
    // Handle error.
}

I/O Sockets

You can use the itls_NetIOSocketConnectCreate() function to connect to a remote server. A new itls_NetIOSocket object is returned and can be used to communicate with the server.

#include "itls_net.h"
...
itls_NetIOSocket *io_socket = NULL;
itls_retval result = itls_NetIOSocketConnectCreate("myhost.example.com", "443",
    ITLS_NET_PROTOCOL_TCP, &io_socket);
if (result != ITLS_OK) {
    // Handle error.
}

A server uses itls_NetIOSocketAcceptCreate() to accept a new connection on a listening socket, returning a new itls_NetIOSocket object to communicate with the client.

#include "itls_net.h"
...
// Create an itls_NetListenSocket object.
...
itls_NetIOSocket *io_socket = NULL;
itls_retval result = itls_NetIOSocketAcceptCreate(listen_socket, &io_socket);
if (result != ITLS_OK) {
    // Handle error.
}

An itls_NetIOSocket object is blocking by default, this can be changed using the itls_NetIOSocketSetBlocking() function. Whether or not an I/O socket is blocking affects the following functions:

  • itls_NetIOSocketReceive()

  • itls_NetIOSocketReceiveWithTimeout()

  • itls_NetIOSocketSend()

  • itls_TLSContextPerformHandshake()

  • itls_TLSContextRead()

  • itls_TLSContextWrite()

  • itls_TLSContextCloseNotify()

#include "itls_net.h"
...
// Create an itls_NetIOSocket object.
...
itls_retval result = itls_NetIOSocketSetBlocking(io_socket, false);
if (result != ITLS_OK) {
    // Handle error.
}

You can call itls_NetIOSocketShutdown() to initiate a shutdown sequence on the underlying socket, terminating the TLS connection. The object still needs to be closed and destroyed using itls_NetIOSocketDestroy().

#include "itls_net.h"
...
// Create an itls_NetIOSocket object.
// Register it with a TLS context and exchange data with it.
...
itls_retval result = itls_NetIOSocketShutdown(io_socket);
if (result != ITLS_OK) {
    // Handle error.
}

To close and destroy an I/O socket:

#include "itls_net.h"
...
// Create an itls_NetIOSocket object.
// Register it with a TLS context and exchange data with it.
...
itls_retval result = itls_NetIOSocketDestroy(&io_socket);
if (result != ITLS_OK) {
    // Handle error.
}

In a production environment, don’t read or write on I/O sockets directly. Instead register the I/O socket with a TLS context object using itls_TLSContextSetIOSocket() and use the TLS context functions to perform a TLS handshake and exchange data securely. See TLS: Connections for more information on using a TLS context.

The following functions are provided as a convenience if you want to send and receive data on your I/O socket for testing purposes during development. They may be useful in a future version of Catalyst TLS where you can register your own I/O callbacks. Then you can choose to continue using this networking API and register these callbacks in wrappers that perform analytics or some other custom function as the data is sent and received.

  • itls_NetIOSocketReceive()

  • itls_NetIOSocketReceiveWithTimeout()

  • itls_NetIOSocketSend()

Polling

itls_net.h provides several APIs to poll on individual sockets or combinations of sockets. Whether a socket is marked as blocking or non-blocking has no effect on whether a socket can be polled or what the result of the polling will be.

You may poll on a single itls_NetListenSocket using itls_NetListenSocketPoll():

#include "itls_net.h"
...
// Create an itls_NetListenSocket object.
...
// 10 second poll timeout, this will depend on your application design.
int32_t timeout = 10000;
itls_retval result = itls_NetListenSocketPoll(listen_socket, timeout);
if (result == ITLS_ETIMEOUT) {
    // Perhaps you want special timeout handling code.
} else if (result != ITLS_OK) {
    // Handle error.
}

Similarly, you may poll on a single itls_NetIOSocket object, and indicate whether you are polling for the ability to read or write (or both).

You should poll for read if a previous API call returned ITLS_EREADAGAIN, indicating that the underlying transport didn’t have data for you to read at the time. Polling for read will block until the socket has data available to read, or the timeout occurs.

You should poll for write if a previous API call returned ITLS_EWRITEAGAIN, indicating that the underlying transport was unable to send your data. Polling for write will block until the socket is able to send data again, or the timeout occurs.

You will probably most often only be polling for read since the conditions where writing is unavailable are rare.

#include "itls_net.h"
...
// Create an itls_NetIOSocket object.
...
// 10 second poll timeout, this will depend on your application design.
int32_t timeout = 10000;
bool read = false;
itls_NetIOSocket *io_socket = NULL;
// Only polling for read, not write.
itls_retval result = itls_NetIOSocketPoll(io_socket, timeout, &read, NULL);
if (result == ITLS_ETIMEOUT) {
    // Perhaps you want special timeout handling code.
} else if (result != ITLS_OK) {
    // Handle error.
}

if (read == true) {
    // Socket has data available to read, you probably want to read it.
}

Polling on a single listening socket or I/O socket is often insufficient, especially if you are writing a server that is expected to handle multiple TLS connections simultaneously. You can use itls_NetSocketPoll() to poll on multiple listening and I/O sockets at the same time. This flexibility does make it more complicated than the single-socket poll functions, so you must take care to understand the function’s semantics fully.

itls_NetSocketPoll() takes 3 arrays to poll on:

  • Listening sockets to poll for incoming connections.

  • I/O sockets to poll for incoming data available to read.

  • I/O sockets to poll for the ability to write. Unless you have filled the underlying send buffer of the socket, an I/O socket will almost always be ready to write. Therefore, you shouldn’t poll on a socket to write unless you actually have data available to write, otherwise your event loop will continually spin from itls_NetSocketPoll() returning immediately.

itls_NetSocketPoll() will fill 3 arrays with the sockets from the polling arrays:

  • Listening sockets that have an incoming connection ready to accept.

  • I/O sockets with data available to read.

  • I/O sockets that can be written to.

The available arrays are the same length as their corresponding poll arrays. The sockets will be placed into the same index of the available array as they were found in the corresponding poll array. This lets you easily map the ready sockets back to the polling sockets. If you use an array of TLS contexts with indices corresponding to the TLS context’s I/O socket in the poll arrays, you can easily map the ready sockets back to the TLS contexts.

Polling will block until any of the sockets is ready, or the timeout occurs.

#include "itls_net.h"

#define LISTEN_ARRAY_SIZE   1
#define IO_ARRAY_SIZE       3
...
// Create itls_NetIOSocket and itls_NetIOSocket objects.
...
// 10 second poll timeout, this will depend on your application design.
int32_t timeout = 10000;

// This server has one socket listening for incoming connections, always check it.
itls_NetListenSocket *listen_sockets[LISTEN_ARRAY_SIZE] = {
    listen_socket
};

// io_socket1 and io_socket3 saw ITLS_EREADAGAIN on the last attempt to read.
itls_NetIOSocket *io_read_sockets[IO_ARRAY_SIZE] = {
    io_socket1,
    NULL,
    io_socket3
};

// io_socket2 and io_socket3 saw ITLS_EWRITEAGAIN on the last attempt to write.
itls_NetIOSocket *io_write_sockets[IO_ARRAY_SIZE] = {
    NULL,
    io_socket2,
    io_socket3
};

// Arrays to receive ready sockets.
itls_NetListenSocket *listen_available[LISTEN_ARRAY_SIZE] = { NULL };
itls_NetIOSocket *io_read_available[IO_ARRAY_SIZE] = { NULL, NULL, NULL };
itls_NetIOSocket *io_write_available[IO_ARRAY_SIZE] = { NULL, NULL, NULL };

itls_retval result = itls_NetSocketPoll(listen_sockets, LISTEN_ARRAY_SIZE,
    io_read_sockets, IO_ARRAY_SIZE, io_write_sockets, IO_ARRAY_SIZE, timeout,
    listen_available, io_read_available, io_write_available);
if (result == ITLS_ETIMEOUT) {
    // Perhaps you want special timeout handling code.
} else if (result != ITLS_OK) {
    // Handle error.
}

for (uint32_t i = 0; i < LISTEN_ARRAY_SIZE; i++) {
    if (listen_available[i] != NULL) {
        // There's an incoming connection ready to accept, handle it.
    }
}

for (uint32_t i = 0; i < IO_ARRAY_SIZE; i++) {
    if (io_read_available[i] != NULL) {
        // The socket has data available to read, handle it.
        // With an array of TLS contexts at the same index as their corresponding
        // I/O sockets you can use the index of available I/O contexts to perform
        // a read on the TLS context.
    }
}

for (uint32_t i = 0; i < IO_ARRAY_SIZE; i++) {
    if (io_write_available[i] != NULL) {
        // The socket is ready to write again, handle it.
        // With an array of TLS contexts at the same index as their corresponding
        // I/O sockets you can use the index of available I/O contexts to perform
        // a write on the TLS context.
    }
}

TLS: Connections

The itls_tls.h header defines objects and functions that are used to perform a TLS handshake with a remote peer and exchange secure application data.

The itls_TLSContext object represents a single TLS context containing all the state required for you to establish a TLS connection with a remote peer and securely exchange data with them. Some of the state is contained in a shared TLS configuration object while other properties are set on individual TLS contexts. Once the TLS context is configured, you can use it to perform a TLS handshake and send and receive application data. You can also query properties of the TLS connection from the context.

TLS Context Object Management

An itls_TLSContext object is created based on a itls_TLSConfig object. The TLS config contains shared properties, such as certificates, CRLs and private keys. The itls_TLSContext object retains a reference to the itls_TLSConfig object (and all other objects contained in the config). Thus, you must not destroy the itls_TLSConfig object, or the objects used by the config, for the lifetime of the itls_TLSContext object. You can only destroy the TLS config used to create TLS contexts after all the TLS contexts created from it have been destroyed.

#include "itls_config.h"
#include "itls_tls.h"
...
// Create and populate an itls_TLSConfig object.
...
itls_TLSContext *context = NULL;
itls_retval result = itls_TLSContextCreate(config, &context);
if (result != ITLS_OK) {
    // Handle error.
}
...
// Use the itls_TLSContext object to establish a connection and exchange data.
...
result = itls_TLSContextDestroy(&context);
if (result != ITLS_OK) {
    // Handle error.
}
...
// Now you may destroy the itls_TLSConfig object.

TLS Context Attributes

Some attributes of a TLS context are set before the TLS connection is established, others are set as a result of a TLS handshake succeeding or failing. You can query these properties after the TLS handshake is complete.

Use itls_TLSContextSetPeerName() to set the name of the remote peer that you are connecting to. Setting the name of the remote peer on a client context will cause the client to send the server_name extension to the server. If the server handles this extension and is hosting multiple servers at the same IP address, it will direct the TLS handshake to the appropriate server.

If you set the peer name on a client or server context, then when the TLS context performs the TLS handshake it will verify that the Subject Name or Subject Alternative Name properties of the peer’s certificate (if any) match the provided name. If there is no match, the certificate verification will fail and the TLS connection will not be established.

#include "itls_tls.h"
...
// Create an itls_TLSContext object.
...
itls_retval result = itls_TLSContextSetPeerName(context, "myhost.example.com");
if (result != ITLS_OK) {
    // Handle error.
}

Once a TLS handshake is complete (or it failed), you can use itls_TLSContextGetX509VerifyResult() to query the result of the certificate verification. If the certificate verification succeeded, ITLS_OK is returned. If the certificate verification failed, ITLS_EX509CERTVERIFYFAILED is returned and the verify_bits parameter is set to a bitwise combination of the verification errors that occurred. The bitmasks for the possible verification errors are found in itls_x509.h.

#include "itls_tls.h"
...
// Create an itls_TLSContext object.
// Attempt to perform a TLS handshake.
...
uint32_t verify_bits = 0;
itls_retval result = itls_TLSContextGetX509VerifyResult(context, &verify_bits);
if (result == ITLS_OK) {
    // Certificate verification failed.
} else if (result == ITLS_EX509CERTVERIFYFAILED) {
    // Log a verification failure message based on the values in verify_bits.
} else {
    // Handle error.
}

Once a TLS handshake is complete and the TLS connection is established you can query the context to learn cipher suite and TLS version was negotiated (currently only TLS 1.2 is supported).

#include "itls_ciphersuite.h"
#include "itls_tls.h"
...
// Create an itls_TLSContext object.
// Perform a TLS handshake.
...
const char *version = NULL;
itls_retval result = itls_TLSContextGetVersionString(context, &version);
if (result != ITLS_OK) {
    // Handle error.
}
printf("Connection established with TLS version: %s\n", version);

itls_Ciphersuite ciphersuite_id;
result = itls_TLSContextGetCiphersuite(context, &ciphersuite_id);
if (result != ITLS_OK) {
    // Handle error.
}
const char *ciphersuite_name = NULL;
result = itls_CiphersuiteGetName(ciphersuite_id, &ciphersuite_name);
if (result != ITLS_OK) {
    // Handle error.
}
printf("Connection established with cipher suite: %s\n", ciphersuite_name);

TLS I/O

Before a TLS I/O operation such as performing a handshake and exchanging application data can occur, you must associate an I/O socket with the TLS context. The I/O socket does the actual data transmission, while the TLS context provides the TLS handshake data or secured application data to send and receive.

The TLS context uses itls_NetIOSocketSend(), itls_NetIOSocketReceive() and itls_NetIOSocketReceiveWithTimeout() internally to send and receive data. If the I/O socket is marked as non-blocking when you set it here, itls_NetIOSocketReceive() is used, otherwise itls_NetIOSocketReceiveWithTimeout() is used. If you want different behaviour, you may change the blocking status of the socket after associating it with the TLS context. This will not change the receive functions used internally.

The itls_TLSContext object retains a reference to the itls_NetIOSocket object. Thus, you must not destroy the itls_NetIOSocket object for the lifetime of the itls_TLSContext object. You can only destroy the I/O socket object associated with the TLS context after the TLS context is destroyed.

#include "itls_net.h"
#include "itls_tls.h"
...
// Connect to the remote peer and get the resulting itls_NetIOSocket object.
// Create an itls_TLSContext object.
...
itls_retval result = itls_TLSContextSetIOSocket(context, io_socket);
if (result != ITLS_OK) {
    // Handle error.
}

Handshake

After an I/O socket connection is established and associated with a TLS context, the next step is to perform the TLS handshake. itls_TLSContextPerformHandshake() handles all the steps of the handshake, you just need to continue calling itls_TLSContextPerformHandshake() until ITLS_OK or an error is returned. See the TLS I/O Return Codes section for values returned by this function that may need special handling.

#include "itls_tls.h"
...
// Connect to the remote peer and get the resulting itls_NetIOSocket object.
// Create an itls_TLSContext object and associate the I/O socket with it.
...
itls_retval result = ITLS_OK;
while (true) {
    bool do_read_poll = false;
    bool do_write_poll = false;

    result = itls_TLSContextPerformHandshake(context);
    if (result == ITLS_EREADAGAIN) {
        do_read_poll = true;
    } else if (result == ITLS_EWRITEAGAIN) {
        do_write_poll = true;
    } else {
        break;
    }

    if (do_read_poll || do_write_poll) {
        // Poll for read or write on the socket, or mark this socket as needing a
        // read or write poll the next time you're ready to poll multiple sockets.
    }
}

if (result == ITLS_ETIMEOUT) {
    // Perhaps you want to do something special, or perhaps you want to treat
    // this as an error.
} else if (result != ITLS_OK) {
    // Handle error.
}

Reading

After the TLS handshake is complete, you will want to send and receive application data on your TLS connection. you will perform a read or a write first depending on the semantics of the application-layer protocol and whether you are acting as a client or server.

Use itls_TLSContextRead() to read TLS application data from your TLS context. The function indicates how much data was copied into your buffer. See the TLS I/O Return Codes section for values returned by this function that may need special handling. Note that even though this is a read function, it can return ITLS_EWRITEAGAIN. This is because this function may process TLS handshake messages in the course of attempting to read application data.

TLS application data is received in records, and data is read up to record boundaries. A TLS record may be up to 16KB, including header and padding. If you attempt a read of fewer bytes than are in the record, the entire record is read into the TLS context and buffered there before returning you any bytes. This also happens if you attempt to read zero bytes - the entire record is read and buffered. Because application data is buffered in the TLS context you must read all buffered data from the context before polling on the underlying socket, otherwise your poll may block even though there is buffered data available. If itls_TLSContextRead() returns ITLS_EREADAGAIN or ITLS_ETIMEOUT then there is no buffered data and it is safe to poll the socket. You may also use the itls_TLSContextGetBufferedByteCount() function to check how much buffered data is remaining in the context.

Read a full record at a time:

#include "itls_net.h"
#include "itls_tls.h"
...
// Connect to the remote peer and get the resulting itls_NetIOSocket object.
// Create an itls_TLSContext object and perform the TLS handshake.
...
uint8_t buffer[16 * 1024] = { 0 };
size_t bytes_read = 0;
itls_retval result = ITLS_OK;

while (true) {
    result = itls_TLSContextRead(context, buffer, sizeof(buffer), &bytes_read);
    if (result != ITLS_EREADAGAIN && result != ITLS_EWRITEAGAIN) {
        break;
    }

    bool read = false;
    bool write = false;
    result = itls_NetIOSocketPoll(io_socket, 10000,
        (result == ITLS_EREADAGAIN) ? &read : NULL,
        (result == ITLS_EWRITEAGAIN) ? &write : NULL);
    if (result != ITLS_OK) {
        break;
    }
}
if (result == ITLS_ETIMEOUT) {
    // Perhaps you want to do something special, or perhaps you want to treat
    // this as an error.
} else if (result != ITLS_OK) {
    // Handle error.
}
// Process bytes_read bytes from buffer.

If you don’t have room for a 16KB buffer on the stack, you can buffer the whole record in the TLS context and use itls_TLSContextGetBufferedByteCount() to determine the required buffer size:

#include "itls_net.h"
#include "itls_tls.h"
...
// Connect to the remote peer and get the resulting itls_NetIOSocket object.
// Create an itls_TLSContext object and perform the TLS handshake.
...
size_t bytes_read = 0;
itls_retval result = ITLS_OK;

while (true) {
    result = itls_TLSContextRead(context, NULL, 0, &bytes_read);
    if (result != ITLS_EREADAGAIN && result != ITLS_EWRITEAGAIN) {
        break;
    }

    bool read = false;
    bool write = false;
    result = itls_NetIOSocketPoll(io_socket, 10000,
        (result == ITLS_EREADAGAIN) ? &read : NULL,
        (result == ITLS_EWRITEAGAIN) ? &write : NULL);
    if (result != ITLS_OK) {
        break;
    }
}
if (result == ITLS_ETIMEOUT) {
    // Perhaps you want to do something special, or perhaps you want to treat
    // this as an error.
} else if (result != ITLS_OK) {
    // Handle error.
}

size_t buffer_size = 0;
result = itls_TLSContextGetBufferedByteCount(context, &buffer_size);
if (result != ITLS_OK) {
    // Handle error.
}

uint8_t *buffer = calloc(1, buffer_size);
if (buffer == NULL) {
    // Handle error.
}

result = itls_TLSContextRead(context, buffer, buffer_size, &bytes_read);
if (result != ITLS_OK) {
    // Handle error.
}

// Process bytes_read bytes from buffer.

free(buffer);

Writing

You may write to the TLS context immediately after the TLS handshake is complete, or after reading and processing application data from the peer.

Use itls_TLSContextWrite() to write TLS application data to your TLS context. The function indicates how much data was used from your buffer. See the TLS I/O Return Codes section for values returned by this function that may need special handling. Note that even though this is a write function, it can return ITLS_EREADAGAIN. This is because this function may process TLS handshake messages in the course of attempting to write application data.

Like reading, writing also operates on TLS application record boundaries. If you attempt to write more data than will fit in a record, only some of your data is written. The amount written is returned to you so you can update your pointers and length values before writing again.

If itls_TLSContextWrite() returns ITLS_EREADAGAIN or ITLS_EWRITEAGAIN, once your socket is ready again you must call itls_TLSContextWrite() again with the same arguments as the previous call.

#include "itls_net.h"
#include "itls_tls.h"
...
// Connect to the remote peer and get the resulting itls_NetIOSocket object.
// Create an itls_TLSContext object and perform the TLS handshake.
...
// Get your buffer and amount to write from somewhere.
size_t amount_to_write = 0;
uint8_t *buffer = get_buffer_to_write(&amount_to_write);

size_t total_bytes_written = 0;
itls_retval result = ITLS_OK;
do {
    size_t bytes_written = 0;
    while (true) {
        result = itls_TLSContextWrite(context, buffer + total_bytes_written,
            amount_to_write - total_bytes_written, &bytes_written);
        if (result != ITLS_EREADAGAIN && result != ITLS_EWRITEAGAIN) {
            break;
        }

        bool read = false;
        bool write = false;
        result = itls_NetIOSocketPoll(io_socket, 10000,
            (result == ITLS_EREADAGAIN) ? &read : NULL,
            (result == ITLS_EWRITEAGAIN) ? &write : NULL);
        if (result != ITLS_OK) {
            break;
        }
    }

    if (result != ITLS_OK) {
        break;
    }

    total_bytes_written += bytes_written;
} while (total_bytes_written < amount_to_write);

if (result == ITLS_ETIMEOUT) {
    // Perhaps you want to do something special, or perhaps you want to treat
    // this as an error.
} else if (result != ITLS_OK) {
    // Handle error.
}

When you are done with your TLS connection, you should send a TLS alert to your peer indicating that you are finished. Use itls_TLSContextCloseNotify() to send this alert. Your peer will then get the ITLS_ECLOSENOTIFY value returned from their I/O function calls. If you receive this alert (i.e. one of your I/O functions returns ITLS_ECLOSENOTIFY), you can be sure that you have received all data from your peer and there was no truncation attack that closed the connection prematurely and prevented you from receiving all the data the peer intended to send. Upon seeing ITLS_ECLOSENOTIFY you should also call itls_TLSContextCloseNotify() to send the same alert back to your peer.

#include "itls_net.h"
#include "itls_tls.h"
...
// Connect to the remote peer and get the resulting itls_NetIOSocket object.
// Create an itls_TLSContext object and perform the TLS handshake.
// Maybe even send and receive some application data.
...
itls_retval result = ITLS_OK;
while (true) {
    result = itls_TLSContextCloseNotify(context);
    if (result != ITLS_EREADAGAIN && result != ITLS_EWRITEAGAIN) {
        break;
    }

    bool read = false;
    bool write = false;
    result = itls_NetIOSocketPoll(io_socket, 10000,
        (result == ITLS_EREADAGAIN) ? &read : NULL,
        (result == ITLS_EWRITEAGAIN) ? &write : NULL);
    if (result != ITLS_OK) {
        break;
    }
}

if (result == ITLS_ETIMEOUT) {
    // Perhaps you want to do something special, or perhaps you want to treat
    // this as an error.
} else if (result != ITLS_OK) {
    // Handle error.
}

TLS I/O Return Codes

The TLS context I/O functions may return the following special return values that don’t indicate an error, but indicate that the caller may need to take some additional action:

  • ITLS_EREADAGAIN: The function is attempting to read from the underlying transport, but there is no data available to read (on a non-blocking socket), a signal interrupted a system call, or the library wants you to attempt the read again for some other reason. Usually you will want to poll the associated socket for reading availability before retrying. This code is returned whether or not the associated socket is non-blocking.

  • ITLS_EWRITEAGAIN: The function is attempting to write to the underlying transport, but a signal interrupted the system call, the socket’s send buffer is full (on a non-blocking socket), or the library wants you to attempt the write again for some other reason. Usually you will want to poll the associated socket for writing availability before retrying. The code is returned whether or not the associated socket is non-blocking.

  • ITLS_ETIMEOUT: This code is only returned if you associated a blocking I/O socket with the TLS context, and you set a non-zero timeout on the TLS config object using itls_TLSConfigSetReadTimeout(). In this case, this code is returned if there was no data available to be read for the duration of the timeout. You will likely want to treat this as any other error code and abort the TLS connection, but you may have reason to retry or do special logging.

  • ITLS_ECLOSENOTIFY: The remote peer has sent the close_notify TLS alert message. This message indicates that the peer has finished sending application data and will close the underlying transport connection. Upon seeing ITLS_ECLOSENOTIFY, you can be assured that you received all the data your peer intended to send and a truncation attack didn’t occur which would have caused you to receive less data than the peer sent.

Technical Information

This section provides further information about some technical aspects of Catalyst TLS.

Build Options

During development of Catalyst TLS, builds use many compiler and linker flags intended to help reduce errors and improve overall code quality.

Compiler Options

When building with clang:

  • -Weverything -Wno-deprecated -Wno-deprecated-declarations -Wno-vla -Wno-packed -Wno-padded -Wno-disabled-macro-expansion -Wno-documentation-unknown-command -Wno-missing-field-initializers -Wno-reserved-id-macro

  • -Winline

  • -fno-stack-protector (for Windows)

  • -fvisibility=hidden

  • -fPIC

  • -std=c99

  • -O3

  • -DNDEBUG -D_FORTIFY_SOURCE=2

  • -D__USE_MINGW_ANSI_STDIO=1 (Windows only)

  • -Werror

When building with gcc:

  • -Wall -Wextra -Waggregate-return -Wbad-function-cast -Wcast-align -Wcast-qual -Wfloat-equal -Wformat-security -Wformat=2 -Winit-self -Wmissing-include-dirs -Wmissing-noreturn -Wmissing-prototypes -Wnested-externs -Wno-deprecated -Wno-deprecated-delcarations -Wold-style-definition -Wpedantic -Wredundant-decls -Wshadow -Wstrict-prototypes -Wswitch-default -Wuninitialized -Wunreachable-code -Wunused -Wvarargs -Wwrite-strings

  • -Winline

  • -fstrict-aliasing -fstrict-overflow -funsafe-loop-optimizations -fstack-check -fstack-protector-all -fvisibility=hidden -fdata-sections -ffunction-sections

  • -fno-stack-protector (for Windows)

  • -fPIC

  • -pedantic

  • -pipe

  • -std=c99

  • -O3

  • -DNDEBUG -D_FORTIFY_SOURCE=2

  • -D__USE_MINGW_ANSI_STDIO=1 (Windows only)

  • -Werror

Linker Options

When building with clang:

  • -Wl,-undefined,error (for macOS) or -Wl,--no-undefined (others)

When building with gcc:

  • -Wl,-dead_strip (for macOS) or -Wl,--gc-sections (others)

  • -Wl,-undefined,error (for macOS) or -Wl,--no-undefined (others)

Apple Bitcode

Catalyst TLS does not support Apple’s Bitcode for iOS, tvOS, or watchOS binaries. For security reasons, Catalyst TLS needs fine control over optimizations and the life cycle of memory buffers, which isn’t possible when producing Bitcode.

Code Stripping

Portions of Catalyst TLS have been designed to maximize code stripping, to help when deploying to embedded systems.

If your compiler/linker supports it, use the -flto option to enable full link-time optimization.

Linux libc

The Linux version of Catalyst TLS is built and tested against the following versions of the libc library:

  • 2.23 (Ubuntu, 64 bit)

  • 2.24 (Raspbian, 32 bit)

Building on Windows

ISARA Catalyst TLS ships with two libraries, libisara_tls containing the TLS implementation and associated functionality, and libiqr_toolkit containing ISARA’s quantum-safe algorithm implementations.

When building an application that links against the static libraries (libisara_tls_static.lib and libiqr_toolkit_static.lib), add the libraries under Linker > Input > Additional Dependencies in Visual Studio. No other changes to your Solution file are necessary.

When linking dynamically, you must specify the import libraries (libisara_tls.lib and libiqr_toolkit.lib) under Linker > Input > Additional Dependencies. Additionally, you must add ITLS_DLL to the preprocessor listing under C/C++ > Preprocessor > Preprocessor Definitions. This will ensure that extern symbols are imported correctly. The DLLs (libisara_tls.dll and libiqr_toolkit.dll) must reside in a location where they can be found by the linker.

ISARA Catalyst TLS is licensed for use:

Copyright © 2019, ISARA Corporation, All Rights Reserved.

The code and other content set out herein is not in the public domain, is considered a trade secret and is confidential to ISARA Corporation. Use, reproduction or distribution, in whole or in part, of such code or other content is strictly prohibited except by express written permission of ISARA Corporation. Please contact ISARA Corporation at info@isara.com for more information.

ISARA Catalyst TLS contains components from mbed TLS governed by the terms of the license below:

Please refer to your sales/support contract for more information about technical support and upgrade entitlements.

Trademarks

ISARA Catalyst™ is a trademark of ISARA Corporation.

Sample Code License

Sample code is covered by the Apache 2.0 license:

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Patent Information

Portions of this software are covered by US Patents 9,614,668, 9,660,978, 9,673,977, and 9,698,986.