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

Reply via email to