From d1fd8449df7f12c6798849a13f1474fd40d66c32 Mon Sep 17 00:00:00 2001
From: venaas <venaas>
Date: Thu, 25 Jan 2007 12:42:48 +0000
Subject: further tls changes. not complete, contains some test code

git-svn-id: https://svn.testnett.uninett.no/radsecproxy/trunk@40 e88ac4ed-0b26-0410-9574-a7f39faa03bf
---
 radsecproxy.c | 275 +++++++++++++++++++++++++++++++++++++++++++++-------------
 radsecproxy.h |   3 +
 2 files changed, 218 insertions(+), 60 deletions(-)

diff --git a/radsecproxy.c b/radsecproxy.c
index 51202e4..d6affca 100644
--- a/radsecproxy.c
+++ b/radsecproxy.c
@@ -6,10 +6,6 @@
  * copyright notice and this permission notice appear in all copies.
  */
 
-/* BUGS:
- * peers can not yet be specified with literal IPv6 addresses due to port syntax
- */
-
 /* TODO:
  * make our server ignore client retrans and do its own instead?
  * accounting
@@ -69,7 +65,8 @@ static struct replyq udp_server_replyq;
 static int udp_server_sock = -1;
 static pthread_mutex_t *ssl_locks;
 static long *ssl_lock_count;
-static SSL_CTX *ssl_ctx = NULL;
+static SSL_CTX *ssl_ctx_cl = NULL;
+static SSL_CTX *ssl_ctx_srv = NULL;
 extern int optind;
 extern char *optarg;
 
@@ -86,9 +83,50 @@ void ssl_locking_callback(int mode, int type, const char *file, int line) {
 	pthread_mutex_unlock(&ssl_locks[type]);
 }
 
-void ssl_init() {
+static int verify_cb(int ok, X509_STORE_CTX *ctx) {
+  char buf[256];
+  X509 *err_cert;
+  int err, depth;
+
+  err_cert = X509_STORE_CTX_get_current_cert(ctx);
+  err = X509_STORE_CTX_get_error(ctx);
+  depth = X509_STORE_CTX_get_error_depth(ctx);
+
+  if (depth > MAX_CERT_DEPTH) {
+      ok = 0;
+      err = X509_V_ERR_CERT_CHAIN_TOO_LONG;
+      X509_STORE_CTX_set_error(ctx, err);
+  }
+
+  if (!ok) {
+      X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
+      printf("verify error: num=%d:%s:depth=%d:%s\n", err, X509_verify_cert_error_string(err), depth, buf);
+
+      switch (err) {
+      case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+	  X509_NAME_oneline(X509_get_issuer_name(ctx->current_cert), buf, 256);
+	  printf("issuer=%s\n", buf);
+	  break;
+      case X509_V_ERR_CERT_NOT_YET_VALID:
+      case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+	  printf("Certificate not yet valid\n");
+	  break;
+      case X509_V_ERR_CERT_HAS_EXPIRED:
+	  printf("Certificate has expired\n");
+	  break;
+      case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+	  printf("Certificate no longer valid (after notAfter)\n");
+	  break;
+      }
+  }
+  printf("certificate verify returns %d\n", ok);
+  return ok;
+}
+
+void ssl_init(SSL_CTX **ctx_srv, SSL_CTX **ctx_cl) {
     int i;
     unsigned long error;
+    STACK_OF(X509_NAME) *calist;
     
     if (!options.tlscertificatefile || !options.tlscertificatekeyfile) {
 	printf("TLSCertificateFile and TLSCertificateKeyFile must be specified for TLS\n");
@@ -114,22 +152,88 @@ void ssl_init() {
         RAND_seed((unsigned char *)&pid, sizeof(pid));
     }
 
-    ssl_ctx = SSL_CTX_new(TLSv1_method());
-    if (!ssl_ctx) {
-	printf("failed to initialise ssl");
-	exit(1);
+#if 0    
+    if (ctx_srv) {
+	*ctx_srv = SSL_CTX_new(TLSv1_server_method());
+	if (!SSL_CTX_use_certificate_chain_file(*ctx_srv, options.tlscertificatefile) ||
+	    !SSL_CTX_use_PrivateKey_file(*ctx_srv, options.tlscertificatekeyfile, SSL_FILETYPE_PEM) ||
+	    !SSL_CTX_check_private_key(*ctx_srv))
+	    goto errexit;
+#if 1	
+#if 1
+	calist = (options.tlscacertificatefile
+		  ? SSL_load_client_CA_file(options.tlscacertificatefile)
+		  : sk_X509_NAME_new_null());
+	if (!calist || (options.tlscacertificatepath &&
+			 !SSL_add_dir_cert_subjects_to_stack(calist, options.tlscacertificatepath)))
+	    goto errexit;
+	SSL_CTX_set_client_CA_list(*ctx_srv, calist);
+#endif	
+	if (!options.tlscacertificatefile && !options.tlscacertificatepath) {
+	    printf("CA Certificate file/path need to be configured\n");
+	    exit(1);
+	}
+	if (!SSL_CTX_load_verify_locations(*ctx_srv, options.tlscacertificatefile, options.tlscacertificatepath))
+	    goto errexit;
+	SSL_CTX_set_verify(*ctx_srv, SSL_VERIFY_PEER, verify_cb);
+	SSL_CTX_set_verify_depth(*ctx_srv, MAX_CERT_DEPTH + 1);
+#endif	
     }
-
-    if (!SSL_CTX_use_certificate_file(ssl_ctx, options.tlscertificatefile, SSL_FILETYPE_PEM)) {
-        while ((error = ERR_get_error()))
-            err("SSL: %s", ERR_error_string(error, NULL));
-        errx("Failed to load certificate");
+    if (ctx_cl) {
+	*ctx_cl = SSL_CTX_new(TLSv1_client_method());
+	if (!SSL_CTX_use_certificate_chain_file(*ctx_cl, options.tlscertificatefile) ||
+	    !SSL_CTX_use_PrivateKey_file(*ctx_cl, options.tlscertificatekeyfile, SSL_FILETYPE_PEM) ||
+	    !SSL_CTX_check_private_key(*ctx_cl))
+	    goto errexit;
+	if (!options.tlscacertificatefile && !options.tlscacertificatepath) {
+	    printf("CA Certificate file/path need to be configured\n");
+	    exit(1);
+	}
+	if (!SSL_CTX_load_verify_locations(*ctx_cl, options.tlscacertificatefile, options.tlscacertificatepath))
+	    goto errexit;
+	SSL_CTX_set_verify(*ctx_cl, SSL_VERIFY_PEER, verify_cb);
+	SSL_CTX_set_verify_depth(*ctx_cl, MAX_CERT_DEPTH + 1);
     }
-    if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, options.tlscertificatekeyfile, SSL_FILETYPE_PEM)) {
-	while ((error = ERR_get_error()))
-	    err("SSL: %s", ERR_error_string(error, NULL));
-	errx("Failed to load private key");
+#else
+    if (ctx_srv) {
+	*ctx_srv = SSL_CTX_new(TLSv1_server_method());
+	if (!SSL_CTX_use_certificate_chain_file(*ctx_srv, options.tlscertificatefile) ||
+	    !SSL_CTX_use_PrivateKey_file(*ctx_srv, options.tlscertificatekeyfile, SSL_FILETYPE_PEM) ||
+	    !SSL_CTX_check_private_key(*ctx_srv))
+	    goto errexit;
+#if 0	
+	calist = (SSL_load_client_CA_file(options.tlscacertificatefile));
+	SSL_CTX_set_client_CA_list(*ctx_srv, calist);
+#endif	
+	if (!SSL_CTX_load_verify_locations(*ctx_srv, options.tlscacertificatefile, NULL/*options.tlscacertificatepath*/))
+	    goto errexit;
+	
+	SSL_CTX_set_verify(*ctx_srv, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb);
+	SSL_CTX_set_verify_depth(*ctx_srv, MAX_CERT_DEPTH + 1);
+    }
+    if (ctx_cl) {
+	*ctx_cl = SSL_CTX_new(TLSv1_client_method());
+	if (!SSL_CTX_use_certificate_chain_file(*ctx_cl, options.tlscertificatefile) ||
+	    !SSL_CTX_use_PrivateKey_file(*ctx_cl, options.tlscertificatekeyfile, SSL_FILETYPE_PEM) ||
+	    !SSL_CTX_check_private_key(*ctx_cl))
+	    goto errexit;
+	if (!SSL_CTX_load_verify_locations(*ctx_cl,options.tlscacertificatefile, NULL/*options.tlscacertificatepath*/))
+	    goto errexit;
+	
+	SSL_CTX_set_verify(*ctx_cl, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb);
+	SSL_CTX_set_verify_depth(*ctx_cl, MAX_CERT_DEPTH + 1);
+#if 0	
+	calist = (SSL_load_client_CA_file(options.tlscacertificatefile));
+	SSL_CTX_set_client_CA_list(*ctx_cl, calist);
+#endif	
     }
+#endif    
+    return;
+    
+ errexit:
+    while ((error = ERR_get_error()))
+	err("SSL: %s", ERR_error_string(error, NULL));
+    exit(1);
 }    
 
 void printauth(char *s, unsigned char *t) {
@@ -295,10 +399,54 @@ unsigned char *radudpget(int s, struct client **client, struct server **server,
     return rad;
 }
 
+int tlsverifycert(struct peer *peer) {
+    int i, l, loc;
+    X509 *cert;
+    X509_NAME *nm;
+    X509_NAME_ENTRY *e;
+    unsigned char *v;
+    unsigned long error;
+
+#if 1
+    if (SSL_get_verify_result(peer->ssl) != X509_V_OK) {
+	printf("tlsverifycert: basic validation failed\n");
+	while ((error = ERR_get_error()))
+	    err("clientwr: TLS: %s", ERR_error_string(error, NULL));
+	return 0;
+    }
+#endif    
+    cert = SSL_get_peer_certificate(peer->ssl);
+    if (!cert) {
+	printf("tlsverifycert: failed to obtain certificate\n");
+	return 0;
+    }
+    nm = X509_get_subject_name(cert);
+    loc = -1;
+    for (;;) {
+	loc = X509_NAME_get_index_by_NID(nm, NID_commonName, loc);
+	if (loc == -1)
+	    break;
+	e = X509_NAME_get_entry(nm, loc);
+	l = ASN1_STRING_to_UTF8(&v, X509_NAME_ENTRY_get_data(e));
+	if (l < 0)
+	    continue;
+	printf("cn: ");
+	for (i = 0; i < l; i++)
+	    printf("%c", v[i]);
+	printf("\n");
+	if (l == strlen(peer->host) && !strncasecmp(peer->host, v, l)) {
+	    printf("tlsverifycert: Found cn matching host %s, All OK\n", peer->host);
+	    return 1;
+	}
+	printf("tlsverifycert: cn not matching host %s\n", peer->host);
+    }
+    X509_free(cert);
+    return 0;
+}
+
 void tlsconnect(struct server *server, struct timeval *when, char *text) {
     struct timeval now;
     time_t elapsed;
-    unsigned long error;
 
     printf("tlsconnect called from %s\n", text);
     pthread_mutex_lock(&server->lock);
@@ -331,12 +479,10 @@ void tlsconnect(struct server *server, struct timeval *when, char *text) {
 	if ((server->sock = connecttoserver(server->peer.addrinfo)) < 0)
 	    continue;
 	SSL_free(server->peer.ssl);
-	server->peer.ssl = SSL_new(ssl_ctx);
+	server->peer.ssl = SSL_new(ssl_ctx_cl);
 	SSL_set_fd(server->peer.ssl, server->sock);
-	if (SSL_connect(server->peer.ssl) > 0)
+	if (SSL_connect(server->peer.ssl) > 0 && tlsverifycert(&server->peer))
 	    break;
-	while ((error = ERR_get_error()))
-	    err("tlsconnect: TLS: %s", ERR_error_string(error, NULL));
     }
     printf("tlsconnect: TLS connection to %s port %s up\n", server->peer.host, server->peer.port);
     gettimeofday(&server->lastconnecttry, NULL);
@@ -1329,30 +1475,32 @@ void *tlsserverrd(void *arg) {
         errx("accept failed, child exiting");
     }
 
-    if (pthread_create(&tlsserverwrth, NULL, tlsserverwr, (void *)client))
-	errx("pthread_create failed");
+    if (1 /*tlsverifycert(&client->peer)*/) {
+	if (pthread_create(&tlsserverwrth, NULL, tlsserverwr, (void *)client))
+	    errx("pthread_create failed");
     
-    for (;;) {
-	buf = radtlsget(client->peer.ssl);
-	if (!buf)
-	    break;
-	printf("tlsserverrd: got Radius message from %s\n", client->peer.host);
-	memset(&rq, 0, sizeof(struct request));
-	to = radsrv(&rq, buf, client);
-	if (!to) {
-	    printf("ignoring request, no place to send it\n");
-	    continue;
+	for (;;) {
+	    buf = radtlsget(client->peer.ssl);
+	    if (!buf)
+		break;
+	    printf("tlsserverrd: got Radius message from %s\n", client->peer.host);
+	    memset(&rq, 0, sizeof(struct request));
+	    to = radsrv(&rq, buf, client);
+	    if (!to) {
+		printf("ignoring request, no place to send it\n");
+		continue;
+	    }
+	    sendrq(to, client, &rq);
 	}
-	sendrq(to, client, &rq);
-    }
-    printf("tlsserverrd: connection lost\n");
-    // stop writer by setting peer.ssl to NULL and give signal in case waiting for data
-    client->peer.ssl = NULL;
-    pthread_mutex_lock(&client->replyq->count_mutex);
-    pthread_cond_signal(&client->replyq->count_cond);
-    pthread_mutex_unlock(&client->replyq->count_mutex);
-    printf("tlsserverrd: waiting for writer to end\n");
-    pthread_join(tlsserverwrth, NULL);
+	printf("tlsserverrd: connection lost\n");
+	// stop writer by setting peer.ssl to NULL and give signal in case waiting for data
+	client->peer.ssl = NULL;
+	pthread_mutex_lock(&client->replyq->count_mutex);
+	pthread_cond_signal(&client->replyq->count_cond);
+	pthread_mutex_unlock(&client->replyq->count_mutex);
+	printf("tlsserverrd: waiting for writer to end\n");
+	pthread_join(tlsserverwrth, NULL);
+    }
     s = SSL_get_fd(ssl);
     SSL_free(ssl);
     close(s);
@@ -1395,7 +1543,7 @@ int tlslistener() {
 	    close(snew);
 	    continue;
 	}
-	client->peer.ssl = SSL_new(ssl_ctx);
+	client->peer.ssl = SSL_new(ssl_ctx_srv);
 	SSL_set_fd(client->peer.ssl, snew);
 	if (pthread_create(&tlsserverth, NULL, tlsserverrd, (void *)client))
 	    errx("pthread_create failed");
@@ -1629,6 +1777,14 @@ void getmainconfig(const char *configfile) {
 	endval[1] = '\0';
 	printf("getmainconfig: %s = %s\n", opt, val);
 	
+	if (!strcasecmp(opt, "TLSCACertificateFile")) {
+	    options.tlscacertificatefile = stringcopy(val, 0);
+	    continue;
+	}
+	if (!strcasecmp(opt, "TLSCACertificatePath")) {
+	    options.tlscacertificatepath = stringcopy(val, 0);
+	    continue;
+	}
 	if (!strcasecmp(opt, "TLSCertificateFile")) {
 	    options.tlscertificatefile = stringcopy(val, 0);
 	    continue;
@@ -1676,7 +1832,7 @@ void parseargs(int argc, char **argv) {
 int main(int argc, char **argv) {
     pthread_t udpserverth;
     //    pthread_attr_t joinable;
-    int i;
+    int i, tlsclients = 0, tlsservers = 0;
     
     //    parseargs(argc, argv);
     getmainconfig("radsecproxy.conf");
@@ -1687,32 +1843,31 @@ int main(int argc, char **argv) {
     //    pthread_attr_setdetachstate(&joinable, PTHREAD_CREATE_JOINABLE);
    
     /* listen on UDP if at least one UDP client */
-    
     for (i = 0; i < client_count; i++)
 	if (clients[i].peer.type == 'U') {
 	    if (pthread_create(&udpserverth, NULL /*&joinable*/, udpserverrd, NULL))
 		errx("pthread_create failed");
 	    break;
 	}
-
-    /* only initialise ssl here if at least one TLS server defined */
+    
+    for (i = 0; i < client_count; i++)
+	if (clients[i].peer.type == 'T') {
+	    tlsclients = 1;
+	    break;
+	}
     for (i = 0; i < server_count; i++)
 	if (servers[i].peer.type == 'T') {
-	    ssl_init();
+	    tlsservers = 1;
 	    break;
 	}
-
+    ssl_init(tlsclients ? &ssl_ctx_srv : NULL, tlsservers ? &ssl_ctx_cl : NULL);
+    
     for (i = 0; i < server_count; i++)
 	if (pthread_create(&servers[i].clientth, NULL, clientwr, (void *)&servers[i]))
 	    errx("pthread_create failed");
 
-    /* start listener if at least one TLS client defined */
-    for (i = 0; i < client_count; i++)
-	if (clients[i].peer.type == 'T') {
-	    if (!ssl_ctx)
-		ssl_init();
-	    return tlslistener();
-	}
+    if (tlsclients)
+	return tlslistener();
 
     /* just hang around doing nothing, anything to do here? */
     for (;;)
diff --git a/radsecproxy.h b/radsecproxy.h
index e6dca52..5719070 100644
--- a/radsecproxy.h
+++ b/radsecproxy.h
@@ -19,6 +19,7 @@
 #define DEFAULT_UDP_PORT "1812"
 #define DEFAULT_TLS_PORT "2083"
 #define REQUEST_TIMEOUT 5
+#define MAX_CERT_DEPTH 5
 
 #define RAD_Access_Request 1
 #define RAD_Access_Accept 2
@@ -43,6 +44,8 @@
 #define RAD_Attr_Value 2
 
 struct options {
+    char *tlscacertificatefile;
+    char *tlscacertificatepath;
     char *tlscertificatefile;
     char *tlscertificatekeyfile;
     char *udpserverport;
-- 
cgit v1.1