diff --git a/kbx/Makefile.am b/kbx/Makefile.am index 42c3c4be8..9f8f7953d 100644 --- a/kbx/Makefile.am +++ b/kbx/Makefile.am @@ -1,96 +1,96 @@ # Keybox Makefile # Copyright (C) 2001, 2002, 2003 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 3 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, see . ## Process this file with automake to produce Makefile.in EXTRA_DIST = mkerrors keyboxd-w32info.rc AM_CPPFLAGS = include $(top_srcdir)/am/cmacros.am if HAVE_W32_SYSTEM resource_objs += keyboxd-w32info.o endif AM_CFLAGS = $(LIBGCRYPT_CFLAGS) $(KSBA_CFLAGS) noinst_LIBRARIES = libkeybox.a libkeybox509.a bin_PROGRAMS = kbxutil libexec_PROGRAMS = keyboxd if HAVE_W32CE_SYSTEM extra_libs = $(LIBASSUAN_LIBS) else extra_libs = endif common_libs = $(libcommon) commonpth_libs = $(libcommonpth) common_sources = \ keybox.h keybox-defs.h keybox-search-desc.h \ keybox-util.c \ keybox-init.c \ keybox-blob.c \ keybox-file.c \ keybox-search.c \ keybox-update.c \ keybox-openpgp.c \ keybox-dump.c libkeybox_a_SOURCES = $(common_sources) libkeybox509_a_SOURCES = $(common_sources) libkeybox_a_CFLAGS = $(AM_CFLAGS) libkeybox509_a_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1 # We need W32SOCKLIBS because the init subsystem code in libcommon # requires it - although we don't actually need it. It is easier # to do it this way. kbxutil_SOURCES = kbxutil.c $(common_sources) kbxutil_CFLAGS = $(AM_CFLAGS) -DKEYBOX_WITH_X509=1 kbxutil_LDADD = $(common_libs) \ $(KSBA_LIBS) $(LIBGCRYPT_LIBS) $(extra_libs) \ $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(W32SOCKLIBS) \ $(NETLIBS) keyboxd_SOURCES = \ keyboxd.c keyboxd.h \ kbxserver.c \ frontend.c frontend.h \ backend.h backend-support.c \ backend-cache.c \ 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) keyboxd_DEPENDENCIES = $(resource_objs) # Make sure that all libs are build before we use them. This is # important for things like make -j2. $(PROGRAMS): $(common_libs) $(commonpth_libs) 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 @@ -1,328 +1,388 @@ /* backend-kbx.c - Keybox format backend for keyboxd * Copyright (C) 2019 g10 Code GmbH * * 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 3 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, see . * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include #include #include "keyboxd.h" #include "../common/i18n.h" #include "backend.h" #include "keybox.h" /* Our definition of the backend handle. */ struct backend_handle_s { enum database_types db_type; /* Always DB_TYPE_KBX. */ unsigned int backend_id; /* Always the id of the backend. */ void *token; /* Used to create a new KEYBOX_HANDLE. */ char filename[1]; }; /* Check that the file FILENAME is a valid keybox file which can be * used here. Common return codes: * * 0 := Valid keybox file * GPG_ERR_ENOENT := No such file * GPG_ERR_NO_OBJ := File exists with size zero. * GPG_ERR_INV_OBJ:= File exists but is not a keybox file. */ static gpg_error_t check_kbx_file_magic (const char *filename) { gpg_error_t err; u32 magic; unsigned char verbuf[4]; estream_t fp; fp = es_fopen (filename, "rb"); if (!fp) return gpg_error_from_syserror (); err = gpg_error (GPG_ERR_INV_OBJ); if (es_fread (&magic, 4, 1, fp) == 1 ) { if (es_fread (&verbuf, 4, 1, fp) == 1 && verbuf[0] == 1 && es_fread (&magic, 4, 1, fp) == 1 && !memcmp (&magic, "KBXf", 4)) { err = 0; } } else /* Maybe empty: Let's create it. */ err = gpg_error (GPG_ERR_NO_OBJ); es_fclose (fp); return err; } /* Create new keybox file. This can also be used if the keybox * already exists but has a length of zero. Do not use it in any * other cases. */ static gpg_error_t create_keybox (const char *filename) { gpg_error_t err; dotlock_t lockhd = NULL; estream_t fp; /* To avoid races with other temporary instances of keyboxd trying * to create or update the keybox, we do the next stuff in a locked * state. */ lockhd = dotlock_create (filename, 0); if (!lockhd) { err = gpg_error_from_syserror (); /* A reason for this to fail is that the directory is not * writable. However, this whole locking stuff does not make * sense if this is the case. An empty non-writable directory * with no keybox is not really useful at all. */ if (opt.verbose) log_info ("can't allocate lock for '%s': %s\n", filename, gpg_strerror (err)); return err; } if ( dotlock_take (lockhd, -1) ) { err = gpg_error_from_syserror (); /* This is something bad. Probably a stale lockfile. */ log_info ("can't lock '%s': %s\n", filename, gpg_strerror (err)); goto leave; } /* Make sure that at least one record is in a new keybox file, so * that the detection magic will work the next time it is used. * We always set the OpenPGP blobs maybe availabale flag. */ fp = es_fopen (filename, "w+b,mode=-rw-------"); if (!fp) { err = gpg_error_from_syserror (); log_error (_("error creating keybox '%s': %s\n"), filename, gpg_strerror (err)); goto leave; } err = _keybox_write_header_blob (NULL, fp, 1); es_fclose (fp); if (err) { log_error (_("error creating keybox '%s': %s\n"), filename, gpg_strerror (err)); goto leave; } if (!opt.quiet) log_info (_("keybox '%s' created\n"), filename); err = 0; leave: if (lockhd) { dotlock_release (lockhd); dotlock_destroy (lockhd); } return err; } /* Install a new resource and return a handle for that backend. */ gpg_error_t be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd, const char *filename, int readonly) { gpg_error_t err; backend_handle_t hd; void *token; (void)ctrl; *r_hd = NULL; hd = xtrycalloc (1, sizeof *hd + strlen (filename)); if (!hd) return gpg_error_from_syserror (); hd->db_type = DB_TYPE_KBX; strcpy (hd->filename, filename); err = check_kbx_file_magic (filename); switch (gpg_err_code (err)) { case 0: break; case GPG_ERR_ENOENT: case GPG_ERR_NO_OBJ: if (readonly) { err = gpg_error (GPG_ERR_ENOENT); goto leave; } err = create_keybox (filename); if (err) goto leave; break; default: goto leave; } err = keybox_register_file (filename, 0, &token); if (err) goto leave; hd->backend_id = be_new_backend_id (); hd->token = token; *r_hd = hd; hd = NULL; leave: xfree (hd); return err; } /* Release the backend handle HD and all its resources. HD is not * valid after a call to this function. */ void be_kbx_release_resource (ctrl_t ctrl, backend_handle_t hd) { (void)ctrl; if (!hd) return; hd->db_type = DB_TYPE_NONE; xfree (hd); } void be_kbx_release_kbx_hd (KEYBOX_HANDLE kbx_hd) { keybox_release (kbx_hd); } /* Helper for be_find_request_part to initialize a kbx request part. */ gpg_error_t be_kbx_init_request_part (backend_handle_t backend_hd, db_request_part_t part) { part->kbx_hd = keybox_new_openpgp (backend_hd->token, 0); if (!part->kbx_hd) return gpg_error_from_syserror (); return 0; } /* Search for the keys described by (DESC,NDESC) and return them to * the caller. BACKEND_HD is the handle for this backend and REQUEST * is the current database request object. */ gpg_error_t be_kbx_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, KEYDB_SEARCH_DESC *desc, unsigned int ndesc) { gpg_error_t err; db_request_part_t part; size_t descindex; unsigned long skipped_long_blobs; 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 (!desc) err = keybox_search_reset (part->kbx_hd); else err = keybox_search (part->kbx_hd, desc, ndesc, KEYBOX_BLOBTYPE_PGP, &descindex, &skipped_long_blobs); if (err == -1) err = gpg_error (GPG_ERR_EOF); if (desc && !err) { /* Successful search operation. */ void *buffer; size_t buflen; enum pubkey_types pubkey_type; unsigned char blobid[20]; err = keybox_get_data (part->kbx_hd, &buffer, &buflen, &pubkey_type); if (err) goto leave; gcry_md_hash_buffer (GCRY_MD_SHA1, blobid, buffer, buflen); err = be_return_pubkey (ctrl, buffer, buflen, pubkey_type, blobid); if (!err) be_cache_pubkey (ctrl, blobid, buffer, buflen, pubkey_type); xfree (buffer); } leave: return err; } -/* 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; size_t descindex; unsigned long skipped_long_blobs; KEYDB_SEARCH_DESC desc; (void)ctrl; log_assert (backend_hd && backend_hd->db_type == DB_TYPE_KBX); 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); if (err) goto leave; err = keybox_search_reset (part->kbx_hd); if (!err) err = keybox_search (part->kbx_hd, &desc, 1, 0, &descindex, &skipped_long_blobs); if (err == -1) err = gpg_error (GPG_ERR_EOF); 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 @@ -1,171 +1,276 @@ /* backend-support.c - Supporting functions for the backend. * Copyright (C) 2019 g10 Code GmbH * * 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 3 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, see . * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #include #include #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 * this structure must start with these fields. */ struct backend_handle_s { enum database_types db_type; unsigned int backend_id; }; /* Return a string with the name of the database type T. */ const char * strdbtype (enum database_types t) { switch (t) { case DB_TYPE_NONE: return "none"; case DB_TYPE_CACHE:return "cache"; case DB_TYPE_KBX: return "keybox"; } return "?"; } /* Return a new backend ID. Backend IDs are used to identify backends * without using the actual object. The number of backend resources * is limited because they are specified in the config file. Thus an * overflow check is not required. */ unsigned int be_new_backend_id (void) { static unsigned int last; return ++last; } /* Release the backend described by HD. This is a generic function * which dispatches to the the actual backend. */ void be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd) { if (!hd) return; switch (hd->db_type) { case DB_TYPE_NONE: xfree (hd); break; case DB_TYPE_CACHE: be_cache_release_resource (ctrl, hd); break; case DB_TYPE_KBX: be_kbx_release_resource (ctrl, hd); break; default: log_error ("%s: faulty backend handle of type %d given\n", __func__, hd->db_type); } } /* Release the request object REQ. */ void be_release_request (db_request_t req) { db_request_part_t part, partn; if (!req) return; for (part = req->part; part; part = partn) { partn = part->next; be_kbx_release_kbx_hd (part->kbx_hd); xfree (part); } } /* Given the backend handle BACKEND_HD and the REQUEST find or * allocate a request part for that backend and store it at R_PART. * On error R_PART is set to NULL and an error returned. */ gpg_error_t be_find_request_part (backend_handle_t backend_hd, db_request_t request, db_request_part_t *r_part) { gpg_error_t err; db_request_part_t part; for (part = request->part; part; part = part->next) if (part->backend_id == backend_hd->backend_id) break; if (!part) { part = xtrycalloc (1, sizeof *part); if (!part) return gpg_error_from_syserror (); part->backend_id = backend_hd->backend_id; if (backend_hd->db_type == DB_TYPE_KBX) { err = be_kbx_init_request_part (backend_hd, part); if (err) { xfree (part); return err; } } part->next = request->part; request->part = part; } *r_part = part; return 0; } /* Return the public key (BUFFER,BUFLEN) which has the type * PUBKEY_TYPE to the caller. */ 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 err; char hexubid[41]; bin2hex (ubid, 20, hexubid); err = status_printf (ctrl, "PUBKEY_INFO", "%d %s", pubkey_type, hexubid); if (err) goto leave; if (ctrl->no_data_return) err = 0; else err = kbxd_write_data_line(ctrl, buffer, 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 @@ -1,140 +1,147 @@ /* backend.h - Definitions for keyboxd backends * Copyright (C) 2019 g10 Code GmbH * * 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 3 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, see . */ #ifndef KBX_BACKEND_H #define KBX_BACKEND_H #include "keybox-search-desc.h" /* Forward declaration of the keybox handle type. */ struct keybox_handle; typedef struct keybox_handle *KEYBOX_HANDLE; /* The types of the backends. */ enum database_types { DB_TYPE_NONE, /* No database at all (unitialized etc.). */ DB_TYPE_CACHE, /* The cache backend (backend-cache.c). */ DB_TYPE_KBX /* Keybox type database (backend-kbx.c). */ }; /* Declaration of the backend handle. Each backend uses its own * hidden handle structure with the only common thing being that the * first field is the database_type to help with debugging. */ struct backend_handle_s; typedef struct backend_handle_s *backend_handle_t; /* Object to store backend specific database information per database * handle. */ struct db_request_part_s { struct db_request_part_s *next; /* Id of the backend instance this object pertains to. */ unsigned int backend_id; /* The handle used for a KBX backend or NULL. */ KEYBOX_HANDLE kbx_hd; /* For the CACHE backend the indices into the bloblist for each * index type. */ struct { unsigned int fpr; unsigned int kid; unsigned int grip; unsigned int ubid; } cache_seqno; }; typedef struct db_request_part_s *db_request_part_t; /* A database request handle. This keeps per session search * information as well as a list of per-backend infos. */ struct db_request_s { unsigned int any_search:1; /* Any search has been done. */ unsigned int any_found:1; /* Any object has been found. */ unsigned int last_cached_valid:1; /* see below */ unsigned int last_cached_final:1; /* see below */ unsigned int last_cached_fprlen:8;/* see below */ db_request_part_t part; /* Counter to track the next to be searched database index. */ unsigned int next_dbidx; /* The last UBID found in the cache and the corresponding keyid and, * if found via fpr, the fingerprint. For the LAST_CACHE_FPRLEN see * above. The entry here is only valid if LAST_CACHE_VALID is set; * if LAST_CACHE_FINAL is also set, this indicates that no further * database searches are required. */ unsigned char last_cached_ubid[20]; u32 last_cached_kid_h; u32 last_cached_kid_l; unsigned char last_cached_fpr[32]; }; /*-- backend-support.c --*/ const char *strdbtype (enum database_types t); unsigned int be_new_backend_id (void); void be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd); void be_release_request (db_request_t req); gpg_error_t be_find_request_part (backend_handle_t backend_hd, db_request_t request, db_request_part_t *r_part); 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 --*/ gpg_error_t be_cache_add_resource (ctrl_t ctrl, backend_handle_t *r_hd); void be_cache_release_resource (ctrl_t ctrl, backend_handle_t hd); gpg_error_t be_cache_search (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request, KEYDB_SEARCH_DESC *desc, unsigned int ndesc); void be_cache_mark_final (ctrl_t ctrl, db_request_t request); void be_cache_pubkey (ctrl_t ctrl, const unsigned char *ubid, const void *blob, unsigned int bloblen, enum pubkey_types pubkey_type); void be_cache_not_found (ctrl_t ctrl, enum pubkey_types pubkey_type, KEYDB_SEARCH_DESC *desc, unsigned int ndesc); /*-- backend-kbx.c --*/ gpg_error_t be_kbx_add_resource (ctrl_t ctrl, backend_handle_t *r_hd, const char *filename, int readonly); void be_kbx_release_resource (ctrl_t ctrl, backend_handle_t hd); void be_kbx_release_kbx_hd (KEYBOX_HANDLE kbx_hd); gpg_error_t be_kbx_init_request_part (backend_handle_t backend_hd, db_request_part_t part); 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 @@ -1,385 +1,465 @@ /* frontend.c - Database fronend code for keyboxd * Copyright (C) 2019 g10 Code GmbH * * 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 3 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, see . * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #include #include #include "keyboxd.h" #include #include "../common/i18n.h" #include "../common/userids.h" #include "backend.h" #include "frontend.h" /* An object to describe a single database. */ struct db_desc_s { enum database_types db_type; backend_handle_t backend_handle; }; typedef struct db_desc_s *db_desc_t; /* The table of databases and the size of that table. */ static db_desc_t databases; static unsigned int no_of_databases; /* Take a lock for reading the databases. */ static void take_read_lock (ctrl_t ctrl) { /* FIXME */ (void)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 * taken in which case this is a nop. */ static void release_lock (ctrl_t ctrl) { /* FIXME */ (void)ctrl; } /* Add a new resource to the database. Depending on the FILENAME * suffix we decide which one to use. This function must be called at * daemon startup because it employs no locking. If FILENAME has no * directory separator, the file is expected or created below * "$GNUPGHOME/public-keys-v1.d/". In READONLY mode the file must * exists; otherwise it is created. */ gpg_error_t kbxd_add_resource (ctrl_t ctrl, const char *filename_arg, int readonly) { gpg_error_t err; char *filename; enum database_types db_type = 0; backend_handle_t handle = NULL; unsigned int n, dbidx; /* Do tilde expansion etc. */ if (!strcmp (filename_arg, "[cache]")) { filename = xstrdup (filename_arg); db_type = DB_TYPE_CACHE; } else if (strchr (filename_arg, DIRSEP_C) #ifdef HAVE_W32_SYSTEM || strchr (filename_arg, '/') /* Windows also accepts a slash. */ #endif ) filename = make_filename (filename_arg, NULL); else filename = make_filename (gnupg_homedir (), GNUPG_PUBLIC_KEYS_DIR, filename_arg, NULL); /* If this is the first call to the function and the request is not * for the cache backend, add the cache backend so that it will * always be the first to be queried. */ if (!no_of_databases && !db_type) { err = kbxd_add_resource (ctrl, "[cache]", 0); if (err) goto leave; } n = strlen (filename); if (db_type) ; /* We already know it. */ else if (n > 4 && !strcmp (filename + n - 4, ".kbx")) db_type = DB_TYPE_KBX; else { log_error (_("can't use file '%s': %s\n"), filename, _("unknown suffix")); err = gpg_error (GPG_ERR_NOT_SUPPORTED); goto leave; } err = gpg_error (GPG_ERR_BUG); switch (db_type) { case DB_TYPE_NONE: /* NOTREACHED */ break; case DB_TYPE_CACHE: err = be_cache_add_resource (ctrl, &handle); break; case DB_TYPE_KBX: err = be_kbx_add_resource (ctrl, &handle, filename, readonly); break; } if (err) goto leave; /* All good, create an entry in the table. */ for (dbidx = 0; dbidx < no_of_databases; dbidx++) if (!databases[dbidx].db_type) break; if (dbidx == no_of_databases) { /* No table yet or table is full. */ if (!databases) { /* Create first set of databases. Note that the initial * type for all entries is DB_TYPE_NONE. */ dbidx = 4; databases = xtrycalloc (dbidx, sizeof *databases); if (!databases) { err = gpg_error_from_syserror (); goto leave; } no_of_databases = dbidx; dbidx = 0; /* Put into first slot. */ } else { db_desc_t newdb; dbidx = no_of_databases + (no_of_databases == 4? 12 : 16); newdb = xtrycalloc (dbidx, sizeof *databases); if (!databases) { err = gpg_error_from_syserror (); goto leave; } for (n=0; n < no_of_databases; n++) newdb[n] = databases[n]; xfree (databases); databases = newdb; n = no_of_databases; no_of_databases = dbidx; dbidx = n; /* Put into first new slot. */ } } databases[dbidx].db_type = db_type; databases[dbidx].backend_handle = handle; handle = NULL; leave: if (err) { log_error ("error adding resource '%s': %s\n", filename, gpg_strerror (err)); be_generic_release_backend (ctrl, handle); } xfree (filename); return err; } /* Release all per session objects. */ void kbxd_release_session_info (ctrl_t ctrl) { if (!ctrl) return; be_release_request (ctrl->opgp_req); ctrl->opgp_req = NULL; be_release_request (ctrl->x509_req); ctrl->x509_req = NULL; } /* Search for the keys described by (DESC,NDESC) and return them to * the caller. If RESET is set, the search state is first reset. */ gpg_error_t kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc, int reset) { gpg_error_t err; int i; unsigned int dbidx; db_desc_t db; db_request_t request; int start_at_ubid = 0; if (DBG_CLOCK) log_clock ("%s: enter", __func__); if (DBG_LOOKUP) { log_debug ("%s: %u search descriptions:\n", __func__, ndesc); for (i = 0; i < ndesc; i ++) { /* char *t = keydb_search_desc_dump (&desc[i]); */ /* log_debug ("%s %d: %s\n", __func__, i, t); */ /* xfree (t); */ } } take_read_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; /* If requested do a reset. Using the reset flag is faster than * letting the caller do a separate call for an intial reset. */ if (!desc || reset) { for (dbidx=0; dbidx < no_of_databases; dbidx++) { db = databases + dbidx; if (!db->db_type) continue; /* Empty slot. */ switch (db->db_type) { case DB_TYPE_NONE: /* NOTREACHED */ break; case DB_TYPE_CACHE: err = 0; /* Nothing to do. */ break; case DB_TYPE_KBX: err = be_kbx_search (ctrl, db->backend_handle, request, NULL, 0); break; } if (err) { log_error ("error during the %ssearch reset: %s\n", reset? "initial ":"", gpg_strerror (err)); goto leave; } } request->any_search = 0; request->any_found = 0; request->next_dbidx = 0; if (!desc) /* Reset only mode */ { err = 0; goto leave; } } /* Move to the next non-empty slot. */ next_db: for (dbidx=request->next_dbidx; (dbidx < no_of_databases && !databases[dbidx].db_type); dbidx++) ; request->next_dbidx = dbidx; if (!(dbidx < no_of_databases)) { /* All databases have been searched. Put the non-found mark * into the cache for all descriptors. * FIXME: We need to see which pubkey type we need to insert. */ be_cache_not_found (ctrl, PUBKEY_TYPE_UNKNOWN, desc, ndesc); err = gpg_error (GPG_ERR_NOT_FOUND); goto leave; } db = databases + dbidx; /* Divert to the backend for the actual search. */ switch (db->db_type) { case DB_TYPE_NONE: /* NOTREACHED */ err = gpg_error (GPG_ERR_INTERNAL); break; case DB_TYPE_CACHE: err = be_cache_search (ctrl, db->backend_handle, request, desc, ndesc); /* Expected error codes from the cache lookup are: * 0 - found and returned via the cache * GPG_ERR_NOT_FOUND - marked in the cache as not available * GPG_ERR_EOF - cache miss. */ break; case DB_TYPE_KBX: if (start_at_ubid) { /* 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", __func__, strdbtype (db->db_type), gpg_strerror (err)); break; } } err = be_kbx_search (ctrl, db->backend_handle, request, desc, ndesc); if (start_at_ubid && gpg_err_code (err) == GPG_ERR_EOF) be_cache_mark_final (ctrl, request); break; } if (DBG_LOOKUP) log_debug ("%s: searched %s (db %u of %u) => %s\n", __func__, strdbtype (db->db_type), dbidx, no_of_databases, gpg_strerror (err)); request->any_search = 1; start_at_ubid = 0; if (!err) { request->any_found = 1; } else if (gpg_err_code (err) == GPG_ERR_EOF) { if (db->db_type == DB_TYPE_CACHE && request->last_cached_valid) { if (request->last_cached_final) goto leave; start_at_ubid = 1; } request->next_dbidx++; goto next_db; } leave: release_lock (ctrl); if (DBG_CLOCK) 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 @@ -1,36 +1,38 @@ /* frontend.h - Definitions for the keyboxd frontend * Copyright (C) 2019 g10 Code GmbH * * 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 3 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, see . */ #ifndef KBX_FRONTEND_H #define KBX_FRONTEND_H #include "keybox-search-desc.h" gpg_error_t kbxd_add_resource (ctrl_t ctrl, const char *filename_arg, int readonly); 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 @@ -1,775 +1,825 @@ /* kbxserver.c - Handle Assuan commands send to the keyboxd * Copyright (C) 2019 g10 Code GmbH * * 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 3 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, see . * SPDX-License-Identifier: GPL-3.0+ */ #include #include #include #include #include #include #include #include #include #include #include "keyboxd.h" #include #include "../common/i18n.h" #include "../common/server-help.h" #include "../common/userids.h" #include "../common/asshelp.h" #include "../common/host2net.h" #include "frontend.h" #define PARM_ERROR(t) assuan_set_error (ctx, \ gpg_error (GPG_ERR_ASS_PARAMETER), (t)) #define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \ /**/: gpg_error (e)) /* Control structure per connection. */ struct server_local_s { /* Data used to associate an Assuan context with local server data */ assuan_context_t assuan_ctx; /* The session id (a counter). */ unsigned int session_id; /* If this flag is set to true this process will be terminated after * the end of this session. */ int stopme; /* If the first both flags are set the assuan logging of data lines * is suppressed. The count variable is used to show the number of * non-logged bytes. */ size_t inhibit_data_logging_count; unsigned int inhibit_data_logging : 1; unsigned int inhibit_data_logging_now : 1; /* This flag is set if the last search command was called with --more. */ unsigned int search_expecting_more : 1; /* This flag is set if the last search command was successful. */ unsigned int search_any_found : 1; /* The first is the current search description as parsed by the * cmd_search. If more than one pattern is required, cmd_search * also allocates and sets multi_search_desc and * multi_search_desc_len. If a search description has ever been * allocated the allocated size is stored at * multi_search_desc_size. */ KEYBOX_SEARCH_DESC search_desc; KEYBOX_SEARCH_DESC *multi_search_desc; unsigned int multi_search_desc_size; unsigned int multi_search_desc_len; /* If not NULL write output to this stream instead of using D lines. */ estream_t outstream; }; /* Return the assuan contxt from the local server info in CTRL. */ static assuan_context_t get_assuan_ctx_from_ctrl (ctrl_t ctrl) { if (!ctrl || !ctrl->server_local) return NULL; return ctrl->server_local->assuan_ctx; } /* If OUTPUT has been used prepare the output FD for use. This needs * to be called by all functions which will in any way use * kbxd_write_data_line later. Whether the output goes to the output * stream is decided by this function. */ static gpg_error_t prepare_outstream (ctrl_t ctrl) { int fd; log_assert (ctrl && ctrl->server_local); if (ctrl->server_local->outstream) return 0; /* Already enabled. */ fd = translate_sys2libc_fd (assuan_get_output_fd (get_assuan_ctx_from_ctrl (ctrl)), 1); if (fd == -1) return 0; /* No Output command active. */ ctrl->server_local->outstream = es_fdopen_nc (fd, "w"); if (!ctrl->server_local->outstream) return gpg_err_code_from_syserror (); return 0; } /* The usual writen function; here with diagnostic output. */ static gpg_error_t kbxd_writen (estream_t fp, const void *buffer, size_t length) { gpg_error_t err; size_t nwritten; if (es_write (fp, buffer, length, &nwritten)) { err = gpg_error_from_syserror (); log_error ("error writing OUTPUT: %s\n", gpg_strerror (err)); } else if (length != nwritten) { err = gpg_error (GPG_ERR_EIO); log_error ("error writing OUTPUT: %s\n", "short write"); } else err = 0; return err; } /* A wrapper around assuan_send_data which makes debugging the output * in verbose mode easier. It also takes CTRL as argument. */ gpg_error_t kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size) { const char *buffer = buffer_arg; assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl); gpg_error_t err; if (!ctx) /* Oops - no assuan context. */ return gpg_error (GPG_ERR_NOT_PROCESSED); /* Write toa file descriptor if enabled. */ if (ctrl && ctrl->server_local && ctrl->server_local->outstream) { unsigned char lenbuf[4]; ulongtobuf (lenbuf, size); err = kbxd_writen (ctrl->server_local->outstream, lenbuf, 4); if (!err) err = kbxd_writen (ctrl->server_local->outstream, buffer, size); if (!err && es_fflush (ctrl->server_local->outstream)) { err = gpg_error_from_syserror (); log_error ("error writing OUTPUT: %s\n", gpg_strerror (err)); } goto leave; } /* If we do not want logging, enable it here. */ if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) ctrl->server_local->inhibit_data_logging_now = 1; if (0 && opt.verbose && buffer && size) { /* Ease reading of output by limiting the line length. */ size_t n, nbytes; nbytes = size; do { n = nbytes > 64? 64 : nbytes; err = assuan_send_data (ctx, buffer, n); if (err) { gpg_err_set_errno (EIO); goto leave; } buffer += n; nbytes -= n; if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */ { gpg_err_set_errno (EIO); goto leave; } } while (nbytes); } else { err = assuan_send_data (ctx, buffer, size); if (err) { gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */ goto leave; } } leave: if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging) { ctrl->server_local->inhibit_data_logging_count += size; ctrl->server_local->inhibit_data_logging_now = 0; } return err; } /* Helper to print a message while leaving a command. */ static gpg_error_t leave_cmd (assuan_context_t ctx, gpg_error_t err) { if (err && opt.verbose) { const char *name = assuan_get_command_name (ctx); if (!name) name = "?"; if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT) log_error ("command '%s' failed: %s\n", name, gpg_strerror (err)); else log_error ("command '%s' failed: %s <%s>\n", name, gpg_strerror (err), gpg_strsource (err)); } return err; } /* Handle OPTION commands. */ static gpg_error_t option_handler (assuan_context_t ctx, const char *key, const char *value) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err = 0; if (!strcmp (key, "lc-messages")) { if (ctrl->lc_messages) xfree (ctrl->lc_messages); ctrl->lc_messages = xtrystrdup (value); if (!ctrl->lc_messages) return out_of_core (); } else err = gpg_error (GPG_ERR_UNKNOWN_OPTION); return err; } static const char hlp_search[] = "SEARCH [--no-data] [[--more] PATTERN]\n" "\n" "Search for the keys identified by PATTERN. With --more more\n" "patterns to be used for the search are expected with the next\n" "command. With --no-data only the search status is returned but\n" "not the actual data. See also \"NEXT\"."; static gpg_error_t cmd_search (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int opt_more, opt_no_data; gpg_error_t err; unsigned int n, k; opt_no_data = has_option (line, "--no-data"); opt_more = has_option (line, "--more"); line = skip_options (line); ctrl->server_local->search_any_found = 0; if (!*line) { if (opt_more) { err = set_error (GPG_ERR_INV_ARG, "--more but no pattern"); goto leave; } else if (!*line && ctrl->server_local->search_expecting_more) { /* It would be too surprising to first set a pattern but * finally add no pattern to search the entire DB. */ err = set_error (GPG_ERR_INV_ARG, "--more pending but no pattern"); goto leave; } else /* No pattern - return the first item. */ { memset (&ctrl->server_local->search_desc, 0, sizeof ctrl->server_local->search_desc); ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_FIRST; } } else { err = classify_user_id (line, &ctrl->server_local->search_desc, 0); if (err) goto leave; } if (opt_more || ctrl->server_local->search_expecting_more) { /* More pattern are expected - store the current one and return * success. */ if (!ctrl->server_local->multi_search_desc_size) { n = 10; ctrl->server_local->multi_search_desc = xtrycalloc (n, sizeof *ctrl->server_local->multi_search_desc); if (!ctrl->server_local->multi_search_desc) { err = gpg_error_from_syserror (); goto leave; } ctrl->server_local->multi_search_desc_size = n; } if (ctrl->server_local->multi_search_desc_len == ctrl->server_local->multi_search_desc_size) { KEYBOX_SEARCH_DESC *desc; n = ctrl->server_local->multi_search_desc_size + 10; desc = xtrycalloc (n, sizeof *desc); if (!desc) { err = gpg_error_from_syserror (); goto leave; } for (k=0; k < ctrl->server_local->multi_search_desc_size; k++) desc[k] = ctrl->server_local->multi_search_desc[k]; xfree (ctrl->server_local->multi_search_desc); ctrl->server_local->multi_search_desc = desc; ctrl->server_local->multi_search_desc_size = n; } /* Actually store. */ ctrl->server_local->multi_search_desc [ctrl->server_local->multi_search_desc_len++] = ctrl->server_local->search_desc; if (opt_more) { /* We need to be called aagain with more pattern. */ ctrl->server_local->search_expecting_more = 1; goto leave; } ctrl->server_local->search_expecting_more = 0; /* Continue with the actual search. */ } else ctrl->server_local->multi_search_desc_len = 0; ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; ctrl->no_data_return = opt_no_data; err = prepare_outstream (ctrl); if (err) ; else if (ctrl->server_local->multi_search_desc_len) err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc, ctrl->server_local->multi_search_desc_len, 1); else err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 1); if (err) goto leave; /* Set a flag for use by NEXT. */ ctrl->server_local->search_any_found = 1; leave: if (err) ctrl->server_local->multi_search_desc_len = 0; ctrl->no_data_return = 0; ctrl->server_local->inhibit_data_logging = 0; return leave_cmd (ctx, err); } static const char hlp_next[] = "NEXT [--no-data]\n" "\n" "Get the next search result from a previus search."; static gpg_error_t cmd_next (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); int opt_no_data; gpg_error_t err; opt_no_data = has_option (line, "--no-data"); line = skip_options (line); if (*line) { err = set_error (GPG_ERR_INV_ARG, "no args expected"); goto leave; } if (!ctrl->server_local->search_any_found) { err = set_error (GPG_ERR_NOTHING_FOUND, "no previous SEARCH"); goto leave; } ctrl->server_local->inhibit_data_logging = 1; ctrl->server_local->inhibit_data_logging_now = 0; ctrl->server_local->inhibit_data_logging_count = 0; ctrl->no_data_return = opt_no_data; err = prepare_outstream (ctrl); if (err) ; else if (ctrl->server_local->multi_search_desc_len) { /* The next condition should never be tru but we better handle * the first/next transition anyway. */ if (ctrl->server_local->multi_search_desc[0].mode == KEYDB_SEARCH_MODE_FIRST) ctrl->server_local->multi_search_desc[0].mode = KEYDB_SEARCH_MODE_NEXT; err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc, ctrl->server_local->multi_search_desc_len, 0); } else { /* We need to do the transition from first to next here. */ if (ctrl->server_local->search_desc.mode == KEYDB_SEARCH_MODE_FIRST) ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_NEXT; err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0); } if (err) goto leave; leave: ctrl->no_data_return = 0; ctrl->server_local->inhibit_data_logging = 0; return leave_cmd (ctx, err); } +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" "\n" "Multi purpose command to return certain information. \n" "Supported values of WHAT are:\n" "\n" "version - Return the version of the program.\n" "pid - Return the process id of the server.\n" "socket_name - Return the name of the socket.\n" "session_id - Return the current session_id.\n" "getenv NAME - Return value of envvar NAME\n"; static gpg_error_t cmd_getinfo (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); gpg_error_t err; char numbuf[50]; if (!strcmp (line, "version")) { const char *s = VERSION; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "pid")) { snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ()); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strcmp (line, "socket_name")) { const char *s = get_kbxd_socket_name (); if (!s) s = "[none]"; err = assuan_send_data (ctx, s, strlen (s)); } else if (!strcmp (line, "session_id")) { snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id); err = assuan_send_data (ctx, numbuf, strlen (numbuf)); } else if (!strncmp (line, "getenv", 6) && (line[6] == ' ' || line[6] == '\t' || !line[6])) { line += 6; while (*line == ' ' || *line == '\t') line++; if (!*line) err = gpg_error (GPG_ERR_MISSING_VALUE); else { const char *s = getenv (line); if (!s) err = set_error (GPG_ERR_NOT_FOUND, "No such envvar"); else err = assuan_send_data (ctx, s, strlen (s)); } } else err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT"); return leave_cmd (ctx, err); } static const char hlp_killkeyboxd[] = "KILLKEYBOXD\n" "\n" "This command allows a user - given sufficient permissions -\n" "to kill this keyboxd process.\n"; static gpg_error_t cmd_killkeyboxd (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; ctrl->server_local->stopme = 1; assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); return gpg_error (GPG_ERR_EOF); } static const char hlp_reloadkeyboxd[] = "RELOADKEYBOXD\n" "\n" "This command is an alternative to SIGHUP\n" "to reload the configuration."; static gpg_error_t cmd_reloadkeyboxd (assuan_context_t ctx, char *line) { (void)ctx; (void)line; kbxd_sighup_action (); return 0; } static const char hlp_output[] = "OUTPUT FD[=]\n" "\n" "Set the file descriptor to write the output data to N. If N is not\n" "given and the operating system supports file descriptor passing, the\n" "file descriptor currently in flight will be used."; /* Tell the assuan library about our commands. */ static int register_commands (assuan_context_t ctx) { static struct { const char *name; assuan_handler_t handler; const char * const help; } 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 }, { "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd }, { NULL, NULL } }; int i, j, rc; for (i=j=0; table[i].name; i++) { rc = assuan_register_command (ctx, table[i].name, table[i].handler, table[i].help); if (rc) return rc; } return 0; } /* Note that we do not reset the list of configured keyservers. */ static gpg_error_t reset_notify (assuan_context_t ctx, char *line) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)line; (void)ctrl; return 0; } /* This function is called by our assuan log handler to test whether a * log message shall really be printed. The function must return * false to inhibit the logging of MSG. CAT gives the requested log * category. MSG might be NULL. */ int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat, const char *msg) { ctrl_t ctrl = assuan_get_pointer (ctx); (void)cat; (void)msg; if (!ctrl || !ctrl->server_local) return 1; /* Can't decide - allow logging. */ if (!ctrl->server_local->inhibit_data_logging) return 1; /* Not requested - allow logging. */ /* Disallow logging if *_now is true. */ return !ctrl->server_local->inhibit_data_logging_now; } /* Startup the server and run the main command loop. With FD = -1, * use stdin/stdout. SESSION_ID is either 0 or a unique number * identifying a session. */ void kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id) { static const char hello[] = "Keyboxd " VERSION " at your service"; static char *hello_line; int rc; assuan_context_t ctx; ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local); if (!ctrl->server_local) { log_error (_("can't allocate control structure: %s\n"), gpg_strerror (gpg_error_from_syserror ())); xfree (ctrl); return; } rc = assuan_new (&ctx); if (rc) { log_error (_("failed to allocate assuan context: %s\n"), gpg_strerror (rc)); kbxd_exit (2); } if (fd == GNUPG_INVALID_FD) { assuan_fd_t filedes[2]; filedes[0] = assuan_fdopen (0); filedes[1] = assuan_fdopen (1); rc = assuan_init_pipe_server (ctx, filedes); } else { rc = assuan_init_socket_server (ctx, fd, (ASSUAN_SOCKET_SERVER_ACCEPTED |ASSUAN_SOCKET_SERVER_FDPASSING)); } if (rc) { assuan_release (ctx); log_error (_("failed to initialize the server: %s\n"), gpg_strerror (rc)); kbxd_exit (2); } rc = register_commands (ctx); if (rc) { log_error (_("failed to the register commands with Assuan: %s\n"), gpg_strerror(rc)); kbxd_exit (2); } if (!hello_line) { hello_line = xtryasprintf ("Home: %s\n" "Config: %s\n" "%s", gnupg_homedir (), /*opt.config_filename? opt.config_filename :*/ "[none]", hello); } ctrl->server_local->assuan_ctx = ctx; assuan_set_pointer (ctx, ctrl); assuan_set_hello_line (ctx, hello_line); assuan_register_option_handler (ctx, option_handler); assuan_register_reset_notify (ctx, reset_notify); ctrl->server_local->session_id = session_id; /* The next call enable the use of status_printf. */ set_assuan_context_func (get_assuan_ctx_from_ctrl); for (;;) { rc = assuan_accept (ctx); if (rc == -1) break; if (rc) { log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc)); break; } #ifndef HAVE_W32_SYSTEM if (opt.verbose) { assuan_peercred_t peercred; if (!assuan_get_peercred (ctx, &peercred)) log_info ("connection from process %ld (%ld:%ld)\n", (long)peercred->pid, (long)peercred->uid, (long)peercred->gid); } #endif rc = assuan_process (ctx); if (rc) { log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc)); continue; } } assuan_close_output_fd (ctx); set_assuan_context_func (NULL); ctrl->server_local->assuan_ctx = NULL; assuan_release (ctx); if (ctrl->server_local->stopme) kbxd_exit (0); if (ctrl->refcount) log_error ("oops: connection control structure still referenced (%d)\n", ctrl->refcount); else { xfree (ctrl->server_local->multi_search_desc); xfree (ctrl->server_local); ctrl->server_local = NULL; } } 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 @@ -1,682 +1,682 @@ /* keybox-openpgp.c - OpenPGP key parsing * Copyright (C) 2001, 2003, 2011 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 3 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, see . */ /* This is a simple OpenPGP parser suitable for all OpenPGP key material. It just provides the functionality required to build and parse an KBX OpenPGP key blob. Thus it is not a complete parser. However it is self-contained and optimized for fast in-memory parsing. Note that we don't support old ElGamal v3 keys anymore. */ #include #include #include #include #include #include #include "keybox-defs.h" #include #include "../common/openpgpdefs.h" #include "../common/host2net.h" struct keyparm_s { const char *mpi; int len; /* int to avoid a cast in gcry_sexp_build. */ }; /* Assume a valid OpenPGP packet at the address pointed to by BUFBTR which has a maximum length as stored at BUFLEN. Return the header information of that packet and advance the pointer stored at BUFPTR to the next packet; also adjust the length stored at BUFLEN to match the remaining bytes. If there are no more packets, store NULL at BUFPTR. Return an non-zero error code on failure or the following data on success: R_DATAPKT = Pointer to the begin of the packet data. R_DATALEN = Length of this data. This has already been checked to fit into the buffer. R_PKTTYPE = The packet type. R_NTOTAL = The total number of bytes of this packet Note that these values are only updated on success. */ static gpg_error_t next_packet (unsigned char const **bufptr, size_t *buflen, unsigned char const **r_data, size_t *r_datalen, int *r_pkttype, size_t *r_ntotal) { const unsigned char *buf = *bufptr; size_t len = *buflen; int c, ctb, pkttype; unsigned long pktlen; if (!len) return gpg_error (GPG_ERR_NO_DATA); ctb = *buf++; len--; if ( !(ctb & 0x80) ) return gpg_error (GPG_ERR_INV_PACKET); /* Invalid CTB. */ if ((ctb & 0x40)) /* New style (OpenPGP) CTB. */ { pkttype = (ctb & 0x3f); if (!len) return gpg_error (GPG_ERR_INV_PACKET); /* No 1st length byte. */ c = *buf++; len--; if (pkttype == PKT_COMPRESSED) return gpg_error (GPG_ERR_UNEXPECTED); /* ... packet in a keyblock. */ if ( c < 192 ) pktlen = c; else if ( c < 224 ) { pktlen = (c - 192) * 256; if (!len) return gpg_error (GPG_ERR_INV_PACKET); /* No 2nd length byte. */ c = *buf++; len--; pktlen += c + 192; } else if (c == 255) { if (len <4 ) return gpg_error (GPG_ERR_INV_PACKET); /* No length bytes. */ pktlen = buf32_to_ulong (buf); buf += 4; len -= 4; } else /* Partial length encoding is not allowed for key packets. */ return gpg_error (GPG_ERR_UNEXPECTED); } else /* Old style CTB. */ { int lenbytes; pktlen = 0; pkttype = (ctb>>2)&0xf; lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3)); if (!lenbytes) /* Not allowed in key packets. */ return gpg_error (GPG_ERR_UNEXPECTED); if (len < lenbytes) return gpg_error (GPG_ERR_INV_PACKET); /* Not enough length bytes. */ for (; lenbytes; lenbytes--) { pktlen <<= 8; pktlen |= *buf++; len--; } } /* Do some basic sanity check. */ switch (pkttype) { case PKT_SIGNATURE: case PKT_SECRET_KEY: case PKT_PUBLIC_KEY: case PKT_SECRET_SUBKEY: case PKT_MARKER: case PKT_RING_TRUST: case PKT_USER_ID: case PKT_PUBLIC_SUBKEY: case PKT_OLD_COMMENT: case PKT_ATTRIBUTE: case PKT_COMMENT: case PKT_GPG_CONTROL: break; /* Okay these are allowed packets. */ default: return gpg_error (GPG_ERR_UNEXPECTED); } if (pkttype == 63 && pktlen == 0xFFFFFFFF) /* Sometimes the decompressing layer enters an error state in which it simply outputs 0xff for every byte read. If we have a stream of 0xff bytes, then it will be detected as a new format packet with type 63 and a 4-byte encoded length that is 4G-1. Since packets with type 63 are private and we use them as a control packet, which won't be 4 GB, we reject such packets as invalid. */ return gpg_error (GPG_ERR_INV_PACKET); if (pktlen > len) return gpg_error (GPG_ERR_INV_PACKET); /* Packet length header too long. */ *r_data = buf; *r_datalen = pktlen; *r_pkttype = pkttype; *r_ntotal = (buf - *bufptr) + pktlen; *bufptr = buf + pktlen; *buflen = len - pktlen; if (!*buflen) *bufptr = NULL; return 0; } /* Take a list of key parameters KP for the OpenPGP ALGO and compute * the keygrip which will be stored at GRIP. GRIP needs to be a * buffer of 20 bytes. */ static gpg_error_t keygrip_from_keyparm (int algo, struct keyparm_s *kp, unsigned char *grip) { gpg_error_t err; gcry_sexp_t s_pkey = NULL; switch (algo) { case PUBKEY_ALGO_DSA: err = gcry_sexp_build (&s_pkey, NULL, "(public-key(dsa(p%b)(q%b)(g%b)(y%b)))", kp[0].len, kp[0].mpi, kp[1].len, kp[1].mpi, kp[2].len, kp[2].mpi, kp[3].len, kp[3].mpi); break; case PUBKEY_ALGO_ELGAMAL: case PUBKEY_ALGO_ELGAMAL_E: err = gcry_sexp_build (&s_pkey, NULL, "(public-key(elg(p%b)(g%b)(y%b)))", kp[0].len, kp[0].mpi, kp[1].len, kp[1].mpi, kp[2].len, kp[2].mpi); break; case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_S: case PUBKEY_ALGO_RSA_E: err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))", kp[0].len, kp[0].mpi, kp[1].len, kp[1].mpi); break; case PUBKEY_ALGO_EDDSA: case PUBKEY_ALGO_ECDSA: case PUBKEY_ALGO_ECDH: { char *curve = openpgp_oidbuf_to_str (kp[0].mpi, kp[0].len); if (!curve) err = gpg_error_from_syserror (); else { err = gcry_sexp_build (&s_pkey, NULL, (algo == PUBKEY_ALGO_EDDSA)? "(public-key(ecc(curve%s)(flags eddsa)(q%b)))": (algo == PUBKEY_ALGO_ECDH && openpgp_oidbuf_is_cv25519 (kp[0].mpi, kp[0].len))? "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))": "(public-key(ecc(curve%s)(q%b)))", curve, kp[1].len, kp[1].mpi); xfree (curve); } } break; default: err = gpg_error (GPG_ERR_PUBKEY_ALGO); break; } if (!err && !gcry_pk_get_keygrip (s_pkey, grip)) { log_info ("kbx: error computing keygrip\n"); err = gpg_error (GPG_ERR_GENERAL); } gcry_sexp_release (s_pkey); if (err) memset (grip, 0, 20); return err; } /* Parse a key packet and store the information in KI. */ static gpg_error_t parse_key (const unsigned char *data, size_t datalen, struct _keybox_openpgp_key_info *ki) { gpg_error_t err; const unsigned char *data_start = data; int i, version, algorithm; size_t n; int npkey; unsigned char hashbuffer[768]; gcry_md_hd_t md; int is_ecc = 0; int is_v5; /* unsigned int pkbytes; for v5: # of octets of the public key params. */ struct keyparm_s keyparm[OPENPGP_MAX_NPKEY]; unsigned char *helpmpibuf[OPENPGP_MAX_NPKEY] = { NULL }; if (datalen < 5) return gpg_error (GPG_ERR_INV_PACKET); version = *data++; datalen--; if (version < 2 || version > 5 ) return gpg_error (GPG_ERR_INV_PACKET); /* Invalid version. */ is_v5 = version == 5; /*timestamp = ((data[0]<<24)|(data[1]<<16)|(data[2]<<8)|(data[3]));*/ data +=4; datalen -=4; if (version < 4) { if (datalen < 2) return gpg_error (GPG_ERR_INV_PACKET); data +=2; datalen -= 2; } if (!datalen) return gpg_error (GPG_ERR_INV_PACKET); algorithm = *data++; datalen--; if (is_v5) { if (datalen < 4) return gpg_error (GPG_ERR_INV_PACKET); /* pkbytes = buf32_to_uint (data); */ data += 4; datalen -= 4; } switch (algorithm) { case PUBKEY_ALGO_RSA: case PUBKEY_ALGO_RSA_E: case PUBKEY_ALGO_RSA_S: npkey = 2; break; case PUBKEY_ALGO_ELGAMAL_E: case PUBKEY_ALGO_ELGAMAL: npkey = 3; break; case PUBKEY_ALGO_DSA: npkey = 4; break; case PUBKEY_ALGO_ECDH: npkey = 3; is_ecc = 1; break; case PUBKEY_ALGO_ECDSA: case PUBKEY_ALGO_EDDSA: npkey = 2; is_ecc = 1; break; default: /* Unknown algorithm. */ return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM); } ki->version = version; ki->algo = algorithm; for (i=0; i < npkey; i++ ) { unsigned int nbits, nbytes; if (datalen < 2) return gpg_error (GPG_ERR_INV_PACKET); if (is_ecc && (i == 0 || i == 2)) { nbytes = data[0]; if (nbytes < 2 || nbytes > 254) return gpg_error (GPG_ERR_INV_PACKET); nbytes++; /* The size byte itself. */ if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); keyparm[i].mpi = data; keyparm[i].len = nbytes; } else { nbits = ((data[0]<<8)|(data[1])); data += 2; datalen -= 2; nbytes = (nbits+7) / 8; if (datalen < nbytes) return gpg_error (GPG_ERR_INV_PACKET); keyparm[i].mpi = data; keyparm[i].len = nbytes; } data += nbytes; datalen -= nbytes; } n = data - data_start; /* Note: Starting here we need to jump to leave on error. */ /* Make sure the MPIs are unsigned. */ for (i=0; i < npkey; i++) { if (!keyparm[i].len || (keyparm[i].mpi[0] & 0x80)) { helpmpibuf[i] = xtrymalloc (1+keyparm[i].len); if (!helpmpibuf[i]) { err = gpg_error_from_syserror (); goto leave; } helpmpibuf[i][0] = 0; memcpy (helpmpibuf[i]+1, keyparm[i].mpi, keyparm[i].len); keyparm[i].mpi = helpmpibuf[i]; keyparm[i].len++; } } err = keygrip_from_keyparm (algorithm, keyparm, ki->grip); if (err) goto leave; if (version < 4) { /* We do not support any other algorithm than RSA in v3 packets. */ if (algorithm < 1 || algorithm > 3) return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); err = gcry_md_open (&md, GCRY_MD_MD5, 0); if (err) return err; /* Oops */ gcry_md_write (md, keyparm[0].mpi, keyparm[0].len); gcry_md_write (md, keyparm[1].mpi, keyparm[1].len); memcpy (ki->fpr, gcry_md_read (md, 0), 16); gcry_md_close (md); ki->fprlen = 16; if (keyparm[0].len < 8) { /* Moduli less than 64 bit are out of the specs scope. Zero them out because this is what gpg does too. */ memset (ki->keyid, 0, 8); } else memcpy (ki->keyid, keyparm[0].mpi + keyparm[0].len - 8, 8); } else { /* Its a pity that we need to prefix the buffer with the tag and a length header: We can't simply pass it to the fast hashing function for that reason. It might be a good idea to have a scatter-gather enabled hash function. What we do here is to use a static buffer if this one is large enough and only use the regular hash functions if this buffer is not large enough. FIXME: Factor this out to a shared fingerprint function. */ if (version == 5) { if ( 5 + n < sizeof hashbuffer ) { hashbuffer[0] = 0x9a; /* CTB */ hashbuffer[1] = (n >> 24);/* 4 byte length header. */ hashbuffer[2] = (n >> 16); hashbuffer[3] = (n >> 8); hashbuffer[4] = (n ); memcpy (hashbuffer + 5, data_start, n); gcry_md_hash_buffer (GCRY_MD_SHA256, ki->fpr, hashbuffer, 5 + n); } else { err = gcry_md_open (&md, GCRY_MD_SHA256, 0); if (err) return err; /* Oops */ gcry_md_putc (md, 0x9a ); /* CTB */ gcry_md_putc (md, (n >> 24)); /* 4 byte length header. */ gcry_md_putc (md, (n >> 16)); gcry_md_putc (md, (n >> 8)); gcry_md_putc (md, (n )); gcry_md_write (md, data_start, n); memcpy (ki->fpr, gcry_md_read (md, 0), 32); gcry_md_close (md); } ki->fprlen = 32; memcpy (ki->keyid, ki->fpr, 8); } else { if ( 3 + n < sizeof hashbuffer ) { hashbuffer[0] = 0x99; /* CTB */ hashbuffer[1] = (n >> 8); /* 2 byte length header. */ hashbuffer[2] = (n ); memcpy (hashbuffer + 3, data_start, n); gcry_md_hash_buffer (GCRY_MD_SHA1, ki->fpr, hashbuffer, 3 + n); } else { err = gcry_md_open (&md, GCRY_MD_SHA1, 0); if (err) return err; /* Oops */ gcry_md_putc (md, 0x99 ); /* CTB */ gcry_md_putc (md, (n >> 8)); /* 2 byte length header. */ gcry_md_putc (md, (n )); gcry_md_write (md, data_start, n); memcpy (ki->fpr, gcry_md_read (md, 0), 20); gcry_md_close (md); } ki->fprlen = 20; memcpy (ki->keyid, ki->fpr+12, 8); } } leave: for (i=0; i < npkey; i++) xfree (helpmpibuf[i]); return err; } /* The caller must pass the address of an INFO structure which will get filled on success with information pertaining to the OpenPGP keyblock IMAGE of length IMAGELEN. Note that a caller does only need to release this INFO structure if the function returns success. If NPARSED is not NULL the actual number of bytes parsed will be stored at this address. */ gpg_error_t _keybox_parse_openpgp (const unsigned char *image, size_t imagelen, size_t *nparsed, keybox_openpgp_info_t info) { gpg_error_t err = 0; const unsigned char *image_start, *data; size_t n, datalen; int pkttype; int first = 1; int read_error = 0; struct _keybox_openpgp_key_info *k, **ktail = NULL; struct _keybox_openpgp_uid_info *u, **utail = NULL; memset (info, 0, sizeof *info); if (nparsed) *nparsed = 0; image_start = image; while (image) { err = next_packet (&image, &imagelen, &data, &datalen, &pkttype, &n); if (err) { read_error = 1; break; } if (first) { if (pkttype == PKT_PUBLIC_KEY) ; else if (pkttype == PKT_SECRET_KEY) info->is_secret = 1; else { err = gpg_error (GPG_ERR_UNEXPECTED); if (nparsed) *nparsed += n; break; } first = 0; } else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) break; /* Next keyblock encountered - ready. */ if (nparsed) *nparsed += n; if (pkttype == PKT_SIGNATURE) { /* For now we only count the total number of signatures. */ info->nsigs++; } else if (pkttype == PKT_USER_ID) { info->nuids++; if (info->nuids == 1) { info->uids.off = data - image_start; info->uids.len = datalen; utail = &info->uids.next; } else { u = xtrycalloc (1, sizeof *u); if (!u) { err = gpg_error_from_syserror (); break; } u->off = data - image_start; u->len = datalen; *utail = u; utail = &u->next; } } else if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) { err = parse_key (data, datalen, &info->primary); if (err) break; } else if( pkttype == PKT_PUBLIC_SUBKEY && datalen && *data == '#' ) { /* Early versions of GnuPG used old PGP comment packets; * luckily all those comments are prefixed by a hash * sign - ignore these packets. */ } else if (pkttype == PKT_PUBLIC_SUBKEY || pkttype == PKT_SECRET_SUBKEY) { info->nsubkeys++; if (info->nsubkeys == 1) { err = parse_key (data, datalen, &info->subkeys); if (err) { info->nsubkeys--; /* We ignore subkeys with unknown algorithms. */ if (gpg_err_code (err) == GPG_ERR_UNKNOWN_ALGORITHM || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM) err = 0; if (err) break; } else ktail = &info->subkeys.next; } else { k = xtrycalloc (1, sizeof *k); if (!k) { err = gpg_error_from_syserror (); break; } err = parse_key (data, datalen, k); if (err) { xfree (k); info->nsubkeys--; /* We ignore subkeys with unknown algorithms. */ if (gpg_err_code (err) == GPG_ERR_UNKNOWN_ALGORITHM || gpg_err_code (err) == GPG_ERR_UNSUPPORTED_ALGORITHM) err = 0; if (err) break; } else { *ktail = k; ktail = &k->next; } } } } if (err) { _keybox_destroy_openpgp_info (info); if (!read_error) { /* Packet parsing worked, thus we should be able to skip the rest of the keyblock. */ while (image) { if (next_packet (&image, &imagelen, &data, &datalen, &pkttype, &n) ) break; /* Another error - stop here. */ if (pkttype == PKT_PUBLIC_KEY || pkttype == PKT_SECRET_KEY) break; /* Next keyblock encountered - ready. */ if (nparsed) *nparsed += n; } } } return err; } /* Release any malloced data in INFO but not INFO itself! */ void _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; xfree (k); } for (u=info->uids.next; u; u = u2) { u2 = u->next; xfree (u); } }