diff --git a/NEWS b/NEWS index 38dee1ec3..0d520f3ac 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,9 @@ Noteworthy changes in version 1.9.21 * Cards are not anymore reseted at the end of a connection. + * [gpgsm] Kludge to allow use of Bundesnetzagentur issued + certificates. + Noteworthy changes in version 1.9.20 (2005-12-20) ------------------------------------------------- diff --git a/TODO b/TODO index 85e08ed16..6033d9150 100644 --- a/TODO +++ b/TODO @@ -18,7 +18,9 @@ might want to have an agent context for each service request * sm/certchain.c ** When a certificate chain was sucessfully verified, make ephemeral certs used in this chain permanent. - +** Try to keep certificate references somewhere + This will help with some of our caching code. We also need to test + that cachining; in particular "regtp_ca_chainlen". * sm/decrypt.c ** replace leading zero in integer hack by a cleaner solution diff --git a/sm/ChangeLog b/sm/ChangeLog index 764faa67d..02505962c 100644 --- a/sm/ChangeLog +++ b/sm/ChangeLog @@ -1,3 +1,12 @@ +2006-03-21 Werner Koch + + * certchain.c (get_regtp_ca_info): New. + (allowed_ca): Use it. + +2006-03-20 Werner Koch + + * qualified.c (gpgsm_is_in_qualified_list): New optional arg COUNTRY. + 2006-02-17 Werner Koch * call-dirmngr.c (start_dirmngr): Print name of dirmngr to be started. diff --git a/sm/certchain.c b/sm/certchain.c index 0c14fc88f..44d72efd3 100644 --- a/sm/certchain.c +++ b/sm/certchain.c @@ -1,5 +1,6 @@ /* certchain.c - certificate chain validation - * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -39,6 +40,10 @@ #include "i18n.h" +static int get_regtp_ca_info (ksba_cert_t cert, int *chainlen); + + + /* If LISTMODE is true, print FORMAT using LISTMODE to FP. If LISTMODE is false, use the string to print an log_info or, if IS_ERROR is true, and log_error. */ @@ -128,6 +133,11 @@ allowed_ca (ksba_cert_t cert, int *chainlen, int listmode, FILE *fp) return err; if (!flag) { + if (get_regtp_ca_info (cert, chainlen)) + { + return 0; /* RegTP issued certificate. */ + } + do_list (1, listmode, fp,_("issuer certificate is not marked as a CA")); return gpg_error (GPG_ERR_BAD_CA_CERT); } @@ -267,7 +277,7 @@ check_cert_policy (ksba_cert_t cert, int listmode, FILE *fplist) } -/* Helper fucntion for find_up. This resets the key handle and search +/* Helper function for find_up. This resets the key handle and search for an issuer ISSUER with a subjectKeyIdentifier of KEYID. Returns 0 obn success or -1 when not found. */ static int @@ -796,7 +806,7 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, { /* Need to consult the list of root certificates for qualified signatures. */ - err = gpgsm_is_in_qualified_list (ctrl, subject_cert); + err = gpgsm_is_in_qualified_list (ctrl, subject_cert, NULL); if (!err) is_qualified = 1; else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND) @@ -807,8 +817,8 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime, gpg_strerror (err)); if ( is_qualified != -1 ) { - /* Cache the result but don't care toomuch about - an error. */ + /* Cache the result but don't care too much + about an error. */ buf[0] = !!is_qualified; err = ksba_cert_set_user_data (subject_cert, "is_qualified", buf, 1); @@ -1181,3 +1191,110 @@ gpgsm_basic_cert_check (ksba_cert_t cert) return rc; } + + +/* Check whether the certificate CERT has been issued by the German + authority for qualified signature. They do not set the + basicConstraints and thus we need this workaround. It works by + looking up the root certificate and checking whether that one is + listed as a qualified certificate for Germany. + + We also try to cache this data but as long as don't keep a + reference to the certificate this won't be used. + + Returns: True if CERT is a RegTP issued CA cert (i.e. the root + certificate itself or one of the CAs). In that case CHAINLEN will + receive the length of the chain which is either 0 or 1. +*/ +static int +get_regtp_ca_info (ksba_cert_t cert, int *chainlen) +{ + gpg_error_t err; + ksba_cert_t next; + int rc = 0; + int i, depth; + char country[3]; + ksba_cert_t array[4]; + char buf[2]; + size_t buflen; + int dummy_chainlen; + + if (!chainlen) + chainlen = &dummy_chainlen; + + *chainlen = 0; + err = ksba_cert_get_user_data (cert, "regtp_ca_chainlen", + &buf, sizeof (buf), &buflen); + if (!err) + { + /* Got info. */ + if (buflen < 2 || !*buf) + return 0; /* Nothing found. */ + *chainlen = buf[1]; + return 1; /* This is a regtp CA. */ + } + else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND) + { + log_error ("ksba_cert_get_user_data(%s) failed: %s\n", + "regtp_ca_chainlen", gpg_strerror (err)); + return 0; /* Nothing found. */ + } + + /* Need to gather the info. This requires to walk up the chain + until we have found the root. Because we are only interested in + German Bundesnetzagentur (former RegTP) derived certificates 3 + levels are enough. (The German signature law demands a 3 tier + hierachy; thus there is only one CA between the EE and the Root + CA.) */ + memset (&array, 0, sizeof array); + + depth = 0; + ksba_cert_ref (cert); + array[depth++] = cert; + ksba_cert_ref (cert); + while (depth < DIM(array) && !(rc=gpgsm_walk_cert_chain (cert, &next))) + { + ksba_cert_release (cert); + ksba_cert_ref (next); + array[depth++] = next; + cert = next; + } + ksba_cert_release (cert); + if (rc != -1 || !depth || depth == DIM(array) ) + { + /* We did not reached the root. */ + goto leave; + } + + /* If this is a German signature law issued certificate, we store + additional additional information. */ + if (!gpgsm_is_in_qualified_list (NULL, array[depth-1], country) + && !strcmp (country, "de")) + { + /* Setting the pathlen for the root CA and the CA flag for the + next one is all what we need to do. */ + err = ksba_cert_set_user_data (array[depth-1], "regtp_ca_chainlen", + "\x01\x01", 2); + if (!err && depth > 1) + err = ksba_cert_set_user_data (array[depth-2], "regtp_ca_chainlen", + "\x01\x00", 2); + if (err) + log_error ("ksba_set_user_data(%s) failed: %s\n", + "regtp_ca_chainlen", gpg_strerror (err)); + for (i=0; i < depth; i++) + ksba_cert_release (array[i]); + *chainlen = (depth>1? 0:1); + return 1; + } + + leave: + /* Nothing special with this certificate. Mark the target + certificate anyway to avoid duplicate lookups. */ + err = ksba_cert_set_user_data (cert, "regtp_ca_chainlen", "", 1); + if (err) + log_error ("ksba_set_user_data(%s) failed: %s\n", + "regtp_ca_chainlen", gpg_strerror (err)); + for (i=0; i < depth; i++) + ksba_cert_release (array[i]); + return 0; +} diff --git a/sm/gpgsm.h b/sm/gpgsm.h index dc863f682..438252050 100644 --- a/sm/gpgsm.h +++ b/sm/gpgsm.h @@ -296,7 +296,8 @@ int gpgsm_decrypt (ctrl_t ctrl, int in_fd, FILE *out_fp); int gpgsm_genkey (ctrl_t ctrl, int in_fd, FILE *out_fp); /*-- qualified.c --*/ -gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert); +gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, + char *country); gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert); gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert); diff --git a/sm/qualified.c b/sm/qualified.c index a52269734..07abaadc4 100644 --- a/sm/qualified.c +++ b/sm/qualified.c @@ -145,24 +145,29 @@ read_list (char *key, char *country, int *lnr) per user because it is not a decision of the user. Returns: 0 if the certificate is included. GPG_ERR_NOT_FOUND if it - is not in the liost or any other error (e.g. if no list of - qualified signatures is available. */ + is not in the list or any other error (e.g. if no list of + qualified signatures is available. If COUNTRY has not been passed + as NULL a string witha maximum length of 2 will be copied into it; + thus the caller needs to provide a buffer of length 3. */ gpg_error_t -gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert) +gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert, char *country) { gpg_error_t err; char *fpr; char key[41]; - char country[2]; + char mycountry[3]; int lnr = 0; + if (country) + *country = 0; + fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1); if (!fpr) return gpg_error (GPG_ERR_GENERAL); if (listfp) rewind (listfp); - while (!(err = read_list (key, country, &lnr))) + while (!(err = read_list (key, mycountry, &lnr))) { if (!strcmp (key, fpr)) break; @@ -170,6 +175,9 @@ gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert) if (gpg_err_code (err) == GPG_ERR_EOF) err = gpg_error (GPG_ERR_NOT_FOUND); + if (!err && country) + strcpy (country, mycountry); + xfree (fpr); return err; }