From 7641b7b5f2c9d5b38c60cd9326bcb4810c37dae5 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Thu, 16 Feb 2017 17:52:49 +0100 Subject: [PATCH] python: Support adding and revoking UIDs. * NEWS: Update. * lang/python/gpg/core.py (Context.key_add_uid): New function. (Context.key_revoke_uid): Likewise. * lang/python/tests/Makefile.am (XTESTS): Add new test. * lang/python/tests/t-quick-key-manipulation.py: New file. Signed-off-by: Justus Winter --- NEWS | 2 + lang/python/gpg/core.py | 24 ++++ lang/python/tests/Makefile.am | 3 +- lang/python/tests/t-quick-key-manipulation.py | 103 ++++++++++++++++++ 4 files changed, 131 insertions(+), 1 deletion(-) create mode 100755 lang/python/tests/t-quick-key-manipulation.py diff --git a/NEWS b/NEWS index d2df4448..889a5265 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,8 @@ Noteworthy changes in version 1.8.1 (unreleased) py: Context.keylist EXTENDED: New keyword arg mode. py: Context.create_key NEW. py: Context.create_subkey NEW. + py: Context.key_add_uid NEW. + py: Context.key_revoke_uid NEW. py: core.pubkey_algo_string NEW. py: core.addrspec_from_uid NEW. diff --git a/lang/python/gpg/core.py b/lang/python/gpg/core.py index 2a4df99b..beaebda2 100644 --- a/lang/python/gpg/core.py +++ b/lang/python/gpg/core.py @@ -651,6 +651,30 @@ class Context(GpgmeWrapper): return self.op_genkey_result() + def key_add_uid(self, key, uid): + """Add a UID + + Add the uid UID to the given KEY. Calling this function is + only valid for the OpenPGP protocol. + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + self.op_adduid(key, uid, 0) + + def key_revoke_uid(self, key, uid): + """Revoke a UID + + Revoke the uid UID from the given KEY. Calling this function + is only valid for the OpenPGP protocol. + + Raises: + GPGMEError -- as signaled by the underlying library + + """ + self.op_revuid(key, uid, 0) + def assuan_transact(self, command, data_cb=None, inquire_cb=None, status_cb=None): """Issue a raw assuan command diff --git a/lang/python/tests/Makefile.am b/lang/python/tests/Makefile.am index 62c6087f..1d5e1db5 100644 --- a/lang/python/tests/Makefile.am +++ b/lang/python/tests/Makefile.am @@ -52,7 +52,8 @@ py_tests = t-wrapper.py \ t-idiomatic.py \ t-protocol-assuan.py \ t-quick-key-creation.py \ - t-quick-subkey-creation.py + t-quick-subkey-creation.py \ + t-quick-key-manipulation.py XTESTS = initial.py $(py_tests) final.py EXTRA_DIST = support.py $(XTESTS) encrypt-only.asc sign-only.asc \ diff --git a/lang/python/tests/t-quick-key-manipulation.py b/lang/python/tests/t-quick-key-manipulation.py new file mode 100755 index 00000000..62c395ab --- /dev/null +++ b/lang/python/tests/t-quick-key-manipulation.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +# Copyright (C) 2017 g10 Code GmbH +# +# This file is part of GPGME. +# +# GPGME 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 2 of the License, or +# (at your option) any later version. +# +# GPGME 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 Lesser General +# Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, see . + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +import gpg +import itertools +import os +import shutil +import time + +import support + +alpha = "Alpha " +bravo = "Bravo " + +def copy_configuration(destination): + home = os.environ['GNUPGHOME'] + shutil.copy(os.path.join(home, "gpg.conf"), destination) + shutil.copy(os.path.join(home, "gpg-agent.conf"), destination) + +with support.TemporaryDirectory() as tmp: + copy_configuration(tmp) + with gpg.Context(home_dir=tmp) as ctx: + res = ctx.create_key(alpha, certify=True) + key = ctx.get_key(res.fpr) + assert len(key.subkeys) == 1, "Expected one primary key and no subkeys" + assert len(key.uids) == 1, "Expected exactly one UID" + + def get_uid(uid): + key = ctx.get_key(res.fpr) + for u in key.uids: + if u.uid == uid: + return u + return None + + # sanity check + uid = get_uid(alpha) + assert uid, "UID alpha not found" + assert uid.revoked == 0 + + # add bravo + ctx.key_add_uid(key, bravo) + uid = get_uid(bravo) + assert uid, "UID bravo not found" + assert uid.revoked == 0 + + # revoke alpha + ctx.key_revoke_uid(key, alpha) + uid = get_uid(alpha) + assert uid, "UID alpha not found" + assert uid.revoked == 1 + uid = get_uid(bravo) + assert uid, "UID bravo not found" + assert uid.revoked == 0 + + # try to revoke the last UID + try: + ctx.key_revoke_uid(key, alpha) + # IMHO this should fail. issue2961. + # assert False, "Expected an error but got none" + except gpg.errors.GpgError: + pass + + # Everything should be the same + uid = get_uid(alpha) + assert uid, "UID alpha not found" + assert uid.revoked == 1 + uid = get_uid(bravo) + assert uid, "UID bravo not found" + assert uid.revoked == 0 + + # try to revoke a non-existent UID + try: + ctx.key_revoke_uid(key, "i dont exist") + # IMHO this should fail. issue2963. + # assert False, "Expected an error but got none" + except gpg.errors.GpgError: + pass + + # try to add an pre-existent UID + try: + ctx.key_add_uid(key, bravo) + assert False, "Expected an error but got none" + except gpg.errors.GpgError: + pass