| /* 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 (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; |
| } |