1
"""Interface to GNU Privacy Guard (GnuPG)
3
GnuPGInterface is a Python module to interface with GnuPG.
4
It concentrates on interacting with GnuPG via filehandles,
5
providing access to control GnuPG via versatile and extensible means.
7
This module is based on GnuPG::Interface, a Perl module by the same author.
9
Normally, using this module will involve creating a
10
GnuPG object, setting some options in it's 'options' data member
11
(which is of type Options), creating some pipes
12
to talk with GnuPG, and then calling the run() method, which will
13
connect those pipes to the GnuPG process. run() returns a
14
Process object, which contains the filehandles to talk to GnuPG with.
18
>>> import GnuPGInterface
20
>>> plaintext = "Three blind mice"
21
>>> passphrase = "This is the passphrase"
23
>>> gnupg = GnuPGInterface.GnuPG()
24
>>> gnupg.options.armor = 1
25
>>> gnupg.options.meta_interactive = 0
26
>>> gnupg.options.extra_args.append('--no-secmem-warning')
28
>>> # Normally we might specify something in
29
>>> # gnupg.options.recipients, like
30
>>> # gnupg.options.recipients = [ '0xABCD1234', 'bob@foo.bar' ]
31
>>> # but since we're doing symmetric-only encryption, it's not needed.
32
>>> # If you are doing standard, public-key encryption, using
33
>>> # --encrypt, you will need to specify recipients before
34
>>> # calling gnupg.run()
36
>>> # First we'll encrypt the test_text input symmetrically
37
>>> p1 = gnupg.run(['--symmetric'],
38
... create_fhs=['stdin', 'stdout', 'passphrase'])
40
>>> p1.handles['passphrase'].write(passphrase)
41
>>> p1.handles['passphrase'].close()
43
>>> p1.handles['stdin'].write(plaintext)
44
>>> p1.handles['stdin'].close()
46
>>> ciphertext = p1.handles['stdout'].read()
47
>>> p1.handles['stdout'].close()
52
>>> # Now we'll decrypt what we just encrypted it,
53
>>> # using the convience method to get the
54
>>> # passphrase to GnuPG
55
>>> gnupg.passphrase = passphrase
57
>>> p2 = gnupg.run(['--decrypt'], create_fhs=['stdin', 'stdout'])
59
>>> p2.handles['stdin'].write(ciphertext)
60
>>> p2.handles['stdin'].close()
62
>>> decrypted_plaintext = p2.handles['stdout'].read()
63
>>> p2.handles['stdout'].close()
68
>>> # Our decrypted plaintext:
69
>>> decrypted_plaintext
72
>>> # ...and see it's the same as what we orignally encrypted
73
>>> assert decrypted_plaintext == plaintext, \
74
"GnuPG decrypted output does not match original input"
77
>>> ##################################################
78
>>> # Now let's trying using run()'s attach_fhs paramter
80
>>> # we're assuming we're running on a unix...
81
>>> input = open('/etc/motd')
83
>>> p1 = gnupg.run(['--symmetric'], create_fhs=['stdout'],
84
... attach_fhs={'stdin': input})
86
>>> # GnuPG will read the stdin from /etc/motd
87
>>> ciphertext = p1.handles['stdout'].read()
92
>>> # Now let's run the output through GnuPG
93
>>> # We'll write the output to a temporary file,
95
>>> temp = tempfile.TemporaryFile()
97
>>> p2 = gnupg.run(['--decrypt'], create_fhs=['stdin'],
98
... attach_fhs={'stdout': temp})
100
>>> # give GnuPG our encrypted stuff from the first run
101
>>> p2.handles['stdin'].write(ciphertext)
102
>>> p2.handles['stdin'].close()
104
>>> # process cleanup
107
>>> # rewind the tempfile and see what GnuPG gave us
109
>>> decrypted_plaintext = temp.read()
111
>>> # compare what GnuPG decrypted with our original input
113
>>> input_data = input.read()
115
>>> assert decrypted_plaintext == input_data, \
116
"GnuPG decrypted output does not match original input"
118
To do things like public-key encryption, simply pass do something
121
gnupg.passphrase = 'My passphrase'
122
gnupg.options.recipients = [ 'bob@foobar.com' ]
123
gnupg.run( ['--sign', '--encrypt'], create_fhs=..., attach_fhs=...)
125
Here is an example of subclassing GnuPGInterface.GnuPG,
126
so that it has an encrypt_string() method that returns
129
>>> import GnuPGInterface
131
>>> class MyGnuPG(GnuPGInterface.GnuPG):
133
... def __init__(self):
134
... GnuPGInterface.GnuPG.__init__(self)
135
... self.setup_my_options()
137
... def setup_my_options(self):
138
... self.options.armor = 1
139
... self.options.meta_interactive = 0
140
... self.options.extra_args.append('--no-secmem-warning')
142
... def encrypt_string(self, string, recipients):
143
... gnupg.options.recipients = recipients # a list!
145
... proc = gnupg.run(['--encrypt'], create_fhs=['stdin', 'stdout'])
147
... proc.handles['stdin'].write(string)
148
... proc.handles['stdin'].close()
150
... output = proc.handles['stdout'].read()
151
... proc.handles['stdout'].close()
156
>>> gnupg = MyGnuPG()
157
>>> ciphertext = gnupg.encrypt_string("The secret", ['0x260C4FA3'])
159
>>> # just a small sanity test here for doctest
161
>>> assert isinstance(ciphertext, types.StringType), \
162
"What GnuPG gave back is not a string!"
164
Here is an example of generating a key:
165
>>> import GnuPGInterface
166
>>> gnupg = GnuPGInterface.GnuPG()
167
>>> gnupg.options.meta_interactive = 0
169
>>> # We will be creative and use the logger filehandle to capture
170
>>> # what GnuPG says this time, instead stderr; no stdout to listen to,
171
>>> # but we capture logger to surpress the dry-run command.
172
>>> # We also have to capture stdout since otherwise doctest complains;
173
>>> # Normally you can let stdout through when generating a key.
175
>>> proc = gnupg.run(['--gen-key'], create_fhs=['stdin', 'stdout',
178
>>> proc.handles['stdin'].write('''Key-Type: DSA
180
... # We are only testing syntax this time, so dry-run
182
... Subkey-Type: ELG-E
183
... Subkey-Length: 1024
184
... Name-Real: Joe Tester
185
... Name-Comment: with stupid passphrase
186
... Name-Email: joe@foo.bar
193
>>> proc.handles['stdin'].close()
195
>>> report = proc.handles['logger'].read()
196
>>> proc.handles['logger'].close()
203
Copyright (C) 2001 Frank J. Tobin, ftobin@neverending.org
207
This library is free software; you can redistribute it and/or
208
modify it under the terms of the GNU Lesser General Public
209
License as published by the Free Software Foundation; either
210
version 2.1 of the License, or (at your option) any later version.
212
This library is distributed in the hope that it will be useful,
213
but WITHOUT ANY WARRANTY; without even the implied warranty of
214
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
215
Lesser General Public License for more details.
217
You should have received a copy of the GNU Lesser General Public
218
License along with this library; if not, write to the Free Software
219
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
220
or see http://www.gnu.org/copyleft/lesser.html
227
__author__ = "Frank J. Tobin, ftobin@neverending.org"
228
__version__ = "0.3.2"
229
__revision__ = "$Id: GnuPGInterface.py,v 1.1 2002/10/29 01:51:36 bescoto Exp $"
231
# "standard" filehandles attached to processes
232
_stds = [ 'stdin', 'stdout', 'stderr' ]
234
# the permissions each type of fh needs to be opened with
235
_fd_modes = { 'stdin': 'w',
244
# correlation between handle names and the arguments we'll pass
245
_fd_options = { 'passphrase': '--passphrase-fd',
246
'logger': '--logger-fd',
247
'status': '--status-fd',
248
'command': '--command-fd' }
251
"""Class instances represent GnuPG.
253
Instance attributes of a GnuPG object are:
255
* call -- string to call GnuPG with. Defaults to "gpg"
257
* passphrase -- Since it is a common operation
258
to pass in a passphrase to GnuPG,
259
and working with the passphrase filehandle mechanism directly
260
can be mundane, if set, the passphrase attribute
261
works in a special manner. If the passphrase attribute is set,
262
and no passphrase file object is sent in to run(),
263
then GnuPG instnace will take care of sending the passphrase to
264
GnuPG, the executable instead of having the user sent it in manually.
266
* options -- Object of type GnuPGInterface.Options.
267
Attribute-setting in options determines
268
the command-line options used when calling GnuPG.
273
self.passphrase = None
274
self.options = Options()
276
def run(self, gnupg_commands, args=None, create_fhs=None, attach_fhs=None):
277
"""Calls GnuPG with the list of string commands gnupg_commands,
278
complete with prefixing dashes.
279
For example, gnupg_commands could be
280
'["--sign", "--encrypt"]'
281
Returns a GnuPGInterface.Process object.
283
args is an optional list of GnuPG command arguments (not options),
284
such as keyID's to export, filenames to process, etc.
286
create_fhs is an optional list of GnuPG filehandle
287
names that will be set as keys of the returned Process object's
288
'handles' attribute. The generated filehandles can be used
289
to communicate with GnuPG via standard input, standard output,
290
the status-fd, passphrase-fd, etc.
292
Valid GnuPG filehandle names are:
301
The purpose of each filehandle is described in the GnuPG
304
attach_fhs is an optional dictionary with GnuPG filehandle
305
names mapping to opened files. GnuPG will read or write
306
to the file accordingly. For example, if 'my_file' is an
307
opened file and 'attach_fhs[stdin] is my_file', then GnuPG
308
will read its standard input from my_file. This is useful
309
if you want GnuPG to read/write to/from an existing file.
312
f = open("encrypted.gpg")
313
gnupg.run(["--decrypt"], attach_fhs={'stdin': f})
315
Using attach_fhs also helps avoid system buffering
316
issues that can arise when using create_fhs, which
317
can cause the process to deadlock.
319
If not mentioned in create_fhs or attach_fhs,
320
GnuPG filehandles which are a std* (stdin, stdout, stderr)
321
are defaulted to the running process' version of handle.
322
Otherwise, that type of handle is simply not used when calling GnuPG.
323
For example, if you do not care about getting data from GnuPG's
324
status filehandle, simply do not specify it.
326
run() returns a Process() object which has a 'handles'
327
which is a dictionary mapping from the handle name
328
(such as 'stdin' or 'stdout') to the respective
329
newly-created FileObject connected to the running GnuPG process.
330
For instance, if the call was
332
process = gnupg.run(["--decrypt"], stdin=1)
334
after run returns 'process.handles["stdin"]'
335
is a FileObject connected to GnuPG's standard input,
336
and can be written to.
339
if args == None: args = []
340
if create_fhs == None: create_fhs = []
341
if attach_fhs == None: attach_fhs = {}
344
if not attach_fhs.has_key(std) \
345
and std not in create_fhs:
346
attach_fhs.setdefault(std, getattr(sys, std))
348
handle_passphrase = 0
350
if self.passphrase != None \
351
and not attach_fhs.has_key('passphrase') \
352
and 'passphrase' not in create_fhs:
353
handle_passphrase = 1
354
create_fhs.append('passphrase')
356
process = self._attach_fork_exec(gnupg_commands, args,
357
create_fhs, attach_fhs)
359
if handle_passphrase:
360
passphrase_fh = process.handles['passphrase']
361
passphrase_fh.write( self.passphrase )
362
passphrase_fh.close()
363
del process.handles['passphrase']
368
def _attach_fork_exec(self, gnupg_commands, args, create_fhs, attach_fhs):
369
"""This is like run(), but without the passphrase-helping
370
(note that run() calls this)."""
374
for fh_name in create_fhs + attach_fhs.keys():
375
if not _fd_modes.has_key(fh_name):
377
"unrecognized filehandle name '%s'; must be one of %s" \
378
% (fh_name, _fd_modes.keys())
380
for fh_name in create_fhs:
381
# make sure the user doesn't specify a filehandle
382
# to be created *and* attached
383
if attach_fhs.has_key(fh_name):
385
"cannot have filehandle '%s' in both create_fhs and attach_fhs" \
389
# fix by drt@un.bewaff.net noting
390
# that since pipes are unidirectional on some systems,
391
# so we have to 'turn the pipe around'
393
if _fd_modes[fh_name] == 'w': pipe = (pipe[1], pipe[0])
394
process._pipes[fh_name] = Pipe(pipe[0], pipe[1], 0)
396
for fh_name, fh in attach_fhs.items():
397
process._pipes[fh_name] = Pipe(fh.fileno(), fh.fileno(), 1)
399
process.pid = os.fork()
401
if process.pid == 0: self._as_child(process, gnupg_commands, args)
402
return self._as_parent(process)
405
def _as_parent(self, process):
406
"""Stuff run after forking in parent"""
407
for k, p in process._pipes.items():
410
process.handles[k] = os.fdopen(p.parent, _fd_modes[k])
412
# user doesn't need these
418
def _as_child(self, process, gnupg_commands, args):
419
"""Stuff run after forking in child"""
422
p = process._pipes[std]
423
os.dup2( p.child, getattr(sys, "__%s__" % std).fileno() )
425
for k, p in process._pipes.items():
426
if p.direct and k not in _stds:
427
# we want the fh to stay open after execing
428
fcntl.fcntl( p.child, fcntl.F_SETFD, 0 )
432
for k, p in process._pipes.items():
433
# set command-line options for non-standard fds
435
fd_args.extend([ _fd_options[k], "%d" % p.child ])
437
if not p.direct: os.close(p.parent)
439
command = [ self.call ] + fd_args + self.options.get_args() \
440
+ gnupg_commands + args
442
os.execvp( command[0], command )
446
"""simple struct holding stuff about pipes we use"""
447
def __init__(self, parent, child, direct):
454
"""Objects of this class encompass options passed to GnuPG.
455
This class is responsible for determining command-line arguments
456
which are based on options. It can be said that a GnuPG
457
object has-a Options object in its options attribute.
459
Attributes which correlate directly to GnuPG options:
461
Each option here defaults to false or None, and is described in
464
Booleans (set these attributes to booleans)
478
Strings (set these attributes to strings)
486
Lists (set these attributes to lists)
488
* recipients (***NOTE*** plural of 'recipient')
493
Meta options are options provided by this module that do
494
not correlate directly to any GnuPG option by name,
495
but are rather bundle of options used to accomplish
496
a specific goal, such as obtaining compatibility with PGP 5.
497
The actual arguments each of these reflects may change with time. Each
498
defaults to false unless otherwise specified.
500
meta_pgp_5_compatible -- If true, arguments are generated to try
501
to be compatible with PGP 5.x.
503
meta_pgp_2_compatible -- If true, arguments are generated to try
504
to be compatible with PGP 2.x.
506
meta_interactive -- If false, arguments are generated to try to
507
help the using program use GnuPG in a non-interactive
508
environment, such as CGI scripts. Default is true.
510
extra_args -- Extra option arguments may be passed in
511
via the attribute extra_args, a list.
513
>>> import GnuPGInterface
515
>>> gnupg = GnuPGInterface.GnuPG()
516
>>> gnupg.options.armor = 1
517
>>> gnupg.options.recipients = ['Alice', 'Bob']
518
>>> gnupg.options.extra_args = ['--no-secmem-warning']
520
>>> # no need for users to call this normally; just for show here
521
>>> gnupg.options.get_args()
522
['--armor', '--recipient', 'Alice', '--recipient', 'Bob', '--no-secmem-warning']
533
self.always_trust = 0
536
self.force_v3_sigs = 0
540
# meta-option booleans
541
self.meta_pgp_5_compatible = 0
542
self.meta_pgp_2_compatible = 0
543
self.meta_interactive = 1
547
self.default_key = None
549
self.compress_algo = None
556
# miscellaneous arguments
559
def get_args( self ):
560
"""Generate a list of GnuPG arguments based upon attributes."""
562
return self.get_meta_args() + self.get_standard_args() + self.extra_args
564
def get_standard_args( self ):
565
"""Generate a list of standard, non-meta or extra arguments"""
567
if self.homedir != None: args.extend( [ '--homedir', self.homedir ] )
568
if self.options != None: args.extend( [ '--options', self.options ] )
569
if self.comment != None: args.extend( [ '--comment', self.comment ] )
570
if self.compress_algo != None: args.extend( [ '--compress-algo', self.compress_algo ] )
571
if self.default_key != None: args.extend( [ '--default-key', self.default_key ] )
573
if self.no_options: args.append( '--no-options' )
574
if self.armor: args.append( '--armor' )
575
if self.textmode: args.append( '--textmode' )
576
if self.no_greeting: args.append( '--no-greeting' )
577
if self.verbose: args.append( '--verbose' )
578
if self.no_verbose: args.append( '--no-verbose' )
579
if self.quiet: args.append( '--quiet' )
580
if self.batch: args.append( '--batch' )
581
if self.always_trust: args.append( '--always-trust' )
582
if self.force_v3_sigs: args.append( '--force-v3-sigs' )
583
if self.rfc1991: args.append( '--rfc1991' )
584
if self.openpgp: args.append( '--openpgp' )
586
for r in self.recipients: args.extend( [ '--recipient', r ] )
587
for r in self.encrypt_to: args.extend( [ '--encrypt-to', r ] )
591
def get_meta_args( self ):
592
"""Get a list of generated meta-arguments"""
595
if self.meta_pgp_5_compatible: args.extend( [ '--compress-algo', '1',
598
if self.meta_pgp_2_compatible: args.append( '--rfc1991' )
599
if not self.meta_interactive: args.extend( [ '--batch', '--no-tty' ] )
605
"""Objects of this class encompass properties of a GnuPG
606
process spawned by GnuPG.run().
608
# gnupg is a GnuPG object
609
process = gnupg.run( [ '--decrypt' ], stdout = 1 )
610
out = process.handles['stdout'].read()
612
os.waitpid( process.pid, 0 )
616
handles -- This is a map of filehandle-names to
617
the file handles, if any, that were requested via run() and hence
618
are connected to the running GnuPG process. Valid names
619
of this map are only those handles that were requested.
621
pid -- The PID of the spawned GnuPG process.
622
Useful to know, since once should call
623
os.waitpid() to clean up the process, especially
624
if multiple calls are made to run().
634
"""Wait on the process to exit, allowing for child cleanup.
635
Will raise an IOError if the process exits non-zero."""
637
e = os.waitpid(self.pid, 0)[1]
639
raise IOError, "GnuPG exited non-zero, with code %d" % (e << 8)
642
import doctest, GnuPGInterface
643
return doctest.testmod(GnuPGInterface)
646
GnuPGInterface = GnuPG
648
if __name__ == '__main__':