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.create_subkey NEW.
|
||||||
py: Context.key_add_uid NEW.
|
py: Context.key_add_uid NEW.
|
||||||
py: Context.key_revoke_uid NEW.
|
py: Context.key_revoke_uid NEW.
|
||||||
|
py: Context.key_sign NEW.
|
||||||
py: core.pubkey_algo_string NEW.
|
py: core.pubkey_algo_string NEW.
|
||||||
py: core.addrspec_from_uid 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
|
one user ID put them all into one string separated by linefeeds
|
||||||
characters (@code{\n}) and set the flag @code{GPGME_KEYSIGN_LFSEP}.
|
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
|
@var{expires} specifies the expiration time of the new signature in
|
||||||
desired expiration date in UTC for the new signature. The common case
|
seconds. The common case is to use 0 to not set an expiration date.
|
||||||
is to use 0 to not set an expiration date. However, if the
|
However, if the configuration of the engine defines a default
|
||||||
configuration of the engine defines a default expiration for key
|
expiration for key signatures, that is still used unless the flag
|
||||||
signatures, that is still used unless the flag
|
|
||||||
@code{GPGME_KEYSIGN_NOEXPIRE} is used. Note that this parameter takes
|
@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
|
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
|
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.
|
# For convenience, we import the modules here.
|
||||||
from . import data, keylist, sig # The subdirs.
|
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.
|
# A complication arises because 'import' is a reserved keyword.
|
||||||
# Import it as 'Import' instead.
|
# Import it as 'Import' instead.
|
||||||
globals()['Import'] = getattr(__import__('', globals(), locals(),
|
globals()['Import'] = getattr(__import__('', globals(), locals(),
|
||||||
[str('import')], 1), "import")
|
[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']
|
'protocol', 'sig', 'sigsum', 'status', 'validity', 'create']
|
||||||
|
|
||||||
# GPGME 1.7 replaced gpgme_op_edit with gpgme_op_interact. We
|
# 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)
|
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,
|
def assuan_transact(self, command,
|
||||||
data_cb=None, inquire_cb=None, status_cb=None):
|
data_cb=None, inquire_cb=None, status_cb=None):
|
||||||
"""Issue a raw assuan command
|
"""Issue a raw assuan command
|
||||||
|
@ -53,7 +53,8 @@ py_tests = t-wrapper.py \
|
|||||||
t-protocol-assuan.py \
|
t-protocol-assuan.py \
|
||||||
t-quick-key-creation.py \
|
t-quick-key-creation.py \
|
||||||
t-quick-subkey-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
|
XTESTS = initial.py $(py_tests) final.py
|
||||||
EXTRA_DIST = support.py $(XTESTS) encrypt-only.asc sign-only.asc \
|
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