wks: Implement server command --install-key.
authorWerner Koch <wk@gnupg.org>
Tue, 20 Feb 2018 10:45:58 +0000 (11:45 +0100)
committerWerner Koch <wk@gnupg.org>
Tue, 20 Feb 2018 10:45:58 +0000 (11:45 +0100)
* tools/wks-util.c (wks_filter_uid): Add arg 'binary'.
* tools/gpg-wks-server.c (main): Expect 2 args for --install-key.
(write_to_file): New.
(check_and_publish): Factor some code out to ...
(compute_hu_fname): ... new.
(command_install_key): Implement.

Signed-off-by: Werner Koch <wk@gnupg.org>
doc/wks.texi
tools/gpg-wks-client.c
tools/gpg-wks-server.c
tools/gpg-wks.h
tools/wks-util.c

index 7f7d515..2960c67 100644 (file)
@@ -181,6 +181,7 @@ Display a brief help page and exit.
 .RI [ options ]
 .B \-\-install-key
 .I file
 .RI [ options ]
 .B \-\-install-key
 .I file
+.I user-id
 .br
 .B gpg-wks-server
 .RI [ options ]
 .br
 .B gpg-wks-server
 .RI [ options ]
@@ -221,14 +222,17 @@ the process returns failure; to suppress the diagnostic, use option
 @option{-q}.  More than one user-id can be given; see also option
 @option{with-file}.
 
 @option{-q}.  More than one user-id can be given; see also option
 @option{with-file}.
 
+The command @option{--install-key} manually installs a key into the
+WKD.  The arguments are a file with the keyblock and the user-id to
+install.
+
 The command @option{--remove-key} uninstalls a key from the WKD.  The
 The command @option{--remove-key} uninstalls a key from the WKD.  The
-process return success in this case; to also print a diagnostic, use
-option @option{-v}.  If the key is not installed a diagnostics is
+process returns success in this case; to also print a diagnostic, use
+option @option{-v}.  If the key is not installed a diagnostic is
 printed and the process returns failure; to suppress the diagnostic,
 use option @option{-q}.
 
 printed and the process returns failure; to suppress the diagnostic,
 use option @option{-q}.
 
-The commands @option{--install-key} and @option{--revoke-key} are not
-yet functional.
+The command @option{--revoke-key} is not yet functional.
 
 
 @mansect options
 
 
 @mansect options
index b86491e..3b19c76 100644 (file)
@@ -872,7 +872,7 @@ command_send (const char *fingerprint, const char *userid)
       estream_t newkey;
 
       es_rewind (key);
       estream_t newkey;
 
       es_rewind (key);
-      err = wks_filter_uid (&newkey, key, thisuid->uid);
+      err = wks_filter_uid (&newkey, key, thisuid->uid, 0);
       if (err)
         {
           log_error ("error filtering key: %s\n", gpg_strerror (err));
       if (err)
         {
           log_error ("error filtering key: %s\n", gpg_strerror (err));
index 008c266..e2b8306 100644 (file)
@@ -1,5 +1,5 @@
 /* gpg-wks-server.c - A server for the Web Key Service protocols.
 /* gpg-wks-server.c - A server for the Web Key Service protocols.
- * Copyright (C) 2016 Werner Koch
+ * Copyright (C) 2016, 2018 Werner Koch
  * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GnuPG.
  * Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
  *
  * This file is part of GnuPG.
@@ -20,7 +20,7 @@
 
 /* The Web Key Service I-D defines an update protocol to store a
  * public key in the Web Key Directory.  The current specification is
 
 /* The Web Key Service I-D defines an update protocol to store a
  * public key in the Web Key Directory.  The current specification is
- * draft-koch-openpgp-webkey-service-01.txt.
+ * draft-koch-openpgp-webkey-service-05.txt.
  */
 
 #include <config.h>
  */
 
 #include <config.h>
@@ -154,7 +154,7 @@ static gpg_error_t command_receive_cb (void *opaque,
                                        const char *mediatype, estream_t fp,
                                        unsigned int flags);
 static gpg_error_t command_list_domains (void);
                                        const char *mediatype, estream_t fp,
                                        unsigned int flags);
 static gpg_error_t command_list_domains (void);
-static gpg_error_t command_install_key (const char *fname);
+static gpg_error_t command_install_key (const char *fname, const char *userid);
 static gpg_error_t command_remove_key (const char *mailaddr);
 static gpg_error_t command_revoke_key (const char *mailaddr);
 static gpg_error_t command_check_key (const char *mailaddr);
 static gpg_error_t command_remove_key (const char *mailaddr);
 static gpg_error_t command_revoke_key (const char *mailaddr);
 static gpg_error_t command_check_key (const char *mailaddr);
@@ -376,9 +376,9 @@ main (int argc, char **argv)
       break;
 
     case aInstallKey:
       break;
 
     case aInstallKey:
-      if (argc != 1)
-        wrong_args ("--install-key FILE");
-      err = command_install_key (*argv);
+      if (argc != 2)
+        wrong_args ("--install-key FILE USER-ID");
+      err = command_install_key (*argv, argv[1]);
       break;
 
     case aRemoveKey:
       break;
 
     case aRemoveKey:
@@ -1339,6 +1339,81 @@ send_congratulation_message (const char *mbox, const char *keyfile)
 }
 
 
 }
 
 
+/* Write the content of SRC to the new file FNAME.  */
+static gpg_error_t
+write_to_file (estream_t src, const char *fname)
+{
+  gpg_error_t err;
+  estream_t dst;
+  char buffer[4096];
+  size_t nread, written;
+
+  dst = es_fopen (fname, "wb");
+  if (!dst)
+    return gpg_error_from_syserror ();
+
+  do
+    {
+      nread = es_fread (buffer, 1, sizeof buffer, src);
+      if (!nread)
+       break;
+      written = es_fwrite (buffer, 1, nread, dst);
+      if (written != nread)
+       break;
+    }
+  while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst));
+  if (!es_feof (src) || es_ferror (src) || es_ferror (dst))
+    {
+      err = gpg_error_from_syserror ();
+      es_fclose (dst);
+      gnupg_remove (fname);
+      return err;
+    }
+
+  if (es_fclose (dst))
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
+      return err;
+    }
+
+  return 0;
+}
+
+
+/* Compute the the full file name for the key with ADDRSPEC and return
+ * it at R_FNAME.  */
+static gpg_error_t
+compute_hu_fname (char **r_fname, const char *addrspec)
+{
+  gpg_error_t err;
+  char *hash;
+  const char *domain;
+  char sha1buf[20];
+
+  *r_fname = NULL;
+
+  domain = strchr (addrspec, '@');
+  if (!domain || !domain[1] || domain == addrspec)
+    return gpg_error (GPG_ERR_INV_ARG);
+  domain++;
+
+  gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1);
+  hash = zb32_encode (sha1buf, 8*20);
+  if (!hash)
+    return gpg_error_from_syserror ();
+
+  *r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
+  if (!*r_fname)
+    err = gpg_error_from_syserror ();
+  else
+    err = 0;
+
+  xfree (hash);
+  return err;
+}
+
+
 /* Check that we have send a request with NONCE and publish the key.  */
 static gpg_error_t
 check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
 /* Check that we have send a request with NONCE and publish the key.  */
 static gpg_error_t
 check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
@@ -1412,24 +1487,10 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
       goto leave;
     }
 
       goto leave;
     }
 
-
   /* Hash user ID and create filename.  */
   /* Hash user ID and create filename.  */
-  s = strchr (address, '@');
-  log_assert (s);
-  gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, address, s - address);
-  hash = zb32_encode (shaxbuf, 8*20);
-  if (!hash)
-    {
-      err = gpg_error_from_syserror ();
-      goto leave;
-    }
-
-  fnewname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
-  if (!fnewname)
-    {
-      err = gpg_error_from_syserror ();
-      goto leave;
-    }
+  err = compute_hu_fname (&fnewname, address);
+  if (err)
+    goto leave;
 
   /* Publish.  */
   err = copy_key_as_binary (fname, fnewname, address);
 
   /* Publish.  */
   err = copy_key_as_binary (fname, fnewname, address);
@@ -1935,16 +1996,122 @@ command_cron (void)
 }
 
 
 }
 
 
-/* Install a single key into the WKD by reading FNAME.  */
+/* Install a single key into the WKD by reading FNAME and extracting
+ * USERID.  */
 static gpg_error_t
 static gpg_error_t
-command_install_key (const char *fname)
+command_install_key (const char *fname, const char *userid)
 {
 {
-  (void)fname;
-  return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+  gpg_error_t err;
+  estream_t fp;
+  char *addrspec = NULL;
+  char *fpr = NULL;
+  uidinfo_list_t uidlist = NULL;
+  uidinfo_list_t uid, thisuid;
+  time_t thistime;
+  char *huname = NULL;
+  int any;
+
+  fp = es_fopen (fname, "rb");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
+      goto leave;
+    }
+
+  addrspec = mailbox_from_userid (userid);
+  if (!addrspec)
+    {
+      log_error ("\"%s\" is not a proper mail address\n", userid);
+      err = gpg_error (GPG_ERR_INV_USER_ID);
+      goto leave;
+    }
+
+  /* List the key so that we can figure out the newest UID with the
+   * requested addrspec.  */
+  err = wks_list_key (fp, &fpr, &uidlist);
+  if (err)
+    {
+      log_error ("error parsing key: %s\n", gpg_strerror (err));
+      err = gpg_error (GPG_ERR_NO_PUBKEY);
+      goto leave;
+    }
+  thistime = 0;
+  thisuid = NULL;
+  any = 0;
+  for (uid = uidlist; uid; uid = uid->next)
+    {
+      if (!uid->mbox)
+        continue; /* Should not happen anyway.  */
+      if (ascii_strcasecmp (uid->mbox, addrspec))
+        continue; /* Not the requested addrspec.  */
+      any = 1;
+      if (uid->created > thistime)
+        {
+          thistime = uid->created;
+          thisuid = uid;
+        }
+    }
+  if (!thisuid)
+    thisuid = uidlist;  /* This is the case for a missing timestamp.  */
+  if (!any)
+    {
+      log_error ("public key in '%s' has no mail address '%s'\n",
+                 fname, addrspec);
+      err = gpg_error (GPG_ERR_INV_USER_ID);
+      goto leave;
+    }
+
+  if (opt.verbose)
+    log_info ("using key with user id '%s'\n", thisuid->uid);
+
+  {
+    estream_t fp2;
+
+    es_rewind (fp);
+    err = wks_filter_uid (&fp2, fp, thisuid->uid, 1);
+    if (err)
+      {
+        log_error ("error filtering key: %s\n", gpg_strerror (err));
+        err = gpg_error (GPG_ERR_NO_PUBKEY);
+        goto leave;
+      }
+    es_fclose (fp);
+    fp = fp2;
+  }
+
+  /* Hash user ID and create filename.  */
+  err = compute_hu_fname (&huname, addrspec);
+  if (err)
+    goto leave;
+
+  /* Publish.  */
+  err = write_to_file (fp, huname);
+  if (err)
+    {
+      log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err));
+      goto leave;
+    }
+
+  /* Make sure it is world readable.  */
+  if (gnupg_chmod (huname, "-rwxr--r--"))
+    log_error ("can't set permissions of '%s': %s\n",
+               huname, gpg_strerror (gpg_err_code_from_syserror()));
+
+  if (!opt.quiet)
+    log_info ("key %s published for '%s'\n", fpr, addrspec);
+
+ leave:
+  xfree (huname);
+  free_uidinfo_list (uidlist);
+  xfree (fpr);
+  xfree (addrspec);
+  es_fclose (fp);
+  return err;
 }
 
 
 }
 
 
-/* Return the filename and optioanlly the addrspec for USERID at
+/* Return the filename and optionally the addrspec for USERID at
  * R_FNAME and R_ADDRSPEC.  R_ADDRSPEC might also be set on error.  */
 static gpg_error_t
 fname_from_userid (const char *userid, char **r_fname, char **r_addrspec)
  * R_FNAME and R_ADDRSPEC.  R_ADDRSPEC might also be set on error.  */
 static gpg_error_t
 fname_from_userid (const char *userid, char **r_fname, char **r_addrspec)
index 1522b72..a5a73c5 100644 (file)
@@ -89,7 +89,7 @@ void free_uidinfo_list (uidinfo_list_t list);
 gpg_error_t wks_list_key (estream_t key, char **r_fpr,
                           uidinfo_list_t *r_mboxes);
 gpg_error_t wks_filter_uid (estream_t *r_newkey, estream_t key,
 gpg_error_t wks_list_key (estream_t key, char **r_fpr,
                           uidinfo_list_t *r_mboxes);
 gpg_error_t wks_filter_uid (estream_t *r_newkey, estream_t key,
-                            const char *uid);
+                            const char *uid, int binary);
 gpg_error_t wks_send_mime (mime_maker_t mime);
 gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream,
                               int ignore_unknown);
 gpg_error_t wks_send_mime (mime_maker_t mime);
 gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream,
                               int ignore_unknown);
index 9c0f489..33f1ae7 100644 (file)
@@ -317,10 +317,13 @@ wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes)
 
 
 /* Run gpg as a filter on KEY and write the output to a new stream
 
 
 /* Run gpg as a filter on KEY and write the output to a new stream
- * stored at R_NEWKEY.  The new key will containn only the user id
- * UID.  Returns 0 on success.  Only one key is expected in KEY. */
+ * stored at R_NEWKEY.  The new key will contain only the user id UID.
+ * Returns 0 on success.  Only one key is expected in KEY.  If BINARY
+ * is set the resulting key is returned as a binary (non-armored)
+ * keyblock.  */
 gpg_error_t
 gpg_error_t
-wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid)
+wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid,
+                int binary)
 {
   gpg_error_t err;
   ccparray_t ccp;
 {
   gpg_error_t err;
   ccparray_t ccp;
@@ -340,8 +343,9 @@ wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid)
     }
 
   /* Prefix the key with the MIME content type.  */
     }
 
   /* Prefix the key with the MIME content type.  */
-  es_fputs ("Content-Type: application/pgp-keys\n"
-            "\n", newkey);
+  if (!binary)
+    es_fputs ("Content-Type: application/pgp-keys\n"
+              "\n", newkey);
 
   filterexp = es_bsprintf ("keep-uid=uid=%s", uid);
   if (!filterexp)
 
   filterexp = es_bsprintf ("keep-uid=uid=%s", uid);
   if (!filterexp)
@@ -361,7 +365,8 @@ wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid)
   ccparray_put (&ccp, "--batch");
   ccparray_put (&ccp, "--status-fd=2");
   ccparray_put (&ccp, "--always-trust");
   ccparray_put (&ccp, "--batch");
   ccparray_put (&ccp, "--status-fd=2");
   ccparray_put (&ccp, "--always-trust");
-  ccparray_put (&ccp, "--armor");
+  if (!binary)
+    ccparray_put (&ccp, "--armor");
   ccparray_put (&ccp, "--import-options=import-export");
   ccparray_put (&ccp, "--import-filter");
   ccparray_put (&ccp, filterexp);
   ccparray_put (&ccp, "--import-options=import-export");
   ccparray_put (&ccp, "--import-filter");
   ccparray_put (&ccp, filterexp);