mirror of
git://git.gnupg.org/gnupg.git
synced 2024-12-22 10:19:57 +01:00
0e892bda4e
* kbx/backend-sqlite.c (table_definitions): Add column UINO to userids. (be_sqlite_local_s): Add fields select_col_uidno and select_col_subkey. (run_select_statement): Also select subkey or uidno column. (be_sqlite_search): Return their values. (store_into_userid): Store the UIDNO. * kbx/backend-support.c (be_return_pubkey): Extend PUBKEY_INFO. -- For an existing database adding the new column to the table userid is straightforward. However if the original version of the schema used an integer for the keyid column, that column has likely be renamed. Make sure that the NOT NULL constraint has also be removed; check the SQLite documentation on how this can be done. Signed-off-by: Werner Koch <wk@gnupg.org>
458 lines
12 KiB
C
458 lines
12 KiB
C
/* 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 <https://www.gnu.org/licenses/>.
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#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 (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 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;
|
|
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;
|
|
}
|