diff --git a/kbx/Makefile.am b/kbx/Makefile.am index 42c3c4be8..9f8f7953d 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -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) diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c index 438d300b0..0b36c5b78 100644 --- a/kbx/backend-kbx.c +++ b/kbx/backend-kbx.c @@ -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; +} diff --git a/kbx/backend-support.c b/kbx/backend-support.c index 62551cafa..f1a97996f 100644 --- a/kbx/backend-support.c +++ b/kbx/backend-support.c @@ -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; +} diff --git a/kbx/backend.h b/kbx/backend.h index 675ec213d..1581ae582 100644 --- a/kbx/backend.h +++ b/kbx/backend.h @@ -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*/ diff --git a/kbx/frontend.c b/kbx/frontend.c index 6e0cbcb11..8ad4fed3c 100644 --- a/kbx/frontend.c +++ b/kbx/frontend.c @@ -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; } + + + +/* 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; +} diff --git a/kbx/frontend.h b/kbx/frontend.h index 55d041fb0..7c86514d0 100644 --- a/kbx/frontend.h +++ b/kbx/frontend.h @@ -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*/ diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c index 929ee6116..0da937f39 100644 --- a/kbx/kbxserver.c +++ b/kbx/kbxserver.c @@ -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); +} + + + static const char hlp_getinfo[] = "GETINFO \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 }, diff --git a/kbx/keybox-openpgp.c b/kbx/keybox-openpgp.c index 7a35475ca..0835909e6 100644 --- a/kbx/keybox-openpgp.c +++ b/kbx/keybox-openpgp.c @@ -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;