Added qualified signature features.

This commit is contained in:
Werner Koch 2005-11-13 19:07:06 +00:00
parent caed7370e9
commit b9633196f4
12 changed files with 535 additions and 9 deletions

View File

@ -1,8 +1,41 @@
2005-11-13 Werner Koch <wk@g10code.com>
* call-agent.c (gpgsm_agent_get_confirmation): New.
* keylist.c (list_cert_std): Print qualified status.
* qualified.c: New.
* certchain.c (gpgsm_validate_chain): Check for qualified
certificates.
* certchain.c (gpgsm_basic_cert_check): Release keydb handle when
no-chain-validation is used.
2005-11-11 Werner Koch <wk@g10code.com>
* keylist.c (print_capabilities): Print is_qualified status.
2005-10-28 Werner Koch <wk@g10code.com>
* certdump.c (pretty_print_sexp): New.
(gpgsm_print_name2): Use it here. This allows proper printing of
DNS names as used with server certificates.
2005-10-10 Werner Koch <wk@g10code.com>
* keylist.c: Add pkaAdress OID as reference.
2005-10-08 Marcus Brinkmann <marcus@g10code.de>
* Makefile.am (gpgsm_LDADD): Add ../gl/libgnu.a after
../common/libcommon.a.
2005-09-13 Werner Koch <wk@g10code.com>
* verify.c (gpgsm_verify): Print a note if the unknown algorithm
is MD2.
* sign.c (gpgsm_sign): Ditto.
* certcheck.c (gpgsm_check_cert_sig): Ditto.
2005-09-08 Werner Koch <wk@g10code.com>
* export.c (popen_protect_tool): Add option --have-cert. We

View File

@ -49,7 +49,8 @@ gpgsm_SOURCES = \
import.c \
export.c \
delete.c \
certreqgen.c
certreqgen.c \
qualified.c
gpgsm_LDADD = ../jnlib/libjnlib.a ../kbx/libkeybox.a \

View File

@ -693,3 +693,24 @@ gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc)
return map_assuan_err (rc);
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc)
{
int rc;
char *fpr;
char line[ASSUAN_LINELENGTH];
rc = start_agent (ctrl);
if (rc)
return rc;
snprintf (line, DIM(line)-1, "GET_CONFIRMATION %s", desc);
line[DIM(line)-1] = 0;
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
return map_assuan_err (rc);
}

View File

@ -276,7 +276,7 @@ inq_certificate (void *opaque, const char *line)
{
size_t n;
/* Send a certificate where a sourceKeyidentifier is included. */
/* Send a certificate where a sourceKeyIdentifier is included. */
line += 12;
while (*line == ' ')
line++;

View File

@ -643,6 +643,9 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime,
int any_no_crl = 0;
int any_crl_too_old = 0;
int any_no_policy_match = 0;
int is_qualified = -1; /* Indicates whether the certificate stems
from a qualified root certificate.
-1 = unknown, 0 = no, 1 = yes. */
int lm = listmode;
gnupg_get_isotime (current_time);
@ -771,6 +774,53 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime,
if (rc)
goto leave;
/* Set the flag for qualified signatures. This flag is
deduced from a list of root certificates allowed for
qualified signatures. */
if (is_qualified == -1)
{
gpg_error_t err;
size_t buflen;
char buf[1];
if (!ksba_cert_get_user_data (cert, "is_qualified",
&buf, sizeof (buf),
&buflen) && buflen)
{
/* We already checked this for this certificate,
thus we simply take it from the user data. */
is_qualified = !!*buf;
}
else
{
/* Need to consult the list of root certificates for
qualified signatures. */
err = gpgsm_is_in_qualified_list (ctrl, subject_cert);
if (!err)
is_qualified = 1;
else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND)
is_qualified = 0;
else
log_error ("checking the list of qualified "
"root certificates failed: %s\n",
gpg_strerror (err));
if ( is_qualified != -1 )
{
/* Cache the result but don't care toomuch about
an error. */
buf[0] = !!is_qualified;
err = ksba_cert_set_user_data (subject_cert,
"is_qualified", buf, 1);
if (err)
log_error ("set_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
}
}
}
/* Check whether we really trust this root certificate. */
rc = gpgsm_agent_istrusted (ctrl, subject_cert);
if (!rc)
;
@ -968,7 +1018,7 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime,
keydb_search_reset (kh);
subject_cert = issuer_cert;
issuer_cert = NULL;
}
} /* End chain traversal. */
if (!listmode)
{
@ -996,6 +1046,27 @@ gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime,
}
leave:
if (is_qualified != -1)
{
/* We figured something about the qualified signature capability
of the certificate under question. Store the result as user
data in the certificate object. We do this even if the
validation itself failed. */
/* Fixme: We should set this flag for all certificates in the
chain for optimizing reasons. */
char buf[1];
gpg_error_t err;
buf[0] = !!is_qualified;
err = ksba_cert_set_user_data (cert, "is_qualified", buf, 1);
if (err)
{
log_error ("set_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
if (!rc)
rc = err;
}
}
if (r_exptime)
gnupg_copy_time (r_exptime, exptime);
xfree (issuer);
@ -1017,7 +1088,7 @@ gpgsm_basic_cert_check (ksba_cert_t cert)
int rc = 0;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh = keydb_new (0);
KEYDB_HANDLE kh;
ksba_cert_t issuer_cert = NULL;
if (opt.no_chain_validation)
@ -1026,6 +1097,7 @@ gpgsm_basic_cert_check (ksba_cert_t cert)
return 0;
}
kh = keydb_new (0);
if (!kh)
{
log_error (_("failed to allocated keyDB handle\n"));

View File

@ -168,6 +168,10 @@ gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert)
if (!algo)
{
log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?");
if (algoid
&& ( !strcmp (algoid, "1.2.840.113549.1.1.2")
||!strcmp (algoid, "1.2.840.113549.2.2")))
log_info (_("(this is the MD2 algorithm)\n"));
return gpg_error (GPG_ERR_GENERAL);
}
rc = gcry_md_open (&md, algo, 0);

View File

@ -484,6 +484,51 @@ print_dn_parts (FILE *fp, struct dn_array_s *dn, int translate)
}
/* Print the S-Expression in BUF, which has a valid length of BUFLEN,
as a human readable string in one line to FP. */
static void
pretty_print_sexp (FILE *fp, const unsigned char *buf, size_t buflen)
{
size_t len;
gcry_sexp_t sexp;
char *result, *p;
if ( gcry_sexp_sscan (&sexp, NULL, (const char*)buf, buflen) )
{
fputs (_("[Error - invalid encoding]"), fp);
return;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
assert (len);
result = xtrymalloc (len);
if (!result)
{
fputs (_("[Error - out of core]"), fp);
gcry_sexp_release (sexp);
return;
}
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, len);
assert (len);
for (p = result; len; len--, p++)
{
if (*p == '\n')
{
if (len > 1) /* Avoid printing the trailing LF. */
fputs ("\\n", fp);
}
else if (*p == '\r')
fputs ("\\r", fp);
else if (*p == '\v')
fputs ("\\v", fp);
else if (*p == '\t')
fputs ("\\t", fp);
else
putc (*p, fp);
}
xfree (result);
gcry_sexp_release (sexp);
}
void
gpgsm_print_name2 (FILE *fp, const char *name, int translate)
@ -507,7 +552,9 @@ gpgsm_print_name2 (FILE *fp, const char *name, int translate)
}
}
else if (*s == '(')
fputs (_("[Error - unknown encoding]"), fp);
{
pretty_print_sexp (fp, s, gcry_sexp_canon_len (s, 0, NULL, NULL));
}
else if (!((*s >= '0' && *s < '9')
|| (*s >= 'A' && *s <= 'Z')
|| (*s >= 'a' && *s <= 'z')))
@ -576,7 +623,7 @@ format_name_writer (void *cookie, const char *buffer, size_t size)
/* Format NAME which is expected to be in rfc2253 format into a better
human readable format. Caller must free the returned string. NULL
is returned in case of an error. With TRANSLATE set to true the
name will be translated to the native encodig. Note that NAME is
name will be translated to the native encoding. Note that NAME is
internally always UTF-8 encoded. */
char *
gpgsm_format_name2 (const char *name, int translate)
@ -658,7 +705,7 @@ gpgsm_format_keydesc (ksba_cert_t cert)
#ifdef ENABLE_NLS
/* The Assuan agent protol requires us to transmit utf-8 strings */
/* The Assuan agent protocol requires us to transmit utf-8 strings */
orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL);
#ifdef HAVE_LANGINFO_CODESET
if (!orig_codeset)

View File

@ -288,6 +288,10 @@ int gpgsm_decrypt (ctrl_t ctrl, int in_fd, FILE *out_fp);
/*-- certreqgen.c --*/
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_qualified_consent (ctrl_t ctrl, ksba_cert_t cert);
/*-- call-agent.c --*/
int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char *digest,
@ -306,6 +310,7 @@ int gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip);
int gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert);
int gpgsm_agent_learn (ctrl_t ctrl);
int gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc);
gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc);
/*-- call-dirmngr.c --*/
int gpgsm_dirmngr_isvalid (ctrl_t ctrl,

View File

@ -66,6 +66,9 @@ struct {
{ "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth" },
{ "1.3.6.1.5.5.7.3.13", "eapOverPPP" },
{ "1.3.6.1.5.5.7.3.14", "wlanSSID" },
{ "2.16.840.1.113730.4.1", "serverGatedCrypto.ns" }, /* Netscape. */
{ "1.3.6.1.4.1.311.10.3.3", "serverGatedCrypto.ms"}, /* Microsoft. */
{ NULL, NULL }
};
@ -160,6 +163,9 @@ static struct {
{ "2.16.840.1.113730.1.12", "netscape-ssl-server-name" },
{ "2.16.840.1.113730.1.13", "netscape-comment" },
/* GnuPG extensions */
{ "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" },
{ NULL }
};
@ -207,6 +213,21 @@ print_capabilities (ksba_cert_t cert, FILE *fp)
{
gpg_error_t err;
unsigned int use;
size_t buflen;
char buffer[1];
err = ksba_cert_get_user_data (cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (!err && buflen)
{
if (*buffer)
putc ('q', fp);
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* Don't know - will not get marked as 'q' */
else
log_debug ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
err = ksba_cert_get_key_usage (cert, &use);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
@ -1032,9 +1053,28 @@ list_cert_std (ctrl_t ctrl, ksba_cert_t cert, FILE *fp, int have_secret,
fprintf (fp, " fingerprint: %s\n", dn?dn:"error");
xfree (dn);
if (with_validation)
{
gpg_error_t tmperr;
size_t buflen;
char buffer[1];
err = gpgsm_validate_chain (ctrl, cert, NULL, 1, fp, 0);
tmperr = ksba_cert_get_user_data (cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (!tmperr && buflen)
{
if (*buffer)
fputs (" [qualified]\n", fp);
}
else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND)
; /* Don't know - will not get marked as 'q' */
else
log_debug ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (tmperr));
if (!err)
fprintf (fp, " [certificate is good]\n");
else

264
sm/qualified.c Normal file
View File

@ -0,0 +1,264 @@
/* qualified.c - Routines related to qualified signatures
* Copyright (C) 2005 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG 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.
*
* GnuPG 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 <string.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
#include <langinfo.h>
#endif
#include "gpgsm.h"
#include "i18n.h"
#include <ksba.h>
/* We open the file only once and keep the open file pointer as well
as the name of the file here. Note that, a listname not equal to
NULL indicates that this module has been intialized and if the
LISTFP is also NULL, no list of qualified signatures exists. */
static char *listname;
static FILE *listfp;
/* Read the trustlist and return entry by entry. KEY must point to a
buffer of at least 41 characters. COUNTRY shall be a buffer of at
least 3 characters to receive the country code of that qualified
signature (i.e. "de" for German and "be" for Belgium).
Reading a valid entry returns 0, EOF is indicated by GPG_ERR_EOF
and any other error condition is indicated by the appropriate error
code. */
static gpg_error_t
read_list (char *key, char *country, int *lnr)
{
gpg_error_t err;
int c, i, j;
char *p, line[256];
*key = 0;
*country = 0;
if (!listname)
{
listname = make_filename (GNUPG_DATADIR, "qualified.txt", NULL);
listfp = fopen (listname, "r");
if (!listfp && errno != ENOENT)
{
err = gpg_error_from_errno (errno);
log_error (_("can't open `%s': %s\n"), listname, gpg_strerror (err));
return err;
}
}
if (!listfp)
return gpg_error (GPG_ERR_EOF);
do
{
if (!fgets (line, DIM(line)-1, listfp) )
{
if (feof (listfp))
return gpg_error (GPG_ERR_EOF);
return gpg_error_from_errno (errno);
}
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line. */
while ( (c=getc (listfp)) != EOF && c != '\n')
;
return gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
}
++*lnr;
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
}
while (!*p || *p == '\n' || *p == '#');
for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
if ( p[i] != ':' )
key[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
key[j] = 0;
if (j != 40 || !(spacep (p+i) || p[i] == '\n'))
{
log_error (_("invalid formatted fingerprint in `%s', line %d\n"),
listname, *lnr);
return gpg_error (GPG_ERR_BAD_DATA);
}
assert (p[i]);
i++;
while (spacep (p+i))
i++;
if ( p[i] >= 'a' && p[i] <= 'z'
&& p[i+1] >= 'a' && p[i+1] <= 'z'
&& (spacep (p+i+2) || p[i+2] == '\n'))
{
country[0] = p[i];
country[1] = p[i+1];
country[2] = 0;
}
else
{
log_error (_("invalid country code in `%s', line %d\n"), listname, *lnr);
return gpg_error (GPG_ERR_BAD_DATA);
}
return 0;
}
/* Check whether the certificate CERT is included in the list of
qualified certificates. This list is similar to the "trustlist.txt"
as maintained by gpg-agent and includes fingerprints of root
certificates to be used for qualified (legally binding like
handwritten) signatures. We keep this list system wide and not
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. */
gpg_error_t
gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert)
{
gpg_error_t err;
char *fpr;
char key[41];
char country[2];
int lnr = 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)))
{
if (!strcmp (key, fpr))
break;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = gpg_error (GPG_ERR_NOT_FOUND);
xfree (fpr);
return err;
}
/* We know that CERT is a qualified certificate. Ask the user for
consent to actually create a signature using this certificate.
Returns: 0 for yes, GPG_ERR_CANCEL for no or any otehr error
code. */
gpg_error_t
gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert)
{
gpg_error_t err;
char *name, *subject, *buffer, *p;
const char *s;
char *orig_codeset = NULL;
name = ksba_cert_get_subject (cert, 0);
if (!name)
return gpg_error (GPG_ERR_GENERAL);
subject = gpgsm_format_name2 (name, 0);
ksba_free (name); name = NULL;
#ifdef ENABLE_NLS
/* The Assuan agent protocol requires us to transmit utf-8 strings */
orig_codeset = bind_textdomain_codeset (PACKAGE_GT, NULL);
#ifdef HAVE_LANGINFO_CODESET
if (!orig_codeset)
orig_codeset = nl_langinfo (CODESET);
#endif
if (orig_codeset)
{ /* We only switch when we are able to restore the codeset later.
Note that bind_textdomain_codeset does only return on memory
errors but not if a codeset is not available. Thus we don't
bother printing a diagnostic here. */
orig_codeset = xstrdup (orig_codeset);
if (!bind_textdomain_codeset (PACKAGE_GT, "utf-8"))
orig_codeset = NULL;
}
#endif
if (asprintf (&name,
_("You are about to create a signature using your "
"certificate:\n"
"\"%s\"\n"
"This will create a qualified signature by law "
"equated to a handwritten signature.\n\n"
"Are you really sure that you want to do this?"),
subject? subject:"?"
) < 0 )
err = gpg_error_from_errno (errno);
else
err = 0;
#ifdef ENABLE_NLS
if (orig_codeset)
bind_textdomain_codeset (PACKAGE_GT, orig_codeset);
#endif
xfree (orig_codeset);
xfree (subject);
if (err)
return err;
buffer = p = xtrymalloc (strlen (name) * 3 + 1);
if (!buffer)
{
err = gpg_error_from_errno (errno);
free (name);
return err;
}
for (s=name; *s; s++)
{
if (*s < ' ' || *s == '+')
{
sprintf (p, "%%%02X", *(unsigned char *)s);
p += 3;
}
else if (*s == ' ')
*p++ = '+';
else
*p++ = *s;
}
*p = 0;
free (name);
err = gpgsm_agent_get_confirmation (ctrl, buffer);
xfree (buffer);
return err;
}

View File

@ -426,6 +426,35 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist,
goto leave;
}
}
/* Check whether one of the certificates is qualified. Note that we
already validated the certificate and thus the user data stored
flag must be available. */
for (cl=signerlist; cl; cl = cl->next)
{
size_t buflen;
char buffer[1];
err = ksba_cert_get_user_data (cl->cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (err || !buflen)
{
log_error (_("checking for qualified certificate failed: %s\n"),
gpg_strerror (err));
rc = err;
goto leave;
}
if (*buffer)
{
err = gpgsm_qualified_consent (ctrl, cl->cert);
if (err)
{
rc = err;
goto leave;
}
}
}
/* Prepare hashing (actually we are figuring out what we have set above)*/
rc = gcry_md_open (&data_md, 0, 0);
@ -443,6 +472,10 @@ gpgsm_sign (CTRL ctrl, CERTLIST signerlist,
if (!algo)
{
log_error ("unknown hash algorithm `%s'\n", algoid? algoid:"?");
if (algoid
&& ( !strcmp (algoid, "1.2.840.113549.1.1.2")
||!strcmp (algoid, "1.2.840.113549.2.2")))
log_info (_("(this is the MD2 algorithm)\n"));
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}

View File

@ -179,8 +179,14 @@ gpgsm_verify (CTRL ctrl, int in_fd, int data_fd, FILE *out_fp)
{
algo = gcry_md_map_name (algoid);
if (!algo)
log_error ("unknown hash algorithm `%s'\n",
algoid? algoid:"?");
{
log_error ("unknown hash algorithm `%s'\n",
algoid? algoid:"?");
if (algoid
&& ( !strcmp (algoid, "1.2.840.113549.1.1.2")
||!strcmp (algoid, "1.2.840.113549.2.2")))
log_info (_("(this is the MD2 algorithm)\n"));
}
else
gcry_md_enable (data_md, algo);
}