1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-06-09 23:39:51 +02:00
gnupg/g10/mailing-list.c
2016-02-08 00:48:17 +01:00

1381 lines
40 KiB
C

/* mailing-list.c - Create a mailing list.
* Copyright (C) 2015 Neal H. Walfield <neal@walfield.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "iobuf.h"
#include "keydb.h"
#include "util.h"
#include "main.h"
#include "ttyio.h"
#include "status.h"
#include "i18n.h"
#include "mailing-list.h"
void
kbnode_dump (KBNODE kb)
{
for (; kb; kb = kb->next)
{
switch (kb->pkt->pkttype)
{
case PKT_PUBLIC_KEY:
log_debug (" public key: %s%s\n",
keystr (kb->pkt->pkt.public_key->keyid),
kb->pkt->pkt.public_key->has_expired ? " (expired)" : "");
break;
case PKT_PUBLIC_SUBKEY:
log_debug (" subkey: %s%s\n",
keystr (kb->pkt->pkt.public_key->keyid),
kb->pkt->pkt.public_key->has_expired ? " (expired)" : "");
break;
case PKT_USER_ID:
log_debug (" user id: %s\n",
kb->pkt->pkt.user_id->name);
break;
case PKT_SIGNATURE:
{
PKT_signature *sig = kb->pkt->pkt.signature;
struct notation *notations = sig_to_notation (sig);
log_debug (" sig by %s: class %x\n",
keystr (sig->keyid), sig->sig_class);
if (notations)
{
struct notation *niter;
log_debug (" Notations:\n");
for (niter = notations; niter; niter = niter->next)
{
log_debug (" %s=%s\n",
niter->name, niter->value);
}
free_notation (notations);
}
}
break;
default:
log_debug (" unknown packet: %d\n", kb->pkt->pkttype);
break;
}
}
}
/* Get a copy of all the session keys and store them in *DEKS and the
total count in *NDEKS. On success, the caller must xfree
deksp. */
gpg_error_t
mailing_list_get_subscriber_list_session_keys (ctrl_t ctrl, KBNODE kb,
DEK **deksp, int *ndeksp)
{
gpg_error_t err;
PKT_public_key *pk = kb->pkt->pkt.public_key;
KBNODE n;
PKT_public_key *sk = NULL;
/* We need to collect all of the keys before we can decrypt (in
order to access key_i, we need key_{i-1} and we aren't guaranteed
to read the keys in order). Thus, we save the raw key data in
this structure. */
struct keydata
{
byte *data;
size_t blen;
};
/* We grow this dynamically. */
struct keydata *keydata = NULL;
int nkeydata = 0;
iobuf_t keydata_initial = iobuf_temp ();
DEK *deks = NULL;
int i;
int last = -1;
for (n = kb; n; n = n->next)
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
sk = n->pkt->pkt.public_key;
if (DBG_PACKET)
log_debug ("%s: Processing signatures for %s\n",
__func__, keystr (sk->keyid));
}
else if (n->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = n->pkt->pkt.signature;
struct notation *notations;
struct notation *niter;
struct keydata k = { 0, 0 };
/* The session key that this key was encrypted with. */
int encrypted_with = -2;
if (! sig->flags.notation)
/* Nothing to do. */
continue;
notations = sig_to_notation (sig);
for (niter = notations; niter; niter = niter->next)
{
if (strcmp ("subscriber-list-session-key@gnupg.org",
niter->name) == 0)
{
k.data = xmalloc (niter->blen);
memcpy (k.data, niter->bdat, niter->blen);
k.blen = niter->blen;
}
else if (strcmp ("subscriber-list-session-key-encrypted-with@gnupg.org",
niter->name) == 0)
{
encrypted_with = atoi (niter->value);
}
else if (strcmp ("mailing-list@gnupg.org", niter->name) == 0)
{
encrypted_with = -1;
}
else if (strcmp ("subscriber-list-key@gnupg.org",
niter->name) == 0)
/* An encrypted initial session key. Just append it to
KEYDATA_INITIAL. */
{
if (DBG_PACKET)
log_debug ("%s: Adding subscriber-list-key for %s\n",
__func__, keystr (sk->keyid));
iobuf_write (keydata_initial, niter->bdat, niter->blen);
}
}
if (k.blen)
{
/* ENCRYPTED_WITH is the index of the key that this key
was encrypted with. Thus, this key is index
ENCRYPTED_WITH+1. */
int session_key_index = encrypted_with + 1;
if (session_key_index < 0)
log_bug ("Have subscriber-list-session-key, but no subscriber-list-session-key-encrypted-with notation!\n");
if (DBG_PACKET)
log_debug ("%s: Got subscriber-list-session-key %d\n",
__func__, session_key_index);
if (session_key_index >= nkeydata)
{
int o = nkeydata;
nkeydata = 2 * (1 + nkeydata);
keydata = xrealloc (keydata, nkeydata * sizeof (*keydata));
memset (&keydata[o], 0, (nkeydata - o) * sizeof (*keydata));
}
if (last < session_key_index)
last = session_key_index;
if (keydata[session_key_index].blen)
log_bug ("Have multiple session keys with index %d?!?\n",
session_key_index);
keydata[session_key_index] = k;
if (session_key_index == 0)
/* Add the initial key to the keydata_initial set. */
iobuf_write (keydata_initial, k.data, k.blen);
}
free_notation (notations);
}
if (! nkeydata)
{
log_error ("Malformed mailing list key: did not find any subscriber-list-session-key notations.\n");
return gpg_error (GPG_ERR_INTERNAL);
}
nkeydata = last + 1;
if (DBG_PACKET)
log_debug ("%s: Found %d subscriber-list-session keys.\n",
__func__, nkeydata);
deks = xmalloc_clear (nkeydata * sizeof (*deks));
last = -1;
for (i = 0; i < nkeydata; i ++)
if (keydata[i].blen)
{
iobuf_t input;
if (last + 1 != i)
{
log_error ("Malformed mailing list key: missing subscriber-list-session-keys %d-%d\n",
last + 1, i - 1);
return gpg_error (GPG_ERR_INTERNAL);
}
last = i;
if (DBG_PACKET)
log_debug ("%s: mailing list key %s: session key %d is %zd bytes\n",
__func__, keystr (pk->keyid), i, keydata[i].blen);
if (i == 0)
{
input =
iobuf_temp_with_content (iobuf_get_temp_buffer (keydata_initial),
iobuf_get_temp_length (keydata_initial));
iobuf_close (keydata_initial);
keydata_initial = NULL;
err = proc_pubkey_packet (ctrl, input, &deks[i]);
if (err)
log_error ("unable to extract mailing list decryption key: %s. Try adding the key subscribed to the mailing list to --try-secret-key KEYID.\n",
gpg_strerror (err));
}
else
{
input = iobuf_temp_with_content (keydata[i].data, keydata[i].blen);
if (! input)
log_bug ("Failed to create iobuf");
/* The encryption function (s2k) needs an ASCII password.
We just hex encode the session key and use that. */
set_next_passphrase (bin2hex (deks[i - 1].key, deks[i - 1].keylen,
NULL));
err = proc_symkey_packet (ctrl, input, &deks[i]);
if (err)
log_error ("Failed to extract session key %d from subscriber-list-session-key notation: %s\n",
i, gpg_strerror (err));
}
if (err)
break;
}
for (i = 0; i < nkeydata; i ++)
xfree (keydata[i].data);
xfree (keydata);
if (keydata_initial)
iobuf_close (keydata_initial);
if (err)
xfree (deks);
else
{
*deksp = deks;
*ndeksp = last + 1;
}
return err;
}
gpg_error_t
mailing_list_add_subscriber (ctrl_t ctrl, KBNODE ml_kb, const char *sub)
{
gpg_error_t err;
/* The mailing list's primary key. */
PKT_public_key *ml_pk = ml_kb->pkt->pkt.public_key;
/* The subscriber's keyblock. */
KBNODE sub_kb = NULL;
/* The subscriber's primary key. */
PKT_public_key *sub_pk = NULL;
/* The subscriber's encryption key. */
PKT_public_key *sub_ek = NULL;
/* The modified copy of SUB_EK that we add to the mailing list's
keyblock. */
PKT_public_key *ml_ek = NULL;
DEK *deks = NULL;
int ndeks;
/* The first session key. */
DEK session_key_initial;
/* The current session key. */
DEK session_key;
/* The index of the current session key. */
int session_key_i;
struct notation *notations = NULL;
err = get_pubkey_byname (NULL, NULL, NULL, sub, &sub_kb, NULL, 0, 0);
if (err)
{
log_error (_("Looking up key '%s': %s\n"),
sub, gpg_strerror (err));
goto out;
}
sub_pk = sub_kb->pkt->pkt.public_key;
{
char keyid_str[20];
char subkeyid_str[20];
format_keyid (ml_pk->keyid, KF_DEFAULT, keyid_str, sizeof (keyid_str));
format_keyid (sub_pk->keyid, KF_DEFAULT,
subkeyid_str, sizeof (subkeyid_str));
if (DBG_PACKET)
log_debug ("%s: addsub %s %s\n", __func__, keyid_str, subkeyid_str);
}
/* Find the encryption key to add and save it in SUB_EK. */
{
KBNODE n;
for (n = sub_kb; n; n = n->next)
if (n->pkt->pkttype == PKT_PUBLIC_KEY
|| n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *ek = n->pkt->pkt.public_key;
/* Ignore invalid keys. */
if (! (ek->pubkey_usage & PUBKEY_USAGE_ENC))
{
if (DBG_PACKET)
log_debug ("%s: Ignoring subkey %s: no encryption capability.\n",
__func__, keystr (ek->keyid));
continue;
}
if (ek->flags.revoked)
{
if (DBG_PACKET)
log_debug ("%s: Ignoring subkey %s: revoked.\n",
__func__, keystr (ek->keyid));
continue;
}
if (ek->has_expired)
{
if (DBG_PACKET)
log_debug ("%s: Ignoring subkey %s: expired.\n",
__func__, keystr (ek->keyid));
continue;
}
if(ek->flags.maybe_revoked && !ek->flags.revoked)
log_info(_("WARNING: this key might be revoked (revocation key"
" not present)\n"));
if (DBG_PACKET)
log_debug ("%s: subkey %s is a candidate.\n",
__func__, keystr (ek->keyid));
if (! sub_ek)
{
sub_ek = ek;
continue;
}
else
/* If there are multiple valid keys, then prefer the
newest one. */
{
if (ek->timestamp > sub_ek->timestamp)
sub_ek = ek;
if (DBG_PACKET)
log_debug ("%s: Preferring subkey %s (it is newer).\n",
__func__, keystr (sub_ek->keyid));
}
}
if (! sub_ek)
{
if (DBG_PACKET)
log_debug ("%s: Key does support encryption.\n", __func__);
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
goto out;
}
/* SUB_EK now holds the selected encryption key. */
}
/* Make sure we haven't already added this encryption key. Or, if
we have and it is unsubscribed, resubscribe it. */
{
KBNODE n;
char sub_ek_fp[MAX_FINGERPRINT_LEN];
size_t sub_ek_fplen;
fingerprint_from_pk (sub_ek, sub_ek_fp, &sub_ek_fplen);
for (n = ml_kb; n; n = n->next)
{
char fp[MAX_FINGERPRINT_LEN];
size_t fplen;
PKT_public_key *x;
if (n->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
x = n->pkt->pkt.public_key;
if (gcry_mpi_cmp (x->pkey[0], GCRYMPI_CONST_TWO) == 0)
/* The subkey is still encrypted. Decrypt them all. */
{
err = mailing_list_subscribers_ext (ctrl, ml_kb, NULL,
&deks, &ndeks);
if (err)
{
log_error ("failed to decrypt subscribers' keys: %s",
gpg_strerror (err));
goto out;
}
assert (x->flags.pkey_decrypted);
assert (gcry_mpi_cmp (x->pkey[0], GCRYMPI_CONST_TWO) != 0);
assert (x->timestamp_saved);
}
fingerprint_from_pk (x, fp, &fplen);
if (sub_ek_fplen == fplen && memcmp (sub_ek_fp, fp, fplen) == 0)
/* Got a match! */
break;
}
if (n)
{
/* XXX: If SUB was a subscriber, but is currently
unsubscriber, re-add. */
log_error ("%s is already a subscriber.\n", sub);
goto out;
}
}
/* Get the initial session key (we need to grant the new subscriber
access to it) and the current session key (we need to encrypt the
new subscriber's parameters with it). */
{
/* We may have already gotten the session keys above. If so,
don't repeat the work. */
if (! deks)
err = mailing_list_get_subscriber_list_session_keys (ctrl, ml_kb,
&deks, &ndeks);
if (err)
{
log_error ("Failed to get session keys for mailing list: %s\n",
gpg_strerror (err));
xfree (deks);
goto out;
}
session_key_initial = deks[0];
session_key_i = ndeks - 1;
session_key = deks[session_key_i];
xfree (deks);
deks = NULL;
}
/* Make a new subkey using the new subscriber's selected encryption
key. */
{
PACKET *pkt;
if (DBG_PACKET)
{
log_debug("%s: keyblock pre:\n", __func__);
kbnode_dump (ml_kb);
}
pkt = xmalloc_clear (sizeof (*pkt));
pkt->pkttype = PKT_PUBLIC_SUBKEY;
ml_ek = xmalloc_clear (sizeof (*ml_ek));
pkt->pkt.public_key = ml_ek;
add_kbnode (ml_kb, new_kbnode (pkt));
/* First copy everything and then clear what we don't need. */
/* XXX: It would be better to just copy the fields that we actually
need. */
*ml_ek = *sub_ek;
ml_ek->main_keyid[0] = ml_pk->keyid[0];
ml_ek->main_keyid[1] = ml_pk->keyid[1];
ml_ek->pubkey_usage = PUBKEY_USAGE_ENC;
ml_ek->timestamp = make_timestamp();
/* Since the parameters are not unique, make sure the time stamps
are. */
{
KBNODE n;
if (DBG_PACKET)
log_debug ("Choose timestamp: %x, checking that it is unique.\n",
ml_ek->timestamp);
for (n = ml_kb; n; n = n->next)
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *x = n->pkt->pkt.public_key;
if (x->timestamp_saved == ml_ek->timestamp)
{
if (DBG_PACKET)
log_debug ("Timestamp collision! Advancing to %x and restarting\n",
ml_ek->timestamp);
ml_ek->timestamp ++;
/* Restart. (This skips the first node, but that's
never a subkey.) */
n = ml_kb;
continue;
}
}
}
ml_ek->expiredate = 0;
ml_ek->max_expiredate = 0;
ml_ek->prefs = NULL;
ml_ek->user_id = NULL;
ml_ek->revkey = NULL;
ml_ek->numrevkeys = 0;
ml_ek->trust_regexp = NULL;
ml_ek->serialno = NULL;
ml_ek->seckey_info = NULL;
}
/* Encrypt the parameters and the current time using the current
session key. */
{
/* Create the data that we want to encrypt. */
iobuf_t params = iobuf_temp ();
int i;
int n = pubkey_get_npkey (ml_ek->pubkey_algo);
/* XXX. */
iobuf_write (params, "XXXYYY ", 7);
for (i = 0; i < n; i ++)
{
err = gpg_mpi_write (params, ml_ek->pkey[i]);
ml_ek->pkey[i] = GCRYMPI_CONST_TWO;
if (err)
{
log_error ("encrypting subscriber's key: %s\n",
gpg_strerror (err));
iobuf_close (params);
goto out;
}
}
iobuf_flush_temp (params);
if (MAILING_LIST_DUMP_NOTATIONS)
/* Save the raw data for debugging purposes. */
{
char *ml_pk_keyid = xstrdup (keystr (ml_pk->keyid));
char *fn = xasprintf ("%s/mailing-list-%s-public-key-%s.txt",
opt.homedir, ml_pk_keyid,
keystr (sub_ek->keyid));
FILE *fp = fopen (fn, "w");
xfree (ml_pk_keyid);
if (fp)
{
log_debug ("Writing unencrypted public-key parameters to %s\n", fn);
fwrite (iobuf_get_temp_buffer (params),
iobuf_get_temp_length (params), 1, fp);
fclose (fp);
}
xfree (fn);
}
/* Setup the encryption pipeline. (Based on encrypt_simple.) */
{
PKT_plaintext pt;
PACKET pkt;
cipher_filter_context_t cfx;
int len = iobuf_get_temp_length (params);
iobuf_t in;
in = iobuf_temp_with_content (iobuf_get_temp_buffer (params), len);
iobuf_close (params);
params = NULL;
if (! in)
{
log_error ("failed to create iobuf");
goto out;
}
params = iobuf_temp ();
memset (&pt, 0, sizeof (pt));
pt.namelen = 0;
pt.mode = 'b';
pt.len = len;
pt.new_ctb = 0;
pt.timestamp = sub_ek->timestamp;
pt.buf = in;
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = &pt;
memset (&cfx, 0, sizeof cfx);
cfx.dek = &session_key;
cfx.datalen = calc_packet_length (&pkt);
iobuf_push_filter (params, cipher_filter, &cfx);
err = build_packet (params, &pkt);
if (err)
{
log_error ("building packet: %s\n", gpg_strerror (err));
goto out;
}
}
iobuf_flush_temp (params);
{
char *notation = "public-key@gnupg.org";
char *buffer = iobuf_get_temp_buffer (params);
size_t len = iobuf_get_temp_length (params);
struct notation *notation_blob
= blob_to_notation (notation, buffer, len);
if (! notation_blob)
{
log_bug ("Failed to create notation: %s=<SE-ESK packet, %zd bytes>\n",
notation, len);
err = gpg_error (GPG_ERR_INTERNAL);
goto out;
}
notation_blob->next = notations;
notations = notation_blob;
if (MAILING_LIST_DUMP_NOTATIONS)
{
char *ml_pk_keyid = xstrdup (keystr (ml_pk->keyid));
char *fn = xasprintf ("%s/mailing-list-%s-public-key-%s.gpg",
opt.homedir, ml_pk_keyid,
keystr (sub_ek->keyid));
FILE *fp = fopen (fn, "w");
xfree (ml_pk_keyid);
if (fp)
{
log_debug ("Writing encrypted public-key parameters to %s (because this SED packet is not preceeded by an ESK packet, an OpenPGP implementation assumes that it is IDEA encoded. As such, to manually dump this packet, you need to use --override-session-key).\n", fn);
fwrite (buffer, len, 1, fp);
fclose (fp);
}
xfree (fn);
}
iobuf_close (params);
}
/* Recompute ml_ek->keyid. */
{
char fp[MAX_FINGERPRINT_LEN];
size_t fplen;
u32 keyid[2];
fingerprint_from_pk (ml_ek, fp, &fplen);
keyid_from_fingerprint (fp, fplen, keyid);
ml_ek->keyid[0] = keyid[0];
ml_ek->keyid[1] = keyid[1];
}
}
/* Add the public-key-encrypted-with notation. */
{
char *notation;
struct notation *notation_blob;
/* The session key used to encrypt the public key parameters. */
notation = xasprintf ("public-key-encrypted-with@gnupg.org=%d",
session_key_i);
notation_blob = string_to_notation (notation, 0);
if (! notation_blob)
{
log_bug ("Failed to create notation: %s\n", notation);
xfree (notation);
err = gpg_error (GPG_ERR_INTERNAL);
goto out;
}
xfree (notation);
notation_blob->next = notations;
notations = notation_blob;
}
/* Add the subscriber-list-key notation. */
{
char *notation;
struct notation *notation_blob;
struct pk_list pk_list;
/* The public key encrypted session key as a packet. */
iobuf_t pk_esk;
char *buffer;
size_t len;
/* The initial session key encrypted with the new subscriber's
public key. */
/* Initialize PK_LIST with just the encryption key. */
pk_list.next = NULL;
pk_list.pk = sub_ek;
/* Throw the key id. */
pk_list.flags = 1;
pk_esk = iobuf_temp ();
if (! pk_esk)
{
log_bug ("Out of memory allocating pk_esk\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto out;
}
err = write_pubkey_enc_from_list (ctrl, &pk_list,
&session_key_initial, pk_esk);
if (err)
{
log_bug ("Failed to generate PK-ESK packet: %s\n",
gpg_strerror (err));
iobuf_close (pk_esk);
goto out;
}
buffer = iobuf_get_temp_buffer (pk_esk);
len = iobuf_get_temp_length (pk_esk);
if (MAILING_LIST_DUMP_NOTATIONS)
{
char *ml_pk_keyid = xstrdup (keystr (ml_pk->keyid));
char *fn = xasprintf ("%s/mailing-list-%s-subscriber-list-key-%s.gpg",
opt.homedir, ml_pk_keyid,
keystr (sub_ek->keyid));
FILE *fp = fopen (fn, "w");
xfree (ml_pk_keyid);
if (fp)
{
log_debug ("Writing subscriber-list-key to %s\n", fn);
fwrite (buffer, len, 1, fp);
fclose (fp);
}
xfree (fn);
}
notation = "subscriber-list-key@gnupg.org";
notation_blob = blob_to_notation (notation, buffer, len);
iobuf_close (pk_esk);
if (! notation_blob)
{
log_bug ("Failed to create notation: %s=<SE-ESK packet, %zd bytes>\n",
notation, len);
err = gpg_error (GPG_ERR_INTERNAL);
goto out;
}
notation_blob->next = notations;
notations = notation_blob;
}
/* Write the binding signature. */
err = write_keybinding (ml_kb, ml_pk, NULL, ml_ek->pubkey_usage,
ml_ek->timestamp, NULL, notations);
if (err)
{
log_error ("Error creating key binding: %s\n", gpg_strerror (err));
goto out;
}
if (DBG_PACKET)
{
log_debug("%s: keyblock after adding self-sig:\n", __func__);
kbnode_dump (ml_kb);
}
/* Save the updated keyblock. */
{
KEYDB_HANDLE hd = keydb_new ();
err = keydb_update_keyblock (hd, ml_kb);
keydb_release (hd);
if (err)
{
log_error ("Error saving %s's keyblock.\n",
keystr (ml_pk->keyid));
goto out;
}
}
out:
free_notation (notations);
if (sub_kb)
release_kbnode (sub_kb);
if (err)
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
return err;
}
gpg_error_t
mailing_list_rm_subscriber (ctrl_t ctrl, KBNODE ml_kb, const char *sub_orig)
{
gpg_error_t err;
/* The mailing list's primary key. */
PKT_public_key *ml_pk = ml_kb->pkt->pkt.public_key;
DEK *deks = NULL;
int ndeks = 0;
char *sub;
int i, j;
PKT_public_key *ek;
struct notation *notations = NULL;
/* Skip leading white space. */
while (*sub_orig == ' ')
sub_orig ++;
/* Kill the leading 0x (if any). */
if (sub_orig[0] == '0' && sub_orig[0] == 'x')
sub_orig += 2;
sub = xstrdup (sub_orig);
if (DBG_PACKET)
log_debug ("%s: sub: '%s'\n", __func__, sub);
/* Remove any spaces and upcase the rest. */
for (i = j = 0; sub[i]; i ++, j ++)
{
while (sub[i] == ' ')
i ++;
if (i != j)
sub[j] = toupper (sub[i]);
}
sub[j] = 0;
if (DBG_PACKET && strcmp (sub_orig, sub) != 0)
log_debug ("%s: sub postprocessed: '%s'\n", __func__, sub);
/* Make sure it is in the form of a keyid (short or long) or a
fingerprint. */
if (strspn (sub, "0123456789ABCDEF") != strlen (sub)
|| !(strlen (sub) == 8 || strlen (sub) != 16 || strlen (sub) != 40))
{
log_error ("'%s' is not a valid key id or fingerprint.\n", sub_orig);
err = gpg_error (GPG_ERR_INV_VALUE);
goto out;
}
/* Get the session keys and make sure the public keys are
decrypted. */
err = mailing_list_subscribers_ext (ctrl, ml_kb, NULL, &deks, &ndeks);
if (err)
{
log_error ("Failed to get session keys: %s\n", gpg_strerror (err));
goto out;
}
/* Iterate and decrypt all of the keys to get their real key ids. */
{
KBNODE n;
for (n = ml_kb; n; n = n->next)
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char id[41];
int match = 0;
ek = n->pkt->pkt.public_key;
if (DBG_PACKET)
log_debug ("%s: considering subkey %s.\n",
__func__, keystr (ek->keyid));
switch (strlen (sub))
{
case 8:
match = strcmp (format_keyid (ek->keyid, KF_SHORT,
id, sizeof (id)),
sub) == 0;
break;
case 16:
match = strcmp (format_keyid (ek->keyid, KF_LONG,
id, sizeof (id)),
sub) == 0;
break;
case 40:
match = strcmp (fingerprint_from_pk (ek, id, NULL), sub) == 0;
break;
default:
assert (! "Unhandled case.");
}
if (match)
break;
}
if (! n)
{
log_error ("No subkey matches %s\n", sub_orig);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto out;
}
if (DBG_PACKET)
log_debug ("%s: subkey %s matched.\n",
__func__, keystr (ek->keyid));
if (ek->has_expired)
{
log_error ("Subscriber %s was already removed.\n", sub_orig);
err = 0;
goto out;
}
/* We need to generate a new session key. */
{
const char *notation = "subscriber-list-session-key@gnupg.org";
struct notation *notation_blob;
STRING2KEY *symkey_s2k = NULL;
DEK *symkey_dek = NULL;
DEK dek;
/* The symmetrically encrypted session key as a packet. */
iobuf_t sk_esk;
char *buffer;
size_t len;
/* setup_symkey needs a passphrase. We have a static passphrase.
To communicate this to setup_symkey, we use the
set_next_passphrase function, which preloads the passphrase and
causes setup_symkey to not ask the user, which is exactly what
we want. */
set_next_passphrase (bin2hex (deks[ndeks - 1].key, deks[ndeks - 1].keylen,
NULL));
err = setup_symkey (&symkey_s2k, &symkey_dek);
if (err)
{
log_bug ("Failed to initialize s2k and dek buffers: %s\n",
gpg_strerror (err));
return err;
}
memset (&dek, 0, sizeof (dek));
dek.algo = default_cipher_algo ();
make_session_key (&dek);
sk_esk = iobuf_temp ();
if (! sk_esk)
{
log_bug ("Out of memory allocating sk_esk\n");
return gpg_error (GPG_ERR_INTERNAL);
}
err = write_symkey_enc (symkey_s2k, symkey_dek, &dek, sk_esk);
if (err)
{
log_bug ("Failed to generate a symmetric key: %s\n",
gpg_strerror (err));
return err;
}
buffer = iobuf_get_temp_buffer (sk_esk);
len = iobuf_get_temp_length (sk_esk);
notation_blob =
blob_to_notation (notation, buffer, len);
if (! notation_blob)
{
log_bug ("Failed to create notation: %s=<SE-ESK packet, %zd bytes>\n",
notation, len);
return gpg_error (GPG_ERR_INTERNAL);
}
if (MAILING_LIST_DUMP_NOTATIONS)
{
char *fn =
xasprintf ("%s/mailing-list-%s-subscriber-list-session-key-%d.gpg",
opt.homedir, keystr (ml_pk->keyid), ndeks);
FILE *fp = fopen (fn, "w");
xfree (fn);
fwrite (buffer, len, 1, fp);
fclose (fp);
}
notation_blob->next = notations;
notations = notation_blob;
}
/* Record the key that the notation was encrypted with. */
{
char *notation
= xasprintf ("subscriber-list-session-key-encrypted-with@gnupg.org=%d",
ndeks - 1);
struct notation *notation_blob;
notation_blob = string_to_notation (notation, 0);
if (! notation_blob)
{
log_bug ("Failed to create notation: %s\n", notation);
return gpg_error (GPG_ERR_INTERNAL);
}
notation_blob->next = notations;
notations = notation_blob;
}
/* Add the notations and update the expiration time. */
for (n = n->next; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next)
{
PKT_signature *sig = n->pkt->pkt.signature;
if (DBG_PACKET)
log_debug ("%s: sig: keyid: %s; class: %x; chosen: %d\n",
__func__, keystr (sig->keyid), sig->sig_class,
sig->flags.chosen_selfsig);
if (ml_pk->keyid[0] == sig->keyid[0] && ml_pk->keyid[1] == sig->keyid[1]
&& sig->sig_class == 0x18
&& sig->flags.chosen_selfsig)
break;
}
if (!n || n->pkt->pkttype != PKT_SIGNATURE)
{
log_error ("subkey %s missing key binding signature!\n", sub_orig);
err = gpg_error (GPG_ERR_INV_DATA);
goto out;
}
/* Modify the signature. */
{
PKT_signature *sig = n->pkt->pkt.signature;
PKT_signature *newsig;
PACKET *newpkt;
KBNODE n2;
ek->expiredate = make_timestamp();
mailing_list_reprotect_one (ek);
err = update_keysig_packet (&newsig, sig, ml_pk, NULL, ek,
ml_pk, notations,
keygen_add_key_expire, ek);
if (err)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (err));
return 0;
}
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
/* Add the packet. */
n2 = new_kbnode (newpkt);
n2->next = n->next;
n->next = n2;
}
}
if (DBG_PACKET)
{
log_debug ("%s: Keyblock after adding new signature marking %s expired:\n",
__func__, keystr (ek->keyid));
kbnode_dump (ml_kb);
}
{
KEYDB_HANDLE hd = keydb_new ();
err = keydb_update_keyblock (hd, ml_kb);
keydb_release (hd);
if (err)
log_error ("Error saving %s's keyblock.\n",
keystr (ml_pk->keyid));
}
out:
free_notation (notations);
xfree (deks);
xfree (sub);
return err;
}
/* Return the actual subscribers to the mailing list in *PKLISTP and,
in the process, update their public key parameters and creation
time stamp with the actual values taken from the encrypted data.
It is ok for PKLISTP to be NULL. In this case, only KB is
updated.
Calling this function multiple times is okay. It just costs some
extra processing time. */
gpg_error_t
mailing_list_subscribers (ctrl_t ctrl, KBNODE kb, PK_LIST *pklistp)
{
return mailing_list_subscribers_ext (ctrl, kb, pklistp, NULL, NULL);
}
gpg_error_t
mailing_list_subscribers_ext (ctrl_t ctrl, KBNODE kb, PK_LIST *pklistp,
DEK **deksp, int *ndeksp)
{
gpg_error_t err;
DEK *deks = NULL;
int ndeks;
PK_LIST pklist = NULL;
KBNODE n;
PKT_public_key *pk = NULL;
err = mailing_list_get_subscriber_list_session_keys (ctrl, kb,
&deks, &ndeks);
if (err)
return err;
for (n = kb; n; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = n->pkt->pkt.public_key;
if (gcry_mpi_cmp (pk->pkey[0], GCRYMPI_CONST_TWO) != 0)
/* Key is already decrypted. No need to do it again. */
{
if (DBG_PACKET)
log_debug ("%s: Already decrypted %s.\n",
__func__, keystr (pk->keyid));
pk = NULL;
}
}
else if (pk && n->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = n->pkt->pkt.signature;
struct notation *notations;
struct notation *x;
int session_key = -1;
char *pkenc = NULL;
int pklen = 0;
notations = sig_to_notation (sig);
if (! notations)
continue;
for (x = notations; x; x = x->next)
if (strcmp (x->name, "public-key-encrypted-with@gnupg.org") == 0)
/* If the public key is encrypted, then this is a subscriber. */
session_key = atoi (x->value);
else if (strcmp (x->name, "public-key@gnupg.org") == 0)
{
pklen = x->blen;
pkenc = xmalloc (pklen);
memcpy (pkenc, x->bdat, pklen);
}
if (session_key == -1 && pkenc)
log_error ("subkey public-key-encrypted-with@gnupg.org notation, but no public-key@gnupg.org notation!");
else if (session_key != -1 && ! pkenc)
log_error ("subkey public-key@gnupg.org notation, but no public-key-encrypted-with@gnupg.org notation!");
else if (session_key != -1 && pkenc)
{
if (session_key >= ndeks)
log_error ("Unable to decrypt subkey %s: session key %d not available.\n",
keystr (pk->keyid), session_key);
else
{
DEK dek = deks[session_key];
iobuf_t encrypted_data
= iobuf_temp_with_content (pkenc, pklen);
estream_t decrypted_data_fp;
char *decrypted_data = NULL;
size_t len = 0;
/* We don't use an MDC as it adds unnecessary
overhead: notations are stored in the hashed
area, which is signed so an MDC doesn't offer any
extra protection. */
int no_mdc_warn = opt.no_mdc_warn;
int ignore_mdc_error = opt.ignore_mdc_error;
u32 timestamp;
decrypted_data_fp = es_fopenmem (0, "rw,samethread");
if (! decrypted_data_fp)
log_fatal ("Error creating memory stream\n");
opt.no_mdc_warn = 1;
opt.ignore_mdc_error = 1;
err = proc_encrypted_data (ctrl, encrypted_data, &dek,
decrypted_data_fp, &timestamp);
opt.no_mdc_warn = no_mdc_warn;
opt.ignore_mdc_error = ignore_mdc_error;
if (es_fclose_snatch (decrypted_data_fp,
(void **) &decrypted_data, &len))
log_fatal ("error snatching memory stream\n");
pk->flags.pkey_decrypted = 1;
pk->timestamp_saved = pk->timestamp;
pk->timestamp = timestamp;
{
char *p = decrypted_data;
int i;
size_t r;
/* XXX: Remove this check (and the data that we
insert). */
if (memcmp (p, "XXXYYY ", 7) != 0)
log_bug ("Bad data.\n");
p += 7; len -= 7;
for (i = 0; i < pubkey_get_npkey (pk->pubkey_algo); i ++)
{
if (len == 0)
err = GPG_ERR_TOO_SHORT;
if (! err)
err = gcry_mpi_scan (&pk->pkey[i], GCRYMPI_FMT_PGP,
p, len, &r);
if (err)
{
log_error ("Error parsing MPI %d\n", i + 1);
goto out;
}
p += r;
len -= r;
}
}
/* Recompute ml_ek->keyid. */
{
char fp[MAX_FINGERPRINT_LEN];
size_t fplen;
u32 keyid[2];
char *keyid_old
= format_keyid (pk->keyid, KF_DEFAULT, NULL, 0);
fingerprint_from_pk (pk, fp, &fplen);
keyid_from_fingerprint (fp, fplen, keyid);
pk->keyid[0] = keyid[0];
pk->keyid[1] = keyid[1];
if (DBG_PACKET)
log_debug ("Decrypted %s -> %s\n",
keyid_old, keystr (keyid));
}
xfree (decrypted_data);
if (pklistp && ! pk->has_expired)
{
PK_LIST r;
r = xmalloc_clear (sizeof *r);
r->pk = copy_public_key (NULL, pk);
r->next = pklist;
pklist = r;
}
}
}
xfree (pkenc);
free_notation (notations);
}
else
{
if (pk)
{
log_info ("Warning: %s is not a valid subscriber (missing notations)\n",
keystr (pk->keyid));
pk = NULL;
}
}
}
out:
if (! err && deksp)
{
*deksp = deks;
*ndeksp = ndeks;
}
else
xfree (deks);
if (err)
release_pk_list (pklist);
else if (pklistp)
*pklistp = pklist;
return err;
}
/* mailing_list_subscribers (and related functions) decrypt the
mailing list parameters and update the keyblock. These should
never be written to disk. This function restores the fake
values. */
gpg_error_t
mailing_list_reprotect_one (PKT_public_key *pk)
{
if (pk->flags.pkey_decrypted)
{
int i;
for (i = 0; i < pubkey_get_npkey (pk->pubkey_algo); i ++)
pk->pkey[i] = GCRYMPI_CONST_TWO;
pk->timestamp = pk->timestamp_saved;
pk->timestamp_saved = 0;
pk->flags.pkey_decrypted = 0;
}
return 0;
}
gpg_error_t
mailing_list_reprotect (KBNODE kb)
{
gpg_error_t err = 0;
PKT_public_key *pk = kb->pkt->pkt.public_key;
KBNODE n;
if (! pk->flags.mailing_list)
/* Not a mailing list key. Nothing to do. */
return 0;
for (n = kb; n; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = n->pkt->pkt.public_key;
err = mailing_list_reprotect_one (pk);
if (err)
return err;
}
}
return err;
}