[libvirt] [PATCH 4/4] Fix validation of CA certificate chains
Doug Goldstein
cardoe at gentoo.org
Wed Aug 7 13:32:26 UTC 2013
On Tue, Aug 6, 2013 at 6:35 AM, Daniel P. Berrange <berrange at redhat.com> wrote:
> From: "Daniel P. Berrange" <berrange at redhat.com>
>
> The code added to validate CA certificates did not take into
> account the possibility that the cacert.pem file can contain
> multiple (concatenated) cert data blocks. Extend the code for
> loading CA certs to use the gnutls APIs for loading cert lists.
> Add test cases to check that multi-level trees of certs will
> validate correctly.
>
> Signed-off-by: Daniel P. Berrange <berrange at redhat.com>
> ---
> src/rpc/virnettlscontext.c | 73 ++++++++++++++++++++++++++++++++++----------
> tests/virnettlscontexttest.c | 59 +++++++++++++++++++++++++++++++++++
> tests/virnettlshelpers.c | 34 +++++++++++++++++++++
> tests/virnettlshelpers.h | 3 ++
> tests/virnettlssessiontest.c | 63 ++++++++++++++++++++++++++++++++++++--
> 5 files changed, 214 insertions(+), 18 deletions(-)
>
> diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c
> index af0ec21..55fb7d0 100644
> --- a/src/rpc/virnettlscontext.c
> +++ b/src/rpc/virnettlscontext.c
> @@ -455,14 +455,15 @@ static int virNetTLSContextCheckCert(gnutls_x509_crt_t cert,
>
> static int virNetTLSContextCheckCertPair(gnutls_x509_crt_t cert,
> const char *certFile,
> - gnutls_x509_crt_t cacert,
> + gnutls_x509_crt_t *cacerts,
> + size_t ncacerts,
> const char *cacertFile,
> bool isServer)
> {
> unsigned int status;
>
> if (gnutls_x509_crt_list_verify(&cert, 1,
> - &cacert, 1,
> + cacerts, ncacerts,
> NULL, 0,
> 0, &status) < 0) {
> virReportError(VIR_ERR_SYSTEM_ERROR, isServer ?
> @@ -500,16 +501,15 @@ static int virNetTLSContextCheckCertPair(gnutls_x509_crt_t cert,
>
>
> static gnutls_x509_crt_t virNetTLSContextLoadCertFromFile(const char *certFile,
> - bool isServer,
> - bool isCA ATTRIBUTE_UNUSED)
> + bool isServer)
> {
> gnutls_datum_t data;
> gnutls_x509_crt_t cert = NULL;
> char *buf = NULL;
> int ret = -1;
>
> - VIR_DEBUG("isServer %d isCA %d certFile %s",
> - isServer, isCA, certFile);
> + VIR_DEBUG("isServer %d certFile %s",
> + isServer, certFile);
>
> if (gnutls_x509_crt_init(&cert) < 0) {
> virReportError(VIR_ERR_SYSTEM_ERROR, "%s",
> @@ -543,40 +543,81 @@ cleanup:
> }
>
>
> +static int virNetTLSContextLoadCACertListFromFile(const char *certFile,
> + gnutls_x509_crt_t *certs,
> + size_t *ncerts)
> +{
> + gnutls_datum_t data;
> + char *buf = NULL;
> + int ret = -1;
> + unsigned int certMax = *ncerts;
> +
> + *ncerts = 0;
> + VIR_DEBUG("certFile %s", certFile);
> +
> + if (virFileReadAll(certFile, (1<<16), &buf) < 0)
> + goto cleanup;
> +
> + data.data = (unsigned char *)buf;
> + data.size = strlen(buf);
> +
> + if (gnutls_x509_crt_list_import(certs, &certMax, &data, GNUTLS_X509_FMT_PEM, 0) < 0) {
> + virReportError(VIR_ERR_SYSTEM_ERROR,
> + _("Unable to import CA certificate list %s"),
> + certFile);
> + goto cleanup;
> + }
> + *ncerts = certMax;
> +
> + ret = 0;
> +
> +cleanup:
> + VIR_FREE(buf);
> + return ret;
> +}
> +
> +
> +#define MAX_CERTS 16
> static int virNetTLSContextSanityCheckCredentials(bool isServer,
> const char *cacertFile,
> const char *certFile)
> {
> gnutls_x509_crt_t cert = NULL;
> - gnutls_x509_crt_t cacert = NULL;
> + gnutls_x509_crt_t cacerts[MAX_CERTS];
> + size_t ncacerts = MAX_CERTS;
> + size_t i;
> int ret = -1;
>
> if ((access(certFile, R_OK) == 0) &&
> - !(cert = virNetTLSContextLoadCertFromFile(certFile, isServer, false)))
> + !(cert = virNetTLSContextLoadCertFromFile(certFile, isServer)))
> goto cleanup;
> if ((access(cacertFile, R_OK) == 0) &&
> - !(cacert = virNetTLSContextLoadCertFromFile(cacertFile, isServer, false)))
> + virNetTLSContextLoadCACertListFromFile(cacertFile, cacerts, &ncacerts) < 0)
> goto cleanup;
>
> if (cert &&
> virNetTLSContextCheckCert(cert, certFile, isServer, false) < 0)
> goto cleanup;
>
> - if (cacert &&
> - virNetTLSContextCheckCert(cacert, cacertFile, isServer, true) < 0)
> - goto cleanup;
> + for (i = 0 ; i < ncacerts ; i++) {
> + if (virNetTLSContextCheckCert(cacerts[i], cacertFile, isServer, true) < 0)
> + goto cleanup;
> + }
>
> - if (cert && cacert &&
> - virNetTLSContextCheckCertPair(cert, certFile, cacert, cacertFile, isServer) < 0)
> + VIR_DEBUG("Here");
> + if (cert && ncacerts &&
> + virNetTLSContextCheckCertPair(cert, certFile, cacerts, ncacerts, cacertFile, isServer) < 0) {
> + VIR_DEBUG("there");
> goto cleanup;
> + }
>
> ret = 0;
>
> cleanup:
> if (cert)
> gnutls_x509_crt_deinit(cert);
> - if (cacert)
> - gnutls_x509_crt_deinit(cacert);
> + for (i = 0 ; i < ncacerts ; i++)
> + gnutls_x509_crt_deinit(cacerts[i]);
> return ret;
> }
>
> diff --git a/tests/virnettlscontexttest.c b/tests/virnettlscontexttest.c
> index 977a095..234922f 100644
> --- a/tests/virnettlscontexttest.c
> +++ b/tests/virnettlscontexttest.c
> @@ -510,6 +510,57 @@ mymain(void)
> DO_CTX_TEST(true, cacertreq.filename, servercertnew1req.filename, true);
> DO_CTX_TEST(false, cacertreq.filename, clientcertnew1req.filename, true);
>
> + TLS_ROOT_REQ(cacertrootreq,
> + "UK", "libvirt root", NULL, NULL, NULL, NULL,
> + true, true, true,
> + true, true, GNUTLS_KEY_KEY_CERT_SIGN,
> + false, false, NULL, NULL,
> + 0, 0);
> + TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
> + "UK", "libvirt level 1a", NULL, NULL, NULL, NULL,
> + true, true, true,
> + true, true, GNUTLS_KEY_KEY_CERT_SIGN,
> + false, false, NULL, NULL,
> + 0, 0);
> + TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
> + "UK", "libvirt level 1b", NULL, NULL, NULL, NULL,
> + true, true, true,
> + true, true, GNUTLS_KEY_KEY_CERT_SIGN,
> + false, false, NULL, NULL,
> + 0, 0);
> + TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
> + "UK", "libvirt level 2a", NULL, NULL, NULL, NULL,
> + true, true, true,
> + true, true, GNUTLS_KEY_KEY_CERT_SIGN,
> + false, false, NULL, NULL,
> + 0, 0);
> + TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
> + "UK", "libvirt.org", NULL, NULL, NULL, NULL,
> + true, true, false,
> + true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
> + true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
> + 0, 0);
> + TLS_CERT_REQ(clientcertlevel2breq, cacertlevel1breq,
> + "UK", "libvirt client level 2b", NULL, NULL, NULL, NULL,
> + true, true, false,
> + true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
> + true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
> + 0, 0);
> +
> + gnutls_x509_crt_t certchain[] = {
> + cacertrootreq.crt,
> + cacertlevel1areq.crt,
> + cacertlevel1breq.crt,
> + cacertlevel2areq.crt,
> + };
> +
> + testTLSWriteCertChain("cacertchain.pem",
> + certchain,
> + ARRAY_CARDINALITY(certchain));
> +
> + DO_CTX_TEST(true, "cacertchain.pem", servercertlevel3areq.filename, false);
> + DO_CTX_TEST(false, "cacertchain.pem", clientcertlevel2breq.filename, false);
> +
> testTLSDiscardCert(&cacertreq);
> testTLSDiscardCert(&cacert1req);
> testTLSDiscardCert(&cacert2req);
> @@ -558,6 +609,14 @@ mymain(void)
> testTLSDiscardCert(&servercertnew1req);
> testTLSDiscardCert(&clientcertnew1req);
>
> + testTLSDiscardCert(&cacertrootreq);
> + testTLSDiscardCert(&cacertlevel1areq);
> + testTLSDiscardCert(&cacertlevel1breq);
> + testTLSDiscardCert(&cacertlevel2areq);
> + testTLSDiscardCert(&servercertlevel3areq);
> + testTLSDiscardCert(&clientcertlevel2breq);
> + unlink("cacertchain.pem");
> +
> testTLSCleanup();
>
> return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE;
> diff --git a/tests/virnettlshelpers.c b/tests/virnettlshelpers.c
> index 8236e82..baf043a 100644
> --- a/tests/virnettlshelpers.c
> +++ b/tests/virnettlshelpers.c
> @@ -406,6 +406,40 @@ testTLSGenerateCert(struct testTLSCertReq *req,
> }
>
>
> +void testTLSWriteCertChain(const char *filename,
> + gnutls_x509_crt_t *certs,
> + size_t ncerts)
> +{
> + size_t i;
> + int fd;
> + int err;
> + static char buffer[1024*1024];
> + size_t size;
> +
> + if ((fd = open(filename, O_WRONLY|O_CREAT, 0600)) < 0) {
> + VIR_WARN("Failed to open %s", filename);
> + abort();
> + }
> +
> + for (i = 0 ; i < ncerts ; i++) {
> + size = sizeof(buffer);
> + if ((err = gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM, buffer, &size) < 0)) {
> + VIR_WARN("Failed to export certificate %s", gnutls_strerror(err));
> + unlink(filename);
> + abort();
> + }
> +
> + if (safewrite(fd, buffer, size) != size) {
> + VIR_WARN("Failed to write certificate to %s", filename);
> + unlink(filename);
> + abort();
> + }
> + }
> +
> + VIR_FORCE_CLOSE(fd);
> +}
> +
> +
> void testTLSDiscardCert(struct testTLSCertReq *req)
> {
> if (!req->crt)
> diff --git a/tests/virnettlshelpers.h b/tests/virnettlshelpers.h
> index 50a4ba1..7c3f8da 100644
> --- a/tests/virnettlshelpers.h
> +++ b/tests/virnettlshelpers.h
> @@ -71,6 +71,9 @@ struct testTLSCertReq {
>
> void testTLSGenerateCert(struct testTLSCertReq *req,
> gnutls_x509_crt_t ca);
> +void testTLSWriteCertChain(const char *filename,
> + gnutls_x509_crt_t *certs,
> + size_t ncerts);
> void testTLSDiscardCert(struct testTLSCertReq *req);
>
> void testTLSInit(void);
> diff --git a/tests/virnettlssessiontest.c b/tests/virnettlssessiontest.c
> index 66df682..c2d34bb 100644
> --- a/tests/virnettlssessiontest.c
> +++ b/tests/virnettlssessiontest.c
> @@ -193,7 +193,7 @@ static int testTLSSessionInit(const void *opaque)
> VIR_WARN("Expected server cert check fail");
> goto cleanup;
> } else {
> - VIR_DEBUG("Not unexpected server cert fail");
> + VIR_DEBUG("No unexpected server cert fail");
> }
> }
>
> @@ -213,7 +213,7 @@ static int testTLSSessionInit(const void *opaque)
> VIR_WARN("Expected client cert check fail");
> goto cleanup;
> } else {
> - VIR_DEBUG("Not unexpected client cert fail");
> + VIR_DEBUG("No unexpected client cert fail");
> }
> }
>
> @@ -405,6 +405,57 @@ mymain(void)
> DO_SESS_TEST(cacertreq.filename, servercertreq.filename, clientcertreq.filename,
> false, false, "libvirt.org", wildcards6);
>
> + TLS_ROOT_REQ(cacertrootreq,
> + "UK", "libvirt root", NULL, NULL, NULL, NULL,
> + true, true, true,
> + true, true, GNUTLS_KEY_KEY_CERT_SIGN,
> + false, false, NULL, NULL,
> + 0, 0);
> + TLS_CERT_REQ(cacertlevel1areq, cacertrootreq,
> + "UK", "libvirt level 1a", NULL, NULL, NULL, NULL,
> + true, true, true,
> + true, true, GNUTLS_KEY_KEY_CERT_SIGN,
> + false, false, NULL, NULL,
> + 0, 0);
> + TLS_CERT_REQ(cacertlevel1breq, cacertrootreq,
> + "UK", "libvirt level 1b", NULL, NULL, NULL, NULL,
> + true, true, true,
> + true, true, GNUTLS_KEY_KEY_CERT_SIGN,
> + false, false, NULL, NULL,
> + 0, 0);
> + TLS_CERT_REQ(cacertlevel2areq, cacertlevel1areq,
> + "UK", "libvirt level 2a", NULL, NULL, NULL, NULL,
> + true, true, true,
> + true, true, GNUTLS_KEY_KEY_CERT_SIGN,
> + false, false, NULL, NULL,
> + 0, 0);
> + TLS_CERT_REQ(servercertlevel3areq, cacertlevel2areq,
> + "UK", "libvirt.org", NULL, NULL, NULL, NULL,
> + true, true, false,
> + true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
> + true, true, GNUTLS_KP_TLS_WWW_SERVER, NULL,
> + 0, 0);
> + TLS_CERT_REQ(clientcertlevel2breq, cacertlevel1breq,
> + "UK", "libvirt client level 2b", NULL, NULL, NULL, NULL,
> + true, true, false,
> + true, true, GNUTLS_KEY_DIGITAL_SIGNATURE | GNUTLS_KEY_KEY_ENCIPHERMENT,
> + true, true, GNUTLS_KP_TLS_WWW_CLIENT, NULL,
> + 0, 0);
> +
> + gnutls_x509_crt_t certchain[] = {
> + cacertrootreq.crt,
> + cacertlevel1areq.crt,
> + cacertlevel1breq.crt,
> + cacertlevel2areq.crt,
> + };
> +
> + testTLSWriteCertChain("cacertchain.pem",
> + certchain,
> + ARRAY_CARDINALITY(certchain));
> +
> + DO_SESS_TEST("cacertchain.pem", servercertlevel3areq.filename, clientcertlevel2breq.filename,
> + false, false, "libvirt.org", NULL);
> +
> testTLSDiscardCert(&clientcertreq);
> testTLSDiscardCert(&clientcertaltreq);
>
> @@ -415,6 +466,14 @@ mymain(void)
> testTLSDiscardCert(&cacertreq);
> testTLSDiscardCert(&altcacertreq);
>
> + testTLSDiscardCert(&cacertrootreq);
> + testTLSDiscardCert(&cacertlevel1areq);
> + testTLSDiscardCert(&cacertlevel1breq);
> + testTLSDiscardCert(&cacertlevel2areq);
> + testTLSDiscardCert(&servercertlevel3areq);
> + testTLSDiscardCert(&clientcertlevel2breq);
> + unlink("cacertchain.pem");
> +
> testTLSCleanup();
>
> return ret==0 ? EXIT_SUCCESS : EXIT_FAILURE;
> --
> 1.8.3.1
To follow Michal's ACK. I've tried just this patch with a chained
certificate I had and I can confirm this worked.
--
Doug Goldstein
More information about the libvir-list
mailing list