Gnutls requires that certificates have basic constraints present to be used as a CA certificate. OpenSSL doesn't add this data by default, so add a sanity check to catch this situation. Also validate that the key usage and key purpose constraints contain correct data
* src/rpc/virnettlscontext.c: Add sanity checking of certificate constraints --- src/rpc/virnettlscontext.c | 130 ++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 126 insertions(+), 4 deletions(-) diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c index 6a06565..7f088d2 100644 --- a/src/rpc/virnettlscontext.c +++ b/src/rpc/virnettlscontext.c @@ -98,6 +98,7 @@ static void virNetTLSLog(int level, const char *str) { static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool isServer, + bool isCA, const char *certFile) { gnutls_datum_t data; @@ -105,6 +106,14 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool isServer, char *buf = NULL; int ret = -1; time_t now; + int status; + int i; + char *buffer = NULL; + size_t size; + unsigned int usage; + + VIR_DEBUG("isServer %d isCA %d certFile %s", + isServer, isCA, certFile); if ((now = time(NULL)) == ((time_t)-1)) { virReportSystemError(errno, "%s", @@ -145,6 +154,117 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool isServer, goto cleanup; } + status = gnutls_x509_crt_get_basic_constraints(cert, NULL, NULL, NULL); + VIR_DEBUG("Cert %s basic constraints %d", certFile, status); + + if (status > 0) { /* It is a CA cert */ + if (!isCA) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("The certificate %s basic constraints show a CA, but we need one for a %s"), + certFile, isServer ? "server" : "client"); + goto cleanup; + } + } else if (status == 0) { /* It is not a CA cert */ + if (isCA) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("The certificate %s basic constraints do not show a CA"), + certFile); + goto cleanup; + } + } else if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { /* Missing basicConstraints */ + if (isCA) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("The certificate %s is missing basic constraints for a CA"), + certFile); + goto cleanup; + } + } else { /* General error */ + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to query certificate %s basic constraints %s"), + certFile, gnutls_strerror(status)); + goto cleanup; + } + + status = gnutls_x509_crt_get_key_usage(cert, &usage, NULL); + + VIR_DEBUG("Cert %s key usage status %d usage %d", certFile, status, usage); + if (status < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to query certificate %s basic constraints %s"), + certFile, gnutls_strerror(status)); + goto cleanup; + } + + if (usage & GNUTLS_KEY_KEY_CERT_SIGN) { + if (!isCA) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %s usage is for certificate signing, but wanted a %s certificate"), + certFile, isServer ? "server" : "client"); + goto cleanup; + } + } else { + if (isCA) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %s usage is for not certificate signing"), + certFile); + goto cleanup; + } + } + + for (i = 0 ; ; i++) { + size = 0; + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, &size, NULL); + + if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + VIR_DEBUG("No key purpose data available, skipping checks"); + break; + } + if (status != GNUTLS_E_SHORT_MEMORY_BUFFER) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to query certificate %s key purpose %s"), + certFile, gnutls_strerror(status)); + goto cleanup; + } + + if (VIR_ALLOC_N(buffer, size) < 0) { + virReportOOMError(); + goto cleanup; + } + + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, &size, NULL); + if (status < 0) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Unable to query certificate %s key purpose %s"), + certFile, gnutls_strerror(status)); + goto cleanup; + } + + VIR_DEBUG("Key purpose %d %s", status, buffer); + if (STREQ(buffer, GNUTLS_KP_TLS_WWW_SERVER)) { + if (isCA || !isServer) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %s purpose is TLS server, but wanted a %s certificate"), + certFile, isCA ? "CA" : "TLS client"); + goto cleanup; + } + } else if (STREQ(buffer, GNUTLS_KP_TLS_WWW_CLIENT)) { + if (isCA || isServer) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %s purpose is TLS client, but wanted a %s certificate"), + certFile, isCA ? "CA" : "TLS server"); + goto cleanup; + } + } else if (STRNEQ(buffer, GNUTLS_KP_ANY)) { + virNetError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %s purpose is wrong, wanted a %s certificate"), + certFile, isCA ? "CA" : (isServer ? "TLS server" : "TLS client")); + goto cleanup; + } + + VIR_FREE(buffer); + } + + ret = 0; cleanup: @@ -152,6 +272,7 @@ cleanup: gnutls_x509_crt_deinit(cert); cert = NULL; } + VIR_FREE(buffer); VIR_FREE(buf); return cert; } @@ -167,11 +288,11 @@ static int virNetTLSContextSanityCheckCredentials(bool isServer, unsigned int status; if (access(certFile, R_OK) == 0) { - if (!(cert = virNetTLSContextSanityCheckCert(isServer, certFile))) + if (!(cert = virNetTLSContextSanityCheckCert(isServer, false, certFile))) goto cleanup; } if (access(cacertFile, R_OK) == 0) { - if (!(cacert = virNetTLSContextSanityCheckCert(isServer, cacertFile))) + if (!(cacert = virNetTLSContextSanityCheckCert(isServer, true, cacertFile))) goto cleanup; } @@ -343,9 +464,10 @@ static virNetTLSContextPtr virNetTLSContextNew(const char *cacert, goto error; } - if (virNetTLSContextSanityCheckCredentials(isServer, cacert, cert) < 0) + if (requireValidCert && + virNetTLSContextSanityCheckCredentials(isServer, cacert, cert) < 0) goto error; - + if (virNetTLSContextLoadCredentials(ctxt, isServer, cacert, cacrl, cert, key) < 0) goto error; -- 1.7.4.4 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list