[git] GPGME - branch, master, updated. gpgme-1.8.0-80-g7641b7b

by Justus Winter cvs at cvs.gnupg.org
Thu Feb 16 17:56:27 CET 2017


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "GnuPG Made Easy".

The branch, master has been updated
       via  7641b7b5f2c9d5b38c60cd9326bcb4810c37dae5 (commit)
       via  13bace25e3d8422f93fd24919994be36042fd220 (commit)
       via  476b97822b169c30cc246c1de2ff94cf89084706 (commit)
       via  3bdce4aa3ddd4a3f55b24678faf978d61daa8909 (commit)
       via  048c5f74b61d5e4fa7617ce7c9111c6754bd4409 (commit)
      from  de708e5934cda380dbc3ae51f587c09041de7562 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 7641b7b5f2c9d5b38c60cd9326bcb4810c37dae5
Author: Justus Winter <justus at g10code.com>
Date:   Thu Feb 16 17:52:49 2017 +0100

    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 <justus at g10code.com>

diff --git a/NEWS b/NEWS
index d2df444..889a526 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 2a4df99..beaebda 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 62c6087..1d5e1db 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 0000000..62c395a
--- /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 <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 os
+import shutil
+import time
+
+import support
+
+alpha = "Alpha <alpha at invalid.example.net>"
+bravo = "Bravo <bravo at invalid.example.net>"
+
+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

commit 13bace25e3d8422f93fd24919994be36042fd220
Author: Justus Winter <justus at g10code.com>
Date:   Thu Feb 16 16:38:21 2017 +0100

    python: Support quick subkey creation.
    
    * NEWS: Update.
    * lang/python/gpg/core.py (Context.create_subkey): New function.
    * lang/python/tests/Makefile.am (XTESTS): Add new test.
    * lang/python/tests/t-quick-subkey-creation.py: New file.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/NEWS b/NEWS
index 7d30b94..d2df444 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,7 @@ Noteworthy changes in version 1.8.1 (unreleased)
  py: Context.home_dir        NEW.
  py: Context.keylist         EXTENDED: New keyword arg mode.
  py: Context.create_key      NEW.
+ py: Context.create_subkey   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 c5af1b1..2a4df99 100644
--- a/lang/python/gpg/core.py
+++ b/lang/python/gpg/core.py
@@ -579,6 +579,78 @@ class Context(GpgmeWrapper):
 
         return self.op_genkey_result()
 
+    def create_subkey(self, key, algorithm=None, expires_in=0, expires=True,
+                      sign=False, encrypt=False, authenticate=False, passphrase=None):
+        """Create a subkey
+
+        Create a subkey for the given KEY.  As subkeys are a concept
+        of OpenPGP, calling this is only valid for the OpenPGP
+        protocol.
+
+        ALGORITHM may be used to specify the public key encryption
+        algorithm for the new subkey.  By default, a reasonable
+        default is chosen.  You may use "future-default" to select an
+        algorithm that will be the default in a future implementation
+        of the engine.  ALGORITHM may be a string like "rsa", or
+        "rsa2048" to explicitly request an algorithm and a key size.
+
+        EXPIRES_IN specifies the expiration time of the subkey in
+        number of seconds since the subkeys creation.  By default, a
+        reasonable expiration time is chosen.  If you want to create a
+        subkey that does not expire, use the keyword argument EXPIRES.
+
+        SIGN, ENCRYPT, and AUTHENTICATE can be used to request the
+        capabilities of the new subkey.  If you don't request any, an
+        encryption subkey is generated.
+
+        If PASSPHRASE is None (the default), then the subkey will not
+        be protected with a passphrase.  If PASSPHRASE is a string, it
+        will be used to protect the subkey.  If PASSPHRASE is True,
+        the passphrase must be supplied using a passphrase callback or
+        out-of-band with a pinentry.
+
+        Keyword arguments:
+        algorithm    -- public key algorithm, see above (default: reasonable)
+        expires_in   -- expiration time in seconds (default: reasonable)
+        expires      -- whether or not the subkey should expire (default: True)
+        sign         -- request the signing capability (see above)
+        encrypt      -- request the encryption capability (see above)
+        authenticate -- request the authentication capability (see above)
+        passphrase   -- protect the subkey with a passphrase (default: no passphrase)
+
+        Returns:
+                     -- an object describing the result of the subkey creation
+
+        Raises:
+        GPGMEError   -- as signaled by the underlying library
+
+        """
+        if util.is_a_string(passphrase):
+            old_pinentry_mode = self.pinentry_mode
+            old_passphrase_cb = getattr(self, '_passphrase_cb', None)
+            self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK
+            def passphrase_cb(hint, desc, prev_bad, hook=None):
+                return passphrase
+            self.set_passphrase_cb(passphrase_cb)
+
+        try:
+            self.op_createsubkey(key, algorithm,
+                                 0, # reserved
+                                 expires_in,
+                                 ((constants.create.SIGN if sign else 0)
+                                  | (constants.create.ENCR if encrypt else 0)
+                                  | (constants.create.AUTH if authenticate else 0)
+                                  | (constants.create.NOPASSWD
+                                     if passphrase == None else 0)
+                                  | (0 if expires else constants.create.NOEXPIRE)))
+        finally:
+            if util.is_a_string(passphrase):
+                self.pinentry_mode = old_pinentry_mode
+                if old_passphrase_cb:
+                    self.set_passphrase_cb(*old_passphrase_cb[1:])
+
+        return self.op_genkey_result()
+
     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 5469e75..62c6087 100644
--- a/lang/python/tests/Makefile.am
+++ b/lang/python/tests/Makefile.am
@@ -51,7 +51,8 @@ py_tests = t-wrapper.py \
 	t-file-name.py \
 	t-idiomatic.py \
 	t-protocol-assuan.py \
-	t-quick-key-creation.py
+	t-quick-key-creation.py \
+	t-quick-subkey-creation.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-subkey-creation.py b/lang/python/tests/t-quick-subkey-creation.py
new file mode 100755
index 0000000..0d9f71f
--- /dev/null
+++ b/lang/python/tests/t-quick-subkey-creation.py
@@ -0,0 +1,121 @@
+#!/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 os
+import shutil
+import time
+
+import support
+
+alpha = "Alpha <alpha at invalid.example.net>"
+bravo = "Bravo <bravo at invalid.example.net>"
+
+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)
+        keys = list(ctx.keylist())
+        assert len(keys) == 1, "Weird number of keys created"
+        key = keys[0]
+        assert key.fpr == res.fpr
+        assert len(key.subkeys) == 1, "Expected one primary key and no subkeys"
+
+        def get_subkey(fpr):
+            k = ctx.get_key(fpr)
+            for sk in k.subkeys:
+                if sk.fpr == fpr:
+                    return sk
+            return None
+
+        # Check gpg.constants.create.NOEXPIRE...
+        res = ctx.create_subkey(key, expires=False)
+        subkey = get_subkey(res.fpr)
+        assert subkey.expires == 0, "Expected subkey not to expire"
+        assert subkey.can_encrypt, \
+            "Default subkey capabilities do not include encryption"
+
+        t = 2 * 24 * 60 * 60
+        slack = 5 * 60
+        res = ctx.create_subkey(key, expires_in=t)
+        subkey = get_subkey(res.fpr)
+        assert abs(time.time() + t - subkey.expires) < slack, \
+            "subkeys expiration time is off"
+
+        # Check capabilities
+        for sign, encrypt, authenticate in itertools.product([False, True],
+                                                             [False, True],
+                                                             [False, True]):
+            # Filter some out
+            if not (sign or encrypt or authenticate):
+                # This triggers the default capabilities tested before.
+                continue
+
+            res = ctx.create_subkey(key, sign=sign, encrypt=encrypt,
+                                    authenticate=authenticate)
+            subkey = get_subkey(res.fpr)
+            assert sign == subkey.can_sign
+            assert encrypt == subkey.can_encrypt
+            assert authenticate == subkey.can_authenticate
+
+        # Check algorithm
+        res = ctx.create_subkey(key, algorithm="rsa")
+        subkey = get_subkey(res.fpr)
+        assert subkey.pubkey_algo == 1
+
+        # Check algorithm with size
+        res = ctx.create_subkey(key, algorithm="rsa1024")
+        subkey = get_subkey(res.fpr)
+        assert subkey.pubkey_algo == 1
+        assert subkey.length == 1024
+
+        # Check algorithm future-default
+        ctx.create_subkey(key, algorithm="future-default")
+
+        # Check passphrase protection.  For this we create a new key
+        # so that we have a key with just one encryption subkey.
+        bravo_res = ctx.create_key(bravo, certify=True)
+        bravo_key = ctx.get_key(bravo_res.fpr)
+        assert len(bravo_key.subkeys) == 1, "Expected one primary key and no subkeys"
+
+        passphrase = "streng geheim"
+        res = ctx.create_subkey(bravo_key, passphrase=passphrase)
+        ciphertext, _, _ = ctx.encrypt(b"hello there",
+                                       recipients=[ctx.get_key(bravo_res.fpr)])
+
+        cb_called = False
+        def cb(*args):
+            global cb_called
+            cb_called = True
+            return passphrase
+        ctx.pinentry_mode = gpg.constants.PINENTRY_MODE_LOOPBACK
+        ctx.set_passphrase_cb(cb)
+
+        plaintext, _, _ = ctx.decrypt(ciphertext)
+        assert plaintext == b"hello there"
+        assert cb_called

commit 476b97822b169c30cc246c1de2ff94cf89084706
Author: Justus Winter <justus at g10code.com>
Date:   Thu Feb 16 14:53:11 2017 +0100

    python: Support quick key creation.
    
    * NEWS: Update.
    * lang/python/gpg/constants/__init__.py: Import new file.
    * lang/python/gpg/constants/create.py: New file.
    * lang/python/gpg/core.py (Context.create_key): New function.
    * lang/python/tests/Makefile.am (XTESTS): Add new test.
    * lang/python/tests/support.py (TemporaryDirectory): New class.
    * lang/python/tests/t-quick-key-creation.py: New file.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/NEWS b/NEWS
index 82f403c..7d30b94 100644
--- a/NEWS
+++ b/NEWS
@@ -21,6 +21,7 @@ Noteworthy changes in version 1.8.1 (unreleased)
  py: Context.__init__        EXTENDED: New keyword arg home_dir.
  py: Context.home_dir        NEW.
  py: Context.keylist         EXTENDED: New keyword arg mode.
+ py: Context.create_key      NEW.
  py: core.pubkey_algo_string NEW.
  py: core.addrspec_from_uid  NEW.
 
diff --git a/lang/python/gpg/constants/__init__.py b/lang/python/gpg/constants/__init__.py
index 4fb3d6f..2bf180e 100644
--- a/lang/python/gpg/constants/__init__.py
+++ b/lang/python/gpg/constants/__init__.py
@@ -25,8 +25,8 @@ util.process_constants('GPGME_', globals())
 del util
 
 # For convenience, we import the modules here.
-from . import data, event, keylist, md, pk
-from . import protocol, sig, sigsum, status, validity
+from . import data, keylist, sig # The subdirs.
+from . import create, event, md, pk, protocol, sigsum, status, validity
 
 # A complication arises because 'import' is a reserved keyword.
 # Import it as 'Import' instead.
@@ -34,7 +34,7 @@ globals()['Import'] = getattr(__import__('', globals(), locals(),
                                          [str('import')], 1), "import")
 
 __all__ = ['data', 'event', 'import', 'keylist', 'md', 'pk',
-           'protocol', 'sig', 'sigsum', 'status', 'validity']
+           'protocol', 'sig', 'sigsum', 'status', 'validity', 'create']
 
 # GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact.  We
 # implement gpg.Context.op_edit using gpgme_op_interact, so the
diff --git a/lang/python/gpg/constants/create.py b/lang/python/gpg/constants/create.py
new file mode 100644
index 0000000..132e96d
--- /dev/null
+++ b/lang/python/gpg/constants/create.py
@@ -0,0 +1,25 @@
+# Flags for key creation
+#
+# 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_CREATE_', globals())
+del util
diff --git a/lang/python/gpg/core.py b/lang/python/gpg/core.py
index 3a63516..c5af1b1 100644
--- a/lang/python/gpg/core.py
+++ b/lang/python/gpg/core.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2016 g10 Code GmbH
+# Copyright (C) 2016-2017 g10 Code GmbH
 # Copyright (C) 2004,2008 Igor Belyi <belyi at users.sourceforge.net>
 # Copyright (C) 2002 John Goerzen <jgoerzen at complete.org>
 #
@@ -501,6 +501,84 @@ class Context(GpgmeWrapper):
         self.set_keylist_mode(mode)
         return self.op_keylist_all(pattern, secret)
 
+    def create_key(self, userid, algorithm=None, expires_in=0, expires=True,
+                   sign=False, encrypt=False, certify=False, authenticate=False,
+                   passphrase=None, force=False):
+        """Create a primary key
+
+        Create a primary key for the user id USERID.
+
+        ALGORITHM may be used to specify the public key encryption
+        algorithm for the new key.  By default, a reasonable default
+        is chosen.  You may use "future-default" to select an
+        algorithm that will be the default in a future implementation
+        of the engine.  ALGORITHM may be a string like "rsa", or
+        "rsa2048" to explicitly request an algorithm and a key size.
+
+        EXPIRES_IN specifies the expiration time of the key in number
+        of seconds since the keys creation.  By default, a reasonable
+        expiration time is chosen.  If you want to create a key that
+        does not expire, use the keyword argument EXPIRES.
+
+        SIGN, ENCRYPT, CERTIFY, and AUTHENTICATE can be used to
+        request the capabilities of the new key.  If you don't request
+        any, a reasonable set of capabilities is selected, and in case
+        of OpenPGP, a subkey with a reasonable set of capabilities is
+        created.
+
+        If PASSPHRASE is None (the default), then the key will not be
+        protected with a passphrase.  If PASSPHRASE is a string, it
+        will be used to protect the key.  If PASSPHRASE is True, the
+        passphrase must be supplied using a passphrase callback or
+        out-of-band with a pinentry.
+
+        Keyword arguments:
+        algorithm    -- public key algorithm, see above (default: reasonable)
+        expires_in   -- expiration time in seconds (default: reasonable)
+        expires      -- whether or not the key should expire (default: True)
+        sign         -- request the signing capability (see above)
+        encrypt      -- request the encryption capability (see above)
+        certify      -- request the certification capability (see above)
+        authenticate -- request the authentication capability (see above)
+        passphrase   -- protect the key with a passphrase (default: no passphrase)
+        force        -- force key creation even if a key with the same userid exists
+                                                          (default: False)
+
+        Returns:
+                     -- an object describing the result of the key creation
+
+        Raises:
+        GPGMEError   -- as signaled by the underlying library
+
+        """
+        if util.is_a_string(passphrase):
+            old_pinentry_mode = self.pinentry_mode
+            old_passphrase_cb = getattr(self, '_passphrase_cb', None)
+            self.pinentry_mode = constants.PINENTRY_MODE_LOOPBACK
+            def passphrase_cb(hint, desc, prev_bad, hook=None):
+                return passphrase
+            self.set_passphrase_cb(passphrase_cb)
+
+        try:
+            self.op_createkey(userid, algorithm,
+                              0, # reserved
+                              expires_in,
+                              None, # extrakey
+                              ((constants.create.SIGN if sign else 0)
+                               | (constants.create.ENCR if encrypt else 0)
+                               | (constants.create.CERT if certify else 0)
+                               | (constants.create.AUTH if authenticate else 0)
+                               | (constants.create.NOPASSWD if passphrase == None else 0)
+                               | (0 if expires else constants.create.NOEXPIRE)
+                               | (constants.create.FORCE if force else 0)))
+        finally:
+            if util.is_a_string(passphrase):
+                self.pinentry_mode = old_pinentry_mode
+                if old_passphrase_cb:
+                    self.set_passphrase_cb(*old_passphrase_cb[1:])
+
+        return self.op_genkey_result()
+
     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 d7f2e58..5469e75 100644
--- a/lang/python/tests/Makefile.am
+++ b/lang/python/tests/Makefile.am
@@ -50,7 +50,8 @@ py_tests = t-wrapper.py \
 	t-encrypt-large.py \
 	t-file-name.py \
 	t-idiomatic.py \
-	t-protocol-assuan.py
+	t-protocol-assuan.py \
+	t-quick-key-creation.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/support.py b/lang/python/tests/support.py
index 0b04bb6..ed5bf61 100644
--- a/lang/python/tests/support.py
+++ b/lang/python/tests/support.py
@@ -20,6 +20,7 @@ del absolute_import, print_function, unicode_literals
 
 import sys
 import os
+import tempfile
 import gpg
 
 # known keys
@@ -72,3 +73,17 @@ def mark_key_trusted(ctx, key):
             return result
     with gpg.Data() as sink:
         ctx.op_edit(key, Editor().edit, sink, sink)
+
+
+# Python2/3 compatibility
+if hasattr(tempfile, "TemporaryDirectory"):
+    # Python3.2 and up
+    TemporaryDirectory = tempfile.TemporaryDirectory
+else:
+    class TemporaryDirectory(object):
+        def __enter__(self):
+            self.path = tempfile.mkdtemp()
+            return self.path
+        def __exit__(self, *args):
+            import shutil
+            shutil.rmtree(self.path)
diff --git a/lang/python/tests/t-quick-key-creation.py b/lang/python/tests/t-quick-key-creation.py
new file mode 100755
index 0000000..ea63dc3
--- /dev/null
+++ b/lang/python/tests/t-quick-key-creation.py
@@ -0,0 +1,151 @@
+#!/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 os
+import shutil
+import time
+
+import support
+
+alpha = "Alpha <alpha at invalid.example.net>"
+
+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)
+
+        keys = list(ctx.keylist())
+        assert len(keys) == 1, "Weird number of keys created"
+
+        key = keys[0]
+        assert key.fpr == res.fpr
+        assert len(key.subkeys) == 2, "Expected one primary key and one subkey"
+        assert key.subkeys[0].expires > 0, "Expected primary key to expire"
+
+        # Try to create a key with the same UID
+        try:
+            ctx.create_key(alpha)
+            assert False, "Expected an error but got none"
+        except gpg.errors.GpgError as e:
+            pass
+
+        # Try to create a key with the same UID, now with force!
+        res2 = ctx.create_key(alpha, force=True)
+        assert res.fpr != res2.fpr
+
+
+# From here on, we use one context, and create unique UIDs
+uid_counter = 0
+def make_uid():
+    global uid_counter
+    uid_counter += 1
+    return "user{0}@invalid.example.org".format(uid_counter)
+
+with support.TemporaryDirectory() as tmp:
+    copy_configuration(tmp)
+    with gpg.Context(home_dir=tmp) as ctx:
+
+        # Check gpg.constants.create.NOEXPIRE...
+        res = ctx.create_key(make_uid(), expires=False)
+        key = ctx.get_key(res.fpr, secret=True)
+        assert key.fpr == res.fpr
+        assert len(key.subkeys) == 2, "Expected one primary key and one subkey"
+        assert key.subkeys[0].expires == 0, "Expected primary key not to expire"
+
+        t = 2 * 24 * 60 * 60
+        slack = 5 * 60
+        res = ctx.create_key(make_uid(), expires_in=t)
+        key = ctx.get_key(res.fpr, secret=True)
+        assert key.fpr == res.fpr
+        assert len(key.subkeys) == 2, "Expected one primary key and one subkey"
+        assert abs(time.time() + t - key.subkeys[0].expires) < slack, \
+            "Primary keys expiration time is off"
+
+        # Check capabilities
+        for sign, encrypt, certify, authenticate in itertools.product([False, True],
+                                                                      [False, True],
+                                                                      [False, True],
+                                                                      [False, True]):
+            # Filter some out
+            if not (sign or encrypt or certify or authenticate):
+                # This triggers the default capabilities tested before.
+                continue
+            if (sign or encrypt or authenticate) and not certify:
+                # The primary key always certifies.
+                continue
+
+            res = ctx.create_key(make_uid(), algorithm="rsa",
+                                 sign=sign, encrypt=encrypt, certify=certify,
+                                 authenticate=authenticate)
+            key = ctx.get_key(res.fpr, secret=True)
+            assert key.fpr == res.fpr
+            assert len(key.subkeys) == 1, \
+                "Expected no subkey for non-default capabilities"
+
+            p = key.subkeys[0]
+            assert sign == p.can_sign
+            assert encrypt == p.can_encrypt
+            assert certify == p.can_certify
+            assert authenticate == p.can_authenticate
+
+        # Check algorithm
+        res = ctx.create_key(make_uid(), algorithm="rsa")
+        key = ctx.get_key(res.fpr, secret=True)
+        assert key.fpr == res.fpr
+        for k in key.subkeys:
+            assert k.pubkey_algo == 1
+
+        # Check algorithm with size
+        res = ctx.create_key(make_uid(), algorithm="rsa1024")
+        key = ctx.get_key(res.fpr, secret=True)
+        assert key.fpr == res.fpr
+        for k in key.subkeys:
+            assert k.pubkey_algo == 1
+            assert k.length == 1024
+
+        # Check algorithm future-default
+        ctx.create_key(make_uid(), algorithm="future-default")
+
+        # Check passphrase protection
+        recipient = make_uid()
+        passphrase = "streng geheim"
+        res = ctx.create_key(recipient, passphrase=passphrase)
+        ciphertext, _, _ = ctx.encrypt(b"hello there", recipients=[ctx.get_key(res.fpr)])
+
+        cb_called = False
+        def cb(*args):
+            global cb_called
+            cb_called = True
+            return passphrase
+        ctx.pinentry_mode = gpg.constants.PINENTRY_MODE_LOOPBACK
+        ctx.set_passphrase_cb(cb)
+
+        plaintext, _, _ = ctx.decrypt(ciphertext)
+        assert plaintext == b"hello there"
+        assert cb_called

commit 3bdce4aa3ddd4a3f55b24678faf978d61daa8909
Author: Justus Winter <justus at g10code.com>
Date:   Thu Feb 16 14:49:27 2017 +0100

    python: Fix passphrase callback wrapping.
    
    * lang/python/helpers.c (pyPassphraseCb): Cope with 'passphrase_info'
    being NULL.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/helpers.c b/lang/python/helpers.c
index 576767c..947819d 100644
--- a/lang/python/helpers.c
+++ b/lang/python/helpers.c
@@ -377,7 +377,21 @@ static gpgme_error_t pyPassphraseCb(void *hook,
     goto leave;
   }
 
-  PyTuple_SetItem(args, 1, PyBytes_FromString(passphrase_info));
+  if (passphrase_info == NULL)
+    {
+      Py_INCREF(Py_None);
+      PyTuple_SetItem(args, 1, Py_None);
+    }
+  else
+    PyTuple_SetItem(args, 1, PyUnicode_DecodeUTF8(passphrase_info,
+                                                  strlen (passphrase_info),
+                                                  "strict"));
+  if (PyErr_Occurred()) {
+    Py_DECREF(args);
+    err_status = gpg_error(GPG_ERR_GENERAL);
+    goto leave;
+  }
+
   PyTuple_SetItem(args, 2, PyBool_FromLong((long)prev_was_bad));
   if (dataarg) {
     Py_INCREF(dataarg);		/* Because GetItem doesn't give a ref but SetItem taketh away */

commit 048c5f74b61d5e4fa7617ce7c9111c6754bd4409
Author: Justus Winter <justus at g10code.com>
Date:   Thu Feb 16 14:42:17 2017 +0100

    python: Fix error handling.
    
    * lang/python/gpgme.i (typemap gpgme_key_t[]): Set an error if a
    non-key element is discovered.
    
    Signed-off-by: Justus Winter <justus at g10code.com>

diff --git a/lang/python/gpgme.i b/lang/python/gpgme.i
index c7fc14e..3e89bb1 100644
--- a/lang/python/gpgme.i
+++ b/lang/python/gpgme.i
@@ -137,7 +137,12 @@
 
       /* Following code is from swig's python.swg.  */
       if ((SWIG_ConvertPtr(pypointer,(void **) &$1[i], $*1_descriptor,SWIG_POINTER_EXCEPTION | $disown )) == -1) {
-	Py_DECREF(pypointer);
+        Py_DECREF(pypointer);
+	PyErr_Format(PyExc_TypeError,
+                     "arg %d: list must contain only gpgme_key_ts, got %s "
+                     "at position %d",
+                     $argnum, pypointer->ob_type->tp_name, i);
+        free($1);
 	return NULL;
       }
       Py_DECREF(pypointer);

-----------------------------------------------------------------------

Summary of changes:
 NEWS                                               |   4 +
 lang/python/gpg/constants/__init__.py              |   6 +-
 .../gpg/constants/{sig/notation.py => create.py}   |   6 +-
 lang/python/gpg/core.py                            | 176 ++++++++++++++++++++-
 lang/python/gpgme.i                                |   7 +-
 lang/python/helpers.c                              |  16 +-
 lang/python/tests/Makefile.am                      |   5 +-
 lang/python/tests/support.py                       |  15 ++
 lang/python/tests/t-quick-key-creation.py          | 151 ++++++++++++++++++
 lang/python/tests/t-quick-key-manipulation.py      | 103 ++++++++++++
 lang/python/tests/t-quick-subkey-creation.py       | 121 ++++++++++++++
 11 files changed, 600 insertions(+), 10 deletions(-)
 copy lang/python/gpg/constants/{sig/notation.py => create.py} (86%)
 create mode 100755 lang/python/tests/t-quick-key-creation.py
 create mode 100755 lang/python/tests/t-quick-key-manipulation.py
 create mode 100755 lang/python/tests/t-quick-subkey-creation.py


hooks/post-receive
-- 
GnuPG Made Easy
http://git.gnupg.org




More information about the Gnupg-commits mailing list