Example: GSKit secure server with asynchronous data receive

This code example can be used to establish a secure server using Global Secure ToolKit (GSKit) APIs.

The server opens the socket, prepares the secure environment, accepts and processes connection requests, exchanges data with the client and ends the session. The client also opens a socket, sets up the secure environment, calls the server and requests a secure connection, exchanges data with the server, and closes the session. The following diagram and description shows the server/client flow of events.

Note: The following example programs use AF_INET address family, but they can be modified to also use the AF_INET6 address family.

Socket flow of events: Secure server that uses asynchronous data receive


GSKit secure server that uses asynchronous data receive

The following sequence of the socket calls provides a description of the graphic. It also describes the relationship between the server and client examples.

  1. The QsoCreateIOCompletionPort() function creates an I/O completion port.
  2. The pthread_create function creates a worker thread to receive data and to echo it back to the client. The worker thread waits for client requests to arrive on the I/O completion port just created.
  3. A call to gsk_environment_open() to obtain a handle to an SSL environment.
  4. One or more calls to gsk_attribute_set_xxxxx() to set attributes of the SSL environment. At a minimum, either a call to gsk_attribute_set_buffer() to set the GSK_OS400_APPLICATION_ID value or to set the GSK_KEYRING_FILE value. Only one of these should be set. It is preferred that you use the GSK_OS400_APPLICATION_ID value. Also ensure you set the type of application (client or server), GSK_SESSION_TYPE, using gsk_attribute_set_enum().
  5. A call to gsk_environment_init() to initialize this environment for SSL processing and to establish the SSL security information for all SSL sessions that run using this environment.
  6. The socket function creates a socket descriptor. The server then issues the standard set of socket calls: bind(), listen(), and accept() to enable a server to accept incoming connection requests.
  7. The gsk_secure_soc_open() function obtains storage for a secure session, sets default values for attributes, and returns a handle that must be saved and used on secure session-related function calls.
  8. One or more calls to gsk_attribute_set_xxxxx() to set attributes of the secure session. At a minimum, a call to gsk_attribute_set_numeric_value() to associate a specific socket with this secure session.
  9. A call to gsk_secure_soc_init() to initiate the SSL handshake negotiation of the cryptographic parameters.
    Note: Typically, a server program must provide a certificate for an SSL handshake to succeed. A server must also have access to the private key that is associated with the server certificate and the key database file where the certificate is stored. In some cases, a client must also provide a certificate during the SSL handshake processing. This occurs if the server to which the client is connecting has enabled client authentication. The gsk_attribute_set_buffer(GSK_OS400_APPLICATION_ID) or gsk_attribute_set_buffer(GSK_KEYRING_FILE) API calls identify (though in dissimilar ways) the key database file from which the certificate and private key that are used during the handshake are obtained.
  10. The gsk_secure_soc_startRecv() function initiates an asynchronous receive operation on a secure session.
  11. The pthread_join synchronizes the server and worker programs. This function waits for the thread to end, detaches the thread, and then returns the threads exit status to the server.
  12. The gsk_secure_soc_close() function ends the secure session.
  13. The gsk_environment_close() function closes the SSL environment.
  14. The close() function ends the listening socket.
  15. The close() ends the accepted (client connection) socket.
  16. The QsoDestroyIOCompletionPort() function destroys the completion port.

Socket flow of events: Worker thread that uses GSKit APIs

  1. After the server application creates a worker thread, it waits for server to send it the incoming client request to process client data with the gsk_secure_soc_startRecv() call. The QsoWaitForIOCompletionPort() function waits on the supplied IO completion port that was specified by the server.
  2. As soon as the client request has been received, the gsk_attribute_get_numeric_value() function gets the socket descriptor associated with the secure session.
  3. The gsk_secure_soc_write() function sends the message to the client using the secure session.
Note: By using the code examples, you agree to the terms of the Code license and disclaimer information.
/* GSK Asynchronous Server Program using Application Id*/

/* "IBM grants you a nonexclusive copyright license   */
/* to use all programming code examples from which    */
/* you can generate similar function tailored to your */
/* own specific needs.                                */
/*                                                    */
/* All sample code is provided by IBM for illustrative*/
/* purposes only. These examples have not been        */
/* thoroughly tested under all conditions. IBM,       */
/* therefore, cannot guarantee or imply reliability,  */
/* serviceability, or function of these programs.     */
/*                                                    */
/* All programs contained herein are provided to you  */
/* "AS IS" without any warranties of any kind. The    */
/* implied warranties of non-infringement,            */
/* merchantability and fitness for a particular       */
/* purpose are expressly disclaimed.  "               */

/* Assummes that application id is already registered */
/* and a certificate has been associated with the     */
/* application id.                                    */
/* No parameters, some comments and many hardcoded    */
/* values to keep it short and simple                 */

/* use following command to create bound program:     */
/* CRTBNDC PGM(PROG/GSKSERVa)                         */
/*         SRCFILE(PROG/CSRC)                         */
/*         SRCMBR(GSKSERVa)                           */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <gskssl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define _MULTI_THREADED
#include "pthread.h"
#include "qsoasync.h"
#define Failure 0
#define Success 1
#define TRUE 1
#define FALSE 0

void *workerThread(void *arg);
/********************************************************************/
/* Descriptive Name: Master thread will establish a client          */
/* connection and hand processing responsibility                    */
/* to a worker thread.                                              */
/* Note: Due to the thread attribute of this program, spawn() must  */
/* be used to invoke.                                               */
/********************************************************************/
int main(void)
{
  gsk_handle my_env_handle=NULL;    /* secure environment handle */
  gsk_handle my_session_handle=NULL;    /* secure session handle */

  struct sockaddr_in address;
  int buf_len, on = 1, rc = 0;
  int sd = -1, lsd = -1, al = -1, ioCompPort = -1;
  int successFlag = FALSE;
  char buff[1024];
  pthread_t thr;
  void *status;
  Qso_OverlappedIO_t ioStruct;

  /*********************************************/
  /* Issue all of the command in a do/while    */
  /* loop so that clean up can happen at end   */
  /*********************************************/
  do
  {
    /*********************************************/
    /* Create an I/O completion port for this    */
    /* process.                        */
    /*********************************************/
    if ((ioCompPort = QsoCreateIOCompletionPort()) < 0)
    {
      perror("QsoCreateIOCompletionPort() failed");
      break;
    }
    /*********************************************/
    /* Create a worker thread                    */
    /* to process all client requests. The       */
    /* worker thread will wait for client        */
    /* requests to arrive on the I/O completion  */
    /* port just created.                        */
    /*********************************************/
    rc = pthread_create(&thr, NULL, workerThread, &ioCompPort);
    if (rc < 0)
    {
      perror("pthread_create() failed");
      break;
    }

    /* open a gsk environment */
    rc = errno = 0;
    rc = gsk_environment_open(&my_env_handle);
    if (rc != GSK_OK)
    {
      printf("gsk_environment_open() failed with rc = %d & errno = %d.\n",
             rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* set the Application ID to use */
    rc = errno = 0;
    rc = gsk_attribute_set_buffer(my_env_handle,
                                  GSK_OS400_APPLICATION_ID,
                                  "MY_SERVER_APP",
                                  13);
    if (rc != GSK_OK)
    {
      printf("gsk_attribute_set_buffer() failed with rc = %d & errno = %d.\n"
             ,rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* set this side as the server            */
    rc = errno = 0;
    rc = gsk_attribute_set_enum(my_env_handle,
                                GSK_SESSION_TYPE,
                                GSK_SERVER_SESSION);
    if (rc != GSK_OK)
    {
      printf("gsk_attribute_set_enum() failed with rc = %d & errno = %d.\n",
             rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* by default SSL_V2, SSL_V3, and TLS_V1 are enabled  */
    /* We will disable SSL_V2 for this example.           */
    rc = errno = 0;
    rc = gsk_attribute_set_enum(my_env_handle,
                                GSK_PROTOCOL_SSLV2,
                                GSK_PROTOCOL_SSLV2_OFF);
    if (rc != GSK_OK)
    {
      printf("gsk_attribute_set_enum() failed with rc = %d & errno = %d.\n",
             rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* set the cipher suite to use.  By default our default list     */
    /* of ciphers is enabled.  For this example we will just use one */
    rc = errno = 0;
    rc = gsk_attribute_set_buffer(my_env_handle,
                                  GSK_V3_CIPHER_SPECS,
                                  "05", /* SSL_RSA_WITH_RC4_128_SHA */
                                  2);
    if (rc != GSK_OK)
    {
      printf("gsk_attribute_set_buffer() failed with rc = %d & errno = %d.\n"
             ,rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* Initialize the secure environment */
    rc = errno = 0;
    rc = gsk_environment_init(my_env_handle);
    if (rc != GSK_OK)
    {
      printf("gsk_environment_init() failed with rc = %d & errno = %d.\n",
             rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* initialize a socket to be used for listening */
    lsd = socket(AF_INET, SOCK_STREAM, 0);
    if (lsd < 0)
    {
      perror("socket() failed");
      break;
    }

    /* set socket so can be reused immediately */
    rc = setsockopt(lsd, SOL_SOCKET,
                    SO_REUSEADDR,
                    (char *)&on,
                    sizeof(on));
    if (rc < 0)
    {
      perror("setsockopt() failed");
      break;
    }

    /* bind to the local server address */
    memset((char *) &address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = 13333;
    address.sin_addr.s_addr = 0;
    rc = bind(lsd, (struct sockaddr *) &address, sizeof(address));
    if (rc < 0)
    {
      perror("bind() failed");
      break;
    }

    /* enable the socket for incoming client connections */
    listen(lsd, 5);
    if (rc < 0)
    {
      perror("listen() failed");
      break;
    }

    /* accept an incoming client connection */
    al = sizeof(address);
    sd = accept(lsd, (struct sockaddr *) &address, &al);
    if (sd < 0)
    {
      perror("accept() failed");
      break;
    }

    /* open a secure session */
    rc = errno = 0;
    rc = gsk_secure_soc_open(my_env_handle, &my_session_handle);
    if (rc != GSK_OK)
    {
      printf("gsk_secure_soc_open() failed with rc = %d & errno = %d.\n",
             rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* associate our socket with the secure session */
    rc=errno=0;
    rc = gsk_attribute_set_numeric_value(my_session_handle,
                                         GSK_FD,
                                         sd);
    if (rc != GSK_OK)
    {
      printf("gsk_attribute_set_numeric_value() failed with rc = %d ", rc);
      printf("and errno = %d.\n", errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* initiate the SSL handshake */
    rc = errno = 0;
    rc = gsk_secure_soc_init(my_session_handle);
    if (rc != GSK_OK)
    {
      printf("gsk_secure_soc_init() failed with rc = %d & errno = %d.\n",
             rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /*********************************************/
    /* Issue gsk_secure_soc_startRecv() to       */
    /* receive client request.                   */
    /* Note:                                     */
    /* postFlag == on denoting request should    */
    /* posted to the I/O completion port, even   */
    /* if request is immediately available.      */
    /* Worker thread will process client request.*/
    /*********************************************/
    /*********************************************/
    /* initialize Qso_OverlappedIO_t structure - */
    /* reserved fields must be hex 00's.         */
    /*********************************************/
    memset(&ioStruct, '\0', sizeof(ioStruct));
    memset((char *) buff, 0, sizeof(buff));
    ioStruct.buffer = buff;
    ioStruct.bufferLength = sizeof(buff);

    /*********************************************/
    /* Store the session handle in the           */
    /* Qso_OverlappedIO_t descriptorHandle field.*/
    /* This area is used to house information    */
    /* defining the state of the client          */
    /* connection. Field descriptorHandle is     */
    /* defined as a (void *) to allow the server */
    /* to address more extensive client          */
    /* connection state if needed.               */
    /*********************************************/
    ioStruct.descriptorHandle = my_session_handle;
    ioStruct.postFlag = 1;
    ioStruct.fillBuffer = 0;

    rc = gsk_secure_soc_startRecv(my_session_handle,
                                  ioCompPort,
                                  &ioStruct);
    if (rc != GSK_AS400_ASYNCHRONOUS_RECV)
    {
      printf("gsk_secure_soc_startRecv() rc = %d & errno = %d.\n",rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /*********************************************/
    /* This is where the server can loop back  */
    /* to accept a new connection.               */
    /*********************************************/

    /*********************************************/
    /* Wait for worker thread to finish          */
    /* processing client connection.             */
    /*********************************************/
    rc = pthread_join(thr, &status);


    /* check status of the worker */
    if ( rc == 0 && (rc = __INT(status)) == Success)
    {
      printf("Success.\n");
      successFlag = TRUE;
    }
    else
    {
      perror("pthread_join() reported failure");
    }
  } while(FALSE);

  /* disable the SSL session */
  if (my_session_handle != NULL)
    gsk_secure_soc_close(&my_session_handle);

  /* disable the SSL environment */
  if (my_env_handle != NULL)
    gsk_environment_close(&my_env_handle);

  /* close the listening socket */
  if (lsd > -1)
    close(lsd);
  /* close the accepted socket */
  if (sd > -1)
    close(sd);

  /* destroy the completion port */
  if (ioCompPort > -1)
    QsoDestroyIOCompletionPort(ioCompPort);

  if (successFlag)
    exit(0);
  else
    exit(-1);
}


/********************************************************************/
/* Function Name: workerThread                                      */
/*                                                                  */
/* Descriptive Name: Process client connection.                     */
/*                                                                  */
/* Note:  To make the sample more straight forward the main routine */
/*        handles all of the clean up although this function can  */
/*        be made responsible for the clientfd and session_handle.  */
/********************************************************************/
void *workerThread(void *arg)
{
    struct timeval waitTime;
    int ioCompPort = -1, clientfd = -1;
    Qso_OverlappedIO_t ioStruct;
    int rc, tID;
    int amtWritten;
    gsk_handle client_session_handle = NULL;
    pthread_t thr;
    pthread_id_np_t t_id;
    t_id = pthread_getthreadid_np();
    tID = t_id.intId.lo;
    /*********************************************/
    /* I/O completion port is passed to this */
    /* routine. */
    /*********************************************/
    ioCompPort = *(int *)arg;
    /*********************************************/
    /* Wait on the supplied I/O completion port */
    /* for a client request. */
    /*********************************************/
    waitTime.tv_sec = 500;
    waitTime.tv_usec = 0;
    rc = QsoWaitForIOCompletion(ioCompPort, &ioStruct, &waitTime);
    if ((rc == 1) &&
        (ioStruct.returnValue == GSK_OK) &&
        (ioStruct.operationCompleted == GSKSECURESOCSTARTRECV))
    /*********************************************/
    /* Client request has been received. */
    /*********************************************/
    ;
    else
    {
      perror("QsoWaitForIOCompletion()/gsk_secure_soc_startRecv() failed");
      printf("ioStruct.returnValue = %d.\n", ioStruct.returnValue);
      return __VOID(Failure);
    }

    /* write results to screen */
    printf("gsk_secure_soc_startRecv() received %d bytes, here they are:\n",
           ioStruct.secureDataTransferSize);
    printf("%s\n",ioStruct.buffer);

    /*********************************************/
    /* Obtain the session handle associated      */
    /* with the client connection.               */
    /*********************************************/
    client_session_handle = ioStruct.descriptorHandle;

    /* get the socket associated with the secure session */
    rc=errno=0;
    rc = gsk_attribute_get_numeric_value(client_session_handle,
                                         GSK_FD,
                                         &clientfd);
    if (rc != GSK_OK)
    {
      printf("gsk_attribute_get_numeric_value() rc = %d & errno = %d.\n",
             rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      return __VOID(Failure);
    }

    /* send the message to the client using the secure session */
    amtWritten = 0;
    rc = gsk_secure_soc_write(client_session_handle,
                              ioStruct.buffer,
                              ioStruct.secureDataTransferSize,
                              &amtWritten);
    if (amtWritten != ioStruct.secureDataTransferSize)
    {
      if (rc != GSK_OK)
      {
        printf("gsk_secure_soc_write() rc = %d and errno = %d.\n",
               rc,errno);
        printf("rc of %d means %s\n", rc, gsk_strerror(rc));
        return __VOID(Failure);
      }
      else
      {
        printf("gsk_secure_soc_write() did not write all data.\n");
        return __VOID(Failure);
      }
    }

    /* write results to screen */
    printf("gsk_secure_soc_write() wrote %d bytes...\n", amtWritten);
    printf("%s\n",ioStruct.buffer);

    return __VOID(Success);
} /* end workerThread */
Related concepts
Global Secure ToolKit (GSKit) APIs
Related reference
Example: Establish a secure client with Global Secure ToolKit (GSKit) APIs
Example: GSKit secure server with asynchronous handshake
Related information
QsoCreateIOCompletionPort()
pthread_create
QsoWaitForIoCompletionPort()
QsoDestroyIOCompletionPort()
bind()
socket()
listen()
close()
accept()
gsk_environment_open()
gsk_attribute_set_buffer
gsk_attribute_set_enum()
gsk_environment_init()
gsk_secure_soc_open()
gsk_attribute_set_numeric_value()
gsk_secure_soc_init()
gsk_secure_soc_startRecv()
pthread_join
gsk_secure_soc_close()
gsk_environment_close()
gsk_attribute_get_numeric_value()
gsk_secure_soc_write()