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 Interface to GNU Privacy Guard (GnuPG)
26
27 GnuPGInterface is a Python module to interface with GnuPG.
28 It concentrates on interacting with GnuPG via filehandles,
29 providing access to control GnuPG via versatile and extensible means.
30
31 This module is based on GnuPG::Interface, a Perl module by the same author.
32
33 Normally, using this module will involve creating a
34 GnuPG object, setting some options in it's 'options' data member
35 (which is of type Options), creating some pipes
36 to talk with GnuPG, and then calling the run() method, which will
37 connect those pipes to the GnuPG process. run() returns a
38 Process object, which contains the filehandles to talk to GnuPG with.
39
40 Example code:
41
42 >>> import GnuPGInterface
43 >>>
44 >>> plaintext = "Three blind mice"
45 >>> passphrase = "This is the passphrase"
46 >>>
47 >>> gnupg = GnuPGInterface.GnuPG()
48 >>> gnupg.options.armor = 1
49 >>> gnupg.options.meta_interactive = 0
50 >>> gnupg.options.extra_args.append('--no-secmem-warning')
51 >>>
52 >>> # Normally we might specify something in
53 >>> # gnupg.options.recipients, like
54 >>> # gnupg.options.recipients = [ '0xABCD1234', 'bob@foo.bar' ]
55 >>> # but since we're doing symmetric-only encryption, it's not needed.
56 >>> # If you are doing standard, public-key encryption, using
57 >>> # --encrypt, you will need to specify recipients before
58 >>> # calling gnupg.run()
59 >>>
60 >>> # First we'll encrypt the test_text input symmetrically
61 >>> p1 = gnupg.run(['--symmetric'],
62 ... create_fhs=['stdin', 'stdout', 'passphrase'])
63 >>>
64 >>> p1.handles['passphrase'].write(passphrase)
65 >>> p1.handles['passphrase'].close()
66 >>>
67 >>> p1.handles['stdin'].write(plaintext)
68 >>> p1.handles['stdin'].close()
69 >>>
70 >>> ciphertext = p1.handles['stdout'].read()
71 >>> p1.handles['stdout'].close()
72 >>>
73 >>> # process cleanup
74 >>> p1.wait()
75 >>>
76 >>> # Now we'll decrypt what we just encrypted it,
77 >>> # using the convience method to get the
78 >>> # passphrase to GnuPG
79 >>> gnupg.passphrase = passphrase
80 >>>
81 >>> p2 = gnupg.run(['--decrypt'], create_fhs=['stdin', 'stdout'])
82 >>>
83 >>> p2.handles['stdin'].write(ciphertext)
84 >>> p2.handles['stdin'].close()
85 >>>
86 >>> decrypted_plaintext = p2.handles['stdout'].read()
87 >>> p2.handles['stdout'].close()
88 >>>
89 >>> # process cleanup
90 >>> p2.wait()
91 >>>
92 >>> # Our decrypted plaintext:
93 >>> decrypted_plaintext
94 'Three blind mice'
95 >>>
96 >>> # ...and see it's the same as what we orignally encrypted
97 >>> assert decrypted_plaintext == plaintext, \
98 "GnuPG decrypted output does not match original input"
99 >>>
100 >>>
101 >>> ##################################################
102 >>> # Now let's trying using run()'s attach_fhs paramter
103 >>>
104 >>> # we're assuming we're running on a unix...
105 >>> input = open('/etc/motd')
106 >>>
107 >>> p1 = gnupg.run(['--symmetric'], create_fhs=['stdout'],
108 ... attach_fhs={'stdin': input})
109 >>>
110 >>> # GnuPG will read the stdin from /etc/motd
111 >>> ciphertext = p1.handles['stdout'].read()
112 >>>
113 >>> # process cleanup
114 >>> p1.wait()
115 >>>
116 >>> # Now let's run the output through GnuPG
117 >>> # We'll write the output to a temporary file,
118 >>> import tempfile
119 >>> temp = tempfile.TemporaryFile()
120 >>>
121 >>> p2 = gnupg.run(['--decrypt'], create_fhs=['stdin'],
122 ... attach_fhs={'stdout': temp})
123 >>>
124 >>> # give GnuPG our encrypted stuff from the first run
125 >>> p2.handles['stdin'].write(ciphertext)
126 >>> p2.handles['stdin'].close()
127 >>>
128 >>> # process cleanup
129 >>> p2.wait()
130 >>>
131 >>> # rewind the tempfile and see what GnuPG gave us
132 >>> temp.seek(0)
133 >>> decrypted_plaintext = temp.read()
134 >>>
135 >>> # compare what GnuPG decrypted with our original input
136 >>> input.seek(0)
137 >>> input_data = input.read()
138 >>>
139 >>> assert decrypted_plaintext == input_data, \
140 "GnuPG decrypted output does not match original input"
141
142 To do things like public-key encryption, simply pass do something
143 like:
144
145 gnupg.passphrase = 'My passphrase'
146 gnupg.options.recipients = [ 'bob@foobar.com' ]
147 gnupg.run( ['--sign', '--encrypt'], create_fhs=..., attach_fhs=...)
148
149 Here is an example of subclassing GnuPGInterface.GnuPG,
150 so that it has an encrypt_string() method that returns
151 ciphertext.
152
153 >>> import GnuPGInterface
154 >>>
155 >>> class MyGnuPG(GnuPGInterface.GnuPG):
156 ...
157 ... def __init__(self):
158 ... GnuPGInterface.GnuPG.__init__(self)
159 ... self.setup_my_options()
160 ...
161 ... def setup_my_options(self):
162 ... self.options.armor = 1
163 ... self.options.meta_interactive = 0
164 ... self.options.extra_args.append('--no-secmem-warning')
165 ...
166 ... def encrypt_string(self, string, recipients):
167 ... gnupg.options.recipients = recipients # a list!
168 ...
169 ... proc = gnupg.run(['--encrypt'], create_fhs=['stdin', 'stdout'])
170 ...
171 ... proc.handles['stdin'].write(string)
172 ... proc.handles['stdin'].close()
173 ...
174 ... output = proc.handles['stdout'].read()
175 ... proc.handles['stdout'].close()
176 ...
177 ... proc.wait()
178 ... return output
179 ...
180 >>> gnupg = MyGnuPG()
181 >>> ciphertext = gnupg.encrypt_string("The secret", ['0x260C4FA3'])
182 >>>
183 >>> # just a small sanity test here for doctest
184 >>> import types
185 >>> assert isinstance(ciphertext, types.StringType), \
186 "What GnuPG gave back is not a string!"
187
188 Here is an example of generating a key:
189 >>> import GnuPGInterface
190 >>> gnupg = GnuPGInterface.GnuPG()
191 >>> gnupg.options.meta_interactive = 0
192 >>>
193 >>> # We will be creative and use the logger filehandle to capture
194 >>> # what GnuPG says this time, instead stderr; no stdout to listen to,
195 >>> # but we capture logger to surpress the dry-run command.
196 >>> # We also have to capture stdout since otherwise doctest complains;
197 >>> # Normally you can let stdout through when generating a key.
198 >>>
199 >>> proc = gnupg.run(['--gen-key'], create_fhs=['stdin', 'stdout',
200 ... 'logger'])
201 >>>
202 >>> proc.handles['stdin'].write('''Key-Type: DSA
203 ... Key-Length: 1024
204 ... # We are only testing syntax this time, so dry-run
205 ... %dry-run
206 ... Subkey-Type: ELG-E
207 ... Subkey-Length: 1024
208 ... Name-Real: Joe Tester
209 ... Name-Comment: with stupid passphrase
210 ... Name-Email: joe@foo.bar
211 ... Expire-Date: 2y
212 ... Passphrase: abc
213 ... %pubring foo.pub
214 ... %secring foo.sec
215 ... ''')
216 >>>
217 >>> proc.handles['stdin'].close()
218 >>>
219 >>> report = proc.handles['logger'].read()
220 >>> proc.handles['logger'].close()
221 >>>
222 >>> proc.wait()
223 """
224
225 import os
226 import sys
227 import fcntl
228
229 __author__ = "Frank J. Tobin, ftobin@neverending.org"
230 __version__ = "0.3.2"
231 __revision__ = "$Id: GnuPGInterface.py,v 1.22 2002/01/11 20:22:04 ftobin Exp $"
232
233
234 _stds = [ 'stdin', 'stdout', 'stderr' ]
235
236
237 _fd_modes = { 'stdin': 'w',
238 'stdout': 'r',
239 'stderr': 'r',
240 'passphrase': 'w',
241 'command': 'w',
242 'logger': 'r',
243 'status': 'r'
244 }
245
246
247 _fd_options = { 'passphrase': '--passphrase-fd',
248 'logger': '--logger-fd',
249 'status': '--status-fd',
250 'command': '--command-fd' }
251
253 """
254 Class instances represent GnuPG
255
256 Instance attributes of a GnuPG object are:
257
258 * call -- string to call GnuPG with. Defaults to "gpg"
259
260 * passphrase -- Since it is a common operation
261 to pass in a passphrase to GnuPG,
262 and working with the passphrase filehandle mechanism directly
263 can be mundane, if set, the passphrase attribute
264 works in a special manner. If the passphrase attribute is set,
265 and no passphrase file object is sent in to run(),
266 then GnuPG instnace will take care of sending the passphrase to
267 GnuPG, the executable instead of having the user sent it in manually.
268
269 * options -- Object of type GnuPGInterface.Options.
270 Attribute-setting in options determines
271 the command-line options used when calling GnuPG.
272 """
273
275 self.call = 'gpg'
276 self.passphrase = None
277 self.options = Options()
278
279 - def run(self, gnupg_commands, args=None, create_fhs=None, attach_fhs=None):
280 """
281 Calls GnuPG with the list of string commands gnupg_commands, complete
282 with prefixing dashes
283
284 For example, gnupg_commands could be
285 '["--sign", "--encrypt"]'
286 Returns a GnuPGInterface.Process object.
287
288 args is an optional list of GnuPG command arguments (not options),
289 such as keyID's to export, filenames to process, etc.
290
291 create_fhs is an optional list of GnuPG filehandle
292 names that will be set as keys of the returned Process object's
293 'handles' attribute. The generated filehandles can be used
294 to communicate with GnuPG via standard input, standard output,
295 the status-fd, passphrase-fd, etc.
296
297 Valid GnuPG filehandle names are:
298 * stdin
299 * stdout
300 * stderr
301 * status
302 * passphase
303 * command
304 * logger
305
306 The purpose of each filehandle is described in the GnuPG
307 documentation.
308
309 attach_fhs is an optional dictionary with GnuPG filehandle
310 names mapping to opened files. GnuPG will read or write
311 to the file accordingly. For example, if 'my_file' is an
312 opened file and 'attach_fhs[stdin] is my_file', then GnuPG
313 will read its standard input from my_file. This is useful
314 if you want GnuPG to read/write to/from an existing file.
315 For instance:
316
317 f = open("encrypted.gpg")
318 gnupg.run(["--decrypt"], attach_fhs={'stdin': f})
319
320 Using attach_fhs also helps avoid system buffering
321 issues that can arise when using create_fhs, which
322 can cause the process to deadlock.
323
324 If not mentioned in create_fhs or attach_fhs,
325 GnuPG filehandles which are a std* (stdin, stdout, stderr)
326 are defaulted to the running process' version of handle.
327 Otherwise, that type of handle is simply not used when calling GnuPG.
328 For example, if you do not care about getting data from GnuPG's
329 status filehandle, simply do not specify it.
330
331 run() returns a Process() object which has a 'handles'
332 which is a dictionary mapping from the handle name
333 (such as 'stdin' or 'stdout') to the respective
334 newly-created FileObject connected to the running GnuPG process.
335 For instance, if the call was
336
337 process = gnupg.run(["--decrypt"], stdin=1)
338
339 after run returns 'process.handles["stdin"]'
340 is a FileObject connected to GnuPG's standard input,
341 and can be written to.
342 """
343 if args is None: args = []
344 if create_fhs is None: create_fhs = []
345 if attach_fhs is None: attach_fhs = {}
346
347 for std in _stds:
348 if std not in attach_fhs \
349 and std not in create_fhs:
350 attach_fhs.setdefault(std, getattr(sys, std))
351
352 handle_passphrase = 0
353
354 if self.passphrase is not None \
355 and 'passphrase' not in attach_fhs \
356 and 'passphrase' not in create_fhs:
357 handle_passphrase = 1
358 create_fhs.append('passphrase')
359
360 process = self._attach_fork_exec(gnupg_commands, args,
361 create_fhs, attach_fhs)
362
363 if handle_passphrase:
364 passphrase_fh = process.handles['passphrase']
365 passphrase_fh.write( self.passphrase )
366 passphrase_fh.close()
367 del process.handles['passphrase']
368
369 return process
370
371
373 """
374 This is like run(), but without the passphrase-helping (note that run()
375 calls this)
376 """
377 process = Process()
378
379 for fh_name in create_fhs + attach_fhs.keys():
380 if fh_name not in _fd_modes:
381 raise KeyError, \
382 "unrecognized filehandle name '%s'; must be one of %s" \
383 % (fh_name, _fd_modes.keys())
384
385 for fh_name in create_fhs:
386
387
388 if fh_name in attach_fhs:
389 raise ValueError, \
390 "cannot have filehandle '%s' in both create_fhs and attach_fhs" \
391 % fh_name
392
393 pipe = os.pipe()
394
395
396
397
398 if _fd_modes[fh_name] == 'w': pipe = (pipe[1], pipe[0])
399 process._pipes[fh_name] = Pipe(pipe[0], pipe[1], 0)
400
401 for fh_name, fh in attach_fhs.items():
402 process._pipes[fh_name] = Pipe(fh.fileno(), fh.fileno(), 1)
403
404 process.pid = os.fork()
405
406 if process.pid == 0: self._as_child(process, gnupg_commands, args)
407 return self._as_parent(process)
408
409
411 """
412 Stuff run after forking in parent
413 """
414 for k, p in process._pipes.items():
415 if not p.direct:
416 os.close(p.child)
417 process.handles[k] = os.fdopen(p.parent, _fd_modes[k])
418
419
420 del process._pipes
421
422 return process
423
424
425 - def _as_child(self, process, gnupg_commands, args):
426 """
427 Stuff run after forking in child
428 """
429
430 for std in _stds:
431 p = process._pipes[std]
432 os.dup2( p.child, getattr(sys, "__%s__" % std).fileno() )
433
434 for k, p in process._pipes.items():
435 if p.direct and k not in _stds:
436
437 fcntl.fcntl( p.child, fcntl.F_SETFD, 0 )
438
439 fd_args = []
440
441 for k, p in process._pipes.items():
442
443 if k not in _stds:
444 fd_args.extend([ _fd_options[k], "%d" % p.child ])
445
446 if not p.direct: os.close(p.parent)
447
448 command = [ self.call ] + fd_args + self.options.get_args() \
449 + gnupg_commands + args
450
451 os.execvp( command[0], command )
452
453
455 """
456 Simple struct holding stuff about pipes we use
457 """
458
459 - def __init__(self, parent, child, direct):
460 self.parent = parent
461 self.child = child
462 self.direct = direct
463
464
466 """
467 Objects of this class encompass options passed to GnuPG.
468 This class is responsible for determining command-line arguments
469 which are based on options. It can be said that a GnuPG
470 object has-a Options object in its options attribute.
471
472 Attributes which correlate directly to GnuPG options:
473
474 Each option here defaults to false or None, and is described in
475 GnuPG documentation.
476
477 Booleans (set these attributes to booleans)
478
479 * armor
480 * no_greeting
481 * no_verbose
482 * quiet
483 * batch
484 * always_trust
485 * rfc1991
486 * openpgp
487 * force_v3_sigs
488 * no_options
489 * textmode
490
491 Strings (set these attributes to strings)
492
493 * homedir
494 * default_key
495 * comment
496 * compress_algo
497 * options
498
499 Lists (set these attributes to lists)
500
501 * recipients (***NOTE*** plural of 'recipient')
502 * encrypt_to
503
504 Meta options
505
506 Meta options are options provided by this module that do
507 not correlate directly to any GnuPG option by name,
508 but are rather bundle of options used to accomplish
509 a specific goal, such as obtaining compatibility with PGP 5.
510 The actual arguments each of these reflects may change with time. Each
511 defaults to false unless otherwise specified.
512
513 meta_pgp_5_compatible -- If true, arguments are generated to try
514 to be compatible with PGP 5.x.
515
516 meta_pgp_2_compatible -- If true, arguments are generated to try
517 to be compatible with PGP 2.x.
518
519 meta_interactive -- If false, arguments are generated to try to
520 help the using program use GnuPG in a non-interactive
521 environment, such as CGI scripts. Default is true.
522
523 extra_args -- Extra option arguments may be passed in
524 via the attribute extra_args, a list.
525
526 >>> import GnuPGInterface
527 >>>
528 >>> gnupg = GnuPGInterface.GnuPG()
529 >>> gnupg.options.armor = 1
530 >>> gnupg.options.recipients = ['Alice', 'Bob']
531 >>> gnupg.options.extra_args = ['--no-secmem-warning']
532 >>>
533 >>> # no need for users to call this normally; just for show here
534 >>> gnupg.options.get_args()
535 ['--armor', '--recipient', 'Alice', '--recipient', 'Bob', '--no-secmem-warning']
536 """
538
539 self.armor = 0
540 self.no_greeting = 0
541 self.verbose = 0
542 self.no_verbose = 0
543 self.quiet = 0
544 self.batch = 0
545 self.always_trust = 0
546 self.rfc1991 = 0
547 self.openpgp = 0
548 self.force_v3_sigs = 0
549 self.no_options = 0
550 self.textmode = 0
551
552
553 self.meta_pgp_5_compatible = 0
554 self.meta_pgp_2_compatible = 0
555 self.meta_interactive = 1
556
557
558 self.homedir = None
559 self.default_key = None
560 self.comment = None
561 self.compress_algo = None
562 self.options = None
563
564
565 self.encrypt_to = []
566 self.recipients = []
567
568
569 self.extra_args = []
570
572 """
573 Generate a list of GnuPG arguments based upon attributes
574 """
575 return self.get_meta_args() + self.get_standard_args() + self.extra_args
576
578 """
579 Generate a list of standard, non-meta or extra arguments
580 """
581 args = []
582 if self.homedir is not None:
583 args.extend( [ '--homedir', self.homedir ] )
584 if self.options is not None:
585 args.extend( [ '--options', self.options ] )
586 if self.comment is not None:
587 args.extend( [ '--comment', self.comment ] )
588 if self.compress_algo is not None:
589 args.extend( [ '--compress-algo', self.compress_algo ] )
590 if self.default_key is not None:
591 args.extend( [ '--default-key', self.default_key ] )
592
593 if self.no_options: args.append( '--no-options' )
594 if self.armor: args.append( '--armor' )
595 if self.textmode: args.append( '--textmode' )
596 if self.no_greeting: args.append( '--no-greeting' )
597 if self.verbose: args.append( '--verbose' )
598 if self.no_verbose: args.append( '--no-verbose' )
599 if self.quiet: args.append( '--quiet' )
600 if self.batch: args.append( '--batch' )
601 if self.always_trust: args.append( '--always-trust' )
602 if self.force_v3_sigs: args.append( '--force-v3-sigs' )
603 if self.rfc1991: args.append( '--rfc1991' )
604 if self.openpgp: args.append( '--openpgp' )
605
606 for r in self.recipients: args.extend( [ '--recipient', r ] )
607 for r in self.encrypt_to: args.extend( [ '--encrypt-to', r ] )
608
609 return args
610
624
625
627 """
628 Objects of this class encompass properties of a GnuPG process spawned by
629 GnuPG.run()
630
631 # gnupg is a GnuPG object
632 process = gnupg.run( [ '--decrypt' ], stdout = 1 )
633 out = process.handles['stdout'].read()
634 ...
635 os.waitpid( process.pid, 0 )
636
637 Data Attributes
638
639 handles -- This is a map of filehandle-names to
640 the file handles, if any, that were requested via run() and hence
641 are connected to the running GnuPG process. Valid names
642 of this map are only those handles that were requested.
643
644 pid -- The PID of the spawned GnuPG process.
645 Useful to know, since once should call
646 os.waitpid() to clean up the process, especially
647 if multiple calls are made to run().
648 """
649
651 self._pipes = {}
652 self.handles = {}
653 self.pid = None
654 self._waited = None
655
657 """
658 Wait on the process to exit, allowing for child cleanup. Will raise an
659 IOError if the process exits non-zero
660 """
661 e = os.waitpid(self.pid, 0)[1]
662 if e != 0:
663 raise IOError, "GnuPG exited non-zero, with code %d" % (e << 8)
664
668
669
670 GnuPGInterface = GnuPG
671
672 if __name__ == '__main__':
673 _run_doctests()
674