[PATCH] g10: implement gpg --quick-revuid
Daniel Kahn Gillmor
dkg at fifthhorseman.net
Fri Jun 17 00:05:57 CEST 2016
* g10/revoke.c (get_default_uid_revocation_reason): new
* g10/keyedit.c (menu_revuid): break out creation of uid revocation
into new function core_revuid
* g10/keyedit.c (keyedit_quick_revuid): new: selects key and
uid, invokes core_revuid
* g10/gpg.c (main): handle --quick-revuid argument
* doc/gpg.texi, doc/whats-new-in-2.1.txt: document --quick-revuid
* tests/openpgp/quick-key-manipulation.test: add simple test of
--quick-* commands
--
This functionality is a counterpart to --quick-adduid, and will be
useful for projects that depend programmatically on gpg to revoke user
IDs (one such example is "monkeysphere-host revoke-servicename").
Signed-off-by: Daniel Kahn Gillmor <dkg at fifthhorseman.net>
---
doc/gpg.texi | 9 +
doc/whats-new-in-2.1.txt | 14 ++
g10/gpg.c | 17 ++
g10/keyedit.c | 268 ++++++++++++++++++++++--------
g10/main.h | 3 +
g10/revoke.c | 10 ++
tests/openpgp/Makefile.am | 1 +
tests/openpgp/quick-key-manipulation.test | 70 ++++++++
8 files changed, 327 insertions(+), 65 deletions(-)
create mode 100755 tests/openpgp/quick-key-manipulation.test
diff --git a/doc/gpg.texi b/doc/gpg.texi
index 6437b90..92ff835 100644
--- a/doc/gpg.texi
+++ b/doc/gpg.texi
@@ -1041,6 +1041,15 @@ the interactive sub-command @code{adduid} of @option{--edit-key} the
white space removed, it is expected to be UTF-8 encoded, and no checks
on its form are applied.
+ at item --quick-revuid @var{user-id} @var{user-id-to-revoke}
+ at opindex quick-revuid
+This command revokes a User ID on an existing key. It cannot be used
+to revoke the last User ID on key (some non-revoked User ID must
+remain), with revocation reason ``User ID is no longer valid''. If
+you want to specify a different revocation reason, or to supply
+supplementary revocation text, you should use the interactive
+sub-command @code{revuid} of @option{--edit-key}.
+
@item --passwd @var{user_id}
@opindex passwd
Change the passphrase of the secret key belonging to the certificate
diff --git a/doc/whats-new-in-2.1.txt b/doc/whats-new-in-2.1.txt
index 6c46b04..d8b9ae1 100644
--- a/doc/whats-new-in-2.1.txt
+++ b/doc/whats-new-in-2.1.txt
@@ -437,6 +437,20 @@ https://gnupg.org/faq/whats-new-in-2.1.html
│ uid [ unknown] EdDSA sample key 1
└────
+ Since version 2.1.14 it possible to revoke a user id on an existing
+ key:
+
+ ┌────
+ │ $ gpg2 -k 8CFDE12197965A9A
+ │ pub ed25519/8CFDE12197965A9A 2014-08-19
+ │ uid [ unknown] Sample 2 <me at example.org>
+ │ uid [ unknown] EdDSA sample key 1
+ │ $ gpg2 --quick-revuid 8CFDE12197965A9A 'EdDSA sample key 1'
+ │ $ gpg2 -k 8CFDE12197965A9A
+ │ pub ed25519/8CFDE12197965A9A 2014-08-19
+ │ uid [ unknown] Sample 2 <me at example.org>
+ └────
+
1.6 Improved Pinentry support
─────────────────────────────
diff --git a/g10/gpg.c b/g10/gpg.c
index 1f2d416..f6013f3 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -118,6 +118,7 @@ enum cmd_and_opt_values
aQuickLSignKey,
aQuickAddUid,
aQuickAddKey,
+ aQuickRevUid,
aListConfig,
aListGcryptConfig,
aGPGConfList,
@@ -430,6 +431,8 @@ static ARGPARSE_OPTS opts[] = {
ARGPARSE_c (aQuickAddUid, "quick-adduid",
N_("quickly add a new user-id")),
ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"),
+ ARGPARSE_c (aQuickRevUid, "quick-revuid",
+ N_("quickly revoke a user-id")),
ARGPARSE_c (aFullKeygen, "full-gen-key" ,
N_("full featured key pair generation")),
ARGPARSE_c (aGenRevoke, "gen-revoke",N_("generate a revocation certificate")),
@@ -2432,6 +2435,7 @@ main (int argc, char **argv)
case aQuickKeygen:
case aQuickAddUid:
case aQuickAddKey:
+ case aQuickRevUid:
case aExportOwnerTrust:
case aImportOwnerTrust:
case aRebuildKeydbCaches:
@@ -3777,6 +3781,7 @@ main (int argc, char **argv)
case aQuickKeygen:
case aQuickAddUid:
case aQuickAddKey:
+ case aQuickRevUid:
case aFullKeygen:
case aKeygen:
case aImport:
@@ -4196,6 +4201,18 @@ main (int argc, char **argv)
}
break;
+ case aQuickRevUid:
+ {
+ const char *uid, *uidtorev;
+
+ if (argc != 2)
+ wrong_args ("--quick-revuid USER-ID USER-ID-TO-REVOKE");
+ uid = *argv++; argc--;
+ uidtorev = *argv++; argc--;
+ keyedit_quick_revuid (ctrl, uid, uidtorev);
+ }
+ break;
+
case aFastImport:
opt.import_options |= IMPORT_FAST;
case aImport:
diff --git a/g10/keyedit.c b/g10/keyedit.c
index d05ea5d..2f106dd 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -87,6 +87,9 @@ static int real_uids_left (KBNODE keyblock);
static int count_selected_keys (KBNODE keyblock);
static int menu_revsig (KBNODE keyblock);
static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock);
+static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
+ const struct revocation_reason_info *reason,
+ int *modified);
static int menu_revkey (KBNODE pub_keyblock);
static int menu_revsubkey (KBNODE pub_keyblock);
#ifndef NO_TRUST_MODELS
@@ -2937,6 +2940,107 @@ keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
keydb_release (kdbhd);
}
+/* Unattended revokation of a keyid. USERNAME specifies the
+ key. UIDTOREV is the user id revoke from the key. */
+void
+keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev)
+{
+ gpg_error_t err;
+ KEYDB_HANDLE kdbhd = NULL;
+ KEYDB_SEARCH_DESC desc;
+ kbnode_t keyblock = NULL;
+ kbnode_t node;
+ int modified = 0;
+ size_t revlen;
+
+#ifdef HAVE_W32_SYSTEM
+ /* See keyedit_menu for why we need this. */
+ check_trustdb_stale ();
+#endif
+
+ /* Search the key; we don't want the whole getkey stuff here. */
+ kdbhd = keydb_new ();
+ if (!kdbhd)
+ {
+ /* Note that keydb_new has already used log_error. */
+ goto leave;
+ }
+
+ err = classify_user_id (username, &desc, 1);
+ if (!err)
+ err = keydb_search (kdbhd, &desc, 1, NULL);
+ if (!err)
+ {
+ err = keydb_get_keyblock (kdbhd, &keyblock);
+ if (err)
+ {
+ log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+ /* Now with the keyblock retrieved, search again to detect an
+ ambiguous specification. We need to save the found state so
+ that we can do an update later. */
+ keydb_push_found_state (kdbhd);
+ err = keydb_search (kdbhd, &desc, 1, NULL);
+ if (!err)
+ err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
+ else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
+ err = 0;
+ keydb_pop_found_state (kdbhd);
+
+ if (!err)
+ {
+ /* We require the secret primary key to revoke a UID. */
+ node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
+ if (!node)
+ BUG ();
+ err = agent_probe_secret_key (ctrl, node->pkt->pkt.public_key);
+ }
+ }
+ if (err)
+ {
+ log_error (_("secret key \"%s\" not found: %s\n"),
+ username, gpg_strerror (err));
+ goto leave;
+ }
+
+ fix_keyblock (&keyblock);
+ setup_main_keyids (keyblock);
+
+ revlen = strlen (uidtorev);
+ /* find the right UID */
+ for (node = keyblock; node; node = node->next)
+ {
+ if (node->pkt->pkttype == PKT_USER_ID &&
+ revlen == node->pkt->pkt.user_id->len &&
+ 0 == memcmp (node->pkt->pkt.user_id->name, uidtorev, revlen))
+ {
+ struct revocation_reason_info *reason = get_default_uid_revocation_reason();
+ err = core_revuid (ctrl, keyblock, node, reason, &modified);
+ release_revocation_reason_info (reason);
+ if (err)
+ {
+ log_error (_("User ID revocation failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+ err = keydb_update_keyblock (kdbhd, keyblock);
+ if (err)
+ {
+ log_error (_("update failed: %s\n"), gpg_strerror (err));
+ goto leave;
+ }
+
+ if (update_trust)
+ revalidation_mark ();
+ goto leave;
+ }
+ }
+
+ leave:
+ release_kbnode (keyblock);
+ keydb_release (kdbhd);
+}
+
/* Find a keyblock by fingerprint because only this uniquely
* identifies a key and may thus be used to select a key for
@@ -6106,6 +6210,95 @@ reloop: /* (must use this, because we are modifing the list) */
}
+/* return 0 if revocation of NODE (which must be a User ID) was
+ successful, non-zero if there was an error. *modified will be set
+ to 1 if a change was made. */
+static int
+core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
+ const struct revocation_reason_info *reason, int *modified)
+{
+ PKT_public_key *pk = keyblock->pkt->pkt.public_key;
+ gpg_error_t rc;
+
+ if (node->pkt->pkttype != PKT_USER_ID)
+ {
+ rc = gpg_error (GPG_ERR_NO_USER_ID);
+ write_status_error ("keysig", rc);
+ log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc));
+ return 1;
+ }
+ else
+ {
+ PKT_user_id *uid = node->pkt->pkt.user_id;
+
+ if (uid->is_revoked)
+ {
+ char *user = utf8_to_native (uid->name, uid->len, 0);
+ log_info (_("user ID \"%s\" is already revoked\n"), user);
+ xfree (user);
+ }
+ else
+ {
+ PACKET *pkt;
+ PKT_signature *sig;
+ struct sign_attrib attrib;
+ u32 timestamp = make_timestamp ();
+
+ if (uid->created >= timestamp)
+ {
+ /* Okay, this is a problem. The user ID selfsig was
+ created in the future, so we need to warn the user and
+ set our revocation timestamp one second after that so
+ everything comes out clean. */
+
+ log_info (_("WARNING: a user ID signature is dated %d"
+ " seconds in the future\n"),
+ uid->created - timestamp);
+
+ timestamp = uid->created + 1;
+ }
+
+ memset (&attrib, 0, sizeof attrib);
+ /* should not need to cast away const here; but
+ revocation_reason_build_cb needs to take a non-const
+ void* in order to meet the function signtuare for the
+ mksubpkt argument to make_keysig_packet */
+ attrib.reason = (struct revocation_reason_info *)reason;
+
+ rc = make_keysig_packet (&sig, pk, uid, NULL, pk, 0x30, 0,
+ timestamp, 0,
+ sign_mk_attrib, &attrib, NULL);
+ if (rc)
+ {
+ write_status_error ("keysig", rc);
+ log_error (_("signing failed: %s\n"), gpg_strerror (rc));
+ return 1;
+ }
+ else
+ {
+ pkt = xmalloc_clear (sizeof *pkt);
+ pkt->pkttype = PKT_SIGNATURE;
+ pkt->pkt.signature = sig;
+ insert_kbnode (node, new_kbnode (pkt), 0);
+
+#ifndef NO_TRUST_MODELS
+ /* If the trustdb has an entry for this key+uid then the
+ trustdb needs an update. */
+ if (!update_trust
+ && (get_validity (ctrl, pk, uid, NULL, 0) & TRUST_MASK) >=
+ TRUST_UNDEFINED)
+ update_trust = 1;
+#endif /*!NO_TRUST_MODELS*/
+
+ node->pkt->pkt.user_id->is_revoked = 1;
+ if (modified)
+ *modified = 1;
+ }
+ }
+ return 0;
+ }
+}
+
/* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if
keyblock changed. */
static int
@@ -6132,75 +6325,20 @@ menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock)
goto leave;
}
- reloop: /* (better this way because we are modifing the keyring) */
+ reloop: /* (better this way because we are modifying the keyring) */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID))
{
- PKT_user_id *uid = node->pkt->pkt.user_id;
-
- if (uid->is_revoked)
- {
- char *user = utf8_to_native (uid->name, uid->len, 0);
- log_info (_("user ID \"%s\" is already revoked\n"), user);
- xfree (user);
- }
- else
- {
- PACKET *pkt;
- PKT_signature *sig;
- struct sign_attrib attrib;
- u32 timestamp = make_timestamp ();
-
- if (uid->created >= timestamp)
- {
- /* Okay, this is a problem. The user ID selfsig was
- created in the future, so we need to warn the user and
- set our revocation timestamp one second after that so
- everything comes out clean. */
-
- log_info (_("WARNING: a user ID signature is dated %d"
- " seconds in the future\n"),
- uid->created - timestamp);
-
- timestamp = uid->created + 1;
- }
-
- memset (&attrib, 0, sizeof attrib);
- attrib.reason = reason;
-
+ int modified = 0;
+ rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified);
+ if (rc)
+ goto leave;
+ if (modified)
+ {
node->flag &= ~NODFLG_SELUID;
-
- rc = make_keysig_packet (&sig, pk, uid, NULL, pk, 0x30, 0,
- timestamp, 0,
- sign_mk_attrib, &attrib, NULL);
- if (rc)
- {
- write_status_error ("keysig", rc);
- log_error (_("signing failed: %s\n"), gpg_strerror (rc));
- goto leave;
- }
- else
- {
- pkt = xmalloc_clear (sizeof *pkt);
- pkt->pkttype = PKT_SIGNATURE;
- pkt->pkt.signature = sig;
- insert_kbnode (node, new_kbnode (pkt), 0);
-
-#ifndef NO_TRUST_MODELS
- /* If the trustdb has an entry for this key+uid then the
- trustdb needs an update. */
- if (!update_trust
- && (get_validity (ctrl, pk, uid, NULL, 0) & TRUST_MASK) >=
- TRUST_UNDEFINED)
- update_trust = 1;
-#endif /*!NO_TRUST_MODELS*/
-
- changed = 1;
- node->pkt->pkt.user_id->is_revoked = 1;
-
- goto reloop;
- }
- }
+ changed = 1;
+ goto reloop;
+ }
}
if (changed)
diff --git a/g10/main.h b/g10/main.h
index 7b716ff..b11a853 100644
--- a/g10/main.h
+++ b/g10/main.h
@@ -289,6 +289,8 @@ void keyedit_quick_adduid (ctrl_t ctrl, const char *username,
const char *newuid);
void keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
const char *usagestr, const char *expirestr);
+void keyedit_quick_revuid (ctrl_t ctrl, const char *username,
+ const char *uidtorev);
void keyedit_quick_sign (ctrl_t ctrl, const char *fpr,
strlist_t uids, strlist_t locusr, int local);
void show_basic_key_info (KBNODE keyblock);
@@ -407,6 +409,7 @@ int gen_desig_revoke (ctrl_t ctrl, const char *uname, strlist_t locusr);
int revocation_reason_build_cb( PKT_signature *sig, void *opaque );
struct revocation_reason_info *
ask_revocation_reason( int key_rev, int cert_rev, int hint );
+struct revocation_reason_info * get_default_uid_revocation_reason(void);
void release_revocation_reason_info( struct revocation_reason_info *reason );
/*-- keylist.c --*/
diff --git a/g10/revoke.c b/g10/revoke.c
index 218ca59..15a91ac 100644
--- a/g10/revoke.c
+++ b/g10/revoke.c
@@ -862,6 +862,16 @@ ask_revocation_reason( int key_rev, int cert_rev, int hint )
return reason;
}
+struct revocation_reason_info *
+get_default_uid_revocation_reason(void)
+{
+ struct revocation_reason_info *reason;
+ reason = xmalloc( sizeof *reason );
+ reason->code = 0x20; /* uid is no longer valid */
+ reason->desc = strdup(""); /* no text */
+ return reason;
+}
+
void
release_revocation_reason_info( struct revocation_reason_info *reason )
{
diff --git a/tests/openpgp/Makefile.am b/tests/openpgp/Makefile.am
index bb1047d..020df78 100644
--- a/tests/openpgp/Makefile.am
+++ b/tests/openpgp/Makefile.am
@@ -52,6 +52,7 @@ TESTS = version.test mds.test \
signencrypt.test signencrypt-dsa.test \
armsignencrypt.test armdetach.test \
armdetachm.test detachm.test genkey1024.test \
+ quick-key-manipulation.test \
conventional.test conventional-mdc.test \
multisig.test verify.test armor.test \
import.test ecc.test 4gb-packet.test \
diff --git a/tests/openpgp/quick-key-manipulation.test b/tests/openpgp/quick-key-manipulation.test
new file mode 100755
index 0000000..4185601
--- /dev/null
+++ b/tests/openpgp/quick-key-manipulation.test
@@ -0,0 +1,70 @@
+#!/bin/sh
+# Copyright 2016 Free Software Foundation, Inc.
+# This file is free software; as a special exception the author gives
+# unlimited permission to copy and/or distribute it, with or without
+# modifications, as long as this notice is preserved. This file is
+# distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY, to the extent permitted by law; without even the implied
+# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
+. $srcdir/defs.inc || exit 3
+
+export PINENTRY_USER_DATA=test
+
+alpha="Alpha <alpha at example.net>"
+bravo="Bravo <bravo at example.net>"
+
+$GPG --with-colons --with-fingerprint --list-secret-keys ="$alpha" &&
+ error "User ID '$alpha'exists when it should not!"
+$GPG --with-colons --with-fingerprint --list-secret-keys ="$bravo" &&
+ error "User ID '$bravo' exists when it should not!"
+
+#info verify that key creation works
+$GPG --quick-gen-key "$alpha" || \
+ error "failed to generate key"
+
+fpr=$($GPG --with-colons --with-fingerprint --list-secret-keys ="$alpha" | \
+ grep '^fpr:' | cut -f10 -d: | head -n1)
+
+$GPG --check-trustdb
+
+cleanup() {
+ $GPG --batch --yes --delete-secret-key "0x$fpr"
+ $GPG --batch --yes --delete-key "0x$fpr"
+}
+
+count_uids_of_secret() {
+ if ! [ $($GPG --with-colons --list-secret-keys ="$1" | \
+ grep -c '^uid:u:') = "$2" ] ; then
+ cleanup
+ error "wrong number of user IDs for '$1' after $3"
+ fi
+}
+
+count_uids_of_secret "$alpha" 1 "key generation"
+
+#info verify that we can add a user ID
+if ! $GPG --quick-adduid ="$alpha" "$bravo" ; then
+ cleanup
+ error "failed to add user id"
+fi
+
+$GPG --check-trustdb
+
+count_uids_of_secret "$alpha" 2 "adding User ID"
+count_uids_of_secret "$bravo" 2 "adding User ID"
+
+#info verify that we can revoke a user ID
+if ! $GPG --quick-revuid ="$bravo" "$alpha"; then
+ cleanup
+ error "failed to revoke user id"
+fi
+
+$GPG --check-trustdb
+
+count_uids_of_secret "$bravo" 1 "revoking user ID"
+
+cleanup
+
+! $GPG --with-colons --list-secret-keys ="$bravo" ||
+ error "key still exists when it should not!"
--
2.8.1
More information about the Gnupg-devel
mailing list