Example: GSKit secure server with asynchronous handshake

The gsk_secure_soc_startInit() API allows you to create secure server applications that can handle requests asynchronously.

The following example illustrates how this API can be used. It is similar to the GSKit secure server with asynchronous data receive example, but uses this API to start a secure session.

The following graphic shows the API calls that are used to negotiate an asynchronous handshake on a secure server.

Socket flow of events: GSKit secure server that uses asynchronous handshake

To view the client portion of this graphic, see GSKit client.

Socket flow of events: GSKit secure server that uses asynchronous handshake

This flow describes the socket calls in the following example application.

  1. The QsoCreateIOCompletionPort() API creates an I/O completion port.
  2. The pthread_create() API creates a worker thread to process all client requests. 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 API 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() API 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 API 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. The gsk_secure_soc_startInit() API starts an asynchronous negotiation of a secure session, using the attributes set for the SSL environment and the secure session. Control returns to the program here. When the handshake processing is completed, the completion port is posted with the results. The thread can continue on with other processing; however, for simplicity, wait here for the worker thread to complete.
    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, which the client is connecting to, has enabled client authentication. The gsk_attribute_set_buffer(GSK_OS400_APPLICATION_ID) or gsk_attribute_set_buffer(GSK_KEYRING_FILE) API call identifies (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 pthread_join synchronizes the server and worker programs. This API waits for the thread to end, detaches the thread, and then returns the thread's exit status to the server.
  11. The gsk_secure_soc_close() API ends the secure session.
  12. The gsk_environment_close() API closes the SSL environment.
  13. The close() API ends the listening socket.
  14. The close() API ends the accepted (client connection) socket.
  15. The QsoDestroyIOCompletionPort() API destroys the completion port.

Socket flow of events: Worker thread that process secure asynchronous requests

  1. After the server application creates a worker thread, it waits for the server to send it the incoming client request to process. The QsoWaitForIOCompletionPort() API waits for the supplied I/O completion port that was specified by the server. This call waits until the gsk_secure_soc_startInit() call is completed.
  2. As soon as the client request has been received, the gsk_attribute_get_numeric_value() API gets the socket descriptor associated with the secure session.
  3. The gsk_secure_soc_read() API receives a message from the client using the secure session.
  4. The gsk_secure_soc_write() API sends the message to the client using the secure session.
Note: By using the examples, you agree to the terms of the Code license and disclaimer information.
Start of change
/* GSK Asynchronous Server Program using Application Id*/
/* and gsk_secure_soc_startInit()                     */

/* 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(MYLIB/GSKSERVSI)                       */
/*         SRCFILE(MYLIB/CSRC)                        */
/*         SRCMBR(GSKSERVSI)                          */

#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_in6 address;
  int buf_len, on = 1, rc = 0;
  int sd = -1, lsd = -1, al, ioCompPort = -1;
  int successFlag = FALSE;
  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;
    printf("gsk_environment_open()\n");
    rc = gsk_environment_open(&my_env_handle);
    if (rc != GSK_OK)
    {
    printf("gsk_environment_open() failed with rc = %d and 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 and 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 and 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 and 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 and errno = %d.\n"
             ,rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }

    /* Initialize the secure environment */
    rc = errno = 0;
    printf("gsk_environment_init()\n");
    rc = gsk_environment_init(my_env_handle);
    if (rc != GSK_OK)
    {
      printf("gsk_environment_init() failed with rc = %d and 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 */
    printf("socket()\n");
    lsd = socket(AF_INET6, 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.sin6_family = AF_INET6;
    address.sin6_port = 13333;
    memcpy(&address.sin6_addr, &in6addr_any, sizeof(in6addr_any));
    printf("bind()\n");
    rc = bind(lsd, (struct sockaddr *) &address, sizeof(address));
    if (rc < 0)
    {
      perror("bind() failed");
      break;
    }

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

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

    /* open a secure session */
    rc = errno = 0;
    printf("gsk_secure_soc_open()\n");
    rc = gsk_secure_soc_open(my_env_handle, &my_session_handle);
    if (rc != GSK_OK)
    {
      printf("gsk_secure_soc_open() failed with rc = %d and 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;
    }

    /*********************************************/
    /* Issue gsk_secure_soc_startInit() to       */
    /* process SSL Handshake flow asynchronously  */
    /*********************************************/
    /*********************************************/
    /* initialize Qso_OverlappedIO_t structure - */
    /* reserved fields must be hex 00's.         */
    /*********************************************/
    memset(&ioStruct, '\0', sizeof(ioStruct));

    /*********************************************/
    /* 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;

    /* initiate the SSL handshake */
    rc = errno = 0;
    printf("gsk_secure_soc_startInit()\n");
    rc = gsk_secure_soc_startInit(my_session_handle, ioCompPort, &ioStruct);
    if (rc != GSK_OS400_ASYNCHRONOUS_SOC_INIT)
    {
      printf("gsk_secure_soc_startInit() rc = %d and errno = %d.\n",rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      break;
    }
    else
      printf("gsk_secure_soc_startInit got GSK_OS400_ASYNCHRONOUS_SOC_INIT\n");

    /*********************************************/
    /* 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);

  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, clientfd;
    Qso_OverlappedIO_t ioStruct;
    int rc, tID;
    int amtWritten, amtRead;
    char buff[1024];
    gsk_handle client_session_handle;
    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 the SSL handshake to complete.        */
    /*********************************************/
    waitTime.tv_sec = 500;
    waitTime.tv_usec = 0;

    sleep(4);
    printf("QsoWaitForIOCompletion()\n");
    rc = QsoWaitForIOCompletion(ioCompPort, &ioStruct, &waitTime);
    if ((rc == 1) &&
        (ioStruct.returnValue == GSK_OK) &&
        (ioStruct.operationCompleted == GSKSECURESOCSTARTINIT))
    /*********************************************/
    /* SSL Handshake has completed.              */
    /*********************************************/
    ;
    else
    {
      printf("QsoWaitForIOCompletion()/gsk_secure_soc_startInit() failed.\n");
      printf("rc == %d, returnValue - %d, operationCompleted = %d\n",
             rc, ioStruct.returnValue, ioStruct.operationCompleted);
      perror("QsoWaitForIOCompletion()/gsk_secure_soc_startInit() failed");
      return __VOID(Failure);
    }

    /*********************************************/
    /* 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;
    printf("gsk_attribute_get_numeric_value()\n");
    rc = gsk_attribute_get_numeric_value(client_session_handle,
                                         GSK_FD,
                                         &clientfd);
    if (rc != GSK_OK)
    {
      printf("gsk_attribute_get_numeric_value() rc = %d and errno = %d.\n",
             rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      return __VOID(Failure);
    }
    /* memset buffer to hex zeros */
    memset((char *) buff, 0, sizeof(buff));
    amtRead = 0;
    /* receive a message from the client using the secure session */
    printf("gsk_secure_soc_read()\n");
    rc = gsk_secure_soc_read(client_session_handle,
                             buff,
                             sizeof(buff),
                             &amtRead);

    if (rc != GSK_OK)
    {
      printf("gsk_secure_soc_read() rc = %d and errno = %d.\n",rc,errno);
      printf("rc of %d means %s\n", rc, gsk_strerror(rc));
      return;
    }

    /* write results to screen */
    printf("gsk_secure_soc_read() received %d bytes, here they are ...\n",
         amtRead);
    printf("%s\n",buff);


    /* send the message to the client using the secure session */
    amtWritten = 0;
    printf("gsk_secure_soc_write()\n");
    rc = gsk_secure_soc_write(client_session_handle,
                              buff,
                              amtRead,
                              &amtWritten);
    if (amtWritten != amtRead)
    {
      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",buff);

    return __VOID(Success);
}
/* end workerThread */
End of change