Package common :: Module GnuPGInterface
[hide private]
[frames] | no frames]

Source Code for Module common.GnuPGInterface

  1  # -*- coding:utf-8 -*- 
  2  ## src/common/GnuPGInterface.py 
  3  ## 
  4  ## Copyright (C) 2001 Frank J. Tobin <ftobin AT neverending.org> 
  5  ## Copyright (C) 2005 Nikos Kouremenos <kourem AT gmail.com> 
  6  ## Copyright (C) 2006-2010 Yann Leboulanger <asterix AT lagaule.org> 
  7  ## Copyright (C) 2008 Jean-Marie Traissard <jim AT lapin.org> 
  8  ## 
  9  ## This file is part of Gajim. 
 10  ## 
 11  ## Gajim is free software; you can redistribute it and/or modify 
 12  ## it under the terms of the GNU General Public License as published 
 13  ## by the Free Software Foundation; version 3 only. 
 14  ## 
 15  ## Gajim is distributed in the hope that it will be useful, 
 16  ## but WITHOUT ANY WARRANTY; without even the implied warranty of 
 17  ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 18  ## GNU General Public License for more details. 
 19  ## 
 20  ## You should have received a copy of the GNU General Public License 
 21  ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. 
 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  # "standard" filehandles attached to processes 
234  _stds = [ 'stdin', 'stdout', 'stderr' ] 
235   
236  # the permissions each type of fh needs to be opened with 
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  # correlation between handle names and the arguments we'll pass 
247  _fd_options = { 'passphrase': '--passphrase-fd', 
248                  'logger':     '--logger-fd', 
249                  'status':     '--status-fd', 
250                  'command':    '--command-fd' } 
251   
252 -class GnuPG:
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
274 - def __init__(self):
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
372 - def _attach_fork_exec(self, gnupg_commands, args, create_fhs, attach_fhs):
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 # make sure the user doesn't specify a filehandle 387 # to be created *and* attached 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 # fix by drt@un.bewaff.net noting 395 # that since pipes are unidirectional on some systems, 396 # so we have to 'turn the pipe around' 397 # if we are writing 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
410 - def _as_parent(self, process):
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 # user doesn't need these 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 # child 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 # we want the fh to stay open after execing 437 fcntl.fcntl( p.child, fcntl.F_SETFD, 0 ) 438 439 fd_args = [] 440 441 for k, p in process._pipes.items(): 442 # set command-line options for non-standard fds 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
454 -class Pipe:
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
465 -class Options:
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 """
537 - def __init__(self):
538 # booleans 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 # meta-option booleans 553 self.meta_pgp_5_compatible = 0 554 self.meta_pgp_2_compatible = 0 555 self.meta_interactive = 1 556 557 # strings 558 self.homedir = None 559 self.default_key = None 560 self.comment = None 561 self.compress_algo = None 562 self.options = None 563 564 # lists 565 self.encrypt_to = [] 566 self.recipients = [] 567 568 # miscellaneous arguments 569 self.extra_args = []
570
571 - def get_args( self ):
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
577 - def get_standard_args( self ):
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
611 - def get_meta_args( self ):
612 """ 613 Get a list of generated meta-arguments 614 """ 615 args = [] 616 617 if self.meta_pgp_5_compatible: args.extend( [ '--compress-algo', '1', 618 '--force-v3-sigs' 619 ] ) 620 if self.meta_pgp_2_compatible: args.append( '--rfc1991' ) 621 if not self.meta_interactive: args.extend( [ '--batch', '--no-tty' ] ) 622 623 return args
624 625
626 -class Process:
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
650 - def __init__(self):
651 self._pipes = {} 652 self.handles = {} 653 self.pid = None 654 self._waited = None
655
656 - def wait(self):
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
665 -def _run_doctests():
666 import doctest, GnuPGInterface 667 return doctest.testmod(GnuPGInterface)
668 669 # deprecated 670 GnuPGInterface = GnuPG 671 672 if __name__ == '__main__': 673 _run_doctests() 674