43fe83
From efc84d335fa6ec218e7ad4c942300b3e5bd27bc8 Mon Sep 17 00:00:00 2001
43fe83
Message-Id: <efc84d335fa6ec218e7ad4c942300b3e5bd27bc8.1377873637.git.jdenemar@redhat.com>
43fe83
From: "Daniel P. Berrange" <berrange@redhat.com>
43fe83
Date: Tue, 13 Aug 2013 11:32:48 +0100
43fe83
Subject: [PATCH] Fix validation of CA certificate chains
43fe83
43fe83
For https://bugzilla.redhat.com/show_bug.cgi?id=994158
43fe83
43fe83
The code added to validate CA certificates did not take into
43fe83
account the possibility that the cacert.pem file can contain
43fe83
multiple (concatenated) cert data blocks. Extend the code for
43fe83
loading CA certs to use the gnutls APIs for loading cert lists.
43fe83
Add test cases to check that multi-level trees of certs will
43fe83
validate correctly.
43fe83
43fe83
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
43fe83
(cherry picked from commit 31d41d9268a6731e303700b5a5825a87a6f36a19)
43fe83
---
43fe83
 src/rpc/virnettlscontext.c   | 70 ++++++++++++++++++++++++++++++++++----------
43fe83
 tests/virnettlscontexttest.c | 59 +++++++++++++++++++++++++++++++++++++
43fe83
 tests/virnettlshelpers.c     | 34 +++++++++++++++++++++
43fe83
 tests/virnettlshelpers.h     |  3 ++
43fe83
 tests/virnettlssessiontest.c | 63 +++++++++++++++++++++++++++++++++++++--
43fe83
 5 files changed, 211 insertions(+), 18 deletions(-)
43fe83
43fe83
diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c
43fe83
index af0ec21..2beee8f 100644
43fe83
--- a/src/rpc/virnettlscontext.c
43fe83
+++ b/src/rpc/virnettlscontext.c
43fe83
@@ -455,14 +455,15 @@ static int virNetTLSContextCheckCert(gnutls_x509_crt_t cert,
43fe83
 
43fe83
 static int virNetTLSContextCheckCertPair(gnutls_x509_crt_t cert,
43fe83
                                          const char *certFile,
43fe83
-                                         gnutls_x509_crt_t cacert,
43fe83
+                                         gnutls_x509_crt_t *cacerts,
43fe83
+                                         size_t ncacerts,
43fe83
                                          const char *cacertFile,
43fe83
                                          bool isServer)
43fe83
 {
43fe83
     unsigned int status;
43fe83
 
43fe83
     if (gnutls_x509_crt_list_verify(&cert, 1,
43fe83
-                                    &cacert, 1,
43fe83
+                                    cacerts, ncacerts,
43fe83
                                     NULL, 0,
43fe83
                                     0, &status) < 0) {
43fe83
         virReportError(VIR_ERR_SYSTEM_ERROR, isServer ?
43fe83
@@ -500,16 +501,15 @@ static int virNetTLSContextCheckCertPair(gnutls_x509_crt_t cert,
43fe83
 
43fe83
 
43fe83
 static gnutls_x509_crt_t virNetTLSContextLoadCertFromFile(const char *certFile,
43fe83
-                                                          bool isServer,
43fe83
-                                                          bool isCA ATTRIBUTE_UNUSED)
43fe83
+                                                          bool isServer)
43fe83
 {
43fe83
     gnutls_datum_t data;
43fe83
     gnutls_x509_crt_t cert = NULL;
43fe83
     char *buf = NULL;
43fe83
     int ret = -1;
43fe83
 
43fe83
-    VIR_DEBUG("isServer %d isCA %d certFile %s",
43fe83
-              isServer, isCA, certFile);
43fe83
+    VIR_DEBUG("isServer %d certFile %s",
43fe83
+              isServer, certFile);
43fe83
 
43fe83
     if (gnutls_x509_crt_init(&cert) < 0) {
43fe83
         virReportError(VIR_ERR_SYSTEM_ERROR, "%s",
43fe83
@@ -543,31 +543,69 @@ cleanup:
43fe83
 }
43fe83
 
43fe83
 
43fe83
+static int virNetTLSContextLoadCACertListFromFile(const char *certFile,
43fe83
+                                                  gnutls_x509_crt_t *certs,
43fe83
+                                                  size_t *ncerts)
43fe83
+{
43fe83
+    gnutls_datum_t data;
43fe83
+    char *buf = NULL;
43fe83
+    int ret = -1;
43fe83
+    unsigned int certMax = *ncerts;
43fe83
+
43fe83
+    *ncerts = 0;
43fe83
+    VIR_DEBUG("certFile %s", certFile);
43fe83
+
43fe83
+    if (virFileReadAll(certFile, (1<<16), &buf) < 0)
43fe83
+        goto cleanup;
43fe83
+
43fe83
+    data.data = (unsigned char *)buf;
43fe83
+    data.size = strlen(buf);
43fe83
+
43fe83
+    if (gnutls_x509_crt_list_import(certs, &certMax, &data, GNUTLS_X509_FMT_PEM, 0) < 0) {
43fe83
+        virReportError(VIR_ERR_SYSTEM_ERROR,
43fe83
+                       _("Unable to import CA certificate list %s"),
43fe83
+                       certFile);
43fe83
+        goto cleanup;
43fe83
+    }
43fe83
+    *ncerts = certMax;
43fe83
+
43fe83
+    ret = 0;
43fe83
+
43fe83
+cleanup:
43fe83
+    VIR_FREE(buf);
43fe83
+    return ret;
43fe83
+}
43fe83
+
43fe83
+
43fe83
+#define MAX_CERTS 16
43fe83
 static int virNetTLSContextSanityCheckCredentials(bool isServer,
43fe83
                                                   const char *cacertFile,
43fe83
                                                   const char *certFile)
43fe83
 {
43fe83
     gnutls_x509_crt_t cert = NULL;
43fe83
-    gnutls_x509_crt_t cacert = NULL;
43fe83
+    gnutls_x509_crt_t cacerts[MAX_CERTS];
43fe83
+    size_t ncacerts = MAX_CERTS;
43fe83
+    size_t i;
43fe83
     int ret = -1;
43fe83
 
43fe83
     if ((access(certFile, R_OK) == 0) &&
43fe83
-        !(cert = virNetTLSContextLoadCertFromFile(certFile, isServer, false)))
43fe83
+        !(cert = virNetTLSContextLoadCertFromFile(certFile, isServer)))
43fe83
         goto cleanup;
43fe83
     if ((access(cacertFile, R_OK) == 0) &&
43fe83
-        !(cacert = virNetTLSContextLoadCertFromFile(cacertFile, isServer, false)))
43fe83
+        virNetTLSContextLoadCACertListFromFile(cacertFile, cacerts, &ncacerts) < 0)
43fe83
         goto cleanup;
43fe83
 
43fe83
     if (cert &&
43fe83
         virNetTLSContextCheckCert(cert, certFile, isServer, false) < 0)
43fe83
         goto cleanup;
43fe83
 
43fe83
-    if (cacert &&
43fe83
-        virNetTLSContextCheckCert(cacert, cacertFile, isServer, true) < 0)
43fe83
-        goto cleanup;
43fe83
+    for (i = 0; i < ncacerts; i++) {
43fe83
+        if (virNetTLSContextCheckCert(cacerts[i], cacertFile, isServer, true) < 0)
43fe83
+            goto cleanup;
43fe83
+    }
43fe83
 
43fe83
-    if (cert && cacert &&
43fe83
-        virNetTLSContextCheckCertPair(cert, certFile, cacert, cacertFile, isServer) < 0)
43fe83
+    if (cert && ncacerts &&
43fe83
+        virNetTLSContextCheckCertPair(cert, certFile, cacerts, ncacerts, cacertFile, isServer) < 0)
43fe83
         goto cleanup;
43fe83
 
43fe83
     ret = 0;
43fe83
@@ -575,8 +613,8 @@ static int virNetTLSContextSanityCheckCredentials(bool isServer,
43fe83
 cleanup:
43fe83
     if (cert)
43fe83
         gnutls_x509_crt_deinit(cert);
43fe83
-    if (cacert)
43fe83
-        gnutls_x509_crt_deinit(cacert);
43fe83
+    for (i = 0; i < ncacerts; i++)
43fe83
+        gnutls_x509_crt_deinit(cacerts[i]);
43fe83
     return ret;
43fe83
 }
43fe83
 
43fe83
diff --git a/tests/virnettlscontexttest.c b/tests/virnettlscontexttest.c
43fe83
index 3012c4a..9ade785 100644
43fe83
--- a/tests/virnettlscontexttest.c
43fe83
+++ b/tests/virnettlscontexttest.c
43fe83
@@ -510,6 +510,57 @@ mymain(void)
43fe83
     DO_CTX_TEST(true, cacertreq.filename, servercertnew1req.filename, true);
43fe83
     DO_CTX_TEST(false, cacertreq.filename, clientcertnew1req.filename, true);
43fe83
 
43fe83
+    TLS_ROOT_REQ(cacertrootreq,
43fe83
+                 "UK", "libvirt root", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, true,
43fe83
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
43fe83
+                 false, false, NULL, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
43fe83
+                 "UK", "libvirt level 1a", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, true,
43fe83
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
43fe83
+                 false, false, NULL, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
43fe83
+                 "UK", "libvirt level 1b", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, true,
43fe83
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
43fe83
+                 false, false, NULL, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
43fe83
+                 "UK", "libvirt level 2a", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, true,
43fe83
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
43fe83
+                 false, false, NULL, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
43fe83
+                 "UK", "libvirt.org", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, false,
43fe83
+                 true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
43fe83
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(clientcertlevel2breq, cacertlevel1breq,
43fe83
+                 "UK", "libvirt client level 2b", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, false,
43fe83
+                 true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
43fe83
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
43fe83
+                 0, 0);
43fe83
+
43fe83
+    gnutls_x509_crt_t certchain[] = {
43fe83
+        cacertrootreq.crt,
43fe83
+        cacertlevel1areq.crt,
43fe83
+        cacertlevel1breq.crt,
43fe83
+        cacertlevel2areq.crt,
43fe83
+    };
43fe83
+
43fe83
+    testTLSWriteCertChain("cacertchain.pem",
43fe83
+                          certchain,
43fe83
+                          ARRAY_CARDINALITY(certchain));
43fe83
+
43fe83
+    DO_CTX_TEST(true, "cacertchain.pem", servercertlevel3areq.filename, false);
43fe83
+    DO_CTX_TEST(false, "cacertchain.pem", clientcertlevel2breq.filename, false);
43fe83
+
43fe83
     testTLSDiscardCert(&cacertreq);
43fe83
     testTLSDiscardCert(&cacert1req);
43fe83
     testTLSDiscardCert(&cacert2req);
43fe83
@@ -558,6 +609,14 @@ mymain(void)
43fe83
     testTLSDiscardCert(&servercertnew1req);
43fe83
     testTLSDiscardCert(&clientcertnew1req);
43fe83
 
43fe83
+    testTLSDiscardCert(&cacertrootreq);
43fe83
+    testTLSDiscardCert(&cacertlevel1areq);
43fe83
+    testTLSDiscardCert(&cacertlevel1breq);
43fe83
+    testTLSDiscardCert(&cacertlevel2areq);
43fe83
+    testTLSDiscardCert(&servercertlevel3areq);
43fe83
+    testTLSDiscardCert(&clientcertlevel2breq);
43fe83
+    unlink("cacertchain.pem");
43fe83
+
43fe83
     testTLSCleanup();
43fe83
 
43fe83
     return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE;
43fe83
diff --git a/tests/virnettlshelpers.c b/tests/virnettlshelpers.c
43fe83
index 8236e82..39a2df6 100644
43fe83
--- a/tests/virnettlshelpers.c
43fe83
+++ b/tests/virnettlshelpers.c
43fe83
@@ -406,6 +406,40 @@ testTLSGenerateCert(struct testTLSCertReq *req,
43fe83
 }
43fe83
 
43fe83
 
43fe83
+void testTLSWriteCertChain(const char *filename,
43fe83
+                           gnutls_x509_crt_t *certs,
43fe83
+                           size_t ncerts)
43fe83
+{
43fe83
+    size_t i;
43fe83
+    int fd;
43fe83
+    int err;
43fe83
+    static char buffer[1024*1024];
43fe83
+    size_t size;
43fe83
+
43fe83
+    if ((fd = open(filename, O_WRONLY|O_CREAT, 0600)) < 0) {
43fe83
+        VIR_WARN("Failed to open %s", filename);
43fe83
+        abort();
43fe83
+    }
43fe83
+
43fe83
+    for (i = 0; i < ncerts; i++) {
43fe83
+        size = sizeof(buffer);
43fe83
+        if ((err = gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM, buffer, &size) < 0)) {
43fe83
+            VIR_WARN("Failed to export certificate %s", gnutls_strerror(err));
43fe83
+            unlink(filename);
43fe83
+            abort();
43fe83
+        }
43fe83
+
43fe83
+        if (safewrite(fd, buffer, size) != size) {
43fe83
+            VIR_WARN("Failed to write certificate to %s", filename);
43fe83
+            unlink(filename);
43fe83
+            abort();
43fe83
+        }
43fe83
+    }
43fe83
+
43fe83
+    VIR_FORCE_CLOSE(fd);
43fe83
+}
43fe83
+
43fe83
+
43fe83
 void testTLSDiscardCert(struct testTLSCertReq *req)
43fe83
 {
43fe83
     if (!req->crt)
43fe83
diff --git a/tests/virnettlshelpers.h b/tests/virnettlshelpers.h
43fe83
index 50a4ba1..7c3f8da 100644
43fe83
--- a/tests/virnettlshelpers.h
43fe83
+++ b/tests/virnettlshelpers.h
43fe83
@@ -71,6 +71,9 @@ struct testTLSCertReq {
43fe83
 
43fe83
 void testTLSGenerateCert(struct testTLSCertReq *req,
43fe83
                          gnutls_x509_crt_t ca);
43fe83
+void testTLSWriteCertChain(const char *filename,
43fe83
+                           gnutls_x509_crt_t *certs,
43fe83
+                           size_t ncerts);
43fe83
 void testTLSDiscardCert(struct testTLSCertReq *req);
43fe83
 
43fe83
 void testTLSInit(void);
43fe83
diff --git a/tests/virnettlssessiontest.c b/tests/virnettlssessiontest.c
43fe83
index 8636fc8..bc176aa 100644
43fe83
--- a/tests/virnettlssessiontest.c
43fe83
+++ b/tests/virnettlssessiontest.c
43fe83
@@ -193,7 +193,7 @@ static int testTLSSessionInit(const void *opaque)
43fe83
             VIR_WARN("Expected server cert check fail");
43fe83
             goto cleanup;
43fe83
         } else {
43fe83
-            VIR_DEBUG("Not unexpected server cert fail");
43fe83
+            VIR_DEBUG("No unexpected server cert fail");
43fe83
         }
43fe83
     }
43fe83
 
43fe83
@@ -213,7 +213,7 @@ static int testTLSSessionInit(const void *opaque)
43fe83
             VIR_WARN("Expected client cert check fail");
43fe83
             goto cleanup;
43fe83
         } else {
43fe83
-            VIR_DEBUG("Not unexpected client cert fail");
43fe83
+            VIR_DEBUG("No unexpected client cert fail");
43fe83
         }
43fe83
     }
43fe83
 
43fe83
@@ -405,6 +405,57 @@ mymain(void)
43fe83
     DO_SESS_TEST(cacertreq.filename, servercertreq.filename, clientcertreq.filename,
43fe83
                  false, false, "libvirt.org", wildcards6);
43fe83
 
43fe83
+    TLS_ROOT_REQ(cacertrootreq,
43fe83
+                 "UK", "libvirt root", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, true,
43fe83
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
43fe83
+                 false, false, NULL, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
43fe83
+                 "UK", "libvirt level 1a", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, true,
43fe83
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
43fe83
+                 false, false, NULL, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
43fe83
+                 "UK", "libvirt level 1b", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, true,
43fe83
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
43fe83
+                 false, false, NULL, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
43fe83
+                 "UK", "libvirt level 2a", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, true,
43fe83
+                 true, true, GNUTLS_KEY_KEY_CERT_SIGN,
43fe83
+                 false, false, NULL, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
43fe83
+                 "UK", "libvirt.org", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, false,
43fe83
+                 true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
43fe83
+                 true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
43fe83
+                 0, 0);
43fe83
+    TLS_CERT_REQ(clientcertlevel2breq, cacertlevel1breq,
43fe83
+                 "UK", "libvirt client level 2b", NULL, NULL, NULL, NULL,
43fe83
+                 true, true, false,
43fe83
+                 true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
43fe83
+                 true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
43fe83
+                 0, 0);
43fe83
+
43fe83
+    gnutls_x509_crt_t certchain[] = {
43fe83
+        cacertrootreq.crt,
43fe83
+        cacertlevel1areq.crt,
43fe83
+        cacertlevel1breq.crt,
43fe83
+        cacertlevel2areq.crt,
43fe83
+    };
43fe83
+
43fe83
+    testTLSWriteCertChain("cacertchain.pem",
43fe83
+                          certchain,
43fe83
+                          ARRAY_CARDINALITY(certchain));
43fe83
+
43fe83
+    DO_SESS_TEST("cacertchain.pem", servercertlevel3areq.filename, clientcertlevel2breq.filename,
43fe83
+                 false, false, "libvirt.org", NULL);
43fe83
+
43fe83
     testTLSDiscardCert(&clientcertreq);
43fe83
     testTLSDiscardCert(&clientcertaltreq);
43fe83
 
43fe83
@@ -415,6 +466,14 @@ mymain(void)
43fe83
     testTLSDiscardCert(&cacertreq);
43fe83
     testTLSDiscardCert(&altcacertreq);
43fe83
 
43fe83
+    testTLSDiscardCert(&cacertrootreq);
43fe83
+    testTLSDiscardCert(&cacertlevel1areq);
43fe83
+    testTLSDiscardCert(&cacertlevel1breq);
43fe83
+    testTLSDiscardCert(&cacertlevel2areq);
43fe83
+    testTLSDiscardCert(&servercertlevel3areq);
43fe83
+    testTLSDiscardCert(&clientcertlevel2breq);
43fe83
+    unlink("cacertchain.pem");
43fe83
+
43fe83
     testTLSCleanup();
43fe83
 
43fe83
     return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE;
43fe83
-- 
43fe83
1.8.3.2
43fe83