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