Implementing a Simple TLS Client

This section illustrates use of the TLS class to implement a simple SSL or TLS client. For background information, we refer the reader to RFC 2246.

First, assume that we have instantiated a TLS object, set up a pseudorandom number generator, and have all certificates required for client and server authentication:

TLS tls;            // instantiate a %TLS object
PRNG prng;          // and a PRNG
cstr rootCerts;     // trust anchors for server certificate authentication
cstr userCerts;     // user's certificate chain for client authentication

To initialize the TLS object, we:

int Init(int cipher, int minor, TokenSignCallback tcb, void *tokeninfoptr)
{
  tls.setcipher(cipher,minor);
  tls.setrand(prng.gens(46),prng.gens(28),prng.gens(20),cdk::timegmt());
  tls.sign = tcb;
  tls.tokeninfoptr = tokeninfoptr;
  return 0;
}

Our basic messaging functions assume the existence of an open communications channel to the server. Here an undocumented object ('sock') will provide that functionality via its Send() and Recv() functions. Using sock, SendRecv() sends anything that might be in the TLS client buffer, then populates the TLS server buffer with whatever data has been received from the server:

int SendRecv()
{
  // returns: 0=did nothing, 1=something, 2=error
  if (+tls.c.buf)
  {
    int i = sock.Send(tls.c.buf);
    if (i != +tls.c.buf) return 2;
    tls.c.buf = 0;
    return 1;
  }
  Sleep(200);
  str x = sock.Recv();
  tls.s.buf += x;
  return !!+x;
}

Get() returns a complete communications record from the server, decrypting it if necessary:

str Get()
{
  if (m_bHTTPS)
  {
    for (;;)
    {
      if (isBad()) return 0;
      str x, y;
      int i = tls.parse(x,y);
      if (i == 0)
      {
        tls.s.buf = y;
        if (tls.unwrap(x,y)) return 0;
        return y;
      }
      if (i == 2) break;
      Sleep(90);
      tls.s.buf += sock.Recv();
    }
  }
  else
  {
    Sleep(90);
    str y = sock.Recv();
    return y;
  }
  return 0;
}

To connect to a server and 'GET' a page, we simply:

str GETpage(cstr addr, cstr file, int port)
{
  int i;
  sock.initconnection(addr,port);
  if (m_bHTTPS) 
  {
    tls.load(userCerts);
    for (;;)
    {
      i = tls.dorecs();
      if (i > 1) return error(tls.lasterror);
      if (tls.isHot()) break;
      if (tls.isBad()) return error(tls.lasterror);
      i = SendRecv();
      if (i > 1) return error(errProtocolFailure);
    }
    i = 0;
    // validate the server certificate:
    if (+rootCerts)
      i = CheckCA(rootCerts,tls.s.cer);
    else if (cbvalidatecert != NULL)
    {
      cdk::tokenop t;
      t.cer = tls.s.cer;
      i = cbvalidatecert(t);
    }
    if (i) return error(i); 
  }
  str data = str("GET /") + file + " HTTP/1.0\r\n\r\n";
  str x = data;
  if (m_bHTTPS) x = tls.wrap(data);
  i = sock.Send(x);
  if (i < 0) return error(errProtocolFailure);
  
  x = 0;
  long t = timegmt();
  do x += Get(); while (!isComplete(x) && t + 30 > timegmt());
  return x; 
}

POSTing is similar. We:

str POSTpage(const str &addr, const str &file, const str &postdata, int port)
{
  int i;
  sock.initconnection(addr,port);
  if (m_bHTTPS)
  {
    tls.load(userCerts);
    for (;;)
    {
      i = tls.dorecs();
      if (i > 1) return error(tls.lasterror);
      if (tls.isHot()) break;
      if (tls.isBad()) return error(tls.lasterror);
      i = SendRecv();
      if (i > 1) return error(errProtocolFailure);
    }
    i = 0;
    // validate the server certificate:
    if (+rootCerts)
      i = CheckCA(rootCerts,tls.s.cer);
    else if (cbvalidatecert != NULL)
    {
      cdk::tokenop t;
      t.cer = tls.s.cer;
      t.tokeninfoptr = tls.tokeninfoptr;
      i = cbvalidatecert(t); // 0 OK non-zero bad.
    }
    if (i) return error(i);
  }
  char szPD[32];
  sprintf(szPD, "%d", postdata.length());
  str header = str("POST /") + file + str(" HTTP/1.0\n")
    + str("Content-type: application/x-www-form-urlencoded\n")
    + str("Content-length: ") + str(szPD)
    + str("\n\n");
  str data = header + postdata;
  str x = data;
  if (m_bHTTPS) x = tls.wrap(data);
  i = sock.Send(x);
  if (i < 0) return error(errProtocolFailure);

  x = 0;
  long t = timegmt();
  do x += Get(); while (!isComplete(x) && t + 30 > timegmt());
  return x; 
}

In both GETpage() and POSTpage() we used the following auxiliary function to determine when a message from the server is complete:

int isComplete(str a)
{
  int start = a.find("\r\n\r\n");
  if (start < 0) return 0;
  int i = a.find("Content-Length: ");
  if (i >= 0) return +a >= start + 4 + atoi(a.c_str()+i+16);
  if (+a > start && a[start] == 0x30)
    return asn(a.skip(start)).isParseable();
  return a.find("</html>") >= 0 || a.find("</HTML>") >= 0;
}

See RFC 2246 for more information.


ISC Cryptographic Development Kit - User's Guide
ISC website
Questions? E-mail ISC technical support
Copyright© 2002-2006 Information Security Corp. All rights reserved.