Enable tls psk
[libradsec.git] / tls.c
diff --git a/tls.c b/tls.c
index ecbbbec..ba3cab5 100644 (file)
--- a/tls.c
+++ b/tls.c
-/*
- * Copyright (C) 2006-2008 Stig Venaas <venaas@uninett.no>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- */
+/* Copyright 2010-2013 NORDUnet A/S. All rights reserved.
+   See LICENSE for licensing information. */
+
+#if defined HAVE_CONFIG_H
+#include <config.h>
+#endif
 
-#include <signal.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <netdb.h>
-#include <string.h>
+#include <stdlib.h>
 #include <unistd.h>
-#include <limits.h>
-#ifdef SYS_SOLARIS9
+#include <assert.h>
 #include <fcntl.h>
-#endif
-#include <sys/time.h>
-#include <sys/types.h>
-#include <sys/select.h>
-#include <ctype.h>
-#include <sys/wait.h>
-#include <arpa/inet.h>
-#include <regex.h>
+#include <limits.h>
+#if defined HAVE_PTHREAD_H
 #include <pthread.h>
+#endif
 #include <openssl/ssl.h>
 #include <openssl/err.h>
-#include "debug.h"
-#include "list.h"
-#include "util.h"
-#include "radsecproxy.h"
+#include <openssl/bn.h>
+#include <openssl/x509v3.h>
+#include <openssl/rand.h>
+#include <openssl/crypto.h>
+#include <radsec/radsec.h>
+#include <radsec/radsec-impl.h>
+
+#include <regex.h>
+#include "radsecproxy/list.h"
+#include "radsecproxy/radsecproxy.h"
+
 #include "tls.h"
 
-int tlsconnect(struct server *server, struct timeval *when, int timeout, char *text) {
-    struct timeval now;
-    time_t elapsed;
-    X509 *cert;
-    unsigned long error;
-    
-    debug(DBG_DBG, "tlsconnect: called from %s", text);
-    pthread_mutex_lock(&server->lock);
-    if (when && memcmp(&server->lastconnecttry, when, sizeof(struct timeval))) {
-       /* already reconnected, nothing to do */
-       debug(DBG_DBG, "tlsconnect(%s): seems already reconnected", text);
-       pthread_mutex_unlock(&server->lock);
-       return 1;
-    }
+static struct tls *
+_get_tlsconf (struct rs_connection *conn, const struct rs_realm *realm)
+{
+  struct tls *c = rs_malloc (conn->ctx, sizeof (struct tls));
 
-    for (;;) {
-       gettimeofday(&now, NULL);
-       elapsed = now.tv_sec - server->lastconnecttry.tv_sec;
-       if (timeout && server->lastconnecttry.tv_sec && elapsed > timeout) {
-           debug(DBG_DBG, "tlsconnect: timeout");
-           if (server->sock >= 0)
-               close(server->sock);
-           SSL_free(server->ssl);
-           server->ssl = NULL;
-           pthread_mutex_unlock(&server->lock);
-           return 0;
-       }
-       if (server->connectionok) {
-           server->connectionok = 0;
-           sleep(2);
-       } else if (elapsed < 1)
-           sleep(2);
-       else if (elapsed < 60) {
-           debug(DBG_INFO, "tlsconnect: sleeping %lds", elapsed);
-           sleep(elapsed);
-       } else if (elapsed < 100000) {
-           debug(DBG_INFO, "tlsconnect: sleeping %ds", 60);
-           sleep(60);
-       } else
-           server->lastconnecttry.tv_sec = now.tv_sec;  /* no sleep at startup */
-       debug(DBG_WARN, "tlsconnect: trying to open TLS connection to %s port %s", server->conf->host, server->conf->port);
-       if (server->sock >= 0)
-           close(server->sock);
-       if ((server->sock = connecttcp(server->conf->addrinfo, getsrcprotores(RAD_TLS))) < 0) {
-           debug(DBG_ERR, "tlsconnect: connecttcp failed");
-           continue;
-       }
-       
-       SSL_free(server->ssl);
-       server->ssl = SSL_new(server->conf->ssl_ctx);
-       SSL_set_fd(server->ssl, server->sock);
-       if (SSL_connect(server->ssl) <= 0) {
-           while ((error = ERR_get_error()))
-               debug(DBG_ERR, "tlsconnect: TLS: %s", ERR_error_string(error, NULL));
-           continue;
-       }
-       cert = verifytlscert(server->ssl);
-       if (!cert)
-           continue;
-       if (verifyconfcert(cert, server->conf)) {
-           X509_free(cert);
-           break;
-       }
-       X509_free(cert);
+  if (c)
+    {
+      memset (c, 0, sizeof (struct tls));
+      /* TODO: Make sure old radsecproxy code doesn't free these all
+        of a sudden, or strdup them.  */
+      c->name = realm->name;
+      c->cacertfile = realm->cacertfile;
+      c->cacertpath = NULL;    /* NYI */
+      c->certfile = realm->certfile;
+      c->certkeyfile = realm->certkeyfile;
+      c->certkeypwd = NULL;    /* NYI */
+      c->cacheexpiry = 0;      /* NYI */
+      c->crlcheck = 0;         /* NYI */
+      c->policyoids = (char **) NULL; /* NYI */
     }
-    debug(DBG_WARN, "tlsconnect: TLS connection to %s port %s up", server->conf->host, server->conf->port);
-    gettimeofday(&server->lastconnecttry, NULL);
-    pthread_mutex_unlock(&server->lock);
-    return 1;
-}
+    else
+      rs_err_conn_push_fl (conn, RSE_NOMEM, __FILE__, __LINE__, NULL);
 
-/* timeout in seconds, 0 means no timeout (blocking), returns when num bytes have been read, or timeout */
-/* returns 0 on timeout, -1 on error and num if ok */
-int sslreadtimeout(SSL *ssl, unsigned char *buf, int num, int timeout) {
-    int s, ndesc, cnt, len;
-    fd_set readfds, writefds;
-    struct timeval timer;
-    
-    s = SSL_get_fd(ssl);
-    if (s < 0)
-       return -1;
-    /* make socket non-blocking? */
-    for (len = 0; len < num; len += cnt) {
-       FD_ZERO(&readfds);
-       FD_SET(s, &readfds);
-       writefds = readfds;
-       if (timeout) {
-           timer.tv_sec = timeout;
-           timer.tv_usec = 0;
-       }
-       ndesc = select(s + 1, &readfds, &writefds, NULL, timeout ? &timer : NULL);
-       if (ndesc < 1)
-           return ndesc;
-
-       cnt = SSL_read(ssl, buf + len, num - len);
-       if (cnt <= 0)
-           switch (SSL_get_error(ssl, cnt)) {
-           case SSL_ERROR_WANT_READ:
-           case SSL_ERROR_WANT_WRITE:
-               cnt = 0;
-               continue;
-           case SSL_ERROR_ZERO_RETURN:
-               /* remote end sent close_notify, send one back */
-               SSL_shutdown(ssl);
-               return -1;
-           default:
-               return -1;
-           }
-    }
-    return num;
+  return c;
 }
 
-/* timeout in seconds, 0 means no timeout (blocking) */
-unsigned char *radtlsget(SSL *ssl, int timeout) {
-    int cnt, len;
-    unsigned char buf[4], *rad;
-
-    for (;;) {
-       cnt = sslreadtimeout(ssl, buf, 4, timeout);
-       if (cnt < 1) {
-           debug(DBG_DBG, cnt ? "radtlsget: connection lost" : "radtlsget: timeout");
-           return NULL;
-       }
-
-       len = RADLEN(buf);
-       rad = malloc(len);
-       if (!rad) {
-           debug(DBG_ERR, "radtlsget: malloc failed");
-           continue;
-       }
-       memcpy(rad, buf, 4);
-       
-       cnt = sslreadtimeout(ssl, rad + 4, len - 4, timeout);
-       if (cnt < 1) {
-           debug(DBG_DBG, cnt ? "radtlsget: connection lost" : "radtlsget: timeout");
-           free(rad);
-           return NULL;
-       }
-       
-       if (len >= 20)
-           break;
-       
-       free(rad);
-       debug(DBG_WARN, "radtlsget: packet smaller than minimum radius size");
+#if defined RS_ENABLE_TLS_PSK
+static unsigned int
+psk_client_cb (SSL *ssl,
+               const char *hint,
+               char *identity,
+               unsigned int max_identity_len,
+               unsigned char *psk,
+               unsigned int max_psk_len)
+{
+  struct rs_connection *conn = NULL;
+  struct rs_credentials *cred = NULL;
+
+  conn = SSL_get_ex_data (ssl, 0);
+  assert (conn != NULL);
+  cred = conn->active_peer->realm->transport_cred;
+  assert (cred != NULL);
+  /* NOTE: Ignoring identity hint from server.  */
+
+  if (strlen (cred->identity) + 1 > max_identity_len)
+    {
+      rs_err_conn_push (conn, RSE_CRED, "PSK identity longer than max %d",
+                        max_identity_len - 1);
+      return 0;
     }
-    
-    debug(DBG_DBG, "radtlsget: got %d bytes", len);
-    return rad;
-}
+  strcpy (identity, cred->identity);
+
+  switch (cred->secret_encoding)
+    {
+    case RS_KEY_ENCODING_UTF8:
+      cred->secret_len = strlen (cred->secret);
+      if (cred->secret_len > max_psk_len)
+        {
+          rs_err_conn_push (conn, RSE_CRED, "PSK secret longer than max %d",
+                            max_psk_len);
+          return 0;
+        }
+      memcpy (psk, cred->secret, cred->secret_len);
+      break;
+    case RS_KEY_ENCODING_ASCII_HEX:
+      {
+        BIGNUM *bn = NULL;
 
-int clientradputtls(struct server *server, unsigned char *rad) {
-    int cnt;
-    size_t len;
-    unsigned long error;
-    struct timeval lastconnecttry;
-    struct clsrvconf *conf = server->conf;
-    
-    len = RADLEN(rad);
-    lastconnecttry = server->lastconnecttry;
-    while ((cnt = SSL_write(server->ssl, rad, len)) <= 0) {
-       while ((error = ERR_get_error()))
-           debug(DBG_ERR, "clientradputtls: TLS: %s", ERR_error_string(error, NULL));
-       if (server->dynamiclookuparg)
-           return 0;
-       tlsconnect(server, &lastconnecttry, 0, "clientradputtls");
-       lastconnecttry = server->lastconnecttry;
+        if (BN_hex2bn (&bn, cred->secret) == 0)
+          {
+            rs_err_conn_push (conn, RSE_CRED, "Unable to convert pskhexstr");
+            if (bn != NULL)
+              BN_clear_free (bn);
+            return 0;
+          }
+        if ((unsigned int) BN_num_bytes (bn) > max_psk_len)
+          {
+            rs_err_conn_push (conn, RSE_CRED, "PSK secret longer than max %d",
+                             max_psk_len);
+            BN_clear_free (bn);
+            return 0;
+          }
+        cred->secret_len = BN_bn2bin (bn, psk);
+        BN_clear_free (bn);
+      }
+      break;
+    default:
+      assert (!"unknown psk encoding");
     }
 
-    server->connectionok = 1;
-    debug(DBG_DBG, "clientradputtls: Sent %d bytes, Radius packet of length %d to TLS peer %s", cnt, len, conf->host);
-    return 1;
+  return cred->secret_len;
 }
+#endif  /* RS_ENABLE_TLS_PSK */
 
-void *tlsclientrd(void *arg) {
-    struct server *server = (struct server *)arg;
-    unsigned char *buf;
-    struct timeval now, lastconnecttry;
-    
-    for (;;) {
-       /* yes, lastconnecttry is really necessary */
-       lastconnecttry = server->lastconnecttry;
-       buf = radtlsget(server->ssl, server->dynamiclookuparg ? IDLE_TIMEOUT : 0);
-       if (!buf) {
-           if (server->dynamiclookuparg)
-               break;
-           tlsconnect(server, &lastconnecttry, 0, "tlsclientrd");
-           continue;
-       }
-
-       if (!replyh(server, buf))
-           free(buf);
-       if (server->dynamiclookuparg) {
-           gettimeofday(&now, NULL);
-           if (now.tv_sec - server->lastreply.tv_sec > IDLE_TIMEOUT) {
-               debug(DBG_INFO, "tlsclientrd: idle timeout for %s", server->conf->name);
-               break;
-           }
-       }
+/** Read \a buf_len bytes from one of the random devices into \a
+    buf. Return 0 on success and -1 on failure. */
+static int
+load_rand_ (uint8_t *buf, size_t buf_len)
+{
+  static const char *fns[] = {"/dev/urandom", "/dev/random", NULL};
+  int i;
+
+  if (buf_len > SSIZE_MAX)
+    return -1;
+
+  for (i = 0; fns[i] != NULL; i++)
+    {
+      size_t nread = 0;
+      int fd = open (fns[i], O_RDONLY);
+      if (fd < 0)
+        continue;
+      while (nread != buf_len)
+        {
+          ssize_t r = read (fd, buf + nread, buf_len - nread);
+          if (r < 0)
+            return -1;
+          if (r == 0)
+            break;
+          nread += r;
+        }
+      close (fd);
+      if (nread != buf_len)
+        return -1;
+      return 0;
     }
-    ERR_remove_state(0);
-    server->clientrdgone = 1;
-    return NULL;
+  return -1;
 }
 
-void *tlsserverwr(void *arg) {
-    int cnt;
-    unsigned long error;
-    struct client *client = (struct client *)arg;
-    struct queue *replyq;
-    struct reply *reply;
-    
-    debug(DBG_DBG, "tlsserverwr: starting for %s", client->conf->host);
-    replyq = client->replyq;
-    for (;;) {
-       pthread_mutex_lock(&replyq->mutex);
-       while (!list_first(replyq->entries)) {
-           if (client->ssl) {      
-               debug(DBG_DBG, "tlsserverwr: waiting for signal");
-               pthread_cond_wait(&replyq->cond, &replyq->mutex);
-               debug(DBG_DBG, "tlsserverwr: got signal");
-           }
-           if (!client->ssl) {
-               /* ssl might have changed while waiting */
-               pthread_mutex_unlock(&replyq->mutex);
-               debug(DBG_DBG, "tlsserverwr: exiting as requested");
-               ERR_remove_state(0);
-               pthread_exit(NULL);
-           }
-       }
-       reply = (struct reply *)list_shift(replyq->entries);
-       pthread_mutex_unlock(&replyq->mutex);
-       cnt = SSL_write(client->ssl, reply->buf, RADLEN(reply->buf));
-       if (cnt > 0)
-           debug(DBG_DBG, "tlsserverwr: sent %d bytes, Radius packet of length %d",
-                 cnt, RADLEN(reply->buf));
-       else
-           while ((error = ERR_get_error()))
-               debug(DBG_ERR, "tlsserverwr: SSL: %s", ERR_error_string(error, NULL));
-       free(reply->buf);
-       free(reply);
-    }
+/** Initialise OpenSSL's PRNG by possibly invoking RAND_poll() and by
+    feeding RAND_seed() data from one of the random devices. If either
+    succeeds, we're happy and return 0. */
+static int
+init_openssl_rand_ (void)
+{
+  long openssl_version = 0;
+  int openssl_random_init_flag = 0;
+  int our_random_init_flag = 0;
+  uint8_t buf[32];
+
+  /* Older OpenSSL has a crash bug in RAND_poll (when a file it opens
+     gets a file descriptor with a number higher than FD_SETSIZE) so
+     use it only for newer versions. */
+  openssl_version = SSLeay ();
+  if (openssl_version >= OPENSSL_V (0,9,8,'c'))
+    openssl_random_init_flag = RAND_poll ();
+
+  our_random_init_flag = !load_rand_ (buf, sizeof(buf));
+  if (our_random_init_flag)
+    RAND_seed (buf, sizeof(buf));
+  memset (buf, 0, sizeof(buf)); /* FIXME: What if memset() is optimised out? */
+
+  if (!openssl_random_init_flag && !our_random_init_flag)
+    return -1;
+  if (!RAND_bytes (buf, sizeof(buf)))
+    return -1;
+  return 0;
 }
 
-void tlsserverrd(struct client *client) {
-    struct request rq;
-    pthread_t tlsserverwrth;
-    
-    debug(DBG_DBG, "tlsserverrd: starting for %s", client->conf->host);
-    
-    if (pthread_create(&tlsserverwrth, NULL, tlsserverwr, (void *)client)) {
-       debug(DBG_ERR, "tlsserverrd: pthread_create failed");
-       return;
-    }
+#if defined HAVE_PTHREADS
+/** Array of pthread_mutex_t for OpenSSL. Allocated and initialised in
+    \a init_locking_ and never freed. */
+static pthread_mutex_t *s_openssl_mutexes = NULL;
+/** Number of pthread_mutex_t's allocated at s_openssl_mutexes. */
+static int s_openssl_mutexes_count = 0;
+
+/** Callback for OpenSSL when a lock is to be held or released. */
+static void
+openssl_locking_cb_ (int mode, int i, const char *file, int line)
+{
+  if (s_openssl_mutexes == NULL || i >= s_openssl_mutexes_count)
+    return;
+  if (mode & CRYPTO_LOCK)
+    pthread_mutex_lock (&s_openssl_mutexes[i]);
+  else
+    pthread_mutex_unlock (&s_openssl_mutexes[i]);
+}
+
+/** Initialise any locking needed for being thread safe. Libradsec has
+    all its own state in one or more struct rs_context and doesn't
+    need locks but libraries used by libradsec may need protection. */
+static int
+init_locking_ ()
+{
+  int i, n;
+  n = CRYPTO_num_locks ();
+
+  s_openssl_mutexes = calloc (n, sizeof(pthread_mutex_t));
+  if (s_openssl_mutexes == NULL)
+    return -RSE_NOMEM;
+  for (i = 0; i < n; i++)
+    pthread_mutex_init (&s_openssl_mutexes[i], NULL);
+  s_openssl_mutexes_count = n;
+
+  return 0;
+}
+#endif  /* HAVE_PTHREADS */
 
-    for (;;) {
-       memset(&rq, 0, sizeof(struct request));
-       rq.buf = radtlsget(client->ssl, 0);
-       if (!rq.buf) {
-           debug(DBG_ERR, "tlsserverrd: connection from %s lost", client->conf->host);
-           break;
-       }
-       debug(DBG_DBG, "tlsserverrd: got Radius message from %s", client->conf->host);
-       rq.from = client;
-       if (!radsrv(&rq)) {
-           debug(DBG_ERR, "tlsserverrd: message authentication/validation failed, closing connection from %s", client->conf->host);
-           break;
-       }
+/** Initialise the TLS library. Return 0 on success, -1 on failure. */
+int
+tls_init ()
+{
+  SSL_load_error_strings ();
+#if defined HAVE_PTHREADS
+  if (CRYPTO_get_locking_callback () == NULL)
+    {
+      assert (s_openssl_mutexes_count == 0);
+      /* Allocate and initialise mutexes. We will never free
+         these. FIXME: Is there a portable way of having a function
+         invoked when a solib is unloaded? -ln */
+      if (init_locking_ ())
+        return -1;
+      CRYPTO_set_locking_callback (openssl_locking_cb_);
     }
-    
-    /* stop writer by setting ssl to NULL and give signal in case waiting for data */
-    client->ssl = NULL;
-    pthread_mutex_lock(&client->replyq->mutex);
-    pthread_cond_signal(&client->replyq->cond);
-    pthread_mutex_unlock(&client->replyq->mutex);
-    debug(DBG_DBG, "tlsserverrd: waiting for writer to end");
-    pthread_join(tlsserverwrth, NULL);
-    removeclientrqs(client);
-    debug(DBG_DBG, "tlsserverrd: reader for %s exiting", client->conf->host);
+#endif  /* HAVE_PTHREADS */
+  SSL_library_init ();
+  return init_openssl_rand_ ();
 }
 
-void *tlsservernew(void *arg) {
-    int s;
-    struct sockaddr_storage from;
-    size_t fromlen = sizeof(from);
-    struct clsrvconf *conf;
-    struct list_node *cur = NULL;
-    SSL *ssl = NULL;
-    X509 *cert = NULL;
-    unsigned long error;
-    struct client *client;
-
-    s = *(int *)arg;
-    if (getpeername(s, (struct sockaddr *)&from, &fromlen)) {
-       debug(DBG_DBG, "tlsservernew: getpeername failed, exiting");
-       goto exit;
+int
+tls_init_conn (struct rs_connection *conn)
+{
+  struct rs_context *ctx = NULL;
+  struct tls *tlsconf = NULL;
+  SSL_CTX *ssl_ctx = NULL;
+  SSL *ssl = NULL;
+  unsigned long sslerr = 0;
+
+  assert (conn->ctx);
+  ctx = conn->ctx;
+
+  tlsconf = _get_tlsconf (conn, conn->active_peer->realm);
+  if (!tlsconf)
+    return -1;
+  ssl_ctx = tlsgetctx (RAD_TLS, tlsconf);
+  if (!ssl_ctx)
+    {
+      for (sslerr = ERR_get_error (); sslerr; sslerr = ERR_get_error ())
+        rs_err_conn_push_fl (conn, RSE_SSLERR, __FILE__, __LINE__,
+                             ERR_error_string (sslerr, NULL));
+      return -1;
     }
-    debug(DBG_WARN, "tlsservernew: incoming TLS connection from %s", addr2string((struct sockaddr *)&from, fromlen));
-
-    conf = find_clconf(RAD_TLS, (struct sockaddr *)&from, &cur);
-    if (conf) {
-       ssl = SSL_new(conf->ssl_ctx);
-       SSL_set_fd(ssl, s);
-
-       if (SSL_accept(ssl) <= 0) {
-           while ((error = ERR_get_error()))
-               debug(DBG_ERR, "tlsservernew: SSL: %s", ERR_error_string(error, NULL));
-           debug(DBG_ERR, "tlsservernew: SSL_accept failed");
-           goto exit;
-       }
-       cert = verifytlscert(ssl);
-       if (!cert)
-           goto exit;
+  ssl = SSL_new (ssl_ctx);
+  if (!ssl)
+    {
+      for (sslerr = ERR_get_error (); sslerr; sslerr = ERR_get_error ())
+       rs_err_conn_push_fl (conn, RSE_SSLERR, __FILE__, __LINE__,
+                            ERR_error_string (sslerr, NULL));
+      return -1;
     }
-    
-    while (conf) {
-       if (verifyconfcert(cert, conf)) {
-           X509_free(cert);
-           client = addclient(conf);
-           if (client) {
-               client->ssl = ssl;
-               tlsserverrd(client);
-               removeclient(client);
-           } else
-               debug(DBG_WARN, "tlsservernew: failed to create new client instance");
-           goto exit;
-       }
-       conf = find_clconf(RAD_TLS, (struct sockaddr *)&from, &cur);
+
+#if defined RS_ENABLE_TLS_PSK
+  if (conn->active_peer->realm->transport_cred != NULL)
+    {
+      SSL_set_psk_client_callback (ssl, psk_client_cb);
+      SSL_set_ex_data (ssl, 0, conn);
     }
-    debug(DBG_WARN, "tlsservernew: ignoring request, no matching TLS client");
-    if (cert)
-       X509_free(cert);
-
- exit:
-    SSL_free(ssl);
-    ERR_remove_state(0);
-    shutdown(s, SHUT_RDWR);
-    close(s);
-    pthread_exit(NULL);
+#endif  /* RS_ENABLE_TLS_PSK */
+
+  conn->tls_ctx = ssl_ctx;
+  conn->tls_ssl = ssl;
+  rs_free (ctx, tlsconf);
+  return RSE_OK;
 }
 
-void *tlslistener(void *arg) {
-    pthread_t tlsserverth;
-    int s, *sp = (int *)arg;
-    struct sockaddr_storage from;
-    size_t fromlen = sizeof(from);
-
-    listen(*sp, 0);
-
-    for (;;) {
-       s = accept(*sp, (struct sockaddr *)&from, &fromlen);
-       if (s < 0) {
-           debug(DBG_WARN, "accept failed");
-           continue;
-       }
-       if (pthread_create(&tlsserverth, NULL, tlsservernew, (void *)&s)) {
-           debug(DBG_ERR, "tlslistener: pthread_create failed");
-           shutdown(s, SHUT_RDWR);
-           close(s);
-           continue;
-       }
-       pthread_detach(tlsserverth);
+/* draft-ietf-radext-radsec-11.txt
+
+       *  Certificate validation MUST include the verification rules as
+          per [RFC5280].
+
+       *  Implementations SHOULD indicate their acceptable Certification
+          Authorities as per section 7.4.4 (server side) and x.y.z
+          ["Trusted CA Indication"] (client side) of [RFC5246] (see
+          Section 3.2)
+
+       *  Implementations SHOULD allow to configure a list of acceptable
+          certificates, identified via certificate fingerprint.  When a
+          fingerprint configured, the fingerprint is prepended with an
+          ASCII label identifying the hash function followed by a colon.
+          Implementations MUST support SHA-1 as the hash algorithm and
+          use the ASCII label "sha-1" to identify the SHA-1 algorithm.
+          The length of a SHA-1 hash is 20 bytes and the length of the
+          corresponding fingerprint string is 65 characters.  An example
+          certificate fingerprint is: sha-
+          1:E1:2D:53:2B:7C:6B:8A:29:A2:76:C8:64:36:0B:08:4B:7A:F1:9E:9D
+
+       *  Peer validation always includes a check on whether the locally
+          configured expected DNS name or IP address of the server that
+          is contacted matches its presented certificate.  DNS names and
+          IP addresses can be contained in the Common Name (CN) or
+          subjectAltName entries.  For verification, only one of these
+          entries is to be considered.  The following precedence
+          applies: for DNS name validation, subjectAltName:DNS has
+          precedence over CN; for IP address validation, subjectAltName:
+          iPAddr has precedence over CN.
+
+       *  Implementations SHOULD allow to configure a set of acceptable
+          values for subjectAltName:URI.
+ */
+int
+tls_verify_cert (struct rs_connection *conn)
+{
+  int err = 0;
+  int success = 0;
+  X509 *peer_cert = NULL;
+  struct in6_addr addr;
+  const char *hostname = NULL;
+
+  assert (conn->active_peer->conn == conn);
+  assert (conn->active_peer->hostname != NULL);
+  hostname = conn->active_peer->hostname;
+
+  /* verifytlscert() performs basic verification as described by
+     OpenSSL VERIFY(1), i.e. verification of the certificate chain.  */
+  peer_cert = verifytlscert (conn->tls_ssl);
+  if (peer_cert == NULL)
+    {
+      err = rs_err_conn_push (conn, RSE_SSLERR,
+                              "basic certificate validation failed");
+      goto out;
     }
-    free(sp);
-    return NULL;
+
+  if (inet_pton (AF_INET, hostname, &addr))
+    success = (subjectaltnameaddr (peer_cert, AF_INET, &addr) == 1);
+  else if (inet_pton (AF_INET6, hostname, &addr))
+    success = (subjectaltnameaddr (peer_cert, AF_INET6, &addr) == 1);
+  else
+    success = (subjectaltnameregexp (peer_cert, GEN_DNS, hostname, NULL) == 1);
+
+  if (!success)
+    success = (cnregexp (peer_cert, hostname, NULL) == 1);
+
+  if (conn->realm->disable_hostname_check)
+    success = 1;
+  if (!success)
+    err = rs_err_conn_push (conn, RSE_CERT, "server certificate doesn't "
+                            "match configured hostname \"%s\"", hostname);
+
+ out:
+  if (peer_cert != NULL)
+    X509_free (peer_cert);
+  return err;
 }