Implemented PKA trust model
authorWerner Koch <wk@gnupg.org>
Thu, 28 Jul 2005 18:59:36 +0000 (18:59 +0000)
committerWerner Koch <wk@gnupg.org>
Thu, 28 Jul 2005 18:59:36 +0000 (18:59 +0000)
19 files changed:
ChangeLog
NEWS
configure.ac
g10/ChangeLog
g10/Makefile.am
g10/free-packet.c
g10/gpgv.c
g10/keygen.c
g10/main.h
g10/mainproc.c
g10/misc.c
g10/packet.h
g10/parse-packet.c
g10/pkclist.c
include/util.h
util/ChangeLog
util/Makefile.am
util/pka.c [new file with mode: 0644]
util/srv.c

index 413d3bc..c646465 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2005-07-28  Werner Koch  <wk@g10code.com>
+
+       * configure.ac (USE_DNS_PKA): Define in addition to USE_DNS_SRV.
+
 2005-07-27  Werner Koch  <wk@g10code.com>
 
        Replaced in all directories all calls to m_free, m_alloc,
diff --git a/NEWS b/NEWS
index d2fa901..ac6a483 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,12 @@ Noteworthy changes in version 1.4.3
       --enable-old-keyserver-helpers.  Note that none of this affects
       finger or LDAP support, which are unchanged.
 
+    * Implemented Public Key Association (PKA) trust model option.
+      This is an optional trust model on top of the standard ones.  It
+      make use of of special DNS records and notation data to
+      associate a mail address with an OpenPGP key.  See: XXXX for a
+      description.
+
 
 Noteworthy changes in version 1.4.2 (2005-07-26)
 ------------------------------------------------
index 1079838..33a9e09 100644 (file)
@@ -563,7 +563,8 @@ AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname,
 AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt,
        [NETLIBS="-lsocket $NETLIBS"]))
 
-dnl Now try for the resolver functions so we can use DNS SRV
+dnl Now try for the resolver functions so we can use DNS SRV and our 
+dnl PKA feature.
 
 AC_ARG_ENABLE(dns-srv,
    AC_HELP_STRING([--disable-dns-srv],
@@ -597,6 +598,7 @@ if (test x"$try_hkp" = xyes || test x"$try_http" = xyes) && test x"$use_dns_srv"
   if test x"$use_dns_srv" = xyes ; then
      AC_DEFINE(USE_DNS_SRV,1,[define to use DNS SRV])
      SRVLIBS=$LIBS
+     AC_DEFINE(USE_DNS_PKA,1,[define to use our experimental DNS PKA])
   fi
 
   LIBS=$_srv_save_libs
index 9c1acbc..f9fab2b 100644 (file)
@@ -1,3 +1,23 @@
+2005-07-28  Werner Koch  <wk@g10code.com>
+
+       * Makefile.am (other_libs): Add SRVLIBS.
+
+       * parse-packet.c (can_handle_critical_notation): We know about
+       pka-address@gnupg.org.
+       * packet.h (PKT_signature): New fields PKA_INFO and PKA_TRIED. 
+       (pka_info_t): New.
+       * free-packet.c (cp_pka_info): New.
+       (free_seckey_enc, copy_signature): Support new fields.
+       * mainproc.c (get_pka_address, pka_uri_from_sig): New.
+       (check_sig_and_print): Try to get the keyserver from the PKA
+       record.
+       * pkclist.c (check_signatures_trust): Adjust the trust based on
+       the PKA.
+       * gpgv.c (parse_keyserver_uri): New stub.
+
+       * keygen.c (has_invalid_email_chars): Moved to ..
+       * misc.c (has_invalid_email_chars): .. here and made global.
+
 2005-07-27  Werner Koch  <wk@g10code.com>
 
        * export.c (do_export_stream): Make two strings translatable.
index 9e50eeb..3c1ba7a 100644 (file)
@@ -28,7 +28,7 @@ if ! HAVE_DOSISH_SYSTEM
 AM_CFLAGS = -DGNUPG_LIBEXECDIR="\"$(libexecdir)/@PACKAGE@\""
 endif
 needed_libs = ../cipher/libcipher.a ../mpi/libmpi.a ../util/libutil.a
-other_libs = $(LIBICONV) $(LIBINTL) $(CAPLIBS)
+other_libs = $(LIBICONV) $(SRVLIBS) $(LIBINTL) $(CAPLIBS)
 
 bin_PROGRAMS = gpg gpgv
 
index 3ede4db..01ab543 100644 (file)
@@ -1,6 +1,6 @@
 /* free-packet.c - cleanup stuff for packets
- * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003
- *                                             Free Software Foundation, Inc.
+ * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
+ *               2005  Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -62,10 +62,17 @@ free_seckey_enc( PKT_signature *sig )
     mpi_free(sig->data[0]);
   for(i=0; i < n; i++ )
     mpi_free( sig->data[i] );
-  
+
   xfree(sig->revkey);
   xfree(sig->hashed);
   xfree(sig->unhashed);
+
+  if (sig->pka_info)
+    {
+      xfree (sig->pka_info->uri);
+      xfree (sig->pka_info);
+    }
+
   xfree(sig);
 }
 
@@ -195,6 +202,21 @@ copy_public_parts_to_secret_key( PKT_public_key *pk, PKT_secret_key *sk )
     sk->keyid[1]    = pk->keyid[1];
 }
 
+
+static pka_info_t *
+cp_pka_info (const pka_info_t *s)
+{
+  pka_info_t *d = xmalloc (sizeof *s + strlen (s->email));
+  
+  d->valid = s->valid;
+  d->checked = s->checked;
+  d->uri = s->uri? xstrdup (s->uri):NULL;
+  memcpy (d->fpr, s->fpr, sizeof s->fpr);
+  strcpy (d->email, s->email);
+  return d;
+}
+
+
 PKT_signature *
 copy_signature( PKT_signature *d, PKT_signature *s )
 {
@@ -210,6 +232,7 @@ copy_signature( PKT_signature *d, PKT_signature *s )
        for(i=0; i < n; i++ )
            d->data[i] = mpi_copy( s->data[i] );
     }
+    d->pka_info = s->pka_info? cp_pka_info (s->pka_info) : NULL;
     d->hashed = cp_subpktarea (s->hashed);
     d->unhashed = cp_subpktarea (s->unhashed);
     if(s->numrevkeys)
index 2921107..1f26876 100644 (file)
@@ -333,6 +333,13 @@ passphrase_to_dek( u32 *keyid, int pubkey_algo,
 }
 
 struct keyserver_spec *parse_preferred_keyserver(PKT_signature *sig) {return NULL;}
+struct keyserver_spec *parse_keyserver_uri(const char *uri,int require_scheme,
+                                           const char *configname,
+                                           unsigned int configlineno)
+{
+  return NULL;
+}
+
 void free_keyserver_spec(struct keyserver_spec *keyserver) {}
 
 /* Stubs to avoid linking to photoid.c */
index ca20ed8..a657c13 100644 (file)
@@ -1609,27 +1609,6 @@ ask_expiredate()
     return x? make_timestamp() + x : 0;
 }
 
-static int
-has_invalid_email_chars( const char *s )
-{
-    int at_seen=0;
-    static char valid_chars[] = "01234567890_-."
-                               "abcdefghijklmnopqrstuvwxyz"
-                               "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
-
-    for( ; *s; s++ ) {
-       if( *s & 0x80 )
-           return 1;
-       if( *s == '@' )
-           at_seen=1;
-       else if( !at_seen && !( !!strchr( valid_chars, *s ) || *s == '+' ) )
-           return 1;
-       else if( at_seen && !strchr( valid_chars, *s ) )
-           return 1;
-    }
-    return 0;
-}
-
 
 static char *
 ask_user_id( int mode )
index 8ffefbb..787b58a 100644 (file)
@@ -125,6 +125,7 @@ char *argsplit(char *string);
 int parse_options(char *str,unsigned int *options,
                  struct parse_options *opts,int noisy);
 char *unescape_percent_string (const unsigned char *s);
+int has_invalid_email_chars (const char *s);
 char *default_homedir (void);
 const char *get_libexecdir (void);
 
index 5913f93..afd347c 100644 (file)
@@ -1296,6 +1296,86 @@ do_proc_packets( CTX c, IOBUF a )
 }
 
 
+/* Helper for pka_uri_from_sig to parse the to-be-verified address out
+   of the notation data. */
+static pka_info_t *
+get_pka_address (PKT_signature *sig)
+{
+  const unsigned char *p;
+  size_t len, n1, n2;
+  int seq = 0;
+  pka_info_t *pka = NULL;
+
+  while ((p = enum_sig_subpkt (sig->hashed, SIGSUBPKT_NOTATION,
+                               &len, &seq, NULL)))
+    {
+      if (len < 8)
+        continue; /* Notation packet is too short. */
+      n1 = (p[4]<<8)|p[5];
+      n2 = (p[6]<<8)|p[7];
+      if (8 + n1 + n2 != len)
+        continue; /* Length fields of notation packet are inconsistent. */
+      p += 8;
+      if (n1 != 21 || memcmp (p, "pka-address@gnupg.org", 21))
+        continue; /* Not the notation we want. */
+      p += n1;
+      if (n2 < 3)
+        continue; /* Impossible email address. */
+
+      if (pka)
+        break; /* For now we only use the first valid PKA notation. In
+                  future we might want to keep additional PKA
+                  notations in a linked list. */
+
+      pka = xmalloc (sizeof *pka + n2);
+      pka->valid = 0;
+      pka->checked = 0;
+      pka->uri = NULL;
+      memcpy (pka->email, p, n2);
+      pka->email[n2] = 0;
+
+      if (has_invalid_email_chars (pka->email))
+        {
+          /* We don't accept invalid mail addresses. */
+          xfree (pka);
+          pka = NULL;
+        }
+    }
+
+  return pka;
+}
+
+
+/* Return the URI from a DNS PKA record.  If this record has already
+   be retrieved for the signature we merely return it; if not we go
+   out and try to get that DNS record. */
+static const char *
+pka_uri_from_sig (PKT_signature *sig)
+{
+  if (!sig->flags.pka_tried)
+    {
+      assert (!sig->pka_info);
+      sig->flags.pka_tried = 1;
+      sig->pka_info = get_pka_address (sig);
+      if (sig->pka_info)
+        {
+          char *uri;
+
+          uri = get_pka_info (sig->pka_info->email, sig->pka_info->fpr);
+          if (uri)
+            {
+              sig->pka_info->valid = 1;
+              if (!*uri)
+                xfree (uri);
+              else
+                sig->pka_info->uri = uri;
+            }
+        }
+    }
+  return sig->pka_info? sig->pka_info->uri : NULL;
+}
+
+
 static int
 check_sig_and_print( CTX c, KBNODE node )
 {
@@ -1419,8 +1499,34 @@ check_sig_and_print( CTX c, KBNODE node )
          }
       }
 
-    /* If the preferred keyserver thing above didn't work, this is a
-       second try. */
+
+    /* If the preferred keyserver thing above didn't work, our second
+       try is to use the URI from a DNS PKA record. */
+    if ( rc == G10ERR_NO_PUBKEY )
+      {
+        const char *uri = pka_uri_from_sig (sig);
+        
+        if (uri)
+          {
+            int res;
+            struct keyserver_spec *spec;
+            
+            spec = parse_keyserver_uri (uri, 0, NULL, 0);
+            if (spec)
+              {
+                glo_ctrl.in_auto_key_retrieve++;
+                res = keyserver_import_keyid (sig->keyid, spec);
+                glo_ctrl.in_auto_key_retrieve--;
+                free_keyserver_spec (spec);
+                if (!res)
+                  rc = do_check_sig(c, node, NULL, &is_expkey, &is_revkey );
+              }
+          }
+      }
+
+
+    /* If the preferred keyserver thing above didn't work and we got
+       no information from the DNS PKA, this is a third try. */
 
     if( rc == G10ERR_NO_PUBKEY && opt.keyserver
        && (opt.keyserver_options.options&KEYSERVER_AUTO_KEY_RETRIEVE))
@@ -1673,8 +1779,11 @@ check_sig_and_print( CTX c, KBNODE node )
            free_public_key( vpk );
        }
 
-       if( !rc )
+       if (!rc)
+          {
+            pka_uri_from_sig (sig); /* Make sure PKA info is available. */
            rc = check_signatures_trust( sig );
+          }
 
        if(sig->flags.expired)
          {
index 380172f..88b7b2b 100644 (file)
@@ -1065,6 +1065,31 @@ unescape_percent_string (const unsigned char *s)
 
 
 
+int
+has_invalid_email_chars (const char *s)
+{
+  int at_seen=0;
+  static char valid_chars[] = ("01234567890_-."
+                               "abcdefghijklmnopqrstuvwxyz"
+                               "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+  for ( ; *s; s++ ) 
+    {
+      if ( *s & 0x80 )
+        return 1;
+      if ( *s == '@' )
+        at_seen=1;
+      else if ( !at_seen && !( !!strchr( valid_chars, *s ) || *s == '+' ) )
+        return 1;
+      else if ( at_seen && !strchr( valid_chars, *s ) )
+        return 1;
+    }
+  return 0;
+}
+
+
+
+
 /* This is a helper function to load a Windows function from either of
    one DLLs. */
 #ifdef HAVE_W32_SYSTEM
index cbbc0d7..0fe87f7 100644 (file)
@@ -122,36 +122,56 @@ struct revocation_key {
   byte fpr[MAX_FINGERPRINT_LEN];
 };
 
-typedef struct {
-    struct {
-       unsigned checked:1; /* signature has been checked */
-       unsigned valid:1;   /* signature is good (if checked is set) */
-        unsigned chosen_selfsig:1; /* a selfsig that is the chosen one */
-       unsigned unknown_critical:1;
-        unsigned exportable:1;
-        unsigned revocable:1;
-        unsigned policy_url:1; /* At least one policy URL is present */
-        unsigned notation:1; /* At least one notation is present */
-        unsigned pref_ks:1;  /* At least one preferred keyserver is present */
-        unsigned expired:1;
-    } flags;
-    u32     keyid[2];      /* 64 bit keyid */
-    u32     timestamp;     /* signature made */
-    u32     expiredate;     /* expires at this date or 0 if not at all */
-    byte    version;
-    byte    sig_class;     /* sig classification, append for MD calculation*/
-    byte    pubkey_algo;    /* algorithm used for public key scheme */
-                           /* (PUBKEY_ALGO_xxx) */
-    byte    digest_algo;    /* algorithm used for digest (DIGEST_ALGO_xxxx) */
-    byte    trust_depth;
-    byte    trust_value;
-    const byte *trust_regexp;
-    struct revocation_key **revkey;
-    int numrevkeys;
-    subpktarea_t *hashed;    /* all subpackets with hashed  data (v4 only) */
-    subpktarea_t *unhashed;  /* ditto for unhashed data */
-    byte digest_start[2];   /* first 2 bytes of the digest */
-    MPI  data[PUBKEY_MAX_NSIG];
+
+/* Object to keep information about a PKA DNS record. */
+typedef struct
+{
+  int valid;    /* An actual PKA record exists for EMAIL. */
+  int checked;  /* Set to true if the FPR has been checked against the
+                   actual key. */
+  char *uri;    /* Malloced string with the URI. NULL if the URI is
+                   not available.*/
+  unsigned char fpr[20]; /* The fingerprint as stored in the PKA RR. */
+  char email[1];/* The email address from the notation data. */
+} pka_info_t;
+
+
+/* Object to keep information pertaining to a signature. */
+typedef struct 
+{
+  struct 
+  {
+    unsigned checked:1;         /* Signature has been checked. */
+    unsigned valid:1;           /* Signature is good (if checked is set). */
+    unsigned chosen_selfsig:1;  /* A selfsig that is the chosen one. */
+    unsigned unknown_critical:1;
+    unsigned exportable:1;
+    unsigned revocable:1;
+    unsigned policy_url:1;  /* At least one policy URL is present */
+    unsigned notation:1;    /* At least one notation is present */
+    unsigned pref_ks:1;     /* At least one preferred keyserver is present */
+    unsigned expired:1;
+    unsigned pka_tried:1;   /* Set if we tried to retrieve the PKA record. */
+  } flags;
+  u32     keyid[2];      /* 64 bit keyid */
+  u32     timestamp;     /* Signature made (seconds since Epoch). */
+  u32     expiredate;     /* Expires at this date or 0 if not at all. */
+  byte    version;
+  byte    sig_class;     /* Sig classification, append for MD calculation. */
+  byte    pubkey_algo;    /* Algorithm used for public key scheme */
+                          /* (PUBKEY_ALGO_xxx) */
+  byte    digest_algo;    /* Algorithm used for digest (DIGEST_ALGO_xxxx). */
+  byte    trust_depth;
+  byte    trust_value;
+  const byte *trust_regexp;
+  struct revocation_key **revkey;
+  int numrevkeys;
+  pka_info_t *pka_info;      /* Malloced PKA data or NULL if not
+                                available.  See also flags.pka_tried. */
+  subpktarea_t *hashed;      /* All subpackets with hashed data (v4 only). */
+  subpktarea_t *unhashed;    /* Ditto for unhashed data. */
+  byte digest_start[2];      /* First 2 bytes of the digest. */
+  MPI  data[PUBKEY_MAX_NSIG];
 } PKT_signature;
 
 #define ATTRIB_IMAGE 1
index 068ffbf..80ebd0d 100644 (file)
@@ -1042,6 +1042,8 @@ can_handle_critical_notation(const byte *name,size_t len)
 {
   if(len==32 && memcmp(name,"preferred-email-encoding@pgp.com",32)==0)
     return 1;
+  if(len==21 && memcmp(name,"pka-address@gnupg.org",21)==0)
+    return 1;
 
   return 0;
 }
index 3967b59..1b32389 100644 (file)
@@ -532,6 +532,48 @@ check_signatures_trust( PKT_signature *sig )
   if ((trustlevel & TRUST_FLAG_DISABLED))
     log_info (_("Note: This key has been disabled.\n"));
 
+  /* If we have PKA information adjust the trustlevel. */
+  if (sig->pka_info && sig->pka_info->valid)
+    {
+      unsigned char fpr[MAX_FINGERPRINT_LEN];
+      PKT_public_key *primary_pk;
+      size_t fprlen;
+      int okay;
+
+      log_info (_("Note: Verified address is `%s'\n"), sig->pka_info->email);
+
+      primary_pk = xmalloc_clear (sizeof *primary_pk);
+      get_pubkey (primary_pk, pk->main_keyid);
+      fingerprint_from_pk (primary_pk, fpr, &fprlen);
+      free_public_key (primary_pk);
+
+      if ( fprlen == 20 && !memcmp (sig->pka_info->fpr, fpr, 20) )
+        okay = 1;
+      else
+        okay = 0;
+
+      switch ( (trustlevel & TRUST_MASK) ) 
+        {
+        case TRUST_UNKNOWN: 
+        case TRUST_UNDEFINED:
+        case TRUST_MARGINAL:
+          if (okay)
+            {
+              trustlevel = ((trustlevel & ~TRUST_MASK) | TRUST_FULLY);
+              log_info ("trustlevel adjusted to FULL due to valid PKA info\n");
+            }
+          /* (fall through) */
+        case TRUST_FULLY:
+          if (!okay)
+            {
+              trustlevel = ((trustlevel & ~TRUST_MASK) | TRUST_NEVER);
+              log_info ("trustlevel adjusted to NEVER due to bad PKA info\n");
+            }
+          break;
+        }
+    }
+
+  /* Now let the user know what up with the trustlevel. */
   switch ( (trustlevel & TRUST_MASK) ) 
     {
     case TRUST_EXPIRED:
index 31e1887..77b2b66 100644 (file)
@@ -253,6 +253,10 @@ int vasprintf (char **result, const char *format, va_list args);
 int asprintf (char **buf, const char *fmt, ...);
 #endif /*_WIN32*/
 
+/*-- pka.c --*/
+char *get_pka_info (const char *address, unsigned char *fpr);
+
+
 
 /**** other missing stuff ****/
 #ifndef HAVE_ATEXIT  /* For SunOS */
index b2c243c..fd18117 100644 (file)
@@ -1,3 +1,8 @@
+2005-07-28  Werner Koch  <wk@g10code.com>
+
+       * pka.c: New.
+       * Makefile.am (pka-test): new.
+
 2005-07-27  Werner Koch  <wk@g10code.com>
 
        * memory.c (FNAMEX, FNAMEXM): New macros to cope with the now used
index a143199..cb2019b 100644 (file)
@@ -38,7 +38,7 @@ endif
 #libutil_a_LDFLAGS =
 libutil_a_SOURCES = logger.c fileutil.c miscutil.c strgutil.c  \
                     ttyio.c  argparse.c memory.c secmem.c errors.c iobuf.c \
-                    dotlock.c http.c srv.h srv.c simple-gettext.c \
+                    dotlock.c http.c srv.h srv.c pka.c simple-gettext.c \
                     membuf.c w32reg.c $(assuan_source)
 
 libutil_a_DEPENDENCIES = @LIBOBJS@ @REGEX_O@
@@ -46,9 +46,14 @@ libutil_a_DEPENDENCIES = @LIBOBJS@ @REGEX_O@
 libutil_a_LIBADD = @LIBOBJS@ @REGEX_O@
 
 http-test:  http.c
-       gcc -DHAVE_CONFIG_H -I. -I. -I.. $(INCLUDES) $(LDFLAGS) -g -Wall \
+       cc -DHAVE_CONFIG_H -I. -I. -I.. $(INCLUDES) $(LDFLAGS) -g -Wall \
            -DTEST -o http-test http.c libutil.a @LIBINTL@ @SRVLIBS@ @CAPLIBS@
 
 srv-test:  srv.c
-       gcc -DHAVE_CONFIG_H -I. -I. -I.. $(INCLUDES) $(LDFLAGS) -g -Wall \
+       cc -DHAVE_CONFIG_H -I. -I. -I.. $(INCLUDES) $(LDFLAGS) -g -Wall \
            -DTEST -o srv-test srv.c libutil.a @LIBINTL@ @SRVLIBS@ @CAPLIBS@
+
+pka-test:  pka.c
+       cc -DHAVE_CONFIG_H -I. -I. -I.. $(INCLUDES) $(LDFLAGS) -g -Wall \
+          -DTEST -o pka-test pka.c libutil.a @LIBINTL@ @SRVLIBS@ @CAPLIBS@
+
diff --git a/util/pka.c b/util/pka.c
new file mode 100644 (file)
index 0000000..95689e0
--- /dev/null
@@ -0,0 +1,254 @@
+/* pka.c - DNS Public Key Association RR access
+ * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+ * USA.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifdef USE_DNS_PKA
+#include <sys/types.h>
+#ifdef _WIN32
+#include <windows.h>
+#else
+#include <netinet/in.h>
+#include <arpa/nameser.h>
+#include <resolv.h>
+#endif
+#endif /* USE_DNS_PKA */
+
+#include "memory.h"
+#include "types.h"
+#include "util.h"
+
+
+#ifdef USE_DNS_PKA
+/* Parse the TXT resource record. Format is:
+
+   v=1;fpr=a4d94e92b0986ab5ee9dcd755de249965b0358a2;uri=string
+   
+   For simplicity white spaces are not allowed.  Because we expect to
+   use a new RRTYPE for this in the future we define the TXT really
+   strict for simplicity: No white spaces, case sensitivity of the
+   names, order must be as given above.  Only URI is optional.
+
+   This function modifies BUFFER.  On success 0 is returned, the 20
+   byte fingerprint stored at FPR and BUFFER contains the URI or an
+   empty string.
+*/
+static int
+parse_txt_record (char *buffer, unsigned char *fpr)
+{
+  char *p, *pend;
+  int i;
+
+  p = buffer;
+  pend = strchr (p, ';');
+  if (!pend)
+    return -1;
+  *pend++ = 0;
+  if (strcmp (p, "v=1"))
+    return -1; /* Wrong or missing version. */
+  
+  p = pend;
+  pend = strchr (p, ';');
+  if (pend)
+    *pend++ = 0;
+  if (strncmp (p, "fpr=", 4))
+    return -1; /* Missing fingerprint part. */
+  p += 4;
+  for (i=0; i < 20 && hexdigitp (p) && hexdigitp (p+1); i++, p += 2)
+    fpr[i] = xtoi_2 (p);
+  if (i != 20)
+    return -1; /* Fingerprint consists not of exactly 40 hexbytes. */
+    
+  p = pend;
+  if (!p || !*p)
+    {
+      *buffer = 0;  
+      return 0; /* Success (no URI given). */
+    }
+  if (strncmp (p, "uri=", 4))
+    return -1; /* Unknown part. */
+  p += 4;
+  /* There is an URI, copy it to the start of the buffer. */
+  while (*p)
+    *buffer++ = *p++;
+  *buffer = 0;
+  return 0;
+}
+
+
+/* For the given email ADDRESS lookup the PKA information in the DNS.
+
+   On success the 20 byte SHA-1 fingerprint is stored at FPR and the
+   URI will be returned in an allocated buffer.  Note that the URI
+   might be an zero length string as this information is optiobnal.
+   Caller must xfree the returned string.
+
+   On error NULL is returned and the 20 bytes at FPR are not
+   defined. */
+char *
+get_pka_info (const char *address, unsigned char *fpr)
+{
+  unsigned char answer[PACKETSZ];
+  int anslen;
+  int qdcount, ancount, nscount, arcount;
+  int rc;
+  unsigned char *p, *pend;
+  const char *domain;
+  char *name;
+
+
+  domain = strrchr (address, '@');
+  if (!domain || domain == address || !domain[1])
+    return NULL; /* invalid mail address given. */
+
+  name = malloc (strlen (address) + 5 + 1);
+  memcpy (name, address, domain - address);
+  strcpy (stpcpy (name + (domain-address), "._pka."), domain+1);
+
+  anslen = res_query (name, C_IN, T_TXT, answer, PACKETSZ);
+  xfree (name);
+  if (anslen < sizeof(HEADER))
+    return NULL; /* DNS resolver returned a too short answer. */
+  if ( (rc=((HEADER*)answer)->rcode) != NOERROR )
+    return NULL; /* DNS resolver returned an error. */
+
+  /* We assume that PACKETSZ is large enough and don't do dynmically
+     expansion of the buffer. */
+  if (anslen > PACKETSZ)
+    return NULL; /* DNS resolver returned a too long answer */
+
+  qdcount = ntohs (((HEADER*)answer)->qdcount);
+  ancount = ntohs (((HEADER*)answer)->ancount);
+  nscount = ntohs (((HEADER*)answer)->nscount);
+  arcount = ntohs (((HEADER*)answer)->arcount);
+
+  if (!ancount)
+    return NULL; /* Got no answer. */
+
+  p = answer + sizeof (HEADER);
+  pend = answer + anslen; /* Actually points directly behind the buffer. */
+
+  while (qdcount-- && p < pend)
+    {
+      rc = dn_skipname (p, pend);
+      if (rc == -1)
+        return NULL;
+      p += rc + QFIXEDSZ; 
+    }
+
+  if (ancount > 1)
+    return NULL; /* more than one possible gpg trustdns record - none used. */
+
+  while (ancount-- && p <= pend)
+    {
+      unsigned int type, class, txtlen, n;
+      char *buffer, *bufp;
+
+      rc = dn_skipname (p, pend);
+      if (rc == -1)
+        return NULL;
+      p += rc;
+      if (p >= pend - 10)
+        return NULL; /* RR too short. */
+
+      type = *p++ << 8;
+      type |= *p++;
+      class = *p++ << 8;
+      class |= *p++;
+      p += 4;
+      txtlen = *p++ << 8;
+      txtlen |= *p++;
+      if (type != T_TXT || class != C_IN)
+        return NULL; /* Answer does not match the query. */
+
+      buffer = bufp = xmalloc (txtlen + 1);
+      while (txtlen && p < pend)
+        {
+          for (n = *p++, txtlen--; txtlen && n && p < pend; txtlen--, n--)
+            *bufp++ = *p++;
+        }
+      *bufp = 0;
+      if (parse_txt_record (buffer, fpr))
+        {
+          xfree (buffer);
+          return NULL; /* Not a valid gpg trustdns RR. */
+        }
+      return buffer;
+    }
+
+  return NULL;
+}
+#else /* !USE_DNS_PKA */
+
+/* Dummy version of the function if we can't use the resolver
+   functions. */
+char *
+get_pka_info (const char *address, unsigned char *fpr)
+{
+  return NULL;
+}
+#endif /* !USE_DNS_PKA */
+
+
+#ifdef TEST
+int
+main(int argc,char *argv[])
+{
+  unsigned char fpr[20];
+  char *uri;
+  int i;
+
+  if (argc < 2)
+    {
+      fprintf (stderr, "usage: pka mail-addresses\n");
+      return 1;
+    }
+  argc--;
+  argv++;
+
+  for (; argc; argc--, argv++)
+    {
+      uri = get_pka_info ( *argv, fpr );
+      printf ("%s", *argv);
+      if (uri)
+        {
+          putchar (' ');
+          for (i=0; i < 20; i++)
+            printf ("%02X", fpr[i]);
+          if (*uri)
+            printf (" %s", uri);
+          xfree (uri);
+        }
+      putchar ('\n');
+    }
+  return 0;
+}
+#endif /* TEST */
+
+/*
+Local Variables:
+compile-command: "cc -DUSE_DNS_PKA -DTEST -I.. -I../include -Wall -g -o pka pka.c -lresolv libutil.a"
+End:
+*/
index a00119d..e146237 100644 (file)
@@ -251,3 +251,9 @@ main(int argc,char *argv[])
   return 0;
 }
 #endif /* TEST */
+
+/*
+Local Variables:
+compile-command: "cc -DTEST -I.. -I../include -Wall -g -o srv srv.c -lresolv libutil.a"
+End:
+*/