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 <justus@g10code.com>
This commit is contained in:
parent
de8494b16b
commit
48634e651f
1
NEWS
1
NEWS
@ -25,6 +25,7 @@ Noteworthy changes in version 1.8.1 (unreleased)
|
||||
py: Context.create_subkey NEW.
|
||||
py: Context.key_add_uid NEW.
|
||||
py: Context.key_revoke_uid NEW.
|
||||
py: Context.key_sign NEW.
|
||||
py: core.pubkey_algo_string NEW.
|
||||
py: core.addrspec_from_uid NEW.
|
||||
|
||||
|
@ -4044,11 +4044,10 @@ object (@code{gpgme_user_id_t}) is to be used. To select more than
|
||||
one user ID put them all into one string separated by linefeeds
|
||||
characters (@code{\n}) and set the flag @code{GPGME_KEYSIGN_LFSEP}.
|
||||
|
||||
@var{expires} can be set to the number of seconds since Epoch of the
|
||||
desired expiration date in UTC for the new signature. The common case
|
||||
is to use 0 to not set an expiration date. However, if the
|
||||
configuration of the engine defines a default expiration for key
|
||||
signatures, that is still used unless the flag
|
||||
@var{expires} specifies the expiration time of the new signature in
|
||||
seconds. The common case is to use 0 to not set an expiration date.
|
||||
However, if the configuration of the engine defines a default
|
||||
expiration for key signatures, that is still used unless the flag
|
||||
@code{GPGME_KEYSIGN_NOEXPIRE} is used. Note that this parameter takes
|
||||
an unsigned long value and not a @code{time_t} to avoid problems on
|
||||
systems which use a signed 32 bit @code{time_t}. Note further that
|
||||
|
@ -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
|
||||
|
25
lang/python/gpg/constants/keysign.py
Normal file
25
lang/python/gpg/constants/keysign.py
Normal file
@ -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
|
@ -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
|
||||
|
@ -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 \
|
||||
|
120
lang/python/tests/t-quick-key-signing.py
Executable file
120
lang/python/tests/t-quick-key-signing.py
Executable file
@ -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])))
|
Loading…
Reference in New Issue
Block a user