diff options
| author | Justus Winter <[email protected]> | 2017-02-17 14:44:35 +0000 | 
|---|---|---|
| committer | Justus Winter <[email protected]> | 2017-02-17 15:28:00 +0000 | 
| commit | 48634e651fcd02431c0518d42ada1f3b402feb2c (patch) | |
| tree | 1bb947a1e09e96982f44d47d15dd3d6b5a825964 /lang/python | |
| parent | python: Fix teardown of ephemeral contexts. (diff) | |
| download | gpgme-48634e651fcd02431c0518d42ada1f3b402feb2c.tar.gz gpgme-48634e651fcd02431c0518d42ada1f3b402feb2c.zip | |
python: Support quick key signing.
* NEWS: Update.
* doc/gpgme.texi (gpgme_op_keysign): Fix the description of the
'expire' argument.
* lang/python/gpg/constants/__init__.py: Import new file.
* lang/python/gpg/constants/keysign.py: New file.
* lang/python/gpg/core.py (Context.key_sign): New function.
* lang/python/tests/Makefile.am (py_tests): Add new test.
* lang/python/tests/t-quick-key-signing.py: New test.
Signed-off-by: Justus Winter <[email protected]>
Diffstat (limited to '')
| -rw-r--r-- | lang/python/gpg/constants/__init__.py | 4 | ||||
| -rw-r--r-- | lang/python/gpg/constants/keysign.py | 25 | ||||
| -rw-r--r-- | lang/python/gpg/core.py | 41 | ||||
| -rw-r--r-- | lang/python/tests/Makefile.am | 3 | ||||
| -rwxr-xr-x | lang/python/tests/t-quick-key-signing.py | 120 | 
5 files changed, 190 insertions, 3 deletions
| diff --git a/lang/python/gpg/constants/__init__.py b/lang/python/gpg/constants/__init__.py index 2bf180e5..79d1fbc1 100644 --- a/lang/python/gpg/constants/__init__.py +++ b/lang/python/gpg/constants/__init__.py @@ -26,14 +26,14 @@ del util  # For convenience, we import the modules here.  from . import data, keylist, sig # The subdirs. -from . import create, event, md, pk, protocol, sigsum, status, validity +from . import create, event, keysign, md, pk, protocol, sigsum, status, validity  # A complication arises because 'import' is a reserved keyword.  # Import it as 'Import' instead.  globals()['Import'] = getattr(__import__('', globals(), locals(),                                           [str('import')], 1), "import") -__all__ = ['data', 'event', 'import', 'keylist', 'md', 'pk', +__all__ = ['data', 'event', 'import', 'keysign', 'keylist', 'md', 'pk',             'protocol', 'sig', 'sigsum', 'status', 'validity', 'create']  # GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact.  We diff --git a/lang/python/gpg/constants/keysign.py b/lang/python/gpg/constants/keysign.py new file mode 100644 index 00000000..fccdbc42 --- /dev/null +++ b/lang/python/gpg/constants/keysign.py @@ -0,0 +1,25 @@ +# Flags for key signing +# +# 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 Lesser General Public License as +# published by the Free Software Foundation; either version 2.1 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 <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +from gpg import util +util.process_constants('GPGME_KEYSIGN_', globals()) +del util diff --git a/lang/python/gpg/core.py b/lang/python/gpg/core.py index 28d4629e..cb4ccf73 100644 --- a/lang/python/gpg/core.py +++ b/lang/python/gpg/core.py @@ -675,6 +675,47 @@ class Context(GpgmeWrapper):          """          self.op_revuid(key, uid, 0) +    def key_sign(self, key, uids=None, expires_in=False, local=False): +        """Sign a key + +        Sign a key with the current set of signing keys.  Calling this +        function is only valid for the OpenPGP protocol. + +        If UIDS is None (the default), then all UIDs are signed.  If +        it is a string, then only the matching UID is signed.  If it +        is a list of strings, then all matching UIDs are signed.  Note +        that a case-sensitive exact string comparison is done. + +        EXPIRES_IN specifies the expiration time of the signature in +        seconds.  If EXPIRES_IN is False, the signature does not +        expire. + +        Keyword arguments: +        uids         -- user ids to sign, see above (default: sign all) +        expires_in   -- validity period of the signature in seconds +                                               (default: do not expire) +        local        -- create a local, non-exportable signature +                                               (default: False) + +        Raises: +        GPGMEError   -- as signaled by the underlying library + +        """ +        flags = 0 +        if uids == None or util.is_a_string(uids): +            pass#through unchanged +        else: +            flags |= constants.keysign.LFSEP +            uids = "\n".join(uids) + +        if not expires_in: +            flags |= constants.keysign.NOEXPIRE + +        if local: +            flags |= constants.keysign.LOCAL + +        self.op_keysign(key, uids, expires_in, flags) +      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 1d5e1db5..7251cd30 100644 --- a/lang/python/tests/Makefile.am +++ b/lang/python/tests/Makefile.am @@ -53,7 +53,8 @@ py_tests = t-wrapper.py \  	t-protocol-assuan.py \  	t-quick-key-creation.py \  	t-quick-subkey-creation.py \ -	t-quick-key-manipulation.py +	t-quick-key-manipulation.py \ +	t-quick-key-signing.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-signing.py b/lang/python/tests/t-quick-key-signing.py new file mode 100755 index 00000000..f9778a33 --- /dev/null +++ b/lang/python/tests/t-quick-key-signing.py @@ -0,0 +1,120 @@ +#!/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 <http://www.gnu.org/licenses/>. + +from __future__ import absolute_import, print_function, unicode_literals +del absolute_import, print_function, unicode_literals + +import gpg +import itertools +import time + +import support + +with support.EphemeralContext() as ctx: +    uid_counter = 0 +    def make_uid(): +        global uid_counter +        uid_counter += 1 +        return "user{0}@invalid.example.org".format(uid_counter) + +    def make_key(): +        uids = [make_uid() for i in range(3)] +        res = ctx.create_key(uids[0], certify=True) +        key = ctx.get_key(res.fpr) +        for u in uids[1:]: +            ctx.key_add_uid(key, u) +        return key, uids + +    def check_sigs(key, expected_sigs): +        keys = list(ctx.keylist(key.fpr, mode=(gpg.constants.keylist.mode.LOCAL +                                               |gpg.constants.keylist.mode.SIGS))) +        assert len(keys) == 1 +        key_uids = {uid.uid: [s for s in uid.signatures] for uid in keys[0].uids} +        expected = list(expected_sigs) + +        while key_uids and expected: +            uid, signing_key, func = expected[0] +            match = False +            for i, s in enumerate(key_uids[uid]): +                if signing_key.fpr.endswith(s.keyid): +                    if func: +                        func(s) +                    match = True +                    break +            if match: +                expected.pop(0) +                key_uids[uid].pop(i) +                if not key_uids[uid]: +                    del key_uids[uid] + +        assert not key_uids, "Superfluous signatures: {0}".format(key_uids) +        assert not expected, "Missing signatures: {0}".format(expected) + +    # Simplest case.  Sign without any options. +    key_a, uids_a = make_key() +    key_b, uids_b = make_key() +    ctx.signers = [key_a] + +    def exportable_non_expiring(s): +        assert s.exportable +        assert s.expires == 0 + +    check_sigs(key_b, itertools.product(uids_b, [key_b], [exportable_non_expiring])) +    ctx.key_sign(key_b) +    check_sigs(key_b, itertools.product(uids_b, [key_b, key_a], [exportable_non_expiring])) + +    # Create a non-exportable signature, and explicitly name all uids. +    key_c, uids_c = make_key() +    ctx.signers = [key_a, key_b] + +    def non_exportable_non_expiring(s): +        assert s.exportable == 0 +        assert s.expires == 0 + +    ctx.key_sign(key_c, local=True, uids=uids_c) +    check_sigs(key_c, +               list(itertools.product(uids_c, [key_c], +                                      [exportable_non_expiring])) +               + list(itertools.product(uids_c, [key_b, key_a], +                                        [non_exportable_non_expiring]))) + +    # Create a non-exportable, expiring signature for a single uid. +    key_d, uids_d = make_key() +    ctx.signers = [key_c] +    expires_in = 600 +    slack = 10 + +    def non_exportable_expiring(s): +        assert s.exportable == 0 +        assert abs(time.time() + expires_in - s.expires) < slack + +    ctx.key_sign(key_d, local=True, expires_in=expires_in, uids=uids_d[0]) +    check_sigs(key_d, +               list(itertools.product(uids_d, [key_d], +                                      [exportable_non_expiring])) +               + list(itertools.product(uids_d[:1], [key_c], +                                        [non_exportable_expiring]))) + +    # Now sign the second in the same fashion, but use a singleton list. +    ctx.key_sign(key_d, local=True, expires_in=expires_in, uids=uids_d[1:2]) +    check_sigs(key_d, +               list(itertools.product(uids_d, [key_d], +                                      [exportable_non_expiring])) +               + list(itertools.product(uids_d[:2], [key_c], +                                        [non_exportable_expiring]))) | 
