mirror of
git://git.gnupg.org/gnupg.git
synced 2025-01-21 14:47:03 +01:00
1041 lines
29 KiB
C
1041 lines
29 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;
|
|
|
|
/* 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 unsubscriber, 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;
|
|
|
|
if (n->pkt->pkttype != PKT_PUBLIC_SUBKEY)
|
|
continue;
|
|
|
|
fingerprint_from_pk (n->pkt->pkt.public_key, 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, readd. */
|
|
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). */
|
|
{
|
|
DEK *deks = NULL;
|
|
int ndeks;
|
|
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);
|
|
}
|
|
|
|
/* 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->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. */
|
|
{
|
|
int i;
|
|
int n = pubkey_get_npkey (ml_ek->pubkey_algo);
|
|
|
|
for (i = 0; i < n; i ++)
|
|
{
|
|
/* XXX: Finish me: actually encrypt the keys; don't just copy
|
|
them. */
|
|
(void) session_key;
|
|
|
|
ml_ek->pkey[i] = gcry_mpi_copy (ml_ek->pkey[i]);
|
|
}
|
|
|
|
/* XXX: Encrypt the creation time. */
|
|
|
|
/* 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);
|
|
|
|
/* XXX */
|
|
if (DBG_PACKET)
|
|
{
|
|
char *fn = xasprintf ("/tmp/subscriber-list-key-%s",
|
|
keystr (sub_ek->keyid));
|
|
FILE *fp = fopen (fn, "w");
|
|
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,
|
|
make_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;
|
|
}
|
|
|
|
err = mailing_list_get_subscriber_list_session_keys (ctrl, ml_kb,
|
|
&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);
|
|
}
|
|
|
|
{
|
|
char *fn = xasprintf ("/tmp/subscriber-list-session-key-%d", 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();
|
|
|
|
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;
|
|
}
|
|
|
|
gpg_error_t
|
|
mailing_list_subscribers (ctrl_t ctrl, KBNODE kb, PK_LIST *pklistp)
|
|
{
|
|
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);
|
|
|
|
for (n = kb; n; n = n->next)
|
|
{
|
|
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
|
|
{
|
|
pk = n->pkt->pkt.public_key;
|
|
if (pk->has_expired)
|
|
/* The subscriber was removed. */
|
|
{
|
|
if (DBG_PACKET)
|
|
log_debug ("%s: Skipping subscriber %s who was unsubscribed.\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;
|
|
|
|
notations = sig_to_notation (sig);
|
|
if (! notations)
|
|
continue;
|
|
|
|
for (x = notations; x; x = x->next)
|
|
/* If the public key is encrypted, then this is a subscriber. */
|
|
if (strcmp (x->name, "public-key-encrypted-with@gnupg.org") == 0)
|
|
{
|
|
PK_LIST r = xmalloc_clear (sizeof *r);
|
|
int i = atoi (x->value);
|
|
|
|
if (i >= ndeks)
|
|
{
|
|
log_error ("Unable to decrypt subkey %s: session key %d not available.\n",
|
|
keystr (pk->keyid), i);
|
|
goto out;
|
|
}
|
|
|
|
/* XXX: Decrypt the public key parameters using the
|
|
session key. */
|
|
(void) i;
|
|
|
|
r->pk = copy_public_key (NULL, pk);
|
|
r->next = pklist;
|
|
pklist = r;
|
|
}
|
|
|
|
free_notation (notations);
|
|
}
|
|
else
|
|
{
|
|
if (pk)
|
|
{
|
|
log_info ("Warning: %s is not a valid subscriber (missing notations)\n",
|
|
keystr (pk->keyid));
|
|
pk = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
out:
|
|
xfree (deks);
|
|
|
|
if (err)
|
|
release_pk_list (pklist);
|
|
else
|
|
*pklistp = pklist;
|
|
|
|
return err;
|
|
}
|