changeset 805:fb5e0fd923ff

Updated verification of the local certificate following GnuTLS 3.x guideline
author Sebastien Decugis <sdecugis@freediameter.net>
date Wed, 22 Aug 2012 22:56:22 +0200
parents c5b7d4a2cc77
children 6e47b13e7100
files extensions/app_diameap/diameap_tls.c include/freeDiameter/libfdcore.h libfdcore/config.c libfdcore/fdcore-internal.h libfdcore/fdd.y
diffstat 5 files changed, 245 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/extensions/app_diameap/diameap_tls.c	Wed Aug 22 00:22:46 2012 +0200
+++ b/extensions/app_diameap/diameap_tls.c	Wed Aug 22 22:56:22 2012 +0200
@@ -142,10 +142,10 @@
 	gnutls_transport_set_pull_function(data->session, diameap_tls_receive);
 	gnutls_transport_set_push_function(data->session, diameap_tls_send);
 	gnutls_transport_set_ptr(data->session, (gnutls_transport_ptr) data);
-#ifndef GNUTLS_VERSION_300
+
 	/* starting version 2.12, this call is not needed */
-	gnutls_transport_set_lowat(data->session, 0);
-#endif /* GNUTLS_VERSION_300 */
+	//gnutls_transport_set_lowat(data->session, 0);
+	
 	return ret;
 }
 
--- a/include/freeDiameter/libfdcore.h	Wed Aug 22 00:22:46 2012 +0200
+++ b/include/freeDiameter/libfdcore.h	Wed Aug 22 22:56:22 2012 +0200
@@ -51,6 +51,7 @@
 #define GNUTLS_DBG_LEVEL ANNOYING
 #endif /* GNUTLS_DBG_LEVEL */
 
+
 /* Check the return value of a GNUTLS function, log and propagate */
 #define CHECK_GNUTLS_DO( __call__, __fallback__ ) {						\
 	int __ret__;										\
@@ -149,7 +150,10 @@
 		gnutls_dh_params_t 		 dh_cache;
 		
 		/* GNUTLS server credential(s) */
-		gnutls_certificate_credentials_t credentials;
+		gnutls_certificate_credentials_t credentials; /* contains local cert + trust anchors */
+		#ifdef GNUTLS_VERSION_300
+		gnutls_x509_trust_list_t         trustlist; /* the logic to check local certificate has changed */
+		#endif /* GNUTLS_VERSION_300 */
 		
 	} 		 cnf_sec_data;
 	
--- a/libfdcore/config.c	Wed Aug 22 00:22:46 2012 +0200
+++ b/libfdcore/config.c	Wed Aug 22 22:56:22 2012 +0200
@@ -73,6 +73,9 @@
 	/* TLS parameters */
 	CHECK_GNUTLS_DO( gnutls_certificate_allocate_credentials (&fd_g_config->cnf_sec_data.credentials), return ENOMEM );
 	CHECK_GNUTLS_DO( gnutls_dh_params_init (&fd_g_config->cnf_sec_data.dh_cache), return ENOMEM );
+#ifdef GNUTLS_VERSION_300
+	CHECK_GNUTLS_DO( gnutls_x509_trust_list_init(&fd_g_config->cnf_sec_data.trustlist, 0), return ENOMEM );
+#endif /* GNUTLS_VERSION_300 */
 
 	return 0;
 }
@@ -141,6 +144,87 @@
 	fd_log_debug("  Origin-State-Id ........ : %u\n", fd_g_config->cnf_orstateid);
 }
 
+/* read contents of a file opened in "rb" mode and alloc this data into a gnutls_datum_t (must be freed afterwards) */
+int fd_conf_stream_to_gnutls_datum(FILE * pemfile, gnutls_datum_t *out)
+{
+	size_t alloc = 0;
+	
+	CHECK_PARAMS( pemfile && out );
+	memset(out, 0, sizeof(gnutls_datum_t));
+
+	do {
+		uint8_t * realloced = NULL;
+		size_t read = 0;
+
+		if (alloc < out->size + BUFSIZ + 1) {
+			alloc += alloc / 2 + BUFSIZ + 1;
+			CHECK_MALLOC_DO( realloced = realloc(out->data, alloc),
+				{
+					free(out->data);
+					return ENOMEM;
+				} )
+			out->data = realloced;
+		}
+
+		read = fread( out->data + out->size, 1, alloc - out->size - 1, pemfile );
+		out->size += read;
+
+		if (ferror(pemfile)) {
+			int err = errno;
+			TRACE_DEBUG(INFO, "An error occurred while reading file: %s\n", strerror(err));
+			return err; 
+		}
+	} while (!feof(pemfile));
+	
+	out->data[out->size] = '\0';
+	return 0;
+}
+
+#ifdef GNUTLS_VERSION_300
+/* inspired from GnuTLS manual */
+static int fd_conf_print_details_func (gnutls_x509_crt_t cert,
+                    gnutls_x509_crt_t issuer, gnutls_x509_crl_t crl,
+                    unsigned int verification_output)
+{
+  char name[512];
+  char issuer_name[512];
+  size_t name_size;
+  size_t issuer_name_size;
+  
+  if (!TRACE_BOOL(GNUTLS_DBG_LEVEL))
+	  return 0;
+
+  issuer_name_size = sizeof (issuer_name);
+  gnutls_x509_crt_get_issuer_dn (cert, issuer_name, &issuer_name_size);
+
+  name_size = sizeof (name);
+  gnutls_x509_crt_get_dn (cert, name, &name_size);
+
+  fd_log_debug("\tSubject: %s\n", name);
+  fd_log_debug("\tIssuer: %s\n", issuer_name);
+
+  if (issuer != NULL)
+    {
+      issuer_name_size = sizeof (issuer_name);
+      gnutls_x509_crt_get_dn (issuer, issuer_name, &issuer_name_size);
+
+      fd_log_debug("\tVerified against: %s\n", issuer_name);
+    }
+
+  if (crl != NULL)
+    {
+      issuer_name_size = sizeof (issuer_name);
+      gnutls_x509_crl_get_issuer_dn (crl, issuer_name, &issuer_name_size);
+
+      fd_log_debug("\tVerified against CRL of: %s\n", issuer_name);
+    }
+
+  fd_log_debug("\tVerification output: %x\n\n", verification_output);
+
+  return 0;
+}
+#endif /* GNUTLS_VERSION_300 */
+
 /* Parse the configuration file (using the yacc parser) */
 int fd_conf_parse()
 {
@@ -291,21 +375,10 @@
 		int ret = 0, i;
 		
 		gnutls_datum_t certfile;
-		size_t alloc = 0;
 		
 		gnutls_x509_crt_t * certs = NULL;
 		unsigned int cert_max = 0;
 		
-		gnutls_x509_crt_t * CA_list;
-		int CA_list_length;
-		
-		gnutls_x509_crl_t * CRL_list;
-		int CRL_list_length;
-		
-		unsigned int verify;
-		time_t now;
-		
-		memset(&certfile, 0, sizeof(certfile));
 		
 		/* Read the certificate file */
 		FILE *stream = fopen (fd_g_config->cnf_sec_data.cert_file, "rb");
@@ -314,30 +387,7 @@
 			TRACE_DEBUG(INFO, "An error occurred while opening '%s': %s\n", fd_g_config->cnf_sec_data.cert_file, strerror(err));
 			return err; 
 		}
-		do {
-			uint8_t * realloced = NULL;
-			size_t read = 0;
-			
-			if (alloc < certfile.size + BUFSIZ + 1) {
-				alloc += alloc / 2 + BUFSIZ + 1;
-				CHECK_MALLOC_DO( realloced = realloc(certfile.data, alloc),
-					{
-						free(certfile.data);
-						return ENOMEM;
-					} )
-				certfile.data = realloced;
-			}
-			
-			read = fread( certfile.data + certfile.size, 1, alloc - certfile.size - 1, stream );
-			certfile.size += read;
-			
-			if (ferror(stream)) {
-				int err = errno;
-				TRACE_DEBUG(INFO, "An error occurred while reading '%s': %s\n", fd_g_config->cnf_sec_data.cert_file, strerror(err));
-				return err; 
-			}
-		} while (!feof(stream));
-		certfile.data[certfile.size] = '\0';
+		CHECK_FCT( fd_conf_stream_to_gnutls_datum(stream, &certfile) );
 		fclose(stream);
 		
 		/* Import the certificate(s) */
@@ -347,7 +397,7 @@
 		}
 		
 		CHECK_MALLOC( certs = calloc(cert_max, sizeof(gnutls_x509_crt_t)) );
-		CHECK_GNUTLS_DO( gnutls_x509_crt_list_import(certs, &cert_max, &certfile, GNUTLS_X509_FMT_PEM, 0),
+		CHECK_GNUTLS_DO( gnutls_x509_crt_list_import(certs, &cert_max, &certfile, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_FAIL_IF_UNSORTED),
 			{
 				TRACE_DEBUG(INFO, "Failed to import the data from file '%s'", fd_g_config->cnf_sec_data.cert_file);
 				free(certfile.data);
@@ -358,55 +408,123 @@
 		ASSERT(cert_max >= 1);
 		
 		/* Now, verify the list against the local CA and CRL */
-		GNUTLS_TRACE( gnutls_certificate_get_x509_cas (fd_g_config->cnf_sec_data.credentials, &CA_list, (unsigned int *) &CA_list_length) );
-		GNUTLS_TRACE( gnutls_certificate_get_x509_crls (fd_g_config->cnf_sec_data.credentials, &CRL_list, (unsigned int *) &CRL_list_length) );
-		CHECK_GNUTLS_DO( gnutls_x509_crt_list_verify(certs, cert_max, CA_list, CA_list_length, CRL_list, CRL_list_length, 0, &verify),
+		
+		#ifdef GNUTLS_VERSION_300
+		
+			/* We use the trust list for this purpose */
+		{
+			unsigned int output;
+			
+			gnutls_x509_trust_list_verify_named_crt (
+						fd_g_config->cnf_sec_data.trustlist, 
+						certs[0], 
+						fd_g_config->cnf_diamid, 
+						fd_g_config->cnf_diamid_len,
+                                		0,
+                                		&output, 
+						fd_conf_print_details_func);
+
+			/* if this certificate is not explicitly trusted verify against CAs 
+			*/
+			if (output != 0)
 			{
-				TRACE_DEBUG(INFO, "Failed to verify the local certificate '%s' against local credentials. Please check your certificate is valid.", fd_g_config->cnf_sec_data.cert_file);
+				gnutls_x509_trust_list_verify_crt (
+							fd_g_config->cnf_sec_data.trustlist, 
+							certs, 
+							cert_max,
+                                			0,
+                                			&output, 
+							fd_conf_print_details_func);
+			}
+			
+			if (output & GNUTLS_CERT_INVALID)
+			{
+				fd_log_debug("TLS: Local certificate chain '%s' is invalid :\n", fd_g_config->cnf_sec_data.cert_file);
+				if (output & GNUTLS_CERT_SIGNER_NOT_FOUND)
+					fd_log_debug(" - The certificate hasn't got a known issuer.\n");
+				if (output & GNUTLS_CERT_SIGNER_NOT_CA)
+					fd_log_debug(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.\n");
+				if (output & GNUTLS_CERT_NOT_ACTIVATED)
+					fd_log_debug(" - The certificate is not yet activated.\n");
+				if (output & GNUTLS_CERT_EXPIRED)
+					fd_log_debug(" - The certificate is expired.\n");
 				return EINVAL;
-			} );
-		if (verify) {
-			fd_log_debug("TLS: Local certificate chain '%s' is invalid :\n", fd_g_config->cnf_sec_data.cert_file);
-			if (verify & GNUTLS_CERT_INVALID)
-				fd_log_debug(" - The certificate is not trusted (unknown CA? expired?)\n");
-			if (verify & GNUTLS_CERT_REVOKED)
-				fd_log_debug(" - The certificate has been revoked.\n");
-			if (verify & GNUTLS_CERT_SIGNER_NOT_FOUND)
-				fd_log_debug(" - The certificate hasn't got a known issuer.\n");
-			if (verify & GNUTLS_CERT_SIGNER_NOT_CA)
-				fd_log_debug(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.\n");
-			if (verify & GNUTLS_CERT_INSECURE_ALGORITHM)
-				fd_log_debug(" - The certificate signature uses a weak algorithm.\n");
-			return EINVAL;
-		}
-	
-		/* Check the local Identity is valid with the certificate */
-		if (!gnutls_x509_crt_check_hostname (certs[0], fd_g_config->cnf_diamid)) {
-			fd_log_debug("TLS: Local certificate '%s' is invalid :\n", fd_g_config->cnf_sec_data.cert_file);
-			fd_log_debug(" - The certificate hostname does not match '%s'\n", fd_g_config->cnf_diamid);
-			return EINVAL;
+			}
+			
+			/* Now check the subject matches our hostname */
+			if (!gnutls_x509_crt_check_hostname (certs[0], fd_g_config->cnf_diamid))
+			{
+				fd_log_debug("TLS: The certificate owner does not match the hostname '%s'\n", fd_g_config->cnf_diamid);
+				return EINVAL;
+			}
+			
 		}
 		
-		/* Check validity of all the certificates in the chain */
-		now = time(NULL);
-		for (i = 0; i < cert_max; i++)
+
+		#else /* GNUTLS_VERSION_300 */ 
+		
+			/* GnuTLS 2.x way of checking certificates */
 		{
-			time_t deadline;
+			gnutls_x509_crt_t * CA_list;
+			int CA_list_length;
 
-			GNUTLS_TRACE( deadline = gnutls_x509_crt_get_expiration_time(certs[i]) );
-			if ((deadline != (time_t)-1) && (deadline < now)) {
+			gnutls_x509_crl_t * CRL_list;
+			int CRL_list_length;
+			
+			unsigned int verify;
+			time_t now;
+			GNUTLS_TRACE( gnutls_certificate_get_x509_cas (fd_g_config->cnf_sec_data.credentials, &CA_list, (unsigned int *) &CA_list_length) );
+			GNUTLS_TRACE( gnutls_certificate_get_x509_crls (fd_g_config->cnf_sec_data.credentials, &CRL_list, (unsigned int *) &CRL_list_length) );
+			CHECK_GNUTLS_DO( gnutls_x509_crt_list_verify(certs, cert_max, CA_list, CA_list_length, CRL_list, CRL_list_length, 0, &verify),
+				{
+					TRACE_DEBUG(INFO, "Failed to verify the local certificate '%s' against local credentials. Please check your certificate is valid.", fd_g_config->cnf_sec_data.cert_file);
+					return EINVAL;
+				} );
+				
+			if (verify) {
 				fd_log_debug("TLS: Local certificate chain '%s' is invalid :\n", fd_g_config->cnf_sec_data.cert_file);
-				fd_log_debug(" - The certificate %d in the chain is expired\n", i);
+				if (verify & GNUTLS_CERT_INVALID)
+					fd_log_debug(" - The certificate is not trusted (unknown CA? expired?)\n");
+				if (verify & GNUTLS_CERT_REVOKED)
+					fd_log_debug(" - The certificate has been revoked.\n");
+				if (verify & GNUTLS_CERT_SIGNER_NOT_FOUND)
+					fd_log_debug(" - The certificate hasn't got a known issuer.\n");
+				if (verify & GNUTLS_CERT_SIGNER_NOT_CA)
+					fd_log_debug(" - The certificate signer is not a CA, or uses version 1, or 3 without basic constraints.\n");
+				if (verify & GNUTLS_CERT_INSECURE_ALGORITHM)
+					fd_log_debug(" - The certificate signature uses a weak algorithm.\n");
 				return EINVAL;
 			}
 
-			GNUTLS_TRACE( deadline = gnutls_x509_crt_get_activation_time(certs[i]) );
-			if ((deadline != (time_t)-1) && (deadline > now)) {
-				fd_log_debug("TLS: Local certificate chain '%s' is invalid :\n", fd_g_config->cnf_sec_data.cert_file);
-				fd_log_debug(" - The certificate %d in the chain is not yet activated\n", i);
+			/* Check the local Identity is valid with the certificate */
+			if (!gnutls_x509_crt_check_hostname (certs[0], fd_g_config->cnf_diamid)) {
+				fd_log_debug("TLS: Local certificate '%s' is invalid :\n", fd_g_config->cnf_sec_data.cert_file);
+				fd_log_debug(" - The certificate hostname does not match '%s'\n", fd_g_config->cnf_diamid);
 				return EINVAL;
 			}
+
+			/* Check validity of all the certificates in the chain */
+			now = time(NULL);
+			for (i = 0; i < cert_max; i++)
+			{
+				time_t deadline;
+
+				GNUTLS_TRACE( deadline = gnutls_x509_crt_get_expiration_time(certs[i]) );
+				if ((deadline != (time_t)-1) && (deadline < now)) {
+					fd_log_debug("TLS: Local certificate chain '%s' is invalid :\n", fd_g_config->cnf_sec_data.cert_file);
+					fd_log_debug(" - The certificate %d in the chain is expired\n", i);
+					return EINVAL;
+				}
+
+				GNUTLS_TRACE( deadline = gnutls_x509_crt_get_activation_time(certs[i]) );
+				if ((deadline != (time_t)-1) && (deadline > now)) {
+					fd_log_debug("TLS: Local certificate chain '%s' is invalid :\n", fd_g_config->cnf_sec_data.cert_file);
+					fd_log_debug(" - The certificate %d in the chain is not yet activated\n", i);
+					return EINVAL;
+				}
+			}
 		}
+		#endif /* GNUTLS_VERSION_300 */ 
 		
 		/* Everything checked OK, free the certificate list */
 		for (i = 0; i < cert_max; i++)
@@ -482,6 +600,9 @@
 		return 0;
 	
 	/* Free the TLS parameters */
+#ifdef GNUTLS_VERSION_300
+	gnutls_x509_trust_list_deinit(fd_g_config->cnf_sec_data.trustlist, 1);
+#endif /* GNUTLS_VERSION_300 */
 	gnutls_priority_deinit(fd_g_config->cnf_sec_data.prio_cache);
 	gnutls_dh_params_deinit(fd_g_config->cnf_sec_data.dh_cache);
 	gnutls_certificate_free_credentials(fd_g_config->cnf_sec_data.credentials);
--- a/libfdcore/fdcore-internal.h	Wed Aug 22 00:22:46 2012 +0200
+++ b/libfdcore/fdcore-internal.h	Wed Aug 22 22:56:22 2012 +0200
@@ -79,6 +79,8 @@
 void fd_conf_dump();
 int fd_conf_parse();
 int fddparse(struct fd_config * conf); /* yacc generated */
+int fd_conf_stream_to_gnutls_datum(FILE * pemfile, gnutls_datum_t *out);
+
 
 /* Extensions */
 int fd_ext_add( char * filename, char * conffile );
--- a/libfdcore/fdd.y	Wed Aug 22 00:22:46 2012 +0200
+++ b/libfdcore/fdd.y	Wed Aug 22 22:56:22 2012 +0200
@@ -528,13 +528,32 @@
 tls_ca:			TLS_CA '=' QSTRING ';'
 			{
 				FILE * fd;
-				fd = fopen($3, "r");
+				fd = fopen($3, "rb");
 				if (fd == NULL) {
 					int ret = errno;
 					TRACE_DEBUG(INFO, "Unable to open CA file %s for reading: %s\n", $3, strerror(ret));
 					yyerror (&yylloc, conf, "Error on file name"); 
 					YYERROR;
 				}
+				#ifdef GNUTLS_VERSION_300
+				{
+					/* We import these CA in the trust list */
+					gnutls_x509_crt_t * calist;
+					unsigned int cacount;
+					gnutls_datum_t cafile;
+					
+					CHECK_FCT_DO( fd_conf_stream_to_gnutls_datum(fd, &cafile), 
+							{ yyerror (&yylloc, conf, "Error reading CA file."); YYERROR; } );
+							
+					CHECK_GNUTLS_DO( gnutls_x509_crt_list_import2(&calist, &cacount, &cafile, GNUTLS_X509_FMT_PEM, 
+										GNUTLS_X509_CRT_LIST_FAIL_IF_UNSORTED),
+							{ yyerror (&yylloc, conf, "Error importing CA file."); YYERROR; } );
+					free(cafile.data);
+					
+					CHECK_GNUTLS_DO( gnutls_x509_trust_list_add_cas (fd_g_config->cnf_sec_data.trustlist, calist, cacount, 0),
+							{ yyerror (&yylloc, conf, "Error saving CA in trust list."); YYERROR; } );
+				}
+				#endif /* GNUTLS_VERSION_300 */
 				fclose(fd);
 				conf->cnf_sec_data.ca_file = $3;
 				CHECK_GNUTLS_DO( conf->cnf_sec_data.ca_file_nr += gnutls_certificate_set_x509_trust_file( 
@@ -542,19 +561,40 @@
 							conf->cnf_sec_data.ca_file,
 							GNUTLS_X509_FMT_PEM),
 						{ yyerror (&yylloc, conf, "Error setting CA parameters."); YYERROR; } );
+						
 			}
 			;
 			
 tls_crl:		TLS_CRL '=' QSTRING ';'
 			{
 				FILE * fd;
-				fd = fopen($3, "r");
+				fd = fopen($3, "rb");
 				if (fd == NULL) {
 					int ret = errno;
 					TRACE_DEBUG(INFO, "Unable to open CRL file %s for reading: %s\n", $3, strerror(ret));
 					yyerror (&yylloc, conf, "Error on file name"); 
 					YYERROR;
 				}
+				#ifdef GNUTLS_VERSION_300
+				{
+					/* We import these CRL in the trust list */
+					gnutls_x509_crl_t * crllist;
+					unsigned int crlcount;
+					gnutls_datum_t crlfile;
+					
+					CHECK_FCT_DO( fd_conf_stream_to_gnutls_datum(fd, &crlfile), 
+							{ yyerror (&yylloc, conf, "Error reading CRL file."); YYERROR; } );
+							
+					CHECK_GNUTLS_DO( gnutls_x509_crl_list_import2(&crllist, &crlcount, &crlfile, GNUTLS_X509_FMT_PEM, 0),
+							{ yyerror (&yylloc, conf, "Error importing CRL file."); YYERROR; } );
+					free(crlfile.data);
+					
+					CHECK_GNUTLS_DO( gnutls_x509_trust_list_add_crls (fd_g_config->cnf_sec_data.trustlist, crllist, crlcount, 
+									GNUTLS_TL_VERIFY_CRL,
+									0),
+							{ yyerror (&yylloc, conf, "Error importing CRL in trust list."); YYERROR; } );
+				}
+				#endif /* GNUTLS_VERSION_300 */
 				fclose(fd);
 				conf->cnf_sec_data.crl_file = $3;
 				CHECK_GNUTLS_DO( gnutls_certificate_set_x509_crl_file( 
"Welcome to our mercurial repository"