From: "Daniel P. Berrange" <berrange(a)redhat.com>
There is some commonality between the code for sanity checking
certs when initializing libvirt and the code for validating
certs during a live TLS session handshake. This patchset splits
up the sanity checking function into several smaller functions
each doing a specific type of check. The cert validation code
is then updated to also call into these functions
* src/rpc/virnettlscontext.c: Refactor cert validation code
---
src/rpc/virnettlscontext.c | 466 +++++++++++++++++++++++++++-----------------
1 files changed, 283 insertions(+), 183 deletions(-)
diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c
index 7d271bc..37b55ad 100644
--- a/src/rpc/virnettlscontext.c
+++ b/src/rpc/virnettlscontext.c
@@ -97,68 +97,51 @@ static void virNetTLSLog(int level, const char *str) {
}
-static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool isServer,
- bool isCA,
- const char *certFile)
+static int virNetTLSContextCheckCertTimes(gnutls_x509_crt_t cert,
+ const char *certFile,
+ bool isServer,
+ bool isCA)
{
- gnutls_datum_t data;
- gnutls_x509_crt_t cert = NULL;
- char *buf = NULL;
- int ret = -1;
time_t now;
- int status;
- int i;
- char *buffer = NULL;
- size_t size;
- unsigned int usage;
- unsigned int critical;
- bool allowClient = false, allowServer = false;
-
- VIR_DEBUG("isServer %d isCA %d certFile %s",
- isServer, isCA, certFile);
if ((now = time(NULL)) == ((time_t)-1)) {
virReportSystemError(errno, "%s",
_("cannot get current time"));
- goto cleanup;
- }
-
- if (gnutls_x509_crt_init(&cert) < 0) {
- virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
- _("Unable to initialize certificate"));
- goto cleanup;
- }
-
- if (virFileReadAll(certFile, (1<<16), &buf) < 0)
- goto cleanup;
-
- data.data = (unsigned char *)buf;
- data.size = strlen(buf);
-
- if (gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_PEM) < 0) {
- virNetError(VIR_ERR_SYSTEM_ERROR, isServer ?
- _("Unable to import server certificate %s") :
- _("Unable to import client certificate %s"),
- certFile);
- goto cleanup;
+ return -1;
}
if (gnutls_x509_crt_get_expiration_time(cert) < now) {
- virNetError(VIR_ERR_SYSTEM_ERROR, isServer ?
- _("The server certificate %s has expired") :
- _("The client certificate %s has expired"),
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ (isCA ?
+ _("The CA certificate %s has expired") :
+ (isServer ?
+ _("The server certificate %s has expired") :
+ _("The client certificate %s has expired"))),
certFile);
- goto cleanup;
+ return -1;
}
if (gnutls_x509_crt_get_activation_time(cert) > now) {
- virNetError(VIR_ERR_SYSTEM_ERROR, isServer ?
- _("The server certificate %s is not yet active") :
- _("The client certificate %s is not yet active"),
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ (isCA ?
+ _("The CA certificate %s is not yet active") :
+ (isServer ?
+ _("The server certificate %s is not yet active") :
+ _("The client certificate %s is not yet active"))),
certFile);
- goto cleanup;
+ return -1;
}
+ return 0;
+}
+
+static int virNetTLSContextCheckCertBasicConstraints(gnutls_x509_crt_t cert,
+ const char *certFile,
+ bool isServer,
+ bool isCA)
+{
+ int status;
+
status = gnutls_x509_crt_get_basic_constraints(cert, NULL, NULL, NULL);
VIR_DEBUG("Cert %s basic constraints %d", certFile, status);
@@ -168,29 +151,40 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
_("The certificate %s basic constraints show a CA, but we
need one for a server") :
_("The certificate %s basic constraints show a CA, but we
need one for a client"),
certFile);
- goto cleanup;
+ return -1;
}
} 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;
+ return -1;
}
} 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;
+ return -1;
}
} else { /* General error */
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Unable to query certificate %s basic constraints %s"),
certFile, gnutls_strerror(status));
- goto cleanup;
+ return -1;
}
+ return 0;
+}
+
+static int virNetTLSContextCheckCertKeyUsage(gnutls_x509_crt_t cert,
+ const char *certFile,
+ bool isCA)
+{
+ int status;
+ unsigned int usage;
+ unsigned int critical;
+
status = gnutls_x509_crt_get_key_usage(cert, &usage, &critical);
VIR_DEBUG("Cert %s key usage status %d usage %d critical %u", certFile,
status, usage, critical);
@@ -202,7 +196,7 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Unable to query certificate %s key usage %s"),
certFile, gnutls_strerror(status));
- goto cleanup;
+ return -1;
}
}
@@ -212,7 +206,7 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Certificate %s usage does not permit certificate
signing"),
certFile);
- goto cleanup;
+ return -1;
} else {
VIR_WARN("Certificate %s usage does not permit certificate
signing",
certFile);
@@ -224,7 +218,7 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Certificate %s usage does not permit digital
signature"),
certFile);
- goto cleanup;
+ return -1;
} else {
VIR_WARN("Certificate %s usage does not permit digital
signature",
certFile);
@@ -235,7 +229,7 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Certificate %s usage does not permit key
encipherment"),
certFile);
- goto cleanup;
+ return -1;
} else {
VIR_WARN("Certificate %s usage does not permit key
encipherment",
certFile);
@@ -243,10 +237,25 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
}
}
+ return 0;
+}
+
+
+static int virNetTLSContextCheckCertKeyPurpose(gnutls_x509_crt_t cert,
+ const char *certFile,
+ bool isServer)
+{
+ int status;
+ int i;
+ unsigned int purposeCritical;
+ unsigned int critical;
+ char *buffer;
+ size_t size;
+ bool allowClient = false, allowServer = false;
+
critical = 0;
for (i = 0 ; ; i++) {
size = 0;
- unsigned int purposeCritical;
status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, &size, NULL);
if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) {
@@ -261,20 +270,21 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Unable to query certificate %s key purpose %s"),
certFile, gnutls_strerror(status));
- goto cleanup;
+ return -1;
}
if (VIR_ALLOC_N(buffer, size) < 0) {
virReportOOMError();
- goto cleanup;
+ return -1;
}
status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, &size,
&purposeCritical);
if (status < 0) {
+ VIR_FREE(buffer);
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Unable to query certificate %s key purpose %s"),
certFile, gnutls_strerror(status));
- goto cleanup;
+ return -1;
}
if (purposeCritical)
critical = true;
@@ -291,24 +301,25 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
VIR_FREE(buffer);
}
- if (!isCA) { /* No purpose checks required for CA certs */
- if (isServer && !allowServer) {
+ if (isServer) {
+ if (!allowServer) {
if (critical) {
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Certificate %s purpose does not allow use for with a
TLS server"),
certFile);
- goto cleanup;
+ return -1;
} else {
VIR_WARN("Certificate %s purpose does not allow use for with a TLS
server",
certFile);
}
}
- if (!isServer && !allowClient) {
+ } else {
+ if (!allowClient) {
if (critical) {
virNetError(VIR_ERR_SYSTEM_ERROR,
_("Certificate %s purpose does not allow use for with a
TLS client"),
certFile);
- goto cleanup;
+ return -1;
} else {
VIR_WARN("Certificate %s purpose does not allow use for with a TLS
client",
certFile);
@@ -316,6 +327,181 @@ static gnutls_x509_crt_t virNetTLSContextSanityCheckCert(bool
isServer,
}
}
+ return 0;
+}
+
+/* Check DN is on tls_allowed_dn_list. */
+static int
+virNetTLSContextCheckCertDNWhitelist(const char *dname,
+ const char *const*wildcards)
+{
+ while (*wildcards) {
+ int ret = fnmatch (*wildcards, dname, 0);
+ if (ret == 0) /* Succesful match */
+ return 1;
+ if (ret != FNM_NOMATCH) {
+ virNetError(VIR_ERR_INTERNAL_ERROR,
+ _("Malformed TLS whitelist regular expression
'%s'"),
+ *wildcards);
+ return -1;
+ }
+
+ wildcards++;
+ }
+
+ /* Log the client's DN for debugging */
+ VIR_DEBUG("Failed whitelist check for client DN '%s'", dname);
+
+ /* This is the most common error: make it informative. */
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("Client's Distinguished Name is not on the list "
+ "of allowed clients (tls_allowed_dn_list). Use "
+ "'certtool -i --infile clientcert.pem' to view the"
+ "Distinguished Name field in the client certificate,"
+ "or run this daemon with --verbose option."));
+ return 0;
+}
+
+
+static int
+virNetTLSContextCheckCertDN(gnutls_x509_crt_t cert,
+ const char *certFile,
+ const char *hostname,
+ const char *const* whitelist)
+{
+ int ret;
+ char name[256];
+ size_t namesize = sizeof name;
+
+ memset(name, 0, namesize);
+
+ ret = gnutls_x509_crt_get_dn(cert, name, &namesize);
+ if (ret != 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Failed to get certificate %s distinguished name: %s"),
+ certFile, gnutls_strerror(ret));
+ return -1;
+ }
+
+ if (whitelist &&
+ virNetTLSContextCheckCertDNWhitelist(name, whitelist) <= 0)
+ return -1;
+
+ if (hostname &&
+ !gnutls_x509_crt_check_hostname(cert, hostname)) {
+ virNetError(VIR_ERR_RPC,
+ _("Certificate %s owner does not match the hostname %s"),
+ certFile, hostname);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int virNetTLSContextCheckCert(gnutls_x509_crt_t cert,
+ const char *certFile,
+ bool isServer,
+ bool isCA)
+{
+ if (virNetTLSContextCheckCertTimes(cert, certFile,
+ isServer, isCA) < 0)
+ return -1;
+
+ if (virNetTLSContextCheckCertBasicConstraints(cert, certFile,
+ isServer, isCA) < 0)
+ return -1;
+
+ if (virNetTLSContextCheckCertKeyUsage(cert, certFile,
+ isCA) < 0)
+ return -1;
+
+ if (!isCA &&
+ virNetTLSContextCheckCertKeyPurpose(cert, certFile,
+ isServer) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+static int virNetTLSContextCheckCertPair(gnutls_x509_crt_t cert,
+ const char *certFile,
+ gnutls_x509_crt_t cacert,
+ const char *cacertFile,
+ bool isServer)
+{
+ unsigned int status;
+
+ if (gnutls_x509_crt_list_verify(&cert, 1,
+ &cacert, 1,
+ NULL, 0,
+ 0, &status) < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, isServer ?
+ _("Unable to verify server certificate %s against CA certificate
%s") :
+ _("Unable to verify client certificate %s against CA certificate
%s"),
+ certFile, cacertFile);
+ return -1;
+ }
+
+ if (status != 0) {
+ const char *reason = _("Invalid certificate");
+
+ if (status & GNUTLS_CERT_INVALID)
+ reason = _("The certificate is not trusted.");
+
+ if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
+ reason = _("The certificate hasn't got a known issuer.");
+
+ if (status & GNUTLS_CERT_REVOKED)
+ reason = _("The certificate has been revoked.");
+
+#ifndef GNUTLS_1_0_COMPAT
+ if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
+ reason = _("The certificate uses an insecure algorithm");
+#endif
+
+ virNetError(VIR_ERR_SYSTEM_ERROR,
+ _("Our own certificate %s failed validation against %s:
%s"),
+ certFile, cacertFile, reason);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static gnutls_x509_crt_t virNetTLSContextLoadCertFromFile(const char *certFile,
+ bool isServer,
+ bool isCA)
+{
+ 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);
+
+ if (gnutls_x509_crt_init(&cert) < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
+ _("Unable to initialize certificate"));
+ goto cleanup;
+ }
+
+ if (virFileReadAll(certFile, (1<<16), &buf) < 0)
+ goto cleanup;
+
+ data.data = (unsigned char *)buf;
+ data.size = strlen(buf);
+
+ if (gnutls_x509_crt_import(cert, &data, GNUTLS_X509_FMT_PEM) < 0) {
+ virNetError(VIR_ERR_SYSTEM_ERROR, isServer ?
+ _("Unable to import server certificate %s") :
+ _("Unable to import client certificate %s"),
+ certFile);
+ goto cleanup;
+ }
ret = 0;
@@ -324,7 +510,6 @@ cleanup:
gnutls_x509_crt_deinit(cert);
cert = NULL;
}
- VIR_FREE(buffer);
VIR_FREE(buf);
return cert;
}
@@ -337,51 +522,25 @@ static int virNetTLSContextSanityCheckCredentials(bool isServer,
gnutls_x509_crt_t cert = NULL;
gnutls_x509_crt_t cacert = NULL;
int ret = -1;
- unsigned int status;
- if (access(certFile, R_OK) == 0) {
- if (!(cert = virNetTLSContextSanityCheckCert(isServer, false, certFile)))
- goto cleanup;
- }
- if (access(cacertFile, R_OK) == 0) {
- if (!(cacert = virNetTLSContextSanityCheckCert(isServer, true, cacertFile)))
- goto cleanup;
- }
-
- if (cert && cacert) {
- if (gnutls_x509_crt_list_verify(&cert, 1,
- &cacert, 1,
- NULL, 0,
- 0, &status) < 0) {
- virNetError(VIR_ERR_SYSTEM_ERROR, "%s", isServer ?
- _("Unable to verify server certificate against CA
certificate") :
- _("Unable to verify client certificate against CA
certificate"));
- goto cleanup;
- }
-
- if (status != 0) {
- const char *reason = _("Invalid certificate");
-
- if (status & GNUTLS_CERT_INVALID)
- reason = _("The certificate is not trusted.");
-
- if (status & GNUTLS_CERT_SIGNER_NOT_FOUND)
- reason = _("The certificate hasn't got a known issuer.");
+ if ((access(certFile, R_OK) == 0) &&
+ !(cert = virNetTLSContextLoadCertFromFile(certFile, isServer, false)))
+ goto cleanup;
+ if ((access(cacertFile, R_OK) == 0) &&
+ !(cacert = virNetTLSContextLoadCertFromFile(cacertFile, isServer, false)))
+ goto cleanup;
- if (status & GNUTLS_CERT_REVOKED)
- reason = _("The certificate has been revoked.");
+ if (cert &&
+ virNetTLSContextCheckCert(cert, certFile, isServer, false) < 0)
+ goto cleanup;
-#ifndef GNUTLS_1_0_COMPAT
- if (status & GNUTLS_CERT_INSECURE_ALGORITHM)
- reason = _("The certificate uses an insecure algorithm");
-#endif
+ if (cacert &&
+ virNetTLSContextCheckCert(cacert, cacertFile, isServer, true) < 0)
+ goto cleanup;
- virNetError(VIR_ERR_SYSTEM_ERROR,
- _("Our own certificate %s failed validation against %s:
%s"),
- certFile, cacertFile, reason);
- goto cleanup;
- }
- }
+ if (cert && cacert &&
+ virNetTLSContextCheckCertPair(cert, certFile, cacert, cacertFile, isServer) <
0)
+ goto cleanup;
ret = 0;
@@ -760,45 +919,6 @@ void virNetTLSContextRef(virNetTLSContextPtr ctxt)
}
-/* Check DN is on tls_allowed_dn_list. */
-static int
-virNetTLSContextCheckDN(virNetTLSContextPtr ctxt,
- const char *dname)
-{
- const char *const*wildcards;
-
- /* If the list is not set, allow any DN. */
- wildcards = ctxt->x509dnWhitelist;
- if (!wildcards)
- return 1;
-
- while (*wildcards) {
- int ret = fnmatch (*wildcards, dname, 0);
- if (ret == 0) /* Succesful match */
- return 1;
- if (ret != FNM_NOMATCH) {
- virNetError(VIR_ERR_INTERNAL_ERROR,
- _("Malformed TLS whitelist regular expression
'%s'"),
- *wildcards);
- return -1;
- }
-
- wildcards++;
- }
-
- /* Log the client's DN for debugging */
- VIR_DEBUG("Failed whitelist check for client DN '%s'", dname);
-
- /* This is the most common error: make it informative. */
- virNetError(VIR_ERR_SYSTEM_ERROR, "%s",
- _("Client's Distinguished Name is not on the list "
- "of allowed clients (tls_allowed_dn_list). Use "
- "'certtool -i --infile clientcert.pem' to view the"
- "Distinguished Name field in the client certificate,"
- "or run this daemon with --verbose option."));
- return 0;
-}
-
static int virNetTLSContextValidCertificate(virNetTLSContextPtr ctxt,
virNetTLSSessionPtr sess)
{
@@ -806,11 +926,6 @@ static int virNetTLSContextValidCertificate(virNetTLSContextPtr
ctxt,
unsigned int status;
const gnutls_datum_t *certs;
unsigned int nCerts, i;
- time_t now;
- char name[256];
- size_t namesize = sizeof name;
-
- memset(name, 0, namesize);
if ((ret = gnutls_certificate_verify_peers2(sess->session, &status)) < 0){
virNetError(VIR_ERR_SYSTEM_ERROR,
@@ -819,12 +934,6 @@ static int virNetTLSContextValidCertificate(virNetTLSContextPtr
ctxt,
goto authdeny;
}
- if ((now = time(NULL)) == ((time_t)-1)) {
- virReportSystemError(errno, "%s",
- _("cannot get current time"));
- goto authfail;
- }
-
if (status != 0) {
const char *reason = _("Invalid certificate");
@@ -876,46 +985,37 @@ static int virNetTLSContextValidCertificate(virNetTLSContextPtr
ctxt,
goto authfail;
}
- if (gnutls_x509_crt_get_expiration_time(cert) < now) {
- /* Warning is reversed from what you expect, since with
- * this code it is the Server checking the client and
- * vica-versa */
- virNetError(VIR_ERR_SYSTEM_ERROR, "%s", sess->isServer ?
- _("The client certificate has expired") :
- _("The server certificate has expired"));
- gnutls_x509_crt_deinit(cert);
- goto authdeny;
- }
-
- if (gnutls_x509_crt_get_activation_time(cert) > now) {
- /* client/server order reversed. see above */
- virNetError(VIR_ERR_SYSTEM_ERROR, "%s", sess->isServer ?
- _("The client certificate is not yet active") :
- _("The server certificate is not yet active"));
+ if (virNetTLSContextCheckCertTimes(cert, "[session]",
+ sess->isServer, i > 0) < 0) {
gnutls_x509_crt_deinit(cert);
goto authdeny;
}
if (i == 0) {
- ret = gnutls_x509_crt_get_dn(cert, name, &namesize);
- if (ret != 0) {
- virNetError(VIR_ERR_SYSTEM_ERROR,
- _("Failed to get certificate distinguished name:
%s"),
- gnutls_strerror(ret));
+ if (virNetTLSContextCheckCertDN(cert, "[session]",
sess->hostname,
+ ctxt->x509dnWhitelist) < 0) {
gnutls_x509_crt_deinit(cert);
- goto authfail;
+ goto authdeny;
+ }
+
+ /* !sess->isServer, since on the client, we're validating the
+ * server's cert, and on the server, the client's cert
+ */
+ if (virNetTLSContextCheckCertBasicConstraints(cert, "[session]",
+ !sess->isServer, false) <
0) {
+ gnutls_x509_crt_deinit(cert);
+ goto authdeny;
}
- if (virNetTLSContextCheckDN(ctxt, name) <= 0) {
+ if (virNetTLSContextCheckCertKeyUsage(cert, "[session]",
+ false) < 0) {
gnutls_x509_crt_deinit(cert);
goto authdeny;
}
- if (sess->hostname &&
- !gnutls_x509_crt_check_hostname(cert, sess->hostname)) {
- virNetError(VIR_ERR_RPC,
- _("Certificate's owner does not match the hostname
(%s)"),
- sess->hostname);
+ /* !sess->isServer - as above */
+ if (virNetTLSContextCheckCertKeyPurpose(cert, "[session]",
+ !sess->isServer) < 0) {
gnutls_x509_crt_deinit(cert);
goto authdeny;
}
--
1.7.6