Skip to main content

Modes of operation

The NanoSSL library provides different modes of operation to cater to a variety of application requirements. This section describes the synchronous and asynchronous modes of operation.

The synchronous mode of operation is easy to use and intended for simple applications, while the asynchronous mode of operation is suitable for real-world applications that concurrently interact over multiple I/O channels in addition to using a SSL/TLS connection.

Functional description

Applications leveraging NanoSSL are conceptualized with a dual-layer structure: the application layer managing network connections and the NanoSSL layer ensuring secure data transmission.

Application and NanoSSL Layer Interaction

The application layer initiates and maintains the TCP socket, listening for incoming client connections. Upon accepting a connection, it may utilize multithreading or subprocesses to manage the TCP session. At this juncture, the NanoSSL library is invoked to perform SSL/TLS handshakes, after which it secures the data exchange by encrypting and decrypting the communication streams.

Synchronous Operation

Figure 1. Synchronous mode control flow
Synchronous mode control flow

Control flow of an SSL server application when using NanoSSL in asynchronous mode


The synchronous operational mode is tightly integrated with the TCP socket, performing I/O tasks directly:

  1. Accepting Connections: The application layer hands over control to NanoSSL with SSL_acceptConnection, transferring the client’s socket for session initiation.

  2. Data Transmission Management: Internally, the socket descriptor is stored, utilized by SSL_send to encrypt and send data, or SSL_recv to decrypt and receive data.

  3. Sending Data: SSL_send encrypts the cleartext buffer and transmits it, returning a status code indicative of the bytes dispatched. SSL_sendPending is used to assess if more data remains in the send buffer.

  4. Receiving Data: Conversely, SSL_recv decrypts received data, delivering it to the application. SSL_recvPending assesses if data is awaiting retrieval.

Tip: While the __ENABLE_MOCANA_SSL_PENDING_DATA_YIELD__ flag exists for CPU yield on multi-threaded platforms, it is recommended to poll SSL_send and SSL_recv alongside their respective pending functions to ensure I/O completion.

Asynchronous Operation

Figure 2. Asynchronous mode control flow
Asynchronous mode control flow

control flow of an SSL server application when using NanoSSL in asynchronous mode


Asynchronous mode decouples from the TCP socket’s direct handling, emphasizing memory-based record management:

  1. Socket Handling: The application layer maintains TCP connection acceptance as in synchronous mode but refrains from providing the socket descriptor to NanoSSL.

  2. Data Encryption: Data to be SSL encrypted is supplied via SSL_ASYNC_sendMessage, with NanoSSL outputting the encrypted record to the connection context.

  3. Sending SSL Records: The SSL record is retrieved with SSL_ASYNC_getSendBuffer, then transmitted over the TCP socket through a platform-dependent transmission method.

  4. Data Decryption: After reading from the TCP socket, the application delivers the data to NanoSSL using SSL_ASYNC_recvMessage2 for decryption, and the plaintext is then fetched with SSL_ASYNC_getRecvBuffer.

Blocking vs Non-Blocking Sockets

The choice between blocking and non-blocking sockets operates independently of the synchronous or asynchronous API usage. Blocking sockets are straightforward and can be employed in both modes. Non-blocking sockets are suitable for situations where immediate data availability is not guaranteed, allowing the application to perform alternate tasks and retry as needed. Practices such as using select or poll are common to determine the readiness of data on the TCP socket.

Each mode and socket type offers unique benefits, and the choice should align with the application’s design for optimal performance and security.

Asynchronous API details

This section describes operation of the asynchronous API calls with example code to illustrate use these APIs and is applicable to both the client and server side. The session negotiation APIs for server and client implementations are first described, followed by the data send and data receive APIs. For additional information about the client-side API, see NanoDTLS API Details (Client Side).

Create a NanoSSL Connection Instance

When NanoSSL is initialized, the maximum number of connection contexts that it must support is specified. Internally, NanoSSL allocates an array of sslConnectDescr structures to store these contexts. When the application accepts a new TCP connection, it calls a NanoSSL API to allocate an unused context from the array to the connection. If NanoSSL successfully allocates a context, it returns the index of that entry in the array to the caller called a connection instance (connectionInstance). The application layer uses the returned connectionInstance to refer to the connection in subsequent calls to NanoSSL, which stores all state information for that connection in that context. ## Create a Certificate Store

To use an SSL/TLS cipher suite that requires certificate-based authentication, a certificate store must be created for use by NanoSSL. In typical use cases, only the SSL server presents its certificate to be authenticated by the SSL client. Details about certificate store creation are beyond the scope of this document. The CERT_STORE_createStore API is used to create an empty certificate store and then the server certificate (or chain of certificates) and key must be added to the certificate store using the CERT_STORE_addIdentityWithCertificateChain API. This certificate store may then be used to create a connection context as described in Create a Connection Context.

Create a Connection Context

The SSL_ASYNC_acceptConnection API is called first when the application is the server side of the SSL/TLS connection. NanoSSL allocates a connectionInstance as described in Create a NanoSSL Connection Instance. Before this API is invoked, the server must have accepted a client TCP connection (in Linux, this step is typically performed with the accept() system call). The application must also have allocated and populated a certificate store.

The prototype of this function is as follows:

    sbyte4 connectionInstance = SSL_ASYNC_acceptConnection (
    sbyte4 appSocket,
    struct certStore *pCertStore)

The first argument is the socket descriptor which is used to uniquely identify a Nano SSL session. The second argument is the pointer to the certificate store. This function returns a connectionInstance on success and a negative response on failure.

Set SSL Session Flags

To use the NanoSSL library in asynchronous mode, the SSL_FLAG_ENABLE_SEND_BUFFER and SSL_FLAG_ENABLE_RECV_BUFFER flags must be set using the SSL_setSessionFlags API as follows:

    SSL_setSessionFlags(
    sbyte4 connectionInstance, 
    (SSL_FLAG_ENABLE_SEND_BUFFER | SSL_FLAG_ENABLE_RECV_BUFFER)
    )

This API call is typically called after obtaining the connectionInstance using the SSL_ASYNC_acceptConnection API as described in Create a Connection Context.

Deliver Data to NanoSSL for Processing

The SSL_ASYNC_recvMessage2 API is called by the server application layer to deliver data received over the SSL/TLS TCP socket to NanoSSL for SSL/TLS protocol processing.

The function prototype for this call is as follows:

    status = SSL_ASYNC_recvMessage2(
    sbyte4 connectionInstance,
    ubyte *pFirstRcvdUnreadByte,
    ubyte4 bytesRcvdRemaining,
    ubyte **ppFirstUnusedByte,
    ubyte4 *bytesRemaining)

where:

  • connectionInstance: Connection instance returned by SSL_ASYNC_acceptConnection.

  • pFirstRcvdUnreadByte: Pointer to the first (oldest) byte of data received on the TCP connection that has not yet been processed by the NanoSSL library.

  • bytesRcvdRemaining: Number of bytes available in the above buffer for NanoSSL to process.

  • ppFirstUnusedByte: Set to point to the first (oldest) byte in the buffer pFirstRcvdUnreadByte that has not yet been consumed by NanoSSL. It is set only if status > 0.

  • bytesRemaining: Set to the number of bytes in the buffer pFirstRcvdUnreadByte that have not yet been consumed by NanoSSL. This value is set only if pFirstUnusedByte is set.

The server application layer waits to receive a client HELLO message. The server application layer reads data on the TCP socket into a buffer. It then calls SSL_ASYNC_recvMessage2() with pFirstRcvdUnreadByte set to the address of this buffer. The argument bytesRcvdRemaining is the number of bytes in the received buffer. The behavior of this function depends on whether the SSL/TLS handshake is complete or pending. The difference between these 2 cases is explained in SSL/TLS Handshake Phase and Data Transfer Phase.

SSL/TLS Handshake Phase

In this phase, when the application layer calls SSL_ASYNC_recvMessage2, the function parses the SSL/TLS packet and feeds it to the internal SSL handshake state machine. This state machine takes care of the SSL/TLS protocol details including message parsing, ciphersuite selection, authentication, and key material generation. The server state machine is always triggered by the arrival of a client HELLO. As the state machine runs, it may produce SSL message records that need to be sent to its peer. Thus, after this call returns, the application layer must look for pending data to be sent to the SSL peer. If there is pending data, it must retrieve it and send it over the TCP connection (see Copy SSL/TLS Record).

Note that in the handshake phase, there is no application data to be delivered to the application layer — all data given to SSL_ASYNC_recvMessage2() is consumed by the state machine, and the return value of SSL_ASYNC_recvMessage2() is typically 0. Furthermore, SSL_ASYNC_recvMessage2() never sets the last two arguments, ppFirstUnusedByte and bytesRemaining.

Data Transfer Phase

The data transfer phase follows the handshake phase. In this phase, the application layers send application data to each other (e.g., HTTP request(s) and response(s)). In this case, the return value of this function should be greater than 0. Such a value indicates that the ciphertext data was converted to plaintext and this plaintext is available for delivery to the application layer. The function may still return 0 if not enough raw data was received to form a full SSL record. As the application layer continues to receive data and delivers it to this function, eventually a full record is received and decrypted by this function and made available for the application layer to read. In this case, it is possible for ppFirstUnusedByte to be set to a non-NULL value which may happen if the data buffer pointed to by pFirstRcvdUnreadByte had the final portion of the current record and some data for the next record as well. In this case, the caller should remember the unused portion of the received TCP data and deliver it to the NanoSSL layer the next time it calls SSL_ASYNC_recvMessage2(). Enable Features for a DTLS Session shows an example of how this API function is used during the data transfer phase.

Retrieve Extracted Data

As shown in Figure 2, the application layer must call the SSL_ASYNC_getRecvBuffer() API to harvest any plaintext data extracted by the previous call to SSL_ASYNC_recvMessage2.

The prototype for this function is as follows:

    sbyte4 SSL_ASYNC_getRecvBuffer(
    sbyte4 connectionInstance,
    ubyte **data,
    ubyte4 *len,
    ubyte4 *pRetProtocol)

where:

  • connectionInstance: Connection instance returned by SSL_ASYNC_acceptConnection.

  • data: Pointer to an address. If plaintext data is waiting to be delivered, the function copies the address of the first byte of plaintext into data.

  • len: Pointer to an unsigned 32-bit integer. If plaintext data is waiting to be delivered, the function copies the length of the plaintext buffer into this argument.

  • pRetProtocol: SSL record header type of the delivered data defined in the TLS RFC. For example, application data is type 23.

The caller must consume (e.g., copy) all data indicated by this function when it returns. See APP_SSL_read for an example of how this API function is used.

Send Data to the Peer

The SSL_ASYNC_sendMessage API is used to send application data to the peer and should not be called until the SSL/TLS handshake is complete. The application layer provides a plaintext data buffer, and this function creates an encrypted SSL/TLS record that is ready for transmission.

The prototype for this function is as follows:

    sbyte4 SSL_ASYNC_sendMessage(
    sbyte4 connectionInstance, sbyte *pBuffer,
    sbyte4 bufferSize, sbyte4 *pBytesSent)

where:

  • connectionInstance: The connection instance returned by SSL_ASYNC_acceptConnection.

  • pBuffer: Pointer to the (plaintext) data buffer that needs to be sent.

  • bufferSize: Number of bytes to be sent in the above (plaintext) buffer.

  • pBytesSent: Pointer to an integer. The function sets it to the number of bytes in the input buffer that was consumed. If less than bufferSize, the application must make another attempt to send the remaining data.

This function allocates memory to hold the SSL/TLS record that it creates and stores the record in the connection context. Mocana recommends that the application retrieve the record and send it on the network before invoking this call again to send fresh data. The call to retrieve the record is shown next.

Copy SSL/TLS Record

The SSL_ASYNC_getSendBuffer function is used to copy out the SSL/TLS record created by a prior call to SSL_ASYNC_sendMessage.

The prototype for this function is as follows:

    sbyte4 SSL_ASYNC_getSendBuffer(
    sbyte4 connectionInstance,
    ubyte *data,
    ubyte4 *len)

where:

  • connectionInstance: The connection instance returned by SSL_ASYNC_acceptConnection.

  • data: Pointer to a data buffer to receive the prepared SSL/TLS record. It must be allocated by the caller and freed after the caller is done using it.

  • len: Pointer to an integer. The caller sets it to the size of the buffer pointed to by data. The function sets it to the number of bytes it copied into the buffer pointed to by data.

If the value of len after this function returns is greater than 0, then the caller should send out len bytes of the buffer starting at data. Note that the application may retrieve the currently prepared SSL/TLS record in chunks using one or more calls to SSL_ASYNC_getSendBuffer. If len is 0 when the call returns, it indicates that the current SSL/TLS record has been entirely retrieved, and SSL_ASYNC_sendMessage may now be called to send fresh data.

Copy SSL/TLS Record into the Buffer

The SSL_ASYNC_getSendBuffer makes a copy of the SSL/TLS record into the buffer supplied by the application layer. To avoid this duplication, a zero-copy version of the above function is available called SSL_ASYNC_getSendBufferZeroCopy. The function prototype is as follows:

    sbyte4 SSL_ASYNC_getSendBufferZeroCopy(
    sbyte4 connectionInstance,
    ubyte **data, ubyte4 *len)

where:

  • connectionInstance: The connection instance returned by SSL_ASYNC_acceptConnection.

  • data: Pointer to a pointer to store the address of the buffer that holds the prepared SSL/TLS record.

  • len: Pointer to an integer. The caller sets this to the number of bytes in the SSL/TLS record. A value of 0 indicates that there is no data available.

If the above call indicates that there is data available, the application layer should send it out on the network. Because the application layer gets a reference to a buffer managed by NanoSSL, it must indicate to NanoSSL when it is done with this buffer using a call to SSL_ASYNC_freeSendBufferZeroCopy. Note that the application must not modify the returned buffer in any way.

Free Record Buffer

The SSL_ASYNC_freeSendBufferZeroCopy function must be used with the SSL_ASYNC_getSendBufferZeroCopy function to tell NanoSSL that the pointer to the SSL/TLS record is no longer needed and the buffer may be freed.

The prototype for this function is as follows:

    sbyte4 SSL_ASYNC_freeSendBufferZeroCopy(
    sbyte4 connectionInstance,
    ubyte4 numUnusedBytes)

where:

  • connectionInstance: The connection instance returned by SSL_ASYNC_acceptConnection.

  • numUnusedBytes: Number of bytes of the SSL/TLS record buffer that it wants NanoSSL to save.

In typical use cases, numUnusedBytes is set to 0 to indicate that the entire SSL/TLS record has been consumed. In this case, NanoSSL frees the memory holding the record. If the caller sets this to a non-zero value, then NanoSSL retains that many bytes of the current record. The application layer retrieves and processes it later, ideally before it prepares another SSL/TLS record to prevent memory leaks and/or data loss.

NanoDTLS API details (client)

Initialize Internal Structures

The DTLS_init API initializes NanoDTLS client/server internal structures. The application should call this function before starting the application servers or client. The prototype for this function is as follows:

    sbyte4 DTLS_init(sbyte4 numServerConnections, sbyte4 numClientConnections);

where:

  • numServerConnections: Maximum number of NanoDTLS server connections to allow. (Each connection requires only a few bytes of memory.) If operating in dual mode, this is the sum of the synchronous and asynchronous server connections.

  • numClientConnections: Maximum number of NanoDTLS client connections to allow.

Allocate Connection Instance

The DTLS_acceptConnection API should be called first when the application is the server side of the DTLS connection. It causes NanoDTLS to allocate a connectionInstance as described in Create a NanoSSL Connection Instance. Before this API invocation, the server should have accepted a client UDP connection (in Linux, this step is typically done using a call to the accept() system call). The application should also have allocated and populated a certificate store.

This function returns a connectionInstance on success and a negative response on failure.

The prototype of this function is as follows:

    sbyte4 connectionInstance = DTLS_acceptConnection (
    sbyte4 *pPeerDescr,
    struct certStore *pCertStore)

where:

  • pPeerDescr: DTLS connection descriptor returned by a call to accept().

  • pCertStore: Pointer to SoT Platform certificate store that contains the DTLS connection's certificate (as a trust point or identity).

Create a Connection Descriptor

The DTLS_connect API creates a connection descriptor for a secure NanoDTLS connection with a remote server.

The prototype of this function is as follows:

    sbyte4  DTLS_connect(peerDescr *pPeerDescr, 
     ubyte sessionIdLen, 
     ubyte * sessionId, 
     ubyte * masterSecret,
     const sbyte* dnsName, 
     struct certStore* pCertStore);

where:

  • pPeerDescr: NanoDTLS connection descriptor returned by a call to udp_connect().

  • sessionIdLen: Number of bytes in sessionId, excluding the NULL terminator.

  • sessionId: Pointer to session ID.

  • masterSecret: Pointer to master secret for the session.

  • dnsName: Pointer to expected DNS name of the server's certificate.

  • pCertStore: Pointer to certificate store that contains the DTLS connection's certificate.

Enable Features for a DTLS Session

The DTLS_ioctl API enables dynamic management (enabling and disabling) of selected features for a specific DTLS session's connection instance.

The prototype of this function is as follows:

    sbyte4  DTLS_ioctl(sbyte4 connectionInstance, ubyte4 setting, void *value);

where:

  • connectionInstance: Connection instance returned from DTLS_connect().

  • Setting: SSL feature flag to dynamically alter; see SSLioctl settings in ssl.h.

  • Value: Value to assign to the \p setting flag.

Establish a Secure Connection

The DTLS_start API begins the process of establishing a secure connection between a client and server by sending a DTLS \c Hello message to a server.

The prototype of the function is as follows:

    sbyte4  DTLS_start(sbyte4 connectionInstance);

where:

  • connectionInstance: Connection instance returned by DTLS_connect.

Send Application Data to a Peer

The DTLS_sendMessage API call is used to send application data to the peer and should not be called until the DTLS handshake is complete. The application layer provides a plaintext data buffer, and this function creates an encrypted DTLS record that is ready for transmission.

The prototype for this function is as follows:

    sbyte4 DTLS_sendMessage(
    sbyte4 connectionInstance, sbyte *pBuffer,
    sbyte4 bufferSize, sbyte4 *pBytesSent)

where:

  • connectionInstance: The connection instance returned by DTLS_connect.

  • pBuffer: Pointer to the (plaintext) data buffer that needs to be sent.

  • bufferSize: Number of bytes to be sent in the above (plaintext) buffer.

  • pBytesSent: Pointer to an integer. The function sets it to the number of bytes in the input buffer that was consumed. If less than bufferSize, the application must make another attempt to send the remaining data.

This function allocates memory to hold the DTLS record that it creates. It stores the record in the connection context. It is recommended that the application retrieve the record and send it on the network before invoking this call again to send fresh data. Use the DTLS_getSendBuffer API to retrieve the record.

Copy the Previous DTLS Record

The DTLS_getSendBuffer API is used to copy out the DTLS record created by a prior call to DTLS_sendMessage.

The prototype for this function is as follows:

    sbyte4 DTLS_getSendBuffer(
    sbyte4 connectionInstance,
    ubyte *data,
    ubyte4 *len)

where:

  • connectionInstance: The connection instance returned by DTLS_connect.

  • data: Pointer to a data buffer to receive the prepared DTLS record. This must be allocated by the caller and freed after the caller is done using it.

  • len: Pointer to an integer. The caller sets it to the size of the buffer pointed to by data. The function sets it to the number of bytes it copied into the buffer pointed to by data.

If the value of len after this function returns is greater than 0, then the caller should send out len bytes of the buffer starting at data. Note that the application may retrieve the currently prepared DTLS record in chunks using one or more calls to DTLS_getSendBuffer. If len is 0 when the call returns, it indicates that the current DTLS record has been entirely retrieved. It is now safe to call DTLS_sendMessage to send fresh data.

Return Received Data for Processing

The DTLS_recvMessage API returns data received over the DTLS UDP socket to NanoSSL for DTLS protocol processing.

The function prototype for this call is as follows:

    status = DTLS_recvMessage(sbyte4 connectionInstance, 
    ubyte *pBytesReceived, 
    ubyte4 numBytesReceived, 
    ubyte **ppRetBytesReceived, 
    ubyte4 *pRetNumRxBytesRemaining);

where:

  • connectionInstance: Connection instance returned by DTLS_connect.

  • pBytesReceived: Pointer to the first (oldest) byte of data received on the UDP connection that has not yet been processed by the NanoDTLS library.

  • numBytesReceived: Number of bytes available in the pBytesReceived buffer for NanoDTLS to process.

  • ppRetBytesReceived: Set to point to the first (oldest) byte in the buffer pBytesReceived that has not yet been consumed by NanoDTLS. It is set only if status > 0.

  • pRetNumRxBytesRemaining: Set to the number of bytes in the buffer pBytesReceived that have not yet been consumed by NanoDTLS. This is set only if ppRetBytesReceived is set.

The operation of this function from a server perspective is as follows. The server application layer waits to receive a client HELLO message. The server application layer reads UDP socket data in a platform specific manner into a buffer. The argument pRetNumRxBytesRemaining is the number of bytes in the received buffer.

Receive Data from Previous Call

As shown in Figure 2, the application layer must call the DTLS_getRecvBuffer() API to harvest any plaintext data extracted by the previous call to DTLS_recvMessage.

The prototype for this function is as follows:

    sbyte4 DTLS_getRecvBuffer(
    sbyte4 connectionInstance,
    ubyte **data,
    ubyte4 *len,
    ubyte4 *pRetProtocol)

where:

  • connectionInstance: Connection instance returned by DTLS_connect.

  • data: Pointer to an address. If there is plaintext data waiting to be delivered, the function copies the address of the first byte of plaintext into data.

  • len: Pointer to an unsigned 32-bit integer. If there is plaintext data waiting to be delivered, the function copies the length of the plaintext buffer into this argument.

  • pRetProtocol: DTLS record header type of the delivered data defined in the DTLS RFC. For example, application data is type 23.

Return Session Flags

The DTLS_getSessionFlags API returns session flags related DTLS connection.

The prototype for this function is as follows:

    sbyte4  DTLS_getSessionFlags(sbyte4 connectionInstance, 
    ubyte4 *pRetFlagDTLS);

where:

  • connectionInstance: Connection instance returned by DTLS_connect.

  • pRetFlagDTLS: pointer to the given connection's session flags.

Return Connection Instances

The DTLS_getConnectionInstance API returns a connection instance for the specified src-dst connection. The returned connection instance may be used as a parameter in subsequent calls to NanoDTLS server functions.

The prototype for this function is as follows:

sbyte4 DTLS_getConnectionInstance(MOC_IP_ADDRESS srcAddr, ubyte2 srcPort, MOC_IP_ADDRESS peerAddr, ubyte2 peerPort)

where:

  • srcAddr: Source’s IP address.

  • srcPort: Source’s port number.

  • peerAddr: Peer’s IP address.

  • peerPort: Peer’s port number.

Verify Client

The DTLS_verifyClientHelloCookie API uses a server-generated stateless cookie to verify that a known client is located at its claimed IP address, thereby preventing DOS (denial of service) attacks. Before calling this function (but after the successful return of DTLS_acceptConnection()), the DTLS_SET_HELLO_VERIFIED ioctl must be set to ensure that the server’s handshake and record sequence numbers are set correctly using the following call:

    DTLS_ioctl(connectionInstance, DTLS_SET_HELLO_VERIFIED, 1).

The prototype for this function is as follows:

    sbyte4 DTLS_verifyClientHelloCookie(MOC_IP_ADDRESS peerAddr, ubyte *pReceived, ubyte4  length,ubyte *pToSend, ubyte4 *pToSendLen)

where:

  • peerAddr: Client’s IP address.

  • pReceived: Pointer to buffer containing HELLO message received from the client.

  • Length: Number of bytes in Hello message (pReceived).

  • pToSend: Pointer to buffer containing HelloVerifyRequest message, which contains the cookie generated by the server for the client.

  • pToSendLen: Pointer to number of bytes in HelloVerifyRequest message (pToSend).

Return Next Open Client Connection Instance

The DTLS_getNextConnectionInstance API returns a server’s next open client connection instance. The application calls this API in an iterative fashion to examine all a server’s client connections in turn, performing necessary message processing and communication for each connection.

The prototype for this function is as follows:

    sbyte4 DTLS_getNextConnectionInstance(ubyte4 *pCookie, sbyte4 *pConnectionInstance, const peerDescr **ppRetPeerDescr)

where:

  • pCookie: At function call, reference to opaque cookie that points to previously returned open connection instance. (The first time this function is called, use a value of NULL.) On return, reference the updated cookie pointing to next connection instance, and save this value for subsequent calls to this function.

  • pConnectionInstance: On return, pointer to next open connection instance.

  • ppRetPeerDescr: On return, pointer to DTLS connection descriptor corresponding to the next open connection instance (pConnectionInstance).

Set Session Flags

The DTLS_setSessionFlags API sets a session flags related to DTLS connections. The application may call this function after it calls DTLS_connect().

The prototype for this function is as follows:

    sbyte4  DTLS_setSessionFlags(sbyte4 connectionInstance, 
    ubyte4 flagsDTLS);

where:

  • connectionInstance: Connection instance returned by DTLS_connect.

  • flagsDTLS: Bitmask of flags to set for the given connection's context.

The context flags are specified by OR-ing the desired bitmask flag definitions, defined in src/ssl/ssl.h: DTLS_FLAG_ENABLE_SRTP_DATA_SEND. To avoid clearing any flags that are already set, application should first call DTLS_getSessionFlags(), then OR the returned value with the desired new flag and only then call DTLS_setSessionFlags().

Close DTLS Session

The DTLS_closeConnection API closes a NanoDTLS session and releases all the resources that are managed by the NanoDTLS client/server.

The prototype for this function is as follows:

    sbyte4 DTLS_closeConnection(sbyte4 connectionInstance);

where:

  • connectionInstance: Connection instance returned by DTLS_connect.

Shut Down DTLS Stack

The DTLS_shutdown API cleans up memory, mutexes, and shuts down the NanoDTLS stack.

The prototype for this function is as follows:

    sbyte4 DTLS_shutdown(void);

Release Internal Memory Tables

The DTLS_releaseTables API releases the NanoDTLS client’s or server’s internal memory tables. It should only be called after a call to DTLS_shutdown(). To resume communication with a device after calling this function, a new connection must be created and then register encryption keys and an X.509 certificate.

The prototype for this function is as follows:

    sbyte4 DTLS_releaseTables(void);

Release Certificate Store Memory

The CERT_STORE_releaseStore API releases (frees) memory used by a TrustCore SDK certificate store, including all its component structures.

The prototype for this function is as follows:

    sbyte4 CERT_STORE_releaseStore(certStorePtr *ppReleaseStore);

where:

  • ppReleaseStore: Pointer to certificate store.

NanoDTLS API usage example

The following example shows the process flow of the NanoDTLS API:

    /* initialize client */
    if (0 > (status = DTLS_init(0, 10)))
    {
        printf("DTLS_init::status %d", status);
    } 
    /*initialize udp socket */
    if (OK > (status = UDP_connect(&pUdpDescr, MOC_UDP_ANY_ADDR, MOC_UDP_ANY_PORT,
        MOC_AND(serverIpAddress), dtls_c_ServerPort, TRUE)))
    {
        printf("UDP_connect::status %d", status);
    }
    if (OK > (status = UDP_getSrcPortAddr(pUdpDescr, &srcPort, &srcAddr)))
    {
        printf("UDP_getSrcPortAddr::status %d", status);
    }
    /*An NanoDTLS-based client calls  DTLS_connect to initiate an DTLS session with a server. The first step in DTLS_connect is a call to SSL_ASYNC_connect() to obtain a connectionInstance and  store it in s->instance, where s is the SSL structure. */
    if (OK > (status = connectionInstance = DTLS_connect(&myPeerDescr, 0,
            NULL, NULL, (const sbyte*)dtls_c_ServerName, pClientSslCertStore)))
    if (OK > (status = DTLS_ioctl(connectionInstance, SSL_SET_VERSION, (void *)dtls_c_DtlsVersion)))
    {
        printf("DTLS_ioctl::status %d", status);
    }
    /* send DTLS client hello, then DTLS_start() is called to send a CLIENT HELLO message to the server. */
    if (OK > (ret = DTLS_start(connectionInstance)))
    {
        printff("DTLS_ioctl::status %d", ret);
    }
    /* client sends the application data */
    status = DTLS_getSendBuffer(connectionInstance, pSendBuffer, &numBytes);
    if (OK > (status = UDP_send(pPeerDescr->pUdpDescr, pSendBuffer, numBytes)))
    {
        printff("UDP_send::status %d", status);
    }
    /* client receives the application data */
    if (OK > (status = UDP_recv(pPeerDescr->pUdpDescr, udpBuffer, 2048, &nRet)))
    {
        printf("UDP_recv::status: %d", status);
    }
    if (OK > (status = DTLS_recvMessage(connectionInstance, udpBuffer, nRet, &pDummy, &dummyLen)))
    {
        printf("DTLS_recvMessage::status: %d", status);
    }
    if (OK > (status = DTLS_getRecvBuffer(connectionInstance, &pRecvBuffer, &numBytes, &retProtocol)))
    {
        printf("DTLS_getRecvBuffer::status: %d", status);
    }
    /* close the connection and release the memory */
    DTLS_closeConnection(connectionInstance); 
    DTLS_shutdown();
    DTLS_releaseTables();

Example flow with NanoSSL APIs

The code snippets provided in this section illustrate the use of the server- and client-side APIs:

  • APP_SSL_accept (below): An application TLS server connection accept method that calls three other NanoSSL APIs, namely SSL_ASYNC_acceptConnection, SSL_ASYNC_recvMessage2, and SSL_ASYNC_sendMessage.

  • APP_SSL_connect: An application TLS client connection method called by the client.

  • APP_SSL_write: An application TLS session that writes and sends application data.

  • APP_SSL_read: An application TLS session that receives and reads application data.

Note that the authoritative source for the examples in this section was the openssl_shim_layer source in the mss/src/openssl_wrapper directory and also provided the composite usage of the NanoSSL asynchronous APIs.

APP_SSL_accept

This OpenSSL shim-equivalent method negotiates an SSL/TLS session with a client after accepting a connection from a client and has the handle to the accepted socket. The server starts SSL negotiation on the newly accepted socket by calling SSL_ASYNC_acceptConnection() to obtain a connectionInstance and stores it in s->instance, where s is the SSL structure. There are two cases depending on whether the socket is blocking or non-blocking.

Blocking Socket

If a socket is blocking, APP_SSL_accept enters the loop and runs steps 1 through 4 until SSL_SOCK_isSecureConnectionEstablished(s->instance) becomes TRUE. Until the handshake phase completes, this condition is FALSE. Once the handshake is complete, this condition becomes TRUE.

Non-Blocking Socket

If the socket is non-blocking, APP_SSL_accept may return before the handshake phase is complete. This return happens between steps 1 and 2 below. If the value returned is <1, it indicates that there was no data to read from the socket and is not a fatal error. In this case, the caller is free to do other tasks such as call APP_SSL_accept multiple times to process any new data received on the socket. Typically, the caller waits in a select or poll loop for data to become available, and calls APP_SSL_accept whenever there is data to read from the socket.

APP_SSL_accept() is called immediately after the server accepts a TCP connection from a client. The following pseudocode describes APP_SSL_accept:

    int appSocket = s->fd;
    s->instance = SSL_ASYNC_acceptConnection(appSocket, pCertStore);
    LOOP until: SSL_SOCK_isSecureConnectionEstablished(s->instance) == TRUE:
    Step 1: numBytesRead = ReadFromTcpSocket(tcpSocket, readBuffer, readBufferSize)
    Step 2: SSL_ASYNC_recvMessage2(s->instance, readBuffer, numBytesRead, &pFirstUnusedByte, &bytesRemaining);
    Step 3: SSL_ASYNC_getSendBuffer(s->instance, WriteBuffer, &numBytesToWrite))
    Step 4: numBytesWritten = WriteToTcpSocket(tcpSocket, WriteBuffer, numBytesToWrite)
    END LOOP
    RETURN status

The details of the call process are as follows:

  • Read data from the TCP socket into the buffer readBuffer and numBytesRead is the number of bytes read. The first message that the server receives is the CLIENT HELLO. All messages received on the socket must be processed by the NanoSSL library. This happens when SSL_accept delivers the data to NanoSSL in Step 2.

  • Call SSL_ASYNC_recvMessage2() to run the receive state machine to pass the data read in Step #1 to recvMessage2(), which parses the buffer and extracts an SSL/TLS message and then runs the server handshake state machine (SM). The SM may produce a response.

  • Call SSL_ASYNC_getSendBuffer() to check for any messages generated by the receiving SM that should be sent to the client. If so, this message is copied into WriteBuffer.

  • If there was something to send, write the message contents to the TCP socket.

Note that during the handshake phase of the SSL/TLS protocol, the received messages are consumed by the NanoSSL library to drive the handshake state machine. None of these messages are delivered to the application layer. After the handshake is completed, application messages may be sent by either peer using APP_SSL_write().

APP_SSL_connect

This OpenSSL-based client calls SSL_connect initiates an SSL/TLS session with a server. It first calls SSL_ASYNC_connect() to obtain a connectionInstance and stores it in s->instance, where s is the SSL structure, and then calls SSL_ASYNC_start() to send a CLIENT HELLO message to the server. After this call, it behaves the same as it does for the server side: APP_SSL_connect goes into a loop waiting to receive responses from the server that in turn triggers the handshake state machine to send out responses until the handshake is complete with either a failure or success. As for the server, there are two cases depending on whether the socket is blocking or non-blocking.

Blocking Socket

If the socket is blocking, APP_SSL_connect() enters the loop and runs steps 1 through 4 until SSL_SOCK_isSecureConnectionEstablished(s->instance) becomes TRUE. Until the handshake phase completes, this condition is FALSE. Once the handshake is complete, this condition becomes TRUE.

Non-Blocking Socket

If the socket is non-blocking, APP_SSL_connect() may be returned before the handshake phase is complete. This return happens between steps 1 and 2 below. If the value returned is < 1, it indicates that there was no data to read from the socket and is not a fatal error. In this case, the caller is free to do other tasks. The caller may call APP_SSL_connect multiple times to process any new data received on the socket. Typically, the caller waits in a select or poll loop for data to become available, and calls APP_SSL_connect whenever there is data to read from the socket.

The following pseudocode describes APP_SSL_connect:

    int appSocket = s->fd;
    s->instance = SSL_ASYNC_connect(appSocket, 0, NULL, NULL, NULL, pCertStore);
    /* Next 3 lines generate a CLIENT HELLO and send it out */
    SSL_ASYNC_start(s->instance)
    SSL_ASYNC_getSendBuffer(s->instance, WriteBuffer,&numBytesToWrite))
    numBytesWritten = WriteToTcpSocket(tcpSocket, WriteBuffer, numBytesToWrite)
    LOOP until: SSL_SOCK_isSecureConnectionEstablished(s->instance) == TRUE:
    Step 1: numBytesRead = ReadFromTcpSocket(tcpSocket, readBuffer, readBufferSize)
    Step 2: SSL_ASYNC_recvMessage2(s->instance, readBuffer, numBytesRead, &pFirstUnusedByte, &bytesRemaining);
    Step 3: SSL_ASYNC_getSendBuffer(s->instance, WriteBuffer, &numBytesToWrite))
    Step 4: numBytesWritten = WriteToTcpSocket(tcpSocket, WriteBuffer, numBytesToWrite)
    END LOOP
    RETURN status

The details of the call process are as follows:

  • Read data from the TCP socket into buffer readBuffer. numBytesRead is the number of bytes read. All messages received on the socket must be processed by the NanoSSL library when APP_SSL_connect delivers the data to NanoSSL in step 2.

  • Call SSL_ASYNC_recvMessage2() to run the receive state machine to pass the data read in step 1 to recvMessage2(), which parses the buffer and extracts an SSL/TLS message and then runs the client handshake state machine (SM). The SM may produce a response.

  • Call SSL_ASYNC_getSendBuffer() to check for any messages generated by the receiving SM that should be sent to the server. If so, this message is copied into WriteBuffer.

  • If there was something to send, write the message contents to the TCP socket.

APP_SSL_write

An application calls APP_SSL_write to send data on an SSL/TLS session to the peer.

The following pseudocode describes APP_SSL_write:

    int toSend = num;   
    char *pCurPtr = buf;
    LOOP until: toSend == 0
    Step 1: SSL_ASYNC_sendMessage(s->instance, (sbyte *)pCurPtr, (sbyte4)toSend, &bytesSent);
    toSend -= bytesSent;
    pCurPtr+= bytesSent;
    Step 2: SSL_ASYNC_getSendBuffer(s->instance, WriteBuffer, &numBytesToWrite)
    Step 3: numBytesWritten = WriteToTcpSocket(tcpSocket, WriteBuffer, numBytesToWrite)
    END LOOP

In the loop, there are two NanoSSL API calls:

  • The call to SSL_ASYNC_sendMessage converts plaintext application data to an encrypted SSL record. Note that it is possible that only part of the data may be used to create the SSL record, which may happen if the data size exceeds 16KB and is why the loop exists and repeatedly invokes steps 1 and 2 until all the data has been sent out.

  • The application retrieves the SSL record created in step 1 and copies the contents of the record into WriteBuffer. Note that SSL_ASYNC_getSendBuffer() may be called with the second argument (i.e., WriteBuffer) set to NULL. In this case, only the size of the current SSL record is copied into the third argument. During this step, the entire SSL record must be retrieved and sent out before another call may be made to SSL_ASYNC_sendMessage(). It is possible to retrieve the current SSL record in chunks by making multiple calls to SSL_ASYNC_getSendBuffer. If there is no more pending data in the current SSL record (i.e. the entire record is retrieved), SSL_ASYNC_getSendBuffer() sets numBytesToWrite to 0.

  • The application sends out the contents of WriteBuffer on to the network.

Zero Copy API Usage

In step 2 above, SSL_ASYNC_getSendBuffer() copies the contents of the current SSL record into the application's supplied buffer. NanoSSL provides an API to get a reference to the internal buffer that holds the current record to avoid copying the data and may improve performance. To use the zero-copy version of the API, the flow looks as follows:

    int toSend = num;
    char *pCurPtr = buf;
    LOOP until: toSend == 0
    Step 1: SSL_ASYNC_sendMessage(s->instance, (sbyte *)pCurPtr, (sbyte4)toSend, 
    &bytesSent);
    toSend -= bytesSent;
    pCurPtr += bytesSent;
    Step 2: SSL_ASYNC_getSendBufferZeroCopy(s->instance, &WriteBuffer, &numBytesToWrite)
    Step 3: numBytesWritten = WriteToTcpSocket(tcpSocket, WriteBuffer, numBytesToWrite)
    Step 4: SSL_ASYNC_freeSendBufferZeroCopy(s->instance, 0)
    END LOOP

The main difference from APP_SSL_write is the call to SSL_ASYNC_getSendBufferZeroCopy() where the function copies the address of the internal buffer holding the SSL record to WriteBuffer, transmits it over the network, and then calls SSL_ASYNC_freeSendBufferZeroCopy() to tell NanoSSL to release the internal buffer.

APP_SSL_read

The application calls APP_SSL_read to receive data on an SSL/TLS session from the peer.

The following pseudocode describes APP_SSL_read:

    Step 1: numBytesRead = ReadFromTcpSocket(tcpSocket, readBuffer,readBufferSize)
    Step 2: status = SSL_ASYNC_recvMessage2(s->instance, readBuffer, numBytesRead, 
    &pFirstUnusedByte, &bytesRemaining);
    Step 3: if (status > 0) {
    SSL_ASYNC_getRecvBuffer(s->instance, &pReadPtr, &bytesAvail, &protocol);
    COPY upto 'num' bytes from pReadPtr to 'buf'
    IF 'bytesAvail' IS_GREATER_THAN 'num' STORE (bytesAvail - num) locally
    }

The details of this process are as follows:

  • The application layer reads raw SSL data from the TCP socket.

  • The application delivers this data to the NanoSSL receive path by calling SSL_ASYNC_recvMessage2(). If the return value is greater than 0, it means there is data waiting for the application to consume.

  • The call SSL_ASYNC_getRecvBuffer() retrieves the application data. The number of bytes available is indicated in bytesAvail. If the buffer passed in to the SSL_read is smaller than bytesAvail, the application layer is responsible for storing the remaining data and returning it the next time it is called.

Sequence diagrams

Asynchronous client sequence diagram

Figure 3. Sequence flow of an SSL client application when using NanoSSL in asynchronous mode.
Sequence flow of an SSL client application when using NanoSSL in asynchronous mode.

Asynchronous server sequence diagram

Figure 4. Sequence flow of an SSL server application when using NanoSSL in asynchronous mode.
Sequence flow of an SSL server application when using NanoSSL in asynchronous mode.