diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:14:06 +0000 |
commit | eee068778cb28ecf3c14e1bf843a95547d72c42d (patch) | |
tree | 0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /kbx/keybox-search.c | |
parent | Initial commit. (diff) | |
download | gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip |
Adding upstream version 2.2.40.upstream/2.2.40upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | kbx/keybox-search.c | 1322 |
1 files changed, 1322 insertions, 0 deletions
diff --git a/kbx/keybox-search.c b/kbx/keybox-search.c new file mode 100644 index 0000000..263a166 --- /dev/null +++ b/kbx/keybox-search.c @@ -0,0 +1,1322 @@ +/* keybox-search.c - Search operations + * Copyright (C) 2001, 2002, 2003, 2004, 2012, + * 2013 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 <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <errno.h> + +#include "keybox-defs.h" +#include <gcrypt.h> +#include "../common/host2net.h" +#include "../common/mbox-util.h" + +#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ + *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) +#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) + + +struct sn_array_s { + int snlen; + unsigned char *sn; +}; + + +#define get32(a) buf32_to_ulong ((a)) +#define get16(a) buf16_to_ulong ((a)) + + +static inline unsigned int +blob_get_blob_flags (KEYBOXBLOB blob) +{ + const unsigned char *buffer; + size_t length; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 8) + return 0; /* oops */ + + return get16 (buffer + 6); +} + + +/* Return the first keyid from the blob. Returns true if + available. */ +static int +blob_get_first_keyid (KEYBOXBLOB blob, u32 *kid) +{ + const unsigned char *buffer; + size_t length, nkeys, keyinfolen; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 48) + return 0; /* blob too short */ + + nkeys = get16 (buffer + 16); + keyinfolen = get16 (buffer + 18); + if (!nkeys || keyinfolen < 28) + return 0; /* invalid blob */ + + kid[0] = get32 (buffer + 32); + kid[1] = get32 (buffer + 36); + + return 1; +} + + +/* Return information on the flag WHAT within the blob BUFFER,LENGTH. + Return the offset and the length (in bytes) of the flag in + FLAGOFF,FLAG_SIZE. */ +gpg_err_code_t +_keybox_get_flag_location (const unsigned char *buffer, size_t length, + int what, size_t *flag_off, size_t *flag_size) +{ + size_t pos; + size_t nkeys, keyinfolen; + size_t nuids, uidinfolen; + size_t nserial; + size_t nsigs, siginfolen, siginfooff; + + switch (what) + { + case KEYBOX_FLAG_BLOB: + if (length < 8) + return GPG_ERR_INV_OBJ; + *flag_off = 6; + *flag_size = 2; + break; + + case KEYBOX_FLAG_OWNERTRUST: + case KEYBOX_FLAG_VALIDITY: + case KEYBOX_FLAG_CREATED_AT: + case KEYBOX_FLAG_SIG_INFO: + if (length < 20) + return GPG_ERR_INV_OBJ; + /* Key info. */ + nkeys = get16 (buffer + 16); + keyinfolen = get16 (buffer + 18 ); + if (keyinfolen < 28) + return GPG_ERR_INV_OBJ; + pos = 20 + keyinfolen*nkeys; + if (pos+2 > length) + return GPG_ERR_INV_OBJ; /* Out of bounds. */ + /* Serial number. */ + nserial = get16 (buffer+pos); + pos += 2 + nserial; + if (pos+4 > length) + return GPG_ERR_INV_OBJ; /* Out of bounds. */ + /* User IDs. */ + nuids = get16 (buffer + pos); pos += 2; + uidinfolen = get16 (buffer + pos); pos += 2; + if (uidinfolen < 12 ) + return GPG_ERR_INV_OBJ; + pos += uidinfolen*nuids; + if (pos+4 > length) + return GPG_ERR_INV_OBJ ; /* Out of bounds. */ + /* Signature info. */ + siginfooff = pos; + nsigs = get16 (buffer + pos); pos += 2; + siginfolen = get16 (buffer + pos); pos += 2; + if (siginfolen < 4 ) + return GPG_ERR_INV_OBJ; + pos += siginfolen*nsigs; + if (pos+1+1+2+4+4+4+4 > length) + return GPG_ERR_INV_OBJ ; /* Out of bounds. */ + *flag_size = 1; + *flag_off = pos; + switch (what) + { + case KEYBOX_FLAG_VALIDITY: + *flag_off += 1; + break; + case KEYBOX_FLAG_CREATED_AT: + *flag_size = 4; + *flag_off += 1+2+4+4+4; + break; + case KEYBOX_FLAG_SIG_INFO: + *flag_size = siginfolen * nsigs; + *flag_off = siginfooff; + break; + default: + break; + } + break; + + default: + return GPG_ERR_INV_FLAG; + } + return 0; +} + + + +/* Return one of the flags WHAT in VALUE from the blob BUFFER of + LENGTH bytes. Return 0 on success or an raw error code. */ +static gpg_err_code_t +get_flag_from_image (const unsigned char *buffer, size_t length, + int what, unsigned int *value) +{ + gpg_err_code_t ec; + size_t pos, size; + + *value = 0; + ec = _keybox_get_flag_location (buffer, length, what, &pos, &size); + if (!ec) + switch (size) + { + case 1: *value = buffer[pos]; break; + case 2: *value = get16 (buffer + pos); break; + case 4: *value = get32 (buffer + pos); break; + default: ec = GPG_ERR_BUG; break; + } + + return ec; +} + + +static int +blob_cmp_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen) +{ + const unsigned char *buffer; + size_t length; + size_t pos, off; + size_t nkeys, keyinfolen; + size_t nserial; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* blob too short */ + + /*keys*/ + nkeys = get16 (buffer + 16); + keyinfolen = get16 (buffer + 18 ); + if (keyinfolen < 28) + return 0; /* invalid blob */ + pos = 20 + keyinfolen*nkeys; + if (pos+2 > length) + return 0; /* out of bounds */ + + /*serial*/ + nserial = get16 (buffer+pos); + off = pos + 2; + if (off+nserial > length) + return 0; /* out of bounds */ + + return nserial == snlen && !memcmp (buffer+off, sn, snlen); +} + + +/* Returns 0 if not found or the number of the key which was found. + For X.509 this is always 1, for OpenPGP this is 1 for the primary + key and 2 and more for the subkeys. */ +static int +blob_cmp_fpr (KEYBOXBLOB blob, const unsigned char *fpr) +{ + const unsigned char *buffer; + size_t length; + size_t pos, off; + size_t nkeys, keyinfolen; + int idx; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* blob too short */ + + /*keys*/ + nkeys = get16 (buffer + 16); + keyinfolen = get16 (buffer + 18 ); + if (keyinfolen < 28) + return 0; /* invalid blob */ + pos = 20; + if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length) + return 0; /* out of bounds */ + + for (idx=0; idx < nkeys; idx++) + { + off = pos + idx*keyinfolen; + if (!memcmp (buffer + off, fpr, 20)) + return idx+1; /* found */ + } + return 0; /* not found */ +} + +static int +blob_cmp_fpr_part (KEYBOXBLOB blob, const unsigned char *fpr, + int fproff, int fprlen) +{ + const unsigned char *buffer; + size_t length; + size_t pos, off; + size_t nkeys, keyinfolen; + int idx; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* blob too short */ + + /*keys*/ + nkeys = get16 (buffer + 16); + keyinfolen = get16 (buffer + 18 ); + if (keyinfolen < 28) + return 0; /* invalid blob */ + pos = 20; + if (pos + (uint64_t)keyinfolen*nkeys > (uint64_t)length) + return 0; /* out of bounds */ + + for (idx=0; idx < nkeys; idx++) + { + off = pos + idx*keyinfolen; + if (!memcmp (buffer + off + fproff, fpr, fprlen)) + return idx+1; /* found */ + } + return 0; /* not found */ +} + + +static int +blob_cmp_name (KEYBOXBLOB blob, int idx, + const char *name, size_t namelen, int substr, int x509) +{ + const unsigned char *buffer; + size_t length; + size_t pos, off, len; + size_t nkeys, keyinfolen; + size_t nuids, uidinfolen; + size_t nserial; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* blob too short */ + + /*keys*/ + nkeys = get16 (buffer + 16); + keyinfolen = get16 (buffer + 18 ); + if (keyinfolen < 28) + return 0; /* invalid blob */ + pos = 20 + keyinfolen*nkeys; + if ((uint64_t)pos+2 > (uint64_t)length) + return 0; /* out of bounds */ + + /*serial*/ + nserial = get16 (buffer+pos); + pos += 2 + nserial; + if (pos+4 > length) + return 0; /* out of bounds */ + + /* user ids*/ + nuids = get16 (buffer + pos); pos += 2; + uidinfolen = get16 (buffer + pos); pos += 2; + if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */) + return 0; /* invalid blob */ + if (pos + uidinfolen*nuids > length) + return 0; /* out of bounds */ + + if (idx < 0) + { /* Compare all names. Note that for X.509 we start with index 1 + so to skip the issuer at index 0. */ + for (idx = !!x509; idx < nuids; idx++) + { + size_t mypos = pos; + + mypos += idx*uidinfolen; + off = get32 (buffer+mypos); + len = get32 (buffer+mypos+4); + if ((uint64_t)off+(uint64_t)len > (uint64_t)length) + return 0; /* error: better stop here out of bounds */ + if (len < 1) + continue; /* empty name */ + if (substr) + { + if (ascii_memcasemem (buffer+off, len, name, namelen)) + return idx+1; /* found */ + } + else + { + if (len == namelen && !memcmp (buffer+off, name, len)) + return idx+1; /* found */ + } + } + } + else + { + if (idx > nuids) + return 0; /* no user ID with that idx */ + pos += idx*uidinfolen; + off = get32 (buffer+pos); + len = get32 (buffer+pos+4); + if (off+len > length) + return 0; /* out of bounds */ + if (len < 1) + return 0; /* empty name */ + + if (substr) + { + if (ascii_memcasemem (buffer+off, len, name, namelen)) + return idx+1; /* found */ + } + else + { + if (len == namelen && !memcmp (buffer+off, name, len)) + return idx+1; /* found */ + } + } + return 0; /* not found */ +} + + +/* Compare all email addresses of the subject. With SUBSTR given as + True a substring search is done in the mail address. The X509 flag + indicated whether the search is done on an X.509 blob. */ +static int +blob_cmp_mail (KEYBOXBLOB blob, const char *name, size_t namelen, int substr, + int x509) +{ + const unsigned char *buffer; + size_t length; + size_t pos, off, len; + size_t nkeys, keyinfolen; + size_t nuids, uidinfolen; + size_t nserial; + int idx; + + /* fixme: this code is common to blob_cmp_mail */ + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* blob too short */ + + /*keys*/ + nkeys = get16 (buffer + 16); + keyinfolen = get16 (buffer + 18 ); + if (keyinfolen < 28) + return 0; /* invalid blob */ + pos = 20 + keyinfolen*nkeys; + if (pos+2 > length) + return 0; /* out of bounds */ + + /*serial*/ + nserial = get16 (buffer+pos); + pos += 2 + nserial; + if (pos+4 > length) + return 0; /* out of bounds */ + + /* user ids*/ + nuids = get16 (buffer + pos); pos += 2; + uidinfolen = get16 (buffer + pos); pos += 2; + if (uidinfolen < 12 /* should add a: || nuidinfolen > MAX_UIDINFOLEN */) + return 0; /* invalid blob */ + if (pos + uidinfolen*nuids > length) + return 0; /* out of bounds */ + + if (namelen < 1) + return 0; + + /* Note that for X.509 we start at index 1 because index 0 is used + for the issuer name. */ + for (idx=!!x509 ;idx < nuids; idx++) + { + size_t mypos = pos; + size_t mylen; + + mypos += idx*uidinfolen; + off = get32 (buffer+mypos); + len = get32 (buffer+mypos+4); + if ((uint64_t)off+(uint64_t)len > (uint64_t)length) + return 0; /* error: better stop here - out of bounds */ + if (x509) + { + if (len < 2 || buffer[off] != '<') + continue; /* empty name or trailing 0 not stored */ + len--; /* one back */ + if ( len < 3 || buffer[off+len] != '>') + continue; /* not a proper email address */ + off++; + len--; + } + else /* OpenPGP. */ + { + /* We need to forward to the mailbox part. */ + mypos = off; + mylen = len; + for ( ; len && buffer[off] != '<'; len--, off++) + ; + if (len < 2 || buffer[off] != '<') + { + /* Mailbox not explicitly given or too short. Restore + OFF and LEN and check whether the entire string + resembles a mailbox without the angle brackets. */ + off = mypos; + len = mylen; + if (!is_valid_mailbox_mem (buffer+off, len)) + continue; /* Not a mail address. */ + } + else /* Seems to be standard user id with mail address. */ + { + off++; /* Point to first char of the mail address. */ + len--; + /* Search closing '>'. */ + for (mypos=off; len && buffer[mypos] != '>'; len--, mypos++) + ; + if (!len || buffer[mypos] != '>' || off == mypos) + continue; /* Not a proper mail address. */ + len = mypos - off; + } + + } + + if (substr) + { + if (ascii_memcasemem (buffer+off, len, name, namelen)) + return idx+1; /* found */ + } + else + { + if (len == namelen && !ascii_memcasecmp (buffer+off, name, len)) + return idx+1; /* found */ + } + } + return 0; /* not found */ +} + + +/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP. + * We don't have the keygrips as meta data, thus we need to parse the + * certificate. Fixme: We might want to return proper error codes + * instead of failing a search for invalid certificates etc. */ +static int +blob_openpgp_has_grip (KEYBOXBLOB blob, const unsigned char *grip) +{ + int rc = 0; + const unsigned char *buffer; + size_t length; + size_t cert_off, cert_len; + struct _keybox_openpgp_info info; + struct _keybox_openpgp_key_info *k; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* Too short. */ + cert_off = get32 (buffer+8); + cert_len = get32 (buffer+12); + if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length) + return 0; /* Too short. */ + + if (_keybox_parse_openpgp (buffer + cert_off, cert_len, NULL, &info)) + return 0; /* Parse error. */ + + if (!memcmp (info.primary.grip, grip, 20)) + { + rc = 1; + goto leave; + } + + if (info.nsubkeys) + { + k = &info.subkeys; + do + { + if (!memcmp (k->grip, grip, 20)) + { + rc = 1; + goto leave; + } + k = k->next; + } + while (k); + } + + leave: + _keybox_destroy_openpgp_info (&info); + return rc; +} + + +#ifdef KEYBOX_WITH_X509 +/* Return true if the key in BLOB matches the 20 bytes keygrip GRIP. + We don't have the keygrips as meta data, thus we need to parse the + certificate. Fixme: We might want to return proper error codes + instead of failing a search for invalid certificates etc. */ +static int +blob_x509_has_grip (KEYBOXBLOB blob, const unsigned char *grip) +{ + int rc; + const unsigned char *buffer; + size_t length; + size_t cert_off, cert_len; + ksba_reader_t reader = NULL; + ksba_cert_t cert = NULL; + ksba_sexp_t p = NULL; + gcry_sexp_t s_pkey; + unsigned char array[20]; + unsigned char *rcp; + size_t n; + + buffer = _keybox_get_blob_image (blob, &length); + if (length < 40) + return 0; /* Too short. */ + cert_off = get32 (buffer+8); + cert_len = get32 (buffer+12); + if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length) + return 0; /* Too short. */ + + rc = ksba_reader_new (&reader); + if (rc) + return 0; /* Problem with ksba. */ + rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len); + if (rc) + goto failed; + rc = ksba_cert_new (&cert); + if (rc) + goto failed; + rc = ksba_cert_read_der (cert, reader); + if (rc) + goto failed; + p = ksba_cert_get_public_key (cert); + if (!p) + goto failed; + n = gcry_sexp_canon_len (p, 0, NULL, NULL); + if (!n) + goto failed; + rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n); + if (rc) + { + gcry_sexp_release (s_pkey); + goto failed; + } + rcp = gcry_pk_get_keygrip (s_pkey, array); + gcry_sexp_release (s_pkey); + if (!rcp) + goto failed; /* Can't calculate keygrip. */ + + xfree (p); + ksba_cert_release (cert); + ksba_reader_release (reader); + return !memcmp (array, grip, 20); + failed: + xfree (p); + ksba_cert_release (cert); + ksba_reader_release (reader); + return 0; +} +#endif /*KEYBOX_WITH_X509*/ + + + +/* + The has_foo functions are used as helpers for search +*/ +static inline int +has_short_kid (KEYBOXBLOB blob, u32 lkid) +{ + unsigned char buf[4]; + buf[0] = lkid >> 24; + buf[1] = lkid >> 16; + buf[2] = lkid >> 8; + buf[3] = lkid; + return blob_cmp_fpr_part (blob, buf, 16, 4); +} + +static inline int +has_long_kid (KEYBOXBLOB blob, u32 mkid, u32 lkid) +{ + unsigned char buf[8]; + buf[0] = mkid >> 24; + buf[1] = mkid >> 16; + buf[2] = mkid >> 8; + buf[3] = mkid; + buf[4] = lkid >> 24; + buf[5] = lkid >> 16; + buf[6] = lkid >> 8; + buf[7] = lkid; + return blob_cmp_fpr_part (blob, buf, 12, 8); +} + +static inline int +has_fingerprint (KEYBOXBLOB blob, const unsigned char *fpr) +{ + return blob_cmp_fpr (blob, fpr); +} + +static inline int +has_keygrip (KEYBOXBLOB blob, const unsigned char *grip) +{ + if (blob_get_type (blob) == KEYBOX_BLOBTYPE_PGP) + return blob_openpgp_has_grip (blob, grip); +#ifdef KEYBOX_WITH_X509 + if (blob_get_type (blob) == KEYBOX_BLOBTYPE_X509) + return blob_x509_has_grip (blob, grip); +#endif + return 0; +} + + +static inline int +has_issuer (KEYBOXBLOB blob, const char *name) +{ + size_t namelen; + + return_val_if_fail (name, 0); + + if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509) + return 0; + + namelen = strlen (name); + return blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1); +} + +static inline int +has_issuer_sn (KEYBOXBLOB blob, const char *name, + const unsigned char *sn, int snlen) +{ + size_t namelen; + + return_val_if_fail (name, 0); + return_val_if_fail (sn, 0); + + if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509) + return 0; + + namelen = strlen (name); + + return (blob_cmp_sn (blob, sn, snlen) + && blob_cmp_name (blob, 0 /* issuer */, name, namelen, 0, 1)); +} + +static inline int +has_sn (KEYBOXBLOB blob, const unsigned char *sn, int snlen) +{ + return_val_if_fail (sn, 0); + + if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509) + return 0; + return blob_cmp_sn (blob, sn, snlen); +} + +static inline int +has_subject (KEYBOXBLOB blob, const char *name) +{ + size_t namelen; + + return_val_if_fail (name, 0); + + if (blob_get_type (blob) != KEYBOX_BLOBTYPE_X509) + return 0; + + namelen = strlen (name); + return blob_cmp_name (blob, 1 /* subject */, name, namelen, 0, 1); +} + + +static inline int +has_username (KEYBOXBLOB blob, const char *name, int substr) +{ + size_t namelen; + int btype; + + return_val_if_fail (name, 0); + + btype = blob_get_type (blob); + if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509) + return 0; + + namelen = strlen (name); + return blob_cmp_name (blob, -1 /* all subject/user names */, name, + namelen, substr, (btype == KEYBOX_BLOBTYPE_X509)); +} + + +static inline int +has_mail (KEYBOXBLOB blob, const char *name, int substr) +{ + size_t namelen; + int btype; + + return_val_if_fail (name, 0); + + btype = blob_get_type (blob); + if (btype != KEYBOX_BLOBTYPE_PGP && btype != KEYBOX_BLOBTYPE_X509) + return 0; + + if (btype == KEYBOX_BLOBTYPE_PGP && *name == '<') + name++; /* Hack to remove the leading '<' for gpg. */ + + namelen = strlen (name); + if (namelen && name[namelen-1] == '>') + namelen--; + return blob_cmp_mail (blob, name, namelen, substr, + (btype == KEYBOX_BLOBTYPE_X509)); +} + + +static void +release_sn_array (struct sn_array_s *array, size_t size) +{ + size_t n; + + for (n=0; n < size; n++) + xfree (array[n].sn); + xfree (array); +} + + +/* Helper to open the file. */ +static gpg_error_t +open_file (KEYBOX_HANDLE hd) +{ + + hd->fp = es_fopen (hd->kb->fname, "rb"); + if (!hd->fp) + { + hd->error = gpg_error_from_syserror (); + return hd->error; + } + + return 0; +} + + + +/* + + The search API + +*/ + +gpg_error_t +keybox_search_reset (KEYBOX_HANDLE hd) +{ + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + if (hd->found.blob) + { + _keybox_release_blob (hd->found.blob); + hd->found.blob = NULL; + } + + if (hd->fp) + { +#if HAVE_W32_SYSTEM + es_fclose (hd->fp); + hd->fp = NULL; +#else + if (es_fseeko (hd->fp, 0, SEEK_SET)) + { + /* Ooops. Seek did not work. Close so that the search will + * open the file again. */ + es_fclose (hd->fp); + hd->fp = NULL; + } +#endif + } + hd->error = 0; + hd->eof = 0; + return 0; +} + + +/* Note: When in ephemeral mode the search function does visit all + blobs but in standard mode, blobs flagged as ephemeral are ignored. + If WANT_BLOBTYPE is not 0 only blobs of this type are considered. + The value at R_SKIPPED is updated by the number of skipped long + records (counts PGP and X.509). */ +gpg_error_t +keybox_search (KEYBOX_HANDLE hd, KEYBOX_SEARCH_DESC *desc, size_t ndesc, + keybox_blobtype_t want_blobtype, + size_t *r_descindex, unsigned long *r_skipped) +{ + gpg_error_t rc; + size_t n; + int need_words, any_skip; + KEYBOXBLOB blob = NULL; + struct sn_array_s *sn_array = NULL; + int pk_no, uid_no; + off_t lastfoundoff; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + + /* Clear last found result but reord the offset of the last found + * blob which we may need later. */ + if (hd->found.blob) + { + lastfoundoff = _keybox_get_blob_fileoffset (hd->found.blob); + _keybox_release_blob (hd->found.blob); + hd->found.blob = NULL; + } + else + lastfoundoff = 0; + + if (hd->error) + return hd->error; /* still in error state */ + if (hd->eof) + return -1; /* still EOF */ + + /* figure out what information we need */ + need_words = any_skip = 0; + for (n=0; n < ndesc; n++) + { + switch (desc[n].mode) + { + case KEYDB_SEARCH_MODE_WORDS: + need_words = 1; + break; + case KEYDB_SEARCH_MODE_FIRST: + /* always restart the search in this mode */ + keybox_search_reset (hd); + lastfoundoff = 0; + break; + default: + break; + } + if (desc[n].skipfnc) + any_skip = 1; + if (desc[n].snlen == -1 && !sn_array) + { + sn_array = xtrycalloc (ndesc, sizeof *sn_array); + if (!sn_array) + return (hd->error = gpg_error_from_syserror ()); + } + } + + (void)need_words; /* Not yet implemented. */ + + if (!hd->fp) + { + rc = open_file (hd); + if (rc) + { + xfree (sn_array); + return rc; + } + /* log_debug ("%s: re-opened file\n", __func__); */ + if (ndesc && desc[0].mode != KEYDB_SEARCH_MODE_FIRST && lastfoundoff) + { + /* Search mode is not first and the last search operation + * returned a blob which also was not the first one. We now + * need to skip over that blob and hope that the file has + * not changed. */ + if (es_fseeko (hd->fp, lastfoundoff, SEEK_SET)) + { + rc = gpg_error_from_syserror (); + log_debug ("%s: seeking to last found offset failed: %s\n", + __func__, gpg_strerror (rc)); + xfree (sn_array); + return gpg_error (GPG_ERR_NOTHING_FOUND); + } + /* log_debug ("%s: re-opened file and sought to last offset\n", */ + /* __func__); */ + rc = _keybox_read_blob (NULL, hd->fp, NULL); + if (rc) + { + log_debug ("%s: skipping last found blob failed: %s\n", + __func__, gpg_strerror (rc)); + xfree (sn_array); + return gpg_error (GPG_ERR_NOTHING_FOUND); + } + } + } + + /* Kludge: We need to convert an SN given as hexstring to its binary + representation - in some cases we are not able to store it in the + search descriptor, because due to the way we use it, it is not + possible to free allocated memory. */ + if (sn_array) + { + const unsigned char *s; + int i, odd; + size_t snlen; + + for (n=0; n < ndesc; n++) + { + if (!desc[n].sn) + ; + else if (desc[n].snlen == -1) + { + unsigned char *sn; + + s = desc[n].sn; + for (i=0; *s && *s != '/'; s++, i++) + ; + odd = (i & 1); + snlen = (i+1)/2; + sn_array[n].sn = xtrymalloc (snlen); + if (!sn_array[n].sn) + { + hd->error = gpg_error_from_syserror (); + release_sn_array (sn_array, n); + return hd->error; + } + sn_array[n].snlen = snlen; + sn = sn_array[n].sn; + s = desc[n].sn; + if (odd) + { + *sn++ = xtoi_1 (s); + s++; + } + for (; *s && *s != '/'; s += 2) + *sn++ = xtoi_2 (s); + } + else + { + const unsigned char *sn; + + sn = desc[n].sn; + snlen = desc[n].snlen; + sn_array[n].sn = xtrymalloc (snlen); + if (!sn_array[n].sn) + { + hd->error = gpg_error_from_syserror (); + release_sn_array (sn_array, n); + return hd->error; + } + sn_array[n].snlen = snlen; + memcpy (sn_array[n].sn, sn, snlen); + } + } + } + + + pk_no = uid_no = 0; + for (;;) + { + unsigned int blobflags; + int blobtype; + + _keybox_release_blob (blob); blob = NULL; + rc = _keybox_read_blob (&blob, hd->fp, NULL); + if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE + && gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX) + { + ++*r_skipped; + continue; /* Skip too large records. */ + } + + if (rc) + break; + + blobtype = blob_get_type (blob); + if (blobtype == KEYBOX_BLOBTYPE_HEADER) + continue; + if (want_blobtype && blobtype != want_blobtype) + continue; + + blobflags = blob_get_blob_flags (blob); + if (!hd->ephemeral && (blobflags & 2)) + continue; /* Not in ephemeral mode but blob is flagged ephemeral. */ + + for (n=0; n < ndesc; n++) + { + switch (desc[n].mode) + { + case KEYDB_SEARCH_MODE_NONE: + never_reached (); + break; + case KEYDB_SEARCH_MODE_EXACT: + uid_no = has_username (blob, desc[n].u.name, 0); + if (uid_no) + goto found; + break; + case KEYDB_SEARCH_MODE_MAIL: + uid_no = has_mail (blob, desc[n].u.name, 0); + if (uid_no) + goto found; + break; + case KEYDB_SEARCH_MODE_MAILSUB: + uid_no = has_mail (blob, desc[n].u.name, 1); + if (uid_no) + goto found; + break; + case KEYDB_SEARCH_MODE_SUBSTR: + uid_no = has_username (blob, desc[n].u.name, 1); + if (uid_no) + goto found; + break; + case KEYDB_SEARCH_MODE_MAILEND: + case KEYDB_SEARCH_MODE_WORDS: + /* not yet implemented */ + break; + case KEYDB_SEARCH_MODE_ISSUER: + if (has_issuer (blob, desc[n].u.name)) + goto found; + break; + case KEYDB_SEARCH_MODE_ISSUER_SN: + if (has_issuer_sn (blob, desc[n].u.name, + sn_array? sn_array[n].sn : desc[n].sn, + sn_array? sn_array[n].snlen : desc[n].snlen)) + goto found; + break; + case KEYDB_SEARCH_MODE_SN: + if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, + sn_array? sn_array[n].snlen : desc[n].snlen)) + goto found; + break; + case KEYDB_SEARCH_MODE_SUBJECT: + if (has_subject (blob, desc[n].u.name)) + goto found; + break; + case KEYDB_SEARCH_MODE_SHORT_KID: + pk_no = has_short_kid (blob, desc[n].u.kid[1]); + if (pk_no) + goto found; + break; + case KEYDB_SEARCH_MODE_LONG_KID: + pk_no = has_long_kid (blob, desc[n].u.kid[0], desc[n].u.kid[1]); + if (pk_no) + goto found; + break; + case KEYDB_SEARCH_MODE_FPR: + case KEYDB_SEARCH_MODE_FPR20: + pk_no = has_fingerprint (blob, desc[n].u.fpr); + if (pk_no) + goto found; + break; + case KEYDB_SEARCH_MODE_KEYGRIP: + if (has_keygrip (blob, desc[n].u.grip)) + goto found; + break; + case KEYDB_SEARCH_MODE_FIRST: + goto found; + break; + case KEYDB_SEARCH_MODE_NEXT: + goto found; + break; + default: + rc = gpg_error (GPG_ERR_INV_VALUE); + goto found; + } + } + continue; + found: + /* Record which DESC we matched on. Note this value is only + meaningful if this function returns with no errors. */ + if(r_descindex) + *r_descindex = n; + for (n=any_skip?0:ndesc; n < ndesc; n++) + { + u32 kid[2]; + + if (desc[n].skipfnc + && blob_get_first_keyid (blob, kid) + && desc[n].skipfnc (desc[n].skipfncvalue, kid, uid_no)) + break; + } + if (n == ndesc) + break; /* got it */ + } + + if (!rc) + { + hd->found.blob = blob; + hd->found.pk_no = pk_no; + hd->found.uid_no = uid_no; + } + else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF) + { + _keybox_release_blob (blob); + hd->eof = 1; + } + else + { + _keybox_release_blob (blob); + hd->error = rc; + } + + if (sn_array) + release_sn_array (sn_array, ndesc); + + return rc; +} + + + + +/* + Functions to return a certificate or a keyblock. To be used after + a successful search operation. +*/ + + +/* Return the last found keyblock. Returns 0 on success and stores a + * new iobuf at R_IOBUF. R_UID_NO and R_PK_NO are used to retun the + * number of the key or user id which was matched the search criteria; + * if not known they are set to 0. */ +gpg_error_t +keybox_get_keyblock (KEYBOX_HANDLE hd, iobuf_t *r_iobuf, + int *r_pk_no, int *r_uid_no) +{ + gpg_error_t err; + const unsigned char *buffer; + size_t length; + size_t image_off, image_len; + size_t siginfo_off, siginfo_len; + + *r_iobuf = NULL; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + if (!hd->found.blob) + return gpg_error (GPG_ERR_NOTHING_FOUND); + + if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_PGP) + return gpg_error (GPG_ERR_WRONG_BLOB_TYPE); + + buffer = _keybox_get_blob_image (hd->found.blob, &length); + if (length < 40) + return gpg_error (GPG_ERR_TOO_SHORT); + image_off = get32 (buffer+8); + image_len = get32 (buffer+12); + if ((uint64_t)image_off+(uint64_t)image_len > (uint64_t)length) + return gpg_error (GPG_ERR_TOO_SHORT); + + err = _keybox_get_flag_location (buffer, length, KEYBOX_FLAG_SIG_INFO, + &siginfo_off, &siginfo_len); + if (err) + return err; + + *r_pk_no = hd->found.pk_no; + *r_uid_no = hd->found.uid_no; + *r_iobuf = iobuf_temp_with_content (buffer+image_off, image_len); + return 0; +} + + +#ifdef KEYBOX_WITH_X509 +/* + Return the last found cert. Caller must free it. + */ +int +keybox_get_cert (KEYBOX_HANDLE hd, ksba_cert_t *r_cert) +{ + const unsigned char *buffer; + size_t length; + size_t cert_off, cert_len; + ksba_reader_t reader = NULL; + ksba_cert_t cert = NULL; + int rc; + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + if (!hd->found.blob) + return gpg_error (GPG_ERR_NOTHING_FOUND); + + if (blob_get_type (hd->found.blob) != KEYBOX_BLOBTYPE_X509) + return gpg_error (GPG_ERR_WRONG_BLOB_TYPE); + + buffer = _keybox_get_blob_image (hd->found.blob, &length); + if (length < 40) + return gpg_error (GPG_ERR_TOO_SHORT); + cert_off = get32 (buffer+8); + cert_len = get32 (buffer+12); + if ((uint64_t)cert_off+(uint64_t)cert_len > (uint64_t)length) + return gpg_error (GPG_ERR_TOO_SHORT); + + rc = ksba_reader_new (&reader); + if (rc) + return rc; + rc = ksba_reader_set_mem (reader, buffer+cert_off, cert_len); + if (rc) + { + ksba_reader_release (reader); + /* fixme: need to map the error codes */ + return gpg_error (GPG_ERR_GENERAL); + } + + rc = ksba_cert_new (&cert); + if (rc) + { + ksba_reader_release (reader); + return rc; + } + + rc = ksba_cert_read_der (cert, reader); + if (rc) + { + ksba_cert_release (cert); + ksba_reader_release (reader); + /* fixme: need to map the error codes */ + return gpg_error (GPG_ERR_GENERAL); + } + + *r_cert = cert; + ksba_reader_release (reader); + return 0; +} + +#endif /*KEYBOX_WITH_X509*/ + +/* Return the flags named WHAT at the address of VALUE. IDX is used + only for certain flags and should be 0 if not required. */ +int +keybox_get_flags (KEYBOX_HANDLE hd, int what, int idx, unsigned int *value) +{ + const unsigned char *buffer; + size_t length; + gpg_err_code_t ec; + + (void)idx; /* Not yet used. */ + + if (!hd) + return gpg_error (GPG_ERR_INV_VALUE); + if (!hd->found.blob) + return gpg_error (GPG_ERR_NOTHING_FOUND); + + buffer = _keybox_get_blob_image (hd->found.blob, &length); + ec = get_flag_from_image (buffer, length, what, value); + return ec? gpg_error (ec):0; +} + +off_t +keybox_offset (KEYBOX_HANDLE hd) +{ + if (!hd->fp) + return 0; + return es_ftello (hd->fp); +} + +gpg_error_t +keybox_seek (KEYBOX_HANDLE hd, off_t offset) +{ + gpg_error_t err; + + if (hd->error) + return hd->error; /* still in error state */ + + if (! hd->fp) + { + if (!offset) + { + /* No need to open the file. An unopened file is effectively at + offset 0. */ + return 0; + } + + err = open_file (hd); + if (err) + return err; + } + + err = es_fseeko (hd->fp, offset, SEEK_SET); + hd->error = gpg_error_from_errno (err); + + return hd->error; +} |