1
0
Fork 0
mirror of git://git.gnupg.org/gnupg.git synced 2025-07-03 22:56:33 +02:00

Merge branch 'STABLE-BRANCH-2-2'

This commit is contained in:
Werner Koch 2018-02-22 16:19:56 +01:00
commit 20539ea5ca
No known key found for this signature in database
GPG key ID: E3FDFF218E45B72B
30 changed files with 654 additions and 336 deletions

View file

@ -325,119 +325,6 @@ main (int argc, char **argv)
struct get_key_status_parm_s
{
const char *fpr;
int found;
int count;
};
static void
get_key_status_cb (void *opaque, const char *keyword, char *args)
{
struct get_key_status_parm_s *parm = opaque;
/*log_debug ("%s: %s\n", keyword, args);*/
if (!strcmp (keyword, "EXPORTED"))
{
parm->count++;
if (!ascii_strcasecmp (args, parm->fpr))
parm->found = 1;
}
}
/* Get a key by fingerprint from gpg's keyring and make sure that the
* mail address ADDRSPEC is included in the key. If EXACT is set the
* returned user id must match Addrspec exactly and not just in the
* addr-spec (mailbox) part. The key is returned as a new memory
* stream at R_KEY. */
static gpg_error_t
get_key (estream_t *r_key, const char *fingerprint, const char *addrspec,
int exact)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv = NULL;
estream_t key = NULL;
struct get_key_status_parm_s parm;
char *filterexp = NULL;
memset (&parm, 0, sizeof parm);
*r_key = NULL;
key = es_fopenmem (0, "w+b");
if (!key)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
/* Prefix the key with the MIME content type. */
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", key);
filterexp = es_bsprintf ("keep-uid=%s=%s", exact? "uid":"mbox", addrspec);
if (!filterexp)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "--export-options=export-minimal");
ccparray_put (&ccp, "--export-filter");
ccparray_put (&ccp, filterexp);
ccparray_put (&ccp, "--export");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, fingerprint);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
parm.fpr = fingerprint;
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
NULL, key,
get_key_status_cb, &parm);
if (!err && parm.count > 1)
err = gpg_error (GPG_ERR_TOO_MANY);
else if (!err && !parm.found)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
{
log_error ("export failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (key);
*r_key = key;
key = NULL;
leave:
es_fclose (key);
xfree (argv);
xfree (filterexp);
return err;
}
/* Add the user id UID to the key identified by FINGERPRINT. */
static gpg_error_t
add_user_id (const char *fingerprint, const char *uid)
@ -767,7 +654,7 @@ command_send (const char *fingerprint, const char *userid)
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
err = get_key (&key, fingerprint, addrspec, 0);
err = wks_get_key (&key, fingerprint, addrspec, 0);
if (err)
goto leave;
@ -782,27 +669,19 @@ command_send (const char *fingerprint, const char *userid)
err = 0;
}
else
err = wkd_get_submission_address (addrspec, &submission_to);
if (err)
{
log_error (_("error looking up submission address for domain '%s': %s\n"),
domain, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
log_error (_("this domain probably doesn't support WKS.\n"));
goto leave;
}
log_info ("submitting request to '%s'\n", submission_to);
/* Get the policy flags. */
if (!fake_submission_addr)
{
/* We first try to get the submission address from the policy
* file (this is the new method). If both are available we
* check that they match and print a warning if not. In the
* latter case we keep on using the one from the
* submission-address file. */
estream_t mbuf;
err = wkd_get_policy_flags (addrspec, &mbuf);
if (err && gpg_err_code (err) != GPG_ERR_NO_DATA)
{
log_error ("error reading policy flags for '%s': %s\n",
submission_to, gpg_strerror (err));
domain, gpg_strerror (err));
goto leave;
}
if (mbuf)
@ -812,8 +691,35 @@ command_send (const char *fingerprint, const char *userid)
if (err)
goto leave;
}
err = wkd_get_submission_address (addrspec, &submission_to);
if (err && !policy.submission_address)
{
log_error (_("error looking up submission address for domain '%s'"
": %s\n"), domain, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
log_error (_("this domain probably doesn't support WKS.\n"));
goto leave;
}
if (submission_to && policy.submission_address
&& ascii_strcasecmp (submission_to, policy.submission_address))
log_info ("Warning: different submission addresses (sa=%s, po=%s)\n",
submission_to, policy.submission_address);
if (!submission_to)
{
submission_to = xtrystrdup (policy.submission_address);
if (!submission_to)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
}
log_info ("submitting request to '%s'\n", submission_to);
if (policy.auth_submit)
log_info ("no confirmation required for '%s'\n", addrspec);
@ -853,7 +759,7 @@ command_send (const char *fingerprint, const char *userid)
estream_t newkey;
es_rewind (key);
err = wks_filter_uid (&newkey, key, thisuid->uid);
err = wks_filter_uid (&newkey, key, thisuid->uid, 0);
if (err)
{
log_error ("error filtering key: %s\n", gpg_strerror (err));
@ -878,7 +784,7 @@ command_send (const char *fingerprint, const char *userid)
* the key again. */
es_fclose (key);
key = NULL;
err = get_key (&key, fingerprint, addrspec, 1);
err = wks_get_key (&key, fingerprint, addrspec, 1);
if (err)
goto leave;
}
@ -1002,6 +908,7 @@ command_send (const char *fingerprint, const char *userid)
free_uidinfo_list (uidlist);
es_fclose (keyenc);
es_fclose (key);
wks_free_policy (&policy);
xfree (addrspec);
return err;
}

View file

@ -1,5 +1,5 @@
/* gpg-wks-server.c - A server for the Web Key Service protocols.
* Copyright (C) 2016 Werner Koch
* Copyright (C) 2016, 2018 Werner Koch
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
@ -20,7 +20,7 @@
/* The Web Key Service I-D defines an update protocol to store a
* public key in the Web Key Directory. The current specification is
* draft-koch-openpgp-webkey-service-01.txt.
* draft-koch-openpgp-webkey-service-05.txt.
*/
#include <config.h>
@ -35,6 +35,7 @@
#include "../common/util.h"
#include "../common/init.h"
#include "../common/sysutils.h"
#include "../common/userids.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "../common/zb32.h"
@ -154,7 +155,7 @@ static gpg_error_t command_receive_cb (void *opaque,
const char *mediatype, estream_t fp,
unsigned int flags);
static gpg_error_t command_list_domains (void);
static gpg_error_t command_install_key (const char *fname);
static gpg_error_t command_install_key (const char *fname, const char *userid);
static gpg_error_t command_remove_key (const char *mailaddr);
static gpg_error_t command_revoke_key (const char *mailaddr);
static gpg_error_t command_check_key (const char *mailaddr);
@ -376,9 +377,9 @@ main (int argc, char **argv)
break;
case aInstallKey:
if (argc != 1)
wrong_args ("--install-key FILE");
err = command_install_key (*argv);
if (argc != 2)
wrong_args ("--install-key FILE USER-ID");
err = command_install_key (*argv, argv[1]);
break;
case aRemoveKey:
@ -1135,6 +1136,8 @@ process_new_key (server_ctx_t ctx, estream_t key)
char *fname = NULL;
struct policy_flags_s policybuf;
memset (&policybuf, 0, sizeof policybuf);
/* First figure out the user id from the key. */
xfree (ctx->fpr);
free_uidinfo_list (ctx->mboxes);
@ -1206,6 +1209,7 @@ process_new_key (server_ctx_t ctx, estream_t key)
xfree (nonce);
xfree (fname);
xfree (dname);
wks_free_policy (&policybuf);
return err;
}
@ -1336,6 +1340,81 @@ send_congratulation_message (const char *mbox, const char *keyfile)
}
/* Write the content of SRC to the new file FNAME. */
static gpg_error_t
write_to_file (estream_t src, const char *fname)
{
gpg_error_t err;
estream_t dst;
char buffer[4096];
size_t nread, written;
dst = es_fopen (fname, "wb");
if (!dst)
return gpg_error_from_syserror ();
do
{
nread = es_fread (buffer, 1, sizeof buffer, src);
if (!nread)
break;
written = es_fwrite (buffer, 1, nread, dst);
if (written != nread)
break;
}
while (!es_feof (src) && !es_ferror (src) && !es_ferror (dst));
if (!es_feof (src) || es_ferror (src) || es_ferror (dst))
{
err = gpg_error_from_syserror ();
es_fclose (dst);
gnupg_remove (fname);
return err;
}
if (es_fclose (dst))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
return err;
}
return 0;
}
/* Compute the the full file name for the key with ADDRSPEC and return
* it at R_FNAME. */
static gpg_error_t
compute_hu_fname (char **r_fname, const char *addrspec)
{
gpg_error_t err;
char *hash;
const char *domain;
char sha1buf[20];
*r_fname = NULL;
domain = strchr (addrspec, '@');
if (!domain || !domain[1] || domain == addrspec)
return gpg_error (GPG_ERR_INV_ARG);
domain++;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, addrspec, domain - addrspec - 1);
hash = zb32_encode (sha1buf, 8*20);
if (!hash)
return gpg_error_from_syserror ();
*r_fname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
if (!*r_fname)
err = gpg_error_from_syserror ();
else
err = 0;
xfree (hash);
return err;
}
/* Check that we have send a request with NONCE and publish the key. */
static gpg_error_t
check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
@ -1409,24 +1488,10 @@ check_and_publish (server_ctx_t ctx, const char *address, const char *nonce)
goto leave;
}
/* Hash user ID and create filename. */
s = strchr (address, '@');
log_assert (s);
gcry_md_hash_buffer (GCRY_MD_SHA1, shaxbuf, address, s - address);
hash = zb32_encode (shaxbuf, 8*20);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
fnewname = make_filename_try (opt.directory, domain, "hu", hash, NULL);
if (!fnewname)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = compute_hu_fname (&fnewname, address);
if (err)
goto leave;
/* Publish. */
err = copy_key_as_binary (fname, fnewname, address);
@ -1897,6 +1962,7 @@ command_list_domains (void)
if (!memcmp (&empty_policy, &policy, sizeof policy))
log_error ("domain %s: empty policy file\n", domain);
}
wks_free_policy (&policy);
}
@ -1931,16 +1997,140 @@ command_cron (void)
}
/* Install a single key into the WKD by reading FNAME. */
/* Install a single key into the WKD by reading FNAME and extracting
* USERID. */
static gpg_error_t
command_install_key (const char *fname)
command_install_key (const char *fname, const char *userid)
{
(void)fname;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
estream_t fp = NULL;
char *addrspec = NULL;
char *fpr = NULL;
uidinfo_list_t uidlist = NULL;
uidinfo_list_t uid, thisuid;
time_t thistime;
char *huname = NULL;
int any;
addrspec = mailbox_from_userid (userid);
if (!addrspec)
{
log_error ("\"%s\" is not a proper mail address\n", userid);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
if (!classify_user_id (fname, &desc, 1)
&& (desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_FPR20))
{
/* FNAME looks like a fingerprint. Get the key from the
* standard keyring. */
err = wks_get_key (&fp, fname, addrspec, 0);
if (err)
{
log_error ("error getting key '%s' (uid='%s'): %s\n",
fname, addrspec, gpg_strerror (err));
goto leave;
}
}
else /* Take it from the file */
{
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
}
/* List the key so that we can figure out the newest UID with the
* requested addrspec. */
err = wks_list_key (fp, &fpr, &uidlist);
if (err)
{
log_error ("error parsing key: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
thistime = 0;
thisuid = NULL;
any = 0;
for (uid = uidlist; uid; uid = uid->next)
{
if (!uid->mbox)
continue; /* Should not happen anyway. */
if (ascii_strcasecmp (uid->mbox, addrspec))
continue; /* Not the requested addrspec. */
any = 1;
if (uid->created > thistime)
{
thistime = uid->created;
thisuid = uid;
}
}
if (!thisuid)
thisuid = uidlist; /* This is the case for a missing timestamp. */
if (!any)
{
log_error ("public key in '%s' has no mail address '%s'\n",
fname, addrspec);
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
if (opt.verbose)
log_info ("using key with user id '%s'\n", thisuid->uid);
{
estream_t fp2;
es_rewind (fp);
err = wks_filter_uid (&fp2, fp, thisuid->uid, 1);
if (err)
{
log_error ("error filtering key: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
es_fclose (fp);
fp = fp2;
}
/* Hash user ID and create filename. */
err = compute_hu_fname (&huname, addrspec);
if (err)
goto leave;
/* Publish. */
err = write_to_file (fp, huname);
if (err)
{
log_error ("copying key to '%s' failed: %s\n", huname,gpg_strerror (err));
goto leave;
}
/* Make sure it is world readable. */
if (gnupg_chmod (huname, "-rwxr--r--"))
log_error ("can't set permissions of '%s': %s\n",
huname, gpg_strerror (gpg_err_code_from_syserror()));
if (!opt.quiet)
log_info ("key %s published for '%s'\n", fpr, addrspec);
leave:
xfree (huname);
free_uidinfo_list (uidlist);
xfree (fpr);
xfree (addrspec);
es_fclose (fp);
return err;
}
/* Return the filename and optioanlly the addrspec for USERID at
/* Return the filename and optionally the addrspec for USERID at
* R_FNAME and R_ADDRSPEC. R_ADDRSPEC might also be set on error. */
static gpg_error_t
fname_from_userid (const char *userid, char **r_fname, char **r_addrspec)

View file

@ -60,6 +60,7 @@ struct
/* The parsed policy flags. */
struct policy_flags_s
{
char *submission_address;
unsigned int mailbox_only : 1;
unsigned int dane_only : 1;
unsigned int auth_submit : 1;
@ -85,13 +86,16 @@ typedef struct uidinfo_list_s *uidinfo_list_t;
void wks_set_status_fd (int fd);
void wks_write_status (int no, const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
void free_uidinfo_list (uidinfo_list_t list);
gpg_error_t wks_get_key (estream_t *r_key, const char *fingerprint,
const char *addrspec, int exact);
gpg_error_t wks_list_key (estream_t key, char **r_fpr,
uidinfo_list_t *r_mboxes);
gpg_error_t wks_filter_uid (estream_t *r_newkey, estream_t key,
const char *uid);
const char *uid, int binary);
gpg_error_t wks_send_mime (mime_maker_t mime);
gpg_error_t wks_parse_policy (policy_flags_t flags, estream_t stream,
int ignore_unknown);
void wks_free_policy (policy_flags_t policy);
/*-- wks-receive.c --*/

View file

@ -132,6 +132,120 @@ free_uidinfo_list (uidinfo_list_t list)
}
struct get_key_status_parm_s
{
const char *fpr;
int found;
int count;
};
static void
get_key_status_cb (void *opaque, const char *keyword, char *args)
{
struct get_key_status_parm_s *parm = opaque;
/*log_debug ("%s: %s\n", keyword, args);*/
if (!strcmp (keyword, "EXPORTED"))
{
parm->count++;
if (!ascii_strcasecmp (args, parm->fpr))
parm->found = 1;
}
}
/* Get a key by fingerprint from gpg's keyring and make sure that the
* mail address ADDRSPEC is included in the key. If EXACT is set the
* returned user id must match Addrspec exactly and not just in the
* addr-spec (mailbox) part. The key is returned as a new memory
* stream at R_KEY. */
gpg_error_t
wks_get_key (estream_t *r_key, const char *fingerprint, const char *addrspec,
int exact)
{
gpg_error_t err;
ccparray_t ccp;
const char **argv = NULL;
estream_t key = NULL;
struct get_key_status_parm_s parm;
char *filterexp = NULL;
memset (&parm, 0, sizeof parm);
*r_key = NULL;
key = es_fopenmem (0, "w+b");
if (!key)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
/* Prefix the key with the MIME content type. */
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", key);
filterexp = es_bsprintf ("keep-uid=%s=%s", exact? "uid":"mbox", addrspec);
if (!filterexp)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory buffer: %s\n", gpg_strerror (err));
goto leave;
}
ccparray_init (&ccp, 0);
ccparray_put (&ccp, "--no-options");
if (!opt.verbose)
ccparray_put (&ccp, "--quiet");
else if (opt.verbose > 1)
ccparray_put (&ccp, "--verbose");
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "--export-options=export-minimal");
ccparray_put (&ccp, "--export-filter");
ccparray_put (&ccp, filterexp);
ccparray_put (&ccp, "--export");
ccparray_put (&ccp, "--");
ccparray_put (&ccp, fingerprint);
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
parm.fpr = fingerprint;
err = gnupg_exec_tool_stream (opt.gpg_program, argv, NULL,
NULL, key,
get_key_status_cb, &parm);
if (!err && parm.count > 1)
err = gpg_error (GPG_ERR_TOO_MANY);
else if (!err && !parm.found)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
{
log_error ("export failed: %s\n", gpg_strerror (err));
goto leave;
}
es_rewind (key);
*r_key = key;
key = NULL;
leave:
es_fclose (key);
xfree (argv);
xfree (filterexp);
return err;
}
/* Helper for wks_list_key and wks_filter_uid. */
static void
@ -317,10 +431,13 @@ wks_list_key (estream_t key, char **r_fpr, uidinfo_list_t *r_mboxes)
/* Run gpg as a filter on KEY and write the output to a new stream
* stored at R_NEWKEY. The new key will containn only the user id
* UID. Returns 0 on success. Only one key is expected in KEY. */
* stored at R_NEWKEY. The new key will contain only the user id UID.
* Returns 0 on success. Only one key is expected in KEY. If BINARY
* is set the resulting key is returned as a binary (non-armored)
* keyblock. */
gpg_error_t
wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid)
wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid,
int binary)
{
gpg_error_t err;
ccparray_t ccp;
@ -340,8 +457,9 @@ wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid)
}
/* Prefix the key with the MIME content type. */
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", newkey);
if (!binary)
es_fputs ("Content-Type: application/pgp-keys\n"
"\n", newkey);
filterexp = es_bsprintf ("keep-uid=uid=%s", uid);
if (!filterexp)
@ -361,7 +479,8 @@ wks_filter_uid (estream_t *r_newkey, estream_t key, const char *uid)
ccparray_put (&ccp, "--batch");
ccparray_put (&ccp, "--status-fd=2");
ccparray_put (&ccp, "--always-trust");
ccparray_put (&ccp, "--armor");
if (!binary)
ccparray_put (&ccp, "--armor");
ccparray_put (&ccp, "--import-options=import-export");
ccparray_put (&ccp, "--import-filter");
ccparray_put (&ccp, filterexp);
@ -443,6 +562,7 @@ gpg_error_t
wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
{
enum tokens {
TOK_SUBMISSION_ADDRESS,
TOK_MAILBOX_ONLY,
TOK_DANE_ONLY,
TOK_AUTH_SUBMIT,
@ -453,6 +573,7 @@ wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
const char *name;
enum tokens token;
} keywords[] = {
{ "submission-address", TOK_SUBMISSION_ADDRESS },
{ "mailbox-only", TOK_MAILBOX_ONLY },
{ "dane-only", TOK_DANE_ONLY },
{ "auth-submit", TOK_AUTH_SUBMIT },
@ -519,6 +640,20 @@ wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
switch (keywords[i].token)
{
case TOK_SUBMISSION_ADDRESS:
if (!value || !*value)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
xfree (flags->submission_address);
flags->submission_address = xtrystrdup (value);
if (!flags->submission_address)
{
err = gpg_error_from_syserror ();
goto leave;
}
break;
case TOK_MAILBOX_ONLY: flags->mailbox_only = 1; break;
case TOK_DANE_ONLY: flags->dane_only = 1; break;
case TOK_AUTH_SUBMIT: flags->auth_submit = 1; break;
@ -553,3 +688,14 @@ wks_parse_policy (policy_flags_t flags, estream_t stream, int ignore_unknown)
return err;
}
void
wks_free_policy (policy_flags_t policy)
{
if (policy)
{
xfree (policy->submission_address);
memset (policy, 0, sizeof *policy);
}
}