Product SiteDocumentation Site

17.2.2. Implementation TLS Clients With GNUTLS

This section describes how to implement a TLS client with full certificate validation (but without certificate revocation checking). Note that the error handling in is only exploratory and needs to be replaced before production use.
The GNUTLS library needs explicit initialization:
gnutls_global_init();
Failing to do so can result in obscure failures in Base64 decoding. See Section 17.1.2, “GNUTLS Pitfalls” for additional aspects of initialization.
Before setting up TLS connections, a credentials objects has to be allocated and initialized with the set of trusted root CAs (Example 17.9, “Initializing a GNUTLS credentials structure”).
Example 17.9. Initializing a GNUTLS credentials structure
// Load the trusted CA certificates.
gnutls_certificate_credentials_t cred = NULL;
int ret = gnutls_certificate_allocate_credentials (&cred);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_certificate_allocate_credentials: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}
// gnutls_certificate_set_x509_system_trust needs GNUTLS version 3.0
// or newer, so we hard-code the path to the certificate store
// instead.
static const char ca_bundle[] = "/etc/ssl/certs/ca-bundle.crt";
ret = gnutls_certificate_set_x509_trust_file
  (cred, ca_bundle, GNUTLS_X509_FMT_PEM);
if (ret == 0) {
  fprintf(stderr, "error: no certificates found in: %s\n", ca_bundle);
  exit(1);
}
if (ret < 0) {
  fprintf(stderr, "error: gnutls_certificate_set_x509_trust_files(%s): %s\n",
	    ca_bundle, gnutls_strerror(ret));
  exit(1);
}

After the last TLS connection has been closed, this credentials object should be freed:
gnutls_certificate_free_credentials(cred);
During its lifetime, the credentials object can be used to initialize TLS session objects from multiple threads, provided that it is not changed.
Once the TCP connection has been established, the Nagle algorithm should be disabled (see Example 17.1, “Deactivating the TCP Nagle algorithm”). After that, the socket can be associated with a new GNUTLS session object. The previously allocated credentials object provides the set of root CAs. The NORMAL set of cipher suites and protocols provides a reasonable default. Then the TLS handshake must be initiated. This is shown in Example 17.10, “Establishing a TLS client connection using GNUTLS”.
Example 17.10. Establishing a TLS client connection using GNUTLS
// Create the session object.
gnutls_session_t session;
ret = gnutls_init(&session, GNUTLS_CLIENT);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_init: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

// Configure the cipher preferences.
const char *errptr = NULL;
ret = gnutls_priority_set_direct(session, "NORMAL", &errptr);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_priority_set_direct: %s\n"
	    "error: at: \"%s\"\n", gnutls_strerror(ret), errptr);
  exit(1);
}

// Install the trusted certificates.
ret = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_credentials_set: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

// Associate the socket with the session object and set the server
// name.
gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t)(uintptr_t)sockfd);
ret = gnutls_server_name_set(session, GNUTLS_NAME_DNS,
			       host, strlen(host));
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_server_name_set: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

// Establish the session.
ret = gnutls_handshake(session);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_handshake: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}

After the handshake has been completed, the server certificate needs to be verified (Example 17.11, “Verifying a server certificate using GNUTLS”). In the example, the user-defined certificate_validity_override function is called if the verification fails, so that a separate, user-specific trust store can be checked. This function call can be omitted if the functionality is not needed.
Example 17.11. Verifying a server certificate using GNUTLS
// Obtain the server certificate chain.  The server certificate
// itself is stored in the first element of the array.
unsigned certslen = 0;
const gnutls_datum_t *const certs =
  gnutls_certificate_get_peers(session, &certslen);
if (certs == NULL || certslen == 0) {
  fprintf(stderr, "error: could not obtain peer certificate\n");
  exit(1);
}

// Validate the certificate chain.
unsigned status = (unsigned)-1;
ret = gnutls_certificate_verify_peers2(session, &status);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_certificate_verify_peers2: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}
if (status != 0 && !certificate_validity_override(certs[0])) {
  gnutls_datum_t msg;
#if GNUTLS_VERSION_AT_LEAST_3_1_4
  int type = gnutls_certificate_type_get (session);
  ret = gnutls_certificate_verification_status_print(status, type, &out, 0);
#else
  ret = -1;
#endif
  if (ret == 0) {
    fprintf(stderr, "error: %s\n", msg.data);
    gnutls_free(msg.data);
    exit(1);
  } else {
    fprintf(stderr, "error: certificate validation failed with code 0x%x\n",
	      status);
    exit(1);
  }
}

In the next step (Example 17.12, “Matching the server host name and certificate in a GNUTLS client”, the certificate must be matched against the host name (note the unusual return value from gnutls_x509_crt_check_hostname). Again, an override function certificate_host_name_override is called. Note that the override must be keyed to the certificate and the host name. The function call can be omitted if the override is not needed.
Example 17.12. Matching the server host name and certificate in a GNUTLS client
// Match the peer certificate against the host name.
// We can only obtain a set of DER-encoded certificates from the
// session object, so we have to re-parse the peer certificate into
// a certificate object.
gnutls_x509_crt_t cert;
ret = gnutls_x509_crt_init(&cert);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_x509_crt_init: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}
// The peer certificate is the first certificate in the list.
ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER);
if (ret != GNUTLS_E_SUCCESS) {
  fprintf(stderr, "error: gnutls_x509_crt_import: %s\n",
	    gnutls_strerror(ret));
  exit(1);
}
ret = gnutls_x509_crt_check_hostname(cert, host);
if (ret == 0 && !certificate_host_name_override(certs[0], host)) {
  fprintf(stderr, "error: host name does not match certificate\n");
  exit(1);
}
gnutls_x509_crt_deinit(cert);

In newer GNUTLS versions, certificate checking and host name validation can be combined using the gnutls_certificate_verify_peers3 function.
An established TLS session can be used for sending and receiving data, as in Example 17.13, “Using a GNUTLS session”.
Example 17.13. Using a GNUTLS session
char buf[4096];
snprintf(buf, sizeof(buf), "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", host);
ret = gnutls_record_send(session, buf, strlen(buf));
if (ret < 0) {
  fprintf(stderr, "error: gnutls_record_send: %s\n", gnutls_strerror(ret));
  exit(1);
}
ret = gnutls_record_recv(session, buf, sizeof(buf));
if (ret < 0) {
  fprintf(stderr, "error: gnutls_record_recv: %s\n", gnutls_strerror(ret));
  exit(1);
}

In order to shut down a connection in an orderly manner, you should call the gnutls_bye function. Finally, the session object can be deallocated using gnutls_deinit (see Example 17.14, “Using a GNUTLS session”).
Example 17.14. Using a GNUTLS session
// Initiate an orderly connection shutdown.
ret = gnutls_bye(session, GNUTLS_SHUT_RDWR);
if (ret < 0) {
  fprintf(stderr, "error: gnutls_bye: %s\n", gnutls_strerror(ret));
  exit(1);
}
// Free the session object.
gnutls_deinit(session);