From 7f42987b075e39847cb576388419747e0a167e42 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Fri, 15 Sep 2006 18:53:37 +0000 Subject: [PATCH] Allow for a global trustlist. --- NEWS | 2 + agent/ChangeLog | 5 + agent/agent.h | 9 +- agent/command.c | 4 +- agent/gpg-agent.c | 7 - agent/trustlist.c | 587 ++++++++++++++++++++++--------------- autogen.sh | 2 +- common/ChangeLog | 7 + common/Makefile.am | 15 + common/convert.c | 136 +++++++++ common/t-convert.c | 204 +++++++++++++ common/util.h | 6 + doc/Makefile.am | 2 +- doc/examples/README | 9 + doc/examples/trustlist.txt | 46 +++ doc/gpg-agent.texi | 27 +- scd/ChangeLog | 2 +- 17 files changed, 802 insertions(+), 268 deletions(-) create mode 100644 common/convert.c create mode 100644 common/t-convert.c create mode 100644 doc/examples/README create mode 100644 doc/examples/trustlist.txt diff --git a/NEWS b/NEWS index 89fbad87c..432abc8ed 100644 --- a/NEWS +++ b/NEWS @@ -26,6 +26,8 @@ Noteworthy changes in version 1.9.23 * gpg-connect-agent has new options to utilize descriptor passing. + * A global trustlist may now be used. See doc/examples/trustlist.txt. + Noteworthy changes in version 1.9.22 (2006-07-27) ------------------------------------------------- diff --git a/agent/ChangeLog b/agent/ChangeLog index 4c4177110..6ba1c36c1 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,8 @@ +2006-09-15 Werner Koch + + * trustlist.c: Entirely rewritten. + (agent_trustlist_housekeeping): Removed and removed all calls. + 2006-09-14 Werner Koch Replaced all call gpg_error_from_errno(errno) by diff --git a/agent/agent.h b/agent/agent.h index ffb6cded8..7559a3e63 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -252,11 +252,10 @@ int agent_get_shadow_info (const unsigned char *shadowkey, /*-- trustlist.c --*/ -int agent_istrusted (const char *fpr); -int agent_listtrusted (void *assuan_context); -int agent_marktrusted (ctrl_t ctrl, const char *name, - const char *fpr, int flag); -void agent_trustlist_housekeeping (void); +gpg_error_t agent_istrusted (const char *fpr); +gpg_error_t agent_listtrusted (void *assuan_context); +gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, + const char *fpr, int flag); void agent_reload_trustlist (void); diff --git a/agent/command.c b/agent/command.c index 1c6ab8b79..94d770a3c 100644 --- a/agent/command.c +++ b/agent/command.c @@ -153,7 +153,7 @@ plus_to_blank (char *s) static size_t percent_plus_unescape (char *string) { - unsigned char *p = string; + unsigned char *p = (unsigned char *)string; size_t n = 0; while (*string) @@ -240,7 +240,7 @@ cmd_istrusted (assuan_context_t ctx, char *line) char *p; char fpr[41]; - /* parse the fingerprint value */ + /* Parse the fingerprint value. */ for (p=line,n=0; hexdigitp (p); p++, n++) ; if (*p || !(n == 40 || n == 32)) diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index c891eb09b..60a7cffb4 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -1427,11 +1427,6 @@ start_connection_thread (void *arg) log_info (_("handler 0x%lx for fd %d started\n"), (long)pth_self (), fd); - /* FIXME: Move this housekeeping into a ticker function. Calling it - for each connection should work but won't work anymore if our - clients start to keep connections. */ - agent_trustlist_housekeeping (); - start_command_handler (-1, fd); if (opt.verbose) log_info (_("handler 0x%lx for fd %d terminated\n"), @@ -1451,8 +1446,6 @@ start_connection_thread_ssh (void *arg) log_info (_("ssh handler 0x%lx for fd %d started\n"), (long)pth_self (), fd); - agent_trustlist_housekeeping (); - start_command_handler_ssh (fd); if (opt.verbose) log_info (_("ssh handler 0x%lx for fd %d terminated\n"), diff --git a/agent/trustlist.c b/agent/trustlist.c index f87cafaee..58a9467f5 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -1,5 +1,5 @@ /* trustlist.c - Maintain the list of trusted keys - * Copyright (C) 2002, 2004 Free Software Foundation, Inc. + * Copyright (C) 2002, 2004, 2006 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -28,213 +28,347 @@ #include #include #include +#include #include "agent.h" #include /* fixme: need a way to avoid assuan calls here */ #include "i18n.h" + +/* A structure to store the information from the trust file. */ +struct trustitem_s +{ + int keyflag; /* The keyflag: '*', 'P' or 'S'. */ + unsigned char fpr[20]; /* The binary fingerprint. */ +}; +typedef struct trustitem_s trustitem_t; + +/* Malloced table and its allocated size with all trust items. */ +static trustitem_t *trusttable; +static size_t trusttablesize; +/* A mutex used to protect the table. */ +static pth_mutex_t trusttable_lock = PTH_MUTEX_INIT; + + + static const char headerblurb[] = "# This is the list of trusted keys. Comment lines, like this one, as\n" -"# well as empty lines are ignored. The entire file may be integrity\n" -"# protected by the use of a MAC, so changing the file does not make\n" -"# sense without the knowledge of the MAC key. Lines do have a length\n" -"# limit but this is not serious limitation as the format of the\n" -"# entries is fixed and checked by gpg-agent: A non-comment line starts\n" -"# with optional white spaces, followed by the SHA-1 fingerpint in hex,\n" -"# optionally followed by a flag character which my either be 'P', 'S'\n" -"# or '*'. Additional data, delimited by white space, is ignored.\n" -"#\n" -"# NOTE: You should give the gpg-agent a HUP after editing this file.\n" +"# well as empty lines are ignored. Lines have a length limit but this\n" +"# is not serious limitation as the format of the entries is fixed and\n" +"# checked by gpg-agent. A non-comment line starts with optional white\n" +"# space, followed by the SHA-1 fingerpint in hex, optionally followed\n" +"# by a flag character which my either be 'P', 'S' or '*'. You should\n" +"# give the gpg-agent a HUP after editing this file.\n" +"\n\n" +"# Include the default trust list\n" +"include-default\n" "\n"; -static FILE *trustfp; -static int trustfp_used; /* Counter to track usage of TRUSTFP. */ -static int reload_trustlist_pending; - -static int -open_list (int append) + +static void +lock_trusttable (void) { - char *fname; + if (!pth_mutex_acquire (&trusttable_lock, 0, NULL)) + log_fatal ("failed to acquire mutex in %s\n", __FILE__); +} - fname = make_filename (opt.homedir, "trustlist.txt", NULL); - trustfp = fopen (fname, append? "a+":"r"); - if (!trustfp && errno == ENOENT) - { - trustfp = fopen (fname, "wx"); - if (!trustfp) - { - gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); - log_error ("can't create `%s': %s\n", fname, strerror (errno)); - xfree (fname); - return tmperr; - } - fputs (headerblurb, trustfp); - fclose (trustfp); - trustfp = fopen (fname, append? "a+":"r"); - } - - if (!trustfp) - { - gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); - log_error ("can't open `%s': %s\n", fname, strerror (errno)); - xfree (fname); - return tmperr; - } - - /*FIXME: check the MAC */ - - return 0; +static void +unlock_trusttable (void) +{ + if (!pth_mutex_release (&trusttable_lock)) + log_fatal ("failed to release mutex in %s\n", __FILE__); } -/* Read the trustlist and return entry by entry. KEY must point to a - buffer of at least 41 characters. KEYFLAG does return either 'P', - 'S' or '*'. - - Reading a valid entry returns 0, EOF returns -1 any other error - returns the appropriate error code. */ -static int -read_list (char *key, int *keyflag) +static gpg_error_t +read_one_trustfile (const char *fname, int allow_include, + trustitem_t **addr_of_table, + size_t *addr_of_tablesize, + int *addr_of_tableidx) { - int rc; - int c, i, j; + gpg_error_t err = 0; + FILE *fp; + int n, c; char *p, line[256]; - - if (!trustfp) + trustitem_t *table, *ti; + int tableidx; + size_t tablesize; + int lnr = 0; + + table = *addr_of_table; + tablesize = *addr_of_tablesize; + tableidx = *addr_of_tableidx; + + fp = fopen (fname, "r"); + if (!fp) { - rc = open_list (0); - if (rc) - return rc; + err = gpg_error_from_syserror (); + log_error (_("error opening `%s': %s\n"), fname, gpg_strerror (err)); + goto leave; } - do + while (fgets (line, DIM(line)-1, fp)) { - if (!fgets (line, DIM(line)-1, trustfp) ) - { - if (feof (trustfp)) - return -1; - return gpg_error (gpg_err_code_from_errno (errno)); - } + lnr++; if (!*line || line[strlen(line)-1] != '\n') { - /* eat until end of line */ - while ( (c=getc (trustfp)) != EOF && c != '\n') + /* Eat until end of line. */ + while ( (c=getc (fp)) != EOF && c != '\n') ; - return gpg_error (*line? GPG_ERR_LINE_TOO_LONG - : GPG_ERR_INCOMPLETE_LINE); + err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG + : GPG_ERR_INCOMPLETE_LINE); + log_error (_("file `%s', line %d: %s\n"), + fname, lnr, gpg_strerror (err)); + continue; } + line[strlen(line)-1] = 0; /* Chop the LF. */ /* Allow for empty lines and spaces */ for (p=line; spacep (p); p++) ; - } - while (!*p || *p == '\n' || *p == '#'); + if (!*p || *p == '#') + continue; - 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 trustlist\n"); - return gpg_error (GPG_ERR_BAD_DATA); - } - assert (p[i]); - if (p[i] == '\n') - *keyflag = '*'; - else - { - i++; - if ( p[i] == 'P' || p[i] == 'p') - *keyflag = 'P'; - else if ( p[i] == 'S' || p[i] == 's') - *keyflag = 'S'; - else if ( p[i] == '*') - *keyflag = '*'; + if (!strncmp (p, "include-default", 15) + && (!p[15] || spacep (p+15))) + { + char *etcname; + gpg_error_t err2; + + if (!allow_include) + { + log_error (_("statement \"%s\" ignored in `%s', line %d\n"), + "include-default", fname, lnr); + continue; + } + /* fixme: Should check for trailing garbage. */ + + etcname = make_filename (GNUPG_SYSCONFDIR, "trustlist.txt", NULL); + if ( !strcmp (etcname, fname) ) /* Same file. */ + log_info (_("statement \"%s\" ignored in `%s', line %d\n"), + "include-default", fname, lnr); + else if ( access (etcname, F_OK) && errno == ENOENT ) + { + /* A non existent system trustlist is not an error. + Just print a note. */ + log_info (_("system trustlist `%s' not available\n"), etcname); + } + else + { + err2 = read_one_trustfile (etcname, 0, + &table, &tablesize, &tableidx); + if (err2) + err = err2; + } + xfree (etcname); + + continue; + } + + if (tableidx == tablesize) /* Need more space. */ + { + trustitem_t *tmp; + size_t tmplen; + + tmplen = tablesize + 20; + tmp = xtryrealloc (table, tmplen * sizeof *table); + if (!tmp) + { + err = gpg_error_from_syserror (); + goto leave; + } + table = tmp; + tablesize = tmplen; + } + + ti = table + tableidx; + + n = hexcolon2bin (p, ti->fpr, 20); + if (n < 0) + { + log_error (_("bad fingerprint in `%s', line %d\n"), fname, lnr); + err = gpg_error (GPG_ERR_BAD_DATA); + continue; + } + p += n; + for (; spacep (p); p++) + ; + + if (!*p) + ti->keyflag = '*'; + else if ( *p == 'P' || *p == 'p') + ti->keyflag = 'P'; + else if ( *p == 'S' || *p == 's') + ti->keyflag = 'S'; + else if ( *p == '*') + ti->keyflag = '*'; else { - log_error ("invalid keyflag in trustlist\n"); - return gpg_error (GPG_ERR_BAD_DATA); + log_error (_("invalid keyflag in `%s', line %d\n"), fname, lnr); + err = gpg_error (GPG_ERR_BAD_DATA); + continue; } - i++; - if ( !(spacep (p+i) || p[i] == '\n')) + p++; + if ( *p && !spacep (p) ) { - log_error ("invalid keyflag in trustlist\n"); - return gpg_error (GPG_ERR_BAD_DATA); + log_error (_("invalid keyflag in `%s', line %d\n"), fname, lnr); + err = gpg_error (GPG_ERR_BAD_DATA); + continue; } + /* Fixme: need to check for trailing garbage. */ + tableidx++; + } + if ( !err && !feof (fp) ) + { + err = gpg_error_from_syserror (); + log_error (_("error reading `%s', line %d: %s\n"), + fname, lnr, gpg_strerror (err)); } + leave: + if (fp) + fclose (fp); + *addr_of_table = table; + *addr_of_tablesize = tablesize; + *addr_of_tableidx = tableidx; + return err; +} + + +/* Read the trust files and update the global table on success. */ +static gpg_error_t +read_trustfiles (void) +{ + gpg_error_t err; + trustitem_t *table, *ti; + int tableidx; + size_t tablesize; + char *fname; + int allow_include = 1; + + tablesize = 10; + table = xtrycalloc (tablesize, sizeof *table); + if (!table) + return gpg_error_from_syserror (); + tableidx = 0; + + fname = make_filename (opt.homedir, "trustlist.txt", NULL); + if ( access (fname, F_OK) ) + { + if ( errno == ENOENT ) + ; /* Silently ignore a non-existing trustfile. */ + else + { + err = gpg_error_from_syserror (); + log_error (_("error opening `%s': %s\n"), fname, gpg_strerror (err)); + } + xfree (fname); + fname = make_filename (GNUPG_SYSCONFDIR, "trustlist.txt", NULL); + allow_include = 0; + } + err = read_one_trustfile (fname, allow_include, + &table, &tablesize, &tableidx); + xfree (fname); + + if (err) + { + xfree (table); + return err; + } + + /* Fixme: we should drop duplicates and sort the table. */ + + ti = xtryrealloc (table, tableidx * sizeof *table); + if (!ti) + { + xfree (table); + return err; + } + + lock_trusttable (); + xfree (trusttable); + trusttable = table; + trusttablesize = tableidx; + unlock_trusttable (); return 0; } + + /* Check whether the given fpr is in our trustdb. We expect FPR to be an all uppercase hexstring of 40 characters. */ -int +gpg_error_t agent_istrusted (const char *fpr) { - int rc; - static char key[41]; - int keyflag; + gpg_error_t err; + trustitem_t *ti; + size_t len; + unsigned char fprbin[20]; - trustfp_used++; - if (trustfp) - rewind (trustfp); - while (!(rc=read_list (key, &keyflag))) + if ( hexcolon2bin (fpr, fprbin, 20) < 0 ) + return gpg_error (GPG_ERR_INV_VALUE); + + if (!trusttable) { - if (!strcmp (key, fpr)) + err = read_trustfiles (); + if (err) { - trustfp_used--; - return 0; + log_error (_("error reading list of trusted root certificates\n")); + return err; } } - if (rc != -1) + + if (trusttable) { - /* Error in the trustdb - close it to give the user a chance for - correction */ - if (trustfp) - fclose (trustfp); - trustfp = NULL; + for (ti=trusttable, len = trusttablesize; len; ti++, len--) + if (!memcmp (ti->fpr, fprbin, 20)) + return 0; /* Trusted. */ } - trustfp_used--; - return rc; + return gpg_error (GPG_ERR_NOT_TRUSTED); } /* Write all trust entries to FP. */ -int +gpg_error_t agent_listtrusted (void *assuan_context) { - int rc; - static char key[51]; - int keyflag; + trustitem_t *ti; + char key[51]; + gpg_error_t err; + size_t len; - trustfp_used++; - if (trustfp) - rewind (trustfp); - while (!(rc=read_list (key, &keyflag))) + if (!trusttable) { - key[40] = ' '; - key[41] = keyflag; - key[42] = '\n'; - assuan_send_data (assuan_context, key, 43); - assuan_send_data (assuan_context, NULL, 0); /* flush */ - } - if (rc == -1) - rc = 0; - if (rc) - { - /* Error in the trustdb - close it to give the user a chance for - correction */ - if (trustfp) - fclose (trustfp); - trustfp = NULL; + err = read_trustfiles (); + if (err) + { + log_error (_("error reading list of trusted root certificates\n")); + return err; + } } - trustfp_used--; - return rc; + + if (trusttable) + { + /* We need to lock the table because the scheduler may interrupt + assuan_send_data and an other thread may then re-read the table. */ + lock_trusttable (); + for (ti=trusttable, len = trusttablesize; len; ti++, len--) + { + bin2hex (ti->fpr, 20, key); + key[40] = ' '; + key[41] = ti->keyflag; + key[42] = '\n'; + assuan_send_data (assuan_context, key, 43); + assuan_send_data (assuan_context, NULL, 0); /* flush */ + } + unlock_trusttable (); + } + + return 0; } @@ -245,52 +379,36 @@ agent_listtrusted (void *assuan_context) actually gets inserted, the user is asked by means of the pin-entry whether this is actual wants he want to do. */ -int +gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) { - int rc; - static char key[41]; - int keyflag; + gpg_error_t err = 0; char *desc; char *fname; + FILE *fp; /* Check whether we are at all allowed to modify the trustlist. This is useful so that the trustlist may be a symlink to a global trustlist with only admin priviliges to modify it. Of course this is not a secure way of denying access, but it avoids the - usual clicking on an Okay buttun thing most users are used to. */ + usual clicking on an Okay button most users are used to. */ fname = make_filename (opt.homedir, "trustlist.txt", NULL); - rc = access (fname, W_OK); - if (rc && errno != ENOENT) + if ( access (fname, W_OK) && errno != ENOENT) { xfree (fname); return gpg_error (GPG_ERR_EPERM); } xfree (fname); - trustfp_used++; - if (trustfp) - rewind (trustfp); - while (!(rc=read_list (key, &keyflag))) + if (!agent_istrusted (fpr)) { - if (!strcmp (key, fpr)) - return 0; - } - if (trustfp) - fclose (trustfp); - trustfp = NULL; - if (rc != -1) - { - trustfp_used--; - return rc; /* Error in the trustlist. */ + return 0; /* We already got this fingerprint. Silently return + success. */ } /* This feature must explicitly been enabled. */ if (!opt.allow_mark_trusted) - { - trustfp_used--; - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } + return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Insert a new one. */ if (asprintf (&desc, @@ -307,21 +425,15 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) " \"%s\"%%0A" "has the fingerprint:%%0A" " %s"), name, fpr) < 0 ) - { - trustfp_used--; - return out_of_core (); - } + return out_of_core (); /* TRANSLATORS: "Correct" is the label of a button and intended to be hit if the fingerprint matches the one of the CA. The other button is "the default "Cancel" of the Pinentry. */ - rc = agent_get_confirmation (ctrl, desc, _("Correct"), NULL); + err = agent_get_confirmation (ctrl, desc, _("Correct"), NULL); free (desc); - if (rc) - { - trustfp_used--; - return rc; - } + if (err) + return err; if (asprintf (&desc, /* TRANSLATORS: This prompt is shown by the Pinentry @@ -336,83 +448,78 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) " \"%s\"%%0A" "to correctly certify user certificates?"), name) < 0 ) - { - trustfp_used--; - return out_of_core (); - } - rc = agent_get_confirmation (ctrl, desc, _("Yes"), _("No")); + return out_of_core (); + + err = agent_get_confirmation (ctrl, desc, _("Yes"), _("No")); free (desc); - if (rc) + if (err) + return err; + + /* Now check again to avoid duplicates. We take the lock to make + sure that nobody else plays with our file. Frankly we don't work + with the trusttable but using this lock is just fine for our + purpose. */ + lock_trusttable (); + if (!agent_istrusted (fpr)) { - trustfp_used--; - return rc; + unlock_trusttable (); + return 0; } - /* Now check again to avoid duplicates. Also open in append mode now. */ - rc = open_list (1); - if (rc) + + fname = make_filename (opt.homedir, "trustlist.txt", NULL); + if ( access (fname, F_OK) && errno == ENOENT) { - trustfp_used--; - return rc; - } - rewind (trustfp); - while (!(rc=read_list (key, &keyflag))) - { - if (!strcmp (key, fpr)) + fp = fopen (fname, "wx"); /* Warning: "x" is a GNU extension. */ + if (!fp) { - trustfp_used--; - return 0; + err = gpg_error_from_syserror (); + log_error ("can't create `%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + unlock_trusttable (); + return err; } + fputs (headerblurb, fp); + fclose (fp); } - if (rc != -1) + fp = fopen (fname, "a+"); + if (!fp) { - if (trustfp) - fclose (trustfp); - trustfp = NULL; - trustfp_used--; - return rc; /* Error in the trustlist. */ + err = gpg_error_from_syserror (); + log_error ("can't open `%s': %s\n", fname, gpg_strerror (err)); + xfree (fname); + unlock_trusttable (); + return err; } - rc = 0; /* Append the key. */ - fflush (trustfp); - fputs ("\n# ", trustfp); - print_sanitized_string (trustfp, name, 0); - fprintf (trustfp, "\n%s %c\n", fpr, flag); - if (ferror (trustfp)) - rc = gpg_error (gpg_err_code_from_errno (errno)); + fputs ("\n# ", fp); + print_sanitized_string (fp, name, 0); + fprintf (fp, "\n%s %c\n", fpr, flag); + if (ferror (fp)) + err = gpg_error_from_syserror (); - /* close because we are in append mode */ - if (fclose (trustfp)) - rc = gpg_error (gpg_err_code_from_errno (errno)); - trustfp = NULL; - trustfp_used--; - return rc; + if (fclose (fp)) + err = gpg_error_from_syserror (); + + if (!err) + agent_reload_trustlist (); + xfree (fname); + unlock_trusttable (); + return err; } -void -agent_trustlist_housekeeping (void) -{ - if (reload_trustlist_pending && !trustfp_used) - { - if (trustfp) - { - fclose (trustfp); - trustfp = NULL; - } - reload_trustlist_pending = 0; - } -} - - -/* Not all editors are editing files in place, thus a changes - trustlist.txt won't be recognozed if we keep the file descriptor - open. This function may be used to explicitly close that file - descriptor, which will force a reopen in turn. */ +/* This function may be called to force reloading of the + trustlist. */ void agent_reload_trustlist (void) { - reload_trustlist_pending = 1; - agent_trustlist_housekeeping (); + /* All we need to do is to delete the trusttable. At the next + access it will get re-read. */ + lock_trusttable (); + xfree (trusttable); + trusttable = NULL; + trusttablesize = 0; + unlock_trusttable (); } diff --git a/autogen.sh b/autogen.sh index 29e19ea14..503ddc0ee 100755 --- a/autogen.sh +++ b/autogen.sh @@ -153,4 +153,4 @@ $AUTOMAKE --gnu; echo "Running autoconf..." $AUTOCONF -echo "You may now run \"./configure --enable-maintainer-mode && make\"." +echo "You may now run \"./configure --sysconfdir=/etc --enable-maintainer-mode && make\"." diff --git a/common/ChangeLog b/common/ChangeLog index e24250c17..8be1ce5c2 100644 --- a/common/ChangeLog +++ b/common/ChangeLog @@ -1,3 +1,10 @@ +2006-09-15 Werner Koch + + * convert.c: New. + (hexcolon2bin): New. + (bin2hex, bin2hexcolon, do_binhex): New. + * t-convert.c: New + 2006-09-14 Werner Koch * util.h (out_of_core): Use new gpg_error_from_syserror function. diff --git a/common/Makefile.am b/common/Makefile.am index d6143de67..28d396490 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -21,6 +21,8 @@ ## Process this file with automake to produce Makefile.in noinst_LIBRARIES = libcommon.a libsimple-pwquery.a +noinst_PROGRAMS = $(module_tests) +TESTS = $(module_tests) AM_CPPFLAGS = -I$(top_srcdir)/gl @@ -39,6 +41,7 @@ libcommon_a_SOURCES = \ gettime.c \ yesno.c \ b64enc.c \ + convert.c \ miscellaneous.c \ xasprintf.c \ xreadline.c \ @@ -60,3 +63,15 @@ libcommon_a_SOURCES = \ libsimple_pwquery_a_SOURCES = \ simple-pwquery.c simple-pwquery.h asshelp.c asshelp.h + +# +# Module tests +# +module_tests = t-convert + +t_common_ldadd = ../jnlib/libjnlib.a ../common/libcommon.a ../gl/libgnu.a \ + $(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) + +t_convert_DEPENDENCIES = convert.c +t_convert_LDADD = $(t_common_ldadd) + diff --git a/common/convert.c b/common/convert.c new file mode 100644 index 000000000..66f612063 --- /dev/null +++ b/common/convert.c @@ -0,0 +1,136 @@ +/* convert.c - Hex conversion functions. + * Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include +#include + +#include "util.h" + + +#define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A')) + + +/* Convert STRING consisting of hex characters into its binary representation + and store that at BUFFER. BUFFER needs to be of LENGTH bytes. The + function check that the STRING will convert exactly to LENGTH + bytes. Colons inbetween the hex digits are allowed, if one colon + has been given a colon is expected very 2 characters. The string + is delimited by either end of string or a white space character. + The function returns -1 on error or the length of the parsed + string. */ +int +hexcolon2bin (const char *string, void *buffer, size_t length) +{ + int i; + const char *s = string; + int need_colon = 0; + + for (i=0; i < length; ) + { + if (i==1 && *s == ':') /* Skip colons between hex digits. */ + { + need_colon = 1; + s++; + } + else if (need_colon && *s == ':') + s++; + else if (need_colon) + return -1; /* Colon expected. */ + if (!hexdigitp (s) || !hexdigitp (s+1)) + return -1; /* Invalid hex digits. */ + ((unsigned char*)buffer)[i++] = xtoi_2 (s); + s += 2; + } + if (*s == ':') + return -1; /* Trailing colons are not allowed. */ + if (*s && (!isascii (*s) || !isspace (*s)) ) + return -1; /* Not followed by Nul or white space. */ + if (i != length) + return -1; /* Not of expected length. */ + if (*s) + s++; /* Skip the delimiter. */ + return s - string; +} + + + +static char * +do_bin2hex (const void *buffer, size_t length, char *stringbuf, int with_colon) +{ + const unsigned char *s; + char *p; + + if (!stringbuf) + { + /* Not really correct for with_colon but we don't care about the + one wasted byte. */ + size_t n = with_colon? 3:2; + size_t nbytes = n * length + 1; + if (length && (nbytes-1) / n != length) + { + errno = ENOMEM; + return NULL; + } + stringbuf = xtrymalloc (nbytes); + if (!stringbuf) + return NULL; + } + + for (s = buffer, p = stringbuf; length; length--, s++) + { + if (with_colon && s != buffer) + *p++ = ':'; + *p++ = tohex ((*s>>4)&15); + *p++ = tohex (*s&15); + } + *p = 0; + + return stringbuf; +} + + +/* Convert LENGTH bytes of data in BUFFER into hex encoding and store + that at the provided STRINGBUF. STRINGBUF must be allocated of at + least (2*LENGTH+1) bytes or be NULL so that the function mallocs an + appropriate buffer. Returns STRINGBUF or NULL on error (which may + only occur if STRINGBUF has been NULL and the internal malloc + failed). */ +char * +bin2hex (const void *buffer, size_t length, char *stringbuf) +{ + return do_bin2hex (buffer, length, stringbuf, 0); +} + +/* Convert LENGTH bytes of data in BUFFER into hex encoding and store + that at the provided STRINGBUF. STRINGBUF must be allocated of at + least (3*LENGTH+1) bytes or be NULL so that the function mallocs an + appropriate buffer. Returns STRINGBUF or NULL on error (which may + only occur if STRINGBUF has been NULL and the internal malloc + failed). */ +char * +bin2hexcolon (const void *buffer, size_t length, char *stringbuf) +{ + return do_bin2hex (buffer, length, stringbuf, 1); +} + + diff --git a/common/t-convert.c b/common/t-convert.c new file mode 100644 index 000000000..52647b085 --- /dev/null +++ b/common/t-convert.c @@ -0,0 +1,204 @@ +/* t-convert.c - Module test for convert.c + * Copyright (C) 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include +#include +#include + +#include "util.h" + +#define pass() do { ; } while(0) +#define fail(a) do { fprintf (stderr, "%s:%d: test %d failed\n",\ + __FILE__,__LINE__, (a)); \ + exit (1); \ + } while(0) + + +static void +test_hexcolon2bin (void) +{ + static const char *valid[] = { + "00112233445566778899aabbccddeeff11223344", + "00112233445566778899AABBCCDDEEFF11223344", + "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44", + "00112233445566778899AABBCCDDEEFF11223344 blah", + "00112233445566778899AABBCCDDEEFF11223344\tblah", + "00112233445566778899AABBCCDDEEFF11223344\nblah", + NULL + }; + static const char *invalid[] = { + "00112233445566778899aabbccddeeff1122334", + "00112233445566778899AABBCCDDEEFF1122334", + "00112233445566778899AABBCCDDEEFG11223344", + ":00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44", + "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44:", + "00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:3344", + "00:1122:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44", + "0011:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44", + "00 11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44", + "00:11 22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:11:22:33:44", + "00112233445566778899aabbccddeeff112233445", + "00112233445566778899aabbccddeeff1122334455", + "00112233445566778899aabbccddeeff11223344blah", + NULL + }; + static const char *valid2[] = { + "00", + "00 x", + NULL + }; + static const char *invalid2[] = { + "", + "0", + "00:", + ":00", + "0:0", + "00x", + " 00", + NULL + }; + unsigned char buffer[20]; + int len; + int i; + + + for (i=0; valid[i]; i++) + { + len = hexcolon2bin (valid[i], buffer, sizeof buffer); + if (len < 0) + fail (i); + if (memcmp (buffer, ("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa" + "\xbb\xcc\xdd\xee\xff\x11\x22\x33\x44"), 20)) + fail (i); + } + if (hexcolon2bin (valid[0], buffer, sizeof buffer) != 40) + fail (0); + if (hexcolon2bin (valid[3], buffer, sizeof buffer) != 41) + fail (0); + + for (i=0; invalid[i]; i++) + { + len = hexcolon2bin (invalid[i], buffer, sizeof buffer); + if (!(len < 0)) + fail (i); + } + + for (i=0; valid2[i]; i++) + { + len = hexcolon2bin (valid2[i], buffer, 1); + if (len < 0) + fail (i); + if (memcmp (buffer, "\x00", 1)) + fail (i); + } + if (hexcolon2bin (valid2[0], buffer, 1) != 2) + fail (0); + if (hexcolon2bin (valid2[1], buffer, 1) != 3) + fail (0); + + for (i=0; invalid2[i]; i++) + { + len = hexcolon2bin (invalid2[i], buffer, 1); + if (!(len < 0)) + fail (i); + } + + +} + + + +static void +test_bin2hex (void) +{ + char stuff[20+1] = ("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa" + "\xbb\xcc\xdd\xee\xff\x01\x10\x02\xa3"); + char hexstuff[] = "00112233445566778899AABBCCDDEEFF011002A3"; + char buffer[2*20+1]; + char *p; + + p = bin2hex (stuff, 20, buffer); + if (!p) + fail (0); + if (p != buffer) + fail (0); + if (strcmp (buffer, hexstuff)) + fail (0); + + p = bin2hex (stuff, 20, NULL); + if (!p) + fail (0); + if (strcmp (p, hexstuff)) + fail (0); + + p = bin2hex (stuff, (size_t)(-1), NULL); + if (p) + fail (0); + if (errno != ENOMEM) + fail (1); +} + + +static void +test_bin2hexcolon (void) +{ + char stuff[20+1] = ("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa" + "\xbb\xcc\xdd\xee\xff\x01\x10\x02\xa3"); + char hexstuff[] = ("00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF" + ":01:10:02:A3"); + char buffer[3*20+1]; + char *p; + + p = bin2hexcolon (stuff, 20, buffer); + if (!p) + fail (0); + if (p != buffer) + fail (0); + if (strcmp (buffer, hexstuff)) + fail (0); + + p = bin2hexcolon (stuff, 20, NULL); + if (!p) + fail (0); + if (strcmp (p, hexstuff)) + fail (0); + + p = bin2hexcolon (stuff, (size_t)(-1), NULL); + if (p) + fail (0); + if (errno != ENOMEM) + fail (1); +} + + + + +int +main (int argc, char **argv) +{ + + test_hexcolon2bin (); + test_bin2hex (); + test_bin2hexcolon (); + + return 0; +} + diff --git a/common/util.h b/common/util.h index 92b88aa8d..da1e098cb 100644 --- a/common/util.h +++ b/common/util.h @@ -144,6 +144,12 @@ int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b); unsigned char *make_simple_sexp_from_hexstr (const char *line, size_t *nscanned); +/*-- convert.c --*/ +int hexcolon2bin (const char *string, void *buffer, size_t length); +char *bin2hex (const void *buffer, size_t length, char *stringbuf); +char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf); + + /*-- homedir.c --*/ const char *default_homedir (void); diff --git a/doc/Makefile.am b/doc/Makefile.am index ec40202e0..57231190c 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -19,7 +19,7 @@ ## Process this file with automake to produce Makefile.in -examples=examples/scd-event +examples = examples/README examples/scd-event examples/trustlist.txt EXTRA_DIST = DETAILS HACKING TRANSLATE OpenPGP KEYSERVER samplekeys.asc \ gnupg-badge-openpgp.eps gnupg-badge-openpgp.jpg \ diff --git a/doc/examples/README b/doc/examples/README new file mode 100644 index 000000000..341dda88a --- /dev/null +++ b/doc/examples/README @@ -0,0 +1,9 @@ +Files in this directory: + + +scd-event A handler script used with scdaemon + +trustlist.txt A list of trustworthy root certificates + (Please check yourself whether you actually trust them) + + diff --git a/doc/examples/trustlist.txt b/doc/examples/trustlist.txt new file mode 100644 index 000000000..1fcae4106 --- /dev/null +++ b/doc/examples/trustlist.txt @@ -0,0 +1,46 @@ +# This is the global list of trusted keys. Comment lines, like this +# one, as well as empty lines are ignored. Lines have a length limit +# but this is not serious limitation as the format of the entries is +# fixed and checked by gpg-agent. A non-comment line starts with +# optional white space, followed by the SHA-1 fingerpint in hex, +# optionally followed by a flag character which my either be 'P', 'S' +# or '*'. This file will be read by gpg-agent if no local trustlist +# is available or if the statement "include-default" is used in the +# local list. You should give the gpg-agent(s) a HUP after editing +# this file. + + +#Serial number: 32D18D +# Issuer: /CN=6R-Ca 1:PN/NameDistinguisher=1/O=RegulierungsbehÈorde +# fÈur Telekommunikation und Post/C=DE +EA:8D:99:DD:36:AA:2D:07:1A:3C:7B:69:00:9E:51:B9:4A:2E:E7:60 S + +#Serial number: 00C48C8D +# Issuer: /CN=7R-CA 1:PN/NameDistinguisher=1/O=RegulierungsbehÈorde +# fÈur Telekommunikation und Post/C=DE +DB:45:3D:1B:B0:1A:F3:23:10:6B:DE:D0:09:61:57:AA:F4:25:E0:5B S + +#Serial number: 01 +# Issuer: /CN=8R-CA 1:PN/O=Regulierungsbehörde für +# Telekommunikation und Post/C=DE +42:6A:F6:78:30:E9:CE:24:5B:EF:41:A2:C1:A8:51:DA:C5:0A:6D:F5 S + +#Serial number: 02 +# Issuer: /CN=9R-CA 1:PN/O=Regulierungsbehörde für +# Telekommunikation und Post/C=DE +75:9A:4A:CE:7C:DA:7E:89:1B:B2:72:4B:E3:76:EA:47:3A:96:97:24 S + +#Serial number: 2A +# Issuer: /CN=10R-CA 1:PN/O=Bundesnetzagentur/C=DE +31:C9:D2:E6:31:4D:0B:CC:2C:1A:45:00:A6:6B:97:98:27:18:8E:CD S + +#Serial number: 2D +# Issuer: /CN=11R-CA 1:PN/O=Bundesnetzagentur/C=DE +A0:8B:DF:3B:AA:EE:3F:9D:64:6C:47:81:23:21:D4:A6:18:81:67:1D S + +#Serial number: 00 +# Issuer: /CN=CA Cert Signing Authority/OU=http:\x2f\x2fwww. +# cacert.org/O=Root CA/EMail=support@cacert.org +13:5C:EC:36:F4:9C:B8:E9:3B:1A:B2:70:CD:80:88:46:76:CE:8F:33 S + + diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index c9a89b91a..54ffb2a73 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -467,17 +467,22 @@ agent. By default they may all be found in the current home directory DC:BD:69:25:48:BD:BB:7E:31:6E:BB:80:D3:00:80:35:D4:F8:A6:CD S @end example - Before entering a key into this file, you need to ensure its - authenticity. How to do this depends on your organisation; your - administrator might have already entered those keys which are deemed - trustworthy enough into this file. Places where to look for the - fingerprint of a root certificate are letters received from the CA or - the website of the CA (after making 100% sure that this is indeed the - website of that CA). You may want to consider allowing interactive - updates of this file by using the @xref{option --allow-mark-trusted}. - This is however not as secure as maintaining this file manually. It is - even advisable to change the permissions to read-only so that this file - can't be changed inadvertently. +Before entering a key into this file, you need to ensure its +authenticity. How to do this depends on your organisation; your +administrator might have already entered those keys which are deemed +trustworthy enough into this file. Places where to look for the +fingerprint of a root certificate are letters received from the CA or +the website of the CA (after making 100% sure that this is indeed the +website of that CA). You may want to consider allowing interactive +updates of this file by using the @xref{option --allow-mark-trusted}. +This is however not as secure as maintaining this file manually. It is +even advisable to change the permissions to read-only so that this file +can't be changed inadvertently. + +As a special feature a line @code{include-default} will include a global +list of trusted certificates (e.g. @file{/etc/gnupg/trustlist.txt}). +This global list is also used if the local list ios not available. + @item sshcontrol diff --git a/scd/ChangeLog b/scd/ChangeLog index 4b133c9e8..b6516527a 100644 --- a/scd/ChangeLog +++ b/scd/ChangeLog @@ -52,7 +52,7 @@ * pcsc-wrapper.c (handle_open, handle_close): Reset card and protocol on error/close. - (handle_status): Don't set the stae if the state is unknown. + (handle_status): Don't set the state if the state is unknown. (handle_reset): Ignore an error if already disconnected. May happen due to system wake-up after hibernation. Suggested by Bob Dunlop.