diff --git a/kbx/backend-kbx.c b/kbx/backend-kbx.c index eff64cfa3..7674cdf2c 100644 --- a/kbx/backend-kbx.c +++ b/kbx/backend-kbx.c @@ -1,457 +1,457 @@ /* 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 available 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 (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; + unsigned long skipped_long_blobs = 0; 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 ubid[UBID_LEN]; err = keybox_get_data (part->kbx_hd, &buffer, &buflen, &pubkey_type, ubid); if (err) goto leave; /* FIXME: Return the ephemeral flag. */ err = be_return_pubkey (ctrl, buffer, buflen, pubkey_type, ubid, 0, 0, 0, 0); if (!err) be_cache_pubkey (ctrl, ubid, buffer, buflen, pubkey_type); xfree (buffer); } leave: return err; } /* 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, const unsigned char *ubid) { gpg_error_t err; db_request_part_t part; size_t descindex; - unsigned long skipped_long_blobs; + unsigned long skipped_long_blobs = 0; 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, UBID_LEN); /* 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; } /* Update (BLOB,BLOBLEN) in the keybox. BACKEND_HD is the handle for * this backend and REQUEST is the current database request object. */ gpg_error_t be_kbx_update (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; /* FIXME: We make use of the fact that we know that the caller * already did a keybox search. This needs to be made more * explicit. */ if (pktype == PUBKEY_TYPE_OPGP) { err = keybox_update_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_update_cert (part->kbx_hd, cert, sha1); } else err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE); leave: ksba_cert_release (cert); return err; } /* Delete the blob from the keybox. BACKEND_HD is the handle for * this backend and REQUEST is the current database request object. */ gpg_error_t be_kbx_delete (ctrl_t ctrl, backend_handle_t backend_hd, db_request_t request) { gpg_error_t err; db_request_part_t part; (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; /* FIXME: We make use of the fact that we know that the caller * already did a keybox search. This needs to be made more * explicit. */ err = keybox_delete (part->kbx_hd); leave: return err; }