kbx: Add first version of STORE command to keyboxd.
authorWerner Koch <wk@gnupg.org>
Tue, 1 Oct 2019 18:09:42 +0000 (20:09 +0200)
committerWerner Koch <wk@gnupg.org>
Tue, 1 Oct 2019 18:09:42 +0000 (20:09 +0200)
* kbx/Makefile.am (keyboxd_CFLAGS): -DKEYBOX_WITH_X509.
(keyboxd_LDADD): Add libksba.
* kbx/kbxserver.c (cmd_store): New.
* kbx/frontend.c (kbxd_store): New.
* kbx/backend-support.c (is_x509_blob): New.
(be_fingerprint_from_blob): New.
* kbx/backend-kbx.c (be_kbx_seek): Add args FPR and FPRLEN.
(be_kbx_insert): New.

Signed-off-by: Werner Koch <wk@gnupg.org>
kbx/Makefile.am
kbx/backend-kbx.c
kbx/backend-support.c
kbx/backend.h
kbx/frontend.c
kbx/frontend.h
kbx/kbxserver.c
kbx/keybox-openpgp.c

index 42c3c4b..9f8f795 100644 (file)
@@ -81,10 +81,10 @@ keyboxd_SOURCES = \
        backend-kbx.c \
        $(common_sources)
 
-keyboxd_CFLAGS = $(AM_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) \
-               $(INCICONV)
+keyboxd_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1 \
+                 $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) $(INCICONV)
 keyboxd_LDADD = $(commonpth_libs) \
-                $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
+                $(KSBA_LIBS) $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
                $(GPG_ERROR_LIBS) $(LIBINTL) $(NETLIBS) $(LIBICONV) \
                $(resource_objs)
 keyboxd_LDFLAGS = $(extra_bin_ldflags)
index 438d300..0b36c5b 100644 (file)
@@ -288,13 +288,15 @@ be_kbx_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request,
 }
 
 
-/* Seek in the keybox to the given UBID.  BACKEND_HD is the handle for
- * this backend and REQUEST is the current database request object.
- * This does a dummy read so that the next search operation starts
- * right after that UBID. */
+/* Seek in the keybox to the given UBID (if UBID is not NULL) or to
+ * the primary fingerprint specified by (FPR,FPRLEN).  BACKEND_HD is
+ * the handle for this backend and REQUEST is the current database
+ * request object.  This does a dummy read so that the next search
+ * operation starts right after that UBID. */
 gpg_error_t
 be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
-                  db_request_t request, unsigned char *ubid)
+             db_request_t request, const unsigned char *ubid,
+             const unsigned char *fpr, unsigned int fprlen)
 {
   gpg_error_t err;
   db_request_part_t part;
@@ -308,8 +310,19 @@ be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
   log_assert (request);
 
   memset (&desc, 0, sizeof desc);
-  desc.mode = KEYDB_SEARCH_MODE_UBID;
-  memcpy (desc.u.ubid, ubid, 20);
+  if (ubid)
+    {
+      desc.mode = KEYDB_SEARCH_MODE_FPR;
+      memcpy (desc.u.ubid, ubid, 20);
+    }
+  else
+    {
+      if (fprlen > sizeof desc.u.fpr)
+        return gpg_error (GPG_ERR_TOO_LARGE);
+      desc.mode = KEYDB_SEARCH_MODE_FPR;
+      memcpy (desc.u.fpr, fpr, fprlen);
+      desc.fprlen = fprlen;
+    }
 
   /* Find the specific request part or allocate it.  */
   err = be_find_request_part (backend_hd, request, &part);
@@ -326,3 +339,50 @@ be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
  leave:
   return err;
 }
+
+
+/* Insert (BLOB,BLOBLEN) into the keybox.  BACKEND_HD is the handle
+ * for this backend and REQUEST is the current database request
+ * object.  */
+gpg_error_t
+be_kbx_insert (ctrl_t ctrl, backend_handle_t backend_hd,
+               db_request_t request, enum pubkey_types pktype,
+               const void *blob, size_t bloblen)
+{
+  gpg_error_t err;
+  db_request_part_t part;
+  ksba_cert_t cert = NULL;
+
+  (void)ctrl;
+
+  log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX);
+  log_assert (request);
+
+  /* Find the specific request part or allocate it.  */
+  err = be_find_request_part (backend_hd, request, &part);
+  if (err)
+    goto leave;
+
+  if (pktype == PUBKEY_TYPE_OPGP)
+    err = keybox_insert_keyblock (part->kbx_hd, blob, bloblen);
+  else if (pktype == PUBKEY_TYPE_X509)
+    {
+      unsigned char sha1[20];
+
+      err = ksba_cert_new (&cert);
+      if (err)
+        goto leave;
+      err = ksba_cert_init_from_mem (cert, blob, bloblen);
+      if (err)
+        goto leave;
+      gcry_md_hash_buffer (GCRY_MD_SHA1, sha1, blob, bloblen);
+
+      err = keybox_insert_cert (part->kbx_hd, cert, sha1);
+    }
+  else
+    err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
+
+ leave:
+  ksba_cert_release (cert);
+  return err;
+}
index 62551ca..f1a9799 100644 (file)
@@ -27,8 +27,9 @@
 #include "keyboxd.h"
 #include "../common/i18n.h"
 #include "../common/asshelp.h"
+#include "../common/tlv.h"
 #include "backend.h"
-#include "keybox.h"
+#include "keybox-defs.h"
 
 
 /* Common definition part of all backend handle.  All definitions of
@@ -169,3 +170,107 @@ be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
  leave:
   return err;
 }
+
+
+
+/* Return true if (BLOB/BLOBLEN) seems to be an X509 certificate.  */
+static int
+is_x509_blob (const unsigned char *blob, size_t bloblen)
+{
+  const unsigned char *p;
+  size_t n, objlen, hdrlen;
+  int class, tag, cons, ndef;
+
+  /* An X.509 certificate can be identified by this DER encoding:
+   *
+   *  30 82 05 B8 30 82 04 A0 A0 03 02 01 02 02 07 15 46 A0 BF 30 07 39
+   *  ----------- +++++++++++ ----- ++++++++ --------------------------
+   *  SEQUENCE    SEQUENCE    [0]   INTEGER  INTEGER
+   *              (tbs)            (version) (s/n)
+   *
+   */
+
+  p = blob;
+  n = bloblen;
+  if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
+    return 0; /* Not a proper BER object.  */
+  if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons))
+    return 0; /* Does not start with a sequence.  */
+
+  if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
+    return 0; /* Not a proper BER object.  */
+  if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons))
+    return 0; /* No TBS sequence.  */
+  if (n < 7 || objlen < 7)
+    return 0; /* Too short:  [0], version and min. s/n required.  */
+
+  if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
+    return 0; /* Not a proper BER object.  */
+  if (!(class == CLASS_CONTEXT && tag == 0 && cons))
+    return 0; /* No context tag.  */
+
+  if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
+    return 0; /* Not a proper BER object.  */
+
+  if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER
+        && !cons && objlen == 1 && n && (*p == 1 || *p == 2)))
+    return 0; /* Unknown X.509 version.  */
+  p++;  /* Skip version number.  */
+  n--;
+
+  if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
+    return 0; /* Not a proper BER object.  */
+  if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER && !cons))
+    return 0;  /* No s/n.  */
+
+  return 1; /* Looks like an X.509 certificate. */
+}
+
+
+/* Return the public key type and the (primary) fingerprint for
+ * (BLOB,BLOBLEN).  R_FPR must point to a buffer of at least 32 bytes,
+ * it received the fi gerprint on success with the length of that
+ * fingerprint stored at R_FPRLEN.  R_PKTYPE receives the public key
+ * type.  */
+gpg_error_t
+be_fingerprint_from_blob (const void *blob, size_t bloblen,
+                          enum pubkey_types *r_pktype,
+                          char *r_fpr, unsigned int *r_fprlen)
+{
+  gpg_error_t err;
+
+  if (is_x509_blob (blob, bloblen))
+    {
+      /* Although libksba has a dedicated function to compute the
+       * fingerprint we compute it here directly because we know that
+       * we have the entire certificate here (we checked the start of
+       * the blob and assume that the length is also okay).  */
+      *r_pktype = PUBKEY_TYPE_X509;
+      gcry_md_hash_buffer (GCRY_MD_SHA1, r_fpr, blob, bloblen);
+      *r_fprlen = 20;
+
+      err = 0;
+    }
+  else
+    {
+      struct _keybox_openpgp_info info;
+
+      err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
+      if (err)
+        {
+          log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err));
+          err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
+        }
+      else
+        {
+          *r_pktype = PUBKEY_TYPE_OPGP;
+          log_assert (info.primary.fprlen <= 32);
+          memcpy (r_fpr, info.primary.fpr, info.primary.fprlen);
+          *r_fprlen = info.primary.fprlen;
+
+          _keybox_destroy_openpgp_info (&info);
+        }
+    }
+
+  return err;
+}
index 675ec21..1581ae5 100644 (file)
@@ -106,6 +106,9 @@ gpg_error_t be_find_request_part (backend_handle_t backend_hd,
 gpg_error_t be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
                               enum pubkey_types pubkey_type,
                               const unsigned char *ubid);
+gpg_error_t be_fingerprint_from_blob (const void *blob, size_t bloblen,
+                                      enum pubkey_types *r_pktype,
+                                      char *r_fpr, unsigned int *r_fprlen);
 
 
 /*-- backend-cache.c --*/
@@ -134,7 +137,11 @@ gpg_error_t be_kbx_search (ctrl_t ctrl, backend_handle_t hd,
                            db_request_t request,
                            KEYDB_SEARCH_DESC *desc, unsigned int ndesc);
 gpg_error_t be_kbx_seek (ctrl_t ctrl, backend_handle_t backend_hd,
-                         db_request_t request, unsigned char *ubid);
+                         db_request_t request, const unsigned char *ubid,
+                         const unsigned char *fpr, unsigned int fprlen);
+gpg_error_t be_kbx_insert (ctrl_t ctrl, backend_handle_t backend_hd,
+                           db_request_t request, enum pubkey_types pktype,
+                           const void *blob, size_t bloblen);
 
 
 #endif /*KBX_BACKEND_H*/
index 6e0cbcb..8ad4fed 100644 (file)
@@ -58,12 +58,12 @@ take_read_lock (ctrl_t ctrl)
 
 
 /* Take a lock for reading and writing the databases.  */
-/* static void */
-/* take_read_write_lock (ctrl_t ctrl) */
-/* { */
-/*   /\* FIXME *\/ */
-/*   (void)ctrl; */
-/* } */
+static void
+take_read_write_lock (ctrl_t ctrl)
+{
+  /* FIXME */
+  (void)ctrl;
+}
 
 
 /* Release a lock.  It is valid to call this even if no lock has been
@@ -339,7 +339,7 @@ kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
         {
           /* We need to set the startpoint for the search.  */
           err = be_kbx_seek (ctrl, db->backend_handle, request,
-                             request->last_cached_ubid);
+                             request->last_cached_ubid, NULL, 0);
           if (err)
             {
               log_debug ("%s: seeking %s to an UBID failed: %s\n",
@@ -383,3 +383,83 @@ kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
     log_clock ("%s: leave (%s)", __func__, err? "not found" : "found");
   return err;
 }
+
+
+\f
+/* Store; that is insert or update the key (BLOB,BLOBLEN).  If
+ * ONLY_UPDATE is set the key must exist.  */
+gpg_error_t
+kbxd_store (ctrl_t ctrl, const void *blob, size_t bloblen, int only_update)
+{
+  gpg_error_t err;
+  db_request_t request;
+  unsigned int dbidx;
+  db_desc_t db;
+  char fpr[32];
+  unsigned int fprlen;
+  enum pubkey_types pktype;
+  int insert = 0;
+
+  if (DBG_CLOCK)
+    log_clock ("%s: enter", __func__);
+
+  take_read_write_lock (ctrl);
+
+  /* Allocate a handle object if none exists for this context.  */
+  if (!ctrl->opgp_req)
+    {
+      ctrl->opgp_req = xtrycalloc (1, sizeof *ctrl->opgp_req);
+      if (!ctrl->opgp_req)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
+  request = ctrl->opgp_req;
+
+  /* Check whether to insert or update.  */
+  err = be_fingerprint_from_blob (blob, bloblen, &pktype, fpr, &fprlen);
+  if (err)
+    goto leave;
+
+  /* FIXME: We force the use of the KBX backend.  */
+  for (dbidx=0; dbidx < no_of_databases; dbidx++)
+    if (databases[dbidx].db_type == DB_TYPE_KBX)
+      break;
+  if (!(dbidx < no_of_databases))
+    {
+      err = gpg_error (GPG_ERR_NOT_INITIALIZED);
+      goto leave;
+    }
+  db = databases + dbidx;
+
+  err = be_kbx_seek (ctrl, db->backend_handle, request, NULL, fpr, fprlen);
+  if (!err)
+    ; /* Found - need to update.  */
+  else if (gpg_err_code (err) == GPG_ERR_EOF)
+    insert = 1; /* Not found - need to insert.  */
+  else
+    {
+      log_debug ("%s: searching fingerprint failed: %s\n",
+                 __func__, gpg_strerror (err));
+      goto leave;
+    }
+
+  if (insert)
+    {
+      err = be_kbx_insert (ctrl, db->backend_handle, request,
+                           pktype, blob, bloblen);
+    }
+  else if (only_update)
+    err = gpg_error (GPG_ERR_DUP_KEY);
+  else /* Update.  */
+    {
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+    }
+
+ leave:
+  release_lock (ctrl);
+  if (DBG_CLOCK)
+    log_clock ("%s: leave", __func__);
+  return err;
+}
index 55d041f..7c86514 100644 (file)
@@ -31,6 +31,8 @@ void kbxd_release_session_info (ctrl_t ctrl);
 gpg_error_t kbxd_search (ctrl_t ctrl,
                          KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
                          int reset);
+gpg_error_t kbxd_store (ctrl_t ctrl, const void *blob, size_t bloblen,
+                        int only_update);
 
 
 #endif /*KBX_FRONTEND_H*/
index 929ee61..0da937f 100644 (file)
@@ -465,6 +465,55 @@ cmd_next (assuan_context_t ctx, char *line)
 }
 
 
+static const char hlp_store[] =
+  "STORE [--update]\n"
+  "\n"
+  "Insert a key into the database.  Whether to insert or update\n"
+  "the key is decided by looking at the primary key's fingerprint.\n"
+  "With option --update the key must already exist. The actual key\n"
+  "material is requested by this function using\n"
+  "  INQUIRE BLOB";
+static gpg_error_t
+cmd_store (assuan_context_t ctx, char *line)
+{
+  ctrl_t ctrl = assuan_get_pointer (ctx);
+  int opt_update;
+  gpg_error_t err;
+  unsigned char *value = NULL;
+  size_t valuelen;
+
+  opt_update = has_option (line, "--update");
+  line = skip_options (line);
+  if (*line)
+    {
+      err = set_error (GPG_ERR_INV_ARG, "no args expected");
+      goto leave;
+    }
+
+  /* Ask for the key material.  */
+  err = assuan_inquire (ctx, "BLOB", &value, &valuelen, 0);
+  if (err)
+    {
+      log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
+      goto leave;
+    }
+
+  if (!valuelen) /* No data received. */
+    {
+      err = gpg_error (GPG_ERR_MISSING_VALUE);
+      goto leave;
+    }
+
+  err = kbxd_store (ctrl, value, valuelen, opt_update);
+
+
+ leave:
+  xfree (value);
+  return leave_cmd (ctx, err);
+}
+
+
+
 \f
 static const char hlp_getinfo[] =
   "GETINFO <what>\n"
@@ -584,6 +633,7 @@ register_commands (assuan_context_t ctx)
   } table[] = {
     { "SEARCH",     cmd_search,     hlp_search },
     { "NEXT",       cmd_next,       hlp_next   },
+    { "STORE",      cmd_store,      hlp_store  },
     { "GETINFO",    cmd_getinfo,    hlp_getinfo },
     { "OUTPUT",     NULL,           hlp_output },
     { "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd },
index 7a35475..0835909 100644 (file)
@@ -667,7 +667,7 @@ _keybox_destroy_openpgp_info (keybox_openpgp_info_t info)
   struct _keybox_openpgp_key_info *k, *k2;
   struct _keybox_openpgp_uid_info *u, *u2;
 
-  assert (!info->primary.next);
+  log_assert (!info->primary.next);
   for (k=info->subkeys.next; k; k = k2)
     {
       k2 = k->next;