This file is indexed.

/usr/lib/python2.7/dist-packages/keysign/gpgmks.py is in gnome-keysign 0.9-1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
#!/usr/bin/env python
#    Copyright 2017 Tobias Mueller <muelli@cryptobitch.de>
#
#    This file is part of GNOME Keysign.
#
#    GNOME Keysign 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 3 of the License, or
#    (at your option) any later version.
#
#    GNOME Keysign 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 General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with GNOME Keysign.  If not, see <http://www.gnu.org/licenses/>.

from datetime import datetime
import logging
import os  # The SigningKeyring uses os.symlink for the agent
from tempfile import NamedTemporaryFile

# The UID object is used in one place, at least,
# to get display the name and email address.
# The Key object is returned from a few functions, so it's
# API is somewhat external.
from .gpgkey import Key, UID
log = logging.getLogger(__name__)


#####
## INTERNAL API
##
import sys
parent_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(os.path.join(parent_dir, "monkeysign"))
from monkeysign.gpg import Keyring, TempKeyring
from monkeysign.gpg import GpgRuntimeError



def UIDExport(uid, keydata):
    """Export only the UID of a key.
    Unfortunately, GnuPG does not provide smth like
    --export-uid-only in order to obtain a UID and its
    signatures."""
    log = logging.getLogger(__name__ + ".UIDExport")
    tmp = TempKeyring()
    # Hm, apparently this needs to be set, otherwise gnupg will issue
    # a stray "gpg: checking the trustdb" which confuses the gnupg library
    tmp.context.set_option('always-trust')
    tmp.import_data(keydata)
    log.debug("Looking for %r", uid)
    for fpr, key in tmp.get_keys(uid).items():
        for u in key.uidslist:
            key_uid = u.uid
            if key_uid != uid:
                log.info('Deleting UID %s from key %s', key_uid, fpr)
                tmp.del_uid(fingerprint=fpr, pattern=key_uid)
    only_uid = tmp.export_data(uid)

    return only_uid


def MinimalExport(keydata):
    '''Returns the minimised version of a key

    For now, you must provide one key only.'''
    tmpkeyring = TempKeyring()
    ret = tmpkeyring.import_data(keydata)
    log.debug("Returned %s after importing %r", ret, keydata)
    assert ret
    tmpkeyring.context.set_option('export-options', 'export-minimal')
    keys_dict = tmpkeyring.get_keys()
    # We assume the keydata to contain one key only
    keys = list(keys_dict.items())
    log.debug("Keys after importing: %s (%s)", keys, keys)
    fingerprint, key = keys[0]
    stripped_key = tmpkeyring.export_data(fingerprint)
    return stripped_key



class SplitKeyring(Keyring):
    def __init__(self, primary_keyring_fname, trustdb_fname, *args, **kwargs):
        # I don't think Keyring is inheriting from object,
        # so we can't use super()
        Keyring.__init__(self, *args, **kwargs)

        self.context.set_option('primary-keyring', primary_keyring_fname)
        self.context.set_option('trustdb-name', trustdb_fname)
        self.context.set_option('no-default-keyring')


class TempSplitKeyring(SplitKeyring):
    """A temporary keyring which will be discarded after use
    
    It creates a temporary file which will be used for a SplitKeyring.
    You may not necessarily be able to use this Keyring as is, because
    gpg1.4 does not like using secret keys which is does not have the
    public keys of in its pubkeyring.
    
    So you may not necessarily be able to perform operations with
    the user's secret keys (like creating signatures).
    """
    def __init__(self, *args, **kwargs):
        # A NamedTemporaryFile deletes the backing file
        self.kr_tempfile = NamedTemporaryFile(prefix='gpgpy-')
        self.kr_fname = self.kr_tempfile.name
        self.tdb_tempfile = NamedTemporaryFile(prefix='gpgpy-tdb-',
                                               delete=True)
        self.tdb_fname = self.tdb_tempfile.name
        # This should delete the file.
        # Why are we doing it?  Well...
        # Turns out that if you run gpg --trustdb-name with an
        # empty file, it complains about an invalid trustdb.
        # If, however, you give it a non-existent filename,
        # it'll happily create a new trustdb.
        # FWIW: Am empty trustdb file seems to be 40 bytes long,
        # but the contents seems to be non-deterministic.
        # Anyway, we'll leak the file :-/
        self.tdb_tempfile.close()

        SplitKeyring.__init__(self, primary_keyring_fname=self.kr_fname,
                                    trustdb_fname=self.tdb_fname,
                                    *args, **kwargs)


class TempSigningKeyring(TempSplitKeyring):
    """A temporary keyring which uses the secret keys of a parent keyring
    
    Creates a temporary keyring which can use the orignal keyring's
    secret keys.  If you don't provide a keyring as argument (i.e. None),
    a default Keyring() will be taken which represents the user's
    regular keyring.

    In fact, this is not much different from a TempSplitKeyring,
    but gpg1.4 does not see the public keys for the secret keys when run with
    --no-default-keyring and --primary-keyring.
    So we copy the public parts of the secret keys into the primary keyring.
    """
    def __init__(self, base_keyring=None, *args, **kwargs):
        # Not a new style class...
        if issubclass(self.__class__, object):
            super(TempSigningKeyring, self).__init__(*args, **kwargs)
        else:
            TempSplitKeyring.__init__(self, *args, **kwargs)

        if base_keyring is None:
            base_keyring = Keyring()

        # Copy the public parts of the secret keys to the tmpkeyring
        for fpr, key in base_keyring.get_keys(None,
                                              secret=True,
                                              public=False).items():
            self.import_data (base_keyring.export_data (fpr))

        ## We don't copy the config file, because we're not using a separate
        ## homedir. So we expect gpg to still use it's normal homedir and thus
        ## it's normal configuration.
        # self.copy_agent_socket(base_keyring)

    def copy_agent_socket(self, base_keyring):
        ## Copied from monkeysign/ui.py as of
        ## 741dde1cc242bf125dd206a019028736d9c4a141

        # install the gpg agent socket for GnuPG 2.1 because
        # --secret-keyring silently fails
        # this is apparently how we should do things:
        # https://lists.gnupg.org/pipermail/gnupg-devel/2015-January/029301.html
        # cargo-culted from caff, thanks guilhem!
        src = base_keyring.get_agent_socket()
        dst = self.get_agent_socket()
        log.info(_('installing symlinks for sockets from %s to %s'), src, dst)
        try:
            os.unlink(dst)
        except OSError as e:
            if e.errno == errno.ENOENT:
                pass
            else:
                raise
        os.symlink(src, dst)




from monkeysign.gpg import Keyring
def parse_sig_list(text):
    '''Parses GnuPG's signature list (i.e. list-sigs)
    
    The format is described in the GnuPG man page'''
    sigslist = []
    for block in text.split("\n"):
        if block.startswith("sig"):
            record = block.split(":")
            log.debug("sig record (%d) %s", len(record), record)
            keyid, timestamp, uid = record[4], record[5], record[9]
            sigslist.append((keyid, timestamp, uid))

    return sigslist


def signatures_for_keyid(keyid, keyring=None):
    '''Returns the list of signatures for a given key id
    
    This will call out to GnuPG list-sigs, using Monkeysign,
    and parse the resulting string into a list of signatures.
    
    A default Keyring will be used unless you pass an instance
    as keyring argument.
    '''
    if keyring is None:
        kr = Keyring()
    else:
        kr = keyring

    # FIXME: this would be better if it was done in monkeysign
    kr.context.call_command(['list-sigs', keyid])
    siglist = parse_sig_list(kr.context.stdout)

    return siglist



## Monkeypatching to get more debug output
import monkeysign.gpg
bc = monkeysign.gpg.Context.build_command
def build_command(*args, **kwargs):
    ret = bc(*args, **kwargs)
    #log.info("Building command %s", ret)
    log.debug("Building cmd: %s", ' '.join(["'%s'" % c for c in ret]))
    return ret
monkeysign.gpg.Context.build_command = build_command


def is_usable(key):
    unusable =    key.invalid or key.disabled \
               or key.expired or key.revoked
    log.debug('Key %s is invalid: %s (i:%s, d:%s, e:%s, r:%s)', key, unusable,
        key.invalid, key.disabled, key.expired, key.revoked)
    return not unusable

def filter_usable_keys(keys):
    usable_keys = [Key.from_monkeysign(key) for key in keys if is_usable(key)]
    log.debug('Identified usable keys: %s', usable_keys)
    return usable_keys


def get_usable_keys_from_keyring(keyring, pattern, public, secret):
    keys_dict = keyring.get_keys(pattern=pattern,
    							 public=public,
    							 secret=secret) or {}
    assert keys_dict is not None, keyring.context.stderr
    # keys_fpr = keys_dict.items()
    keys = keys_dict.values()
    return filter_usable_keys(keys)


def sign_keydata(keydata, error_cb=None, homedir=None):
    """Signs OpenPGP keydata with your regular GnuPG secret keys
    
    If error_cb is provided, that function is called with any exception
    occuring during signing of the key.  If error_cb is False, any
    exception is raised.
    
    yields pairs of (uid, signed_uid)
    """
    log = logging.getLogger(__name__ + ':sign_keydata_encrypt')

    tmpkeyring = TempSigningKeyring(homedir=homedir,
        base_keyring=Keyring(homedir=homedir))
    # Eventually, we want to let the user select their keys to sign with
    # For now, we just take whatever is there.
    secret_keys = get_usable_secret_keys(homedir=homedir)
    log.info('Signing with these keys: %s', secret_keys)

    stripped_key = MinimalExport(keydata)
    fingerprint = fingerprint_from_keydata(stripped_key)

    log.debug('Trying to import key\n%s', stripped_key)
    if tmpkeyring.import_data(stripped_key):
        # 3. for every user id (or all, if -a is specified)
        # 3.1. sign the uid, using gpg-agent
        keys = tmpkeyring.get_keys(fingerprint)
        log.info("Found keys %s for fp %s", keys, fingerprint)
        if len(keys) != 1:
            raise ValueError("We received multiple keys for fp %s: %s"
                             % (fingerprint, keys))
        key = keys[fingerprint]
        uidlist = key.uidslist
        
        for secret_key in secret_keys:
            secret_fpr = secret_key.fpr
            log.info('Setting up to sign with %s', secret_fpr)
            # We need to --always-trust, because GnuPG would print
            # warning about the trustdb.  I think this is because
            # we have a newly signed key whose trust GnuPG wants to
            # incorporate into the trust decision.
            tmpkeyring.context.set_option('always-trust')
            tmpkeyring.context.set_option('local-user', secret_fpr)
            # FIXME: For now, we sign all UIDs. This is bad.
            try:
                ret = tmpkeyring.sign_key(fingerprint, signall=True)
            except GpgRuntimeError as e:
                uid = uidlist[0].uid
                log.exception("Error signing %r with secret key %r. stdout: %r, stderr: %r",
                    uid, secret_key, tmpkeyring.context.stdout, tmpkeyring.context.stderr)
                if error_cb:
                    e.uid = uid
                    error_cb (e)
                else:
                    raise
                continue
            log.info("Result of signing %s on key %s: %s", uidlist[0].uid, fingerprint, ret)


        for uid in uidlist:
            uid_str = uid.uid
            log.info("Processing uid %r %s", uid, uid_str)

            # 3.2. export and encrypt the signature
            # 3.3. mail the key to the user
            signed_key = UIDExport(uid_str, tmpkeyring.export_data(uid_str))
            log.info("Exported %d bytes of signed key", len(signed_key))
            yield (uid, signed_key)

##
## END OF INTERNAL API
#####





def openpgpkey_from_data(keydata):
    "Creates an OpenPGP object from given data"
    keyring = TempKeyring()
    if not keyring.import_data(keydata):
        raise ValueError("Could not import %r  -  stdout: %r, stderr: %r",
                         keydata,
                         keyring.context.stdout, keyring.context.stderr)
    # As we have imported only one key, we should also
    # only have one key at our hands now.
    keys = keyring.get_keys()
    if len(keys) != 1:
        log.debug('Operation on keydata "%s" failed', keydata)
        raise ValueError("Expected exactly one key, but got %d: %r" % (
        				 len(keys), keys))
    else:
        # The first (key, value) pair in the keys dict
        # next(iter(keys.items()))[0] might be semantically
        # more correct than list(d.items()) as we don't care
        # much about having a list created, but I think it's
        # more legible.
        fpr_key = list(keys.items())[0]
        # is composed of the fpr as key and an OpenPGP key as value
        key = fpr_key[1]
        return Key.from_monkeysign(key)



def get_public_key_data(fpr, homedir=None):
    """Returns keydata for a given fingerprint
    
    In fact, fpr could be anything that gpg happily exports.
    """
    keyring = Keyring(homedir=homedir)
    keydata = keyring.export_data(fpr)
    if not keydata:
        s = "No data to export for {} (in {})".format(fpr, homedir)
        raise ValueError(s)
    return keydata




def fingerprint_from_keydata(keydata):
    '''Returns the OpenPGP Fingerprint for a given key'''
    openpgpkey = openpgpkey_from_data(keydata)
    return openpgpkey.fpr


def get_usable_keys(pattern="", homedir=None):
    '''Uses get_keys on the keyring and filters for
    non revoked, expired, disabled, or invalid keys'''
    log.debug('Retrieving keys for %s, %s', pattern, homedir)
    keyring = Keyring(homedir=homedir)
    return get_usable_keys_from_keyring(keyring=keyring,
    	pattern=pattern, public=True, secret=False)


def get_usable_secret_keys(pattern="", homedir=None):
    '''Returns all secret keys which can be used to sign a key'''
    keyring = Keyring(homedir=homedir)
    return get_usable_keys_from_keyring(keyring=keyring,
    	pattern=pattern, public=False, secret=True)



def sign_keydata_and_encrypt(keydata, error_cb=None, homedir=None):
    """Signs OpenPGP keydata with your regular GnuPG secret keys
    and encrypts the result under the given key
    
    error_cb can be a function that is called with any exception
    occuring during signing of the key.
    """
    tmpkeyring = TempKeyring()
    tmpkeyring.import_data(keydata)
    tmpkeyring.context.set_option('always-trust')
    for (uid, signed_key) in sign_keydata(keydata,
        error_cb=error_cb, homedir=homedir):
            if not uid.revoked:
                encrypted_key = tmpkeyring.encrypt_data(data=signed_key,
                    recipient=uid.uid)
                yield (UID.from_monkeysign(uid), encrypted_key)