This file is indexed.

/usr/lib/python2.7/dist-packages/xxdiff/scripts/encrypted.py is in xxdiff-scripts 1:4.0.1+hg487+dfsg-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
# This file is part of the xxdiff package.  See xxdiff for license and details.

"""xx-encrypted [<options>] <encrypted-file> [<encrypted file> ...]

Compare and merge contents of encrypted files relatively safely.

This script wraps around xxdiff, first decrypting the input files to temporary
files (for a short time) and running xxdiff on these files.  There are two
typical uses of this program:

1) it is used to compare two encrypted files.  With the --output option, a
   decision is required and an encrypted version of the merged file is output to
   the specified file and the merged file deleted promptly.  Note that without
   the --output option, even if the merged file is saved, it is deleted once
   xxdiff exits.

2) it is used to split and resolve CVS conflicts in an armored encrypted file
   (see --unmerge option).  The merged file is encrypted and output over the
   conflictual input file (i.e. it replaces it with the encrypted version of the
   merged file).  This is very useful if you maintain armored encrypted files in
   CVS repositories because otherwise an encrypted file with a CVS conflict in
   it becomes useless.

Using gpg-agent
---------------

Usage of this program with password caching using gpg-agent makes it much easier
to call on multiple files.  The user's password given key is asked only once by
gpg-agent, kept in memory, and then decryption occurs without user intervention.

Merging Encrypted Files with Mercurial
--------------------------------------

Mercurial can be easily configured so that this tool gets invoked automatically
when encrypted files need to be merged. This way you can independently
edit encrypted files using Emacs (I use a custom mailcrypt setup to seamlessly
decode on open and encode on save) and when tracked in a Mercurial repository,
if the encrypted files have been edited in two separate revisions, xx-encrypted
is invoked automatically and brings up a 3-way xxdiff to merge them interactively.
Here's the configuration I use in my .hgrc::

  [merge-tools]
  xx-encrypted =
  xx-encrypted.priority = 100
  xx-encrypted.premerge = False
  xx-encrypted.args = $local $base $other -o $output

  [merge-patterns]
  **.asc = xx-encrypted
  **.gpg = xx-encrypted

Alternatively, you may just use xx-hg-merge which deals with all file types,
encrypted or not:

  [merge-patterns]
  ** = xx-hg-merge

"""

__moredoc__ = """
Safety Notes
------------

The encrypted files are decrypted to temporary files for a short amount of time,
and are deleted when xxdiff appears.  Note that their deletion is as safe as
Python's tempfile module allows it to be (in the author's opinion, safe
enough). I left comments in the code to allow a user to review where the files
are decrypted so they can judge by themselves if it is safe enough for their
use.

We could do much better in terms of safety if we could feed the input files to
xxdiff through different file descriptors (not impossible to implement) AND
calculate the diffs internally.

(Note that if someone can manipulate which program is used to actually perform
the diffs (e.g. modifying an unsuspecting user's resources in ~/.xxdiffrc), they
could feed the decrypted files to an arbitrary program.)
"""

__author__ = "Martin Blais <blais@furius.ca>"
__depends__ = ['xxdiff', 'Python-2.4', 'GnuPG']


# stdlib imports.
import sys, os, re
from os.path import *
from tempfile import NamedTemporaryFile
from subprocess import Popen, PIPE

# xxdiff imports.
import xxdiff.scripts
import xxdiff.invoke
import xxdiff.scm.cvs
from xxdiff.scripts import tmpprefix


decodecmd = '%(gpg)s --decrypt --use-agent '
encodecmd_noarmor = '%(gpg)s --encrypt --use-agent '
encodecmd = encodecmd_noarmor + '--armor '


def get_recipient(text, gpg):
    """ Extract the recipient/key name from the given encrypted text, without
    saving any temporary file. """
    p = Popen(('gpg', '--list-only'), stdin=PIPE, stdout=PIPE, stderr=PIPE)
    out, err = p.communicate(text)
    mo = re.compile('.*public key is ([0-9A-F]+)', re.M).search(err)
    if mo:
        return mo.group(1)


def diff_encrypted(textlist, opts, outmerged=None):
    """
    Run a comparison of the encrypted texts specified in textlists and if an
    'outmerged' filename is specified, encrypt the merged file into it. Note
    that the texts are not filenames, but actual contents of files.
    """
    m = {'gpg': opts.gpg}

    # Create temporary files.
    tempfiles = []
    for t in xrange(len(textlist)):
        f = NamedTemporaryFile(prefix=tmpprefix)
        print '== TEMPFILE', f.name
        tempfiles.append(f)

    # Figure out the key/recipient for the first file.
    if opts.recipient is None:
        opts.recipient = get_recipient(textlist[0], opts.gpg)

    # Always create a temporary file for the merged file, we will delete it for
    # sure, since it would contain decrypted content if saved.

    # Decode the files.
    for i in xrange(len(textlist)):
        t, f = textlist[i], tempfiles[i]

        # Decode one file to an existing temporary file.
        fin, fout = os.popen2(decodecmd % m, 'w')
        fin.write(t)
        fin.close()
        decoded_output = fout.read()
        fout.close()

        f.write(decoded_output)
        f.flush()

    # Spawn xxdiff on the temporary/decoded files.
    waiter = xxdiff.invoke.xxdiff_decision(
        opts, nowait=1, *map(lambda x: x.name, tempfiles))

    # Close and automatically delete the temporary/decoded files.
    for f in tempfiles:
        f.close()

    print 'Waiting...'
    decision, mergedf, retcode = waiter()

    if decision != 'NODECISION' and outmerged:
        # Read the decoded merged output file from xxdiff.
        textm = mergedf.read()
        assert textm

        # Close and automatically delete the decoded merged output file.
        mergedf.close()

        # Encode the merged output text.
        if not opts.dont_armor:
            cmd = encodecmd % m
        else:
            cmd = encodecmd_noarmor % m

        if opts.recipient:
            cmd += ' --recipient "%s"' % opts.recipient

        fin, fout = os.popen2(cmd, 'w')
        fin.write(textm)
        fin.close()
        encoded_output = fout.read()
        fout.close()

        # Write out the encoded output file.
        try:
            f = open(outmerged, 'w')
            f.write(encoded_output)
            f.close()
        except IOError, e:
            print >> sys.stderr, \
                  'Error: cannot write to encoded merged file.'
            raise e

    return decision


def parse_options():
    """
    Parse the options.
    """
    import optparse
    parser = optparse.OptionParser(__doc__)

    xxdiff.invoke.options_graft(parser)

    parser.add_option('-g', '--gpg', default="gpg",
                      help="Specify path to gpg program to use.")

    parser.add_option('-A', '--dont-armor', action='store_true',
                      help="Create output file in binary format.")

    parser.add_option('-o', '--output', action='store',
                      help="Require and encrypt merged output.")

    parser.add_option('-u', '--unmerge', action='store_true',
                      help="Split CVS conflicts in single input file and "
                      "encrypt required output merged file over input.")

    parser.add_option('-r', '--recipient', action='store',
                      help="Encrypt for user id name.")

    global opts
    xxdiff.scripts.install_autocomplete(parser)

    opts, args = parser.parse_args()

    if not args:
        raise parser.error('No files to decrypt and compare.')

    xxdiff.invoke.options_validate(opts, parser, logs=sys.stdout)

    return opts, args

def encrypted_main():
    opts, args = parse_options()

    if isabs(opts.gpg) and not exists(opts.gpg):
        raise SystemExit('Error: gpg program does not exist in "%s"' %
                         opts.gpg)

    if not (opts.unmerge or opts.output):
        if opts.dont_armor:
            print >> sys.stderr, "Warning: there will be no output file, " + \
                  "--dont-armor will means nothing special."
        if opts.recipient:
            print >> sys.stderr, "Warning: there will be no output file, " + \
                  "--recipient will means nothing special."

    if opts.unmerge:
        for fn in args: # do all files specified on cmdline, why not.
            # Read input conflict file.
            text = open(fn, 'r').read()
            text1, text2 = xxdiff.scm.cvs.unmerge2(text)
            diff_encrypted([text1, text2], opts, fn)
    else:
        if len(args) <= 1:
            raise SystemExit("Error: you need to specify 2 or 3 arguments.")

        textlist = []
        for fn in args:
            text = open(fn, 'r').read()
            textlist.append(text)

        diff_encrypted(textlist, opts, opts.output)


main = encrypted_main

if __name__ == '__main__':
    main()