The TLS cert validation logic will be reused for the new impl of the virt-pki-validate tool. Signed-off-by: Daniel P. Berrangé <berrange@xxxxxxxxxx> --- po/POTFILES | 1 + src/rpc/meson.build | 1 + src/rpc/virnettlscert.c | 553 +++++++++++++++++++++++++++++++++++++ src/rpc/virnettlscert.h | 42 +++ src/rpc/virnettlscontext.c | 535 +---------------------------------- 5 files changed, 606 insertions(+), 526 deletions(-) create mode 100644 src/rpc/virnettlscert.c create mode 100644 src/rpc/virnettlscert.h diff --git a/po/POTFILES b/po/POTFILES index 4ad7e19e08..0f68c652eb 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -219,6 +219,7 @@ src/rpc/virnetserverprogram.c src/rpc/virnetserverservice.c src/rpc/virnetsocket.c src/rpc/virnetsshsession.c +src/rpc/virnettlscert.c src/rpc/virnettlscontext.c src/secret/secret_driver.c src/security/security_apparmor.c diff --git a/src/rpc/meson.build b/src/rpc/meson.build index d11d532d0f..8bdbf5c88f 100644 --- a/src/rpc/meson.build +++ b/src/rpc/meson.build @@ -2,6 +2,7 @@ gendispatch_prog = find_program('gendispatch.pl') tlsconfig_sources = [ files('virnettlsconfig.c'), + files('virnettlscert.c'), ] socket_sources = tlsconfig_sources + [ diff --git a/src/rpc/virnettlscert.c b/src/rpc/virnettlscert.c new file mode 100644 index 0000000000..2e1e4c56d5 --- /dev/null +++ b/src/rpc/virnettlscert.c @@ -0,0 +1,553 @@ +/* + * virnettlscert.c: TLS x509 certificate helpers + * + * Copyright (C) 2010-2024 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "virnettlscert.h" + +#include "viralloc.h" +#include "virfile.h" +#include "virlog.h" +#include "virerror.h" + +#define VIR_FROM_THIS VIR_FROM_NONE + +VIR_LOG_INIT("rpc.nettlscert"); + +static int virNetTLSCertCheckTimes(gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA) +{ + time_t now; + + if ((now = time(NULL)) == ((time_t)-1)) { + virReportSystemError(errno, "%s", + _("cannot get current time")); + return -1; + } + + if (gnutls_x509_crt_get_expiration_time(cert) < now) { + virReportError(VIR_ERR_SYSTEM_ERROR, + (isCA ? + _("The CA certificate %1$s has expired") : + (isServer ? + _("The server certificate %1$s has expired") : + _("The client certificate %1$s has expired"))), + certFile); + return -1; + } + + if (gnutls_x509_crt_get_activation_time(cert) > now) { + virReportError(VIR_ERR_SYSTEM_ERROR, + (isCA ? + _("The CA certificate %1$s is not yet active") : + (isServer ? + _("The server certificate %1$s is not yet active") : + _("The client certificate %1$s is not yet active"))), + certFile); + return -1; + } + + return 0; +} + + +static int virNetTLSCertCheckBasicConstraints(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); + + if (status > 0) { /* It is a CA cert */ + if (!isCA) { + virReportError(VIR_ERR_SYSTEM_ERROR, isServer ? + _("The certificate %1$s basic constraints show a CA, but we need one for a server") : + _("The certificate %1$s basic constraints show a CA, but we need one for a client"), + certFile); + return -1; + } + } else if (status == 0) { /* It is not a CA cert */ + if (isCA) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("The certificate %1$s basic constraints do not show a CA"), + certFile); + return -1; + } + } else if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { /* Missing basicConstraints */ + if (isCA) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("The certificate %1$s is missing basic constraints for a CA"), + certFile); + return -1; + } + } else { /* General error */ + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Unable to query certificate %1$s basic constraints %2$s"), + certFile, gnutls_strerror(status)); + return -1; + } + + return 0; +} + + +static int virNetTLSCertCheckKeyUsage(gnutls_x509_crt_t cert, + const char *certFile, + bool isCA) +{ + int status; + unsigned int usage = 0; + unsigned int critical = 0; + + 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); + if (status < 0) { + if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { + usage = isCA ? GNUTLS_KEY_KEY_CERT_SIGN : + GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_ENCIPHERMENT; + } else { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Unable to query certificate %1$s key usage %2$s"), + certFile, gnutls_strerror(status)); + return -1; + } + } + + if (isCA) { + if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) { + if (critical) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %1$s usage does not permit certificate signing"), + certFile); + return -1; + } else { + VIR_WARN("Certificate %s usage does not permit certificate signing", + certFile); + } + } + } else { + if (!(usage & GNUTLS_KEY_DIGITAL_SIGNATURE)) { + if (critical) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %1$s usage does not permit digital signature"), + certFile); + return -1; + } else { + VIR_WARN("Certificate %s usage does not permit digital signature", + certFile); + } + } + if (!(usage & GNUTLS_KEY_KEY_ENCIPHERMENT)) { + if (critical) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %1$s usage does not permit key encipherment"), + certFile); + return -1; + } else { + VIR_WARN("Certificate %s usage does not permit key encipherment", + certFile); + } + } + } + + return 0; +} + + +static int virNetTLSCertCheckKeyPurpose(gnutls_x509_crt_t cert, + const char *certFile, + bool isServer) +{ + int status; + size_t i; + unsigned int purposeCritical; + unsigned int critical; + char *buffer = NULL; + size_t size; + bool allowClient = false, allowServer = false; + + critical = 0; + 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 at slot %zu", i); + + /* If there is no data at all, then we must allow client/server to pass */ + if (i == 0) + allowServer = allowClient = true; + break; + } + if (status != GNUTLS_E_SHORT_MEMORY_BUFFER) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Unable to query certificate %1$s key purpose %2$s"), + certFile, gnutls_strerror(status)); + return -1; + } + + buffer = g_new0(char, size); + status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, &size, &purposeCritical); + if (status < 0) { + VIR_FREE(buffer); + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Unable to query certificate %1$s key purpose %2$s"), + certFile, gnutls_strerror(status)); + return -1; + } + if (purposeCritical) + critical = true; + + VIR_DEBUG("Key purpose %d %s critical %u", status, buffer, purposeCritical); + if (STREQ(buffer, GNUTLS_KP_TLS_WWW_SERVER)) { + allowServer = true; + } else if (STREQ(buffer, GNUTLS_KP_TLS_WWW_CLIENT)) { + allowClient = true; + } else if (STRNEQ(buffer, GNUTLS_KP_ANY)) { + allowServer = allowClient = true; + } + + VIR_FREE(buffer); + } + + if (isServer) { + if (!allowServer) { + if (critical) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %1$s purpose does not allow use for with a TLS server"), + certFile); + return -1; + } else { + VIR_WARN("Certificate %s purpose does not allow use for with a TLS server", + certFile); + } + } + } else { + if (!allowClient) { + if (critical) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Certificate %1$s purpose does not allow use for with a TLS client"), + certFile); + return -1; + } else { + VIR_WARN("Certificate %s purpose does not allow use for with a TLS client", + certFile); + } + } + } + + return 0; +} + +/* Check DN is on tls_allowed_dn_list. */ +static int +virNetTLSCertCheckDNACL(const char *dname, + const char *const *wildcards) +{ + while (*wildcards) { + if (g_pattern_match_simple(*wildcards, dname)) + return 1; + + wildcards++; + } + + /* Log the client's DN for debugging */ + VIR_DEBUG("Failed ACL check for client DN '%s'", dname); + + /* This is the most common error: make it informative. */ + virReportError(VIR_ERR_SYSTEM_ERROR, "%s", + _("Client's Distinguished Name is not on the list of allowed clients (tls_allowed_dn_list). Use 'virt-pki-query-dn clientcert.pem' to view the Distinguished Name field in the client certificate, or run this daemon with --verbose option.")); + return 0; +} + + +static int +virNetTLSCertCheckDN(gnutls_x509_crt_t cert, + const char *certFile, + const char *hostname, + const char *dname, + const char *const *acl) +{ + if (acl && dname && + virNetTLSCertCheckDNACL(dname, acl) <= 0) + return -1; + + if (hostname && + !gnutls_x509_crt_check_hostname(cert, hostname)) { + virReportError(VIR_ERR_RPC, + _("Certificate %1$s owner does not match the hostname %2$s"), + certFile, hostname); + return -1; + } + + return 0; +} + + +static int virNetTLSCertCheck(gnutls_x509_crt_t cert, + const char *certFile, + bool isServer, + bool isCA) +{ + if (virNetTLSCertCheckTimes(cert, certFile, isServer, isCA) < 0) + return -1; + + if (virNetTLSCertCheckBasicConstraints(cert, certFile, isServer, isCA) < 0) + return -1; + + if (virNetTLSCertCheckKeyUsage(cert, certFile, isCA) < 0) + return -1; + + if (!isCA && + virNetTLSCertCheckKeyPurpose(cert, certFile, isServer) < 0) + return -1; + + return 0; +} + + +static int virNetTLSCertCheckPair(gnutls_x509_crt_t cert, + const char *certFile, + gnutls_x509_crt_t *cacerts, + size_t ncacerts, + const char *cacertFile, + bool isServer) +{ + unsigned int status; + + if (gnutls_x509_crt_list_verify(&cert, 1, + cacerts, ncacerts, + NULL, 0, + 0, &status) < 0) { + virReportError(VIR_ERR_SYSTEM_ERROR, isServer ? + _("Unable to verify server certificate %1$s against CA certificate %2$s") : + _("Unable to verify client certificate %1$s against CA certificate %2$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."); + + if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + reason = _("The certificate uses an insecure algorithm"); + + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Our own certificate %1$s failed validation against %2$s: %3$s"), + certFile, cacertFile, reason); + return -1; + } + + return 0; +} + + +gnutls_x509_crt_t virNetTLSCertLoadFromFile(const char *certFile, + bool isServer) +{ + gnutls_datum_t data; + gnutls_x509_crt_t cert = NULL; + g_autofree char *buf = NULL; + int ret = -1; + + VIR_DEBUG("isServer %d certFile %s", + isServer, certFile); + + if (gnutls_x509_crt_init(&cert) < 0) { + virReportError(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) { + virReportError(VIR_ERR_SYSTEM_ERROR, isServer ? + _("Unable to import server certificate %1$s") : + _("Unable to import client certificate %1$s"), + certFile); + goto cleanup; + } + + ret = 0; + + cleanup: + if (ret != 0) { + g_clear_pointer(&cert, gnutls_x509_crt_deinit); + } + return cert; +} + + +static int virNetTLSCertLoadCAListFromFile(const char *certFile, + gnutls_x509_crt_t *certs, + unsigned int certMax, + size_t *ncerts) +{ + gnutls_datum_t data; + g_autofree char *buf = NULL; + + *ncerts = 0; + VIR_DEBUG("certFile %s", certFile); + + if (virFileReadAll(certFile, (1<<16), &buf) < 0) + return -1; + + 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 %1$s"), + certFile); + return -1; + } + *ncerts = certMax; + + return 0; +} + + +#define MAX_CERTS 16 +int virNetTLSCertSanityCheck(bool isServer, + const char *cacertFile, + const char *certFile) +{ + gnutls_x509_crt_t cert = NULL; + gnutls_x509_crt_t cacerts[MAX_CERTS] = { 0 }; + size_t ncacerts = 0; + size_t i; + int ret = -1; + + if ((access(certFile, R_OK) == 0) && + !(cert = virNetTLSCertLoadFromFile(certFile, isServer))) + goto cleanup; + if ((access(cacertFile, R_OK) == 0) && + virNetTLSCertLoadCAListFromFile(cacertFile, cacerts, + MAX_CERTS, &ncacerts) < 0) + goto cleanup; + + if (cert && + virNetTLSCertCheck(cert, certFile, isServer, false) < 0) + goto cleanup; + + for (i = 0; i < ncacerts; i++) { + if (virNetTLSCertCheck(cacerts[i], cacertFile, isServer, true) < 0) + goto cleanup; + } + + if (cert && ncacerts && + virNetTLSCertCheckPair(cert, certFile, cacerts, ncacerts, cacertFile, isServer) < 0) + goto cleanup; + + ret = 0; + + cleanup: + if (cert) + gnutls_x509_crt_deinit(cert); + for (i = 0; i < ncacerts; i++) + gnutls_x509_crt_deinit(cacerts[i]); + return ret; +} + +int virNetTLSCertValidateCA(gnutls_x509_crt_t cert, + bool isServer) +{ + if (virNetTLSCertCheckTimes(cert, "[session]", + isServer, true) < 0) { + return -1; + } + return 0; +} + +char *virNetTLSCertValidate(gnutls_x509_crt_t cert, + bool isServer, + const char *hostname, + const char *const *x509dnACL) +{ + size_t dnamesize = 256; + g_autofree char *dname = g_new0(char, dnamesize); + int ret; + + if (virNetTLSCertCheckTimes(cert, "[session]", + isServer, false) < 0) { + return NULL; + } + + ret = gnutls_x509_crt_get_dn(cert, dname, &dnamesize); + if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { + VIR_DEBUG("Reallocating dname to fit %zu bytes", dnamesize); + dname = g_realloc(dname, dnamesize); + ret = gnutls_x509_crt_get_dn(cert, dname, &dnamesize); + } + if (ret != 0) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("Failed to get certificate %1$s distinguished name: %2$s"), + "[session]", gnutls_strerror(ret)); + return NULL; + } + + VIR_DEBUG("Peer DN is %s", dname); + + if (virNetTLSCertCheckDN(cert, "[session]", hostname, + dname, x509dnACL) < 0) { + return NULL; + } + + /* !isServer, since on the client, we're validating the + * server's cert, and on the server, the client's cert + */ + if (virNetTLSCertCheckBasicConstraints(cert, "[session]", + !isServer, false) < 0) { + return NULL; + } + + if (virNetTLSCertCheckKeyUsage(cert, "[session]", + false) < 0) { + return NULL; + } + + /* !isServer - as above */ + if (virNetTLSCertCheckKeyPurpose(cert, "[session]", + !isServer) < 0) { + return NULL; + } + + return g_steal_pointer(&dname); +} diff --git a/src/rpc/virnettlscert.h b/src/rpc/virnettlscert.h new file mode 100644 index 0000000000..0ac511a141 --- /dev/null +++ b/src/rpc/virnettlscert.h @@ -0,0 +1,42 @@ +/* + * virnettlscert.h: TLS x509 certificate helpers + * + * Copyright (C) 2010-2024 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see + * <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <gnutls/x509.h> + +#include "internal.h" + +int virNetTLSCertSanityCheck(bool isServer, + const char *cacertFile, + const char *certFile); + +int virNetTLSCertValidateCA(gnutls_x509_crt_t cert, + bool isServer); + +char *virNetTLSCertValidate(gnutls_x509_crt_t cert, + bool isServer, + const char *hostname, + const char *const *x509dnACL); + +gnutls_x509_crt_t virNetTLSCertLoadFromFile(const char *certFile, + bool isServer); diff --git a/src/rpc/virnettlscontext.c b/src/rpc/virnettlscontext.c index a56abee009..e8023133b4 100644 --- a/src/rpc/virnettlscontext.c +++ b/src/rpc/virnettlscontext.c @@ -28,6 +28,7 @@ #include "virnettlscontext.h" #include "virnettlsconfig.h" +#include "virnettlscert.h" #include "virstring.h" #include "viralloc.h" @@ -110,466 +111,6 @@ static void virNetTLSLog(int level G_GNUC_UNUSED, } -static int virNetTLSContextCheckCertTimes(gnutls_x509_crt_t cert, - const char *certFile, - bool isServer, - bool isCA) -{ - time_t now; - - if ((now = time(NULL)) == ((time_t)-1)) { - virReportSystemError(errno, "%s", - _("cannot get current time")); - return -1; - } - - if (gnutls_x509_crt_get_expiration_time(cert) < now) { - virReportError(VIR_ERR_SYSTEM_ERROR, - (isCA ? - _("The CA certificate %1$s has expired") : - (isServer ? - _("The server certificate %1$s has expired") : - _("The client certificate %1$s has expired"))), - certFile); - return -1; - } - - if (gnutls_x509_crt_get_activation_time(cert) > now) { - virReportError(VIR_ERR_SYSTEM_ERROR, - (isCA ? - _("The CA certificate %1$s is not yet active") : - (isServer ? - _("The server certificate %1$s is not yet active") : - _("The client certificate %1$s is not yet active"))), - certFile); - 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); - - if (status > 0) { /* It is a CA cert */ - if (!isCA) { - virReportError(VIR_ERR_SYSTEM_ERROR, isServer ? - _("The certificate %1$s basic constraints show a CA, but we need one for a server") : - _("The certificate %1$s basic constraints show a CA, but we need one for a client"), - certFile); - return -1; - } - } else if (status == 0) { /* It is not a CA cert */ - if (isCA) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("The certificate %1$s basic constraints do not show a CA"), - certFile); - return -1; - } - } else if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { /* Missing basicConstraints */ - if (isCA) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("The certificate %1$s is missing basic constraints for a CA"), - certFile); - return -1; - } - } else { /* General error */ - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Unable to query certificate %1$s basic constraints %2$s"), - certFile, gnutls_strerror(status)); - return -1; - } - - return 0; -} - - -static int virNetTLSContextCheckCertKeyUsage(gnutls_x509_crt_t cert, - const char *certFile, - bool isCA) -{ - int status; - unsigned int usage = 0; - unsigned int critical = 0; - - 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); - if (status < 0) { - if (status == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) { - usage = isCA ? GNUTLS_KEY_KEY_CERT_SIGN : - GNUTLS_KEY_DIGITAL_SIGNATURE|GNUTLS_KEY_KEY_ENCIPHERMENT; - } else { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Unable to query certificate %1$s key usage %2$s"), - certFile, gnutls_strerror(status)); - return -1; - } - } - - if (isCA) { - if (!(usage & GNUTLS_KEY_KEY_CERT_SIGN)) { - if (critical) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Certificate %1$s usage does not permit certificate signing"), - certFile); - return -1; - } else { - VIR_WARN("Certificate %s usage does not permit certificate signing", - certFile); - } - } - } else { - if (!(usage & GNUTLS_KEY_DIGITAL_SIGNATURE)) { - if (critical) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Certificate %1$s usage does not permit digital signature"), - certFile); - return -1; - } else { - VIR_WARN("Certificate %s usage does not permit digital signature", - certFile); - } - } - if (!(usage & GNUTLS_KEY_KEY_ENCIPHERMENT)) { - if (critical) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Certificate %1$s usage does not permit key encipherment"), - certFile); - return -1; - } else { - VIR_WARN("Certificate %s usage does not permit key encipherment", - certFile); - } - } - } - - return 0; -} - - -static int virNetTLSContextCheckCertKeyPurpose(gnutls_x509_crt_t cert, - const char *certFile, - bool isServer) -{ - int status; - size_t i; - unsigned int purposeCritical; - unsigned int critical; - char *buffer = NULL; - size_t size; - bool allowClient = false, allowServer = false; - - critical = 0; - 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 at slot %zu", i); - - /* If there is no data at all, then we must allow client/server to pass */ - if (i == 0) - allowServer = allowClient = true; - break; - } - if (status != GNUTLS_E_SHORT_MEMORY_BUFFER) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Unable to query certificate %1$s key purpose %2$s"), - certFile, gnutls_strerror(status)); - return -1; - } - - buffer = g_new0(char, size); - status = gnutls_x509_crt_get_key_purpose_oid(cert, i, buffer, &size, &purposeCritical); - if (status < 0) { - VIR_FREE(buffer); - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Unable to query certificate %1$s key purpose %2$s"), - certFile, gnutls_strerror(status)); - return -1; - } - if (purposeCritical) - critical = true; - - VIR_DEBUG("Key purpose %d %s critical %u", status, buffer, purposeCritical); - if (STREQ(buffer, GNUTLS_KP_TLS_WWW_SERVER)) { - allowServer = true; - } else if (STREQ(buffer, GNUTLS_KP_TLS_WWW_CLIENT)) { - allowClient = true; - } else if (STRNEQ(buffer, GNUTLS_KP_ANY)) { - allowServer = allowClient = true; - } - - VIR_FREE(buffer); - } - - if (isServer) { - if (!allowServer) { - if (critical) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Certificate %1$s purpose does not allow use for with a TLS server"), - certFile); - return -1; - } else { - VIR_WARN("Certificate %s purpose does not allow use for with a TLS server", - certFile); - } - } - } else { - if (!allowClient) { - if (critical) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Certificate %1$s purpose does not allow use for with a TLS client"), - certFile); - return -1; - } else { - VIR_WARN("Certificate %s purpose does not allow use for with a TLS client", - certFile); - } - } - } - - return 0; -} - -/* Check DN is on tls_allowed_dn_list. */ -static int -virNetTLSContextCheckCertDNACL(const char *dname, - const char *const *wildcards) -{ - while (*wildcards) { - if (g_pattern_match_simple(*wildcards, dname)) - return 1; - - wildcards++; - } - - /* Log the client's DN for debugging */ - VIR_DEBUG("Failed ACL check for client DN '%s'", dname); - - /* This is the most common error: make it informative. */ - virReportError(VIR_ERR_SYSTEM_ERROR, "%s", - _("Client's Distinguished Name is not on the list of allowed clients (tls_allowed_dn_list). Use 'virt-pki-query-dn 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 *dname, - const char *const *acl) -{ - if (acl && dname && - virNetTLSContextCheckCertDNACL(dname, acl) <= 0) - return -1; - - if (hostname && - !gnutls_x509_crt_check_hostname(cert, hostname)) { - virReportError(VIR_ERR_RPC, - _("Certificate %1$s owner does not match the hostname %2$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 *cacerts, - size_t ncacerts, - const char *cacertFile, - bool isServer) -{ - unsigned int status; - - if (gnutls_x509_crt_list_verify(&cert, 1, - cacerts, ncacerts, - NULL, 0, - 0, &status) < 0) { - virReportError(VIR_ERR_SYSTEM_ERROR, isServer ? - _("Unable to verify server certificate %1$s against CA certificate %2$s") : - _("Unable to verify client certificate %1$s against CA certificate %2$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."); - - if (status & GNUTLS_CERT_INSECURE_ALGORITHM) - reason = _("The certificate uses an insecure algorithm"); - - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Our own certificate %1$s failed validation against %2$s: %3$s"), - certFile, cacertFile, reason); - return -1; - } - - return 0; -} - - -static gnutls_x509_crt_t virNetTLSContextLoadCertFromFile(const char *certFile, - bool isServer) -{ - gnutls_datum_t data; - gnutls_x509_crt_t cert = NULL; - g_autofree char *buf = NULL; - int ret = -1; - - VIR_DEBUG("isServer %d certFile %s", - isServer, certFile); - - if (gnutls_x509_crt_init(&cert) < 0) { - virReportError(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) { - virReportError(VIR_ERR_SYSTEM_ERROR, isServer ? - _("Unable to import server certificate %1$s") : - _("Unable to import client certificate %1$s"), - certFile); - goto cleanup; - } - - ret = 0; - - cleanup: - if (ret != 0) { - g_clear_pointer(&cert, gnutls_x509_crt_deinit); - } - return cert; -} - - -static int virNetTLSContextLoadCACertListFromFile(const char *certFile, - gnutls_x509_crt_t *certs, - unsigned int certMax, - size_t *ncerts) -{ - gnutls_datum_t data; - g_autofree char *buf = NULL; - - *ncerts = 0; - VIR_DEBUG("certFile %s", certFile); - - if (virFileReadAll(certFile, (1<<16), &buf) < 0) - return -1; - - 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 %1$s"), - certFile); - return -1; - } - *ncerts = certMax; - - return 0; -} - - -#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 cacerts[MAX_CERTS] = { 0 }; - size_t ncacerts = 0; - size_t i; - int ret = -1; - - if ((access(certFile, R_OK) == 0) && - !(cert = virNetTLSContextLoadCertFromFile(certFile, isServer))) - goto cleanup; - if ((access(cacertFile, R_OK) == 0) && - virNetTLSContextLoadCACertListFromFile(cacertFile, cacerts, - MAX_CERTS, &ncacerts) < 0) - goto cleanup; - - if (cert && - virNetTLSContextCheckCert(cert, certFile, isServer, false) < 0) - goto cleanup; - - for (i = 0; i < ncacerts; i++) { - if (virNetTLSContextCheckCert(cacerts[i], cacertFile, isServer, true) < 0) - goto cleanup; - } - - if (cert && ncacerts && - virNetTLSContextCheckCertPair(cert, certFile, cacerts, ncacerts, cacertFile, isServer) < 0) - goto cleanup; - - ret = 0; - - cleanup: - if (cert) - gnutls_x509_crt_deinit(cert); - for (i = 0; i < ncacerts; i++) - gnutls_x509_crt_deinit(cacerts[i]); - return ret; -} - - static int virNetTLSContextLoadCredentials(virNetTLSContext *ctxt, bool isServer, const char *cacert, @@ -683,7 +224,7 @@ static virNetTLSContext *virNetTLSContextNew(const char *cacert, } if (sanityCheckCert && - virNetTLSContextSanityCheckCredentials(isServer, cacert, cert) < 0) + virNetTLSCertSanityCheck(isServer, cacert, cert) < 0) goto error; if (virNetTLSContextLoadCredentials(ctxt, isServer, cacert, cacrl, cert, key) < 0) @@ -846,7 +387,7 @@ int virNetTLSContextReloadForServer(virNetTLSContext *ctxt, goto error; } - if (virNetTLSContextSanityCheckCredentials(true, cacert, cert)) + if (virNetTLSCertSanityCheck(true, cacert, cert)) goto error; if (virNetTLSContextLoadCredentials(ctxt, true, cacert, cacrl, cert, key)) @@ -876,65 +417,6 @@ virNetTLSContext *virNetTLSContextNewClient(const char *cacert, sanityCheckCert, requireValidCert, false); } -static int virNetTLSContextCertValidateCA(gnutls_x509_crt_t cert, - bool isServer) -{ - if (virNetTLSContextCheckCertTimes(cert, "[session]", isServer, true) < 0) - return -1; - - return 0; -} - -static char *virNetTLSContextCertValidate(gnutls_x509_crt_t cert, - bool isServer, - const char *hostname, - const char *const *x509dnACL) -{ - size_t dnamesize = 256; - g_autofree char *dname = g_new0(char, dnamesize); - int ret; - - if (virNetTLSContextCheckCertTimes(cert, "[session]", - isServer, false) < 0) - return NULL; - - ret = gnutls_x509_crt_get_dn(cert, dname, &dnamesize); - if (ret == GNUTLS_E_SHORT_MEMORY_BUFFER) { - VIR_DEBUG("Reallocating dname to fit %zu bytes", dnamesize); - dname = g_realloc(dname, dnamesize); - ret = gnutls_x509_crt_get_dn(cert, dname, &dnamesize); - } - if (ret != 0) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("Failed to get certificate %1$s distinguished name: %2$s"), - "[session]", gnutls_strerror(ret)); - return NULL; - } - - VIR_DEBUG("Peer DN is %s", dname); - - if (virNetTLSContextCheckCertDN(cert, "[session]", hostname, - dname, x509dnACL) < 0) - return NULL; - - /* !isServer, since on the client, we're validating the - * server's cert, and on the server, the client's cert - */ - if (virNetTLSContextCheckCertBasicConstraints(cert, "[session]", - !isServer, false) < 0) - return NULL; - - if (virNetTLSContextCheckCertKeyUsage(cert, "[session]", - false) < 0) - return NULL; - - /* !isServer - as above */ - if (virNetTLSContextCheckCertKeyPurpose(cert, "[session]", - !isServer) < 0) - return NULL; - - return g_steal_pointer(&dname); -} static int virNetTLSContextValidCertificate(virNetTLSContext *ctxt, virNetTLSSession *sess) @@ -1005,15 +487,16 @@ static int virNetTLSContextValidCertificate(virNetTLSContext *ctxt, } if (i == 0) { - if (!(sess->x509dname = virNetTLSContextCertValidate(cert, - sess->isServer, - sess->hostname, - ctxt->x509dnACL))) { + if (!(sess->x509dname = virNetTLSCertValidate(cert, + sess->isServer, + sess->hostname, + ctxt->x509dnACL))) { gnutls_x509_crt_deinit(cert); goto authdeny; } } else { - if (virNetTLSContextCertValidateCA(cert, sess->isServer) < 0) { + if (virNetTLSCertValidateCA(cert, + sess->isServer) < 0) { gnutls_x509_crt_deinit(cert); goto authdeny; } -- 2.43.0