summaryrefslogtreecommitdiffstats
path: root/dirmngr/validate.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
commiteee068778cb28ecf3c14e1bf843a95547d72c42d (patch)
tree0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /dirmngr/validate.c
parentInitial commit. (diff)
downloadgnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz
gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip
Adding upstream version 2.2.40.upstream/2.2.40upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dirmngr/validate.c')
-rw-r--r--dirmngr/validate.c1305
1 files changed, 1305 insertions, 0 deletions
diff --git a/dirmngr/validate.c b/dirmngr/validate.c
new file mode 100644
index 0000000..901c165
--- /dev/null
+++ b/dirmngr/validate.c
@@ -0,0 +1,1305 @@
+/* validate.c - Validate a certificate chain.
+ * Copyright (C) 2001, 2003, 2004, 2008 Free Software Foundation, Inc.
+ * Copyright (C) 2004, 2006, 2008, 2017 g10 Code GmbH
+ *
+ * This file is part of DirMngr.
+ *
+ * DirMngr is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * DirMngr 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "dirmngr.h"
+#include "certcache.h"
+#include "crlcache.h"
+#include "validate.h"
+#include "misc.h"
+
+
+/* Mode parameters for cert_check_usage(). */
+enum cert_usage_modes
+ {
+ CERT_USAGE_MODE_SIGN, /* Usable for encryption. */
+ CERT_USAGE_MODE_ENCR, /* Usable for signing. */
+ CERT_USAGE_MODE_VRFY, /* Usable for verification. */
+ CERT_USAGE_MODE_DECR, /* Usable for decryption. */
+ CERT_USAGE_MODE_CERT, /* Usable for cert signing. */
+ CERT_USAGE_MODE_OCSP, /* Usable for OCSP respone signing. */
+ CERT_USAGE_MODE_CRL /* Usable for CRL signing. */
+ };
+
+
+/* While running the validation function we need to keep track of the
+ certificates and the validation outcome of each. We use this type
+ for it. */
+struct chain_item_s
+{
+ struct chain_item_s *next;
+ ksba_cert_t cert; /* The certificate. */
+ unsigned char fpr[20]; /* Fingerprint of the certificate. */
+ int is_self_signed; /* This certificate is self-signed. */
+ int is_valid; /* The certifiate is valid except for revocations. */
+};
+typedef struct chain_item_s *chain_item_t;
+
+
+/* A couple of constants with Object Identifiers. */
+static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1";
+static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2";
+static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3";
+static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4";
+static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8";
+static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9";
+
+
+/* Prototypes. */
+static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert);
+
+
+/* Make sure that the values defined in the headers are correct. We
+ * can't use the preprocessor due to the use of enums. */
+static void
+check_header_constants (void)
+{
+ log_assert (CERTTRUST_CLASS_SYSTEM == VALIDATE_FLAG_TRUST_SYSTEM);
+ log_assert (CERTTRUST_CLASS_CONFIG == VALIDATE_FLAG_TRUST_CONFIG);
+ log_assert (CERTTRUST_CLASS_HKP == VALIDATE_FLAG_TRUST_HKP);
+ log_assert (CERTTRUST_CLASS_HKPSPOOL == VALIDATE_FLAG_TRUST_HKPSPOOL);
+
+#undef X
+#define X (VALIDATE_FLAG_TRUST_SYSTEM | VALIDATE_FLAG_TRUST_CONFIG \
+ | VALIDATE_FLAG_TRUST_HKP | VALIDATE_FLAG_TRUST_HKPSPOOL)
+
+#if ( X & VALIDATE_FLAG_MASK_TRUST ) != X
+# error VALIDATE_FLAG_MASK_TRUST is bad
+#endif
+#if ( ~X & VALIDATE_FLAG_MASK_TRUST )
+# error VALIDATE_FLAG_MASK_TRUST is bad
+#endif
+
+#undef X
+}
+
+
+/* Check whether CERT contains critical extensions we don't know
+ about. */
+static gpg_error_t
+unknown_criticals (ksba_cert_t cert)
+{
+ static const char *known[] = {
+ "2.5.29.15", /* keyUsage */
+ "2.5.29.19", /* basic Constraints */
+ "2.5.29.32", /* certificatePolicies */
+ "2.5.29.37", /* extendedKeyUsage */
+ NULL
+ };
+ int i, idx, crit;
+ const char *oid;
+ int unsupported;
+ strlist_t sl;
+ gpg_error_t err, rc;
+
+ rc = 0;
+ for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
+ &oid, &crit, NULL, NULL));idx++)
+ {
+ if (!crit)
+ continue;
+ for (i=0; known[i] && strcmp (known[i],oid); i++)
+ ;
+ unsupported = !known[i];
+
+ /* If this critical extension is not supported, check the list
+ of to be ignored extensions to see whether we claim that it
+ is supported. */
+ if (unsupported && opt.ignored_cert_extensions)
+ {
+ for (sl=opt.ignored_cert_extensions;
+ sl && strcmp (sl->d, oid); sl = sl->next)
+ ;
+ if (sl)
+ unsupported = 0;
+ }
+
+ if (unsupported)
+ {
+ log_error (_("critical certificate extension %s is not supported"),
+ oid);
+ rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
+ }
+ }
+ if (err && gpg_err_code (err) != GPG_ERR_EOF)
+ rc = err; /* Such an error takes precedence. */
+
+ return rc;
+}
+
+
+/* Basic check for supported policies. */
+static gpg_error_t
+check_cert_policy (ksba_cert_t cert)
+{
+ static const char *allowed[] = {
+ "2.289.9.9",
+ NULL
+ };
+ gpg_error_t err;
+ int idx;
+ char *p, *haystack;
+ char *policies;
+ int any_critical;
+
+ err = ksba_cert_get_cert_policies (cert, &policies);
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ return 0; /* No policy given. */
+ if (err)
+ return err;
+
+ /* STRING is a line delimited list of certifiate policies as stored
+ in the certificate. The line itself is colon delimited where the
+ first field is the OID of the policy and the second field either
+ N or C for normal or critical extension */
+ if (opt.verbose > 1)
+ log_info ("certificate's policy list: %s\n", policies);
+
+ /* The check is very minimal but won't give false positives */
+ any_critical = !!strstr (policies, ":C");
+
+ /* See whether we find ALLOWED (which is an OID) in POLICIES */
+ for (idx=0; allowed[idx]; idx++)
+ {
+ for (haystack=policies; (p=strstr (haystack, allowed[idx]));
+ haystack = p+1)
+ {
+ if ( !(p == policies || p[-1] == '\n') )
+ continue; /* Does not match the begin of a line. */
+ if (p[strlen (allowed[idx])] != ':')
+ continue; /* The length does not match. */
+ /* Yep - it does match: Return okay. */
+ ksba_free (policies);
+ return 0;
+ }
+ }
+
+ if (!any_critical)
+ {
+ log_info (_("Note: non-critical certificate policy not allowed"));
+ err = 0;
+ }
+ else
+ {
+ log_info (_("certificate policy not allowed"));
+ err = gpg_error (GPG_ERR_NO_POLICY_MATCH);
+ }
+
+ ksba_free (policies);
+ return err;
+}
+
+
+static gpg_error_t
+allowed_ca (ksba_cert_t cert, int *chainlen)
+{
+ gpg_error_t err;
+ int flag;
+
+ err = ksba_cert_is_ca (cert, &flag, chainlen);
+ if (err)
+ return err;
+ if (!flag)
+ {
+ if (!is_trusted_cert (cert, CERTTRUST_CLASS_CONFIG))
+ {
+ /* The German SigG Root CA's certificate does not flag
+ itself as a CA; thus we relax this requirement if we
+ trust a root CA. I think this is reasonable. Note, that
+ gpgsm implements a far stricter scheme here. */
+ if (chainlen)
+ *chainlen = 3; /* That is what the SigG implements. */
+ if (opt.verbose)
+ log_info (_("accepting root CA not marked as a CA"));
+ }
+ else
+ {
+ log_error (_("issuer certificate is not marked as a CA"));
+ return gpg_error (GPG_ERR_BAD_CA_CERT);
+ }
+ }
+ return 0;
+}
+
+/* Helper for validate_cert_chain. */
+static gpg_error_t
+check_revocations (ctrl_t ctrl, chain_item_t chain)
+{
+ gpg_error_t err = 0;
+ int any_revoked = 0;
+ int any_no_crl = 0;
+ int any_crl_too_old = 0;
+ chain_item_t ci;
+
+ log_assert (ctrl->check_revocations_nest_level >= 0);
+ log_assert (chain);
+
+ if (ctrl->check_revocations_nest_level > 10)
+ {
+ log_error (_("CRL checking too deeply nested\n"));
+ return gpg_error(GPG_ERR_BAD_CERT_CHAIN);
+ }
+ ctrl->check_revocations_nest_level++;
+
+
+ for (ci=chain; ci; ci = ci->next)
+ {
+ assert (ci->cert);
+ if (ci == chain)
+ {
+ /* It does not make sense to check the root certificate for
+ revocations. In almost all cases this will lead to a
+ catch-22 as the root certificate is the final trust
+ anchor for the certificates and the CRLs. We expect the
+ user to remove root certificates from the list of trusted
+ certificates in case they have been revoked. */
+ if (opt.verbose)
+ cert_log_name (_("not checking CRL for"), ci->cert);
+ continue;
+ }
+
+ if (opt.verbose)
+ cert_log_name (_("checking CRL for"), ci->cert);
+ err = crl_cache_cert_isvalid (ctrl, ci->cert, 0);
+ if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN)
+ {
+ err = crl_cache_reload_crl (ctrl, ci->cert);
+ if (!err)
+ err = crl_cache_cert_isvalid (ctrl, ci->cert, 0);
+ }
+ switch (gpg_err_code (err))
+ {
+ case 0: err = 0; break;
+ case GPG_ERR_CERT_REVOKED: any_revoked = 1; err = 0; break;
+ case GPG_ERR_NO_CRL_KNOWN: any_no_crl = 1; err = 0; break;
+ case GPG_ERR_CRL_TOO_OLD: any_crl_too_old = 1; err = 0; break;
+ default: break;
+ }
+ }
+ ctrl->check_revocations_nest_level--;
+
+
+ if (err)
+ ;
+ else if (any_revoked)
+ err = gpg_error (GPG_ERR_CERT_REVOKED);
+ else if (any_no_crl)
+ err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
+ else if (any_crl_too_old)
+ err = gpg_error (GPG_ERR_CRL_TOO_OLD);
+ else
+ err = 0;
+ return err;
+}
+
+
+/* Check whether CERT is a root certificate. ISSUERDN and SUBJECTDN
+ are the DNs already extracted by the caller from CERT. Returns
+ True if this is the case. */
+static int
+is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn)
+{
+ gpg_error_t err;
+ int result = 0;
+ ksba_sexp_t serialno;
+ ksba_sexp_t ak_keyid;
+ ksba_name_t ak_name;
+ ksba_sexp_t ak_sn;
+ const char *ak_name_str;
+ ksba_sexp_t subj_keyid = NULL;
+
+ if (!issuerdn || !subjectdn)
+ return 0; /* No. */
+
+ if (strcmp (issuerdn, subjectdn))
+ return 0; /* No. */
+
+ err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn);
+ if (err)
+ {
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ return 1; /* Yes. Without a authorityKeyIdentifier this needs
+ to be the Root certificate (our trust anchor). */
+ log_error ("error getting authorityKeyIdentifier: %s\n",
+ gpg_strerror (err));
+ return 0; /* Well, it is broken anyway. Return No. */
+ }
+
+ serialno = ksba_cert_get_serial (cert);
+ if (!serialno)
+ {
+ log_error ("error getting serialno: %s\n", gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Check whether the auth name's matches the issuer name+sn. If
+ that is the case this is a root certificate. */
+ ak_name_str = ksba_name_enum (ak_name, 0);
+ if (ak_name_str
+ && !strcmp (ak_name_str, issuerdn)
+ && !cmp_simple_canon_sexp (ak_sn, serialno))
+ {
+ result = 1; /* Right, CERT is self-signed. */
+ goto leave;
+ }
+
+ /* Similar for the ak_keyid. */
+ if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid)
+ && !cmp_simple_canon_sexp (ak_keyid, subj_keyid))
+ {
+ result = 1; /* Right, CERT is self-signed. */
+ goto leave;
+ }
+
+
+ leave:
+ ksba_free (subj_keyid);
+ ksba_free (ak_keyid);
+ ksba_name_release (ak_name);
+ ksba_free (ak_sn);
+ ksba_free (serialno);
+ return result;
+}
+
+
+/* Validate the certificate CHAIN up to the trust anchor. Optionally
+ return the closest expiration time in R_EXPTIME (this is useful for
+ caching issues). MODE is one of the VALIDATE_MODE_* constants.
+
+ Note that VALIDATE_MODE_OCSP is not used due to the removal of the
+ system service in 2.1.15. Instead only the callback to gpgsm to
+ validate a certificate is used.
+
+ If R_TRUST_ANCHOR is not NULL and the validation would fail only
+ because the root certificate is not trusted, the hexified
+ fingerprint of that root certificate is stored at R_TRUST_ANCHOR
+ and success is returned. The caller needs to free the value at
+ R_TRUST_ANCHOR; in all other cases NULL is stored there. */
+gpg_error_t
+validate_cert_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime,
+ unsigned int flags, char **r_trust_anchor)
+{
+ gpg_error_t err = 0;
+ int depth, maxdepth;
+ char *issuer = NULL;
+ char *subject = NULL;
+ ksba_cert_t subject_cert = NULL;
+ ksba_cert_t issuer_cert = NULL;
+ ksba_isotime_t current_time;
+ ksba_isotime_t exptime;
+ int any_expired = 0;
+ int any_no_policy_match = 0;
+ chain_item_t chain;
+
+ check_header_constants ();
+
+ if (r_exptime)
+ *r_exptime = 0;
+ *exptime = 0;
+
+ if (r_trust_anchor)
+ *r_trust_anchor = NULL;
+
+ if (DBG_X509)
+ dump_cert ("subject", cert);
+
+ /* May the target certificate be used for this purpose? */
+ if ((flags & VALIDATE_FLAG_OCSP) && (err = check_cert_use_ocsp (cert)))
+ return err;
+ if ((flags & VALIDATE_FLAG_CRL) && (err = check_cert_use_crl (cert)))
+ return err;
+
+ /* If we already validated the certificate not too long ago, we can
+ avoid the excessive computations and lookups unless the caller
+ asked for the expiration time. */
+ if (!r_exptime)
+ {
+ size_t buflen;
+ time_t validated_at;
+
+ err = ksba_cert_get_user_data (cert, "validated_at",
+ &validated_at, sizeof (validated_at),
+ &buflen);
+ if (err || buflen != sizeof (validated_at) || !validated_at)
+ err = 0; /* Not available or other error. */
+ else
+ {
+ /* If the validation is not older than 30 minutes we are ready. */
+ if (validated_at < gnupg_get_time () + (30*60))
+ {
+ if (opt.verbose)
+ log_info ("certificate is good (cached)\n");
+ /* Note, that we can't jump to leave here as this would
+ falsely updated the validation timestamp. */
+ return 0;
+ }
+ }
+ }
+
+ /* Get the current time. */
+ gnupg_get_isotime (current_time);
+
+ /* We walk up the chain until we find a trust anchor. */
+ subject_cert = cert;
+ maxdepth = 10; /* Sensible limit on the length of the chain. */
+ chain = NULL;
+ depth = 0;
+ for (;;)
+ {
+ /* Get the subject and issuer name from the current
+ certificate. */
+ ksba_free (issuer);
+ ksba_free (subject);
+ issuer = ksba_cert_get_issuer (subject_cert, 0);
+ subject = ksba_cert_get_subject (subject_cert, 0);
+
+ if (!issuer)
+ {
+ log_error (_("no issuer found in certificate\n"));
+ err = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ /* Handle the notBefore and notAfter timestamps. */
+ {
+ ksba_isotime_t not_before, not_after;
+
+ err = ksba_cert_get_validity (subject_cert, 0, not_before);
+ if (!err)
+ err = ksba_cert_get_validity (subject_cert, 1, not_after);
+ if (err)
+ {
+ log_error (_("certificate with invalid validity: %s"),
+ gpg_strerror (err));
+ err = gpg_error (GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ /* Keep track of the nearest expiration time in EXPTIME. */
+ if (*not_after)
+ {
+ if (!*exptime)
+ gnupg_copy_time (exptime, not_after);
+ else if (strcmp (not_after, exptime) < 0 )
+ gnupg_copy_time (exptime, not_after);
+ }
+
+ /* Check whether the certificate is already valid. */
+ if (*not_before && strcmp (current_time, not_before) < 0 )
+ {
+ log_error (_("certificate not yet valid"));
+ log_info ("(valid from ");
+ dump_isotime (not_before);
+ log_printf (")\n");
+ err = gpg_error (GPG_ERR_CERT_TOO_YOUNG);
+ goto leave;
+ }
+
+ /* Now check whether the certificate has expired. */
+ if (*not_after && strcmp (current_time, not_after) > 0 )
+ {
+ log_error (_("certificate has expired"));
+ log_info ("(expired at ");
+ dump_isotime (not_after);
+ log_printf (")\n");
+ any_expired = 1;
+ }
+ }
+
+ /* Do we have any critical extensions in the certificate we
+ can't handle? */
+ err = unknown_criticals (subject_cert);
+ if (err)
+ goto leave; /* yes. */
+
+ /* Check that given policies are allowed. */
+ err = check_cert_policy (subject_cert);
+ if (gpg_err_code (err) == GPG_ERR_NO_POLICY_MATCH)
+ {
+ any_no_policy_match = 1;
+ err = 0;
+ }
+ else if (err)
+ goto leave;
+
+ /* Is this a self-signed certificate? */
+ if (is_root_cert (subject_cert, issuer, subject))
+ {
+ /* Yes, this is our trust anchor. */
+ if (check_cert_sig (subject_cert, subject_cert) )
+ {
+ log_error (_("selfsigned certificate has a BAD signature"));
+ err = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
+ : GPG_ERR_BAD_CERT);
+ goto leave;
+ }
+
+ /* Is this certificate allowed to act as a CA. */
+ err = allowed_ca (subject_cert, NULL);
+ if (err)
+ goto leave; /* No. */
+
+ err = is_trusted_cert (subject_cert,
+ (flags & VALIDATE_FLAG_MASK_TRUST));
+ if (!err)
+ ; /* Yes we trust this cert. */
+ else if (gpg_err_code (err) == GPG_ERR_NOT_TRUSTED)
+ {
+ char *fpr;
+
+ log_error (_("root certificate is not marked trusted"));
+ fpr = get_fingerprint_hexstring (subject_cert);
+ log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
+ dump_cert ("issuer", subject_cert);
+ if (r_trust_anchor)
+ {
+ /* Caller wants to do another trustiness check. */
+ *r_trust_anchor = fpr;
+ err = 0;
+ }
+ else
+ xfree (fpr);
+ }
+ else
+ {
+ log_error (_("checking trustworthiness of "
+ "root certificate failed: %s\n"),
+ gpg_strerror (err));
+ }
+ if (err)
+ goto leave;
+
+ /* Prepend the certificate to our list. */
+ {
+ chain_item_t ci;
+
+ ci = xtrycalloc (1, sizeof *ci);
+ if (!ci)
+ {
+ err = gpg_error_from_errno (errno);
+ goto leave;
+ }
+ ksba_cert_ref (subject_cert);
+ ci->cert = subject_cert;
+ cert_compute_fpr (subject_cert, ci->fpr);
+ ci->next = chain;
+ chain = ci;
+ }
+
+ if (opt.verbose)
+ {
+ if (r_trust_anchor && *r_trust_anchor)
+ log_info ("root certificate is good but not trusted\n");
+ else
+ log_info ("root certificate is good and trusted\n");
+ }
+
+ break; /* Okay: a self-signed certificate is an end-point. */
+ }
+
+ /* To avoid loops, we use an arbitrary limit on the length of
+ the chain. */
+ depth++;
+ if (depth > maxdepth)
+ {
+ log_error (_("certificate chain too long\n"));
+ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+ goto leave;
+ }
+
+ /* Find the next cert up the tree. */
+ ksba_cert_release (issuer_cert); issuer_cert = NULL;
+ err = find_issuing_cert (ctrl, subject_cert, &issuer_cert);
+ if (err)
+ {
+ if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+ {
+ log_error (_("issuer certificate not found"));
+ log_info ("issuer certificate: #/");
+ dump_string (issuer);
+ log_printf ("\n");
+ }
+ else
+ log_error (_("issuer certificate not found: %s\n"),
+ gpg_strerror (err));
+ /* Use a better understandable error code. */
+ err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
+ goto leave;
+ }
+
+/* try_another_cert: */
+ if (DBG_X509)
+ {
+ log_debug ("got issuer's certificate:\n");
+ dump_cert ("issuer", issuer_cert);
+ }
+
+ /* Now check the signature of the certificate. FIXME: we should
+ * delay this until later so that faked certificates can't be
+ * turned into a DoS easily. */
+ err = check_cert_sig (issuer_cert, subject_cert);
+ if (err)
+ {
+ log_error (_("certificate has a BAD signature"));
+#if 0
+ if (gpg_err_code (err) == GPG_ERR_BAD_SIGNATURE)
+ {
+ /* We now try to find other issuer certificates which
+ might have been used. This is required because some
+ CAs are reusing the issuer and subject DN for new
+ root certificates without using a authorityKeyIdentifier. */
+ rc = find_up (kh, subject_cert, issuer, 1);
+ if (!rc)
+ {
+ ksba_cert_t tmp_cert;
+
+ rc = keydb_get_cert (kh, &tmp_cert);
+ if (rc || !compare_certs (issuer_cert, tmp_cert))
+ {
+ /* The find next did not work or returned an
+ identical certificate. We better stop here
+ to avoid infinite checks. */
+ rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
+ ksba_cert_release (tmp_cert);
+ }
+ else
+ {
+ do_list (0, lm, fp, _("found another possible matching "
+ "CA certificate - trying again"));
+ ksba_cert_release (issuer_cert);
+ issuer_cert = tmp_cert;
+ goto try_another_cert;
+ }
+ }
+ }
+#endif
+ /* Return a more descriptive error code than the one
+ * returned from the signature checking. */
+ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+ goto leave;
+ }
+
+ /* Check that the length of the chain is not longer than allowed
+ * by the CA. */
+ {
+ int chainlen;
+
+ err = allowed_ca (issuer_cert, &chainlen);
+ if (err)
+ goto leave;
+ if (chainlen >= 0 && (depth - 1) > chainlen)
+ {
+ log_error (_("certificate chain longer than allowed by CA (%d)"),
+ chainlen);
+ err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
+ goto leave;
+ }
+ }
+
+ /* May that certificate be used for certification? */
+ err = check_cert_use_cert (issuer_cert);
+ if (err)
+ goto leave; /* No. */
+
+ /* Prepend the certificate to our list. */
+ {
+ chain_item_t ci;
+
+ ci = xtrycalloc (1, sizeof *ci);
+ if (!ci)
+ {
+ err = gpg_error_from_errno (errno);
+ goto leave;
+ }
+ ksba_cert_ref (subject_cert);
+ ci->cert = subject_cert;
+ cert_compute_fpr (subject_cert, ci->fpr);
+ ci->next = chain;
+ chain = ci;
+ }
+
+ if (opt.verbose)
+ log_info (_("certificate is good\n"));
+
+ /* Now to the next level up. */
+ subject_cert = issuer_cert;
+ issuer_cert = NULL;
+ }
+
+ /* Even if we have no error here we need to check whether we
+ * encountered an error somewhere during the checks. Set the error
+ * code to the most critical one. */
+ if (!err)
+ {
+ if (any_expired)
+ err = gpg_error (GPG_ERR_CERT_EXPIRED);
+ else if (any_no_policy_match)
+ err = gpg_error (GPG_ERR_NO_POLICY_MATCH);
+ }
+
+ if (!err && opt.verbose)
+ {
+ chain_item_t citem;
+
+ log_info (_("certificate chain is good\n"));
+ for (citem = chain; citem; citem = citem->next)
+ cert_log_name (" certificate", citem->cert);
+ }
+
+ /* Now check for revocations unless CRL checks are disabled or we
+ * are non-recursive CRL mode. */
+ if (!err
+ && !(flags & VALIDATE_FLAG_NOCRLCHECK)
+ && !((flags & VALIDATE_FLAG_CRL)
+ && !(flags & VALIDATE_FLAG_RECURSIVE)))
+ { /* Now that everything is fine, walk the chain and check each
+ * certificate for revocations.
+ *
+ * 1. item in the chain - The root certificate.
+ * 2. item - the CA below the root
+ * last item - the target certificate.
+ *
+ * Now for each certificate in the chain check whether it has
+ * been included in a CRL and thus be revoked. We don't do OCSP
+ * here because this does not seem to make much sense. This
+ * might become a recursive process and we should better cache
+ * our validity results to avoid double work. Far worse a
+ * catch-22 may happen for an improper setup hierarchy and we
+ * need a way to break up such a deadlock. */
+ err = check_revocations (ctrl, chain);
+ }
+
+ if (!err && opt.verbose)
+ {
+ if (r_trust_anchor && *r_trust_anchor)
+ log_info ("target certificate may be valid\n");
+ else
+ log_info ("target certificate is valid\n");
+ }
+ else if (err && opt.verbose)
+ log_info ("target certificate is NOT valid\n");
+
+
+ leave:
+ if (!err && !(r_trust_anchor && *r_trust_anchor))
+ {
+ /* With no error we can update the validation cache. We do this
+ * for all certificates in the chain. Note that we can't use
+ * the cache if the caller requested to check the trustiness of
+ * the root certificate himself. Adding such a feature would
+ * require us to also store the fingerprint of root
+ * certificate. */
+ chain_item_t citem;
+ time_t validated_at = gnupg_get_time ();
+
+ for (citem = chain; citem; citem = citem->next)
+ {
+ err = ksba_cert_set_user_data (citem->cert, "validated_at",
+ &validated_at, sizeof (validated_at));
+ if (err)
+ {
+ log_error ("set_user_data(validated_at) failed: %s\n",
+ gpg_strerror (err));
+ err = 0;
+ }
+ }
+ }
+
+ if (r_exptime)
+ gnupg_copy_time (r_exptime, exptime);
+ ksba_free (issuer);
+ ksba_free (subject);
+ ksba_cert_release (issuer_cert);
+ if (subject_cert != cert)
+ ksba_cert_release (subject_cert);
+ while (chain)
+ {
+ chain_item_t ci_next = chain->next;
+ if (chain->cert)
+ ksba_cert_release (chain->cert);
+ xfree (chain);
+ chain = ci_next;
+ }
+ if (err && r_trust_anchor && *r_trust_anchor)
+ {
+ xfree (*r_trust_anchor);
+ *r_trust_anchor = NULL;
+ }
+ return err;
+}
+
+
+
+/* Return the public key algorithm id from the S-expression PKEY.
+ FIXME: libgcrypt should provide such a function. Note that this
+ implementation uses the names as used by libksba. */
+static int
+pk_algo_from_sexp (gcry_sexp_t pkey)
+{
+ gcry_sexp_t l1, l2;
+ const char *name;
+ size_t n;
+ int algo;
+
+ l1 = gcry_sexp_find_token (pkey, "public-key", 0);
+ if (!l1)
+ return 0; /* Not found. */
+ l2 = gcry_sexp_cadr (l1);
+ gcry_sexp_release (l1);
+
+ name = gcry_sexp_nth_data (l2, 0, &n);
+ if (!name)
+ algo = 0; /* Not found. */
+ else if (n==3 && !memcmp (name, "rsa", 3))
+ algo = GCRY_PK_RSA;
+ else if (n==3 && !memcmp (name, "dsa", 3))
+ algo = GCRY_PK_DSA;
+ else if (n==13 && !memcmp (name, "ambiguous-rsa", 13))
+ algo = GCRY_PK_RSA;
+ else
+ algo = 0;
+ gcry_sexp_release (l2);
+ return algo;
+}
+
+
+/* Return the hash algorithm's algo id from its name given in the
+ * non-null termnated string in (buffer,buflen). Returns 0 on failure
+ * or if the algo is not known. */
+static int
+hash_algo_from_buffer (const void *buffer, size_t buflen)
+{
+ char *string;
+ int algo;
+
+ string = xtrymalloc (buflen + 1);
+ if (!string)
+ {
+ log_error (_("out of core\n"));
+ return 0;
+ }
+ memcpy (string, buffer, buflen);
+ string[buflen] = 0;
+ algo = gcry_md_map_name (string);
+ if (!algo)
+ log_error ("unknown digest algorithm '%s' used in certificate\n", string);
+ xfree (string);
+ return algo;
+}
+
+
+/* Return an unsigned integer from the non-null termnated string
+ * (buffer,buflen). Returns 0 on failure. */
+static unsigned int
+uint_from_buffer (const void *buffer, size_t buflen)
+{
+ char *string;
+ unsigned int val;
+
+ string = xtrymalloc (buflen + 1);
+ if (!string)
+ {
+ log_error (_("out of core\n"));
+ return 0;
+ }
+ memcpy (string, buffer, buflen);
+ string[buflen] = 0;
+ val = strtoul (string, NULL, 10);
+ xfree (string);
+ return val;
+}
+
+
+/* Check the signature on CERT using the ISSUER_CERT. This function
+ * does only test the cryptographic signature and nothing else. It is
+ * assumed that the ISSUER_CERT is valid. */
+static gpg_error_t
+check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert)
+{
+ gpg_error_t err;
+ const char *algoid;
+ gcry_md_hd_t md;
+ int algo;
+ ksba_sexp_t p;
+ size_t n;
+ gcry_sexp_t s_sig, s_hash, s_pkey;
+ const char *algo_name; /* hash algorithm name converted to lower case. */
+ int digestlen;
+ unsigned char *digest;
+ int use_pss = 0;
+ unsigned int saltlen;
+
+ /* Hash the target certificate using the algorithm from that certificate. */
+ algoid = ksba_cert_get_digest_algo (cert);
+ algo = gcry_md_map_name (algoid);
+ if (!algo && algoid && !strcmp (algoid, "1.2.840.113549.1.1.10"))
+ use_pss = 1;
+ else if (!algo)
+ {
+ log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?");
+ return gpg_error (GPG_ERR_GENERAL);
+ }
+
+ /* Get the signature value out of the target certificate. */
+ p = ksba_cert_get_sig_val (cert);
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ ksba_free (p);
+ return gpg_error (GPG_ERR_BUG);
+ }
+ err = gcry_sexp_sscan ( &s_sig, NULL, p, n);
+ ksba_free (p);
+ if (err)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err));
+ return err;
+ }
+ if (DBG_CRYPTO)
+ gcry_log_debugsxp ("sigval", s_sig);
+
+ if (use_pss)
+ {
+ /* Extract the hash algorithm and the salt length from the sigval. */
+ gcry_buffer_t ioarray[2] = { {0}, {0} };
+
+ err = gcry_sexp_extract_param (s_sig, "sig-val",
+ "&'hash-algo''salt-length'",
+ ioarray+0, ioarray+1, NULL);
+ if (err)
+ {
+ gcry_sexp_release (s_sig);
+ log_error ("extracting params from PSS failed: %s\n",
+ gpg_strerror (err));
+ return err;
+ }
+ algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len);
+ saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len);
+ xfree (ioarray[0].data);
+ xfree (ioarray[1].data);
+ if (saltlen < 20)
+ {
+ log_error ("length of PSS salt too short\n");
+ gcry_sexp_release (s_sig);
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+ if (!algo)
+ {
+ gcry_sexp_release (s_sig);
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+ /* Add some restrictions; see ../sm/certcheck.c for details. */
+ switch (algo)
+ {
+ case GCRY_MD_SHA1:
+ case GCRY_MD_SHA256:
+ case GCRY_MD_SHA384:
+ case GCRY_MD_SHA512:
+ case GCRY_MD_SHA3_256:
+ case GCRY_MD_SHA3_384:
+ case GCRY_MD_SHA3_512:
+ break;
+ default:
+ log_error ("PSS hash algorithm '%s' rejected\n",
+ gcry_md_algo_name (algo));
+ gcry_sexp_release (s_sig);
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+
+ if (gcry_md_get_algo_dlen (algo) != saltlen)
+ {
+ log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n",
+ gcry_md_algo_name (algo), saltlen);
+ gcry_sexp_release (s_sig);
+ return gpg_error (GPG_ERR_DIGEST_ALGO);
+ }
+ }
+
+ algo_name = hash_algo_to_string (algo);
+ err = gcry_md_open (&md, algo, 0);
+ if (err)
+ {
+ log_error ("md_open failed: %s\n", gpg_strerror (err));
+ gcry_sexp_release (s_sig);
+ return err;
+ }
+ if (DBG_HASHING)
+ gcry_md_debug (md, "hash.cert");
+
+ err = ksba_cert_hash (cert, 1, HASH_FNC, md);
+ if (err)
+ {
+ log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (err));
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ return err;
+ }
+ gcry_md_final (md);
+
+ /* Get the public key from the issuer certificate. */
+ p = ksba_cert_get_public_key (issuer_cert);
+ n = gcry_sexp_canon_len (p, 0, NULL, NULL);
+ if (!n)
+ {
+ log_error ("libksba did not return a proper S-Exp\n");
+ gcry_md_close (md);
+ ksba_free (p);
+ gcry_sexp_release (s_sig);
+ return gpg_error (GPG_ERR_BUG);
+ }
+ err = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
+ ksba_free (p);
+ if (err)
+ {
+ log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err));
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ return err;
+ }
+
+
+ /* Prepare the values for signature verification. At this point we
+ * have these values:
+ *
+ * S_PKEY - S-expression with the issuer's public key.
+ * S_SIG - Signature value as given in the certificate.
+ * MD - Finalized hash context with hash of the certificate.
+ * ALGO_NAME - Lowercase hash algorithm name
+ * SALTLEN - Salt length for rsaPSS.
+ */
+ digestlen = gcry_md_get_algo_dlen (algo);
+ digest = gcry_md_read (md, algo);
+ if (use_pss)
+ {
+ err = gcry_sexp_build (&s_hash, NULL,
+ "(data (flags pss)"
+ "(hash %s %b)"
+ "(salt-length %u))",
+ algo_name,
+ (int)digestlen,
+ digest,
+ saltlen);
+ }
+ else if (pk_algo_from_sexp (s_pkey) == GCRY_PK_DSA)
+ {
+ /* NB.: We support only SHA-1 here because we had problems back
+ * then to get test data for DSA-2. Meanwhile DSA has been
+ * replaced by ECDSA which we do not yet support. */
+ if (digestlen != 20)
+ {
+ log_error ("DSA requires the use of a 160 bit hash algorithm\n");
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_pkey);
+ return gpg_error (GPG_ERR_INTERNAL);
+ }
+ err = gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))",
+ (int)digestlen, digest);
+ }
+ else /* Not DSA - we assume RSA */
+ {
+ err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
+ algo_name, (int)digestlen, digest);
+ }
+
+ if (!err)
+ err = gcry_pk_verify (s_sig, s_hash, s_pkey);
+ if (DBG_X509)
+ log_debug ("gcry_pk_verify: %s\n", gpg_strerror (err));
+ gcry_md_close (md);
+ gcry_sexp_release (s_sig);
+ gcry_sexp_release (s_hash);
+ gcry_sexp_release (s_pkey);
+ return err;
+}
+
+
+
+/* Return 0 if CERT is usable for MODE. */
+static gpg_error_t
+check_cert_usage (ksba_cert_t cert, enum cert_usage_modes mode)
+{
+ gpg_error_t err;
+ unsigned int use;
+ char *extkeyusages;
+ int have_ocsp_signing = 0;
+
+ err = ksba_cert_get_ext_key_usages (cert, &extkeyusages);
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ err = 0; /* No policy given. */
+ if (!err)
+ {
+ unsigned int extusemask = ~0; /* Allow all. */
+
+ if (extkeyusages)
+ {
+ char *p, *pend;
+ int any_critical = 0;
+
+ extusemask = 0;
+
+ p = extkeyusages;
+ while (p && (pend=strchr (p, ':')))
+ {
+ *pend++ = 0;
+ /* Only care about critical flagged usages. */
+ if ( *pend == 'C' )
+ {
+ any_critical = 1;
+ if ( !strcmp (p, oid_kp_serverAuth))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_KEY_ENCIPHERMENT
+ | KSBA_KEYUSAGE_KEY_AGREEMENT);
+ else if ( !strcmp (p, oid_kp_clientAuth))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_KEY_AGREEMENT);
+ else if ( !strcmp (p, oid_kp_codeSigning))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE);
+ else if ( !strcmp (p, oid_kp_emailProtection))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_NON_REPUDIATION
+ | KSBA_KEYUSAGE_KEY_ENCIPHERMENT
+ | KSBA_KEYUSAGE_KEY_AGREEMENT);
+ else if ( !strcmp (p, oid_kp_timeStamping))
+ extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_NON_REPUDIATION);
+ }
+
+ /* This is a hack to cope with OCSP. Note that we do
+ not yet fully comply with the requirements and that
+ the entire CRL/OCSP checking thing should undergo a
+ thorough review and probably redesign. */
+ if ( !strcmp (p, oid_kp_ocspSigning))
+ have_ocsp_signing = 1;
+
+ if ((p = strchr (pend, '\n')))
+ p++;
+ }
+ ksba_free (extkeyusages);
+ extkeyusages = NULL;
+
+ if (!any_critical)
+ extusemask = ~0; /* Reset to the don't care mask. */
+ }
+
+
+ err = ksba_cert_get_key_usage (cert, &use);
+ if (gpg_err_code (err) == GPG_ERR_NO_DATA)
+ {
+ err = 0;
+ if (opt.verbose && (mode == CERT_USAGE_MODE_SIGN
+ || mode == CERT_USAGE_MODE_ENCR))
+ log_info (_("no key usage specified - assuming all usages\n"));
+ use = ~0;
+ }
+
+ /* Apply extKeyUsage. */
+ use &= extusemask;
+
+ }
+ if (err)
+ {
+ log_error (_("error getting key usage information: %s\n"),
+ gpg_strerror (err));
+ ksba_free (extkeyusages);
+ return err;
+ }
+
+ switch (mode)
+ {
+ case CERT_USAGE_MODE_SIGN:
+ case CERT_USAGE_MODE_VRFY:
+ if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
+ | KSBA_KEYUSAGE_NON_REPUDIATION)))
+ return 0;
+ log_info (mode == CERT_USAGE_MODE_VRFY
+ ? _("certificate should not have been used for signing\n")
+ : _("certificate is not usable for signing\n"));
+ break;
+
+ case CERT_USAGE_MODE_ENCR:
+ case CERT_USAGE_MODE_DECR:
+ if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT
+ | KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
+ return 0;
+ log_info (mode == CERT_USAGE_MODE_DECR
+ ? _("certificate should not have been used for encryption\n")
+ : _("certificate is not usable for encryption\n"));
+ break;
+
+ case CERT_USAGE_MODE_CERT:
+ if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN)))
+ return 0;
+ log_info (_("certificate should not have "
+ "been used for certification\n"));
+ break;
+
+ case CERT_USAGE_MODE_OCSP:
+ if (use != ~0
+ && (have_ocsp_signing
+ || (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN
+ |KSBA_KEYUSAGE_CRL_SIGN))))
+ return 0;
+ log_info (_("certificate should not have "
+ "been used for OCSP response signing\n"));
+ break;
+
+ case CERT_USAGE_MODE_CRL:
+ if ((use & (KSBA_KEYUSAGE_CRL_SIGN)))
+ return 0;
+ log_info (_("certificate should not have "
+ "been used for CRL signing\n"));
+ break;
+ }
+
+ return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+}
+
+
+/* Return 0 if the certificate CERT is usable for certification. */
+gpg_error_t
+check_cert_use_cert (ksba_cert_t cert)
+{
+ return check_cert_usage (cert, CERT_USAGE_MODE_CERT);
+}
+
+/* Return 0 if the certificate CERT is usable for signing OCSP
+ responses. */
+gpg_error_t
+check_cert_use_ocsp (ksba_cert_t cert)
+{
+ return check_cert_usage (cert, CERT_USAGE_MODE_OCSP);
+}
+
+/* Return 0 if the certificate CERT is usable for signing CRLs. */
+gpg_error_t
+check_cert_use_crl (ksba_cert_t cert)
+{
+ return check_cert_usage (cert, CERT_USAGE_MODE_CRL);
+}