HTTPS

HTTPS is a common protocol to securely transfer data and files over a network. Since the XDK does not support HTTPS natively with an own high-level API a HTTPS connection needs to be configured manually. This section will provide an introduction on how to send HTTPS messages using the ServalStack library and the low level Simplelink library.

  1. General information
    1. The HTTPS Protocol
  2. API Overview
  3. Preparation
    1. Getting the Certificate
    2. Converting the Certificate to Hex
    3. Storing the Certificate
  4. Simplelink Implementation
    1. Includes
    2. Handling the Certificate
    3. Establishing the Connection
    4. Performing A GET Request
    5. Performing A POST Request
  5. ServalStack Implementation
    1. HTTP Code Example
    2. Include the GitHub certificate
    3. Enable Security Options in the Makefile
    4. Integrate the Certificate
    5. Defining the Security Callback
    6. Adjust Connection and Port
  6. Appendix
    1. GitHub certificate hex values
    2. Formatting the HTTP response
  7. Full Code Examples
    1. Simplelink
      1. GET REQUEST
      2. POST REQUEST

General information

The HTTPS Protocol

HTTPS (HyperText Transfer Protocol Secure) is a data transfer protocol that runs on top of the TCP/IP protocol stack as shown in Picture 1. Regarding the transmission of data, HTTPS uses the same technologies and paradigms as HTTP, but unlike plain HTTP it has an extra layer for encryption. This additional layer handles digital certificates that are used for authentication via a socket by using the protocols SSL (Secure Socket Layer) or TLS (Transport Layer Security). The default port for HTTPS is the port 443.

Note: The XDK uses version 1.1 of the HTTP protocol (“HTTP/1.1”). The complete specification of this version is available in the form of an RFC document.

Image

API Overview

In general, it is recommended to develop an application based on the highest API level the XDK framework supports. The opposite will be necessary for HTTPS, since there is no high level HTTPS API, yet. Since the XDK is equipped with a Texas Instruments CC3100 Wi-Fi chip, the XDK comes with the TI Simplelink API that provides a low level access to the chip and also includes some other APIs, like the socket interface, which is necessary for implementing an HTTPS connection. Below, you can find a list of the main functions that will be used in this section. For a full description please refer to the corresponding API.

Function

Description

sl_Socket()

Create a socket instance. Returns a handle for the created socket (in form of an integer identifier)

sl_SetSockOpt()

Generic function for setting any options for the passed socket. This section will show how to set the certificate and the secure method with this function. See the API documentation to learn which other settings can be made.

sl_Connect()

Use this function to open the socket connection.

sl_Close()

Use this function to close the socket connection.

sl_Send()

Use this function to send a request via the given socket.

sl_Recv()

Use this function to receive data from the given socket.

Preparation

As mentioned above, the difference between HTTP and HTTPS is the authentication layer. The authentication requires digital certificates. The server possesses a private key for deciphering messages, and offers a public key that is used to encrypt messages. To use HTTPS, the client needs to have the correct public certificate for the requested website. The following steps will show an example on how to download, convert and include the binary of the GitHub certificate. For this, the Mozilla Firefox web browser is used. The entire project will be based on an empty XdkApplicationTemplate, which can be found in the Welcome-Screen of the XDK-Workbench.

Getting the Certificate

  • Open the GitHub website.
  • In your navigation bar a little green lock should be displayed.
  • Click it and follow the link in the first row of the appearing menu.
  • Now select “More Information”.
  • Navigate to the security tab and view the certificate information.
  • In the details tab, you will see the certificate hierarchy.
  • It is possible that the hierarchy might take a moment to appear.

For HTTPS usage, you have to download the root certificate, which is the highest one in the chain. Select the root certificate, which is the “DigiCert High Assurance EV Root CA” certificate in our example and export it to a .cer or .der file, which are the binary data types for certificates.

Image

Note: If you are having trouble with the authentication later on, it is most likely the case that you have downloaded the wrong certificate in this step. If you know the name of the certificate you can also download it immediately at the certificate authorities (CA) website. In the given example, the CA is DigiCert. You can find a list of all their root certificates here.

Converting the Certificate to Hex

The downloaded file will have the extension .cer or .der, but for usage in the XDK application, the easiest way is to convert the file into a hexadecimal format, so it can be copied into a C-array. For a C-array, the hexadecimal representation must be comma-separated and the numbers must have the leading 0x notation.

The conversion can be done in several ways.

The easiest way to convert the certificate is to use the command line tool xxd that is by default available on Linux and Mac operating systems. Use the tool as such: xxd -i path/to/the/file.cer. The output of this command is directly usable C code.

If you do not have access to a Bash Shell, you can also use multiple different online tools or programs. An easy-to-use online tool is this File to hexadecimal converter, because you can directly upload a file and get a C-formatted response. Image

Storing the Certificate

To keep the implementation code clean, the metadata of the certificate can be stored in a header file. Additionally, the HTTPS API will require the length of the certificate and the targets host name as well as a filename. Put the following code in your header file:

#define HOST_NAME "github.com"
#define CA_FILE_NAME "digicert.der"

unsigned char digicert_root_crt[] = {
  // Place your hex values here.
  // In case you had trouble with converting the binary, see the actual hex
  // values of GitHub in the appendix!
};
int digicert_root_crt_len = sizeof(digicert_root_crt);

If you are not sure if you have done the bin to hex conversion correctly, you can find the correct hex values in the appendix.

Note: Depending on the purpose of the application, it might also be useful to store the certificate on an SD card and load it on run time. In this case, please see the SdCardExample in the workbench.

Includes

The main logic of the Simplelink HTTPS implementation is based on the functions that are provided by the Simplelink API. To access them, include the API at the beginning of your implementation file. Some additional interfaces are necessary in this section, such as stdio.h for printing and PIp.h for handling IPs.

#include "stdio.h"
#include "simplelink.h"
#include "PIp.h"
#include "PAL_initialize_ih.h"

Below, you can find a list of the main functions from the Simplelink API that will be used in this section. For a full description please refer to the corresponding API.

Function

Description

sl_Socket()

Create a socket instance. Returns a handle for the created socket (in form of an integer identifier)

sl_SetSockOpt()

Generic function for setting any options for the passed socket. This section will show how to set the certificate and the secure method with this function. See the API documentation to learn which other settings can be made.

sl_Connect()

Use this function to open the socket connection.

sl_Close()

Use this function to close the socket connection.

sl_Send()

Use this function to send a request via the given socket.

sl_Recv()

Use this function to receive data from the given socket.

To perform a network request, a client needs a working network connection to the server.

To establish a connection to the local Wi-Fi with your XDK, please refer to the section Wi-Fi under Connectivity. It does not only provide information on setup and configuration of Wi-Fi, but it also features a minimal network connection snippet, which can be used in any implementation.

Handling the Certificate

Each certificate has a valid-from and a valid-until timestamp as can be seen in the screenshot below.

Image

Only in between these two dates will a HTTPS request with this certain certificate be successful. Since the XDK has no internal clock it is necessary to set a date and time. For this purpose, the actual date and time is of low importance, as long as the value is within the valid timeframe. After creating any SlDateTime_t value, the datetime can be written to the device by using the sl_DevSet function that belongs to the Device Interface of the Simplelink API.

To use the certificate, it has to be written to the flash memory of the XDK by using the functions sl_FsOpen, sl_FsWrite and sl_FsClose.

Note: The maximum size a file writer can handle is 1024 bytes. If your certificate has more than 1024 bytes you need to loop the writing.

The following code shows a simple function that implements the previously described steps. You can call it after your network setup call. As parameters, you can provide CA_FILE_NAME, digicert_root_crt and digicert_root_crt_len which you should have defined in your header file.

// Flash the Certificate BEFORE attempting to connect.
// I.e. call this function in appInitSystem() before connectServerSecure from
// Code 8
void flashCertificate(char *fileName, _u8* data, _u32 length) {
  // For the purpose of readability this code has no error handling.
  // The simplelink API provides return codes of the type _i32 that can be
  // checked for the value SL_RET_CODE_OK

  // The datetime is required for certificate validation:
  SlDateTime_t dateTime;
  dateTime.sl_tm_day = (_u32)12;
  dateTime.sl_tm_mon = (_u32)3;
  dateTime.sl_tm_year = (_u32)2017;
  dateTime.sl_tm_hour = (_u32)0;
  dateTime.sl_tm_min = (_u32)0;
  dateTime.sl_tm_sec = (_u32)0;

  sl_DevSet(
      SL_DEVICE_GENERAL_CONFIGURATION,
      SL_DEVICE_GENERAL_CONFIGURATION_DATE_TIME,

      sizeof(SlDateTime_t),
      (_u8 *)(&dateTime)
  );

  // If a file with the same name already exists, call this first:
  // sl_FsDel((_u8*) CA_FILE_NAME, 0)
  // The file handle should not be 0 after a file was successfully created:
  _i32 fileHandle = 0;

  sl_FsOpen(
      (_u8*) fileName,
      FS_MODE_OPEN_CREATE(
      1024, _FS_FILE_PUBLIC_WRITE | _FS_FILE_PUBLIC_READ
      ),
      NULL,
      &fileHandle
  );

  // If the file is longer than 1024 bytes, you need to loop the writing.
  // "length" contains the length of the certificate
  // "writtenLength" contains the amount of actually written bytes.
  _i32 writtenLen = sl_FsWrite(fileHandle, 0, data, length);

  sl_FsClose(fileHandle, NULL, NULL, 0);
}

Establishing the Connection

The connection via HTTPS is basically a normal TCP socket with some specific settings to enable the TLS certificate. For general information see the documentation of the Simplelink Socket Interface in socket.h in the SDK at xdk110 > Libraries > WiFi > 3rd-party > TI > simplelink > include.

In general, a socket can be created by calling sl_Socket(). To demand a secure socket, the value SL_SEC_SOCKET must be passed as the parameter for the protocol. The return value will be an Integer value identifying this socket. Bind it to a variable to use it in the following calls.

To bind the certificate to this socket, you can use the sl_SetSockOpt() function. Now you can connect to the chosen host by calling sl_Connect(). After successfully connecting, you can perform your requests until you close your connection with sl_Close().

// Call this function after flashCertificate() in appInitSystem()
void connectServerSecure(void){
  // For the purpose of readability this code has no error handling.
  // The simplelink API provides return codes of the type _i32 that can be
  // checked for the value SL_RET_CODE_OK

  // Getting the IP address of HOST_NAME:
  Ip_Address_T destAddr = 0;
  PAL_getIpaddress((uint8_t*) HOST_NAME, &destAddr);

  // Creating a Socket (socketHandle ≦ 0 indicates an error):
  _i16 socketHandle = sl_Socket(SL_AF_INET, SL_SOCK_STREAM, SL_SEC_SOCKET);

  // Adding the certificate to the socket:
  sl_SetSockOpt(
    socketHandle,
    SL_SOL_SOCKET,
    SL_SO_SECURE_FILES_CA_FILE_NAME,
    CA_FILE_NAME,
    strlen(CA_FILE_NAME)
  );

  // Configuration of the connection settings (IP, Port, etc.):
  SlSockAddrIn_t addr;
  addr.sin_family = SL_AF_INET;
  addr.sin_port = sl_Htons(443);
  addr.sin_addr.s_addr = destAddr;

  // Connecting:
  sl_Connect(socketHandle, ( SlSockAddr_t *)&addr, sizeof(SlSockAddrIn_t));

  // Implementation Placeholder
  // Do your request, etc. here
  // Closing:
  sl_Close(socketHandle);
}

Note: The secure method is set by default to SL_SO_SEC_METHOD_SSLv3_TLSV1_2. The following code shows how this can be changed. You can find the other possible options in the socket API.

// Use this before connecting:
SlSockSecureMethod secMethod;
secMethod.secureMethod = SL_SO_SEC_METHOD_SSLv3_TLSV1_2;
sl_SetSockOpt(
  socketHandle,
  SL_SOL_SOCKET,
  SL_SO_SECMETHOD,
  (_u8 *)&secMethod,
  sizeof(secMethod)
);

Performing A GET Request

This chapter will help you create a minimal GET request. The following snippet will perform a request for a specific page of the given host and simply prints the result to the console. The socket API is providing two basic functions for this: sl_Send() and sl_Recv(). You can copy the following function to your code and call it in your connectServerSecure() function as shown below.

// GET request for https://github.com/about
sendGetRequest(socketHandle, HOST_NAME, "/about");

void sendGetRequest(_i16 socketHandle, char* host, char* path)
{
  char outBuf[1024];
  char inBuf[1024];
  sprintf(
    outBuf,
    "GET https://%s%s HTTP/1.1\r\nHost: %s\r\n\r\n", host, path, host
  );
  sl_Send(socketHandle, (const void *) outBuf, strlen(outBuf), 0);

  sl_Recv(socketHandle, inBuf, 1024, 0);
  printf("%s\r\n", inBuf);
}

Note: This is just an example with a fixed-length char array. Increase the buffer size in the example code if necessary or use a loop.

The request above will not process the returned information in any way, since it is just a demonstration of the API functions. For a more advanced function that has some string formatting logic, please see the corresponding code in the appendix.

Performing A POST Request

This chapter will help you create a minimal POST request. The following snippet will perform a request for a specific page of the given host and send dummy JSON data to it. In addition, a minimal number of HTTP headers, which are serialized with the sprintf() function will be used. Afterwards, the server response will be printed to the console of the XDK-Workbench.

The socket API is providing two basic functions for this: sl_Send() and sl_Recv(). You can copy the following function to your code and call it in your connectServerSecure() function as shown below.

void sendPostRequest(_i16 socketHandle, char* host, char* path) {
    char sendBuffer[1024];
    char recvBuffer[512];

      _i16 bytesSent = 0;
      _i16 bytesReceived = 0;

      char* body = "{\"status\":true}";
      int length = 0;

      length += sprintf(sendBuffer+length, "POST %s HTTP/1.1\r\n",path);
      length += sprintf(sendBuffer+length, "Host: %s\r\n",host);
      //length += sprintf(sendBuffer+length, "Accept: */* \r\n");
      length += sprintf(sendBuffer+length, "Content-Length: %d\r\n",strlen(body));
      length += sprintf(sendBuffer+length, "Content-Type: application/json\r\n\r\n");

      length += sprintf(sendBuffer+length, body);
    length += sprintf(sendBuffer+length, "\r\n");

      printf("--- HTTPS Request --- \r\n");
      printf("%s", sendBuffer);
      printf("\r\n---------------\r\n");
      printf("\r\n---------------\r\n");
      bytesSent = sl_Send(socketHandle, (const void *) sendBuffer, length, 0 );
      if( bytesSent <= 0 ) {
            printf("sl_Send failed: %i\r\n", bytesSent);
            return;
      }

      printf("--- HTTPS Response --- \r\n");
        sl_Recv(socketHandle, recvBuffer, 512, 0);
        printf("%s \n\r",recvBuffer);
        printf("\r\n---------------\r\n");
}

Note: This is just an example with a minimalistic number of headers. If your destination server requires more certain headers, you can simply add them, such as the commented Accept header. Additionally, increase the required buffer size in the example code for the sendBuffer if necessary.

The request above will not process the returned information in any way, since it is just a demonstration of the API functions. It is recommended to use the corresponding code in the appendix for formatting the response correctly. Otherwise, garbage which is stored in the empty elements of the recvBuffer might be displayed in the console of the XDK-Workbench.

ServalStack Implementation

The ServalStack library offers a high level implementation for HTTP with Security options. The Security Options presented here are, strictly speaking, not changing the HTTP implementation itself. Instead, the Security Options represent a different layer. As such, the implementation presented here will be based upon an HTTP code example derived from the HTTP guide. This chapter focusses on the Security Options. For more information on the HTTP code example, please refer to the HTTP guide.

HTTP Code Example

The following code will be the basis for the HTTP portion of the HTTPS implementation. Copy this into your project’s source-file.

/* system header files */
#include <stdio.h>

/* additional interface header files */
#include "FreeRTOS.h"
#include "timers.h"
#include "BCDS_CmdProcessor.h"
#include "BCDS_Assert.h"

#include "BCDS_WlanConnect.h"
#include "BCDS_ServalPal.h"
#include "BCDS_ServalPalWiFi.h"
#include "Serval_HttpClient.h"
#include "BCDS_NetworkConfig.h"

#define HOST_NAME     "github.com"
#define PORT          80
#define WIFI_SSID     "yourWifiNetworkSSID"
#define WIFI_PSK      "yourWifiNetworkPW"

Ip_Address_T destAddr;
Ip_Port_T port;
CmdProcessor_T ServalCmdProcessor;

/* local functions */
static retcode_t onHTTPResponseReceived(HttpSession_T *httpSession, Msg_T *msg_ptr, retcode_t status) {
    (void) (httpSession);
    if (status == RC_OK && msg_ptr != NULL) {
        Http_StatusCode_T statusCode = HttpMsg_getStatusCode(msg_ptr);
        char const *contentType = HttpMsg_getContentType(msg_ptr);
        char const *content_ptr;
        unsigned int contentLength = 0;

        HttpMsg_getContent(msg_ptr, &content_ptr, &contentLength);
        char content[contentLength+1];
        strncpy(content, content_ptr, contentLength);
        content[contentLength] = 0;
        printf("HTTP RESPONSE: %d [%s]\r\n", statusCode, contentType);
        printf("%s\r\n", content);
    }

    else {
        printf("Failed to receive HTTP response!\r\n");
    }
    return(RC_OK);
}

static retcode_t onHTTPRequestSent(Callable_T *callfunc, retcode_t status) {
    (void) (callfunc);
    if (status != RC_OK) {
        printf("Failed to send HTTP request!\r\n");
    }
    return(RC_OK);
}

void createAndSendGetMessage(void){
    // assemble the request message
    Msg_T* msg_ptr;

    HttpClient_initRequest(&destAddr, port, &msg_ptr);

    HttpMsg_setReqMethod(msg_ptr, Http_Method_Get);
    HttpMsg_setReqUrl(msg_ptr, "/ip");

    // send the request
    static Callable_T sentCallable;
    Callable_assign(&sentCallable, &onHTTPRequestSent);
    HttpClient_pushRequest(msg_ptr, &sentCallable, &onHTTPResponseReceived);
}

void networkSetup(void){
    WlanConnect_SSID_T connectSSID = (WlanConnect_SSID_T) WIFI_SSID;
    WlanConnect_PassPhrase_T connectPassPhrase = (WlanConnect_PassPhrase_T) WIFI_PSK;
    WlanConnect_Init();
    WlanConnect_WPA(connectSSID, connectPassPhrase, NULL);
}

void servalPalSetup(void) {
    CmdProcessor_Initialize(&ServalCmdProcessor, "ServalCmdPrc", 3, 600, 10);
    ServalPal_Initialize(&ServalCmdProcessor);
    ServalPalWiFi_Init();
    ServalPalWiFi_StateChangeInfo_T stateChangeInfo = { SERVALPALWIFI_OPEN, INT16_C(0) };
    ServalPalWiFi_NotifyWiFiEvent(SERVALPALWIFI_STATE_CHANGE, &stateChangeInfo);
}

void prepareClient(void) {
    networkSetup();
    servalPalSetup();
    HttpClient_initialize();

    port = Ip_convertIntToPort(PORT);
    NetworkConfig_GetIpAddress((uint8_t*) HOST_NAME, &destAddr);
}

void prepareSecurityOptions(void) {
  // most of the new code will be included here
}

void appInitSystem(void * CmdProcessorHandle, uint32_t param2)
{
    if (CmdProcessorHandle == NULL)
    {
        printf("Command processor handle is null \n\r");
        assert(false);
    }
    BCDS_UNUSED(param2);

    prepareClient();
    prepareSecurityOptions();
    createAndSendGetMessage();
}

After changing the Wi-Fi SSID and the Password to your local SSID and Password, the project can already be compiled and flashed onto the XDK. If more sophisticated Wi-Fi options are necessary, please refer to the Wi-Fi article in Connectivity. A request will then be send to github.com using HTTP. The response will be empty with status 301, representing the status message Moved Permanently. This is because github.com can only be accessed using HTTPS.

Part of the code is the declaration and call of the function prepareSecurityOptions(). This function will be filled with most of the functions that need to be called to enable the security feature. The following chapters focus on what changes need to be made to this code example to send an HTTPS request instead of plain HTTP.

Include the GitHub certificate

As a first step, the GitHub certificate has to be made accessible in the code. For this, the certificate can be either:

  • Put in a header-file, which is then included in the code
  • Put directly into the code

Both options will serve the purpose of this guide. For the rest of the guide, it is assumed that the following variables are available in the source file, and filled with the certificate and the certificate’s length respectively:

unsigned char digicert_root_crt[];
int digicert_root_crt_len;

Enable Security Options in the Makefile

Per default, Security Options in the ServalStack Library are unavailable. Thus, they first have to be enabled by exporting Macros. The macros will enable TLS for ServalStack Clients, define the Cipher that is used and increase the size and amount of ServalStack messages that can be send at a time. This is because establishing a secure connection requires more messages than plain HTTP, due to the handshake messages.

export SERVAL_ENABLE_TLS_CLIENT=1
export SERVAL_ENABLE_TLS_ECC=1
export SERVAL_ENABLE_TLS_PSK=0
export SERVAL_ENABLE_DTLS_PSK=0
export SERVAL_ENABLE_DTLS_ECC=1
export SERVAL_MAX_NUM_MESSAGES=8
export SERVAL_MAX_SIZE_APP_PACKET=900
export SERVAL_ENABLE_TLS=1
export EscTls_CIPHER_SUITE=TLS_RSA_WITH_AES_256_CBC_SHA256

export BCDS_SERVALSTACK_MACROS = \
    -D SERVAL_CYCURTLS_HANDSHAKE_BUFFER_SIZE=5500

The effect of the exports in the makefile are the following:

  • SERVAL_ENABLE_TLS_CLIENT – Per default, ServalStack does not allow TLS Client connections, thus they have to be enabled first.
  • SERVAL_ENABLE_TLS_ECC and SERVAL_ENABLE_TLS_PSK – Per default, the ServalStack uses Pre Shared Keys for TLS connections, but for HTTPS, certificates are used. Thus, certificates have to be enabled, and PSK have to be disabled, since both cannot be enabled at the same time.
  • SERVAL_ENABLE_DTLS_ECC and SERVAL_ENABLE_DTLS_PSK – Same as for TLS.
  • SERVAL_MAX_NUM_MESSAGES – During a secured connection, the number of maximum simultaneous messages is greater than usual. Hence, the maximum must be raised.
  • SERVAL_MAX_SIZE_APP_PACKET – Since at some point the certificate must be sent, the maximum size of packets must be raised to accommodate the length of the certificate.
  • SERVAL_ENABLE_TLS – TLS must be enabled for the application.
  • EscTls_CIPHER_SUITE – Using this macro, a cipher suite can be chosen. Client and Server will negotiate a cipher to encrypt / decrypt the messages during the handshake. Thus, both must be able to use the same cipher.
  • SERVAL_CYCURTLS_HANDSHAKE_BUFFER_SIZE – To establish a connection to the HTTPS server, a handshake must be completed between client and server first. These are a few control messages to ensure that the connection is truly secure on both ends. As the application must buffer the content of those messages until the handshake is completed, an appropriate buffer size is allocated.

Note: These values should not be set from within the SDK files, as they would affect all other applications as well and may produce unexpected results.

Integrate the Certificate

To use the certificate, it must first be parsed and made available to the ServalStack API. For this, the following header-files must be included first.

#include "Serval_Security.h"
#include "cycurlib_config.h"
#include "tls_core.h"
#include "x509.h"

The first include is the ServalStack’s Security API. This is the interface between the application and the ServalStack’s Security Options. The other three includes are part of the third party encryption library provided by ESCRPYT GmbH for embedded security. These will be used to embed the GitHub Certificate for use in the ServalStack API.

First of all, the certificate has to be parsed using the ESCRPYT library. For this, first declare a global variable of type EscX509_CertificateT called trustedCertificate. This variable will be used to hold the parsed certificate. To parse the certificate stored in digicert_root_crt, call the following function within prepareSecurityOptions():

EscX509_Parse(&trustedCertificate, digicert_root_crt, digicert_root_crt_len);

Defining the Security Callback

Now, the certificate is available in a format that the ServalStack’s Security API understands. But, the certificate is not yet known to the Security API. It will be made available during the sending process itself. For this, a callback will be defined, that is called during the sending process by the ServalStack. This callback will retrieve security information for the API during sending.

The following code shows the implementation of such a callback.

static retcode_t httpsSecurityCallback(SecurityToken_T token, SecurityData_T* tokenData)
{
    switch (token.type) {
        default:
            return RC_DTLS_TOKEN_NOT_PROVIDED;
    }
}

This callback basically consists of a switch-statement concerning token.type. Per default, the function returns RC_DTLS_TOKEN_NOT_PROVIDED, to indicate that a specific token is not provided by this callback. The token can be of one of the following seven types.

  1. CURRENT_TIME
  2. CIPHERS
  3. PSK_SERVER_ID_HINT
  4. PSK_PEER_KEY_AND_ID
  5. CRT_CA
  6. CRT_PEER_NAME
  7. CRT_OWN_CERTIFICATE

The types that are relevant in this HTTPS guide are marked bold. Of those three, we will only use CRT_CA and CURRENT_TIME

Thus, the first case to add is CRT_CA. This one is essential, since the certificate will be returned to the security API in this case. To return the certificate, add the following code to the switch statement:

case CRT_CA:
    printf("securityCallback - CRT_CA request\r\n");
    return Serval_copyToOutputBuffer(
        &tokenData->caData.trustedCertificates,
        (const char*) &trustedCertificate,
        sizeof(trustedCertificate)
    );

The function Serval_copyToOutputBuffer() is used to copy the certificate to the certificate field of tokenData. Afterwards, the ServalStack’s API has the certificate available for use internally.

Usually, there is a validity period for a certificate, and the Wi-Fi chip of the XDK needs to know the current time. Thus, the second case to add is CURRENT_TIME. When this case occurs, the callback returns the time to be used for the HTTPS connection. To return the time, add the following code to the switch statement:

case CURRENT_TIME:
    tokenData->timeData.timestamp = (UNIX_TIMESTAMP_OFFSET + 1522857145);
    return RC_OK;

If a time is set, RC_OK must be returned by the callback. The time in this code example is set to the first of April 2018, 00:00:00. ServalStack expects an NTP timestamp with the epoch 1900-01-01 00:00:00, but if a UNIX timestamp is used instead, then the UNIX_TIMESTAMP_OFFSET must be added to the timestamp. Otherwise, if time is not set (successfully), then RC_DTLS_TOKEN_NOT_PROVIDED must be returned. If no time is set, but RC_DTLS_TOKEN_NOT_PROVIDED is not returned, then the connection will fail most likely fail.

With this, the security callback is implemented as needed. If it is required for the use case, the Security Callback can return based on the destination address. For this, the tokenData input provides the field peer, representing the destination server. The following code shows how to print the destination IP as an unsigned 32 bit integer from within the security callback:

printf("peer address: %lu\n\r", *(tokenData->peer.address));

Finally, the security callback must be given to the Security API. For this, add the following line to prepareSecurityOptions():

Security_setCallback(securityCallback);

At this point, the functions securityCallback and prepareSecurityOptions() should have the following implementation:

static retcode_t securityCallback(SecurityToken_T token, SecurityData_T* tokenData)
{
    printf("peer address: %lu\n\r", *(tokenData->peer.address));
    switch (token.type) {
        case CURRENT_TIME:
            tokenData->timeData.timestamp = (UNIX_TIMESTAMP_OFFSET + 1522857145);
            return RC_OK;
        case CRT_CA:
            printf("securityCallback - CRT_CA request\r\n");
            return Serval_copyToOutputBuffer(
                &tokenData->caData.trustedCertificates,
                (const char*) &trustedCertificate,
                sizeof(trustedCertificate)
            );

        default:
            return RC_DTLS_TOKEN_NOT_PROVIDED;
    }
}

void prepareSecurityOptions(void) {
    EscX509_Parse(&trustedCertificate, digicert_root_crt, digicert_root_crt_len);
    Security_setCallback(securityCallback);
}

Adjust Connection and Port

As a last step, the HTTP code base must be adjusted to send a request via a secure connection. For this, the API to initialize a request has to be exchanged and the port must be adjusted as well.

The port can be set the constant PORT to 443. This constant is set to 80 initially.

To send initialize a secure connection, change the call of HttpClient_initRequest() to HttpClient_initSecureRequest(). The input values stay the same, as there is no difference between those calls, other than the fact that the ServalStack’s security API will be involved in the sending process.

Finally, all the changes are made and the application be compiled, flashed and run on the XDK.

Appendix

GitHub certificate hex values

In case you had any trouble with getting or converting the GitHub Certificate, you can copy the following lines:

unsigned char digicert_root_crt[] = {
  0x30, 0x82, 0x03, 0xc5, 0x30, 0x82, 0x02, 0xad, 0xa0, 0x03, 0x02, 0x01,
  0x02, 0x02, 0x10, 0x02, 0xac, 0x5c, 0x26, 0x6a, 0x0b, 0x40, 0x9b, 0x8f,
  0x0b, 0x79, 0xf2, 0xae, 0x46, 0x25, 0x77, 0x30, 0x0d, 0x06, 0x09, 0x2a,
  0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x6c,
  0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55,
  0x53, 0x31, 0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c,
  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63,
  0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77,
  0x77, 0x77, 0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e,
  0x63, 0x6f, 0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03,
  0x13, 0x22, 0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48,
  0x69, 0x67, 0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63,
  0x65, 0x20, 0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41,
  0x30, 0x1e, 0x17, 0x0d, 0x30, 0x36, 0x31, 0x31, 0x31, 0x30, 0x30, 0x30,
  0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x31, 0x31, 0x31, 0x31,
  0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x6c, 0x31, 0x0b,
  0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31,
  0x15, 0x30, 0x13, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x0c, 0x44, 0x69,
  0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x49, 0x6e, 0x63, 0x31, 0x19,
  0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x0b, 0x13, 0x10, 0x77, 0x77, 0x77,
  0x2e, 0x64, 0x69, 0x67, 0x69, 0x63, 0x65, 0x72, 0x74, 0x2e, 0x63, 0x6f,
  0x6d, 0x31, 0x2b, 0x30, 0x29, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x22,
  0x44, 0x69, 0x67, 0x69, 0x43, 0x65, 0x72, 0x74, 0x20, 0x48, 0x69, 0x67,
  0x68, 0x20, 0x41, 0x73, 0x73, 0x75, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x20,
  0x45, 0x56, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x41, 0x30, 0x82,
  0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d,
  0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82,
  0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc6, 0xcc, 0xe5, 0x73, 0xe6,
  0xfb, 0xd4, 0xbb, 0xe5, 0x2d, 0x2d, 0x32, 0xa6, 0xdf, 0xe5, 0x81, 0x3f,
  0xc9, 0xcd, 0x25, 0x49, 0xb6, 0x71, 0x2a, 0xc3, 0xd5, 0x94, 0x34, 0x67,
  0xa2, 0x0a, 0x1c, 0xb0, 0x5f, 0x69, 0xa6, 0x40, 0xb1, 0xc4, 0xb7, 0xb2,
  0x8f, 0xd0, 0x98, 0xa4, 0xa9, 0x41, 0x59, 0x3a, 0xd3, 0xdc, 0x94, 0xd6,
  0x3c, 0xdb, 0x74, 0x38, 0xa4, 0x4a, 0xcc, 0x4d, 0x25, 0x82, 0xf7, 0x4a,
  0xa5, 0x53, 0x12, 0x38, 0xee, 0xf3, 0x49, 0x6d, 0x71, 0x91, 0x7e, 0x63,
  0xb6, 0xab, 0xa6, 0x5f, 0xc3, 0xa4, 0x84, 0xf8, 0x4f, 0x62, 0x51, 0xbe,
  0xf8, 0xc5, 0xec, 0xdb, 0x38, 0x92, 0xe3, 0x06, 0xe5, 0x08, 0x91, 0x0c,
  0xc4, 0x28, 0x41, 0x55, 0xfb, 0xcb, 0x5a, 0x89, 0x15, 0x7e, 0x71, 0xe8,
  0x35, 0xbf, 0x4d, 0x72, 0x09, 0x3d, 0xbe, 0x3a, 0x38, 0x50, 0x5b, 0x77,
  0x31, 0x1b, 0x8d, 0xb3, 0xc7, 0x24, 0x45, 0x9a, 0xa7, 0xac, 0x6d, 0x00,
  0x14, 0x5a, 0x04, 0xb7, 0xba, 0x13, 0xeb, 0x51, 0x0a, 0x98, 0x41, 0x41,
  0x22, 0x4e, 0x65, 0x61, 0x87, 0x81, 0x41, 0x50, 0xa6, 0x79, 0x5c, 0x89,
  0xde, 0x19, 0x4a, 0x57, 0xd5, 0x2e, 0xe6, 0x5d, 0x1c, 0x53, 0x2c, 0x7e,
  0x98, 0xcd, 0x1a, 0x06, 0x16, 0xa4, 0x68, 0x73, 0xd0, 0x34, 0x04, 0x13,
  0x5c, 0xa1, 0x71, 0xd3, 0x5a, 0x7c, 0x55, 0xdb, 0x5e, 0x64, 0xe1, 0x37,
  0x87, 0x30, 0x56, 0x04, 0xe5, 0x11, 0xb4, 0x29, 0x80, 0x12, 0xf1, 0x79,
  0x39, 0x88, 0xa2, 0x02, 0x11, 0x7c, 0x27, 0x66, 0xb7, 0x88, 0xb7, 0x78,
  0xf2, 0xca, 0x0a, 0xa8, 0x38, 0xab, 0x0a, 0x64, 0xc2, 0xbf, 0x66, 0x5d,
  0x95, 0x84, 0xc1, 0xa1, 0x25, 0x1e, 0x87, 0x5d, 0x1a, 0x50, 0x0b, 0x20,
  0x12, 0xcc, 0x41, 0xbb, 0x6e, 0x0b, 0x51, 0x38, 0xb8, 0x4b, 0xcb, 0x02,
  0x03, 0x01, 0x00, 0x01, 0xa3, 0x63, 0x30, 0x61, 0x30, 0x0e, 0x06, 0x03,
  0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x01, 0x86,
  0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05,
  0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e,
  0x04, 0x16, 0x04, 0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47,
  0x01, 0xd4, 0x98, 0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3,
  0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
  0x14, 0xb1, 0x3e, 0xc3, 0x69, 0x03, 0xf8, 0xbf, 0x47, 0x01, 0xd4, 0x98,
  0x26, 0x1a, 0x08, 0x02, 0xef, 0x63, 0x64, 0x2b, 0xc3, 0x30, 0x0d, 0x06,
  0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00,
  0x03, 0x82, 0x01, 0x01, 0x00, 0x1c, 0x1a, 0x06, 0x97, 0xdc, 0xd7, 0x9c,
  0x9f, 0x3c, 0x88, 0x66, 0x06, 0x08, 0x57, 0x21, 0xdb, 0x21, 0x47, 0xf8,
  0x2a, 0x67, 0xaa, 0xbf, 0x18, 0x32, 0x76, 0x40, 0x10, 0x57, 0xc1, 0x8a,
  0xf3, 0x7a, 0xd9, 0x11, 0x65, 0x8e, 0x35, 0xfa, 0x9e, 0xfc, 0x45, 0xb5,
  0x9e, 0xd9, 0x4c, 0x31, 0x4b, 0xb8, 0x91, 0xe8, 0x43, 0x2c, 0x8e, 0xb3,
  0x78, 0xce, 0xdb, 0xe3, 0x53, 0x79, 0x71, 0xd6, 0xe5, 0x21, 0x94, 0x01,
  0xda, 0x55, 0x87, 0x9a, 0x24, 0x64, 0xf6, 0x8a, 0x66, 0xcc, 0xde, 0x9c,
  0x37, 0xcd, 0xa8, 0x34, 0xb1, 0x69, 0x9b, 0x23, 0xc8, 0x9e, 0x78, 0x22,
  0x2b, 0x70, 0x43, 0xe3, 0x55, 0x47, 0x31, 0x61, 0x19, 0xef, 0x58, 0xc5,
  0x85, 0x2f, 0x4e, 0x30, 0xf6, 0xa0, 0x31, 0x16, 0x23, 0xc8, 0xe7, 0xe2,
  0x65, 0x16, 0x33, 0xcb, 0xbf, 0x1a, 0x1b, 0xa0, 0x3d, 0xf8, 0xca, 0x5e,
  0x8b, 0x31, 0x8b, 0x60, 0x08, 0x89, 0x2d, 0x0c, 0x06, 0x5c, 0x52, 0xb7,
  0xc4, 0xf9, 0x0a, 0x98, 0xd1, 0x15, 0x5f, 0x9f, 0x12, 0xbe, 0x7c, 0x36,
  0x63, 0x38, 0xbd, 0x44, 0xa4, 0x7f, 0xe4, 0x26, 0x2b, 0x0a, 0xc4, 0x97,
  0x69, 0x0d, 0xe9, 0x8c, 0xe2, 0xc0, 0x10, 0x57, 0xb8, 0xc8, 0x76, 0x12,
  0x91, 0x55, 0xf2, 0x48, 0x69, 0xd8, 0xbc, 0x2a, 0x02, 0x5b, 0x0f, 0x44,
  0xd4, 0x20, 0x31, 0xdb, 0xf4, 0xba, 0x70, 0x26, 0x5d, 0x90, 0x60, 0x9e,
  0xbc, 0x4b, 0x17, 0x09, 0x2f, 0xb4, 0xcb, 0x1e, 0x43, 0x68, 0xc9, 0x07,
  0x27, 0xc1, 0xd2, 0x5c, 0xf7, 0xea, 0x21, 0xb9, 0x68, 0x12, 0x9c, 0x3c,
  0x9c, 0xbf, 0x9e, 0xfc, 0x80, 0x5c, 0x9b, 0x63, 0xcd, 0xec, 0x47, 0xaa,
  0x25, 0x27, 0x67, 0xa0, 0x37, 0xf3, 0x00, 0x82, 0x7d, 0x54, 0xd7, 0xa9,
  0xf8, 0xe9, 0x2e, 0x13, 0xa3, 0x77, 0xe8, 0x1f, 0x4a
};

Formatting the HTTP response

The code below is an example of how the response could be formatted. Please note that this code only prints 10 lines and then breaks the loop, since it is just showing the general scheme. Feel free to increase the number up to the length you want.

void sendGetRequest(_i16 socketHandle, char* host, char* path) {

    char inBuf[1024];
    _i16 bytesReceived = 0;

    // Perform GET or POST request here

    printf("HTTP response:\r\n");
    printf("\r\n======\r\n");

    int linesPrinted = 0;

    do {
        bytesReceived = sl_Recv(socketHandle, inBuf, 1024, 0);
        int lastStart = 0;
        int pos = 0;
        while (linesPrinted < 10 && pos < bytesReceived) {
            if (inBuf[pos] == '\n') {
                printf("%.*s", pos - lastStart - 1, inBuf + lastStart);
                printf("\r\n"); lastStart = pos; linesPrinted++;
            }
            pos++;
        }
    } while(bytesReceived > 0);

    if (linesPrinted == 10) {
        printf("...\r\n");
    }
    printf("\r\n======\r\n");
}